Merge remote-tracking branch 'upstream/master' into OpenSlides-3
This commit is contained in:
commit
401e7821ae
@ -11,6 +11,7 @@ Core:
|
|||||||
- Change URL schema [#3798].
|
- Change URL schema [#3798].
|
||||||
- Update to channels2 [#3796].
|
- Update to channels2 [#3796].
|
||||||
- Drop Python 3.5 support[#3805].
|
- Drop Python 3.5 support[#3805].
|
||||||
|
- Adds a websocket protocol [#3807].
|
||||||
|
|
||||||
|
|
||||||
Version 2.3 (unreleased)
|
Version 2.3 (unreleased)
|
||||||
@ -19,24 +20,28 @@ Version 2.3 (unreleased)
|
|||||||
Agenda:
|
Agenda:
|
||||||
- New item type 'hidden'. New visibilty filter in agenda [#3790].
|
- New item type 'hidden'. New visibilty filter in agenda [#3790].
|
||||||
|
|
||||||
|
Elections:
|
||||||
|
- Support to change decimal places for elections with a plugin [#3803]
|
||||||
|
|
||||||
Motions:
|
Motions:
|
||||||
- New feature to scroll the projector to a specific line [#3748].
|
- New feature to scroll the projector to a specific line [#3748].
|
||||||
- New possibility to sort submitters [#3647].
|
- New possibility to sort submitters [#3647].
|
||||||
- New representation of amendments (paragraph based creation, new diff
|
- New representation of amendments (paragraph based creation, new diff
|
||||||
and list views for amendments) [#3637].
|
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 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 table of contents with page numbers and categories in PDF [#3766].
|
||||||
- New teporal field "modified final version" where the final version can
|
- New teporal field "modified final version" where the final version can
|
||||||
be edited [#3781].
|
be edited [#3781].
|
||||||
- New config to show amendments also in motions table [#3792]
|
- New config to show amendments also in motions table [#3792].
|
||||||
|
- Support to change decimal places for polls with a plugin [#3803].
|
||||||
|
|
||||||
Core:
|
Core:
|
||||||
- Python 3.4 is not supported anymore [#3777].
|
- Python 3.4 is not supported anymore [#3777].
|
||||||
- Support Python 3.7 [#3786].
|
- Support Python 3.7 [#3786].
|
||||||
- Updated pdfMake to 0.1.37 [#3766].
|
- Updated pdfMake to 0.1.37 [#3766].
|
||||||
- Updated Django to 2.1 [#3777, #3786].
|
- Updated Django to 2.1 [#3777, #3786].
|
||||||
- Adds a websocket protocol [#3807].
|
- Changed behavior of collectstatic management command [#3804].
|
||||||
|
|
||||||
|
|
||||||
Version 2.2 (2018-06-06)
|
Version 2.2 (2018-06-06)
|
||||||
|
@ -47,7 +47,7 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
|||||||
return {key: full_data[key] for key in whitelist}
|
return {key: full_data[key] for key in whitelist}
|
||||||
|
|
||||||
# Parse data.
|
# 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'):
|
if has_perm(user, 'agenda.can_manage') and has_perm(user, 'agenda.can_see_internal_items'):
|
||||||
# Managers with special permission can see everything.
|
# Managers with special permission can see everything.
|
||||||
data = full_data
|
data = full_data
|
||||||
@ -62,6 +62,7 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
|||||||
|
|
||||||
# In internal and hidden case managers and non managers see only some fields
|
# 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.
|
# 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((
|
blocked_keys_internal_hidden_case = set(full_data[0].keys()) - set((
|
||||||
'id',
|
'id',
|
||||||
'title',
|
'title',
|
||||||
@ -83,7 +84,7 @@ class ItemAccessPermissions(BaseAccessPermissions):
|
|||||||
if full['is_hidden'] and can_see_hidden:
|
if full['is_hidden'] and can_see_hidden:
|
||||||
# Same filtering for internal and hidden items
|
# Same filtering for internal and hidden items
|
||||||
data.append(filtered_data(full, blocked_keys_internal_hidden_case))
|
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))
|
data.append(filtered_data(full, blocked_keys_internal_hidden_case))
|
||||||
else: # agenda item
|
else: # agenda item
|
||||||
data.append(filtered_data(full, blocked_keys_non_internal_hidden_case))
|
data.append(filtered_data(full, blocked_keys_non_internal_hidden_case))
|
||||||
|
@ -67,7 +67,7 @@ def get_config_variables():
|
|||||||
{'value': '1', 'display_name': 'Public item'},
|
{'value': '1', 'display_name': 'Public item'},
|
||||||
{'value': '2', 'display_name': 'Internal item'},
|
{'value': '2', 'display_name': 'Internal item'},
|
||||||
{'value': '3', 'display_name': 'Hidden 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,
|
weight=227,
|
||||||
group='Agenda',
|
group='Agenda',
|
||||||
subgroup='General')
|
subgroup='General')
|
||||||
|
@ -883,7 +883,7 @@ angular.module('OpenSlidesApp.agenda.site', [
|
|||||||
gettext('[Begin speech] starts the countdown, [End speech] stops the ' +
|
gettext('[Begin speech] starts the countdown, [End speech] stops the ' +
|
||||||
'countdown.');
|
'countdown.');
|
||||||
gettext('Agenda visibility');
|
gettext('Agenda visibility');
|
||||||
gettext('Default visibility for new agenda items');
|
gettext('Default visibility for new agenda items (except topics)');
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
76
openslides/assignments/migrations/0005_auto_20180822_1042.py
Normal file
76
openslides/assignments/migrations/0005_auto_20180822_1042.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# 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'))]),
|
||||||
|
),
|
||||||
|
]
|
@ -1,8 +1,10 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from decimal import Decimal
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.fields import GenericRelation
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
|
from django.core.validators import MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext as _, ugettext_noop
|
from django.utils.translation import ugettext as _, ugettext_noop
|
||||||
|
|
||||||
@ -18,7 +20,7 @@ from openslides.poll.models import (
|
|||||||
)
|
)
|
||||||
from openslides.utils.autoupdate import inform_changed_data
|
from openslides.utils.autoupdate import inform_changed_data
|
||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.models import MinMaxIntegerField, RESTModelMixin
|
from openslides.utils.models import RESTModelMixin
|
||||||
|
|
||||||
from .access_permissions import AssignmentAccessPermissions
|
from .access_permissions import AssignmentAccessPermissions
|
||||||
|
|
||||||
@ -422,9 +424,11 @@ class AssignmentPoll(RESTModelMixin, CollectDefaultVotesMixin, # type: ignore
|
|||||||
max_length=79,
|
max_length=79,
|
||||||
blank=True)
|
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' """
|
""" 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' """
|
""" General no votes, used for pollmethod 'votes' """
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -3,6 +3,7 @@ from django.utils.translation import ugettext as _
|
|||||||
|
|
||||||
from openslides.poll.serializers import default_votes_validator
|
from openslides.poll.serializers import default_votes_validator
|
||||||
from openslides.utils.rest_api import (
|
from openslides.utils.rest_api import (
|
||||||
|
DecimalField,
|
||||||
DictField,
|
DictField,
|
||||||
IntegerField,
|
IntegerField,
|
||||||
ListField,
|
ListField,
|
||||||
@ -98,7 +99,7 @@ class AssignmentAllPollSerializer(ModelSerializer):
|
|||||||
options = AssignmentOptionSerializer(many=True, read_only=True)
|
options = AssignmentOptionSerializer(many=True, read_only=True)
|
||||||
votes = ListField(
|
votes = ListField(
|
||||||
child=DictField(
|
child=DictField(
|
||||||
child=IntegerField(min_value=-2)),
|
child=DecimalField(max_digits=15, decimal_places=6, min_value=-2)),
|
||||||
write_only=True,
|
write_only=True,
|
||||||
required=False)
|
required=False)
|
||||||
has_votes = SerializerMethodField()
|
has_votes = SerializerMethodField()
|
||||||
|
@ -14,6 +14,12 @@ angular.module('OpenSlidesApp.assignments', [])
|
|||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: 'assignments/polloption',
|
name: 'assignments/polloption',
|
||||||
useClass: jsDataModel,
|
useClass: jsDataModel,
|
||||||
|
// Change the stringified numbers to floats.
|
||||||
|
beforeInject: function (resource, instance) {
|
||||||
|
_.forEach(instance.votes, function (vote) {
|
||||||
|
vote.weight = parseFloat(vote.weight);
|
||||||
|
});
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getVotes: function () {
|
getVotes: function () {
|
||||||
if (!this.poll.has_votes) {
|
if (!this.poll.has_votes) {
|
||||||
@ -154,6 +160,15 @@ angular.module('OpenSlidesApp.assignments', [])
|
|||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: name,
|
name: name,
|
||||||
useClass: jsDataModel,
|
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: {
|
methods: {
|
||||||
getResourceName: function () {
|
getResourceName: function () {
|
||||||
return name;
|
return name;
|
||||||
@ -307,6 +322,24 @@ angular.module('OpenSlidesApp.assignments', [])
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
.provider('AssignmentPollDecimalPlaces', [
|
||||||
|
function () {
|
||||||
|
this.$get = ['$q', function ($q) {
|
||||||
|
return {
|
||||||
|
getPlaces: function (poll, find) {
|
||||||
|
if (find) {
|
||||||
|
return $q(function (resolve) {
|
||||||
|
resolve(0);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
.factory('AssignmentRelatedUser', [
|
.factory('AssignmentRelatedUser', [
|
||||||
'DS',
|
'DS',
|
||||||
function (DS) {
|
function (DS) {
|
||||||
|
@ -9,7 +9,8 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
|
|||||||
'HTMLValidizer',
|
'HTMLValidizer',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
'PDFLayout',
|
'PDFLayout',
|
||||||
function($filter, HTMLValidizer, gettextCatalog, PDFLayout) {
|
'AssignmentPollDecimalPlaces',
|
||||||
|
function($filter, HTMLValidizer, gettextCatalog, PDFLayout, AssignmentPollDecimalPlaces) {
|
||||||
|
|
||||||
var createInstance = function(assignment) {
|
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
|
//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 = '';
|
var voteVal = '';
|
||||||
if (voteObject) {
|
if (voteObject) {
|
||||||
if (printLabel) {
|
if (printLabel) {
|
||||||
voteVal += voteObject.label + ': ';
|
voteVal += voteObject.label + ': ';
|
||||||
}
|
}
|
||||||
voteVal += voteObject.value;
|
voteVal += $filter('number')(voteObject.value, precision);
|
||||||
|
|
||||||
if (voteObject.percentStr) {
|
if (voteObject.percentStr) {
|
||||||
voteVal += ' ' + voteObject.percentStr;
|
voteVal += ' ' + voteObject.percentStr;
|
||||||
@ -135,6 +136,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
|
|||||||
_.forEach(assignment.polls, function(poll, pollIndex) {
|
_.forEach(assignment.polls, function(poll, pollIndex) {
|
||||||
if (poll.published) {
|
if (poll.published) {
|
||||||
var pollTableBody = [];
|
var pollTableBody = [];
|
||||||
|
var precision = AssignmentPollDecimalPlaces.getPlaces(poll);
|
||||||
|
|
||||||
resultBody.push({
|
resultBody.push({
|
||||||
text: gettextCatalog.getString('Ballot') + ' ' + (pollIndex+1),
|
text: gettextCatalog.getString('Ballot') + ' ' + (pollIndex+1),
|
||||||
@ -163,14 +165,14 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
|
|||||||
if (poll.pollmethod == 'votes') {
|
if (poll.pollmethod == 'votes') {
|
||||||
tableLine.push(
|
tableLine.push(
|
||||||
{
|
{
|
||||||
text: parseVoteValue(votes[0], false),
|
text: parseVoteValue(votes[0], false, precision),
|
||||||
style: PDFLayout.flipTableRowStyle(pollTableBody.length)
|
style: PDFLayout.flipTableRowStyle(pollTableBody.length)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
var resultBlock = [];
|
var resultBlock = [];
|
||||||
_.forEach(votes, function(vote) {
|
_.forEach(votes, function(vote) {
|
||||||
resultBlock.push(parseVoteValue(vote, true));
|
resultBlock.push(parseVoteValue(vote, true, precision));
|
||||||
});
|
});
|
||||||
tableLine.push({
|
tableLine.push({
|
||||||
text: resultBlock,
|
text: resultBlock,
|
||||||
@ -189,7 +191,7 @@ angular.module('OpenSlidesApp.assignments.pdf', ['OpenSlidesApp.core.pdf'])
|
|||||||
style: 'tableConclude'
|
style: 'tableConclude'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: parseVoteValue(poll.getVote(fieldName), false),
|
text: parseVoteValue(poll.getVote(fieldName), false, precision),
|
||||||
style: 'tableConclude'
|
style: 'tableConclude'
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
@ -16,15 +16,27 @@ angular.module('OpenSlidesApp.assignments.projector', ['OpenSlidesApp.assignment
|
|||||||
.controller('SlideAssignmentCtrl', [
|
.controller('SlideAssignmentCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'Assignment',
|
'Assignment',
|
||||||
|
'AssignmentPoll',
|
||||||
'AssignmentPhases',
|
'AssignmentPhases',
|
||||||
|
'AssignmentPollDecimalPlaces',
|
||||||
'User',
|
'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.
|
// 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
|
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||||
// class.
|
// class.
|
||||||
var id = $scope.element.id;
|
var id = $scope.element.id;
|
||||||
$scope.showResult = $scope.element.poll;
|
$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');
|
Assignment.bindOne(id, $scope, 'assignment');
|
||||||
$scope.phases = AssignmentPhases;
|
$scope.phases = AssignmentPhases;
|
||||||
User.bindAll({}, $scope, 'users');
|
User.bindAll({}, $scope, 'users');
|
||||||
|
@ -218,11 +218,15 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
'Config',
|
'Config',
|
||||||
'AssignmentPollDetailCtrlCache',
|
'AssignmentPollDetailCtrlCache',
|
||||||
'AssignmentPoll',
|
'AssignmentPoll',
|
||||||
function ($scope, MajorityMethodChoices, Config, AssignmentPollDetailCtrlCache, AssignmentPoll) {
|
'AssignmentPollDecimalPlaces',
|
||||||
|
function ($scope, MajorityMethodChoices, Config, AssignmentPollDetailCtrlCache,
|
||||||
|
AssignmentPoll, AssignmentPollDecimalPlaces) {
|
||||||
// Define choices.
|
// Define choices.
|
||||||
$scope.methodChoices = MajorityMethodChoices;
|
$scope.methodChoices = MajorityMethodChoices;
|
||||||
// TODO: Get $scope.baseChoices from config_variables.py without copying them.
|
// TODO: Get $scope.baseChoices from config_variables.py without copying them.
|
||||||
|
|
||||||
|
$scope.votesPrecision = AssignmentPollDecimalPlaces.getPlaces($scope.poll);
|
||||||
|
|
||||||
// Setup empty cache with default values.
|
// Setup empty cache with default values.
|
||||||
if (typeof AssignmentPollDetailCtrlCache[$scope.poll.id] === 'undefined') {
|
if (typeof AssignmentPollDetailCtrlCache[$scope.poll.id] === 'undefined') {
|
||||||
AssignmentPollDetailCtrlCache[$scope.poll.id] = {
|
AssignmentPollDetailCtrlCache[$scope.poll.id] = {
|
||||||
@ -689,9 +693,11 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
'AssignmentPoll',
|
'AssignmentPoll',
|
||||||
'assignmentpollId',
|
'assignmentpollId',
|
||||||
|
'AssignmentPollDecimalPlaces',
|
||||||
'ballot',
|
'ballot',
|
||||||
'ErrorMessage',
|
'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
|
// set initial values for form model by create deep copy of assignmentpoll object
|
||||||
// so detail view is not updated while editing poll
|
// so detail view is not updated while editing poll
|
||||||
var assignmentpoll = angular.copy(AssignmentPoll.get(assignmentpollId));
|
var assignmentpoll = angular.copy(AssignmentPoll.get(assignmentpollId));
|
||||||
@ -700,6 +706,9 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
$scope.formFields = [];
|
$scope.formFields = [];
|
||||||
$scope.alert = {};
|
$scope.alert = {};
|
||||||
|
|
||||||
|
// For number inputs
|
||||||
|
var step = Math.pow(10, -AssignmentPollDecimalPlaces.getPlaces(assignmentpoll));
|
||||||
|
|
||||||
// add dynamic form fields
|
// add dynamic form fields
|
||||||
var options = $filter('orderBy')(assignmentpoll.options, 'weight');
|
var options = $filter('orderBy')(assignmentpoll.options, 'weight');
|
||||||
_.forEach(options, function(option) {
|
_.forEach(options, function(option) {
|
||||||
@ -720,6 +729,7 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
label: gettextCatalog.getString('Yes'),
|
label: gettextCatalog.getString('Yes'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
min: -2,
|
min: -2,
|
||||||
|
step: step,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
defaultValue: defaultValue.yes
|
defaultValue: defaultValue.yes
|
||||||
@ -733,6 +743,7 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
label: gettextCatalog.getString('No'),
|
label: gettextCatalog.getString('No'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
min: -2,
|
min: -2,
|
||||||
|
step: step,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
defaultValue: defaultValue.no
|
defaultValue: defaultValue.no
|
||||||
@ -747,6 +758,7 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
label: gettextCatalog.getString('Abstain'),
|
label: gettextCatalog.getString('Abstain'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
min: -2,
|
min: -2,
|
||||||
|
step: step,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
defaultValue: defaultValue.abstain
|
defaultValue: defaultValue.abstain
|
||||||
@ -771,6 +783,7 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
label: option.candidate.get_full_name(),
|
label: option.candidate.get_full_name(),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
min: -2,
|
min: -2,
|
||||||
|
step: step,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
defaultValue: defaultValue
|
defaultValue: defaultValue
|
||||||
@ -785,6 +798,7 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
templateOptions: {
|
templateOptions: {
|
||||||
label: gettextCatalog.getString('Abstain'),
|
label: gettextCatalog.getString('Abstain'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
step: step,
|
||||||
min: -2,
|
min: -2,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -794,6 +808,7 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
templateOptions: {
|
templateOptions: {
|
||||||
label: gettextCatalog.getString('No'),
|
label: gettextCatalog.getString('No'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
step: step,
|
||||||
min: -2,
|
min: -2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -810,6 +825,7 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
templateOptions: {
|
templateOptions: {
|
||||||
label: gettextCatalog.getString('Valid ballots'),
|
label: gettextCatalog.getString('Valid ballots'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
step: step,
|
||||||
min: -2,
|
min: -2,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -819,6 +835,7 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
templateOptions: {
|
templateOptions: {
|
||||||
label: gettextCatalog.getString('Invalid ballots'),
|
label: gettextCatalog.getString('Invalid ballots'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
step: step,
|
||||||
min: -2,
|
min: -2,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -828,6 +845,7 @@ angular.module('OpenSlidesApp.assignments.site', [
|
|||||||
templateOptions: {
|
templateOptions: {
|
||||||
label: gettextCatalog.getString('Casted ballots'),
|
label: gettextCatalog.getString('Casted ballots'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
step: step,
|
||||||
min: -2,
|
min: -2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,7 @@
|
|||||||
<div ng-init="votes = option.getVotes()">
|
<div ng-init="votes = option.getVotes()">
|
||||||
<div ng-repeat="vote in votes">
|
<div ng-repeat="vote in votes">
|
||||||
<span ng-if="poll.pollmethod == 'yna' || poll.pollmethod == 'yn'">{{ vote.label }}:</span>
|
<span ng-if="poll.pollmethod == 'yna' || poll.pollmethod == 'yn'">{{ vote.label }}:</span>
|
||||||
{{ vote.value }} {{ vote.percentStr }}
|
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
|
||||||
<div ng-if="vote.percentNumber >= 0">
|
<div ng-if="vote.percentNumber >= 0">
|
||||||
<uib-progressbar ng-if="$index == 0" value="vote.percentNumber" type="success"></uib-progressbar>
|
<uib-progressbar ng-if="$index == 0" value="vote.percentNumber" type="success"></uib-progressbar>
|
||||||
<uib-progressbar ng-if="$index == 1" value="vote.percentNumber" type="danger"></uib-progressbar>
|
<uib-progressbar ng-if="$index == 1" value="vote.percentNumber" type="danger"></uib-progressbar>
|
||||||
@ -244,10 +244,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<td ng-hide="method === 'disabled'">
|
<td ng-hide="method === 'disabled'">
|
||||||
<span ng-if="option.majorityReached >= 0" class="text-success" translate>
|
<span ng-if="option.majorityReached >= 0" class="text-success" translate>
|
||||||
Quorum ({{ option.getVoteYes() - option.majorityReached }}) reached.
|
Quorum ({{ (option.getVoteYes() - option.majorityReached) | number:votesPrecision }}) reached.
|
||||||
</span>
|
</span>
|
||||||
<span ng-if="option.majorityReached < 0" class="text-danger" translate>
|
<span ng-if="option.majorityReached < 0" class="text-danger" translate>
|
||||||
Quorum ({{ option.getVoteYes() - option.majorityReached }}) not reached.
|
Quorum ({{ (option.getVoteYes() - option.majorityReached) | number:votesPrecision }}) not reached.
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- total votes (valid/invalid/casts) -->
|
<!-- total votes (valid/invalid/casts) -->
|
||||||
@ -255,31 +255,31 @@
|
|||||||
<td>
|
<td>
|
||||||
<translate>Abstain</translate>
|
<translate>Abstain</translate>
|
||||||
<td>
|
<td>
|
||||||
{{ poll.getVote('votesabstain').value }}
|
{{ poll.getVote('votesabstain').value | number:votesPrecision }}
|
||||||
{{ poll.getVote('votesabstain').percentStr }}
|
{{ poll.getVote('votesabstain').percentStr }}
|
||||||
<tr ng-if="poll.pollmethod === 'votes'">
|
<tr ng-if="poll.pollmethod === 'votes'">
|
||||||
<td>
|
<td>
|
||||||
<translate>No</translate>
|
<translate>No</translate>
|
||||||
<td>
|
<td>
|
||||||
{{ poll.getVote('votesno').value }}
|
{{ poll.getVote('votesno').value | number:votesPrecision }}
|
||||||
{{ poll.getVote('votesno').percentStr }}
|
{{ poll.getVote('votesno').percentStr }}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<translate>Valid ballots</translate>
|
<translate>Valid ballots</translate>
|
||||||
<td>
|
<td>
|
||||||
{{ poll.getVote('votesvalid').value }}
|
{{ poll.getVote('votesvalid').value | number:votesPrecision }}
|
||||||
{{ poll.getVote('votesvalid').percentStr }}
|
{{ poll.getVote('votesvalid').percentStr }}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<translate>Invalid ballots</translate>
|
<translate>Invalid ballots</translate>
|
||||||
<td>
|
<td>
|
||||||
{{ poll.getVote('votesinvalid').value }}
|
{{ poll.getVote('votesinvalid').value | number:votesPrecision }}
|
||||||
{{ poll.getVote('votesinvalid').percentStr }}
|
{{ poll.getVote('votesinvalid').percentStr }}
|
||||||
<tr class="total bg-info">
|
<tr class="total bg-info">
|
||||||
<td>
|
<td>
|
||||||
<translate>Casted ballots</translate>
|
<translate>Casted ballots</translate>
|
||||||
<td>
|
<td>
|
||||||
{{ poll.getVote('votescast').value }}
|
{{ poll.getVote('votescast').value | number:votesPrecision }}
|
||||||
{{ poll.getVote('votescast').percentStr }}
|
{{ poll.getVote('votescast').percentStr }}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -49,16 +49,19 @@
|
|||||||
<div ng-init="votes = option.getVotes()">
|
<div ng-init="votes = option.getVotes()">
|
||||||
<div ng-show="poll.pollmethod == 'yna' || poll.pollmethod == 'yn'">
|
<div ng-show="poll.pollmethod == 'yna' || poll.pollmethod == 'yn'">
|
||||||
<span ng-show="poll.pollmethod == 'yna'">
|
<span ng-show="poll.pollmethod == 'yna'">
|
||||||
{{ votes[0].label | translate }}: {{ votes[0].value }} {{ votes[0].percentStr }}<br>
|
{{ votes[0].label | translate }}: {{ votes[0].value | number:votesPrecision }} {{ votes[0].percentStr }}<br>
|
||||||
{{ votes[1].label | translate }}: {{ votes[1].value }} {{ votes[1].percentStr }}<br>
|
{{ votes[1].label | translate }}: {{ votes[1].value | number:votesPrecision }} {{ votes[1].percentStr }}<br>
|
||||||
{{ votes[2].label | translate }}: {{ votes[2].value }} {{ votes[2].percentStr }}</span>
|
{{ votes[2].label | translate }}: {{ votes[2].value | number:votesPrecision }} {{ votes[2].percentStr }}</span>
|
||||||
<span ng-show="poll.pollmethod == 'yn'">
|
<span ng-show="poll.pollmethod == 'yn'">
|
||||||
{{ votes[0].label | translate }}: {{ votes[0].value }} {{ votes[0].percentStr }}<br>
|
{{ votes[0].label | translate }}: {{ votes[0].value | number:votesPrecision }} {{ votes[0].percentStr }}<br>
|
||||||
{{ votes[1].label | translate }}: {{ votes[1].value }} {{ votes[1].percentStr }}</span>
|
{{ votes[1].label | translate }}: {{ votes[1].value | number:votesPrecision }} {{ votes[1].percentStr }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="poll.pollmethod == 'votes'">
|
<div ng-show="poll.pollmethod == 'votes'">
|
||||||
<div ng-repeat="vote in votes">
|
<div ng-repeat="vote in votes">
|
||||||
{{ vote.value }} {{ vote.percentStr }}
|
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
|
||||||
|
<div style="float:right; width:200px;" ng-if="vote.percentNumber >= 0">
|
||||||
|
<uib-progressbar value="vote.percentNumber" type="success"></uib-progressbar>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -68,29 +71,29 @@
|
|||||||
<td>
|
<td>
|
||||||
<translate>Abstain</translate>
|
<translate>Abstain</translate>
|
||||||
<td ng-init="vote = poll.getVote('votesabstain')">
|
<td ng-init="vote = poll.getVote('votesabstain')">
|
||||||
{{ vote.value }} {{ vote.percentStr }}
|
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
|
||||||
<tr class="total" ng-if="poll.has_votes && poll.pollmethod === 'votes' && poll.getVote('votesno').value !== null">
|
<tr class="total" ng-if="poll.has_votes && poll.pollmethod === 'votes' && poll.getVote('votesno').value !== null">
|
||||||
<td>
|
<td>
|
||||||
<translate>No</translate>
|
<translate>No</translate>
|
||||||
<td ng-init="vote = poll.getVote('votesno')">
|
<td ng-init="vote = poll.getVote('votesno')">
|
||||||
{{ vote.value }} {{ vote.percentStr }}
|
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
|
||||||
<tr class="total" ng-if="poll.has_votes && poll.getVote('votesvalid').value !== null">
|
<tr class="total" ng-if="poll.has_votes && poll.getVote('votesvalid').value !== null">
|
||||||
<td>
|
<td>
|
||||||
<translate>Valid ballots</translate>
|
<translate>Valid ballots</translate>
|
||||||
<td ng-init="vote = poll.getVote('votesvalid')">
|
<td ng-init="vote = poll.getVote('votesvalid')">
|
||||||
{{ vote.value }} {{ vote.percentStr }}
|
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
|
||||||
|
|
||||||
<tr class="total" ng-if="poll.has_votes && poll.getVote('votesinvalid').value !== null">
|
<tr class="total" ng-if="poll.has_votes && poll.getVote('votesinvalid').value !== null">
|
||||||
<td>
|
<td>
|
||||||
<translate>Invalid ballots</translate>
|
<translate>Invalid ballots</translate>
|
||||||
<td ng-init="vote = poll.getVote('votesinvalid')">
|
<td ng-init="vote = poll.getVote('votesinvalid')">
|
||||||
{{ vote.value }} {{ vote.percentStr }}
|
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
|
||||||
|
|
||||||
<tr class="total bg-info" ng-if="poll.has_votes && poll.getVote('votescast').value !== null">
|
<tr class="total bg-info" ng-if="poll.has_votes && poll.getVote('votescast').value !== null">
|
||||||
<td>
|
<td>
|
||||||
<translate>Casted ballots</translate>
|
<translate>Casted ballots</translate>
|
||||||
<td ng-init="vote = poll.getVote('votescast')">
|
<td ng-init="vote = poll.getVote('votescast')">
|
||||||
{{ vote.value }} {{ vote.percentStr }}
|
{{ vote.value | number:votesPrecision }} {{ vote.percentStr }}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ from django.conf import settings
|
|||||||
from django.contrib.staticfiles.management.commands.collectstatic import (
|
from django.contrib.staticfiles.management.commands.collectstatic import (
|
||||||
Command as CollectStatic,
|
Command as CollectStatic,
|
||||||
)
|
)
|
||||||
|
from django.contrib.staticfiles.utils import matches_patterns
|
||||||
from django.core.management.base import CommandError
|
from django.core.management.base import CommandError
|
||||||
from django.db.utils import OperationalError
|
from django.db.utils import OperationalError
|
||||||
|
|
||||||
@ -19,6 +20,8 @@ class Command(CollectStatic):
|
|||||||
js_filename = 'webclient-{}.js'
|
js_filename = 'webclient-{}.js'
|
||||||
|
|
||||||
def handle(self, **options: Any) -> str:
|
def handle(self, **options: Any) -> str:
|
||||||
|
if options['link']:
|
||||||
|
raise CommandError("Option 'link' is not supported.")
|
||||||
try:
|
try:
|
||||||
self.view = WebclientJavaScriptView()
|
self.view = WebclientJavaScriptView()
|
||||||
except OperationalError:
|
except OperationalError:
|
||||||
@ -27,24 +30,37 @@ class Command(CollectStatic):
|
|||||||
return super().handle(**options)
|
return super().handle(**options)
|
||||||
|
|
||||||
def collect(self) -> Dict[str, Any]:
|
def collect(self) -> Dict[str, Any]:
|
||||||
|
result = super().collect()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
destination_dir = os.path.join(settings.STATICFILES_DIRS[0], 'js')
|
destination_dir = os.path.join(settings.STATIC_ROOT, 'js')
|
||||||
except IndexError:
|
except IndexError:
|
||||||
# If the user does not want do have staticfiles, he should not get
|
# If the user does not want do have staticfiles, he should not get
|
||||||
# the webclient files either.
|
# the webclient files either.
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
if self.dry_run:
|
||||||
|
self.log('Pretending to write WebclientJavaScriptView for all realms.', level=1)
|
||||||
else:
|
else:
|
||||||
if not os.path.exists(destination_dir):
|
if not os.path.exists(destination_dir):
|
||||||
os.makedirs(destination_dir)
|
os.makedirs(destination_dir)
|
||||||
|
|
||||||
for realm in self.realms:
|
for realm in self.realms:
|
||||||
filename = self.js_filename.format(realm)
|
filename = self.js_filename.format(realm)
|
||||||
content = self.view.get(realm=realm).content
|
# Matches only the basename.
|
||||||
|
if matches_patterns(filename, self.ignore_patterns):
|
||||||
|
continue
|
||||||
path = os.path.join(destination_dir, filename)
|
path = os.path.join(destination_dir, filename)
|
||||||
|
if matches_patterns(path, self.ignore_patterns):
|
||||||
|
continue
|
||||||
|
|
||||||
|
content = self.view.get(realm=realm).content
|
||||||
with open(path, 'wb+') as f:
|
with open(path, 'wb+') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
self.stdout.write("Written WebclientJavaScriptView for realm {} to '{}'".format(
|
message = "Written WebclientJavaScriptView for realm {} to '{}'".format(
|
||||||
realm,
|
realm,
|
||||||
path))
|
path)
|
||||||
|
self.log(message, level=1)
|
||||||
|
result['modified'].append(path)
|
||||||
|
|
||||||
return super().collect()
|
return result
|
||||||
|
@ -962,7 +962,9 @@ angular.module('OpenSlidesApp.core.pdf', [])
|
|||||||
currentParagraph.text.push(create('text', ' '));
|
currentParagraph.text.push(create('text', ' '));
|
||||||
} else if (isInsideAList(element) && lineNumberMode === 'none') {
|
} else if (isInsideAList(element) && lineNumberMode === 'none') {
|
||||||
// Put a spacer there, if there is one BR in a list
|
// 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;
|
currentParagraph.lineHeight = 1.25;
|
||||||
alreadyConverted.push(currentParagraph);
|
alreadyConverted.push(currentParagraph);
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
56
openslides/motions/migrations/0010_auto_20180822_1042.py
Normal file
56
openslides/motions/migrations/0010_auto_20180822_1042.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# 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'))]),
|
||||||
|
),
|
||||||
|
]
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Dict
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.utils.translation import ugettext as _
|
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 ..poll.serializers import default_votes_validator
|
||||||
from ..utils.rest_api import (
|
from ..utils.rest_api import (
|
||||||
CharField,
|
CharField,
|
||||||
|
DecimalField,
|
||||||
DictField,
|
DictField,
|
||||||
Field,
|
Field,
|
||||||
IntegerField,
|
IntegerField,
|
||||||
@ -101,12 +102,11 @@ class WorkflowSerializer(ModelSerializer):
|
|||||||
Serializer for motion.models.Workflow objects.
|
Serializer for motion.models.Workflow objects.
|
||||||
"""
|
"""
|
||||||
states = StateSerializer(many=True, read_only=True)
|
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:
|
class Meta:
|
||||||
model = Workflow
|
model = Workflow
|
||||||
fields = ('id', 'name', 'states', 'first_state',)
|
fields = ('id', 'name', 'states', 'first_state',)
|
||||||
|
read_only_fields = ('first_state',)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
@ -127,17 +127,6 @@ class WorkflowSerializer(ModelSerializer):
|
|||||||
workflow.save()
|
workflow.save()
|
||||||
return workflow
|
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):
|
class MotionCommentsJSONSerializerField(Field):
|
||||||
"""
|
"""
|
||||||
@ -212,7 +201,7 @@ class MotionPollSerializer(ModelSerializer):
|
|||||||
no = SerializerMethodField()
|
no = SerializerMethodField()
|
||||||
abstain = SerializerMethodField()
|
abstain = SerializerMethodField()
|
||||||
votes = DictField(
|
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)
|
write_only=True)
|
||||||
has_votes = SerializerMethodField()
|
has_votes = SerializerMethodField()
|
||||||
|
|
||||||
@ -238,21 +227,21 @@ class MotionPollSerializer(ModelSerializer):
|
|||||||
|
|
||||||
def get_yes(self, obj):
|
def get_yes(self, obj):
|
||||||
try:
|
try:
|
||||||
result = self.get_votes_dict(obj)['Yes']
|
result: Optional[str] = str(self.get_votes_dict(obj)['Yes'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
result = None
|
result = None
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_no(self, obj):
|
def get_no(self, obj):
|
||||||
try:
|
try:
|
||||||
result = self.get_votes_dict(obj)['No']
|
result: Optional[str] = str(self.get_votes_dict(obj)['No'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
result = None
|
result = None
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_abstain(self, obj):
|
def get_abstain(self, obj):
|
||||||
try:
|
try:
|
||||||
result = self.get_votes_dict(obj)['Abstain']
|
result: Optional[str] = str(self.get_votes_dict(obj)['Abstain'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
result = None
|
result = None
|
||||||
return result
|
return result
|
||||||
|
@ -112,7 +112,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.motion-text.line-numbers-none li > br {
|
.motion-text.line-numbers-none li > br {
|
||||||
margin-top: 8px;
|
margin-top: 6px;
|
||||||
content: " ";
|
content: " ";
|
||||||
display: block;
|
display: block;
|
||||||
&.os-line-break {
|
&.os-line-break {
|
||||||
|
@ -54,7 +54,7 @@ angular.module('OpenSlidesApp.motions', [
|
|||||||
name: 'motions/workflow',
|
name: 'motions/workflow',
|
||||||
methods: {
|
methods: {
|
||||||
getFirstState: function () {
|
getFirstState: function () {
|
||||||
return DS.get('motions/state', this.first_state);
|
return DS.get('motions/state', this.first_state_id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
relations: {
|
relations: {
|
||||||
@ -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: {
|
methods: {
|
||||||
// Returns percent base. Returns undefined if calculation is not possible in general.
|
// Returns percent base. Returns undefined if calculation is not possible in general.
|
||||||
getPercentBase: function (config, type) {
|
getPercentBase: function (config, type) {
|
||||||
@ -196,6 +204,24 @@ angular.module('OpenSlidesApp.motions', [
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
.provider('MotionPollDecimalPlaces', [
|
||||||
|
function () {
|
||||||
|
this.$get = ['$q', function ($q) {
|
||||||
|
return {
|
||||||
|
getPlaces: function (poll, find) {
|
||||||
|
if (find) {
|
||||||
|
return $q(function (resolve) {
|
||||||
|
resolve(0);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
.factory('MotionStateAndRecommendationParser', [
|
.factory('MotionStateAndRecommendationParser', [
|
||||||
'DS',
|
'DS',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
|
@ -17,9 +17,10 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
|
|||||||
'Config',
|
'Config',
|
||||||
'Motion',
|
'Motion',
|
||||||
'MotionComment',
|
'MotionComment',
|
||||||
|
'MotionPollDecimalPlaces',
|
||||||
'OpenSlidesSettings',
|
'OpenSlidesSettings',
|
||||||
function($q, $filter, operator, gettextCatalog, PDFLayout, PdfMakeConverter, ImageConverter,
|
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
|
* Provides the content as JS objects for Motions in pdfMake context
|
||||||
* @constructor
|
* @constructor
|
||||||
@ -185,40 +186,41 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
|
|||||||
column2.push('');
|
column2.push('');
|
||||||
column3.push('');
|
column3.push('');
|
||||||
}
|
}
|
||||||
|
var precision = MotionPollDecimalPlaces.getPlaces(poll);
|
||||||
// yes
|
// yes
|
||||||
var yes = poll.getVote(poll.yes, 'yes');
|
var yes = poll.getVote(poll.yes, 'yes');
|
||||||
column1.push(gettextCatalog.getString('Yes') + ':');
|
column1.push(gettextCatalog.getString('Yes') + ':');
|
||||||
column2.push(yes.value);
|
column2.push($filter('number')(yes.value, precision));
|
||||||
column3.push(yes.percentStr);
|
column3.push(yes.percentStr);
|
||||||
// no
|
// no
|
||||||
var no = poll.getVote(poll.no, 'no');
|
var no = poll.getVote(poll.no, 'no');
|
||||||
column1.push(gettextCatalog.getString('No') + ':');
|
column1.push(gettextCatalog.getString('No') + ':');
|
||||||
column2.push(no.value);
|
column2.push($filter('number')(no.value, precision));
|
||||||
column3.push(no.percentStr);
|
column3.push(no.percentStr);
|
||||||
// abstain
|
// abstain
|
||||||
var abstain = poll.getVote(poll.abstain, 'abstain');
|
var abstain = poll.getVote(poll.abstain, 'abstain');
|
||||||
column1.push(gettextCatalog.getString('Abstain') + ':');
|
column1.push(gettextCatalog.getString('Abstain') + ':');
|
||||||
column2.push(abstain.value);
|
column2.push($filter('number')(abstain.value, precision));
|
||||||
column3.push(abstain.percentStr);
|
column3.push(abstain.percentStr);
|
||||||
// votes valid
|
// votes valid
|
||||||
if (poll.votesvalid) {
|
if (poll.votesvalid) {
|
||||||
var valid = poll.getVote(poll.votesvalid, 'votesvalid');
|
var valid = poll.getVote(poll.votesvalid, 'votesvalid');
|
||||||
column1.push(gettextCatalog.getString('Valid votes') + ':');
|
column1.push(gettextCatalog.getString('Valid votes') + ':');
|
||||||
column2.push(valid.value);
|
column2.push($filter('number')(valid.value, precision));
|
||||||
column3.push(valid.percentStr);
|
column3.push(valid.percentStr);
|
||||||
}
|
}
|
||||||
// votes invalid
|
// votes invalid
|
||||||
if (poll.votesvalid) {
|
if (poll.votesvalid) {
|
||||||
var invalid = poll.getVote(poll.votesinvalid, 'votesinvalid');
|
var invalid = poll.getVote(poll.votesinvalid, 'votesinvalid');
|
||||||
column1.push(gettextCatalog.getString('Invalid votes') + ':');
|
column1.push(gettextCatalog.getString('Invalid votes') + ':');
|
||||||
column2.push(invalid.value);
|
column2.push($filter('number')(invalid.value, precision));
|
||||||
column3.push(invalid.percentStr);
|
column3.push(invalid.percentStr);
|
||||||
}
|
}
|
||||||
// votes cast
|
// votes cast
|
||||||
if (poll.votescast) {
|
if (poll.votescast) {
|
||||||
var cast = poll.getVote(poll.votescast, 'votescast');
|
var cast = poll.getVote(poll.votescast, 'votescast');
|
||||||
column1.push(gettextCatalog.getString('Votes cast') + ':');
|
column1.push(gettextCatalog.getString('Votes cast') + ':');
|
||||||
column2.push(cast.value);
|
column2.push($filter('number')(cast.value, precision));
|
||||||
column3.push(cast.percentStr);
|
column3.push(cast.percentStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,9 @@ angular.module('OpenSlidesApp.motions.projector', [
|
|||||||
'User',
|
'User',
|
||||||
'Notify',
|
'Notify',
|
||||||
'ProjectorID',
|
'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.
|
// 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
|
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||||
// class.
|
// class.
|
||||||
@ -67,7 +69,20 @@ angular.module('OpenSlidesApp.motions.projector', [
|
|||||||
$scope.motion = Motion.get(motionId);
|
$scope.motion = Motion.get(motionId);
|
||||||
$scope.amendment_diff_paragraphs = $scope.motion.getAmendmentParagraphsLinesDiff();
|
$scope.amendment_diff_paragraphs = $scope.motion.getAmendmentParagraphsLinesDiff();
|
||||||
$scope.viewChangeRecommendations.setVersion($scope.motion, $scope.motion.active_version);
|
$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
|
// Change recommendation viewing
|
||||||
$scope.viewChangeRecommendations = ChangeRecommendationView;
|
$scope.viewChangeRecommendations = ChangeRecommendationView;
|
||||||
|
@ -750,7 +750,8 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
function (gettextCatalog) {
|
function (gettextCatalog) {
|
||||||
return {
|
return {
|
||||||
getFormFields: function () {
|
getFormFields: function (precision) {
|
||||||
|
var step = Math.pow(10, -precision);
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
key: 'yes',
|
key: 'yes',
|
||||||
@ -758,6 +759,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
templateOptions: {
|
templateOptions: {
|
||||||
label: gettextCatalog.getString('Yes'),
|
label: gettextCatalog.getString('Yes'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
step: step,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -767,6 +769,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
templateOptions: {
|
templateOptions: {
|
||||||
label: gettextCatalog.getString('No'),
|
label: gettextCatalog.getString('No'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
step: step,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -776,6 +779,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
templateOptions: {
|
templateOptions: {
|
||||||
label: gettextCatalog.getString('Abstain'),
|
label: gettextCatalog.getString('Abstain'),
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
step: step,
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -784,6 +788,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
type: 'input',
|
type: 'input',
|
||||||
templateOptions: {
|
templateOptions: {
|
||||||
label: gettextCatalog.getString('Valid votes'),
|
label: gettextCatalog.getString('Valid votes'),
|
||||||
|
step: step,
|
||||||
type: 'number'
|
type: 'number'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -792,6 +797,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
type: 'input',
|
type: 'input',
|
||||||
templateOptions: {
|
templateOptions: {
|
||||||
label: gettextCatalog.getString('Invalid votes'),
|
label: gettextCatalog.getString('Invalid votes'),
|
||||||
|
step: step,
|
||||||
type: 'number'
|
type: 'number'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -800,6 +806,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
type: 'input',
|
type: 'input',
|
||||||
templateOptions: {
|
templateOptions: {
|
||||||
label: gettextCatalog.getString('Votes cast'),
|
label: gettextCatalog.getString('Votes cast'),
|
||||||
|
step: step,
|
||||||
type: 'number'
|
type: 'number'
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
@ -836,6 +843,10 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
var someMotionsHaveAmendments = _.some(motions, function (motion) {
|
var someMotionsHaveAmendments = _.some(motions, function (motion) {
|
||||||
return motion.hasAmendments();
|
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) {
|
var getMetaInformationOptions = function (disabled) {
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
disabled = {};
|
disabled = {};
|
||||||
@ -1130,11 +1141,14 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
'MajorityMethodChoices',
|
'MajorityMethodChoices',
|
||||||
'Config',
|
'Config',
|
||||||
'MotionPollDetailCtrlCache',
|
'MotionPollDetailCtrlCache',
|
||||||
function ($scope, MajorityMethodChoices, Config, MotionPollDetailCtrlCache) {
|
'MotionPollDecimalPlaces',
|
||||||
|
function ($scope, MajorityMethodChoices, Config, MotionPollDetailCtrlCache, MotionPollDecimalPlaces) {
|
||||||
// Define choices.
|
// Define choices.
|
||||||
$scope.methodChoices = MajorityMethodChoices;
|
$scope.methodChoices = MajorityMethodChoices;
|
||||||
// TODO: Get $scope.baseChoices from config_variables.py without copying them.
|
// TODO: Get $scope.baseChoices from config_variables.py without copying them.
|
||||||
|
|
||||||
|
$scope.votesPrecision = MotionPollDecimalPlaces.getPlaces($scope.poll);
|
||||||
|
|
||||||
// Setup empty cache with default values.
|
// Setup empty cache with default values.
|
||||||
if (typeof MotionPollDetailCtrlCache[$scope.poll.id] === 'undefined') {
|
if (typeof MotionPollDetailCtrlCache[$scope.poll.id] === 'undefined') {
|
||||||
MotionPollDetailCtrlCache[$scope.poll.id] = {
|
MotionPollDetailCtrlCache[$scope.poll.id] = {
|
||||||
@ -2517,17 +2531,19 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
'MotionPoll',
|
'MotionPoll',
|
||||||
'MotionPollForm',
|
'MotionPollForm',
|
||||||
|
'MotionPollDecimalPlaces',
|
||||||
'motionpollId',
|
'motionpollId',
|
||||||
'voteNumber',
|
'voteNumber',
|
||||||
'ErrorMessage',
|
'ErrorMessage',
|
||||||
function ($scope, gettextCatalog, MotionPoll, MotionPollForm, motionpollId,
|
function ($scope, gettextCatalog, MotionPoll, MotionPollForm, MotionPollDecimalPlaces,
|
||||||
voteNumber, ErrorMessage) {
|
motionpollId, voteNumber, ErrorMessage) {
|
||||||
// set initial values for form model by create deep copy of motionpoll object
|
// set initial values for form model by create deep copy of motionpoll object
|
||||||
// so detail view is not updated while editing poll
|
// so detail view is not updated while editing poll
|
||||||
var motionpoll = MotionPoll.get(motionpollId);
|
var motionpoll = MotionPoll.get(motionpollId);
|
||||||
$scope.model = angular.copy(motionpoll);
|
$scope.model = angular.copy(motionpoll);
|
||||||
$scope.voteNumber = voteNumber;
|
$scope.voteNumber = voteNumber;
|
||||||
$scope.formFields = MotionPollForm.getFormFields();
|
var precision = MotionPollDecimalPlaces.getPlaces(motionpoll);
|
||||||
|
$scope.formFields = MotionPollForm.getFormFields(precision);
|
||||||
$scope.alert = {};
|
$scope.alert = {};
|
||||||
|
|
||||||
// save motionpoll
|
// save motionpoll
|
||||||
@ -3291,6 +3307,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
|
|
||||||
// misc strings (used dynamically in templates by translate filter)
|
// misc strings (used dynamically in templates by translate filter)
|
||||||
gettext('needed');
|
gettext('needed');
|
||||||
|
gettext('Amendment');
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -49,7 +49,9 @@ angular.module('OpenSlidesApp.motions.workflow', [])
|
|||||||
return Workflow.lastModified(workflowId);
|
return Workflow.lastModified(workflowId);
|
||||||
}, function () {
|
}, function () {
|
||||||
$scope.workflow = Workflow.get(workflowId);
|
$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.newActionWord = gettextCatalog.getString(state.action_word);
|
||||||
state.newRecommendationLabel = gettextCatalog.getString(state.recommendation_label);
|
state.newRecommendationLabel = gettextCatalog.getString(state.recommendation_label);
|
||||||
});
|
});
|
||||||
@ -120,13 +122,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
|
// Save expand state so the session
|
||||||
if ($sessionStorage.motionStateTableExpandState) {
|
if ($sessionStorage.motionStateTableExpandState) {
|
||||||
$scope.toggleExpandContent();
|
$scope.toggleExpandContent();
|
||||||
|
@ -174,7 +174,7 @@
|
|||||||
<div ng-if="!motion.isAmendment && motion.isAllowed('can_see_amendments')">
|
<div ng-if="!motion.isAmendment && motion.isAllowed('can_see_amendments')">
|
||||||
<h3 translate>Amendments</h3>
|
<h3 translate>Amendments</h3>
|
||||||
<a ng-if="motion.hasAmendments()" ui-sref="motions.motion.amendment-list({id: motion.id})">
|
<a ng-if="motion.hasAmendments()" ui-sref="motions.motion.amendment-list({id: motion.id})">
|
||||||
{{ motion.getAmendments().length }} <translate>Amendments</translate><br>
|
{{ motion.getAmendments().length }} {{ (motion.getAmendments().length === 1 ? 'Amendment' : 'Amendments') | translate }}<br>
|
||||||
</a>
|
</a>
|
||||||
<button ng-if="motion.isAllowed('can_create_amendment')" ng-click="newAmendment()" class="btn btn-default btn-sm">
|
<button ng-if="motion.isAllowed('can_create_amendment')" ng-click="newAmendment()" class="btn btn-default btn-sm">
|
||||||
<i class="fa fa-plus"></i>
|
<i class="fa fa-plus"></i>
|
||||||
@ -404,7 +404,7 @@
|
|||||||
<td ng-init="voteYes = poll.getVote(poll.yes, 'yes')">
|
<td ng-init="voteYes = poll.getVote(poll.yes, 'yes')">
|
||||||
<span class="result-label"><translate>Yes</translate>:</span>
|
<span class="result-label"><translate>Yes</translate>:</span>
|
||||||
<span class="result_value">
|
<span class="result_value">
|
||||||
{{ voteYes.value }} {{ voteYes.percentStr }}
|
{{ voteYes.value | number:votesPrecision }} {{ voteYes.percentStr }}
|
||||||
</span>
|
</span>
|
||||||
<div ng-if="voteYes.percentNumber">
|
<div ng-if="voteYes.percentNumber">
|
||||||
<uib-progressbar value="voteYes.percentNumber" type="success"></uib-progressbar>
|
<uib-progressbar value="voteYes.percentNumber" type="success"></uib-progressbar>
|
||||||
@ -416,7 +416,7 @@
|
|||||||
<td ng-init="voteNo = poll.getVote(poll.no, 'no')">
|
<td ng-init="voteNo = poll.getVote(poll.no, 'no')">
|
||||||
<span class="result-label"><translate>No</translate>:</span>
|
<span class="result-label"><translate>No</translate>:</span>
|
||||||
<span class="result_value" >
|
<span class="result_value" >
|
||||||
{{ voteNo.value }} {{ voteNo.percentStr }}
|
{{ voteNo.value | number:votesPrecision }} {{ voteNo.percentStr }}
|
||||||
</span>
|
</span>
|
||||||
<div ng-if="voteNo.percentNumber">
|
<div ng-if="voteNo.percentNumber">
|
||||||
<uib-progressbar value="voteNo.percentNumber" type="danger"></uib-progressbar>
|
<uib-progressbar value="voteNo.percentNumber" type="danger"></uib-progressbar>
|
||||||
@ -428,7 +428,7 @@
|
|||||||
<td ng-init="voteAbstain = poll.getVote(poll.abstain, 'abstain')">
|
<td ng-init="voteAbstain = poll.getVote(poll.abstain, 'abstain')">
|
||||||
<span class="result-label"><translate>Abstain</translate>:</span>
|
<span class="result-label"><translate>Abstain</translate>:</span>
|
||||||
<span class="result_value">
|
<span class="result_value">
|
||||||
{{ voteAbstain.value }} {{ voteAbstain.percentStr }}
|
{{ voteAbstain.value | number:votesPrecision }} {{ voteAbstain.percentStr }}
|
||||||
</span>
|
</span>
|
||||||
<div ng-if="voteAbstain.percentNumber">
|
<div ng-if="voteAbstain.percentNumber">
|
||||||
<uib-progressbar value="voteAbstain.percentNumber" type="warning"></uib-progressbar>
|
<uib-progressbar value="voteAbstain.percentNumber" type="warning"></uib-progressbar>
|
||||||
@ -440,7 +440,7 @@
|
|||||||
<td ng-init="votesValid = poll.getVote(poll.votesvalid, 'votesvalid')">
|
<td ng-init="votesValid = poll.getVote(poll.votesvalid, 'votesvalid')">
|
||||||
<span class="result-label"><translate>Valid votes</translate>:</span>
|
<span class="result-label"><translate>Valid votes</translate>:</span>
|
||||||
<span class="result_value">
|
<span class="result_value">
|
||||||
{{ votesValid.value }} {{ votesValid.percentStr }}
|
{{ votesValid.value | number:votesPrecision }} {{ votesValid.percentStr }}
|
||||||
</span>
|
</span>
|
||||||
<!-- invalid votes -->
|
<!-- invalid votes -->
|
||||||
<tr ng-if="poll.votesinvalid !== null">
|
<tr ng-if="poll.votesinvalid !== null">
|
||||||
@ -449,7 +449,7 @@
|
|||||||
<td ng-init="votesInvalid = poll.getVote(poll.votesinvalid, 'votesinvalid')">
|
<td ng-init="votesInvalid = poll.getVote(poll.votesinvalid, 'votesinvalid')">
|
||||||
<span class="result-label"><translate>Invalid votes</translate>:</span>
|
<span class="result-label"><translate>Invalid votes</translate>:</span>
|
||||||
<span class="result_value">
|
<span class="result_value">
|
||||||
{{ votesInvalid.value }} {{ votesInvalid.percentStr }}
|
{{ votesInvalid.value | number:votesPrecision }} {{ votesInvalid.percentStr }}
|
||||||
</span>
|
</span>
|
||||||
<!-- votes cast -->
|
<!-- votes cast -->
|
||||||
<tr class="total" ng-if="poll.votescast !== null">
|
<tr class="total" ng-if="poll.votescast !== null">
|
||||||
@ -458,7 +458,7 @@
|
|||||||
<td ng-init="votesCast = poll.getVote(poll.votescast, 'votescast')">
|
<td ng-init="votesCast = poll.getVote(poll.votescast, 'votescast')">
|
||||||
<span class="result-label"><translate>Votes cast</translate>:</span>
|
<span class="result-label"><translate>Votes cast</translate>:</span>
|
||||||
<span class="result_value">
|
<span class="result_value">
|
||||||
{{ votesCast.value }} {{ votesCast.percentStr }}
|
{{ votesCast.value | number:votesPrecision }} {{ votesCast.percentStr }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- majority calculation -->
|
<!-- majority calculation -->
|
||||||
@ -479,10 +479,10 @@
|
|||||||
<td>
|
<td>
|
||||||
<div os-perms="motions.can_manage">
|
<div os-perms="motions.can_manage">
|
||||||
<span class="text-success" ng-if="isReached() >= 0" translate>
|
<span class="text-success" ng-if="isReached() >= 0" translate>
|
||||||
Quorum ({{ voteYes.value - isReached() }}) reached.
|
Quorum ({{ (voteYes.value - isReached()) | number:votesPrecision }}) reached.
|
||||||
</span>
|
</span>
|
||||||
<span class="text-danger" ng-if="isReached() < 0" translate>
|
<span class="text-danger" ng-if="isReached() < 0" translate>
|
||||||
Quorum ({{ voteYes.value - isReached() }}) not reached.
|
Quorum ({{ (voteYes.value - isReached()) | number:votesPrecision }}) not reached.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -556,16 +556,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align: right;" ng-if="(change_recommendations | filter:{motion_version_id:version}:true).length > 0">
|
<div style="text-align: right;" ng-if="(change_recommendations | filter:{motion_version_id:version}:true).length > 0">
|
||||||
<button class="btn btn-default"
|
<button class="btn btn-default btn-sm" os-perms="motions.can_manage"
|
||||||
ng-bootbox-confirm="{{ 'Do you want to copy the final version to the modified final version field?' | translate }}"
|
ng-bootbox-confirm="{{ 'Do you want to copy the final version to the final print template?' | translate }}"
|
||||||
ng-bootbox-confirm-action="viewChangeRecommendations.copyToModifiedFinalVersion(motion, version)">
|
ng-bootbox-confirm-action="viewChangeRecommendations.copyToModifiedFinalVersion(motion, version)">
|
||||||
<i class="fa fa-file-text"></i>
|
<i class="fa fa-file-text"></i>
|
||||||
<translate>Copy to modified final version</translate>
|
<translate>Create final print template</translate>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="text-align: right;" ng-if="motion.state.versioning && (change_recommendations | filter:{motion_version_id:version}:true).length > 0">
|
<div style="text-align: right;" ng-if="motion.state.versioning && (change_recommendations | filter:{motion_version_id:version}:true).length > 0">
|
||||||
<button class="btn btn-default"
|
<button class="btn btn-default btn-sm"
|
||||||
ng-bootbox-confirm="{{ 'Do you want to create a new version of this motion based on this changes?' | translate }}"
|
ng-bootbox-confirm="{{ 'Do you want to create a new version of this motion based on this changes?' | translate }}"
|
||||||
ng-bootbox-confirm-action="viewChangeRecommendations.newVersionIncludingChanges(motion, version, false)">
|
ng-bootbox-confirm-action="viewChangeRecommendations.newVersionIncludingChanges(motion, version, false)">
|
||||||
<i class="fa fa-file-text"></i>
|
<i class="fa fa-file-text"></i>
|
||||||
|
@ -103,13 +103,14 @@
|
|||||||
ng-checked="viewChangeRecommendations.mode == 'agreed'">
|
ng-checked="viewChangeRecommendations.mode == 'agreed'">
|
||||||
<translate>Final version</translate>
|
<translate>Final version</translate>
|
||||||
</label>
|
</label>
|
||||||
<label class="btn btn-sm btn-default" ng-if="motion.getModifiedFinalVersion()"
|
<label class="btn btn-sm btn-default" os-perms="motions.can_manage"
|
||||||
|
ng-show="motion.getModifiedFinalVersion()"
|
||||||
ng-class="{active: (viewChangeRecommendations.mode == 'modified_agreed')}"
|
ng-class="{active: (viewChangeRecommendations.mode == 'modified_agreed')}"
|
||||||
ng-click="viewChangeRecommendations.mode = 'modified_agreed'">
|
ng-click="viewChangeRecommendations.mode = 'modified_agreed'">
|
||||||
<input type="radio" name="viewChangeRecommendations.mode" value="modified_agreed"
|
<input type="radio" name="viewChangeRecommendations.mode" value="modified_agreed"
|
||||||
ng-model="viewChangeRecommendations.mode"
|
ng-model="viewChangeRecommendations.mode"
|
||||||
ng-checked="viewChangeRecommendations.mode == 'modified_agreed'">
|
ng-checked="viewChangeRecommendations.mode == 'modified_agreed'">
|
||||||
<translate>Modified final version</translate>
|
<translate>Final print template</translate>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -141,6 +142,11 @@
|
|||||||
<i class="fa fa-check" ng-if="viewChangeRecommendations.mode == 'agreed'"></i>
|
<i class="fa fa-check" ng-if="viewChangeRecommendations.mode == 'agreed'"></i>
|
||||||
<translate>Final version</translate>
|
<translate>Final version</translate>
|
||||||
</a>
|
</a>
|
||||||
|
<li os-perms="motions.can_manage">
|
||||||
|
<a href="" ng-click="viewChangeRecommendations.mode = 'modified_agreed'">
|
||||||
|
<i class="fa fa-check" ng-if="viewChangeRecommendations.mode == 'modified_agreed'"></i>
|
||||||
|
<translate>Final print template</translate>
|
||||||
|
</a>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
<!-- Modified agreed view -->
|
<!-- Modified agreed view -->
|
||||||
<div ng-if="viewChangeRecommendations.mode == 'modified_agreed'">
|
<div ng-if="viewChangeRecommendations.mode == 'modified_agreed'">
|
||||||
|
<div style="text-align: right;">
|
||||||
|
<button class="btn btn-default btn-danger btn-sm"
|
||||||
|
ng-bootbox-confirm="{{ 'Do you want to delete the final print template?' | translate }}"
|
||||||
|
ng-bootbox-confirm-action="viewChangeRecommendations.deleteModifiedFinalVersion(motion, version)">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
<translate>Delete final print template</translate>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div id="view-modified-agreed-inline-editor" ng-bind-html="motion.getModifiedFinalVersionWithLineBreaks(version) | trusted"
|
<div id="view-modified-agreed-inline-editor" ng-bind-html="motion.getModifiedFinalVersionWithLineBreaks(version) | trusted"
|
||||||
class="motion-text motion-text-original line-numbers-{{ lineNumberMode }}"
|
class="motion-text motion-text-original line-numbers-{{ lineNumberMode }}"
|
||||||
contenteditable="{{ modifiedFinalVersionInlineEditing.isEditable }}">
|
contenteditable="{{ modifiedFinalVersionInlineEditing.isEditable }}">
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align: right;">
|
|
||||||
<button class="btn btn-default btn-danger"
|
|
||||||
ng-bootbox-confirm="{{ 'Do you want to delete the modified final version?' | translate }}"
|
|
||||||
ng-bootbox-confirm-action="viewChangeRecommendations.deleteModifiedFinalVersion(motion, version)">
|
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
<translate>Delete modified final version</translate>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="motion-save-toolbar" ng-class="{ 'visible': modifiedFinalVersionInlineEditing.active && modifiedFinalVersionInlineEditing.changed }">
|
<div class="motion-save-toolbar" ng-class="{ 'visible': modifiedFinalVersionInlineEditing.active && modifiedFinalVersionInlineEditing.changed }">
|
||||||
<div class="changed-hint" translate>The modified final version have been changed.</div>
|
<div class="changed-hint" translate>The modified final version have been changed.</div>
|
||||||
<button type="button" ng-click="modifiedFinalVersionInlineEditing.save()" class="btn btn-primary" translate>
|
<button type="button" ng-click="modifiedFinalVersionInlineEditing.save()" class="btn btn-primary" translate>
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
<td ng-init="voteYes = poll.getVote(poll.yes, 'yes')">
|
<td ng-init="voteYes = poll.getVote(poll.yes, 'yes')">
|
||||||
<span class="result_label"><translate>Yes</translate>:</span>
|
<span class="result_label"><translate>Yes</translate>:</span>
|
||||||
<span class="result_value">
|
<span class="result_value">
|
||||||
{{ voteYes.value }} {{ voteYes.percentStr }}
|
{{ voteYes.value | number:getPollVotesPrecision(poll) }} {{ voteYes.percentStr }}
|
||||||
</span>
|
</span>
|
||||||
<div ng-if="voteYes.percentNumber">
|
<div ng-if="voteYes.percentNumber">
|
||||||
<uib-progressbar value="voteYes.percentNumber" type="success"></uib-progressbar>
|
<uib-progressbar value="voteYes.percentNumber" type="success"></uib-progressbar>
|
||||||
@ -43,7 +43,7 @@
|
|||||||
<td ng-init="voteNo = poll.getVote(poll.no, 'no')">
|
<td ng-init="voteNo = poll.getVote(poll.no, 'no')">
|
||||||
<span class="result_label"><translate>No</translate>:</span>
|
<span class="result_label"><translate>No</translate>:</span>
|
||||||
<span class="result_value" >
|
<span class="result_value" >
|
||||||
{{ voteNo.value }} {{ voteNo.percentStr }}
|
{{ voteNo.value | number:getPollVotesPrecision(poll) }} {{ voteNo.percentStr }}
|
||||||
</span>
|
</span>
|
||||||
<div ng-if="voteNo.percentNumber">
|
<div ng-if="voteNo.percentNumber">
|
||||||
<uib-progressbar value="voteNo.percentNumber" type="danger"></uib-progressbar>
|
<uib-progressbar value="voteNo.percentNumber" type="danger"></uib-progressbar>
|
||||||
@ -55,7 +55,7 @@
|
|||||||
<td ng-init="voteAbstain = poll.getVote(poll.abstain, 'abstain')">
|
<td ng-init="voteAbstain = poll.getVote(poll.abstain, 'abstain')">
|
||||||
<span class="result_label"><translate>Abstain</translate>:</span>
|
<span class="result_label"><translate>Abstain</translate>:</span>
|
||||||
<span class="result_value">
|
<span class="result_value">
|
||||||
{{ voteAbstain.value }} {{ voteAbstain.percentStr }}
|
{{ voteAbstain.value | number:getPollVotesPrecision(poll) }} {{ voteAbstain.percentStr }}
|
||||||
</span>
|
</span>
|
||||||
<div ng-if="voteAbstain.percentNumber">
|
<div ng-if="voteAbstain.percentNumber">
|
||||||
<uib-progressbar value="voteAbstain.percentNumber" type="warning"></uib-progressbar>
|
<uib-progressbar value="voteAbstain.percentNumber" type="warning"></uib-progressbar>
|
||||||
|
@ -24,22 +24,9 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h3 ng-mouseover="firstStateHover=true" ng-mouseleave="firstStateHover=false">
|
<h3>
|
||||||
<translate>First state</translate>:
|
<translate>First state</translate>:
|
||||||
{{ workflow.getFirstState().name | translate }}
|
{{ workflow.getFirstState().name | translate }}
|
||||||
<span uib-dropdown>
|
|
||||||
<span id="firstStateDropdown" class="pointer" uib-dropdown-toggle>
|
|
||||||
<i class="fa fa-cog" ng-if="firstStateHover"></i>
|
|
||||||
</span>
|
|
||||||
<ul class="dropdown-menu" aria-labelledby="firstStateDropdown">
|
|
||||||
<li ng-repeat="state in workflow.states">
|
|
||||||
<a href ng-click="setFirstState(state)">
|
|
||||||
<i class="fa fa-check" ng-if="workflow.first_state === state.id"></i>
|
|
||||||
{{ state.name | translate }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</span>
|
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -53,7 +40,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="info-head small">
|
<th class="info-head small">
|
||||||
<h4 translate>Permissions</h4>
|
<h4 translate>Permissions</h4>
|
||||||
<th ng-repeat="state in workflow.states" ng-mouseover="thHover=true" ng-mouseleave="thHover=false">
|
<th ng-repeat="state in states" ng-mouseover="thHover=true" ng-mouseleave="thHover=false">
|
||||||
<span class="optional">
|
<span class="optional">
|
||||||
{{ state.name | translate }}
|
{{ state.name | translate }}
|
||||||
</span>
|
</span>
|
||||||
@ -67,7 +54,7 @@
|
|||||||
<i class="fa fa-pencil fa-lg"></i></a>
|
<i class="fa fa-pencil fa-lg"></i></a>
|
||||||
|
|
||||||
<!--delete-->
|
<!--delete-->
|
||||||
<a href="" class="text-danger" ng-if="state.id !== workflow.first_state"
|
<a href="" class="text-danger" ng-if="state.id !== workflow.first_state_id"
|
||||||
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
|
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
|
||||||
<b>{{ state.name | translate }}</b>"
|
<b>{{ state.name | translate }}</b>"
|
||||||
ng-bootbox-confirm-action="delete(state)">
|
ng-bootbox-confirm-action="delete(state)">
|
||||||
@ -82,7 +69,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<b translate>Action word</b>
|
<b translate>Action word</b>
|
||||||
</td>
|
</td>
|
||||||
<td ng-repeat="state in workflow.states" ng-mouseover="tdHover=true" ng-mouseleave="tdHover=false">
|
<td ng-repeat="state in states" ng-mouseover="tdHover=true" ng-mouseleave="tdHover=false">
|
||||||
<div class="popover-wrapper">
|
<div class="popover-wrapper">
|
||||||
<span editable-text="state.newActionWord"
|
<span editable-text="state.newActionWord"
|
||||||
onaftersave="setMember(state, 'action_word', state.newActionWord)">
|
onaftersave="setMember(state, 'action_word', state.newActionWord)">
|
||||||
@ -101,7 +88,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<b translate>Recommendation label</b>
|
<b translate>Recommendation label</b>
|
||||||
</td>
|
</td>
|
||||||
<td ng-repeat="state in workflow.states" ng-mouseover="tdHover=true" ng-mouseleave="tdHover=false">
|
<td ng-repeat="state in states" ng-mouseover="tdHover=true" ng-mouseleave="tdHover=false">
|
||||||
<div class="popover-wrapper">
|
<div class="popover-wrapper">
|
||||||
<span editable-text="state.newRecommendationLabel"
|
<span editable-text="state.newRecommendationLabel"
|
||||||
onaftersave="setMember(state, 'recommendation_label', state.newRecommendationLabel)">
|
onaftersave="setMember(state, 'recommendation_label', state.newRecommendationLabel)">
|
||||||
@ -120,7 +107,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<b>{{ member.displayName | translate }}</b>
|
<b>{{ member.displayName | translate }}</b>
|
||||||
</td>
|
</td>
|
||||||
<td ng-repeat="state in workflow.states" class="pointer"
|
<td ng-repeat="state in states" class="pointer"
|
||||||
ng-click="changeBooleanMember(state, member.name)">
|
ng-click="changeBooleanMember(state, member.name)">
|
||||||
<!-- Simulating a checkbox with FontAwesome icons. -->
|
<!-- Simulating a checkbox with FontAwesome icons. -->
|
||||||
<i class="fa"
|
<i class="fa"
|
||||||
@ -131,7 +118,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<b translate>Label color</b>
|
<b translate>Label color</b>
|
||||||
</td>
|
</td>
|
||||||
<td ng-repeat="state in workflow.states" ng-mouseover="tdHover=true" ng-mouseleave="tdHover=false">
|
<td ng-repeat="state in states" ng-mouseover="tdHover=true" ng-mouseleave="tdHover=false">
|
||||||
<span uib-dropdown>
|
<span uib-dropdown>
|
||||||
<span id="dropdownCssClass{{ state.id }}" class="pointer" uib-dropdown-toggle>
|
<span id="dropdownCssClass{{ state.id }}" class="pointer" uib-dropdown-toggle>
|
||||||
<span class="label" ng-class="'label-' + state.css_class">
|
<span class="label" ng-class="'label-' + state.css_class">
|
||||||
@ -154,7 +141,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<b translate>Required permission to see</b>
|
<b translate>Required permission to see</b>
|
||||||
</td>
|
</td>
|
||||||
<td ng-repeat="state in workflow.states" ng-mouseover="tdHover=true" ng-mouseleave="tdHover=false">
|
<td ng-repeat="state in states" ng-mouseover="tdHover=true" ng-mouseleave="tdHover=false">
|
||||||
<span uib-dropdown>
|
<span uib-dropdown>
|
||||||
<span id="dropdownPermission{{ state.id }}" class="pointer" uib-dropdown-toggle>
|
<span id="dropdownPermission{{ state.id }}" class="pointer" uib-dropdown-toggle>
|
||||||
<div class="no-overflow">
|
<div class="no-overflow">
|
||||||
@ -182,7 +169,7 @@
|
|||||||
<td>
|
<td>
|
||||||
<b translate>Next states</b>
|
<b translate>Next states</b>
|
||||||
</td>
|
</td>
|
||||||
<td ng-repeat="state in workflow.states" ng-mouseover="tdHover=true" ng-mouseleave="tdHover=false">
|
<td ng-repeat="state in states" ng-mouseover="tdHover=true" ng-mouseleave="tdHover=false">
|
||||||
<span ng-if="state.getNextStates().length === 0" class="text-muted">
|
<span ng-if="state.getNextStates().length === 0" class="text-muted">
|
||||||
—
|
—
|
||||||
</span>
|
</span>
|
||||||
@ -197,7 +184,7 @@
|
|||||||
<i class="fa fa-cog" ng-if="tdHover"></i>
|
<i class="fa fa-cog" ng-if="tdHover"></i>
|
||||||
</span>
|
</span>
|
||||||
<ul class="dropdown-menu" aria-labelledby="dropdownNextStates{{ state.id }}">
|
<ul class="dropdown-menu" aria-labelledby="dropdownNextStates{{ state.id }}">
|
||||||
<li ng-repeat="s in workflow.states">
|
<li ng-repeat="s in states">
|
||||||
<a href ng-click="clickNextStateEntry(state, s.id)">
|
<a href ng-click="clickNextStateEntry(state, s.id)">
|
||||||
<i class="fa fa-check" ng-if="state.next_states_id.indexOf(s.id) > -1"></i>
|
<i class="fa fa-check" ng-if="state.next_states_id.indexOf(s.id) > -1"></i>
|
||||||
{{ s.name | translate }}
|
{{ s.name | translate }}
|
||||||
|
@ -918,13 +918,6 @@ class WorkflowViewSet(ModelViewSet, ProtectedErrorMessageMixin):
|
|||||||
result = False
|
result = False
|
||||||
return result
|
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):
|
def destroy(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Customized view endpoint to delete a motion poll.
|
Customized view endpoint to delete a motion poll.
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import locale
|
import locale
|
||||||
|
from decimal import Decimal
|
||||||
from typing import Optional, Type
|
from typing import Optional, Type
|
||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.core.validators import MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from openslides.utils.models import MinMaxIntegerField
|
|
||||||
|
|
||||||
|
|
||||||
class BaseOption(models.Model):
|
class BaseOption(models.Model):
|
||||||
"""
|
"""
|
||||||
@ -44,7 +44,8 @@ class BaseVote(models.Model):
|
|||||||
Subclasses have to define an option field. This must be a ForeignKeyField
|
Subclasses have to define an option field. This must be a ForeignKeyField
|
||||||
to a subclass of BasePoll.
|
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)
|
value = models.CharField(max_length=255, null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -72,9 +73,12 @@ class CollectDefaultVotesMixin(models.Model):
|
|||||||
Mixin for a poll to collect the default vote values for valid votes,
|
Mixin for a poll to collect the default vote values for valid votes,
|
||||||
invalid votes and votes cast.
|
invalid votes and votes cast.
|
||||||
"""
|
"""
|
||||||
votesvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2)
|
votesvalid = models.DecimalField(null=True, blank=True, validators=[
|
||||||
votesinvalid = MinMaxIntegerField(null=True, blank=True, min_value=-2)
|
MinValueValidator(Decimal('-2'))], max_digits=15, decimal_places=6)
|
||||||
votescast = MinMaxIntegerField(null=True, blank=True, min_value=-2)
|
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:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
@ -185,7 +185,8 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides
|
|||||||
'ErrorMessage',
|
'ErrorMessage',
|
||||||
function($scope, $state, Topic, TopicForm, Agenda, Config, ErrorMessage) {
|
function($scope, $state, Topic, TopicForm, Agenda, Config, ErrorMessage) {
|
||||||
$scope.model = {
|
$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
|
// get all form fields
|
||||||
$scope.formFields = TopicForm.getFormFields(true);
|
$scope.formFields = TopicForm.getFormFields(true);
|
||||||
|
@ -1810,7 +1810,7 @@ angular.module('OpenSlidesApp.users.site', [
|
|||||||
gettext('Can see agenda');
|
gettext('Can see agenda');
|
||||||
gettext('Can manage agenda');
|
gettext('Can manage agenda');
|
||||||
gettext('Can manage list of speakers');
|
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');
|
gettext('Can put oneself on the list of speakers');
|
||||||
// assignments
|
// assignments
|
||||||
gettext('Can see elections');
|
gettext('Can see elections');
|
||||||
|
@ -34,10 +34,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer-top">
|
<div class="spacer-top">
|
||||||
|
<translate>Initial password</translate>: {{ user.default_password }}
|
||||||
<span uib-tooltip="{{ 'Initial password can not be changed.' | translate }}">
|
<span uib-tooltip="{{ 'Initial password can not be changed.' | translate }}">
|
||||||
<i class="fa fa-info-circle"></i>
|
<i class="fa fa-info-circle"></i>
|
||||||
<translate>Initial password</translate>: {{ user.default_password }}
|
</span><br>
|
||||||
</span>
|
<translate>Username</translate>: {{ user.username }}
|
||||||
<span class="pull-right spacer-right pointer" ng-click="showPassword = !showPassword">
|
<span class="pull-right spacer-right pointer" ng-click="showPassword = !showPassword">
|
||||||
<translate>Show password</translate>
|
<translate>Show password</translate>
|
||||||
<i class="fa" ng-class="showPassword ? 'fa-check-square-o' : 'fa-square-o'"></i>
|
<i class="fa" ng-class="showPassword ? 'fa-check-square-o' : 'fa-square-o'"></i>
|
||||||
|
@ -17,6 +17,7 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from rest_framework.serializers import (
|
from rest_framework.serializers import (
|
||||||
CharField,
|
CharField,
|
||||||
|
DecimalField,
|
||||||
DictField,
|
DictField,
|
||||||
Field,
|
Field,
|
||||||
FileField,
|
FileField,
|
||||||
@ -43,9 +44,9 @@ from .auth import user_to_collection_user
|
|||||||
from .collection import Collection, CollectionElement
|
from .collection import Collection, CollectionElement
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['status', 'detail_route', 'list_route', 'SimpleMetadata', 'CreateModelMixin',
|
__all__ = ['detail_route', 'DecimalField', 'list_route', 'SimpleMetadata', 'CreateModelMixin',
|
||||||
'DestroyModelMixin', 'UpdateModelMixin', 'CharField', 'DictField', 'FileField',
|
'DestroyModelMixin', 'UpdateModelMixin', 'CharField', 'DictField', 'FileField',
|
||||||
'IntegerField', 'JSONField', 'ListField', 'ListSerializer', 'RelatedField',
|
'IntegerField', 'JSONField', 'ListField', 'ListSerializer', 'status', 'RelatedField',
|
||||||
'SerializerMethodField', 'ValidationError']
|
'SerializerMethodField', 'ValidationError']
|
||||||
|
|
||||||
|
|
||||||
|
@ -1161,21 +1161,6 @@ class CreateWorkflow(TestCase):
|
|||||||
first_state = workflow.first_state
|
first_state = workflow.first_state
|
||||||
self.assertEqual(type(first_state), 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):
|
class UpdateWorkflow(TestCase):
|
||||||
"""
|
"""
|
||||||
@ -1195,38 +1180,6 @@ class UpdateWorkflow(TestCase):
|
|||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(workflow.name, 'test_name_wofi38DiWLT"8d3lwfo3')
|
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):
|
class DeleteWorkflow(TestCase):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user