Merge pull request #895 from ostcar/utils

Cleaned up utils
This commit is contained in:
Oskar Hahn 2013-10-13 12:37:45 -07:00
commit 840ac34c80
22 changed files with 803 additions and 540 deletions

View File

@ -184,16 +184,12 @@ class SetClosed(RedirectView, SingleObjectMixin):
model = Item model = Item
def get_ajax_context(self, **kwargs): def get_ajax_context(self, **kwargs):
context = super(SetClosed, self).get_ajax_context(**kwargs) closed = self.kwargs['closed']
closed = kwargs['closed']
if closed: if closed:
link = reverse('item_open', args=[self.object.id]) link = reverse('item_open', args=[self.object.pk])
else: else:
link = reverse('item_close', args=[self.object.id]) link = reverse('item_close', args=[self.object.pk])
context.update({ return super(SetClosed, self).get_ajax_context(closed=closed, link=link)
'closed': kwargs['closed'],
'link': link})
return context
def pre_redirect(self, request, *args, **kwargs): def pre_redirect(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
@ -214,6 +210,7 @@ class ItemUpdate(UpdateView):
model = Item model = Item
context_object_name = 'item' context_object_name = 'item'
success_url_name = 'item_overview' success_url_name = 'item_overview'
url_name_args = []
def get_form_class(self): def get_form_class(self):
if self.object.content_object: if self.object.content_object:
@ -233,6 +230,7 @@ class ItemCreate(CreateView):
context_object_name = 'item' context_object_name = 'item'
form_class = ItemForm form_class = ItemForm
success_url_name = 'item_overview' success_url_name = 'item_overview'
url_name_args = []
class ItemDelete(DeleteView): class ItemDelete(DeleteView):

View File

@ -25,11 +25,10 @@ from django.utils.translation import ungettext, ugettext as _
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet
from openslides.utils.template import Tab from openslides.utils.template import Tab
from openslides.utils.utils import gen_confirm_form
from openslides.utils.views import ( from openslides.utils.views import (
CreateView, DeleteView, RedirectView, UpdateView, ListView, PDFView, 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.person import get_person
from openslides.utils.utils import html_strong from openslides.utils.utils import html_strong
from openslides.config.api import config from openslides.config.api import config
@ -150,8 +149,6 @@ class AssignmentRunView(SingleObjectMixin, PermissionMixin, View):
class AssignmentRunDeleteView(SingleObjectMixin, RedirectView): class AssignmentRunDeleteView(SingleObjectMixin, RedirectView):
model = Assignment model = Assignment
url_name = 'assignment_detail' 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): def pre_redirect(self, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
@ -162,54 +159,49 @@ class AssignmentRunDeleteView(SingleObjectMixin, RedirectView):
except Exception, e: except Exception, e:
messages.error(self.request, e) messages.error(self.request, e)
else: 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: else:
messages.error(self.request, _('The candidate list is already closed.')) messages.error(self.request, _('The candidate list is already closed.'))
class AssignmentRunOtherDeleteView(SingleObjectMixin, QuestionMixin, class AssignmentRunOtherDeleteView(SingleObjectMixin, QuestionView):
RedirectView):
model = Assignment model = Assignment
permission_required = 'assignment.can_manage_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): def get_question_message(self):
self._get_person_information(*args, **kwargs) self._get_person_information()
if not self.is_blocked: 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: else:
message = _("Do you really want to unblock %s for the election?") % html_strong(self.person) question = _("Do you really want to unblock %s for the election?") % html_strong(self.person)
gen_confirm_form(self.request, message, reverse('assignment_delother', return question
args=[self.object.pk, kwargs['user_id']]))
def pre_post_redirect(self, *args, **kwargs): def on_clicked_yes(self):
self._get_person_information(*args, **kwargs) self._get_person_information()
if self.get_answer() == 'yes':
self.case_yes()
def get_answer(self):
if 'submit' in self.request.POST:
return 'yes'
def case_yes(self):
try: try:
self.object.delrun(self.person, blocked=False) self.object.delrun(self.person, blocked=False)
except Exception, e: except Exception, e:
messages.error(self.request, e) self.error = e
else: else:
messages.success(self.request, self.get_success_message()) self.error = False
def get_success_message(self): def create_final_message(self):
success_message = _("Candidate %s was withdrawn successfully.") % html_strong(self.person) 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: if self.is_blocked:
success_message = _("%s was unblocked successfully.") % html_strong(self.person) message = _("%s was unblocked successfully.") % html_strong(self.person)
return success_message return message
def _get_person_information(self, *args, **kwargs): def _get_person_information(self):
self.object = self.get_object() 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) self.is_blocked = self.object.is_blocked(self.person)
@ -310,7 +302,7 @@ class AssignmentPollDeleteView(DeleteView):
def get_redirect_url(self, **kwargs): def get_redirect_url(self, **kwargs):
return reverse('assignment_detail', args=[self.assignment.id]) return reverse('assignment_detail', args=[self.assignment.id])
def get_success_message(self): def get_final_message(self):
return _('Ballot was successfully deleted.') return _('Ballot was successfully deleted.')

View File

@ -69,11 +69,9 @@ STATICFILES_DIRS = (
fs2unicode(os.path.join(SITE_ROOT, 'static')), 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 = ( STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.FileSystemFinder',
'openslides.utils.staticfiles.AppDirectoriesFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
) )
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'

View File

@ -40,6 +40,7 @@ class MediafileCreateView(CreateView):
model = Mediafile model = Mediafile
permission_required = 'mediafile.can_upload' permission_required = 'mediafile.can_upload'
success_url_name = 'mediafile_list' success_url_name = 'mediafile_list'
url_name_args = []
def get_form(self, form_class): def get_form(self, form_class):
form_kwargs = self.get_form_kwargs() form_kwargs = self.get_form_kwargs()
@ -71,6 +72,7 @@ class MediafileUpdateView(UpdateView):
permission_required = 'mediafile.can_manage' permission_required = 'mediafile.can_manage'
form_class = MediafileUpdateForm form_class = MediafileUpdateForm
success_url_name = 'mediafile_list' success_url_name = 'mediafile_list'
url_name_args = []
def get_form_kwargs(self, *args, **kwargs): def get_form_kwargs(self, *args, **kwargs):
form_kwargs = super(MediafileUpdateView, self).get_form_kwargs(*args, **kwargs) form_kwargs = super(MediafileUpdateView, self).get_form_kwargs(*args, **kwargs)
@ -86,10 +88,10 @@ class MediafileDeleteView(DeleteView):
permission_required = 'mediafile.can_manage' permission_required = 'mediafile.can_manage'
success_url_name = 'mediafile_list' 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".""" """Deletes the file in the filesystem, if user clicks "Yes"."""
self.object.mediafile.delete() 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): def register_tab(request):

View File

@ -11,7 +11,6 @@
:copyright: (c) 20112013 by the OpenSlides team, see AUTHORS. :copyright: (c) 20112013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from reportlab.platypus import SimpleDocTemplate
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib import messages from django.contrib import messages
@ -20,26 +19,26 @@ from django.db.models import Model
from django.http import Http404, HttpResponseRedirect from django.http import Http404, HttpResponseRedirect
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.translation import ugettext as _, ugettext_lazy, ugettext_noop 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.pdf import stylesheet
from openslides.utils.template import Tab
from openslides.utils.utils import html_strong, htmldiff
from openslides.utils.views import ( from openslides.utils.views import (
TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView, 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 .csv_import import import_motions
from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll,
MotionVersion, State, WorkflowError, Category)
from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin, from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin,
MotionDisableVersioningMixin, MotionCategoryMixin, MotionDisableVersioningMixin, MotionCategoryMixin,
MotionIdentifierMixin, MotionWorkflowMixin, MotionImportForm) 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 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'] 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') return _('%s was successfully deleted.') % _('Motion')
motion_delete = MotionDeleteView.as_view() motion_delete = MotionDeleteView.as_view()
@ -326,21 +325,20 @@ class VersionDeleteView(DeleteView):
raise Http404('You can not delete the active version of a motion.') raise Http404('You can not delete the active version of a motion.')
return version return version
def get_success_url_name_args(self): def get_url_name_args(self):
return (self.object.motion_id, ) return (self.object.motion_id, )
version_delete = VersionDeleteView.as_view() version_delete = VersionDeleteView.as_view()
class VersionPermitView(SingleObjectMixin, QuestionMixin, RedirectView): class VersionPermitView(SingleObjectMixin, QuestionView):
""" """
View to permit a version of a motion. View to permit a version of a motion.
""" """
model = Motion model = Motion
question_url_name = 'motion_version_detail' final_message = ugettext_lazy('Version successfully permitted.')
success_url_name = 'motion_version_detail'
success_message = ugettext_lazy('Version successfully permitted.')
permission_required = 'motion.can_manage_motion' permission_required = 'motion.can_manage_motion'
question_url_name = 'motion_version_detail'
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
""" """
@ -360,13 +358,13 @@ class VersionPermitView(SingleObjectMixin, QuestionMixin, RedirectView):
""" """
return [self.object.pk, self.version.version_number] 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 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 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'. Activate the version, if the user chooses 'yes'.
""" """
@ -418,7 +416,7 @@ class VersionDiffView(DetailView):
version_diff = VersionDiffView.as_view() version_diff = VersionDiffView.as_view()
class SupportView(SingleObjectMixin, QuestionMixin, RedirectView): class SupportView(SingleObjectMixin, QuestionView):
""" """
View to support or unsupport a motion. View to support or unsupport a motion.
@ -452,7 +450,7 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
else: else:
return True return True
def get_question(self): def get_question_message(self):
""" """
Return the question string. Return the question string.
""" """
@ -461,7 +459,7 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
else: else:
return _('Do you really want to unsupport this motion?') 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. 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.unsupport(person=user)
self.object.write_log([ugettext_noop('Motion unsupported')], user) self.object.write_log([ugettext_noop('Motion unsupported')], user)
def get_success_message(self): def get_final_message(self):
""" """
Return the success message. Return the success message.
""" """
@ -486,12 +484,6 @@ class SupportView(SingleObjectMixin, QuestionMixin, RedirectView):
else: else:
return _("You have unsupported this motion successfully.") 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_support = SupportView.as_view(support=True)
motion_unsupport = SupportView.as_view(support=False) motion_unsupport = SupportView.as_view(support=False)
@ -558,6 +550,7 @@ class PollUpdateView(PollMixin, PollFormView):
""" """
View to update a MotionPoll. View to update a MotionPoll.
""" """
poll_class = MotionPoll poll_class = MotionPoll
""" """
Poll Class to use for this view. Poll Class to use for this view.
@ -595,11 +588,11 @@ class PollDeleteView(PollMixin, DeleteView):
model = MotionPoll model = MotionPoll
def case_yes(self): def on_clicked_yes(self):
""" """
Write a log message, if the form is valid. 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) self.object.motion.write_log([ugettext_noop('Poll deleted')], self.request.user)
def get_redirect_url(self, **kwargs): def get_redirect_url(self, **kwargs):

