OpenSlides/openslides/poll/models.py

210 lines
6.3 KiB
Python

import locale
from decimal import Decimal
from typing import Optional, Type
from django.core.exceptions import ObjectDoesNotExist
from django.core.validators import MinValueValidator
from django.db import models
from django.utils.translation import ugettext as _
class BaseOption(models.Model):
"""
Base option class for a poll.
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: Optional[Type['BaseVote']] = None
class Meta:
abstract = True
def get_votes(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):
try:
return self.get_votes().get(value=name)
except self.get_vote_class().DoesNotExist:
raise KeyError
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.DecimalField(default=Decimal('1'), null=True, validators=[
MinValueValidator(Decimal('-2'))], max_digits=15, decimal_places=6)
value = models.CharField(max_length=255, null=True)
class Meta:
abstract = True
def __str__(self):
return self.print_weight()
def get_value(self):
return _(self.value)
def print_weight(self, raw=False):
if raw:
return self.weight
try:
percent_base = self.option.poll.get_percent_base()
except AttributeError:
# The poll class is no child of CollectVotesCast
percent_base = 0
return print_value(self.weight, percent_base)
class CollectDefaultVotesMixin(models.Model):
"""
Mixin for a poll to collect the default vote values for valid votes,
invalid votes and votes cast.
"""
votesvalid = models.DecimalField(null=True, blank=True, validators=[
MinValueValidator(Decimal('-2'))], max_digits=15, decimal_places=6)
votesinvalid = models.DecimalField(null=True, blank=True, validators=[
MinValueValidator(Decimal('-2'))], max_digits=15, decimal_places=6)
votescast = models.DecimalField(null=True, blank=True, validators=[
MinValueValidator(Decimal('-2'))], max_digits=15, decimal_places=6)
class Meta:
abstract = True
def get_percent_base_choice(self):
"""
Returns one of the strings of the percent base.
"""
raise NotImplementedError('You have to provide a get_percent_base_choice() method.')
class PublishPollMixin(models.Model):
"""
Mixin for a poll to add a flag whether the poll is published or not.
"""
published = models.BooleanField(default=False)
class Meta:
abstract = True
def set_published(self, published):
self.published = published
self.save()
class BasePoll(models.Model):
"""
Base poll class.
"""
vote_values = ['Votes']
class Meta:
abstract = True
def has_votes(self):
"""
Returns True if there are votes in the poll.
"""
if self.get_votes().exists():
return True
return False
def set_options(self, options_data=[], skip_autoupdate=False):
"""
Adds new option objects to the poll.
option_data: A list of arguments for the option.
"""
for option_data in options_data:
option = self.get_option_class()(**option_data)
option.poll = self
option.save(skip_autoupdate=skip_autoupdate)
def get_options(self):
"""
Returns the option objects for the poll.
"""
return self.get_option_class().objects.filter(poll=self)
def get_option_class(self):
"""
Returns the option class for the poll. Default is self.option_class.
"""
return self.option_class
def get_vote_values(self):
"""
Returns the possible values for the poll. Default is as list.
"""
return self.vote_values
def get_vote_class(self):
"""
Returns the related vote class.
"""
return self.get_option_class().vote_class
def get_votes(self):
"""
Return a QuerySet with all vote objects related to this poll.
"""
return self.get_vote_class().objects.filter(option__poll__id=self.id)
def set_vote_objects_with_values(self, option, data, skip_autoupdate=False):
"""
Creates or updates the vote objects for the poll.
"""
for value in self.get_vote_values():
try:
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(skip_autoupdate=skip_autoupdate)
def get_vote_objects_with_values(self, option_id):
"""
Returns the vote values and their weight as a list with two elements.
"""
values = []
for value in self.get_vote_values():
try:
vote = self.get_votes().filter(option=option_id).get(value=value)
except ObjectDoesNotExist:
values.append(self.get_vote_class()(value=value, weight=''))
else:
values.append(vote)
return values
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:
verbose_value = _('majority')
elif value == -2:
verbose_value = _('undocumented')
elif value is None:
verbose_value = _('undocumented')
else:
if percent_base:
locale.setlocale(locale.LC_ALL, '')
verbose_value = u'%d (%s %%)' % (value, locale.format('%.1f', value * percent_base))
else:
verbose_value = u'%s' % value
return verbose_value