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 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.core.exceptions import ValidationError
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.
"""
# 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.
"""

View File

@ -8,12 +8,12 @@ from .models import Assignment
class AssignmentForm(CssClassMixin, forms.ModelForm):
posts = forms.IntegerField(
open_posts = forms.IntegerField(
min_value=1, initial=1, label=ugettext_lazy("Number of available posts"))
class Meta:
model = Assignment
exclude = ('status', 'elected')
fields = ('title', 'description', 'open_posts', 'poll_description_default')
class AssignmentRunForm(CssClassMixin, forms.Form):

View File

@ -8,7 +8,7 @@ class AssignmentMainMenuEntry(MainMenuEntry):
Main menu entry for the assignment app.
"""
verbose_name = ugettext_lazy('Elections')
required_permission = 'assignment.can_see_assignment'
required_permission = 'assignment.can_see_assignments'
default_weight = 40
pattern_name = 'assignment_list'
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.db import models
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.models import AbsoluteUrlMixin
from openslides.utils.rest_api import RESTModelMixin
from openslides.utils.utils import html_strong
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")
person = models.ForeignKey(User, db_index=True)
elected = models.BooleanField(default=False)
blocked = models.BooleanField(default=False)
STATUS_CANDIDATE = 1
STATUS_ELECTED = 2
STATUS_BLOCKED = 3
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:
unique_together = ("assignment", "person")
unique_together = ('assignment', 'user')
def __str__(self):
return str(self.person)
return "%s <-> %s" % (self.assignment, self.user)
def get_root_rest_element(self):
"""
@ -44,35 +56,87 @@ class AssignmentCandidate(RESTModelMixin, models.Model):
class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
slide_callback_name = 'assignment'
STATUS = (
('sea', ugettext_lazy('Searching for candidates')),
('vot', ugettext_lazy('Voting')),
('fin', ugettext_lazy('Finished')),
PHASE_SEARCH = 1
PHASE_VOTING = 2
PHASE_FINISHED = 3
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"))
description = models.TextField(null=True, blank=True, verbose_name=ugettext_lazy("Description"))
posts = models.PositiveSmallIntegerField(verbose_name=ugettext_lazy("Number of available posts"))
title = models.CharField(
max_length=100,
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(
max_length=79, null=True, blank=True,
max_length=79,
blank=True,
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 for the assignment.
"""
items = GenericRelation(Item)
"""
Agenda items for this assignment.
"""
class Meta:
permissions = (
('can_see_assignment', ugettext_noop('Can see elections')), # TODO: Add plural s to the codestring
('can_nominate_other', ugettext_noop('Can nominate another person')),
('can_see_assignments', ugettext_noop('Can see elections')),
('can_nominate_other', ugettext_noop('Can nominate another participant')),
('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')
def __str__(self):
return self.name
return self.title
def get_absolute_url(self, link='detail'):
"""
Returns absolute url to the assignment instance.
"""
if link == 'detail':
url = reverse('assignment_detail', args=[str(self.pk)])
elif link == 'update':
@ -80,125 +144,115 @@ class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
elif link == 'delete':
url = reverse('assignment_delete', args=[str(self.pk)])
else:
url = super(Assignment, self).get_absolute_url(link)
url = super().get_absolute_url(link)
return url
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
candidate: The user who will be a candidate
person: The user who chooses the candidate
Retuns the context to generate the assignment slide.
"""
# TODO: don't make any permission checks here.
# Use other Exceptions
if self.is_candidate(candidate):
raise NameError(_('<b>%s</b> is already a candidate.') % candidate)
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)
return super().get_slide_context(
polls=self.polls.filter(published=True),
vote_results=self.vote_results(only_published=True),
**context)
@property
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
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):
candidates = self.assignment_candidates.exclude(blocked=True)
@property
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:
candidates = candidates.filter(elected=True)
Costs one database query.
"""
return self.candidates.filter(pk=user.pk).exists()
if only_candidate:
candidates = candidates.filter(elected=False)
def is_elected(self, user):
"""
Returns True if the user is elected for this assignment.
# TODO: rewrite this with a queryset
participants = []
for candidate in candidates.all():
participants.append(candidate.person)
return participants
Costs one database query.
"""
return self.elected.filter(pk=user.pk).exists()
def set_elected(self, person, value=True):
candidate = self.assignment_candidates.get(person=person)
candidate.elected = value
candidate.save()
def is_blocked(self, user):
"""
Returns True if the user is blockt for candidature.
def is_elected(self, person):
return person in self.elected
Costs one database query.
"""
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
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':
yesnoabstain = False
elif config['assignment_poll_vote_values'] == 'yesnoabstain':
@ -206,19 +260,20 @@ class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
else:
# config['assignment_poll_vote_values'] == 'auto'
# 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
else:
yesnoabstain = False
poll = AssignmentPoll.objects.create(
assignment=self,
# Create the poll with the candidates.
poll = self.polls.create(
description=self.poll_description_default,
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)
for item in items:
# Add all candidates to all agenda items for this assignment
# TODO: Try to do this in a bulk create
for item in self.items.all():
for candidate in self.candidates:
try:
Speaker.objects.add(candidate, item)
@ -231,14 +286,15 @@ class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
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.
"""
vote_results_dict = SortedDict()
# All polls related to this assigment
polls = self.poll_set.all()
polls = self.polls.all()
if only_published:
polls = polls.filter(published=True)
# All PollOption-Objects related to this assignment
options = []
for poll in polls:
@ -263,7 +319,7 @@ class Assignment(RESTModelMixin, SlideMixin, AbsoluteUrlMixin, models.Model):
return vote_results_dict
def get_agenda_title(self):
return self.name
return str(self)
def get_agenda_title_supplement(self):
return '(%s)' % _('Assignment')
@ -296,15 +352,14 @@ class AssignmentOption(RESTModelMixin, BaseOption):
class AssignmentPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
PublishPollMixin, AbsoluteUrlMixin, BasePoll):
slide_callback_name = 'assignmentpoll'
"""Name of the callback for the slide system."""
option_class = AssignmentOption
assignment = models.ForeignKey(Assignment, related_name='poll_set')
assignment = models.ForeignKey(Assignment, related_name='polls')
yesnoabstain = models.BooleanField(default=False)
description = models.CharField(
max_length=79, null=True, blank=True,
max_length=79,
blank=True,
verbose_name=ugettext_lazy("Comment on the ballot paper"))
def __str__(self):
@ -321,7 +376,7 @@ class AssignmentPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
elif link == 'delete':
url = reverse('assignmentpoll_delete', args=[str(self.pk)])
else:
url = super(AssignmentPoll, self).get_absolute_url(link)
url = super().get_absolute_url(link)
return url
def get_assignment(self):
@ -334,17 +389,17 @@ class AssignmentPoll(RESTModelMixin, SlideMixin, CollectDefaultVotesMixin,
return [ugettext_noop('Votes')]
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):
return config['assignment_poll_100_percent_base']
def append_pollform_fields(self, fields):
fields.append('description')
super(AssignmentPoll, self).append_pollform_fields(fields)
super().append_pollform_fields(fields)
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):
"""

View File

@ -2,7 +2,7 @@ from django.utils.translation import ugettext_lazy
from openslides.utils.personal_info import PersonalInfo
from .models import Assignment
from .models import Assignment, AssignmentRelatedUser
class AssignmentPersonalInfo(PersonalInfo):
@ -13,6 +13,5 @@ class AssignmentPersonalInfo(PersonalInfo):
default_weight = 40
def get_queryset(self):
return Assignment.objects.filter(
assignmentcandidate__person=self.request.user,
assignmentcandidate__blocked=False)
return (Assignment.objects.filter(assignment_related_users__user=self.request.user)
.exclude(assignment_related_users__status=AssignmentRelatedUser.STATUS_BLOCKED))

View File

@ -3,23 +3,22 @@ from openslides.utils.rest_api import serializers
from .models import (
models,
Assignment,
AssignmentCandidate,
AssignmentRelatedUser,
AssignmentOption,
AssignmentPoll,
AssignmentVote)
class AssignmentCandidateSerializer(serializers.ModelSerializer):
class AssignmentRelatedUserSerializer(serializers.ModelSerializer):
"""
Serializer for assignment.models.AssignmentCandidate objects.
Serializer for assignment.models.AssignmentRelatedUser objects.
"""
class Meta:
model = AssignmentCandidate
model = AssignmentRelatedUser
fields = (
'id',
'person',
'elected',
'blocked',)
'user',
'status')
class AssignmentVoteSerializer(serializers.ModelSerializer):
@ -103,8 +102,8 @@ class AssignmentFullSerializer(serializers.ModelSerializer):
"""
Serializer for assignment.models.Assignment objects. With all polls.
"""
assignmentcandidate_set = AssignmentCandidateSerializer(many=True, read_only=True)
poll_set = AssignmentAllPollSerializer(many=True, read_only=True)
related_users = AssignmentRelatedUserSerializer(many=True, read_only=True)
polls = AssignmentAllPollSerializer(many=True, read_only=True)
class Meta:
model = Assignment
@ -114,9 +113,9 @@ class AssignmentFullSerializer(serializers.ModelSerializer):
'description',
'posts',
'status',
'assignmentcandidate_set',
'related_users',
'poll_description_default',
'poll_set',
'polls',
'tags',)
@ -124,7 +123,8 @@ class AssignmentShortSerializer(AssignmentFullSerializer):
"""
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:
model = Assignment
@ -134,7 +134,7 @@ class AssignmentShortSerializer(AssignmentFullSerializer):
'description',
'posts',
'status',
'assignmentcandidate_set',
'related_users',
'poll_description_default',
'poll_set',
'polls',
'tags',)

