From 2c46d3ae0a3016aea9ac741a0c381d9adab902ad Mon Sep 17 00:00:00 2001 From: Emanuel Schuetze Date: Sat, 9 Jan 2016 19:27:02 +0100 Subject: [PATCH] Mark candidate as elected. Updated assignment detail. --- openslides/agenda/static/js/agenda/site.js | 4 +- .../static/templates/agenda/item-detail.html | 4 +- openslides/assignments/serializers.py | 20 ++++- .../assignments/static/js/assignments/site.js | 27 +++--- .../assignments/assignment-detail.html | 84 +++++++++++++------ openslides/assignments/views.py | 9 +- openslides/core/static/css/app.css | 7 ++ 7 files changed, 106 insertions(+), 49 deletions(-) diff --git a/openslides/agenda/static/js/agenda/site.js b/openslides/agenda/static/js/agenda/site.js index fc806061d..27aa213a9 100644 --- a/openslides/agenda/static/js/agenda/site.js +++ b/openslides/agenda/static/js/agenda/site.js @@ -189,7 +189,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) function ($scope, $filter, $http, Agenda, User, item) { Agenda.bindOne(item.id, $scope, 'item'); User.bindAll({}, $scope, 'users'); - $scope.speaker = {}; + $scope.speakerSelectBox = {}; $scope.alert = {}; $scope.speakers = $filter('orderBy')(item.speakers, 'weight'); $scope.$watch(function () { @@ -208,9 +208,11 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) .success(function(data){ $scope.alert.show = false; $scope.speakers = item.speakers; + $scope.speakerSelectBox = {}; }) .error(function(data){ $scope.alert = { type: 'danger', msg: data.detail, show: true }; + $scope.speakerSelectBox = {}; }); }; // delete speaker(!) from list of speakers diff --git a/openslides/agenda/static/templates/agenda/item-detail.html b/openslides/agenda/static/templates/agenda/item-detail.html index f637ce744..7211e37b7 100644 --- a/openslides/agenda/static/templates/agenda/item-detail.html +++ b/openslides/agenda/static/templates/agenda/item-detail.html @@ -89,7 +89,7 @@ {{alert.msg}}
- + {{ $select.selected.get_full_name() }} @@ -98,7 +98,7 @@ - + diff --git a/openslides/assignments/serializers.py b/openslides/assignments/serializers.py index f14968be3..6f8c81f9a 100644 --- a/openslides/assignments/serializers.py +++ b/openslides/assignments/serializers.py @@ -7,6 +7,7 @@ from openslides.utils.rest_api import ( ListField, ListSerializer, ModelSerializer, + SerializerMethodField, ValidationError, ) @@ -47,10 +48,17 @@ class AssignmentOptionSerializer(ModelSerializer): Serializer for assignment.models.AssignmentOption objects. """ votes = AssignmentVoteSerializer(many=True, read_only=True) + is_elected = SerializerMethodField() class Meta: model = AssignmentOption - fields = ('id', 'candidate', 'votes', 'poll') + fields = ('id', 'candidate', 'is_elected', 'votes', 'poll') + + def get_is_elected(self, obj): + """ + Returns the election status of the candidate of this option. + """ + return obj.poll.assignment.is_elected(obj.candidate) class FilterPollListSerializer(ListSerializer): @@ -81,6 +89,7 @@ class AssignmentAllPollSerializer(ModelSerializer): child=IntegerField(min_value=-2)), write_only=True, required=False) + has_votes = SerializerMethodField() class Meta: model = AssignmentPoll @@ -94,9 +103,16 @@ class AssignmentAllPollSerializer(ModelSerializer): 'votesinvalid', 'votescast', 'votes', + 'has_votes', 'assignment') # js-data needs the assignment-id in the nested object to define relations. read_only_fields = ('yesnoabstain',) + def get_has_votes(self, obj): + """ + Returns True if this poll has some votes. + """ + return obj.has_votes() + @transaction.atomic def update(self, instance, validated_data): """ @@ -142,7 +158,7 @@ class AssignmentShortPollSerializer(AssignmentAllPollSerializer): """ Serializer for assignment.models.AssignmentPoll objects. - Serializes only short polls. + Serializes only short polls (excluded unpublished polls). """ class Meta: list_serializer_class = FilterPollListSerializer diff --git a/openslides/assignments/static/js/assignments/site.js b/openslides/assignments/static/js/assignments/site.js index bcfb23b63..960b08fcd 100644 --- a/openslides/assignments/static/js/assignments/site.js +++ b/openslides/assignments/static/js/assignments/site.js @@ -245,16 +245,18 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) User.bindAll({}, $scope, 'users'); Assignment.bindOne(assignment.id, $scope, 'assignment'); Assignment.loadRelations(assignment, 'agenda_item'); - $scope.candidate = {}; + $scope.candidateSelectBox = {}; $scope.alert = {}; // add (nominate) candidate $scope.addCandidate = function (userId) { $http.post('/rest/assignments/assignment/' + assignment.id + '/candidature_other/', {'user': userId}) .success(function(data){ $scope.alert.show = false; + $scope.candidateSelectBox = {}; }) .error(function(data){ $scope.alert = { type: 'danger', msg: data.detail, show: true }; + $scope.candidateSelectBox = {}; }); }; // remove candidate @@ -266,15 +268,6 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) $scope.alert = { type: 'danger', msg: data.detail, show: true }; }); }; - // remove blocked candidate from "block-list" - $scope.removeBlockedCandidate = function (userId) { - $http.delete('/rest/assignments/assignment/' + assignment.id + '/candidature_other/', - {headers: {'Content-Type': 'application/json'}, - data: JSON.stringify({user: userId})}) - .error(function(data){ - $scope.alert = { type: 'danger', msg: data.detail, show: true }; - }); - }; // add me (nominate self as candidate) $scope.addMe = function () { $http.post('/rest/assignments/assignment/' + assignment.id + '/candidature_self/', {}) @@ -316,7 +309,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) $scope.alert = { type: 'danger', msg: data.detail, show: true }; }); }; - // delete ballt + // delete ballot $scope.deleteBallot = function (poll) { poll.DSDestroy(); } @@ -353,6 +346,18 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) $scope.alert = { type: 'danger', msg: message, show: true }; }); }; + // mark candidate as (not) elected + $scope.markElected = function (user, reverse) { + if (reverse) { + $http.delete( + '/rest/assignments/assignment/' + assignment.id + '/mark_elected/', + {headers: {'Content-Type': 'application/json'}, + data: JSON.stringify({user: user})}) + } else { + $http.post('/rest/assignments/assignment/' + assignment.id + '/mark_elected/', {'user': user}) + } + + }; // Just mark some vote value strings for translation. gettext('Yes'), gettext('No'), gettext('Abstain'); diff --git a/openslides/assignments/static/templates/assignments/assignment-detail.html b/openslides/assignments/static/templates/assignments/assignment-detail.html index 633529ecc..c47580974 100644 --- a/openslides/assignments/static/templates/assignments/assignment-detail.html +++ b/openslides/assignments/static/templates/assignments/assignment-detail.html @@ -42,8 +42,9 @@

