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
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):

View File

@ -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.')

View File

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

View File

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

View File

@ -11,7 +11,6 @@
:copyright: (c) 20112013 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):

View File

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

View File

@ -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.<br>"
"<strong>Important:</strong> 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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@
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.
"""
@ -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)

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.
"""
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)

View File

@ -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
<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)
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"<strong>%s</strong>" % 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

View File

@ -6,7 +6,7 @@
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.
"""
@ -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([
'<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 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([
'<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):
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:

View File

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

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