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:
parent
969816be37
commit
66b853a10a
@ -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 {
|
||||||
|
@ -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 }}
|
||||||
|
@ -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 %}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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]
|
||||||
|
Loading…
Reference in New Issue
Block a user