Changes in projector and core app and in utils.

Changed api for main menu entries.
Enhanced http error pages using a classed based views.
Moved dashboard and select widgets view from projector to core app.
Also some small clean ups.
This commit is contained in:
Norman Jäckel 2013-12-09 23:56:01 +01:00
parent 452e6cfaed
commit 21ff62dd32
65 changed files with 674 additions and 464 deletions

View File

@ -9,7 +9,7 @@ Version 1.6 (unreleased)
[https://github.com/OpenSlides/OpenSlides/issues?milestone=14]
Participants:
- Disable widgets by default.
- Disabled widgets by default.
Files:
- Enabled update and delete view for uploader refering to his own files.
Other:
@ -18,6 +18,9 @@ Other:
- Renamed config api classes.
- Renamed some classes of the poll api.
- Inserted api for the personal info widget.
- Changed api for main menu entries.
- Enhanced http error pages.
- Moved dashboard and select widgets view from projector to core app.
Version 1.5.1 (unreleased)

View File

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from . import personal_info, signals, slides, widgets # noqa
from . import main_menu, personal_info, signals, slides, widgets # noqa

View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy
from openslides.utils.main_menu import MainMenuEntry
class AgendaMainMenuEntry(MainMenuEntry):
"""
Main menu entry for the agenda app.
"""
verbose_name = ugettext_lazy('Agenda')
permission_required = 'agenda.can_see_agenda'
default_weight = 20
pattern_name = 'item_overview'
icon_css_class = 'icon-calendar'

View File

@ -15,7 +15,6 @@ from openslides.config.api import config
from openslides.projector.api import get_active_slide, update_projector
from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.pdf import stylesheet
from openslides.utils.template import Tab
from openslides.utils.utils import html_strong
from openslides.utils.views import (CreateView, DeleteView, FormView, PDFView,
RedirectView, SingleObjectMixin,
@ -569,7 +568,7 @@ class CurrentListOfSpeakersView(RedirectView):
messages.error(request, _(
'There is no list of speakers for the current slide. '
'Please choose the agenda item manually from the agenda.'))
return reverse('dashboard')
return reverse('core_dashboard')
if self.set_speaker:
if item.speaker_list_closed:
@ -613,25 +612,11 @@ class CurrentListOfSpeakersView(RedirectView):
if item.type == Item.ORGANIZATIONAL_ITEM:
if reverse_to_dashboard or not self.request.user.has_perm('agenda.can_see_orga_items'):
return reverse('dashboard')
return reverse('core_dashboard')
else:
return reverse('item_view', args=[item.pk])
else:
if reverse_to_dashboard or not self.request.user.has_perm('agenda.can_see_agenda'):
return reverse('dashboard')
return reverse('core_dashboard')
else:
return reverse('item_view', args=[item.pk])
def register_tab(request):
"""
Registers the agenda tab.
"""
selected = request.path.startswith('/agenda/')
return Tab(
title=_('Agenda'),
app='agenda',
url=reverse('item_overview'),
permission=(request.user.has_perm('agenda.can_see_agenda') or
request.user.has_perm('agenda.can_manage_agenda')),
selected=selected)

View File

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from . import personal_info, signals, slides, widgets # noqa
from . import main_menu, personal_info, signals, slides, template, widgets # noqa

View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy
from openslides.utils.main_menu import MainMenuEntry
class AssignmentMainMenuEntry(MainMenuEntry):
"""
Main menu entry for the assignment app.
"""
verbose_name = ugettext_lazy('Elections')
permission_required = 'assignment.can_see_assignment'
default_weight = 40
pattern_name = 'assignment_list'
icon_css_class = 'icon-charts'

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -1,3 +1,11 @@
.icon-charts {
background-image: url("../img/glyphicons_041_charts.png");
background-position: 0px center;
}
.leftmenu ul li.active a span.ico i.icon-charts {
background-image: url("../img/glyphicons_041_charts_white.png");
}
td.elected {
background-color: #BED4DE !important;
}

View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
from django.dispatch import receiver
from openslides.utils.signals import template_manipulation
@receiver(template_manipulation, dispatch_uid="add_assignment_stylesheets")
def add_assignment_stylesheets(sender, request, context, **kwargs):
"""
Adds the assignment.css to the context.
"""
context['extra_stylefiles'].append('styles/assignment.css')

View File

@ -19,7 +19,6 @@ from openslides.participant.models import Group, User
from openslides.poll.views import PollFormView
from openslides.utils.pdf import stylesheet
from openslides.utils.person import get_person
from openslides.utils.template import Tab
from openslides.utils.utils import html_strong
from openslides.utils.views import (CreateView, DeleteView, DetailView,
ListView, PDFView, PermissionMixin,
@ -606,18 +605,3 @@ class AssignmentPollPDF(PDFView):
('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
('VALIGN', (0, 0), (-1, -1), 'TOP')]))
story.append(t)
def register_tab(request):
selected = request.path.startswith('/assignment/')
return Tab(
title=_('Elections'),
app='assignment',
url=reverse('assignment_list'),
permission=(
request.user.has_perm('assignment.can_see_assignment') or
request.user.has_perm('assignment.can_nominate_other') or
request.user.has_perm('assignment.can_nominate_self') or
request.user.has_perm('assignment.can_manage_assignment')),
selected=selected,
)

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import main_menu # noqa

View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy
from openslides.utils.main_menu import MainMenuEntry
from .signals import config_signal
class ConfigMainMenuEntry(MainMenuEntry):
"""
Main menu entry for the config app.
"""
verbose_name = ugettext_lazy('Configuration')
default_weight = 70
pattern_name = 'config_first_config_collection_view'
icon_css_class = 'icon-cog'
def check_permission(self):
"""
Checks against all permissions of all config collections.
"""
for receiver, config_collection in config_signal.send(sender=self):
if config_collection.is_shown():
if self.request.user.has_perm(config_collection.required_permission):
return_value = True
break
else:
return_value = False
return return_value

View File

@ -5,7 +5,6 @@ from django.contrib import messages
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from openslides.utils.template import Tab
from openslides.utils.views import FormView
from .api import config
@ -109,15 +108,3 @@ class ConfigView(FormView):
config[key] = form.cleaned_data[key]
messages.success(self.request, _('%s settings successfully saved.') % _(self.config_collection.title))
return super(ConfigView, self).form_valid(form)
def register_tab(request):
"""
Registers the entry for this app in the main menu.
"""
return Tab(
title=_('Configuration'),
app='config',
url=reverse('config_first_config_collection_view'),
permission=request.user.has_perm('config.can_manage'),
selected=request.path.startswith('/config/'))

View File

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from . import signals, widgets # noqa
from . import main_menu, signals, widgets # noqa

View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy
from openslides.utils.main_menu import MainMenuEntry
class DashboardMainMenuEntry(MainMenuEntry):
"""
Main menu entry to the dashboard.
"""
verbose_name = ugettext_lazy('Dashboard')
permission_required = 'projector.can_see_dashboard'
default_weight = 10
icon_css_class = 'icon-home'
pattern_name = 'core_dashboard'

View File

Before

Width:  |  Height:  |  Size: 979 B

After

Width:  |  Height:  |  Size: 979 B

View File

@ -1,3 +0,0 @@
.icon-welcome {
background-position: 0 -24px;
}

View File

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends 'base.html' %}
{% load i18n %}
{% load staticfiles %}
@ -19,7 +19,7 @@
{% block content %}
<h1>{% trans 'Dashboard' %}
<small class="pull-right">
<a href="{% url 'projector_select_widgets' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Manage widgets' %}">
<a href="{% url 'core_select_widgets' %}" class="btn btn-mini" rel="tooltip" data-original-title="{% trans 'Manage widgets' %}">
<i class="icon-th-large"></i>
{% trans 'Widgets' %}
</a>

