Added view to follow recommendations.
for all motions of a motion block
This commit is contained in:
parent
0270c31b32
commit
20f8875dcd
@ -177,12 +177,12 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
|
||||
// Split up state name
|
||||
// example: "motions.motion.detail.update" -> ['motions', 'motion', 'detail', 'update']
|
||||
var patterns = state.name.split('.')
|
||||
var patterns = state.name.split('.');
|
||||
|
||||
// set app and module name from state
|
||||
// - appName: patterns[0] (e.g. "motions")
|
||||
// - moduleNames: patterns without first element (e.g. ["motion", "detail", "update"])
|
||||
var appName = ''
|
||||
var appName = '';
|
||||
var moduleName = '';
|
||||
var moduleNames = [];
|
||||
if (patterns.length > 0) {
|
||||
@ -194,7 +194,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
// example: ["motionBlock", "detail"] -> ["motion-block", "detail"]
|
||||
for (var i = 0; i < moduleNames.length; i++) {
|
||||
moduleNames[i] = moduleNames[i].replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase();
|
||||
};
|
||||
}
|
||||
|
||||
// use special templateUrl for create and update view
|
||||
// example: ["motion", "detail", "update"] -> "motion-form"
|
||||
@ -1436,7 +1436,11 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
}
|
||||
])
|
||||
|
||||
.filter("toArray", function(){
|
||||
.filter('toArray', function(){
|
||||
/*
|
||||
* Transforms an object to an array. Items of the array are the values of
|
||||
* the object elements.
|
||||
*/
|
||||
return function(obj) {
|
||||
var result = [];
|
||||
angular.forEach(obj, function(val, key) {
|
||||
|
@ -17,6 +17,7 @@ from openslides.poll.models import (
|
||||
BaseVote,
|
||||
CollectDefaultVotesMixin,
|
||||
)
|
||||
from openslides.utils.autoupdate import inform_changed_data
|
||||
from openslides.utils.models import RESTModelMixin
|
||||
from openslides.utils.search import user_name_helper
|
||||
|
||||
@ -192,7 +193,7 @@ class Motion(RESTModelMixin, models.Model):
|
||||
return self.title
|
||||
|
||||
# TODO: Use transaction
|
||||
def save(self, use_version=None, *args, **kwargs):
|
||||
def save(self, use_version=None, skip_autoupdate=False, *args, **kwargs):
|
||||
"""
|
||||
Save the motion.
|
||||
|
||||
@ -225,14 +226,19 @@ class Motion(RESTModelMixin, models.Model):
|
||||
if not self.identifier and isinstance(self.identifier, str):
|
||||
self.identifier = None
|
||||
|
||||
super(Motion, self).save(*args, **kwargs)
|
||||
# Always skip autoupdate. Maybe we run it later in this method.
|
||||
super(Motion, self).save(skip_autoupdate=True, *args, **kwargs)
|
||||
|
||||
if 'update_fields' in kwargs:
|
||||
# Do not save the version data if only some motion fields are updated.
|
||||
if not skip_autoupdate:
|
||||
inform_changed_data(self)
|
||||
return
|
||||
|
||||
if use_version is False:
|
||||
# We do not need to save the version.
|
||||
if not skip_autoupdate:
|
||||
inform_changed_data(self)
|
||||
return
|
||||
elif use_version is None:
|
||||
use_version = self.get_last_version()
|
||||
@ -249,6 +255,8 @@ class Motion(RESTModelMixin, models.Model):
|
||||
if use_version.id is None:
|
||||
if not self.version_data_changed(use_version):
|
||||
# We do not need to save the version.
|
||||
if not skip_autoupdate:
|
||||
inform_changed_data(self)
|
||||
return
|
||||
version_number = self.versions.aggregate(Max('version_number'))['version_number__max'] or 0
|
||||
use_version.version_number = version_number + 1
|
||||
@ -256,16 +264,22 @@ class Motion(RESTModelMixin, models.Model):
|
||||
# Necessary line if the version was set before the motion got an id.
|
||||
use_version.motion = use_version.motion
|
||||
|
||||
use_version.save()
|
||||
# Always skip autoupdate. Maybe we run it later in this method.
|
||||
use_version.save(skip_autoupdate=True)
|
||||
|
||||
# Set the active version of this motion. This has to be done after the
|
||||
# version is saved in the database.
|
||||
# TODO: Move parts of these last lines of code outside the save method
|
||||
# when other versions than the last ones should be edited later on.
|
||||
# when other versions than the last one should be edited later on.
|
||||
if self.active_version is None or not self.state.leave_old_version_active:
|
||||
# TODO: Don't call this if it was not a new version
|
||||
self.active_version = use_version
|
||||
self.save(update_fields=['active_version'])
|
||||
# Always skip autoupdate. Maybe we run it later in this method.
|
||||
self.save(update_fields=['active_version'], skip_autoupdate=True)
|
||||
|
||||
# Finally run autoupdate if it is not skipped by caller.
|
||||
if not skip_autoupdate:
|
||||
inform_changed_data(self)
|
||||
|
||||
def version_data_changed(self, version):
|
||||
"""
|
||||
@ -530,6 +544,13 @@ class Motion(RESTModelMixin, models.Model):
|
||||
recommendation = State.objects.get(pk=recommendation)
|
||||
self.recommendation = recommendation
|
||||
|
||||
def follow_recommendation(self):
|
||||
"""
|
||||
Set the state of this motion to its recommendation.
|
||||
"""
|
||||
if self.recommendation is not None:
|
||||
self.set_state(self.recommendation)
|
||||
|
||||
def get_agenda_title(self):
|
||||
"""
|
||||
Return a simple title string for the agenda.
|
||||
@ -624,7 +645,7 @@ class Motion(RESTModelMixin, models.Model):
|
||||
|
||||
return actions
|
||||
|
||||
def write_log(self, message_list, person=None):
|
||||
def write_log(self, message_list, person=None, skip_autoupdate=False):
|
||||
"""
|
||||
Write a log message.
|
||||
|
||||
@ -633,7 +654,8 @@ class Motion(RESTModelMixin, models.Model):
|
||||
"""
|
||||
if person and not person.is_authenticated():
|
||||
person = None
|
||||
MotionLog.objects.create(motion=self, message_list=message_list, person=person)
|
||||
motion_log = MotionLog(motion=self, message_list=message_list, person=person)
|
||||
motion_log.save(skip_autoupdate=skip_autoupdate)
|
||||
|
||||
def is_amendment(self):
|
||||
"""
|
||||
|
@ -127,6 +127,7 @@ angular.module('OpenSlidesApp.motions.motionBlock', [])
|
||||
|
||||
.controller('MotionBlockDetailCtrl', [
|
||||
'$scope',
|
||||
'$http',
|
||||
'ngDialog',
|
||||
'Motion',
|
||||
'MotionBlockForm',
|
||||
@ -134,7 +135,7 @@ angular.module('OpenSlidesApp.motions.motionBlock', [])
|
||||
'motionBlock',
|
||||
'Projector',
|
||||
'ProjectionDefault',
|
||||
function($scope, ngDialog, Motion, MotionBlockForm, MotionBlock, motionBlock, Projector, ProjectionDefault) {
|
||||
function($scope, $http, ngDialog, Motion, MotionBlockForm, MotionBlock, motionBlock, Projector, ProjectionDefault) {
|
||||
MotionBlock.bindOne(motionBlock.id, $scope, 'motionBlock');
|
||||
Motion.bindAll({}, $scope, 'motions');
|
||||
$scope.$watch(function () {
|
||||
@ -148,6 +149,15 @@ angular.module('OpenSlidesApp.motions.motionBlock', [])
|
||||
$scope.openDialog = function (topic) {
|
||||
ngDialog.open(MotionBlockForm.getDialog(motionBlock));
|
||||
};
|
||||
$scope.followRecommendations = function () {
|
||||
$http.post('/rest/motions/motion-block/' + motionBlock.id + '/follow_recommendations/')
|
||||
.success(function(data) {
|
||||
$scope.alert = { type: 'success', msg: data.detail, show: true };
|
||||
})
|
||||
.error(function(data) {
|
||||
$scope.alert = { type: 'danger', msg: data.detail, show: true };
|
||||
});
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
|
@ -26,12 +26,11 @@
|
||||
</div>
|
||||
|
||||
<div class="details">
|
||||
<!-- set state button (TODO)-->
|
||||
<a os-perms="motions.can_manage" class="btn btn-default btn"
|
||||
ng-bootbox-confirm="{{ 'Are you sure you want to override the state of all motions of this motion block?' | translate }}"
|
||||
ng-bootbox-confirm-action="" translate>
|
||||
ng-bootbox-confirm-action="followRecommendations()" translate>
|
||||
<i class="fa fa-magic fa-lg"></i>
|
||||
<translate>Set state for each motion according to their recommendation</translate>
|
||||
<translate>Follow recommendations for all motions</translate>
|
||||
</a>
|
||||
|
||||
<div class="row spacer form-group">
|
||||
|
@ -9,8 +9,9 @@ from django.utils.translation import ugettext_noop
|
||||
from reportlab.platypus import SimpleDocTemplate
|
||||
from rest_framework import status
|
||||
|
||||
from openslides.core.config import config
|
||||
from openslides.utils.rest_api import (
|
||||
from ..core.config import config
|
||||
from ..utils.autoupdate import inform_changed_data
|
||||
from ..utils.rest_api import (
|
||||
DestroyModelMixin,
|
||||
GenericViewSet,
|
||||
ModelViewSet,
|
||||
@ -19,8 +20,7 @@ from openslides.utils.rest_api import (
|
||||
ValidationError,
|
||||
detail_route,
|
||||
)
|
||||
from openslides.utils.views import APIView, PDFView, SingleObjectMixin
|
||||
|
||||
from ..utils.views import APIView, PDFView, SingleObjectMixin
|
||||
from .access_permissions import (
|
||||
CategoryAccessPermissions,
|
||||
MotionAccessPermissions,
|
||||
@ -467,13 +467,35 @@ class MotionBlockViewSet(ModelViewSet):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action == 'metadata':
|
||||
result = self.request.user.has_perm('motions.can_see')
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy'):
|
||||
elif self.action in ('create', 'partial_update', 'update', 'destroy', 'follow_recommendations'):
|
||||
result = (self.request.user.has_perm('motions.can_see') and
|
||||
self.request.user.has_perm('motions.can_manage'))
|
||||
else:
|
||||
result = False
|
||||
return result
|
||||
|
||||
@detail_route(methods=['post'])
|
||||
def follow_recommendations(self, request, pk=None):
|
||||
"""
|
||||
View to set the states of all motions of this motion block each to
|
||||
its recommendation. It is a POST request without any data.
|
||||
"""
|
||||
motion_block = self.get_object()
|
||||
instances = []
|
||||
with transaction.atomic():
|
||||
for motion in motion_block.motion_set.all():
|
||||
# Follow recommendation.
|
||||
motion.follow_recommendation()
|
||||
motion.save(skip_autoupdate=True)
|
||||
# Write the log message.
|
||||
motion.write_log(
|
||||
message_list=[ugettext_noop('State set to'), ' ', motion.state.name],
|
||||
person=request.user,
|
||||
skip_autoupdate=True)
|
||||
instances.append(motion)
|
||||
inform_changed_data(instances)
|
||||
return Response({'detail': _('Followed recommendations successfully.')})
|
||||
|
||||
|
||||
class WorkflowViewSet(ModelViewSet):
|
||||
"""
|
||||
|
@ -145,7 +145,7 @@ def inform_changed_data(instances, information=None):
|
||||
"""
|
||||
root_instances = set()
|
||||
if not isinstance(instances, Iterable):
|
||||
# Make surce instance is an iterable
|
||||
# Make sure instances is an iterable
|
||||
instances = (instances, )
|
||||
for instance in instances:
|
||||
try:
|
||||
|
@ -7,7 +7,7 @@ from rest_framework.test import APIClient
|
||||
|
||||
from openslides.core.config import config
|
||||
from openslides.core.models import Tag
|
||||
from openslides.motions.models import Category, Motion, State
|
||||
from openslides.motions.models import Category, Motion, MotionBlock, State
|
||||
from openslides.users.models import User
|
||||
from openslides.utils.test import TestCase
|
||||
|
||||
@ -726,3 +726,40 @@ class NumberMotionsInCategory(TestCase):
|
||||
self.assertEqual(Motion.objects.get(pk=self.motion.pk).identifier, None)
|
||||
self.assertEqual(Motion.objects.get(pk=self.motion_2.pk).identifier, 'test_prefix_ahz6tho2mooH8 2')
|
||||
self.assertEqual(Motion.objects.get(pk=self.motion_3.pk).identifier, 'test_prefix_ahz6tho2mooH8 1')
|
||||
|
||||
|
||||
class FollowRecommendationsForMotionBlock(TestCase):
|
||||
"""
|
||||
Tests following the recommendations of motions in an motion block.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.state_id_accepted = 2 # This should be the id of the state 'accepted'.
|
||||
self.state_id_rejected = 3 # This should be the id of the state 'rejected'.
|
||||
|
||||
self.client = APIClient()
|
||||
self.client.login(username='admin', password='admin')
|
||||
|
||||
self.motion_block = MotionBlock.objects.create(
|
||||
title='test_motion_block_name_Ufoopiub7quaezaepeic')
|
||||
|
||||
self.motion = Motion(
|
||||
title='test_title_yo8ohy5eifeiyied2AeD',
|
||||
text='test_text_chi1aeth5faPhueQu8oh',
|
||||
motion_block=self.motion_block)
|
||||
self.motion.save()
|
||||
self.motion.set_recommendation(self.state_id_accepted)
|
||||
self.motion.save()
|
||||
|
||||
self.motion_2 = Motion(
|
||||
title='test_title_eith0EemaW8ahZa9Piej',
|
||||
text='test_text_haeho1ohk3ou7pau2Jee',
|
||||
motion_block=self.motion_block)
|
||||
self.motion_2.save()
|
||||
self.motion_2.set_recommendation(self.state_id_rejected)
|
||||
self.motion_2.save()
|
||||
|
||||
def test_follow_recommendations_for_motion_block(self):
|
||||
response = self.client.post(reverse('motionblock-follow-recommendations', args=[self.motion_block.pk]))
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(Motion.objects.get(pk=self.motion.pk).state.id, self.state_id_accepted)
|
||||
self.assertEqual(Motion.objects.get(pk=self.motion_2.pk).state.id, self.state_id_rejected)
|
||||
|
Loading…
Reference in New Issue
Block a user