diff --git a/.travis.yml b/.travis.yml
index 9a9f8ec19..24f2819b0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -27,7 +27,7 @@ script:
- node_modules/.bin/karma start --browsers PhantomJS tests/karma/karma.conf.js
- DJANGO_SETTINGS_MODULE='tests.settings' coverage run ./manage.py test tests.unit
- - coverage report --fail-under=43
+ - coverage report --fail-under=42
- DJANGO_SETTINGS_MODULE='tests.settings' coverage run ./manage.py test tests.integration
- coverage report --fail-under=73
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index dff0b5cde..9d25be287 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -8,7 +8,8 @@ Version 2.3 (unreleased)
========================
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].
Version 2.2 (2018-06-06)
diff --git a/openslides/agenda/static/templates/agenda/list-of-speakers-partial-management.html b/openslides/agenda/static/templates/agenda/list-of-speakers-partial-management.html
index 8effcedcd..5299fdea2 100644
--- a/openslides/agenda/static/templates/agenda/list-of-speakers-partial-management.html
+++ b/openslides/agenda/static/templates/agenda/list-of-speakers-partial-management.html
@@ -1,26 +1,26 @@
-
@@ -79,7 +79,7 @@
- -
+
-
{{ $index + 1 }}.
{{ speaker.user.get_full_name() }}
diff --git a/openslides/core/static/css/core/_site.scss b/openslides/core/static/css/core/_site.scss
index 124eb6d17..63c12c2e8 100644
--- a/openslides/core/static/css/core/_site.scss
+++ b/openslides/core/static/css/core/_site.scss
@@ -274,11 +274,8 @@ strong, b, th {
.meta {
- h3 {
- font-family: $font-condensed-light;
- }
-
.heading, h3 {
+ font-family: $font-condensed-light;
font-size: 22px;
line-height: 24px;
font-weight: 300;
diff --git a/openslides/motions/access_permissions.py b/openslides/motions/access_permissions.py
index 013f8ab9f..57ccba291 100644
--- a/openslides/motions/access_permissions.py
+++ b/openslides/motions/access_permissions.py
@@ -43,7 +43,8 @@ class MotionAccessPermissions(BaseAccessPermissions):
for full in full_data:
# Check if user is submitter of this motion.
if isinstance(user, CollectionElement):
- is_submitter = user.get_full_data()['id'] in full.get('submitters_id', [])
+ is_submitter = user.get_full_data()['id'] in [
+ submitter['user_id'] for submitter in full.get('submitters', [])]
else:
# Anonymous users can not be submitters.
is_submitter = False
diff --git a/openslides/motions/migrations/0006_submitter_model.py b/openslides/motions/migrations/0006_submitter_model.py
new file mode 100644
index 000000000..dc890b5d2
--- /dev/null
+++ b/openslides/motions/migrations/0006_submitter_model.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.8 on 2018-02-09 07:18
+from __future__ import unicode_literals
+
+import django.db.models.deletion
+from django.conf import settings
+from django.contrib.auth.models import AnonymousUser
+from django.db import migrations, models
+
+import openslides.utils.models
+
+
+def move_submitters_to_own_model(apps, schema_editor):
+ Motion = apps.get_model('motions', 'Motion')
+ Submitter = apps.get_model('motions', 'Submitter')
+
+ for motion in Motion.objects.all():
+ weight = 0
+ for user in motion.submittersOld.all():
+ # We cannot use the add method here, so do it manually...
+ if Submitter.objects.filter(user=user, motion=motion).exists():
+ continue # The user is already a submitter. Skip this duplicate.
+ if isinstance(user, AnonymousUser):
+ continue # Skip the anonymous
+
+ submitter = Submitter(user=user, motion=motion, weight=weight)
+ submitter.save(force_insert=True)
+ weight += 1
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('motions', '0005_auto_20180202_1318'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='motion',
+ old_name='submitters',
+ new_name='submittersOld',
+ ),
+ migrations.CreateModel(
+ name='Submitter',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('weight', models.IntegerField(null=True)),
+ ],
+ options={
+ 'default_permissions': (),
+ },
+ bases=(openslides.utils.models.RESTModelMixin, models.Model),
+ ),
+ migrations.AddField(
+ model_name='submitter',
+ name='motion',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submitters', to='motions.Motion'),
+ ),
+ migrations.AddField(
+ model_name='submitter',
+ name='user',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.RunPython(
+ move_submitters_to_own_model
+ ),
+ migrations.RemoveField(
+ model_name='motion',
+ name='submittersOld',
+ ),
+ ]
diff --git a/openslides/motions/models.py b/openslides/motions/models.py
index 0f5fc73c0..afca64661 100644
--- a/openslides/motions/models.py
+++ b/openslides/motions/models.py
@@ -1,6 +1,7 @@
from typing import Any, Dict # noqa
from django.conf import settings
+from django.contrib.auth.models import AnonymousUser
from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.db import IntegrityError, models, transaction
@@ -21,6 +22,7 @@ from openslides.poll.models import (
CollectDefaultVotesMixin,
)
from openslides.utils.autoupdate import inform_changed_data
+from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.models import RESTModelMixin
from .access_permissions import (
@@ -157,11 +159,6 @@ class Motion(RESTModelMixin, models.Model):
Tags to categorise motions.
"""
- submitters = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='motion_submitters', blank=True)
- """
- Users who submit this motion.
- """
-
supporters = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='motion_supporters', blank=True)
"""
Users who support this motion.
@@ -548,7 +545,7 @@ class Motion(RESTModelMixin, models.Model):
"""
Returns True if user is a submitter of this motion, else False.
"""
- return user in self.submitters.all()
+ return self.submitters.filter(user=user).exists()
def is_supporter(self, user):
"""
@@ -706,6 +703,69 @@ class Motion(RESTModelMixin, models.Model):
yield from amendment.get_amendments_deep()
+class SubmitterManager(models.Manager):
+ """
+ Manager for Submitter model. Provides a customized add method.
+ """
+ def add(self, user, motion, skip_autoupdate=False):
+ """
+ Customized manager method to prevent anonymous users to be a
+ submitter and that someone is not twice a submitter. Cares also
+ for the initial sorting of the submitters.
+ """
+ if self.filter(user=user, motion=motion).exists():
+ raise OpenSlidesError(
+ _('{user} is already a submitter.').format(user=user))
+ if isinstance(user, AnonymousUser):
+ raise OpenSlidesError(
+ _('An anonymous user can not be a submitter.'))
+ weight = (self.filter(motion=motion).aggregate(
+ models.Max('weight'))['weight__max'] or 0)
+ submitter = self.model(user=user, motion=motion, weight=weight + 1)
+ submitter.save(force_insert=True, skip_autoupdate=skip_autoupdate)
+ return submitter
+
+
+class Submitter(RESTModelMixin, models.Model):
+ """
+ M2M Model for submitters.
+ """
+
+ objects = SubmitterManager()
+ """
+ Use custom Manager.
+ """
+
+ user = models.ForeignKey(
+ settings.AUTH_USER_MODEL,
+ on_delete=models.CASCADE)
+ """
+ ForeignKey to the user who is the submitter.
+ """
+
+ motion = models.ForeignKey(
+ Motion,
+ on_delete=models.CASCADE,
+ related_name='submitters')
+ """
+ ForeignKey to the motion.
+ """
+
+ weight = models.IntegerField(null=True)
+
+ class Meta:
+ default_permissions = ()
+
+ def __str__(self):
+ return str(self.user)
+
+ def get_root_rest_element(self):
+ """
+ Returns the motion to this instance which is the root REST element.
+ """
+ return self.motion
+
+
class MotionVersion(RESTModelMixin, models.Model):
"""
A MotionVersion object saves some date of the motion.
diff --git a/openslides/motions/projector.py b/openslides/motions/projector.py
index bbc02575f..68d9e9073 100644
--- a/openslides/motions/projector.py
+++ b/openslides/motions/projector.py
@@ -29,7 +29,8 @@ class MotionSlide(ProjectorElement):
yield motion.agenda_item
yield motion.state.workflow
yield from self.required_motions_for_state_and_recommendation(motion)
- yield from motion.submitters.all()
+ for submitter in motion.submitters.all():
+ yield submitter.user
yield from motion.supporters.all()
yield from MotionChangeRecommendation.objects.filter(motion_version=motion.get_active_version().id)
diff --git a/openslides/motions/serializers.py b/openslides/motions/serializers.py
index 0dcdd4031..da929765b 100644
--- a/openslides/motions/serializers.py
+++ b/openslides/motions/serializers.py
@@ -24,6 +24,7 @@ from .models import (
MotionPoll,
MotionVersion,
State,
+ Submitter,
Workflow,
)
@@ -290,6 +291,20 @@ class MotionChangeRecommendationSerializer(ModelSerializer):
return data
+class SubmitterSerializer(ModelSerializer):
+ """
+ Serializer for motion.models.Submitter objects.
+ """
+ class Meta:
+ model = Submitter
+ fields = (
+ 'id',
+ 'user',
+ 'motion',
+ 'weight',
+ )
+
+
class MotionSerializer(ModelSerializer):
"""
Serializer for motion.models.Motion objects.
@@ -310,6 +325,7 @@ class MotionSerializer(ModelSerializer):
write_only=True)
agenda_type = IntegerField(write_only=True, required=False, min_value=1, max_value=2)
agenda_parent_id = IntegerField(write_only=True, required=False, min_value=1)
+ submitters = SubmitterSerializer(many=True, read_only=True)
class Meta:
model = Motion
@@ -374,10 +390,6 @@ class MotionSerializer(ModelSerializer):
motion.agenda_item_update_information['type'] = validated_data.get('agenda_type')
motion.agenda_item_update_information['parent_id'] = validated_data.get('agenda_parent_id')
motion.save()
- if validated_data.get('submitters'):
- motion.submitters.add(*validated_data['submitters'])
- elif validated_data['request_user'].is_authenticated():
- motion.submitters.add(validated_data['request_user'])
motion.supporters.add(*validated_data.get('supporters', []))
motion.attachments.add(*validated_data.get('attachments', []))
motion.tags.add(*validated_data.get('tags', []))
diff --git a/openslides/motions/signals.py b/openslides/motions/signals.py
index 79303177a..4bc838522 100644
--- a/openslides/motions/signals.py
+++ b/openslides/motions/signals.py
@@ -130,6 +130,7 @@ def required_users(sender, request_user, **kwargs):
if has_perm(request_user, 'motions.can_see'):
for motion_collection_element in Collection(Motion.get_collection_string()).element_generator():
full_data = motion_collection_element.get_full_data()
- submitters_supporters.update(full_data['submitters_id'])
+ submitters_supporters.update(
+ [submitter['user_id'] for submitter in full_data['submitters']])
submitters_supporters.update(full_data['supporters_id'])
return submitters_supporters
diff --git a/openslides/motions/static/js/motions/base.js b/openslides/motions/static/js/motions/base.js
index 3f868e113..93de52069 100644
--- a/openslides/motions/static/js/motions/base.js
+++ b/openslides/motions/static/js/motions/base.js
@@ -209,6 +209,22 @@ angular.module('OpenSlidesApp.motions', [
}
])
+.factory('Submitter', [
+ 'DS',
+ function (DS) {
+ return DS.defineResource({
+ name: 'motions/submitter',
+ relations: {
+ belongsTo: {
+ 'users/user': {
+ localField: 'user',
+ localKey: 'user_id',
+ }
+ }
+ }
+ });
+ }
+])
.factory('Motion', [
'DS',
@@ -571,6 +587,7 @@ angular.module('OpenSlidesApp.motions', [
* There are the following possible actions.
* - see
* - update
+ * - update_submitters
* - delete
* - create_poll
* - support
@@ -604,6 +621,8 @@ angular.module('OpenSlidesApp.motions', [
this.state.allow_submitter_edit
)
);
+ case 'update_submitters':
+ return operator.hasPerms('motions.can_manage');
case 'delete':
return (
operator.hasPerms('motions.can_manage') ||
@@ -755,20 +774,18 @@ angular.module('OpenSlidesApp.motions', [
localField: 'attachments',
localKeys: 'attachments_id',
},
- 'users/user': [
- {
- localField: 'submitters',
- localKeys: 'submitters_id',
- },
- {
- localField: 'supporters',
- localKeys: 'supporters_id',
- }
- ],
+ 'users/user': {
+ localField: 'supporters',
+ localKeys: 'supporters_id',
+ },
'motions/motion-poll': {
localField: 'polls',
foreignKey: 'motion_id',
- }
+ },
+ 'motions/submitter': {
+ localField: 'submitters',
+ foreignKey: 'motion_id',
+ },
},
hasOne: {
'motions/workflowstate': [
@@ -987,7 +1004,8 @@ angular.module('OpenSlidesApp.motions', [
'Category',
'Workflow',
'MotionChangeRecommendation',
- function(Motion, Category, Workflow, MotionChangeRecommendation) {}
+ 'Submitter',
+ function(Motion, Category, Workflow, MotionChangeRecommendation, Submitter) {}
])
diff --git a/openslides/motions/static/js/motions/csv.js b/openslides/motions/static/js/motions/csv.js
index c839498ae..756552864 100644
--- a/openslides/motions/static/js/motions/csv.js
+++ b/openslides/motions/static/js/motions/csv.js
@@ -5,11 +5,12 @@
angular.module('OpenSlidesApp.motions.csv', [])
.factory('MotionCsvExport', [
+ '$filter',
'gettextCatalog',
'Config',
'CsvDownload',
'lineNumberingService',
- function (gettextCatalog, Config, CsvDownload, lineNumberingService) {
+ function ($filter, gettextCatalog, Config, CsvDownload, lineNumberingService) {
var makeHeaderline = function (params) {
var headerline = ['Identifier', 'Title'];
if (params.include.text) {
@@ -77,8 +78,12 @@ angular.module('OpenSlidesApp.motions.csv', [])
// Submitters
if (params.include.submitters) {
var submitters = [];
- angular.forEach(motion.submitters, function(user) {
- var user_short_name = [user.title, user.first_name, user.last_name].join(' ').trim();
+ _.forEach($filter('orderBy')(motion.submitters, 'weight'), function (user) {
+ var user_short_name = [
+ user.user.title,
+ user.user.first_name,
+ user.user.last_name
+ ].join(' ').trim();
submitters.push(user_short_name);
});
row.push('"' + submitters.join('; ') + '"');
diff --git a/openslides/motions/static/js/motions/docx.js b/openslides/motions/static/js/motions/docx.js
index 7d0baa8e4..eeb66a263 100644
--- a/openslides/motions/static/js/motions/docx.js
+++ b/openslides/motions/static/js/motions/docx.js
@@ -7,6 +7,7 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
.factory('MotionDocxExport', [
'$http',
'$q',
+ '$filter',
'operator',
'Config',
'Category',
@@ -15,8 +16,8 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
'lineNumberingService',
'Html2DocxConverter',
'MotionComment',
- function ($http, $q, operator, Config, Category, gettextCatalog, FileSaver, lineNumberingService,
- Html2DocxConverter, MotionComment) {
+ function ($http, $q, $filter, operator, Config, Category, gettextCatalog,
+ FileSaver, lineNumberingService, Html2DocxConverter, MotionComment) {
var PAGEBREAK = '';
@@ -116,9 +117,11 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
id: motion.id,
identifier: motion.identifier || '',
title: title,
- submitters: params.include.submitters ? _.map(motion.submitters, function (submitter) {
- return submitter.get_full_name();
- }).join(', ') : '',
+ submitters: params.include.submitters ? _.map(
+ $filter('orderBy')(motion.submitters, 'weight'), function (submitter) {
+ return submitter.user.get_full_name();
+ }
+ ).join(', ') : '',
status: motion.getStateName(),
// Miscellaneous stuff
preamble: gettextCatalog.getString(Config.get('motions_preamble').value),
diff --git a/openslides/motions/static/js/motions/pdf.js b/openslides/motions/static/js/motions/pdf.js
index 1b51fb797..4364272e7 100644
--- a/openslides/motions/static/js/motions/pdf.js
+++ b/openslides/motions/static/js/motions/pdf.js
@@ -6,6 +6,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
.factory('MotionContentProvider', [
'$q',
+ '$filter',
'operator',
'gettextCatalog',
'PDFLayout',
@@ -17,7 +18,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
'Motion',
'MotionComment',
'OpenSlidesSettings',
- function($q, operator, gettextCatalog, PDFLayout, PdfMakeConverter, ImageConverter,
+ function($q, $filter, operator, gettextCatalog, PDFLayout, PdfMakeConverter, ImageConverter,
HTMLValidizer, Category, Config, Motion, MotionComment, OpenSlidesSettings) {
/**
* Provides the content as JS objects for Motions in pdfMake context
@@ -86,9 +87,11 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
var metaTableBody = [];
// submitters
- var submitters = _.map(motion.submitters, function (submitter) {
- return submitter.get_full_name();
- }).join(', ');
+ var submitters = _.map(
+ $filter('orderBy')(motion.submitters, 'weight'), function (submitter) {
+ return submitter.user.get_full_name();
+ }
+ ).join(', ');
if (params.include.submitters) {
metaTableBody.push([
{
diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js
index e04173578..14dc9786c 100644
--- a/openslides/motions/static/js/motions/site.js
+++ b/openslides/motions/static/js/motions/site.js
@@ -89,6 +89,19 @@ angular.module('OpenSlidesApp.motions.site', [
}
]
})
+ .state('motions.motion.submitters', {
+ url: '/submitters/{id:int}',
+ controller: 'MotionSubmitterCtrl',
+ resolve: {
+ motionId: ['$stateParams', function($stateParams) {
+ return $stateParams.id;
+ }],
+ },
+ data: {
+ title: gettext('Submitters'),
+ basePerm: 'motions.can_manage',
+ },
+ })
.state('motions.motion.import', {
url: '/import',
controller: 'MotionImportCtrl',
@@ -388,67 +401,73 @@ angular.module('OpenSlidesApp.motions.site', [
getFormFields: function (isCreateForm) {
var workflows = Workflow.getAll();
var images = Mediafile.getAllImages();
- var formFields = [
- {
+ var formFields = [];
+ formFields.push({
key: 'identifier',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Identifier')
},
hide: true
- },
- {
- key: 'submitters_id',
- type: 'select-multiple',
- templateOptions: {
- label: gettextCatalog.getString('Submitters'),
- options: User.getAll(),
- ngOptions: 'option.id as option.full_name for option in to.options',
- placeholder: gettextCatalog.getString('Select or search a submitter ...')
+ });
+
+ if (isCreateForm) {
+ formFields.push({
+ key: 'submitters_id',
+ type: 'select-multiple',
+ templateOptions: {
+ label: gettextCatalog.getString('Submitters'),
+ options: User.getAll(),
+ ngOptions: 'option.id as option.full_name for option in to.options',
+ placeholder: gettextCatalog.getString('Select or search a submitter ...'),
+ },
+ hide: !operator.hasPerms('motions.can_manage')
+ });
+ }
+
+ formFields = formFields.concat([
+ {
+ key: 'title',
+ type: 'input',
+ templateOptions: {
+ label: gettextCatalog.getString('Title'),
+ required: true
+ }
},
- hide: !operator.hasPerms('motions.can_manage')
- },
- {
- key: 'title',
- type: 'input',
- templateOptions: {
- label: gettextCatalog.getString('Title'),
- required: true
+ {
+ template: '
' + Config.translate(Config.get('motions_preamble').value) + '
'
+ },
+ {
+ key: 'text',
+ type: 'editor',
+ templateOptions: {
+ label: gettextCatalog.getString('Text'),
+ required: true
+ },
+ data: {
+ ckeditorOptions: Editor.getOptions()
+ }
+ },
+ {
+ key: 'reason',
+ type: 'editor',
+ templateOptions: {
+ label: gettextCatalog.getString('Reason'),
+ },
+ data: {
+ ckeditorOptions: Editor.getOptions()
+ }
+ },
+ {
+ key: 'disable_versioning',
+ type: 'checkbox',
+ templateOptions: {
+ label: gettextCatalog.getString('Trivial change'),
+ description: gettextCatalog.getString("Don't create a new version.")
+ },
+ hide: true
}
- },
- {
- template: '' + Config.translate(Config.get('motions_preamble').value) + '
'
- },
- {
- key: 'text',
- type: 'editor',
- templateOptions: {
- label: gettextCatalog.getString('Text'),
- required: true
- },
- data: {
- ckeditorOptions: Editor.getOptions()
- }
- },
- {
- key: 'reason',
- type: 'editor',
- templateOptions: {
- label: gettextCatalog.getString('Reason'),
- },
- data: {
- ckeditorOptions: Editor.getOptions()
- }
- },
- {
- key: 'disable_versioning',
- type: 'checkbox',
- templateOptions: {
- label: gettextCatalog.getString('Trivial change'),
- description: gettextCatalog.getString("Don't create a new version.")
- },
- hide: true
- }];
+ ]);
// show as agenda item + parent item
if (isCreateForm) {
@@ -1192,7 +1211,7 @@ angular.module('OpenSlidesApp.motions.site', [
];
$scope.filter.propertyDict = {
'submitters': function (submitter) {
- return submitter.get_short_name();
+ return submitter.user.get_short_name();
},
'supporters': function (supporter) {
return supporter.get_short_name();
@@ -1235,7 +1254,7 @@ angular.module('OpenSlidesApp.motions.site', [
display_name: gettext('Identifier')},
{name: 'getTitle()',
display_name: gettext('Title')},
- {name: 'submitters[0].get_short_name()',
+ {name: 'submitters[0].user.get_short_name()',
display_name: gettext('Submitters')},
{name: 'category.' + Config.get('motions_export_category_sorting').value,
display_name: gettext('Category')},
@@ -2117,7 +2136,7 @@ angular.module('OpenSlidesApp.motions.site', [
'operator',
'ErrorMessage',
'EditingWarning',
- function($scope, $state, Motion, Category, Config, Mediafile, MotionForm,
+ function ($scope, $state, Motion, Category, Config, Mediafile, MotionForm,
Tag, User, Workflow, Agenda, motionId, operator, ErrorMessage,
EditingWarning) {
Category.bindAll({}, $scope, 'categories');
@@ -2176,7 +2195,7 @@ angular.module('OpenSlidesApp.motions.site', [
Motion.inject(motion);
// save changed motion object on server
Motion.save(motion).then(
- function(success) {
+ function (success) {
if (gotoDetailView) {
$state.go('motions.motion.detail', {id: success.id});
}
@@ -2201,7 +2220,7 @@ angular.module('OpenSlidesApp.motions.site', [
'motionpollId',
'voteNumber',
'ErrorMessage',
- function($scope, gettextCatalog, MotionPoll, MotionPollForm, motionpollId,
+ function ($scope, gettextCatalog, MotionPoll, MotionPollForm, motionpollId,
voteNumber, ErrorMessage) {
// set initial values for form model by create deep copy of motionpoll object
// so detail view is not updated while editing poll
@@ -2220,16 +2239,76 @@ angular.module('OpenSlidesApp.motions.site', [
votesinvalid: poll.votesinvalid,
votescast: poll.votescast
})
- .then(function(success) {
+ .then(function (success) {
$scope.alert.show = false;
$scope.closeThisDialog();
- }, function(error) {
+ }, function (error) {
$scope.alert = ErrorMessage.forAlert(error);
});
};
}
])
+.controller('MotionSubmitterCtrl', [
+ '$scope',
+ '$filter',
+ '$http',
+ 'User',
+ 'Motion',
+ 'motionId',
+ 'ErrorMessage',
+ function ($scope, $filter, $http, User, Motion, motionId, ErrorMessage) {
+ User.bindAll({}, $scope, 'users');
+ $scope.submitterSelectBox = {};
+ $scope.alert = {};
+
+ $scope.$watch(function () {
+ return Motion.lastModified(motionId);
+ }, function () {
+ $scope.motion = Motion.get(motionId);
+ $scope.submitters = $filter('orderBy')($scope.motion.submitters, 'weight');
+ });
+
+ $scope.addSubmitter = function (userId) {
+ $scope.submitterSelectBox = {};
+ $http.post('/rest/motions/motion/' + $scope.motion.id + '/manage_submitters/', {
+ 'user': userId
+ }).then(
+ function (success) {
+ $scope.alert.show = false;
+ }, function (error) {
+ $scope.alert = ErrorMessage.forAlert(error);
+ }
+ );
+ };
+
+ $scope.removeSubmitter = function (userId) {
+ $http.delete('/rest/motions/motion/' + $scope.motion.id + '/manage_submitters/', {
+ headers: {'Content-Type': 'application/json'},
+ data: JSON.stringify({user: userId})
+ }).then(
+ function (success) {
+ $scope.alert.show = false;
+ }, function (error) {
+ $scope.alert = ErrorMessage.forAlert(error);
+ }
+ );
+ };
+
+ // save reordered list of submitters
+ $scope.treeOptions = {
+ dropped: function (event) {
+ var submitterIds = _.map($scope.submitters, function (submitter) {
+ return submitter.id;
+ });
+ $http.post('/rest/motions/motion/' + $scope.motion.id + '/sort_submitters/', {
+ submitters: submitterIds,
+ });
+ }
+ };
+ }
+])
+
.controller('MotionImportCtrl', [
'$scope',
'$q',
@@ -2239,7 +2318,7 @@ angular.module('OpenSlidesApp.motions.site', [
'MotionBlock',
'User',
'MotionCsvExport',
- function($scope, $q, gettext, Category, Motion, MotionBlock, User, MotionCsvExport) {
+ function ($scope, $q, gettext, Category, Motion, MotionBlock, User, MotionCsvExport) {
// set initial data for csv import
$scope.motions = [];
@@ -2295,7 +2374,7 @@ angular.module('OpenSlidesApp.motions.site', [
}
// submitter
if (motion.submitter && motion.submitter !== '') {
- angular.forEach(User.getAll(), function (user) {
+ _.forEach(User.getAll(), function (user) {
var user_short_name = [user.title, user.first_name, user.last_name].join(' ').trim();
if (user_short_name == motion.submitter.trim()) {
motion.submitters_id = [user.id];
diff --git a/openslides/motions/static/templates/motions/motion-detail.html b/openslides/motions/static/templates/motions/motion-detail.html
index f365ffa30..fab9b04e2 100644
--- a/openslides/motions/static/templates/motions/motion-detail.html
+++ b/openslides/motions/static/templates/motions/motion-detail.html
@@ -125,9 +125,15 @@
-
Submitters
-
- {{ submitter.get_full_name() }}
+
Submitters
+
+ Submitters
+
+
+
+
+
+ {{ submitter.user.get_full_name() }}
@@ -211,7 +217,7 @@
- {{ config('motions_recommendations_by') }}
+ {{ config('motions_recommendations_by') | translate }}