Merge branch 'stable/1.6.x' into 'master'.

Conflicts:
	CHANGELOG
	openslides/agenda/views.py
	openslides/assignment/views.py
	openslides/mediafile/views.py
	openslides/users/views.py
	openslides/utils/views.py
	tests/motion/test_pdf.py
	tests/settings.py
This commit is contained in:
Norman Jäckel 2015-01-02 22:03:34 +01:00
commit dcd8b7a639
20 changed files with 419 additions and 212 deletions

View File

@ -6,6 +6,7 @@ http://openslides.org
Version 2.0.0 (unreleased) Version 2.0.0 (unreleased)
========================== ==========================
[https://github.com/OpenSlides/OpenSlides/milestones/2.0]
Other: Other:
- Changed supported Python version to >= 3.3. - Changed supported Python version to >= 3.3.
@ -15,6 +16,16 @@ Other:
template signals and slides. template signals and slides.
Version 1.7.0 (unreleased)
==========================
[https://github.com/OpenSlides/OpenSlides/milestones/1.7]
Motions:
- Added possibility to hide motions from non staff users in some states.
Other:
- Cleaned up utils.views to increase performance when fetching single objects
from the database for a view (#1378).
Version 1.6.1 (2014-12-08) Version 1.6.1 (2014-12-08)
========================== ==========================

View File

@ -216,6 +216,21 @@ Komplexer Arbeitsablauf
+---------------------+-----------------+---------------+------------+---------------+------------------------+---------------+ +---------------------+-----------------+---------------+------------+---------------+------------------------+---------------+
Experteneinstellung
'''''''''''''''''''
Es kann eingestellt werden, dass ein Antrag in einem bestimmten Status nur
für bestimmte Benutzer sichtbar ist. Die Anpassung kann vor dem ersten
Anlegen der Datenbank in der Datei ``openslides/motion/signals.py`` in der
Funktion ``create_builtin_workflows()`` erfolgen: Dazu beim gewünschten
Status beispielsweise die Zeile
``required_permission_to_see='motion.can_manage_motion',`` einfügen. Die
Anpassung kann aber auch bei bestehender Datenbank erfolgen: Dazu mit einem
entsprechenden Tool direkt auf die Datenbank zugreifen und beim gewünschten
Status das Feld ``required_permission_to_see`` beispielsweise mit der
Zeichenkette ``motion.can_manage_motion`` überschreiben.
Versionierung Versionierung
------------- -------------

View File

@ -185,15 +185,14 @@ class AgendaItemView(SingleObjectMixin, FormView):
return check_permission return check_permission
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
self.object = self.get_object() list_of_speakers = self.get_object().get_list_of_speakers()
list_of_speakers = self.object.get_list_of_speakers()
active_slide = get_active_slide() active_slide = get_active_slide()
active_type = active_slide.get('type', None) active_type = active_slide.get('type', None)
kwargs.update({ kwargs.update({
'object': self.object, 'object': self.get_object(),
'list_of_speakers': list_of_speakers, 'list_of_speakers': list_of_speakers,
'is_on_the_list_of_speakers': Speaker.objects.filter( 'is_on_the_list_of_speakers': Speaker.objects.filter(
item=self.object, begin_time=None, user=self.request.user).exists(), item=self.get_object(), begin_time=None, user=self.request.user).exists(),
'active_type': active_type, 'active_type': active_type,
}) })
return super(AgendaItemView, self).get_context_data(**kwargs) return super(AgendaItemView, self).get_context_data(**kwargs)
@ -208,7 +207,7 @@ class AgendaItemView(SingleObjectMixin, FormView):
return kwargs return kwargs
class SetClosed(RedirectView, SingleObjectMixin): class SetClosed(SingleObjectMixin, RedirectView):
""" """
Close or open an item. Close or open an item.
""" """
@ -220,15 +219,14 @@ class SetClosed(RedirectView, SingleObjectMixin):
def get_ajax_context(self, **kwargs): def get_ajax_context(self, **kwargs):
closed = self.kwargs['closed'] closed = self.kwargs['closed']
if closed: if closed:
link = reverse('item_open', args=[self.object.pk]) link = reverse('item_open', args=[self.get_object().pk])
else: else:
link = reverse('item_close', args=[self.object.pk]) link = reverse('item_close', args=[self.get_object().pk])
return super(SetClosed, self).get_ajax_context(closed=closed, link=link) return super(SetClosed, self).get_ajax_context(closed=closed, link=link)
def pre_redirect(self, request, *args, **kwargs): def pre_redirect(self, request, *args, **kwargs):
self.object = self.get_object()
closed = kwargs['closed'] closed = kwargs['closed']
self.object.set_closed(closed) self.get_object().set_closed(closed)
return super(SetClosed, self).pre_redirect(request, *args, **kwargs) return super(SetClosed, self).pre_redirect(request, *args, **kwargs)
def get_url_name_args(self): def get_url_name_args(self):
@ -247,7 +245,7 @@ class ItemUpdate(UpdateView):
url_name_args = [] url_name_args = []
def get_form_class(self): def get_form_class(self):
if self.object.content_object: if self.get_object().content_object:
form = RelatedItemForm form = RelatedItemForm
else: else:
form = ItemForm form = ItemForm
@ -288,7 +286,7 @@ class ItemDelete(DeleteView):
try: try:
options = self.item_delete_answer_options options = self.item_delete_answer_options
except AttributeError: except AttributeError:
if self.object.children.exists(): if self.get_object().children.exists():
options = [('all', _("Yes, with all child items."))] + self.answer_options options = [('all', _("Yes, with all child items."))] + self.answer_options
else: else:
options = self.answer_options options = self.answer_options
@ -299,13 +297,13 @@ class ItemDelete(DeleteView):
""" """
Deletes the item but not its children. Deletes the item but not its children.
""" """
self.object.delete(with_children=False) self.get_object().delete(with_children=False)
def on_clicked_all(self): def on_clicked_all(self):
""" """
Deletes the item and its children. Deletes the item and its children.
""" """
self.object.delete(with_children=True) self.get_object().delete(with_children=True)
def get_final_message(self): def get_final_message(self):
""" """
@ -314,9 +312,9 @@ class ItemDelete(DeleteView):
# OpenSlidesError (invalid answer) should never be raised here because # OpenSlidesError (invalid answer) should never be raised here because
# this method should only be called if the answer is 'yes' or 'all'. # this method should only be called if the answer is 'yes' or 'all'.
if self.get_answer() == 'yes': if self.get_answer() == 'yes':
message = _('Item %s was successfully deleted.') % html_strong(self.object) message = _('Item %s was successfully deleted.') % html_strong(self.get_object())
else: else:
message = _('Item %s and its children were successfully deleted.') % html_strong(self.object) message = _('Item %s and its children were successfully deleted.') % html_strong(self.get_object())
return message return message
@ -331,18 +329,11 @@ class CreateRelatedAgendaItemView(SingleObjectMixin, RedirectView):
url_name = 'item_overview' url_name = 'item_overview'
url_name_args = [] url_name_args = []
def get(self, request, *args, **kwargs):
"""
Set self.object to the relevant object.
"""
self.object = self.get_object()
return super(CreateRelatedAgendaItemView, self).get(request, *args, **kwargs)
def pre_redirect(self, request, *args, **kwargs): def pre_redirect(self, request, *args, **kwargs):
""" """
Create the agenda item. Create the agenda item.
""" """
self.item = Item.objects.create(content_object=self.object) self.item = Item.objects.create(content_object=self.get_object())
class AgendaNumberingView(QuestionView): class AgendaNumberingView(QuestionView):
@ -390,12 +381,11 @@ class SpeakerAppendView(SingleObjectMixin, RedirectView):
model = Item model = Item
def pre_redirect(self, request, *args, **kwargs): def pre_redirect(self, request, *args, **kwargs):
self.object = self.get_object() if self.get_object().speaker_list_closed:
if self.object.speaker_list_closed:
messages.error(request, _('The list of speakers is closed.')) messages.error(request, _('The list of speakers is closed.'))
else: else:
try: try:
Speaker.objects.add(item=self.object, user=request.user) Speaker.objects.add(item=self.get_object(), user=request.user)
except OpenSlidesError as e: except OpenSlidesError as e:
messages.error(request, e) messages.error(request, e)
else: else:
@ -420,11 +410,10 @@ class SpeakerDeleteView(DeleteView):
return True return True
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
try: if self.get_object() is None:
return super(SpeakerDeleteView, self).get(*args, **kwargs)
except Speaker.DoesNotExist:
messages.error(self.request, _('You are not on the list of speakers.'))
return super(RedirectView, self).get(*args, **kwargs) return super(RedirectView, self).get(*args, **kwargs)
else:
return super().get(*args, **kwargs)
def get_object(self): def get_object(self):
""" """
@ -434,10 +423,22 @@ class SpeakerDeleteView(DeleteView):
object with the request.user as speaker. object with the request.user as speaker.
""" """
try: try:
return Speaker.objects.get(pk=self.kwargs['speaker']) speaker = self._object
except KeyError: except AttributeError:
return Speaker.objects.filter( speaker_pk = self.kwargs.get('speaker')
item=self.kwargs['pk'], user=self.request.user).exclude(weight=None).get() if speaker_pk is not None:
queryset = Speaker.objects.filter(pk=speaker_pk)
else:
queryset = Speaker.objects.filter(
item=self.kwargs['pk'], user=self.request.user).exclude(weight=None)
try:
speaker = queryset.get()
except Speaker.DoesNotExist:
speaker = None
if speaker_pk is not None:
messages.error(self.request, _('You are not on the list of speakers.'))
self._object = speaker
return speaker
def get_url_name_args(self): def get_url_name_args(self):
return [self.kwargs['pk']] return [self.kwargs['pk']]
@ -458,22 +459,21 @@ class SpeakerSpeakView(SingleObjectMixin, RedirectView):
model = Item model = Item
def pre_redirect(self, *args, **kwargs): def pre_redirect(self, *args, **kwargs):
self.object = self.get_object()
try: try:
speaker = Speaker.objects.filter( speaker = Speaker.objects.filter(
user=kwargs['user_id'], user=kwargs['user_id'],
item=self.object, item=self.get_object(),
begin_time=None).get() begin_time=None).get()
except Speaker.DoesNotExist: # TODO: Check the MultipleObjectsReturned error here? except Speaker.DoesNotExist: # TODO: Check the MultipleObjectsReturned error here?
messages.error( messages.error(
self.request, self.request,
_('%(user)s is not on the list of %(item)s.') _('%(user)s is not on the list of %(item)s.')
% {'user': kwargs['user_id'], 'item': self.object}) % {'user': kwargs['user_id'], 'item': self.get_object()})
else: else:
speaker.begin_speach() speaker.begin_speach()
def get_url_name_args(self): def get_url_name_args(self):
return [self.object.pk] return [self.get_object().pk]
class SpeakerEndSpeachView(SingleObjectMixin, RedirectView): class SpeakerEndSpeachView(SingleObjectMixin, RedirectView):
@ -485,21 +485,20 @@ class SpeakerEndSpeachView(SingleObjectMixin, RedirectView):
model = Item model = Item
def pre_redirect(self, *args, **kwargs): def pre_redirect(self, *args, **kwargs):
self.object = self.get_object()
try: try:
speaker = Speaker.objects.filter( speaker = Speaker.objects.filter(
item=self.object, item=self.get_object(),
end_time=None).exclude(begin_time=None).get() end_time=None).exclude(begin_time=None).get()
except Speaker.DoesNotExist: except Speaker.DoesNotExist:
messages.error( messages.error(
self.request, self.request,
_('There is no one speaking at the moment according to %(item)s.') _('There is no one speaking at the moment according to %(item)s.')
% {'item': self.object}) % {'item': self.get_object()})
else: else:
speaker.end_speach() speaker.end_speach()
def get_url_name_args(self): def get_url_name_args(self):
return [self.object.pk] return [self.get_object().pk]
class SpeakerListCloseView(SingleObjectMixin, RedirectView): class SpeakerListCloseView(SingleObjectMixin, RedirectView):
@ -512,12 +511,11 @@ class SpeakerListCloseView(SingleObjectMixin, RedirectView):
url_name = 'item_view' url_name = 'item_view'
def pre_redirect(self, *args, **kwargs): def pre_redirect(self, *args, **kwargs):
self.object = self.get_object() self.get_object().speaker_list_closed = not self.reopen
self.object.speaker_list_closed = not self.reopen self.get_object().save()
self.object.save()
def get_url_name_args(self): def get_url_name_args(self):
return [self.object.pk] return [self.get_object().pk]
class SpeakerChangeOrderView(SingleObjectMixin, RedirectView): class SpeakerChangeOrderView(SingleObjectMixin, RedirectView):
@ -530,17 +528,12 @@ class SpeakerChangeOrderView(SingleObjectMixin, RedirectView):
model = Item model = Item
url_name = 'item_view' url_name = 'item_view'
def pre_redirect(self, args, **kwargs):
self.object = self.get_object()
def pre_post_redirect(self, request, *args, **kwargs): def pre_post_redirect(self, request, *args, **kwargs):
""" """
Reorder the list of speaker. Reorder the list of speaker.
Take the string 'sort_order' from the post-data, and use this order. Take the string 'sort_order' from the post-data, and use this order.
""" """
self.object = self.get_object()
try: try:
with transaction.atomic(): with transaction.atomic():
for (counter, speaker) in enumerate(self.request.POST['sort_order'].split(',')): for (counter, speaker) in enumerate(self.request.POST['sort_order'].split(',')):
@ -549,7 +542,7 @@ class SpeakerChangeOrderView(SingleObjectMixin, RedirectView):
except IndexError: except IndexError:
raise IntegrityError raise IntegrityError
try: try:
speaker = Speaker.objects.filter(item=self.object).get(pk=speaker_pk) speaker = Speaker.objects.filter(item=self.get_object()).get(pk=speaker_pk)
except Speaker.DoesNotExist: except Speaker.DoesNotExist:
raise IntegrityError raise IntegrityError
speaker.weight = counter + 1 speaker.weight = counter + 1
@ -558,7 +551,7 @@ class SpeakerChangeOrderView(SingleObjectMixin, RedirectView):
messages.error(request, _('Could not change order. Invalid data.')) messages.error(request, _('Could not change order. Invalid data.'))
def get_url_name_args(self): def get_url_name_args(self):
return [self.object.pk] return [self.get_object().pk]
class CurrentListOfSpeakersView(RedirectView): class CurrentListOfSpeakersView(RedirectView):

View File

@ -40,31 +40,30 @@ class AssignmentDetail(DetailView):
context['form'] = self.form_class(self.request.POST) context['form'] = self.form_class(self.request.POST)
else: else:
context['form'] = self.form_class() context['form'] = self.form_class()
polls = self.object.poll_set.all() polls = self.get_object().poll_set.all()
if not self.request.user.has_perm('assignment.can_manage_assignment'): if not self.request.user.has_perm('assignment.can_manage_assignment'):
polls = self.object.poll_set.filter(published=True) polls = self.get_object().poll_set.filter(published=True)
vote_results = self.object.vote_results(only_published=True) vote_results = self.get_object().vote_results(only_published=True)
else: else:
polls = self.object.poll_set.all() polls = self.get_object().poll_set.all()
vote_results = self.object.vote_results(only_published=False) vote_results = self.get_object().vote_results(only_published=False)
blocked_candidates = [ blocked_candidates = [
candidate.person for candidate in candidate.person for candidate in
self.object.assignment_candidates.filter(blocked=True)] self.get_object().assignment_candidates.filter(blocked=True)]
context['polls'] = polls context['polls'] = polls
context['vote_results'] = vote_results context['vote_results'] = vote_results
context['blocked_candidates'] = blocked_candidates context['blocked_candidates'] = blocked_candidates
context['user_is_candidate'] = self.object.is_candidate(self.request.user) context['user_is_candidate'] = self.get_object().is_candidate(self.request.user)
return context return context
def post(self, *args, **kwargs): def post(self, *args, **kwargs):
self.object = self.get_object()
if self.request.user.has_perm('assignment.can_nominate_other'): if self.request.user.has_perm('assignment.can_nominate_other'):
form = self.form_class(self.request.POST) form = self.form_class(self.request.POST)
if form.is_valid(): if form.is_valid():
user = form.cleaned_data['candidate'] user = form.cleaned_data['candidate']
try: try:
self.object.run(user, self.request.user) self.get_object().run(user, self.request.user)
except NameError as e: except NameError as e:
messages.error(self.request, e) messages.error(self.request, e)
else: else:
@ -98,18 +97,17 @@ class AssignmentSetStatusView(SingleObjectMixin, RedirectView):
url_name = 'assignment_detail' url_name = 'assignment_detail'
def pre_redirect(self, *args, **kwargs): def pre_redirect(self, *args, **kwargs):
self.object = self.get_object()
status = kwargs.get('status') status = kwargs.get('status')
if status is not None: if status is not None:
try: try:
self.object.set_status(status) self.get_object().set_status(status)
except ValueError as e: except ValueError as e:
messages.error(self.request, e) messages.error(self.request, e)
else: else:
messages.success( messages.success(
self.request, self.request,
_('Election status was set to: %s.') % _('Election status was set to: %s.') %
html_strong(self.object.get_status_display()) html_strong(self.get_object().get_status_display())
) )
@ -134,11 +132,10 @@ class AssignmentRunDeleteView(SingleObjectMixin, RedirectView):
url_name = 'assignment_detail' url_name = 'assignment_detail'
def pre_redirect(self, *args, **kwargs): def pre_redirect(self, *args, **kwargs):
self.object = self.get_object() if self.get_object().status == 'sea' or self.request.user.has_perm(
if self.object.status == 'sea' or self.request.user.has_perm(
"assignment.can_manage_assignment"): "assignment.can_manage_assignment"):
try: try:
self.object.delrun(self.request.user, blocked=True) self.get_object().delrun(self.request.user, blocked=True)
except Exception as e: except Exception as e:
# TODO: only catch relevant exception # TODO: only catch relevant exception
messages.error(self.request, e) messages.error(self.request, e)
@ -165,7 +162,7 @@ class AssignmentRunOtherDeleteView(SingleObjectMixin, QuestionView):
def on_clicked_yes(self): def on_clicked_yes(self):
self._get_person_information() self._get_person_information()
try: try:
self.object.delrun(self.person, blocked=False) self.get_object().delrun(self.person, blocked=False)
except Exception as e: except Exception as e:
# TODO: only catch relevant exception # TODO: only catch relevant exception
self.error = e self.error = e
@ -185,9 +182,8 @@ class AssignmentRunOtherDeleteView(SingleObjectMixin, QuestionView):
return message return message
def _get_person_information(self): def _get_person_information(self):
self.object = self.get_object()
self.person = User.objects.get(pk=self.kwargs.get('user_id')) self.person = User.objects.get(pk=self.kwargs.get('user_id'))
self.is_blocked = self.object.is_blocked(self.person) self.is_blocked = self.get_object().is_blocked(self.person)
class PollCreateView(SingleObjectMixin, RedirectView): class PollCreateView(SingleObjectMixin, RedirectView):
@ -196,8 +192,7 @@ class PollCreateView(SingleObjectMixin, RedirectView):
url_name = 'assignment_detail' url_name = 'assignment_detail'
def pre_redirect(self, *args, **kwargs): def pre_redirect(self, *args, **kwargs):
self.object = self.get_object() self.get_object().gen_poll()
self.object.gen_poll()
messages.success(self.request, _("New ballot was successfully created.")) messages.success(self.request, _("New ballot was successfully created."))
@ -232,15 +227,15 @@ class SetPublishStatusView(SingleObjectMixin, RedirectView):
def pre_redirect(self, *args, **kwargs): def pre_redirect(self, *args, **kwargs):
try: try:
self.object = self.get_object() poll = self.get_object()
except self.model.DoesNotExist: except self.model.DoesNotExist:
messages.error(self.request, _('Ballot ID %d does not exist.') % messages.error(self.request, _('Ballot ID %d does not exist.') %
int(kwargs['poll_id'])) int(kwargs['poll_id']))
else: else:
if self.object.published: if poll.published:
self.object.set_published(False) poll.set_published(False)
else: else:
self.object.set_published(True) poll.set_published(True)
class SetElectedView(SingleObjectMixin, RedirectView): class SetElectedView(SingleObjectMixin, RedirectView):
@ -250,19 +245,18 @@ class SetElectedView(SingleObjectMixin, RedirectView):
allow_ajax = True allow_ajax = True
def pre_redirect(self, *args, **kwargs): def pre_redirect(self, *args, **kwargs):
self.object = self.get_object()
self.person = User.objects.get(pk=kwargs['user_id']) self.person = User.objects.get(pk=kwargs['user_id'])
self.elected = kwargs['elected'] self.elected = kwargs['elected']
self.object.set_elected(self.person, self.elected) self.get_object().set_elected(self.person, self.elected)
def get_ajax_context(self, **kwargs): def get_ajax_context(self, **kwargs):
if self.elected: if self.elected:
link = reverse('assignment_user_not_elected', link = reverse('assignment_user_not_elected',
args=[self.object.id, self.person.person_id]) args=[self.get_object().id, self.person.person_id])
text = _('not elected') text = _('not elected')
else: else:
link = reverse('assignment_user_elected', link = reverse('assignment_user_elected',
args=[self.object.id, self.person.person_id]) args=[self.get_object().id, self.person.person_id])
text = _('elected') text = _('elected')
return {'elected': self.elected, 'link': link, 'text': text} return {'elected': self.elected, 'link': link, 'text': text}
@ -283,7 +277,7 @@ class AssignmentPollDeleteView(DeleteView):
super(AssignmentPollDeleteView, self).pre_post_redirect(request, *args, **kwargs) super(AssignmentPollDeleteView, self).pre_post_redirect(request, *args, **kwargs)
def set_assignment(self): def set_assignment(self):
self.assignment = self.object.assignment self.assignment = self.get_object().assignment
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])

View File

@ -85,7 +85,7 @@ class MediafileUpdateView(MediafileViewMixin, UpdateView):
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)
form_kwargs['initial'].update({'uploader': self.object.uploader.pk}) form_kwargs['initial'].update({'uploader': self.get_object().uploader.pk})
return form_kwargs return form_kwargs
@ -102,7 +102,7 @@ class MediafileDeleteView(DeleteView):
def on_clicked_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.get_object().mediafile.delete()
return super(MediafileDeleteView, self).on_clicked_yes(*args, **kwargs) return super(MediafileDeleteView, self).on_clicked_yes(*args, **kwargs)

