From 66b853a10a35cf80ed2654236ae656f4bc7d5c3d Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Fri, 13 Jul 2012 11:16:06 +0200 Subject: [PATCH] redesign the poll api, so it don't creates poll-tables. Also, it is easier to get the poll for a vote object. #288 show percent after the vote results --- openslides/application/models.py | 25 ++-- .../templates/application/poll_view.html | 4 +- .../templates/application/view.html | 14 ++- openslides/application/views.py | 1 + openslides/assignment/models.py | 10 +- openslides/poll/forms.py | 3 +- openslides/poll/models.py | 113 +++++++++++++----- openslides/poll/views.py | 8 +- 8 files changed, 121 insertions(+), 57 deletions(-) diff --git a/openslides/application/models.py b/openslides/application/models.py index e17d84185..cc585f231 100644 --- a/openslides/application/models.py +++ b/openslides/application/models.py @@ -12,21 +12,23 @@ from datetime import datetime -from django.db import models -from django.db.models import Max from django.contrib.auth.models import User from django.core.urlresolvers import reverse +from django.db import models +from django.db.models import Max +from django.dispatch import receiver from django.utils.translation import pgettext from django.utils.translation import ugettext_lazy as _, ugettext_noop from openslides.utils.utils import _propper_unicode from openslides.config.models import config +from openslides.config.signals import default_config_value from openslides.participant.models import Profile from openslides.poll.models import (BaseOption, BasePoll, CountVotesCast, - CountInvalid, Vote) + CountInvalid, BaseVote) from openslides.projector.api import register_slidemodel from openslides.projector.models import SlideMixin @@ -537,14 +539,13 @@ class AVersion(models.Model): register_slidemodel(Application) +class ApplicationVote(BaseVote): + option = models.ForeignKey('ApplicationOption') + + class ApplicationOption(BaseOption): - def __getattr__(self, name): - if name in ['Yes', 'No', 'Abstain']: - try: - return self.get_votes().get(value=name) - except Vote.DoesNotExist: - return None - raise AttributeError(name) + poll = models.ForeignKey('ApplicationPoll') + vote_class = ApplicationVote class ApplicationPoll(BasePoll, CountInvalid, CountVotesCast): @@ -572,10 +573,6 @@ class ApplicationPoll(BasePoll, CountInvalid, CountVotesCast): return self.application.applicationpoll_set.filter(id__lte=self.id).count() -from django.dispatch import receiver -from openslides.config.signals import default_config_value - - @receiver(default_config_value, dispatch_uid="application_default_config") def default_config(sender, key, **kwargs): return { diff --git a/openslides/application/templates/application/poll_view.html b/openslides/application/templates/application/poll_view.html index ceebd977a..479f1eb29 100644 --- a/openslides/application/templates/application/poll_view.html +++ b/openslides/application/templates/application/poll_view.html @@ -6,10 +6,10 @@ {% block title %}{{ block.super }} – {% trans "Application No." %} {{ application.number }} – {% trans "Vote" %}{% endblock %} {% block content %} -

{%trans "Application No." %} {{ application.number }} – {%trans "Vote" %}

+

{% trans "Application No." %} {{ application.number }} – {% trans "Vote" %}

{{ application.title }}

-

{%trans "Results of" %} {{ ballot }}. {%trans "Vote" %}

+

{% trans "Results of" %} {{ ballot }}. {% trans "Vote" %}