View File

@ -138,11 +138,11 @@ class Group(SlideMixin, PersonMixin, Person, DjangoGroup):
Return the URL to the user group. Return the URL to the user group.
""" """
if link == 'detail' or link == 'view': 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': 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': 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) return super(Group, self).get_absolute_url(link)

View File

@ -40,7 +40,7 @@ from openslides.utils.utils import (
template, delete_default_permissions, html_strong) template, delete_default_permissions, html_strong)
from openslides.utils.views import ( from openslides.utils.views import (
FormView, PDFView, CreateView, UpdateView, DeleteView, PermissionMixin, FormView, PDFView, CreateView, UpdateView, DeleteView, PermissionMixin,
RedirectView, SingleObjectMixin, ListView, QuestionMixin, DetailView) RedirectView, SingleObjectMixin, ListView, QuestionView, DetailView)
from openslides.config.api import config from openslides.config.api import config
from openslides.projector.projector import Widget from openslides.projector.projector import Widget
@ -155,6 +155,7 @@ class UserDeleteView(DeleteView):
permission_required = 'participant.can_manage_participant' permission_required = 'participant.can_manage_participant'
model = User model = User
success_url_name = 'user_overview' success_url_name = 'user_overview'
url_name_args = []
def pre_redirect(self, request, *args, **kwargs): def pre_redirect(self, request, *args, **kwargs):
if self.object == self.request.user: if self.object == self.request.user:
@ -335,14 +336,14 @@ class UserImportView(FormView):
return super(UserImportView, self).form_valid(form) 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. Set the Passwort for a user to his default password.
""" """
permission_required = 'participant.can_manage_participant' permission_required = 'participant.can_manage_participant'
model = User model = User
allow_ajax = True 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): def get(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
@ -351,10 +352,10 @@ class ResetPasswordView(SingleObjectMixin, QuestionMixin, RedirectView):
def get_redirect_url(self, **kwargs): def get_redirect_url(self, **kwargs):
return reverse('user_edit', args=[self.object.id]) return reverse('user_edit', args=[self.object.id])
def case_yes(self): def on_clicked_yes(self):
self.object.reset_password() 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) 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' permission_required = 'participant.can_manage_participant'
model = Group model = Group
success_url_name = 'user_group_overview' success_url_name = 'user_group_overview'
url_name_args = []
def pre_redirect(self, request, *args, **kwargs): def pre_redirect(self, request, *args, **kwargs):
if not self.is_protected_from_deleting(): if not self.is_protected_from_deleting():
@ -466,14 +468,17 @@ def login(request):
try: try:
admin = User.objects.get(pk=1) admin = User.objects.get(pk=1)
if admin.check_password(admin.default_password): 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'] = _( extra_content['first_time_message'] = _(
"Installation was successfully! Use %(user)s " "Installation was successfully! Use %(user)s "
"(password: %(password)s) for first login.<br>" "(password: %(password)s) for first login.<br>"
"<strong>Important:</strong> Please change the password after " "<strong>Important:</strong> Please change the password after "
"first login! Otherwise this message still appears for " "first login! Otherwise this message still appears for "
"everyone and could be a security risk.") % { "everyone and could be a security risk.") % user_data
'user': html_strong(admin.username),
'password': html_strong(admin.default_password)}
extra_content['next'] = reverse('password_change') extra_content['next'] = reverse('password_change')
except User.DoesNotExist: except User.DoesNotExist:
pass pass

