Merge pull request #1964 from emanuelschuetze/assignmentpoll

Assignmentpoll slide
This commit is contained in:
Norman Jäckel 2016-02-15 23:50:43 +01:00
commit 2d5f5c685c
11 changed files with 177 additions and 41 deletions

View File

@ -185,7 +185,6 @@ class Item(RESTModelMixin, models.Model):
An Agenda Item An Agenda Item
""" """
objects = ItemManager() objects = ItemManager()
slide_callback_name = 'agenda'
AGENDA_ITEM = 1 AGENDA_ITEM = 1
HIDDEN_ITEM = 2 HIDDEN_ITEM = 2

View File

@ -81,15 +81,22 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
}, },
// override isProjected function of jsDataModel factory // override isProjected function of jsDataModel factory
isProjected: function () { isProjected: function () {
// Returns true if there is a projector element with the same
// name and the same id.
var projector = Projector.get(1); var projector = Projector.get(1);
if (typeof projector === 'undefined') return false; var isProjected;
if (typeof projector !== 'undefined') {
var self = this; var self = this;
var predicate = function (element) { var predicate = function (element) {
return element.name == self.content_object.collection && return element.name == self.content_object.collection &&
typeof element.id !== 'undefined' && typeof element.id !== 'undefined' &&
element.id == self.content_object.id; element.id == self.content_object.id;
}; };
return typeof _.findKey(projector.elements, predicate) === 'string'; isProjected = typeof _.findKey(projector.elements, predicate) === 'string';
} else {
isProjected = false;
}
return isProjected;
}, },
// project list of speakers // project list of speakers
projectListOfSpeakers: function() { projectListOfSpeakers: function() {

View File

@ -49,7 +49,6 @@ class AssignmentRelatedUser(RESTModelMixin, models.Model):
class Assignment(RESTModelMixin, models.Model): class Assignment(RESTModelMixin, models.Model):
slide_callback_name = 'assignment'
PHASE_SEARCH = 0 PHASE_SEARCH = 0
PHASE_VOTING = 1 PHASE_VOTING = 1
@ -345,7 +344,6 @@ class AssignmentOption(RESTModelMixin, BaseOption):
class AssignmentPoll(RESTModelMixin, CollectDefaultVotesMixin, class AssignmentPoll(RESTModelMixin, CollectDefaultVotesMixin,
PublishPollMixin, BasePoll): PublishPollMixin, BasePoll):
slide_callback_name = 'assignmentpoll'
option_class = AssignmentOption option_class = AssignmentOption
assignment = models.ForeignKey( assignment = models.ForeignKey(

View File

@ -2,7 +2,7 @@ from openslides.core.exceptions import ProjectorException
from openslides.core.views import TagViewSet from openslides.core.views import TagViewSet
from openslides.utils.projector import ProjectorElement, ProjectorRequirement from openslides.utils.projector import ProjectorElement, ProjectorRequirement
from .models import Assignment from .models import Assignment, AssignmentPoll
from .views import AssignmentViewSet from .views import AssignmentViewSet
@ -20,6 +20,11 @@ class AssignmentSlide(ProjectorElement):
# Detail slide. # Detail slide.
if not Assignment.objects.filter(pk=pk).exists(): if not Assignment.objects.filter(pk=pk).exists():
raise ProjectorException('Election does not exist.') raise ProjectorException('Election does not exist.')
poll_id = self.config_entry.get('poll')
if poll_id is not None:
# Poll slide.
if not AssignmentPoll.objects.filter(pk=poll_id).exists():
raise ProjectorException('Poll does not exist.')
def get_requirements(self, config_entry): def get_requirements(self, config_entry):
pk = config_entry.get('id') pk = config_entry.get('id')

View File

@ -72,14 +72,21 @@ angular.module('OpenSlidesApp.assignments', [])
]) ])
.factory('AssignmentPoll', [ .factory('AssignmentPoll', [
'$http',
'DS', 'DS',
'jsDataModel',
'gettextCatalog', 'gettextCatalog',
'AssignmentPollOption', 'AssignmentPollOption',
'Config', 'Config',
function (DS, gettextCatalog, AssignmentPollOption, Config) { function ($http, DS, jsDataModel, gettextCatalog, AssignmentPollOption, Config) {
var name = 'assignments/poll';
return DS.defineResource({ return DS.defineResource({
name: 'assignments/poll', name: name,
useClass: jsDataModel,
methods: { methods: {
getResourceName: function () {
return name;
},
// returns object with value and percent (for votes valid/invalid/cast only) // returns object with value and percent (for votes valid/invalid/cast only)
getVote: function (vote) { getVote: function (vote) {
if (!this.has_votes || !vote) { if (!this.has_votes || !vote) {
@ -148,11 +155,12 @@ angular.module('OpenSlidesApp.assignments', [])
.factory('Assignment', [ .factory('Assignment', [
'$http', '$http',
'DS', 'DS',
'Projector',
'AssignmentRelatedUser', 'AssignmentRelatedUser',
'AssignmentPoll', 'AssignmentPoll',
'jsDataModel', 'jsDataModel',
'gettext', 'gettext',
function ($http, DS, AssignmentRelatedUser, AssignmentPoll, jsDataModel, gettext) { function ($http, DS, Projector, AssignmentRelatedUser, AssignmentPoll, jsDataModel, gettext) {
var name = 'assignments/assignment'; var name = 'assignments/assignment';
var phases; var phases;
return DS.defineResource({ return DS.defineResource({
@ -183,6 +191,45 @@ angular.module('OpenSlidesApp.assignments', [])
// subtitle of search result // subtitle of search result
getSearchResultSubtitle: function () { getSearchResultSubtitle: function () {
return "Election"; return "Election";
},
// override project function of jsDataModel factory
project: function (poll_id) {
return $http.post(
'/rest/core/projector/1/prune_elements/',
[{name: 'assignments/assignment', id: this.id, poll: poll_id}]
);
},
// override isProjected function of jsDataModel factory
isProjected: function (poll_id) {
// Returns true if there is a projector element with the name
// 'assignments/assignment'.
var projector = Projector.get(1);
var isProjected;
if (typeof projector !== 'undefined') {
var self = this;
var predicate = function (element) {
var value;
if (typeof poll_id === 'undefined') {
// Assignment detail slide without poll
value = element.name == 'assignments/assignment' &&
typeof element.id !== 'undefined' &&
element.id == self.id &&
typeof element.poll === 'undefined';
} else {
// Assignment detail slide with specific poll
value = element.name == 'assignments/assignment' &&
typeof element.id !== 'undefined' &&
element.id == self.id &&
typeof element.poll !== 'undefined' &&
element.poll == poll_id;
}
return value;
};
isProjected = typeof _.findKey(projector.elements, predicate) === 'string';
} else {
isProjected = false;
}
return isProjected;
} }
}, },
relations: { relations: {

View File

@ -22,6 +22,7 @@ angular.module('OpenSlidesApp.assignments.projector', ['OpenSlidesApp.assignment
// Add it to the coresponding get_requirements method of the ProjectorElement // Add it to the coresponding get_requirements method of the ProjectorElement
// class. // class.
var id = $scope.element.id; var id = $scope.element.id;
var poll = $scope.element.poll;
// load assignemt object and related agenda item // load assignemt object and related agenda item
Assignment.find(id).then(function(assignment) { Assignment.find(id).then(function(assignment) {

View File

@ -143,14 +143,22 @@
<button ng-if="!poll.published" ng-click="publishBallot(poll, true)" <button ng-if="!poll.published" ng-click="publishBallot(poll, true)"
class="btn btn-default btn-sm"> class="btn btn-default btn-sm">
<i class="fa fa-toggle-off"></i> <i class="fa fa-toggle-off"></i>
3. <translate>Publish ballot</translate> 3. <translate>Publish</translate>
</button> </button>
<button ng-if="poll.published" ng-click="publishBallot(poll, false)" <button ng-if="poll.published" ng-click="publishBallot(poll, false)"
class="btn btn-default btn-sm"> class="btn btn-default btn-sm">
<i class="fa fa-toggle-on"></i> <i class="fa fa-toggle-on"></i>
<translate>Published</translate> 3. <translate>Published</translate>
</button> </button>
<a class="btn btn-danger btn-sm" <i class="fa fa-arrow-right"></i>
<button os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
ng-class="{ 'btn-primary': assignment.isProjected(poll.id) }"
ng-click="assignment.project(poll.id)"
title="{{ 'Project ballot' | translate }}">
<i class="fa fa-video-camera"></i>
4. <translate>Project</translate>
</button>
| <a class="btn btn-danger btn-sm"
ng-bootbox-confirm="{{ 'Are you sure you want to delete this ballot?' | translate }}" ng-bootbox-confirm="{{ 'Are you sure you want to delete this ballot?' | translate }}"
ng-bootbox-confirm-action="deleteBallot(poll)"> ng-bootbox-confirm-action="deleteBallot(poll)">
<i class="fa fa-times"></i> <i class="fa fa-times"></i>

View File

@ -19,13 +19,81 @@
</div> </div>
<!-- Description --> <!-- Description -->
<div class="white-space-pre-line">{{ assignment.description }}</div> <div ng-hide="element.poll" class="white-space-pre-line">
{{ assignment.description }}
</div>
<!-- Candidates --> <!-- Candidates -->
<div ng-hide="element.poll">
<h3 translate>Candidates</h3> <h3 translate>Candidates</h3>
<ol> <ol>
<li ng-repeat="related_user in assignment.assignment_related_users"> <li ng-repeat="related_user in assignment.assignment_related_users">
{{ related_user.user.get_full_name() }} {{ related_user.user.get_full_name() }}
<i ng-if="related_user.elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i> <i ng-if="related_user.elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
</ol> </ol>
</div>
<!-- vote results -->
<div ng-show="element.poll" class="electionresults spacer" ng-repeat="poll in assignment.polls | filter: {id: element.poll}">
<table class="table table-bordered table-striped minimumTable">
<tr>
<th translate>Candidates
<th ng-if="poll.has_votes" class="col-sm-6" translate>Votes
<!-- candidates (poll options) -->
<tr ng-repeat="option in poll.options">
<!-- candidate name -->
<td>
<i ng-if="option.is_elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
<strong>{{ option.candidate.get_full_name() }}</strong>
<!-- votes -->
<td ng-if="poll.has_votes">
<div ng-init="votes = option.getVotes()">
<div ng-show="poll.yesnoabstain">
<span>
{{ votes[0].label }}: <strong>{{ votes[0].value }}</strong> ·
{{ votes[1].label }}: {{ votes[1].value }} ·
{{ votes[2].label }}: {{ votes[2].value }} </span>
<uib-progress ng-if="votes[0].percentNumber">
<uib-bar value="votes[0].percentNumber" type="success">
<span ng-hide="votes[0].percentNumber < 5">{{votes[0].percentNumber}} %</span>
</uib-bar>
<uib-bar value="votes[1].percentNumber" type="danger">
<span ng-hide="votes[1].percentNumber < 5">{{votes[1].percentNumber}} %</span>
</uib-bar>
<uib-bar value="votes[2].percentNumber" type="warning"></uib-bar>
<span ng-hide="votes[2].percentNumber < 5">{{votes[2].percentNumber}} %</span>
</uib-bar>
</uib-progress>
</div>
<div ng-hide="poll.yesnoabstain">
<div ng-repeat="vote in votes">
{{ vote.value }} {{ vote.percentStr }} - {{vote.percentNumber}}
<div ng-if="vote.percentNumber">
<uib-progressbar value="vote.percentNumber" type="success"></uib-progressbar>
</div>
</div>
</div>
</div>
<!-- total votes (valid/invalid/casts) -->
<tr class="total">
<td>
<translate>Valid votes</translate>
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote(poll.votesvalid)">
{{ vote.value }} {{ vote.percent }}
<tr class="total">
<td>
<translate>Invalid votes</translate>
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote(poll.votesinvalid)">
{{ vote.value }}
<tr class="total bg-info">
<td>
<translate>Votes cast</translate>
<td ng-if="poll.has_votes" ng-init="vote = poll.getVote(poll.votescast)">
{{ vote.value }} {{ vote.percent }}
</table>
</div>
</div> </div>

View File

@ -168,6 +168,12 @@ li {
.result .bold { .result .bold {
font-weight: bold; font-weight: bold;
} }
.electionresults table {
width: calc(100% - 230px);
}
.electionresults .progress {
margin-bottom: 0;
}
hr { hr {
margin: 10px 0; margin: 10px 0;
} }

View File

@ -261,14 +261,19 @@ angular.module('OpenSlidesApp.core', [
// Returns true if there is a projector element with the same // Returns true if there is a projector element with the same
// name and the same id. // name and the same id.
var projector = Projector.get(1); var projector = Projector.get(1);
if (typeof projector === 'undefined') return false; var isProjected;
if (typeof projector !== 'undefined') {
var self = this; var self = this;
var predicate = function (element) { var predicate = function (element) {
return element.name == self.getResourceName() && return element.name == self.getResourceName() &&
typeof element.id !== 'undefined' && typeof element.id !== 'undefined' &&
element.id == self.id; element.id == self.id;
}; };
return typeof _.findKey(projector.elements, predicate) === 'string'; isProjected = typeof _.findKey(projector.elements, predicate) === 'string';
} else {
isProjected = false;
}
return isProjected;
}; };
return BaseModel; return BaseModel;
} }

View File

@ -30,11 +30,6 @@ class Motion(RESTModelMixin, models.Model):
This class is the main entry point to all other classes related to a motion. This class is the main entry point to all other classes related to a motion.
""" """
slide_callback_name = 'motion'
"""
Name of the callback for the slide-system.
"""
active_version = models.ForeignKey( active_version = models.ForeignKey(
'MotionVersion', 'MotionVersion',
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
@ -738,9 +733,6 @@ class MotionOption(RESTModelMixin, BaseOption):
class MotionPoll(RESTModelMixin, CollectDefaultVotesMixin, BasePoll): class MotionPoll(RESTModelMixin, CollectDefaultVotesMixin, BasePoll):
"""The Class to saves the vote result for a motion poll.""" """The Class to saves the vote result for a motion poll."""
slide_callback_name = 'motionpoll'
"""Name of the callback for the slide-system."""
motion = models.ForeignKey( motion = models.ForeignKey(
Motion, Motion,
on_delete=models.CASCADE, on_delete=models.CASCADE,