Assignment improvements

- Use ckeditor for assignment description field (e.g. for candidatures
  using html).
- Use 2 decimal places for percentage of voting results (motions/assignments)
- Allow css style attributes 'float' and 'padding'.
- Remove progress bar in election result slide to get more space (height)
  for candidate rows.
This commit is contained in:
Emanuel Schütze 2017-11-20 16:40:39 +01:00
parent 8da08d9b36
commit 3d264683ff
9 changed files with 40 additions and 36 deletions

View File

@ -12,6 +12,7 @@ from openslides.utils.rest_api import (
ValidationError, ValidationError,
) )
from ..utils.validate import validate_html
from .models import ( from .models import (
Assignment, Assignment,
AssignmentOption, AssignmentOption,
@ -209,6 +210,11 @@ class AssignmentFullSerializer(ModelSerializer):
'tags',) 'tags',)
validators = (posts_validator,) validators = (posts_validator,)
def validate(self, data):
if 'description' in data:
data['description'] = validate_html(data['description'])
return data
class AssignmentShortSerializer(AssignmentFullSerializer): class AssignmentShortSerializer(AssignmentFullSerializer):
""" """
@ -231,3 +237,8 @@ class AssignmentShortSerializer(AssignmentFullSerializer):
'agenda_item_id', 'agenda_item_id',
'tags',) 'tags',)
validators = (posts_validator,) validators = (posts_validator,)
def validate(self, data):
if 'description' in data:
data['description'] = validate_html(data['description'])
return data

View File

