Merge pull request #1446 from ostcar/assignments

rework of assignment
This commit is contained in:
Oskar Hahn 2015-02-12 10:17:24 +01:00
commit 2859980922
18 changed files with 475 additions and 438 deletions

View File

@ -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.
""" """

View File

@ -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):

View File

@ -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'

View File

@ -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):
""" """

View File

@ -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)

View File

@ -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',)

View File

@ -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,29 +328,29 @@
<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-->
{% endif %} {% endif %}
</div> </div>
</div> <!--/row--> </div> <!--/row-->
{% endblock %} {% endblock %}

View File

@ -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">

View File

@ -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>&nbsp; </a>&nbsp;
{% 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>

View File

@ -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>

View File

@ -1,4 +1,4 @@
{{ object.name }} {{ object.title }}
{{ object.description }} {{ object.description }}
{{ object.candidates }} {{ object.candidates }}
{{ object.tags.all }} {{ object.tags.all }}

View File

@ -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')

View File

@ -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'/>.&nbsp; %s" % candidate, Paragraph("<seq id='counter'/>.&nbsp; %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']))

View File

@ -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')

View File

@ -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)

View File

@ -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())

View File

@ -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)

View File

@ -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)