diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py index 7e905e4dd..2b0360d9a 100644 --- a/openslides/agenda/views.py +++ b/openslides/agenda/views.py @@ -3,6 +3,7 @@ from django.db import transaction from django.utils.translation import ugettext as _ from openslides.core.config import config +from openslides.utils.autoupdate import inform_changed_data from openslides.utils.exceptions import OpenSlidesError from openslides.utils.rest_api import ( GenericViewSet, @@ -211,9 +212,12 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV with transaction.atomic(): for speaker in valid_speakers: speaker.weight = weight - speaker.save() + speaker.save(skip_autoupdate=True) weight += 1 + # send autoupdate + inform_changed_data(item) + # Initiate response. return Response({'detail': _('List of speakers successfully sorted.')}) diff --git a/openslides/assignments/migrations/0003_candidate_weight.py b/openslides/assignments/migrations/0003_candidate_weight.py new file mode 100644 index 000000000..51ef01e21 --- /dev/null +++ b/openslides/assignments/migrations/0003_candidate_weight.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2016-12-16 11:18 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assignments', '0002_assignmentpoll_pollmethod'), + ] + + operations = [ + migrations.AddField( + model_name='assignmentrelateduser', + name='weight', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='assignmentoption', + name='weight', + field=models.IntegerField(default=0), + ), + ] diff --git a/openslides/assignments/models.py b/openslides/assignments/models.py index 40fe85a00..08c506698 100644 --- a/openslides/assignments/models.py +++ b/openslides/assignments/models.py @@ -28,14 +28,31 @@ class AssignmentRelatedUser(RESTModelMixin, models.Model): """ Many to Many table between an assignment and user. """ + assignment = models.ForeignKey( 'Assignment', on_delete=models.CASCADE, related_name='assignment_related_users') + """ + ForeinKey to the assignment. + """ + user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE) + """ + ForeinKey to the user who is related to the assignment. + """ + elected = models.BooleanField(default=False) + """ + Saves the election state of each user + """ + + weight = models.IntegerField(default=0) + """ + The sort order of the candidates. + """ class Meta: default_permissions = () @@ -193,9 +210,14 @@ class Assignment(RESTModelMixin, models.Model): """ Adds the user as candidate. """ + weight = self.assignment_related_users.aggregate( + models.Max('weight'))['weight__max'] or 0 + defaults = { + 'elected': False, + 'weight': weight + 1} related_user, __ = self.assignment_related_users.update_or_create( user=user, - defaults={'elected': False}) + defaults=defaults) def set_elected(self, user): """ @@ -249,7 +271,13 @@ class Assignment(RESTModelMixin, models.Model): poll = self.polls.create( description=self.poll_description_default, pollmethod=pollmethod) - poll.set_options({'candidate': user} for user in candidates) + options = [] + related_users = AssignmentRelatedUser.objects.filter(assignment__id=self.id) + for related_user in related_users: + options.append({ + 'candidate': related_user.user, + 'weight': related_user.weight}) + poll.set_options(options) # Add all candidates to list of speakers of related agenda item # TODO: Try to do this in a bulk create @@ -360,6 +388,8 @@ class AssignmentOption(RESTModelMixin, BaseOption): candidate = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE) + weight = models.IntegerField(default=0) + vote_class = AssignmentVote class Meta: diff --git a/openslides/assignments/serializers.py b/openslides/assignments/serializers.py index ea5802c18..730dbc25c 100644 --- a/openslides/assignments/serializers.py +++ b/openslides/assignments/serializers.py @@ -32,7 +32,8 @@ class AssignmentRelatedUserSerializer(ModelSerializer): 'id', 'user', 'elected', - 'assignment') # js-data needs the assignment-id in the nested object to define relations. + 'assignment', + 'weight') # js-data needs the assignment-id in the nested object to define relations. class AssignmentVoteSerializer(ModelSerializer): @@ -53,7 +54,7 @@ class AssignmentOptionSerializer(ModelSerializer): class Meta: model = AssignmentOption - fields = ('id', 'candidate', 'is_elected', 'votes', 'poll') + fields = ('id', 'candidate', 'is_elected', 'votes', 'poll', 'weight') def get_is_elected(self, obj): """ diff --git a/openslides/assignments/static/js/assignments/pdf.js b/openslides/assignments/static/js/assignments/pdf.js index c49fa29d3..f73c95e20 100644 --- a/openslides/assignments/static/js/assignments/pdf.js +++ b/openslides/assignments/static/js/assignments/pdf.js @@ -5,9 +5,10 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf']) .factory('AssignmentContentProvider', [ + '$filter', 'gettextCatalog', 'PDFLayout', - function(gettextCatalog, PDFLayout) { + function($filter, gettextCatalog, PDFLayout) { var createInstance = function(assignment) { @@ -60,10 +61,11 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf']) // show candidate list (if assignment phase is not 'finished') var createCandidateList = function() { if (assignment.phase != 2) { + var candidates = $filter('orderBy')(assignment.assignment_related_users, 'weight'); var candidatesText = gettextCatalog.getString("Candidates") + ": "; var userList = []; - angular.forEach(assignment.assignment_related_users, function(assignmentsRelatedUser) { + angular.forEach(candidates, function(assignmentsRelatedUser) { userList.push({ text: assignmentsRelatedUser.user.get_full_name(), margin: [0, 0, 0, 10], @@ -263,9 +265,10 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf']) }]) .factory('BallotContentProvider', [ + '$filter', 'gettextCatalog', 'PDFLayout', - function(gettextCatalog, PDFLayout) { + function($filter, gettextCatalog, PDFLayout) { var createInstance = function(scope, poll, pollNumber) { @@ -324,15 +327,16 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf']) }; var createSelectionField = function() { + var candidates = $filter('orderBy')(poll.options, 'weight'); var candidateBallotList = []; if (poll.pollmethod == 'votes') { - angular.forEach(poll.options, function(option) { + angular.forEach(candidates, function(option) { var candidate = option.candidate.get_full_name(); candidateBallotList.push(PDFLayout.createBallotEntry(candidate)); }); } else { - angular.forEach(poll.options, function(option) { + angular.forEach(candidates, function(option) { var candidate = option.candidate.get_full_name(); candidateBallotList.push(createYNBallotEntry(candidate)); }); diff --git a/openslides/assignments/static/js/assignments/site.js b/openslides/assignments/static/js/assignments/site.js index 160db603d..7b1618ddb 100644 --- a/openslides/assignments/static/js/assignments/site.js +++ b/openslides/assignments/static/js/assignments/site.js @@ -440,6 +440,7 @@ angular.module('OpenSlidesApp.assignments.site', [ .controller('AssignmentDetailCtrl', [ '$scope', '$http', + '$filter', 'filterFilter', 'gettext', 'ngDialog', @@ -456,11 +457,10 @@ angular.module('OpenSlidesApp.assignments.site', [ 'PdfMakeDocumentProvider', 'PdfMakeBallotPaperProvider', 'gettextCatalog', - function($scope, $http, filterFilter, gettext, ngDialog, AssignmentForm, operator, Assignment, User, - assignment, phases, Projector, ProjectionDefault, AssignmentContentProvider, BallotContentProvider, + function($scope, $http, $filter, filterFilter, gettext, ngDialog, AssignmentForm, operator, Assignment, + User, assignment, phases, Projector, ProjectionDefault, AssignmentContentProvider, BallotContentProvider, PdfMakeDocumentProvider, PdfMakeBallotPaperProvider, gettextCatalog) { User.bindAll({}, $scope, 'users'); - Assignment.bindOne(assignment.id, $scope, 'assignment'); Assignment.loadRelations(assignment, 'agenda_item'); $scope.$watch(function () { return Projector.lastModified(); @@ -470,6 +470,13 @@ angular.module('OpenSlidesApp.assignments.site', [ $scope.defaultProjectorId = projectiondefault.projector_id; } }); + $scope.$watch(function () { + return Assignment.lastModified(assignment.id); + }, function () { + // setup sorting of candidates + $scope.relatedUsersSorted = $filter('orderBy')(assignment.assignment_related_users, 'weight'); + $scope.assignment = Assignment.get(assignment.id); + }); $scope.candidateSelectBox = {}; $scope.phases = phases; $scope.alert = {}; @@ -532,6 +539,18 @@ angular.module('OpenSlidesApp.assignments.site', [ else return false; }; + // Sort all candidates + $scope.treeOptions = { + dropped: function () { + var sortedCandidates = []; + _.forEach($scope.relatedUsersSorted, function (user) { + sortedCandidates.push(user.id); + }); + $http.post('/rest/assignments/assignment/' + $scope.assignment.id + '/sort_related_users/', + {related_users: sortedCandidates} + ); + } + }; // update phase $scope.updatePhase = function (phase_id) { assignment.phase = phase_id; @@ -730,11 +749,12 @@ angular.module('OpenSlidesApp.assignments.site', [ .controller('AssignmentPollUpdateCtrl', [ '$scope', + '$filter', 'gettextCatalog', 'AssignmentPoll', 'assignmentpoll', 'ballot', - function($scope, gettextCatalog, AssignmentPoll, assignmentpoll, ballot) { + function($scope, $filter, gettextCatalog, AssignmentPoll, assignmentpoll, ballot) { // set initial values for form model by create deep copy of assignmentpoll object // so detail view is not updated while editing poll $scope.model = angular.copy(assignmentpoll); @@ -743,7 +763,8 @@ angular.module('OpenSlidesApp.assignments.site', [ $scope.alert = {}; // add dynamic form fields - assignmentpoll.options.forEach(function(option) { + var options = $filter('orderBy')(assignmentpoll.options, 'weight'); + options.forEach(function(option) { var defaultValue; if (assignmentpoll.pollmethod == 'yna' || assignmentpoll.pollmethod == 'yn') { if (assignmentpoll.pollmethod == 'yna') { diff --git a/openslides/assignments/static/templates/assignments/assignment-detail.html b/openslides/assignments/static/templates/assignments/assignment-detail.html index 900e1e4a5..a6036b330 100644 --- a/openslides/assignments/static/templates/assignments/assignment-detail.html +++ b/openslides/assignments/static/templates/assignments/assignment-detail.html @@ -82,17 +82,21 @@