Merge pull request #1248 from emanuelschuetze/fix-1102

New poll config option for 100% base (fixed #1102 and #1240)
This commit is contained in:
Emanuel Schütze 2014-04-28 22:08:40 +02:00
commit 1fc0803846
17 changed files with 212 additions and 82 deletions

View File

@ -21,6 +21,7 @@ Participants:
Files: Files:
- Enabled update and delete view for uploader refering to his own files. - Enabled update and delete view for uploader refering to his own files.
Other: Other:
- New config option to set the 100% base for polls (motions/elections).
- Changed widget api. Used new metaclass. - Changed widget api. Used new metaclass.
- Changed api for plugins. Used entry points to detect them automaticly. - Changed api for plugins. Used entry points to detect them automaticly.
- Renamed config api classes. - Renamed config api classes.

View File

@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.agenda.models import Item, Speaker from openslides.agenda.models import Item, Speaker
from openslides.config.api import config from openslides.config.api import config
from openslides.poll.models import (BaseOption, BasePoll, BaseVote, from openslides.poll.models import (BaseOption, BasePoll, BaseVote,
CollectInvalid, CollectVotesCast, CollectDefaultVotesMixin,
PublishPollMixin) PublishPollMixin)
from openslides.projector.models import RelatedModelMixin, SlideMixin from openslides.projector.models import RelatedModelMixin, SlideMixin
from openslides.utils.exceptions import OpenSlidesError from openslides.utils.exceptions import OpenSlidesError
@ -271,7 +271,7 @@ class AssignmentOption(BaseOption):
return unicode(self.candidate) return unicode(self.candidate)
class AssignmentPoll(RelatedModelMixin, CollectInvalid, CollectVotesCast, class AssignmentPoll(RelatedModelMixin, CollectDefaultVotesMixin,
PublishPollMixin, AbsoluteUrlMixin, BasePoll): PublishPollMixin, AbsoluteUrlMixin, BasePoll):
option_class = AssignmentOption option_class = AssignmentOption
assignment = models.ForeignKey(Assignment, related_name='poll_set') assignment = models.ForeignKey(Assignment, related_name='poll_set')
@ -319,5 +319,9 @@ class AssignmentPoll(RelatedModelMixin, CollectInvalid, CollectVotesCast,
def get_ballot(self): def get_ballot(self):
return self.assignment.poll_set.filter(id__lte=self.id).count() return self.assignment.poll_set.filter(id__lte=self.id).count()
def get_percent_base_choice(self):
return config['assignment_poll_100_percent_base']
def append_pollform_fields(self, fields): def append_pollform_fields(self, fields):
fields.append('description') fields.append('description')
super(AssignmentPoll, self).append_pollform_fields(fields)

View File

@ -7,6 +7,7 @@ from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
from openslides.config.signals import config_signal from openslides.config.signals import config_signal
from openslides.poll.models import PERCENT_BASE_CHOICES
@receiver(config_signal, dispatch_uid='setup_assignment_config') @receiver(config_signal, dispatch_uid='setup_assignment_config')
@ -26,6 +27,14 @@ def setup_assignment_config(sender, **kwargs):
('auto', ugettext_lazy('Automatic assign of method')), ('auto', ugettext_lazy('Automatic assign of method')),
('votes', ugettext_lazy('Always one option per candidate')), ('votes', ugettext_lazy('Always one option per candidate')),
('yesnoabstain', ugettext_lazy('Always Yes-No-Abstain per candidate'))))) ('yesnoabstain', ugettext_lazy('Always Yes-No-Abstain per candidate')))))
assignment_poll_100_percent_base = ConfigVariable(
name='assignment_poll_100_percent_base',
default_value='WITHOUT_INVALID',
form_field=forms.ChoiceField(
widget=forms.Select(),
required=False,
label=ugettext_lazy('The 100 % base of an election result consists of'),
choices=PERCENT_BASE_CHOICES))
assignment_pdf_ballot_papers_selection = ConfigVariable( assignment_pdf_ballot_papers_selection = ConfigVariable(
name='assignment_pdf_ballot_papers_selection', name='assignment_pdf_ballot_papers_selection',
default_value='CUSTOM_NUMBER', default_value='CUSTOM_NUMBER',
@ -55,6 +64,7 @@ def setup_assignment_config(sender, **kwargs):
group_ballot = ConfigGroup( group_ballot = ConfigGroup(
title=ugettext_lazy('Ballot and ballot papers'), title=ugettext_lazy('Ballot and ballot papers'),
variables=(assignment_poll_vote_values, variables=(assignment_poll_vote_values,
assignment_poll_100_percent_base,
assignment_pdf_ballot_papers_selection, assignment_pdf_ballot_papers_selection,
assignment_pdf_ballot_papers_number, assignment_pdf_ballot_papers_number,
assignment_publish_winner_results_only)) assignment_publish_winner_results_only))

View File

@ -187,8 +187,8 @@
<td> <td>
{% if candidate in assignment.elected %} {% if candidate in assignment.elected %}
{% if perms.assignment.can_manage_assignment %} {% if perms.assignment.can_manage_assignment %}
<a class="election_link elected" href="{% url 'assignment_user_not_elected' assignment.id candidate.person_id %}" <a class="election_link elected tooltip-bottom" href="{% url 'assignment_user_not_elected' assignment.id candidate.person_id %}"
rel="tooltip" data-original-title="{% trans 'Mark candidate as elected' %}"></a> data-original-title="{% trans 'Mark candidate as elected' %}"></a>
{% else %} {% else %}
<a class="elected"> <a class="elected">
<img src="{% static 'img/voting-yes.png' %}" class="tooltip-bottom" data-original-title="{% trans 'Candidate is elected' %}"> <img src="{% static 'img/voting-yes.png' %}" class="tooltip-bottom" data-original-title="{% trans 'Candidate is elected' %}">
@ -196,7 +196,8 @@
{% endif %} {% endif %}
{% else %} {% else %}
{% if perms.assignment.can_manage_assignment %} {% if perms.assignment.can_manage_assignment %}
<a class="election_link" href="{% url 'assignment_user_elected' assignment.id candidate.person_id %}"></a> <a class="election_link tooltip-bottom" href="{% url 'assignment_user_elected' assignment.id candidate.person_id %}"
data-original-title="{% trans 'Mark candidate as elected' %}"></a>
{% endif %} {% endif %}
{% endif %} {% endif %}
<a href="{{ candidate|absolute_url }}">{{ candidate }}</a> <a href="{{ candidate|absolute_url }}">{{ candidate }}</a>
@ -221,13 +222,29 @@
{% endif %} {% endif %}
</tr> </tr>
{% endfor %} {% endfor %}
<tr>
<td>{% trans 'Valid votes' %}</td>
{% for poll in polls %}
{% if poll.published or perms.assignment.can_manage_assignment %}
<td style="white-space:nowrap;">
{% if poll.has_votes %}
<img src="{% static 'img/voting-yes-grey.png' %}" class="tooltip-left" data-original-title="{% trans 'Valid votes' %}">
{{ poll.print_votesvalid }}
{% endif %}
</td>
{% endif %}
{% endfor %}
{% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
<td></td>
{% endif %}
</tr>
<tr> <tr>
<td>{% trans 'Invalid votes' %}</td> <td>{% trans 'Invalid votes' %}</td>
{% for poll in polls %} {% for poll in polls %}
{% if poll.published or perms.assignment.can_manage_assignment %} {% if poll.published or perms.assignment.can_manage_assignment %}
<td style="white-space:nowrap;"> <td style="white-space:nowrap;">
{% if poll.has_votes %} {% if poll.has_votes %}
<img src="{% static 'img/voting-invalid.png' %}" class="tooltip-left" data-original-title="{% trans 'Invalid' %}"> <img src="{% static 'img/voting-invalid.png' %}" class="tooltip-left" data-original-title="{% trans 'Invalid votes' %}">
{{ poll.print_votesinvalid }} {{ poll.print_votesinvalid }}
{% endif %} {% endif %}
</td> </td>
@ -238,13 +255,13 @@
{% endif %} {% endif %}
</tr> </tr>
<tr class="info total"> <tr class="info total">
<td><strong>{% trans 'Votes cast' %}</strong></td> <td>{% trans 'Votes cast' %}</td>
{% for poll in polls %} {% for poll in polls %}
{% if poll.published or perms.assignment.can_manage_assignment %} {% if poll.published or perms.assignment.can_manage_assignment %}
<td style="white-space:nowrap;"> <td style="white-space:nowrap;">
{% if poll.has_votes %} {% if poll.has_votes %}
<img src="{% static 'img/voting-total.png' %}" class="tooltip-left" data-original-title="{% trans 'Votes cast' %}"> <img src="{% static 'img/voting-total.png' %}" class="tooltip-left" data-original-title="{% trans 'Votes cast' %}">
<strong>{{ poll.print_votescast }}</strong> {{ poll.print_votescast }}
{% endif %} {% endif %}
</td> </td>
{% endif %} {% endif %}

View File

@ -50,6 +50,16 @@
{% endfor %} {% endfor %}
</tr> </tr>
{% endfor %} {% endfor %}
<tr>
<td>{% trans "Valid votes" %}</td>
{% for value in poll.get_vote_values %}
{% if forloop.first %}
<td>{{ pollform.votesvalid.errors }}{{ pollform.votesvalid }}</td>
{% else %}
<td></td>
{% endif %}
{% endfor %}
</tr>
<tr> <tr>
<td>{% trans "Invalid votes" %}</td> <td>{% trans "Invalid votes" %}</td>
{% for value in poll.get_vote_values %} {% for value in poll.get_vote_values %}

View File

@ -80,12 +80,23 @@
{% endfor %} {% endfor %}
</tr> </tr>
{% endfor %} {% endfor %}
<tr class="total">
<td>{% trans 'Valid votes' %}</td>
{% for poll in polls %}
<td style="white-space:nowrap;">
{% if poll.has_votes %}
<img src="{% static 'img/voting-yes-grey.png' %}" title="{% trans 'Valid votes' %}">
{{ poll.print_votesvalid }}
{% endif %}
</td>
{% endfor %}
</tr>
<tr> <tr>
<td>{% trans 'Invalid votes' %}</td> <td>{% trans 'Invalid votes' %}</td>
{% for poll in polls %} {% for poll in polls %}
<td style="white-space:nowrap;"> <td style="white-space:nowrap;">
{% if poll.has_votes %} {% if poll.has_votes %}
<img src="{% static 'img/voting-invalid.png' %}" title="{% trans 'Invalid' %}"> <img src="{% static 'img/voting-invalid.png' %}" title="{% trans 'Invalid votes' %}">
{{ poll.print_votesinvalid }} {{ poll.print_votesinvalid }}
{% endif %} {% endif %}
</td> </td>
@ -94,13 +105,13 @@
</tr> </tr>
<tr class="total"> <tr class="total">
<td> <td>
<strong>{% trans 'Votes cast' %}</strong> {% trans 'Votes cast' %}
</td> </td>
{% for poll in polls %} {% for poll in polls %}
<td style="white-space:nowrap;"> <td style="white-space:nowrap;">
{% if poll.has_votes %} {% if poll.has_votes %}
<img src="{% static 'img/voting-total.png' %}" title="{% trans 'Votes cast' %}"> <img src="{% static 'img/voting-total.png' %}" title="{% trans 'Votes cast' %}">
<strong>{{ poll.print_votescast }}</strong> {{ poll.print_votescast }}
{% endif %} {% endif %}
</td> </td>
{% endfor %} {% endfor %}

View File

@ -423,20 +423,30 @@ class AssignmentPDF(PDFView):
pass pass
data_votes.append(row) data_votes.append(row)
# Add votes invalid row # Add valid votes row
if poll.votesvalid is not None:
footrow_one = [] footrow_one = []
footrow_one.append(_("Invalid votes")) footrow_one.append(_("Valid votes"))
for poll in polls: for poll in polls:
footrow_one.append(poll.print_votesinvalid()) footrow_one.append(poll.print_votesvalid())
data_votes.append(footrow_one) data_votes.append(footrow_one)
# Add votes cast row # Add invalid votes row
if poll.votesinvalid is not None:
footrow_two = [] footrow_two = []
footrow_two.append(_("Votes cast")) footrow_two.append(_("Invalid votes"))
for poll in polls: for poll in polls:
footrow_two.append(poll.print_votescast()) footrow_two.append(poll.print_votesinvalid())
data_votes.append(footrow_two) data_votes.append(footrow_two)
# Add votes cast row
if poll.votescast is not None:
footrow_three = []
footrow_three.append(_("Votes cast"))
for poll in polls:
footrow_three.append(poll.print_votescast())
data_votes.append(footrow_three)
table_votes = Table(data_votes) table_votes = Table(data_votes)
table_votes.setStyle( table_votes.setStyle(
TableStyle([ TableStyle([

View File

@ -9,8 +9,7 @@ from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.config.api import config from openslides.config.api import config
from openslides.mediafile.models import Mediafile from openslides.mediafile.models import Mediafile
from openslides.poll.models import (BaseOption, BasePoll, BaseVote, from openslides.poll.models import (BaseOption, BasePoll, BaseVote, CollectDefaultVotesMixin)
CollectInvalid, CollectVotesCast)
from openslides.projector.models import RelatedModelMixin, SlideMixin from openslides.projector.models import RelatedModelMixin, SlideMixin
from jsonfield import JSONField from jsonfield import JSONField
from openslides.utils.models import AbsoluteUrlMixin from openslides.utils.models import AbsoluteUrlMixin
@ -694,8 +693,7 @@ class MotionOption(BaseOption):
"""The VoteClass, to witch this Class links.""" """The VoteClass, to witch this Class links."""
class MotionPoll(RelatedModelMixin, CollectInvalid, CollectVotesCast, class MotionPoll(RelatedModelMixin, CollectDefaultVotesMixin, AbsoluteUrlMixin, BasePoll):
AbsoluteUrlMixin, BasePoll):
"""The Class to saves the poll results for a motion poll.""" """The Class to saves the poll results for a motion poll."""
motion = models.ForeignKey(Motion, related_name='polls') motion = models.ForeignKey(Motion, related_name='polls')
@ -746,6 +744,9 @@ class MotionPoll(RelatedModelMixin, CollectInvalid, CollectVotesCast,
def get_related_model(self): def get_related_model(self):
return self.motion return self.motion
def get_percent_base_choice(self):
return config['motion_poll_100_percent_base']
class State(models.Model): class State(models.Model):
""" """

View File

@ -113,17 +113,21 @@ def motion_to_pdf(pdf, motion):
for poll in polls: for poll in polls:
ballotcounter += 1 ballotcounter += 1
option = poll.get_options()[0] option = poll.get_options()[0]
yes, no, abstain, invalid, votecast = ( yes, no, abstain = (option['Yes'], option['No'], option['Abstain'])
option['Yes'], option['No'], option['Abstain'], valid, invalid, votescast = ('', '', '')
poll.print_votesinvalid(), poll.print_votescast()) if poll.votesvalid is not None:
valid = "<br/>%s: %s" % (_("Valid votes"), poll.print_votesvalid())
if poll.votesinvalid is not None:
invalid = "<br/>%s: %s" % (_("Invalid votes"), poll.print_votesinvalid())
if poll.votescast is not None:
votescast = "<br/>%s: %s" % (_("Votes cast"), poll.print_votescast())
if len(polls) > 1: if len(polls) > 1:
cell6b.append(Paragraph("%s. %s" % (ballotcounter, _("Vote")), cell6b.append(Paragraph("%s. %s" % (ballotcounter, _("Vote")),
stylesheet['Bold'])) stylesheet['Bold']))
cell6b.append(Paragraph( cell6b.append(Paragraph(
"%s: %s <br/> %s: %s <br/> %s: %s <br/> %s: %s <br/> %s: %s" % "%s: %s <br/> %s: %s <br/> %s: %s <br/> %s %s %s" %
(_("Yes"), yes, _("No"), no, _("Abstention"), abstain, _("Invalid"), (_("Yes"), yes, _("No"), no, _("Abstention"), abstain, valid, invalid, votescast),
invalid, _("Votes cast"), votecast), stylesheet['Normal'])) stylesheet['Normal']))
cell6b.append(Spacer(0, 0.2 * cm)) cell6b.append(Spacer(0, 0.2 * cm))
motion_data.append([cell6a, cell6b]) motion_data.append([cell6a, cell6b])

View File

@ -8,6 +8,7 @@ from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable from openslides.config.api import ConfigGroup, ConfigGroupedCollection, ConfigVariable
from openslides.config.signals import config_signal from openslides.config.signals import config_signal
from openslides.core.signals import post_database_setup from openslides.core.signals import post_database_setup
from openslides.poll.models import PERCENT_BASE_CHOICES
from .models import State, Workflow from .models import State, Workflow
@ -85,7 +86,15 @@ def setup_motion_config(sender, **kwargs):
title=ugettext_lazy('Supporters'), title=ugettext_lazy('Supporters'),
variables=(motion_min_supporters, motion_remove_supporters)) variables=(motion_min_supporters, motion_remove_supporters))
# Ballot papers # Voting and ballot papers
motion_poll_100_percent_base = ConfigVariable(
name='motion_poll_100_percent_base',
default_value='WITHOUT_INVALID',
form_field=forms.ChoiceField(
widget=forms.Select(),
required=False,
label=ugettext_lazy('The 100 % base of a voting result consists of'),
choices=PERCENT_BASE_CHOICES))
motion_pdf_ballot_papers_selection = ConfigVariable( motion_pdf_ballot_papers_selection = ConfigVariable(
name='motion_pdf_ballot_papers_selection', name='motion_pdf_ballot_papers_selection',
default_value='CUSTOM_NUMBER', default_value='CUSTOM_NUMBER',
@ -106,8 +115,8 @@ def setup_motion_config(sender, **kwargs):
min_value=1, min_value=1,
label=ugettext_lazy('Custom number of ballot papers'))) label=ugettext_lazy('Custom number of ballot papers')))
group_ballot_papers = ConfigGroup( group_ballot_papers = ConfigGroup(
title=ugettext_lazy('Ballot papers'), title=ugettext_lazy('Voting and ballot papers'),
variables=(motion_pdf_ballot_papers_selection, motion_pdf_ballot_papers_number)) variables=(motion_poll_100_percent_base, motion_pdf_ballot_papers_selection, motion_pdf_ballot_papers_number))
# PDF # PDF
motion_pdf_title = ConfigVariable( motion_pdf_title = ConfigVariable(

View File

@ -50,3 +50,9 @@ td.diff_header {
#motion-vote-results img { #motion-vote-results img {
margin-top: -4px; margin-top: -4px;
} }
#motion-vote-results .resultline {
border-top: 1px solid;
padding-top: 5px;
margin: 5px 0;
width: 10em;
}

View File

@ -221,10 +221,21 @@
<img src="{% static 'img/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ option.Yes }}<br> <img src="{% static 'img/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ option.Yes }}<br>
<img src="{% static 'img/voting-no.png' %}" title="{% trans 'No' %}"> {{ option.No }}<br> <img src="{% static 'img/voting-no.png' %}" title="{% trans 'No' %}"> {{ option.No }}<br>
<img src="{% static 'img/voting-abstention.png' %}" title="{% trans 'Abstention' %}"> {{ option.Abstain }}<br> <img src="{% static 'img/voting-abstention.png' %}" title="{% trans 'Abstention' %}"> {{ option.Abstain }}<br>
<img src="{% static 'img/voting-invalid.png' %}" title="{% trans 'Invalid' %}"> {{ poll.print_votesinvalid }}<br> {% if poll.votesvalid != None or poll.votesinvalid != None %}
<div style="border-top: 1px solid; padding-top: 5px; margin: 5px 0; width: 10em;"> <div class="resultline">
{% if poll.votesvalid != None %}
<img src="{% static 'img/voting-yes-grey.png' %}" title="{% trans 'Valid votes' %}"> {{ poll.print_votesvalid }}<br>
{% endif %}
{% if poll.votesinvalid != None %}
<img src="{% static 'img/voting-invalid.png' %}" title="{% trans 'Invalid votes' %}"> {{ poll.print_votesinvalid }}<br>
{% endif %}
</div>
{% endif %}
{% if poll.votescast != None %}
<div class="resultline">
<img src="{% static 'img/voting-total.png' %}" title="{% trans 'Votes cast' %}"> {{ poll.print_votescast }} <img src="{% static 'img/voting-total.png' %}" title="{% trans 'Votes cast' %}"> {{ poll.print_votescast }}
</div> </div>
{% endif %}
{% endwith %} {% endwith %}
{% else %} {% else %}
{% if perms.motion.can_manage_motion %} {% if perms.motion.can_manage_motion %}

View File

@ -45,6 +45,10 @@
</tr> </tr>
{% endfor %} {% endfor %}
<tr class="total"> <tr class="total">
<td>{% trans "Valid votes" %}</td>
<td>{{ pollform.votesvalid.errors }}{{ pollform.votesvalid }}</td>
</tr>
<tr class="">
<td>{% trans "Invalid votes" %}</td> <td>{% trans "Invalid votes" %}</td>
<td>{{ pollform.votesinvalid.errors }}{{ pollform.votesinvalid }}</td> <td>{{ pollform.votesinvalid.errors }}{{ pollform.votesinvalid }}</td>
</tr> </tr>

View File

@ -23,9 +23,19 @@
<img src="{% static 'img/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ option.Yes }} <br> <img src="{% static 'img/voting-yes.png' %}" title="{% trans 'Yes' %}"> {{ option.Yes }} <br>
<img src="{% static 'img/voting-no.png' %}" title="{% trans 'No' %}"> {{ option.No }} <br> <img src="{% static 'img/voting-no.png' %}" title="{% trans 'No' %}"> {{ option.No }} <br>
<img src="{% static 'img/voting-abstention.png' %}" title="{% trans 'Abstention' %}"> {{ option.Abstain }}<br> <img src="{% static 'img/voting-abstention.png' %}" title="{% trans 'Abstention' %}"> {{ option.Abstain }}<br>
<img src="{% static 'img/voting-invalid.png' %}" title="{% trans 'Invalid' %}"> {{ poll.print_votesinvalid }}<br> {% if poll.votesvalid != None or poll.votesinvalid != None %}
<hr> <div class="resultline"></div>
{% if poll.votesvalid != None %}
<img src="{% static 'img/voting-yes-grey.png' %}" title="{% trans 'Valid votes' %}"> {{ poll.print_votesvalid }}<br>
{% endif %}
{% if poll.votesinvalid != None %}
<img src="{% static 'img/voting-invalid.png' %}" title="{% trans 'Invalid votes' %}"> {{ poll.print_votesinvalid }}<br>
{% endif %}
{% endif %}
{% if poll.votescast != None %}
<div class="resultline"></div>
<img src="{% static 'img/voting-total.png' %}" title="{% trans 'Votes cast' %}"> {{ poll.print_votescast }} <img src="{% static 'img/voting-total.png' %}" title="{% trans 'Votes cast' %}"> {{ poll.print_votescast }}
{% endif %}
</div> </div>
{% endwith %} {% endwith %}
{% else %} {% else %}

View File

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import locale
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import models from django.db import models
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
@ -60,17 +62,28 @@ class BaseVote(models.Model):
if raw: if raw:
return self.weight return self.weight
try: try:
percent_base = self.option.poll.percent_base() percent_base = self.option.poll.get_percent_base()
except AttributeError: except AttributeError:
# The poll class is no child of CollectVotesCast # The poll class is no child of CollectVotesCast
percent_base = 0 percent_base = 0
return print_value(self.weight, percent_base) return print_value(self.weight, percent_base)
class CollectVotesCast(models.Model): PERCENT_BASE_CHOICES = (
('WITHOUT_INVALID', ugettext_lazy('All valid votes (Yes + No + Abstention)')),
('WITH_INVALID', ugettext_lazy('All votes casts (valid + invalid votes)')),
('DISABLED', ugettext_lazy('Disabled (no percents)')))
class CollectDefaultVotesMixin(models.Model):
""" """
Mixin for a poll to collect the votes cast. Mixin for a poll to collect the default vote values for valid votes,
invalid votes and votes cast.
""" """
votesvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2,
verbose_name=ugettext_lazy('Votes valid'))
votesinvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2,
verbose_name=ugettext_lazy('Votes invalid'))
votescast = MinMaxIntegerField(null=True, blank=True, min_value=-2, votescast = MinMaxIntegerField(null=True, blank=True, min_value=-2,
verbose_name=ugettext_lazy('Votes cast')) verbose_name=ugettext_lazy('Votes cast'))
@ -78,39 +91,46 @@ class CollectVotesCast(models.Model):
abstract = True abstract = True
def append_pollform_fields(self, fields): def append_pollform_fields(self, fields):
fields.append('votescast') fields.append('votesvalid')
super(CollectVotesCast, self).append_pollform_fields(fields)
def print_votescast(self):
return print_value(self.votescast, self.percent_base())
def percent_base(self):
if self.votescast > 0:
return 100 / float(self.votescast)
return 0
class CollectInvalid(models.Model):
"""
Mixin for a poll to collect invalid votes.
"""
votesinvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2,
verbose_name=ugettext_lazy('Votes invalid'))
class Meta:
abstract = True
def append_pollform_fields(self, fields):
fields.append('votesinvalid') fields.append('votesinvalid')
super(CollectInvalid, self).append_pollform_fields(fields) fields.append('votescast')
super(CollectDefaultVotesMixin, self).append_pollform_fields(fields)
def get_percent_base_choice(self):
"""
Returns one of the three strings in PERCENT_BASE_CHOICES.
"""
raise NotImplementedError('You have to provide a get_percent_base_choice() method.')
def print_votesvalid(self):
if self.get_percent_base_choice() == 'DISABLED':
value = print_value(self.votesvalid, None)
else:
value = print_value(self.votesvalid, self.get_percent_base())
return value
def print_votesinvalid(self): def print_votesinvalid(self):
try: if self.get_percent_base_choice() == 'WITH_INVALID':
percent_base = self.percent_base() value = print_value(self.votesinvalid, self.get_percent_base())
except AttributeError: else:
# The poll class is no child of CollectVotesCast value = print_value(self.votesinvalid, None)
percent_base = 0 return value
return print_value(self.votesinvalid, percent_base)
def print_votescast(self):
if self.get_percent_base_choice() == 'WITH_INVALID':
value = print_value(self.votescast, self.get_percent_base())
else:
value = print_value(self.votescast, None)
return value
def get_percent_base(self):
if self.get_percent_base_choice() == "WITHOUT_INVALID" and self.votesvalid > 0:
base = 100 / float(self.votesvalid)
elif self.get_percent_base_choice() == "WITH_INVALID" and self.votescast > 0:
base = 100 / float(self.votescast)
else:
base = None
return base
class PublishPollMixin(models.Model): class PublishPollMixin(models.Model):
@ -251,7 +271,8 @@ def print_value(value, percent_base=0):
verbose_value = _('undocumented') verbose_value = _('undocumented')
else: else:
if percent_base: if percent_base:
verbose_value = u'%d (%.2f %%)' % (value, value * percent_base) locale.setlocale(locale.LC_ALL, '')
verbose_value = u'%d (%s %%)' % (value, locale.format('%.1f', value * percent_base))
else: else:
verbose_value = u'%s' % value verbose_value = u'%s' % value
return verbose_value return verbose_value

View File

@ -104,9 +104,10 @@ li {
.well h4.first { .well h4.first {
margin-top: 0; margin-top: 0;
} }
.well .results hr { .resultline {
border-top: 1px solid;
margin: 5px 0; margin: 5px 0;
border: 1px solid #E3E3E3; width: 10em;
} }
hr { hr {
margin: 10px 0; margin: 10px 0;

View File

@ -86,10 +86,10 @@ class TestMotionDetailView(MotionViewTestCase):
response = self.staff_client.post( response = self.staff_client.post(
'/motion/1/poll/1/edit/', '/motion/1/poll/1/edit/',
{'option-1-Yes': '10', {'option-1-Yes': '10',
'pollform-votescast': '50'}) 'pollform-votesvalid': '50'})
self.assertRedirects(response, '/motion/1/') self.assertRedirects(response, '/motion/1/')
response = self.staff_client.get('/motion/1/') response = self.staff_client.get('/motion/1/')
self.assertContains(response, '100.00 %') self.assertContains(response, '50 (100')
def test_deleted_supporter(self): def test_deleted_supporter(self):
config['motion_min_supporters'] = 1 config['motion_min_supporters'] = 1