Inserted api for the personal info widget.
This commit is contained in:
parent
9bb7cd1388
commit
9a3243706b
@ -17,6 +17,7 @@ Other:
|
|||||||
- Changed api for plugins.
|
- Changed api for plugins.
|
||||||
- Renamed config api classes.
|
- Renamed config api classes.
|
||||||
- Renamed some classes of the poll api.
|
- Renamed some classes of the poll api.
|
||||||
|
- Inserted api for the personal info widget.
|
||||||
|
|
||||||
|
|
||||||
Version 1.5.1 (unreleased)
|
Version 1.5.1 (unreleased)
|
||||||
|
@ -4,58 +4,19 @@
|
|||||||
{% load tags %}
|
{% load tags %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<ul style="line-height: 180%">
|
{% for infoblock in infoblocks %}
|
||||||
{% trans 'I am on the list of speakers of the following items:' %}
|
{% if infoblock.is_active %}
|
||||||
{% for item in items %}
|
<ul style="line-height: 180%">
|
||||||
<li><a href="{{ item|absolute_url }}">{{ item }}</a></li>
|
{{ infoblock.headline }}:
|
||||||
{% empty %}
|
{% for object in infoblock.get_queryset %}
|
||||||
<li><i>{% trans 'None' %}</i></li>
|
<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 %}
|
{% 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 %}
|
{% endblock %}
|
||||||
|
@ -3,18 +3,17 @@
|
|||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.utils.translation import ugettext_lazy
|
from django.utils.translation import ugettext_lazy
|
||||||
|
|
||||||
|
from openslides.utils.personal_info import PersonalInfo
|
||||||
from openslides.utils.widgets import Widget
|
from openslides.utils.widgets import Widget
|
||||||
|
|
||||||
|
|
||||||
class PersonalInfoWidget(Widget):
|
class PersonalInfoWidget(Widget):
|
||||||
"""
|
"""
|
||||||
Provides a widget for personal info. It shows your submitted and supported
|
Provides a widget for personal info. It shows all info block given by the
|
||||||
motions, where you are on the list of speakers and where you are supporter
|
personal info api. See openslides.utils.personal_info.PersonalInfo.
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
name = 'personal_info'
|
name = 'personal_info'
|
||||||
verbose_name = ugettext_lazy('My items, motions and elections')
|
verbose_name = ugettext_lazy('My personal info')
|
||||||
default_column = 1
|
default_column = 1
|
||||||
default_weight = 80
|
default_weight = 80
|
||||||
template_name = 'account/widget_personal_info.html'
|
template_name = 'account/widget_personal_info.html'
|
||||||
@ -27,16 +26,11 @@ class PersonalInfoWidget(Widget):
|
|||||||
|
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
"""
|
"""
|
||||||
The widget is disabled if there can neither the agenda app, nor the
|
The widget is disabled if there are no info blocks at the moment.
|
||||||
motion app nor the assignment app be found.
|
|
||||||
"""
|
"""
|
||||||
for module in ('agenda', 'motion', 'assignment'):
|
for infoblock in PersonalInfo.get_all(self.request):
|
||||||
try:
|
if infoblock.is_active():
|
||||||
__import__('openslides.%s' % module)
|
active = super(PersonalInfoWidget, self).is_active()
|
||||||
except ImportError:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
active = True
|
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
active = False
|
active = False
|
||||||
@ -46,30 +40,4 @@ class PersonalInfoWidget(Widget):
|
|||||||
"""
|
"""
|
||||||
Adds the context to the widget.
|
Adds the context to the widget.
|
||||||
"""
|
"""
|
||||||
try:
|
return super(PersonalInfoWidget, self).get_context_data(infoblocks=PersonalInfo.get_all(self.request), **context)
|
||||||
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)
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- 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 -*-
|
# -*- 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 -*-
|
# -*- 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):
|
def get_all_objects(cls, request):
|
||||||
"""
|
"""
|
||||||
Collects all objects of the class created by the SignalConnectMetaClass
|
Collects all objects of the class created by the SignalConnectMetaClass
|
||||||
from all apps via signal. If they have a default weight, they are sorted.
|
from all apps via signal. If they have a get_default_weight method,
|
||||||
Does not return objects where check_permission returns False.
|
they are sorted. Does not return objects where check_permission returns
|
||||||
|
False.
|
||||||
|
|
||||||
Expects a request object.
|
Expects a request object.
|
||||||
|
|
||||||
This classmethod is added as get_all classmethod to every class using the
|
This classmethod is added as get_all classmethod to every class using
|
||||||
SignalConnectMetaClass.
|
the SignalConnectMetaClass.
|
||||||
"""
|
"""
|
||||||
all_objects = [obj for __, obj in cls.signal.send(sender=cls, request=request) if obj.check_permission()]
|
all_objects = [obj for __, obj in cls.signal.send(sender=cls, request=request) if obj.check_permission()]
|
||||||
if hasattr(cls, 'get_default_weight'):
|
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):
|
def test_widget_appearance(self):
|
||||||
response = self.client.get('/projector/dashboard/')
|
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):
|
def test_item_list(self):
|
||||||
agenda = self.import_agenda()
|
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