From e8fa0eca5aa0ebd2fffd934813742bf57a3a12d5 Mon Sep 17 00:00:00 2001 From: Maximilian Krambach Date: Fri, 26 Aug 2016 13:46:57 +0200 Subject: [PATCH 1/2] New calculate percentage options (fixes issue #2182) --- CHANGELOG | 2 + openslides/assignments/config_variables.py | 13 +- .../migrations/0003_auto_20160907_0946.py | 28 ++++ openslides/assignments/models.py | 27 ++-- openslides/assignments/serializers.py | 12 +- .../assignments/static/js/assignments/base.js | 127 ++++++++++++++---- .../assignments/static/js/assignments/site.js | 28 ++-- .../assignments/assignment-detail.html | 86 ++++++------ .../assignments/slide_assignment.html | 20 +-- openslides/assignments/views.py | 10 +- openslides/motions/config_variables.py | 15 ++- openslides/motions/pdf.py | 6 +- openslides/motions/static/js/motions/base.js | 34 +++-- openslides/motions/static/js/motions/site.js | 17 +-- .../templates/motions/motion-detail.html | 16 +-- openslides/poll/models.py | 37 +---- 16 files changed, 282 insertions(+), 196 deletions(-) create mode 100644 openslides/assignments/migrations/0003_auto_20160907_0946.py diff --git a/CHANGELOG b/CHANGELOG index 54ea48a31..2245ca1a7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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. diff --git a/openslides/assignments/config_variables.py b/openslides/assignments/config_variables.py index e742293d2..06f52a6d8 100644 --- a/openslides/assignments/config_variables.py +++ b/openslides/assignments/config_variables.py @@ -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') diff --git a/openslides/assignments/migrations/0003_auto_20160907_0946.py b/openslides/assignments/migrations/0003_auto_20160907_0946.py new file mode 100644 index 000000000..b45036247 --- /dev/null +++ b/openslides/assignments/migrations/0003_auto_20160907_0946.py @@ -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), + ), + ] diff --git a/openslides/assignments/models.py b/openslides/assignments/models.py index 5b1b15c65..b997c33b8 100644 --- a/openslides/assignments/models.py +++ b/openslides/assignments/models.py @@ -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'] diff --git a/openslides/assignments/serializers.py b/openslides/assignments/serializers.py index de1603e09..a61d8c8d9 100644 --- a/openslides/assignments/serializers.py +++ b/openslides/assignments/serializers.py @@ -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', diff --git a/openslides/assignments/static/js/assignments/base.js b/openslides/assignments/static/js/assignments/base.js index e3928c054..e580f0a0b 100644 --- a/openslides/assignments/static/js/assignments/base.js +++ b/openslides/assignments/static/js/assignments/base.js @@ -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: { diff --git a/openslides/assignments/static/js/assignments/site.js b/openslides/assignments/static/js/assignments/site.js index 23074e1b2..53ac5f4bd 100644 --- a/openslides/assignments/static/js/assignments/site.js +++ b/openslides/assignments/static/js/assignments/site.js @@ -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'); diff --git a/openslides/assignments/static/templates/assignments/assignment-detail.html b/openslides/assignments/static/templates/assignments/assignment-detail.html index 54dc164ac..3ecb28029 100644 --- a/openslides/assignments/static/templates/assignments/assignment-detail.html +++ b/openslides/assignments/static/templates/assignments/assignment-detail.html @@ -160,7 +160,7 @@ 4. Project - | @@ -178,53 +178,51 @@ Candidates Votes - - + + + + + + + + {{ option.candidate.get_full_name() }} - - - - - - - {{ option.candidate.get_full_name() }} - - - -
-
- {{ vote.label }}: - {{ vote.value }} {{ vote.percentStr }} -
- + + +
+
+ {{ vote.label }}: + {{ vote.value }} {{ vote.percentStr }} +
+ +
-
- - - - - Valid votes - - {{ vote.value }} {{ vote.percent }} - - - - Invalid votes - - {{ vote.value }} - - - - Votes cast - - {{ vote.value }} {{ vote.percent }} - + + + + + Valid ballots + + {{ vote.value }} {{ vote.percentStr }} + + + + Invalid ballots + + {{ vote.value }} {{ vote.percentStr }} + + + + Casted ballots + + {{ vote.value }} {{ vote.percentStr }} +
diff --git a/openslides/assignments/static/templates/assignments/slide_assignment.html b/openslides/assignments/static/templates/assignments/slide_assignment.html index e230cc283..86866e619 100644 --- a/openslides/assignments/static/templates/assignments/slide_assignment.html +++ b/openslides/assignments/static/templates/assignments/slide_assignment.html @@ -51,12 +51,12 @@
-
- +
+ {{ votes[0].label }}: {{ votes[0].value }} · {{ votes[1].label }}: {{ votes[1].value }} · {{ votes[2].label }}: {{ votes[2].value }} - + {{ votes[0].label }}: {{ votes[0].value }} · {{ votes[1].label }}: {{ votes[1].value }} @@ -71,7 +71,7 @@
-
+
{{ vote.value }} {{ vote.percentStr }}
@@ -84,19 +84,19 @@ - Valid votes + Valid ballots - {{ vote.value }} {{ vote.percent }} + {{ vote.value }} {{ vote.percentStr }} - Invalid votes + Invalid ballots - {{ vote.value }} + {{ vote.value }} {{ vote.percentStr }} - Votes cast + Casted ballots - {{ vote.value }} {{ vote.percent }} + {{ vote.value }} {{ vote.percentStr }}
diff --git a/openslides/assignments/views.py b/openslides/assignments/views.py index cc4e61932..8d0e80240 100644 --- a/openslides/assignments/views.py +++ b/openslides/assignments/views.py @@ -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("%(circle)s \ diff --git a/openslides/motions/config_variables.py b/openslides/motions/config_variables.py index bc7590f67..c8752dac8 100644 --- a/openslides/motions/config_variables.py +++ b/openslides/motions/config_variables.py @@ -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') diff --git a/openslides/motions/pdf.py b/openslides/motions/pdf.py index fbbf224c3..8d101d6d3 100644 --- a/openslides/motions/pdf.py +++ b/openslides/motions/pdf.py @@ -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 = "
%s: %s" % (_("Valid votes"), poll.print_votesvalid()) + valid = "
%s" % (_("Valid votes")) if poll.votesinvalid is not None: - invalid = "
%s: %s" % (_("Invalid votes"), poll.print_votesinvalid()) + invalid = "
%s" % (_("Invalid votes")) if poll.votescast is not None: - votescast = "
%s: %s" % (_("Votes cast"), poll.print_votescast()) + votescast = "
%s" % (_("Votes cast")) if len(polls) > 1: cell6b.append(Paragraph("%s. %s" % (ballotcounter, _("Vote")), stylesheet['Bold'])) diff --git a/openslides/motions/static/js/motions/base.js b/openslides/motions/static/js/motions/base.js index a93f73be6..ce0a24e36 100644 --- a/openslides/motions/static/js/motions/base.js +++ b/openslides/motions/static/js/motions/base.js @@ -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 + "%)"; } diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index c9c88cff1..65764c88b 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -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)'); diff --git a/openslides/motions/static/templates/motions/motion-detail.html b/openslides/motions/static/templates/motions/motion-detail.html index fcbcec778..a6d498a52 100644 --- a/openslides/motions/static/templates/motions/motion-detail.html +++ b/openslides/motions/static/templates/motions/motion-detail.html @@ -233,7 +233,7 @@ - Valid votes: + Valid ballots: {{ votesValid.value }} {{ votesValid.percentStr }} @@ -242,24 +242,18 @@ - Invalid votes: + Invalid ballots: - {{ votesInvalid.value }} - - {{ votesInvalid.percentStr }} - + {{ votesInvalid.value }} {{ votesInvalid.percentStr }} - Votes cast: + Casted ballots: - {{ votesCast.value }} - - {{ votesCast.percentStr }} - + {{ votesCast.value }} {{ votesCast.percentStr }} diff --git a/openslides/poll/models.py b/openslides/poll/models.py index d0891a675..a450ae790 100644 --- a/openslides/poll/models.py +++ b/openslides/poll/models.py @@ -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): """ From 256a8d920316e11af3f1330a9ae1c747c8eb02e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Fri, 23 Sep 2016 15:41:50 +0200 Subject: [PATCH 2/2] Small style changes. --- openslides/assignments/config_variables.py | 2 +- openslides/assignments/static/js/assignments/site.js | 6 +++--- openslides/motions/static/js/motions/site.js | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openslides/assignments/config_variables.py b/openslides/assignments/config_variables.py index 06f52a6d8..a22566743 100644 --- a/openslides/assignments/config_variables.py +++ b/openslides/assignments/config_variables.py @@ -37,7 +37,7 @@ def get_config_variables(): {'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 " + + "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', diff --git a/openslides/assignments/static/js/assignments/site.js b/openslides/assignments/static/js/assignments/site.js index 53ac5f4bd..c99a0a28f 100644 --- a/openslides/assignments/static/js/assignments/site.js +++ b/openslides/assignments/static/js/assignments/site.js @@ -765,9 +765,9 @@ 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('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('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 %.'); gettext('Yes/No/Abstain per candidate'); gettext('Yes/No per candidate'); gettext('All valid ballots'); diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index 65764c88b..baaa136f4 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -1867,7 +1867,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid gettext('All valid ballots'); gettext('All casted ballots'); gettext('Disabled (no percents)'); - gettext('Yes and No votes'); gettext('Number of ballot papers (selection)'); gettext('Number of all delegates'); gettext('Number of all participants');