Merge pull request #1964 from emanuelschuetze/assignmentpoll
Assignmentpoll slide
This commit is contained in:
commit
2d5f5c685c
@ -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
|
||||||
|
@ -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() {
|
||||||
|
@ -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(
|
||||||
|
@ -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')
|
||||||
|
@ -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: {
|
||||||
|
@ -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) {
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user