diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py
index f306fc727..c0a59c7c1 100644
--- a/openslides/agenda/views.py
+++ b/openslides/agenda/views.py
@@ -184,16 +184,12 @@ class SetClosed(RedirectView, SingleObjectMixin):
model = Item
def get_ajax_context(self, **kwargs):
- context = super(SetClosed, self).get_ajax_context(**kwargs)
- closed = kwargs['closed']
+ closed = self.kwargs['closed']
if closed:
- link = reverse('item_open', args=[self.object.id])
+ link = reverse('item_open', args=[self.object.pk])
else:
- link = reverse('item_close', args=[self.object.id])
- context.update({
- 'closed': kwargs['closed'],
- 'link': link})
- return context
+ link = reverse('item_close', args=[self.object.pk])
+ return super(SetClosed, self).get_ajax_context(closed=closed, link=link)
def pre_redirect(self, request, *args, **kwargs):
self.object = self.get_object()
@@ -214,6 +210,7 @@ class ItemUpdate(UpdateView):
model = Item
context_object_name = 'item'
success_url_name = 'item_overview'
+ url_name_args = []
def get_form_class(self):
if self.object.content_object:
@@ -233,6 +230,7 @@ class ItemCreate(CreateView):
context_object_name = 'item'
form_class = ItemForm
success_url_name = 'item_overview'
+ url_name_args = []
class ItemDelete(DeleteView):
diff --git a/openslides/assignment/views.py b/openslides/assignment/views.py
index 822c996ac..c84e12a81 100644
--- a/openslides/assignment/views.py
+++ b/openslides/assignment/views.py
@@ -25,11 +25,10 @@ from django.utils.translation import ungettext, ugettext as _
from openslides.utils.pdf import stylesheet
from openslides.utils.template import Tab
-from openslides.utils.utils import gen_confirm_form
from openslides.utils.views import (
CreateView, DeleteView, RedirectView, UpdateView, ListView, PDFView,
- DetailView, View, PermissionMixin, SingleObjectMixin, QuestionMixin)
+ DetailView, View, PermissionMixin, SingleObjectMixin, QuestionView)
from openslides.utils.person import get_person
from openslides.utils.utils import html_strong
from openslides.config.api import config
@@ -150,8 +149,6 @@ class AssignmentRunView(SingleObjectMixin, PermissionMixin, View):
class AssignmentRunDeleteView(SingleObjectMixin, RedirectView):
model = Assignment
url_name = 'assignment_detail'
- success_message = _("You have withdrawn your candidature successfully. "
- "You can not be nominated by other participants anymore.")
def pre_redirect(self, *args, **kwargs):
self.object = self.get_object()
@@ -162,54 +159,49 @@ class AssignmentRunDeleteView(SingleObjectMixin, RedirectView):
except Exception, e:
messages.error(self.request, e)
else:
- messages.success(self.request, self.success_message)
+ messages.success(self.request, _(
+ 'You have withdrawn your candidature successfully. '
+ 'You can not be nominated by other participants anymore.'))
else:
messages.error(self.request, _('The candidate list is already closed.'))
-class AssignmentRunOtherDeleteView(SingleObjectMixin, QuestionMixin,
- RedirectView):
+class AssignmentRunOtherDeleteView(SingleObjectMixin, QuestionView):
model = Assignment
permission_required = 'assignment.can_manage_assignment'
- question_url_name = 'assignment_detail'
- success_url_name = 'assignment_detail'
- success_message = ''
- def pre_redirect(self, *args, **kwargs):
- self._get_person_information(*args, **kwargs)
+ def get_question_message(self):
+ self._get_person_information()
if not self.is_blocked:
- message = _("Do you really want to withdraw %s from the election?") % html_strong(self.person)
+ question = _("Do you really want to withdraw %s from the election?") % html_strong(self.person)
else:
- message = _("Do you really want to unblock %s for the election?") % html_strong(self.person)
- gen_confirm_form(self.request, message, reverse('assignment_delother',
- args=[self.object.pk, kwargs['user_id']]))
+ question = _("Do you really want to unblock %s for the election?") % html_strong(self.person)
+ return question
- def pre_post_redirect(self, *args, **kwargs):
- self._get_person_information(*args, **kwargs)
- if self.get_answer() == 'yes':
- self.case_yes()
-
- def get_answer(self):
- if 'submit' in self.request.POST:
- return 'yes'
-
- def case_yes(self):
+ def on_clicked_yes(self):
+ self._get_person_information()
try:
self.object.delrun(self.person, blocked=False)
except Exception, e:
- messages.error(self.request, e)
+ self.error = e
else:
- messages.success(self.request, self.get_success_message())
+ self.error = False
- def get_success_message(self):
- success_message = _("Candidate %s was withdrawn successfully.") % html_strong(self.person)
+ def create_final_message(self):
+ if self.error:
+ messages.error(self.request, self.error)
+ else:
+ messages.success(self.request, self.get_final_message())
+
+ def get_final_message(self):
+ message = _("Candidate %s was withdrawn successfully.") % html_strong(self.person)
if self.is_blocked:
- success_message = _("%s was unblocked successfully.") % html_strong(self.person)
- return success_message
+ message = _("%s was unblocked successfully.") % html_strong(self.person)
+ return message
- def _get_person_information(self, *args, **kwargs):
+ def _get_person_information(self):
self.object = self.get_object()
- self.person = get_person(kwargs.get('user_id'))
+ self.person = get_person(self.kwargs.get('user_id'))
self.is_blocked = self.object.is_blocked(self.person)
@@ -310,7 +302,7 @@ class AssignmentPollDeleteView(DeleteView):
def get_redirect_url(self, **kwargs):
return reverse('assignment_detail', args=[self.assignment.id])
- def get_success_message(self):
+ def get_final_message(self):
return _('Ballot was successfully deleted.')
diff --git a/openslides/global_settings.py b/openslides/global_settings.py
index cbb6728fa..8de28da6b 100644
--- a/openslides/global_settings.py
+++ b/openslides/global_settings.py
@@ -69,11 +69,9 @@ STATICFILES_DIRS = (
fs2unicode(os.path.join(SITE_ROOT, 'static')),
)
-#XXX: Note this setting (as well as our workaround finder)
-# can be removed again once django-bug-#18404 has been resolved
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
- 'openslides.utils.staticfiles.AppDirectoriesFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
diff --git a/openslides/mediafile/views.py b/openslides/mediafile/views.py
index 6f80b4da6..009d7cb62 100644
--- a/openslides/mediafile/views.py
+++ b/openslides/mediafile/views.py
@@ -40,6 +40,7 @@ class MediafileCreateView(CreateView):
model = Mediafile
permission_required = 'mediafile.can_upload'
success_url_name = 'mediafile_list'
+ url_name_args = []
def get_form(self, form_class):
form_kwargs = self.get_form_kwargs()
@@ -71,6 +72,7 @@ class MediafileUpdateView(UpdateView):
permission_required = 'mediafile.can_manage'
form_class = MediafileUpdateForm
success_url_name = 'mediafile_list'
+ url_name_args = []
def get_form_kwargs(self, *args, **kwargs):
form_kwargs = super(MediafileUpdateView, self).get_form_kwargs(*args, **kwargs)
@@ -86,10 +88,10 @@ class MediafileDeleteView(DeleteView):
permission_required = 'mediafile.can_manage'
success_url_name = 'mediafile_list'
- def case_yes(self, *args, **kwargs):
+ def on_clicked_yes(self, *args, **kwargs):
"""Deletes the file in the filesystem, if user clicks "Yes"."""
self.object.mediafile.delete()
- return super(MediafileDeleteView, self).case_yes(*args, **kwargs)
+ return super(MediafileDeleteView, self).on_clicked_yes(*args, **kwargs)
def register_tab(request):
diff --git a/openslides/motion/views.py b/openslides/motion/views.py
index 69638b707..5ba0a2381 100644
--- a/openslides/motion/views.py
+++ b/openslides/motion/views.py
@@ -11,7 +11,6 @@
:copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
-from reportlab.platypus import SimpleDocTemplate
from django.core.urlresolvers import reverse
from django.contrib import messages
@@ -20,26 +19,26 @@ from django.db.models import Model
from django.http import Http404, HttpResponseRedirect
from django.utils.text import slugify
from django.utils.translation import ugettext as _, ugettext_lazy, ugettext_noop
+from reportlab.platypus import SimpleDocTemplate
-from openslides.agenda.views import (
- CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView)
-from openslides.config.api import config
-from openslides.poll.views import PollFormView
-from openslides.projector.api import get_active_slide, update_projector
-from openslides.projector.projector import Widget
from openslides.utils.pdf import stylesheet
-from openslides.utils.template import Tab
-from openslides.utils.utils import html_strong, htmldiff
from openslides.utils.views import (
TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView,
- DetailView, ListView, FormView, QuestionMixin, SingleObjectMixin)
+ DetailView, ListView, FormView, QuestionView, SingleObjectMixin)
+from openslides.utils.template import Tab
+from openslides.utils.utils import html_strong, htmldiff
+from openslides.poll.views import PollFormView
+from openslides.projector.api import get_active_slide
+from openslides.projector.projector import Widget
+from openslides.config.api import config
+from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView
from .csv_import import import_motions
+from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll,
+ MotionVersion, State, WorkflowError, Category)
from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin,
MotionDisableVersioningMixin, MotionCategoryMixin,
MotionIdentifierMixin, MotionWorkflowMixin, MotionImportForm)
-from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll,
- MotionVersion, State, WorkflowError, Category)
from .pdf import motions_to_pdf, motion_to_pdf, motion_poll_to_pdf
@@ -297,7 +296,7 @@ class MotionDeleteView(DeleteView):
"""
return self.get_object().get_allowed_actions(request.user)['delete']
- def get_success_message(self):
+ def get_final_message(self):
return _('%s was successfully deleted.') % _('Motion')
motion_delete = MotionDeleteView.as_view()
@@ -326,21 +325,20 @@ class VersionDeleteView(DeleteView):
raise Http404('You can not delete the active version of a motion.')
return version
- def get_success_url_name_args(self):
+ def get_url_name_args(self):
return (self.object.motion_id, )
version_delete = VersionDeleteView.as_view()
-class VersionPermitView(SingleObjectMixin, QuestionMixin, RedirectView):
+class VersionPermitView(SingleObjectMixin, QuestionView):
"""
View to permit a version of a motion.
"""
model = Motion
- question_url_name = 'motion_version_detail'
- success_url_name = 'motion_version_detail'
- success_message = ugettext_lazy('Version successfully permitted.')
+ final_message = ugettext_lazy('Version successfully permitted.')
permission_required = 'motion.can_manage_motion'
+ question_url_name = 'motion_version_detail'
def get(self, *args, **kwargs):
"""
@@ -360,13 +358,13 @@ class VersionPermitView(SingleObjectMixin, QuestionMixin, RedirectView):
"""
return [self.object.pk, self.version.version_number]
- def get_question(self):
+ def get_question_message(self):
"""
Return a string, shown to the user as question to permit the version.
"""
return _('Are you sure you want permit version %s?') % self.version.version_number
- def case_yes(self):
+ def on_clicked_yes(self):
"""
Activate the version, if the user chooses 'yes'.
"""
@@ -418,7 +416,7 @@ class VersionDiffView(DetailView):
version_diff = VersionDiffView.as_view()
-class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
+class SupportView(SingleObjectMixin, QuestionView):
"""
View to support or unsupport a motion.
@@ -452,7 +450,7 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
else:
return True
- def get_question(self):
+ def get_question_message(self):
"""
Return the question string.
"""
@@ -461,7 +459,7 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
else:
return _('Do you really want to unsupport this motion?')
- def case_yes(self):
+ def on_clicked_yes(self):
"""
Append or remove the request.user from the motion.
@@ -477,7 +475,7 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
self.object.unsupport(person=user)
self.object.write_log([ugettext_noop('Motion unsupported')], user)
- def get_success_message(self):
+ def get_final_message(self):
"""
Return the success message.
"""
@@ -486,12 +484,6 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
else:
return _("You have unsupported this motion successfully.")
- def get_redirect_url(self, **kwargs):
- """
- Return the url, the view should redirect to.
- """
- return self.object.get_absolute_url()
-
motion_support = SupportView.as_view(support=True)
motion_unsupport = SupportView.as_view(support=False)
@@ -558,6 +550,7 @@ class PollUpdateView(PollMixin, PollFormView):
"""
View to update a MotionPoll.
"""
+
poll_class = MotionPoll
"""
Poll Class to use for this view.
@@ -595,11 +588,11 @@ class PollDeleteView(PollMixin, DeleteView):
model = MotionPoll
- def case_yes(self):
+ def on_clicked_yes(self):
"""
Write a log message, if the form is valid.
"""
- super(PollDeleteView, self).case_yes()
+ super(PollDeleteView, self).on_clicked_yes()
self.object.motion.write_log([ugettext_noop('Poll deleted')], self.request.user)
def get_redirect_url(self, **kwargs):
diff --git a/openslides/participant/models.py b/openslides/participant/models.py
index 31f7f11c2..9da40bbc4 100644
--- a/openslides/participant/models.py
+++ b/openslides/participant/models.py
@@ -138,11 +138,11 @@ class Group(SlideMixin, PersonMixin, Person, DjangoGroup):
Return the URL to the user group.
"""
if link == 'detail' or link == 'view':
- return reverse('user_group_view', args=[str(self.id)])
+ return reverse('user_group_view', args=[str(self.pk)])
if link == 'update' or link == 'edit':
- return reverse('user_group_edit', args=[str(self.id)])
+ return reverse('user_group_edit', args=[str(self.pk)])
if link == 'delete':
- return reverse('user_group_delete', args=[str(self.id)])
+ return reverse('user_group_delete', args=[str(self.pk)])
return super(Group, self).get_absolute_url(link)
diff --git a/openslides/participant/views.py b/openslides/participant/views.py
index 2d0b04801..0396d9eea 100644
--- a/openslides/participant/views.py
+++ b/openslides/participant/views.py
@@ -40,7 +40,7 @@ from openslides.utils.utils import (
template, delete_default_permissions, html_strong)
from openslides.utils.views import (
FormView, PDFView, CreateView, UpdateView, DeleteView, PermissionMixin,
- RedirectView, SingleObjectMixin, ListView, QuestionMixin, DetailView)
+ RedirectView, SingleObjectMixin, ListView, QuestionView, DetailView)
from openslides.config.api import config
from openslides.projector.projector import Widget
@@ -155,6 +155,7 @@ class UserDeleteView(DeleteView):
permission_required = 'participant.can_manage_participant'
model = User
success_url_name = 'user_overview'
+ url_name_args = []
def pre_redirect(self, request, *args, **kwargs):
if self.object == self.request.user:
@@ -335,14 +336,14 @@ class UserImportView(FormView):
return super(UserImportView, self).form_valid(form)
-class ResetPasswordView(SingleObjectMixin, QuestionMixin, RedirectView):
+class ResetPasswordView(SingleObjectMixin, QuestionView):
"""
Set the Passwort for a user to his default password.
"""
permission_required = 'participant.can_manage_participant'
model = User
allow_ajax = True
- question = ugettext_lazy('Do you really want to reset the password?')
+ question_message = ugettext_lazy('Do you really want to reset the password?')
def get(self, request, *args, **kwargs):
self.object = self.get_object()
@@ -351,10 +352,10 @@ class ResetPasswordView(SingleObjectMixin, QuestionMixin, RedirectView):
def get_redirect_url(self, **kwargs):
return reverse('user_edit', args=[self.object.id])
- def case_yes(self):
+ def on_clicked_yes(self):
self.object.reset_password()
- def get_success_message(self):
+ def get_final_message(self):
return _('The Password for %s was successfully reset.') % html_strong(self.object)
@@ -432,6 +433,7 @@ class GroupDeleteView(DeleteView):
permission_required = 'participant.can_manage_participant'
model = Group
success_url_name = 'user_group_overview'
+ url_name_args = []
def pre_redirect(self, request, *args, **kwargs):
if not self.is_protected_from_deleting():
@@ -466,14 +468,17 @@ def login(request):
try:
admin = User.objects.get(pk=1)
if admin.check_password(admin.default_password):
+ user_data = {
+ 'user': html_strong(admin.username),
+ 'password': html_strong(admin.default_password)}
+
extra_content['first_time_message'] = _(
"Installation was successfully! Use %(user)s "
"(password: %(password)s) for first login.
"
"Important: Please change the password after "
"first login! Otherwise this message still appears for "
- "everyone and could be a security risk.") % {
- 'user': html_strong(admin.username),
- 'password': html_strong(admin.default_password)}
+ "everyone and could be a security risk.") % user_data
+
extra_content['next'] = reverse('password_change')
except User.DoesNotExist:
pass
diff --git a/openslides/poll/models.py b/openslides/poll/models.py
index 19821b3d1..986247d97 100644
--- a/openslides/poll/models.py
+++ b/openslides/poll/models.py
@@ -14,7 +14,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils.translation import ugettext_lazy as _, ugettext_noop
-from openslides.utils.modelfields import MinMaxIntegerField
+from openslides.utils.models import MinMaxIntegerField
class BaseOption(models.Model):
diff --git a/openslides/poll/views.py b/openslides/poll/views.py
index 2293e8c54..1571c10a2 100644
--- a/openslides/poll/views.py
+++ b/openslides/poll/views.py
@@ -14,10 +14,10 @@ from django.http import HttpResponseRedirect
from django.forms.models import modelform_factory
from django.core.exceptions import ImproperlyConfigured
-from openslides.utils.views import TemplateView, UrlMixin
+from openslides.utils.views import TemplateView, FormMixin
-class PollFormView(UrlMixin, TemplateView):
+class PollFormView(FormMixin, TemplateView):
poll_class = None
def get(self, request, *args, **kwargs):
diff --git a/openslides/projector/views.py b/openslides/projector/views.py
index 1d8b90d36..fd726ca65 100644
--- a/openslides/projector/views.py
+++ b/openslides/projector/views.py
@@ -23,7 +23,7 @@ from django.shortcuts import redirect
from django.template import RequestContext
from django.utils.translation import ugettext as _
-from openslides.utils.template import render_block_to_string, Tab
+from openslides.utils.template import Tab
from openslides.utils.views import (
TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin)
from openslides.config.api import config
diff --git a/openslides/templates/403.html b/openslides/templates/403.html
index c21b8cb3c..6fc6432ef 100644
--- a/openslides/templates/403.html
+++ b/openslides/templates/403.html
@@ -1,5 +1,8 @@
{% extends "base.html" %}
+{% load i18n %}
+
{% block content %}
-
{{ error }}
+ {% trans 'Permission Denied' %}
+ {% trans 'Sorry, you have no rights to see this page.' %}
{% endblock %}
diff --git a/openslides/utils/modelfields.py b/openslides/utils/models.py
similarity index 83%
rename from openslides/utils/modelfields.py
rename to openslides/utils/models.py
index e3ccd1ef4..85aa5a23b 100644
--- a/openslides/utils/modelfields.py
+++ b/openslides/utils/models.py
@@ -6,7 +6,7 @@
Modelfields for OpenSlides
- :copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
+ :copyright: 2011–2013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
@@ -14,6 +14,10 @@ from django.db import models
class MinMaxIntegerField(models.IntegerField):
+ """
+ IntegerField with options to set a min- and a max-value.
+ """
+
def __init__(self, min_value=None, max_value=None, *args, **kwargs):
self.min_value, self.max_value = min_value, max_value
super(MinMaxIntegerField, self).__init__(*args, **kwargs)
diff --git a/openslides/utils/pdf.py b/openslides/utils/pdf.py
old mode 100755
new mode 100644
diff --git a/openslides/utils/staticfiles.py b/openslides/utils/staticfiles.py
deleted file mode 100755
index 05a075ef9..000000000
--- a/openslides/utils/staticfiles.py
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
- openslides.utils.staticfiles
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
- StaticFiels fix for the django bug #18404.
-
- :copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
- :license: GNU GPL, see LICENSE for more details.
-"""
-
-import os
-import sys
-
-from django.core.files.storage import FileSystemStorage
-from django.utils.importlib import import_module
-from django.contrib.staticfiles.finders import (
- AppDirectoriesFinder as _AppDirectoriesFinder)
-
-
-# This is basically a copy of
-# django.contrib.staticfiles.storage.AppStaticStorage
-# with the fix for django bug #18404 applied
-# see https://code.djangoproject.com/ticket/18404 for details
-class AppStaticStorage(FileSystemStorage):
- """
- A file system storage backend that takes an app module and works
- for the ``static`` directory of it.
- """
- prefix = None
- source_dir = 'static'
-
- def __init__(self, app, *args, **kwargs):
- """
- Returns a static file storage if available in the given app.
- """
- # app is the actual app module
- mod = import_module(app)
- mod_path = os.path.dirname(mod.__file__)
- location = os.path.join(mod_path, self.source_dir)
- fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
- location = location.decode(fs_encoding)
- super(AppStaticStorage, self).__init__(location, *args, **kwargs)
-
-
-class AppDirectoriesFinder(_AppDirectoriesFinder):
- storage_class = AppStaticStorage
diff --git a/openslides/utils/template.py b/openslides/utils/template.py
index 27f77df0a..2b237c0b4 100644
--- a/openslides/utils/template.py
+++ b/openslides/utils/template.py
@@ -10,11 +10,11 @@
:license: GNU GPL, see LICENSE for more details.
"""
-from django.template import loader, Context
-from django.template.loader_tags import BlockNode, ExtendsNode
-
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
@@ -22,62 +22,3 @@ class Tab(object):
self.url = url
self.permission = permission
self.selected = selected
-
-
-## All following function are only needed to render a block from a template
-## and could be removed, if the template worked with an include-statement instead.
-## Its only used for ajax-request from the projector.
-
-def get_template(template):
- if isinstance(template, (tuple, list)):
- return loader.select_template(template)
- return loader.get_template(template)
-
-
-class BlockNotFound(Exception):
- pass
-
-
-def render_template_block(template, block, context):
- """
- Renders a single block from a template. This template should have previously
- been rendered.
- """
- return render_template_block_nodelist(template.nodelist, block, context)
-
-
-def render_template_block_nodelist(nodelist, block, context):
- for node in nodelist:
- if isinstance(node, BlockNode) and node.name == block:
- return node.render(context)
- for key in ('nodelist', 'nodelist_true', 'nodelist_false'):
- if hasattr(node, key):
- try:
- return render_template_block_nodelist(
- getattr(node, key), block, context)
- except:
- pass
- for node in nodelist:
- if isinstance(node, ExtendsNode):
- try:
- return render_template_block(
- node.get_parent(context), block, context)
- except BlockNotFound:
- pass
- raise BlockNotFound
-
-
-def render_block_to_string(template_name, block, dictionary=None,
- context_instance=None):
- """
- Loads the given template_name and renders the given block with the given
- dictionary as context. Returns a string.
- """
- dictionary = dictionary or {}
- t = get_template(template_name)
- if context_instance:
- context_instance.update(dictionary)
- else:
- context_instance = Context(dictionary)
- t.render(context_instance)
- return render_template_block(t, block, context_instance)
diff --git a/openslides/utils/utils.py b/openslides/utils/utils.py
index fb4fb8030..c207a4985 100644
--- a/openslides/utils/utils.py
+++ b/openslides/utils/utils.py
@@ -11,54 +11,12 @@
"""
import difflib
-import json
-import sys
-from django.contrib import messages
from django.contrib.auth.models import Permission
-from django.core.context_processors import csrf
-from django.core.urlresolvers import reverse
-from django.http import HttpResponse, HttpResponseForbidden
-from django.shortcuts import render_to_response, redirect
+from django.shortcuts import render_to_response
from django.template import RequestContext
-from django.template.loader import render_to_string
-from django.utils.translation import ugettext as _, ugettext_lazy
-from openslides.utils.signals import template_manipulation
-
-
-def gen_confirm_form(request, message, url):
- """
- Generate a message-form.
-
- Deprecated. Use Class base Views instead.
- """
- messages.warning(
- request,
- """
- %s
-
- """
- % (message, url, csrf(request)['csrf_token'], _("Yes"), _("No")))
-
-
-def del_confirm_form(request, object, name=None, delete_link=None):
- """
- Creates a question to delete an object.
-
- Deprecated. Use Class base Views instead.
- """
- if name is None:
- name = object
- if delete_link is None:
- delete_link = object.get_absolute_url('delete')
- gen_confirm_form(
- request, _('Do you really want to delete %s?')
- % html_strong(name), delete_link)
+from .signals import template_manipulation
def template(template_name):
@@ -67,6 +25,8 @@ def template(template_name):
Deprecated. Use class based views instead.
"""
+ # TODO: Write the login page an the usersettings page with class based views
+ # Remove this function afterwards
def renderer(func):
def wrapper(request, *args, **kwargs):
output = func(request, *args, **kwargs)
@@ -85,36 +45,12 @@ def template(template_name):
return renderer
-def permission_required(perm, login_url=None):
- """
- Decorator for views that checks whether a user has a particular permission
- enabled, redirecting to the log-in page if necessary.
-
- Deprecated.
- """
- def renderer(func):
- def wrapper(request, *args, **kw):
- if request.user.has_perm(perm):
- return func(request, *args, **kw)
- if request.user.is_authenticated():
- return render_to_forbidden(request)
- return redirect(reverse('user_login'))
- return wrapper
- return renderer
-
-
-def render_to_forbidden(request,
- error=ugettext_lazy("Sorry, you have no rights to see this page.")):
- # TODO: Integrate this function into the PermissionMixin once the
- # above function is deleted.
- return HttpResponseForbidden(render_to_string(
- '403.html', {'error': error}, context_instance=RequestContext(request)))
-
-
def delete_default_permissions(**kwargs):
"""
Deletes the permissions, django creates by default for the admin.
"""
+ # TODO: Create an participant app which does not create the permissions.
+ # Delete this function afterwards
for p in Permission.objects.all():
if (p.codename.startswith('add') or
p.codename.startswith('delete') or
@@ -122,29 +58,26 @@ def delete_default_permissions(**kwargs):
p.delete()
-def ajax_request(data):
- """
- generates a HTTPResponse-Object with json-Data for a
- ajax response.
-
- Deprecated.
- """
- return HttpResponse(json.dumps(data))
-
-
def html_strong(string):
+ """
+ Returns the text wrapped in an HTML-Strong element.
+ """
return u"%s" % string
def htmldiff(text1, text2):
- """Return string of html diff between two strings (text1 and text2)"""
-
+ """
+ Return string of html diff between two strings (text1 and text2)
+ """
diff = difflib.HtmlDiff(wrapcolumn=60)
return diff.make_table(text1.splitlines(), text2.splitlines())
-def int_or_none(object):
+def int_or_none(var):
+ """
+ Trys to convert 'var' into an integer. Returns None if an TypeError occures.
+ """
try:
- return int(object)
- except TypeError:
+ return int(var)
+ except (TypeError, ValueError):
return None
diff --git a/openslides/utils/views.py b/openslides/utils/views.py
index 6265b5294..9ab1b4be7 100644
--- a/openslides/utils/views.py
+++ b/openslides/utils/views.py
@@ -6,7 +6,7 @@
Views for OpenSlides.
- :copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
+ :copyright: 2011–2013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
@@ -15,257 +15,280 @@ from cStringIO import StringIO
from reportlab.platypus import SimpleDocTemplate, Spacer
from reportlab.lib.units import cm
+from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.context_processors import csrf
-from django.core.exceptions import ImproperlyConfigured
+from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.core.urlresolvers import reverse
-from django.conf import settings
from django.dispatch import receiver
-from django.http import HttpResponseServerError, HttpResponse, HttpResponseRedirect
-from django.utils.decorators import method_decorator
-from django.utils.translation import ugettext as _, ugettext_lazy
-from django.utils.importlib import import_module
+from django.http import (HttpResponse, HttpResponseRedirect,
+ HttpResponseServerError)
from django.template import RequestContext
from django.template.loader import render_to_string
-from django.views.generic import (
- TemplateView as _TemplateView,
- RedirectView as _RedirectView,
- UpdateView as _UpdateView,
- CreateView as _CreateView,
- View as _View,
- FormView as _FormView,
- ListView as _ListView,
- DetailView as _DetailView,
-)
+from django.utils.decorators import method_decorator
+from django.utils.importlib import import_module
+from django.utils.translation import ugettext as _
+from django.utils.translation import ugettext_lazy
+from django.views import generic as django_views
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.list import TemplateResponseMixin
+from reportlab.lib.units import cm
+from reportlab.platypus import SimpleDocTemplate, Spacer
-from openslides.utils.utils import render_to_forbidden, html_strong
-from openslides.utils.signals import template_manipulation
-from openslides.utils.pdf import firstPage, laterPages
-
+from .pdf import firstPage, laterPages
+from .signals import template_manipulation
+from .utils import html_strong
NO_PERMISSION_REQUIRED = 'No permission required'
-View = _View
-
-
-class SetCookieMixin(object):
- def render_to_response(self, context, **response_kwargs):
- response = TemplateResponseMixin.render_to_response(
- self, context, **response_kwargs)
- if 'cookie' in context:
- response.set_cookie(context['cookie'][0], context['cookie'][1])
- return response
+View = django_views.View
class LoginMixin(object):
+ """
+ Mixin for Views, that only can be viseted from users how are logedin.
+ """
+
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
+ """
+ Check if the user is loged in.
+ """
return super(LoginMixin, self).dispatch(request, *args, **kwargs)
class PermissionMixin(object):
+ """
+ Mixin for views, that only can be visited from users with special rights.
+
+ Set the attribute 'permission_required' to the required permission string.
+ """
permission_required = NO_PERMISSION_REQUIRED
def has_permission(self, request, *args, **kwargs):
+ """
+ Checks if the user has the required permission.
+ """
if self.permission_required == NO_PERMISSION_REQUIRED:
return True
else:
return request.user.has_perm(self.permission_required)
def dispatch(self, request, *args, **kwargs):
+ """
+ Check if the user has the permission.
+
+ If the user is not logged in, redirect the user to the login page.
+ """
if not self.has_permission(request, *args, **kwargs):
if not request.user.is_authenticated():
path = request.get_full_path()
return HttpResponseRedirect(
"%s?next=%s" % (settings.LOGIN_URL, path))
else:
- return render_to_forbidden(request)
+ raise PermissionDenied
return super(PermissionMixin, self).dispatch(request, *args, **kwargs)
class AjaxMixin(object):
+ """
+ Mixin to response to an ajax request with an json object.
+ """
+
def get_ajax_context(self, **kwargs):
- return {}
+ """
+ Returns a dictonary with the context for the ajax response.
+ """
+ return kwargs
def ajax_get(self, request, *args, **kwargs):
- return HttpResponse(json.dumps(self.get_ajax_context(**kwargs)))
+ """
+ Returns the HttpResponse.
+ """
+ return HttpResponse(json.dumps(self.get_ajax_context()))
class ExtraContextMixin(object):
+ """
+ Mixin to send the signal 'template_manipulation' to add extra content to the
+ context of the view.
+
+ For example this is used to add the main menu of openslides.
+ """
+
def get_context_data(self, **kwargs):
+ """
+ Sends the signal.
+ """
context = super(ExtraContextMixin, self).get_context_data(**kwargs)
- context.setdefault('extra_javascript', [])
template_manipulation.send(
sender=self.__class__, request=self.request, context=context)
return context
class UrlMixin(object):
- """
- Mixin to provide the use of url names in views with success url and
- apply url.
-
- The initial value of url_name_args is None, but the default given by
- the used get_url_name_args method is [], because in the future, the
- method might return another default value. Compare this with the QuestionMixin.
- """
- apply_url_name = None
- success_url_name = None
url_name_args = None
+
+ def get_url(self, url_name=None, url=None, args=None, use_absolute_url_link=None):
+ """
+ Returns an url.
+
+ Tries
+ 1. to use the reverse for the attribute 'url_name',
+ 2. to use the attribute 'url' or
+ 3. to use self.object.get_absolute_url().
+
+ Uses the attribute 'use_absolute_url_link' as argument for
+ get_absolute_url in the third step. If the attribute is 'None' then
+ the default value of get_absolute_url is used.
+
+ Raises ImproperlyConfigured if no url can be found.
+ """
+ if url_name:
+ value = reverse(url_name, args=args or [])
+ elif url:
+ value = url
+ else:
+ try:
+ if use_absolute_url_link is None:
+ value = self.object.get_absolute_url()
+ else:
+ value = self.object.get_absolute_url(use_absolute_url_link)
+ except AttributeError:
+ raise ImproperlyConfigured(
+ 'No url to redirect to. See openslides.utils.views.UrlMixin for more details.')
+ return value
+
+ def get_url_name_args(self):
+ """
+ Returns the arguments for the url name.
+
+ Default is an empty list or [self.object.pk] if this exist.
+ """
+ if self.url_name_args is not None:
+ value = self.url_name_args
+ else:
+ try:
+ pk = self.object.pk
+ except AttributeError:
+ value = []
+ else:
+ if pk:
+ value = [pk]
+ else:
+ value = []
+ return value
+
+
+class FormMixin(UrlMixin):
+ """
+ Mixin for views with forms.
+ """
+
+ use_apply = True
+ success_url_name = None
success_url = None
+ success_message = None
+ apply_url_name = None
apply_url = None
+ error_message = ugettext_lazy('Please check the form for errors.')
def get_apply_url(self):
- if self.apply_url_name:
- return reverse(self.apply_url_name, args=self.get_url_name_args())
- elif self.apply_url:
- return self.apply_url
- else:
- try:
- return self.object.get_absolute_url('edit')
- except AttributeError:
- raise ImproperlyConfigured(
- "No URL to redirect to. Provide an apply_url_name.")
+ """
+ Returns the url when the user clicks on 'apply'.
+ """
+ return self.get_url(self.apply_url_name, self.apply_url,
+ args=self.get_url_name_args(),
+ use_absolute_url_link='update')
def get_success_url(self):
- if 'apply' in self.request.POST:
- return self.get_apply_url()
+ """
+ Returns the url when the user submits a form.
- if self.success_url_name:
- return reverse(self.success_url_name, args=self.get_url_name_args())
- elif self.success_url:
- return self.success_url
+ Redirects to get_apply_url if self.use_apply is True
+ """
+ if self.use_apply and 'apply' in self.request.POST:
+ value = self.get_apply_url()
else:
- try:
- return self.object.get_absolute_url()
- except AttributeError:
- raise ImproperlyConfigured(
- "No URL to redirect to. Either provide an URL or define "
- "a get_absolute_url method of the model.")
+ value = self.get_url(self.success_url_name, self.success_url,
+ args=self.get_url_name_args())
+ return value
- def get_url_name_args(self):
- """
- Returns the arguments for the url name. Default is an empty list.
- """
- if self.url_name_args is not None:
- return self.url_name_args
- return []
+ def form_valid(self, form):
+ value = super(FormMixin, self).form_valid(form)
+ messages.success(self.request, self.get_success_message())
+ return value
-
-class QuestionMixin(object):
- """
- Mixin for questions to the requesting user.
-
- The initial value of url_name_args is None, but the default given by
- the used get_url_name_args method is [self.object.pk] if it exist, else
- an empty list. Set url_name_args to an empty list, if you use an url
- name which does not need any arguments.
- """
- question = ugettext_lazy('Are you sure?')
- success_message = ugettext_lazy('Thank you for your answer')
- answer_options = [('yes', ugettext_lazy("Yes")), ('no', ugettext_lazy("No"))]
- question_url_name = None
- success_url_name = None
- url_name_args = None
-
- def get_redirect_url(self, **kwargs):
- # TODO: raise error when question_url_name/success_url_name is not present
- if self.request.method == 'GET':
- return reverse(self.question_url_name, args=self.get_question_url_name_args())
- else:
- return reverse(self.success_url_name, args=self.get_success_url_name_args())
-
- def get_question_url_name_args(self):
- return self.get_url_name_args()
-
- def get_success_url_name_args(self):
- return self.get_url_name_args()
-
- def get_url_name_args(self):
- """
- Returns the arguments for the url name. Default is an empty list
- or [self.object.pk] if this exist.
- """
- if self.url_name_args is not None:
- return self.url_name_args
- try:
- return [self.object.pk]
- except AttributeError:
- return []
-
- def pre_redirect(self, request, *args, **kwargs):
- """
- Prints the question in a GET request.
- """
- self.confirm_form()
-
- def get_question(self):
- return unicode(self.question)
-
- def get_answer_options(self):
- return self.answer_options
-
- def get_answer_url(self):
- try:
- return self.answer_url
- except AttributeError:
- return self.request.path
-
- def confirm_form(self):
- option_fields = "\n".join([
- '' % (option[0], unicode(option[1]))
- for option in self.get_answer_options()])
- messages.warning(
- self.request,
- """
- %(message)s
-
- """ % {'message': self.get_question(),
- 'url': self.get_answer_url(),
- 'csrf': csrf(self.request)['csrf_token'],
- 'option_fields': option_fields})
-
- def pre_post_redirect(self, request, *args, **kwargs):
- # Reacts on the response of the user in a POST-request.
- # TODO: call the methodes for all possible answers.
- if self.get_answer() == 'yes':
- self.case_yes()
- messages.success(request, self.get_success_message())
-
- def get_answer(self):
- for option in self.get_answer_options():
- if option[0] in self.request.POST:
- return option[0]
- return None
-
- def case_yes(self):
- # TODO: raise a warning
- pass
+ def form_invalid(self, form):
+ value = super(FormMixin, self).form_invalid(form)
+ messages.error(self.request, self.get_error_message())
+ return value
def get_success_message(self):
return self.success_message
+ def get_error_message(self):
+ return self.error_message
-class TemplateView(PermissionMixin, ExtraContextMixin, _TemplateView):
+
+class ModelFormMixin(FormMixin):
+ """
+ Mixin for FormViews.
+ """
+
+ def form_valid(self, form):
+ """
+ Called if the form is valid.
+
+ 1. saves the form into the model,
+ 2. calls 'self.manipulate_object,
+ 3. saves the object in the database,
+ 4. calls self.post_save.
+ """
+ self.object = form.save(commit=False)
+ self.manipulate_object(form)
+ self.object.save()
+ self.post_save(form)
+ messages.success(self.request, self.get_success_message())
+ return HttpResponseRedirect(self.get_success_url())
+
+ def manipulate_object(self, form):
+ """
+ Called before the object is saved into the database.
+ """
+ pass
+
+ def post_save(self, form):
+ """
+ Called after the object is saved into the database.
+ """
+ form.save_m2m()
+
+
+class TemplateView(PermissionMixin, ExtraContextMixin, django_views.TemplateView):
+ """
+ View to return with an template.
+ """
pass
-class ListView(PermissionMixin, SetCookieMixin, ExtraContextMixin, _ListView):
+class ListView(PermissionMixin, ExtraContextMixin, django_views.ListView):
+ """
+ View to show a list of model objects.
+ """
pass
class AjaxView(PermissionMixin, AjaxMixin, View):
+ """
+ View for ajax requests.
+ """
def get(self, request, *args, **kwargs):
return self.ajax_get(request, *args, **kwargs)
-class RedirectView(PermissionMixin, AjaxMixin, _RedirectView):
+class RedirectView(PermissionMixin, AjaxMixin, UrlMixin, django_views.RedirectView):
"""
View to redirect to another url.
@@ -277,12 +300,19 @@ class RedirectView(PermissionMixin, AjaxMixin, _RedirectView):
permanent = False
allow_ajax = False
url_name = None
- url_name_args = None
def pre_redirect(self, request, *args, **kwargs):
+ """
+ Called before the redirect.
+ """
+ # TODO: Also call this method on post-request.
+ # Add pre_get_redirect for get requests.
pass
def pre_post_redirect(self, request, *args, **kwargs):
+ """
+ Called before the redirect, if it is a post request.
+ """
pass
def get(self, request, *args, **kwargs):
@@ -291,62 +321,143 @@ class RedirectView(PermissionMixin, AjaxMixin, _RedirectView):
elif request.method == 'POST':
self.pre_post_redirect(request, *args, **kwargs)
- if self.request.is_ajax() and self.allow_ajax:
+ if request.is_ajax() and self.allow_ajax:
return self.ajax_get(request, *args, **kwargs)
return super(RedirectView, self).get(request, *args, **kwargs)
def get_redirect_url(self, **kwargs):
- if self.url_name is not None:
- return reverse(self.url_name, args=self.get_url_name_args())
+ """
+ Returns the url to which the redirect should go.
+ """
+ return self.get_url(self.url_name, self.url,
+ args=self.get_url_name_args())
+
+
+class QuestionView(RedirectView):
+ """
+ Mixin for questions to the requesting user.
+
+ The BaseView has to be a RedirectView.
+ """
+
+ question_message = ugettext_lazy('Are you sure?')
+ final_message = ugettext_lazy('Thank you for your answer.')
+ answer_options = [('yes', ugettext_lazy("Yes")), ('no', ugettext_lazy("No"))]
+ question_url_name = None
+ question_url = None
+
+ def get_redirect_url(self, **kwargs):
+ """
+ Returns the url to which the view should redirect.
+ """
+ if self.request.method == 'GET':
+ url = self.get_url(self.question_url_name, self.question_url,
+ args=self.get_url_name_args())
else:
- return super(RedirectView, self).get_redirect_url(**kwargs)
+ url = super(QuestionView, self).get_redirect_url()
+ return url
- def get_url_name_args(self):
+ def pre_redirect(self, request, *args, **kwargs):
"""
- Returns the arguments for the url name. Default is an empty list
- or [self.object.pk] if this exist.
+ Prints the question in a GET request.
"""
- if self.url_name_args is not None:
- return self.url_name_args
- try:
- return [self.object.pk]
- except AttributeError:
- return []
+ self.confirm_form()
+
+ def get_question_message(self):
+ """
+ Returns the question.
+ """
+ return self.question_message
+
+ def get_answer_options(self):
+ """
+ Returns the possible answers.
+
+ This is a list of tubles. The first element of the tuble is the key,
+ the second element is shown to the user.
+ """
+ return self.answer_options
+
+ def confirm_form(self):
+ """
+ Returns the form to show in the message.
+ """
+ option_fields = "\n".join([
+ ''
+ % (option[0], unicode(option[1]))
+ for option in self.get_answer_options()])
+ messages.warning(
+ self.request,
+ """
+ %(message)s
+
+ """ % {'message': self.get_question_message(),
+ 'url': self.request.path,
+ 'csrf': csrf(self.request)['csrf_token'],
+ 'option_fields': option_fields})
+
+ def pre_post_redirect(self, request, *args, **kwargs):
+ """
+ Calls the method for the answer the user clicked.
+
+ The method name is on_clicked_ANSWER where ANSWER is the key from the
+ clicked answer. See get_answer_options. If this method is not defined,
+ raises a NotImplementedError.
+
+ If the method returns True, then the success message is printed to the
+ user.
+ """
+ method_name = 'on_clicked_%s' % self.get_answer()
+ method = getattr(self, method_name, None)
+ if method is None:
+ pass
+ else:
+ method()
+ self.create_final_message()
+
+ def get_answer(self):
+ """
+ Returns the key of the clicked answer.
+
+ Raises ImproperlyConfigured, if the answer is not one of
+ get_answer_options.
+ """
+ for option_key, option_name in self.get_answer_options():
+ if option_key in self.request.POST:
+ return option_key
+ raise ImproperlyConfigured('%s is not a valid answer' % self.request.POST)
+
+ def get_final_message(self):
+ """
+ Returns the message to show after the action.
+
+ Uses the attribute 'final_messsage' as default
+ """
+ return self.final_message
+
+ def create_final_message(self):
+ """
+ Creates a message.
+ """
+ messages.success(self.request, self.get_final_message())
-class FormView(PermissionMixin, ExtraContextMixin, UrlMixin, _FormView):
- def form_invalid(self, form):
- messages.error(self.request, _('Please check the form for errors.'))
- return super(FormView, self).form_invalid(form)
+class FormView(PermissionMixin, ExtraContextMixin, FormMixin,
+ django_views.FormView):
+ """
+ View for forms.
+ """
+ pass
-class ModelFormMixin(object):
- def form_valid(self, form):
- self.object = form.save(commit=False)
- self.manipulate_object(form)
- self.object.save()
- self.post_save(form)
- return HttpResponseRedirect(self.get_success_url())
-
- def manipulate_object(self, form):
- pass
-
- def post_save(self, form):
- form.save_m2m()
-
-
-class UpdateView(PermissionMixin, UrlMixin, ExtraContextMixin,
- ModelFormMixin, _UpdateView):
- success_message = None
-
- def form_invalid(self, form):
- messages.error(self.request, _('Please check the form for errors.'))
- return super(UpdateView, self).form_invalid(form)
-
- def form_valid(self, form):
- value = super(UpdateView, self).form_valid(form)
- messages.success(self.request, self.get_success_message())
- return value
+class UpdateView(PermissionMixin, ExtraContextMixin,
+ ModelFormMixin, django_views.UpdateView):
+ """
+ View to update an model object.
+ """
def get_success_message(self):
if self.success_message is None:
@@ -356,18 +467,11 @@ class UpdateView(PermissionMixin, UrlMixin, ExtraContextMixin,
return message
-class CreateView(PermissionMixin, UrlMixin, ExtraContextMixin,
- ModelFormMixin, _CreateView):
- success_message = None
-
- def form_invalid(self, form):
- messages.error(self.request, _('Please check the form for errors.'))
- return super(CreateView, self).form_invalid(form)
-
- def form_valid(self, form):
- value = super(CreateView, self).form_valid(form)
- messages.success(self.request, self.get_success_message())
- return value
+class CreateView(PermissionMixin, ExtraContextMixin,
+ ModelFormMixin, django_views.CreateView):
+ """
+ View to create a model object.
+ """
def get_success_message(self):
if self.success_message is None:
@@ -377,8 +481,12 @@ class CreateView(PermissionMixin, UrlMixin, ExtraContextMixin,
return message
-class DeleteView(SingleObjectMixin, QuestionMixin, RedirectView):
- question_url_name = None
+class DeleteView(SingleObjectMixin, QuestionView):
+ """
+ View to delete an model object.
+ """
+
+ success_url = None
success_url_name = None
def get(self, request, *args, **kwargs):
@@ -386,32 +494,53 @@ class DeleteView(SingleObjectMixin, QuestionMixin, RedirectView):
return super(DeleteView, self).get(request, *args, **kwargs)
def get_redirect_url(self, **kwargs):
- if self.question_url_name is None and (self.request.method == 'GET' or
- self.get_answer() == 'no'):
- return self.object.get_absolute_url()
- else:
- return super(DeleteView, self).get_redirect_url(**kwargs)
+ """
+ Returns the url on which the delete dialog is shown and the url after
+ the deleten.
- def get_question(self):
+ On GET-requests and on aborted POST-requests, redirect to the detail
+ view as default. The attributes question_url_name or question_url can
+ define other urls.
+ """
+ if self.request.method == 'GET' or self.get_answer() == 'no':
+ url = self.get_url(self.question_url_name, self.question_url,
+ args=self.get_url_name_args())
+ else:
+ url = self.get_url(self.success_url_name, self.success_url,
+ args=self.get_url_name_args())
+ return url
+
+ def get_question_message(self):
+ """
+ Returns the question for the delete dialog.
+ """
return _('Do you really want to delete %s?') % html_strong(self.object)
- def case_yes(self):
+ def on_clicked_yes(self):
+ """
+ Deletes the object.
+ """
self.object.delete()
- def get_success_message(self):
+ def get_final_message(self):
+ """
+ Prints the success message to the user.
+ """
return _('%s was successfully deleted.') % html_strong(self.object)
- def get_url_name_args(self):
- return []
-
-class DetailView(PermissionMixin, ExtraContextMixin, _DetailView):
- def get(self, request, *args, **kwargs):
- self.object = self.get_object()
- return super(DetailView, self).get(request, *args, **kwargs)
+class DetailView(PermissionMixin, ExtraContextMixin, django_views.DetailView):
+ """
+ View to show an model object.
+ """
+ pass
class PDFView(PermissionMixin, View):
+ """
+ View to generate an PDF.
+ """
+
filename = _('undefined-filename')
top_space = 3
document_title = None
@@ -480,6 +609,8 @@ def send_register_tab(sender, request, context, **kwargs):
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:
diff --git a/tests/motion/test_views.py b/tests/motion/test_views.py
index 7726aef1c..859e6c6dd 100644
--- a/tests/motion/test_views.py
+++ b/tests/motion/test_views.py
@@ -370,14 +370,14 @@ class TestMotionDeleteView(MotionViewTestCase):
self.assertRedirects(response, '/motion/2/')
def test_admin(self):
- response = self.admin_client.post('/motion/2/del/', {})
+ response = self.admin_client.post('/motion/2/del/', {'yes': 'yes'})
self.assertRedirects(response, '/motion/')
def test_delegate(self):
- response = self.delegate_client.post('/motion/2/del/', {})
+ response = self.delegate_client.post('/motion/2/del/', {'yes': 'yes'})
self.assertEqual(response.status_code, 403)
motion = Motion.objects.get(pk=2).add_submitter(self.delegate)
- response = self.delegate_client.post('/motion/2/del/', {})
+ response = self.delegate_client.post('/motion/2/del/', {'yes': 'yes'})
self.assertEqual(response.status_code, 403)
@@ -394,7 +394,7 @@ class TestVersionPermitView(MotionViewTestCase):
def test_post(self):
new_version = self.motion1.get_last_version()
response = self.admin_client.post('/motion/1/version/2/permit/', {'yes': 1})
- self.assertRedirects(response, '/motion/1/version/2/')
+ self.assertRedirects(response, '/motion/1/')
self.assertEqual(self.motion1.get_active_version(), new_version)
def test_activate_old_version(self):
diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py
new file mode 100644
index 000000000..090a5ae00
--- /dev/null
+++ b/tests/utils/test_utils.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ Tests for openslides utils.utils
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ TODO: Move this test to the correct place when the projector app is cleaned up.
+
+ :copyright: 2011–2013 by OpenSlides team, see AUTHORS.
+ :license: GNU GPL, see LICENSE for more details.
+"""
+
+from openslides.utils.test import TestCase
+from openslides.utils.utils import html_strong, int_or_none
+
+
+class Test_functions(TestCase):
+ def test_string(self):
+ self.assertEqual(html_strong('some text'), 'some text')
+
+ def test_int_or_none(self):
+ self.assertEqual(int_or_none('5'), 5)
+ self.assertEqual(int_or_none(5), 5)
+ self.assertIsNone(int_or_none('text'))
+ self.assertIsNone(int_or_none(None))
diff --git a/tests/utils/test_views.py b/tests/utils/test_views.py
new file mode 100644
index 000000000..111477741
--- /dev/null
+++ b/tests/utils/test_views.py
@@ -0,0 +1,210 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ Tests for openslides utils.utils
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ TODO: Move this test to the correct place when the projector app is cleaned up.
+
+ :copyright: 2011–2013 by OpenSlides team, see AUTHORS.
+ :license: GNU GPL, see LICENSE for more details.
+"""
+
+from django.contrib.messages.storage import default_storage
+from django.core.exceptions import ImproperlyConfigured
+from django.core.urlresolvers import clear_url_caches
+from django.dispatch import receiver
+from django.http.response import HttpResponseNotAllowed
+from django.test import RequestFactory
+from django.test.client import Client
+from django.test.utils import override_settings
+
+from openslides.participant.models import User
+from openslides.utils import views
+from openslides.utils.signals import template_manipulation
+from openslides.utils.test import TestCase
+
+from . import views as test_views
+
+
+@override_settings(ROOT_URLCONF='tests.utils.urls')
+class ViewTestCase(TestCase):
+ rf = RequestFactory()
+
+ def setUp(self):
+ # Clear the cache for the urlresolver, so the overriden settings works.
+ clear_url_caches()
+
+
+class LoginMixinTest(ViewTestCase):
+ def test_dispatch(self):
+ view = test_views.LoginMixinView.as_view()
+ client = Client()
+ response = client.get('/login_mixin/')
+ self.assertEqual(response['Location'], 'http://testserver/login/?next=/login_mixin/')
+
+ client.login(username='admin', password='admin')
+ response = client.get('/login_mixin/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'Well done.')
+
+
+class PermissionMixinTest(ViewTestCase):
+ def test_dispatch(self):
+ client = Client()
+
+ # View without permission_required
+ response = client.get('/permission_mixin1/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'Well done.')
+
+ # View with permission_required without login
+ response = client.get('/permission_mixin2/')
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(response['Location'], 'http://testserver/login/?next=/permission_mixin2/')
+
+ # View with permission_required, with login, without permission
+ client.login(username='admin', password='admin')
+ response = client.get('/permission_mixin2/')
+ self.assertEqual(response.status_code, 403)
+
+ # View with permission_required, with login, with permission
+ response = client.get('/permission_mixin3/')
+ self.assertEqual(response.status_code, 200)
+
+
+class AjaxMixinTest(ViewTestCase):
+ def test_ajax_get(self):
+ view = test_views.AjaxMixinView()
+ ajax_get = view.ajax_get
+ response = ajax_get(self.rf.get('/', {}))
+ self.assertEqual(response.content, '{"new_context": "newer_context"}')
+
+ def test_get_ajax_context(self):
+ get_ajax_context = test_views.AjaxMixinView().get_ajax_context
+ self.assertEqual(get_ajax_context()['new_context'], 'newer_context')
+ self.assertEqual(
+ get_ajax_context(test_content='some_content')['test_content'],
+ 'some_content')
+
+
+class ExtraContextMixinTest(ViewTestCase):
+ """
+ Tests the ExtraContextMixin by testen the TemplateView
+ """
+ def test_get_context_data(self):
+ view = views.TemplateView()
+ get_context_data = view.get_context_data
+ view.request = self.rf.get('/', {})
+
+ context = get_context_data()
+ self.assertIn('tabs', context)
+
+ context = get_context_data(some_context='context')
+ self.assertIn('tabs', context)
+ self.assertIn('some_context', context)
+
+ template_manipulation.connect(set_context, dispatch_uid='set_context_test')
+ context = get_context_data()
+ self.assertIn('tabs', context)
+ self.assertIn('new_context', context)
+ template_manipulation.disconnect(set_context, dispatch_uid='set_context_test')
+
+
+class UrlMixinTest(ViewTestCase):
+ def test_get_url(self):
+ get_url = test_views.UrlMixinView().get_url
+
+ # url_name has the higher priority
+ self.assertEqual(get_url('test_url_mixin', 'view_url'),
+ '/url_mixin/')
+
+ # If the url_name is none, return the second argument
+ self.assertEqual(get_url(None, 'view_url'),
+ 'view_url')
+
+ # Test argument in url
+ self.assertEqual(get_url('test_url_mixin_args', None,
+ args=[1]),
+ '/url_mixin_args/1/')
+
+ # No Url given.
+ self.assertRaisesMessage(
+ ImproperlyConfigured,
+ 'No url to redirect to. See openslides.utils.views.UrlMixin for more details.',
+ get_url, None, None)
+
+ def test_get_url_with_object(self):
+ get_url = test_views.UrlMixinViewWithObject().get_url
+
+ self.assertEqual(get_url(None, None),
+ 'default_url')
+ self.assertEqual(get_url(None, None, use_absolute_url_link='detail'),
+ 'detail_url')
+
+ def test_get_url_name_args(self):
+ view = test_views.UrlMixinViewWithObject()
+ get_url_name_args = view.get_url_name_args
+ view.url_name_args = [1]
+
+ self.assertEqual(get_url_name_args(), [1])
+
+ view.url_name_args = None
+ self.assertEqual(get_url_name_args(), [])
+
+ view.object.pk = 5
+ self.assertEqual(get_url_name_args(), [5])
+
+
+## class QuestionMixinTest(ViewTestCase):
+ ## def test_get_redirect_url(self):
+ ## view = views.QuestionMixin()
+ ## get_redirect_url = view.get_redirect_url
+##
+ ## view.request = self.rf.get('/')
+ ## view.question_url = 'redirect_to_get_url'
+ ## self.assertEqual(get_redirect_url(), 'redirect_to_get_url')
+##
+ ## view.request = self.rf.post('/')
+ ## view.success_url = 'redirect_to_post_url'
+ ## self.assertEqual(get_redirect_url(), 'redirect_to_post_url')
+##
+ ## def test_get_question(self):
+ ## view = views.QuestionMixin()
+ ## get_redirect_url = view.get_question
+##
+ ## self.assertEqual(get_redirect_url(), 'Are you sure?')
+##
+ ## view.question = 'new_question'
+ ## self.assertEqual(get_redirect_url(), 'new_question')
+##
+ ## def test_get_answer_options(self):
+ ## view = views.QuestionMixin()
+ ## get_answer_options = view.get_answer_options
+##
+ ## self.assertIn('yes', dict(get_answer_options()))
+ ## self.assertIn('no', dict(get_answer_options()))
+##
+ ## view.answer_options = [('new_answer', 'Answer')]
+ ## self.assertNotIn('yes', dict(get_answer_options()))
+ ## self.assertIn('new_answer', dict(get_answer_options()))
+##
+ ## def test_confirm_form(self):
+ ## view = views.QuestionMixin()
+ ## confirm_form = view.confirm_form
+ ## view.request = self.rf.get('/')
+ ## view.request._messages = default_storage(view.request)
+##
+ ## confirm_form()
+ ## message = "".join(view.request._messages._queued_messages[0].message.split())
+ ## self.assertEqual(
+ ## message, 'Areyousure?'
+ ## '' 'Yes'
+ ## 'No')
+
+
+def set_context(sender, request, context, **kwargs):
+ """
+ receiver for testing the ExtraContextMixin
+ """
+ context.update({'new_context': 'some new context'})
diff --git a/tests/utils/urls.py b/tests/utils/urls.py
new file mode 100644
index 000000000..484b04388
--- /dev/null
+++ b/tests/utils/urls.py
@@ -0,0 +1,29 @@
+from django.conf.urls import patterns, url
+
+from openslides.urls import urlpatterns
+
+from . import views
+
+
+urlpatterns += patterns(
+ '',
+ url(r'^url_mixin/$',
+ views.UrlMixinView.as_view(),
+ name='test_url_mixin'),
+
+ url(r'^url_mixin_args/(?P\d+)/$',
+ views.UrlMixinView.as_view(),
+ name='test_url_mixin_args'),
+
+ url(r'^login_mixin/$',
+ views.LoginMixinView.as_view()),
+
+ url(r'^permission_mixin1/$',
+ views.PermissionMixinView.as_view()),
+
+ url(r'^permission_mixin2/$',
+ views.PermissionMixinView.as_view(permission_required='permission_string')),
+
+ url(r'^permission_mixin3/$',
+ views.PermissionMixinView.as_view(permission_required='agenda.can_see_agenda')),
+)
diff --git a/tests/utils/views.py b/tests/utils/views.py
new file mode 100644
index 000000000..8af2be1ac
--- /dev/null
+++ b/tests/utils/views.py
@@ -0,0 +1,47 @@
+from django.http import HttpResponse
+
+from openslides.utils import views
+
+
+class GetAbsoluteUrl(object):
+ """
+ Generates objects with a get_absolute_url method.
+
+ Helper Class for the tests.
+ """
+ def get_absolute_url(self, link='default'):
+ if link == 'detail':
+ url = 'detail_url'
+ elif link == 'update':
+ url = 'update_url'
+ elif link == 'default':
+ url = 'default_url'
+ return url
+
+
+class LoginMixinView(views.LoginMixin, views.View):
+ def get(self, *args, **kwargs):
+ response = HttpResponse('Well done.')
+ response.status_code = 200
+ return response
+
+
+class PermissionMixinView(views.PermissionMixin, views.View):
+ def get(self, *args, **kwargs):
+ response = HttpResponse('Well done.')
+ response.status_code = 200
+ return response
+
+
+class AjaxMixinView(views.AjaxMixin, views.View):
+ def get_ajax_context(self, **kwargs):
+ return super(AjaxMixinView, self).get_ajax_context(
+ new_context='newer_context', **kwargs)
+
+
+class UrlMixinView(views.UrlMixin, views.View):
+ pass
+
+
+class UrlMixinViewWithObject(views.UrlMixin, views.View):
+ object = GetAbsoluteUrl()