Motion template fixes

- Add has_votes to motion serializer and add poll.has_votes check in motion detail view.
- Fix motion meta box 3 column layout.
- Added missing intents
- Show motionPoll edit form in dialog. Moved into new template.
- Clean up assignmentpoll form (like motionPoll)
This commit is contained in:
Emanuel Schuetze 2016-01-14 22:43:49 +01:00
parent 440a38b387
commit 694ed6f1cc
5 changed files with 293 additions and 246 deletions

View File

@ -136,40 +136,6 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
} }
]) ])
// Provide generic assignmentpoll form fields for create and update view
.factory('AssignmentPollFormFieldFactory', [
'gettextCatalog',
function (gettextCatalog) {
return {
getFormFields: function () {
return [
{
key: 'description',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Comment on the ballot paper')
}
},
{
key: 'yes',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Yes'),
type: 'number',
required: true
}
},
{
key: 'poll_description_default',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Default comment on the ballot paper')
}
}];
}
}
}
])
.controller('AssignmentListCtrl', [ .controller('AssignmentListCtrl', [
'$scope', '$scope',
'ngDialog', 'ngDialog',
@ -340,6 +306,8 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
template: 'static/templates/assignments/assignmentpoll-form.html', template: 'static/templates/assignments/assignmentpoll-form.html',
controller: 'AssignmentPollUpdateCtrl', controller: 'AssignmentPollUpdateCtrl',
className: 'ngdialog-theme-default', className: 'ngdialog-theme-default',
closeByEscape: false,
closeByDocument: false,
resolve: { resolve: {
assignmentpoll: function (AssignmentPoll) { assignmentpoll: function (AssignmentPoll) {
return AssignmentPoll.find(poll.id); return AssignmentPoll.find(poll.id);
@ -443,6 +411,8 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
$scope.model = assignmentpoll; $scope.model = assignmentpoll;
$scope.ballot = ballot; $scope.ballot = ballot;
$scope.formFields = []; $scope.formFields = [];
$scope.alert = {};
// add dynamic form fields // add dynamic form fields
assignmentpoll.options.forEach(function(option) { assignmentpoll.options.forEach(function(option) {
if (assignmentpoll.yesnoabstain) { if (assignmentpoll.yesnoabstain) {
@ -528,9 +498,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
} }
); );
// save assignmentpoll
// save assignment
$scope.save = function (poll) { $scope.save = function (poll) {
var votes = []; var votes = [];
if (assignmentpoll.yesnoabstain) { if (assignmentpoll.yesnoabstain) {
@ -556,8 +524,16 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
votescast: poll.votescast votescast: poll.votescast
}) })
.then(function(success) { .then(function(success) {
$scope.alert.show = false;
$scope.closeThisDialog(); $scope.closeThisDialog();
}) })
.catch(function(error) {
var message = '';
for (var e in error.data) {
message += e + ': ' + error.data[e] + ' ';
}
$scope.alert = { type: 'danger', msg: message, show: true };
});
}; };
} }
]); ]);

View File

@ -1,8 +1,17 @@
<h1><translate>Ballot</translate> {{ ballot }}</h1> <h1><translate>Ballot</translate> {{ ballot }}</h1>
<form name="assignmentpollForm" ng-submit="save(model)"> <alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
{{ alert.msg }}
</alert>
<p>
<translate>Special values</translate>:
<span class="badge badge-success">-1</span> = <translate>majority</translate>
<span class="badge">-2</span> = <translate>undocumented</translate>
<form name="assignmentPollForm" ng-submit="save(model)">
<formly-form model="model" fields="formFields"> <formly-form model="model" fields="formFields">
<button type="submit" ng-disabled="assignmentForm.$invalid" class="btn btn-primary" translate> <button type="submit" ng-disabled="assignmentPollForm.$invalid" class="btn btn-primary" translate>
Save Save
</button> </button>
<button ng-click="closeThisDialog()" class="btn btn-default" translate> <button ng-click="closeThisDialog()" class="btn btn-default" translate>

View File

@ -100,6 +100,7 @@ class MotionPollSerializer(ModelSerializer):
votes = DictField( votes = DictField(
child=IntegerField(min_value=-2, allow_null=True), child=IntegerField(min_value=-2, allow_null=True),
write_only=True) write_only=True)
has_votes = SerializerMethodField()
class Meta: class Meta:
model = MotionPoll model = MotionPoll
@ -112,7 +113,8 @@ class MotionPollSerializer(ModelSerializer):
'votesvalid', 'votesvalid',
'votesinvalid', 'votesinvalid',
'votescast', 'votescast',
'votes',) 'votes',
'has_votes')
def to_representation(self, obj): def to_representation(self, obj):
""" """
@ -150,6 +152,12 @@ class MotionPollSerializer(ModelSerializer):
result = None result = None
return result return result
def get_has_votes(self, obj):
"""
Returns True if this poll has some votes.
"""
return obj.has_votes()
@transaction.atomic @transaction.atomic
def update(self, instance, validated_data): def update(self, instance, validated_data):
""" """

