From da7a7a9d68e27b833203bfcda845e9e71ec9dfae Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Fri, 1 Feb 2013 12:51:54 +0100 Subject: [PATCH] Add Poll-system to the new motion app --- openslides/motion/models.py | 46 +++++++++++++ .../templates/motion/motion_detail.html | 60 +++++++++++++++++ .../motion/templates/motion/poll_form.html | 48 ++++++++++++++ openslides/motion/urls.py | 15 +++++ openslides/motion/views.py | 64 ++++++++++++++++--- 5 files changed, 223 insertions(+), 10 deletions(-) create mode 100644 openslides/motion/templates/motion/poll_form.html diff --git a/openslides/motion/models.py b/openslides/motion/models.py index 510d481ac..0be499419 100644 --- a/openslides/motion/models.py +++ b/openslides/motion/models.py @@ -267,6 +267,13 @@ class Motion(SlideMixin, models.Model): #else: #self.writelog(_("Supporter: -%s") % (person)) + def create_poll(self): + # TODO: auto increment the poll_number in the Database + poll_number = self.polls.aggregate(Max('poll_number'))['poll_number__max'] or 0 + poll = MotionPoll.objects.create(motion=self, poll_number=poll_number + 1) + poll.set_options() + return poll + class MotionVersion(models.Model): title = models.CharField(max_length=255, verbose_name=ugettext_lazy("Title")) @@ -307,3 +314,42 @@ class Comment(models.Model): text = models.TextField() author = PersonField() creation_time = models.DateTimeField(auto_now=True) + + +class MotionVote(BaseVote): + option = models.ForeignKey('MotionOption') + + +class MotionOption(BaseOption): + poll = models.ForeignKey('MotionPoll') + vote_class = MotionVote + + +class MotionPoll(CountInvalid, CountVotesCast, BasePoll): + option_class = MotionOption + vote_values = [ + ugettext_noop('Yes'), ugettext_noop('No'), ugettext_noop('Abstain')] + + motion = models.ForeignKey(Motion, related_name='polls') + poll_number = models.PositiveIntegerField(default=1) + + class Meta: + unique_together = ("motion", "poll_number") + + def get_absolute_url(self, link='edit'): + return reverse('motion_poll_edit', args=[str(self.motion.pk), + str(self.poll_number)]) + + def get_motion(self): + return self.motion + + def set_options(self): + #TODO: maybe it is possible with .create() to call this without poll=self + self.get_option_class()(poll=self).save() + + def append_pollform_fields(self, fields): + CountInvalid.append_pollform_fields(self, fields) + CountVotesCast.append_pollform_fields(self, fields) + + def get_ballot(self): + return self.motion.motionpoll_set.filter(id__lte=self.id).count() diff --git a/openslides/motion/templates/motion/motion_detail.html b/openslides/motion/templates/motion/motion_detail.html index fd5880ada..86c27502e 100644 --- a/openslides/motion/templates/motion/motion_detail.html +++ b/openslides/motion/templates/motion/motion_detail.html @@ -22,4 +22,64 @@ {% endif %} {% endfor %} + + +

{% trans "Vote results" %}:

+ {% with motion.polls.all as polls %} + {% if not polls.exists %} + {% if perms.motion.can_manage_motion %} + + + {% trans 'New vote' %} + + + {% else %} + - + {% endif %} + {% endif %} + + {% endwith %} {% endblock %} diff --git a/openslides/motion/templates/motion/poll_form.html b/openslides/motion/templates/motion/poll_form.html new file mode 100644 index 000000000..f7cd3635a --- /dev/null +++ b/openslides/motion/templates/motion/poll_form.html @@ -0,0 +1,48 @@ +{% extends 'base.html' %} + +{% load i18n %} +{% load staticfiles %} +{% load tags %} + +{% block content %} +

{{ motion }}

+ {% trans "Special values" %}: -1 = {% trans 'majority' %}; -2 = {% trans 'undocumented' %} +
{% csrf_token %} + {{ pre_form }} + + + + + + + {% for value in forms.0 %} + + + + + {% endfor %} + + + + + + + + +
{% trans "Option" %}{% trans "Votes" %}
{{ value.label }}{{ value.errors }}{{ value }}
{% trans "Invalid votes" %}{{ pollform.votesinvalid.errors }}{{ pollform.votesinvalid }}
{% trans "Votes cast" %}{{ pollform.votescast.errors }}{{ pollform.votescast }}
+ + {{ post_form }} + +

