Merge pull request #1709 from emanuelschuetze/assignments-rework
Assignments template improvements
This commit is contained in:
commit
6a4cc97469
@ -161,7 +161,6 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
}
|
||||
$scope.alert = { type: 'danger', msg: message, show: true };
|
||||
});
|
||||
;
|
||||
};
|
||||
// delete related item
|
||||
$scope.deleteRelatedItem = function (item) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
<h1 translate>Agenda</h1>
|
||||
|
||||
<div id="submenu">
|
||||
<a ng-click="newDialog()" ng-dialog-class="ngdialog-theme-plain"os-perms="agenda.can_manage" class="btn btn-primary btn-sm">
|
||||
<a ng-click="newDialog()" os-perms="agenda.can_manage" class="btn btn-primary btn-sm">
|
||||
<i class="fa fa-plus fa-lg"></i>
|
||||
<translate>New</translate>
|
||||
</a>
|
||||
|
24
openslides/assignments/migrations/0002_auto_20151126_2153.py
Normal file
24
openslides/assignments/migrations/0002_auto_20151126_2153.py
Normal file
@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assignments', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='assignmentoption',
|
||||
name='poll',
|
||||
field=models.ForeignKey(to='assignments.AssignmentPoll', related_name='options'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='assignmentvote',
|
||||
name='option',
|
||||
field=models.ForeignKey(to='assignments.AssignmentOption', related_name='votes'),
|
||||
),
|
||||
]
|
@ -106,7 +106,7 @@ class Assignment(RESTModelMixin, models.Model):
|
||||
settings.AUTH_USER_MODEL,
|
||||
through='AssignmentRelatedUser')
|
||||
"""
|
||||
Users that a candidates, elected or blocked as candidate.
|
||||
Users that are candidates, elected or blocked as candidate.
|
||||
|
||||
See AssignmentRelatedUser for more infos.
|
||||
"""
|
||||
@ -180,7 +180,7 @@ class Assignment(RESTModelMixin, models.Model):
|
||||
|
||||
def is_blocked(self, user):
|
||||
"""
|
||||
Returns True if the user is blockt for candidature.
|
||||
Returns True if the user is blocked for candidature.
|
||||
|
||||
Costs one database query.
|
||||
"""
|
||||
@ -253,16 +253,15 @@ class Assignment(RESTModelMixin, models.Model):
|
||||
yesnoabstain=yesnoabstain)
|
||||
poll.set_options({'candidate': user} for user in candidates)
|
||||
|
||||
# Add all candidates to all agenda items for this assignment
|
||||
# Add all candidates to list of speakers of related agenda item
|
||||
# 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)
|
||||
except OpenSlidesError:
|
||||
# The Speaker is already on the list. Do nothing.
|
||||
# TODO: Find a smart way not to catch the error concerning AnonymousUser.
|
||||
pass
|
||||
for candidate in self.candidates:
|
||||
try:
|
||||
Speaker.objects.add(candidate, self.agenda_item)
|
||||
except OpenSlidesError:
|
||||
# The Speaker is already on the list. Do nothing.
|
||||
# TODO: Find a smart way not to catch the error concerning AnonymousUser.
|
||||
pass
|
||||
|
||||
return poll
|
||||
|
||||
@ -320,7 +319,7 @@ class Assignment(RESTModelMixin, models.Model):
|
||||
|
||||
|
||||
class AssignmentVote(RESTModelMixin, BaseVote):
|
||||
option = models.ForeignKey('AssignmentOption')
|
||||
option = models.ForeignKey('AssignmentOption', related_name='votes')
|
||||
|
||||
def get_root_rest_element(self):
|
||||
"""
|
||||
@ -330,7 +329,7 @@ class AssignmentVote(RESTModelMixin, BaseVote):
|
||||
|
||||
|
||||
class AssignmentOption(RESTModelMixin, BaseOption):
|
||||
poll = models.ForeignKey('AssignmentPoll')
|
||||
poll = models.ForeignKey('AssignmentPoll', related_name='options')
|
||||
candidate = models.ForeignKey(settings.AUTH_USER_MODEL)
|
||||
vote_class = AssignmentVote
|
||||
|
||||
|
@ -45,8 +45,8 @@ class AssignmentSlide(ProjectorElement):
|
||||
view_class=user.get_view_class(),
|
||||
view_action='retrieve',
|
||||
pk=str(user.pk))
|
||||
for poll in assignment.polls.all().prefetch_related('assignmentoption_set'):
|
||||
for option in poll.assignmentoption_set.all():
|
||||
for poll in assignment.polls.all().prefetch_related('options'):
|
||||
for option in poll.options.all():
|
||||
yield ProjectorRequirement(
|
||||
view_class=option.candidate.get_view_class(),
|
||||
view_action='retrieve',
|
||||
|
@ -46,11 +46,11 @@ class AssignmentOptionSerializer(ModelSerializer):
|
||||
"""
|
||||
Serializer for assignment.models.AssignmentOption objects.
|
||||
"""
|
||||
assignmentvote_set = AssignmentVoteSerializer(many=True, read_only=True)
|
||||
votes = AssignmentVoteSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = AssignmentOption
|
||||
fields = ('candidate', 'assignmentvote_set',)
|
||||
fields = ('candidate', 'votes',)
|
||||
|
||||
|
||||
class FilterPollListSerializer(ListSerializer):
|
||||
@ -75,7 +75,7 @@ class AssignmentAllPollSerializer(ModelSerializer):
|
||||
|
||||
Serializes all polls.
|
||||
"""
|
||||
assignmentoption_set = AssignmentOptionSerializer(many=True, read_only=True)
|
||||
options = AssignmentOptionSerializer(many=True, read_only=True)
|
||||
votes = ListField(
|
||||
child=DictField(
|
||||
child=IntegerField(min_value=-2)),
|
||||
@ -88,7 +88,7 @@ class AssignmentAllPollSerializer(ModelSerializer):
|
||||
'yesnoabstain',
|
||||
'description',
|
||||
'published',
|
||||
'assignmentoption_set',
|
||||
'options',
|
||||
'votesvalid',
|
||||
'votesinvalid',
|
||||
'votescast',
|
||||
@ -148,7 +148,7 @@ class AssignmentShortPollSerializer(AssignmentAllPollSerializer):
|
||||
'yesnoabstain',
|
||||
'description',
|
||||
'published',
|
||||
'assignmentoption_set',
|
||||
'options',
|
||||
'votesvalid',
|
||||
'votesinvalid',
|
||||
'votescast',)
|
||||
|
@ -9,7 +9,7 @@ angular.module('OpenSlidesApp.assignments', [])
|
||||
'Config',
|
||||
function (DS, Config) {
|
||||
return DS.defineResource({
|
||||
name: 'assignments/poll',
|
||||
name: 'assignments/assignmentpoll',
|
||||
relations: {
|
||||
belongsTo: {
|
||||
'assignments/assignment': {
|
||||
@ -44,12 +44,13 @@ angular.module('OpenSlidesApp.assignments', [])
|
||||
'AssignmentRelatedUser',
|
||||
'AssignmentPoll',
|
||||
'jsDataModel',
|
||||
function (DS, AssignmentRelatedUser, AssignmentPoll, jsDataModel) {
|
||||
'gettext',
|
||||
function (DS, AssignmentRelatedUser, AssignmentPoll, jsDataModel, gettext) {
|
||||
var name = 'assignments/assignment';
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
agendaSupplement: '(Assignment)',
|
||||
agendaSupplement: '(' + gettext('Election') + ')',
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
@ -73,6 +74,10 @@ angular.module('OpenSlidesApp.assignments', [])
|
||||
'assignments/relateduser': {
|
||||
localField: 'assignment_related_users',
|
||||
foreignKey: 'assignment_id',
|
||||
},
|
||||
'assignments/assignmentpoll': {
|
||||
localField: 'polls',
|
||||
foreignKey: 'assignment_id',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,9 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
resolve: {
|
||||
assignment: function(Assignment, $stateParams) {
|
||||
return Assignment.find($stateParams.id);
|
||||
},
|
||||
users: function(User) {
|
||||
return User.findAll();
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -54,54 +57,423 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
});
|
||||
})
|
||||
|
||||
.controller('AssignmentListCtrl', function($scope, Assignment, phases) {
|
||||
Assignment.bindAll({}, $scope, 'assignments');
|
||||
// get all item types via OPTIONS request
|
||||
$scope.phases = phases.data.actions.POST.phase.choices;
|
||||
|
||||
// setup table sorting
|
||||
$scope.sortColumn = 'title';
|
||||
$scope.filterPresent = '';
|
||||
$scope.reverse = false;
|
||||
// function to sort by clicked column
|
||||
$scope.toggleSort = function ( column ) {
|
||||
if ( $scope.sortColumn === column ) {
|
||||
$scope.reverse = !$scope.reverse;
|
||||
// Provide generic assignment form fields for create and update view
|
||||
.factory('AssignmentFormFieldFactory', [
|
||||
'gettext',
|
||||
function (gettext) {
|
||||
return {
|
||||
getFormFields: function () {
|
||||
return [
|
||||
{
|
||||
key: 'title',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettext('Title'),
|
||||
required: true
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'description',
|
||||
type: 'textarea',
|
||||
templateOptions: {
|
||||
label: gettext('Description')
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'open_posts',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettext('Number of members to be elected'),
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'poll_description_default',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettext('Default comment on the ballot paper')
|
||||
}
|
||||
}];
|
||||
}
|
||||
}
|
||||
$scope.sortColumn = column;
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
// delete selected assignment
|
||||
$scope.delete = function (assignment) {
|
||||
Assignment.destroy(assignment.id);
|
||||
};
|
||||
})
|
||||
|
||||
.controller('AssignmentDetailCtrl', function($scope, Assignment, assignment) {
|
||||
Assignment.bindOne(assignment.id, $scope, 'assignment');
|
||||
Assignment.loadRelations(assignment, 'agenda_item');
|
||||
})
|
||||
|
||||
.controller('AssignmentCreateCtrl', function($scope, $state, Assignment) {
|
||||
$scope.assignment = {};
|
||||
$scope.save = function(assignment) {
|
||||
Assignment.create(assignment).then(
|
||||
function(success) {
|
||||
$state.go('assignments.assignment.list');
|
||||
// Provide generic assignmentpoll form fields for create and update view
|
||||
.factory('AssignmentPollFormFieldFactory', [
|
||||
'gettext',
|
||||
function (gettext) {
|
||||
return {
|
||||
getFormFields: function () {
|
||||
return [
|
||||
{
|
||||
key: 'description',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettext('Comment on the ballot paper')
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'yes',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettext('Yes'),
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'poll_description_default',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettext('Default comment on the ballot paper')
|
||||
}
|
||||
}];
|
||||
}
|
||||
);
|
||||
};
|
||||
})
|
||||
}
|
||||
}
|
||||
])
|
||||
.controller('AssignmentListCtrl', [
|
||||
'$scope',
|
||||
'ngDialog',
|
||||
'Assignment',
|
||||
'phases',
|
||||
function($scope, ngDialog, Assignment, phases) {
|
||||
Assignment.bindAll({}, $scope, 'assignments');
|
||||
// get all item types via OPTIONS request
|
||||
$scope.phases = phases.data.actions.POST.phase.choices;
|
||||
$scope.alert = {};
|
||||
|
||||
.controller('AssignmentUpdateCtrl', function($scope, $state, Assignment, assignment) {
|
||||
$scope.assignment = assignment; // do not use .binOne(...) so autoupdate is not activated
|
||||
$scope.save = function (assignment) {
|
||||
Assignment.save(assignment).then(
|
||||
function(success) {
|
||||
$state.go('assignments.assignment.list');
|
||||
// setup table sorting
|
||||
$scope.sortColumn = 'title';
|
||||
$scope.filterPresent = '';
|
||||
$scope.reverse = false;
|
||||
// function to sort by clicked column
|
||||
$scope.toggleSort = function ( column ) {
|
||||
if ( $scope.sortColumn === column ) {
|
||||
$scope.reverse = !$scope.reverse;
|
||||
}
|
||||
$scope.sortColumn = column;
|
||||
};
|
||||
|
||||
// open new customslide dialog
|
||||
$scope.newDialog = function () {
|
||||
ngDialog.open({
|
||||
template: 'static/templates/assignments/assignment-form.html',
|
||||
controller: 'AssignmentCreateCtrl',
|
||||
className: 'ngdialog-theme-default wide-form'
|
||||
});
|
||||
};
|
||||
// edit view of related item (content object)
|
||||
$scope.editDialog = function (assignment) {
|
||||
ngDialog.open({
|
||||
template: 'static/templates/assignments/assignment-form.html',
|
||||
controller: 'AssignmentUpdateCtrl',
|
||||
className: 'ngdialog-theme-default wide-form',
|
||||
resolve: {
|
||||
assignment: function(Assignment) {
|
||||
return Assignment.find(assignment.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
// save changed item
|
||||
$scope.save = function (assignment) {
|
||||
Assignment.save(assignment).then(
|
||||
function(success) {
|
||||
assignment.quickEdit = false;
|
||||
$scope.alert.show = false;
|
||||
},
|
||||
function(error){
|
||||
var message = '';
|
||||
for (var e in error.data) {
|
||||
message += e + ': ' + error.data[e] + ' ';
|
||||
}
|
||||
$scope.alert = { type: 'danger', msg: message, show: true };
|
||||
});
|
||||
};
|
||||
// delete all selected assignments
|
||||
$scope.deleteMultiple = function () {
|
||||
angular.forEach($scope.assignments, function (assignment) {
|
||||
if (assignment.selected)
|
||||
Assignment.destroy(assignment.id);
|
||||
});
|
||||
$scope.isDeleteMode = false;
|
||||
$scope.uncheckAll();
|
||||
};
|
||||
// delete single assignment
|
||||
$scope.delete = function (assignment) {
|
||||
Assignment.destroy(assignment.id);
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('AssignmentDetailCtrl', [
|
||||
'$scope',
|
||||
'$http',
|
||||
'ngDialog',
|
||||
'operator',
|
||||
'Assignment',
|
||||
'User',
|
||||
'assignment',
|
||||
function($scope, $http, ngDialog, operator, Assignment, User, assignment) {
|
||||
User.bindAll({}, $scope, 'users');
|
||||
Assignment.bindOne(assignment.id, $scope, 'assignment');
|
||||
Assignment.loadRelations(assignment, 'agenda_item');
|
||||
$scope.candidate = {};
|
||||
$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;
|
||||
})
|
||||
.error(function(data){
|
||||
$scope.alert = { type: 'danger', msg: data.detail, show: true };
|
||||
});
|
||||
};
|
||||
// remove candidate
|
||||
$scope.removeCandidate = 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 };
|
||||
});
|
||||
};
|
||||
// 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/', {})
|
||||
.success(function(data){
|
||||
$scope.alert.show = false;
|
||||
})
|
||||
.error(function(data){
|
||||
$scope.alert = { type: 'danger', msg: data.detail, show: true };
|
||||
});
|
||||
};
|
||||
// remove me (withdraw own candidature)
|
||||
$scope.removeMe = function () {
|
||||
$http.delete('/rest/assignments/assignment/' + assignment.id + '/candidature_self/')
|
||||
.success(function(data){
|
||||
$scope.alert.show = false;
|
||||
})
|
||||
.error(function(data){
|
||||
$scope.alert = { type: 'danger', msg: data.detail, show: true };
|
||||
});
|
||||
};
|
||||
// check if current user is already a candidate (status=1)
|
||||
$scope.isCandidate = function () {
|
||||
var check = assignment.assignment_related_users.map( function(candidate) {
|
||||
if ( candidate.status == 1)
|
||||
return candidate.user_id;
|
||||
}).indexOf(operator.user.id);
|
||||
if (check > -1)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
};
|
||||
// create new ballot
|
||||
$scope.createBallot = function () {
|
||||
$http.post('/rest/assignments/assignment/' + assignment.id + '/create_poll/')
|
||||
.success(function(data){
|
||||
$scope.alert.show = false;
|
||||
})
|
||||
.error(function(data){
|
||||
$scope.alert = { type: 'danger', msg: data.detail, show: true };
|
||||
});
|
||||
};
|
||||
// delete ballt
|
||||
$scope.deleteBallot = function (poll) {
|
||||
poll.DSDestroy();
|
||||
}
|
||||
// edit poll dialog
|
||||
$scope.editPollDialog = function (poll) {
|
||||
ngDialog.open({
|
||||
template: 'static/templates/assignments/assignmentpoll-form.html',
|
||||
controller: 'AssignmentPollUpdateCtrl',
|
||||
className: 'ngdialog-theme-default',
|
||||
resolve: {
|
||||
assignmentpoll: function(AssignmentPoll) {
|
||||
return AssignmentPoll.find(poll.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
// publish ballot
|
||||
$scope.publishBallot = function () {
|
||||
// TODO poll.DSUpdate()
|
||||
$http.put('/rest/assignments/assignment/' + assignment.id + '/publish_poll/')
|
||||
.success(function(data){
|
||||
$scope.alert.show = false;
|
||||
})
|
||||
.error(function(data){
|
||||
$scope.alert = { type: 'danger', msg: data.detail, show: true };
|
||||
});
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('AssignmentCreateCtrl', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'Assignment',
|
||||
'AssignmentFormFieldFactory',
|
||||
function($scope, $state, Assignment, AssignmentFormFieldFactory) {
|
||||
$scope.assignment = {};
|
||||
// get all form fields
|
||||
$scope.formFields = AssignmentFormFieldFactory.getFormFields();
|
||||
|
||||
// save assignment
|
||||
$scope.save = function(assignment) {
|
||||
Assignment.create(assignment).then(
|
||||
function(success) {
|
||||
$scope.closeThisDialog();
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('AssignmentUpdateCtrl', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'Assignment',
|
||||
'AssignmentFormFieldFactory',
|
||||
'assignment',
|
||||
function($scope, $state, Assignment, AssignmentFormFieldFactory, assignment) {
|
||||
// set initial values for form model
|
||||
$scope.model = assignment;
|
||||
// get all form fields
|
||||
$scope.formFields = AssignmentFormFieldFactory.getFormFields();
|
||||
|
||||
// save assignment
|
||||
$scope.save = function (assignment) {
|
||||
Assignment.save(assignment).then(
|
||||
function(success) {
|
||||
$scope.closeThisDialog();
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('AssignmentPollUpdateCtrl', [
|
||||
'$scope',
|
||||
'$state',
|
||||
'gettext',
|
||||
'AssignmentPoll',
|
||||
'assignmentpoll',
|
||||
function($scope, $state, gettext, AssignmentPoll, assignmentpoll) {
|
||||
// set initial values for form model
|
||||
$scope.model = assignmentpoll;
|
||||
$scope.formFields = [];
|
||||
// add dynamic form fields
|
||||
assignmentpoll.options.forEach(function(option) {
|
||||
$scope.formFields.push(
|
||||
{
|
||||
noFormControl: true,
|
||||
template: '<strong>User#' + option.candidate_id + '</strong>'
|
||||
},
|
||||
{
|
||||
key: 'yes_' + option.candidate_id,
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettext('Yes'),
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'no_' + option.candidate_id,
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettext('No'),
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
},
|
||||
{
|
||||
key:'abstain_' + option.candidate_id,
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettext('Abstain'),
|
||||
type: 'number',
|
||||
required: true
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
// add general form fields
|
||||
$scope.formFields.push(
|
||||
{
|
||||
key: 'votesvalid',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettext('Votes valid'),
|
||||
type: 'number'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'votesinvalid',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettext('Votes invalid'),
|
||||
type: 'number'
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'votescast',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettext('Votes cast'),
|
||||
type: 'number'
|
||||
}
|
||||
},
|
||||
// TODO: update description in separat request
|
||||
// (without vote result values)
|
||||
{
|
||||
key: 'description',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettext('Comment on the ballot paper')
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
|
||||
// save assignment
|
||||
$scope.save = function (poll) {
|
||||
var votes = [];
|
||||
assignmentpoll.options.forEach(function(option) {
|
||||
votes.push({
|
||||
"Yes": poll['yes_' + option.candidate_id],
|
||||
"No": poll['no_' + option.candidate_id],
|
||||
"Abstain": poll['abstain_' + option.candidate_id]
|
||||
});
|
||||
});
|
||||
poll.DSUpdate({
|
||||
assignment_id: poll.assignment_id,
|
||||
votes: votes,
|
||||
votesvalid: poll.votesvalid,
|
||||
votesinvalid: poll.votesinvalid,
|
||||
votescast: poll.votescast
|
||||
})
|
||||
.then(function(success) {
|
||||
$scope.closeThisDialog();
|
||||
})
|
||||
};
|
||||
}
|
||||
]);
|
||||
|
||||
}());
|
||||
|
@ -24,18 +24,98 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
Agenda Item: {{ assignment.agenda_item }}
|
||||
|
||||
|
||||
<h3 translate>Description</h3>
|
||||
<div class="white-space-pre-line">{{ assignment.description }}</div>
|
||||
|
||||
<h3 translate>Candidates</h3>
|
||||
<ul>
|
||||
<ol>
|
||||
<li ng-repeat="related_user in assignment.assignment_related_users" ng-if="related_user.status == 1">
|
||||
User: {{ related_user.user.get_full_name() }}
|
||||
</li>
|
||||
</ul>
|
||||
<a ui-sref="users.user.detail({id: related_user.user_id})">{{ related_user.user.get_full_name() }}</a>
|
||||
<button os-perms="assignments.can_manage" ng-click="removeCandidate(related_user.user_id)"
|
||||
class="btn btn-default btn-xs">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</ol>
|
||||
|
||||
<div class="form-group">
|
||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{alert.msg}}
|
||||
</alert>
|
||||
<div os-perms="assignments.can_nominate_other" class="input-group">
|
||||
<ui-select ng-model="candidate.selected" ng-change="addCandidate(candidate.selected.id)">
|
||||
<ui-select-match placeholder="{{ 'Select or search a participant...' | translate }}">
|
||||
{{ $select.selected.get_full_name() }}
|
||||
</ui-select-match>
|
||||
<ui-select-choices repeat="user in users | filter: $select.search">
|
||||
<div ng-bind-html="user.get_full_name() | highlight: $select.search"></div>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
<span class="input-group-btn">
|
||||
<a ng-click="candidate={}" class="btn btn-default">
|
||||
<i class="fa fa-times-circle"></i>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<p os-perm="assignments.can_nominate_self">
|
||||
<button ng-if="!isCandidate()" ng-click="addMe()" class="btn btn-default">
|
||||
<i class="fa fa-plus"></i>
|
||||
<translate>Add me</translate>
|
||||
</button>
|
||||
<button ng-if="isCandidate()" ng-click="removeMe()" class="btn btn-default">
|
||||
<i class="fa fa-minus"></i>
|
||||
<translate>Remove me</translate>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h3 translate>Election result</h3>
|
||||
<!-- TODO -->
|
||||
<button os-perms="assignments.can_manage" ng-click="createBallot()" class="btn btn-default btn-sm">
|
||||
<i class="fa fa-bar-chart fa-lg"></i>
|
||||
<translate>New ballot</translate>
|
||||
</button>
|
||||
|
||||
<uib-tabset class="spacer">
|
||||
<uib-tab ng-repeat="poll in assignment.polls" heading="Ballot {{$index+1}}">
|
||||
<div os-perms="assignments.can_manage" class="spacer">
|
||||
<button ng-click="editPollDialog(poll)"
|
||||
class="btn btn-default btn-sm">
|
||||
<i class="fa fa-pencil"></i>
|
||||
<translate>Edit</translate>
|
||||
</button>
|
||||
<!-- angular requires to open the link in new tab with "target='_blank'".
|
||||
Otherwise the pdf url can't be open in same window; angular redirects to "/". -->
|
||||
<a ui-sref="assignmentpoll_pdf({poll_pk: poll.id})" target="_blank"
|
||||
class="btn btn-default btn-sm">
|
||||
<i class="fa fa-file-pdf-o"></i> Ballot paper
|
||||
</a>
|
||||
<button os-perms-lite="assignments.can_manage" ng-if="!poll.published" ng-click="publishBallot(poll)"
|
||||
class="btn btn-primary btn-sm">
|
||||
<i class="fa fa-globe"></i>
|
||||
<translate>Publish result</translate>
|
||||
</button>
|
||||
<button os-perms-lite="assignments.can_manage" ng-if="poll.published" ng-click="unpublishBallot(poll)"
|
||||
class="btn btn-default btn-sm">
|
||||
<i class="fa fa-globe"></i>
|
||||
<translate>Unpublish result</translate>
|
||||
</button>
|
||||
<a ng-click="deleteBallot(poll)" class="btn btn-default btn-sm">
|
||||
<i class="fa fa-times"></i>
|
||||
<translate>Delete</translate>
|
||||
</a>
|
||||
</div>
|
||||
<div class="results">
|
||||
<div ng-repeat="option in poll.options">
|
||||
<strong>User#{{ option.candidate_id }}</strong>
|
||||
<div ng-if="option.votes.length > 0">
|
||||
<div ng-repeat="vote in option.votes">
|
||||
{{ vote.value}}: {{ vote.weight}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
Valid votes: {{ poll.votesvalid }}<br>
|
||||
Invalid votes: {{ poll.votesinvalid }}<br>
|
||||
Votes cast: {{ poll.votescast }}
|
||||
</div>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
|
@ -1,31 +1,13 @@
|
||||
<h1 ng-if="assignment.id" translate>Edit election</h1>
|
||||
<h1 ng-if="!assignment.id" translate>New election</h1>
|
||||
|
||||
<div id="submenu">
|
||||
<a ui-sref="assignments.assignment.list" class="btn btn-sm btn-default">
|
||||
<i class="fa fa-angle-double-left fa-lg"></i>
|
||||
<translate>Back to overview</translate>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<form name="assignmentForm">
|
||||
<div class="form-group">
|
||||
<label for="inputTitle" translate>Title</label>
|
||||
<input type="text" ng-model="assignment.title" class="form-control" name="inputTitle" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="textareaDesciption" translate>Description</label>
|
||||
<textarea ng-model="assignment.description" class="form-control" name="textareaDescription" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="inputPosts" translate>Number of members to be elected</label>
|
||||
<input type="number" ng-model="assignment.open_posts" class="form-control" name="inputPosts" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" ng-click="save(assignment)" class="btn btn-primary" translate>
|
||||
Save
|
||||
</button>
|
||||
<button ui-sref="assignments.assignment.list" class="btn btn-default" translate>
|
||||
Cancel
|
||||
</button>
|
||||
<form name="assignmentForm" ng-submit="save(model)">
|
||||
<formly-form model="model" fields="formFields">
|
||||
<button type="submit" ng-disabled="assignmentForm.$invalid" class="btn btn-primary" translate>
|
||||
Submit
|
||||
</button>
|
||||
<button ng-click="closeThisDialog()" class="btn btn-default" translate>
|
||||
Cancel
|
||||
</button>
|
||||
</formly-form>
|
||||
</form>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<h1 translate>Elections</h1>
|
||||
|
||||
<div id="submenu">
|
||||
<a ui-sref="assignments.assignment.create" os-perms="assignments.can_manage" class="btn btn-primary btn-sm">
|
||||
<a ng-click="newDialog()" os-perms="assignments.can_manage" class="btn btn-primary btn-sm">
|
||||
<i class="fa fa-plus fa-lg"></i>
|
||||
<translate>New</translate>
|
||||
</a>
|
||||
@ -17,64 +17,129 @@
|
||||
|
||||
<div class="row form-group">
|
||||
<div class="col-sm-8">
|
||||
<!-- TODO: select filter for phases -->
|
||||
<form class="form-inline">
|
||||
<!-- delete mode -->
|
||||
<div os-perms-lite="assignments.can_manage" class="form-group">
|
||||
<label for="deleteSwitcher" translate>Delete mode</label>
|
||||
<switch id="deleteSwitcher" ng-model="isDeleteMode" ng-change="uncheckAll()"
|
||||
on="{{'On'|translate}}" off="{{'Off'|translate}}"
|
||||
class="green wide form-control">
|
||||
</switch>
|
||||
</div>
|
||||
<!-- delete button -->
|
||||
<a ng-show="isDeleteMode && (assignments|filter:{selected:true}).length > 0"
|
||||
os-perms="assignments.can_manage" ng-click="deleteMultiple()"
|
||||
class="btn btn-primary btn-sm form-control">
|
||||
<i class="fa fa-trash fa-lg"></i>
|
||||
<translate>Delete selected elections</translate>
|
||||
</a>
|
||||
<!-- phase filter -->
|
||||
|
||||
<select ng-model="phaseFilter" class="form-control" id="phaseFilter">
|
||||
<option value="" translate>--- Select phase ---</option>
|
||||
<option ng-repeat="phase in phases" value="{{ phase.value }}">{{ phase.display_name }}</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<input type="text" os-focus-me ng-model="filter.search" class="form-control"
|
||||
placeholder="{{ 'Filter' | translate}}">
|
||||
<div class="input-group">
|
||||
<div class="input-group-addon"><i class="fa fa-filter"></i></div>
|
||||
<input type="text" os-focus-me ng-model="filter.search" class="form-control"
|
||||
placeholder="{{ 'Filter' | translate}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<!-- projector column -->
|
||||
<th ng-show="!isDeleteMode" os-perms="core.can_manage_projector" class="firstColumn">
|
||||
<!-- delete selection column -->
|
||||
<th ng-show="isDeleteMode" os-perms-lite="assignments.can_manage" class="firstColumn deleteColumn">
|
||||
<input type="checkbox" ng-model="selectedAll" ng-change="checkAll()">
|
||||
<th ng-click="toggleSort('title')" class="sortable">
|
||||
<translate>Title</translate>
|
||||
<i class="pull-right fa" ng-show="sortColumn === 'title' && header.sortable != false"
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
<th ng-click="toggleSort('open_posts')" class="sortable">
|
||||
<translate>Posts</translate>
|
||||
<th ng-click="toggleSort('open_posts')" class="sortable optional">
|
||||
<translate>Candidates</translate> / <translate>Posts</translate>
|
||||
<i class="pull-right fa" ng-show="sortColumn === 'open_posts' && header.sortable != false"
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
<th ng-click="toggleSort('phase')" class="sortable">
|
||||
<translate>State</translate>
|
||||
<th ng-click="toggleSort('phase')" class="sortable optional">
|
||||
<translate>Phase</translate>
|
||||
<i class="pull-right fa" ng-show="sortColumn === 'phase' && header.sortable != false"
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
<th os-perms="assignments.can_manage core.can_manage_projector" class="minimum">
|
||||
<translate>Actions</translate>
|
||||
<tbody>
|
||||
<tr ng-repeat="assignment in assignments | filter: filter.search |
|
||||
orderBy: sortColumn:reverse" ng-class="{ 'activeline': assignment.isProjected() }">
|
||||
<td><a ui-sref="assignments.assignment.detail({id: assignment.id})">{{ assignment.title }}</a>
|
||||
<td class="optional">{{ assignment.open_posts }}
|
||||
<td class="optional">
|
||||
<tr ng-repeat="assignment in assignments | filter: filter.search | filter: {phase: phaseFilter} |
|
||||
orderBy: sortColumn:reverse"
|
||||
class="animate-item"
|
||||
ng-class="{ 'activeline': assignment.isProjected(), 'selected': assignment.selected }">
|
||||
<!-- projector column -->
|
||||
<td ng-show="!isDeleteMode" os-perms-lite="core.can_manage_projector">
|
||||
<a class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': assignment.isProjected() }"
|
||||
ng-click="assignment.project()"
|
||||
title="{{ 'Project assignment' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<!-- delete selection column -->
|
||||
<td ng-show="isDeleteMode" os-perms="assignments.can_manage" class="deleteColumn">
|
||||
<input type="checkbox" ng-model="assignment.selected">
|
||||
<!-- assignment data colums -->
|
||||
<td ng-if="!assignment.quickEdit" ng-mouseover="assignment.hover=true" ng-mouseleave="assignment.hover=false">
|
||||
<strong><a ui-sref="assignments.assignment.detail({id: assignment.id})">{{ assignment.title }}</a></strong>
|
||||
<div os-perms="assignments.can_manage" class="hoverActions" ng-class="{'hiddenDiv': !assignment.hover}">
|
||||
<a href="" ng-click="editDialog(assignment)" translate>Edit</a> |
|
||||
<a href="" ng-click="assignment.quickEdit=true" translate>QuickEdit</a> |
|
||||
<!-- TODO: translate confirm message -->
|
||||
<a href="" class="text-danger"
|
||||
ng-bootbox-confirm="Are you sure you want to delete <b>{{ assignment.title }}</b>?"
|
||||
ng-bootbox-confirm-action="delete(assignment)" translate>Delete</a>
|
||||
</div>
|
||||
<td ng-if="!assignment.quickEdit" class="optional"><span class="badge">{{ assignment.open_posts }}</span>
|
||||
<td ng-if="!assignment.quickEdit" class="optional">
|
||||
<span class="label" ng-class="{'label-primary': assignment.phase == 0,
|
||||
'label-warning': assignment.phase == 1,
|
||||
'label-success': assignment.phase == 2 }">
|
||||
{{ phases[assignment.phase].display_name }}
|
||||
</span>
|
||||
<td os-perms="assignments.can_manage core.can_manage_projector" class="nobr">
|
||||
<!-- project -->
|
||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': assignment.isProjected() }"
|
||||
ng-click="assignment.project()"
|
||||
title="{{ 'Project election' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<!-- edit -->
|
||||
<a ui-sref="assignments.assignment.detail.update({id: assignment.id })" os-perms="assignments.can_manage"
|
||||
class="btn btn-default btn-sm"
|
||||
title="{{ 'Edit' | translate}}">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
<!-- delete -->
|
||||
<a os-perms="assignments.can_manage" class="btn btn-danger btn-sm"
|
||||
ng-bootbox-confirm="Are you sure you want to delete <b>{{ assignment.title }}</b>?"
|
||||
ng-bootbox-confirm-action="delete(assignment)"
|
||||
title="{{ 'Delete' | translate }}">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</a>
|
||||
<!-- quickEdit columns -->
|
||||
<td ng-if="assignment.quickEdit" colspan="3">
|
||||
<h4>{{ assignment.title }} <span class="text-muted">– Quick Edit</span></h4>
|
||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
|
||||
{{alert.msg}}
|
||||
</alert>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<label for="inputTitle" translate>Title</label>
|
||||
<input type="text" ng-model="assignment.title" class="form-control input-sm" id="inputTitle">
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<label for="inputPosts" translate>Number of members to be elected</label>
|
||||
<input type="number" ng-model="assignment.open_posts" class="form-control input-sm" id="inputPosts">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<label for="selectPhase" translate>Phase</label>
|
||||
<select ng-options="phase.value as phase.display_name for phase in phases"
|
||||
ng-model="assignment.phase" class="form-control" id="selectPhase">
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-xs-6"></div>
|
||||
</div>
|
||||
<div class="spacer">
|
||||
<button ng-click="assignment.quickEdit=false" class="btn btn-default pull-left" translate>
|
||||
Cancel
|
||||
</button>
|
||||
<button ng-click="save(assignment)" class="btn btn-primary" translate>
|
||||
Update
|
||||
</button>
|
||||
<a href="" ng-click="editDialog(assignment)"
|
||||
class="pull-right" translate>Edit election...</a>
|
||||
</div>
|
||||
</table>
|
||||
|
@ -0,0 +1,12 @@
|
||||
<h1 translate>Update ballot</h1>
|
||||
|
||||
<form name="assignmentpollForm" ng-submit="save(model)">
|
||||
<formly-form model="model" fields="formFields">
|
||||
<button type="submit" ng-disabled="assignmentForm.$invalid" class="btn btn-primary" translate>
|
||||
Submit
|
||||
</button>
|
||||
<button ng-click="closeThisDialog()" class="btn btn-default" translate>
|
||||
Cancel
|
||||
</button>
|
||||
</formly-form>
|
||||
</form>
|
@ -166,7 +166,7 @@ class AssignmentViewSet(ModelViewSet):
|
||||
self.permission_denied(request)
|
||||
if not request.user.has_perm('assignments.can_manage'):
|
||||
if assignment.is_blocked(user):
|
||||
raise ValidationError({'detail': _('User %s does not want to be an candidate.') % user})
|
||||
raise ValidationError({'detail': _('User %s does not want to be a candidate. Only a manager can do this.') % user})
|
||||
if assignment.is_elected(user):
|
||||
raise ValidationError({'detail': _('User %s is already elected.') % user})
|
||||
# If the user is already a candidate he can be nominated nevertheless.
|
||||
@ -344,7 +344,7 @@ class AssignmentPDF(PDFView):
|
||||
length = len(vote_results)
|
||||
for candidate, poll_list in vote_results.iteritems():
|
||||
row = []
|
||||
candidate_string = candidate.clean_name
|
||||
candidate_string = candidate.get_short_name()
|
||||
if candidate in elected_candidates:
|
||||
candidate_string = "* " + candidate_string
|
||||
if candidate.name_suffix and length < 20:
|
||||
@ -549,7 +549,7 @@ class AssignmentPollPDF(PDFView):
|
||||
candidate = option.candidate
|
||||
cell.append(Paragraph("<font name='circlefont' size='15'>%s</font> \
|
||||
<font name='Ubuntu'>%s</font>" %
|
||||
(circle, candidate.clean_name), stylesheet['Ballot_option_name']))
|
||||
(circle, candidate.get_short_name()), stylesheet['Ballot_option_name']))
|
||||
if candidate.structure_level:
|
||||
cell.append(Paragraph(
|
||||
"(%s)" % candidate.structure_level,
|
||||
|
@ -381,7 +381,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
})
|
||||
|
||||
|
||||
// Provide generic motion form fields for create and update view
|
||||
// Provide generic customslide form fields for create and update view
|
||||
.factory('CustomslideFormFieldFactory', [
|
||||
'gettext',
|
||||
'CKEditorOptions',
|
||||
|
Loading…
Reference in New Issue
Block a user