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
This commit is contained in:
Oskar Hahn 2012-07-13 11:16:06 +02:00
parent 969816be37
commit 66b853a10a
8 changed files with 121 additions and 57 deletions

View File

@ -12,21 +12,23 @@
from datetime import datetime from datetime import datetime
from django.db import models
from django.db.models import Max
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.urlresolvers import reverse 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 pgettext
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _, ugettext_noop
from openslides.utils.utils import _propper_unicode from openslides.utils.utils import _propper_unicode
from openslides.config.models import config from openslides.config.models import config
from openslides.config.signals import default_config_value
from openslides.participant.models import Profile from openslides.participant.models import Profile
from openslides.poll.models import (BaseOption, BasePoll, CountVotesCast, from openslides.poll.models import (BaseOption, BasePoll, CountVotesCast,
CountInvalid, Vote) CountInvalid, BaseVote)
from openslides.projector.api import register_slidemodel from openslides.projector.api import register_slidemodel
from openslides.projector.models import SlideMixin from openslides.projector.models import SlideMixin
@ -537,14 +539,13 @@ class AVersion(models.Model):
register_slidemodel(Application) register_slidemodel(Application)
class ApplicationVote(BaseVote):
option = models.ForeignKey('ApplicationOption')
class ApplicationOption(BaseOption): class ApplicationOption(BaseOption):
def __getattr__(self, name): poll = models.ForeignKey('ApplicationPoll')
if name in ['Yes', 'No', 'Abstain']: vote_class = ApplicationVote
try:
return self.get_votes().get(value=name)
except Vote.DoesNotExist:
return None
raise AttributeError(name)
class ApplicationPoll(BasePoll, CountInvalid, CountVotesCast): 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() 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") @receiver(default_config_value, dispatch_uid="application_default_config")
def default_config(sender, key, **kwargs): def default_config(sender, key, **kwargs):
return { return {

View File

@ -6,10 +6,10 @@
{% block title %}{{ block.super }} {% trans "Application No." %} {{ application.number }} {% trans "Vote" %}{% endblock %} {% block title %}{{ block.super }} {% trans "Application No." %} {{ application.number }} {% trans "Vote" %}{% endblock %}
{% block content %} {% block content %}
<h1>{%trans "Application No." %} {{ application.number }} {%trans "Vote" %}</h1> <h1>{% trans "Application No." %} {{ application.number }} {% trans "Vote" %}</h1>
<h3>{{ application.title }}</h3> <h3>{{ application.title }}</h3>
<p>{%trans "Results of" %} {{ ballot }}. {%trans "Vote" %}</p> <p>{% trans "Results of" %} {{ ballot }}. {% trans "Vote" %}</p>
<i class="helptext">{% trans "Special values" %}: -1 = {% trans 'majority' %}; -2 = {% trans 'undocumented' %}</i> <i class="helptext">{% trans "Special values" %}: -1 = {% trans 'majority' %}; -2 = {% trans 'undocumented' %}</i>
<form action="" method="post" class="small-form">{% csrf_token %} <form action="" method="post" class="small-form">{% csrf_token %}
{{ pre_form }} {{ pre_form }}

View File

@ -50,7 +50,9 @@
{% if perms.application.can_manage_application %} {% if perms.application.can_manage_application %}
{% if "genpoll" in actions %} {% if "genpoll" in actions %}
<a href='{% url application_gen_poll application.id %}'> <a href='{% url application_gen_poll application.id %}'>
<span class="button"><span class="icon statistics">{%trans 'New vote' %}</span></span> <span class="button">
<span class="icon statistics">{%trans 'New vote' %}</span>
</span>
</a> </a>
{% else %} {% else %}
- -
@ -65,8 +67,12 @@
<li> <li>
{% if perms.application.can_manage_application %} {% if perms.application.can_manage_application %}
<strong>{{ forloop.counter }}. {% trans "Vote" %} </strong> <strong>{{ forloop.counter }}. {% trans "Vote" %} </strong>
<a class="icon edit" href="{% url application_poll_view poll.id %}" title="{% trans 'Edit Vote' %}"><span></span></a> <a class="icon edit" href="{% url application_poll_view poll.id %}" title="{% trans 'Edit Vote' %}">
<a class="icon delete" href="{% url application_poll_delete poll.id %}" title="{% trans 'Delete Vote' %}"><span></span></a> <span></span>
</a>
<a class="icon delete" href="{% url application_poll_delete poll.id %}" title="{% trans 'Delete Vote' %}">
<span></span>
</a>
{% elif poll.has_votes %} {% elif poll.has_votes %}
<strong>{{ forloop.counter }}. {% trans "Vote" %}:</strong> <strong>{{ forloop.counter }}. {% trans "Vote" %}:</strong>
{% endif %} {% endif %}

View File

@ -496,6 +496,7 @@ class ApplicationDelete(DeleteView):
else: else:
self.gen_confirm_form(request, _('Do you really want to delete <b>%s</b>?') % self.object, self.object.get_absolute_url('delete')) self.gen_confirm_form(request, _('Do you really want to delete <b>%s</b>?') % self.object, self.object.get_absolute_url('delete'))
class ViewPoll(PollFormView): class ViewPoll(PollFormView):
permission_required = 'application.can_manage_application' permission_required = 'application.can_manage_application'
poll_class = ApplicationPoll poll_class = ApplicationPoll

View File

@ -24,7 +24,7 @@ from openslides.projector.projector import SlideMixin
from openslides.participant.models import Profile from openslides.participant.models import Profile
from openslides.poll.models import (BasePoll, CountInvalid, CountVotesCast, from openslides.poll.models import (BasePoll, CountInvalid, CountVotesCast,
BaseOption, PublishPollMixin) BaseOption, PublishPollMixin, BaseVote)
from openslides.agenda.models import Item from openslides.agenda.models import Item
@ -143,7 +143,7 @@ class Assignment(models.Model, SlideMixin):
# candidate related to this poll # candidate related to this poll
poll_option = poll.get_options().get(candidate=candidate) poll_option = poll.get_options().get(candidate=candidate)
for vote in poll_option.get_votes(): for vote in poll_option.get_votes():
votes[vote.value] = vote.get_weight() votes[vote.value] = vote.print_weight()
except AssignmentOption.DoesNotExist: except AssignmentOption.DoesNotExist:
# candidate not in related to this poll # candidate not in related to this poll
votes = None votes = None
@ -196,8 +196,14 @@ class Assignment(models.Model, SlideMixin):
register_slidemodel(Assignment) register_slidemodel(Assignment)
class AssignmentVote(BaseVote):
option = models.ForeignKey('AssignmentOption')
class AssignmentOption(BaseOption): class AssignmentOption(BaseOption):
poll = models.ForeignKey('AssignmentPoll')
candidate = models.ForeignKey(Profile) candidate = models.ForeignKey(Profile)
vote_class = AssignmentVote
def __unicode__(self): def __unicode__(self):
return unicode(self.candidate) return unicode(self.candidate)

View File

@ -14,7 +14,6 @@ from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from openslides.utils.forms import CssClassMixin from openslides.utils.forms import CssClassMixin
from openslides.poll.models import Vote
class OptionForm(forms.Form, CssClassMixin): class OptionForm(forms.Form, CssClassMixin):
@ -27,7 +26,7 @@ class OptionForm(forms.Form, CssClassMixin):
for vote in extra: for vote in extra:
key = vote.value key = vote.value
value = vote.get_value() value = vote.get_value()
weight = vote.get_weight(raw=True) weight = vote.print_weight(raw=True)
self.fields[key] = forms.IntegerField( self.fields[key] = forms.IntegerField(
label=value, label=value,
initial=weight, initial=weight,

View File

@ -10,6 +10,7 @@
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.core.exceptions import ObjectDoesNotExist
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _, ugettext_noop from django.utils.translation import ugettext_lazy as _, ugettext_noop
@ -17,35 +18,57 @@ from openslides.utils.modelfields import MinMaxIntegerField
class BaseOption(models.Model): 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): 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): class BaseVote(models.Model):
text = models.CharField(max_length=255) """
Base Vote class for an option.
def __unicode__(self): Subclasses have to define a option-field, which are a subclass of
return self.text BaseOption.
"""
class Vote(models.Model):
option = models.ForeignKey(BaseOption)
#profile = models.ForeignKey(Profile) # TODO: we need a person+ here
weight = models.IntegerField(default=1, null=True) # Use MinMaxIntegerField weight = models.IntegerField(default=1, null=True) # Use MinMaxIntegerField
value = models.CharField(max_length=255, null=True) value = models.CharField(max_length=255, null=True)
def get_weight(self, raw=False): def print_weight(self, raw=False):
if raw: if raw:
return self.weight 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): def get_value(self):
return unicode(_(self.value)) return _(self.value)
def __unicode__(self): def __unicode__(self):
return self.get_weight() return self.print_weight()
class Meta:
abstract = True
class CountVotesCast(models.Model): class CountVotesCast(models.Model):
@ -58,6 +81,9 @@ class CountVotesCast(models.Model):
def print_votescast(self): def print_votescast(self):
return print_value(self.votescast) return print_value(self.votescast)
def percent_base(self):
return 100 / float(self.votescast)
class Meta: class Meta:
abstract = True abstract = True
@ -70,7 +96,13 @@ class CountInvalid(models.Model):
fields.append('votesinvalid') fields.append('votesinvalid')
def print_votesinvalid(self): 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: class Meta:
abstract = True abstract = True
@ -88,14 +120,16 @@ class PublishPollMixin(models.Model):
class BasePoll(models.Model): class BasePoll(models.Model):
option_class = TextOption """
Base poll class.
"""
vote_values = [ugettext_noop('votes')] vote_values = [ugettext_noop('votes')]
def has_votes(self): def has_votes(self):
""" """
Return True, the there are votes in the poll. 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 True
return False return False
@ -128,6 +162,19 @@ class BasePoll(models.Model):
""" """
return self.vote_values 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): def set_form_values(self, option, data):
# TODO: recall this function. It has nothing to do with a form # 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(): for value in self.get_vote_values():
try: try:
vote = Vote.objects.filter(option=option).get(value=value) vote = self.get_votes().filter(option=option).get(value=value)
except Vote.DoesNotExist: except ObjectDoesNotExist:
vote = Vote(option=option, value=value) vote = self.get_vote_class()(option=option, value=value)
vote.weight = data[value] vote.weight = data[value]
vote.save() vote.save()
@ -150,10 +197,11 @@ class BasePoll(models.Model):
values = [] values = []
for value in self.get_vote_values(): for value in self.get_vote_values():
try: 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) values.append(vote)
except Vote.DoesNotExist: except ObjectDoesNotExist:
values.append(Vote(value=value, weight='')) values.append(self.get_vote_class()(value=value, weight=''))
return values return values
def get_vote_form(self, **kwargs): def get_vote_form(self, **kwargs):
@ -175,12 +223,19 @@ class BasePoll(models.Model):
forms.append(form) forms.append(form)
return forms return forms
class Meta:
abstract = True
def print_value(value, percent_base=0):
def print_value(value):
if value == -1: if value == -1:
value = _('majority') return unicode(_('majority'))
elif value == -2: elif value == -2:
value = _('undocumented') return unicode(_('undocumented'))
elif value is None: elif value is None:
value = '' return u''
return unicode(value) if not percent_base:
return u'%s' % value
return u'%d (%.2f %%)' % (value, value * percent_base)

View File

@ -48,14 +48,14 @@ class PollFormView(TemplateView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.set_poll(self.kwargs['poll_id']) 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() FormClass = self.get_modelform_class()
pollform = FormClass(data=self.request.POST, instance=self.poll, pollform = FormClass(data=self.request.POST, instance=self.poll,
prefix='pollform') prefix='pollform')
error = False error = False
for form in forms: for form in option_forms:
if not form.is_valid(): if not form.is_valid():
error = True error = True
@ -64,11 +64,11 @@ class PollFormView(TemplateView):
if error: if error:
return self.render_to_response(self.get_context_data( return self.render_to_response(self.get_context_data(
forms=forms, forms=option_forms,
pollform=pollform, pollform=pollform,
)) ))
for form in forms: for form in option_forms:
data = {} data = {}
for value in self.poll.get_vote_values(): for value in self.poll.get_vote_values():
data[value] = form.cleaned_data[value] data[value] = form.cleaned_data[value]