Merge pull request #1149 from normanjaeckel/PersonalInfoClass
Inserted api for the personal info widget.
This commit is contained in:
commit
452e6cfaed
@ -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)
|
||||
|
@ -4,58 +4,19 @@
|
||||
{% load tags %}
|
||||
|
||||
{% block content %}
|
||||
<ul style="line-height: 180%">
|
||||
{% trans 'I am on the list of speakers of the following items:' %}
|
||||
{% for item in items %}
|
||||
<li><a href="{{ item|absolute_url }}">{{ item }}</a></li>
|
||||
{% empty %}
|
||||
<li><i>{% trans 'None' %}</i></li>
|
||||
{% for infoblock in infoblocks %}
|
||||
{% if infoblock.is_active %}
|
||||
<ul style="line-height: 180%">
|
||||
{{ infoblock.headline }}:
|
||||
{% for object in infoblock.get_queryset %}
|
||||
<li><a href="{{ object|absolute_url }}">{{ object }}</a></li>
|
||||
{% empty %}
|
||||
<li><i>{% trans 'None' %}</i></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if not forloop.last %}
|
||||
<hr />
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<hr />
|
||||
<ul style="line-height: 180%">
|
||||
{% trans 'I submitted the following motions:' %}
|
||||
{% for motion in submitted_motions %}
|
||||
<li>
|
||||
<a href="{{ motion|absolute_url }}">
|
||||
{{ motion.identifier|add:' | '|default:'' }}
|
||||
{{ motion }}
|
||||
</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li><i>{% trans 'None' %}</i></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% if 'motion_min_supporters'|get_config %}
|
||||
<hr />
|
||||
<ul style="line-height: 180%">
|
||||
{% trans 'I support the following motions:' %}
|
||||
{% for motion in supported_motions %}
|
||||
<li>
|
||||
<a href="{{ motion|absolute_url }}">
|
||||
{% if motion.identifier %}
|
||||
[# {{ motion.identifier }}]
|
||||
{% else %}
|
||||
[---]
|
||||
{% endif %}
|
||||
{{ motion }}
|
||||
</a>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li><i>{% trans 'None' %}</i></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<hr />
|
||||
<ul style="line-height: 180%">
|
||||
{% trans 'I am candidate for the following elections:' %}
|
||||
{% for assignment in assignments %}
|
||||
<li><a href="{{ assignment|absolute_url }}">{{ assignment }}</a></li>
|
||||
{% empty %}
|
||||
<li><i>{% trans 'None' %}</i></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
@ -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)
|
||||
|
@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import signals, slides, widgets # noqa
|
||||
from . import personal_info, signals, slides, widgets # noqa
|
||||
|
20
openslides/agenda/personal_info.py
Normal file
20
openslides/agenda/personal_info.py
Normal file
@ -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)
|
@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import signals, slides, widgets # noqa
|
||||
from . import personal_info, signals, slides, widgets # noqa
|
||||
|
20
openslides/assignment/personal_info.py
Normal file
20
openslides/assignment/personal_info.py
Normal file
@ -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)
|
@ -1,3 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import signals, slides, widgets # noqa
|
||||
from . import personal_info, signals, slides, widgets # noqa
|
||||
|
34
openslides/motion/personal_info.py
Normal file
34
openslides/motion/personal_info.py
Normal file
@ -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
|
@ -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'):
|
||||
|
57
openslides/utils/personal_info.py
Normal file
57
openslides/utils/personal_info.py
Normal file
@ -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
|
@ -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()
|
||||
|
31
tests/utils/test_personal_info.py
Normal file
31
tests/utils/test_personal_info.py
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user