diff --git a/CHANGELOG b/CHANGELOG index 90eacdeb0..ceff56008 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ Other: - Changed api for plugins. - Renamed config api classes. - Renamed some classes of the poll api. +- Inserted api for the personal info widget. Version 1.5.1 (unreleased) diff --git a/openslides/account/templates/account/widget_personal_info.html b/openslides/account/templates/account/widget_personal_info.html index e51048bff..d8b37ba8e 100644 --- a/openslides/account/templates/account/widget_personal_info.html +++ b/openslides/account/templates/account/widget_personal_info.html @@ -4,58 +4,19 @@ {% load tags %} {% block content %} - - -
- - -{% if 'motion_min_supporters'|get_config %} -
- -{% endif %} - -
- {% endblock %} diff --git a/openslides/account/widgets.py b/openslides/account/widgets.py index 6d30c7cfe..c159aec06 100644 --- a/openslides/account/widgets.py +++ b/openslides/account/widgets.py @@ -3,18 +3,17 @@ from django.contrib.auth.models import AnonymousUser from django.utils.translation import ugettext_lazy +from openslides.utils.personal_info import PersonalInfo from openslides.utils.widgets import Widget class PersonalInfoWidget(Widget): """ - Provides a widget for personal info. It shows your submitted and supported - motions, where you are on the list of speakers and where you are supporter - or candidate. If one of the modules agenda, motion or assignment does - not exist, it is not loaded. If all does not exist, the widget disapears. + Provides a widget for personal info. It shows all info block given by the + personal info api. See openslides.utils.personal_info.PersonalInfo. """ name = 'personal_info' - verbose_name = ugettext_lazy('My items, motions and elections') + verbose_name = ugettext_lazy('My personal info') default_column = 1 default_weight = 80 template_name = 'account/widget_personal_info.html' @@ -27,16 +26,11 @@ class PersonalInfoWidget(Widget): def is_active(self): """ - The widget is disabled if there can neither the agenda app, nor the - motion app nor the assignment app be found. + The widget is disabled if there are no info blocks at the moment. """ - for module in ('agenda', 'motion', 'assignment'): - try: - __import__('openslides.%s' % module) - except ImportError: - continue - else: - active = True + for infoblock in PersonalInfo.get_all(self.request): + if infoblock.is_active(): + active = super(PersonalInfoWidget, self).is_active() break else: active = False @@ -46,30 +40,4 @@ class PersonalInfoWidget(Widget): """ Adds the context to the widget. """ - try: - from openslides.agenda.models import Item - except ImportError: - pass - else: - context.update({ - 'items': Item.objects.filter( - speaker__person=self.request.user, - speaker__begin_time=None)}) - try: - from openslides.motion.models import Motion - except ImportError: - pass - else: - context.update({ - 'submitted_motions': Motion.objects.filter(submitter__person=self.request.user), - 'supported_motions': Motion.objects.filter(supporter__person=self.request.user)}) - try: - from openslides.assignment.models import Assignment - except ImportError: - pass - else: - context.update({ - 'assignments': Assignment.objects.filter( - assignmentcandidate__person=self.request.user, - assignmentcandidate__blocked=False)}) - return super(PersonalInfoWidget, self).get_context_data(**context) + return super(PersonalInfoWidget, self).get_context_data(infoblocks=PersonalInfo.get_all(self.request), **context) diff --git a/openslides/agenda/__init__.py b/openslides/agenda/__init__.py index 635921a8c..fbfccf222 100644 --- a/openslides/agenda/__init__.py +++ b/openslides/agenda/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -from . import signals, slides, widgets # noqa +from . import personal_info, signals, slides, widgets # noqa diff --git a/openslides/agenda/personal_info.py b/openslides/agenda/personal_info.py new file mode 100644 index 000000000..f70356c78 --- /dev/null +++ b/openslides/agenda/personal_info.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from django.utils.translation import ugettext_lazy + +from openslides.utils.personal_info import PersonalInfo + +from .models import Item + + +class AgendaPersonalInfo(PersonalInfo): + """ + Class for personal info block for the agenda app. + """ + headline = ugettext_lazy('I am on the list of speakers of the following items') + default_weight = 10 + + def get_queryset(self): + return Item.objects.filter( + speaker__person=self.request.user, + speaker__begin_time=None) diff --git a/openslides/assignment/__init__.py b/openslides/assignment/__init__.py index 635921a8c..fbfccf222 100644 --- a/openslides/assignment/__init__.py +++ b/openslides/assignment/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -from . import signals, slides, widgets # noqa +from . import personal_info, signals, slides, widgets # noqa diff --git a/openslides/assignment/personal_info.py b/openslides/assignment/personal_info.py new file mode 100644 index 000000000..ecebdcab3 --- /dev/null +++ b/openslides/assignment/personal_info.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from django.utils.translation import ugettext_lazy + +from openslides.utils.personal_info import PersonalInfo + +from .models import Assignment + + +class AssignmentPersonalInfo(PersonalInfo): + """ + Class for personal info block for the assignment app. + """ + headline = ugettext_lazy('I am candidate for the following elections') + default_weight = 40 + + def get_queryset(self): + return Assignment.objects.filter( + assignmentcandidate__person=self.request.user, + assignmentcandidate__blocked=False) diff --git a/openslides/motion/__init__.py b/openslides/motion/__init__.py index 635921a8c..fbfccf222 100644 --- a/openslides/motion/__init__.py +++ b/openslides/motion/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -from . import signals, slides, widgets # noqa +from . import personal_info, signals, slides, widgets # noqa diff --git a/openslides/motion/personal_info.py b/openslides/motion/personal_info.py new file mode 100644 index 000000000..a3a283841 --- /dev/null +++ b/openslides/motion/personal_info.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +from django.utils.translation import ugettext_lazy + +from openslides.config.api import config +from openslides.utils.personal_info import PersonalInfo + +from .models import Motion + + +class MotionSubmitterPersonalInfo(PersonalInfo): + """ + Class for personal info block for motion submitters. + """ + headline = ugettext_lazy('I submitted the following motions') + default_weight = 20 + + def get_queryset(self): + return Motion.objects.filter(submitter__person=self.request.user) + + +class MotionSupporterPersonalInfo(PersonalInfo): + """ + Class for personal info block for motion supporters. + """ + headline = ugettext_lazy('I support the following motions') + default_weight = 30 + + def get_queryset(self): + if config['motion_min_supporters']: + return_value = Motion.objects.filter(supporter__person=self.request.user) + else: + return_value = None + return return_value diff --git a/openslides/utils/dispatch.py b/openslides/utils/dispatch.py index 56659fddf..5e4eb8d1f 100644 --- a/openslides/utils/dispatch.py +++ b/openslides/utils/dispatch.py @@ -62,13 +62,14 @@ class SignalConnectMetaClass(type): def get_all_objects(cls, request): """ Collects all objects of the class created by the SignalConnectMetaClass - from all apps via signal. If they have a default weight, they are sorted. - Does not return objects where check_permission returns False. + from all apps via signal. If they have a get_default_weight method, + they are sorted. Does not return objects where check_permission returns + False. Expects a request object. - This classmethod is added as get_all classmethod to every class using the - SignalConnectMetaClass. + This classmethod is added as get_all classmethod to every class using + the SignalConnectMetaClass. """ all_objects = [obj for __, obj in cls.signal.send(sender=cls, request=request) if obj.check_permission()] if hasattr(cls, 'get_default_weight'): diff --git a/openslides/utils/personal_info.py b/openslides/utils/personal_info.py new file mode 100644 index 000000000..27a42ed7f --- /dev/null +++ b/openslides/utils/personal_info.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +from django.dispatch import Signal + +from .dispatch import SignalConnectMetaClass + + +class PersonalInfo(object): + """ + Base class for a personal info collection for the personal info widget. + + Every app which wants to add info has to create a class subclassing + from this base class. For the content the headline and default_weight + argument and the get_queryset method have to be set. The __metaclass__ + attribute (SignalConnectMetaClass) does the rest of the magic. + """ + __metaclass__ = SignalConnectMetaClass + signal = Signal(providing_args=['request']) + headline = None + default_weight = 0 + + def __init__(self, sender, request, **kwargs): + """ + Initialize the personal info instance. This is done when the signal is sent. + + Only the required request argument is used. Because of Django's signal + API, we have to take also a sender argument and wildcard keyword + arguments. But they are not used here. + """ + self.request = request + + @classmethod + def get_dispatch_uid(cls): + """ + Returns the classname as a unique string for each class. Returns None for + the base class so it will not be connected to the signal. + """ + if not cls.__name__ == 'PersonalInfo': + return cls.__name__ + + def get_default_weight(self): + """ + Returns the default weight of the personal info class. + """ + return self.default_weight + + def get_queryset(self): + """ + Returns a queryset of objects for the personal info widget. + """ + raise NotImplementedError('Your class %s has to define a get_queryset method.' % self.__class__) + + def is_active(self): + """ + Returns True if the infoblock is shown in the widget. + """ + return self.get_queryset() is not None diff --git a/tests/account/test_widgets.py b/tests/account/test_widgets.py index 83ea372ad..5bb98e438 100644 --- a/tests/account/test_widgets.py +++ b/tests/account/test_widgets.py @@ -52,7 +52,7 @@ class PersonalInfoWidget(TestCase): def test_widget_appearance(self): response = self.client.get('/projector/dashboard/') - self.assertContains(response, 'My items, motions and elections', status_code=200) + self.assertContains(response, 'My personal info', status_code=200) def test_item_list(self): agenda = self.import_agenda() diff --git a/tests/utils/test_personal_info.py b/tests/utils/test_personal_info.py new file mode 100644 index 000000000..0909b4714 --- /dev/null +++ b/tests/utils/test_personal_info.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +from django.contrib.auth.models import AnonymousUser +from django.test.client import RequestFactory + +from openslides.utils.test import TestCase +from openslides.utils.personal_info import PersonalInfo + + +class PersonalInfoObject(TestCase): + def get_infoblock(self, name): + request = RequestFactory().get('/') + request.user = AnonymousUser() + for infoblock in PersonalInfo.get_all(request): + if type(infoblock).__name__ == name: + value = infoblock + break + else: + value = False + return value + + def test_get_queryset(self): + + class TestInfoBlock_cu1Beir1zie5yeitie4e(PersonalInfo): + pass + + infoblock = self.get_infoblock('TestInfoBlock_cu1Beir1zie5yeitie4e') + self.assertRaisesMessage( + NotImplementedError, + 'Your class %s has to define a get_queryset method.' % repr(TestInfoBlock_cu1Beir1zie5yeitie4e), + infoblock.get_queryset)