View File

@ -0,0 +1,8 @@
{% extends 'base.html' %}
{% block title %}{{ http_error.status_code }} {{ http_error.name }} {{ block.super }}{% endblock %}
{% block content %}
<h1>{{ http_error.name }}</h1>
<p>{{ http_error.description }}</p>
{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "base.html" %}
{% extends 'base.html' %}
{% load i18n %}
@ -7,7 +7,7 @@
{% block content %}
<h1>{% trans 'Select widgets' %}
<small class="pull-right">
<a href="{% url 'dashboard' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
<a href="{% url 'core_dashboard' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans 'Back to overview' %}</a>
</small>
</h1>

View File

@ -10,14 +10,21 @@ urlpatterns = patterns(
'',
# Redirect to dashboard URL
url(r'^$',
RedirectView.as_view(url='projector/dashboard/'),
RedirectView.as_view(url_name='core_dashboard'),
name='home',),
url(r'^dashboard/$',
views.DashboardView.as_view(),
name='core_dashboard'),
url(r'^dashboard/select_widgets/$',
views.SelectWidgetsView.as_view(),
name='core_select_widgets'),
url(r'^version/$',
views.VersionView.as_view(),
name='core_version',),
url(r'^search/$',
views.SearchView(),
name='search',),
)
name='core_search',))

View File

@ -1,8 +1,13 @@
# -*- coding: utf-8 -*-
from django.conf import settings
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.shortcuts import redirect, render_to_response
from django.template import RequestContext
from django.utils.importlib import import_module
from django.utils.translation import ugettext as _
from haystack.views import SearchView as _SearchView
from openslides import get_version as get_openslides_version
@ -10,7 +15,73 @@ from openslides import get_git_commit_id, RELEASE
from openslides.config.api import config
from openslides.utils.plugins import get_plugin_description, get_plugin_verbose_name, get_plugin_version
from openslides.utils.signals import template_manipulation
from openslides.utils.views import TemplateView
from openslides.utils.views import AjaxMixin, TemplateView, View
from openslides.utils.widgets import Widget
from .forms import SelectWidgetsForm
class DashboardView(AjaxMixin, TemplateView):
"""
Overview over all possible slides, the overlays and a live view: the
Dashboard of OpenSlides. This main view uses the widget api to collect all
widgets from all apps. See openslides.utils.widgets.Widget for more details.
"""
permission_required = 'projector.can_see_dashboard' # TODO: Rename this to core.can_see_dashboard
template_name = 'core/dashboard.html'
def get_context_data(self, **kwargs):
context = super(DashboardView, self).get_context_data(**kwargs)
widgets = []
for widget in Widget.get_all(self.request):
if widget.is_active():
widgets.append(widget)
context['extra_stylefiles'].extend(widget.get_stylesheets())
context['extra_javascript'].extend(widget.get_javascript_files())
context['widgets'] = widgets
return context
class SelectWidgetsView(TemplateView):
"""
Shows a form to select which widgets should be displayed on the own
dashboard. The setting is saved in the session.
"""
# TODO: Use another base view class here, e. g. a FormView
permission_required = 'projector.can_see_dashboard' # TODO: Rename this to core.can_see_dashboard
template_name = 'core/select_widgets.html'
def get_context_data(self, **kwargs):
context = super(SelectWidgetsView, self).get_context_data(**kwargs)
widgets = Widget.get_all(self.request)
for widget in widgets:
initial = {'widget': widget.is_active()}
prefix = widget.name
if self.request.method == 'POST':
widget.form = SelectWidgetsForm(
self.request.POST,
prefix=prefix,
initial=initial)
else:
widget.form = SelectWidgetsForm(prefix=prefix, initial=initial)
context['widgets'] = widgets
return context
def post(self, request, *args, **kwargs):
"""
Activates or deactivates the widgets in a post request.
"""
context = self.get_context_data(**kwargs)
session_widgets = self.request.session.get('widgets', {})
for widget in context['widgets']:
if widget.form.is_valid():
session_widgets[widget.name] = widget.form.cleaned_data['widget']
else:
messages.error(request, _('There are errors in the form.'))
break
else:
self.request.session['widgets'] = session_widgets
return redirect(reverse('core_dashboard'))
class VersionView(TemplateView):
@ -82,3 +153,30 @@ class SearchView(_SearchView):
else:
models.append([module.Index.modelfilter_name, module.Index.modelfilter_value])
return models
class ErrorView(View):
"""
View for Http 403, 404 and 500 error pages.
"""
status_code = None
def dispatch(self, request, *args, **kwargs):
http_error_strings = {
403: {'name': _('Forbidden'),
'description': _('Sorry, you have no permission to see this page.'),
'status_code': '403'},
404: {'name': _('Not Found'),
'description': _('Sorry, the requested page could not be found.'),
'status_code': '404'},
500: {'name': _('Internal Server Error'),
'description': _('Sorry, there was an unknown error. Please contact the event manager.'),
'status_code': '500'}}
context = {}
context['http_error'] = http_error_strings[self.status_code]
template_manipulation.send(sender=self.__class__, request=request, context=context)
response = render_to_response(
'core/error.html',
context_instance=RequestContext(request, context))
response.status_code = self.status_code
return response

View File

@ -14,7 +14,7 @@ class WelcomeWidget(Widget):
default_column = 1
default_weight = 10
template_name = 'core/widget_welcome.html'
stylesheets = ['styles/core.css']
icon_css_class = 'icon-home'
def get_verbose_name(self):
return config['welcome_title']

View File

