From 07ffb3b6c57431891a3fa0b0bd59ecc1aa9b9ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Mon, 20 Aug 2018 21:01:30 +0200 Subject: [PATCH 01/11] Fixed settings creation for windows. --- openslides/utils/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openslides/utils/main.py b/openslides/utils/main.py index 6d20240f9..efa575d13 100644 --- a/openslides/utils/main.py +++ b/openslides/utils/main.py @@ -197,7 +197,7 @@ def get_win32_app_data_dir() -> str: # TODO: Write other exception raise Exception("Could not determine Windows' APPDATA path") - return buf.value.decode('utf-8') + return buf.value # type: ignore def get_win32_portable_dir() -> str: From 27e0564a23cf37c1214f1f017844b904e4be4d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Wed, 22 Aug 2018 17:00:08 +0200 Subject: [PATCH 02/11] Changed default agenda type for new topics. --- openslides/agenda/access_permissions.py | 2 +- openslides/agenda/config_variables.py | 2 +- openslides/agenda/static/js/agenda/site.js | 2 +- openslides/topics/static/js/topics/site.js | 3 ++- openslides/users/static/js/users/site.js | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openslides/agenda/access_permissions.py b/openslides/agenda/access_permissions.py index 89a47a3b7..26689b8e6 100644 --- a/openslides/agenda/access_permissions.py +++ b/openslides/agenda/access_permissions.py @@ -83,7 +83,7 @@ class ItemAccessPermissions(BaseAccessPermissions): if full['is_hidden'] and can_see_hidden: # Same filtering for internal and hidden items data.append(filtered_data(full, blocked_keys_internal_hidden_case)) - if full['is_internal']: + elif full['is_internal']: data.append(filtered_data(full, blocked_keys_internal_hidden_case)) else: # agenda item data.append(filtered_data(full, blocked_keys_non_internal_hidden_case)) diff --git a/openslides/agenda/config_variables.py b/openslides/agenda/config_variables.py index e47cf9c78..d52db6f08 100644 --- a/openslides/agenda/config_variables.py +++ b/openslides/agenda/config_variables.py @@ -67,7 +67,7 @@ def get_config_variables(): {'value': '1', 'display_name': 'Public item'}, {'value': '2', 'display_name': 'Internal item'}, {'value': '3', 'display_name': 'Hidden item'}), - label='Default visibility for new agenda items', + label='Default visibility for new agenda items (except topics)', weight=227, group='Agenda', subgroup='General') diff --git a/openslides/agenda/static/js/agenda/site.js b/openslides/agenda/static/js/agenda/site.js index b5d41755d..2735aabdf 100644 --- a/openslides/agenda/static/js/agenda/site.js +++ b/openslides/agenda/static/js/agenda/site.js @@ -883,7 +883,7 @@ angular.module('OpenSlidesApp.agenda.site', [ gettext('[Begin speech] starts the countdown, [End speech] stops the ' + 'countdown.'); gettext('Agenda visibility'); - gettext('Default visibility for new agenda items'); + gettext('Default visibility for new agenda items (except topics)'); } ]); diff --git a/openslides/topics/static/js/topics/site.js b/openslides/topics/static/js/topics/site.js index 5c70fb084..627bee0a7 100644 --- a/openslides/topics/static/js/topics/site.js +++ b/openslides/topics/static/js/topics/site.js @@ -185,7 +185,8 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides 'ErrorMessage', function($scope, $state, Topic, TopicForm, Agenda, Config, ErrorMessage) { $scope.model = { - agenda_type: parseInt(Config.get('agenda_new_items_default_visibility').value), + agenda_type: 1, // Default is a public item. The config field + // 'agenda_new_items_default_visibility' is not used. }; // get all form fields $scope.formFields = TopicForm.getFormFields(true); diff --git a/openslides/users/static/js/users/site.js b/openslides/users/static/js/users/site.js index 6f158b7a6..e9a3df953 100644 --- a/openslides/users/static/js/users/site.js +++ b/openslides/users/static/js/users/site.js @@ -1810,7 +1810,7 @@ angular.module('OpenSlidesApp.users.site', [ gettext('Can see agenda'); gettext('Can manage agenda'); gettext('Can manage list of speakers'); - gettext('Can see hidden items and time scheduling of agenda'); + gettext('Can see internal items and time scheduling of agenda'); gettext('Can put oneself on the list of speakers'); // assignments gettext('Can see elections'); From 9bac396b676f1ff43f113e535902f76d17453ddf Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Wed, 22 Aug 2018 17:34:16 +0200 Subject: [PATCH 03/11] Support for decimal places in motion and assignment polls --- CHANGELOG.rst | 4 + .../migrations/0005_auto_20180822_1042.py | 75 +++++++++++++++++++ openslides/assignments/models.py | 10 ++- openslides/assignments/serializers.py | 3 +- .../assignments/static/js/assignments/base.js | 27 +++++++ .../assignments/static/js/assignments/pdf.js | 14 ++-- .../static/js/assignments/projector.js | 14 +++- .../assignments/static/js/assignments/site.js | 22 +++++- .../assignments/assignment-detail.html | 16 ++-- .../assignments/slide_assignment.html | 22 +++--- .../migrations/0010_auto_20180822_1042.py | 55 ++++++++++++++ openslides/motions/serializers.py | 11 +-- openslides/motions/static/js/motions/base.js | 20 +++++ openslides/motions/static/js/motions/pdf.js | 16 ++-- .../motions/static/js/motions/projector.js | 17 ++++- openslides/motions/static/js/motions/site.js | 22 ++++-- .../templates/motions/motion-detail.html | 16 ++-- .../templates/motions/slide_motion.html | 6 +- openslides/poll/models.py | 16 ++-- openslides/utils/rest_api.py | 1 + 20 files changed, 320 insertions(+), 67 deletions(-) create mode 100644 openslides/assignments/migrations/0005_auto_20180822_1042.py create mode 100644 openslides/motions/migrations/0010_auto_20180822_1042.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ec57c0864..627007c1e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,9 @@ Version 2.3 (unreleased) Agenda: - New item type 'hidden'. New visibilty filter in agenda [#3790]. +Elections: + - Support to change decimal places for elections with a plugin [#3803] + Motions: - New feature to scroll the projector to a specific line [#3748]. - New possibility to sort submitters [#3647]. @@ -21,6 +24,7 @@ Motions: - New teporal field "modified final version" where the final version can be edited [#3781]. - New config to show amendments also in motions table [#3792] + - Support to change decimal places for polls with a plugin [#3803] Core: - Python 3.4 is not supported anymore [#3777]. diff --git a/openslides/assignments/migrations/0005_auto_20180822_1042.py b/openslides/assignments/migrations/0005_auto_20180822_1042.py new file mode 100644 index 000000000..c53bf1b82 --- /dev/null +++ b/openslides/assignments/migrations/0005_auto_20180822_1042.py @@ -0,0 +1,75 @@ +# Generated by Django 2.1 on 2018-08-22 08:42 + +from decimal import Decimal +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assignments', '0004_auto_20180703_1523'), + ] + + operations = [ + migrations.AlterField( + model_name='assignmentpoll', + name='votescast', + field=models.DecimalField( + blank=True, + decimal_places=6, + max_digits=15, + null=True, + validators=[django.core.validators.MinValueValidator(Decimal('-2'))]), + ), + migrations.AlterField( + model_name='assignmentpoll', + name='votesinvalid', + field=models.DecimalField( + blank=True, + decimal_places=6, + max_digits=15, + null=True, + validators=[django.core.validators.MinValueValidator(Decimal('-2'))]), + ), + migrations.AlterField( + model_name='assignmentpoll', + name='votesvalid', + field=models.DecimalField( + blank=True, + decimal_places=6, + max_digits=15, + null=True, + validators=[django.core.validators.MinValueValidator(Decimal('-2'))]), + ), + migrations.AlterField( + model_name='assignmentvote', + name='weight', + field=models.DecimalField( + decimal_places=6, + default=Decimal('1'), + max_digits=15, + null=True, + validators=[django.core.validators.MinValueValidator(Decimal('-2'))]), + ), + migrations.AlterField( + model_name='assignmentpoll', + name='votesabstain', + field=models.DecimalField( + blank=True, + decimal_places=6, + max_digits=15, + null=True, + validators=[django.core.validators.MinValueValidator(Decimal('-2'))]), + ), + migrations.AlterField( + model_name='assignmentpoll', + name='votesno', + field=models.DecimalField( + blank=True, + decimal_places=6, + max_digits=15, + null=True, + validators=[django.core.validators.MinValueValidator(Decimal('-2'))]), + ), + ] diff --git a/openslides/assignments/models.py b/openslides/assignments/models.py index d6bc5cf7c..54c491dfd 100644 --- a/openslides/assignments/models.py +++ b/openslides/assignments/models.py @@ -1,8 +1,10 @@ from collections import OrderedDict +from decimal import Decimal from typing import Any, Dict, List, Optional # noqa from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation +from django.core.validators import MinValueValidator from django.db import models from django.utils.translation import ugettext as _ from django.utils.translation import ugettext_noop @@ -19,7 +21,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 MinMaxIntegerField, RESTModelMixin +from openslides.utils.models import RESTModelMixin from .access_permissions import AssignmentAccessPermissions @@ -423,9 +425,11 @@ class AssignmentPoll(RESTModelMixin, CollectDefaultVotesMixin, # type: ignore max_length=79, blank=True) - votesabstain = MinMaxIntegerField(null=True, blank=True, min_value=-2) + votesabstain = models.DecimalField(null=True, blank=True, validators=[ + MinValueValidator(Decimal('-2'))], max_digits=15, decimal_places=6) """ General abstain votes, used for pollmethod 'votes' """ - votesno = MinMaxIntegerField(null=True, blank=True, min_value=-2) + votesno = models.DecimalField(null=True, blank=True, validators=[ + MinValueValidator(Decimal('-2'))], max_digits=15, decimal_places=6) """ General no votes, used for pollmethod 'votes' """ class Meta: diff --git a/openslides/assignments/serializers.py b/openslides/assignments/serializers.py index 845ca4e1c..438fd46f8 100644 --- a/openslides/assignments/serializers.py +++ b/openslides/assignments/serializers.py @@ -3,6 +3,7 @@ from django.utils.translation import ugettext as _ from openslides.poll.serializers import default_votes_validator from openslides.utils.rest_api import ( + DecimalField, DictField, IntegerField, ListField, @@ -98,7 +99,7 @@ class AssignmentAllPollSerializer(ModelSerializer): options = AssignmentOptionSerializer(many=True, read_only=True) votes = ListField( child=DictField( - child=IntegerField(min_value=-2)), + child=DecimalField(max_digits=15, decimal_places=6, min_value=-2)), write_only=True, required=False) has_votes = SerializerMethodField() diff --git a/openslides/assignments/static/js/assignments/base.js b/openslides/assignments/static/js/assignments/base.js index a4f64ac67..c971c99a2 100644 --- a/openslides/assignments/static/js/assignments/base.js +++ b/openslides/assignments/static/js/assignments/base.js @@ -14,6 +14,12 @@ angular.module('OpenSlidesApp.assignments', []) return DS.defineResource({ name: 'assignments/polloption', useClass: jsDataModel, + // Change the stringified numbers to floats. + beforeInject: function (resource, instance) { + _.forEach(instance.votes, function (vote) { + vote.weight = parseFloat(vote.weight); + }); + }, methods: { getVotes: function () { if (!this.poll.has_votes) { @@ -154,6 +160,15 @@ angular.module('OpenSlidesApp.assignments', []) return DS.defineResource({ name: name, useClass: jsDataModel, + // Change the stringified numbers to floats. + beforeInject: function (resource, instance) { + var attrs = ['votescast', 'votesinvalid', 'votesvalid', 'votesabstain', 'votesno']; + _.forEach(attrs, function (attr) { + if (instance[attr] !== null) { + instance[attr] = parseFloat(instance[attr]); + } + }); + }, methods: { getResourceName: function () { return name; @@ -307,6 +322,18 @@ angular.module('OpenSlidesApp.assignments', []) } ]) +.provider('AssignmentPollDecimalPlaces', [ + function () { + this.$get = [function () { + return { + getPlaces: function (poll, find) { + return 0; + }, + }; + }]; + } +]) + .factory('AssignmentRelatedUser', [ 'DS', function (DS) { diff --git a/openslides/assignments/static/js/assignments/pdf.js b/openslides/assignments/static/js/assignments/pdf.js index c0f6f0ac7..bbd4399dc 100644 --- a/openslides/assignments/static/js/assignments/pdf.js +++ b/openslides/assignments/static/js/assignments/pdf.js @@ -9,7 +9,8 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf']) 'HTMLValidizer', 'gettextCatalog', 'PDFLayout', - function($filter, HTMLValidizer, gettextCatalog, PDFLayout) { + 'AssignmentPollDecimalPlaces', + function($filter, HTMLValidizer, gettextCatalog, PDFLayout, AssignmentPollDecimalPlaces) { var createInstance = function(assignment) { @@ -113,13 +114,13 @@ 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 parseVoteValue = function(voteObject, printLabel, precision) { var voteVal = ''; if (voteObject) { if (printLabel) { voteVal += voteObject.label + ': '; } - voteVal += voteObject.value; + voteVal += $filter('number')(voteObject.value, precision); if (voteObject.percentStr) { voteVal += ' ' + voteObject.percentStr; @@ -135,6 +136,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf']) _.forEach(assignment.polls, function(poll, pollIndex) { if (poll.published) { var pollTableBody = []; + var precision = AssignmentPollDecimalPlaces.getPlaces(poll); resultBody.push({ text: gettextCatalog.getString('Ballot') + ' ' + (pollIndex+1), @@ -163,14 +165,14 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf']) if (poll.pollmethod == 'votes') { tableLine.push( { - text: parseVoteValue(votes[0], false), + text: parseVoteValue(votes[0], false, precision), style: PDFLayout.flipTableRowStyle(pollTableBody.length) } ); } else { var resultBlock = []; _.forEach(votes, function(vote) { - resultBlock.push(parseVoteValue(vote, true)); + resultBlock.push(parseVoteValue(vote, true, precision)); }); tableLine.push({ text: resultBlock, @@ -189,7 +191,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf']) style: 'tableConclude' }, { - text: parseVoteValue(poll.getVote(fieldName), false), + text: parseVoteValue(poll.getVote(fieldName), false, precision), style: 'tableConclude' }, ]); diff --git a/openslides/assignments/static/js/assignments/projector.js b/openslides/assignments/static/js/assignments/projector.js index 0e25e8155..ac27a8c21 100644 --- a/openslides/assignments/static/js/assignments/projector.js +++ b/openslides/assignments/static/js/assignments/projector.js @@ -16,15 +16,27 @@ angular.module('OpenSlidesApp.assignments.projector', ['OpenSlidesApp.assignment .controller('SlideAssignmentCtrl', [ '$scope', 'Assignment', + 'AssignmentPoll', 'AssignmentPhases', + 'AssignmentPollDecimalPlaces', 'User', - function($scope, Assignment, AssignmentPhases, User) { + function($scope, Assignment, AssignmentPoll, AssignmentPhases, AssignmentPollDecimalPlaces, User) { // Attention! Each object that is used here has to be dealt on server side. // Add it to the coresponding get_requirements method of the ProjectorElement // class. var id = $scope.element.id; $scope.showResult = $scope.element.poll; + if ($scope.showResult) { + var poll = AssignmentPoll.get($scope.showResult); + $scope.votesPrecision = 0; + if (poll) { + AssignmentPollDecimalPlaces.getPlaces(poll, true).then(function (decimalPlaces) { + $scope.votesPrecision = decimalPlaces; + }); + } + } + Assignment.bindOne(id, $scope, 'assignment'); $scope.phases = AssignmentPhases; User.bindAll({}, $scope, 'users'); diff --git a/openslides/assignments/static/js/assignments/site.js b/openslides/assignments/static/js/assignments/site.js index 7ab0a8511..155a4725d 100644 --- a/openslides/assignments/static/js/assignments/site.js +++ b/openslides/assignments/static/js/assignments/site.js @@ -218,11 +218,15 @@ angular.module('OpenSlidesApp.assignments.site', [ 'Config', 'AssignmentPollDetailCtrlCache', 'AssignmentPoll', - function ($scope, MajorityMethodChoices, Config, AssignmentPollDetailCtrlCache, AssignmentPoll) { + 'AssignmentPollDecimalPlaces', + function ($scope, MajorityMethodChoices, Config, AssignmentPollDetailCtrlCache, + AssignmentPoll, AssignmentPollDecimalPlaces) { // Define choices. $scope.methodChoices = MajorityMethodChoices; // TODO: Get $scope.baseChoices from config_variables.py without copying them. + $scope.votesPrecision = AssignmentPollDecimalPlaces.getPlaces($scope.poll); + // Setup empty cache with default values. if (typeof AssignmentPollDetailCtrlCache[$scope.poll.id] === 'undefined') { AssignmentPollDetailCtrlCache[$scope.poll.id] = { @@ -689,9 +693,11 @@ angular.module('OpenSlidesApp.assignments.site', [ 'gettextCatalog', 'AssignmentPoll', 'assignmentpollId', + 'AssignmentPollDecimalPlaces', 'ballot', 'ErrorMessage', - function($scope, $filter, gettextCatalog, AssignmentPoll, assignmentpollId, ballot, ErrorMessage) { + function($scope, $filter, gettextCatalog, AssignmentPoll, assignmentpollId, + AssignmentPollDecimalPlaces, ballot, ErrorMessage) { // set initial values for form model by create deep copy of assignmentpoll object // so detail view is not updated while editing poll var assignmentpoll = angular.copy(AssignmentPoll.get(assignmentpollId)); @@ -700,6 +706,9 @@ angular.module('OpenSlidesApp.assignments.site', [ $scope.formFields = []; $scope.alert = {}; + // For number inputs + var step = Math.pow(10, -AssignmentPollDecimalPlaces.getPlaces(assignmentpoll)); + // add dynamic form fields var options = $filter('orderBy')(assignmentpoll.options, 'weight'); _.forEach(options, function(option) { @@ -720,6 +729,7 @@ angular.module('OpenSlidesApp.assignments.site', [ label: gettextCatalog.getString('Yes'), type: 'number', min: -2, + step: step, required: true }, defaultValue: defaultValue.yes @@ -733,6 +743,7 @@ angular.module('OpenSlidesApp.assignments.site', [ label: gettextCatalog.getString('No'), type: 'number', min: -2, + step: step, required: true }, defaultValue: defaultValue.no @@ -747,6 +758,7 @@ angular.module('OpenSlidesApp.assignments.site', [ label: gettextCatalog.getString('Abstain'), type: 'number', min: -2, + step: step, required: true }, defaultValue: defaultValue.abstain @@ -771,6 +783,7 @@ angular.module('OpenSlidesApp.assignments.site', [ label: option.candidate.get_full_name(), type: 'number', min: -2, + step: step, required: true, }, defaultValue: defaultValue @@ -785,6 +798,7 @@ angular.module('OpenSlidesApp.assignments.site', [ templateOptions: { label: gettextCatalog.getString('Abstain'), type: 'number', + step: step, min: -2, } }, @@ -794,6 +808,7 @@ angular.module('OpenSlidesApp.assignments.site', [ templateOptions: { label: gettextCatalog.getString('No'), type: 'number', + step: step, min: -2, } } @@ -810,6 +825,7 @@ angular.module('OpenSlidesApp.assignments.site', [ templateOptions: { label: gettextCatalog.getString('Valid ballots'), type: 'number', + step: step, min: -2, } }, @@ -819,6 +835,7 @@ angular.module('OpenSlidesApp.assignments.site', [ templateOptions: { label: gettextCatalog.getString('Invalid ballots'), type: 'number', + step: step, min: -2, } }, @@ -828,6 +845,7 @@ angular.module('OpenSlidesApp.assignments.site', [ templateOptions: { label: gettextCatalog.getString('Casted ballots'), type: 'number', + step: step, min: -2, } } diff --git a/openslides/assignments/static/templates/assignments/assignment-detail.html b/openslides/assignments/static/templates/assignments/assignment-detail.html index 098240882..d585d345e 100644 --- a/openslides/assignments/static/templates/assignments/assignment-detail.html +++ b/openslides/assignments/static/templates/assignments/assignment-detail.html @@ -234,7 +234,7 @@
{{ vote.label }}: - {{ vote.value }} {{ vote.percentStr }} + {{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
@@ -244,10 +244,10 @@
- Quorum ({{ option.getVoteYes() - option.majorityReached }}) reached. + Quorum ({{ (option.getVoteYes() - option.majorityReached) | number:votesPrecision }}) reached. - Quorum ({{ option.getVoteYes() - option.majorityReached }}) not reached. + Quorum ({{ (option.getVoteYes() - option.majorityReached) | number:votesPrecision }}) not reached. @@ -255,31 +255,31 @@ Abstain - {{ poll.getVote('votesabstain').value }} + {{ poll.getVote('votesabstain').value | number:votesPrecision }} {{ poll.getVote('votesabstain').percentStr }} No - {{ poll.getVote('votesno').value }} + {{ poll.getVote('votesno').value | number:votesPrecision }} {{ poll.getVote('votesno').percentStr }} Valid ballots - {{ poll.getVote('votesvalid').value }} + {{ poll.getVote('votesvalid').value | number:votesPrecision }} {{ poll.getVote('votesvalid').percentStr }} Invalid ballots - {{ poll.getVote('votesinvalid').value }} + {{ poll.getVote('votesinvalid').value | number:votesPrecision }} {{ poll.getVote('votesinvalid').percentStr }} Casted ballots - {{ poll.getVote('votescast').value }} + {{ poll.getVote('votescast').value | number:votesPrecision }} {{ poll.getVote('votescast').percentStr }} diff --git a/openslides/assignments/static/templates/assignments/slide_assignment.html b/openslides/assignments/static/templates/assignments/slide_assignment.html index 708bc7339..e3954e0e0 100644 --- a/openslides/assignments/static/templates/assignments/slide_assignment.html +++ b/openslides/assignments/static/templates/assignments/slide_assignment.html @@ -49,16 +49,16 @@
- {{ votes[0].label | translate }}: {{ votes[0].value }} {{ votes[0].percentStr }}
- {{ votes[1].label | translate }}: {{ votes[1].value }} {{ votes[1].percentStr }}
- {{ votes[2].label | translate }}: {{ votes[2].value }} {{ votes[2].percentStr }}
+ {{ votes[0].label | translate }}: {{ votes[0].value | number:votesPrecision }} {{ votes[0].percentStr }}
+ {{ votes[1].label | translate }}: {{ votes[1].value | number:votesPrecision }} {{ votes[1].percentStr }}
+ {{ votes[2].label | translate }}: {{ votes[2].value | number:votesPrecision }} {{ votes[2].percentStr }} - {{ votes[0].label | translate }}: {{ votes[0].value }} {{ votes[0].percentStr }}
- {{ votes[1].label | translate }}: {{ votes[1].value }} {{ votes[1].percentStr }}
+ {{ votes[0].label | translate }}: {{ votes[0].value | number:votesPrecision }} {{ votes[0].percentStr }}
+ {{ votes[1].label | translate }}: {{ votes[1].value | number:votesPrecision }} {{ votes[1].percentStr }}
- {{ vote.value }} {{ vote.percentStr }} + {{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
@@ -68,29 +68,29 @@ Abstain - {{ vote.value }} {{ vote.percentStr }} + {{ vote.value | number:votesPrecision }} {{ vote.percentStr }} No - {{ vote.value }} {{ vote.percentStr }} + {{ vote.value | number:votesPrecision }} {{ vote.percentStr }} Valid ballots - {{ vote.value }} {{ vote.percentStr }} + {{ vote.value | number:votesPrecision }} {{ vote.percentStr }} Invalid ballots - {{ vote.value }} {{ vote.percentStr }} + {{ vote.value | number:votesPrecision }} {{ vote.percentStr }} Casted ballots - {{ vote.value }} {{ vote.percentStr }} + {{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
diff --git a/openslides/motions/migrations/0010_auto_20180822_1042.py b/openslides/motions/migrations/0010_auto_20180822_1042.py new file mode 100644 index 000000000..fa9fb0522 --- /dev/null +++ b/openslides/motions/migrations/0010_auto_20180822_1042.py @@ -0,0 +1,55 @@ +# Generated by Django 2.1 on 2018-08-22 08:42 + +from decimal import Decimal +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('motions', '0009_motionversion_modified_final_version'), + ] + + operations = [ + migrations.AlterField( + model_name='motionpoll', + name='votescast', + field=models.DecimalField( + blank=True, + decimal_places=6, + max_digits=15, + null=True, + validators=[django.core.validators.MinValueValidator(Decimal('-2'))]), + ), + migrations.AlterField( + model_name='motionpoll', + name='votesinvalid', + field=models.DecimalField( + blank=True, + decimal_places=6, + max_digits=15, + null=True, + validators=[django.core.validators.MinValueValidator(Decimal('-2'))]), + ), + migrations.AlterField( + model_name='motionpoll', + name='votesvalid', + field=models.DecimalField( + blank=True, + decimal_places=6, + max_digits=15, + null=True, + validators=[django.core.validators.MinValueValidator(Decimal('-2'))]), + ), + migrations.AlterField( + model_name='motionvote', + name='weight', + field=models.DecimalField( + decimal_places=6, + default=Decimal('1'), + max_digits=15, + null=True, + validators=[django.core.validators.MinValueValidator(Decimal('-2'))]), + ), + ] diff --git a/openslides/motions/serializers.py b/openslides/motions/serializers.py index 6f2791bc2..a5979f0b5 100644 --- a/openslides/motions/serializers.py +++ b/openslides/motions/serializers.py @@ -1,4 +1,4 @@ -from typing import Dict # noqa +from typing import Dict, Optional # noqa from django.db import transaction from django.utils.translation import ugettext as _ @@ -6,6 +6,7 @@ from django.utils.translation import ugettext as _ from ..poll.serializers import default_votes_validator from ..utils.rest_api import ( CharField, + DecimalField, DictField, Field, IntegerField, @@ -212,7 +213,7 @@ class MotionPollSerializer(ModelSerializer): no = SerializerMethodField() abstain = SerializerMethodField() votes = DictField( - child=IntegerField(min_value=-2, allow_null=True), + child=DecimalField(max_digits=15, decimal_places=6, min_value=-2, allow_null=True), write_only=True) has_votes = SerializerMethodField() @@ -238,21 +239,21 @@ class MotionPollSerializer(ModelSerializer): def get_yes(self, obj): try: - result = self.get_votes_dict(obj)['Yes'] + result = str(self.get_votes_dict(obj)['Yes']) # type: Optional[str] except KeyError: result = None return result def get_no(self, obj): try: - result = self.get_votes_dict(obj)['No'] + result = str(self.get_votes_dict(obj)['No']) # type: Optional[str] except KeyError: result = None return result def get_abstain(self, obj): try: - result = self.get_votes_dict(obj)['Abstain'] + result = str(self.get_votes_dict(obj)['Abstain']) # type: Optional[str] except KeyError: result = None return result diff --git a/openslides/motions/static/js/motions/base.js b/openslides/motions/static/js/motions/base.js index d2741bb4c..7296b3fc3 100644 --- a/openslides/motions/static/js/motions/base.js +++ b/openslides/motions/static/js/motions/base.js @@ -85,6 +85,14 @@ angular.module('OpenSlidesApp.motions', [ } } }, + beforeInject: function (resource, instance) { + var attrs = ['yes', 'no', 'abstain', 'votescast', 'votesinvalid', 'votesvalid']; + _.forEach(attrs, function (attr) { + if (instance[attr] !== null) { + instance[attr] = parseFloat(instance[attr]); + } + }); + }, methods: { // Returns percent base. Returns undefined if calculation is not possible in general. getPercentBase: function (config, type) { @@ -196,6 +204,18 @@ angular.module('OpenSlidesApp.motions', [ } ]) +.provider('MotionPollDecimalPlaces', [ + function () { + this.$get = [function () { + return { + getPlaces: function (poll, find) { + return 0; + }, + }; + }]; + } +]) + .factory('MotionStateAndRecommendationParser', [ 'DS', 'gettextCatalog', diff --git a/openslides/motions/static/js/motions/pdf.js b/openslides/motions/static/js/motions/pdf.js index f7803f3bc..070918641 100644 --- a/openslides/motions/static/js/motions/pdf.js +++ b/openslides/motions/static/js/motions/pdf.js @@ -17,9 +17,10 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) 'Config', 'Motion', 'MotionComment', + 'MotionPollDecimalPlaces', 'OpenSlidesSettings', function($q, $filter, operator, gettextCatalog, PDFLayout, PdfMakeConverter, ImageConverter, - HTMLValidizer, Category, Config, Motion, MotionComment, OpenSlidesSettings) { + HTMLValidizer, Category, Config, Motion, MotionComment, MotionPollDecimalPlaces, OpenSlidesSettings) { /** * Provides the content as JS objects for Motions in pdfMake context * @constructor @@ -185,40 +186,41 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) column2.push(''); column3.push(''); } + var precision = MotionPollDecimalPlaces.getPlaces(poll); // yes var yes = poll.getVote(poll.yes, 'yes'); column1.push(gettextCatalog.getString('Yes') + ':'); - column2.push(yes.value); + column2.push($filter('number')(yes.value, precision)); column3.push(yes.percentStr); // no var no = poll.getVote(poll.no, 'no'); column1.push(gettextCatalog.getString('No') + ':'); - column2.push(no.value); + column2.push($filter('number')(no.value, precision)); column3.push(no.percentStr); // abstain var abstain = poll.getVote(poll.abstain, 'abstain'); column1.push(gettextCatalog.getString('Abstain') + ':'); - column2.push(abstain.value); + column2.push($filter('number')(abstain.value, precision)); column3.push(abstain.percentStr); // votes valid if (poll.votesvalid) { var valid = poll.getVote(poll.votesvalid, 'votesvalid'); column1.push(gettextCatalog.getString('Valid votes') + ':'); - column2.push(valid.value); + column2.push($filter('number')(valid.value, precision)); column3.push(valid.percentStr); } // votes invalid if (poll.votesvalid) { var invalid = poll.getVote(poll.votesinvalid, 'votesinvalid'); column1.push(gettextCatalog.getString('Invalid votes') + ':'); - column2.push(invalid.value); + column2.push($filter('number')(invalid.value, precision)); column3.push(invalid.percentStr); } // votes cast if (poll.votescast) { var cast = poll.getVote(poll.votescast, 'votescast'); column1.push(gettextCatalog.getString('Votes cast') + ':'); - column2.push(cast.value); + column2.push($filter('number')(cast.value, precision)); column3.push(cast.percentStr); } } diff --git a/openslides/motions/static/js/motions/projector.js b/openslides/motions/static/js/motions/projector.js index 4b2854f30..d7107eded 100644 --- a/openslides/motions/static/js/motions/projector.js +++ b/openslides/motions/static/js/motions/projector.js @@ -26,7 +26,9 @@ angular.module('OpenSlidesApp.motions.projector', [ 'User', 'Notify', 'ProjectorID', - function($scope, Config, Motion, MotionChangeRecommendation, ChangeRecommendationView, User, Notify, ProjectorID) { + 'MotionPollDecimalPlaces', + function($scope, Config, Motion, MotionChangeRecommendation, ChangeRecommendationView, User, + Notify, ProjectorID, MotionPollDecimalPlaces) { // Attention! Each object that is used here has to be dealt on server side. // Add it to the coresponding get_requirements method of the ProjectorElement // class. @@ -67,8 +69,21 @@ angular.module('OpenSlidesApp.motions.projector', [ $scope.motion = Motion.get(motionId); $scope.amendment_diff_paragraphs = $scope.motion.getAmendmentParagraphsLinesDiff(); $scope.viewChangeRecommendations.setVersion($scope.motion, $scope.motion.active_version); + _.forEach($scope.motion.polls, function (poll) { + MotionPollDecimalPlaces.getPlaces(poll, true).then(function (decimalPlaces) { + precisionCache[poll.id] = decimalPlaces; + }); + }); }); + var precisionCache = {}; + $scope.getPollVotesPrecision = function (poll) { + if (!precisionCache[poll.id]) { + return 0; + } + return precisionCache[poll.id]; + }; + // Change recommendation viewing $scope.viewChangeRecommendations = ChangeRecommendationView; $scope.viewChangeRecommendations.initProjector($scope, Motion.get(motionId), $scope.mode); diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index 00efc70fc..2bce52ba7 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -750,7 +750,8 @@ angular.module('OpenSlidesApp.motions.site', [ 'gettextCatalog', function (gettextCatalog) { return { - getFormFields: function () { + getFormFields: function (precision) { + var step = Math.pow(10, -precision); return [ { key: 'yes', @@ -758,6 +759,7 @@ angular.module('OpenSlidesApp.motions.site', [ templateOptions: { label: gettextCatalog.getString('Yes'), type: 'number', + step: step, required: true } }, @@ -767,6 +769,7 @@ angular.module('OpenSlidesApp.motions.site', [ templateOptions: { label: gettextCatalog.getString('No'), type: 'number', + step: step, required: true } }, @@ -776,6 +779,7 @@ angular.module('OpenSlidesApp.motions.site', [ templateOptions: { label: gettextCatalog.getString('Abstain'), type: 'number', + step: step, required: true } }, @@ -784,6 +788,7 @@ angular.module('OpenSlidesApp.motions.site', [ type: 'input', templateOptions: { label: gettextCatalog.getString('Valid votes'), + step: step, type: 'number' } }, @@ -792,6 +797,7 @@ angular.module('OpenSlidesApp.motions.site', [ type: 'input', templateOptions: { label: gettextCatalog.getString('Invalid votes'), + step: step, type: 'number' } }, @@ -800,6 +806,7 @@ angular.module('OpenSlidesApp.motions.site', [ type: 'input', templateOptions: { label: gettextCatalog.getString('Votes cast'), + step: step, type: 'number' } }]; @@ -1130,11 +1137,14 @@ angular.module('OpenSlidesApp.motions.site', [ 'MajorityMethodChoices', 'Config', 'MotionPollDetailCtrlCache', - function ($scope, MajorityMethodChoices, Config, MotionPollDetailCtrlCache) { + 'MotionPollDecimalPlaces', + function ($scope, MajorityMethodChoices, Config, MotionPollDetailCtrlCache, MotionPollDecimalPlaces) { // Define choices. $scope.methodChoices = MajorityMethodChoices; // TODO: Get $scope.baseChoices from config_variables.py without copying them. + $scope.votesPrecision = MotionPollDecimalPlaces.getPlaces($scope.poll); + // Setup empty cache with default values. if (typeof MotionPollDetailCtrlCache[$scope.poll.id] === 'undefined') { MotionPollDetailCtrlCache[$scope.poll.id] = { @@ -2517,17 +2527,19 @@ angular.module('OpenSlidesApp.motions.site', [ 'gettextCatalog', 'MotionPoll', 'MotionPollForm', + 'MotionPollDecimalPlaces', 'motionpollId', 'voteNumber', 'ErrorMessage', - function ($scope, gettextCatalog, MotionPoll, MotionPollForm, motionpollId, - voteNumber, ErrorMessage) { + function ($scope, gettextCatalog, MotionPoll, MotionPollForm, MotionPollDecimalPlaces, + motionpollId, voteNumber, ErrorMessage) { // set initial values for form model by create deep copy of motionpoll object // so detail view is not updated while editing poll var motionpoll = MotionPoll.get(motionpollId); $scope.model = angular.copy(motionpoll); $scope.voteNumber = voteNumber; - $scope.formFields = MotionPollForm.getFormFields(); + var precision = MotionPollDecimalPlaces.getPlaces(motionpoll); + $scope.formFields = MotionPollForm.getFormFields(precision); $scope.alert = {}; // save motionpoll diff --git a/openslides/motions/static/templates/motions/motion-detail.html b/openslides/motions/static/templates/motions/motion-detail.html index bf533ad27..720ece363 100644 --- a/openslides/motions/static/templates/motions/motion-detail.html +++ b/openslides/motions/static/templates/motions/motion-detail.html @@ -404,7 +404,7 @@ Yes: - {{ voteYes.value }} {{ voteYes.percentStr }} + {{ voteYes.value | number:votesPrecision }} {{ voteYes.percentStr }}
@@ -416,7 +416,7 @@ No: - {{ voteNo.value }} {{ voteNo.percentStr }} + {{ voteNo.value | number:votesPrecision }} {{ voteNo.percentStr }}
@@ -428,7 +428,7 @@ Abstain: - {{ voteAbstain.value }} {{ voteAbstain.percentStr }} + {{ voteAbstain.value | number:votesPrecision }} {{ voteAbstain.percentStr }}
@@ -440,7 +440,7 @@ Valid votes: - {{ votesValid.value }} {{ votesValid.percentStr }} + {{ votesValid.value | number:votesPrecision }} {{ votesValid.percentStr }} @@ -449,7 +449,7 @@ Invalid votes: - {{ votesInvalid.value }} {{ votesInvalid.percentStr }} + {{ votesInvalid.value | number:votesPrecision }} {{ votesInvalid.percentStr }} @@ -458,7 +458,7 @@ Votes cast: - {{ votesCast.value }} {{ votesCast.percentStr }} + {{ votesCast.value | number:votesPrecision }} {{ votesCast.percentStr }} @@ -479,10 +479,10 @@
- Quorum ({{ voteYes.value - isReached() }}) reached. + Quorum ({{ (voteYes.value - isReached()) | number:votesPrecision }}) reached. - Quorum ({{ voteYes.value - isReached() }}) not reached. + Quorum ({{ (voteYes.value - isReached()) | number:votesPrecision }}) not reached.
diff --git a/openslides/motions/static/templates/motions/slide_motion.html b/openslides/motions/static/templates/motions/slide_motion.html index 7916ec22a..abe461ed4 100644 --- a/openslides/motions/static/templates/motions/slide_motion.html +++ b/openslides/motions/static/templates/motions/slide_motion.html @@ -31,7 +31,7 @@ Yes: - {{ voteYes.value }} {{ voteYes.percentStr }} + {{ voteYes.value | number:getPollVotesPrecision(poll) }} {{ voteYes.percentStr }}
@@ -43,7 +43,7 @@ No: - {{ voteNo.value }} {{ voteNo.percentStr }} + {{ voteNo.value | number:getPollVotesPrecision(poll) }} {{ voteNo.percentStr }}
@@ -55,7 +55,7 @@ Abstain: - {{ voteAbstain.value }} {{ voteAbstain.percentStr }} + {{ voteAbstain.value | number:getPollVotesPrecision(poll) }} {{ voteAbstain.percentStr }}
diff --git a/openslides/poll/models.py b/openslides/poll/models.py index 545e3f111..aec301cb3 100644 --- a/openslides/poll/models.py +++ b/openslides/poll/models.py @@ -1,12 +1,12 @@ import locale +from decimal import Decimal from typing import Type # noqa from django.core.exceptions import ObjectDoesNotExist +from django.core.validators import MinValueValidator from django.db import models from django.utils.translation import ugettext as _ -from openslides.utils.models import MinMaxIntegerField - class BaseOption(models.Model): """ @@ -44,7 +44,8 @@ class BaseVote(models.Model): Subclasses have to define an option field. This must be a ForeignKeyField to a subclass of BasePoll. """ - weight = models.IntegerField(default=1, null=True) # Use MinMaxIntegerField + weight = models.DecimalField(default=Decimal('1'), null=True, validators=[ + MinValueValidator(Decimal('-2'))], max_digits=15, decimal_places=6) value = models.CharField(max_length=255, null=True) class Meta: @@ -72,9 +73,12 @@ class CollectDefaultVotesMixin(models.Model): Mixin for a poll to collect the default vote values for valid votes, invalid votes and votes cast. """ - votesvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2) - votesinvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2) - votescast = MinMaxIntegerField(null=True, blank=True, min_value=-2) + votesvalid = models.DecimalField(null=True, blank=True, validators=[ + MinValueValidator(Decimal('-2'))], max_digits=15, decimal_places=6) + votesinvalid = models.DecimalField(null=True, blank=True, validators=[ + MinValueValidator(Decimal('-2'))], max_digits=15, decimal_places=6) + votescast = models.DecimalField(null=True, blank=True, validators=[ + MinValueValidator(Decimal('-2'))], max_digits=15, decimal_places=6) class Meta: abstract = True diff --git a/openslides/utils/rest_api.py b/openslides/utils/rest_api.py index bd44c5fe9..dad1b61a5 100644 --- a/openslides/utils/rest_api.py +++ b/openslides/utils/rest_api.py @@ -18,6 +18,7 @@ from rest_framework.routers import DefaultRouter from rest_framework.serializers import ModelSerializer as _ModelSerializer from rest_framework.serializers import ( # noqa CharField, + DecimalField, DictField, Field, FileField, From 9c1290ee0b467b1b5c020c5c6cca5a82e4407717 Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Mon, 16 Jul 2018 09:37:53 +0200 Subject: [PATCH 04/11] Do not allow changing a workflow's first state (closes #3778) --- CHANGELOG.rst | 4 +- openslides/motions/serializers.py | 14 +----- openslides/motions/static/js/motions/base.js | 2 +- .../motions/static/js/motions/workflow.js | 7 --- .../templates/motions/workflow-detail.html | 17 +------ openslides/motions/views.py | 7 --- tests/integration/motions/test_viewset.py | 47 ------------------- 7 files changed, 6 insertions(+), 92 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ec57c0864..5e1430bc4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,14 +15,14 @@ Motions: - New possibility to sort submitters [#3647]. - New representation of amendments (paragraph based creation, new diff and list views for amendments) [#3637]. - - New feature to customize workflows and states [#3772]. + - New feature to customize workflows and states [#3772, #3785]. - New config options to show logos on the right side in PDF [#3768]. - New table of contents with page numbers and categories in PDF [#3766]. - New teporal field "modified final version" where the final version can be edited [#3781]. - New config to show amendments also in motions table [#3792] - Core: +Core: - Python 3.4 is not supported anymore [#3777]. - Support Python 3.7 [#3786]. - Updated pdfMake to 0.1.37 [#3766]. diff --git a/openslides/motions/serializers.py b/openslides/motions/serializers.py index 6f2791bc2..b62fef864 100644 --- a/openslides/motions/serializers.py +++ b/openslides/motions/serializers.py @@ -101,12 +101,11 @@ class WorkflowSerializer(ModelSerializer): Serializer for motion.models.Workflow objects. """ states = StateSerializer(many=True, read_only=True) - # The first_state is checked in the update() method - first_state = PrimaryKeyRelatedField(queryset=State.objects.all(), required=False) class Meta: model = Workflow fields = ('id', 'name', 'states', 'first_state',) + read_only_fields = ('first_state',) @transaction.atomic def create(self, validated_data): @@ -127,17 +126,6 @@ class WorkflowSerializer(ModelSerializer): workflow.save() return workflow - @transaction.atomic - def update(self, workflow, validated_data): - """ - Check, if the first state is in the right workflow. - """ - first_state = validated_data.get('first_state') - if first_state is not None: - if workflow.pk != first_state.workflow.pk: - raise ValidationError({'detail': 'You cannot select a state which is not in the workflow as the first state.'}) - return super().update(workflow, validated_data) - class MotionCommentsJSONSerializerField(Field): """ diff --git a/openslides/motions/static/js/motions/base.js b/openslides/motions/static/js/motions/base.js index d2741bb4c..b8986e3b1 100644 --- a/openslides/motions/static/js/motions/base.js +++ b/openslides/motions/static/js/motions/base.js @@ -54,7 +54,7 @@ angular.module('OpenSlidesApp.motions', [ name: 'motions/workflow', methods: { getFirstState: function () { - return DS.get('motions/state', this.first_state); + return DS.get('motions/state', this.first_state_id); }, }, relations: { diff --git a/openslides/motions/static/js/motions/workflow.js b/openslides/motions/static/js/motions/workflow.js index 2d3cad8e0..db22d55aa 100644 --- a/openslides/motions/static/js/motions/workflow.js +++ b/openslides/motions/static/js/motions/workflow.js @@ -120,13 +120,6 @@ angular.module('OpenSlidesApp.motions.workflow', []) }); }; - $scope.setFirstState = function (state) { - $scope.workflow.first_state = state.id; - Workflow.save($scope.workflow).then(null, function (error) { - $scope.alert = ErrorMessage.forAlert(error); - }); - }; - // Save expand state so the session if ($sessionStorage.motionStateTableExpandState) { $scope.toggleExpandContent(); diff --git a/openslides/motions/static/templates/motions/workflow-detail.html b/openslides/motions/static/templates/motions/workflow-detail.html index 77452a0f0..29c8da0ca 100644 --- a/openslides/motions/static/templates/motions/workflow-detail.html +++ b/openslides/motions/static/templates/motions/workflow-detail.html @@ -24,22 +24,9 @@
-

+

First state: {{ workflow.getFirstState().name | translate }} - - - - - -

@@ -67,7 +54,7 @@   - diff --git a/openslides/motions/views.py b/openslides/motions/views.py index 29b151acf..33a84916b 100644 --- a/openslides/motions/views.py +++ b/openslides/motions/views.py @@ -919,13 +919,6 @@ class WorkflowViewSet(ModelViewSet, ProtectedErrorMessageMixin): result = False return result - def create(self, *args, **kwargs): - try: - response = super().create(*args, **kwargs) - except WorkflowError as e: - raise ValidationError({'detail': e.args[0]}) - return response - def destroy(self, *args, **kwargs): """ Customized view endpoint to delete a motion poll. diff --git a/tests/integration/motions/test_viewset.py b/tests/integration/motions/test_viewset.py index ea8a38cfc..800e5e3ea 100644 --- a/tests/integration/motions/test_viewset.py +++ b/tests/integration/motions/test_viewset.py @@ -1258,21 +1258,6 @@ class CreateWorkflow(TestCase): first_state = workflow.first_state self.assertEqual(type(first_state), State) - def test_creation_with_wrong_first_state(self): - response = self.client.post( - reverse('workflow-list'), - {'name': 'test_name_OoCoo3MeiT9li5Iengu9', - 'first_state': 1}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - def test_creation_with_not_existing_first_state(self): - Workflow.objects.all().delete() - response = self.client.post( - reverse('workflow-list'), - {'name': 'test_name_OoCoo3MeiT9li5Iengu9', - 'first_state': 49}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - class UpdateWorkflow(TestCase): """ @@ -1292,38 +1277,6 @@ class UpdateWorkflow(TestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(workflow.name, 'test_name_wofi38DiWLT"8d3lwfo3') - def test_change_first_state_correct(self): - first_state = self.workflow.first_state - other_workflow_state = self.workflow.states.exclude(pk=first_state.pk).first() - response = self.client.patch( - reverse('workflow-detail', args=[self.workflow.pk]), - {'first_state': other_workflow_state.pk}) - - workflow = Workflow.objects.get(pk=self.workflow.id) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(workflow.first_state, other_workflow_state) - - def test_change_first_state_not_existing(self): - first_state = self.workflow.first_state - response = self.client.patch( - reverse('workflow-detail', args=[self.workflow.pk]), - {'first_state': 42}) - - workflow = Workflow.objects.get(pk=self.workflow.id) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(workflow.first_state, first_state) - - def test_change_first_state_wrong_workflow(self): - first_state = self.workflow.first_state - other_workflow = Workflow.objects.exclude(pk=self.workflow.pk).first() - response = self.client.patch( - reverse('workflow-detail', args=[self.workflow.pk]), - {'first_state': other_workflow.first_state.pk}) - - workflow = Workflow.objects.get(pk=self.workflow.id) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(workflow.first_state, first_state) - class DeleteWorkflow(TestCase): """ From dc4fd6a93e104614531f5416aa9c6cadaed463dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Thu, 23 Aug 2018 09:48:47 +0200 Subject: [PATCH 05/11] Fixed agenda get_restricted_data() if no agenda items exist. --- openslides/agenda/access_permissions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openslides/agenda/access_permissions.py b/openslides/agenda/access_permissions.py index 26689b8e6..30010cd5f 100644 --- a/openslides/agenda/access_permissions.py +++ b/openslides/agenda/access_permissions.py @@ -47,7 +47,7 @@ class ItemAccessPermissions(BaseAccessPermissions): return {key: full_data[key] for key in whitelist} # Parse data. - if has_perm(user, 'agenda.can_see'): + if full_data and has_perm(user, 'agenda.can_see'): if has_perm(user, 'agenda.can_manage') and has_perm(user, 'agenda.can_see_internal_items'): # Managers with special permission can see everything. data = full_data @@ -62,6 +62,7 @@ class ItemAccessPermissions(BaseAccessPermissions): # In internal and hidden case managers and non managers see only some fields # so that list of speakers is provided regardless. Hidden items can only be seen by managers. + # We know that full_data has at least one entry which can be used to parse the keys. blocked_keys_internal_hidden_case = set(full_data[0].keys()) - set(( 'id', 'title', From 2cd826202589715068b94ab417b16cde782c06f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emanuel=20Sch=C3=BCtze?= Date: Thu, 23 Aug 2018 11:12:53 +0200 Subject: [PATCH 06/11] Fix sorting of states in workflow detail view. --- openslides/motions/static/js/motions/workflow.js | 4 +++- .../templates/motions/workflow-detail.html | 16 ++++++++-------- .../templates/users/user-change-password.html | 5 +++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/openslides/motions/static/js/motions/workflow.js b/openslides/motions/static/js/motions/workflow.js index db22d55aa..79ace06ed 100644 --- a/openslides/motions/static/js/motions/workflow.js +++ b/openslides/motions/static/js/motions/workflow.js @@ -49,7 +49,9 @@ angular.module('OpenSlidesApp.motions.workflow', []) return Workflow.lastModified(workflowId); }, function () { $scope.workflow = Workflow.get(workflowId); - _.forEach($scope.workflow.states, function (state) { + $scope.states = $scope.workflow.states; + $scope.states = _.orderBy($scope.states, 'id'); + _.forEach($scope.states, function (state) { state.newActionWord = gettextCatalog.getString(state.action_word); state.newRecommendationLabel = gettextCatalog.getString(state.recommendation_label); }); diff --git a/openslides/motions/static/templates/motions/workflow-detail.html b/openslides/motions/static/templates/motions/workflow-detail.html index 29c8da0ca..c59e31dd9 100644 --- a/openslides/motions/static/templates/motions/workflow-detail.html +++ b/openslides/motions/static/templates/motions/workflow-detail.html @@ -40,7 +40,7 @@

Permissions

- + {{ state.name | translate }} @@ -69,7 +69,7 @@ Action word - +
@@ -88,7 +88,7 @@ Recommendation label - +
+ Initial password: {{ user.default_password }} - Initial password: {{ user.default_password }} - +
+ Username: {{ user.username }} Show password From cbd970ba9de8147724de041b8d52c28fcf687623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Thu, 23 Aug 2018 11:12:57 +0200 Subject: [PATCH 07/11] Fixed assignment election bar for votes mode. Fixed #3598. --- .../static/templates/assignments/slide_assignment.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openslides/assignments/static/templates/assignments/slide_assignment.html b/openslides/assignments/static/templates/assignments/slide_assignment.html index e3954e0e0..fb62f65cb 100644 --- a/openslides/assignments/static/templates/assignments/slide_assignment.html +++ b/openslides/assignments/static/templates/assignments/slide_assignment.html @@ -59,6 +59,9 @@
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }} +
+ +
From 0036567f7d552753ad62a263eb82b36f98e81ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emanuel=20Sch=C3=BCtze?= Date: Thu, 23 Aug 2018 15:39:15 +0200 Subject: [PATCH 08/11] Fixed TypeError in Motion and Assignment Slides (Fixed#3813) in MotionPollDecimalPlaces and AssignmentPollDecimalPlaces --- openslides/assignments/static/js/assignments/base.js | 10 ++++++++-- openslides/motions/static/js/motions/base.js | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/openslides/assignments/static/js/assignments/base.js b/openslides/assignments/static/js/assignments/base.js index c971c99a2..08f481809 100644 --- a/openslides/assignments/static/js/assignments/base.js +++ b/openslides/assignments/static/js/assignments/base.js @@ -324,10 +324,16 @@ angular.module('OpenSlidesApp.assignments', []) .provider('AssignmentPollDecimalPlaces', [ function () { - this.$get = [function () { + this.$get = ['$q', function ($q) { return { getPlaces: function (poll, find) { - return 0; + if (find) { + return $q(function (resolve) { + resolve(0); + }); + } else { + return 0; + } }, }; }]; diff --git a/openslides/motions/static/js/motions/base.js b/openslides/motions/static/js/motions/base.js index e524a30fe..8b9d4e8d9 100644 --- a/openslides/motions/static/js/motions/base.js +++ b/openslides/motions/static/js/motions/base.js @@ -206,10 +206,16 @@ angular.module('OpenSlidesApp.motions', [ .provider('MotionPollDecimalPlaces', [ function () { - this.$get = [function () { + this.$get = ['$q', function ($q) { return { getPlaces: function (poll, find) { - return 0; + if (find) { + return $q(function (resolve) { + resolve(0); + }); + } else { + return 0; + } }, }; }]; From 3578d0e850ce77cdf00b6aa1440008fa5463a980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Wed, 22 Aug 2018 21:33:33 +0200 Subject: [PATCH 09/11] Fixed collectstattic command so that it does not write into openslides app dir. --- CHANGELOG.rst | 5 ++- .../core/management/commands/collectstatic.py | 42 +++++++++++++------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2819b99be..4a39b771c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,14 +23,15 @@ Motions: - New table of contents with page numbers and categories in PDF [#3766]. - New teporal field "modified final version" where the final version can be edited [#3781]. - - New config to show amendments also in motions table [#3792] - - Support to change decimal places for polls with a plugin [#3803] + - New config to show amendments also in motions table [#3792]. + - Support to change decimal places for polls with a plugin [#3803]. Core: - Python 3.4 is not supported anymore [#3777]. - Support Python 3.7 [#3786]. - Updated pdfMake to 0.1.37 [#3766]. - Updated Django to 2.1 [#3777, #3786]. + - Changed behavior of collectstatic management command [#3804]. Version 2.2 (2018-06-06) diff --git a/openslides/core/management/commands/collectstatic.py b/openslides/core/management/commands/collectstatic.py index 314dfd204..330cc28fc 100644 --- a/openslides/core/management/commands/collectstatic.py +++ b/openslides/core/management/commands/collectstatic.py @@ -4,6 +4,7 @@ from typing import Any, Dict from django.conf import settings from django.contrib.staticfiles.management.commands.collectstatic import \ Command as CollectStatic +from django.contrib.staticfiles.utils import matches_patterns from django.core.management.base import CommandError from django.db.utils import OperationalError @@ -18,6 +19,8 @@ class Command(CollectStatic): js_filename = 'webclient-{}.js' def handle(self, **options: Any) -> str: + if options['link']: + raise CommandError("Option 'link' is not supported.") try: self.view = WebclientJavaScriptView() except OperationalError: @@ -26,24 +29,37 @@ class Command(CollectStatic): return super().handle(**options) def collect(self) -> Dict[str, Any]: + result = super().collect() + try: - destination_dir = os.path.join(settings.STATICFILES_DIRS[0], 'js') + destination_dir = os.path.join(settings.STATIC_ROOT, 'js') except IndexError: # If the user does not want do have staticfiles, he should not get # the webclient files either. pass else: - if not os.path.exists(destination_dir): - os.makedirs(destination_dir) + if self.dry_run: + self.log('Pretending to write WebclientJavaScriptView for all realms.', level=1) + else: + if not os.path.exists(destination_dir): + os.makedirs(destination_dir) - for realm in self.realms: - filename = self.js_filename.format(realm) - content = self.view.get(realm=realm).content - path = os.path.join(destination_dir, filename) - with open(path, 'wb+') as f: - f.write(content) - self.stdout.write("Written WebclientJavaScriptView for realm {} to '{}'".format( - realm, - path)) + for realm in self.realms: + filename = self.js_filename.format(realm) + # Matches only the basename. + if matches_patterns(filename, self.ignore_patterns): + continue + path = os.path.join(destination_dir, filename) + if matches_patterns(path, self.ignore_patterns): + continue - return super().collect() + content = self.view.get(realm=realm).content + with open(path, 'wb+') as f: + f.write(content) + message = "Written WebclientJavaScriptView for realm {} to '{}'".format( + realm, + path) + self.log(message, level=1) + result['modified'].append(path) + + return result From cece35b3a5ef4cf3c365858663a857e94b21cdb4 Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Thu, 23 Aug 2018 10:06:49 +0200 Subject: [PATCH 10/11] Layout changes for motion detail view and PDF --- openslides/core/static/js/core/pdf.js | 4 +++- openslides/motions/static/css/motions/_site.scss | 2 +- openslides/motions/static/js/motions/site.js | 4 ++++ .../static/templates/motions/motion-detail.html | 10 +++++----- .../templates/motions/motion-detail/toolbar.html | 10 ++++++++-- .../motion-detail/view-modified-agreed.html | 16 ++++++++-------- 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/openslides/core/static/js/core/pdf.js b/openslides/core/static/js/core/pdf.js index 94d352e42..61ceee2ea 100644 --- a/openslides/core/static/js/core/pdf.js +++ b/openslides/core/static/js/core/pdf.js @@ -962,7 +962,9 @@ angular.module('OpenSlidesApp.core.pdf', []) currentParagraph.text.push(create('text', ' ')); } else if (isInsideAList(element) && lineNumberMode === 'none') { // Put a spacer there, if there is one BR in a list - alreadyConverted.push(create('text', ' ')); + var spacer = create('text', ' '); + spacer.lineHeight = 0.25; + alreadyConverted.push(spacer); } currentParagraph.lineHeight = 1.25; alreadyConverted.push(currentParagraph); diff --git a/openslides/motions/static/css/motions/_site.scss b/openslides/motions/static/css/motions/_site.scss index da149a8b7..0c809459c 100644 --- a/openslides/motions/static/css/motions/_site.scss +++ b/openslides/motions/static/css/motions/_site.scss @@ -112,7 +112,7 @@ } .motion-text.line-numbers-none li > br { - margin-top: 8px; + margin-top: 6px; content: " "; display: block; &.os-line-break { diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index 2bce52ba7..80d29d723 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -843,6 +843,10 @@ angular.module('OpenSlidesApp.motions.site', [ var someMotionsHaveAmendments = _.some(motions, function (motion) { return motion.hasAmendments(); }); + // if amendments amendments are already included. We owudl have them twice, if the option is enabled. + if (Config.get('motions_amendments_main_table').value) { + someMotionsHaveAmendments = false; + } var getMetaInformationOptions = function (disabled) { if (!disabled) { disabled = {}; diff --git a/openslides/motions/static/templates/motions/motion-detail.html b/openslides/motions/static/templates/motions/motion-detail.html index 720ece363..078582ef2 100644 --- a/openslides/motions/static/templates/motions/motion-detail.html +++ b/openslides/motions/static/templates/motions/motion-detail.html @@ -174,7 +174,7 @@
-
-
@@ -141,6 +142,11 @@ Final version +
  • + + + Final print template +
  • diff --git a/openslides/motions/static/templates/motions/motion-detail/view-modified-agreed.html b/openslides/motions/static/templates/motions/motion-detail/view-modified-agreed.html index 50aeb8468..e55dbc472 100644 --- a/openslides/motions/static/templates/motions/motion-detail/view-modified-agreed.html +++ b/openslides/motions/static/templates/motions/motion-detail/view-modified-agreed.html @@ -1,17 +1,17 @@
    +
    + +
    -
    - -
    The modified final version have been changed.