View File

@ -472,6 +472,7 @@ class Motion(SlideMixin, AbsoluteUrlMixin, models.Model):
The dictonary contains the following actions. The dictonary contains the following actions.
* see
* update / edit * update / edit
* delete * delete
* create_poll * create_poll
@ -481,9 +482,14 @@ class Motion(SlideMixin, AbsoluteUrlMixin, models.Model):
* reset_state * reset_state
""" """
actions = { actions = {
'update': ((self.is_submitter(person) and 'see': (person.has_perm('motion.can_see_motion') and
self.state.allow_submitter_edit) or (not self.state.required_permission_to_see or
person.has_perm('motion.can_manage_motion')), person.has_perm(self.state.required_permission_to_see) or
self.is_submitter(person))),
'update': (person.has_perm('motion.can_manage_motion') or
(self.is_submitter(person) and
self.state.allow_submitter_edit)),
'delete': person.has_perm('motion.can_manage_motion'), 'delete': person.has_perm('motion.can_manage_motion'),
@ -774,6 +780,16 @@ class State(models.Model):
icon = models.CharField(max_length=255) icon = models.CharField(max_length=255)
"""A string representing the url to the icon-image.""" """A string representing the url to the icon-image."""
required_permission_to_see = models.CharField(max_length=255, blank=True)
"""
A permission string. If not empty, the user has to have this permission to
see a motion in this state.
To use this feature change the database entry of a state object and add
your favourite permission string. You can do this e. g. by editing the
definitions in create_builtin_workflows() in openslides/motion/signals.py.
"""
allow_support = models.BooleanField(default=False) allow_support = models.BooleanField(default=False)
"""If true, persons can support the motion in this state.""" """If true, persons can support the motion in this state."""

View File

@ -12,17 +12,16 @@ from openslides.config.api import config
from openslides.users.models import Group, User # TODO: remove this line from openslides.users.models import Group, User # TODO: remove this line
from openslides.utils.pdf import stylesheet from openslides.utils.pdf import stylesheet
from .models import Category, Motion from .models import Category
# Needed to count the delegates # Needed to count the delegates
# TODO: find another way to do this. # TODO: find another way to do this.
def motions_to_pdf(pdf): def motions_to_pdf(pdf, motions):
""" """
Create a PDF with all motions. Create a PDF with all motions.
""" """
motions = Motion.objects.all()
motions = natsorted(motions, key=attrgetter('identifier')) motions = natsorted(motions, key=attrgetter('identifier'))
all_motion_cover(pdf, motions) all_motion_cover(pdf, motions)
for motion in motions: for motion in motions:

View File

@ -30,8 +30,30 @@ class MotionListView(ListView):
""" """
View, to list all motions. View, to list all motions.
""" """
required_permission = 'motion.can_see_motion'
model = Motion model = Motion
required_permission = 'motion.can_see_motion'
# The template name must be set explicitly because the overridden method
# get_queryset() does not return a QuerySet any more so that Django can't
# generate the template name from the name of the model.
template_name = 'motion/motion_list.html'
# The attribute context_object_name must be set explicitly because the
# overridden method get_queryset() does not return a QuerySet any more so
# that Django can't generate the context_object_name from the name of the
# model.
context_object_name = 'motion_list'
def get_queryset(self, *args, **kwargs):
"""
Returns not a QuerySet but a filtered list of motions. Excludes motions
that the user is not allowed to see.
"""
queryset = super(MotionListView, self).get_queryset(*args, **kwargs)
motions = []
for motion in queryset:
if (not motion.state.required_permission_to_see or
self.request.user.has_perm(motion.state.required_permission_to_see)):
motions.append(motion)
return motions
motion_list = MotionListView.as_view() motion_list = MotionListView.as_view()
@ -40,9 +62,14 @@ class MotionDetailView(DetailView):
""" """
Show one motion. Show one motion.
""" """
required_permission = 'motion.can_see_motion'
model = Motion model = Motion
def check_permission(self, request, *args, **kwargs):
"""
Check if the request.user has the permission to see the motion.
"""
return self.get_object().get_allowed_actions(request.user)['see']
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" """
Return the template context. Return the template context.
@ -53,14 +80,14 @@ class MotionDetailView(DetailView):
version_number = self.kwargs.get('version_number', None) version_number = self.kwargs.get('version_number', None)
if version_number is not None: if version_number is not None:
try: try:
version = self.object.versions.get(version_number=int(version_number)) version = self.get_object().versions.get(version_number=int(version_number))
except MotionVersion.DoesNotExist: except MotionVersion.DoesNotExist:
raise Http404('Version %s not found' % version_number) raise Http404('Version %s not found' % version_number)
else: else:
version = self.object.get_active_version() version = self.get_object().get_active_version()
kwargs.update({ kwargs.update({
'allowed_actions': self.object.get_allowed_actions(self.request.user), 'allowed_actions': self.get_object().get_allowed_actions(self.request.user),
'version': version, 'version': version,
'title': version.title, 'title': version.title,
'text': version.text, 'text': version.text,
@ -299,6 +326,9 @@ class VersionDeleteView(DeleteView):
success_url_name = 'motion_detail' success_url_name = 'motion_detail'
def get_object(self): def get_object(self):
try:
version = self._object
except AttributeError:
try: try:
motion = Motion.objects.get(pk=int(self.kwargs.get('pk'))) motion = Motion.objects.get(pk=int(self.kwargs.get('pk')))
except Motion.DoesNotExist: except Motion.DoesNotExist:
@ -311,10 +341,11 @@ class VersionDeleteView(DeleteView):
raise Http404('Version %s not found.' % self.kwargs.get('version_number')) raise Http404('Version %s not found.' % self.kwargs.get('version_number'))
if version == motion.active_version: if version == motion.active_version:
raise Http404('You can not delete the active version of a motion.') raise Http404('You can not delete the active version of a motion.')
self._object = version
return version return version
def get_url_name_args(self): def get_url_name_args(self):
return (self.object.motion_id, ) return (self.get_object().motion_id, )
version_delete = VersionDeleteView.as_view() version_delete = VersionDeleteView.as_view()
@ -330,12 +361,11 @@ class VersionPermitView(SingleObjectMixin, QuestionView):
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
""" """
Set self.object to a motion. Sets self.version to a motion version.
""" """
self.object = self.get_object()
version_number = self.kwargs.get('version_number', None) version_number = self.kwargs.get('version_number', None)
try: try:
self.version = self.object.versions.get(version_number=int(version_number)) self.version = self.get_object().versions.get(version_number=int(version_number))
except MotionVersion.DoesNotExist: except MotionVersion.DoesNotExist:
raise Http404('Version %s not found.' % version_number) raise Http404('Version %s not found.' % version_number)
return super(VersionPermitView, self).get(*args, **kwargs) return super(VersionPermitView, self).get(*args, **kwargs)
@ -344,7 +374,7 @@ class VersionPermitView(SingleObjectMixin, QuestionView):
""" """
Returns a list with arguments to create the success- and question_url. Returns a list with arguments to create the success- and question_url.
""" """
return [self.object.pk, self.version.version_number] return [self.get_object().pk, self.version.version_number]
def get_question_message(self): def get_question_message(self):
""" """
@ -356,9 +386,9 @@ class VersionPermitView(SingleObjectMixin, QuestionView):
""" """
Activate the version, if the user chooses 'yes'. Activate the version, if the user chooses 'yes'.
""" """
self.object.active_version = self.version self.get_object().active_version = self.version
self.object.save(update_fields=['active_version']) self.get_object().save(update_fields=['active_version'])
self.object.write_log( self.get_object().write_log(
message_list=[ugettext_noop('Version'), message_list=[ugettext_noop('Version'),
' %d ' % self.version.version_number, ' %d ' % self.version.version_number,
ugettext_noop('permitted')], ugettext_noop('permitted')],
@ -371,10 +401,15 @@ class VersionDiffView(DetailView):
""" """
Show diff between two versions of a motion. Show diff between two versions of a motion.
""" """
required_permission = 'motion.can_see_motion'
model = Motion model = Motion
template_name = 'motion/motion_diff.html' template_name = 'motion/motion_diff.html'
def check_permission(self, request, *args, **kwargs):
"""
Check if the request.user has the permission to see the motion.
"""
return self.get_object().get_allowed_actions(request.user)['see']
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" """
Return the template context with versions and html diff strings. Return the template context with versions and html diff strings.
@ -382,8 +417,8 @@ class VersionDiffView(DetailView):
try: try:
rev1 = int(self.request.GET['rev1']) rev1 = int(self.request.GET['rev1'])
rev2 = int(self.request.GET['rev2']) rev2 = int(self.request.GET['rev2'])
version_rev1 = self.object.versions.get(version_number=rev1) version_rev1 = self.get_object().versions.get(version_number=rev1)
version_rev2 = self.object.versions.get(version_number=rev2) version_rev2 = self.get_object().versions.get(version_number=rev2)
diff_text = htmldiff(version_rev1.text, version_rev2.text) diff_text = htmldiff(version_rev1.text, version_rev2.text)
diff_reason = htmldiff(version_rev1.reason, version_rev2.reason) diff_reason = htmldiff(version_rev1.reason, version_rev2.reason)
except (KeyError, ValueError, MotionVersion.DoesNotExist): except (KeyError, ValueError, MotionVersion.DoesNotExist):
@ -417,18 +452,11 @@ class SupportView(SingleObjectMixin, QuestionView):
model = Motion model = Motion
support = True support = True
def get(self, request, *args, **kwargs):
"""
Set self.object to a motion.
"""
self.object = self.get_object()
return super(SupportView, self).get(request, *args, **kwargs)
def check_permission(self, request): def check_permission(self, request):
""" """
Return True if the user can support or unsupport the motion. Else: False. Return True if the user can support or unsupport the motion. Else: False.
""" """
allowed_actions = self.object.get_allowed_actions(request.user) allowed_actions = self.get_object().get_allowed_actions(request.user)
if self.support and not allowed_actions['support']: if self.support and not allowed_actions['support']:
messages.error(request, _('You can not support this motion.')) messages.error(request, _('You can not support this motion.'))
return False return False
@ -457,11 +485,11 @@ class SupportView(SingleObjectMixin, QuestionView):
if self.check_permission(self.request): if self.check_permission(self.request):
user = self.request.user user = self.request.user
if self.support: if self.support:
self.object.support(person=user) self.get_object().support(person=user)
self.object.write_log([ugettext_noop('Motion supported')], user) self.get_object().write_log([ugettext_noop('Motion supported')], user)
else: else:
self.object.unsupport(person=user) self.get_object().unsupport(person=user)
self.object.write_log([ugettext_noop('Motion unsupported')], user) self.get_object().write_log([ugettext_noop('Motion unsupported')], user)
def get_final_message(self): def get_final_message(self):
""" """
@ -484,26 +512,19 @@ class PollCreateView(SingleObjectMixin, RedirectView):
model = Motion model = Motion
url_name = 'motionpoll_detail' url_name = 'motionpoll_detail'
def get(self, request, *args, **kwargs):
"""
Set self.object to a motion.
"""
self.object = self.get_object()
return super(PollCreateView, self).get(request, *args, **kwargs)
def pre_redirect(self, request, *args, **kwargs): def pre_redirect(self, request, *args, **kwargs):
""" """
Create the poll for the motion. Create the poll for the motion.
""" """
self.poll = self.object.create_poll() self.poll = self.get_object().create_poll()
self.object.write_log([ugettext_noop("Poll created")], request.user) self.get_object().write_log([ugettext_noop("Poll created")], request.user)
messages.success(request, _("New vote was successfully created.")) messages.success(request, _("New vote was successfully created."))
def get_redirect_url(self, **kwargs): def get_redirect_url(self, **kwargs):
""" """
Return the URL to the UpdateView of the poll. Return the URL to the UpdateView of the poll.
""" """
return reverse('motionpoll_update', args=[self.object.pk, self.poll.poll_number]) return reverse('motionpoll_update', args=[self.get_object().pk, self.poll.poll_number])
poll_create = PollCreateView.as_view() poll_create = PollCreateView.as_view()
@ -523,16 +544,21 @@ class PollMixin(object):
Use the motion id and the poll_number from the url kwargs to get the Use the motion id and the poll_number from the url kwargs to get the
object. object.
""" """
try:
obj = self._object
except AttributeError:
queryset = MotionPoll.objects.filter( queryset = MotionPoll.objects.filter(
motion=self.kwargs['pk'], motion=self.kwargs['pk'],
poll_number=self.kwargs['poll_number']) poll_number=self.kwargs['poll_number'])
return get_object_or_404(queryset) obj = get_object_or_404(queryset)
self._object = obj
return obj
def get_url_name_args(self): def get_url_name_args(self):
""" """
Return the arguments to create the url to the success_url. Return the arguments to create the url to the success_url.
""" """
return [self.object.motion.pk] return [self.get_object().motion.pk]
class PollUpdateView(PollMixin, PollFormView): class PollUpdateView(PollMixin, PollFormView):
@ -564,7 +590,7 @@ class PollUpdateView(PollMixin, PollFormView):
Write a log message, if the form is valid. Write a log message, if the form is valid.
""" """
value = super(PollUpdateView, self).form_valid(form) value = super(PollUpdateView, self).form_valid(form)
self.object.write_log([ugettext_noop('Poll updated')], self.request.user) self.get_object().write_log([ugettext_noop('Poll updated')], self.request.user)
return value return value
poll_update = PollUpdateView.as_view() poll_update = PollUpdateView.as_view()
@ -582,13 +608,13 @@ class PollDeleteView(PollMixin, DeleteView):
Write a log message, if the form is valid. Write a log message, if the form is valid.
""" """
super(PollDeleteView, self).on_clicked_yes() super(PollDeleteView, self).on_clicked_yes()
self.object.motion.write_log([ugettext_noop('Poll deleted')], self.request.user) self.get_object().motion.write_log([ugettext_noop('Poll deleted')], self.request.user)
def get_redirect_url(self, **kwargs): def get_redirect_url(self, **kwargs):
""" """
Return the URL to the DetailView of the motion. Return the URL to the DetailView of the motion.
""" """
return reverse('motion_detail', args=[self.object.motion.pk]) return reverse('motion_detail', args=[self.get_object().motion.pk])
poll_delete = PollDeleteView.as_view() poll_delete = PollDeleteView.as_view()
@ -601,15 +627,11 @@ class PollPDFView(PollMixin, PDFView):
required_permission = 'motion.can_manage_motion' required_permission = 'motion.can_manage_motion'
top_space = 0 top_space = 0
def get(self, *args, **kwargs):
self.object = self.get_object()
return super(PollPDFView, self).get(*args, **kwargs)
def get_filename(self): def get_filename(self):
""" """
Return the filename for the PDF. Return the filename for the PDF.
""" """
return u'%s%s_%s' % (_("Motion"), str(self.object.poll_number), _("Poll")) return u'%s%s_%s' % (_("Motion"), str(self.get_object().poll_number), _("Poll"))
def get_template(self, buffer): def get_template(self, buffer):
return SimpleDocTemplate( return SimpleDocTemplate(
@ -623,7 +645,7 @@ class PollPDFView(PollMixin, PDFView):
""" """
Append PDF objects. Append PDF objects.
""" """
motion_poll_to_pdf(pdf, self.object) motion_poll_to_pdf(pdf, self.get_object())
poll_pdf = PollPDFView.as_view() poll_pdf = PollPDFView.as_view()
@ -644,26 +666,25 @@ class MotionSetStateView(SingleObjectMixin, RedirectView):
""" """
Save the new state and write a log message. Save the new state and write a log message.
""" """
self.object = self.get_object()
success = False success = False
if self.reset: if self.reset:
self.object.reset_state() self.get_object().reset_state()
success = True success = True
elif self.object.state.id == int(kwargs['state']): elif self.get_object().state.id == int(kwargs['state']):
messages.error(request, _('You can not set the state of the motion. It is already done.')) messages.error(request, _('You can not set the state of the motion. It is already done.'))
elif int(kwargs['state']) not in [state.id for state in self.object.state.next_states.all()]: elif int(kwargs['state']) not in [state.id for state in self.get_object().state.next_states.all()]:
messages.error(request, _('You can not set the state of the motion to %s.') % _(State.objects.get(pk=int(kwargs['state'])).name)) messages.error(request, _('You can not set the state of the motion to %s.') % _(State.objects.get(pk=int(kwargs['state'])).name))
else: else:
self.object.set_state(int(kwargs['state'])) self.get_object().set_state(int(kwargs['state']))
success = True success = True
if success: if success:
self.object.save(update_fields=['state', 'identifier']) self.get_object().save(update_fields=['state', 'identifier'])
self.object.write_log( self.get_object().write_log(
message_list=[ugettext_noop('State changed to'), ' ', self.object.state.name], # TODO: Change string to 'State set to ...' message_list=[ugettext_noop('State changed to'), ' ', self.get_object().state.name], # TODO: Change string to 'State set to ...'
person=self.request.user) person=self.request.user)
messages.success(request, messages.success(request,
_('The state of the motion was set to %s.') _('The state of the motion was set to %s.')
% html_strong(_(self.object.state.name))) % html_strong(_(self.get_object().state.name)))
set_state = MotionSetStateView.as_view() set_state = MotionSetStateView.as_view()
reset_state = MotionSetStateView.as_view(reset=True) reset_state = MotionSetStateView.as_view(reset=True)
@ -680,32 +701,41 @@ class CreateRelatedAgendaItemView(_CreateRelatedAgendaItemView):
Create the agenda item. Create the agenda item.
""" """
super(CreateRelatedAgendaItemView, self).pre_redirect(request, *args, **kwargs) super(CreateRelatedAgendaItemView, self).pre_redirect(request, *args, **kwargs)
self.object.write_log([ugettext_noop('Agenda item created')], self.request.user) self.get_object().write_log([ugettext_noop('Agenda item created')], self.request.user)
create_agenda_item = CreateRelatedAgendaItemView.as_view() create_agenda_item = CreateRelatedAgendaItemView.as_view()
class MotionPDFView(SingleObjectMixin, PDFView): class MotionPDFView(SingleObjectMixin, PDFView):
""" """
Create the PDF for one, or all motions. Create the PDF for one or for all motions.
If self.print_all_motions is True, the view returns a PDF with all motions. If self.print_all_motions is True, the view returns a PDF with all motions.
If self.print_all_motions is False, the view returns a PDF with only one If self.print_all_motions is False, the view returns a PDF with only one
motion. motion.
""" """
required_permission = 'motion.can_see_motion'
model = Motion model = Motion
top_space = 0 top_space = 0
print_all_motions = False print_all_motions = False
def get(self, request, *args, **kwargs): def check_permission(self, request, *args, **kwargs):
""" """
Set self.object to a motion. Checks if the requesting user has the permission to see the motion as
PDF.
""" """
if not self.print_all_motions: if self.print_all_motions:
self.object = self.get_object() is_allowed = request.user.has_perm('motion.can_see_motion')
return super(MotionPDFView, self).get(request, *args, **kwargs) else:
is_allowed = self.get_object().get_allowed_actions(request.user)['see']
return is_allowed
def get_object(self, *args, **kwargs):
if self.print_all_motions:
obj = None
else:
obj = super(MotionPDFView, self).get_object(*args, **kwargs)
return obj
def get_filename(self): def get_filename(self):
""" """
@ -714,10 +744,10 @@ class MotionPDFView(SingleObjectMixin, PDFView):
if self.print_all_motions: if self.print_all_motions:
return _("Motions") return _("Motions")
else: else:
if self.object.identifier: if self.get_object().identifier:
suffix = self.object.identifier.replace(' ', '') suffix = self.get_object().identifier.replace(' ', '')
else: else:
suffix = self.object.title.replace(' ', '_') suffix = self.get_object().title.replace(' ', '_')
suffix = slugify(suffix) suffix = slugify(suffix)
return '%s-%s' % (_("Motion"), suffix) return '%s-%s' % (_("Motion"), suffix)
@ -726,9 +756,14 @@ class MotionPDFView(SingleObjectMixin, PDFView):
Append PDF objects. Append PDF objects.
""" """
if self.print_all_motions: if self.print_all_motions:
motions_to_pdf(pdf) motions = []
for motion in Motion.objects.all():
if (not motion.state.required_permission_to_see or
self.request.user.has_perm(motion.state.required_permission_to_see)):
motions.append(motion)
motions_to_pdf(pdf, motions)
else: else:
motion_to_pdf(pdf, self.object) motion_to_pdf(pdf, self.get_object())
motion_list_pdf = MotionPDFView.as_view(print_all_motions=True) motion_list_pdf = MotionPDFView.as_view(print_all_motions=True)
motion_detail_pdf = MotionPDFView.as_view(print_all_motions=False) motion_detail_pdf = MotionPDFView.as_view(print_all_motions=False)

View File

@ -9,11 +9,11 @@ class PollFormView(FormMixin, TemplateView):
poll_class = None poll_class = None
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.poll = self.object = self.get_object() self.poll = self.get_object()
return super(PollFormView, self).get(request, *args, **kwargs) return super(PollFormView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.poll = self.object = self.get_object() self.poll = self.get_object()
option_forms = self.poll.get_vote_forms(data=self.request.POST) option_forms = self.poll.get_vote_forms(data=self.request.POST)
FormClass = self.get_modelform_class() FormClass = self.get_modelform_class()
@ -55,8 +55,13 @@ class PollFormView(FormMixin, TemplateView):
""" """
Returns the poll object. Raises Http404 if the poll does not exist. Returns the poll object. Raises Http404 if the poll does not exist.
""" """
try:
obj = self._object
except AttributeError:
queryset = self.get_poll_class().objects.filter(pk=self.kwargs['poll_id']) queryset = self.get_poll_class().objects.filter(pk=self.kwargs['poll_id'])
return get_object_or_404(queryset) obj = get_object_or_404(queryset)
self._object = obj
return obj
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(PollFormView, self).get_context_data(**kwargs) context = super(PollFormView, self).get_context_data(**kwargs)

View File

@ -156,13 +156,13 @@ class UserDeleteView(DeleteView):
url_name_args = [] 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.get_object() == self.request.user:
messages.error(request, _("You can not delete yourself.")) messages.error(request, _("You can not delete yourself."))
else: else:
super().pre_redirect(request, *args, **kwargs) super().pre_redirect(request, *args, **kwargs)
def pre_post_redirect(self, request, *args, **kwargs): def pre_post_redirect(self, request, *args, **kwargs):
if self.object == self.request.user: if self.get_object() == self.request.user:
messages.error(self.request, _("You can not delete yourself.")) messages.error(self.request, _("You can not delete yourself."))
else: else:
super().pre_post_redirect(request, *args, **kwargs) super().pre_post_redirect(request, *args, **kwargs)
@ -179,21 +179,20 @@ class SetUserStatusView(SingleObjectMixin, RedirectView):
model = User model = User
def pre_redirect(self, request, *args, **kwargs): def pre_redirect(self, request, *args, **kwargs):
self.object = self.get_object()
action = kwargs['action'] action = kwargs['action']
if action == 'activate': if action == 'activate':
self.object.is_active = True self.get_object().is_active = True
elif action == 'deactivate': elif action == 'deactivate':
if self.object.user == self.request.user: if self.get_object().user == self.request.user:
messages.error(request, _("You can not deactivate yourself.")) messages.error(request, _("You can not deactivate yourself."))
else: else:
self.object.is_active = False self.get_object().is_active = False
self.object.save() self.get_object().save()
return super(SetUserStatusView, self).pre_redirect(request, *args, **kwargs) return super(SetUserStatusView, self).pre_redirect(request, *args, **kwargs)
def get_ajax_context(self, **kwargs): def get_ajax_context(self, **kwargs):
context = super(SetUserStatusView, self).get_ajax_context(**kwargs) context = super(SetUserStatusView, self).get_ajax_context(**kwargs)
context['active'] = self.object.is_active context['active'] = self.get_object().is_active
return context return context
@ -249,19 +248,14 @@ class ResetPasswordView(SingleObjectMixin, QuestionView):
allow_ajax = True allow_ajax = True
question_message = 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()
return super(ResetPasswordView, self).get(request, *args, **kwargs)
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.get_object().id])
def on_clicked_yes(self): def on_clicked_yes(self):
self.object.reset_password() self.get_object().reset_password()
self.object.save()
def get_final_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.get_object())
class GroupListView(ListView): class GroupListView(ListView):
@ -367,12 +361,12 @@ class GroupDeleteView(DeleteView):
""" """
Checks whether the group is protected. Checks whether the group is protected.
""" """
if self.object.pk in [1, 2]: if self.get_object().pk in [1, 2]:
messages.error(self.request, _('You can not delete this group.')) messages.error(self.request, _('You can not delete this group.'))
return True return True
if (not self.request.user.is_superuser and if (not self.request.user.is_superuser and
get_protected_perm() in self.object.permissions.all() and get_protected_perm() in self.get_object().permissions.all() and
not Group.objects.exclude(pk=self.object.pk).filter( not Group.objects.exclude(pk=self.get_object().pk).filter(
permissions__in=[get_protected_perm()], permissions__in=[get_protected_perm()],
user__pk=self.request.user.pk).exists()): user__pk=self.request.user.pk).exists()):
messages.error( messages.error(

View File

@ -11,7 +11,6 @@ from django.http import (HttpResponse, HttpResponseRedirect)
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _, ugettext_lazy from django.utils.translation import ugettext as _, ugettext_lazy
from django.views import generic as django_views from django.views import generic as django_views
from django.views.generic.detail import SingleObjectMixin
from reportlab.lib.units import cm from reportlab.lib.units import cm
from reportlab.platypus import SimpleDocTemplate, Spacer from reportlab.platypus import SimpleDocTemplate, Spacer
@ -163,6 +162,32 @@ class UrlMixin(object):
return value return value
class SingleObjectMixin(django_views.detail.SingleObjectMixin):
"""
Mixin for single objects from the database.
"""
def dispatch(self, *args, **kwargs):
if not hasattr(self, 'object'):
# Save the object not only in the cache but in the public
# attribute self.object because Django expects this later.
# Because get_object() has an internal cache this line is not a
# performance problem.
self.object = self.get_object()
return super().dispatch(*args, **kwargs)
def get_object(self, *args, **kwargs):
"""
Returns the single object from database or cache.
"""
try:
obj = self._object
except AttributeError:
obj = super().get_object(*args, **kwargs)
self._object = obj
return obj
class FormMixin(UrlMixin): class FormMixin(UrlMixin):
""" """
Mixin for views with forms. Mixin for views with forms.
@ -438,15 +463,15 @@ class FormView(PermissionMixin, ExtraContextMixin, FormMixin,
pass pass
class UpdateView(PermissionMixin, ExtraContextMixin, ModelFormMixin, class UpdateView(PermissionMixin, ExtraContextMixin,
django_views.UpdateView): ModelFormMixin, SingleObjectMixin, django_views.UpdateView):
""" """
View to update an model object. View to update an model object.
""" """
def get_success_message(self): def get_success_message(self):
if self.success_message is None: if self.success_message is None:
message = _('%s was successfully modified.') % html_strong(self.object) message = _('%s was successfully modified.') % html_strong(self.get_object())
else: else:
message = self.success_message message = self.success_message
return message return message
@ -456,6 +481,10 @@ class CreateView(PermissionMixin, ExtraContextMixin,
ModelFormMixin, django_views.CreateView): ModelFormMixin, django_views.CreateView):
""" """
View to create a model object. View to create a model object.
Note: This class has a django method get_object() which is different form
the method in openslides.utils.views.SingleObjectMixin. The result
is not cached.
""" """
def get_success_message(self): def get_success_message(self):
@ -473,10 +502,6 @@ class DeleteView(SingleObjectMixin, QuestionView):
success_url = None success_url = None
success_url_name = None success_url_name = None
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
def get_redirect_url(self, **kwargs): def get_redirect_url(self, **kwargs):
""" """
Returns the url on which the delete dialog is shown and the url after Returns the url on which the delete dialog is shown and the url after
@ -506,22 +531,22 @@ class DeleteView(SingleObjectMixin, QuestionView):
""" """
Returns the question for the delete dialog. 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.get_object())
def on_clicked_yes(self): def on_clicked_yes(self):
""" """
Deletes the object. Deletes the object.
""" """
self.object.delete() self.get_object().delete()
def get_final_message(self): def get_final_message(self):
""" """
Prints the success message to the user. Prints the success message to the user.
""" """
return _('%s was successfully deleted.') % html_strong(self.object) return _('%s was successfully deleted.') % html_strong(self.get_object())
class DetailView(PermissionMixin, ExtraContextMixin, django_views.DetailView): class DetailView(PermissionMixin, ExtraContextMixin, SingleObjectMixin, django_views.DetailView):
""" """
View to show an model object. View to show an model object.
""" """

View File

@ -205,8 +205,8 @@ class MediafileTest(TestCase):
response = clients['client_normal_user'].get('/mediafile/1/del/') response = clients['client_normal_user'].get('/mediafile/1/del/')
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
bad_client = Client() bad_client = Client()
response = bad_client.get('/mediafile/2/del/') response = bad_client.get('/mediafile/1/del/')
self.assertRedirects(response, expected_url='/login/?next=/mediafile/2/del/', status_code=302, target_status_code=200) self.assertRedirects(response, expected_url='/login/?next=/mediafile/1/del/', status_code=302, target_status_code=200)
def test_delete_mediafile_get_request_own_file(self): def test_delete_mediafile_get_request_own_file(self):
self.object.uploader = self.vip_user self.object.uploader = self.vip_user

View File

@ -14,6 +14,11 @@ class MotionPDFTest(TestCase):
self.admin_client = Client() self.admin_client = Client()
self.admin_client.login(username='admin', password='admin') self.admin_client.login(username='admin', password='admin')
# Registered
self.registered = User.objects.create_user('registered', 'registered')
self.registered_client = Client()
self.registered_client.login(username='registered', password='registered')
def test_render_nested_list(self): def test_render_nested_list(self):
Motion.objects.create( Motion.objects.create(
title='Test Title chieM6Aing8Eegh9ePhu', title='Test Title chieM6Aing8Eegh9ePhu',
@ -23,3 +28,17 @@ class MotionPDFTest(TestCase):
'<li>Element 2 rel0liiGh0bi3ree6Jei</li></ul>') '<li>Element 2 rel0liiGh0bi3ree6Jei</li></ul>')
response = self.admin_client.get('/motion/1/pdf/') response = self.admin_client.get('/motion/1/pdf/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_get_without_required_permission_from_state(self):
motion = Motion.objects.create(title='motion_title_zthguis8qqespgknme52')
motion.state.required_permission_to_see = 'motion.can_manage_motion'
motion.state.save()
response = self.registered_client.get('/motion/1/pdf/')
self.assertEqual(response.status_code, 403)
def test_get_with_filtered_motion_list(self):
motion = Motion.objects.create(title='motion_title_qwgvzf6487guni0oikcc')
motion.state.required_permission_to_see = 'motion.can_manage_motion'
motion.state.save()
response = self.registered_client.get('/motion/pdf/')
self.assertEqual(response.status_code, 200)

View File

@ -52,6 +52,20 @@ class TestMotionListView(MotionViewTestCase):
def test_get(self): def test_get(self):
self.check_url('/motion/', self.admin_client, 200) self.check_url('/motion/', self.admin_client, 200)
def test_get_with_motion(self):
self.motion1.title = 'motion1_iozaixeeDuMah8sheGhe'
self.motion1.save()
response = self.admin_client.get('/motion/')
self.assertContains(response, 'motion1_iozaixeeDuMah8sheGhe')
def test_get_with_filtered_motion_list(self):
self.motion1.state.required_permission_to_see = 'motion.can_manage_motion'
self.motion1.state.save()
self.motion1.title = 'motion1_djfplquczyxasvvgdnmbr'
self.motion1.save()
response = self.registered_client.get('/motion/')
self.assertNotContains(response, 'motion1_djfplquczyxasvvgdnmbr')
class TestMotionDetailView(MotionViewTestCase): class TestMotionDetailView(MotionViewTestCase):
def test_get(self): def test_get(self):
@ -96,6 +110,21 @@ class TestMotionDetailView(MotionViewTestCase):
self.registered.delete() self.registered.delete()
self.assertNotContains(self.admin_client.get('/motion/1/'), 'registered') self.assertNotContains(self.admin_client.get('/motion/1/'), 'registered')
def test_get_without_required_permission_from_state(self):
self.motion1.state.required_permission_to_see = 'motion.can_manage_motion'
self.motion1.state.save()
self.check_url('/motion/1/', self.admin_client, 200)
self.check_url('/motion/1/', self.registered_client, 403)
self.motion1.set_state(state=State.objects.get(name='permitted'))
self.motion1.save()
self.check_url('/motion/1/', self.registered_client, 200)
def test_get_without_required_permission_from_state_but_by_submitter(self):
self.motion1.state.required_permission_to_see = 'motion.can_manage_motion'
self.motion1.state.save()
self.motion1.add_submitter(self.registered)
self.check_url('/motion/1/', self.registered_client, 200)
class TestMotionDetailVersionView(MotionViewTestCase): class TestMotionDetailVersionView(MotionViewTestCase):
def test_get(self): def test_get(self):
@ -107,6 +136,28 @@ class TestMotionDetailVersionView(MotionViewTestCase):
self.check_url('/motion/1/version/500/', self.admin_client, 404) self.check_url('/motion/1/version/500/', self.admin_client, 404)
class TestMotionVersionDiffView(MotionViewTestCase):
def test_get_without_required_permission_from_state(self):
self.motion1.reason = 'reason1_bnmkjiutufjbnvcde334'
self.motion1.save()
self.motion1.title = 'motion1_bnvhfzqsgxcyvasfr57t'
self.motion1.save(use_version=self.motion1.get_new_version())
response = self.registered_client.get(
'/motion/1/diff/',
{'rev1': '1', 'rev2': '2'})
self.assertNotContains(response, 'At least one version number is not valid.')
self.assertEqual(response.status_code, 200)
self.motion1.state.required_permission_to_see = 'motion.can_manage_motion'
self.motion1.state.save()
response = self.registered_client.get(
'/motion/1/diff/',
{'rev1': '1', 'rev2': '2'})
self.assertEqual(response.status_code, 403)
class TestMotionCreateView(MotionViewTestCase): class TestMotionCreateView(MotionViewTestCase):
url = '/motion/new/' url = '/motion/new/'

View File

@ -25,6 +25,13 @@ DATABASES = {
'HOST': '', 'HOST': '',
'PORT': ''}} 'PORT': ''}}
# Add OpenSlides plugins to this list
INSTALLED_PLUGINS = (
'tests.utils',
)
INSTALLED_APPS += INSTALLED_PLUGINS
# Some other settings # Some other settings
TIME_ZONE = 'Europe/Berlin' TIME_ZONE = 'Europe/Berlin'

8
tests/utils/models.py Normal file
View File

@ -0,0 +1,8 @@
from django.db import models
class DummyModel(models.Model):
"""
Dummy model to test some model views.
"""
title = models.CharField(max_length=255)

View File

@ -0,0 +1,5 @@
<html>
<body>
{{ object.title }}
</body>
</html>

View File

@ -3,6 +3,7 @@ from unittest.mock import patch
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import clear_url_caches from django.core.urlresolvers import clear_url_caches
from django.db import connection, reset_queries
from django.test import RequestFactory from django.test import RequestFactory
from django.test.client import Client from django.test.client import Client
from django.test.utils import override_settings from django.test.utils import override_settings
@ -12,6 +13,7 @@ from openslides.utils.signals import template_manipulation
from openslides.utils.test import TestCase from openslides.utils.test import TestCase
from . import views as test_views from . import views as test_views
from .models import DummyModel
@override_settings(ROOT_URLCONF='tests.utils.urls') @override_settings(ROOT_URLCONF='tests.utils.urls')
@ -192,6 +194,17 @@ class QuestionViewTest(ViewTestCase):
self.assertIn('the question', question) self.assertIn('the question', question)
class DetailViewTest(ViewTestCase):
def test_get_object_cache(self):
with self.settings(DEBUG=True):
DummyModel.objects.create(title='title_ooth8she7yos1Oi8Boh3')
reset_queries()
client = Client()
response = client.get('/dummy_detail_view/1/')
self.assertContains(response, 'title_ooth8she7yos1Oi8Boh3')
self.assertEqual(len(connection.queries), 3)
def set_context(sender, request, context, **kwargs): def set_context(sender, request, context, **kwargs):
""" """
receiver for testing the ExtraContextMixin receiver for testing the ExtraContextMixin

View File

@ -26,4 +26,7 @@ urlpatterns += patterns(
url(r'^permission_mixin3/$', url(r'^permission_mixin3/$',
views.PermissionMixinView.as_view(required_permission='agenda.can_see_agenda')), views.PermissionMixinView.as_view(required_permission='agenda.can_see_agenda')),
url(r'^dummy_detail_view/(?P<pk>\d+)/$',
views.DummyDetailView.as_view()),
) )

View File

@ -2,6 +2,8 @@ from django.http import HttpResponse
from openslides.utils import views from openslides.utils import views
from .models import DummyModel
class GetAbsoluteUrl(object): class GetAbsoluteUrl(object):
""" """
@ -45,3 +47,15 @@ class UrlMixinView(views.UrlMixin, views.View):
class UrlMixinViewWithObject(views.UrlMixin, views.View): class UrlMixinViewWithObject(views.UrlMixin, views.View):
object = GetAbsoluteUrl() object = GetAbsoluteUrl()
class DummyDetailView(views.DetailView):
model = DummyModel
def get_context_data(self, **context):
context = super(DummyDetailView, self).get_context_data(**context)
# Just call get_object() some times to test the cache
self.get_object()
self.get_object()
self.get_object()
return context