Added general abstain/no fields for assignments. Used in votes mode.

This commit is contained in:
FinnStutzenstein 2018-07-03 15:47:31 +02:00
parent 2da894b517
commit bb654f7517
8 changed files with 166 additions and 83 deletions

View File

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-07-03 13:23
from __future__ import unicode_literals
from django.db import migrations
from openslides.utils.models import MinMaxIntegerField
class Migration(migrations.Migration):
dependencies = [
('assignments', '0003_candidate_weight'),
]
operations = [
migrations.AddField(
model_name='assignmentpoll',
name='votesabstain',
field=MinMaxIntegerField(null=True, blank=True, min_value=-2),
),
migrations.AddField(
model_name='assignmentpoll',
name='votesno',
field=MinMaxIntegerField(null=True, blank=True, min_value=-2),
),
]

View File

@ -19,7 +19,7 @@ from openslides.poll.models import (
)
from openslides.utils.autoupdate import inform_changed_data
from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.models import RESTModelMixin
from openslides.utils.models import MinMaxIntegerField, RESTModelMixin
from .access_permissions import AssignmentAccessPermissions
@ -423,6 +423,11 @@ class AssignmentPoll(RESTModelMixin, CollectDefaultVotesMixin, # type: ignore
max_length=79,
blank=True)
votesabstain = MinMaxIntegerField(null=True, blank=True, min_value=-2)
""" General abstain votes, used for pollmethod 'votes' """
votesno = MinMaxIntegerField(null=True, blank=True, min_value=-2)
""" General no votes, used for pollmethod 'votes' """
class Meta:
default_permissions = ()

View File

@ -111,6 +111,8 @@ class AssignmentAllPollSerializer(ModelSerializer):
'description',
'published',
'options',
'votesabstain',
'votesno',
'votesvalid',
'votesinvalid',
'votescast',
@ -160,6 +162,8 @@ class AssignmentAllPollSerializer(ModelSerializer):
# Update remaining writeable fields.
instance.description = validated_data.get('description', instance.description)
instance.published = validated_data.get('published', instance.published)
instance.votesabstain = validated_data.get('votesabstain', instance.votesabstain)
instance.votesno = validated_data.get('votesno', instance.votesno)
instance.votesvalid = validated_data.get('votesvalid', instance.votesvalid)
instance.votesinvalid = validated_data.get('votesinvalid', instance.votesinvalid)
instance.votescast = validated_data.get('votescast', instance.votescast)
@ -182,6 +186,8 @@ class AssignmentShortPollSerializer(AssignmentAllPollSerializer):
'description',
'published',
'options',
'votesabstain',
'votesno',
'votesvalid',
'votesinvalid',
'votescast',

View File