+ + + + {% trans 'Cancel' %} + +

+
+{% endblock %} diff --git a/openslides/motion/urls.py b/openslides/motion/urls.py index ebc1c2155..bdd7575a6 100644 --- a/openslides/motion/urls.py +++ b/openslides/motion/urls.py @@ -47,4 +47,19 @@ urlpatterns = patterns('openslides.motion.views', 'motion_unsupport', name='motion_unsupport', ), + + url(r'^(?P\d+)/create_poll/$', + 'poll_create', + name='motion_poll_create', + ), + + url(r'^(?P\d+)/poll/(?P\d+)/edit$', + 'poll_edit', + name='motion_poll_edit', + ), + + ## url(r'^poll/(?P\d+)/del/$', + ## 'delete_poll', + ## name='motion_poll_delete', + ## ), ) diff --git a/openslides/motion/views.py b/openslides/motion/views.py index 4875dae36..c0cada64d 100644 --- a/openslides/motion/views.py +++ b/openslides/motion/views.py @@ -22,13 +22,14 @@ from django.http import Http404 from openslides.utils.pdf import stylesheet from openslides.utils.views import ( TemplateView, RedirectView, UpdateView, CreateView, DeleteView, PDFView, - DetailView, ListView, FormView, QuestionMixin) + DetailView, ListView, FormView, QuestionMixin, SingleObjectMixin) from openslides.utils.template import Tab from openslides.utils.utils import html_strong +from openslides.poll.views import PollFormView from openslides.projector.api import get_active_slide from openslides.projector.projector import Widget, SLIDE from openslides.config.models import config -from .models import Motion, MotionSubmitter, MotionSupporter +from .models import Motion, MotionSubmitter, MotionSupporter, MotionPoll from .forms import (BaseMotionForm, MotionSubmitterMixin, MotionSupporterMixin, MotionCreateNewVersionMixin, ConfigForm) @@ -81,14 +82,16 @@ class MotionMixin(object): def post_save(self, form): super(MotionMixin, self).post_save(form) # TODO: only delete and save neccessary submitters and supporter - self.object.submitter.all().delete() - self.object.supporter.all().delete() - MotionSubmitter.objects.bulk_create( - [MotionSubmitter(motion=self.object, person=person) - for person in form.cleaned_data['submitter']]) - MotionSupporter.objects.bulk_create( - [MotionSupporter(motion=self.object, person=person) - for person in form.cleaned_data['supporter']]) + if 'submitter' in form.cleaned_data: + self.object.submitter.all().delete() + MotionSubmitter.objects.bulk_create( + [MotionSubmitter(motion=self.object, person=person) + for person in form.cleaned_data['submitter']]) + if 'supporter' in form.cleaned_data: + self.object.supporter.all().delete() + MotionSupporter.objects.bulk_create( + [MotionSupporter(motion=self.object, person=person) + for person in form.cleaned_data['supporter']]) def get_form_class(self): form_classes = [BaseMotionForm] @@ -180,6 +183,47 @@ motion_support = SupportView.as_view(support=True) motion_unsupport = SupportView.as_view(support=False) +class PollCreateView(SingleObjectMixin, RedirectView): + permission_required = 'motion.can_manage_motion' + model = Motion + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + return super(PollCreateView, self).get(request, *args, **kwargs) + + def pre_redirect(self, request, *args, **kwargs): + self.poll = self.object.create_poll() + messages.success(request, _("New vote was successfully created.")) + + def get_redirect_url(self, **kwargs): + return reverse('motion_poll_edit', args=[self.object.pk, self.poll.poll_number]) + +poll_create = PollCreateView.as_view() + + +class PollUpdateView(PollFormView): + permission_required = 'motion.can_manage_motion' + poll_class = MotionPoll + template_name = 'motion/poll_form.html' + success_url_name = 'motion_detail' + + def get_object(self): + return MotionPoll.objects.filter( + motion=self.kwargs['pk'], + poll_number=self.kwargs['poll_number']).get() + + def get_context_data(self, **kwargs): + context = super(PollUpdateView, self).get_context_data(**kwargs) + context.update({ + 'motion': self.poll.motion}) + return context + + def get_url_name_args(self): + return [self.poll.motion.pk] + +poll_edit = PollUpdateView.as_view() + + class Config(FormView): permission_required = 'config.can_manage_config' form_class = ConfigForm