Candidates

    -
  1. +
  2. {{ related_user.user.get_full_name() }} + - Ballot paper + + 1. Print ballot paper + + +
-
-
-
- {{ option.candidate.get_full_name() }}
- {{ option.votes[0].value | translate }}: {{ option.votes[0].weight }}
- {{ option.votes[1].value | translate }}: {{ option.votes[1].weight }}
- {{ option.votes[2].value | translate }}: {{ option.votes[2].weight }} -
-
-
- {{ option.candidate.get_full_name() }}: {{ option.votes[0].weight}} -
-
-
- Valid votes: {{ poll.votesvalid }}
- Invalid votes: {{ poll.votesinvalid }}
- Votes cast: {{ poll.votescast }} +
+ + + + + + +
Elected + Candidates + Votes +
+ + + {{ option.candidate.get_full_name() }} + + + {{ option.votes[0].value | translate }}: {{ option.votes[0].weight }}
+ {{ option.votes[1].value | translate }}: {{ option.votes[1].weight }}
+ {{ option.votes[2].value | translate }}: {{ option.votes[2].weight }} +
+ + {{ option.votes[0].weight}} + +
+ + Valid votes + + {{ poll.votesvalid }} +
+ + Invalid votes + + {{ poll.votesinvalid }} +
+ + Votes cast + + {{ poll.votescast }} +
diff --git a/openslides/assignments/views.py b/openslides/assignments/views.py index fac971a3c..44f6eab89 100644 --- a/openslides/assignments/views.py +++ b/openslides/assignments/views.py @@ -146,8 +146,6 @@ class AssignmentViewSet(ModelViewSet): """ user = self.get_user_from_request_data(request) assignment = self.get_object() - if assignment.is_elected(user): - raise ValidationError({'detail': _('User %s is already elected.') % user}) if request.method == 'POST': message = self.nominate_other(request, user, assignment) else: @@ -156,15 +154,14 @@ class AssignmentViewSet(ModelViewSet): return Response({'detail': message}) def nominate_other(self, request, user, assignment): + if assignment.is_elected(user): + raise ValidationError({'detail': _('User %s is already elected.') % user}) if assignment.phase == assignment.PHASE_FINISHED: detail = _('You can not nominate someone to this election because it is finished.') raise ValidationError({'detail': detail}) if assignment.phase == assignment.PHASE_VOTING and not request.user.has_perm('assignments.can_manage'): # To nominate another user during voting you have to be a manager. self.permission_denied(request) - if not request.user.has_perm('assignments.can_manage'): - if assignment.is_elected(user): - raise ValidationError({'detail': _('User %s is already elected.') % user}) if assignment.is_candidate(user): raise ValidationError({'detail': _('User %s is already nominated.') % user}) assignment.set_candidate(user) @@ -177,7 +174,7 @@ class AssignmentViewSet(ModelViewSet): if assignment.phase == assignment.PHASE_FINISHED: detail = _('You can not delete someones candidature to this election because it is finished.') raise ValidationError({'detail': detail}) - if not assignment.is_candidate(user): + if not assignment.is_candidate(user) and not assignment.is_elected(user): raise ValidationError({'detail': _('User %s has no status in this election.') % user}) assignment.delete_related_user(user) return _('Candidate %s was withdrawn successfully.') % user diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css index 185f3c9dd..d625c0641 100644 --- a/openslides/core/static/css/app.css +++ b/openslides/core/static/css/app.css @@ -659,6 +659,9 @@ img { .result_label { margin-top: 5px; } +tr.total td { + font-weight: bold; +} /* Chatbox */ #chatbox { @@ -685,6 +688,10 @@ img { width: 1px; } +.minimumTable { + width: auto; +} + .deleteColumn { text-align: center; background-color: #ff9999 !important;