Rework of assignment
fixes: #1429, #1345 A database recreation is needed
This commit is contained in:
parent
85dfbb185f
commit
c4c9322321
@ -1,7 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
@ -95,9 +95,8 @@ class Item(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, MPTTModel):
|
|||||||
"""
|
"""
|
||||||
Field for generic relation to a related object. Id of the object.
|
Field for generic relation to a related object. Id of the object.
|
||||||
"""
|
"""
|
||||||
# TODO: rename it to object_pk
|
|
||||||
|
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
"""
|
"""
|
||||||
Field for generic relation to a related object. General field to the related object.
|
Field for generic relation to a related object. General field to the related object.
|
||||||
"""
|
"""
|
||||||
|
@ -8,12 +8,12 @@ from .models import Assignment
|
|||||||
|
|
||||||
|
|
||||||
class AssignmentForm(CssClassMixin, forms.ModelForm):
|
class AssignmentForm(CssClassMixin, forms.ModelForm):
|
||||||
posts = forms.IntegerField(
|
open_posts = forms.IntegerField(
|
||||||
min_value=1, initial=1, label=ugettext_lazy("Number of available posts"))
|
min_value=1, initial=1, label=ugettext_lazy("Number of available posts"))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Assignment
|
model = Assignment
|
||||||
exclude = ('status', 'elected')
|
fields = ('title', 'description', 'open_posts', 'poll_description_default')
|
||||||
|
|
||||||
|
|
||||||
class AssignmentRunForm(CssClassMixin, forms.Form):
|
class AssignmentRunForm(CssClassMixin, forms.Form):
|
||||||
|
@ -8,7 +8,7 @@ class AssignmentMainMenuEntry(MainMenuEntry):
|
|||||||
Main menu entry for the assignment app.
|
Main menu entry for the assignment app.
|
||||||
"""
|
"""
|
||||||
verbose_name = ugettext_lazy('Elections')
|
verbose_name = ugettext_lazy('Elections')
|
||||||
required_permission = 'assignment.can_see_assignment'
|
required_permission = 'assignment.can_see_assignments'
|
||||||
default_weight = 40
|
default_weight = 40
|
||||||
pattern_name = 'assignment_list'
|
pattern_name = 'assignment_list'
|
||||||
icon_css_class = 'icon-assignment'
|
icon_css_class = 'icon-assignment'
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
@ -15,24 +15,36 @@ from openslides.projector.models import SlideMixin
|
|||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.models import AbsoluteUrlMixin
|
from openslides.utils.models import AbsoluteUrlMixin
|
||||||
from openslides.utils.rest_api import RESTModelMixin
|
from openslides.utils.rest_api import RESTModelMixin
|
||||||
from openslides.utils.utils import html_strong
|
|
||||||
from openslides.users.models import User
|
from openslides.users.models import User
|
||||||
|
|
||||||
|
|
||||||
class AssignmentCandidate(RESTModelMixin, models.Model):
|
class AssignmentRelatedUser(RESTModelMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
Many2Many table between an assignment and the candidates.
|
Many to Many table between an assignment and user.
|
||||||
"""
|
"""
|
||||||
assignment = models.ForeignKey("Assignment")
|
STATUS_CANDIDATE = 1
|
||||||
person = models.ForeignKey(User, db_index=True)
|
STATUS_ELECTED = 2
|
||||||
elected = models.BooleanField(default=False)
|
STATUS_BLOCKED = 3
|
||||||
blocked = models.BooleanField(default=False)
|
STATUSES = (
|
||||||
|
(STATUS_CANDIDATE, ugettext_lazy('candidate')),
|
||||||
|
(STATUS_ELECTED, ugettext_lazy('elected')),
|
||||||
|
(STATUS_BLOCKED, ugettext_lazy('blocked')),
|
||||||
|
)
|
||||||
|
|
||||||
|
assignment = models.ForeignKey(
|
||||||
|
'Assignment',
|
||||||
|
db_index=True,
|
||||||
|
related_name='assignment_related_users')
|
||||||
|
user = models.ForeignKey(User, db_index=True)
|
||||||
|
status = models.IntegerField(
|
||||||
|
choices=STATUSES,
|
||||||
|
default=STATUS_CANDIDATE)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ("assignment", "person")
|
unique_together = ('assignment', 'user')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.person)
|
return "%s <-> %s" % (self.assignment, self.user)
|
||||||
|
|
||||||
def get_root_rest_element(self):
|
def get_root_rest_element(self):
|
||||||
"""
|
"""
|
||||||
@ -44,35 +56,87 @@ class AssignmentCandidate(RESTModelMixin, models.Model):
|
|||||||
class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
||||||
slide_callback_name = 'assignment'
|
slide_callback_name = 'assignment'
|
||||||
|
|
||||||
STATUS = (
|
PHASE_SEARCH = 1
|
||||||
('sea', ugettext_lazy('Searching for candidates')),
|
PHASE_VOTING = 2
|
||||||
('vot', ugettext_lazy('Voting')),
|
PHASE_FINISHED = 3
|
||||||
('fin', ugettext_lazy('Finished')),
|
|
||||||
|
PHASES = (
|
||||||
|
(PHASE_SEARCH, ugettext_lazy('Searching for candidates')),
|
||||||
|
(PHASE_VOTING, ugettext_lazy('Voting')),
|
||||||
|
(PHASE_FINISHED, ugettext_lazy('Finished')),
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(max_length=100, verbose_name=ugettext_lazy("Name"))
|
title = models.CharField(
|
||||||
description = models.TextField(null=True, blank=True, verbose_name=ugettext_lazy("Description"))
|
max_length=100,
|
||||||
posts = models.PositiveSmallIntegerField(verbose_name=ugettext_lazy("Number of available posts"))
|
verbose_name=ugettext_lazy("Title"))
|
||||||
|
"""
|
||||||
|
Title of the assignment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
description = models.TextField(
|
||||||
|
blank=True,
|
||||||
|
verbose_name=ugettext_lazy("Description"))
|
||||||
|
"""
|
||||||
|
Text to describe the assignment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
open_posts = models.PositiveSmallIntegerField(
|
||||||
|
verbose_name=ugettext_lazy("Number of members to be elected"))
|
||||||
|
"""
|
||||||
|
The number of members to be elected.
|
||||||
|
"""
|
||||||
|
|
||||||
poll_description_default = models.CharField(
|
poll_description_default = models.CharField(
|
||||||
max_length=79, null=True, blank=True,
|
max_length=79,
|
||||||
|
blank=True,
|
||||||
verbose_name=ugettext_lazy("Default comment on the ballot paper"))
|
verbose_name=ugettext_lazy("Default comment on the ballot paper"))
|
||||||
status = models.CharField(max_length=3, choices=STATUS, default='sea')
|
"""
|
||||||
|
Default text for the poll description.
|
||||||
|
"""
|
||||||
|
|
||||||
|
phase = models.IntegerField(
|
||||||
|
choices=PHASES,
|
||||||
|
default=PHASE_SEARCH)
|
||||||
|
"""
|
||||||
|
Phase in which the assignment is.
|
||||||
|
"""
|
||||||
|
|
||||||
|
related_users = models.ManyToManyField(
|
||||||
|
User,
|
||||||
|
through='AssignmentRelatedUser')
|
||||||
|
"""
|
||||||
|
Users that a candidates, elected or blocked as candidate.
|
||||||
|
|
||||||
|
See AssignmentRelatedUser for more infos.
|
||||||
|
"""
|
||||||
|
|
||||||
tags = models.ManyToManyField(Tag, blank=True)
|
tags = models.ManyToManyField(Tag, blank=True)
|
||||||
|
"""
|
||||||
|
Tags for the assignment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
items = GenericRelation(Item)
|
||||||
|
"""
|
||||||
|
Agenda items for this assignment.
|
||||||
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
('can_see_assignment', ugettext_noop('Can see elections')), # TODO: Add plural s to the codestring
|
('can_see_assignments', ugettext_noop('Can see elections')),
|
||||||
('can_nominate_other', ugettext_noop('Can nominate another person')),
|
('can_nominate_other', ugettext_noop('Can nominate another participant')),
|
||||||
('can_nominate_self', ugettext_noop('Can nominate oneself')),
|
('can_nominate_self', ugettext_noop('Can nominate oneself')),
|
||||||
('can_manage_assignment', ugettext_noop('Can manage elections')), # TODO: Add plural s also to the codestring
|
('can_manage_assignments', ugettext_noop('Can manage elections')),
|
||||||
)
|
)
|
||||||
ordering = ('name',)
|
ordering = ('title', )
|
||||||
verbose_name = ugettext_noop('Election')
|
verbose_name = ugettext_noop('Election')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.title
|
||||||
|
|
||||||
def get_absolute_url(self, link='detail'):
|
def get_absolute_url(self, link='detail'):
|
||||||
|
"""
|
||||||
|
Returns absolute url to the assignment instance.
|
||||||
|
"""
|
||||||
if link == 'detail':
|
if link == 'detail':
|
||||||
url = reverse('assignment_detail', args=[str(self.pk)])
|
url = reverse('assignment_detail', args=[str(self.pk)])
|
||||||
elif link == 'update':
|
elif link == 'update':
|
||||||
@ -80,125 +144,115 @@ class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
elif link == 'delete':
|
elif link == 'delete':
|
||||||
url = reverse('assignment_delete', args=[str(self.pk)])
|
url = reverse('assignment_delete', args=[str(self.pk)])
|
||||||
else:
|
else:
|
||||||
url = super(Assignment, self).get_absolute_url(link)
|
url = super().get_absolute_url(link)
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def get_slide_context(self, **context):
|
def get_slide_context(self, **context):
|
||||||
context.update({
|
|
||||||
'polls': self.poll_set.filter(published=True),
|
|
||||||
'vote_results': self.vote_results(only_published=True)})
|
|
||||||
return super(Assignment, self).get_slide_context(**context)
|
|
||||||
|
|
||||||
def set_status(self, status):
|
|
||||||
status_dict = dict(self.STATUS)
|
|
||||||
if status not in status_dict:
|
|
||||||
raise ValueError(_('%s is not a valid status.') % html_strong(status))
|
|
||||||
if self.status == status:
|
|
||||||
raise ValueError(
|
|
||||||
_('The election status is already %s.') % html_strong(status_dict[status]))
|
|
||||||
self.status = status
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def run(self, candidate, person=None):
|
|
||||||
"""
|
"""
|
||||||
run for a vote
|
Retuns the context to generate the assignment slide.
|
||||||
candidate: The user who will be a candidate
|
|
||||||
person: The user who chooses the candidate
|
|
||||||
"""
|
"""
|
||||||
# TODO: don't make any permission checks here.
|
return super().get_slide_context(
|
||||||
# Use other Exceptions
|
polls=self.polls.filter(published=True),
|
||||||
if self.is_candidate(candidate):
|
vote_results=self.vote_results(only_published=True),
|
||||||
raise NameError(_('<b>%s</b> is already a candidate.') % candidate)
|
**context)
|
||||||
if not person.has_perm("assignment.can_manage_assignment") and self.status != 'sea':
|
|
||||||
raise NameError(_('The candidate list is already closed.'))
|
|
||||||
candidature = self.assignment_candidates.filter(person=candidate)
|
|
||||||
if candidature and candidate != person and \
|
|
||||||
not person.has_perm("assignment.can_manage_assignment"):
|
|
||||||
# if the candidature is blocked and anotherone tries to run the
|
|
||||||
# candidate
|
|
||||||
raise NameError(
|
|
||||||
_('%s does not want to be a candidate.') % candidate)
|
|
||||||
elif candidature:
|
|
||||||
candidature[0].blocked = False
|
|
||||||
candidature[0].save()
|
|
||||||
else:
|
|
||||||
AssignmentCandidate(assignment=self, person=candidate).save()
|
|
||||||
|
|
||||||
def delrun(self, candidate, blocked=True):
|
|
||||||
"""
|
|
||||||
stop running for a vote
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
candidature = self.assignment_candidates.get(person=candidate)
|
|
||||||
except AssignmentCandidate.DoesNotExist:
|
|
||||||
raise Exception(_('%s is no candidate') % candidate)
|
|
||||||
|
|
||||||
if not candidature.blocked:
|
|
||||||
if blocked:
|
|
||||||
candidature.blocked = True
|
|
||||||
candidature.save()
|
|
||||||
else:
|
|
||||||
candidature.delete()
|
|
||||||
else:
|
|
||||||
candidature.delete()
|
|
||||||
|
|
||||||
def is_candidate(self, person):
|
|
||||||
"""
|
|
||||||
return True, if person is a candidate.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return self.assignment_candidates.filter(person=person).exclude(blocked=True).exists()
|
|
||||||
except AttributeError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_blocked(self, person):
|
|
||||||
"""
|
|
||||||
return True, if the person is blockt for candidature.
|
|
||||||
"""
|
|
||||||
return self.assignment_candidates.filter(person=person).filter(blocked=True).exists()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def assignment_candidates(self):
|
|
||||||
return AssignmentCandidate.objects.filter(assignment=self)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def candidates(self):
|
def candidates(self):
|
||||||
return self.get_participants(only_candidate=True)
|
"""
|
||||||
|
Queryset that represents the candidates for the assignment.
|
||||||
|
"""
|
||||||
|
return self.related_users.filter(
|
||||||
|
assignmentrelateduser__status=AssignmentRelatedUser.STATUS_CANDIDATE)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def elected(self):
|
def elected(self):
|
||||||
return self.get_participants(only_elected=True)
|
"""
|
||||||
|
Queryset that represents all elected users for the assignment.
|
||||||
|
"""
|
||||||
|
return self.related_users.filter(
|
||||||
|
assignmentrelateduser__status=AssignmentRelatedUser.STATUS_ELECTED)
|
||||||
|
|
||||||
def get_participants(self, only_elected=False, only_candidate=False):
|
@property
|
||||||
candidates = self.assignment_candidates.exclude(blocked=True)
|
def blocked(self):
|
||||||
|
"""
|
||||||
|
Queryset that represents all blocked users for the assignment.
|
||||||
|
"""
|
||||||
|
return self.related_users.filter(
|
||||||
|
assignmentrelateduser__status=AssignmentRelatedUser.STATUS_BLOCKED)
|
||||||
|
|
||||||
assert not (only_elected and only_candidate)
|
def is_candidate(self, user):
|
||||||
|
"""
|
||||||
|
Returns True if user is a candidate.
|
||||||
|
|
||||||
if only_elected:
|
Costs one database query.
|
||||||
candidates = candidates.filter(elected=True)
|
"""
|
||||||
|
return self.candidates.filter(pk=user.pk).exists()
|
||||||
|
|
||||||
if only_candidate:
|
def is_elected(self, user):
|
||||||
candidates = candidates.filter(elected=False)
|
"""
|
||||||
|
Returns True if the user is elected for this assignment.
|
||||||
|
|
||||||
# TODO: rewrite this with a queryset
|
Costs one database query.
|
||||||
participants = []
|
"""
|
||||||
for candidate in candidates.all():
|
return self.elected.filter(pk=user.pk).exists()
|
||||||
participants.append(candidate.person)
|
|
||||||
return participants
|
|
||||||
|
|
||||||
def set_elected(self, person, value=True):
|
def is_blocked(self, user):
|
||||||
candidate = self.assignment_candidates.get(person=person)
|
"""
|
||||||
candidate.elected = value
|
Returns True if the user is blockt for candidature.
|
||||||
candidate.save()
|
|
||||||
|
|
||||||
def is_elected(self, person):
|
Costs one database query.
|
||||||
return person in self.elected
|
"""
|
||||||
|
return self.blocked.filter(pk=user.pk).exists()
|
||||||
|
|
||||||
def gen_poll(self):
|
def set_candidate(self, user):
|
||||||
|
"""
|
||||||
|
Adds the user as candidate.
|
||||||
|
"""
|
||||||
|
related_user, __ = self.assignment_related_users.update_or_create(
|
||||||
|
user=user,
|
||||||
|
defaults={'status': AssignmentRelatedUser.STATUS_CANDIDATE})
|
||||||
|
|
||||||
|
def set_elected(self, user):
|
||||||
|
"""
|
||||||
|
Makes user an elected user for this assignment.
|
||||||
|
"""
|
||||||
|
related_user, __ = self.assignment_related_users.update_or_create(
|
||||||
|
user=user,
|
||||||
|
defaults={'status': AssignmentRelatedUser.STATUS_ELECTED})
|
||||||
|
|
||||||
|
def set_blocked(self, user):
|
||||||
|
"""
|
||||||
|
Block user from this assignment, so he can not get an candidate.
|
||||||
|
"""
|
||||||
|
related_user, __ = self.assignment_related_users.update_or_create(
|
||||||
|
user=user,
|
||||||
|
defaults={'status': AssignmentRelatedUser.STATUS_BLOCKED})
|
||||||
|
|
||||||
|
def delete_related_user(self, user):
|
||||||
|
"""
|
||||||
|
Delete the connection from the assignment to the user.
|
||||||
|
"""
|
||||||
|
self.assignment_related_users.filter(user=user).delete()
|
||||||
|
|
||||||
|
def set_phase(self, phase):
|
||||||
|
"""
|
||||||
|
Sets the phase attribute of the assignment.
|
||||||
|
|
||||||
|
Raises a ValueError if the phase is not valide.
|
||||||
|
"""
|
||||||
|
if phase not in dict(self.PHASES):
|
||||||
|
raise ValueError("Invalid phase %s" % phase)
|
||||||
|
|
||||||
|
self.phase = phase
|
||||||
|
|
||||||
|
def create_poll(self):
|
||||||
"""
|
"""
|
||||||
Creates an new poll for the assignment and adds all candidates to all
|
Creates an new poll for the assignment and adds all candidates to all
|
||||||
lists of speakers of related agenda items.
|
lists of speakers of related agenda items.
|
||||||
"""
|
"""
|
||||||
|
candidates = self.candidates.all()
|
||||||
|
|
||||||
|
# Find out the method of the election
|
||||||
if config['assignment_poll_vote_values'] == 'votes':
|
if config['assignment_poll_vote_values'] == 'votes':
|
||||||
yesnoabstain = False
|
yesnoabstain = False
|
||||||
elif config['assignment_poll_vote_values'] == 'yesnoabstain':
|
elif config['assignment_poll_vote_values'] == 'yesnoabstain':
|
||||||
@ -206,19 +260,20 @@ class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
else:
|
else:
|
||||||
# config['assignment_poll_vote_values'] == 'auto'
|
# config['assignment_poll_vote_values'] == 'auto'
|
||||||
# candidates <= available posts -> yes/no/abstain
|
# candidates <= available posts -> yes/no/abstain
|
||||||
if len(self.candidates) <= (self.posts - len(self.elected)):
|
if len(candidates) <= (self.open_posts - self.elected.count()):
|
||||||
yesnoabstain = True
|
yesnoabstain = True
|
||||||
else:
|
else:
|
||||||
yesnoabstain = False
|
yesnoabstain = False
|
||||||
|
|
||||||
poll = AssignmentPoll.objects.create(
|
# Create the poll with the candidates.
|
||||||
assignment=self,
|
poll = self.polls.create(
|
||||||
description=self.poll_description_default,
|
description=self.poll_description_default,
|
||||||
yesnoabstain=yesnoabstain)
|
yesnoabstain=yesnoabstain)
|
||||||
poll.set_options([{'candidate': person} for person in self.candidates])
|
poll.set_options({'candidate': user} for user in candidates)
|
||||||
|
|
||||||
items = Item.objects.filter(content_type=ContentType.objects.get_for_model(Assignment), object_id=self.pk)
|
# Add all candidates to all agenda items for this assignment
|
||||||
for item in items:
|
# TODO: Try to do this in a bulk create
|
||||||
|
for item in self.items.all():
|
||||||
for candidate in self.candidates:
|
for candidate in self.candidates:
|
||||||
try:
|
try:
|
||||||
Speaker.objects.add(candidate, item)
|
Speaker.objects.add(candidate, item)
|
||||||
@ -231,14 +286,15 @@ class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
|
|
||||||
def vote_results(self, only_published):
|
def vote_results(self, only_published):
|
||||||
"""
|
"""
|
||||||
returns a table represented as a list with all candidates from all
|
Returns a table represented as a list with all candidates from all
|
||||||
related polls and their vote results.
|
related polls and their vote results.
|
||||||
"""
|
"""
|
||||||
vote_results_dict = SortedDict()
|
vote_results_dict = SortedDict()
|
||||||
# All polls related to this assigment
|
|
||||||
polls = self.poll_set.all()
|
polls = self.polls.all()
|
||||||
if only_published:
|
if only_published:
|
||||||
polls = polls.filter(published=True)
|
polls = polls.filter(published=True)
|
||||||
|
|
||||||
# All PollOption-Objects related to this assignment
|
# All PollOption-Objects related to this assignment
|
||||||
options = []
|
options = []
|
||||||
for poll in polls:
|
for poll in polls:
|
||||||
@ -263,7 +319,7 @@ class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
|
|||||||
return vote_results_dict
|
return vote_results_dict
|
||||||
|
|
||||||
def get_agenda_title(self):
|
def get_agenda_title(self):
|
||||||
return self.name
|
return str(self)
|
||||||
|
|
||||||
def get_agenda_title_supplement(self):
|
def get_agenda_title_supplement(self):
|
||||||
return '(%s)' % _('Assignment')
|
return '(%s)' % _('Assignment')
|
||||||
@ -296,15 +352,14 @@ class AssignmentOption(RESTModelMixin, BaseOption):
|
|||||||
|
|
||||||
class AssignmentPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
|
class AssignmentPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
|
||||||
PublishPollMixin, AbsoluteUrlMixin, BasePoll):
|
PublishPollMixin, AbsoluteUrlMixin, BasePoll):
|
||||||
|
|
||||||
slide_callback_name = 'assignmentpoll'
|
slide_callback_name = 'assignmentpoll'
|
||||||
"""Name of the callback for the slide system."""
|
|
||||||
|
|
||||||
option_class = AssignmentOption
|
option_class = AssignmentOption
|
||||||
assignment = models.ForeignKey(Assignment, related_name='poll_set')
|
|
||||||
|
assignment = models.ForeignKey(Assignment, related_name='polls')
|
||||||
yesnoabstain = models.BooleanField(default=False)
|
yesnoabstain = models.BooleanField(default=False)
|
||||||
description = models.CharField(
|
description = models.CharField(
|
||||||
max_length=79, null=True, blank=True,
|
max_length=79,
|
||||||
|
blank=True,
|
||||||
verbose_name=ugettext_lazy("Comment on the ballot paper"))
|
verbose_name=ugettext_lazy("Comment on the ballot paper"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -321,7 +376,7 @@ class AssignmentPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
|
|||||||
elif link == 'delete':
|
elif link == 'delete':
|
||||||
url = reverse('assignmentpoll_delete', args=[str(self.pk)])
|
url = reverse('assignmentpoll_delete', args=[str(self.pk)])
|
||||||
else:
|
else:
|
||||||
url = super(AssignmentPoll, self).get_absolute_url(link)
|
url = super().get_absolute_url(link)
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def get_assignment(self):
|
def get_assignment(self):
|
||||||
@ -334,17 +389,17 @@ class AssignmentPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
|
|||||||
return [ugettext_noop('Votes')]
|
return [ugettext_noop('Votes')]
|
||||||
|
|
||||||
def get_ballot(self):
|
def get_ballot(self):
|
||||||
return self.assignment.poll_set.filter(id__lte=self.id).count()
|
return self.assignment.polls.filter(id__lte=self.pk).count()
|
||||||
|
|
||||||
def get_percent_base_choice(self):
|
def get_percent_base_choice(self):
|
||||||
return config['assignment_poll_100_percent_base']
|
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)
|
super().append_pollform_fields(fields)
|
||||||
|
|
||||||
def get_slide_context(self, **context):
|
def get_slide_context(self, **context):
|
||||||
return super(AssignmentPoll, self).get_slide_context(poll=self)
|
return super().get_slide_context(poll=self)
|
||||||
|
|
||||||
def get_root_rest_element(self):
|
def get_root_rest_element(self):
|
||||||
"""
|
"""
|
||||||
|
@ -2,7 +2,7 @@ from django.utils.translation import ugettext_lazy
|
|||||||
|
|
||||||
from openslides.utils.personal_info import PersonalInfo
|
from openslides.utils.personal_info import PersonalInfo
|
||||||
|
|
||||||
from .models import Assignment
|
from .models import Assignment, AssignmentRelatedUser
|
||||||
|
|
||||||
|
|
||||||
class AssignmentPersonalInfo(PersonalInfo):
|
class AssignmentPersonalInfo(PersonalInfo):
|
||||||
@ -13,6 +13,5 @@ class AssignmentPersonalInfo(PersonalInfo):
|
|||||||
default_weight = 40
|
default_weight = 40
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Assignment.objects.filter(
|
return (Assignment.objects.filter(assignment_related_users__user=self.request.user)
|
||||||
assignmentcandidate__person=self.request.user,
|
.exclude(assignment_related_users__status=AssignmentRelatedUser.STATUS_BLOCKED))
|
||||||
assignmentcandidate__blocked=False)
|
|
||||||
|
@ -3,23 +3,22 @@ from openslides.utils.rest_api import serializers
|
|||||||
from .models import (
|
from .models import (
|
||||||
models,
|
models,
|
||||||
Assignment,
|
Assignment,
|
||||||
AssignmentCandidate,
|
AssignmentRelatedUser,
|
||||||
AssignmentOption,
|
AssignmentOption,
|
||||||
AssignmentPoll,
|
AssignmentPoll,
|
||||||
AssignmentVote)
|
AssignmentVote)
|
||||||
|
|
||||||
|
|
||||||
class AssignmentCandidateSerializer(serializers.ModelSerializer):
|
class AssignmentRelatedUserSerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
Serializer for assignment.models.AssignmentCandidate objects.
|
Serializer for assignment.models.AssignmentRelatedUser objects.
|
||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AssignmentCandidate
|
model = AssignmentRelatedUser
|
||||||
fields = (
|
fields = (
|
||||||
'id',
|
'id',
|
||||||
'person',
|
'user',
|
||||||
'elected',
|
'status')
|
||||||
'blocked',)
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentVoteSerializer(serializers.ModelSerializer):
|
class AssignmentVoteSerializer(serializers.ModelSerializer):
|
||||||
@ -103,8 +102,8 @@ class AssignmentFullSerializer(serializers.ModelSerializer):
|
|||||||
"""
|
"""
|
||||||
Serializer for assignment.models.Assignment objects. With all polls.
|
Serializer for assignment.models.Assignment objects. With all polls.
|
||||||
"""
|
"""
|
||||||
assignmentcandidate_set = AssignmentCandidateSerializer(many=True, read_only=True)
|
related_users = AssignmentRelatedUserSerializer(many=True, read_only=True)
|
||||||
poll_set = AssignmentAllPollSerializer(many=True, read_only=True)
|
polls = AssignmentAllPollSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Assignment
|
model = Assignment
|
||||||
@ -114,9 +113,9 @@ class AssignmentFullSerializer(serializers.ModelSerializer):
|
|||||||
'description',
|
'description',
|
||||||
'posts',
|
'posts',
|
||||||
'status',
|
'status',
|
||||||
'assignmentcandidate_set',
|
'related_users',
|
||||||
'poll_description_default',
|
'poll_description_default',
|
||||||
'poll_set',
|
'polls',
|
||||||
'tags',)
|
'tags',)
|
||||||
|
|
||||||
|
|
||||||
@ -124,7 +123,8 @@ class AssignmentShortSerializer(AssignmentFullSerializer):
|
|||||||
"""
|
"""
|
||||||
Serializer for assignment.models.Assignment objects. Without unpublished poll.
|
Serializer for assignment.models.Assignment objects. Without unpublished poll.
|
||||||
"""
|
"""
|
||||||
poll_set = AssignmentShortPollSerializer(many=True, read_only=True)
|
related_users = AssignmentRelatedUserSerializer(many=True, read_only=True)
|
||||||
|
polls = AssignmentShortPollSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Assignment
|
model = Assignment
|
||||||
@ -134,7 +134,7 @@ class AssignmentShortSerializer(AssignmentFullSerializer):
|
|||||||
'description',
|
'description',
|
||||||
'posts',
|
'posts',
|
||||||
'status',
|
'status',
|
||||||
'assignmentcandidate_set',
|
'related_users',
|
||||||
'poll_description_default',
|
'poll_description_default',
|
||||||
'poll_set',
|
'polls',
|
||||||
'tags',)
|
'tags',)
|
||||||
|
@ -35,13 +35,13 @@
|
|||||||
<span class="glyphicon glyphicon-facetime-video" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-facetime-video" aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.assignment.can_manage_assignment or perms.agenda.can_manage_agenda %}
|
{% if perms.assignment.can_manage_assignments or perms.agenda.can_manage_agenda %}
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">
|
<a data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">
|
||||||
<span class="optional-small">{% trans 'More actions' %}</span> <span class="caret"></span>
|
<span class="optional-small">{% trans 'More actions' %}</span> <span class="caret"></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu pull-right">
|
<ul class="dropdown-menu pull-right">
|
||||||
{% if perms.assignment.can_manage_assignment %}
|
{% if perms.assignment.can_manage_assignments %}
|
||||||
<!-- edit -->
|
<!-- edit -->
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ assignment|absolute_url:'update' }}">
|
<a href="{{ assignment|absolute_url:'update' }}">
|
||||||
@ -88,31 +88,27 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<br>
|
<br>
|
||||||
<!-- Candidates -->
|
<!-- Candidates -->
|
||||||
{% if assignment.status != "fin" %}
|
{% if assignment.phase != assignment.PHASE_FINISHED %}
|
||||||
<h4>{% trans "Candidates" %}</h4>
|
<h4>{% trans "Candidates" %}</h4>
|
||||||
<ol>
|
<ol>
|
||||||
{% for person in assignment.get_participants %}
|
{% for person in assignment.candidates %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ person|absolute_url }}">{{ person }}</a>
|
<a href="{{ person|absolute_url }}">{{ person }}</a>
|
||||||
{% if perms.assignment.can_manage_assignment %}
|
{% if perms.assignment.can_manage_assignments %}
|
||||||
{% if assignment.status == "sea" or assignment.status == "vot" %}
|
<a href="{% url 'assignment_del_candidate_other' assignment.id person.pk %}"
|
||||||
<a href="{% url 'assignment_delother' assignment.id person.pk %}"
|
class="btn btn-default btn-xs"
|
||||||
class="btn btn-default btn-xs"
|
rel="tooltip" data-original-title="{% trans 'Remove candidate' %}">
|
||||||
rel="tooltip" data-original-title="{% trans 'Remove candidate' %}">
|
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
|
||||||
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
|
</a>
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if person in assignment.elected %}
|
{% if person in assignment.elected %}
|
||||||
| <b>{% trans "elected" %}</b>
|
| <b>{% trans "elected" %}</b>
|
||||||
{% if perms.assignment.can_manage_assignment %}
|
{% if perms.assignment.can_manage_assignments %}
|
||||||
{% if assignment.status == "sea" or assignment.status == "vot" %}
|
<a href="{% url 'assignment_user_not_elected' assignment.id person.pk %}"
|
||||||
<a href="{% url 'assignment_user_not_elected' assignment.id person.pk %}"
|
class="btn btn-default btn-xs"
|
||||||
class="btn btn-default btn-xs"
|
rel="tooltip" data-original-title="{% trans 'Mark candidate as not elected' %}">
|
||||||
rel="tooltip" data-original-title="{% trans 'Mark candidate as not elected' %}">
|
<span class="glyphicon glyphicon-ban-circle" aria-hidden="true"></span>
|
||||||
<span class="glyphicon glyphicon-ban-circle" aria-hidden="true"></span>
|
</a>
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
@ -122,18 +118,18 @@
|
|||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
{% if assignment.status == "sea" or perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
{% if assignment.phase == assignment.PHASE_SEARCH or perms.assignment.can_manage_assignments and assignment.phase == assignment.PHASE_VOTING %}
|
||||||
{% if perms.assignment.can_nominate_self or perms.assignment.can_nominate_other %}
|
{% if perms.assignment.can_nominate_self or perms.assignment.can_nominate_other %}
|
||||||
<form action="" method="post">{% csrf_token %}
|
<form action="" method="post">{% csrf_token %}
|
||||||
{% if perms.assignment.can_nominate_self %}
|
{% if perms.assignment.can_nominate_self %}
|
||||||
<p>
|
<p>
|
||||||
{% if user_is_candidate %}
|
{% if user_is_candidate %}
|
||||||
<a href='{% url 'assignment_delrun' assignment.id %}' class="btn btn-default btn-sm">
|
<a href='{% url 'assignment_del_candidate' assignment.id %}' class="btn btn-default btn-sm">
|
||||||
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
|
||||||
{% trans 'Withdraw self candidature' %}
|
{% trans 'Withdraw self candidature' %}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href='{% url 'assignment_run' assignment.id %}' class="btn btn-default btn-sm">
|
<a href='{% url 'assignment_candidate' assignment.id %}' class="btn btn-default btn-sm">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
{% trans 'Self candidature' %}
|
{% trans 'Self candidature' %}
|
||||||
</a>
|
</a>
|
||||||
@ -161,13 +157,13 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if perms.assignment.can_manage_assignment and blocked_candidates and assignment.status != "fin" %}
|
{% if perms.assignment.can_manage_assignments and blocked_candidates and assignment.phase != assignment.PHASE_FINISHED %}
|
||||||
<h4>{% trans "Blocked Candidates" %}</h4>
|
<h4>{% trans "Blocked Candidates" %}</h4>
|
||||||
<ul>
|
<ul>
|
||||||
{% for person in blocked_candidates %}
|
{% for person in blocked_candidates %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ person|absolute_url }}">{{ person }}</a>
|
<a href="{{ person|absolute_url }}">{{ person }}</a>
|
||||||
<a class="btn btn-mini" href="{% url 'assignment_delother' assignment.id person.pk %}"
|
<a class="btn btn-mini" href="{% url 'assignment_del_candidate_other' assignment.id person.pk %}"
|
||||||
rel="tooltip" data-original-title="{% trans 'Remove candidate' %}">
|
rel="tooltip" data-original-title="{% trans 'Remove candidate' %}">
|
||||||
<i class="icon-ban-circle"></i>
|
<i class="icon-ban-circle"></i>
|
||||||
</a>
|
</a>
|
||||||
@ -180,7 +176,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<!-- Election result -->
|
<!-- Election result -->
|
||||||
{% if assignment.status != "sea" or polls.exists %}
|
{% if assignment.phase != assignment.PHASE_SEARCH or polls.exists %}
|
||||||
<h4>{% trans "Election result" %}</h4>
|
<h4>{% trans "Election result" %}</h4>
|
||||||
{% if polls.exists %}
|
{% if polls.exists %}
|
||||||
<table id="election-result-table" class="table table-striped table-bordered">
|
<table id="election-result-table" class="table table-striped table-bordered">
|
||||||
@ -188,11 +184,11 @@
|
|||||||
<th>{% trans "Candidates" %}</th>
|
<th>{% trans "Candidates" %}</th>
|
||||||
{% for poll in polls %}
|
{% for poll in polls %}
|
||||||
<th style="white-space: nowrap;" class="col-sm-1">
|
<th style="white-space: nowrap;" class="col-sm-1">
|
||||||
{% if perms.assignment.can_manage_assignment %}<p class="text-center">{% endif %}
|
{% if perms.assignment.can_manage_assignments %}<p class="text-center">{% endif %}
|
||||||
{{ poll.get_ballot|ordinal|safe }} {% trans 'ballot' %}
|
{{ poll.get_ballot|ordinal|safe }} {% trans 'ballot' %}
|
||||||
{% if perms.assignment.can_manage_assignment %}
|
{% if perms.assignment.can_manage_assignments %}
|
||||||
<a class="publish_link btn btn-sm btn-danger {% if poll.published %}btn-primary{% endif %}"
|
<a class="publish_link btn btn-sm btn-danger {% if poll.published %}btn-primary{% endif %}"
|
||||||
href="{% url 'assignmentpoll_publish_status' poll.id %}"
|
href="{% url 'assignmentpoll_publish_poll' poll.id %}"
|
||||||
rel="tooltip" data-original-title="{% trans 'Publish result' %}">
|
rel="tooltip" data-original-title="{% trans 'Publish result' %}">
|
||||||
{% if poll.published %}
|
{% if poll.published %}
|
||||||
<i class="icon-checked-new_white"></i>
|
<i class="icon-checked-new_white"></i>
|
||||||
@ -217,7 +213,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</th>
|
</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
{% if assignment.candidates and perms.assignment.can_manage_assignments and assignment.phase == assignment.PHASE_VOTING %}
|
||||||
<th class="col-sm-1 nobr">
|
<th class="col-sm-1 nobr">
|
||||||
<a href="{% url 'assignmentpoll_create' assignment.pk %}" class="btn btn-default btn-sm">
|
<a href="{% url 'assignmentpoll_create' assignment.pk %}" class="btn btn-default btn-sm">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
@ -230,7 +226,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{% if candidate in assignment.elected %}
|
{% if candidate in assignment.elected %}
|
||||||
{% if perms.assignment.can_manage_assignment %}
|
{% if perms.assignment.can_manage_assignments %}
|
||||||
<a class="election_link elected tooltip-bottom" href="{% url 'assignment_user_not_elected' assignment.id candidate.pk %}"
|
<a class="election_link elected tooltip-bottom" href="{% url 'assignment_user_not_elected' assignment.id candidate.pk %}"
|
||||||
data-original-title="{% trans 'Mark candidate as elected' %}"></a>
|
data-original-title="{% trans 'Mark candidate as elected' %}"></a>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -239,7 +235,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if perms.assignment.can_manage_assignment %}
|
{% if perms.assignment.can_manage_assignments %}
|
||||||
<a class="election_link tooltip-bottom" href="{% url 'assignment_user_elected' assignment.id candidate.pk %}"
|
<a class="election_link tooltip-bottom" href="{% url 'assignment_user_elected' assignment.id candidate.pk %}"
|
||||||
data-original-title="{% trans 'Mark candidate as elected' %}"></a>
|
data-original-title="{% trans 'Mark candidate as elected' %}"></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -261,7 +257,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
{% if assignment.candidates and perms.assignment.can_manage_assignments and assignment.phase == assignment.PHASE_VOTING %}
|
||||||
<td></td>
|
<td></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
@ -269,7 +265,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{% trans 'Valid votes' %}</td>
|
<td>{% trans 'Valid 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_assignments %}
|
||||||
<td style="white-space:nowrap;">
|
<td style="white-space:nowrap;">
|
||||||
{% if poll.has_votes %}
|
{% if poll.has_votes %}
|
||||||
<img src="{% static 'img/voting-yes-grey.png' %}" class="tooltip-left" data-original-title="{% trans 'Valid votes' %}">
|
<img src="{% static 'img/voting-yes-grey.png' %}" class="tooltip-left" data-original-title="{% trans 'Valid votes' %}">
|
||||||
@ -278,14 +274,14 @@
|
|||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
{% if assignment.candidates and perms.assignment.can_manage_assignments and assignment.phase == assignment.PHASE_VOTING %}
|
||||||
<td></td>
|
<td></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</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_assignments %}
|
||||||
<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 votes' %}">
|
<img src="{% static 'img/voting-invalid.png' %}" class="tooltip-left" data-original-title="{% trans 'Invalid votes' %}">
|
||||||
@ -294,14 +290,14 @@
|
|||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
{% if assignment.candidates and perms.assignment.can_manage_assignments and assignment.phase == assignment.PHASE_VOTING %}
|
||||||
<td></td>
|
<td></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="info total">
|
<tr class="info total">
|
||||||
<td>{% trans 'Votes cast' %}</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_assignments %}
|
||||||
<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' %}">
|
||||||
@ -310,14 +306,14 @@
|
|||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
{% if assignment.candidates and perms.assignment.can_manage_assignments and assignment.phase == assignment.PHASE_VOTING %}
|
||||||
<td></td>
|
<td></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i>{% trans "No ballots available." %}</i>
|
<i>{% trans "No ballots available." %}</i>
|
||||||
{% if assignment.candidates and perms.assignment.can_manage_assignment and assignment.status == "vot" %}
|
{% if assignment.candidates and perms.assignment.can_manage_assignments and assignment.phase == assignment.PHASE_VOTING %}
|
||||||
<p>
|
<p>
|
||||||
<a href='{% url 'assignmentpoll_create' assignment.id %}' class="btn btn-default">
|
<a href='{% url 'assignmentpoll_create' assignment.id %}' class="btn btn-default">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
@ -332,25 +328,25 @@
|
|||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
<div class="well">
|
<div class="well">
|
||||||
<!-- Text -->
|
<!-- Text -->
|
||||||
<h5>{% trans "Status" %}:</h5>
|
<h5>{% trans "Phases" %}:</h5>
|
||||||
{% trans assignment.get_status_display %}
|
{% trans assignment.get_phase_display %}
|
||||||
<!-- Posts -->
|
<!-- Posts -->
|
||||||
<h5>{% trans "Number of available posts" %}:</h5>
|
<h5>{% trans "Number of available posts" %}:</h5>
|
||||||
{{ assignment.posts }}
|
{{ assignment.posts }}
|
||||||
</div> <!--/well-->
|
</div> <!--/well-->
|
||||||
|
|
||||||
{% if perms.assignment.can_manage_assignment %}
|
{% if perms.assignment.can_manage_assignments %}
|
||||||
<div class="well">
|
<div class="well">
|
||||||
<h4>{% trans "Change status" %}:</h4>
|
<h4>{% trans "Change phase" %}:</h4>
|
||||||
<div class="btn-group btn-group-vertical" data-toggle="buttons-radio">
|
<div class="btn-group btn-group-vertical" data-toggle="buttons-radio">
|
||||||
<a href="{% url 'assignment_set_status' assignment.id 'sea' %}"
|
<a href="{% url 'assignment_set_phase' assignment.id assignment.PHASE_SEARCH %}"
|
||||||
class="btn btn-default btn-sm {% if 'sea' in assignment.status %}active{% endif %}">
|
class="btn btn-default btn-sm {% if assignment.phase == assignment.PHASE_SEARCH %}active{% endif %}">
|
||||||
{% trans 'Searching for candidates' %}</a>
|
{% trans 'Searching for candidates' %}</a>
|
||||||
<a href="{% url 'assignment_set_status' assignment.id 'vot' %}"
|
<a href="{% url 'assignment_set_phase' assignment.id assignment.PHASE_VOTING %}"
|
||||||
class="btn btn-default btn-sm {% if 'vot' in assignment.status %}active{% endif %}">
|
class="btn btn-default btn-sm {% if assignment.phase == assignment.PHASE_VOTING %}active{% endif %}">
|
||||||
{% trans 'Voting' %}</a>
|
{% trans 'Voting' %}</a>
|
||||||
<a href="{% url 'assignment_set_status' assignment.id 'fin' %}"
|
<a href="{% url 'assignment_set_phase' assignment.id assignment.PHASE_FINISHED %}"
|
||||||
class="btn btn-default btn-sm {% if 'fin' in assignment.status %}active{% endif %}">
|
class="btn btn-default btn-sm {% if assignment.phase == assignment.PHASE_FINISHED %}active{% endif %}">
|
||||||
{% trans 'Finished' %}</a>
|
{% trans 'Finished' %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div> <!--/well-->
|
</div> <!--/well-->
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{% trans "Elections" %}
|
<h1>{% trans "Elections" %}
|
||||||
<small class="pull-right">
|
<small class="pull-right">
|
||||||
{% if perms.assignment.can_manage_assignment %}
|
{% if perms.assignment.can_manage_assignments %}
|
||||||
<a href="{% url 'assignment_create' %}" class="btn btn-primary btn-sm"
|
<a href="{% url 'assignment_create' %}" class="btn btn-primary btn-sm"
|
||||||
rel="tooltip" data-original-title="{% trans 'New election' %}">
|
rel="tooltip" data-original-title="{% trans 'New election' %}">
|
||||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||||
@ -23,7 +23,7 @@
|
|||||||
<span class="optional-small"> {% trans 'Tags' %}</span>
|
<span class="optional-small"> {% trans 'Tags' %}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.assignment.can_see_assignment %}
|
{% if perms.assignment.can_see_assignments %}
|
||||||
<a href="{% url 'assignment_list_pdf' %}" class="btn btn-default btn-sm"
|
<a href="{% url 'assignment_list_pdf' %}" class="btn btn-default btn-sm"
|
||||||
rel="tooltip" data-original-title="{% trans 'Print all elections as PDF' %}" target="_blank">
|
rel="tooltip" data-original-title="{% trans 'Print all elections as PDF' %}" target="_blank">
|
||||||
<span class="glyphicon glyphicon-print" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-print" aria-hidden="true"></span>
|
||||||
@ -38,8 +38,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Election" %}</th>
|
<th>{% trans "Election" %}</th>
|
||||||
<th class="optional">{% trans "Candidates" %}</th>
|
<th class="optional">{% trans "Candidates" %}</th>
|
||||||
<th>{% trans "Status" %}</th>
|
<th>{% trans "Phase" %}</th>
|
||||||
{% if perms.assignment.can_manage_assignment or perms.core.can_manage_projector %}
|
{% if perms.assignment.can_manage_assignments or perms.core.can_manage_projector %}
|
||||||
<th class="mini_width">{% trans "Actions" %}</th>
|
<th class="mini_width">{% trans "Actions" %}</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
@ -54,16 +54,16 @@
|
|||||||
<td class="optional">
|
<td class="optional">
|
||||||
<!-- posts -->
|
<!-- posts -->
|
||||||
{% trans "Posts" context "Number of searched candidates for an election" %}:
|
{% trans "Posts" context "Number of searched candidates for an election" %}:
|
||||||
<span class="badge badge-info">{{ object.posts }}</span>
|
<span class="badge badge-info">{{ object.open_posts }}</span>
|
||||||
<!-- candidates -->
|
<!-- candidates -->
|
||||||
{% if object.status != 'fin' %}
|
{% if object.phase != object.PHASE_FINISHED %}
|
||||||
| {% trans "Candidates" %}: <span class="badge badge-warning">{{ object.get_participants|length }}</span>
|
| {% trans "Candidates" %}: <span class="badge badge-warning">{{ object.candidates.count }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- elected candidates -->
|
<!-- elected candidates -->
|
||||||
| {% trans "Elected" %}: <span class="badge badge-success">{{ object.elected|length }}</span>
|
| {% trans "Elected" %}: <span class="badge badge-success">{{ object.elected.count }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td><span class="label label-info">{{ object.get_status_display }}</status></td>
|
<td><span class="label label-info">{{ object.get_phase_display }}</span></td>
|
||||||
{% if perms.assignment.can_manage_assignment or perms.core.can_manage_projector %}
|
{% if perms.assignment.can_manage_assignments or perms.core.can_manage_projector %}
|
||||||
<td>
|
<td>
|
||||||
<span style="width: 1px; white-space: nowrap;">
|
<span style="width: 1px; white-space: nowrap;">
|
||||||
{% if perms.core.can_manage_projector %}
|
{% if perms.core.can_manage_projector %}
|
||||||
@ -73,7 +73,7 @@
|
|||||||
<span class="glyphicon glyphicon-facetime-video" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-facetime-video" aria-hidden="true"></span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.assignment.can_manage_assignment %}
|
{% if perms.assignment.can_manage_assignments %}
|
||||||
<a href="{{ object|absolute_url:'update' }}"
|
<a href="{{ object|absolute_url:'update' }}"
|
||||||
rel="tooltip" data-original-title="{% trans 'Edit' %}"
|
rel="tooltip" data-original-title="{% trans 'Edit' %}"
|
||||||
class="btn btn-default btn-sm">
|
class="btn btn-default btn-sm">
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
rel="tooltip" data-original-title="{% trans 'Show' %}">
|
rel="tooltip" data-original-title="{% trans 'Show' %}">
|
||||||
<i class="icon-facetime-video {% if assignment.is_active_slide %}icon-white{% endif %}"></i>
|
<i class="icon-facetime-video {% if assignment.is_active_slide %}icon-white{% endif %}"></i>
|
||||||
</a>
|
</a>
|
||||||
{% if perms.assignment.can_manage_assignment %}
|
{% if perms.assignment.can_manage_assignments %}
|
||||||
<a href="{{ assignment|absolute_url:'update' }}"
|
<a href="{{ assignment|absolute_url:'update' }}"
|
||||||
rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini right">
|
rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini right">
|
||||||
<i class="icon-pencil"></i>
|
<i class="icon-pencil"></i>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load highlight %}
|
{% load highlight %}
|
||||||
|
|
||||||
{% if perms.assignment.can_see_assignment %}
|
{% if perms.assignment.can_see_assignments %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ result.object.get_absolute_url }}">{{ result.object }}</a><br>
|
<a href="{{ result.object.get_absolute_url }}">{{ result.object }}</a><br>
|
||||||
<span class="app">{% trans "Election" %}</a></span><br>
|
<span class="app">{% trans "Election" %}</a></span><br>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{{ object.name }}
|
{{ object.title }}
|
||||||
{{ object.description }}
|
{{ object.description }}
|
||||||
{{ object.candidates }}
|
{{ object.candidates }}
|
||||||
{{ object.tags.all }}
|
{{ object.tags.all }}
|
||||||
|
@ -24,21 +24,21 @@ urlpatterns = patterns(
|
|||||||
views.AssignmentDeleteView.as_view(),
|
views.AssignmentDeleteView.as_view(),
|
||||||
name='assignment_delete'),
|
name='assignment_delete'),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/setstatus/(?P<status>[a-z]{3})/$',
|
url(r'^(?P<pk>\d+)/set_phase/(?P<phase>\d+)/$',
|
||||||
views.AssignmentSetStatusView.as_view(),
|
views.AssignmentSetPhaseView.as_view(),
|
||||||
name='assignment_set_status'),
|
name='assignment_set_phase'),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/run/$',
|
url(r'^(?P<pk>\d+)/candidate/$',
|
||||||
views.AssignmentRunView.as_view(),
|
views.AssignmentCandidateView.as_view(),
|
||||||
name='assignment_run'),
|
name='assignment_candidate'),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/delrun/$',
|
url(r'^(?P<pk>\d+)/delete_candidate/$',
|
||||||
views.AssignmentRunDeleteView.as_view(),
|
views.AssignmentDeleteCandidateshipView.as_view(),
|
||||||
name='assignment_delrun'),
|
name='assignment_del_candidate'),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/delother/(?P<user_id>[^/]+)/$',
|
url(r'^(?P<pk>\d+)/delother/(?P<user_pk>[^/]+)/$',
|
||||||
views.AssignmentRunOtherDeleteView.as_view(),
|
views.AssignmentDeleteCandidateshipOtherView.as_view(),
|
||||||
name='assignment_delother'),
|
name='assignment_del_candidate_other'),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/agenda/$',
|
url(r'^(?P<pk>\d+)/agenda/$',
|
||||||
views.CreateRelatedAgendaItemView.as_view(),
|
views.CreateRelatedAgendaItemView.as_view(),
|
||||||
@ -52,7 +52,7 @@ urlpatterns = patterns(
|
|||||||
views.AssignmentPDF.as_view(),
|
views.AssignmentPDF.as_view(),
|
||||||
name='assignment_pdf'),
|
name='assignment_pdf'),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/gen_poll/$',
|
url(r'^(?P<pk>\d+)/create_poll/$',
|
||||||
views.PollCreateView.as_view(),
|
views.PollCreateView.as_view(),
|
||||||
name='assignmentpoll_create'),
|
name='assignmentpoll_create'),
|
||||||
|
|
||||||
@ -64,22 +64,26 @@ urlpatterns = patterns(
|
|||||||
views.AssignmentPollDeleteView.as_view(),
|
views.AssignmentPollDeleteView.as_view(),
|
||||||
name='assignmentpoll_delete'),
|
name='assignmentpoll_delete'),
|
||||||
|
|
||||||
url(r'^poll/(?P<poll_id>\d+)/print/$',
|
url(r'^poll/(?P<poll_pk>\d+)/print/$',
|
||||||
views.AssignmentPollPDF.as_view(),
|
views.AssignmentPollPDF.as_view(),
|
||||||
name='assignmentpoll_pdf'),
|
name='assignmentpoll_pdf'),
|
||||||
|
|
||||||
# TODO: use seperate urls to publish and unpublish the poll
|
|
||||||
# see assignment_user_elected
|
|
||||||
url(r'^poll/(?P<pk>\d+)/pub/$',
|
url(r'^poll/(?P<pk>\d+)/pub/$',
|
||||||
views.SetPublishStatusView.as_view(),
|
views.SetPublishPollView.as_view(),
|
||||||
name='assignmentpoll_publish_status'),
|
{'publish': True},
|
||||||
|
name='assignmentpoll_publish_poll'),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/elected/(?P<user_id>[^/]+)/$',
|
url(r'^poll/(?P<pk>\d+)/unpub/$',
|
||||||
|
views.SetPublishPollView.as_view(),
|
||||||
|
{'publish': False},
|
||||||
|
name='assignmentpoll_unpublish_poll'),
|
||||||
|
|
||||||
|
url(r'^(?P<pk>\d+)/elected/(?P<user_pk>[^/]+)/$',
|
||||||
views.SetElectedView.as_view(),
|
views.SetElectedView.as_view(),
|
||||||
{'elected': True},
|
{'elected': True},
|
||||||
name='assignment_user_elected'),
|
name='assignment_user_elected'),
|
||||||
|
|
||||||
url(r'^(?P<pk>\d+)/notelected/(?P<user_id>[^/]+)/$',
|
url(r'^(?P<pk>\d+)/notelected/(?P<user_pk>[^/]+)/$',
|
||||||
views.SetElectedView.as_view(),
|
views.SetElectedView.as_view(),
|
||||||
{'elected': False},
|
{'elected': False},
|
||||||
name='assignment_user_not_elected')
|
name='assignment_user_not_elected')
|
||||||
|
@ -2,7 +2,6 @@ from cgi import escape
|
|||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.shortcuts import redirect
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.utils.translation import ungettext
|
from django.utils.translation import ungettext
|
||||||
from reportlab.lib import colors
|
from reportlab.lib import colors
|
||||||
@ -18,9 +17,9 @@ from openslides.utils.pdf import stylesheet
|
|||||||
from openslides.utils.rest_api import viewsets
|
from openslides.utils.rest_api import viewsets
|
||||||
from openslides.utils.utils import html_strong
|
from openslides.utils.utils import html_strong
|
||||||
from openslides.utils.views import (CreateView, DeleteView, DetailView,
|
from openslides.utils.views import (CreateView, DeleteView, DetailView,
|
||||||
ListView, PDFView, PermissionMixin,
|
ListView, PDFView,
|
||||||
QuestionView, RedirectView,
|
QuestionView, RedirectView,
|
||||||
SingleObjectMixin, UpdateView, View)
|
SingleObjectMixin, UpdateView)
|
||||||
|
|
||||||
from .forms import AssignmentForm, AssignmentRunForm
|
from .forms import AssignmentForm, AssignmentRunForm
|
||||||
from .models import Assignment, AssignmentPoll
|
from .models import Assignment, AssignmentPoll
|
||||||
@ -28,150 +27,183 @@ from .serializers import AssignmentFullSerializer, AssignmentShortSerializer
|
|||||||
|
|
||||||
|
|
||||||
class AssignmentListView(ListView):
|
class AssignmentListView(ListView):
|
||||||
"""ListView for all Assignments"""
|
"""
|
||||||
required_permission = 'assignment.can_see_assignment'
|
Lists all assignments.
|
||||||
|
"""
|
||||||
|
required_permission = 'assignment.can_see_assignments'
|
||||||
model = Assignment
|
model = Assignment
|
||||||
|
|
||||||
|
|
||||||
class AssignmentDetail(DetailView):
|
class AssignmentDetail(DetailView):
|
||||||
required_permission = 'assignment.can_see_assignment'
|
"""
|
||||||
|
Shows one assignment.
|
||||||
|
"""
|
||||||
|
# TODO: use another view as 'run form' when updating this to angular
|
||||||
|
required_permission = 'assignment.can_see_assignments'
|
||||||
model = Assignment
|
model = Assignment
|
||||||
form_class = AssignmentRunForm
|
form_class = AssignmentRunForm
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
def get_context_data(self, *args, **kwargs):
|
||||||
context = super(AssignmentDetail, self).get_context_data(*args, **kwargs)
|
context = super().get_context_data(*args, **kwargs)
|
||||||
|
assignment = self.get_object()
|
||||||
if self.request.method == 'POST':
|
if self.request.method == 'POST':
|
||||||
context['form'] = self.form_class(self.request.POST)
|
context['form'] = self.form_class(self.request.POST)
|
||||||
else:
|
else:
|
||||||
context['form'] = self.form_class()
|
context['form'] = self.form_class()
|
||||||
polls = self.get_object().poll_set.all()
|
|
||||||
if not self.request.user.has_perm('assignment.can_manage_assignment'):
|
|
||||||
polls = self.get_object().poll_set.filter(published=True)
|
|
||||||
vote_results = self.get_object().vote_results(only_published=True)
|
|
||||||
else:
|
|
||||||
polls = self.get_object().poll_set.all()
|
|
||||||
vote_results = self.get_object().vote_results(only_published=False)
|
|
||||||
|
|
||||||
blocked_candidates = [
|
polls = assignment.polls.all()
|
||||||
candidate.person for candidate in
|
if not self.request.user.has_perm('assignment.can_manage_assignments'):
|
||||||
self.get_object().assignment_candidates.filter(blocked=True)]
|
polls = polls.filter(published=True)
|
||||||
|
vote_results = assignment.vote_results(only_published=True)
|
||||||
|
else:
|
||||||
|
polls = self.get_object().polls.all()
|
||||||
|
vote_results = assignment.vote_results(only_published=False)
|
||||||
|
|
||||||
context['polls'] = polls
|
context['polls'] = polls
|
||||||
context['vote_results'] = vote_results
|
context['vote_results'] = vote_results
|
||||||
context['blocked_candidates'] = blocked_candidates
|
context['blocked_candidates'] = assignment.blocked
|
||||||
context['user_is_candidate'] = self.get_object().is_candidate(self.request.user)
|
context['user_is_candidate'] = assignment.is_candidate(self.request.user)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def post(self, *args, **kwargs):
|
def post(self, *args, **kwargs):
|
||||||
if self.request.user.has_perm('assignment.can_nominate_other'):
|
if self.request.user.has_perm('assignment.can_nominate_other'):
|
||||||
|
assignment = self.get_object()
|
||||||
form = self.form_class(self.request.POST)
|
form = self.form_class(self.request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
user = form.cleaned_data['candidate']
|
user = form.cleaned_data['candidate']
|
||||||
try:
|
if (assignment.phase == assignment.PHASE_SEARCH or
|
||||||
self.get_object().run(user, self.request.user)
|
self.request.user.has_perm('assignment.can_manage_assignments')):
|
||||||
except NameError as e:
|
if (assignment.is_blocked(user) and
|
||||||
messages.error(self.request, e)
|
not self.request.user.has_perm('assignment.can_manage_assignments')):
|
||||||
|
messages.error(
|
||||||
|
self.request,
|
||||||
|
_("User %s does not want to be an candidate") % user)
|
||||||
|
elif assignment.is_elected(user):
|
||||||
|
messages.error(
|
||||||
|
self.request,
|
||||||
|
_("User %s is already elected") % html_strong(user))
|
||||||
|
elif assignment.is_candidate(user):
|
||||||
|
messages.error(
|
||||||
|
self.request,
|
||||||
|
_("User %s is already an candidate") % html_strong(user))
|
||||||
|
else:
|
||||||
|
assignment.set_candidate(user)
|
||||||
|
messages.success(
|
||||||
|
self.request,
|
||||||
|
_("User %s was nominated successfully.") % html_strong(user))
|
||||||
else:
|
else:
|
||||||
messages.success(self.request, _(
|
messages.error(
|
||||||
"Candidate %s was nominated successfully.")
|
self.request,
|
||||||
% html_strong(user))
|
_("You can not add candidates to this assignment"))
|
||||||
return super(AssignmentDetail, self).get(*args, **kwargs)
|
return super(AssignmentDetail, self).get(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class AssignmentCreateView(CreateView):
|
class AssignmentCreateView(CreateView):
|
||||||
|
required_permission = 'assignment.can_manage_assignments'
|
||||||
model = Assignment
|
model = Assignment
|
||||||
form_class = AssignmentForm
|
form_class = AssignmentForm
|
||||||
required_permission = 'assignment.can_manage_assignment'
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentUpdateView(UpdateView):
|
class AssignmentUpdateView(UpdateView):
|
||||||
|
required_permission = 'assignment.can_manage_assignments'
|
||||||
model = Assignment
|
model = Assignment
|
||||||
form_class = AssignmentForm
|
form_class = AssignmentForm
|
||||||
required_permission = 'assignment.can_manage_assignment'
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentDeleteView(DeleteView):
|
class AssignmentDeleteView(DeleteView):
|
||||||
required_permission = 'assignment.can_manage_assignment'
|
required_permission = 'assignment.can_manage_assignments'
|
||||||
model = Assignment
|
model = Assignment
|
||||||
success_url_name = 'assignment_list'
|
success_url_name = 'assignment_list'
|
||||||
|
|
||||||
|
|
||||||
class AssignmentSetStatusView(SingleObjectMixin, RedirectView):
|
class AssignmentSetPhaseView(SingleObjectMixin, RedirectView):
|
||||||
|
required_permission = 'assignment.can_manage_assignments'
|
||||||
model = Assignment
|
model = Assignment
|
||||||
required_permission = 'assignment.can_manage_assignment'
|
|
||||||
url_name = 'assignment_detail'
|
url_name = 'assignment_detail'
|
||||||
|
|
||||||
def pre_redirect(self, *args, **kwargs):
|
def pre_redirect(self, *args, **kwargs):
|
||||||
status = kwargs.get('status')
|
phase = int(kwargs.get('phase'))
|
||||||
if status is not None:
|
|
||||||
try:
|
|
||||||
self.get_object().set_status(status)
|
|
||||||
except ValueError as e:
|
|
||||||
messages.error(self.request, e)
|
|
||||||
else:
|
|
||||||
messages.success(
|
|
||||||
self.request,
|
|
||||||
_('Election status was set to: %s.') %
|
|
||||||
html_strong(self.get_object().get_status_display())
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentRunView(SingleObjectMixin, PermissionMixin, View):
|
|
||||||
model = Assignment
|
|
||||||
required_permission = 'assignment.can_nominate_self'
|
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
|
||||||
assignment = self.get_object()
|
assignment = self.get_object()
|
||||||
try:
|
try:
|
||||||
assignment.run(self.request.user, self.request.user)
|
assignment.set_phase(phase)
|
||||||
except NameError as e:
|
except ValueError as e:
|
||||||
messages.error(self.request, e)
|
messages.error(self.request, e)
|
||||||
else:
|
else:
|
||||||
|
assignment.save()
|
||||||
messages.success(
|
messages.success(
|
||||||
self.request, _('You have set your candidature successfully.'))
|
self.request,
|
||||||
return redirect(reverse('assignment_detail', args=[assignment.pk]))
|
_('Election status was set to: %s.') %
|
||||||
|
html_strong(assignment.get_phase_display()))
|
||||||
|
|
||||||
|
|
||||||
class AssignmentRunDeleteView(SingleObjectMixin, RedirectView):
|
class AssignmentCandidateView(SingleObjectMixin, RedirectView):
|
||||||
|
required_permission = 'assignment.can_nominate_self'
|
||||||
model = Assignment
|
model = Assignment
|
||||||
url_name = 'assignment_detail'
|
url_name = 'assignment_detail'
|
||||||
|
|
||||||
def pre_redirect(self, *args, **kwargs):
|
def pre_redirect(self, *args, **kwargs):
|
||||||
if self.get_object().status == 'sea' or self.request.user.has_perm(
|
assignment = self.get_object()
|
||||||
"assignment.can_manage_assignment"):
|
if (assignment.phase == assignment.PHASE_SEARCH or
|
||||||
try:
|
self.request.user.has_perm('assignment.can_manage_assignments')):
|
||||||
self.get_object().delrun(self.request.user, blocked=True)
|
user = self.request.user
|
||||||
except Exception as e:
|
if assignment.is_elected(user):
|
||||||
# TODO: only catch relevant exception
|
messages.error(
|
||||||
messages.error(self.request, e)
|
self.request,
|
||||||
|
_("You are already elected"))
|
||||||
|
elif assignment.is_candidate(user):
|
||||||
|
messages.error(
|
||||||
|
self.request,
|
||||||
|
_("You are already an candidate"))
|
||||||
else:
|
else:
|
||||||
messages.success(self.request, _(
|
assignment.set_candidate(user)
|
||||||
'You have withdrawn your candidature successfully. '
|
messages.success(
|
||||||
'You can not be nominated by other participants anymore.'))
|
self.request,
|
||||||
|
_("You were nominated successfully."))
|
||||||
|
else:
|
||||||
|
messages.error(
|
||||||
|
self.request,
|
||||||
|
_("You can not candidate to this assignment"))
|
||||||
|
|
||||||
|
|
||||||
|
class AssignmentDeleteCandidateshipView(SingleObjectMixin, RedirectView):
|
||||||
|
required_permission = None # Any user can withdraw his candidature
|
||||||
|
model = Assignment
|
||||||
|
url_name = 'assignment_detail'
|
||||||
|
|
||||||
|
def pre_redirect(self, *args, **kwargs):
|
||||||
|
assignment = self.get_object()
|
||||||
|
if (assignment.phase == assignment.PHASE_SEARCH or
|
||||||
|
self.request.user.has_perm('assignment.can_manage_assignments')):
|
||||||
|
user = self.request.user
|
||||||
|
assignment.set_blocked(user)
|
||||||
|
messages.success(self.request, _(
|
||||||
|
'You have withdrawn your candidature successfully. '
|
||||||
|
'You can not be nominated by other participants anymore.'))
|
||||||
else:
|
else:
|
||||||
messages.error(self.request, _('The candidate list is already closed.'))
|
messages.error(self.request, _('The candidate list is already closed.'))
|
||||||
|
|
||||||
|
|
||||||
class AssignmentRunOtherDeleteView(SingleObjectMixin, QuestionView):
|
class AssignmentDeleteCandidateshipOtherView(SingleObjectMixin, QuestionView):
|
||||||
|
required_permission = 'assignment.can_manage_assignments'
|
||||||
model = Assignment
|
model = Assignment
|
||||||
required_permission = 'assignment.can_manage_assignment'
|
|
||||||
|
|
||||||
def get_question_message(self):
|
def get_question_message(self):
|
||||||
self._get_person_information()
|
self.user = User.objects.get(pk=self.kwargs.get('user_pk'))
|
||||||
if not self.is_blocked:
|
assignment = self.get_object()
|
||||||
question = _("Do you really want to withdraw %s from the election?") % html_strong(self.person)
|
if assignment.is_blocked:
|
||||||
|
question = _("Do you really want to unblock %s for the election?") % html_strong(self.user)
|
||||||
else:
|
else:
|
||||||
question = _("Do you really want to unblock %s for the election?") % html_strong(self.person)
|
question = _("Do you really want to withdraw %s from the election?") % html_strong(self.user)
|
||||||
return question
|
return question
|
||||||
|
|
||||||
def on_clicked_yes(self):
|
def on_clicked_yes(self):
|
||||||
self._get_person_information()
|
self.user = User.objects.get(pk=self.kwargs.get('user_pk'))
|
||||||
try:
|
assignment = self.get_object()
|
||||||
self.get_object().delrun(self.person, blocked=False)
|
if not assignment.is_elected(self.user):
|
||||||
except Exception as e:
|
assignment.delete_related_user(self.user)
|
||||||
# TODO: only catch relevant exception
|
|
||||||
self.error = e
|
|
||||||
else:
|
|
||||||
self.error = False
|
self.error = False
|
||||||
|
else:
|
||||||
|
self.error = _("User %s is already elected") % html_strong(self.user)
|
||||||
|
|
||||||
def create_final_message(self):
|
def create_final_message(self):
|
||||||
if self.error:
|
if self.error:
|
||||||
@ -180,14 +212,7 @@ class AssignmentRunOtherDeleteView(SingleObjectMixin, QuestionView):
|
|||||||
messages.success(self.request, self.get_final_message())
|
messages.success(self.request, self.get_final_message())
|
||||||
|
|
||||||
def get_final_message(self):
|
def get_final_message(self):
|
||||||
message = _("Candidate %s was withdrawn successfully.") % html_strong(self.person)
|
return _("Candidate %s was withdrawn successfully.") % html_strong(self.user)
|
||||||
if self.is_blocked:
|
|
||||||
message = _("%s was unblocked successfully.") % html_strong(self.person)
|
|
||||||
return message
|
|
||||||
|
|
||||||
def _get_person_information(self):
|
|
||||||
self.person = User.objects.get(pk=self.kwargs.get('user_id'))
|
|
||||||
self.is_blocked = self.get_object().is_blocked(self.person)
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentViewSet(viewsets.ModelViewSet):
|
class AssignmentViewSet(viewsets.ModelViewSet):
|
||||||
@ -203,16 +228,16 @@ class AssignmentViewSet(viewsets.ModelViewSet):
|
|||||||
permission to see assignments and in case of create, update or destroy
|
permission to see assignments and in case of create, update or destroy
|
||||||
requests the permission to manage assignments.
|
requests the permission to manage assignments.
|
||||||
"""
|
"""
|
||||||
if (not request.user.has_perm('assignment.can_see_assignment') or
|
if (not request.user.has_perm('assignment.can_see_assignments') or
|
||||||
(self.action in ('create', 'update', 'destroy') and not
|
(self.action in ('create', 'update', 'destroy') and not
|
||||||
request.user.has_perm('assignment.can_manage_assignment'))):
|
request.user.has_perm('assignment.can_manage_assignments'))):
|
||||||
self.permission_denied(request)
|
self.permission_denied(request)
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
"""
|
"""
|
||||||
Returns different serializer classes with respect to users permissions.
|
Returns different serializer classes with respect to users permissions.
|
||||||
"""
|
"""
|
||||||
if self.request.user.has_perm('assignment.can_manage_assignment'):
|
if self.request.user.has_perm('assignment.can_manage_assignments'):
|
||||||
serializer_class = AssignmentFullSerializer
|
serializer_class = AssignmentFullSerializer
|
||||||
else:
|
else:
|
||||||
serializer_class = AssignmentShortSerializer
|
serializer_class = AssignmentShortSerializer
|
||||||
@ -220,16 +245,17 @@ class AssignmentViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class PollCreateView(SingleObjectMixin, RedirectView):
|
class PollCreateView(SingleObjectMixin, RedirectView):
|
||||||
|
required_permission = 'assignment.can_manage_assignments'
|
||||||
model = Assignment
|
model = Assignment
|
||||||
required_permission = 'assignment.can_manage_assignment'
|
|
||||||
url_name = 'assignment_detail'
|
url_name = 'assignment_detail'
|
||||||
|
|
||||||
def pre_redirect(self, *args, **kwargs):
|
def pre_redirect(self, *args, **kwargs):
|
||||||
self.get_object().gen_poll()
|
self.get_object().create_poll()
|
||||||
messages.success(self.request, _("New ballot was successfully created."))
|
messages.success(self.request, _("New ballot was successfully created."))
|
||||||
|
|
||||||
|
|
||||||
class PollUpdateView(PollFormView):
|
class PollUpdateView(PollFormView):
|
||||||
|
required_permission = 'assignment.can_manage_assignments'
|
||||||
poll_class = AssignmentPoll
|
poll_class = AssignmentPoll
|
||||||
template_name = 'assignment/assignmentpoll_form.html'
|
template_name = 'assignment/assignmentpoll_form.html'
|
||||||
|
|
||||||
@ -238,49 +264,46 @@ class PollUpdateView(PollFormView):
|
|||||||
self.assignment = self.poll.get_assignment()
|
self.assignment = self.poll.get_assignment()
|
||||||
context['assignment'] = self.assignment
|
context['assignment'] = self.assignment
|
||||||
context['poll'] = self.poll
|
context['poll'] = self.poll
|
||||||
context['polls'] = self.assignment.poll_set.all()
|
context['polls'] = self.assignment.polls.all()
|
||||||
context['ballotnumber'] = self.poll.get_ballot()
|
context['ballotnumber'] = self.poll.get_ballot()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return_url = ''
|
|
||||||
if 'apply' not in self.request.POST:
|
if 'apply' not in self.request.POST:
|
||||||
return_url = reverse('assignment_detail', args=[self.poll.assignment.id])
|
return_url = reverse('assignment_detail', args=[self.poll.assignment.id])
|
||||||
|
else:
|
||||||
|
return_url = ''
|
||||||
return return_url
|
return return_url
|
||||||
|
|
||||||
|
|
||||||
class SetPublishStatusView(SingleObjectMixin, RedirectView):
|
class SetPublishPollView(SingleObjectMixin, RedirectView):
|
||||||
|
required_permission = 'assignment.can_manage_assignments'
|
||||||
model = AssignmentPoll
|
model = AssignmentPoll
|
||||||
required_permission = 'assignment.can_manage_assignment'
|
|
||||||
url_name = 'assignment_detail'
|
url_name = 'assignment_detail'
|
||||||
allow_ajax = True
|
allow_ajax = True
|
||||||
|
publish = False
|
||||||
|
|
||||||
def get_ajax_context(self, **kwargs):
|
def get_ajax_context(self, **context):
|
||||||
return {'published': self.object.published}
|
return super().get_ajax_context(
|
||||||
|
published=self.object.published,
|
||||||
|
**context)
|
||||||
|
|
||||||
def pre_redirect(self, *args, **kwargs):
|
def pre_redirect(self, *args, **kwargs):
|
||||||
try:
|
poll = self.get_object()
|
||||||
poll = self.get_object()
|
poll.set_published(kwargs['publish'])
|
||||||
except self.model.DoesNotExist:
|
|
||||||
messages.error(self.request, _('Ballot ID %d does not exist.') %
|
|
||||||
int(kwargs['poll_id']))
|
|
||||||
else:
|
|
||||||
if poll.published:
|
|
||||||
poll.set_published(False)
|
|
||||||
else:
|
|
||||||
poll.set_published(True)
|
|
||||||
|
|
||||||
|
|
||||||
class SetElectedView(SingleObjectMixin, RedirectView):
|
class SetElectedView(SingleObjectMixin, RedirectView):
|
||||||
|
required_permission = 'assignment.can_manage_assignments'
|
||||||
model = Assignment
|
model = Assignment
|
||||||
required_permission = 'assignment.can_manage_assignment'
|
|
||||||
url_name = 'assignment_detail'
|
url_name = 'assignment_detail'
|
||||||
allow_ajax = True
|
allow_ajax = True
|
||||||
|
|
||||||
def pre_redirect(self, *args, **kwargs):
|
def pre_redirect(self, *args, **kwargs):
|
||||||
self.person = User.objects.get(pk=kwargs['user_id'])
|
self.person = User.objects.get(pk=kwargs['user_id'])
|
||||||
self.elected = kwargs['elected']
|
self.elected = kwargs['elected']
|
||||||
self.get_object().set_elected(self.person, self.elected)
|
# TODO: un-elect users if self.elected is False
|
||||||
|
self.get_object().set_elected(self.person)
|
||||||
|
|
||||||
def get_ajax_context(self, **kwargs):
|
def get_ajax_context(self, **kwargs):
|
||||||
if self.elected:
|
if self.elected:
|
||||||
@ -298,16 +321,16 @@ class AssignmentPollDeleteView(DeleteView):
|
|||||||
"""
|
"""
|
||||||
Delete an assignment poll object.
|
Delete an assignment poll object.
|
||||||
"""
|
"""
|
||||||
required_permission = 'assignment.can_manage_assignment'
|
required_permission = 'assignment.can_manage_assignments'
|
||||||
model = AssignmentPoll
|
model = AssignmentPoll
|
||||||
|
|
||||||
def pre_redirect(self, request, *args, **kwargs):
|
def pre_redirect(self, request, *args, **kwargs):
|
||||||
self.set_assignment()
|
self.set_assignment()
|
||||||
super(AssignmentPollDeleteView, self).pre_redirect(request, *args, **kwargs)
|
super().pre_redirect(request, *args, **kwargs)
|
||||||
|
|
||||||
def pre_post_redirect(self, request, *args, **kwargs):
|
def pre_post_redirect(self, request, *args, **kwargs):
|
||||||
self.set_assignment()
|
self.set_assignment()
|
||||||
super(AssignmentPollDeleteView, self).pre_post_redirect(request, *args, **kwargs)
|
super().pre_post_redirect(request, *args, **kwargs)
|
||||||
|
|
||||||
def set_assignment(self):
|
def set_assignment(self):
|
||||||
self.assignment = self.get_object().assignment
|
self.assignment = self.get_object().assignment
|
||||||
@ -320,26 +343,26 @@ class AssignmentPollDeleteView(DeleteView):
|
|||||||
|
|
||||||
|
|
||||||
class AssignmentPDF(PDFView):
|
class AssignmentPDF(PDFView):
|
||||||
required_permission = 'assignment.can_see_assignment'
|
required_permission = 'assignment.can_see_assignments'
|
||||||
top_space = 0
|
top_space = 0
|
||||||
|
|
||||||
def get_filename(self):
|
def get_filename(self):
|
||||||
try:
|
try:
|
||||||
assignment_id = self.kwargs['pk']
|
assignment = Assignment.objects.get(pk=self.kwargs['pk'])
|
||||||
assignment = Assignment.objects.get(id=assignment_id)
|
|
||||||
filename = u'%s-%s' % (
|
filename = u'%s-%s' % (
|
||||||
_("Assignment"),
|
_("Assignment"),
|
||||||
assignment.name.replace(' ', '_'))
|
assignment.title.replace(' ', '_'))
|
||||||
except:
|
except:
|
||||||
filename = _("Elections")
|
filename = _("Elections")
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
def append_to_pdf(self, story):
|
def append_to_pdf(self, story):
|
||||||
try:
|
try:
|
||||||
assignment_id = self.kwargs['pk']
|
assignment_pk = self.kwargs['pk']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
assignment_id = None
|
assignment_pk = None
|
||||||
if assignment_id is None: # print all assignments
|
|
||||||
|
if assignment_pk is None: # print all assignments
|
||||||
title = escape(config["assignment_pdf_title"])
|
title = escape(config["assignment_pdf_title"])
|
||||||
story.append(Paragraph(title, stylesheet['Heading1']))
|
story.append(Paragraph(title, stylesheet['Heading1']))
|
||||||
preamble = escape(config["assignment_pdf_preamble"])
|
preamble = escape(config["assignment_pdf_preamble"])
|
||||||
@ -356,31 +379,31 @@ class AssignmentPDF(PDFView):
|
|||||||
# List of assignments
|
# List of assignments
|
||||||
for assignment in assignments:
|
for assignment in assignments:
|
||||||
story.append(Paragraph(
|
story.append(Paragraph(
|
||||||
escape(assignment.name), stylesheet['Heading3']))
|
escape(assignment.title), stylesheet['Heading3']))
|
||||||
# Assignment details (each assignment on single page)
|
# Assignment details (each assignment on single page)
|
||||||
for assignment in assignments:
|
for assignment in assignments:
|
||||||
story.append(PageBreak())
|
story.append(PageBreak())
|
||||||
# append the assignment to the story-object
|
# append the assignment to the story-object
|
||||||
self.get_assignment(assignment, story)
|
self.get_assignment(assignment, story)
|
||||||
else: # print selected assignment
|
else: # print selected assignment
|
||||||
assignment = Assignment.objects.get(id=assignment_id)
|
assignment = Assignment.objects.get(pk=assignment_pk)
|
||||||
# append the assignment to the story-object
|
# append the assignment to the story-object
|
||||||
self.get_assignment(assignment, story)
|
self.get_assignment(assignment, story)
|
||||||
|
|
||||||
def get_assignment(self, assignment, story):
|
def get_assignment(self, assignment, story):
|
||||||
# title
|
# title
|
||||||
story.append(Paragraph(
|
story.append(Paragraph(
|
||||||
_("Election: %s") % escape(assignment.name), stylesheet['Heading1']))
|
_("Election: %s") % escape(assignment.title), stylesheet['Heading1']))
|
||||||
story.append(Spacer(0, 0.5 * cm))
|
story.append(Spacer(0, 0.5 * cm))
|
||||||
|
|
||||||
# Filling table rows...
|
# Filling table rows...
|
||||||
data = []
|
data = []
|
||||||
polls = assignment.poll_set.filter(published=True)
|
polls = assignment.polls.filter(published=True)
|
||||||
# 1. posts
|
# 1. posts
|
||||||
data.append([
|
data.append([
|
||||||
Paragraph("%s:" %
|
Paragraph("%s:" %
|
||||||
_("Number of available posts"), stylesheet['Bold']),
|
_("Number of members to be elected"), stylesheet['Bold']),
|
||||||
Paragraph(str(assignment.posts), stylesheet['Paragraph'])])
|
Paragraph(str(assignment.open_posts), stylesheet['Paragraph'])])
|
||||||
|
|
||||||
# 2a. if no polls available print candidates
|
# 2a. if no polls available print candidates
|
||||||
if not polls:
|
if not polls:
|
||||||
@ -393,7 +416,7 @@ class AssignmentPDF(PDFView):
|
|||||||
[],
|
[],
|
||||||
Paragraph("<seq id='counter'/>. %s" % candidate,
|
Paragraph("<seq id='counter'/>. %s" % candidate,
|
||||||
stylesheet['Signaturefield'])])
|
stylesheet['Signaturefield'])])
|
||||||
if assignment.status == "sea":
|
if assignment.phase == assignment.PHASE_SEARCH:
|
||||||
for x in range(0, 7):
|
for x in range(0, 7):
|
||||||
data.append([
|
data.append([
|
||||||
[],
|
[],
|
||||||
@ -517,16 +540,16 @@ class CreateRelatedAgendaItemView(_CreateRelatedAgendaItemView):
|
|||||||
|
|
||||||
|
|
||||||
class AssignmentPollPDF(PDFView):
|
class AssignmentPollPDF(PDFView):
|
||||||
required_permission = 'assignment.can_manage_assignment'
|
required_permission = 'assignment.can_manage_assignments'
|
||||||
top_space = 0
|
top_space = 0
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
self.poll = AssignmentPoll.objects.get(id=self.kwargs['poll_id'])
|
self.poll = AssignmentPoll.objects.get(pk=self.kwargs['poll_pk'])
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_filename(self):
|
def get_filename(self):
|
||||||
filename = u'%s-%s_%s' % (
|
filename = u'%s-%s_%s' % (
|
||||||
_("Election"), self.poll.assignment.name.replace(' ', '_'),
|
_("Election"), self.poll.assignment.title.replace(' ', '_'),
|
||||||
self.poll.get_ballot())
|
self.poll.get_ballot())
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
@ -543,7 +566,7 @@ class AssignmentPollPDF(PDFView):
|
|||||||
cell = []
|
cell = []
|
||||||
cell.append(Spacer(0, 0.8 * cm))
|
cell.append(Spacer(0, 0.8 * cm))
|
||||||
cell.append(Paragraph(
|
cell.append(Paragraph(
|
||||||
_("Election") + ": " + self.poll.assignment.name,
|
_("Election") + ": " + self.poll.assignment.title,
|
||||||
stylesheet['Ballot_title']))
|
stylesheet['Ballot_title']))
|
||||||
cell.append(Paragraph(
|
cell.append(Paragraph(
|
||||||
self.poll.description or '',
|
self.poll.description or '',
|
||||||
@ -555,7 +578,7 @@ class AssignmentPollPDF(PDFView):
|
|||||||
"%d candidate", "%d candidates", len(options)) % len(options)
|
"%d candidate", "%d candidates", len(options)) % len(options)
|
||||||
available_posts_string = ungettext(
|
available_posts_string = ungettext(
|
||||||
"%d available post", "%d available posts",
|
"%d available post", "%d available posts",
|
||||||
self.poll.assignment.posts) % self.poll.assignment.posts
|
self.poll.assignment.open_posts) % self.poll.assignment.open_posts
|
||||||
cell.append(Paragraph(
|
cell.append(Paragraph(
|
||||||
"%s, %s, %s" % (ballot_string, candidate_string, available_posts_string),
|
"%s, %s, %s" % (ballot_string, candidate_string, available_posts_string),
|
||||||
stylesheet['Ballot_description']))
|
stylesheet['Ballot_description']))
|
||||||
|
@ -76,7 +76,7 @@ def create_builtin_groups_and_admin():
|
|||||||
perm_15 = Permission.objects.get(content_type=ct_motion, codename='can_see_motion')
|
perm_15 = Permission.objects.get(content_type=ct_motion, codename='can_see_motion')
|
||||||
|
|
||||||
ct_assignment = ContentType.objects.get(app_label='assignment', model='assignment')
|
ct_assignment = ContentType.objects.get(app_label='assignment', model='assignment')
|
||||||
perm_16 = Permission.objects.get(content_type=ct_assignment, codename='can_see_assignment')
|
perm_16 = Permission.objects.get(content_type=ct_assignment, codename='can_see_assignments')
|
||||||
|
|
||||||
ct_users = ContentType.objects.get(app_label='users', model='user')
|
ct_users = ContentType.objects.get(app_label='users', model='user')
|
||||||
perm_users_can_see_name = Permission.objects.get(content_type=ct_users, codename='can_see_name')
|
perm_users_can_see_name = Permission.objects.get(content_type=ct_users, codename='can_see_name')
|
||||||
@ -114,7 +114,7 @@ def create_builtin_groups_and_admin():
|
|||||||
# Staff (pk 4)
|
# Staff (pk 4)
|
||||||
perm_41 = Permission.objects.get(content_type=ct_agenda, codename='can_manage_agenda')
|
perm_41 = Permission.objects.get(content_type=ct_agenda, codename='can_manage_agenda')
|
||||||
perm_42 = Permission.objects.get(content_type=ct_motion, codename='can_manage_motion')
|
perm_42 = Permission.objects.get(content_type=ct_motion, codename='can_manage_motion')
|
||||||
perm_43 = Permission.objects.get(content_type=ct_assignment, codename='can_manage_assignment')
|
perm_43 = Permission.objects.get(content_type=ct_assignment, codename='can_manage_assignments')
|
||||||
perm_44 = Permission.objects.get(content_type=ct_users, codename='can_manage')
|
perm_44 = Permission.objects.get(content_type=ct_users, codename='can_manage')
|
||||||
perm_45 = Permission.objects.get(content_type=ct_core, codename='can_manage_projector')
|
perm_45 = Permission.objects.get(content_type=ct_core, codename='can_manage_projector')
|
||||||
perm_46 = Permission.objects.get(content_type=ct_core, codename='can_use_chat')
|
perm_46 = Permission.objects.get(content_type=ct_core, codename='can_use_chat')
|
||||||
|
@ -91,8 +91,8 @@ class PersonalInfoWidget(TestCase):
|
|||||||
def test_candidate_list(self):
|
def test_candidate_list(self):
|
||||||
assignment = self.import_assignment()
|
assignment = self.import_assignment()
|
||||||
if assignment:
|
if assignment:
|
||||||
assignment_1 = assignment.models.Assignment.objects.create(name='Hausmeister ooKoh7roApoo3phe', posts=1)
|
assignment_1 = assignment.models.Assignment.objects.create(title='Hausmeister ooKoh7roApoo3phe', open_posts=1)
|
||||||
assignment_1.run(candidate=self.user, person=self.user)
|
assignment_1.set_candidate(self.user)
|
||||||
response = self.client.get('/dashboard/')
|
response = self.client.get('/dashboard/')
|
||||||
self.assertContains(response, 'I am candidate for the following elections:', status_code=200)
|
self.assertContains(response, 'I am candidate for the following elections:', status_code=200)
|
||||||
self.assertContains(response, 'Hausmeister ooKoh7roApoo3phe', status_code=200)
|
self.assertContains(response, 'Hausmeister ooKoh7roApoo3phe', status_code=200)
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
from django.test.client import Client
|
|
||||||
|
|
||||||
from openslides.agenda.models import Item, Speaker
|
|
||||||
from openslides.assignment.models import Assignment
|
|
||||||
from openslides.users.models import User
|
|
||||||
from openslides.utils.test import TestCase
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentModelTest(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
# Admin
|
|
||||||
self.admin = User.objects.get(pk=1)
|
|
||||||
self.admin_client = Client()
|
|
||||||
self.admin_client.login(username='admin', password='admin')
|
|
||||||
|
|
||||||
def test_delete_with_related_item(self):
|
|
||||||
assignment = Assignment.objects.create(name='assignment_name_fgdhensbch34zfu1284ds', posts=1)
|
|
||||||
response = self.admin_client.get('/assignment/1/agenda/')
|
|
||||||
self.assertRedirects(response, '/agenda/')
|
|
||||||
self.assertEqual(Item.objects.get(pk=1).get_title(), 'assignment_name_fgdhensbch34zfu1284ds')
|
|
||||||
assignment.delete()
|
|
||||||
self.assertTrue(Item.objects.filter(pk=1).exists())
|
|
||||||
|
|
||||||
def test_begin_speach(self):
|
|
||||||
assignment = Assignment.objects.create(name='test_assignment_gjbnchs4620sdfhjfsksj1', posts=1)
|
|
||||||
item = Item.objects.create(content_object=assignment)
|
|
||||||
person_1 = User.objects.create(username='user_1_bnhdjgd8747djcbjd8fg')
|
|
||||||
person_2 = User.objects.create(username='user_2_qmlkohid6qvx5q0fbmh9')
|
|
||||||
person_3 = User.objects.create(username='user_3_nbjf74jf9bjag219ou96')
|
|
||||||
assignment.run(person_1, person_1)
|
|
||||||
assignment.run(person_2, person_2)
|
|
||||||
assignment.run(person_3, person_3)
|
|
||||||
Speaker.objects.add(person_1, item)
|
|
||||||
self.assertEqual(item.speaker_set.count(), 1)
|
|
||||||
|
|
||||||
assignment.gen_poll()
|
|
||||||
self.assertTrue(item.speaker_set.filter(user=person_1).exists())
|
|
||||||
self.assertTrue(item.speaker_set.filter(user=person_2).exists())
|
|
||||||
self.assertTrue(item.speaker_set.filter(user=person_3).exists())
|
|
@ -16,11 +16,11 @@ class AssignmentPDFTest(TestCase):
|
|||||||
self.admin_client.login(username='admin', password='admin')
|
self.admin_client.login(username='admin', password='admin')
|
||||||
|
|
||||||
def test_render_pdf(self):
|
def test_render_pdf(self):
|
||||||
Assignment.objects.create(name='assignment_name_ith8qua1Eiferoqu5ju2', description="test", posts=1)
|
Assignment.objects.create(title='assignment_name_ith8qua1Eiferoqu5ju2', description="test", open_posts=1)
|
||||||
response = self.admin_client.get('/assignment/print/')
|
response = self.admin_client.get('/assignment/print/')
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_render_many_posts(self):
|
def test_render_many_posts(self):
|
||||||
Assignment.objects.create(name='assignment_name_cohZ9shaipee3Phaing4', description="test", posts=20)
|
Assignment.objects.create(title='assignment_name_cohZ9shaipee3Phaing4', description="test", open_posts=20)
|
||||||
response = self.admin_client.get('/assignment/print/')
|
response = self.admin_client.get('/assignment/print/')
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
@ -34,7 +34,7 @@ class AssignmentViewTestCase(TestCase):
|
|||||||
self.registered_client = Client()
|
self.registered_client = Client()
|
||||||
self.registered_client.login(username='registered', password='registered')
|
self.registered_client.login(username='registered', password='registered')
|
||||||
|
|
||||||
self.assignment1 = Assignment.objects.create(name='test', posts=2)
|
self.assignment1 = Assignment.objects.create(title='test', open_posts=2)
|
||||||
|
|
||||||
def check_url(self, url, test_client, response_cose):
|
def check_url(self, url, test_client, response_cose):
|
||||||
response = test_client.get(url)
|
response = test_client.get(url)
|
||||||
@ -45,7 +45,7 @@ class AssignmentViewTestCase(TestCase):
|
|||||||
class TestAssignmentPollDelete(AssignmentViewTestCase):
|
class TestAssignmentPollDelete(AssignmentViewTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestAssignmentPollDelete, self).setUp()
|
super(TestAssignmentPollDelete, self).setUp()
|
||||||
self.assignment1.gen_poll()
|
self.assignment1.create_poll()
|
||||||
|
|
||||||
def test_get(self):
|
def test_get(self):
|
||||||
response = self.check_url('/assignment/poll/1/del/', self.admin_client, 302)
|
response = self.check_url('/assignment/poll/1/del/', self.admin_client, 302)
|
||||||
@ -65,7 +65,7 @@ class TestAssignmentDetailView(AssignmentViewTestCase):
|
|||||||
self.assertContains(response, 'No candidates available.')
|
self.assertContains(response, 'No candidates available.')
|
||||||
self.assertNotContains(response, 'Blocked Candidates')
|
self.assertNotContains(response, 'Blocked Candidates')
|
||||||
|
|
||||||
response = self.delegate_client.get('/assignment/1/run/')
|
response = self.delegate_client.get('/assignment/1/candidate/')
|
||||||
self.assertTrue(self.assignment1.is_candidate(self.delegate))
|
self.assertTrue(self.assignment1.is_candidate(self.delegate))
|
||||||
self.assertFalse(self.assignment1.is_blocked(self.delegate))
|
self.assertFalse(self.assignment1.is_blocked(self.delegate))
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ class TestAssignmentDetailView(AssignmentViewTestCase):
|
|||||||
self.assertNotContains(response, 'No candidates available.')
|
self.assertNotContains(response, 'No candidates available.')
|
||||||
self.assertNotContains(response, 'Blocked Candidates')
|
self.assertNotContains(response, 'Blocked Candidates')
|
||||||
|
|
||||||
response = self.delegate_client.get('/assignment/1/delrun/')
|
response = self.delegate_client.get('/assignment/1/delete_candidate/')
|
||||||
self.assertFalse(self.assignment1.is_candidate(self.delegate))
|
self.assertFalse(self.assignment1.is_candidate(self.delegate))
|
||||||
self.assertTrue(self.assignment1.is_blocked(self.delegate))
|
self.assertTrue(self.assignment1.is_blocked(self.delegate))
|
||||||
|
|
||||||
@ -89,19 +89,19 @@ class TestAssignmentPollCreateView(TestCase):
|
|||||||
def test_assignment_add_candidate(self):
|
def test_assignment_add_candidate(self):
|
||||||
admin = User.objects.get(pk=1)
|
admin = User.objects.get(pk=1)
|
||||||
self.assignment = Assignment.objects.create(
|
self.assignment = Assignment.objects.create(
|
||||||
name='test_assignment_oiL2heerookiegeirai0',
|
title='test_assignment_oiL2heerookiegeirai0',
|
||||||
posts=1)
|
open_posts=1)
|
||||||
self.assignment.run(admin, admin)
|
self.assignment.set_candidate(admin)
|
||||||
self.assertEqual(len(Assignment.objects.get(pk=self.assignment.pk).candidates), 1)
|
self.assertEqual(len(Assignment.objects.get(pk=self.assignment.pk).candidates), 1)
|
||||||
|
|
||||||
def test_assignment_poll_creation(self):
|
def test_assignment_poll_creation(self):
|
||||||
self.test_assignment_add_candidate()
|
self.test_assignment_add_candidate()
|
||||||
self.assignment.set_status('vot')
|
self.assignment.set_phase(self.assignment.PHASE_VOTING)
|
||||||
admin_client = Client()
|
admin_client = Client()
|
||||||
admin_client.login(username='admin', password='admin')
|
admin_client.login(username='admin', password='admin')
|
||||||
self.assertFalse(AssignmentPoll.objects.exists())
|
self.assertFalse(AssignmentPoll.objects.exists())
|
||||||
self.assertEqual(config['assignment_poll_vote_values'], 'auto')
|
self.assertEqual(config['assignment_poll_vote_values'], 'auto')
|
||||||
response = admin_client.get('/assignment/1/gen_poll/')
|
response = admin_client.get('/assignment/1/create_poll/')
|
||||||
self.assertRedirects(response, '/assignment/1/')
|
self.assertRedirects(response, '/assignment/1/')
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
self.assertEqual(poll.assignment, self.assignment)
|
self.assertEqual(poll.assignment, self.assignment)
|
||||||
@ -117,10 +117,10 @@ class TestAssignmentPollPdfView(TestCase):
|
|||||||
def test_assignment_create_poll_pdf(self):
|
def test_assignment_create_poll_pdf(self):
|
||||||
# Create a assignment with a poll
|
# Create a assignment with a poll
|
||||||
admin = User.objects.get(pk=1)
|
admin = User.objects.get(pk=1)
|
||||||
assignment = Assignment.objects.create(name='assignment1', posts=1)
|
assignment = Assignment.objects.create(title='assignment1', open_posts=1)
|
||||||
assignment.run(admin, admin)
|
assignment.set_candidate(admin)
|
||||||
assignment.set_status('vot')
|
assignment.set_phase(assignment.PHASE_VOTING)
|
||||||
assignment.gen_poll()
|
assignment.create_poll()
|
||||||
client = Client()
|
client = Client()
|
||||||
client.login(username='admin', password='admin')
|
client.login(username='admin', password='admin')
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ class TestPollUpdateView(TestCase):
|
|||||||
"""
|
"""
|
||||||
Tests that a 404 is returned, when a non existing poll is requested.
|
Tests that a 404 is returned, when a non existing poll is requested.
|
||||||
"""
|
"""
|
||||||
Assignment.objects.create(name='test assignment', posts=1)
|
Assignment.objects.create(title='test assignment', open_posts=1)
|
||||||
url = '/assignment/poll/1/edit/'
|
url = '/assignment/poll/1/edit/'
|
||||||
|
|
||||||
response = self.admin_client.get(url)
|
response = self.admin_client.get(url)
|
||||||
|
Loading…
Reference in New Issue
Block a user