@ -71,7 +71,7 @@ angular.module('OpenSlidesApp.assignments', [])
var skipPercents = config === 'YES_NO' && vote.value === 'Abstain'; var skipPercents = config === 'YES_NO' && vote.value === 'Abstain';
if (base && !skipPercents) { if (base && !skipPercents) {
percentNumber = Math.round(vote.weight * 100 / base * 10) / 10; percentNumber = Math.round(vote.weight * 100 / base * 100) / 100;
percentStr = "(" + percentNumber + "%)"; percentStr = "(" + percentNumber + "%)";
} }
votes.push({ votes.push({

View File

@ -96,11 +96,13 @@ angular.module('OpenSlidesApp.assignments.site', [
.factory('AssignmentForm', [ .factory('AssignmentForm', [
'gettextCatalog', 'gettextCatalog',
'operator', 'operator',
'Editor',
'Mediafile',
'Tag', 'Tag',
'Assignment', 'Assignment',
'Agenda', 'Agenda',
'AgendaTree', 'AgendaTree',
function (gettextCatalog, operator, Tag, Assignment, Agenda, AgendaTree) { function (gettextCatalog, operator, Editor, Mediafile, Tag, Assignment, Agenda, AgendaTree) {
return { return {
// ngDialog for assignment form // ngDialog for assignment form
getDialog: function (assignment) { getDialog: function (assignment) {
@ -117,6 +119,7 @@ angular.module('OpenSlidesApp.assignments.site', [
}, },
// angular-formly fields for assignment form // angular-formly fields for assignment form
getFormFields: function (isCreateForm) { getFormFields: function (isCreateForm) {
var images = Mediafile.getAllImages();
var formFields = [ var formFields = [
{ {
key: 'title', key: 'title',
@ -128,9 +131,12 @@ angular.module('OpenSlidesApp.assignments.site', [
}, },
{ {
key: 'description', key: 'description',
type: 'textarea', type: 'editor',
templateOptions: { templateOptions: {
label: gettextCatalog.getString('Description') label: gettextCatalog.getString('Description')
},
data: {
ckeditorOptions: Editor.getOptions(images)
} }
}, },
{ {

View File

@ -77,10 +77,10 @@
<div class="details"> <div class="details">
<div ng-if="assignment.description"> <div ng-if="assignment.description">
<h3 translate>Description</h3> <h3 translate>Description</h3>
<div class="white-space-pre-line">{{ assignment.description }}</div> <div ng-bind-html="assignment.description | trusted"></div>
</div> </div>
<div ng-if="assignment.phase !== 2"> <div ng-if="assignment.phase !== 2" class="clearfix">
<h3 translate>Candidates</h3> <h3 translate>Candidates</h3>
<div ui-tree="treeOptions" ng-if="assignment.assignment_related_users.length" <div ui-tree="treeOptions" ng-if="assignment.assignment_related_users.length"
data-empty-placeholder-enabled="false"> data-empty-placeholder-enabled="false">

View File

@ -9,12 +9,13 @@
</div> </div>
<!-- Description --> <!-- Description -->
<div ng-if="!showResult && assignment.description !== ''" class="white-space-pre-line zoomcontent"> <div ng-if="!showResult && assignment.description !== ''"
{{ assignment.description }} ng-bind-html="assignment.description | trusted"
class="zoomcontent">
</div> </div>
<!-- Candidates --> <!-- Candidates -->
<div class="zoomcontent" ng-if="!showResult"> <div class="zoomcontent" ng-if="!showResult" class="clearfix">
<h3 translate>Candidates</h3> <h3 translate>Candidates</h3>
<ul> <ul>
<li ng-repeat="related_user in assignment.assignment_related_users | orderBy:'weight'"> <li ng-repeat="related_user in assignment.assignment_related_users | orderBy:'weight'">
@ -33,45 +34,31 @@
<table class="table table-bordered table-striped minimumTable"> <table class="table table-bordered table-striped minimumTable">
<tr> <tr>
<th translate>Candidates <th translate>Candidates
<th ng-if="poll.has_votes" class="col-sm-6" translate>Votes</th> <th ng-if="poll.has_votes" class="col-sm-5" translate>Votes</th>
<!-- candidates (poll options) --> <!-- candidates (poll options) -->
<tr ng-repeat="option in poll.options | orderBy:'weight'"> <tr ng-repeat="option in poll.options | orderBy:'weight'">
<!-- candidate name --> <!-- candidate name -->
<td> <td class="bold">
<i ng-if="option.is_elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i> <i ng-if="option.is_elected" class="fa fa-star" title="{{ 'is elected' | translate }}"></i>
<strong>{{ option.candidate.get_full_name() }}</strong> {{ option.candidate.get_full_name() }}
<!-- votes --> <!-- votes -->
<td ng-if="poll.has_votes"> <td ng-if="poll.has_votes" class="bold">
<div ng-init="votes = option.getVotes()"> <div ng-init="votes = option.getVotes()">
<div ng-show="poll.pollmethod == 'yna' || poll.pollmethod == 'yn'"> <div ng-show="poll.pollmethod == 'yna' || poll.pollmethod == 'yn'">
<span ng-show="poll.pollmethod == 'yna'"> <span ng-show="poll.pollmethod == 'yna'">
{{ votes[0].label | translate }}: {{ votes[0].value }} · {{ votes[0].label | translate }}: {{ votes[0].value }} {{ votes[0].percentStr }}<br>
{{ votes[1].label | translate }}: {{ votes[1].value }} · {{ votes[1].label | translate }}: {{ votes[1].value }} {{ votes[1].percentStr }}<br>
{{ votes[2].label | translate }}: {{ votes[2].value }} </span> {{ votes[2].label | translate }}: {{ votes[2].value }} {{ votes[2].percentStr }}</span>
<span ng-show="poll.pollmethod == 'yn'"> <span ng-show="poll.pollmethod == 'yn'">
{{ votes[0].label | translate }}: {{ votes[0].value }} · {{ votes[0].label | translate }}: {{ votes[0].value }} {{ votes[0].percentStr }}<br>
{{ votes[1].label | translate }}: {{ votes[1].value }}</span> {{ votes[1].label | translate }}: {{ votes[1].value }} {{ votes[1].percentStr }}</span>
<uib-progress ng-if="votes[0].percentNumber>=0">
<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">
<span ng-hide="votes[2].percentNumber < 5">{{votes[2].percentNumber}} %</span>
</uib-bar>
</uib-progress>
</div> </div>
<div ng-show="poll.pollmethod == 'votes'"> <div ng-show="poll.pollmethod == 'votes'">
<div ng-repeat="vote in votes"> <div ng-repeat="vote in votes">
{{ vote.value }} {{ vote.percentStr }} {{ vote.value }} {{ vote.percentStr }}
<div ng-if="vote.percentNumber >= 0">
<uib-progressbar value="vote.percentNumber" type="success"></uib-progressbar>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -273,7 +273,7 @@ li {
font-size: 120%; font-size: 120%;
line-height: 40px; line-height: 40px;
} }
.result .bold { .result .bold, .electionresults .bold {
font-weight: bold; font-weight: bold;
} }
.electionresults table { .electionresults table {

View File

@ -1030,9 +1030,9 @@ angular.module('OpenSlidesApp.core', [
allowedContent: allowedContent:
'h1 h2 h3 b i u strike sup sub strong em;' + 'h1 h2 h3 b i u strike sup sub strong em;' +
'blockquote p pre table' + 'blockquote p pre table' +
'(text-align-left,text-align-center,text-align-right,text-align-justify,os-split-before,os-split-after){text-align};' + '(text-align-left,text-align-center,text-align-right,text-align-justify,os-split-before,os-split-after){text-align, float, padding};' +
'a[!href];' + 'a[!href];' +
'img[!src,alt]{width,height,float};' + 'img[!src,alt]{width,height,float, padding};' +
'tr th td caption;' + 'tr th td caption;' +
'li(os-split-before,os-split-after); ol(os-split-before,os-split-after)[start]{list-style-type};' + 'li(os-split-before,os-split-after); ol(os-split-before,os-split-after)[start]{list-style-type};' +
'ul(os-split-before,os-split-after){list-style};' + 'ul(os-split-before,os-split-after){list-style};' +

View File

@ -155,7 +155,7 @@ angular.module('OpenSlidesApp.motions', [
// Calculate percent value // Calculate percent value
var base = this.getPercentBase(config, type); var base = this.getPercentBase(config, type);
if (base) { if (base) {
percentNumber = Math.round(vote * 100 / (base) * 10) / 10; percentNumber = Math.round(vote * 100 / (base) * 100) / 100;
percentStr = '(' + percentNumber + ' %)'; percentStr = '(' + percentNumber + ' %)';
} }
return { return {

View File

@ -16,7 +16,7 @@ allowed_attributes = {
'ol': ['start'], 'ol': ['start'],
} }
allowed_styles = [ allowed_styles = [
'color', 'background-color', 'height', 'width', 'text-align' 'color', 'background-color', 'height', 'width', 'text-align', 'float', 'padding'
] ]