Merge pull request #1149 from normanjaeckel/PersonalInfoClass

Inserted api for the personal info widget.
This commit is contained in:
Norman Jäckel 2013-12-09 03:22:28 -08:00
commit 452e6cfaed
13 changed files with 195 additions and 102 deletions

View File

@ -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)

View File

@ -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 %}

View File

@ -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)

View File

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from . import signals, slides, widgets # noqa
from . import personal_info, signals, slides, widgets # noqa

View 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)

View File

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from . import signals, slides, widgets # noqa
from . import personal_info, signals, slides, widgets # noqa

View 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)

View File

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from . import signals, slides, widgets # noqa
from . import personal_info, signals, slides, widgets # noqa

View 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

View File

@ -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'):

View 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

View File

@ -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()

View 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)