@ -117,6 +117,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.i18n',
'django.core.context_processors.static',
'openslides.utils.auth.anonymous_context_additions',
'openslides.utils.main_menu.main_menu_entries',
)
CACHES = {

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from . import slides, widgets # noqa
from . import main_menu, slides, template, widgets # noqa

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy
from openslides.utils.main_menu import MainMenuEntry
class MediafileMainMenuEntry(MainMenuEntry):
"""
Main menu entry for the mediafile app.
"""
verbose_name = ugettext_lazy('Files')
default_weight = 60
pattern_name = 'mediafile_list'
icon_css_class = 'icon-paperclip'
def check_permission(self):
return (
self.request.user.has_perm('mediafile.can_see') or
self.request.user.has_perm('mediafile.can_upload') or
self.request.user.has_perm('mediafile.can_manage'))

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,8 +1,8 @@
/** Navigation icons (mapping to glyphicons-halflings) **/
.icon-mediafile {
.icon-paperclip {
background-image: url("../img/glyphicons_062_paperclip.png");
background-position: 0;
}
.leftmenu ul li.active a span.ico i.icon-mediafile {
.leftmenu ul li.active a span.ico i.icon-paperclip {
background-image: url("../img/glyphicons_062_paperclip_white.png");
}

View File

@ -0,0 +1,13 @@
# -*- coding: utf-8 -*-
from django.dispatch import receiver
from openslides.utils.signals import template_manipulation
@receiver(template_manipulation, dispatch_uid="add_mediafile_stylesheets")
def add_mediafile_stylesheets(sender, request, context, **kwargs):
"""
Adds the mediafile.css to the context.
"""
context['extra_stylefiles'].append('styles/mediafile.css')

View File

@ -1,12 +1,9 @@
# -*- coding: utf-8 -*-
from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.utils.translation import ugettext as _
from openslides.config.api import config
from openslides.projector.api import get_active_slide
from openslides.utils.template import Tab
from openslides.utils.tornado_webserver import ProjectorSocketHandler
from openslides.utils.views import (AjaxView, CreateView, DeleteView, RedirectView, ListView,
UpdateView)
@ -199,7 +196,7 @@ class PdfToggleFullscreenView(RedirectView):
Toggle fullscreen mode for pdf presentations.
"""
allow_ajax = True
url_name = 'dashboard'
url_name = 'core_dashboard'
def get_ajax_context(self, *args, **kwargs):
config['pdf_fullscreen'] = not config['pdf_fullscreen']
@ -208,19 +205,3 @@ class PdfToggleFullscreenView(RedirectView):
ProjectorSocketHandler.send_updates(
{'calls': {'toggle_fullscreen': config['pdf_fullscreen']}})
return {'fullscreen': config['pdf_fullscreen']}
def register_tab(request):
"""
Inserts a new Tab to the views for files.
"""
selected = request.path.startswith('/mediafile/')
return Tab(
title=_('Files'),
app='mediafile', # TODO: Rename this to icon='mediafile' later
stylefile='styles/mediafile.css',
url=reverse('mediafile_list'),
permission=(request.user.has_perm('mediafile.can_see') or
request.user.has_perm('mediafile.can_upload') or
request.user.has_perm('mediafile.can_manage')),
selected=selected)

View File

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from . import personal_info, signals, slides, widgets # noqa
from . import main_menu, personal_info, signals, slides, widgets # noqa

View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy
from openslides.utils.main_menu import MainMenuEntry
class MotionMainMenuEntry(MainMenuEntry):
"""
Main menu entry for the motion app.
"""
verbose_name = ugettext_lazy('Motions')
permission_required = 'motion.can_see_motion'
default_weight = 30
pattern_name = 'motion_list'
icon_css_class = 'icon-file'

View File

@ -12,7 +12,6 @@ from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelate
from openslides.config.api import config
from openslides.poll.views import PollFormView
from openslides.projector.api import get_active_slide, update_projector
from openslides.utils.template import Tab
from openslides.utils.utils import html_strong, htmldiff
from openslides.utils.views import (CreateView, DeleteView, DetailView,
FormView, ListView, PDFView, QuestionView,
@ -810,16 +809,3 @@ class MotionCSVImportView(FormView):
return super(MotionCSVImportView, self).form_valid(form)
motion_csv_import = MotionCSVImportView.as_view()
def register_tab(request):
"""
Return the motion tab.
"""
# TODO: Find a better way to set the selected var.
return Tab(
title=_('Motions'),
app='motion',
url=reverse('motion_list'),
permission=request.user.has_perm('motion.can_see_motion'),
selected=request.path.startswith('/motion/'))

View File

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from . import signals, slides, widgets # noqa
from . import main_menu, signals, slides, widgets # noqa

View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy
from openslides.utils.main_menu import MainMenuEntry
class ParticipantMainMenuEntry(MainMenuEntry):
"""
Main menu entry for the participant app.
"""
verbose_name = ugettext_lazy('Participants')
permission_required = 'participant.can_see_participant'
default_weight = 50
pattern_name = 'user_overview'
icon_css_class = 'icon-user'

View File

@ -43,7 +43,7 @@
{% trans 'Login' %}
</button>
{% if os_enable_anonymous_login %}
<a id="anonymous_login" class="btn" href="{% url 'dashboard' %}">
<a id="anonymous_login" class="btn" href="{% url 'core_dashboard' %}">
{% trans 'Continue as guest' %}
</a>
{% endif %}

View File

@ -10,7 +10,6 @@ from django.utils.translation import ugettext as _
from django.utils.translation import activate, ugettext_lazy
from openslides.config.api import config
from openslides.utils.template import Tab
from openslides.utils.utils import (delete_default_permissions, html_strong,
template)
from openslides.utils.views import (CreateView, DeleteView, DetailView,
@ -420,7 +419,7 @@ def user_settings_password(request):
if form.is_valid():
form.save()
messages.success(request, _('Password successfully changed.'))
return redirect(reverse('dashboard'))
return redirect(reverse('core_dashboard'))
else:
messages.error(request, _('Please check the form for errors.'))
else:
@ -429,18 +428,3 @@ def user_settings_password(request):
return {
'form': form,
}
def register_tab(request):
"""
Registers the participant tab.
"""
selected = request.path.startswith('/participant/')
return Tab(
title=_('Participants'),
app='participant',
url=reverse('user_overview'),
permission=(
request.user.has_perm('participant.can_see_participant') or
request.user.has_perm('participant.can_manage_participant')),
selected=selected)

View File

@ -110,7 +110,8 @@ class ProjectorSlide(SlideMixin, models.Model):
"""
Model for Slides, only for the projector. Also called custom slides.
"""
# TODO: Rename it to CustomSlide
# TODO: Rename it to CustomSlide and move it to core app.
# Check and rename permissions.
slide_callback_name = 'projector_slide'
title = models.CharField(max_length=256, verbose_name=ugettext_lazy("Title"))

View File

@ -7,7 +7,7 @@
{% block content %}
<h1>{% trans 'Custom slide' %}
<small class="pull-right">
<a href="{% url 'dashboard' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
<a href="{% url 'core_dashboard' %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to overview" %}</a>
</small>
</h1>
<form action="" method="post">{% csrf_token %}

View File

@ -23,14 +23,6 @@ urlpatterns = patterns(
views.ActivateView.as_view(),
name='projector_activate_slide'),
url(r'^dashboard/$',
views.DashboardView.as_view(),
name='dashboard'),
url(r'^widgets/$',
views.SelectWidgetsView.as_view(),
name='projector_select_widgets'),
url(r'^overlay_message/$',
views.OverlayMessageView.as_view(),
name='projector_overlay_message'),

View File

@ -1,45 +1,18 @@
# -*- coding: utf-8 -*-
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
from openslides.config.api import config
from openslides.mediafile.models import Mediafile
from openslides.utils.tornado_webserver import ProjectorSocketHandler
from openslides.utils.template import Tab
from openslides.utils.views import (AjaxMixin, CreateView, DeleteView,
from openslides.utils.views import (CreateView, DeleteView,
RedirectView, TemplateView, UpdateView)
from openslides.utils.widgets import Widget
from .api import (call_on_projector, get_active_slide,
get_overlays, get_projector_content, get_projector_overlays,
get_projector_overlays_js, reset_countdown, set_active_slide,
start_countdown, stop_countdown, update_projector_overlay)
from .forms import SelectWidgetsForm
from .models import ProjectorSlide
class DashboardView(AjaxMixin, TemplateView):
"""
Overview over all possible slides, the overlays and a live view.
"""
template_name = 'projector/dashboard.html'
permission_required = 'projector.can_see_dashboard'
def get_context_data(self, **kwargs):
context = super(DashboardView, self).get_context_data(**kwargs)
widgets = []
for widget in Widget.get_all(self.request):
if widget.is_active():
widgets.append(widget)
context['extra_stylefiles'].extend(widget.get_stylesheets())
context['extra_javascript'].extend(widget.get_javascript_files())
context['widgets'] = widgets
return context
class ProjectorView(TemplateView):
"""
The Projector-Page.
@ -73,7 +46,7 @@ class ActivateView(RedirectView):
Activate a Slide.
"""
permission_required = 'projector.can_manage_projector'
url_name = 'dashboard'
url_name = 'core_dashboard'
allow_ajax = True
def pre_redirect(self, request, *args, **kwargs):
@ -95,51 +68,12 @@ class ActivateView(RedirectView):
'scale': config['projector_scale']})
class SelectWidgetsView(TemplateView):
"""
Show a Form to Select the widgets.
"""
permission_required = 'projector.can_see_dashboard'
template_name = 'projector/select_widgets.html'
def get_context_data(self, **kwargs):
context = super(SelectWidgetsView, self).get_context_data(**kwargs)
widgets = Widget.get_all(self.request)
for widget in widgets:
initial = {'widget': widget.is_active()}
prefix = widget.name
if self.request.method == 'POST':
widget.form = SelectWidgetsForm(self.request.POST, prefix=prefix,
initial=initial)
else:
widget.form = SelectWidgetsForm(prefix=prefix, initial=initial)
context['widgets'] = widgets
return context
def post(self, request, *args, **kwargs):
"""
Activates or deactivates the widgets in a post request.
"""
context = self.get_context_data(**kwargs)
session_widgets = self.request.session.get('widgets', {})
for widget in context['widgets']:
if widget.form.is_valid():
session_widgets[widget.name] = widget.form.cleaned_data['widget']
else:
messages.error(request, _('Errors in the form.'))
break
else:
self.request.session['widgets'] = session_widgets
return redirect(reverse('dashboard'))
class ProjectorControllView(RedirectView):
"""
Scale or scroll the projector.
"""
permission_required = 'projector.can_manage_projector'
url_name = 'dashboard'
url_name = 'core_dashboard'
allow_ajax = True
def pre_redirect(self, request, *args, **kwargs):
@ -173,7 +107,7 @@ class CountdownControllView(RedirectView):
Start, stop or reset the countdown.
"""
permission_required = 'projector.can_manage_projector'
url_name = 'dashboard'
url_name = 'core_dashboard'
allow_ajax = True
def pre_redirect(self, request, *args, **kwargs):
@ -205,7 +139,7 @@ class OverlayMessageView(RedirectView):
"""
Sets or clears the overlay message
"""
url_name = 'dashboard'
url_name = 'core_dashboard'
allow_ajax = True
permission_required = 'projector.can_manage_projector'
@ -226,7 +160,7 @@ class ActivateOverlay(RedirectView):
"""
Activate or deactivate an overlay.
"""
url_name = 'dashboard'
url_name = 'core_dashboard'
allow_ajax = True
permission_required = 'projector.can_manage_projector'
@ -256,7 +190,7 @@ class CustomSlideCreateView(CreateView):
template_name = 'projector/new.html'
model = ProjectorSlide
context_object_name = 'customslide'
success_url_name = 'dashboard'
success_url_name = 'core_dashboard'
url_name_args = []
@ -268,7 +202,7 @@ class CustomSlideUpdateView(UpdateView):
template_name = 'projector/new.html'
model = ProjectorSlide
context_object_name = 'customslide'
success_url_name = 'dashboard'
success_url_name = 'core_dashboard'
url_name_args = []
@ -278,18 +212,4 @@ class CustomSlideDeleteView(DeleteView):
"""
permission_required = 'projector.can_manage_projector'
model = ProjectorSlide
success_url_name = 'dashboard'
def register_tab(request):
"""
Register the projector tab.
"""
selected = request.path.startswith('/projector/')
return Tab(
title=_('Dashboard'),
app='dashboard',
url=reverse('dashboard'),
permission=request.user.has_perm('projector.can_see_dashboard'),
selected=selected,
)
success_url_name = 'core_dashboard'

View File

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<h1>{% trans 'Permission Denied' %}</h1>
{% trans 'Sorry, you have no rights to see this page.' %}
{% endblock %}

View File

@ -1,7 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<h1>{% trans "Page not found." %}</h1>
{% endblock %}

View File

@ -1,7 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<h1>{% trans "Server Error" %}</h1>
{% endblock %}

View File

@ -3,7 +3,7 @@
{% load staticfiles %}
<!DOCTYPE html>
<html lang="{{LANGUAGE_CODE}}">
<html lang="{{ LANGUAGE_CODE }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -29,7 +29,7 @@
{% block loginbutton %}
<div class="pull-right">
<!-- Search field -->
<form class="navbar-search" action="{% url 'search' %}" method="get">
<form class="navbar-search" action="{% url 'core_search' %}" method="get">
<div class="input-append">
<input type="text" id="id_q" name="q" class="input-medium" placeholder="{% trans 'Search' %}">
<button type="submit" class="btn"><i class="icon-search"></i></button>
@ -60,61 +60,56 @@
</div>
</div> <!--/.container-fluid-->
{% block body %}
<!-- Container -->
<div class="container-fluid" id="container">
<div class="row-fluid">
{% block body %}
<!-- Container -->
<div class="container-fluid" id="container">
<div class="row-fluid">
<!-- Sidebar navigation (main menu) -->
<div class="span2 leftmenu lefticon">
<ul>
{% for entry in main_menu_entries %}
<li{% if entry.is_active %} class="active"{% endif %}>
<a href="{{ entry.get_url }}" class="tooltip-right">
<span class="ico"><i class="{{ entry.get_icon_css_class }}"></i></span>
<span class="text">{{ entry }}</span>
</a>
</li>
{% endfor %}
</ul>
</div>
<!-- Sidebar navigation-->
<div class="span2 leftmenu lefticon">
<ul>
{% for tab in tabs %}
{% if tab.permission %}
<li{% if tab.selected %} class="active"{% endif %}>
<a href="{{ tab.url }}" class="tooltip-right">
<span class="ico"><i class="icon-{{ tab.app }}"></i></span>
<span class="text">{{ tab.title }}</span>
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
<!-- Content -->
<div id="content" class="span10">
<div class="row-fluid">
<div class="span12">
<div id="notifications">
<div id="dummy-notification" class="notification" style="display:none">
<button type="button" class="close" data-dismiss="alert">×</button>
</div>
{% for message in messages %}
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message|safe }}
</div>
{% endfor %}
</div>
{% block content %}{% endblock %}
</div>
</div>
<hr />
<footer>
<small>
&copy; Copyright 20112013 | Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a> | <a href="{% url 'core_version' %}">Version</a>
</small>
</footer>
</div><!--/#content-->
<!-- Content -->
<div id="content" class="span10">
<div class="row-fluid">
<div class="span12">
<div id="notifications">
<div id="dummy-notification" class="notification" style="display:none">
<button type="button" class="close" data-dismiss="alert">×</button>
</div>
{% for message in messages %}
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message|safe }}
</div>
{% endfor %}
</div>
</div><!--/.row-fluid-->
</div><!--/#container-->
{% endblock %}<!--/body-->
{% block content %}
{% endblock %}
</div>
</div>
<hr />
<footer>
<small>
&copy; Copyright 20112013 | Powered by <a href="http://openslides.org" target="_blank">OpenSlides</a> | <a href="{% url 'core_version' %}">Version</a>
</small>
</footer>
</div><!--/content-->
</div><!--/row-->
</div><!--/container-fluid-->
{% endblock %}<!--/body-->
<!-- JavaScript (Placed at the end of the document so the pages load faster) -->
<!-- JavaScript (Placed at the end of the document so the pages load faster) -->
<script src="{% static 'javascript/jquery.min.js' %}" type="text/javascript"></script>
<script src="{% static 'javascript/bootstrap.min.js' %}" type="text/javascript"></script>
<script src="{% static 'javascript/utils.js' %}" type="text/javascript"></script>

View File

@ -3,9 +3,12 @@
from django.conf import settings
from django.conf.urls import include, patterns, url
from openslides.core.views import ErrorView
from openslides.utils.plugins import get_urlpatterns
handler500 = 'openslides.utils.views.server_error'
handler403 = ErrorView.as_view(status_code=403)
handler404 = ErrorView.as_view(status_code=404)
handler500 = ErrorView.as_view(status_code=500)
urlpatterns = []

View File

@ -14,9 +14,15 @@ class SignalConnectMetaClass(type):
The classmethod get_all_objects is added as get_all classmethod to every
class using this metaclass. Calling this on a base class or on child
classes will retrieve all connected children, on instance for each child
class. These instances will have a check_permission method which
returns True by default. You can override this method to return False
on runtime if you want to filter some children.
class.
These instances will have a check_permission method which returns True
by default. You can override this method to return False on runtime if
you want to filter some children.
They will also have a get_default_weight method which returns the value
of the default_weight attribute which is 0 by default. You can override
the attribute or the method to sort the children.
Example:
@ -37,7 +43,8 @@ class SignalConnectMetaClass(type):
"""
def __new__(metaclass, class_name, class_parents, class_attributes):
"""
Creates the class and connects it to the signal if so.
Creates the class and connects it to the signal if so. Adds all
default attributes and methods.
"""
class_attributes['get_all'] = get_all_objects
new_class = super(SignalConnectMetaClass, metaclass).__new__(
@ -53,8 +60,12 @@ class SignalConnectMetaClass(type):
raise NotImplementedError('Your class %s must have a signal argument, which must be a Django Signal instance.' % class_name)
else:
signal.connect(new_class, dispatch_uid=dispatch_uid)
if not hasattr(new_class, 'check_permission'):
setattr(new_class, 'check_permission', check_permission)
attributes = {'check_permission': check_permission,
'get_default_weight': get_default_weight,
'default_weight': 0}
for name, attribute in attributes.items():
if not hasattr(new_class, name):
setattr(new_class, name, attribute)
return new_class
@ -62,9 +73,8 @@ class SignalConnectMetaClass(type):
def get_all_objects(cls, request):
"""
Collects all objects of the class created by the SignalConnectMetaClass
from all apps via signal. If they have a get_default_weight method,
they are sorted. Does not return objects where check_permission returns
False.
from all apps via signal. They are sorted using the get_default_weight
method. Does not return objects where check_permission returns False.
Expects a request object.
@ -72,8 +82,7 @@ def get_all_objects(cls, request):
the SignalConnectMetaClass.
"""
all_objects = [obj for __, obj in cls.signal.send(sender=cls, request=request) if obj.check_permission()]
if hasattr(cls, 'get_default_weight'):
all_objects.sort(key=lambda obj: obj.get_default_weight())
all_objects.sort(key=lambda obj: obj.get_default_weight())
return all_objects
@ -85,3 +94,14 @@ def check_permission(self):
SignalConnectMetaClass.
"""
return True
def get_default_weight(self):
"""
Returns the value of the default_weight attribute by default. Override
this to sort some children on runtime.
This method is added to every instance of classes using the
SignalConnectMetaClass.
"""
return self.default_weight

View File

@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
from django.core.urlresolvers import reverse
from django.dispatch import Signal, receiver
from .dispatch import SignalConnectMetaClass
from .signals import template_manipulation
class MainMenuEntry(object):
"""
Base class for a main menu entry.
Every app which wants to add entries has to create a class subclassing
from this base class. For the appearance the verbose_name, the
pattern_name and the icon-css-class attribute have to be set. The
__metaclass__ attribute (SignalConnectMetaClass) does the rest of the
magic.
For the appearance there are some optional attributes like
permission_required, default_weight, stylesheets and javascript_files.
"""
__metaclass__ = SignalConnectMetaClass
signal = Signal(providing_args=['request'])
verbose_name = None
permission_required = None
default_weight = 0
pattern_name = None
icon_css_class = 'icon-home'
stylesheets = None
javascript_files = None
def __init__(self, sender, request, **kwargs):
"""
Initializes the main menu entry instance. This is done when the signal
is sent.
Only the required request argument is used. Because of Django's signal
API, we have to take also a sender argument and wildcard keyword
arguments. But they are not used here.
"""
self.request = request
def __unicode__(self):
if self.verbose_name is None:
raise NotImplementedError(
'The main menu entry class %s must provide a verbose_name '
'attribute or override the __unicode__ method.' % type(self).__name__)
return unicode(self.verbose_name)
@classmethod
def get_dispatch_uid(cls):
"""
Returns the classname as a unique string for each class. Returns None
for the base class so it will not be connected to the signal.
"""
if not cls.__name__ == 'MainMenuEntry':
return cls.__name__
def check_permission(self):
"""
Returns True if the request user is allowed to see the entry.
"""
return self.permission_required is None or self.request.user.has_perm(self.permission_required)
def get_icon_css_class(self):
"""
Returns the css class name of the icon. Default is 'icon-home'.
"""
return self.icon_css_class
def get_url(self):
"""
Returns the url of the entry.
"""
if self.pattern_name is None:
raise NotImplementedError(
'The main menu entry class %s must provide a pattern_name '
'attribute or override the get_url method.' % type(self).__name__)
return reverse(self.pattern_name)
def is_active(self):
"""
Returns True if the entry is selected at the moment.
"""
try:
return_value = isinstance(self, self.request.active_main_menu_class)
except AttributeError:
return_value = self.request.path.startswith(self.get_url())
return return_value
def get_stylesheets(self):
"""
Returns an interable of stylesheets to be loaded.
"""
return iter(self.stylesheets or [])
def get_javascript_files(self):
"""
Returns an interable of javascript files to be loaded.
"""
return iter(self.javascript_files or [])
def main_menu_entries(request):
"""
Adds all main menu entries to the request context as template context
processor.
"""
return {'main_menu_entries': MainMenuEntry.get_all(request)}
@receiver(template_manipulation, dispatch_uid="add_main_menu_context")
def add_main_menu_context(sender, request, context, **kwargs):
"""
Adds all stylefiles from all main menu entries to the context.
"""
for main_menu_entry in MainMenuEntry.get_all(request):
context['extra_stylefiles'].extend(main_menu_entry.get_stylesheets())
context['extra_javascript'].extend(main_menu_entry.get_javascript_files())

View File

@ -11,7 +11,7 @@ class PersonalInfo(object):
Every app which wants to add info has to create a class subclassing
from this base class. For the content the headline and default_weight
argument and the get_queryset method have to be set. The __metaclass__
attribute and the get_queryset method have to be set. The __metaclass__
attribute (SignalConnectMetaClass) does the rest of the magic.
"""
__metaclass__ = SignalConnectMetaClass
@ -21,7 +21,7 @@ class PersonalInfo(object):
def __init__(self, sender, request, **kwargs):
"""
Initialize the personal info instance. This is done when the signal is sent.
Initializes the personal info instance. This is done when the signal is sent.
Only the required request argument is used. Because of Django's signal
API, we have to take also a sender argument and wildcard keyword
@ -38,12 +38,6 @@ class PersonalInfo(object):
if not cls.__name__ == 'PersonalInfo':
return cls.__name__
def get_default_weight(self):
"""
Returns the default weight of the personal info class.
"""
return self.default_weight
def get_queryset(self):
"""
Returns a queryset of objects for the personal info widget.

View File

@ -2,4 +2,16 @@
from django.dispatch import Signal
template_manipulation = Signal(providing_args=['request', 'context'])
class TemplateManipulationSignal(Signal):
"""
Derived class to ensure that the key extra_stylefiles and extra_javascript
exist in the context dictionary.
"""
def send(self, **kwargs):
kwargs['context'].setdefault('extra_stylefiles', [])
kwargs['context'].setdefault('extra_javascript', [])
return super(TemplateManipulationSignal, self).send(**kwargs)
template_manipulation = TemplateManipulationSignal(providing_args=['request', 'context'])

View File

@ -1,14 +0,0 @@
# -*- coding: utf-8 -*-
class Tab(object):
"""
Entry for the main menu of OpenSlides.
"""
def __init__(self, title='', app='', stylefile='', url='', permission=True, selected=False):
self.title = title
self.app = app
self.stylefile = stylefile
self.url = url
self.permission = permission
self.selected = selected

View File

@ -9,13 +9,8 @@ from django.contrib.auth.decorators import login_required
from django.core.context_processors import csrf
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.core.urlresolvers import reverse
from django.dispatch import receiver
from django.http import (HttpResponse, HttpResponseRedirect,
HttpResponseServerError)
from django.template import RequestContext
from django.template.loader import render_to_string
from django.http import (HttpResponse, HttpResponseRedirect)
from django.utils.decorators import method_decorator
from django.utils.importlib import import_module
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from django.views import generic as django_views
@ -585,42 +580,3 @@ class PDFView(PermissionMixin, View):
def get(self, request, *args, **kwargs):
return self.render_to_response(self.get_filename())
def server_error(request, template_name='500.html'):
"""
500 error handler.
Templates: `500.html`
"""
return HttpResponseServerError(render_to_string(
template_name, context_instance=RequestContext(request)))
@receiver(template_manipulation, dispatch_uid="send_register_tab")
def send_register_tab(sender, request, context, **kwargs):
"""
Receiver to the template_manipulation signal. Collects from the file
views.py in all apps the tabs setup by the function register_tab.
Inserts the tab objects and also the extra_stylefiles to the context.
"""
tabs = []
if 'extra_stylefiles' in context:
extra_stylefiles = context['extra_stylefiles']
else:
extra_stylefiles = []
context.setdefault('extra_javascript', [])
# TODO: Do not go over the filesystem by any request
for app in settings.INSTALLED_APPS:
try:
mod = import_module(app + '.views')
tab = mod.register_tab(request)
tabs.append(tab)
if tab.stylefile:
extra_stylefiles.append(tab.stylefile)
except (ImportError, AttributeError):
continue
context.update({
'tabs': tabs,
'extra_stylefiles': extra_stylefiles})

View File

@ -12,15 +12,15 @@ class Widget(object):
"""
Base class for a widget for the dashboard.
Every app which wants to add a widget to the dashboard has to create a
Every app which wants to add widgets to the dashboard has to create a
widget class subclassing from this base class. The name attribute has to
be set. The __metaclass__ attribute (SignalConnectMetaClass) does the
rest of the magic.
For the appearance of the widget there are some more attributes like
For the appearance of the widget there are some optional attributes like
verbose_name, permission_required, default_column, default_weight,
default_active, template_name, context, icon, more_link_pattern_name,
stylesheets and javascript_files.
default_active, template_name, context, icon_css_class,
more_link_pattern_name, stylesheets and javascript_files.
"""
__metaclass__ = SignalConnectMetaClass
signal = Signal(providing_args=['request'])
@ -39,7 +39,7 @@ class Widget(object):
def __init__(self, sender, request, **kwargs):
"""
Initialize the widget instance. This is done when the signal is sent.
Initializes the widget instance. This is done when the signal is sent.
Only the required request argument is used. Because of Django's signal
API, we have to take also a sender argument and wildcard keyword
@ -73,12 +73,6 @@ class Widget(object):
"""
return self.permission_required is None or self.request.user.has_perm(self.permission_required)
def get_default_weight(self):
"""
Returns the default weight of the widget.
"""
return self.default_weight
def is_active(self):
"""
Returns True if the widget is active to be displayed.

View File

@ -51,7 +51,7 @@ class PersonalInfoWidget(TestCase):
self.client.login(username='HansMeiser', password='default')
def test_widget_appearance(self):
response = self.client.get('/projector/dashboard/')
response = self.client.get('/dashboard/')
self.assertContains(response, 'My personal info', status_code=200)
def test_item_list(self):
@ -59,11 +59,11 @@ class PersonalInfoWidget(TestCase):
if agenda:
item_1 = agenda.models.Item.objects.create(title='My Item Title iw5ohNgee4eiYahb5Eiv')
speaker = agenda.models.Speaker.objects.add(item=item_1, person=self.user)
response = self.client.get('/projector/dashboard/')
response = self.client.get('/dashboard/')
self.assertContains(response, 'I am on the list of speakers of the following items:', status_code=200)
self.assertContains(response, 'My Item Title iw5ohNgee4eiYahb5Eiv', status_code=200)
speaker.begin_speach()
response = self.client.get('/projector/dashboard/')
response = self.client.get('/dashboard/')
self.assertNotContains(response, 'My Item Title iw5ohNgee4eiYahb5Eiv', status_code=200)
def test_submitter_list(self):
@ -73,7 +73,7 @@ class PersonalInfoWidget(TestCase):
motion_2 = motion.models.Motion.objects.create(title='My Motion Title quielohL7vah1weochai', text='My Motion Text')
motion.models.MotionSubmitter.objects.create(motion=motion_1, person=self.user)
motion.models.MotionSubmitter.objects.create(motion=motion_2, person=self.user)
response = self.client.get('/projector/dashboard/')
response = self.client.get('/dashboard/')
self.assertContains(response, 'I submitted the following motions:', status_code=200)
self.assertContains(response, 'My Motion Title pa8aeNohYai0ahge', status_code=200)
self.assertContains(response, 'My Motion Title quielohL7vah1weochai', status_code=200)
@ -86,7 +86,7 @@ class PersonalInfoWidget(TestCase):
motion.models.MotionSupporter.objects.create(motion=motion_1, person=self.user)
motion.models.MotionSupporter.objects.create(motion=motion_2, person=self.user)
config['motion_min_supporters'] = 1
response = self.client.get('/projector/dashboard/')
response = self.client.get('/dashboard/')
self.assertContains(response, 'I support the following motions:', status_code=200)
self.assertContains(response, 'My Motion Title jahN9phaiThae5ooKubu', status_code=200)
self.assertContains(response, 'My Motion Title vech9ash8aeh9eej2Ga2', status_code=200)
@ -96,6 +96,6 @@ class PersonalInfoWidget(TestCase):
if assignment:
assignment_1 = assignment.models.Assignment.objects.create(name='Hausmeister ooKoh7roApoo3phe', posts=1)
assignment_1.run(candidate=self.user, person=self.user)
response = self.client.get('/projector/dashboard/')
response = self.client.get('/dashboard/')
self.assertContains(response, 'I am candidate for the following elections:', status_code=200)
self.assertContains(response, 'Hausmeister ooKoh7roApoo3phe', status_code=200)

View File

@ -240,7 +240,7 @@ class SpeakerListOpenView(SpeakerViewTestCase):
class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
def test_global_redirect_url(self):
response = self.speaker1_client.get('/agenda/list_of_speakers/')
self.assertRedirects(response, '/projector/dashboard/')
self.assertRedirects(response, '/dashboard/')
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
set_active_slide('agenda', pk=1)
@ -249,7 +249,7 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
def test_global_add_url(self):
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
self.assertRedirects(response, '/projector/dashboard/')
self.assertRedirects(response, '/dashboard/')
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
set_active_slide('agenda', pk=1)
@ -265,38 +265,38 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
def test_global_next_speaker_url(self):
response = self.admin_client.get('/agenda/list_of_speakers/next/')
self.assertRedirects(response, '/projector/dashboard/')
self.assertRedirects(response, '/dashboard/')
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
set_active_slide('agenda', pk=1)
response = self.admin_client.get('/agenda/list_of_speakers/next/')
self.assertRedirects(response, '/projector/dashboard/')
self.assertRedirects(response, '/dashboard/')
self.assertMessage(response, 'The list of speakers is empty.')
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
self.assertTrue(Speaker.objects.get(item__pk='1').begin_time is None)
response = self.admin_client.get('/agenda/list_of_speakers/next/')
self.assertRedirects(response, '/projector/dashboard/')
self.assertRedirects(response, '/dashboard/')
self.assertTrue(Speaker.objects.get(item__pk='1').begin_time is not None)
def test_global_end_speach_url(self):
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
self.assertRedirects(response, '/projector/dashboard/')
self.assertRedirects(response, '/dashboard/')
self.assertMessage(response, 'There is no list of speakers for the current slide. Please choose the agenda item manually from the agenda.')
set_active_slide('agenda', pk=1)
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
self.assertRedirects(response, '/projector/dashboard/')
self.assertRedirects(response, '/dashboard/')
self.assertMessage(response, 'There is no one speaking at the moment.')
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
self.assertTrue(Speaker.objects.get(item__pk='1').begin_time is None)
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
self.assertRedirects(response, '/projector/dashboard/')
self.assertRedirects(response, '/dashboard/')
self.assertMessage(response, 'There is no one speaking at the moment.')
response = self.admin_client.get('/agenda/list_of_speakers/next/')
self.assertTrue(Speaker.objects.get(item__pk='1').end_time is None)
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
self.assertRedirects(response, '/projector/dashboard/')
self.assertRedirects(response, '/dashboard/')
self.assertTrue(Speaker.objects.get(item__pk='1').end_time is not None)

View File

@ -1,15 +1,70 @@
# -*- coding: utf-8 -*-
from django.test.client import Client
from mock import patch
from django.test.client import Client, RequestFactory
from mock import MagicMock, patch
from openslides import get_version
from openslides.agenda.models import Item
from openslides.config.api import config
from openslides.core import views
from openslides.participant.models import User
from openslides.utils.test import TestCase
class SelectWidgetsViewTest(TestCase):
rf = RequestFactory()
@patch('openslides.core.views.SelectWidgetsForm')
@patch('openslides.core.views.TemplateView.get_context_data')
@patch('openslides.core.views.Widget')
def test_get_context_data(self, mock_Widget, mock_get_context_data,
mock_SelectWidgetsForm):
view = views.SelectWidgetsView()
view.request = self.rf.get('/')
view.request.session = MagicMock()
widget = MagicMock()
widget.name = 'some_widget_Bohsh1Pa0eeziRaihu8O'
widget.is_active.return_value = True
mock_Widget.get_all.return_value = [widget]
mock_get_context_data.return_value = {}
# Test get
context = view.get_context_data()
self.assertIn('widgets', context)
self.assertIn(widget, context['widgets'])
mock_SelectWidgetsForm.assert_called_with(
prefix='some_widget_Bohsh1Pa0eeziRaihu8O', initial={'widget': True})
# Test post
view.request = self.rf.post('/')
view.request.session = MagicMock()
context = view.get_context_data()
mock_SelectWidgetsForm.assert_called_with(
view.request.POST, prefix='some_widget_Bohsh1Pa0eeziRaihu8O', initial={'widget': True})
@patch('openslides.core.views.messages')
def test_post(self, mock_messages):
view = views.SelectWidgetsView()
view.request = self.rf.post('/')
view.request.session = {}
widget = MagicMock()
widget.name = 'some_widget_ahgaeree8JeReichue8u'
context = {'widgets': [widget]}
mock_context_data = MagicMock(return_value=context)
with patch('openslides.core.views.SelectWidgetsView.get_context_data', mock_context_data):
widget.form.is_valid.return_value = True
view.post(view.request)
self.assertIn('some_widget_ahgaeree8JeReichue8u', view.request.session['widgets'])
# Test with errors in form
widget.form.is_valid.return_value = False
view.request.session = {}
view.post(view.request)
self.assertNotIn('widgets', view.request.session)
mock_messages.error.assert_called_with(view.request, 'There are errors in the form.')
class VersionViewTest(TestCase):
def setUp(self):
User.objects.create_user('CoreMaximilian', 'xxx@xx.xx', 'default')

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from django.contrib.auth.models import AnonymousUser
from django.test.client import Client, RequestFactory
from mock import call, MagicMock, patch
@ -19,6 +20,7 @@ class ProjectorViewTest(TestCase):
mock_get_projector_overlays_js):
view = views.ProjectorView()
view.request = self.rf.get('/')
view.request.user = AnonymousUser()
# Test preview
view.kwargs = {'callback': 'slide_callback'}
@ -59,60 +61,6 @@ class ActivateViewTest(TestCase):
self.assertTrue(mock_call_on_projector.called)
class SelectWidgetsViewTest(TestCase):
rf = RequestFactory()
@patch('openslides.projector.views.SelectWidgetsForm')
@patch('openslides.projector.views.TemplateView.get_context_data')
@patch('openslides.projector.views.Widget')
def test_get_context_data(self, mock_Widget, mock_get_context_data,
mock_SelectWidgetsForm):
view = views.SelectWidgetsView()
view.request = self.rf.get('/')
view.request.session = MagicMock()
widget = MagicMock()
widget.name = 'some_widget_Bohsh1Pa0eeziRaihu8O'
widget.is_active.return_value = True
mock_Widget.get_all.return_value = [widget]
mock_get_context_data.return_value = {}
# Test get
context = view.get_context_data()
self.assertIn('widgets', context)
self.assertIn(widget, context['widgets'])
mock_SelectWidgetsForm.assert_called_with(
prefix='some_widget_Bohsh1Pa0eeziRaihu8O', initial={'widget': True})
# Test post
view.request = self.rf.post('/')
view.request.session = MagicMock()
context = view.get_context_data()
mock_SelectWidgetsForm.assert_called_with(
view.request.POST, prefix='some_widget_Bohsh1Pa0eeziRaihu8O', initial={'widget': True})
@patch('openslides.projector.views.messages')
def test_post(self, mock_messages):
view = views.SelectWidgetsView()
view.request = self.rf.post('/')
view.request.session = {}
widget = MagicMock()
widget.name = 'some_widget_ahgaeree8JeReichue8u'
context = {'widgets': [widget]}
mock_context_data = MagicMock(return_value=context)
with patch('openslides.projector.views.SelectWidgetsView.get_context_data', mock_context_data):
widget.form.is_valid.return_value = True
view.post(view.request)
self.assertIn('some_widget_ahgaeree8JeReichue8u', view.request.session['widgets'])
# Test with errors in form
widget.form.is_valid.return_value = False
view.request.session = {}
view.post(view.request)
self.assertNotIn('widgets', view.request.session)
mock_messages.error.assert_called_with(view.request, 'Errors in the form.')
class ProjectorControllViewTest(TestCase):
@patch('openslides.projector.views.call_on_projector')
def test_bigger(self, mock_call_on_projector):
@ -176,7 +124,7 @@ class CustomSlidesTest(TestCase):
response = self.admin_client.get(url)
self.assertTemplateUsed(response, 'projector/new.html')
response = self.admin_client.post(url, {'title': 'test_title_roo2xi2EibooHie1kohd', 'weight': '0'})
self.assertRedirects(response, '/projector/dashboard/')
self.assertRedirects(response, '/dashboard/')
self.assertTrue(ProjectorSlide.objects.filter(title='test_title_roo2xi2EibooHie1kohd').exists())
def test_update(self):
@ -188,7 +136,7 @@ class CustomSlidesTest(TestCase):
self.assertTemplateUsed(response, 'projector/new.html')
self.assertContains(response, 'test_title_jeeDeB3aedei8ahceeso')
response = self.admin_client.post(url, {'title': 'test_title_ai8Ooboh5bahr6Ee7goo', 'weight': '0'})
self.assertRedirects(response, '/projector/dashboard/')
self.assertRedirects(response, '/dashboard/')
self.assertEqual(ProjectorSlide.objects.get(pk=1).title, 'test_title_ai8Ooboh5bahr6Ee7goo')
def test_delete(self):
@ -199,7 +147,7 @@ class CustomSlidesTest(TestCase):
response = self.admin_client.get(url)
self.assertRedirects(response, '/projector/1/edit/')
response = self.admin_client.post(url, {'yes': 'true'})
self.assertRedirects(response, '/projector/dashboard/')
self.assertRedirects(response, '/dashboard/')
self.assertFalse(ProjectorSlide.objects.exists())

View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
from django.contrib.auth.models import AnonymousUser
from django.test.client import RequestFactory
from openslides.utils.test import TestCase
from openslides.utils.main_menu import MainMenuEntry
class MainMenuEntryObject(TestCase):
request_factory = RequestFactory()
def get_entry(self, cls):
request = self.request_factory.get('/')
request.user = AnonymousUser()
for entry in MainMenuEntry.get_all(request):
if type(entry) == cls:
value = entry
break
else:
value = False
return value
def test_appearance(self):
class TestMenuEntryOne(MainMenuEntry):
pattern_name = 'core_version'
verbose_name = 'Menu entry for testing gae2thooc4che4thaoNo'
self.assertEqual(unicode(self.get_entry(TestMenuEntryOne)), u'Menu entry for testing gae2thooc4che4thaoNo')
def test_missing_verbose_name(self):
class TestMenuEntryBadOne(MainMenuEntry):
pattern_name = 'core_version'
entry = self.get_entry(TestMenuEntryBadOne)
text = ('The main menu entry class TestMenuEntryBadOne must provide a '
'verbose_name attribute or override the __unicode__ method.')
self.assertRaisesMessage(NotImplementedError, text, unicode, entry)
def test_missing_pattern_name(self):
class TestMenuEntryBadTwo(MainMenuEntry):
verbose_name = 'Menu entry for testing ahVeibai1iecaish2aeR'
entry = self.get_entry(TestMenuEntryBadTwo)
text = ('The main menu entry class TestMenuEntryBadTwo must provide a '
'pattern_name attribute or override the get_url method.')
self.assertRaisesMessage(NotImplementedError, text, entry.get_url)

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import clear_url_caches
from django.test import RequestFactory
@ -76,23 +77,23 @@ class AjaxMixinTest(ViewTestCase):
class ExtraContextMixinTest(ViewTestCase):
"""
Tests the ExtraContextMixin by testen the TemplateView
Tests the ExtraContextMixin by testing the TemplateView.
"""
def test_get_context_data(self):
view = views.TemplateView()
get_context_data = view.get_context_data
view.request = self.rf.get('/', {})
view.request.user = AnonymousUser()
context = get_context_data()
self.assertIn('tabs', context)
self.assertIn('extra_stylefiles', context)
self.assertIn('extra_javascript', context)
context = get_context_data(some_context='context')
self.assertIn('tabs', context)
self.assertIn('some_context', context)
template_manipulation.connect(set_context, dispatch_uid='set_context_test')
context = get_context_data()
self.assertIn('tabs', context)
self.assertIn('new_context', context)
template_manipulation.disconnect(set_context, dispatch_uid='set_context_test')