diff --git a/openslides/assignment/models.py b/openslides/assignment/models.py
index 650d517d1..5579e5ea8 100644
--- a/openslides/assignment/models.py
+++ b/openslides/assignment/models.py
@@ -18,22 +18,31 @@ from django.utils.datastructures import SortedDict
from openslides.utils.person import PersonField
from openslides.utils.utils import html_strong
from openslides.config.api import config
-from openslides.projector.models import SlideMixin
+from openslides.projector.models import SlideMixin, RelatedModelMixin
from openslides.poll.models import (
BasePoll, CountInvalid, CountVotesCast, BaseOption, PublishPollMixin, BaseVote)
-class AssignmentCandidate(models.Model):
+class AssignmentCandidate(RelatedModelMixin, models.Model):
+ """
+ Many2Many table between an assignment and the candidates.
+ """
assignment = models.ForeignKey("Assignment")
person = PersonField(db_index=True)
elected = models.BooleanField(default=False)
blocked = models.BooleanField(default=False)
+ class Meta:
+ unique_together = ("assignment", "person")
+
def __unicode__(self):
return unicode(self.person)
- class Meta:
- unique_together = ("assignment", "person")
+ def get_related_model(self):
+ """
+ Returns the assignment
+ """
+ return self.assignment
class Assignment(SlideMixin, models.Model):
@@ -74,6 +83,12 @@ class Assignment(SlideMixin, models.Model):
return reverse('assignment_delete', args=[str(self.id)])
return super(Assignment, self).get_absolute_url(link)
+ def get_slide_context(self, **context):
+ context.update({
+ 'polls': self.poll_set.filter(published=True),
+ 'vote_results': self.vote_results(only_published=True)})
+ return super(Assignment, self).get_slide_context(**context)
+
def set_status(self, status):
status_dict = dict(self.STATUS)
if status not in status_dict:
@@ -241,15 +256,29 @@ class AssignmentOption(BaseOption):
return unicode(self.candidate)
-class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin):
+class AssignmentPoll(RelatedModelMixin, CountInvalid, CountVotesCast,
+ PublishPollMixin, BasePoll):
option_class = AssignmentOption
assignment = models.ForeignKey(Assignment, related_name='poll_set')
yesnoabstain = models.NullBooleanField()
+ def __unicode__(self):
+ return _("Ballot %d") % self.get_ballot()
+
+ @models.permalink
+ def get_absolute_url(self, link='detail'):
+ if link == 'view' or link == 'detail' or link == 'update':
+ return ('assignment_poll_view', [str(self.pk)])
+ if link == 'delete':
+ return ('assignment_poll_delete', [str(self.pk)])
+
def get_assignment(self):
return self.assignment
+ def get_related_model(self):
+ return self.assignment
+
def get_vote_values(self):
if self.yesnoabstain is None:
if config['assignment_poll_vote_values'] == 'votes':
@@ -274,13 +303,3 @@ class AssignmentPoll(BasePoll, CountInvalid, CountVotesCast, PublishPollMixin):
def get_ballot(self):
return self.assignment.poll_set.filter(id__lte=self.id).count()
-
- @models.permalink
- def get_absolute_url(self, link='detail'):
- if link == 'view' or link == 'detail' or link == 'update':
- return ('assignment_poll_view', [str(self.id)])
- if link == 'delete':
- return ('assignment_poll_delete', [str(self.id)])
-
- def __unicode__(self):
- return _("Ballot %d") % self.get_ballot()
diff --git a/openslides/assignment/slides.py b/openslides/assignment/slides.py
index dee057a82..744fac9f6 100644
--- a/openslides/assignment/slides.py
+++ b/openslides/assignment/slides.py
@@ -10,29 +10,8 @@
:license: GNU GPL, see LICENSE for more details.
"""
-from django.template.loader import render_to_string
-
-from openslides.config.api import config
-from openslides.projector.api import register_slide
+from openslides.projector.api import register_slide_model
from .models import Assignment
-def assignment_slide(**kwargs):
- """
- Slide for an Assignment
- """
- assignment_pk = kwargs.get('pk', None)
- try:
- assignment = Assignment.objects.get(pk=assignment_pk)
- except Assignment.DoesNotExist:
- return ''
-
- polls = assignment.poll_set
- context = {
- 'polls': polls.filter(published=True),
- 'vote_results': assignment.vote_results(only_published=True),
- 'assignment': assignment}
-
- return render_to_string('assignment/slide.html', context)
-
-register_slide(Assignment.slide_callback_name, assignment_slide)
+register_slide_model(Assignment, 'assignment/slide.html')
diff --git a/openslides/assignment/templates/assignment/assignment_detail.html b/openslides/assignment/templates/assignment/assignment_detail.html
index bba20fbaa..53daa97e5 100644
--- a/openslides/assignment/templates/assignment/assignment_detail.html
+++ b/openslides/assignment/templates/assignment/assignment_detail.html
@@ -179,7 +179,7 @@
{% endfor %}
{% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
-
+
{% trans 'New ballot' %}
|
diff --git a/openslides/assignment/urls.py b/openslides/assignment/urls.py
index fcc7dc16b..5fec7c4ce 100644
--- a/openslides/assignment/urls.py
+++ b/openslides/assignment/urls.py
@@ -99,6 +99,8 @@ urlpatterns = patterns('openslides.assignment.views',
name='assignment_poll_delete',
),
+ # TODO: use seperate urls to publish and unpublish the poll
+ # see assignment_user_elected
url(r'^poll/(?P\d+)/pub/$',
SetPublishStatusView.as_view(),
name='assignment_poll_publish_status',
diff --git a/openslides/assignment/views.py b/openslides/assignment/views.py
index eb44c59e1..822c996ac 100644
--- a/openslides/assignment/views.py
+++ b/openslides/assignment/views.py
@@ -246,10 +246,10 @@ class PollUpdateView(PollFormView):
class SetPublishStatusView(SingleObjectMixin, RedirectView):
model = AssignmentPoll
permission_required = 'assignment.can_manage_assignment'
- url_name = 'assignment_list'
+ url_name = 'assignment_detail'
allow_ajax = True
- def get_ajax_context(self):
+ def get_ajax_context(self, **kwargs):
return {'published': self.object.published}
def pre_redirect(self, *args, **kwargs):
@@ -258,15 +258,11 @@ class SetPublishStatusView(SingleObjectMixin, RedirectView):
except self.model.DoesNotExist:
messages.error(self.request, _('Ballot ID %d does not exist.') %
int(kwargs['poll_id']))
- return
- if self.object.published:
- self.object.set_published(False)
else:
- self.object.set_published(True)
- if self.object.published:
- messages.success(self.request, _("Ballot successfully published."))
- else:
- messages.success(self.request, _("Ballot successfully unpublished."))
+ if self.object.published:
+ self.object.set_published(False)
+ else:
+ self.object.set_published(True)
class SetElectedView(SingleObjectMixin, RedirectView):
@@ -281,14 +277,14 @@ class SetElectedView(SingleObjectMixin, RedirectView):
self.elected = kwargs['elected']
self.object.set_elected(self.person, self.elected)
- def get_ajax_context(self):
+ def get_ajax_context(self, **kwargs):
if self.elected:
link = reverse('assignment_user_not_elected',
args=[self.object.id, self.person.person_id])
text = _('not elected')
else:
link = reverse('assignment_user_elected',
- args=[self.self.object.id, self.person.person_id])
+ args=[self.object.id, self.person.person_id])
text = _('elected')
return {'elected': self.elected, 'link': link, 'text': text}
diff --git a/openslides/motion/models.py b/openslides/motion/models.py
index 053b43c49..bb204c1d5 100644
--- a/openslides/motion/models.py
+++ b/openslides/motion/models.py
@@ -23,14 +23,15 @@ from django.utils import formats
from django.utils.translation import pgettext
from django.utils.translation import ugettext as _, ugettext_lazy, ugettext_noop
-from openslides.utils.jsonfield import JSONField
-from openslides.utils.person import PersonField
+from openslides.agenda.models import Item
from openslides.config.api import config
+from openslides.participant.models import User
from openslides.poll.models import (
BaseOption, BasePoll, CountVotesCast, CountInvalid, BaseVote)
-from openslides.participant.models import User
-from openslides.projector.models import SlideMixin
-from openslides.agenda.models import Item
+from openslides.projector.models import SlideMixin, RelatedModelMixin
+from openslides.projector.api import (update_projector, get_active_slide)
+from openslides.utils.jsonfield import JSONField
+from openslides.utils.person import PersonField
from .exceptions import MotionError, WorkflowError
@@ -584,7 +585,7 @@ class MotionVersion(models.Model):
return self.active_version.exists()
-class MotionSubmitter(models.Model):
+class MotionSubmitter(RelatedModelMixin, models.Model):
"""Save the submitter of a Motion."""
motion = models.ForeignKey('Motion', related_name="submitter")
@@ -597,6 +598,9 @@ class MotionSubmitter(models.Model):
"""Return the name of the submitter as string."""
return unicode(self.person)
+ def get_related_model(self):
+ return self.motion
+
class MotionSupporter(models.Model):
"""Save the submitter of a Motion."""
@@ -695,7 +699,7 @@ class MotionOption(BaseOption):
"""The VoteClass, to witch this Class links."""
-class MotionPoll(CountInvalid, CountVotesCast, BasePoll):
+class MotionPoll(RelatedModelMixin, CountInvalid, CountVotesCast, BasePoll):
"""The Class to saves the poll results for a motion poll."""
motion = models.ForeignKey(Motion, related_name='polls')
@@ -722,7 +726,8 @@ class MotionPoll(CountInvalid, CountVotesCast, BasePoll):
return _('Vote %d') % self.poll_number
def get_absolute_url(self, link='edit'):
- """Return an URL for the poll.
+ """
+ Return an URL for the poll.
The keyargument 'link' can be 'edit' or 'delete'.
"""
@@ -744,9 +749,13 @@ class MotionPoll(CountInvalid, CountVotesCast, BasePoll):
CountInvalid.append_pollform_fields(self, fields)
CountVotesCast.append_pollform_fields(self, fields)
+ def get_related_model(self):
+ return self.motion
+
class State(models.Model):
- """Defines a state for a motion.
+ """
+ Defines a state for a motion.
Every state belongs to a workflow. All states of a workflow are linked together
via 'next_states'. One of these states is the first state, but this
diff --git a/openslides/motion/slides.py b/openslides/motion/slides.py
index 13b05c9a0..17ff5ca03 100644
--- a/openslides/motion/slides.py
+++ b/openslides/motion/slides.py
@@ -10,26 +10,7 @@
:license: GNU GPL, see LICENSE for more details.
"""
-from django.template.loader import render_to_string
-
-from openslides.projector.api import register_slide
+from openslides.projector.api import register_slide_model
from .models import Motion
-
-def motion_slide(**kwargs):
- """
- Slide for the motion app.
- """
- motion_pk = kwargs.get('pk', None)
- try:
- motion = Motion.objects.get(pk=motion_pk)
- except Motion.DoesNotExist:
- return ''
-
- context = {
- 'motion': motion,
- 'title': motion.title}
-
- return render_to_string('motion/slide.html', context)
-
-register_slide(Motion.slide_callback_name, motion_slide)
+register_slide_model(Motion, 'motion/slide.html')
diff --git a/openslides/motion/views.py b/openslides/motion/views.py
index 260445d37..82bdf5156 100644
--- a/openslides/motion/views.py
+++ b/openslides/motion/views.py
@@ -11,36 +11,36 @@
:copyright: (c) 2011–2013 by the OpenSlides team, see AUTHORS.
:license: GNU GPL, see LICENSE for more details.
"""
+from reportlab.platypus import SimpleDocTemplate
from django.core.urlresolvers import reverse
from django.contrib import messages
from django.db import transaction
from django.db.models import Model
+from django.http import Http404, HttpResponseRedirect
from django.utils.text import slugify
from django.utils.translation import ugettext as _, ugettext_lazy, ugettext_noop
-from django.http import Http404, HttpResponseRedirect
-
-from reportlab.platypus import SimpleDocTemplate
+from openslides.agenda.views import (
+ CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView)
+from openslides.config.api import config
+from openslides.poll.views import PollFormView
+from openslides.projector.api import get_active_slide, update_projector
+from openslides.projector.projector import Widget
from openslides.utils.pdf import stylesheet
+from openslides.utils.template import Tab
+from openslides.utils.utils import html_strong, htmldiff
from openslides.utils.views import (
TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView,
DetailView, ListView, FormView, QuestionMixin, SingleObjectMixin)
-from openslides.utils.template import Tab
-from openslides.utils.utils import html_strong, htmldiff
-from openslides.poll.views import PollFormView
-from openslides.projector.api import get_active_slide
-from openslides.projector.projector import Widget
-from openslides.config.api import config
-from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView
-from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll,
- MotionVersion, State, WorkflowError, Category)
+from .csv_import import import_motions
from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin,
MotionDisableVersioningMixin, MotionCategoryMixin,
MotionIdentifierMixin, MotionWorkflowMixin, MotionImportForm)
+from .models import (Motion, MotionSubmitter, MotionSupporter, MotionPoll,
+ MotionVersion, State, WorkflowError, Category)
from .pdf import motions_to_pdf, motion_to_pdf, motion_poll_to_pdf
-from .csv_import import import_motions
class MotionListView(ListView):
@@ -127,6 +127,15 @@ class MotionEditMixin(object):
MotionSupporter.objects.bulk_create(
[MotionSupporter(motion=self.object, person=person)
for person in form.cleaned_data['supporter']])
+
+ # Update the projector if the motion is on it. This can not be done in
+ # the model, because bulk_create does not call the save method.
+ active_slide = get_active_slide()
+ active_slide_pk = active_slide.get('pk', None)
+ if (active_slide['callback'] == 'motion' and
+ unicode(self.object.pk) == unicode(active_slide_pk)):
+ update_projector()
+
messages.success(self.request, self.get_success_message())
return HttpResponseRedirect(self.get_success_url())
diff --git a/openslides/participant/models.py b/openslides/participant/models.py
index 931f0f5ca..31f7f11c2 100644
--- a/openslides/participant/models.py
+++ b/openslides/participant/models.py
@@ -80,6 +80,11 @@ class User(SlideMixin, PersonMixin, Person, DjangoUser):
return reverse('user_delete', args=[str(self.id)])
return super(User, self).get_absolute_url(link)
+ def get_slide_context(self, **context):
+ # Does not call super. In this case the context would override the name
+ # 'user'.
+ return {'shown_user': self}
+
@property
def clean_name(self):
if self.title:
diff --git a/openslides/participant/slides.py b/openslides/participant/slides.py
index 58ff0f91b..62a529ff1 100644
--- a/openslides/participant/slides.py
+++ b/openslides/participant/slides.py
@@ -10,39 +10,8 @@
:license: GNU GPL, see LICENSE for more details.
"""
-from django.template.loader import render_to_string
-
-from openslides.projector.api import register_slide
+from openslides.projector.api import register_slide_model
from .models import User, Group
-
-def user_slide(**kwargs):
- """
- Slide for the user model.
- """
- user_pk = kwargs.get('pk', None)
- try:
- user = User.objects.get(pk=user_pk)
- except User.DoesNotExist:
- return ''
-
- context = {'shown_user': user}
- return render_to_string('participant/user_slide.html', context)
-
-register_slide(User.slide_callback_name, user_slide)
-
-
-def group_slide(**kwargs):
- """
- Slide for the group model.
- """
- group_pk = kwargs.get('pk', None)
- try:
- group = Group.objects.get(pk=group_pk)
- except Group.DoesNotExist:
- return ''
-
- context = {'group': group}
- return render_to_string('participant/group_slide.html', context)
-
-register_slide(Group.slide_callback_name, group_slide)
+register_slide_model(User, 'participant/user_slide.html')
+register_slide_model(Group, 'participant/group_slide.html')
diff --git a/openslides/projector/api.py b/openslides/projector/api.py
index a0937a54f..91f07b593 100644
--- a/openslides/projector/api.py
+++ b/openslides/projector/api.py
@@ -40,18 +40,17 @@ def update_projector():
def update_projector_overlay(overlay):
"""
- Update one overlay on the projector.
+ Update one or all overlay on the projector.
Checks if the overlay is activated and updates it in this case.
- 'overlay' has to be an overlay object, the name of a ovleray or None.
-
- If 'overlay' is None, then all overlays are updated.
+ The argument 'overlay' has to be an overlay object, the name of a
+ ovleray or None. If it is None, all overlays will be updated.
"""
- if isinstance(overlay, basestring):
- overlay = get_overlays()[overlay]
if overlay is None:
overlays = [overlay for overlay in get_overlays().values()]
+ elif isinstance(overlay, basestring):
+ overlays = [get_overlays()[overlay]]
else:
overlays = [overlay]
@@ -132,6 +131,33 @@ def register_slide(name, callback):
slide_callback[name] = callback
+def register_slide_model(SlideModel, template):
+ """
+ Shortcut for register_slide for a Model with the SlideMixin.
+
+ The Argument 'SlideModel' has to be a Django-Model-Class, which is a subclass
+ of SlideMixin. Template has to be a string to the path of a template.
+ """
+
+ def model_slide(**kwargs):
+ """
+ Return the html code for the model slide.
+ """
+ slide_pk = kwargs.get('pk', None)
+
+ try:
+ slide = SlideModel.objects.get(pk=slide_pk)
+ except SlideModel.DoesNotExist:
+ slide = None
+ context = {'slide': None}
+ else:
+ context = slide.get_slide_context()
+
+ return render_to_string(template, context)
+
+ register_slide(SlideModel.slide_callback_name, model_slide)
+
+
def set_active_slide(callback, kwargs={}):
"""
Set the active Slide.
@@ -147,7 +173,7 @@ def set_active_slide(callback, kwargs={}):
def get_active_slide():
"""
- Returns the dictonary, witch defindes the active slide.
+ Returns the dictonary, which defines the active slide.
"""
return config['projector_active_slide']
diff --git a/openslides/projector/models.py b/openslides/projector/models.py
index 57d2a110f..eafd4b9d9 100644
--- a/openslides/projector/models.py
+++ b/openslides/projector/models.py
@@ -19,6 +19,39 @@ from django.core.urlresolvers import reverse
from openslides.utils.utils import int_or_none
+class RelatedModelMixin(object):
+ """
+ Mixin for motion related models, that appear on the motion slide.
+ """
+
+ def save(self, *args, **kwargs):
+ """
+ Saves the model and updates the projector, if the motion in on it.
+ """
+ from .api import update_projector
+ value = super(RelatedModelMixin, self).save(*args, **kwargs)
+ if self.get_related_model().is_active_slide():
+ update_projector()
+ return value
+
+ def delete(self, *args, **kwargs):
+ """
+ Deletes the model and updates the projector, if the motion in on it.
+ """
+ from .api import update_projector
+ value = super(RelatedModelMixin, self).delete(*args, **kwargs)
+ if self.get_related_model().is_active_slide():
+ update_projector()
+ return value
+
+ def get_related_model(self):
+ """
+ Return the pk of the related model.
+ """
+ raise ImproperlyConfigured(
+ '%s has to have a method "get_related_model_pk"' % type(self))
+
+
class SlideMixin(object):
"""
A Mixin for a Django-Model, for making the model a slide.
@@ -34,42 +67,32 @@ class SlideMixin(object):
Updates the projector, if 'self' is the active slide.
"""
from openslides.projector.api import update_projector
- super(SlideMixin, self).save(*args, **kwargs)
+ value = super(SlideMixin, self).save(*args, **kwargs)
if self.is_active_slide():
update_projector()
+ return value
def delete(self, *args, **kwargs):
from openslides.projector.api import update_projector
- super(SlideMixin, self).delete(*args, **kwargs)
+ value = super(SlideMixin, self).delete(*args, **kwargs)
if self.is_active_slide():
update_projector()
-
- def get_slide_callback_name(self):
- """
- Returns the name of the slide callback, which is used to render the slide.
- """
- if self.slide_callback_name is None:
- raise ImproperlyConfigured(
- "SlideMixin requires either a definition of 'slide_callback_name'"
- " or an implementation of 'get_slide_callback_name()'")
- else:
- return self.slide_callback_name
+ return value
def get_absolute_url(self, link='projector'):
"""
- Return the url to activate the slide, if link == 'projector'
+ Return the url to activate the slide, if link == 'projector'.
"""
- if link == 'projector':
- url_name = 'projector_activate_slide'
- elif link == 'projector_preview':
- url_name = 'projector_preview'
-
if link in ('projector', 'projector_preview'):
- return '%s?pk=%d' % (
+ url_name = {'projector': 'projector_activate_slide',
+ 'projector_preview': 'projector_preview'}[link]
+ value = '%s?pk=%d' % (
reverse(url_name,
- args=[self.get_slide_callback_name()]),
+ args=[self.slide_callback_name]),
self.pk)
- return super(SlideMixin, self).get_absolute_url(link)
+ else:
+ value = super(SlideMixin, self).get_absolute_url(link)
+ return value
def is_active_slide(self):
"""
@@ -78,15 +101,24 @@ class SlideMixin(object):
from openslides.projector.api import get_active_slide
active_slide = get_active_slide()
pk = int_or_none(active_slide.get('pk', None))
-
- return (active_slide['callback'] == self.get_slide_callback_name() and
+ return (active_slide['callback'] == self.slide_callback_name and
self.pk == pk)
+ def get_slide_context(self, **context):
+ """
+ Returns the context for the template which renders the slide.
+ """
+ slide_name = self._meta.object_name.lower()
+ context.update({'slide': self,
+ slide_name: self})
+ return context
+
class ProjectorSlide(SlideMixin, models.Model):
"""
Model for Slides, only for the projector. Also called custom slides.
"""
+ # TODO: Rename it to CustomSlide
slide_callback_name = 'projector_slide'
title = models.CharField(max_length=256, verbose_name=ugettext_lazy("Title"))
diff --git a/openslides/projector/projector.py b/openslides/projector/projector.py
index ede8e3f7e..92daea2d0 100644
--- a/openslides/projector/projector.py
+++ b/openslides/projector/projector.py
@@ -102,7 +102,7 @@ class Overlay(object):
def set_active(self, active):
"""
- Publish or depublish the ovleray on the projector.
+ Publish or depublish the overlay on the projector.
publish, if active is true,
depublish, if active is false.
diff --git a/openslides/projector/slides.py b/openslides/projector/slides.py
index 6ecd7901e..cb20e3b0e 100644
--- a/openslides/projector/slides.py
+++ b/openslides/projector/slides.py
@@ -10,27 +10,9 @@
:license: GNU GPL, see LICENSE for more details.
"""
-from django.template.loader import render_to_string
-
-from openslides.config.api import config
-from openslides.projector.api import register_slide
+from openslides.projector.api import register_slide_model
from .models import ProjectorSlide
-def projector_slide(**kwargs):
- """
- Return the html code for a custom slide.
- """
- slide_pk = kwargs.get('pk', None)
-
- try:
- slide = ProjectorSlide.objects.get(pk=slide_pk)
- except ProjectorSlide.DoesNotExist:
- slide = None
-
- context = {'slide': slide}
- return render_to_string('projector/slide_projectorslide.html', context)
-
-
-register_slide('projector_slide', projector_slide)
+register_slide_model(ProjectorSlide, 'projector/slide_projectorslide.html')
diff --git a/openslides/utils/tornado_webserver.py b/openslides/utils/tornado_webserver.py
index f626ef474..a6a7219c6 100644
--- a/openslides/utils/tornado_webserver.py
+++ b/openslides/utils/tornado_webserver.py
@@ -39,8 +39,14 @@ class DjangoStaticFileHandler(StaticFileHandler):
class ProjectorSocketHandler(WebSocketHandler):
+ """
+ Handels the websocket for the projector.
+ """
waiters = set()
+ # The following lines can be uncommented, if there are any problems with
+ # websockts in iOS Safari 5.0
+
## def allow_draft76(self):
## # for iOS 5.0 Safari
## return True