Merge pull request #1162 from normanjaeckel/MoveDashboard
Move dashboard to core app.
This commit is contained in:
commit
0a9a99cfb0
@ -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)
|
||||
|
@ -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
|
||||
|
16
openslides/agenda/main_menu.py
Normal file
16
openslides/agenda/main_menu.py
Normal 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'
|
@ -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)
|
||||
|
@ -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
|
||||
|
16
openslides/assignment/main_menu.py
Normal file
16
openslides/assignment/main_menu.py
Normal 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'
|
BIN
openslides/assignment/static/img/glyphicons_041_charts.png
Normal file
BIN
openslides/assignment/static/img/glyphicons_041_charts.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
openslides/assignment/static/img/glyphicons_041_charts_white.png
Normal file
BIN
openslides/assignment/static/img/glyphicons_041_charts_white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
@ -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;
|
||||
}
|
||||
|
13
openslides/assignment/template.py
Normal file
13
openslides/assignment/template.py
Normal 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')
|
@ -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,
|
||||
)
|
||||
|
@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import main_menu # noqa
|
30
openslides/config/main_menu.py
Normal file
30
openslides/config/main_menu.py
Normal 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
|
@ -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/'))
|
||||
|
@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import signals, widgets # noqa
|
||||
from . import main_menu, signals, widgets # noqa
|
||||
|
16
openslides/core/main_menu.py
Normal file
16
openslides/core/main_menu.py
Normal 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'
|
Before Width: | Height: | Size: 979 B After Width: | Height: | Size: 979 B |
@ -1,3 +0,0 @@
|
||||
.icon-welcome {
|
||||
background-position: 0 -24px;
|
||||
}
|
@ -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>
|
8
openslides/core/templates/core/error.html
Normal file
8
openslides/core/templates/core/error.html
Normal 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 %}
|
@ -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>
|
||||
|
@ -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',))
|
||||
|
@ -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
|
||||
|
@ -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']
|
||||
|
@ -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 = {
|
||||
|
@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import slides, widgets # noqa
|
||||
from . import main_menu, slides, template, widgets # noqa
|
||||
|
21
openslides/mediafile/main_menu.py
Normal file
21
openslides/mediafile/main_menu.py
Normal 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'))
|
BIN
openslides/mediafile/static/img/glyphicons_062_paperclip.png
Normal file
BIN
openslides/mediafile/static/img/glyphicons_062_paperclip.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
@ -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");
|
||||
}
|
||||
|
13
openslides/mediafile/template.py
Normal file
13
openslides/mediafile/template.py
Normal 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')
|
@ -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)
|
||||
|
@ -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
|
||||
|
16
openslides/motion/main_menu.py
Normal file
16
openslides/motion/main_menu.py
Normal 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'
|
@ -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/'))
|
||||
|
@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import signals, slides, widgets # noqa
|
||||
from . import main_menu, signals, slides, widgets # noqa
|
||||
|
16
openslides/participant/main_menu.py
Normal file
16
openslides/participant/main_menu.py
Normal 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'
|
@ -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 %}
|
||||
|
@ -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)
|
||||
|
@ -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"))
|
||||
|
@ -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 %}
|
||||
|
@ -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'),
|
||||
|
@ -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'
|
||||
|
@ -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 %}
|
@ -1,7 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Page not found." %}</h1>
|
||||
{% endblock %}
|
@ -1,7 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Server Error" %}</h1>
|
||||
{% endblock %}
|
@ -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>
|
||||
© Copyright 2011–2013 | 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>
|
||||
© Copyright 2011–2013 | 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>
|
||||
|
@ -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 = []
|
||||
|
||||
|
@ -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
|
||||
|
120
openslides/utils/main_menu.py
Normal file
120
openslides/utils/main_menu.py
Normal 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())
|
@ -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.
|
||||
|
@ -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'])
|
||||
|
@ -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
|
@ -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})
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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())
|
||||
|
||||
|
||||
|
47
tests/utils/test_main_menu.py
Normal file
47
tests/utils/test_main_menu.py
Normal 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)
|
@ -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')
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user