Added view to follow recommendations.

for all motions of a motion block
This commit is contained in:
Norman Jäckel 2016-10-14 21:48:02 +02:00 committed by Emanuel Schütze
parent 0270c31b32
commit 20f8875dcd
7 changed files with 116 additions and 22 deletions

View File

@ -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) {

View File

@ -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):
"""

View File

@ -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 };
});
};
}
])

View File

@ -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">

View File

@ -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):
"""

View File

@ -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:

View File

@ -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)