View File

@ -35,13 +35,13 @@
<span class="glyphicon glyphicon-facetime-video" aria-hidden="true"></span>
</a>
{% 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">
<a data-toggle="dropdown" class="btn btn-default btn-sm dropdown-toggle">
<span class="optional-small">{% trans 'More actions' %}</span> <span class="caret"></span>
</a>
<ul class="dropdown-menu pull-right">
{% if perms.assignment.can_manage_assignment %}
{% if perms.assignment.can_manage_assignments %}
<!-- edit -->
<li>
<a href="{{ assignment|absolute_url:'update' }}">
@ -88,31 +88,27 @@
{% endif %}
<br>
<!-- Candidates -->
{% if assignment.status != "fin" %}
{% if assignment.phase != assignment.PHASE_FINISHED %}
<h4>{% trans "Candidates" %}</h4>
<ol>
{% for person in assignment.get_participants %}
{% for person in assignment.candidates %}
<li>
<a href="{{ person|absolute_url }}">{{ person }}</a>
{% if perms.assignment.can_manage_assignment %}
{% if assignment.status == "sea" or assignment.status == "vot" %}
<a href="{% url 'assignment_delother' assignment.id person.pk %}"
class="btn btn-default btn-xs"
rel="tooltip" data-original-title="{% trans 'Remove candidate' %}">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</a>
{% endif %}
{% if perms.assignment.can_manage_assignments %}
<a href="{% url 'assignment_del_candidate_other' assignment.id person.pk %}"
class="btn btn-default btn-xs"
rel="tooltip" data-original-title="{% trans 'Remove candidate' %}">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</a>
{% endif %}
{% if person in assignment.elected %}
| <b>{% trans "elected" %}</b>
{% if perms.assignment.can_manage_assignment %}
{% if assignment.status == "sea" or assignment.status == "vot" %}
<a href="{% url 'assignment_user_not_elected' assignment.id person.pk %}"
class="btn btn-default btn-xs"
rel="tooltip" data-original-title="{% trans 'Mark candidate as not elected' %}">
<span class="glyphicon glyphicon-ban-circle" aria-hidden="true"></span>
</a>
{% endif %}
{% if perms.assignment.can_manage_assignments %}
<a href="{% url 'assignment_user_not_elected' assignment.id person.pk %}"
class="btn btn-default btn-xs"
rel="tooltip" data-original-title="{% trans 'Mark candidate as not elected' %}">
<span class="glyphicon glyphicon-ban-circle" aria-hidden="true"></span>
</a>
{% endif %}
{% endif %}
</li>
@ -122,18 +118,18 @@
</li>
{% endfor %}
</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 %}
<form action="" method="post">{% csrf_token %}
{% if perms.assignment.can_nominate_self %}
<p>
{% 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>
{% trans 'Withdraw self candidature' %}
</a>
{% 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>
{% trans 'Self candidature' %}
</a>
@ -161,13 +157,13 @@
{% 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>
<ul>
{% for person in blocked_candidates %}
<li>
<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' %}">
<i class="icon-ban-circle"></i>
</a>
@ -180,7 +176,7 @@
<!-- Election result -->
{% if assignment.status != "sea" or polls.exists %}
{% if assignment.phase != assignment.PHASE_SEARCH or polls.exists %}
<h4>{% trans "Election result" %}</h4>
{% if polls.exists %}
<table id="election-result-table" class="table table-striped table-bordered">
@ -188,11 +184,11 @@
<th>{% trans "Candidates" %}</th>
{% for poll in polls %}
<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' %}
{% 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 %}"
href="{% url 'assignmentpoll_publish_status' poll.id %}"
href="{% url 'assignmentpoll_publish_poll' poll.id %}"
rel="tooltip" data-original-title="{% trans 'Publish result' %}">
{% if poll.published %}
<i class="icon-checked-new_white"></i>
@ -217,7 +213,7 @@
{% endif %}
</th>
{% 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">
<a href="{% url 'assignmentpoll_create' assignment.pk %}" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
@ -230,7 +226,7 @@
<tr>
<td>
{% 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 %}"
data-original-title="{% trans 'Mark candidate as elected' %}"></a>
{% else %}
@ -239,7 +235,7 @@
</a>
{% endif %}
{% 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 %}"
data-original-title="{% trans 'Mark candidate as elected' %}"></a>
{% endif %}
@ -261,7 +257,7 @@
{% endif %}
</td>
{% 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>
{% endif %}
</tr>
@ -269,7 +265,7 @@
<tr>
<td>{% trans 'Valid votes' %}</td>
{% 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;">
{% if poll.has_votes %}
<img src="{% static 'img/voting-yes-grey.png' %}" class="tooltip-left" data-original-title="{% trans 'Valid votes' %}">
@ -278,14 +274,14 @@
</td>
{% endif %}
{% 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>
{% endif %}
</tr>
<tr>
<td>{% trans 'Invalid votes' %}</td>
{% 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;">
{% if poll.has_votes %}
<img src="{% static 'img/voting-invalid.png' %}" class="tooltip-left" data-original-title="{% trans 'Invalid votes' %}">
@ -294,14 +290,14 @@
</td>
{% endif %}
{% 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>
{% endif %}
</tr>
<tr class="info total">
<td>{% trans 'Votes cast' %}</td>
{% 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;">
{% if poll.has_votes %}
<img src="{% static 'img/voting-total.png' %}" class="tooltip-left" data-original-title="{% trans 'Votes cast' %}">
@ -310,14 +306,14 @@
</td>
{% endif %}
{% 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>
{% endif %}
</tr>
</table>
{% else %}
<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>
<a href='{% url 'assignmentpoll_create' assignment.id %}' class="btn btn-default">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
@ -332,29 +328,29 @@
<div class="col-sm-3">
<div class="well">
<!-- Text -->
<h5>{% trans "Status" %}:</h5>
{% trans assignment.get_status_display %}
<h5>{% trans "Phases" %}:</h5>
{% trans assignment.get_phase_display %}
<!-- Posts -->
<h5>{% trans "Number of available posts" %}:</h5>
{{ assignment.posts }}
</div> <!--/well-->
{% if perms.assignment.can_manage_assignment %}
{% if perms.assignment.can_manage_assignments %}
<div class="well">
<h4>{% trans "Change status" %}:</h4>
<h4>{% trans "Change phase" %}:</h4>
<div class="btn-group btn-group-vertical" data-toggle="buttons-radio">
<a href="{% url 'assignment_set_status' assignment.id 'sea' %}"
class="btn btn-default btn-sm {% if 'sea' in assignment.status %}active{% endif %}">
<a href="{% url 'assignment_set_phase' assignment.id assignment.PHASE_SEARCH %}"
class="btn btn-default btn-sm {% if assignment.phase == assignment.PHASE_SEARCH %}active{% endif %}">
{% trans 'Searching for candidates' %}</a>
<a href="{% url 'assignment_set_status' assignment.id 'vot' %}"
class="btn btn-default btn-sm {% if 'vot' in assignment.status %}active{% endif %}">
<a href="{% url 'assignment_set_phase' assignment.id assignment.PHASE_VOTING %}"
class="btn btn-default btn-sm {% if assignment.phase == assignment.PHASE_VOTING %}active{% endif %}">
{% trans 'Voting' %}</a>
<a href="{% url 'assignment_set_status' assignment.id 'fin' %}"
class="btn btn-default btn-sm {% if 'fin' in assignment.status %}active{% endif %}">
<a href="{% url 'assignment_set_phase' assignment.id assignment.PHASE_FINISHED %}"
class="btn btn-default btn-sm {% if assignment.phase == assignment.PHASE_FINISHED %}active{% endif %}">
{% trans 'Finished' %}</a>
</div>
</div> <!--/well-->
{% endif %}
</div>
</div>
</div> <!--/row-->
{% endblock %}

View File

@ -9,7 +9,7 @@
{% block content %}
<h1>{% trans "Elections" %}
<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"
rel="tooltip" data-original-title="{% trans 'New election' %}">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
@ -23,7 +23,7 @@
<span class="optional-small"> {% trans 'Tags' %}</span>
</a>
{% 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"
rel="tooltip" data-original-title="{% trans 'Print all elections as PDF' %}" target="_blank">
<span class="glyphicon glyphicon-print" aria-hidden="true"></span>
@ -38,8 +38,8 @@
<tr>
<th>{% trans "Election" %}</th>
<th class="optional">{% trans "Candidates" %}</th>
<th>{% trans "Status" %}</th>
{% if perms.assignment.can_manage_assignment or perms.core.can_manage_projector %}
<th>{% trans "Phase" %}</th>
{% if perms.assignment.can_manage_assignments or perms.core.can_manage_projector %}
<th class="mini_width">{% trans "Actions" %}</th>
{% endif %}
</tr>
@ -54,16 +54,16 @@
<td class="optional">
<!-- posts -->
{% 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 -->
{% if object.status != 'fin' %}
| {% trans "Candidates" %}: <span class="badge badge-warning">{{ object.get_participants|length }}</span>
{% if object.phase != object.PHASE_FINISHED %}
| {% trans "Candidates" %}: <span class="badge badge-warning">{{ object.candidates.count }}</span>
{% endif %}
<!-- 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><span class="label label-info">{{ object.get_status_display }}</status></td>
{% if perms.assignment.can_manage_assignment or perms.core.can_manage_projector %}
<td><span class="label label-info">{{ object.get_phase_display }}</span></td>
{% if perms.assignment.can_manage_assignments or perms.core.can_manage_projector %}
<td>
<span style="width: 1px; white-space: nowrap;">
{% if perms.core.can_manage_projector %}
@ -73,7 +73,7 @@
<span class="glyphicon glyphicon-facetime-video" aria-hidden="true"></span>
</a>
{% endif %}
{% if perms.assignment.can_manage_assignment %}
{% if perms.assignment.can_manage_assignments %}
<a href="{{ object|absolute_url:'update' }}"
rel="tooltip" data-original-title="{% trans 'Edit' %}"
class="btn btn-default btn-sm">

View File

@ -11,7 +11,7 @@
rel="tooltip" data-original-title="{% trans 'Show' %}">
<i class="icon-facetime-video {% if assignment.is_active_slide %}icon-white{% endif %}"></i>
</a>&nbsp;
{% if perms.assignment.can_manage_assignment %}
{% if perms.assignment.can_manage_assignments %}
<a href="{{ assignment|absolute_url:'update' }}"
rel="tooltip" data-original-title="{% trans 'Edit' %}" class="btn btn-mini right">
<i class="icon-pencil"></i>

View File

@ -1,7 +1,7 @@
{% load i18n %}
{% load highlight %}
{% if perms.assignment.can_see_assignment %}
{% if perms.assignment.can_see_assignments %}
<li>
<a href="{{ result.object.get_absolute_url }}">{{ result.object }}</a><br>
<span class="app">{% trans "Election" %}</a></span><br>

View File

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

View File

@ -24,21 +24,21 @@ urlpatterns = patterns(
views.AssignmentDeleteView.as_view(),
name='assignment_delete'),
url(r'^(?P<pk>\d+)/setstatus/(?P<status>[a-z]{3})/$',
views.AssignmentSetStatusView.as_view(),
name='assignment_set_status'),
url(r'^(?P<pk>\d+)/set_phase/(?P<phase>\d+)/$',
views.AssignmentSetPhaseView.as_view(),
name='assignment_set_phase'),
url(r'^(?P<pk>\d+)/run/$',
views.AssignmentRunView.as_view(),
name='assignment_run'),
url(r'^(?P<pk>\d+)/candidate/$',
views.AssignmentCandidateView.as_view(),
name='assignment_candidate'),
url(r'^(?P<pk>\d+)/delrun/$',
views.AssignmentRunDeleteView.as_view(),
name='assignment_delrun'),
url(r'^(?P<pk>\d+)/delete_candidate/$',
views.AssignmentDeleteCandidateshipView.as_view(),
name='assignment_del_candidate'),
url(r'^(?P<pk>\d+)/delother/(?P<user_id>[^/]+)/$',
views.AssignmentRunOtherDeleteView.as_view(),
name='assignment_delother'),
url(r'^(?P<pk>\d+)/delother/(?P<user_pk>[^/]+)/$',
views.AssignmentDeleteCandidateshipOtherView.as_view(),
name='assignment_del_candidate_other'),
url(r'^(?P<pk>\d+)/agenda/$',
views.CreateRelatedAgendaItemView.as_view(),
@ -52,7 +52,7 @@ urlpatterns = patterns(
views.AssignmentPDF.as_view(),
name='assignment_pdf'),
url(r'^(?P<pk>\d+)/gen_poll/$',
url(r'^(?P<pk>\d+)/create_poll/$',
views.PollCreateView.as_view(),
name='assignmentpoll_create'),
@ -64,22 +64,26 @@ urlpatterns = patterns(
views.AssignmentPollDeleteView.as_view(),
name='assignmentpoll_delete'),
url(r'^poll/(?P<poll_id>\d+)/print/$',
url(r'^poll/(?P<poll_pk>\d+)/print/$',
views.AssignmentPollPDF.as_view(),
name='assignmentpoll_pdf'),
# TODO: use seperate urls to publish and unpublish the poll
# see assignment_user_elected
url(r'^poll/(?P<pk>\d+)/pub/$',
views.SetPublishStatusView.as_view(),
name='assignmentpoll_publish_status'),
views.SetPublishPollView.as_view(),
{'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(),
{'elected': True},
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(),
{'elected': False},
name='assignment_user_not_elected')

View File

@ -2,7 +2,6 @@ from cgi import escape
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
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.utils import html_strong
from openslides.utils.views import (CreateView, DeleteView, DetailView,
ListView, PDFView, PermissionMixin,
ListView, PDFView,
QuestionView, RedirectView,
SingleObjectMixin, UpdateView, View)
SingleObjectMixin, UpdateView)
from .forms import AssignmentForm, AssignmentRunForm
from .models import Assignment, AssignmentPoll
@ -28,150 +27,183 @@ from .serializers import AssignmentFullSerializer, AssignmentShortSerializer
class AssignmentListView(ListView):
"""ListView for all Assignments"""
required_permission = 'assignment.can_see_assignment'
"""
Lists all assignments.
"""
required_permission = 'assignment.can_see_assignments'
model = Assignment
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
form_class = AssignmentRunForm
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':
context['form'] = self.form_class(self.request.POST)
else:
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 = [
candidate.person for candidate in
self.get_object().assignment_candidates.filter(blocked=True)]
polls = assignment.polls.all()
if not self.request.user.has_perm('assignment.can_manage_assignments'):
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['vote_results'] = vote_results
context['blocked_candidates'] = blocked_candidates
context['user_is_candidate'] = self.get_object().is_candidate(self.request.user)
context['blocked_candidates'] = assignment.blocked
context['user_is_candidate'] = assignment.is_candidate(self.request.user)
return context
def post(self, *args, **kwargs):
if self.request.user.has_perm('assignment.can_nominate_other'):
assignment = self.get_object()
form = self.form_class(self.request.POST)
if form.is_valid():
user = form.cleaned_data['candidate']
try:
self.get_object().run(user, self.request.user)
except NameError as e:
messages.error(self.request, e)
if (assignment.phase == assignment.PHASE_SEARCH or
self.request.user.has_perm('assignment.can_manage_assignments')):
if (assignment.is_blocked(user) and
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:
messages.success(self.request, _(
"Candidate %s was nominated successfully.")
% html_strong(user))
messages.error(
self.request,
_("You can not add candidates to this assignment"))
return super(AssignmentDetail, self).get(*args, **kwargs)
class AssignmentCreateView(CreateView):
required_permission = 'assignment.can_manage_assignments'
model = Assignment
form_class = AssignmentForm
required_permission = 'assignment.can_manage_assignment'
class AssignmentUpdateView(UpdateView):
required_permission = 'assignment.can_manage_assignments'
model = Assignment
form_class = AssignmentForm
required_permission = 'assignment.can_manage_assignment'
class AssignmentDeleteView(DeleteView):
required_permission = 'assignment.can_manage_assignment'
required_permission = 'assignment.can_manage_assignments'
model = Assignment
success_url_name = 'assignment_list'
class AssignmentSetStatusView(SingleObjectMixin, RedirectView):
class AssignmentSetPhaseView(SingleObjectMixin, RedirectView):
required_permission = 'assignment.can_manage_assignments'
model = Assignment
required_permission = 'assignment.can_manage_assignment'
url_name = 'assignment_detail'
def pre_redirect(self, *args, **kwargs):
status = kwargs.get('status')
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):
phase = int(kwargs.get('phase'))
assignment = self.get_object()
try:
assignment.run(self.request.user, self.request.user)
except NameError as e:
assignment.set_phase(phase)
except ValueError as e:
messages.error(self.request, e)
else:
assignment.save()
messages.success(
self.request, _('You have set your candidature successfully.'))
return redirect(reverse('assignment_detail', args=[assignment.pk]))
self.request,
_('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
url_name = 'assignment_detail'
def pre_redirect(self, *args, **kwargs):
if self.get_object().status == 'sea' or self.request.user.has_perm(
"assignment.can_manage_assignment"):
try:
self.get_object().delrun(self.request.user, blocked=True)
except Exception as e:
# TODO: only catch relevant exception
messages.error(self.request, e)
assignment = self.get_object()
if (assignment.phase == assignment.PHASE_SEARCH or
self.request.user.has_perm('assignment.can_manage_assignments')):
user = self.request.user
if assignment.is_elected(user):
messages.error(
self.request,
_("You are already elected"))
elif assignment.is_candidate(user):
messages.error(
self.request,
_("You are already an candidate"))
else:
messages.success(self.request, _(
'You have withdrawn your candidature successfully. '
'You can not be nominated by other participants anymore.'))
assignment.set_candidate(user)
messages.success(
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:
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
required_permission = 'assignment.can_manage_assignment'
def get_question_message(self):
self._get_person_information()
if not self.is_blocked:
question = _("Do you really want to withdraw %s from the election?") % html_strong(self.person)
self.user = User.objects.get(pk=self.kwargs.get('user_pk'))
assignment = self.get_object()
if assignment.is_blocked:
question = _("Do you really want to unblock %s for the election?") % html_strong(self.user)
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
def on_clicked_yes(self):
self._get_person_information()
try:
self.get_object().delrun(self.person, blocked=False)
except Exception as e:
# TODO: only catch relevant exception
self.error = e
else:
self.user = User.objects.get(pk=self.kwargs.get('user_pk'))
assignment = self.get_object()
if not assignment.is_elected(self.user):
assignment.delete_related_user(self.user)
self.error = False
else:
self.error = _("User %s is already elected") % html_strong(self.user)
def create_final_message(self):
if self.error:
@ -180,14 +212,7 @@ class AssignmentRunOtherDeleteView(SingleObjectMixin, QuestionView):
messages.success(self.request, self.get_final_message())
def get_final_message(self):
message = _("Candidate %s was withdrawn successfully.") % html_strong(self.person)
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)
return _("Candidate %s was withdrawn successfully.") % html_strong(self.user)
class AssignmentViewSet(viewsets.ModelViewSet):
@ -203,16 +228,16 @@ class AssignmentViewSet(viewsets.ModelViewSet):
permission to see assignments and in case of create, update or destroy
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
request.user.has_perm('assignment.can_manage_assignment'))):
request.user.has_perm('assignment.can_manage_assignments'))):
self.permission_denied(request)
def get_serializer_class(self):
"""
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
else:
serializer_class = AssignmentShortSerializer
@ -220,16 +245,17 @@ class AssignmentViewSet(viewsets.ModelViewSet):
class PollCreateView(SingleObjectMixin, RedirectView):
required_permission = 'assignment.can_manage_assignments'
model = Assignment
required_permission = 'assignment.can_manage_assignment'
url_name = 'assignment_detail'
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."))
class PollUpdateView(PollFormView):
required_permission = 'assignment.can_manage_assignments'
poll_class = AssignmentPoll
template_name = 'assignment/assignmentpoll_form.html'
@ -238,49 +264,46 @@ class PollUpdateView(PollFormView):
self.assignment = self.poll.get_assignment()
context['assignment'] = self.assignment
context['poll'] = self.poll
context['polls'] = self.assignment.poll_set.all()
context['polls'] = self.assignment.polls.all()
context['ballotnumber'] = self.poll.get_ballot()
return context
def get_success_url(self):
return_url = ''
if 'apply' not in self.request.POST:
return_url = reverse('assignment_detail', args=[self.poll.assignment.id])
else:
return_url = ''
return return_url
class SetPublishStatusView(SingleObjectMixin, RedirectView):
class SetPublishPollView(SingleObjectMixin, RedirectView):
required_permission = 'assignment.can_manage_assignments'
model = AssignmentPoll
required_permission = 'assignment.can_manage_assignment'
url_name = 'assignment_detail'
allow_ajax = True
publish = False
def get_ajax_context(self, **kwargs):
return {'published': self.object.published}
def get_ajax_context(self, **context):
return super().get_ajax_context(
published=self.object.published,
**context)
def pre_redirect(self, *args, **kwargs):
try:
poll = self.get_object()
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)
poll = self.get_object()
poll.set_published(kwargs['publish'])
class SetElectedView(SingleObjectMixin, RedirectView):
required_permission = 'assignment.can_manage_assignments'
model = Assignment
required_permission = 'assignment.can_manage_assignment'
url_name = 'assignment_detail'
allow_ajax = True
def pre_redirect(self, *args, **kwargs):
self.person = User.objects.get(pk=kwargs['user_id'])
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):
if self.elected:
@ -298,16 +321,16 @@ class AssignmentPollDeleteView(DeleteView):
"""
Delete an assignment poll object.
"""
required_permission = 'assignment.can_manage_assignment'
required_permission = 'assignment.can_manage_assignments'
model = AssignmentPoll
def pre_redirect(self, request, *args, **kwargs):
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):
self.set_assignment()
super(AssignmentPollDeleteView, self).pre_post_redirect(request, *args, **kwargs)
super().pre_post_redirect(request, *args, **kwargs)
def set_assignment(self):
self.assignment = self.get_object().assignment
@ -320,26 +343,26 @@ class AssignmentPollDeleteView(DeleteView):
class AssignmentPDF(PDFView):
required_permission = 'assignment.can_see_assignment'
required_permission = 'assignment.can_see_assignments'
top_space = 0
def get_filename(self):
try:
assignment_id = self.kwargs['pk']
assignment = Assignment.objects.get(id=assignment_id)
assignment = Assignment.objects.get(pk=self.kwargs['pk'])
filename = u'%s-%s' % (
_("Assignment"),
assignment.name.replace(' ', '_'))
assignment.title.replace(' ', '_'))
except:
filename = _("Elections")
return filename
def append_to_pdf(self, story):
try:
assignment_id = self.kwargs['pk']
assignment_pk = self.kwargs['pk']
except KeyError:
assignment_id = None
if assignment_id is None: # print all assignments
assignment_pk = None
if assignment_pk is None: # print all assignments
title = escape(config["assignment_pdf_title"])
story.append(Paragraph(title, stylesheet['Heading1']))
preamble = escape(config["assignment_pdf_preamble"])
@ -356,31 +379,31 @@ class AssignmentPDF(PDFView):
# List of assignments
for assignment in assignments:
story.append(Paragraph(
escape(assignment.name), stylesheet['Heading3']))
escape(assignment.title), stylesheet['Heading3']))
# Assignment details (each assignment on single page)
for assignment in assignments:
story.append(PageBreak())
# append the assignment to the story-object
self.get_assignment(assignment, story)
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
self.get_assignment(assignment, story)
def get_assignment(self, assignment, story):
# title
story.append(Paragraph(
_("Election: %s") % escape(assignment.name), stylesheet['Heading1']))
_("Election: %s") % escape(assignment.title), stylesheet['Heading1']))
story.append(Spacer(0, 0.5 * cm))
# Filling table rows...
data = []
polls = assignment.poll_set.filter(published=True)
polls = assignment.polls.filter(published=True)
# 1. posts
data.append([
Paragraph("%s:" %
_("Number of available posts"), stylesheet['Bold']),
Paragraph(str(assignment.posts), stylesheet['Paragraph'])])
_("Number of members to be elected"), stylesheet['Bold']),
Paragraph(str(assignment.open_posts), stylesheet['Paragraph'])])
# 2a. if no polls available print candidates
if not polls:
@ -393,7 +416,7 @@ class AssignmentPDF(PDFView):
[],
Paragraph("<seq id='counter'/>.&nbsp; %s" % candidate,
stylesheet['Signaturefield'])])
if assignment.status == "sea":
if assignment.phase == assignment.PHASE_SEARCH:
for x in range(0, 7):
data.append([
[],
@ -517,16 +540,16 @@ class CreateRelatedAgendaItemView(_CreateRelatedAgendaItemView):
class AssignmentPollPDF(PDFView):
required_permission = 'assignment.can_manage_assignment'
required_permission = 'assignment.can_manage_assignments'
top_space = 0
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)
def get_filename(self):
filename = u'%s-%s_%s' % (
_("Election"), self.poll.assignment.name.replace(' ', '_'),
_("Election"), self.poll.assignment.title.replace(' ', '_'),
self.poll.get_ballot())
return filename
@ -543,7 +566,7 @@ class AssignmentPollPDF(PDFView):
cell = []
cell.append(Spacer(0, 0.8 * cm))
cell.append(Paragraph(
_("Election") + ": " + self.poll.assignment.name,
_("Election") + ": " + self.poll.assignment.title,
stylesheet['Ballot_title']))
cell.append(Paragraph(
self.poll.description or '',
@ -555,7 +578,7 @@ class AssignmentPollPDF(PDFView):
"%d candidate", "%d candidates", len(options)) % len(options)
available_posts_string = ungettext(
"%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(
"%s, %s, %s" % (ballot_string, candidate_string, available_posts_string),
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')
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')
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)
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_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_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')

View File

@ -91,8 +91,8 @@ class PersonalInfoWidget(TestCase):
def test_candidate_list(self):
assignment = self.import_assignment()
if assignment:
assignment_1 = assignment.models.Assignment.objects.create(name='Hausmeister ooKoh7roApoo3phe', posts=1)
assignment_1.run(candidate=self.user, person=self.user)
assignment_1 = assignment.models.Assignment.objects.create(title='Hausmeister ooKoh7roApoo3phe', open_posts=1)
assignment_1.set_candidate(self.user)
response = self.client.get('/dashboard/')
self.assertContains(response, 'I am candidate for the following elections:', 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')
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/')
self.assertEqual(response.status_code, 200)
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/')
self.assertEqual(response.status_code, 200)

View File

@ -34,7 +34,7 @@ class AssignmentViewTestCase(TestCase):
self.registered_client = Client()
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):
response = test_client.get(url)
@ -45,7 +45,7 @@ class AssignmentViewTestCase(TestCase):
class TestAssignmentPollDelete(AssignmentViewTestCase):
def setUp(self):
super(TestAssignmentPollDelete, self).setUp()
self.assignment1.gen_poll()
self.assignment1.create_poll()
def test_get(self):
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.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.assertFalse(self.assignment1.is_blocked(self.delegate))
@ -73,7 +73,7 @@ class TestAssignmentDetailView(AssignmentViewTestCase):
self.assertNotContains(response, 'No candidates available.')
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.assertTrue(self.assignment1.is_blocked(self.delegate))
@ -89,19 +89,19 @@ class TestAssignmentPollCreateView(TestCase):
def test_assignment_add_candidate(self):
admin = User.objects.get(pk=1)
self.assignment = Assignment.objects.create(
name='test_assignment_oiL2heerookiegeirai0',
posts=1)
self.assignment.run(admin, admin)
title='test_assignment_oiL2heerookiegeirai0',
open_posts=1)
self.assignment.set_candidate(admin)
self.assertEqual(len(Assignment.objects.get(pk=self.assignment.pk).candidates), 1)
def test_assignment_poll_creation(self):
self.test_assignment_add_candidate()
self.assignment.set_status('vot')
self.assignment.set_phase(self.assignment.PHASE_VOTING)
admin_client = Client()
admin_client.login(username='admin', password='admin')
self.assertFalse(AssignmentPoll.objects.exists())
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/')
poll = AssignmentPoll.objects.get()
self.assertEqual(poll.assignment, self.assignment)
@ -117,10 +117,10 @@ class TestAssignmentPollPdfView(TestCase):
def test_assignment_create_poll_pdf(self):
# Create a assignment with a poll
admin = User.objects.get(pk=1)
assignment = Assignment.objects.create(name='assignment1', posts=1)
assignment.run(admin, admin)
assignment.set_status('vot')
assignment.gen_poll()
assignment = Assignment.objects.create(title='assignment1', open_posts=1)
assignment.set_candidate(admin)
assignment.set_phase(assignment.PHASE_VOTING)
assignment.create_poll()
client = Client()
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.
"""
Assignment.objects.create(name='test assignment', posts=1)
Assignment.objects.create(title='test assignment', open_posts=1)
url = '/assignment/poll/1/edit/'
response = self.admin_client.get(url)