View File

@ -295,6 +295,69 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
} }
]) ])
// Provide generic motionpoll form fields for poll update view
.factory('MotionPollForm', [
'gettextCatalog',
function (gettextCatalog) {
return {
getFormFields: function () {
return [
{
key: 'yes',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Yes'),
type: 'number',
required: true
}
},
{
key: 'no',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('No'),
type: 'number',
required: true
}
},
{
key: 'abstain',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Abstain'),
type: 'number',
required: true
}
},
{
key: 'votesvalid',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Valid votes'),
type: 'number'
}
},
{
key: 'votesinvalid',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Invalid votes'),
type: 'number'
}
},
{
key: 'votescast',
type: 'input',
templateOptions: {
label: gettextCatalog.getString('Votes cast'),
type: 'number'
}
}];
}
}
}
])
.controller('MotionListCtrl', [ .controller('MotionListCtrl', [
'$scope', '$scope',
'$state', '$state',
@ -430,58 +493,54 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
Workflow.bindAll({}, $scope, 'workflows'); Workflow.bindAll({}, $scope, 'workflows');
Motion.loadRelations(motion, 'agenda_item'); Motion.loadRelations(motion, 'agenda_item');
$scope.alert = {};
$scope.isCollapsed = true; $scope.isCollapsed = true;
// open edit dialog // open edit dialog
$scope.openDialog = function (motion) { $scope.openDialog = function (motion) {
ngDialog.open(MotionForm.getDialog(motion)); ngDialog.open(MotionForm.getDialog(motion));
}; };
// support
$scope.support = function () { $scope.support = function () {
$http.post('/rest/motions/motion/' + motion.id + '/support/'); $http.post('/rest/motions/motion/' + motion.id + '/support/');
} }
// unsupport
$scope.unsupport = function () { $scope.unsupport = function () {
$http.delete('/rest/motions/motion/' + motion.id + '/support/'); $http.delete('/rest/motions/motion/' + motion.id + '/support/');
} }
// update state
$scope.updateState = function (state_id) { $scope.updateState = function (state_id) {
$http.put('/rest/motions/motion/' + motion.id + '/set_state/', {'state': state_id}); $http.put('/rest/motions/motion/' + motion.id + '/set_state/', {'state': state_id});
} }
// reset state
$scope.reset_state = function (state_id) { $scope.reset_state = function (state_id) {
$http.put('/rest/motions/motion/' + motion.id + '/set_state/', {}); $http.put('/rest/motions/motion/' + motion.id + '/set_state/', {});
} }
// create poll
$scope.create_poll = function () { $scope.create_poll = function () {
$http.post('/rest/motions/motion/' + motion.id + '/create_poll/', {}) $http.post('/rest/motions/motion/' + motion.id + '/create_poll/', {});
.success(function(data){
$scope.alert.show = false;
})
.error(function(data){
$scope.alert = { type: 'danger', msg: data.detail, show: true };
});
} }
// open poll update dialog
$scope.openPollDialog = function (poll, voteNumber) {
ngDialog.open({
template: 'static/templates/motions/motionpoll-form.html',
controller: 'MotionPollUpdateCtrl',
className: 'ngdialog-theme-default',
closeByEscape: false,
closeByDocument: false,
resolve: {
motionpoll: function (MotionPoll) {
return MotionPoll.find(poll.id);
},
voteNumber: function () {
return voteNumber;
}
}
});
};
// delete poll
$scope.delete_poll = function (poll) { $scope.delete_poll = function (poll) {
poll.DSDestroy(); poll.DSDestroy();
} }
$scope.update_poll = function (poll) {
poll.DSUpdate({
motion_id: motion.id,
votes: {"Yes": poll.yes, "No": poll.no, "Abstain": poll.abstain},
votesvalid: poll.votesvalid,
votesinvalid: poll.votesinvalid,
votescast: poll.votescast
})
.then(function(success) {
$scope.alert.show = false;
poll.isEditMode = false;
})
.catch(function(error) {
var message = '';
for (var e in error.data) {
message += e + ': ' + error.data[e] + ' ';
}
$scope.alert = { type: 'danger', msg: message, show: true };
});
}
} }
]) ])
@ -587,6 +646,45 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
} }
]) ])
.controller('MotionPollUpdateCtrl', [
'$scope',
'$state',
'gettextCatalog',
'MotionPoll',
'MotionPollForm',
'motionpoll',
'voteNumber',
function($scope, $state, gettextCatalog, MotionPoll, MotionPollForm, motionpoll, voteNumber) {
// set initial values for form model
$scope.model = motionpoll;
$scope.voteNumber = voteNumber;
$scope.formFields = MotionPollForm.getFormFields();
$scope.alert = {};
// save motionpoll
$scope.save = function (poll) {
poll.DSUpdate({
motion_id: poll.motion_id,
votes: {"Yes": poll.yes, "No": poll.no, "Abstain": poll.abstain},
votesvalid: poll.votesvalid,
votesinvalid: poll.votesinvalid,
votescast: poll.votescast
})
.then(function(success) {
$scope.alert.show = false;
$scope.closeThisDialog();
})
.catch(function(error) {
var message = '';
for (var e in error.data) {
message += e + ': ' + error.data[e] + ' ';
}
$scope.alert = { type: 'danger', msg: message, show: true };
});
};
}
])
.controller('MotionImportCtrl', [ .controller('MotionImportCtrl', [
'$scope', '$scope',
'gettext', 'gettext',
@ -759,6 +857,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
} }
]) ])
.controller('CategoryListCtrl', function($scope, Category) { .controller('CategoryListCtrl', function($scope, Category) {
Category.bindAll({}, $scope, 'categories'); Category.bindAll({}, $scope, 'categories');

View File

@ -52,7 +52,7 @@
</div> </div>
<div class="detail" uib-collapse="isMeta"> <div class="detail" uib-collapse="isMeta">
<div class="row"> <div class="row">
<div class="col-sm-4"> <div class="col-md-4">
<!-- submitters --> <!-- submitters -->
<h3 translate>Submitters</h3> <h3 translate>Submitters</h3>
<div ng-repeat="submitter in motion.submitters"> <div ng-repeat="submitter in motion.submitters">
@ -78,7 +78,7 @@
</button> </button>
</div> </div>
</div> </div>
<div class="col-sm-4"> <div class="col-md-4">
<!-- Category --> <!-- Category -->
<h3 ng-if="motion.category" translate>Category</h3> <h3 ng-if="motion.category" translate>Category</h3>
{{ motion.category.name }} {{ motion.category.name }}
@ -106,71 +106,26 @@
</button> </button>
</div> </div>
</div> </div>
<div class="col-sm-3"> <div class="col-md-4">
<h3 ng-if="motion.polls.length > 0" translate>Voting result</h3> <h3 ng-if="motion.polls.length > 0" translate>Voting result</h3>
<ol class="slimlist"> <ol class="slimlist">
<li ng-repeat="poll in motion.polls" class="spacer"> <li ng-repeat="poll in motion.polls" class="spacer"
<translate translate-context="ballot">Vote</translate> ng-if="poll.has_votes || operator.hasPerms('motions.can_manage')">
<button os-perms="motions.can_manage" ng-click="$parent.poll.isEditMode=true;" <strong><translate translate-context="ballot">Vote</translate></strong>
class="btn btn-default btn-xs"> <!-- Edit poll -->
<button os-perms="motions.can_manage" ng-click="openPollDialog(poll, $index+1)"
class="btn btn-default btn-xs" title="{{ 'Edit' | translate }}">
<i class="fa fa-pencil"></i> <i class="fa fa-pencil"></i>
</button> </button>
<button os-perms="motions.can_manage" ng-click="delete_poll(poll)" <!-- Delete poll -->
class="btn btn-default btn-xs"> <button os-perms="motions.can_manage" class="btn btn-default btn-xs"
ng-bootbox-confirm="{{ 'Are you sure you want to delete this poll?' | translate }}"
ng-bootbox-confirm-action="delete_poll(poll)"
title="{{ 'Delete' | translate }}">
<i class="fa fa-times"></i> <i class="fa fa-times"></i>
</button> </button>
<div ng-show="poll.isEditMode" class="spacer"> <!-- Poll results -->
<form> <div ng-show="poll.has_votes" class="pollresults">
<p>
<translate>Special values</translate>:<br>
<span class="badge badge-success">-1</span> = <translate>majority</translate><br>
<span class="badge">-2</span> = <translate>undocumented</translate>
</p>
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">
{{alert.msg}}
</alert>
<!-- yes -->
<div class="input-group col-sm-8 spacer">
<div class="input-group-addon" title="{{ 'Yes' | translate }}"><i class="fa fa-thumbs-up"></i></div>
<input type="number" ng-model="poll.yes" class="form-control input-sm" placeholder="{{ 'Yes' | translate }}">
</div>
<!-- no -->
<div class="input-group col-sm-8">
<div class="input-group-addon" title="{{ 'No' | translate }}"><i class="fa fa-thumbs-down"></i></div>
<input type="number" ng-model="poll.no" class="form-control input-sm" placeholder="{{ 'No' | translate }}">
</div>
<!-- abstain -->
<div class="input-group col-sm-8">
<div class="input-group-addon" title="{{ 'Abstain' | translate }}"><b>&empty;</b></div>
<input type="number" ng-model="poll.abstain" class="form-control input-sm" placeholder="{{ 'Abstain' | translate }}">
</div>
<!-- valid votes -->
<div class="input-group col-sm-8 spacer">
<div class="input-group-addon" title="{{ 'Valid votes' | translate }}"><i class="fa fa-check"></i></div>
<input type="number" ng-model="poll.votesvalid" class="form-control input-sm" placeholder="{{ 'Valid votes' | translate }}">
</div>
<!-- invalid votes -->
<div class="input-group col-sm-8">
<div class="input-group-addon" title="{{ 'Invalid votes' | translate }}"><i class="fa fa-ban"></i></div>
<input type="number" ng-model="poll.votesinvalid" class="form-control input-sm" placeholder="{{ 'Invalid votes' | translate }}">
</div>
<!-- votes cast -->
<div class="input-group col-sm-8 spacer">
<div class="input-group-addon" title="{{ 'Votes cast' | translate }}"><b>&sum;</b></div>
<input type="number" ng-model="poll.votescast" class="form-control input-sm" placeholder="{{ 'Votes cast' | translate }}">
</div>
<!-- buttons -->
<div class="spacer">
<button type="submit" ng-click="update_poll(poll)" class="btn btn-primary" translate>
Save
</button>
<button ng-click="poll.isEditMode=false;" class="btn btn-default" translate>
Cancel
</button>
</div>
</form>
</div>
<div ng-show="!poll.isEditMode" class="pollresults">
<table class="table"> <table class="table">
<!-- yes --> <!-- yes -->
<tr> <tr>