{% trans "Special values" %}: -1 = {% trans 'majority' %}; -2 = {% trans 'undocumented' %}
{% csrf_token %} {{ pre_form }} diff --git a/openslides/application/templates/application/view.html b/openslides/application/templates/application/view.html index 5181e9789..e5d1aca78 100644 --- a/openslides/application/templates/application/view.html +++ b/openslides/application/templates/application/view.html @@ -50,7 +50,9 @@ {% if perms.application.can_manage_application %} {% if "genpoll" in actions %} - {%trans 'New vote' %} + + {%trans 'New vote' %} + {% else %} - @@ -65,8 +67,12 @@
  • {% if perms.application.can_manage_application %} {{ forloop.counter }}. {% trans "Vote" %} - - + + + + + + {% elif poll.has_votes %} {{ forloop.counter }}. {% trans "Vote" %}: {% endif %} @@ -206,7 +212,7 @@ {% endif %} {% trans "Version" %} {{ version.aid }} - + {% if application.public_version != application.last_version %} ⋅ {% if version == application.public_version %} diff --git a/openslides/application/views.py b/openslides/application/views.py index b7b0ae3d2..ad4ab436c 100644 --- a/openslides/application/views.py +++ b/openslides/application/views.py @@ -496,6 +496,7 @@ class ApplicationDelete(DeleteView): else: self.gen_confirm_form(request, _('Do you really want to delete %s?') % self.object, self.object.get_absolute_url('delete')) + class ViewPoll(PollFormView): permission_required = 'application.can_manage_application' poll_class = ApplicationPoll diff --git a/openslides/assignment/models.py b/openslides/assignment/models.py index eaa3207d1..f80fb441f 100644 --- a/openslides/assignment/models.py +++ b/openslides/assignment/models.py @@ -24,7 +24,7 @@ from openslides.projector.projector import SlideMixin from openslides.participant.models import Profile from openslides.poll.models import (BasePoll, CountInvalid, CountVotesCast, - BaseOption, PublishPollMixin) + BaseOption, PublishPollMixin, BaseVote) from openslides.agenda.models import Item @@ -143,7 +143,7 @@ class Assignment(models.Model, SlideMixin): # candidate related to this poll poll_option = poll.get_options().get(candidate=candidate) for vote in poll_option.get_votes(): - votes[vote.value] = vote.get_weight() + votes[vote.value] = vote.print_weight() except AssignmentOption.DoesNotExist: # candidate not in related to this poll votes = None @@ -196,8 +196,14 @@ class Assignment(models.Model, SlideMixin): register_slidemodel(Assignment) +class AssignmentVote(BaseVote): + option = models.ForeignKey('AssignmentOption') + + class AssignmentOption(BaseOption): + poll = models.ForeignKey('AssignmentPoll') candidate = models.ForeignKey(Profile) + vote_class = AssignmentVote def __unicode__(self): return unicode(self.candidate) diff --git a/openslides/poll/forms.py b/openslides/poll/forms.py index c42c80d4b..2fd893b11 100644 --- a/openslides/poll/forms.py +++ b/openslides/poll/forms.py @@ -14,7 +14,6 @@ from django import forms from django.utils.translation import ugettext_lazy as _ from openslides.utils.forms import CssClassMixin -from openslides.poll.models import Vote class OptionForm(forms.Form, CssClassMixin): @@ -27,7 +26,7 @@ class OptionForm(forms.Form, CssClassMixin): for vote in extra: key = vote.value value = vote.get_value() - weight = vote.get_weight(raw=True) + weight = vote.print_weight(raw=True) self.fields[key] = forms.IntegerField( label=value, initial=weight, diff --git a/openslides/poll/models.py b/openslides/poll/models.py index 8ad108258..e24494aad 100644 --- a/openslides/poll/models.py +++ b/openslides/poll/models.py @@ -10,6 +10,7 @@ :license: GNU GPL, see LICENSE for more details. """ +from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.utils.translation import ugettext_lazy as _, ugettext_noop @@ -17,35 +18,57 @@ from openslides.utils.modelfields import MinMaxIntegerField class BaseOption(models.Model): - poll = models.ForeignKey('BasePoll') + """ + Base option class for a Poll. + + Subclasses have to define a poll-field, which are a subclass of BasePoll. + """ def get_votes(self): - return Vote.objects.filter(option=self) + return self.get_vote_class().objects.filter(option=self) + + def __getitem__(self, name): + try: + return self.get_votes().get(value=name) + except self.get_vote_class().DoesNotExist: + return None + + def get_vote_class(self): + return self.vote_class + + class Meta: + abstract = True -class TextOption(BaseOption): - text = models.CharField(max_length=255) +class BaseVote(models.Model): + """ + Base Vote class for an option. - def __unicode__(self): - return self.text - - -class Vote(models.Model): - option = models.ForeignKey(BaseOption) - #profile = models.ForeignKey(Profile) # TODO: we need a person+ here + Subclasses have to define a option-field, which are a subclass of + BaseOption. + """ weight = models.IntegerField(default=1, null=True) # Use MinMaxIntegerField value = models.CharField(max_length=255, null=True) - def get_weight(self, raw=False): + def print_weight(self, raw=False): if raw: return self.weight - return print_value(self.weight) + try: + percent_base = self.option.poll.percent_base() + except AttributeError: + # The poll class is no child of CountVotesCast + percent_base = 0 + + return print_value(self.weight, percent_base) def get_value(self): - return unicode(_(self.value)) + return _(self.value) def __unicode__(self): - return self.get_weight() + return self.print_weight() + + class Meta: + abstract = True class CountVotesCast(models.Model): @@ -58,6 +81,9 @@ class CountVotesCast(models.Model): def print_votescast(self): return print_value(self.votescast) + def percent_base(self): + return 100 / float(self.votescast) + class Meta: abstract = True @@ -70,7 +96,13 @@ class CountInvalid(models.Model): fields.append('votesinvalid') def print_votesinvalid(self): - return print_value(self.votesinvalid) + try: + percent_base = self.percent_base() + except AttributeError: + # The poll class is no child of CountVotesCast + percent_base = 0 + + return print_value(self.votesinvalid, percent_base) class Meta: abstract = True @@ -88,14 +120,16 @@ class PublishPollMixin(models.Model): class BasePoll(models.Model): - option_class = TextOption + """ + Base poll class. + """ vote_values = [ugettext_noop('votes')] def has_votes(self): """ Return True, the there are votes in the poll. """ - if self.get_options().filter(vote__isnull=False): + if self.get_votes().exists(): return True return False @@ -128,6 +162,19 @@ class BasePoll(models.Model): """ return self.vote_values + + def get_vote_class(self): + """ + Return the releatet vote class. + """ + return self.get_option_class().vote_class + + def get_votes(self): + """ + Return a QuerySet with all vote objects, releatet to this poll. + """ + return self.get_vote_class().objects + def set_form_values(self, option, data): # TODO: recall this function. It has nothing to do with a form """ @@ -135,9 +182,9 @@ class BasePoll(models.Model): """ for value in self.get_vote_values(): try: - vote = Vote.objects.filter(option=option).get(value=value) - except Vote.DoesNotExist: - vote = Vote(option=option, value=value) + vote = self.get_votes().filter(option=option).get(value=value) + except ObjectDoesNotExist: + vote = self.get_vote_class()(option=option, value=value) vote.weight = data[value] vote.save() @@ -150,10 +197,11 @@ class BasePoll(models.Model): values = [] for value in self.get_vote_values(): try: - vote = Vote.objects.filter(option=option_id).get(value=value) + vote = self.get_votes().filter(option=option_id) \ + .get(value=value) values.append(vote) - except Vote.DoesNotExist: - values.append(Vote(value=value, weight='')) + except ObjectDoesNotExist: + values.append(self.get_vote_class()(value=value, weight='')) return values def get_vote_form(self, **kwargs): @@ -175,12 +223,19 @@ class BasePoll(models.Model): forms.append(form) return forms + class Meta: + abstract = True + + +def print_value(value, percent_base=0): -def print_value(value): if value == -1: - value = _('majority') + return unicode(_('majority')) elif value == -2: - value = _('undocumented') + return unicode(_('undocumented')) elif value is None: - value = '' - return unicode(value) + return u'' + if not percent_base: + return u'%s' % value + + return u'%d (%.2f %%)' % (value, value * percent_base) diff --git a/openslides/poll/views.py b/openslides/poll/views.py index 362ae1628..ef58218d9 100644 --- a/openslides/poll/views.py +++ b/openslides/poll/views.py @@ -48,14 +48,14 @@ class PollFormView(TemplateView): def post(self, request, *args, **kwargs): self.set_poll(self.kwargs['poll_id']) - forms = self.poll.get_vote_forms(data=self.request.POST) + option_forms = self.poll.get_vote_forms(data=self.request.POST) FormClass = self.get_modelform_class() pollform = FormClass(data=self.request.POST, instance=self.poll, prefix='pollform') error = False - for form in forms: + for form in option_forms: if not form.is_valid(): error = True @@ -64,11 +64,11 @@ class PollFormView(TemplateView): if error: return self.render_to_response(self.get_context_data( - forms=forms, + forms=option_forms, pollform=pollform, )) - for form in forms: + for form in option_forms: data = {} for value in self.poll.get_vote_values(): data[value] = form.cleaned_data[value]