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:
parent
452e6cfaed
commit
21ff62dd32
@ -9,7 +9,7 @@ Version 1.6 (unreleased)
|
|||||||
[https://github.com/OpenSlides/OpenSlides/issues?milestone=14]
|
[https://github.com/OpenSlides/OpenSlides/issues?milestone=14]
|
||||||
|
|
||||||
Participants:
|
Participants:
|
||||||
- Disable widgets by default.
|
- Disabled widgets by default.
|
||||||
Files:
|
Files:
|
||||||
- Enabled update and delete view for uploader refering to his own files.
|
- Enabled update and delete view for uploader refering to his own files.
|
||||||
Other:
|
Other:
|
||||||
@ -18,6 +18,9 @@ Other:
|
|||||||
- Renamed config api classes.
|
- Renamed config api classes.
|
||||||
- Renamed some classes of the poll api.
|
- Renamed some classes of the poll api.
|
||||||
- Inserted api for the personal info widget.
|
- 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)
|
Version 1.5.1 (unreleased)
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- 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.projector.api import get_active_slide, update_projector
|
||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.pdf import stylesheet
|
from openslides.utils.pdf import stylesheet
|
||||||
from openslides.utils.template import Tab
|
|
||||||
from openslides.utils.utils import html_strong
|
from openslides.utils.utils import html_strong
|
||||||
from openslides.utils.views import (CreateView, DeleteView, FormView, PDFView,
|
from openslides.utils.views import (CreateView, DeleteView, FormView, PDFView,
|
||||||
RedirectView, SingleObjectMixin,
|
RedirectView, SingleObjectMixin,
|
||||||
@ -569,7 +568,7 @@ class CurrentListOfSpeakersView(RedirectView):
|
|||||||
messages.error(request, _(
|
messages.error(request, _(
|
||||||
'There is no list of speakers for the current slide. '
|
'There is no list of speakers for the current slide. '
|
||||||
'Please choose the agenda item manually from the agenda.'))
|
'Please choose the agenda item manually from the agenda.'))
|
||||||
return reverse('dashboard')
|
return reverse('core_dashboard')
|
||||||
|
|
||||||
if self.set_speaker:
|
if self.set_speaker:
|
||||||
if item.speaker_list_closed:
|
if item.speaker_list_closed:
|
||||||
@ -613,25 +612,11 @@ class CurrentListOfSpeakersView(RedirectView):
|
|||||||
|
|
||||||
if item.type == Item.ORGANIZATIONAL_ITEM:
|
if item.type == Item.ORGANIZATIONAL_ITEM:
|
||||||
if reverse_to_dashboard or not self.request.user.has_perm('agenda.can_see_orga_items'):
|
if reverse_to_dashboard or not self.request.user.has_perm('agenda.can_see_orga_items'):
|
||||||
return reverse('dashboard')
|
return reverse('core_dashboard')
|
||||||
else:
|
else:
|
||||||
return reverse('item_view', args=[item.pk])
|
return reverse('item_view', args=[item.pk])
|
||||||
else:
|
else:
|
||||||
if reverse_to_dashboard or not self.request.user.has_perm('agenda.can_see_agenda'):
|
if reverse_to_dashboard or not self.request.user.has_perm('agenda.can_see_agenda'):
|
||||||
return reverse('dashboard')
|
return reverse('core_dashboard')
|
||||||
else:
|
else:
|
||||||
return reverse('item_view', args=[item.pk])
|
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 -*-
|
# -*- 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 {
|
td.elected {
|
||||||
background-color: #BED4DE !important;
|
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.poll.views import PollFormView
|
||||||
from openslides.utils.pdf import stylesheet
|
from openslides.utils.pdf import stylesheet
|
||||||
from openslides.utils.person import get_person
|
from openslides.utils.person import get_person
|
||||||
from openslides.utils.template import Tab
|
|
||||||
from openslides.utils.utils import html_strong
|
from openslides.utils.utils import html_strong
|
||||||
from openslides.utils.views import (CreateView, DeleteView, DetailView,
|
from openslides.utils.views import (CreateView, DeleteView, DetailView,
|
||||||
ListView, PDFView, PermissionMixin,
|
ListView, PDFView, PermissionMixin,
|
||||||
@ -606,18 +605,3 @@ class AssignmentPollPDF(PDFView):
|
|||||||
('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
|
('GRID', (0, 0), (-1, -1), 0.25, colors.grey),
|
||||||
('VALIGN', (0, 0), (-1, -1), 'TOP')]))
|
('VALIGN', (0, 0), (-1, -1), 'TOP')]))
|
||||||
story.append(t)
|
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.core.urlresolvers import reverse
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from openslides.utils.template import Tab
|
|
||||||
from openslides.utils.views import FormView
|
from openslides.utils.views import FormView
|
||||||
|
|
||||||
from .api import config
|
from .api import config
|
||||||
@ -109,15 +108,3 @@ class ConfigView(FormView):
|
|||||||
config[key] = form.cleaned_data[key]
|
config[key] = form.cleaned_data[key]
|
||||||
messages.success(self.request, _('%s settings successfully saved.') % _(self.config_collection.title))
|
messages.success(self.request, _('%s settings successfully saved.') % _(self.config_collection.title))
|
||||||
return super(ConfigView, self).form_valid(form)
|
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 -*-
|
# -*- 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 i18n %}
|
||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
@ -19,7 +19,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans 'Dashboard' %}
|
<h1>{% trans 'Dashboard' %}
|
||||||
<small class="pull-right">
|
<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>
|
<i class="icon-th-large"></i>
|
||||||
{% trans 'Widgets' %}
|
{% trans 'Widgets' %}
|
||||||
</a>
|
</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 %}
|
{% load i18n %}
|
||||||
|
|
||||||
@ -7,7 +7,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans 'Select widgets' %}
|
<h1>{% trans 'Select widgets' %}
|
||||||
<small class="pull-right">
|
<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>
|
</small>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
@ -10,14 +10,21 @@ urlpatterns = patterns(
|
|||||||
'',
|
'',
|
||||||
# Redirect to dashboard URL
|
# Redirect to dashboard URL
|
||||||
url(r'^$',
|
url(r'^$',
|
||||||
RedirectView.as_view(url='projector/dashboard/'),
|
RedirectView.as_view(url_name='core_dashboard'),
|
||||||
name='home',),
|
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/$',
|
url(r'^version/$',
|
||||||
views.VersionView.as_view(),
|
views.VersionView.as_view(),
|
||||||
name='core_version',),
|
name='core_version',),
|
||||||
|
|
||||||
url(r'^search/$',
|
url(r'^search/$',
|
||||||
views.SearchView(),
|
views.SearchView(),
|
||||||
name='search',),
|
name='core_search',))
|
||||||
)
|
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib import messages
|
||||||
from django.core.exceptions import PermissionDenied
|
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.importlib import import_module
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
from haystack.views import SearchView as _SearchView
|
from haystack.views import SearchView as _SearchView
|
||||||
|
|
||||||
from openslides import get_version as get_openslides_version
|
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.config.api import config
|
||||||
from openslides.utils.plugins import get_plugin_description, get_plugin_verbose_name, get_plugin_version
|
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.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):
|
class VersionView(TemplateView):
|
||||||
@ -82,3 +153,30 @@ class SearchView(_SearchView):
|
|||||||
else:
|
else:
|
||||||
models.append([module.Index.modelfilter_name, module.Index.modelfilter_value])
|
models.append([module.Index.modelfilter_name, module.Index.modelfilter_value])
|
||||||
return models
|
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_column = 1
|
||||||
default_weight = 10
|
default_weight = 10
|
||||||
template_name = 'core/widget_welcome.html'
|
template_name = 'core/widget_welcome.html'
|
||||||
stylesheets = ['styles/core.css']
|
icon_css_class = 'icon-home'
|
||||||
|
|
||||||
def get_verbose_name(self):
|
def get_verbose_name(self):
|
||||||
return config['welcome_title']
|
return config['welcome_title']
|
||||||
|
@ -117,6 +117,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
|||||||
'django.core.context_processors.i18n',
|
'django.core.context_processors.i18n',
|
||||||
'django.core.context_processors.static',
|
'django.core.context_processors.static',
|
||||||
'openslides.utils.auth.anonymous_context_additions',
|
'openslides.utils.auth.anonymous_context_additions',
|
||||||
|
'openslides.utils.main_menu.main_menu_entries',
|
||||||
)
|
)
|
||||||
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- 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) **/
|
/** Navigation icons (mapping to glyphicons-halflings) **/
|
||||||
.icon-mediafile {
|
.icon-paperclip {
|
||||||
background-image: url("../img/glyphicons_062_paperclip.png");
|
background-image: url("../img/glyphicons_062_paperclip.png");
|
||||||
background-position: 0;
|
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");
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
|
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.projector.api import get_active_slide
|
from openslides.projector.api import get_active_slide
|
||||||
from openslides.utils.template import Tab
|
|
||||||
from openslides.utils.tornado_webserver import ProjectorSocketHandler
|
from openslides.utils.tornado_webserver import ProjectorSocketHandler
|
||||||
from openslides.utils.views import (AjaxView, CreateView, DeleteView, RedirectView, ListView,
|
from openslides.utils.views import (AjaxView, CreateView, DeleteView, RedirectView, ListView,
|
||||||
UpdateView)
|
UpdateView)
|
||||||
@ -199,7 +196,7 @@ class PdfToggleFullscreenView(RedirectView):
|
|||||||
Toggle fullscreen mode for pdf presentations.
|
Toggle fullscreen mode for pdf presentations.
|
||||||
"""
|
"""
|
||||||
allow_ajax = True
|
allow_ajax = True
|
||||||
url_name = 'dashboard'
|
url_name = 'core_dashboard'
|
||||||
|
|
||||||
def get_ajax_context(self, *args, **kwargs):
|
def get_ajax_context(self, *args, **kwargs):
|
||||||
config['pdf_fullscreen'] = not config['pdf_fullscreen']
|
config['pdf_fullscreen'] = not config['pdf_fullscreen']
|
||||||
@ -208,19 +205,3 @@ class PdfToggleFullscreenView(RedirectView):
|
|||||||
ProjectorSocketHandler.send_updates(
|
ProjectorSocketHandler.send_updates(
|
||||||
{'calls': {'toggle_fullscreen': config['pdf_fullscreen']}})
|
{'calls': {'toggle_fullscreen': config['pdf_fullscreen']}})
|
||||||
return {'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 -*-
|
# -*- 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.config.api import config
|
||||||
from openslides.poll.views import PollFormView
|
from openslides.poll.views import PollFormView
|
||||||
from openslides.projector.api import get_active_slide, update_projector
|
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.utils import html_strong, htmldiff
|
||||||
from openslides.utils.views import (CreateView, DeleteView, DetailView,
|
from openslides.utils.views import (CreateView, DeleteView, DetailView,
|
||||||
FormView, ListView, PDFView, QuestionView,
|
FormView, ListView, PDFView, QuestionView,
|
||||||
@ -810,16 +809,3 @@ class MotionCSVImportView(FormView):
|
|||||||
return super(MotionCSVImportView, self).form_valid(form)
|
return super(MotionCSVImportView, self).form_valid(form)
|
||||||
|
|
||||||
motion_csv_import = MotionCSVImportView.as_view()
|
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 -*-
|
# -*- 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' %}
|
{% trans 'Login' %}
|
||||||
</button>
|
</button>
|
||||||
{% if os_enable_anonymous_login %}
|
{% 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' %}
|
{% trans 'Continue as guest' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -10,7 +10,6 @@ from django.utils.translation import ugettext as _
|
|||||||
from django.utils.translation import activate, ugettext_lazy
|
from django.utils.translation import activate, ugettext_lazy
|
||||||
|
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
from openslides.utils.template import Tab
|
|
||||||
from openslides.utils.utils import (delete_default_permissions, html_strong,
|
from openslides.utils.utils import (delete_default_permissions, html_strong,
|
||||||
template)
|
template)
|
||||||
from openslides.utils.views import (CreateView, DeleteView, DetailView,
|
from openslides.utils.views import (CreateView, DeleteView, DetailView,
|
||||||
@ -420,7 +419,7 @@ def user_settings_password(request):
|
|||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
messages.success(request, _('Password successfully changed.'))
|
messages.success(request, _('Password successfully changed.'))
|
||||||
return redirect(reverse('dashboard'))
|
return redirect(reverse('core_dashboard'))
|
||||||
else:
|
else:
|
||||||
messages.error(request, _('Please check the form for errors.'))
|
messages.error(request, _('Please check the form for errors.'))
|
||||||
else:
|
else:
|
||||||
@ -429,18 +428,3 @@ def user_settings_password(request):
|
|||||||
return {
|
return {
|
||||||
'form': form,
|
'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.
|
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'
|
slide_callback_name = 'projector_slide'
|
||||||
|
|
||||||
title = models.CharField(max_length=256, verbose_name=ugettext_lazy("Title"))
|
title = models.CharField(max_length=256, verbose_name=ugettext_lazy("Title"))
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans 'Custom slide' %}
|
<h1>{% trans 'Custom slide' %}
|
||||||
<small class="pull-right">
|
<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>
|
</small>
|
||||||
</h1>
|
</h1>
|
||||||
<form action="" method="post">{% csrf_token %}
|
<form action="" method="post">{% csrf_token %}
|
||||||
|
@ -23,14 +23,6 @@ urlpatterns = patterns(
|
|||||||
views.ActivateView.as_view(),
|
views.ActivateView.as_view(),
|
||||||
name='projector_activate_slide'),
|
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/$',
|
url(r'^overlay_message/$',
|
||||||
views.OverlayMessageView.as_view(),
|
views.OverlayMessageView.as_view(),
|
||||||
name='projector_overlay_message'),
|
name='projector_overlay_message'),
|
||||||
|
@ -1,45 +1,18 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- 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.config.api import config
|
||||||
from openslides.mediafile.models import Mediafile
|
from openslides.mediafile.models import Mediafile
|
||||||
from openslides.utils.tornado_webserver import ProjectorSocketHandler
|
from openslides.utils.tornado_webserver import ProjectorSocketHandler
|
||||||
from openslides.utils.template import Tab
|
from openslides.utils.views import (CreateView, DeleteView,
|
||||||
from openslides.utils.views import (AjaxMixin, CreateView, DeleteView,
|
|
||||||
RedirectView, TemplateView, UpdateView)
|
RedirectView, TemplateView, UpdateView)
|
||||||
from openslides.utils.widgets import Widget
|
|
||||||
|
|
||||||
from .api import (call_on_projector, get_active_slide,
|
from .api import (call_on_projector, get_active_slide,
|
||||||
get_overlays, get_projector_content, get_projector_overlays,
|
get_overlays, get_projector_content, get_projector_overlays,
|
||||||
get_projector_overlays_js, reset_countdown, set_active_slide,
|
get_projector_overlays_js, reset_countdown, set_active_slide,
|
||||||
start_countdown, stop_countdown, update_projector_overlay)
|
start_countdown, stop_countdown, update_projector_overlay)
|
||||||
from .forms import SelectWidgetsForm
|
|
||||||
from .models import ProjectorSlide
|
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):
|
class ProjectorView(TemplateView):
|
||||||
"""
|
"""
|
||||||
The Projector-Page.
|
The Projector-Page.
|
||||||
@ -73,7 +46,7 @@ class ActivateView(RedirectView):
|
|||||||
Activate a Slide.
|
Activate a Slide.
|
||||||
"""
|
"""
|
||||||
permission_required = 'projector.can_manage_projector'
|
permission_required = 'projector.can_manage_projector'
|
||||||
url_name = 'dashboard'
|
url_name = 'core_dashboard'
|
||||||
allow_ajax = True
|
allow_ajax = True
|
||||||
|
|
||||||
def pre_redirect(self, request, *args, **kwargs):
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
@ -95,51 +68,12 @@ class ActivateView(RedirectView):
|
|||||||
'scale': config['projector_scale']})
|
'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):
|
class ProjectorControllView(RedirectView):
|
||||||
"""
|
"""
|
||||||
Scale or scroll the projector.
|
Scale or scroll the projector.
|
||||||
"""
|
"""
|
||||||
permission_required = 'projector.can_manage_projector'
|
permission_required = 'projector.can_manage_projector'
|
||||||
url_name = 'dashboard'
|
url_name = 'core_dashboard'
|
||||||
allow_ajax = True
|
allow_ajax = True
|
||||||
|
|
||||||
def pre_redirect(self, request, *args, **kwargs):
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
@ -173,7 +107,7 @@ class CountdownControllView(RedirectView):
|
|||||||
Start, stop or reset the countdown.
|
Start, stop or reset the countdown.
|
||||||
"""
|
"""
|
||||||
permission_required = 'projector.can_manage_projector'
|
permission_required = 'projector.can_manage_projector'
|
||||||
url_name = 'dashboard'
|
url_name = 'core_dashboard'
|
||||||
allow_ajax = True
|
allow_ajax = True
|
||||||
|
|
||||||
def pre_redirect(self, request, *args, **kwargs):
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
@ -205,7 +139,7 @@ class OverlayMessageView(RedirectView):
|
|||||||
"""
|
"""
|
||||||
Sets or clears the overlay message
|
Sets or clears the overlay message
|
||||||
"""
|
"""
|
||||||
url_name = 'dashboard'
|
url_name = 'core_dashboard'
|
||||||
allow_ajax = True
|
allow_ajax = True
|
||||||
permission_required = 'projector.can_manage_projector'
|
permission_required = 'projector.can_manage_projector'
|
||||||
|
|
||||||
@ -226,7 +160,7 @@ class ActivateOverlay(RedirectView):
|
|||||||
"""
|
"""
|
||||||
Activate or deactivate an overlay.
|
Activate or deactivate an overlay.
|
||||||
"""
|
"""
|
||||||
url_name = 'dashboard'
|
url_name = 'core_dashboard'
|
||||||
allow_ajax = True
|
allow_ajax = True
|
||||||
permission_required = 'projector.can_manage_projector'
|
permission_required = 'projector.can_manage_projector'
|
||||||
|
|
||||||
@ -256,7 +190,7 @@ class CustomSlideCreateView(CreateView):
|
|||||||
template_name = 'projector/new.html'
|
template_name = 'projector/new.html'
|
||||||
model = ProjectorSlide
|
model = ProjectorSlide
|
||||||
context_object_name = 'customslide'
|
context_object_name = 'customslide'
|
||||||
success_url_name = 'dashboard'
|
success_url_name = 'core_dashboard'
|
||||||
url_name_args = []
|
url_name_args = []
|
||||||
|
|
||||||
|
|
||||||
@ -268,7 +202,7 @@ class CustomSlideUpdateView(UpdateView):
|
|||||||
template_name = 'projector/new.html'
|
template_name = 'projector/new.html'
|
||||||
model = ProjectorSlide
|
model = ProjectorSlide
|
||||||
context_object_name = 'customslide'
|
context_object_name = 'customslide'
|
||||||
success_url_name = 'dashboard'
|
success_url_name = 'core_dashboard'
|
||||||
url_name_args = []
|
url_name_args = []
|
||||||
|
|
||||||
|
|
||||||
@ -278,18 +212,4 @@ class CustomSlideDeleteView(DeleteView):
|
|||||||
"""
|
"""
|
||||||
permission_required = 'projector.can_manage_projector'
|
permission_required = 'projector.can_manage_projector'
|
||||||
model = ProjectorSlide
|
model = ProjectorSlide
|
||||||
success_url_name = 'dashboard'
|
success_url_name = 'core_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,
|
|
||||||
)
|
|
||||||
|
@ -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 %}
|
{% load staticfiles %}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{LANGUAGE_CODE}}">
|
<html lang="{{ LANGUAGE_CODE }}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
@ -29,7 +29,7 @@
|
|||||||
{% block loginbutton %}
|
{% block loginbutton %}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<!-- Search field -->
|
<!-- 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">
|
<div class="input-append">
|
||||||
<input type="text" id="id_q" name="q" class="input-medium" placeholder="{% trans 'Search' %}">
|
<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>
|
<button type="submit" class="btn"><i class="icon-search"></i></button>
|
||||||
@ -60,61 +60,56 @@
|
|||||||
</div>
|
</div>
|
||||||
</div> <!--/.container-fluid-->
|
</div> <!--/.container-fluid-->
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<!-- Container -->
|
||||||
|
<div class="container-fluid" id="container">
|
||||||
|
<div class="row-fluid">
|
||||||
|
|
||||||
{% block body %}
|
<!-- Sidebar navigation (main menu) -->
|
||||||
<!-- Container -->
|
<div class="span2 leftmenu lefticon">
|
||||||
<div class="container-fluid" id="container">
|
<ul>
|
||||||
<div class="row-fluid">
|
{% 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-->
|
<!-- Content -->
|
||||||
<div class="span2 leftmenu lefticon">
|
<div id="content" class="span10">
|
||||||
<ul>
|
<div class="row-fluid">
|
||||||
{% for tab in tabs %}
|
<div class="span12">
|
||||||
{% if tab.permission %}
|
<div id="notifications">
|
||||||
<li{% if tab.selected %} class="active"{% endif %}>
|
<div id="dummy-notification" class="notification" style="display:none">
|
||||||
<a href="{{ tab.url }}" class="tooltip-right">
|
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||||
<span class="ico"><i class="icon-{{ tab.app }}"></i></span>
|
</div>
|
||||||
<span class="text">{{ tab.title }}</span>
|
{% for message in messages %}
|
||||||
</a>
|
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">
|
||||||
</li>
|
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||||
{% endif %}
|
{{ message|safe }}
|
||||||
{% endfor %}
|
</div>
|
||||||
</ul>
|
{% endfor %}
|
||||||
</div>
|
</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><!--/.row-fluid-->
|
||||||
<div id="content" class="span10">
|
</div><!--/#container-->
|
||||||
<div class="row-fluid">
|
{% endblock %}<!--/body-->
|
||||||
<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-->
|
|
||||||
</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/jquery.min.js' %}" type="text/javascript"></script>
|
||||||
<script src="{% static 'javascript/bootstrap.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>
|
<script src="{% static 'javascript/utils.js' %}" type="text/javascript"></script>
|
||||||
|
@ -3,9 +3,12 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, patterns, url
|
from django.conf.urls import include, patterns, url
|
||||||
|
|
||||||
|
from openslides.core.views import ErrorView
|
||||||
from openslides.utils.plugins import get_urlpatterns
|
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 = []
|
urlpatterns = []
|
||||||
|
|
||||||
|
@ -14,9 +14,15 @@ class SignalConnectMetaClass(type):
|
|||||||
The classmethod get_all_objects is added as get_all classmethod to every
|
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
|
class using this metaclass. Calling this on a base class or on child
|
||||||
classes will retrieve all connected children, on instance for each child
|
classes will retrieve all connected children, on instance for each child
|
||||||
class. These instances will have a check_permission method which
|
class.
|
||||||
returns True by default. You can override this method to return False
|
|
||||||
on runtime if you want to filter some children.
|
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:
|
Example:
|
||||||
|
|
||||||
@ -37,7 +43,8 @@ class SignalConnectMetaClass(type):
|
|||||||
"""
|
"""
|
||||||
def __new__(metaclass, class_name, class_parents, class_attributes):
|
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
|
class_attributes['get_all'] = get_all_objects
|
||||||
new_class = super(SignalConnectMetaClass, metaclass).__new__(
|
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)
|
raise NotImplementedError('Your class %s must have a signal argument, which must be a Django Signal instance.' % class_name)
|
||||||
else:
|
else:
|
||||||
signal.connect(new_class, dispatch_uid=dispatch_uid)
|
signal.connect(new_class, dispatch_uid=dispatch_uid)
|
||||||
if not hasattr(new_class, 'check_permission'):
|
attributes = {'check_permission': check_permission,
|
||||||
setattr(new_class, '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
|
return new_class
|
||||||
|
|
||||||
|
|
||||||
@ -62,9 +73,8 @@ class SignalConnectMetaClass(type):
|
|||||||
def get_all_objects(cls, request):
|
def get_all_objects(cls, request):
|
||||||
"""
|
"""
|
||||||
Collects all objects of the class created by the SignalConnectMetaClass
|
Collects all objects of the class created by the SignalConnectMetaClass
|
||||||
from all apps via signal. If they have a get_default_weight method,
|
from all apps via signal. They are sorted using the get_default_weight
|
||||||
they are sorted. Does not return objects where check_permission returns
|
method. Does not return objects where check_permission returns False.
|
||||||
False.
|
|
||||||
|
|
||||||
Expects a request object.
|
Expects a request object.
|
||||||
|
|
||||||
@ -72,8 +82,7 @@ def get_all_objects(cls, request):
|
|||||||
the SignalConnectMetaClass.
|
the SignalConnectMetaClass.
|
||||||
"""
|
"""
|
||||||
all_objects = [obj for __, obj in cls.signal.send(sender=cls, request=request) if obj.check_permission()]
|
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
|
return all_objects
|
||||||
|
|
||||||
|
|
||||||
@ -85,3 +94,14 @@ def check_permission(self):
|
|||||||
SignalConnectMetaClass.
|
SignalConnectMetaClass.
|
||||||
"""
|
"""
|
||||||
return True
|
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
|
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
|
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.
|
attribute (SignalConnectMetaClass) does the rest of the magic.
|
||||||
"""
|
"""
|
||||||
__metaclass__ = SignalConnectMetaClass
|
__metaclass__ = SignalConnectMetaClass
|
||||||
@ -21,7 +21,7 @@ class PersonalInfo(object):
|
|||||||
|
|
||||||
def __init__(self, sender, request, **kwargs):
|
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
|
Only the required request argument is used. Because of Django's signal
|
||||||
API, we have to take also a sender argument and wildcard keyword
|
API, we have to take also a sender argument and wildcard keyword
|
||||||
@ -38,12 +38,6 @@ class PersonalInfo(object):
|
|||||||
if not cls.__name__ == 'PersonalInfo':
|
if not cls.__name__ == 'PersonalInfo':
|
||||||
return cls.__name__
|
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):
|
def get_queryset(self):
|
||||||
"""
|
"""
|
||||||
Returns a queryset of objects for the personal info widget.
|
Returns a queryset of objects for the personal info widget.
|
||||||
|
@ -2,4 +2,16 @@
|
|||||||
|
|
||||||
from django.dispatch import Signal
|
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.context_processors import csrf
|
||||||
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.dispatch import receiver
|
from django.http import (HttpResponse, HttpResponseRedirect)
|
||||||
from django.http import (HttpResponse, HttpResponseRedirect,
|
|
||||||
HttpResponseServerError)
|
|
||||||
from django.template import RequestContext
|
|
||||||
from django.template.loader import render_to_string
|
|
||||||
from django.utils.decorators import method_decorator
|
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 as _
|
||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import ugettext_lazy
|
||||||
from django.views import generic as django_views
|
from django.views import generic as django_views
|
||||||
@ -585,42 +580,3 @@ class PDFView(PermissionMixin, View):
|
|||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
return self.render_to_response(self.get_filename())
|
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.
|
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
|
widget class subclassing from this base class. The name attribute has to
|
||||||
be set. The __metaclass__ attribute (SignalConnectMetaClass) does the
|
be set. The __metaclass__ attribute (SignalConnectMetaClass) does the
|
||||||
rest of the magic.
|
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,
|
verbose_name, permission_required, default_column, default_weight,
|
||||||
default_active, template_name, context, icon, more_link_pattern_name,
|
default_active, template_name, context, icon_css_class,
|
||||||
stylesheets and javascript_files.
|
more_link_pattern_name, stylesheets and javascript_files.
|
||||||
"""
|
"""
|
||||||
__metaclass__ = SignalConnectMetaClass
|
__metaclass__ = SignalConnectMetaClass
|
||||||
signal = Signal(providing_args=['request'])
|
signal = Signal(providing_args=['request'])
|
||||||
@ -39,7 +39,7 @@ class Widget(object):
|
|||||||
|
|
||||||
def __init__(self, sender, request, **kwargs):
|
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
|
Only the required request argument is used. Because of Django's signal
|
||||||
API, we have to take also a sender argument and wildcard keyword
|
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)
|
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):
|
def is_active(self):
|
||||||
"""
|
"""
|
||||||
Returns True if the widget is active to be displayed.
|
Returns True if the widget is active to be displayed.
|
||||||
|
@ -51,7 +51,7 @@ class PersonalInfoWidget(TestCase):
|
|||||||
self.client.login(username='HansMeiser', password='default')
|
self.client.login(username='HansMeiser', password='default')
|
||||||
|
|
||||||
def test_widget_appearance(self):
|
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)
|
self.assertContains(response, 'My personal info', status_code=200)
|
||||||
|
|
||||||
def test_item_list(self):
|
def test_item_list(self):
|
||||||
@ -59,11 +59,11 @@ class PersonalInfoWidget(TestCase):
|
|||||||
if agenda:
|
if agenda:
|
||||||
item_1 = agenda.models.Item.objects.create(title='My Item Title iw5ohNgee4eiYahb5Eiv')
|
item_1 = agenda.models.Item.objects.create(title='My Item Title iw5ohNgee4eiYahb5Eiv')
|
||||||
speaker = agenda.models.Speaker.objects.add(item=item_1, person=self.user)
|
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, 'I am on the list of speakers of the following items:', status_code=200)
|
||||||
self.assertContains(response, 'My Item Title iw5ohNgee4eiYahb5Eiv', status_code=200)
|
self.assertContains(response, 'My Item Title iw5ohNgee4eiYahb5Eiv', status_code=200)
|
||||||
speaker.begin_speach()
|
speaker.begin_speach()
|
||||||
response = self.client.get('/projector/dashboard/')
|
response = self.client.get('/dashboard/')
|
||||||
self.assertNotContains(response, 'My Item Title iw5ohNgee4eiYahb5Eiv', status_code=200)
|
self.assertNotContains(response, 'My Item Title iw5ohNgee4eiYahb5Eiv', status_code=200)
|
||||||
|
|
||||||
def test_submitter_list(self):
|
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_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_1, person=self.user)
|
||||||
motion.models.MotionSubmitter.objects.create(motion=motion_2, 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, 'I submitted the following motions:', status_code=200)
|
||||||
self.assertContains(response, 'My Motion Title pa8aeNohYai0ahge', status_code=200)
|
self.assertContains(response, 'My Motion Title pa8aeNohYai0ahge', status_code=200)
|
||||||
self.assertContains(response, 'My Motion Title quielohL7vah1weochai', 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_1, person=self.user)
|
||||||
motion.models.MotionSupporter.objects.create(motion=motion_2, person=self.user)
|
motion.models.MotionSupporter.objects.create(motion=motion_2, person=self.user)
|
||||||
config['motion_min_supporters'] = 1
|
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, 'I support the following motions:', status_code=200)
|
||||||
self.assertContains(response, 'My Motion Title jahN9phaiThae5ooKubu', status_code=200)
|
self.assertContains(response, 'My Motion Title jahN9phaiThae5ooKubu', status_code=200)
|
||||||
self.assertContains(response, 'My Motion Title vech9ash8aeh9eej2Ga2', status_code=200)
|
self.assertContains(response, 'My Motion Title vech9ash8aeh9eej2Ga2', status_code=200)
|
||||||
@ -96,6 +96,6 @@ class PersonalInfoWidget(TestCase):
|
|||||||
if assignment:
|
if assignment:
|
||||||
assignment_1 = assignment.models.Assignment.objects.create(name='Hausmeister ooKoh7roApoo3phe', posts=1)
|
assignment_1 = assignment.models.Assignment.objects.create(name='Hausmeister ooKoh7roApoo3phe', posts=1)
|
||||||
assignment_1.run(candidate=self.user, person=self.user)
|
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, 'I am candidate for the following elections:', status_code=200)
|
||||||
self.assertContains(response, 'Hausmeister ooKoh7roApoo3phe', status_code=200)
|
self.assertContains(response, 'Hausmeister ooKoh7roApoo3phe', status_code=200)
|
||||||
|
@ -240,7 +240,7 @@ class SpeakerListOpenView(SpeakerViewTestCase):
|
|||||||
class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
||||||
def test_global_redirect_url(self):
|
def test_global_redirect_url(self):
|
||||||
response = self.speaker1_client.get('/agenda/list_of_speakers/')
|
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.')
|
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)
|
set_active_slide('agenda', pk=1)
|
||||||
@ -249,7 +249,7 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
|||||||
|
|
||||||
def test_global_add_url(self):
|
def test_global_add_url(self):
|
||||||
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
|
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.')
|
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)
|
set_active_slide('agenda', pk=1)
|
||||||
@ -265,38 +265,38 @@ class GlobalListOfSpeakersLinks(SpeakerViewTestCase):
|
|||||||
|
|
||||||
def test_global_next_speaker_url(self):
|
def test_global_next_speaker_url(self):
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/next/')
|
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.')
|
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)
|
set_active_slide('agenda', pk=1)
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/next/')
|
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.')
|
self.assertMessage(response, 'The list of speakers is empty.')
|
||||||
|
|
||||||
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
|
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
|
||||||
self.assertTrue(Speaker.objects.get(item__pk='1').begin_time is None)
|
self.assertTrue(Speaker.objects.get(item__pk='1').begin_time is None)
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/next/')
|
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)
|
self.assertTrue(Speaker.objects.get(item__pk='1').begin_time is not None)
|
||||||
|
|
||||||
def test_global_end_speach_url(self):
|
def test_global_end_speach_url(self):
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
|
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.')
|
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)
|
set_active_slide('agenda', pk=1)
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
|
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.')
|
self.assertMessage(response, 'There is no one speaking at the moment.')
|
||||||
|
|
||||||
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
|
response = self.speaker1_client.get('/agenda/list_of_speakers/add/')
|
||||||
self.assertTrue(Speaker.objects.get(item__pk='1').begin_time is None)
|
self.assertTrue(Speaker.objects.get(item__pk='1').begin_time is None)
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
|
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.')
|
self.assertMessage(response, 'There is no one speaking at the moment.')
|
||||||
|
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/next/')
|
response = self.admin_client.get('/agenda/list_of_speakers/next/')
|
||||||
self.assertTrue(Speaker.objects.get(item__pk='1').end_time is None)
|
self.assertTrue(Speaker.objects.get(item__pk='1').end_time is None)
|
||||||
response = self.admin_client.get('/agenda/list_of_speakers/end_speach/')
|
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)
|
self.assertTrue(Speaker.objects.get(item__pk='1').end_time is not None)
|
||||||
|
@ -1,15 +1,70 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from django.test.client import Client
|
from django.test.client import Client, RequestFactory
|
||||||
from mock import patch
|
from mock import MagicMock, patch
|
||||||
|
|
||||||
from openslides import get_version
|
from openslides import get_version
|
||||||
from openslides.agenda.models import Item
|
from openslides.agenda.models import Item
|
||||||
from openslides.config.api import config
|
from openslides.config.api import config
|
||||||
|
from openslides.core import views
|
||||||
from openslides.participant.models import User
|
from openslides.participant.models import User
|
||||||
from openslides.utils.test import TestCase
|
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):
|
class VersionViewTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
User.objects.create_user('CoreMaximilian', 'xxx@xx.xx', 'default')
|
User.objects.create_user('CoreMaximilian', 'xxx@xx.xx', 'default')
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.test.client import Client, RequestFactory
|
from django.test.client import Client, RequestFactory
|
||||||
from mock import call, MagicMock, patch
|
from mock import call, MagicMock, patch
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ class ProjectorViewTest(TestCase):
|
|||||||
mock_get_projector_overlays_js):
|
mock_get_projector_overlays_js):
|
||||||
view = views.ProjectorView()
|
view = views.ProjectorView()
|
||||||
view.request = self.rf.get('/')
|
view.request = self.rf.get('/')
|
||||||
|
view.request.user = AnonymousUser()
|
||||||
|
|
||||||
# Test preview
|
# Test preview
|
||||||
view.kwargs = {'callback': 'slide_callback'}
|
view.kwargs = {'callback': 'slide_callback'}
|
||||||
@ -59,60 +61,6 @@ class ActivateViewTest(TestCase):
|
|||||||
self.assertTrue(mock_call_on_projector.called)
|
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):
|
class ProjectorControllViewTest(TestCase):
|
||||||
@patch('openslides.projector.views.call_on_projector')
|
@patch('openslides.projector.views.call_on_projector')
|
||||||
def test_bigger(self, mock_call_on_projector):
|
def test_bigger(self, mock_call_on_projector):
|
||||||
@ -176,7 +124,7 @@ class CustomSlidesTest(TestCase):
|
|||||||
response = self.admin_client.get(url)
|
response = self.admin_client.get(url)
|
||||||
self.assertTemplateUsed(response, 'projector/new.html')
|
self.assertTemplateUsed(response, 'projector/new.html')
|
||||||
response = self.admin_client.post(url, {'title': 'test_title_roo2xi2EibooHie1kohd', 'weight': '0'})
|
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())
|
self.assertTrue(ProjectorSlide.objects.filter(title='test_title_roo2xi2EibooHie1kohd').exists())
|
||||||
|
|
||||||
def test_update(self):
|
def test_update(self):
|
||||||
@ -188,7 +136,7 @@ class CustomSlidesTest(TestCase):
|
|||||||
self.assertTemplateUsed(response, 'projector/new.html')
|
self.assertTemplateUsed(response, 'projector/new.html')
|
||||||
self.assertContains(response, 'test_title_jeeDeB3aedei8ahceeso')
|
self.assertContains(response, 'test_title_jeeDeB3aedei8ahceeso')
|
||||||
response = self.admin_client.post(url, {'title': 'test_title_ai8Ooboh5bahr6Ee7goo', 'weight': '0'})
|
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')
|
self.assertEqual(ProjectorSlide.objects.get(pk=1).title, 'test_title_ai8Ooboh5bahr6Ee7goo')
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
@ -199,7 +147,7 @@ class CustomSlidesTest(TestCase):
|
|||||||
response = self.admin_client.get(url)
|
response = self.admin_client.get(url)
|
||||||
self.assertRedirects(response, '/projector/1/edit/')
|
self.assertRedirects(response, '/projector/1/edit/')
|
||||||
response = self.admin_client.post(url, {'yes': 'true'})
|
response = self.admin_client.post(url, {'yes': 'true'})
|
||||||
self.assertRedirects(response, '/projector/dashboard/')
|
self.assertRedirects(response, '/dashboard/')
|
||||||
self.assertFalse(ProjectorSlide.objects.exists())
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.urlresolvers import clear_url_caches
|
from django.core.urlresolvers import clear_url_caches
|
||||||
from django.test import RequestFactory
|
from django.test import RequestFactory
|
||||||
@ -76,23 +77,23 @@ class AjaxMixinTest(ViewTestCase):
|
|||||||
|
|
||||||
class ExtraContextMixinTest(ViewTestCase):
|
class ExtraContextMixinTest(ViewTestCase):
|
||||||
"""
|
"""
|
||||||
Tests the ExtraContextMixin by testen the TemplateView
|
Tests the ExtraContextMixin by testing the TemplateView.
|
||||||
"""
|
"""
|
||||||
def test_get_context_data(self):
|
def test_get_context_data(self):
|
||||||
view = views.TemplateView()
|
view = views.TemplateView()
|
||||||
get_context_data = view.get_context_data
|
get_context_data = view.get_context_data
|
||||||
view.request = self.rf.get('/', {})
|
view.request = self.rf.get('/', {})
|
||||||
|
view.request.user = AnonymousUser()
|
||||||
|
|
||||||
context = get_context_data()
|
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')
|
context = get_context_data(some_context='context')
|
||||||
self.assertIn('tabs', context)
|
|
||||||
self.assertIn('some_context', context)
|
self.assertIn('some_context', context)
|
||||||
|
|
||||||
template_manipulation.connect(set_context, dispatch_uid='set_context_test')
|
template_manipulation.connect(set_context, dispatch_uid='set_context_test')
|
||||||
context = get_context_data()
|
context = get_context_data()
|
||||||
self.assertIn('tabs', context)
|
|
||||||
self.assertIn('new_context', context)
|
self.assertIn('new_context', context)
|
||||||
template_manipulation.disconnect(set_context, dispatch_uid='set_context_test')
|
template_manipulation.disconnect(set_context, dispatch_uid='set_context_test')
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user