Merge pull request #1146 from normanjaeckel/ReworkPoll

Rename some classes of the poll api. Clean up the poll api.
This commit is contained in:
Norman Jäckel 2013-12-06 15:38:41 -08:00
commit 9bb7cd1388
5 changed files with 103 additions and 88 deletions

View File

@ -16,6 +16,7 @@ Other:
- Changed widget api. Used new metaclass. - Changed widget api. Used new metaclass.
- Changed api for plugins. - Changed api for plugins.
- Renamed config api classes. - Renamed config api classes.
- Renamed some classes of the poll api.
Version 1.5.1 (unreleased) Version 1.5.1 (unreleased)

View File

@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy, ugettext_noop
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,
CountInvalid, CountVotesCast, CollectInvalid, CollectVotesCast,
PublishPollMixin) PublishPollMixin)
from openslides.projector.models import RelatedModelMixin, SlideMixin from openslides.projector.models import RelatedModelMixin, SlideMixin
from openslides.utils.person import PersonField from openslides.utils.person import PersonField
@ -249,7 +249,7 @@ class AssignmentOption(BaseOption):
return unicode(self.candidate) return unicode(self.candidate)
class AssignmentPoll(RelatedModelMixin, CountInvalid, CountVotesCast, class AssignmentPoll(RelatedModelMixin, CollectInvalid, CollectVotesCast,
PublishPollMixin, BasePoll): PublishPollMixin, BasePoll):
option_class = AssignmentOption option_class = AssignmentOption
@ -290,9 +290,5 @@ class AssignmentPoll(RelatedModelMixin, CountInvalid, CountVotesCast,
else: else:
return [ugettext_noop('Votes')] return [ugettext_noop('Votes')]
def append_pollform_fields(self, fields):
CountInvalid.append_pollform_fields(self, fields)
CountVotesCast.append_pollform_fields(self, fields)
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()

View File

@ -10,7 +10,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,
CountInvalid, CountVotesCast) CollectInvalid, CollectVotesCast)
from openslides.projector.models import RelatedModelMixin, SlideMixin from openslides.projector.models import RelatedModelMixin, SlideMixin
from openslides.utils.jsonfield import JSONField from openslides.utils.jsonfield import JSONField
from openslides.utils.person import PersonField from openslides.utils.person import PersonField
@ -686,7 +686,7 @@ class MotionOption(BaseOption):
"""The VoteClass, to witch this Class links.""" """The VoteClass, to witch this Class links."""
class MotionPoll(RelatedModelMixin, CountInvalid, CountVotesCast, BasePoll): class MotionPoll(RelatedModelMixin, CollectInvalid, CollectVotesCast, 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')
@ -731,11 +731,6 @@ class MotionPoll(RelatedModelMixin, CountInvalid, CountVotesCast, BasePoll):
# or call this in save() # or call this in save()
self.get_option_class()(poll=self).save() self.get_option_class()(poll=self).save()
def append_pollform_fields(self, fields):
"""Apend the fields for invalid and votecast to the ModelForm."""
CountInvalid.append_pollform_fields(self, fields)
CountVotesCast.append_pollform_fields(self, fields)
def get_related_model(self): def get_related_model(self):
return self.motion return self.motion

View File

@ -2,44 +2,59 @@
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_lazy as _ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_noop from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.utils.models import MinMaxIntegerField from openslides.utils.models import MinMaxIntegerField
class BaseOption(models.Model): class BaseOption(models.Model):
""" """
Base option class for a Poll. Base option class for a poll.
Subclasses have to define a poll-field, which are a subclass of BasePoll. Subclasses have to define a poll field. This must be a ForeignKeyField
to a subclass of BasePoll. There must also be a vote_class attribute
which has to be a subclass of BaseVote. Otherwise you have to override the
get_vote_class method.
""" """
vote_class = None
class Meta:
abstract = True
def get_votes(self): def get_votes(self):
return self.get_vote_class().objects.filter(option=self) return self.get_vote_class().objects.filter(option=self)
def get_vote_class(self):
if self.vote_class is None:
raise NotImplementedError('The option class %s has to have an attribute vote_class.' % self)
return self.vote_class
def __getitem__(self, name): def __getitem__(self, name):
try: try:
return self.get_votes().get(value=name) return self.get_votes().get(value=name)
except self.get_vote_class().DoesNotExist: except self.get_vote_class().DoesNotExist:
return None return None
def get_vote_class(self):
return self.vote_class class BaseVote(models.Model):
"""
Base vote class for an option.
Subclasses have to define an option field. This must be a ForeignKeyField
to a subclass of BasePoll.
"""
weight = models.IntegerField(default=1, null=True) # Use MinMaxIntegerField
value = models.CharField(max_length=255, null=True)
class Meta: class Meta:
abstract = True abstract = True
def __unicode__(self):
return self.print_weight()
class BaseVote(models.Model): def get_value(self):
""" return _(self.value)
Base Vote class for an option.
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 print_weight(self, raw=False): def print_weight(self, raw=False):
if raw: if raw:
@ -47,27 +62,24 @@ class BaseVote(models.Model):
try: try:
percent_base = self.option.poll.percent_base() percent_base = self.option.poll.percent_base()
except AttributeError: except AttributeError:
# The poll class is no child of CountVotesCast # 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)
def get_value(self):
return _(self.value)
def __unicode__(self): class CollectVotesCast(models.Model):
return self.print_weight() """
Mixin for a poll to collect the votes cast.
"""
votescast = MinMaxIntegerField(null=True, blank=True, min_value=-2,
verbose_name=ugettext_lazy('Votes cast'))
class Meta: class Meta:
abstract = True abstract = True
class CountVotesCast(models.Model):
votescast = MinMaxIntegerField(null=True, blank=True, min_value=-2,
verbose_name=_("Votes cast"))
def append_pollform_fields(self, fields): def append_pollform_fields(self, fields):
fields.append('votescast') fields.append('votescast')
super(CollectVotesCast, self).append_pollform_fields(fields)
def print_votescast(self): def print_votescast(self):
return print_value(self.votescast, self.percent_base()) return print_value(self.votescast, self.percent_base())
@ -77,40 +89,43 @@ class CountVotesCast(models.Model):
return 100 / float(self.votescast) return 100 / float(self.votescast)
return 0 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: class Meta:
abstract = True abstract = True
class CountInvalid(models.Model):
votesinvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2,
verbose_name=_("Votes invalid"))
def append_pollform_fields(self, fields): def append_pollform_fields(self, fields):
fields.append('votesinvalid') fields.append('votesinvalid')
super(CollectInvalid, self).append_pollform_fields(fields)
def print_votesinvalid(self): def print_votesinvalid(self):
try: try:
percent_base = self.percent_base() percent_base = self.percent_base()
except AttributeError: except AttributeError:
# The poll class is no child of CountVotesCast # The poll class is no child of CollectVotesCast
percent_base = 0 percent_base = 0
return print_value(self.votesinvalid, percent_base) return print_value(self.votesinvalid, percent_base)
class Meta:
abstract = True
class PublishPollMixin(models.Model): class PublishPollMixin(models.Model):
"""
Mixin for a poll to add a flag whether the poll is published or not.
"""
published = models.BooleanField(default=False) published = models.BooleanField(default=False)
class Meta:
abstract = True
def set_published(self, published): def set_published(self, published):
self.published = published self.published = published
self.save() self.save()
class Meta:
abstract = True
class BasePoll(models.Model): class BasePoll(models.Model):
""" """
@ -118,9 +133,12 @@ class BasePoll(models.Model):
""" """
vote_values = [ugettext_noop('votes')] vote_values = [ugettext_noop('votes')]
class Meta:
abstract = True
def has_votes(self): def has_votes(self):
""" """
Return True, the there are votes in the poll. Returns True if there are votes in the poll.
""" """
if self.get_votes().exists(): if self.get_votes().exists():
return True return True
@ -128,9 +146,9 @@ class BasePoll(models.Model):
def set_options(self, options_data=[]): def set_options(self, options_data=[]):
""" """
Add new Option pbjects to the poll. Adds new option objects to the poll.
option_data: A List of arguments for the Option. option_data: A list of arguments for the option.
""" """
for option_data in options_data: for option_data in options_data:
option = self.get_option_class()(**option_data) option = self.get_option_class()(**option_data)
@ -139,38 +157,37 @@ class BasePoll(models.Model):
def get_options(self): def get_options(self):
""" """
Return the option objects for the poll. Returns the option objects for the poll.
""" """
return self.get_option_class().objects.filter(poll=self) return self.get_option_class().objects.filter(poll=self)
def get_option_class(self): def get_option_class(self):
""" """
Return the option class for the poll. Default is self.option_class. Returns the option class for the poll. Default is self.option_class.
""" """
return self.option_class return self.option_class
def get_vote_values(self): def get_vote_values(self):
""" """
Return the possible values for the poll as list. Returns the possible values for the poll. Default is as list.
""" """
return self.vote_values return self.vote_values
def get_vote_class(self): def get_vote_class(self):
""" """
Return the releatet vote class. Returns the related vote class.
""" """
return self.get_option_class().vote_class return self.get_option_class().vote_class
def get_votes(self): def get_votes(self):
""" """
Return a QuerySet with all vote objects, releatet to this poll. Return a QuerySet with all vote objects related to this poll.
""" """
return self.get_vote_class().objects.filter(option__poll__id=self.id) return self.get_vote_class().objects.filter(option__poll__id=self.id)
def set_form_values(self, option, data): def set_vote_objects_with_values(self, option, data):
# TODO: recall this function. It has nothing to do with a form
""" """
Create or update the vote objects for the poll. Creates or updates the vote objects for the poll.
""" """
for value in self.get_vote_values(): for value in self.get_vote_values():
try: try:
@ -180,33 +197,31 @@ class BasePoll(models.Model):
vote.weight = data[value] vote.weight = data[value]
vote.save() vote.save()
def get_form_values(self, option_id): def get_vote_objects_with_values(self, option_id):
# TODO: recall this function. It has nothing to do with a form
""" """
Return a the values and the weight of the values as a list with two Returns the vote values and their weight as a list with two elements.
elements.
""" """
values = [] values = []
for value in self.get_vote_values(): for value in self.get_vote_values():
try: try:
vote = self.get_votes().filter(option=option_id) \ vote = self.get_votes().filter(option=option_id).get(value=value)
.get(value=value)
values.append(vote)
except ObjectDoesNotExist: except ObjectDoesNotExist:
values.append(self.get_vote_class()(value=value, weight='')) values.append(self.get_vote_class()(value=value, weight=''))
else:
values.append(vote)
return values return values
def get_vote_form(self, **kwargs): def get_vote_form(self, **kwargs):
""" """
Return the form for one option of the poll. Returns the form for one option of the poll.
""" """
from openslides.poll.forms import OptionForm from openslides.poll.forms import OptionForm
return OptionForm(extra=self.get_form_values(kwargs['formid']), return OptionForm(extra=self.get_vote_objects_with_values(kwargs['formid']),
**kwargs) **kwargs)
def get_vote_forms(self, **kwargs): def get_vote_forms(self, **kwargs):
""" """
Return a list of forms for the poll Returns a list of forms for the poll.
""" """
forms = [] forms = []
for option in self.get_options(): for option in self.get_options():
@ -215,19 +230,28 @@ class BasePoll(models.Model):
forms.append(form) forms.append(form)
return forms return forms
class Meta: def append_pollform_fields(self, fields):
abstract = True """
Appends additional field to a given list of fields. By default it
appends nothing.
"""
pass
def print_value(value, percent_base=0): def print_value(value, percent_base=0):
"""
Returns a human readable string for the vote value. It is 'majority',
'undocumented' or the vote value with percent value if so.
"""
if value == -1: if value == -1:
return unicode(_('majority')) verbose_value = _('majority')
elif value == -2: elif value == -2:
return unicode(_('undocumented')) verbose_value = _('undocumented')
elif value is None: elif value is None:
return unicode(_('undocumented')) verbose_value = _('undocumented')
if not percent_base: else:
return u'%s' % value if percent_base:
verbose_value = u'%d (%.2f %%)' % (value, value * percent_base)
return u'%d (%.2f %%)' % (value, value * percent_base) else:
verbose_value = u'%s' % value
return verbose_value

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from django.core.exceptions import ImproperlyConfigured
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
@ -40,7 +39,7 @@ class PollFormView(FormMixin, TemplateView):
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]
self.poll.set_form_values(form.option, data) self.poll.set_vote_objects_with_values(form.option, data)
pollform.save() pollform.save()
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
@ -49,9 +48,9 @@ class PollFormView(FormMixin, TemplateView):
if self.poll_class is not None: if self.poll_class is not None:
return self.poll_class return self.poll_class
else: else:
raise ImproperlyConfigured( raise NotImplementedError(
"No poll class defined. Either provide a poll_class or define" 'No poll class defined. Either provide a poll_class or define '
" a get_poll_class method.") 'a get_poll_class method.')
def get_object(self): def get_object(self):
return self.get_poll_class().objects.get(pk=self.kwargs['poll_id']) return self.get_poll_class().objects.get(pk=self.kwargs['poll_id'])