View File

@ -14,7 +14,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _, ugettext_noop 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): class BaseOption(models.Model):

View File

@ -14,10 +14,10 @@ from django.http import HttpResponseRedirect
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.core.exceptions import ImproperlyConfigured 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 poll_class = None
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):

View File

@ -23,7 +23,7 @@ from django.shortcuts import redirect
from django.template import RequestContext from django.template import RequestContext
from django.utils.translation import ugettext as _ 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 ( from openslides.utils.views import (
TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin) TemplateView, RedirectView, CreateView, UpdateView, DeleteView, AjaxMixin)
from openslides.config.api import config from openslides.config.api import config

View File

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

View File

@ -6,7 +6,7 @@
Modelfields for OpenSlides Modelfields for OpenSlides
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
@ -14,6 +14,10 @@ from django.db import models
class MinMaxIntegerField(models.IntegerField): 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): def __init__(self, min_value=None, max_value=None, *args, **kwargs):
self.min_value, self.max_value = min_value, max_value self.min_value, self.max_value = min_value, max_value
super(MinMaxIntegerField, self).__init__(*args, **kwargs) super(MinMaxIntegerField, self).__init__(*args, **kwargs)

0
openslides/utils/pdf.py Executable file → Normal file
View File

View File

@ -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

View File

@ -10,11 +10,11 @@
:license: GNU GPL, see LICENSE for more details. :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): class Tab(object):
"""
Entry for the main menu of OpenSlides.
"""
def __init__(self, title='', app='', stylefile='', url='', permission=True, selected=False): def __init__(self, title='', app='', stylefile='', url='', permission=True, selected=False):
self.title = title self.title = title
self.app = app self.app = app
@ -22,62 +22,3 @@ class Tab(object):
self.url = url self.url = url
self.permission = permission self.permission = permission
self.selected = selected 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)

