diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index 99a72f390..dc9a030ae 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -17,10 +17,11 @@ from openslides.projector.api import (get_active_slide, reset_countdown, update_projector, update_projector_overlay) from openslides.projector.models import SlideMixin from openslides.utils.exceptions import OpenSlidesError +from openslides.utils.models import AbsoluteUrlMixin from openslides.utils.person.models import PersonField -class Item(SlideMixin, MPTTModel): +class Item(SlideMixin, AbsoluteUrlMixin, MPTTModel): """ An Agenda Item @@ -296,7 +297,7 @@ class SpeakerManager(models.Manager): return self.create(item=item, person=person, weight=weight + 1) -class Speaker(models.Model): +class Speaker(AbsoluteUrlMixin, models.Model): """ Model for the Speaker list. """ @@ -346,10 +347,13 @@ class Speaker(models.Model): def get_absolute_url(self, link='detail'): if link == 'detail': - return self.person.get_absolute_url('detail') - if link == 'delete': - return reverse('agenda_speaker_delete', - args=[self.item.pk, self.pk]) + url = self.person.get_absolute_url('detail') + elif link == 'delete': + url = reverse('agenda_speaker_delete', + args=[self.item.pk, self.pk]) + else: + url = super(Speaker, self).get_absolute_url(link) + return url def check_and_update_projector(self): """ diff --git a/openslides/assignment/models.py b/openslides/assignment/models.py index 27ca2d221..204e1ef31 100644 --- a/openslides/assignment/models.py +++ b/openslides/assignment/models.py @@ -11,6 +11,7 @@ from openslides.poll.models import (BaseOption, BasePoll, BaseVote, CollectInvalid, CollectVotesCast, PublishPollMixin) from openslides.projector.models import RelatedModelMixin, SlideMixin +from openslides.utils.models import AbsoluteUrlMixin from openslides.utils.person import PersonField from openslides.utils.utils import html_strong @@ -37,7 +38,7 @@ class AssignmentCandidate(RelatedModelMixin, models.Model): return self.assignment -class Assignment(SlideMixin, models.Model): +class Assignment(SlideMixin, AbsoluteUrlMixin, models.Model): slide_callback_name = 'assignment' STATUS = ( @@ -68,13 +69,15 @@ class Assignment(SlideMixin, models.Model): return self.name def get_absolute_url(self, link='detail'): - if link == 'detail' or link == 'view': - return reverse('assignment_detail', args=[str(self.id)]) - if link == 'update' or link == 'update': - return reverse('assignment_update', args=[str(self.id)]) - if link == 'delete': - return reverse('assignment_delete', args=[str(self.id)]) - return super(Assignment, self).get_absolute_url(link) + if link == 'detail': + url = reverse('assignment_detail', args=[str(self.pk)]) + elif link == 'update': + url = reverse('assignment_update', args=[str(self.pk)]) + elif link == 'delete': + url = reverse('assignment_delete', args=[str(self.pk)]) + else: + url = super(Assignment, self).get_absolute_url(link) + return url def get_slide_context(self, **context): context.update({ @@ -250,7 +253,7 @@ class AssignmentOption(BaseOption): class AssignmentPoll(RelatedModelMixin, CollectInvalid, CollectVotesCast, - PublishPollMixin, BasePoll): + PublishPollMixin, AbsoluteUrlMixin, BasePoll): option_class = AssignmentOption assignment = models.ForeignKey(Assignment, related_name='poll_set') @@ -259,12 +262,14 @@ class AssignmentPoll(RelatedModelMixin, CollectInvalid, CollectVotesCast, def __unicode__(self): return _("Ballot %d") % self.get_ballot() - @models.permalink - def get_absolute_url(self, link='detail'): - if link == 'view' or link == 'detail' or link == 'update': - return ('assignment_poll_view', [str(self.pk)]) - if link == 'delete': - return ('assignment_poll_delete', [str(self.pk)]) + def get_absolute_url(self, link='update'): + if link == 'update': + url = reverse('assignment_poll_view', args=[str(self.pk)]) + elif link == 'delete': + url = reverse('assignment_poll_delete', args=[str(self.pk)]) + else: + url = super(AssignmentPoll, self).get_absolute_url(link) + return url def get_assignment(self): return self.assignment diff --git a/openslides/core/templatetags/tags.py b/openslides/core/templatetags/tags.py index a0e4d05d3..a35424e90 100644 --- a/openslides/core/templatetags/tags.py +++ b/openslides/core/templatetags/tags.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -import warnings - from django import template from django.utils.translation import ugettext as _ @@ -26,16 +24,6 @@ def trans(value): return _(value) -@register.simple_tag -def model_url(object, link='view'): - warnings.warn("model_url is deprecated; use absolute_url instead", - DeprecationWarning) - try: - return object.get_absolute_url(link) - except ValueError: - return '' - - @register.filter def absolute_url(model, link=None): """ diff --git a/openslides/mediafile/models.py b/openslides/mediafile/models.py index 28ec44dca..555cc4f3d 100644 --- a/openslides/mediafile/models.py +++ b/openslides/mediafile/models.py @@ -8,10 +8,11 @@ from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_lazy, ugettext_noop from openslides.projector.models import SlideMixin +from openslides.utils.models import AbsoluteUrlMixin from openslides.utils.person.models import PersonField -class Mediafile(SlideMixin, models.Model): +class Mediafile(SlideMixin, AbsoluteUrlMixin, models.Model): """ Class for uploaded files which can be delivered under a certain url. """ @@ -74,10 +75,12 @@ class Mediafile(SlideMixin, models.Model): 'update' or 'delete'. """ if link == 'update': - return reverse('mediafile_update', kwargs={'pk': str(self.id)}) - if link == 'delete': - return reverse('mediafile_delete', kwargs={'pk': str(self.id)}) - return super(Mediafile, self).get_absolute_url(link) + url = reverse('mediafile_update', kwargs={'pk': str(self.pk)}) + elif link == 'delete': + url = reverse('mediafile_delete', kwargs={'pk': str(self.pk)}) + else: + url = super(Mediafile, self).get_absolute_url(link) + return url def get_filesize(self): """ diff --git a/openslides/motion/models.py b/openslides/motion/models.py index 6516b3f12..20111d0e5 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -13,12 +13,13 @@ from openslides.poll.models import (BaseOption, BasePoll, BaseVote, CollectInvalid, CollectVotesCast) from openslides.projector.models import RelatedModelMixin, SlideMixin from jsonfield import JSONField +from openslides.utils.models import AbsoluteUrlMixin from openslides.utils.person import PersonField from .exceptions import WorkflowError -class Motion(SlideMixin, models.Model): +class Motion(SlideMixin, AbsoluteUrlMixin, models.Model): """ The Motion Class. @@ -174,12 +175,14 @@ class Motion(SlideMixin, models.Model): The keyword argument 'link' can be 'detail', 'update' or 'delete'. """ if link == 'detail': - return reverse('motion_detail', args=[str(self.id)]) - if link == 'update': - return reverse('motion_update', args=[str(self.id)]) - if link == 'delete': - return reverse('motion_delete', args=[str(self.id)]) - return super(Motion, self).get_absolute_url(link) + url = reverse('motion_detail', args=[str(self.pk)]) + elif link == 'update': + url = reverse('motion_update', args=[str(self.pk)]) + elif link == 'delete': + url = reverse('motion_delete', args=[str(self.pk)]) + else: + url = super(Motion, self).get_absolute_url(link) + return url def version_data_changed(self, version): """ @@ -515,7 +518,7 @@ class Motion(SlideMixin, models.Model): MotionLog.objects.create(motion=self, message_list=message_list, person=person) -class MotionVersion(models.Model): +class MotionVersion(AbsoluteUrlMixin, models.Model): """ A MotionVersion object saves some date of the motion. """ @@ -558,12 +561,15 @@ class MotionVersion(models.Model): The keyargument link can be 'detail' or 'delete'. """ - if link == 'view' or link == 'detail': - return reverse('motion_version_detail', args=[str(self.motion.id), - str(self.version_number)]) - if link == 'delete': - return reverse('motion_version_delete', args=[str(self.motion.id), - str(self.version_number)]) + if link == 'detail': + url = reverse('motion_version_detail', args=[str(self.motion.pk), + str(self.version_number)]) + elif link == 'delete': + url = reverse('motion_version_delete', args=[str(self.motion.pk), + str(self.version_number)]) + else: + url = super(MotionVersion, self).get_absolute_url(link) + return url @property def active(self): @@ -602,7 +608,7 @@ class MotionSupporter(models.Model): return unicode(self.person) -class Category(models.Model): +class Category(AbsoluteUrlMixin, models.Model): name = models.CharField(max_length=255, verbose_name=ugettext_lazy("Category name")) """Name of the category.""" @@ -617,9 +623,12 @@ class Category(models.Model): def get_absolute_url(self, link='update'): if link == 'update': - return reverse('motion_category_update', args=[str(self.id)]) - if link == 'delete': - return reverse('motion_category_delete', args=[str(self.id)]) + url = reverse('motion_category_update', args=[str(self.pk)]) + elif link == 'delete': + url = reverse('motion_category_delete', args=[str(self.pk)]) + else: + url = super(Category, self).get_absolute_url(link) + return url class Meta: ordering = ['prefix'] @@ -685,7 +694,8 @@ class MotionOption(BaseOption): """The VoteClass, to witch this Class links.""" -class MotionPoll(RelatedModelMixin, CollectInvalid, CollectVotesCast, BasePoll): +class MotionPoll(RelatedModelMixin, CollectInvalid, CollectVotesCast, + AbsoluteUrlMixin, BasePoll): """The Class to saves the poll results for a motion poll.""" motion = models.ForeignKey(Motion, related_name='polls') @@ -718,11 +728,14 @@ class MotionPoll(RelatedModelMixin, CollectInvalid, CollectVotesCast, BasePoll): The keyargument 'link' can be 'update' or 'delete'. """ if link == 'update': - return reverse('motion_poll_update', args=[str(self.motion.pk), - str(self.poll_number)]) - if link == 'delete': - return reverse('motion_poll_delete', args=[str(self.motion.pk), - str(self.poll_number)]) + url = reverse('motion_poll_update', args=[str(self.motion.pk), + str(self.poll_number)]) + elif link == 'delete': + url = reverse('motion_poll_delete', args=[str(self.motion.pk), + str(self.poll_number)]) + else: + url = super(MotionPoll, self).get_absolute_url(link) + return url def set_options(self): """Create the option class for this poll.""" diff --git a/openslides/participant/models.py b/openslides/participant/models.py index b9f0642bb..d1bc080aa 100644 --- a/openslides/participant/models.py +++ b/openslides/participant/models.py @@ -12,11 +12,12 @@ from django.utils.translation import ugettext_lazy, ugettext_noop from openslides.config.api import config from openslides.projector.models import SlideMixin +from openslides.utils.models import AbsoluteUrlMixin from openslides.utils.person import Person, PersonMixin from openslides.utils.person.signals import receive_persons -class User(SlideMixin, PersonMixin, Person, DjangoUser): +class User(SlideMixin, PersonMixin, Person, AbsoluteUrlMixin, DjangoUser): slide_callback_name = 'user' person_prefix = 'user' @@ -65,12 +66,14 @@ class User(SlideMixin, PersonMixin, Person, DjangoUser): Return the URL to the user. """ if link == 'detail': - return reverse('user_view', args=[str(self.id)]) - if link == 'update': - return reverse('user_edit', args=[str(self.id)]) - if link == 'delete': - return reverse('user_delete', args=[str(self.id)]) - return super(User, self).get_absolute_url(link) + url = reverse('user_view', args=[str(self.pk)]) + elif link == 'update': + url = reverse('user_edit', args=[str(self.pk)]) + elif link == 'delete': + url = reverse('user_delete', args=[str(self.pk)]) + else: + url = super(User, self).get_absolute_url(link) + return url def get_slide_context(self, **context): # Does not call super. In this case the context would override the name @@ -109,7 +112,7 @@ class User(SlideMixin, PersonMixin, Person, DjangoUser): return self.last_name.lower() -class Group(SlideMixin, PersonMixin, Person, DjangoGroup): +class Group(SlideMixin, PersonMixin, Person, AbsoluteUrlMixin, DjangoGroup): slide_callback_name = 'group' person_prefix = 'group' @@ -130,12 +133,14 @@ class Group(SlideMixin, PersonMixin, Person, DjangoGroup): Return the URL to the user group. """ if link == 'detail': - return reverse('user_group_view', args=[str(self.pk)]) - if link == 'update': - return reverse('user_group_edit', args=[str(self.pk)]) - if link == 'delete': - return reverse('user_group_delete', args=[str(self.pk)]) - return super(Group, self).get_absolute_url(link) + url = reverse('user_group_view', args=[str(self.pk)]) + elif link == 'update': + url = reverse('user_group_edit', args=[str(self.pk)]) + elif link == 'delete': + url = reverse('user_group_delete', args=[str(self.pk)]) + else: + url = super(Group, self).get_absolute_url(link) + return url class UsersAndGroupsToPersons(object): diff --git a/openslides/projector/models.py b/openslides/projector/models.py index b61257e29..35d5ecd4c 100644 --- a/openslides/projector/models.py +++ b/openslides/projector/models.py @@ -5,6 +5,7 @@ from django.core.urlresolvers import reverse from django.db import models from django.utils.translation import ugettext_lazy, ugettext_noop +from openslides.utils.models import AbsoluteUrlMixin from openslides.utils.utils import int_or_none @@ -106,7 +107,7 @@ class SlideMixin(object): return context -class ProjectorSlide(SlideMixin, models.Model): +class ProjectorSlide(SlideMixin, AbsoluteUrlMixin, models.Model): """ Model for Slides, only for the projector. Also called custom slides. """ @@ -118,19 +119,21 @@ class ProjectorSlide(SlideMixin, models.Model): text = models.TextField(null=True, blank=True, verbose_name=ugettext_lazy("Text")) weight = models.IntegerField(default=0, verbose_name=ugettext_lazy("Weight")) - def get_absolute_url(self, link='update'): - if link == 'update': - return reverse('customslide_edit', args=[str(self.pk)]) - if link == 'delete': - return reverse('customslide_delete', args=[str(self.pk)]) - return super(ProjectorSlide, self).get_absolute_url(link) - - def __unicode__(self): - return self.title - class Meta: permissions = ( ('can_manage_projector', ugettext_noop("Can manage the projector")), ('can_see_projector', ugettext_noop("Can see the projector")), ('can_see_dashboard', ugettext_noop("Can see the dashboard")), ) + + def __unicode__(self): + return self.title + + def get_absolute_url(self, link='update'): + if link == 'update': + url = reverse('customslide_edit', args=[str(self.pk)]) + elif link == 'delete': + url = reverse('customslide_delete', args=[str(self.pk)]) + else: + url = super(ProjectorSlide, self).get_absolute_url(link) + return url diff --git a/openslides/utils/models.py b/openslides/utils/models.py index 6bbacf28b..bd1ccb50d 100644 --- a/openslides/utils/models.py +++ b/openslides/utils/models.py @@ -16,3 +16,19 @@ class MinMaxIntegerField(models.IntegerField): defaults = {'min_value': self.min_value, 'max_value': self.max_value} defaults.update(kwargs) return super(MinMaxIntegerField, self).formfield(**defaults) + + +class AbsoluteUrlMixin(object): + """ + Mixin that raises a ValueError if the name of an url was not found with + get_absolute_url. + + The Mixin has to be placed as last OpenSlides-Mixin before the django + model class. + """ + + def get_absolute_url(self, link=None): + """ + Raises a ValueError. + """ + raise ValueError('Unknown Link "%s" for model "%s"' % (link, type(self))) diff --git a/openslides/utils/person/api.py b/openslides/utils/person/api.py index cfa766afe..4e251cab2 100644 --- a/openslides/utils/person/api.py +++ b/openslides/utils/person/api.py @@ -51,9 +51,14 @@ class Person(object): * delete You should raise an 'ValueError', if your person does not have - one of this links. + one of this links, or use the AbsoluteURLMixin, which raises the + ValueError for you. """ - raise ValueError('This person object has no url.') + try: + url = super(Person, self).get_absolute_url(link) + except AttributeError: + raise ValueError('This person object has no url.') + return url class Persons(object): diff --git a/openslides/utils/views.py b/openslides/utils/views.py index 6f1ca8d0b..6d7611578 100644 --- a/openslides/utils/views.py +++ b/openslides/utils/views.py @@ -141,7 +141,8 @@ class UrlMixin(object): 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.') + 'No url to redirect to. See openslides.utils.views.UrlMixin ' + 'for more details.') return value def get_url_name_args(self): diff --git a/tests/utils/test_models.py b/tests/utils/test_models.py new file mode 100644 index 000000000..0fb5523b2 --- /dev/null +++ b/tests/utils/test_models.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from openslides.utils.models import AbsoluteUrlMixin +from openslides.utils.test import TestCase + + +class MyModel(AbsoluteUrlMixin): + """" + Model for testing + """ + def get_absolute_url(self, link='default'): + if link == 'default' or link == 'known': + url = 'my url' + else: + url = super(MyModel, self).get_absolute_url(link) + return url + + +class TestAbsoluteUrlMixin(TestCase): + def test_get_absolute_url(self): + my_object = MyModel() + + self.assertEqual(my_object.get_absolute_url(), 'my url') + self.assertEqual(my_object.get_absolute_url('known'), 'my url') + self.assertRaisesMessage( + ValueError, + 'Unknown Link "unknown" for model ""', + my_object.get_absolute_url, 'unknown')