@ -214,7 +214,9 @@ angular.module('OpenSlidesApp.assignments', [])
if (_.findIndex(this.options, predicate) !== -1) {
base = void 0;
} else {
if (typeof base === 'undefined' && type !== 'votescast' && type !== 'votesinvalid' && type !== 'votesvalid') {
if (typeof base === 'undefined' && type !== 'votesabstain' &&
type !== 'votesno' && type !== 'votescast' &&
type !== 'votesinvalid' && type !== 'votesvalid') {
base = _.reduce(this.options, function (sum, option) {
return sum + option.votes[0].weight;
}, 0);
@ -240,6 +242,12 @@ angular.module('OpenSlidesApp.assignments', [])
config = Config.get('assignments_poll_100_percent_base').value;
switch (type) {
case 'votesabstain':
vote = this.votesabstain;
break;
case 'votesno':
vote = this.votesno;
break;
case 'votesinvalid':
vote = this.votesinvalid;
break;

View File

@ -67,7 +67,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
var candidatesText = gettextCatalog.getString("Candidates") + ": ";
var userList = [];
angular.forEach(candidates, function(assignmentsRelatedUser) {
_.forEach(candidates, function(assignmentsRelatedUser) {
userList.push({
text: assignmentsRelatedUser.user.get_full_name(),
margin: [0, 0, 0, 10],
@ -114,30 +114,30 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
//creates the voting string for the result table and differentiates between special values
var parseVoteValue = function(voteObject, printLabel) {
var voteVal = "";
var voteVal = '';
if (voteObject) {
if (printLabel) {
voteVal += voteObject.label + ": ";
voteVal += voteObject.label + ': ';
}
voteVal += voteObject.value;
if (voteObject.percentStr) {
voteVal += " " + voteObject.percentStr;
voteVal += ' ' + voteObject.percentStr;
}
}
voteVal += "\n";
voteVal += '\n';
return voteVal;
};
// creates the election result table
var createPollResultTable = function() {
var resultBody = [];
angular.forEach(assignment.polls, function(poll, pollIndex) {
_.forEach(assignment.polls, function(poll, pollIndex) {
if (poll.published) {
var pollTableBody = [];
resultBody.push({
text: gettextCatalog.getString("Ballot") + " " + (pollIndex+1),
text: gettextCatalog.getString('Ballot') + ' ' + (pollIndex+1),
bold: true,
style: 'textItem',
margin: [0, 15, 0, 0]
@ -145,16 +145,16 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
pollTableBody.push([
{
text: gettextCatalog.getString("Candidates"),
text: gettextCatalog.getString('Candidates'),
style: 'tableHeader',
},
{
text: gettextCatalog.getString("Votes"),
text: gettextCatalog.getString('Votes'),
style: 'tableHeader',
}
]);
angular.forEach(poll.options, function(pollOption, optionIndex) {
_.forEach(poll.options, function(pollOption, optionIndex) {
var candidateName = pollOption.candidate.get_full_name();
var votes = pollOption.getVotes(); // 0 = yes, 1 = no, 2 = abstain
var tableLine = [];
@ -169,7 +169,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
);
} else {
var resultBlock = [];
angular.forEach(votes, function(vote) {
_.forEach(votes, function(vote) {
resultBlock.push(parseVoteValue(vote, true));
});
tableLine.push({
@ -181,44 +181,26 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
pollTableBody.push(tableLine);
});
if (poll.votesvalid) {
pollTableBody.push([
{
text: gettextCatalog.getString("Valid ballots"),
style: 'tableConclude'
},
{
text: parseVoteValue(poll.getVote('votesvalid'), false),
style: 'tableConclude'
},
]);
}
var pushConcludeRow = function (title, fieldName) {
if (poll[fieldName]) {
pollTableBody.push([
{
text: gettextCatalog.getString(title),
style: 'tableConclude'
},
{
text: parseVoteValue(poll.getVote(fieldName), false),
style: 'tableConclude'
},
]);
}
};
if (poll.votesinvalid) {
pollTableBody.push([
{
text: gettextCatalog.getString("Invalid ballots"),
style: 'tableConclude'
},
{
text: parseVoteValue(poll.getVote('votesinvalid'), false),
style: 'tableConclude'
},
]);
}
if (poll.votescast) {
pollTableBody.push([
{
text: gettextCatalog.getString("Casted ballots"),
style: 'tableConclude'
},
{
text: parseVoteValue(poll.getVote('votescast'), false),
style: 'tableConclude'
},
]);
}
pushConcludeRow('Abstain', 'votesabstain');
pushConcludeRow('No', 'votesno');
pushConcludeRow('Valid ballots', 'votesvalid');
pushConcludeRow('Invalid ballots', 'votesinvalid');
pushConcludeRow('Casted ballots', 'votescast');
var resultTableJsonSting = {
table: {
@ -236,7 +218,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
// add the legend to the result body
if (assignment.polls.length > 0 && isElectedSemaphore) {
resultBody.push({
text: "* = " + gettextCatalog.getString("is elected"),
text: '* = ' + gettextCatalog.getString('is elected'),
margin: [0, 5, 0, 0],
});
}
@ -366,12 +348,17 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
var candidateBallotList = [];
if (poll.pollmethod == 'votes') {
angular.forEach(candidates, function(option) {
_.forEach(candidates, function(option) {
var candidate = option.candidate.get_full_name();
candidateBallotList.push(PDFLayout.createBallotEntry(candidate));
});
// Add 'no' option
var no = gettextCatalog.getString('No');
var ballotEntry = PDFLayout.createBallotEntry(no);
ballotEntry.margin[1] = 25; // top margin
candidateBallotList.push(ballotEntry);
} else {
angular.forEach(candidates, function(option) {
_.forEach(candidates, function(option) {
var candidate;
if (option.candidate) {
candidate = option.candidate.get_full_name();
@ -565,7 +552,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
};
var toc = [];
angular.forEach(assignmentTitles, function(title) {
_.forEach(assignmentTitles, function(title) {
toc.push({
text: title,
style: "tableofcontent"
@ -656,7 +643,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
createBallotPdf: function (assignment, pollId) {
var thePoll;
var pollNumber;
angular.forEach(assignment.polls, function(poll, pollIndex) {
_.forEach(assignment.polls, function(poll, pollIndex) {
if (poll.id == pollId) {
thePoll = poll;
pollNumber = pollIndex+1;

View File

@ -708,7 +708,7 @@ angular.module('OpenSlidesApp.assignments.site', [
// add dynamic form fields
var options = $filter('orderBy')(assignmentpoll.options, 'weight');
options.forEach(function(option) {
_.forEach(options, function(option) {
var defaultValue;
if (assignmentpoll.pollmethod == 'yna' || assignmentpoll.pollmethod == 'yn') {
defaultValue = {};
@ -783,35 +783,60 @@ angular.module('OpenSlidesApp.assignments.site', [
});
}
});
// add general form fields
$scope.formFields.push(
if (assignmentpoll.pollmethod == 'votes'){
$scope.formFields.push(
{
key: 'votesvalid',
key: 'votesabstain',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Valid ballots'),
label: gettextCatalog.getString('Abstain'),
type: 'number',
min: -2,
}
},
{
key: 'votesinvalid',
key: 'votesno',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Invalid ballots'),
type: 'number',
min: -2,
}
},
{
key: 'votescast',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Casted ballots'),
label: gettextCatalog.getString('No'),
type: 'number',
min: -2,
}
}
);
}
// add general form fields
$scope.formFields.push(
{
template: '<hr class="smallhr">',
},
{
key: 'votesvalid',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Valid ballots'),
type: 'number',
min: -2,
}
},
{
key: 'votesinvalid',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Invalid ballots'),
type: 'number',
min: -2,
}
},
{
key: 'votescast',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Casted ballots'),
type: 'number',
min: -2,
}
}
);
// save assignmentpoll
@ -820,22 +845,22 @@ angular.module('OpenSlidesApp.assignments.site', [
if (assignmentpoll.pollmethod == 'yna') {
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]
'Yes': poll['yes_' + option.candidate_id],
'No': poll['no_' + option.candidate_id],
'Abstain': poll['abstain_' + option.candidate_id]
});
});
} else if (assignmentpoll.pollmethod == 'yn') {
assignmentpoll.options.forEach(function(option) {
votes.push({
"Yes": poll['yes_' + option.candidate_id],
"No": poll['no_' + option.candidate_id]
'Yes': poll['yes_' + option.candidate_id],
'No': poll['no_' + option.candidate_id]
});
});
} else {
assignmentpoll.options.forEach(function(option) {
votes.push({
"Votes": poll['vote_' + option.candidate_id],
'Votes': poll['vote_' + option.candidate_id],
});
});
}
@ -843,9 +868,11 @@ angular.module('OpenSlidesApp.assignments.site', [
poll.DSUpdate({
assignment_id: poll.assignment_id,
votes: votes,
votesabstain: poll.votesabstain,
votesno: poll.votesno,
votesvalid: poll.votesvalid,
votesinvalid: poll.votesinvalid,
votescast: poll.votescast
votescast: poll.votescast,
})
.then(function(success) {
$scope.alert.show = false;

View File

@ -195,10 +195,6 @@
{{ option.candidate.get_full_name() }}
</a>
</ul>
<strong translate>Election method</strong><br>
<span ng-if="poll.pollmethod == 'votes'" translate>One vote per candidate</span>
<span ng-if="poll.pollmethod == 'yna'" translate>Yes/No/Abstain per candidate</span>
<span ng-if="poll.pollmethod == 'yn'" translate>Yes/No per candidate</span>
</div>
<!-- Settings for majority calculations -->
@ -254,6 +250,18 @@
</span>
<!-- total votes (valid/invalid/casts) -->
<tr ng-if="poll.pollmethod === 'votes'">
<td>
<translate>Abstain</translate>
<td>
{{ poll.getVote('votesabstain').value }}
{{ poll.getVote('votesabstain').percentStr }}
<tr ng-if="poll.pollmethod === 'votes'">
<td>
<translate>No</translate>
<td>
{{ poll.getVote('votesno').value }}
{{ poll.getVote('votesno').percentStr }}
<tr>
<td>
<translate>Valid ballots</translate>
@ -273,6 +281,11 @@
{{ poll.getVote('votescast').value }}
{{ poll.getVote('votescast').percentStr }}
</table>
<strong translate>Election method</strong><br>
<span ng-if="poll.pollmethod == 'votes'" translate>One vote per candidate</span>
<span ng-if="poll.pollmethod == 'yna'" translate>Yes/No/Abstain per candidate</span>
<span ng-if="poll.pollmethod == 'yn'" translate>Yes/No per candidate</span>
</div>
</div>
</uib-tab>

View File

@ -63,7 +63,17 @@
</div>
</div>
<!-- total votes (valid/invalid/casts) -->
<!-- total votes (abstain/no/valid/invalid/casts) -->
<tr class="total" ng-if="poll.has_votes && poll.pollmethod === 'votes' && poll.getVote('votesabstain').value !== null">
<td>
<translate>Abstain</translate>
<td ng-init="vote = poll.getVote('votesabstain')">
{{ vote.value }} {{ vote.percentStr }}
<tr class="total" ng-if="poll.has_votes && poll.pollmethod === 'votes' && poll.getVote('votesno').value !== null">
<td>
<translate>No</translate>
<td ng-init="vote = poll.getVote('votesno')">
{{ vote.value }} {{ vote.percentStr }}
<tr class="total" ng-if="poll.has_votes && poll.getVote('votesvalid').value !== null">
<td>
<translate>Valid ballots</translate>