Sort candidates in assignments
This commit is contained in:
parent
447fd35f53
commit
3b1ab265eb
@ -3,6 +3,7 @@ from django.db import transaction
|
|||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from openslides.core.config import config
|
from openslides.core.config import config
|
||||||
|
from openslides.utils.autoupdate import inform_changed_data
|
||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.rest_api import (
|
from openslides.utils.rest_api import (
|
||||||
GenericViewSet,
|
GenericViewSet,
|
||||||
@ -211,9 +212,12 @@ class ItemViewSet(ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericV
|
|||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for speaker in valid_speakers:
|
for speaker in valid_speakers:
|
||||||
speaker.weight = weight
|
speaker.weight = weight
|
||||||
speaker.save()
|
speaker.save(skip_autoupdate=True)
|
||||||
weight += 1
|
weight += 1
|
||||||
|
|
||||||
|
# send autoupdate
|
||||||
|
inform_changed_data(item)
|
||||||
|
|
||||||
# Initiate response.
|
# Initiate response.
|
||||||
return Response({'detail': _('List of speakers successfully sorted.')})
|
return Response({'detail': _('List of speakers successfully sorted.')})
|
||||||
|
|
||||||
|
25
openslides/assignments/migrations/0003_candidate_weight.py
Normal file
25
openslides/assignments/migrations/0003_candidate_weight.py
Normal file
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -28,14 +28,31 @@ class AssignmentRelatedUser(RESTModelMixin, models.Model):
|
|||||||
"""
|
"""
|
||||||
Many to Many table between an assignment and user.
|
Many to Many table between an assignment and user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assignment = models.ForeignKey(
|
assignment = models.ForeignKey(
|
||||||
'Assignment',
|
'Assignment',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='assignment_related_users')
|
related_name='assignment_related_users')
|
||||||
|
"""
|
||||||
|
ForeinKey to the assignment.
|
||||||
|
"""
|
||||||
|
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.CASCADE)
|
on_delete=models.CASCADE)
|
||||||
|
"""
|
||||||
|
ForeinKey to the user who is related to the assignment.
|
||||||
|
"""
|
||||||
|
|
||||||
elected = models.BooleanField(default=False)
|
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:
|
class Meta:
|
||||||
default_permissions = ()
|
default_permissions = ()
|
||||||
@ -193,9 +210,14 @@ class Assignment(RESTModelMixin, models.Model):
|
|||||||
"""
|
"""
|
||||||
Adds the user as candidate.
|
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(
|
related_user, __ = self.assignment_related_users.update_or_create(
|
||||||
user=user,
|
user=user,
|
||||||
defaults={'elected': False})
|
defaults=defaults)
|
||||||
|
|
||||||
def set_elected(self, user):
|
def set_elected(self, user):
|
||||||
"""
|
"""
|
||||||
@ -249,7 +271,13 @@ class Assignment(RESTModelMixin, models.Model):
|
|||||||
poll = self.polls.create(
|
poll = self.polls.create(
|
||||||
description=self.poll_description_default,
|
description=self.poll_description_default,
|
||||||
pollmethod=pollmethod)
|
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
|
# Add all candidates to list of speakers of related agenda item
|
||||||
# TODO: Try to do this in a bulk create
|
# TODO: Try to do this in a bulk create
|
||||||
@ -360,6 +388,8 @@ class AssignmentOption(RESTModelMixin, BaseOption):
|
|||||||
candidate = models.ForeignKey(
|
candidate = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.CASCADE)
|
on_delete=models.CASCADE)
|
||||||
|
weight = models.IntegerField(default=0)
|
||||||
|
|
||||||
vote_class = AssignmentVote
|
vote_class = AssignmentVote
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -32,7 +32,8 @@ class AssignmentRelatedUserSerializer(ModelSerializer):
|
|||||||
'id',
|
'id',
|
||||||
'user',
|
'user',
|
||||||
'elected',
|
'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):
|
class AssignmentVoteSerializer(ModelSerializer):
|
||||||
@ -53,7 +54,7 @@ class AssignmentOptionSerializer(ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AssignmentOption
|
model = AssignmentOption
|
||||||
fields = ('id', 'candidate', 'is_elected', 'votes', 'poll')
|
fields = ('id', 'candidate', 'is_elected', 'votes', 'poll', 'weight')
|
||||||
|
|
||||||
def get_is_elected(self, obj):
|
def get_is_elected(self, obj):
|
||||||
"""
|
"""
|
||||||
|
@ -5,9 +5,10 @@
|
|||||||
angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
|
angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
|
||||||
|
|
||||||
.factory('AssignmentContentProvider', [
|
.factory('AssignmentContentProvider', [
|
||||||
|
'$filter',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
'PDFLayout',
|
'PDFLayout',
|
||||||
function(gettextCatalog, PDFLayout) {
|
function($filter, gettextCatalog, PDFLayout) {
|
||||||
|
|
||||||
var createInstance = function(assignment) {
|
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')
|
// show candidate list (if assignment phase is not 'finished')
|
||||||
var createCandidateList = function() {
|
var createCandidateList = function() {
|
||||||
if (assignment.phase != 2) {
|
if (assignment.phase != 2) {
|
||||||
|
var candidates = $filter('orderBy')(assignment.assignment_related_users, 'weight');
|
||||||
var candidatesText = gettextCatalog.getString("Candidates") + ": ";
|
var candidatesText = gettextCatalog.getString("Candidates") + ": ";
|
||||||
var userList = [];
|
var userList = [];
|
||||||
|
|
||||||
angular.forEach(assignment.assignment_related_users, function(assignmentsRelatedUser) {
|
angular.forEach(candidates, function(assignmentsRelatedUser) {
|
||||||
userList.push({
|
userList.push({
|
||||||
text: assignmentsRelatedUser.user.get_full_name(),
|
text: assignmentsRelatedUser.user.get_full_name(),
|
||||||
margin: [0, 0, 0, 10],
|
margin: [0, 0, 0, 10],
|
||||||
@ -263,9 +265,10 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
|
|||||||
}])
|
}])
|
||||||
|
|
||||||
.factory('BallotContentProvider', [
|
.factory('BallotContentProvider', [
|
||||||
|
'$filter',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
'PDFLayout',
|
'PDFLayout',
|
||||||
function(gettextCatalog, PDFLayout) {
|
function($filter, gettextCatalog, PDFLayout) {
|
||||||
|
|
||||||
var createInstance = function(scope, poll, pollNumber) {
|
var createInstance = function(scope, poll, pollNumber) {
|
||||||
|
|
||||||
@ -324,15 +327,16 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
|
|||||||
};
|
};
|
||||||
|
|
||||||
var createSelectionField = function() {
|
var createSelectionField = function() {
|
||||||
|
var candidates = $filter('orderBy')(poll.options, 'weight');
|
||||||
var candidateBallotList = [];
|
var candidateBallotList = [];
|
||||||
|
|
||||||
if (poll.pollmethod == 'votes') {
|
if (poll.pollmethod == 'votes') {
|
||||||
angular.forEach(poll.options, function(option) {
|
angular.forEach(candidates, function(option) {
|
||||||
var candidate = option.candidate.get_full_name();
|
var candidate = option.candidate.get_full_name();
|
||||||
candidateBallotList.push(PDFLayout.createBallotEntry(candidate));
|
candidateBallotList.push(PDFLayout.createBallotEntry(candidate));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
angular.forEach(poll.options, function(option) {
|
angular.forEach(candidates, function(option) {
|
||||||
var candidate = option.candidate.get_full_name();
|
var candidate = option.candidate.get_full_name();
|
||||||
candidateBallotList.push(createYNBallotEntry(candidate));
|
candidateBallotList.push(createYNBallotEntry(candidate));
|
||||||
});
|
});
|
||||||
|
@ -440,6 +440,7 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
.controller('AssignmentDetailCtrl', [
|
.controller('AssignmentDetailCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'$http',
|
'$http',
|
||||||
|
'$filter',
|
||||||
'filterFilter',
|
'filterFilter',
|
||||||
'gettext',
|
'gettext',
|
||||||
'ngDialog',
|
'ngDialog',
|
||||||
@ -456,11 +457,10 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
'PdfMakeDocumentProvider',
|
'PdfMakeDocumentProvider',
|
||||||
'PdfMakeBallotPaperProvider',
|
'PdfMakeBallotPaperProvider',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
function($scope, $http, filterFilter, gettext, ngDialog, AssignmentForm, operator, Assignment, User,
|
function($scope, $http, $filter, filterFilter, gettext, ngDialog, AssignmentForm, operator, Assignment,
|
||||||
assignment, phases, Projector, ProjectionDefault, AssignmentContentProvider, BallotContentProvider,
|
User, assignment, phases, Projector, ProjectionDefault, AssignmentContentProvider, BallotContentProvider,
|
||||||
PdfMakeDocumentProvider, PdfMakeBallotPaperProvider, gettextCatalog) {
|
PdfMakeDocumentProvider, PdfMakeBallotPaperProvider, gettextCatalog) {
|
||||||
User.bindAll({}, $scope, 'users');
|
User.bindAll({}, $scope, 'users');
|
||||||
Assignment.bindOne(assignment.id, $scope, 'assignment');
|
|
||||||
Assignment.loadRelations(assignment, 'agenda_item');
|
Assignment.loadRelations(assignment, 'agenda_item');
|
||||||
$scope.$watch(function () {
|
$scope.$watch(function () {
|
||||||
return Projector.lastModified();
|
return Projector.lastModified();
|
||||||
@ -470,6 +470,13 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
$scope.defaultProjectorId = projectiondefault.projector_id;
|
$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.candidateSelectBox = {};
|
||||||
$scope.phases = phases;
|
$scope.phases = phases;
|
||||||
$scope.alert = {};
|
$scope.alert = {};
|
||||||
@ -532,6 +539,18 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
else
|
else
|
||||||
return false;
|
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
|
// update phase
|
||||||
$scope.updatePhase = function (phase_id) {
|
$scope.updatePhase = function (phase_id) {
|
||||||
assignment.phase = phase_id;
|
assignment.phase = phase_id;
|
||||||
@ -730,11 +749,12 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
|
|
||||||
.controller('AssignmentPollUpdateCtrl', [
|
.controller('AssignmentPollUpdateCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
|
'$filter',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
'AssignmentPoll',
|
'AssignmentPoll',
|
||||||
'assignmentpoll',
|
'assignmentpoll',
|
||||||
'ballot',
|
'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
|
// set initial values for form model by create deep copy of assignmentpoll object
|
||||||
// so detail view is not updated while editing poll
|
// so detail view is not updated while editing poll
|
||||||
$scope.model = angular.copy(assignmentpoll);
|
$scope.model = angular.copy(assignmentpoll);
|
||||||
@ -743,7 +763,8 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
$scope.alert = {};
|
$scope.alert = {};
|
||||||
|
|
||||||
// add dynamic form fields
|
// add dynamic form fields
|
||||||
assignmentpoll.options.forEach(function(option) {
|
var options = $filter('orderBy')(assignmentpoll.options, 'weight');
|
||||||
|
options.forEach(function(option) {
|
||||||
var defaultValue;
|
var defaultValue;
|
||||||
if (assignmentpoll.pollmethod == 'yna' || assignmentpoll.pollmethod == 'yn') {
|
if (assignmentpoll.pollmethod == 'yna' || assignmentpoll.pollmethod == 'yn') {
|
||||||
if (assignmentpoll.pollmethod == 'yna') {
|
if (assignmentpoll.pollmethod == 'yna') {
|
||||||
|
@ -82,17 +82,21 @@
|
|||||||
|
|
||||||
<div ng-if="assignment.phase != 2">
|
<div ng-if="assignment.phase != 2">
|
||||||
<h3 translate>Candidates</h3>
|
<h3 translate>Candidates</h3>
|
||||||
<ul>
|
<div ui-tree="treeOptions" ng-if="assignment.assignment_related_users.length">
|
||||||
<li ng-repeat="related_user in assignment.assignment_related_users">
|
<ol ui-tree-nodes="" ng-model="relatedUsersSorted">
|
||||||
<a ui-sref="users.user.detail({id: related_user.user_id})">{{ related_user.user.get_full_name() }}</a>
|
<li ui-tree-node ng-repeat="related_user in assignment.assignment_related_users | orderBy:'weight'">
|
||||||
|
<i ui-tree-handle="" class="fa fa-arrows-v spacer-right" os-perms="assignments.can_manage"></i>
|
||||||
|
<a class="spacer-right" ui-sref="users.user.detail({id: related_user.user_id})">
|
||||||
|
{{ related_user.user.get_full_name() }}
|
||||||
|
</a>
|
||||||
<i ng-if="related_user.elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
|
<i ng-if="related_user.elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
|
||||||
<button os-perms="assignments.can_manage" ng-click="removeCandidate(related_user.user_id)"
|
<a href os-perms="assignments.can_manage" ng-click="removeCandidate(related_user.user_id)" class="btn btn-default btn-xs">
|
||||||
class="btn btn-default btn-xs">
|
|
||||||
<i class="fa fa-times"></i>
|
<i class="fa fa-times"></i>
|
||||||
</button>
|
</a>
|
||||||
</ul>
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group spacer-top-lg">
|
||||||
<div uib-alert ng-show="alert.show" ng-class="'alert-' + (alert.type || 'warning')" ng-click="alert={}" close="alert={}">
|
<div uib-alert ng-show="alert.show" ng-class="'alert-' + (alert.type || 'warning')" ng-click="alert={}" close="alert={}">
|
||||||
{{ alert.msg }}
|
{{ alert.msg }}
|
||||||
</div>
|
</div>
|
||||||
@ -174,7 +178,7 @@
|
|||||||
<div ng-if="!poll.has_votes">
|
<div ng-if="!poll.has_votes">
|
||||||
<strong translate>Candidates</strong>
|
<strong translate>Candidates</strong>
|
||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
<li ng-repeat="option in poll.options">
|
<li ng-repeat="option in poll.options | orderBy:'weight'">
|
||||||
<a ui-sref="users.user.detail({id: option.candidate.id})">
|
<a ui-sref="users.user.detail({id: option.candidate.id})">
|
||||||
{{ option.candidate.get_full_name() }}
|
{{ option.candidate.get_full_name() }}
|
||||||
</a>
|
</a>
|
||||||
@ -202,7 +206,7 @@
|
|||||||
<th translate ng-hide="method === 'disabled'">Quorum
|
<th translate ng-hide="method === 'disabled'">Quorum
|
||||||
</th>
|
</th>
|
||||||
<!-- candidates (poll options) -->
|
<!-- candidates (poll options) -->
|
||||||
<tr ng-repeat="option in poll.options">
|
<tr ng-repeat="option in poll.options | orderBy:'weight'">
|
||||||
<!-- candidate name -->
|
<!-- candidate name -->
|
||||||
<td>
|
<td>
|
||||||
<span os-perms="assignments.can_manage">
|
<span os-perms="assignments.can_manage">
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<div ng-if="!showResult">
|
<div ng-if="!showResult">
|
||||||
<h3 translate>Candidates</h3>
|
<h3 translate>Candidates</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li ng-repeat="related_user in assignment.assignment_related_users">
|
<li ng-repeat="related_user in assignment.assignment_related_users | orderBy:'weight'">
|
||||||
{{ related_user.user.get_full_name() }}
|
{{ related_user.user.get_full_name() }}
|
||||||
<i ng-if="related_user.elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
|
<i ng-if="related_user.elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
|
||||||
</ul>
|
</ul>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<th ng-if="poll.has_votes" class="col-sm-6" translate>Votes</th>
|
<th ng-if="poll.has_votes" class="col-sm-6" translate>Votes</th>
|
||||||
|
|
||||||
<!-- candidates (poll options) -->
|
<!-- candidates (poll options) -->
|
||||||
<tr ng-repeat="option in poll.options">
|
<tr ng-repeat="option in poll.options | orderBy:'weight'">
|
||||||
|
|
||||||
<!-- candidate name -->
|
<!-- candidate name -->
|
||||||
<td>
|
<td>
|
||||||
|
@ -2,6 +2,7 @@ from django.contrib.auth import get_user_model
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
from openslides.utils.autoupdate import inform_changed_data
|
||||||
from openslides.utils.rest_api import (
|
from openslides.utils.rest_api import (
|
||||||
DestroyModelMixin,
|
DestroyModelMixin,
|
||||||
GenericViewSet,
|
GenericViewSet,
|
||||||
@ -13,7 +14,7 @@ from openslides.utils.rest_api import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from .access_permissions import AssignmentAccessPermissions
|
from .access_permissions import AssignmentAccessPermissions
|
||||||
from .models import Assignment, AssignmentPoll
|
from .models import Assignment, AssignmentPoll, AssignmentRelatedUser
|
||||||
from .serializers import AssignmentAllPollSerializer
|
from .serializers import AssignmentAllPollSerializer
|
||||||
|
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ class AssignmentViewSet(ModelViewSet):
|
|||||||
# Everybody is allowed to see the metadata.
|
# Everybody is allowed to see the metadata.
|
||||||
result = True
|
result = True
|
||||||
elif self.action in ('create', 'partial_update', 'update', 'destroy',
|
elif self.action in ('create', 'partial_update', 'update', 'destroy',
|
||||||
'mark_elected', 'create_poll'):
|
'mark_elected', 'create_poll', 'sort_related_users'):
|
||||||
result = (self.request.user.has_perm('assignments.can_see') and
|
result = (self.request.user.has_perm('assignments.can_see') and
|
||||||
self.request.user.has_perm('assignments.can_manage'))
|
self.request.user.has_perm('assignments.can_manage'))
|
||||||
elif self.action == 'candidature_self':
|
elif self.action == 'candidature_self':
|
||||||
@ -186,6 +187,48 @@ class AssignmentViewSet(ModelViewSet):
|
|||||||
assignment.create_poll()
|
assignment.create_poll()
|
||||||
return Response({'detail': _('Ballot created successfully.')})
|
return Response({'detail': _('Ballot created successfully.')})
|
||||||
|
|
||||||
|
@detail_route(methods=['post'])
|
||||||
|
def sort_related_users(self, request, pk=None):
|
||||||
|
"""
|
||||||
|
Special view endpoint to sort the assignment related users.
|
||||||
|
|
||||||
|
Expects a list of IDs of the related users (pk of AssignmentRelatedUser model).
|
||||||
|
"""
|
||||||
|
assignment = self.get_object()
|
||||||
|
|
||||||
|
# Check data
|
||||||
|
related_user_ids = request.data.get('related_users')
|
||||||
|
if not isinstance(related_user_ids, list):
|
||||||
|
raise ValidationError(
|
||||||
|
{'detail': _('users has to be a list of IDs.')})
|
||||||
|
|
||||||
|
# Get all related users from AssignmentRelatedUser.
|
||||||
|
related_users = {}
|
||||||
|
for related_user in AssignmentRelatedUser.objects.filter(assignment__id=assignment.id):
|
||||||
|
related_users[related_user.pk] = related_user
|
||||||
|
|
||||||
|
# Check all given candidates from the request
|
||||||
|
valid_related_users = []
|
||||||
|
for related_user_id in related_user_ids:
|
||||||
|
if not isinstance(related_user_id, int) or related_users.get(related_user_id) is None:
|
||||||
|
raise ValidationError(
|
||||||
|
{'detail': _('Invalid data.')})
|
||||||
|
valid_related_users.append(related_users[related_user_id])
|
||||||
|
|
||||||
|
# Sort the related users
|
||||||
|
weight = 1
|
||||||
|
with transaction.atomic():
|
||||||
|
for valid_related_user in valid_related_users:
|
||||||
|
valid_related_user.weight = weight
|
||||||
|
valid_related_user.save(skip_autoupdate=True)
|
||||||
|
weight += 1
|
||||||
|
|
||||||
|
# send autoupdate
|
||||||
|
inform_changed_data(assignment)
|
||||||
|
|
||||||
|
# Initiate response.
|
||||||
|
return Response({'detail': _('Assignment related users successfully sorted.')})
|
||||||
|
|
||||||
|
|
||||||
class AssignmentPollViewSet(UpdateModelMixin, DestroyModelMixin, GenericViewSet):
|
class AssignmentPollViewSet(UpdateModelMixin, DestroyModelMixin, GenericViewSet):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user