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 %}
-
- {% trans 'I am on the list of speakers of the following items:' %}
- {% for item in items %}
- - {{ item }}
- {% empty %}
- - {% trans 'None' %}
+ {% for infoblock in infoblocks %}
+ {% if infoblock.is_active %}
+
+ {{ infoblock.headline }}:
+ {% for object in infoblock.get_queryset %}
+ - {{ object }}
+ {% empty %}
+ - {% trans 'None' %}
+ {% endfor %}
+
+ {% if not forloop.last %}
+
+ {% endif %}
+ {% endif %}
{% endfor %}
-
-
-
-
-
-{% if 'motion_min_supporters'|get_config %}
-
-
-{% endif %}
-
-
-
- {% trans 'I am candidate for the following elections:' %}
- {% for assignment in assignments %}
- - {{ assignment }}
- {% empty %}
- - {% trans 'None' %}
- {% endfor %}
-
{% 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)