View File

@ -11,54 +11,12 @@
""" """
import difflib import difflib
import json
import sys
from django.contrib import messages
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.core.context_processors import csrf from django.shortcuts import render_to_response
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseForbidden
from django.shortcuts import render_to_response, redirect
from django.template import RequestContext 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 from .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
<form action="%s" method="post">
<input type="hidden" value="%s" name="csrfmiddlewaretoken">
<button type="submit" name="submit" class="btn btn-mini">%s</button>
<button name="cancel" class="btn btn-mini">%s</button>
</form>
"""
% (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)
def template(template_name): def template(template_name):
@ -67,6 +25,8 @@ def template(template_name):
Deprecated. Use class based views instead. 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 renderer(func):
def wrapper(request, *args, **kwargs): def wrapper(request, *args, **kwargs):
output = func(request, *args, **kwargs) output = func(request, *args, **kwargs)
@ -85,36 +45,12 @@ def template(template_name):
return renderer 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): def delete_default_permissions(**kwargs):
""" """
Deletes the permissions, django creates by default for the admin. 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(): for p in Permission.objects.all():
if (p.codename.startswith('add') or if (p.codename.startswith('add') or
p.codename.startswith('delete') or p.codename.startswith('delete') or
@ -122,29 +58,26 @@ def delete_default_permissions(**kwargs):
p.delete() 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): def html_strong(string):
"""
Returns the text wrapped in an HTML-Strong element.
"""
return u"<strong>%s</strong>" % string return u"<strong>%s</strong>" % string
def htmldiff(text1, text2): 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) diff = difflib.HtmlDiff(wrapcolumn=60)
return diff.make_table(text1.splitlines(), text2.splitlines()) 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: try:
return int(object) return int(var)
except TypeError: except (TypeError, ValueError):
return None return None

View File

@ -6,7 +6,7 @@
Views for OpenSlides. Views for OpenSlides.
:copyright: 2011, 2012 by OpenSlides team, see AUTHORS. :copyright: 20112013 by OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
@ -15,257 +15,280 @@ from cStringIO import StringIO
from reportlab.platypus import SimpleDocTemplate, Spacer from reportlab.platypus import SimpleDocTemplate, Spacer
from reportlab.lib.units import cm from reportlab.lib.units import cm
from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required 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 from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.conf import settings
from django.dispatch import receiver from django.dispatch import receiver
from django.http import HttpResponseServerError, HttpResponse, HttpResponseRedirect from django.http import (HttpResponse, HttpResponseRedirect,
from django.utils.decorators import method_decorator HttpResponseServerError)
from django.utils.translation import ugettext as _, ugettext_lazy
from django.utils.importlib import import_module
from django.template import RequestContext from django.template import RequestContext
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.views.generic import ( from django.utils.decorators import method_decorator
TemplateView as _TemplateView, from django.utils.importlib import import_module
RedirectView as _RedirectView, from django.utils.translation import ugettext as _
UpdateView as _UpdateView, from django.utils.translation import ugettext_lazy
CreateView as _CreateView, from django.views import generic as django_views
View as _View,
FormView as _FormView,
ListView as _ListView,
DetailView as _DetailView,
)
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.views.generic.list import TemplateResponseMixin 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 .pdf import firstPage, laterPages
from openslides.utils.signals import template_manipulation from .signals import template_manipulation
from openslides.utils.pdf import firstPage, laterPages from .utils import html_strong
NO_PERMISSION_REQUIRED = 'No permission required' NO_PERMISSION_REQUIRED = 'No permission required'
View = _View View = django_views.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
class LoginMixin(object): class LoginMixin(object):
"""
Mixin for Views, that only can be viseted from users how are logedin.
"""
@method_decorator(login_required) @method_decorator(login_required)
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
"""
Check if the user is loged in.
"""
return super(LoginMixin, self).dispatch(request, *args, **kwargs) return super(LoginMixin, self).dispatch(request, *args, **kwargs)
class PermissionMixin(object): 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 permission_required = NO_PERMISSION_REQUIRED
def has_permission(self, request, *args, **kwargs): def has_permission(self, request, *args, **kwargs):
"""
Checks if the user has the required permission.
"""
if self.permission_required == NO_PERMISSION_REQUIRED: if self.permission_required == NO_PERMISSION_REQUIRED:
return True return True
else: else:
return request.user.has_perm(self.permission_required) return request.user.has_perm(self.permission_required)
def dispatch(self, request, *args, **kwargs): 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 self.has_permission(request, *args, **kwargs):
if not request.user.is_authenticated(): if not request.user.is_authenticated():
path = request.get_full_path() path = request.get_full_path()
return HttpResponseRedirect( return HttpResponseRedirect(
"%s?next=%s" % (settings.LOGIN_URL, path)) "%s?next=%s" % (settings.LOGIN_URL, path))
else: else:
return render_to_forbidden(request) raise PermissionDenied
return super(PermissionMixin, self).dispatch(request, *args, **kwargs) return super(PermissionMixin, self).dispatch(request, *args, **kwargs)
class AjaxMixin(object): class AjaxMixin(object):
"""
Mixin to response to an ajax request with an json object.
"""
def get_ajax_context(self, **kwargs): 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): 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): 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): def get_context_data(self, **kwargs):
"""
Sends the signal.
"""
context = super(ExtraContextMixin, self).get_context_data(**kwargs) context = super(ExtraContextMixin, self).get_context_data(**kwargs)
context.setdefault('extra_javascript', [])
template_manipulation.send( template_manipulation.send(
sender=self.__class__, request=self.request, context=context) sender=self.__class__, request=self.request, context=context)
return context return context
class UrlMixin(object): 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 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_url = None
success_message = None
apply_url_name = None
apply_url = None apply_url = None
error_message = ugettext_lazy('Please check the form for errors.')
def get_apply_url(self): def get_apply_url(self):
if self.apply_url_name: """
return reverse(self.apply_url_name, args=self.get_url_name_args()) Returns the url when the user clicks on 'apply'.
elif self.apply_url: """
return self.apply_url return self.get_url(self.apply_url_name, self.apply_url,
else: args=self.get_url_name_args(),
try: use_absolute_url_link='update')
return self.object.get_absolute_url('edit')
except AttributeError:
raise ImproperlyConfigured(
"No URL to redirect to. Provide an apply_url_name.")
def get_success_url(self): 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: Redirects to get_apply_url if self.use_apply is True
return reverse(self.success_url_name, args=self.get_url_name_args()) """
elif self.success_url: if self.use_apply and 'apply' in self.request.POST:
return self.success_url value = self.get_apply_url()
else: else:
try: value = self.get_url(self.success_url_name, self.success_url,
return self.object.get_absolute_url() args=self.get_url_name_args())
except AttributeError: return value
raise ImproperlyConfigured(
"No URL to redirect to. Either provide an URL or define "
"a get_absolute_url method of the model.")
def get_url_name_args(self): def form_valid(self, form):
""" value = super(FormMixin, self).form_valid(form)
Returns the arguments for the url name. Default is an empty list. messages.success(self.request, self.get_success_message())
""" return value
if self.url_name_args is not None:
return self.url_name_args
return []
def form_invalid(self, form):
class QuestionMixin(object): value = super(FormMixin, self).form_invalid(form)
""" messages.error(self.request, self.get_error_message())
Mixin for questions to the requesting user. return value
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([
'<button type="submit" class="btn btn-mini" name="%s">%s</button>' % (option[0], unicode(option[1]))
for option in self.get_answer_options()])
messages.warning(
self.request,
"""
%(message)s
<form action="%(url)s" method="post">
<input type="hidden" value="%(csrf)s" name="csrfmiddlewaretoken">
%(option_fields)s
</form>
""" % {'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 get_success_message(self): def get_success_message(self):
return self.success_message 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 pass
class ListView(PermissionMixin, SetCookieMixin, ExtraContextMixin, _ListView): class ListView(PermissionMixin, ExtraContextMixin, django_views.ListView):
"""
View to show a list of model objects.
"""
pass pass
class AjaxView(PermissionMixin, AjaxMixin, View): class AjaxView(PermissionMixin, AjaxMixin, View):
"""
View for ajax requests.
"""
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return self.ajax_get(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. View to redirect to another url.
@ -277,12 +300,19 @@ class RedirectView(PermissionMixin, AjaxMixin, _RedirectView):
permanent = False permanent = False
allow_ajax = False allow_ajax = False
url_name = None url_name = None
url_name_args = None
def pre_redirect(self, request, *args, **kwargs): 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 pass
def pre_post_redirect(self, request, *args, **kwargs): def pre_post_redirect(self, request, *args, **kwargs):
"""
Called before the redirect, if it is a post request.
"""
pass pass
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -291,62 +321,143 @@ class RedirectView(PermissionMixin, AjaxMixin, _RedirectView):
elif request.method == 'POST': elif request.method == 'POST':
self.pre_post_redirect(request, *args, **kwargs) 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 self.ajax_get(request, *args, **kwargs)
return super(RedirectView, self).get(request, *args, **kwargs) return super(RedirectView, self).get(request, *args, **kwargs)
def get_redirect_url(self, **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: 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 Prints the question in a GET request.
or [self.object.pk] if this exist.
""" """
if self.url_name_args is not None: self.confirm_form()
return self.url_name_args
try: def get_question_message(self):
return [self.object.pk] """
except AttributeError: Returns the question.
return [] """
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([
'<button type="submit" class="btn btn-mini" name="%s">%s</button>'
% (option[0], unicode(option[1]))
for option in self.get_answer_options()])
messages.warning(
self.request,
"""
%(message)s
<form action="%(url)s" method="post">
<input type="hidden" value="%(csrf)s" name="csrfmiddlewaretoken">
%(option_fields)s
</form>
""" % {'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): class FormView(PermissionMixin, ExtraContextMixin, FormMixin,
def form_invalid(self, form): django_views.FormView):
messages.error(self.request, _('Please check the form for errors.')) """
return super(FormView, self).form_invalid(form) View for forms.
"""
pass
class ModelFormMixin(object): class UpdateView(PermissionMixin, ExtraContextMixin,
def form_valid(self, form): ModelFormMixin, django_views.UpdateView):
self.object = form.save(commit=False) """
self.manipulate_object(form) View to update an model object.
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
def get_success_message(self): def get_success_message(self):
if self.success_message is None: if self.success_message is None:
@ -356,18 +467,11 @@ class UpdateView(PermissionMixin, UrlMixin, ExtraContextMixin,
return message return message
class CreateView(PermissionMixin, UrlMixin, ExtraContextMixin, class CreateView(PermissionMixin, ExtraContextMixin,
ModelFormMixin, _CreateView): ModelFormMixin, django_views.CreateView):
success_message = None """
View to create a model object.
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
def get_success_message(self): def get_success_message(self):
if self.success_message is None: if self.success_message is None:
@ -377,8 +481,12 @@ class CreateView(PermissionMixin, UrlMixin, ExtraContextMixin,
return message return message
class DeleteView(SingleObjectMixin, QuestionMixin, RedirectView): class DeleteView(SingleObjectMixin, QuestionView):
question_url_name = None """
View to delete an model object.
"""
success_url = None
success_url_name = None success_url_name = None
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
@ -386,32 +494,53 @@ class DeleteView(SingleObjectMixin, QuestionMixin, RedirectView):
return super(DeleteView, self).get(request, *args, **kwargs) return super(DeleteView, self).get(request, *args, **kwargs)
def get_redirect_url(self, **kwargs): def get_redirect_url(self, **kwargs):
if self.question_url_name is None and (self.request.method == 'GET' or """
self.get_answer() == 'no'): Returns the url on which the delete dialog is shown and the url after
return self.object.get_absolute_url() the deleten.
else:
return super(DeleteView, self).get_redirect_url(**kwargs)
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) 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() 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) return _('%s was successfully deleted.') % html_strong(self.object)
def get_url_name_args(self):
return []
class DetailView(PermissionMixin, ExtraContextMixin, django_views.DetailView):
class DetailView(PermissionMixin, ExtraContextMixin, _DetailView): """
def get(self, request, *args, **kwargs): View to show an model object.
self.object = self.get_object() """
return super(DetailView, self).get(request, *args, **kwargs) pass
class PDFView(PermissionMixin, View): class PDFView(PermissionMixin, View):
"""
View to generate an PDF.
"""
filename = _('undefined-filename') filename = _('undefined-filename')
top_space = 3 top_space = 3
document_title = None document_title = None
@ -480,6 +609,8 @@ def send_register_tab(sender, request, context, **kwargs):
extra_stylefiles = context['extra_stylefiles'] extra_stylefiles = context['extra_stylefiles']
else: else:
extra_stylefiles = [] extra_stylefiles = []
context.setdefault('extra_javascript', [])
# TODO: Do not go over the filesystem by any request # TODO: Do not go over the filesystem by any request
for app in settings.INSTALLED_APPS: for app in settings.INSTALLED_APPS:
try: try:

View File

@ -370,14 +370,14 @@ class TestMotionDeleteView(MotionViewTestCase):
self.assertRedirects(response, '/motion/2/') self.assertRedirects(response, '/motion/2/')
def test_admin(self): 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/') self.assertRedirects(response, '/motion/')
def test_delegate(self): 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) self.assertEqual(response.status_code, 403)
motion = Motion.objects.get(pk=2).add_submitter(self.delegate) 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) self.assertEqual(response.status_code, 403)
@ -394,7 +394,7 @@ class TestVersionPermitView(MotionViewTestCase):
def test_post(self): def test_post(self):
new_version = self.motion1.get_last_version() new_version = self.motion1.get_last_version()
response = self.admin_client.post('/motion/1/version/2/permit/', {'yes': 1}) 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) self.assertEqual(self.motion1.get_active_version(), new_version)
def test_activate_old_version(self): def test_activate_old_version(self):

25
tests/utils/test_utils.py Normal file
View File

@ -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: 20112013 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'), '<strong>some text</strong>')
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))

210
tests/utils/test_views.py Normal file
View File

@ -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: 20112013 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?<formaction="/"method="post">'
## '<inputtype="hidden"value="NOTPROVIDED"name="csrfmiddlewaretoken">' '<buttontype="submit"class="btnbtn-mini"name="yes">Yes</button>'
## '<buttontype="submit"class="btnbtn-mini"name="no">No</button></form>')
def set_context(sender, request, context, **kwargs):
"""
receiver for testing the ExtraContextMixin
"""
context.update({'new_context': 'some new context'})

29
tests/utils/urls.py Normal file
View File

@ -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<arg>\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')),
)

47
tests/utils/views.py Normal file
View File

@ -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()