New calculate percentage options (fixes issue #2182)
This commit is contained in:
parent
66aa42021a
commit
e8fa0eca5a
@ -14,6 +14,7 @@ Agenda:
|
||||
|
||||
Assignments:
|
||||
- Remove unused assignment config to publish winner election results only.
|
||||
- Added options to calculate percentages on different bases.
|
||||
|
||||
Core:
|
||||
- Added support for big assemblies with lots of users.
|
||||
@ -27,6 +28,7 @@ Motions:
|
||||
- Added configurable fields for comments.
|
||||
- Added recommendations for motions.
|
||||
- Changed label of former state "commited a bill" to "refered to committee".
|
||||
- Added options to calculate percentages on different bases.
|
||||
|
||||
Users:
|
||||
- Added field is_committee and new default group Committees.
|
||||
|
@ -1,7 +1,6 @@
|
||||
from django.core.validators import MinValueValidator
|
||||
|
||||
from openslides.core.config import ConfigVariable
|
||||
from openslides.poll.models import PERCENT_BASE_CHOICES
|
||||
|
||||
|
||||
def get_config_variables():
|
||||
@ -28,10 +27,18 @@ def get_config_variables():
|
||||
|
||||
yield ConfigVariable(
|
||||
name='assignments_poll_100_percent_base',
|
||||
default_value='WITHOUT_INVALID',
|
||||
default_value='YES_NO_ABSTAIN',
|
||||
input_type='choice',
|
||||
label='The 100 % base of an election result consists of',
|
||||
choices=PERCENT_BASE_CHOICES,
|
||||
choices=(
|
||||
{'value': 'YES_NO_ABSTAIN', 'display_name': 'Yes/No/Abstain per candidate'},
|
||||
{'value': 'YES_NO', 'display_name': 'Yes/No per candidate'},
|
||||
{'value': 'VALID', 'display_name': 'All valid ballots'},
|
||||
{'value': 'CAST', 'display_name': 'All casted ballots'},
|
||||
{'value': 'DISABLED', 'display_name': 'Disabled (no percents)'}),
|
||||
help_text="For Yes/No/Abstain and Yes/No the 100 % base depends on the election method: If there are " +
|
||||
"more candidates than open posts, the sum of all votes of all candidates is 100%. Otherwise " +
|
||||
"the sum of all votes per candidate is 100 %.",
|
||||
weight=420,
|
||||
group='Elections',
|
||||
subgroup='Ballot and ballot papers')
|
||||
|
28
openslides/assignments/migrations/0003_auto_20160907_0946.py
Normal file
28
openslides/assignments/migrations/0003_auto_20160907_0946.py
Normal file
@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.9 on 2016-09-07 09:46
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assignments', '0002_assignmentpoll_yesno'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='assignmentpoll',
|
||||
name='yesno',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='assignmentpoll',
|
||||
name='yesnoabstain',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='assignmentpoll',
|
||||
name='pollmethod',
|
||||
field=models.CharField(default='yna', max_length=5),
|
||||
),
|
||||
]
|
@ -208,29 +208,23 @@ class Assignment(RESTModelMixin, models.Model):
|
||||
|
||||
# Find out the method of the election
|
||||
if config['assignments_poll_vote_values'] == 'votes':
|
||||
yesnoabstain = False
|
||||
yesno = False
|
||||
pollmethod = 'votes'
|
||||
elif config['assignments_poll_vote_values'] == 'yesnoabstain':
|
||||
yesnoabstain = True
|
||||
yesno = False
|
||||
pollmethod = 'yna'
|
||||
elif config['assignments_poll_vote_values'] == 'yesno':
|
||||
yesnoabstain = False
|
||||
yesno = True
|
||||
pollmethod = 'yn'
|
||||
else:
|
||||
# config['assignments_poll_vote_values'] == 'auto'
|
||||
# candidates <= available posts -> yes/no/abstain
|
||||
if len(candidates) <= (self.open_posts - self.elected.count()):
|
||||
yesno = False
|
||||
yesnoabstain = True
|
||||
pollmethod = 'yna'
|
||||
else:
|
||||
yesno = False
|
||||
yesnoabstain = False
|
||||
pollmethod = 'votes'
|
||||
|
||||
# Create the poll with the candidates.
|
||||
poll = self.polls.create(
|
||||
description=self.poll_description_default,
|
||||
yesnoabstain=yesnoabstain,
|
||||
yesno=yesno)
|
||||
pollmethod=pollmethod)
|
||||
poll.set_options({'candidate': user} for user in candidates)
|
||||
|
||||
# Add all candidates to list of speakers of related agenda item
|
||||
@ -364,8 +358,9 @@ class AssignmentPoll(RESTModelMixin, CollectDefaultVotesMixin,
|
||||
Assignment,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='polls')
|
||||
yesnoabstain = models.BooleanField(default=False)
|
||||
yesno = models.BooleanField(default=False)
|
||||
pollmethod = models.CharField(
|
||||
max_length=5,
|
||||
default='yna')
|
||||
description = models.CharField(
|
||||
max_length=79,
|
||||
blank=True)
|
||||
@ -377,9 +372,9 @@ class AssignmentPoll(RESTModelMixin, CollectDefaultVotesMixin,
|
||||
return self.assignment
|
||||
|
||||
def get_vote_values(self):
|
||||
if self.yesnoabstain:
|
||||
if self.pollmethod == 'yna':
|
||||
return ['Yes', 'No', 'Abstain']
|
||||
elif self.yesno:
|
||||
elif self.pollmethod == 'yn':
|
||||
return ['Yes', 'No']
|
||||
else:
|
||||
return ['Votes']
|
||||
|
@ -96,8 +96,7 @@ class AssignmentAllPollSerializer(ModelSerializer):
|
||||
model = AssignmentPoll
|
||||
fields = (
|
||||
'id',
|
||||
'yesnoabstain',
|
||||
'yesno',
|
||||
'pollmethod',
|
||||
'description',
|
||||
'published',
|
||||
'options',
|
||||
@ -107,7 +106,7 @@ class AssignmentAllPollSerializer(ModelSerializer):
|
||||
'votes',
|
||||
'has_votes',
|
||||
'assignment') # js-data needs the assignment-id in the nested object to define relations.
|
||||
read_only_fields = ('yesnoabstain',)
|
||||
read_only_fields = ('pollmethod',)
|
||||
validators = (default_votes_validator,)
|
||||
|
||||
def get_has_votes(self, obj):
|
||||
@ -122,12 +121,12 @@ class AssignmentAllPollSerializer(ModelSerializer):
|
||||
Customized update method for polls. To update votes use the write
|
||||
only field 'votes'.
|
||||
|
||||
Example data for a 'yesnoabstain'=true poll with two candidates:
|
||||
Example data for a 'pollmethod'='yna' poll with two candidates:
|
||||
|
||||
"votes": [{"Yes": 10, "No": 4, "Abstain": -2},
|
||||
{"Yes": -1, "No": 0, "Abstain": -2}]
|
||||
|
||||
Example data for a 'yesnoabstain'=false poll with two candidates:
|
||||
Example data for a 'pollmethod' ='yn' poll with two candidates:
|
||||
"votes": [{"Votes": 10}, {"Votes": 0}]
|
||||
"""
|
||||
# Update votes.
|
||||
@ -168,8 +167,7 @@ class AssignmentShortPollSerializer(AssignmentAllPollSerializer):
|
||||
model = AssignmentPoll
|
||||
fields = (
|
||||
'id',
|
||||
'yesnoabstain',
|
||||
'yesno',
|
||||
'pollmethod',
|
||||
'description',
|
||||
'published',
|
||||
'options',
|
||||
|
@ -21,28 +21,85 @@ angular.module('OpenSlidesApp.assignments', [])
|
||||
var poll = this.poll;
|
||||
var votes = [];
|
||||
var config = Config.get('assignments_poll_100_percent_base').value;
|
||||
var impossible = false;
|
||||
var yes = null, no = null, abstain = null;
|
||||
angular.forEach(this.votes, function(vote) {
|
||||
if (vote.value == "Yes" || vote.value == "Votes") {
|
||||
yes = vote.weight;
|
||||
} else if (vote.value == "No") {
|
||||
no = vote.weight;
|
||||
} else if (vote.value == "Abstain") {
|
||||
abstain = vote.weight;
|
||||
}
|
||||
});
|
||||
//calculation for several candidates without yes/no options
|
||||
var do_sum_of_all = false;
|
||||
var sum_of_votes = 0;
|
||||
if (poll.options.length > 1 && poll.pollmethod == 'votes') {
|
||||
do_sum_of_all = true;
|
||||
}
|
||||
if (do_sum_of_all === true) {
|
||||
angular.forEach(poll.options, function(option) {
|
||||
angular.forEach(option.votes, function(vote) {
|
||||
if (vote.value == "Votes") {
|
||||
if (vote.weight >= 0 ) {
|
||||
sum_of_votes = sum_of_votes + vote.weight;
|
||||
} else {
|
||||
impossible = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
angular.forEach(this.votes, function(vote) {
|
||||
// check for special value
|
||||
var value;
|
||||
switch (vote.weight) {
|
||||
case -1:
|
||||
value = gettextCatalog.getString('majority');
|
||||
impossible = true;
|
||||
break;
|
||||
case -2:
|
||||
value = gettextCatalog.getString('undocumented');
|
||||
impossible = true;
|
||||
break;
|
||||
default:
|
||||
value = vote.weight;
|
||||
if (vote.weight >= 0) {
|
||||
value = vote.weight;
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// calculate percent value
|
||||
var percentStr, percentNumber;
|
||||
if (config == "WITHOUT_INVALID" && poll.votesvalid > 0 && vote.weight >= 0) {
|
||||
percentNumber = Math.round(vote.weight * 100 / poll.votesvalid * 10) / 10;
|
||||
} else if (config == "WITH_INVALID" && poll.votescast > 0 && vote.weight >= 0) {
|
||||
percentNumber = Math.round(vote.weight * 100 / (poll.votescast) * 10) / 10;
|
||||
var percentStr, percentNumber, base;
|
||||
if (config == "VALID") {
|
||||
if (poll.votesvalid && poll.votesvalid > 0) {
|
||||
base = poll.votesvalid;
|
||||
}
|
||||
} else if ( config == "CAST") {
|
||||
if (poll.votescast && poll.votescast > 0) {
|
||||
base = poll.votescast;
|
||||
}
|
||||
} else if (config == "YES_NO" && !impossible) {
|
||||
if (vote.value == "Yes" || vote.value == "No" || vote.value == "Votes"){
|
||||
if (do_sum_of_all) {
|
||||
base = sum_of_votes;
|
||||
} else {
|
||||
base = yes + no;
|
||||
}
|
||||
}
|
||||
} else if (config == "YES_NO_ABSTAIN" && !impossible) {
|
||||
if (do_sum_of_all) {
|
||||
base = sum_of_votes;
|
||||
} else {
|
||||
base = yes + no + abstain;
|
||||
}
|
||||
}
|
||||
if (percentNumber >= 0 ) {
|
||||
if (base !== 'undefined' && vote.weight >= 0) {
|
||||
percentNumber = Math.round(vote.weight * 100 / base * 10) / 10;
|
||||
}
|
||||
if (percentNumber >= 0 && percentNumber !== 'undefined') {
|
||||
percentStr = "(" + percentNumber + "%)";
|
||||
}
|
||||
votes.push({
|
||||
@ -88,34 +145,50 @@ angular.module('OpenSlidesApp.assignments', [])
|
||||
return name;
|
||||
},
|
||||
// returns object with value and percent (for votes valid/invalid/cast only)
|
||||
getVote: function (vote) {
|
||||
if (!this.has_votes || !vote) {
|
||||
return;
|
||||
}
|
||||
var value = '';
|
||||
switch (vote) {
|
||||
case -1:
|
||||
value = gettextCatalog.getString('majority');
|
||||
getVote: function (type) {
|
||||
var value, percentStr, vote;
|
||||
switch(type) {
|
||||
case 'votesinvalid':
|
||||
vote = this.votesinvalid;
|
||||
break;
|
||||
case -2:
|
||||
value = gettextCatalog.getString('undocumented');
|
||||
case 'votesvalid':
|
||||
vote = this.votesvalid;
|
||||
break;
|
||||
default:
|
||||
value = vote;
|
||||
case 'votescast':
|
||||
vote = this.votescast;
|
||||
break;
|
||||
}
|
||||
// calculate percent value
|
||||
var config = Config.get('assignments_poll_100_percent_base').value;
|
||||
var percent;
|
||||
if ((config == "WITHOUT_INVALID" && vote == this.votesvalid && vote >= 0) ||
|
||||
(config == "WITH_INVALID" && vote == this.votescast && vote >= 0)) {
|
||||
percent = '(100%)';
|
||||
if (this.has_votes && vote) {
|
||||
switch (vote) {
|
||||
case -1:
|
||||
value = gettextCatalog.getString('majority');
|
||||
break;
|
||||
case -2:
|
||||
value = gettextCatalog.getString('undocumented');
|
||||
break;
|
||||
default:
|
||||
value = vote;
|
||||
}
|
||||
if (vote >= 0) {
|
||||
var config = Config.get('assignments_poll_100_percent_base').value;
|
||||
var percentNumber;
|
||||
if (config == "CAST" && this.votescast && this.votescast > 0) {
|
||||
percentNumber = Math.round(vote * 100 / this.votescast * 10) / 10;
|
||||
} else if (config == "VALID" && this.votesvalid && this.votesvalid >= 0) {
|
||||
if (type === 'votesvalid'){
|
||||
percentNumber = Math.round(vote * 100 / this.votesvalid * 10) / 10;
|
||||
}
|
||||
}
|
||||
if (percentNumber !== 'undefined' && percentNumber >= 0 && percentNumber <=100) {
|
||||
percentStr = "(" + percentNumber + "%)";
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
'value': value,
|
||||
'percent': percent
|
||||
'percentStr': percentStr
|
||||
};
|
||||
},
|
||||
}
|
||||
},
|
||||
relations: {
|
||||
belongsTo: {
|
||||
|
@ -460,6 +460,7 @@ 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) {
|
||||
@ -590,8 +591,8 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
// add dynamic form fields
|
||||
assignmentpoll.options.forEach(function(option) {
|
||||
var defaultValue;
|
||||
if (assignmentpoll.yesnoabstain || assignmentpoll.yesno) {
|
||||
if (assignmentpoll.yesnoabstain) {
|
||||
if (assignmentpoll.pollmethod == 'yna' || assignmentpoll.pollmethod == 'yn') {
|
||||
if (assignmentpoll.pollmethod == 'yna') {
|
||||
defaultValue = {
|
||||
'yes': '',
|
||||
'no': '',
|
||||
@ -608,7 +609,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
if (option.votes.length) {
|
||||
defaultValue.yes = option.votes[0].weight;
|
||||
defaultValue.no = option.votes[1].weight;
|
||||
if (assignmentpoll.yesnoabstain){
|
||||
if (assignmentpoll.pollmethod == 'yna'){
|
||||
defaultValue.abstain = option.votes[2].weight;
|
||||
}
|
||||
}
|
||||
@ -637,7 +638,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
},
|
||||
defaultValue: defaultValue.no
|
||||
});
|
||||
if (assignmentpoll.yesnoabstain){
|
||||
if (assignmentpoll.pollmethod == 'yna'){
|
||||
$scope.formFields.push(
|
||||
{
|
||||
key:'abstain_' + option.candidate_id,
|
||||
@ -673,7 +674,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
key: 'votesvalid',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Votes valid'),
|
||||
label: gettextCatalog.getString('Valid ballots'),
|
||||
type: 'number'
|
||||
}
|
||||
},
|
||||
@ -681,7 +682,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
key: 'votesinvalid',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Votes invalid'),
|
||||
label: gettextCatalog.getString('Invalid ballots'),
|
||||
type: 'number'
|
||||
}
|
||||
},
|
||||
@ -689,7 +690,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
key: 'votescast',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Votes cast'),
|
||||
label: gettextCatalog.getString('Casted ballots'),
|
||||
type: 'number'
|
||||
}
|
||||
},
|
||||
@ -707,7 +708,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
// save assignmentpoll
|
||||
$scope.save = function (poll) {
|
||||
var votes = [];
|
||||
if (assignmentpoll.yesnoabstain) {
|
||||
if (assignmentpoll.pollmethod == 'yna') {
|
||||
assignmentpoll.options.forEach(function(option) {
|
||||
votes.push({
|
||||
"Yes": poll['yes_' + option.candidate_id],
|
||||
@ -715,7 +716,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
"Abstain": poll['abstain_' + option.candidate_id]
|
||||
});
|
||||
});
|
||||
} else if (assignmentpoll.yesno) {
|
||||
} else if (assignmentpoll.pollmethod == 'yn') {
|
||||
assignmentpoll.options.forEach(function(option) {
|
||||
votes.push({
|
||||
"Yes": poll['yes_' + option.candidate_id],
|
||||
@ -764,8 +765,13 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
gettext('Elections');
|
||||
gettext('Ballot and ballot papers');
|
||||
gettext('The 100 % base of an election result consists of');
|
||||
gettext('All valid votes (Yes/No/Abstain)');
|
||||
gettext('All votes cast (including invalid votes)');
|
||||
gettext('For Yes/No/Abstain and Yes/No the 100 % base depends ' +
|
||||
'on the election method: If there are more candidates ' +
|
||||
'than open posts, the sum of all votes of all candidates is 100 %.');
|
||||
gettext('Yes/No/Abstain per candidate');
|
||||
gettext('Yes/No per candidate');
|
||||
gettext('All valid ballots');
|
||||
gettext('All casted ballots');
|
||||
gettext('Disabled (no percents)');
|
||||
gettext('Number of ballot papers (selection)');
|
||||
gettext('Number of all delegates');
|
||||
|
@ -160,7 +160,7 @@
|
||||
<i class="fa fa-video-camera"></i>
|
||||
4. <translate>Project</translate>
|
||||
</button>
|
||||
| <a class="btn btn-danger btn-sm"
|
||||
<a class="btn btn-danger btn-sm"
|
||||
ng-bootbox-confirm="{{ 'Are you sure you want to delete this ballot?' | translate }}"
|
||||
ng-bootbox-confirm-action="deleteBallot(poll)">
|
||||
<i class="fa fa-times"></i>
|
||||
@ -178,53 +178,51 @@
|
||||
<th translate>Candidates
|
||||
<th ng-if="poll.has_votes" class="col-sm-6" translate>Votes</th>
|
||||
|
||||
<!-- candidates (poll options) -->
|
||||
<tr ng-repeat="option in poll.options">
|
||||
<!-- candidates (poll options) -->
|
||||
<tr ng-repeat="option in poll.options">
|
||||
<!-- elected -->
|
||||
<td class="minimum">
|
||||
<button os-perms="assignments.can_manage"
|
||||
ng-click="markElected(option.candidate_id, option.is_elected)" class="btn btn-default btn-xs">
|
||||
<i ng-if="option.is_elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
|
||||
<i ng-if="!option.is_elected" class="fa fa-star-o" title="{{ 'is not elected' | translate }}"></i>
|
||||
</button>
|
||||
<!-- candidate name -->
|
||||
<td>
|
||||
<a ui-sref="users.user.detail({id: option.candidate.id})">{{ option.candidate.get_full_name() }}</a>
|
||||
|
||||
<!-- elected -->
|
||||
<td class="minimum">
|
||||
<button os-perms="assignments.can_manage"
|
||||
ng-click="markElected(option.candidate_id, option.is_elected)" class="btn btn-default btn-xs">
|
||||
<i ng-if="option.is_elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
|
||||
<i ng-if="!option.is_elected" class="fa fa-star-o" title="{{ 'is not elected' | translate }}"></i>
|
||||
</button>
|
||||
|
||||
<!-- candidate name -->
|
||||
<td>
|
||||
<a ui-sref="users.user.detail({id: option.candidate.id})">{{ option.candidate.get_full_name() }}</a>
|
||||
|
||||
<!-- votes -->
|
||||
<td ng-if="poll.has_votes">
|
||||
<div ng-init="votes = option.getVotes()">
|
||||
<div ng-repeat="vote in votes">
|
||||
<span ng-if="poll.yesnoabstain || poll.yesno">{{ vote.label }}:</span>
|
||||
{{ vote.value }} {{ vote.percentStr }}
|
||||
<div ng-if="vote.percentNumber">
|
||||
<uib-progressbar value="vote.percentNumber" type="success"></uib-progressbar>
|
||||
<!-- votes -->
|
||||
<td ng-if="poll.has_votes">
|
||||
<div ng-init="votes = option.getVotes()">
|
||||
<div ng-repeat="vote in votes">
|
||||
<span ng-if="poll.pollmethod == 'yna' || poll.pollmethod == 'yn'">{{ vote.label }}:</span>
|
||||
{{ vote.value }} {{ vote.percentStr }}
|
||||
<div ng-if="vote.percentNumber">
|
||||
<uib-progressbar value="vote.percentNumber" type="success"></uib-progressbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- total votes (valid/invalid/casts) -->
|
||||
<tr>
|
||||
<td>
|
||||
<td>
|
||||
<translate>Valid votes</translate>
|
||||
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote(poll.votesvalid)">
|
||||
{{ vote.value }} {{ vote.percent }}
|
||||
<tr>
|
||||
<td>
|
||||
<td>
|
||||
<translate>Invalid votes</translate>
|
||||
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote(poll.votesinvalid)">
|
||||
{{ vote.value }}
|
||||
<tr class="total bg-info">
|
||||
<td>
|
||||
<td>
|
||||
<translate>Votes cast</translate>
|
||||
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote(poll.votescast)">
|
||||
{{ vote.value }} {{ vote.percent }}
|
||||
</table>
|
||||
<!-- total votes (valid/invalid/casts) -->
|
||||
<tr>
|
||||
<td>
|
||||
<td>
|
||||
<translate>Valid ballots</translate>
|
||||
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote('votesvalid')">
|
||||
{{ vote.value }} {{ vote.percentStr }}
|
||||
<tr>
|
||||
<td>
|
||||
<td>
|
||||
<translate>Invalid ballots</translate>
|
||||
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote('votesinvalid')">
|
||||
{{ vote.value }} {{ vote.percentStr }}
|
||||
<tr class="total bg-info">
|
||||
<td>
|
||||
<td>
|
||||
<translate>Casted ballots</translate>
|
||||
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote('votescast')">
|
||||
{{ vote.value }} {{ vote.percentStr }}
|
||||
</table>
|
||||
</div>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
|
@ -51,12 +51,12 @@
|
||||
<!-- votes -->
|
||||
<td ng-if="poll.has_votes">
|
||||
<div ng-init="votes = option.getVotes()">
|
||||
<div ng-show="poll.yesnoabstain || poll.yesno">
|
||||
<span ng-show="poll.yesnoabstain">
|
||||
<div ng-show="poll.pollmethod == 'yna' || poll.pollmethod == 'yn'">
|
||||
<span ng-show="poll.pollmethod == 'yna'">
|
||||
{{ votes[0].label }}: <strong>{{ votes[0].value }}</strong> ·
|
||||
{{ votes[1].label }}: {{ votes[1].value }} ·
|
||||
{{ votes[2].label }}: {{ votes[2].value }} </span>
|
||||
<span ng-show="poll.yesno">
|
||||
<span ng-show="poll.pollmethod == 'yn'">
|
||||
{{ votes[0].label }}: <strong>{{ votes[0].value }}</strong> ·
|
||||
{{ votes[1].label }}: {{ votes[1].value }}</span>
|
||||
<uib-progress ng-if="votes[0].percentNumber>=0">
|
||||
@ -71,7 +71,7 @@
|
||||
</uib-bar>
|
||||
</uib-progress>
|
||||
</div>
|
||||
<div ng-hide="poll.yesnoabstain || poll.yesno">
|
||||
<div ng-show="poll.pollmethod == 'votes'">
|
||||
<div ng-repeat="vote in votes">
|
||||
{{ vote.value }} {{ vote.percentStr }}
|
||||
<div ng-if="vote.percentNumber >= 0">
|
||||
@ -84,19 +84,19 @@
|
||||
<!-- total votes (valid/invalid/casts) -->
|
||||
<tr class="total">
|
||||
<td>
|
||||
<translate>Valid votes</translate>
|
||||
<translate>Valid ballots</translate>
|
||||
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote(poll.votesvalid)">
|
||||
{{ vote.value }} {{ vote.percent }}
|
||||
{{ vote.value }} {{ vote.percentStr }}
|
||||
<tr class="total">
|
||||
<td>
|
||||
<translate>Invalid votes</translate>
|
||||
<translate>Invalid ballots</translate>
|
||||
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote(poll.votesinvalid)">
|
||||
{{ vote.value }}
|
||||
{{ vote.value }} {{ vote.percentStr }}
|
||||
<tr class="total bg-info">
|
||||
<td>
|
||||
<translate>Votes cast</translate>
|
||||
<translate>Casted ballots</translate>
|
||||
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote(poll.votescast)">
|
||||
{{ vote.value }} {{ vote.percent }}
|
||||
{{ vote.value }} {{ vote.percentStr }}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -358,7 +358,7 @@ class AssignmentPDF(PDFView):
|
||||
footrow_one.append(_("Valid votes"))
|
||||
votesvalid_is_used = False
|
||||
for poll in polls:
|
||||
footrow_one.append(poll.print_votesvalid())
|
||||
footrow_one.append(poll.votesvalid)
|
||||
if poll.votesvalid is not None:
|
||||
votesvalid_is_used = True
|
||||
if votesvalid_is_used:
|
||||
@ -369,7 +369,7 @@ class AssignmentPDF(PDFView):
|
||||
footrow_two.append(_("Invalid votes"))
|
||||
votesinvalid_is_used = False
|
||||
for poll in polls:
|
||||
footrow_two.append(poll.print_votesinvalid())
|
||||
footrow_two.append(poll.votesinvalid)
|
||||
if poll.votesinvalid is not None:
|
||||
votesinvalid_is_used = True
|
||||
if votesinvalid_is_used:
|
||||
@ -380,7 +380,7 @@ class AssignmentPDF(PDFView):
|
||||
footrow_three.append(_("Votes cast"))
|
||||
votescast_is_used = False
|
||||
for poll in polls:
|
||||
footrow_three.append(poll.print_votescast())
|
||||
footrow_three.append(poll.votescast)
|
||||
if poll.votescast is not None:
|
||||
votescast_is_used = True
|
||||
if votescast_is_used:
|
||||
@ -488,7 +488,7 @@ class AssignmentPollPDF(PDFView):
|
||||
counter = 0
|
||||
cellcolumnA = []
|
||||
# Choose kind of ballot paper (YesNoAbstain, YesNo or Yes)
|
||||
if self.poll.yesnoabstain or self.poll.yesno: # YesNoAbstain/YesNo ballot: max 27 candidates
|
||||
if self.poll.pollmethod in ['yna', 'yn']: # YesNoAbstain/YesNo ballot: max 27 candidates
|
||||
for option in options:
|
||||
counter += 1
|
||||
candidate = option.candidate
|
||||
@ -498,7 +498,7 @@ class AssignmentPollPDF(PDFView):
|
||||
cell.append(Paragraph(
|
||||
"(%s)" % candidate.structure_level,
|
||||
stylesheet['Ballot_option_suffix_YNA']))
|
||||
if self.poll.yesnoabstain:
|
||||
if self.poll.pollmethod == 'yna':
|
||||
cell.append(Paragraph(
|
||||
" ", stylesheet['Ballot_option_suffix_YNA']))
|
||||
cell.append(Paragraph("<font name='circlefont' size='15'>%(circle)s</font> \
|
||||
|
@ -1,7 +1,6 @@
|
||||
from django.core.validators import MinValueValidator
|
||||
|
||||
from openslides.core.config import ConfigVariable
|
||||
from openslides.poll.models import PERCENT_BASE_CHOICES
|
||||
|
||||
from .models import Workflow
|
||||
|
||||
@ -23,10 +22,6 @@ def get_config_variables():
|
||||
papers' and 'PDF'. The generator has to be evaluated during app loading
|
||||
(see apps.py).
|
||||
"""
|
||||
PERCENT_BASE_CHOICES_MOTION = ({
|
||||
'value': "WITHOUT_ABSTAIN",
|
||||
'display_name': 'Yes and No votes'},)
|
||||
PERCENT_BASE_CHOICES_MOTION += PERCENT_BASE_CHOICES
|
||||
|
||||
# General
|
||||
yield ConfigVariable(
|
||||
@ -180,10 +175,16 @@ def get_config_variables():
|
||||
|
||||
yield ConfigVariable(
|
||||
name='motions_poll_100_percent_base',
|
||||
default_value='WITHOUT_INVALID',
|
||||
default_value='YES_NO_ABSTAIN',
|
||||
input_type='choice',
|
||||
label='The 100 % base of a voting result consists of',
|
||||
choices=PERCENT_BASE_CHOICES_MOTION,
|
||||
choices=(
|
||||
{'value': 'YES_NO_ABSTAIN', 'display_name': 'Yes/No/Abstain'},
|
||||
{'value': 'YES_NO', 'display_name': 'Yes/No'},
|
||||
{'value': 'VALID', 'display_name': 'All valid ballots'},
|
||||
{'value': 'CAST', 'display_name': 'All casted ballots'},
|
||||
{'value': 'DISABLED', 'display_name': 'Disabled (no percents)'}
|
||||
),
|
||||
weight=355,
|
||||
group='Motions',
|
||||
subgroup='Voting and ballot papers')
|
||||
|
@ -112,11 +112,11 @@ def motion_to_pdf(pdf, motion):
|
||||
yes, no, abstain = (option['Yes'], option['No'], option['Abstain'])
|
||||
valid, invalid, votescast = ('', '', '')
|
||||
if poll.votesvalid is not None:
|
||||
valid = "<br/>%s: %s" % (_("Valid votes"), poll.print_votesvalid())
|
||||
valid = "<br/>%s" % (_("Valid votes"))
|
||||
if poll.votesinvalid is not None:
|
||||
invalid = "<br/>%s: %s" % (_("Invalid votes"), poll.print_votesinvalid())
|
||||
invalid = "<br/>%s" % (_("Invalid votes"))
|
||||
if poll.votescast is not None:
|
||||
votescast = "<br/>%s: %s" % (_("Votes cast"), poll.print_votescast())
|
||||
votescast = "<br/>%s" % (_("Votes cast"))
|
||||
if len(polls) > 1:
|
||||
cell6b.append(Paragraph("%s. %s" % (ballotcounter, _("Vote")),
|
||||
stylesheet['Bold']))
|
||||
|
@ -82,31 +82,49 @@ angular.module('OpenSlidesApp.motions', [
|
||||
if (!this.has_votes) {
|
||||
return;
|
||||
}
|
||||
var impossible = false;
|
||||
var value = '';
|
||||
switch (vote) {
|
||||
case -1:
|
||||
value = gettextCatalog.getString('majority');
|
||||
impossible = true;
|
||||
break;
|
||||
case -2:
|
||||
value = gettextCatalog.getString('undocumented');
|
||||
impossible = true;
|
||||
break;
|
||||
default:
|
||||
value = vote;
|
||||
if (vote >= 0) {
|
||||
value = vote;
|
||||
} else {
|
||||
value = 0; //value was not defined
|
||||
}
|
||||
break;
|
||||
}
|
||||
// calculate percent value
|
||||
var config = Config.get('motions_poll_100_percent_base').value;
|
||||
var percentStr;
|
||||
var percentNumber = null;
|
||||
if (config == "WITHOUT_INVALID" && this.votesvalid > 0 && vote >= 0) {
|
||||
percentNumber = Math.round(vote * 100 / this.votesvalid * 10) / 10;
|
||||
} else if (config == "WITH_INVALID" && this.votescast > 0 && vote >= 0) {
|
||||
percentNumber = Math.round(vote * 100 / (this.votescast) * 10) / 10;
|
||||
} else if (config == "WITHOUT_ABSTAIN" && vote >= 0) {
|
||||
if (type == 'yes' || type == 'no') {
|
||||
percentNumber = Math.round(vote * 100 / (this.yes + this.no) * 10) / 10;
|
||||
var base = null;
|
||||
if (!impossible) {
|
||||
if (config == "YES_NO_ABSTAIN") {
|
||||
if (type == 'yes' || type == 'no' || type == 'abstain') {
|
||||
base = this.yes + this.no + this.abstain;
|
||||
}
|
||||
} else if (config == "YES_NO") {
|
||||
if (type == 'yes' || type == 'no') {
|
||||
base = this.yes + this.no;
|
||||
}
|
||||
} else if (config == "VALID" && type !== 'votescast' && type !== 'votesinvalid' &&
|
||||
this.votesvalid > 0) {
|
||||
base = this.votesvalid;
|
||||
} else if (config == "CAST" && this.votescast > 0) {
|
||||
base = this.votescast;
|
||||
}
|
||||
}
|
||||
if (base !== null) {
|
||||
percentNumber = Math.round(vote * 100 / (base) * 10) / 10;
|
||||
}
|
||||
if (percentNumber !== null) {
|
||||
percentStr = "(" + percentNumber + "%)";
|
||||
}
|
||||
|
@ -85,8 +85,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
|
||||
no = poll.no ? poll.no : '-',
|
||||
noRelative = poll.getVote(poll.no, 'no').percentStr,
|
||||
abstain = poll.abstain ? poll.abstain : '-',
|
||||
abstainrelativeGet = poll.getVote(poll.abstain, 'abstain').percentStr,
|
||||
abstainRelative = abstainrelativeGet ? abstainrelativeGet : '',
|
||||
abstainRelative = poll.getVote(poll.abstain, 'abstain').percentStr,
|
||||
valid = poll.votesvalid ? poll.votesvalid : '-',
|
||||
validRelative = poll.getVote(poll.votesvalid, 'votesvalid').percentStr,
|
||||
number = {
|
||||
@ -122,7 +121,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
|
||||
yesPart = converter.createElement("columns", createPart(gettextCatalog.getString("Yes"), yes, yesRelative)),
|
||||
noPart = converter.createElement("columns", createPart(gettextCatalog.getString("No"), no, noRelative)),
|
||||
abstainPart = converter.createElement("columns", createPart(gettextCatalog.getString("Abstain"), abstain, abstainRelative)),
|
||||
totalPart = converter.createElement("columns", createPart(gettextCatalog.getString("Valid votes"), valid, validRelative)),
|
||||
totalPart = converter.createElement("columns", createPart(gettextCatalog.getString("Valid ballots"), valid, validRelative)),
|
||||
heading = converter.createElement("columns", [number, headerText]),
|
||||
pollResult = converter.createElement("stack", [
|
||||
heading, yesPart, noPart, abstainPart, totalPart
|
||||
@ -794,7 +793,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
|
||||
key: 'votesvalid',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Valid votes'),
|
||||
label: gettextCatalog.getString('Valid ballots'),
|
||||
type: 'number'
|
||||
}
|
||||
},
|
||||
@ -802,7 +801,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
|
||||
key: 'votesinvalid',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Invalid votes'),
|
||||
label: gettextCatalog.getString('Invalid ballots'),
|
||||
type: 'number'
|
||||
}
|
||||
},
|
||||
@ -810,7 +809,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
|
||||
key: 'votescast',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Votes cast'),
|
||||
label: gettextCatalog.getString('Casted ballots'),
|
||||
type: 'number'
|
||||
}
|
||||
}];
|
||||
@ -1863,8 +1862,10 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid
|
||||
// subgroup Voting and ballot papers
|
||||
gettext('Voting and ballot papers');
|
||||
gettext('The 100 % base of a voting result consists of');
|
||||
gettext('All valid votes (Yes/No/Abstain)');
|
||||
gettext('All votes cast (including invalid votes)');
|
||||
gettext('Yes/No/Abstain');
|
||||
gettext('Yes/No');
|
||||
gettext('All valid ballots');
|
||||
gettext('All casted ballots');
|
||||
gettext('Disabled (no percents)');
|
||||
gettext('Yes and No votes');
|
||||
gettext('Number of ballot papers (selection)');
|
||||
|
@ -233,7 +233,7 @@
|
||||
<td class="icon">
|
||||
<i class="fa fa-check fa-lg"></i>
|
||||
<td ng-init="votesValid = poll.getVote(poll.votesvalid, 'votesvalid')">
|
||||
<span class="result_label"><translate>Valid votes</translate>:</span>
|
||||
<span class="result_label"><translate>Valid ballots</translate>:</span>
|
||||
<span class="result_value">
|
||||
{{ votesValid.value }} {{ votesValid.percentStr }}
|
||||
</span>
|
||||
@ -242,24 +242,18 @@
|
||||
<td class="icon">
|
||||
<i class="fa fa-ban fa-lg"></i>
|
||||
<td ng-init="votesInvalid = poll.getVote(poll.votesinvalid, 'votesinvalid')">
|
||||
<span class="result_label"><translate>Invalid votes</translate>:</span>
|
||||
<span class="result_label"><translate>Invalid ballots</translate>:</span>
|
||||
<span class="result_value">
|
||||
{{ votesInvalid.value }}
|
||||
<span ng-if="config('motions_poll_100_percent_base') == 'WITH_INVALID'">
|
||||
{{ votesInvalid.percentStr }}
|
||||
</span>
|
||||
{{ votesInvalid.value }} {{ votesInvalid.percentStr }}
|
||||
</span>
|
||||
<!-- votes cast -->
|
||||
<tr class="total" ng-if="poll.votescast !== null">
|
||||
<td class="icon">
|
||||
<strong style="font-size: 16px">∑</strong>
|
||||
<td ng-init="votesCast = poll.getVote(poll.votescast, 'votescast')">
|
||||
<span class="result_label"><translate>Votes cast</translate>:</span>
|
||||
<span class="result_label"><translate>Casted ballots</translate>:</span>
|
||||
<span class="result_value">
|
||||
{{ votesCast.value }}
|
||||
<span ng-if="config('motions_poll_100_percent_base') == 'WITH_INVALID'">
|
||||
{{ votesCast.percentStr }}
|
||||
</span>
|
||||
{{ votesCast.value }} {{ votesCast.percentStr }}
|
||||
</span>
|
||||
</table>
|
||||
</ol>
|
||||
|
@ -65,11 +65,6 @@ class BaseVote(models.Model):
|
||||
percent_base = 0
|
||||
return print_value(self.weight, percent_base)
|
||||
|
||||
PERCENT_BASE_CHOICES = (
|
||||
{'value': 'WITHOUT_INVALID', 'display_name': 'All valid votes (Yes/No/Abstain)'},
|
||||
{'value': 'WITH_INVALID', 'display_name': 'All votes cast (including invalid votes)'},
|
||||
{'value': 'DISABLED', 'display_name': 'Disabled (no percents)'})
|
||||
|
||||
|
||||
class CollectDefaultVotesMixin(models.Model):
|
||||
"""
|
||||
@ -91,40 +86,10 @@ class CollectDefaultVotesMixin(models.Model):
|
||||
|
||||
def get_percent_base_choice(self):
|
||||
"""
|
||||
Returns one of the three strings in PERCENT_BASE_CHOICES.
|
||||
Returns one of the strings of the percent base.
|
||||
"""
|
||||
raise NotImplementedError('You have to provide a get_percent_base_choice() method.')
|
||||
|
||||
def print_votesvalid(self):
|
||||
if self.get_percent_base_choice() == 'DISABLED':
|
||||
value = print_value(self.votesvalid, None)
|
||||
else:
|
||||
value = print_value(self.votesvalid, self.get_percent_base())
|
||||
return value
|
||||
|
||||
def print_votesinvalid(self):
|
||||
if self.get_percent_base_choice() == 'WITH_INVALID':
|
||||
value = print_value(self.votesinvalid, self.get_percent_base())
|
||||
else:
|
||||
value = print_value(self.votesinvalid, None)
|
||||
return value
|
||||
|
||||
def print_votescast(self):
|
||||
if self.get_percent_base_choice() == 'WITH_INVALID':
|
||||
value = print_value(self.votescast, self.get_percent_base())
|
||||
else:
|
||||
value = print_value(self.votescast, None)
|
||||
return value
|
||||
|
||||
def get_percent_base(self):
|
||||
if self.get_percent_base_choice() == "WITHOUT_INVALID" and self.votesvalid and self.votesvalid > 0:
|
||||
base = 100 / float(self.votesvalid)
|
||||
elif self.get_percent_base_choice() == "WITH_INVALID" and self.votescast and self.votescast > 0:
|
||||
base = 100 / float(self.votescast)
|
||||
else:
|
||||
base = None
|
||||
return base
|
||||
|
||||
|
||||
class PublishPollMixin(models.Model):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user