diff --git a/CHANGELOG b/CHANGELOG index 3d6319273..a350f4488 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ Agenda: - Fixed wrong sorting of last speakers [#3193]. - Fixed issue when sorting a new inserted speaker [#3210]. - New permission for managing lists of speakers [#3366]. +- Fixed multiple request on creation of agenda related items [#3341]. Motions: - New export dialog [#3185]. diff --git a/openslides/agenda/signals.py b/openslides/agenda/signals.py index 7fff95ea7..f9a8187fc 100644 --- a/openslides/agenda/signals.py +++ b/openslides/agenda/signals.py @@ -14,16 +14,24 @@ def listen_to_related_object_post_save(sender, instance, created, **kwargs): Receiver function to create agenda items. It is connected to the signal django.db.models.signals.post_save during app loading. - Do not run caching and autoupdate if the instance as an attribute - skip_autoupdate (regardless of its truthy or falsy conent). + The agenda_item_update_information container may have fields like type, + parent_id, comment, duration, weight or skip_autoupdate. + + Do not run caching and autoupdate if the instance has a key + skip_autoupdate in the agenda_item_update_information container. """ if hasattr(instance, 'get_agenda_title'): if created: + attrs = {} + for attr in ('type', 'parent_id', 'comment', 'duration', 'weight'): + if instance.agenda_item_update_information.get(attr): + attrs[attr] = instance.agenda_item_update_information.get(attr) + Item.objects.create(content_object=instance, **attrs) + # If the object is created, the related_object has to be sent again. - Item.objects.create(content_object=instance) - if not hasattr(instance, 'skip_autoupdate'): + if not instance.agenda_item_update_information.get('skip_autoupdate'): inform_changed_data(instance) - elif not hasattr(instance, 'skip_autoupdate'): + elif not instance.agenda_item_update_information.get('skip_autoupdate'): # If the object has changed, then also the agenda item has to be sent. inform_changed_data(instance.agenda_item) diff --git a/openslides/agenda/static/js/agenda/base.js b/openslides/agenda/static/js/agenda/base.js index 8709164d7..fa296f8be 100644 --- a/openslides/agenda/static/js/agenda/base.js +++ b/openslides/agenda/static/js/agenda/base.js @@ -21,32 +21,6 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users']) } ]) -.factory('AgendaUpdate',[ - 'Agenda', - 'operator', - function(Agenda, operator) { - return { - saveChanges: function (item_id, changes) { - // change agenda item only if user has the permission to do that - if (operator.hasPerms('agenda.can_manage agenda.can_see_hidden_items')) { - Agenda.find(item_id).then(function (item) { - var something = false; - _.each(changes, function(change) { - if (change.value !== item[change.key]) { - item[change.key] = change.value; - something = true; - } - }); - if (something === true) { - Agenda.save(item); - } - }); - } - } - }; - } -]) - .factory('Agenda', [ '$http', 'DS', diff --git a/openslides/assignments/models.py b/openslides/assignments/models.py index ede9fe5d8..3c5ea7396 100644 --- a/openslides/assignments/models.py +++ b/openslides/assignments/models.py @@ -330,6 +330,11 @@ class Assignment(RESTModelMixin, models.Model): vote_results_dict[candidate].append(votes) return vote_results_dict + """ + Container for runtime information for agenda app (on create or update of this instance). + """ + agenda_item_update_information = {} # type: Dict[str, Any] + def get_agenda_title(self): return str(self) diff --git a/openslides/assignments/serializers.py b/openslides/assignments/serializers.py index 9feac5635..218513679 100644 --- a/openslides/assignments/serializers.py +++ b/openslides/assignments/serializers.py @@ -194,6 +194,8 @@ class AssignmentFullSerializer(ModelSerializer): """ assignment_related_users = AssignmentRelatedUserSerializer(many=True, read_only=True) polls = AssignmentAllPollSerializer(many=True, read_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) class Meta: model = Assignment @@ -207,6 +209,8 @@ class AssignmentFullSerializer(ModelSerializer): 'poll_description_default', 'polls', 'agenda_item_id', + 'agenda_type', + 'agenda_parent_id', 'tags',) validators = (posts_validator,) @@ -215,6 +219,19 @@ class AssignmentFullSerializer(ModelSerializer): data['description'] = validate_html(data['description']) return data + def create(self, validated_data): + """ + Customized create method. Set information about related agenda item + into agenda_item_update_information container. + """ + agenda_type = validated_data.pop('agenda_type', None) + agenda_parent_id = validated_data.pop('agenda_parent_id', None) + assignment = Assignment(**validated_data) + assignment.agenda_item_update_information['type'] = agenda_type + assignment.agenda_item_update_information['parent_id'] = agenda_parent_id + assignment.save() + return assignment + class AssignmentShortSerializer(AssignmentFullSerializer): """ diff --git a/openslides/assignments/static/js/assignments/site.js b/openslides/assignments/static/js/assignments/site.js index 71a870f5e..ecd053ba8 100644 --- a/openslides/assignments/static/js/assignments/site.js +++ b/openslides/assignments/static/js/assignments/site.js @@ -155,21 +155,21 @@ angular.module('OpenSlidesApp.assignments.site', [ templateOptions: { label: gettextCatalog.getString('Default comment on the ballot paper') } - }, - { - key: 'showAsAgendaItem', - type: 'checkbox', - templateOptions: { - label: gettextCatalog.getString('Show as agenda item'), - description: gettextCatalog.getString('If deactivated the election appears as internal item on agenda.') - }, - hide: !(operator.hasPerms('assignments.can_manage') && operator.hasPerms('agenda.can_manage')) }]; - // parent item + // show as agenda item + parent item if (isCreateForm) { formFields.push({ - key: 'agenda_parent_item_id', + key: 'showAsAgendaItem', + type: 'checkbox', + templateOptions: { + label: gettextCatalog.getString('Show as agenda item'), + description: gettextCatalog.getString('If deactivated the election appears as internal item on agenda.') + }, + hide: !(operator.hasPerms('assignments.can_manage') && operator.hasPerms('agenda.can_manage')) + }); + formFields.push({ + key: 'agenda_parent_id', type: 'select-single', templateOptions: { label: gettextCatalog.getString('Parent item'), @@ -627,9 +627,8 @@ angular.module('OpenSlidesApp.assignments.site', [ 'Assignment', 'AssignmentForm', 'Agenda', - 'AgendaUpdate', 'ErrorMessage', - function($scope, $state, Assignment, AssignmentForm, Agenda, AgendaUpdate, ErrorMessage) { + function($scope, $state, Assignment, AssignmentForm, Agenda, ErrorMessage) { $scope.model = {}; // set default value for open posts form field $scope.model.open_posts = 1; @@ -637,13 +636,10 @@ angular.module('OpenSlidesApp.assignments.site', [ $scope.formFields = AssignmentForm.getFormFields(true); // save assignment $scope.save = function(assignment, gotoDetailView) { + assignment.agenda_type = assignment.showAsAgendaItem ? 1 : 2; + // The attribute assignment.agenda_parent_id is set by the form, see form definition. Assignment.create(assignment).then( function (success) { - // type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item, - // see openslides.agenda.models.Item.ITEM_TYPE. - var changes = [{key: 'type', value: (assignment.showAsAgendaItem ? 1 : 2)}, - {key: 'parent_id', value: assignment.agenda_parent_item_id}]; - AgendaUpdate.saveChanges(success.agenda_item_id,changes); if (gotoDetailView) { $state.go('assignments.assignment.detail', {id: success.id}); } @@ -663,10 +659,9 @@ angular.module('OpenSlidesApp.assignments.site', [ 'Assignment', 'AssignmentForm', 'Agenda', - 'AgendaUpdate', 'assignmentId', 'ErrorMessage', - function($scope, $state, Assignment, AssignmentForm, Agenda, AgendaUpdate, assignmentId, ErrorMessage) { + function($scope, $state, Assignment, AssignmentForm, Agenda, assignmentId, ErrorMessage) { var assignment = Assignment.get(assignmentId); $scope.alert = {}; // set initial values for form model by create deep copy of assignment object @@ -674,26 +669,14 @@ angular.module('OpenSlidesApp.assignments.site', [ $scope.model = angular.copy(assignment); // get all form fields $scope.formFields = AssignmentForm.getFormFields(); - var agenda_item = Agenda.get(assignment.agenda_item_id); - for (var i = 0; i < $scope.formFields.length; i++) { - if ($scope.formFields[i].key == "showAsAgendaItem") { - // get state from agenda item (hidden/internal or agenda item) - $scope.formFields[i].defaultValue = !assignment.agenda_item.is_hidden; - } else if($scope.formFields[i].key == 'agenda_parent_item_id') { - $scope.formFields[i].defaultValue = agenda_item.parent_id; - } - } // save assignment $scope.save = function (assignment, gotoDetailView) { // inject the changed assignment (copy) object back into DS store Assignment.inject(assignment); - // save change assignment object on server + // save changed assignment object on server Assignment.save(assignment).then( function(success) { - var changes = [{key: 'type', value: (assignment.showAsAgendaItem ? 1 : 2)}, - {key: 'parent_id', value: assignment.agenda_parent_item_id}]; - AgendaUpdate.saveChanges(success.agenda_item_id,changes); if (gotoDetailView) { $state.go('assignments.assignment.detail', {id: success.id}); } diff --git a/openslides/motions/models.py b/openslides/motions/models.py index 7f792ea36..e550e5975 100644 --- a/openslides/motions/models.py +++ b/openslides/motions/models.py @@ -1,3 +1,5 @@ +from typing import Any, Dict # noqa + from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation from django.core.exceptions import ImproperlyConfigured, ValidationError @@ -628,6 +630,11 @@ class Motion(RESTModelMixin, models.Model): if self.recommendation is not None: self.set_state(self.recommendation) + """ + Container for runtime information for agenda app (on create or update of this instance). + """ + agenda_item_update_information = {} # type: Dict[str, Any] + def get_agenda_title(self): """ Return a simple title string for the agenda. @@ -895,6 +902,11 @@ class MotionBlock(RESTModelMixin, models.Model): id=self.pk) return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs) # type: ignore + """ + Container for runtime information for agenda app (on create or update of this instance). + """ + agenda_item_update_information = {} # type: Dict[str, Any] + @property def agenda_item(self): """ diff --git a/openslides/motions/serializers.py b/openslides/motions/serializers.py index e5d7bcc2d..5fe55f434 100644 --- a/openslides/motions/serializers.py +++ b/openslides/motions/serializers.py @@ -49,9 +49,25 @@ class MotionBlockSerializer(ModelSerializer): """ Serializer for motion.models.Category objects. """ + 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) + class Meta: model = MotionBlock - fields = ('id', 'title', 'agenda_item_id',) + fields = ('id', 'title', 'agenda_item_id', 'agenda_type', 'agenda_parent_id',) + + def create(self, validated_data): + """ + Customized create method. Set information about related agenda item + into agenda_item_update_information container. + """ + agenda_type = validated_data.pop('agenda_type', None) + agenda_parent_id = validated_data.pop('agenda_parent_id', None) + motion_block = MotionBlock(**validated_data) + motion_block.agenda_item_update_information['type'] = agenda_type + motion_block.agenda_item_update_information['parent_id'] = agenda_parent_id + motion_block.save() + return motion_block class StateSerializer(ModelSerializer): @@ -288,6 +304,8 @@ class MotionSerializer(ModelSerializer): required=False, validators=[validate_workflow_field], 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) class Meta: model = Motion @@ -314,6 +332,8 @@ class MotionSerializer(ModelSerializer): 'attachments', 'polls', 'agenda_item_id', + 'agenda_type', + 'agenda_parent_id', 'log_messages',) read_only_fields = ('state', 'recommendation',) # Some other fields are also read_only. See definitions above. @@ -332,6 +352,9 @@ class MotionSerializer(ModelSerializer): def create(self, validated_data): """ Customized method to create a new motion from some data. + + Set also information about related agenda item into + agenda_item_update_information container. """ motion = Motion() motion.title = validated_data['title'] @@ -344,6 +367,8 @@ class MotionSerializer(ModelSerializer): motion.comments = validated_data.get('comments') motion.parent = validated_data.get('parent') motion.reset_state(validated_data.get('workflow_id')) + 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']) diff --git a/openslides/motions/static/js/motions/motion-block.js b/openslides/motions/static/js/motions/motion-block.js index b871ecc20..2a00aadc6 100644 --- a/openslides/motions/static/js/motions/motion-block.js +++ b/openslides/motions/static/js/motions/motion-block.js @@ -69,8 +69,8 @@ angular.module('OpenSlidesApp.motions.motionBlock', []) }; }, // Get angular-formly fields. - getFormFields: function () { - return [ + getFormFields: function (isCreateForm) { + var formFields = [ { key: 'title', type: 'input', @@ -78,7 +78,11 @@ angular.module('OpenSlidesApp.motions.motionBlock', []) label: gettextCatalog.getString('Title') } }, - { + ]; + + // show as agenda item + parent item + if (isCreateForm) { + formFields.push({ key: 'showAsAgendaItem', type: 'checkbox', templateOptions: { @@ -86,18 +90,21 @@ angular.module('OpenSlidesApp.motions.motionBlock', []) description: gettextCatalog.getString('If deactivated it appears as internal item on agenda.') }, hide: !(operator.hasPerms('motions.can_manage') && operator.hasPerms('agenda.can_manage')) - }, - { - key: 'agenda_parent_item_id', + }); + formFields.push({ + key: 'agenda_parent_id', type: 'select-single', templateOptions: { label: gettextCatalog.getString('Parent item'), options: AgendaTree.getFlatTree(Agenda.getAll()), ngOptions: 'item.id as item.getListViewTitle() for item in to.options | notself : model.agenda_item_id', placeholder: gettextCatalog.getString('Select a parent item ...') - } - } - ]; + }, + hide: !operator.hasPerms('agenda.can_manage') + }); + } + + return formFields; } }; } @@ -190,24 +197,20 @@ angular.module('OpenSlidesApp.motions.motionBlock', []) '$scope', 'MotionBlock', 'MotionBlockForm', - 'AgendaUpdate', - function($scope, MotionBlock, MotionBlockForm, AgendaUpdate) { + function($scope, MotionBlock, MotionBlockForm) { // Prepare form. $scope.model = {}; $scope.model.showAsAgendaItem = true; // Get all form fields. - $scope.formFields = MotionBlockForm.getFormFields(); + $scope.formFields = MotionBlockForm.getFormFields(true); // Save form. $scope.save = function (motionBlock) { + motionBlock.agenda_type = motionBlock.showAsAgendaItem ? 1 : 2; + // The attribute motionBlock.agenda_parent_id is set by the form, see form definition. MotionBlock.create(motionBlock).then( function (success) { - // type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item, - // see openslides.agenda.models.Item.ITEM_TYPE. - var changes = [{key: 'type', value: (motionBlock.showAsAgendaItem ? 1 : 2)}, - {key: 'parent_id', value: motionBlock.agenda_parent_item_id}]; - AgendaUpdate.saveChanges(success.agenda_item_id, changes); $scope.closeThisDialog(); }, function (error) { @@ -227,11 +230,8 @@ angular.module('OpenSlidesApp.motions.motionBlock', []) '$state', 'MotionBlock', 'MotionBlockForm', - 'AgendaUpdate', 'motionBlockId', - function($scope, $state, MotionBlock, MotionBlockForm, AgendaUpdate, motionBlockId) { - // TODO: Check #2486 and remove some agenda related code. - //MotionBlock.loadRelations(motionBlock, 'agenda_item'); + function($scope, $state, MotionBlock, MotionBlockForm, motionBlockId) { $scope.alert = {}; // Prepare form. Set initial values by creating a deep copy of @@ -241,23 +241,14 @@ angular.module('OpenSlidesApp.motions.motionBlock', []) // Get all form fields. $scope.formFields = MotionBlockForm.getFormFields(); - for (var i = 0; i < $scope.formFields.length; i++) { - if ($scope.formFields[i].key == 'showAsAgendaItem') { - // Get state from agenda item (hidden/internal or agenda item). - $scope.formFields[i].defaultValue = !motionBlock.agenda_item.is_hidden; - } else if ($scope.formFields[i].key == 'agenda_parent_item_id') { - $scope.formFields[i].defaultValue = motionBlock.agenda_item.parent_id; - } - } + // Save form. $scope.save = function (motionBlock) { + // inject the changed motionBlock (copy) object back into DS store + MotionBlock.inject(motionBlock); + // save changed motionBlock object on server MotionBlock.create(motionBlock).then( function (success) { - // type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item, - // see openslides.agenda.models.Item.ITEM_TYPE. - var changes = [{key: 'type', value: (motionBlock.showAsAgendaItem ? 1 : 2)}, - {key: 'parent_id', value: motionBlock.agenda_parent_item_id}]; - AgendaUpdate.saveChanges(success.agenda_item_id,changes); $scope.closeThisDialog(); }, function (error) { diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index b06ae5421..547999e97 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -376,21 +376,21 @@ angular.module('OpenSlidesApp.motions.site', [ description: gettextCatalog.getString("Don't create a new version.") }, hide: true - }, - { - key: 'showAsAgendaItem', - type: 'checkbox', - templateOptions: { - label: gettextCatalog.getString('Show as agenda item'), - description: gettextCatalog.getString('If deactivated the motion appears as internal item on agenda.') - }, - hide: !(operator.hasPerms('motions.can_manage') && operator.hasPerms('agenda.can_manage')) }]; - // parent item + // show as agenda item + parent item if (isCreateForm) { formFields.push({ - key: 'agenda_parent_item_id', + key: 'showAsAgendaItem', + type: 'checkbox', + templateOptions: { + label: gettextCatalog.getString('Show as agenda item'), + description: gettextCatalog.getString('If deactivated the motion appears as internal item on agenda.') + }, + hide: !(operator.hasPerms('motions.can_manage') && operator.hasPerms('agenda.can_manage')) + }); + formFields.push({ + key: 'agenda_parent_id', type: 'select-single', templateOptions: { label: gettextCatalog.getString('Parent item'), @@ -1890,10 +1890,9 @@ angular.module('OpenSlidesApp.motions.site', [ 'User', 'Workflow', 'Agenda', - 'AgendaUpdate', 'ErrorMessage', function($scope, $state, gettext, gettextCatalog, operator, Motion, MotionForm, - Category, Config, Mediafile, Tag, User, Workflow, Agenda, AgendaUpdate, ErrorMessage) { + Category, Config, Mediafile, Tag, User, Workflow, Agenda, ErrorMessage) { Category.bindAll({}, $scope, 'categories'); Mediafile.bindAll({}, $scope, 'mediafiles'); Tag.bindAll({}, $scope, 'tags'); @@ -1927,13 +1926,10 @@ angular.module('OpenSlidesApp.motions.site', [ // save motion $scope.save = function (motion, gotoDetailView) { + motion.agenda_type = motion.showAsAgendaItem ? 1 : 2; + // The attribute motion.agenda_parent_id is set by the form, see form definition. Motion.create(motion).then( function(success) { - // type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item, - // see openslides.agenda.models.Item.ITEM_TYPE. - var changes = [{key: 'type', value: (motion.showAsAgendaItem ? 1 : 2)}, - {key: 'parent_id', value: motion.agenda_parent_item_id}]; - AgendaUpdate.saveChanges(success.agenda_item_id, changes); if (isAmendment || gotoDetailView) { $state.go('motions.motion.detail', {id: success.id}); } @@ -1959,13 +1955,12 @@ angular.module('OpenSlidesApp.motions.site', [ 'User', 'Workflow', 'Agenda', - 'AgendaUpdate', 'motionId', 'operator', 'ErrorMessage', 'EditingWarning', function($scope, $state, Motion, Category, Config, Mediafile, MotionForm, - Tag, User, Workflow, Agenda, AgendaUpdate, motionId, operator, ErrorMessage, + Tag, User, Workflow, Agenda, motionId, operator, ErrorMessage, EditingWarning) { Category.bindAll({}, $scope, 'categories'); Mediafile.bindAll({}, $scope, 'mediafiles'); @@ -2007,18 +2002,10 @@ angular.module('OpenSlidesApp.motions.site', [ $scope.formFields[i].hide = false; } } - if ($scope.formFields[i].key == "showAsAgendaItem" && motion.agenda_item) { - // get state from agenda item (hidden/internal or agenda item) - $scope.formFields[i].defaultValue = !motion.agenda_item.is_hidden; - } if ($scope.formFields[i].key == "workflow_id") { // get saved workflow id from state $scope.formFields[i].defaultValue = motion.state.workflow_id; } - if ($scope.formFields[i].key == "agenda_parent_item_id") { - // get current parent_id of the agenda item - $scope.formFields[i].defaultValue = motion.agenda_item.parent_id; - } } // Displaying a warning, if other users edit this motion too @@ -2029,14 +2016,9 @@ angular.module('OpenSlidesApp.motions.site', [ $scope.save = function (motion, gotoDetailView) { // inject the changed motion (copy) object back into DS store Motion.inject(motion); - // save change motion object on server + // save changed motion object on server Motion.save(motion).then( function(success) { - // type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item, - // see openslides.agenda.models.Item.ITEM_TYPE. - var changes = [{key: 'type', value: (motion.showAsAgendaItem ? 1 : 2)}, - {key: 'parent_id', value: motion.agenda_parent_item_id}]; - AgendaUpdate.saveChanges(success.agenda_item_id,changes); if (gotoDetailView) { $state.go('motions.motion.detail', {id: success.id}); } diff --git a/openslides/motions/views.py b/openslides/motions/views.py index cc4679c79..304bf304b 100644 --- a/openslides/motions/views.py +++ b/openslides/motions/views.py @@ -596,7 +596,8 @@ class CategoryViewSet(ModelViewSet): # Remove old identifiers for motion in motions: motion.identifier = None - motion.skip_autoupdate = True # This line is to skip agenda item autoupdate. See agenda/signals.py. + # This line is to skip agenda item autoupdate. See agenda/signals.py. + motion.agenda_item_update_information['skip_autoupdate'] = True motion.save(skip_autoupdate=True) # Set new identifers and change identifiers of amendments. @@ -615,7 +616,8 @@ class CategoryViewSet(ModelViewSet): obj['new_identifier'], child.identifier, count=1) - child.skip_autoupdate = True # This line is to skip agenda item autoupdate. See agenda/signals.py. + # This line is to skip agenda item autoupdate. See agenda/signals.py. + child.agenda_item_update_information['skip_autoupdate'] = True child.save(skip_autoupdate=True) instances.append(child) instances.append(child.agenda_item) diff --git a/openslides/topics/models.py b/openslides/topics/models.py index e31478c7d..1f869304a 100644 --- a/openslides/topics/models.py +++ b/openslides/topics/models.py @@ -1,3 +1,5 @@ +from typing import Any, Dict # noqa + from django.contrib.contenttypes.fields import GenericRelation from django.db import models @@ -54,6 +56,11 @@ class Topic(RESTModelMixin, models.Model): id=self.pk) return super().delete(skip_autoupdate=skip_autoupdate, *args, **kwargs) # type: ignore + """ + Container for runtime information for agenda app (on create or update of this instance). + """ + agenda_item_update_information = {} # type: Dict[str, Any] + @property def agenda_item(self): """ diff --git a/openslides/topics/serializers.py b/openslides/topics/serializers.py index d58ac369a..7529ee80e 100644 --- a/openslides/topics/serializers.py +++ b/openslides/topics/serializers.py @@ -1,4 +1,4 @@ -from openslides.utils.rest_api import ModelSerializer +from openslides.utils.rest_api import CharField, IntegerField, ModelSerializer from openslides.utils.validate import validate_html from .models import Topic @@ -8,11 +8,49 @@ class TopicSerializer(ModelSerializer): """ Serializer for core.models.Topic objects. """ + 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) + agenda_comment = CharField(write_only=True, required=False, allow_blank=True) + agenda_duration = IntegerField(write_only=True, required=False, min_value=1) + agenda_weight = IntegerField(write_only=True, required=False, min_value=1) + class Meta: model = Topic - fields = ('id', 'title', 'text', 'attachments', 'agenda_item_id') + fields = ( + 'id', + 'title', + 'text', + 'attachments', + 'agenda_item_id', + 'agenda_type', + 'agenda_parent_id', + 'agenda_comment', + 'agenda_duration', + 'agenda_weight', + ) def validate(self, data): if 'text' in data: data['text'] = validate_html(data['text']) return data + + def create(self, validated_data): + """ + Customized create method. Set information about related agenda item + into agenda_item_update_information container. + """ + agenda_type = validated_data.pop('agenda_type', None) + agenda_parent_id = validated_data.pop('agenda_parent_id', None) + agenda_comment = validated_data.pop('agenda_comment', None) + agenda_duration = validated_data.pop('agenda_duration', None) + agenda_weight = validated_data.pop('agenda_weight', None) + attachments = validated_data.pop('attachments', []) + topic = Topic(**validated_data) + topic.agenda_item_update_information['type'] = agenda_type + topic.agenda_item_update_information['parent_id'] = agenda_parent_id + topic.agenda_item_update_information['comment'] = agenda_comment + topic.agenda_item_update_information['duration'] = agenda_duration + topic.agenda_item_update_information['weight'] = agenda_weight + topic.save() + topic.attachments.add(*attachments) + return topic diff --git a/openslides/topics/static/js/topics/site.js b/openslides/topics/static/js/topics/site.js index 6bb13ba94..4be30afef 100644 --- a/openslides/topics/static/js/topics/site.js +++ b/openslides/topics/static/js/topics/site.js @@ -116,28 +116,28 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides } }); } - // show as agenda item - formFields.push({ - key: 'showAsAgendaItem', - type: 'checkbox', - templateOptions: { - label: gettextCatalog.getString('Show as agenda item'), - description: gettextCatalog.getString('If deactivated it appears as internal item on agenda.') - }, - hide: !operator.hasPerms('agenda.can_manage') - }); - // parent item + // show as agenda item + parent item if (isCreateForm) { formFields.push({ - key: 'agenda_parent_item_id', + key: 'showAsAgendaItem', + type: 'checkbox', + templateOptions: { + label: gettextCatalog.getString('Show as agenda item'), + description: gettextCatalog.getString('If deactivated it appears as internal item on agenda.') + }, + hide: !operator.hasPerms('agenda.can_manage') + }); + formFields.push({ + key: 'agenda_parent_id', type: 'select-single', templateOptions: { label: gettextCatalog.getString('Parent item'), options: AgendaTree.getFlatTree(Agenda.getAll()), ngOptions: 'item.id as item.getListViewTitle() for item in to.options | notself : model.agenda_item_id', placeholder: gettextCatalog.getString('Select a parent item ...') - } + }, + hide: !operator.hasPerms('agenda.can_manage') }); } @@ -186,9 +186,8 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides 'Topic', 'TopicForm', 'Agenda', - 'AgendaUpdate', 'ErrorMessage', - function($scope, $state, Topic, TopicForm, Agenda, AgendaUpdate, ErrorMessage) { + function($scope, $state, Topic, TopicForm, Agenda, ErrorMessage) { $scope.topic = {}; $scope.model = {}; $scope.model.showAsAgendaItem = true; @@ -196,13 +195,10 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides $scope.formFields = TopicForm.getFormFields(true); // save form $scope.save = function (topic) { + topic.agenda_type = topic.showAsAgendaItem ? 1 : 2; + // The attribute topic.agenda_parent_id is set by the form, see form definition. Topic.create(topic).then( function (success) { - // type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item, - // see openslides.agenda.models.Item.ITEM_TYPE. - var changes = [{key: 'type', value: (topic.showAsAgendaItem ? 1 : 2)}, - {key: 'parent_id', value: topic.agenda_parent_item_id}]; - AgendaUpdate.saveChanges(success.agenda_item_id,changes); $scope.closeThisDialog(); }, function (error) { $scope.alert = ErrorMessage.forAlert(error); @@ -218,10 +214,9 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides 'Topic', 'TopicForm', 'Agenda', - 'AgendaUpdate', 'topicId', 'ErrorMessage', - function($scope, $state, Topic, TopicForm, Agenda, AgendaUpdate, topicId, ErrorMessage) { + function($scope, $state, Topic, TopicForm, Agenda, topicId, ErrorMessage) { var topic = Topic.get(topicId); $scope.alert = {}; // set initial values for form model by create deep copy of topic object @@ -229,25 +224,17 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides $scope.model = angular.copy(topic); // get all form fields $scope.formFields = TopicForm.getFormFields(); - for (var i = 0; i < $scope.formFields.length; i++) { - if ($scope.formFields[i].key == "showAsAgendaItem") { - // get state from agenda item (hidden/internal or agenda item) - $scope.formFields[i].defaultValue = !topic.agenda_item.is_hidden; - } else if ($scope.formFields[i].key == "agenda_parent_item_id") { - $scope.formFields[i].defaultValue = topic.agenda_item.parent_id; - } - } + // save form $scope.save = function (topic) { - Topic.create(topic).then( + // inject the changed topic (copy) object back into DS store + Topic.inject(topic); + // save changed topic object on server + Topic.save(topic).then( function(success) { - // type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item, - // see openslides.agenda.models.Item.ITEM_TYPE. - var changes = [{key: 'type', value: (topic.showAsAgendaItem ? 1 : 2)}, - {key: 'parent_id', value: topic.agenda_parent_item_id}]; - AgendaUpdate.saveChanges(success.agenda_item_id,changes); $scope.closeThisDialog(); - }, function (error) { + }, + function (error) { // save error: revert all changes by restore // (refresh) original topic object from server Topic.refresh(topic); @@ -265,8 +252,7 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides 'Topic', 'HumanTimeConverter', 'TopicsCsvExample', - 'AgendaUpdate', - function($scope, gettext, Agenda, Topic, HumanTimeConverter, TopicsCsvExample, AgendaUpdate) { + function($scope, gettext, Agenda, Topic, HumanTimeConverter, TopicsCsvExample) { // Big TODO: Change wording from "item" to "topic". // import from textarea $scope.importByLine = function () { @@ -275,12 +261,11 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides $scope.importcounter = 0; $scope.titleItems.forEach(function(title, index) { var item = {title: title}; + item.agenda_type = 1; // The new topic is not hidden. + item.agenda_weight = 1000 + index; // TODO: create all items in bulk mode Topic.create(item).then( function(success) { - var changes = [{key: 'type', value: 1}, - {key: 'weight', value: 1000 + index}]; - AgendaUpdate.saveChanges(success.agenda_item_id, changes); $scope.importcounter++; } ); @@ -363,13 +348,12 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides $scope.csvImporting = true; angular.forEach($scope.items, function (item) { if (item.selected && !item.importerror) { + item.agenda_type = item.type; + item.agenda_comment = item.comment; + item.agenda_duration = item.duration; + item.agenda_weight = item.weight; Topic.create(item).then( function(success) { - var changes = [{key: 'duration', value: item.duration}, - {key: 'comment', value: item.comment}, - {key: 'type', value: item.type}, - {key: 'weight', value: item.weight}]; - AgendaUpdate.saveChanges(success.agenda_item_id, changes); item.imported = true; } );