Several template fixes and clean up

- Use ng-cloak for hide template parts while loading.
- Set html lang attribute dynamically (Fixes #1546)
- Clean up: Rename 'dashboard' to 'home'.
- Show duration of speech in minutes. (Fixes #1882)
- Save agenda specific stuff for customslides. (Fixes #1887)
- Remove title from QuickEdit from.
- Checkbox for item.closed is now visible for manager only.
- Agenda list view: Show list of speakers link also for normal users.
- Improve slide templates: Show agenda item number and subtitle.
- Fixed agenda title for motions and assignments.
  (Don't load motions and assignmetn in agenda app.)
- Added missing seach template.
This commit is contained in:
Emanuel Schuetze 2016-01-25 21:22:22 +01:00
parent 741cae028c
commit 23503eb4ba
35 changed files with 345 additions and 146 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
# General # General
*.pyc *.pyc
*.swp *.swp
*.swo
*~ *~
# Virtual Environment # Virtual Environment

View File

@ -299,6 +299,17 @@ class Item(RESTModelMixin, models.Model):
raise NotImplementedError('You have to provide a get_agenda_title ' raise NotImplementedError('You have to provide a get_agenda_title '
'method on your related model.') 'method on your related model.')
@property
def list_view_title(self):
"""
Return get_agenda_list_view_title() from the content_object.
"""
try:
return self.content_object.get_agenda_list_view_title()
except AttributeError:
raise NotImplementedError('You have to provide a get_agenda_list_view_title '
'method on your related model.')
def is_hidden(self): def is_hidden(self):
""" """
Returns True if the type of this object itself is a hidden item or any Returns True if the type of this object itself is a hidden item or any

View File

@ -53,6 +53,7 @@ class ItemSerializer(ModelSerializer):
'id', 'id',
'item_number', 'item_number',
'title', 'title',
'list_view_title',
'comment', 'comment',
'closed', 'closed',
'type', 'type',

View File

@ -48,12 +48,24 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
try { try {
title = this.getContentObject().getAgendaTitle(); title = this.getContentObject().getAgendaTitle();
} catch (e) { } catch (e) {
// Only use this.title when the content object is not // when the content object is not in the DS store.
// in the DS store.
title = this.title; title = this.title;
} }
if (this.getContentResource().agendaSupplement) { if (this.item_number) {
title = gettextCatalog.getString(this.getContentResource().agendaSupplement) + ' ' + title; title = this.item_number + ' · ' + title;
}
return title;
},
getAgendaTitle: function () {
return this.title;
},
getListViewTitle: function () {
var title;
try {
title = this.getContentObject().getAgendaListViewTitle();
} catch (e) {
// when the content object is not in the DS store
title = this.list_view_title;
} }
if (this.item_number) { if (this.item_number) {
title = this.item_number + ' · ' + title; title = this.item_number + ' · ' + title;

View File

@ -280,6 +280,14 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
$scope.alert = { type: 'danger', msg: data.detail, show: true }; $scope.alert = { type: 'danger', msg: data.detail, show: true };
}); });
}; };
// gets speech duration of selected speaker in seconds
$scope.getDuration = function (speaker) {
var beginTimestamp = new Date(speaker.begin_time).getTime()
var endTimestamp = new Date(speaker.end_time).getTime()
// calculate duration in seconds
return Math.floor((endTimestamp - beginTimestamp) / 1000);
}
// save reordered list of speakers // save reordered list of speakers
$scope.treeOptions = { $scope.treeOptions = {
dropped: function (event) { dropped: function (event) {

View File

@ -7,7 +7,7 @@
</a> </a>
<a href="" ng-click="open(item)" class="btn btn-sm btn-default"> <a href="" ng-click="open(item)" class="btn btn-sm btn-default">
<i class="fa fa-angle-double-left fa-lg"></i> <i class="fa fa-angle-double-left fa-lg"></i>
{{ item.getContentResource().verboseName }} {{ item.getContentResource().verboseName | translate }}
</a> </a>
<!-- project list of speakers --> <!-- project list of speakers -->
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm" <a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
@ -22,7 +22,7 @@
ng-click="item.project()" ng-click="item.project()"
title="{{ 'Project item' | translate }}"> title="{{ 'Project item' | translate }}">
<i class="fa fa-video-camera"></i> <i class="fa fa-video-camera"></i>
{{ item.getContentResource().verboseName }} {{ item.getContentResource().verboseName | translate }}
</a> </a>
</div> </div>
<h1>{{ item.getTitle() }}</h1> <h1>{{ item.getTitle() }}</h1>
@ -68,7 +68,7 @@
<div class="spacer"> <div class="spacer">
<h3 translate>Last speakers</h3> <h3 translate>Last speakers</h3>
<button ng-click="showOldSpeakers = !showOldSpeakers" <button ng-click="showOldSpeakers = !showOldSpeakers"
class="btn btn-xs btn-default"> class="btn btn-sm btn-default">
<translate ng-if="!showOldSpeakers">Show</translate> <translate ng-if="!showOldSpeakers">Show</translate>
<translate ng-if="showOldSpeakers">Hide</translate> <translate ng-if="showOldSpeakers">Hide</translate>
</button> </button>
@ -77,8 +77,9 @@
<li ng-repeat="speaker in item.speakers | filter: {end_time: '!!'}"> <li ng-repeat="speaker in item.speakers | filter: {end_time: '!!'}">
{{ speaker.user.get_full_name() }} {{ speaker.user.get_full_name() }}
<small class="grey"> <small class="grey">
[{{ speaker.begin_time | date:'yyyy-MM-dd HH:mm:ss' }} {{ getDuration(speaker) | osSecondsToTime }} <translate>minutes</translate>
{{ speaker.end_time | date:'yyyy-MM-dd HH:mm:ss' }}] (<translate>Start time</translate>:
{{ speaker.begin_time | date:'yyyy-MM-dd HH:mm:ss' }})
</small> </small>
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)" <button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
class="btn btn-default btn-xs" title="{{ 'Remove' | translate }}"> class="btn btn-default btn-xs" title="{{ 'Remove' | translate }}">
@ -94,11 +95,11 @@
filter: {end_time: null, begin_time: '!!'}"> filter: {end_time: null, begin_time: '!!'}">
{{ speaker.user.get_full_name() }} {{ speaker.user.get_full_name() }}
<button os-perms="agenda.can_manage" ng-click="endSpeech()" <button os-perms="agenda.can_manage" ng-click="endSpeech()"
class="btn btn-default btn-xs" title="{{ 'End speech' | translate }}"> class="btn btn-default btn-sm" title="{{ 'End speech' | translate }}">
<i class="fa fa-microphone-slash"></i> <i class="fa fa-microphone-slash"></i> <translate>Stop</translate>
</button> </button>
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)" <button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
class="btn btn-default btn-xs" title="{{ 'Remove' | translate }}"> class="btn btn-default btn-sm" title="{{ 'Remove' | translate }}">
<i class="fa fa-times"></i> <i class="fa fa-times"></i>
</button> </button>
</strong> </strong>
@ -113,11 +114,11 @@
{{ $index + 1 }}. {{ $index + 1 }}.
{{ speaker.user.get_full_name() }} {{ speaker.user.get_full_name() }}
<button os-perms="agenda.can_manage" ng-click="beginSpeech(speaker.id)" <button os-perms="agenda.can_manage" ng-click="beginSpeech(speaker.id)"
class="btn btn-default btn-xs" title="{{ 'Begin speech' | translate }}"> class="btn btn-default btn-sm" title="{{ 'Begin speech' | translate }}">
<i class="fa fa-microphone"></i> <i class="fa fa-microphone"></i> <translate>Start</translate>
</button> </button>
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)" <button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
class="btn btn-default btn-xs" title="{{ 'Remove' | translate }}"> class="btn btn-default btn-sm" title="{{ 'Remove' | translate }}">
<i class="fa fa-times"></i> <i class="fa fa-times"></i>
</button> </button>
</ol> </ol>

View File

@ -28,19 +28,18 @@
<div class="details"> <div class="details">
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col-sm-7">
<form class="form-inline"> <div class="form-inline">
<!-- delete mode --> <!-- delete mode -->
<button os-perms="agenda.can_manage" class="btn" <button os-perms="agenda.can_manage" class="btn"
ng-class="$parent.isDeleteMode ? 'btn-primary' : 'btn-default'" ng-class="$parent.isDeleteMode ? 'btn-primary' : 'btn-default'"
ng-click="$parent.isDeleteMode = !$parent.isDeleteMode; uncheckAll()"> ng-click="$parent.isDeleteMode = !$parent.isDeleteMode; uncheckAll()">
<i class="fa fa-check-square-o"></i> <i class="fa fa-check-square-o"></i>
<translate>Select ...</translate> <translate>Select ...</translate>
</button> </button>
<div class="form-group">
<!-- project agenda button --> <!-- project agenda button -->
<a os-perms="core.can_manage_projector" <a os-perms="core.can_manage_projector"
class="btn btn-default form-control" class="btn btn-default"
title="{{ 'Project agenda' | translate }}" title="{{ 'Project agenda' | translate }}"
ng-click="projectAgenda()" ng-click="projectAgenda()"
ng-class="{ 'btn-primary': isAgendaProjected() }"> ng-class="{ 'btn-primary': isAgendaProjected() }">
@ -49,15 +48,14 @@
</a> </a>
<!-- auto numbering button --> <!-- auto numbering button -->
<a os-perms="core.can_manage_projector" <a os-perms="core.can_manage_projector"
class="btn btn-default form-control" class="btn btn-default"
ng-click="autoNumbering()"> ng-click="autoNumbering()">
<i class="fa fa-sort-numeric-asc"></i> <i class="fa fa-sort-numeric-asc"></i>
<translate>Number agenda</translate> <translate>Numbering</translate>
</a> </a>
</div> </div>
</form>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-5">
<div class="form-inline text-right"> <div class="form-inline text-right">
<div class="form-group"> <div class="form-group">
<div class="input-group"> <div class="input-group">
@ -138,29 +136,34 @@
style="padding-left: calc(8px + {{ item.parentCount }}*15px)"> style="padding-left: calc(8px + {{ item.parentCount }}*15px)">
<strong> <strong>
<a href="" ng-click="open(item)"> <a href="" ng-click="open(item)">
{{ item.getTitle() }} {{ item.getListViewTitle() }}
</a> </a>
</strong> </strong>
<span ng-if="item.is_hidden" title="{{ 'Internal item' | translate }}"><i class="fa fa-ban"></i></span> <span ng-if="item.is_hidden" title="{{ 'Internal item' | translate }}"><i class="fa fa-ban"></i></span>
<div ng-if="item.comment"> <div ng-if="item.comment">
<small><i class="fa fa-info-circle"></i> {{ item.comment }}</small> <small><i class="fa fa-info-circle"></i> {{ item.comment }}</small>
</div> </div>
<div os-perms="agenda.can_manage" class="hoverActions" ng-class="{'hiddenDiv': !item.hover}"> <div os-perms="agenda.can_see" class="hoverActions" ng-class="{'hiddenDiv': !item.hover}">
<a ui-sref="agenda.item.detail({id: item.id})" translate>List of speakers</a> | <a ui-sref="agenda.item.detail({id: item.id})" translate>List of speakers</a>
<a href="" ng-click="editDialog(item)" translate>Edit</a> | <span os-perms="agenda.can_manage"> |
<a href="" ng-click="item.quickEdit=true" translate>QuickEdit</a> <a href="" ng-click="editDialog(item)" translate>Edit</a> |
<span ng-if="item.content_object.collection == 'core/customslide'"> | <a href="" ng-click="item.quickEdit=true" translate>QuickEdit</a>
<a href="" class="text-danger" <span ng-if="item.content_object.collection == 'core/customslide'"> |
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br> <a href="" class="text-danger"
<b>{{ item.getTitle() }}</b>" ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
ng-bootbox-confirm-action="deleteRelatedItem(item)" translate>Delete</a> <b>{{ item.getTitle() }}</b>"
ng-bootbox-confirm-action="deleteRelatedItem(item)" translate>Delete</a>
</span>
</span> </span>
</div> </div>
<td ng-show="!item.quickEdit" os-perms="agenda.can_see_hidden_items" class="optional"> <td ng-show="!item.quickEdit" os-perms="agenda.can_see_hidden_items" class="optional">
{{ item.duration }} {{ item.duration }}
<span ng-if="item.duration" translate-comment="'h' means time in hours" translate>h</span> <span ng-if="item.duration" translate-comment="'h' means time in hours" translate>h</span>
<td ng-if="!item.quickEdit"> <td ng-if="!item.quickEdit">
<input type="checkbox" ng-model="item.closed" ng-change="save(item.id);"> <span os-perms="!agenda.can_manage">
<i ng-if="item.closed" class="fa fa-check-square-o"></i>
</span>
<input os-perms="agenda.can_manage" type="checkbox" ng-model="item.closed" ng-change="save(item.id);">
<!-- quickEdit columns --> <!-- quickEdit columns -->
<td ng-show="item.quickEdit" os-perms="agenda.can_manage" colspan="3"> <td ng-show="item.quickEdit" os-perms="agenda.can_manage" colspan="3">
<form ng-submit="save(item)"> <form ng-submit="save(item)">
@ -170,8 +173,8 @@
</uib-alert> </uib-alert>
<div class="row"> <div class="row">
<div class="col-xs-6"> <div class="col-xs-6">
<label for="inputTitle" translate>Title</label> <label for="inputItemNumber" translate>Item number</label>
<input type="text" ng-model="item.title" class="form-control input-sm" id="inputTitle"> <input type="text" ng-model="item.item_number" class="form-control input-sm" id="inputItemNumber">
</div> </div>
<div class="col-xs-6"> <div class="col-xs-6">
<label for="inputComment" translate>Comment</label> <label for="inputComment" translate>Comment</label>
@ -180,25 +183,15 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-xs-6"> <div class="col-xs-6">
<label for="inputItemNumber" translate>Item number</label> <!-- item type: AGENDA_ITEM = 1, HIDDEN_ITEM = 2 -->
<input type="text" ng-model="item.item_number" class="form-control input-sm" id="inputItemNumber"> <input type="checkbox" ng-model="item.type" ng-true-value="1" ng-false-value="2">
<translate>Show as agenda item</translate>
</div> </div>
<div class="col-xs-6"> <div class="col-xs-6">
<label for="inputDuration" translate>Duration</label> <label for="inputDuration" translate>Duration</label>
<input type="text" ng-model="item.duration" class="form-control input-sm" id="inputDuration"> <input type="text" ng-model="item.duration" class="form-control input-sm" id="inputDuration">
</div> </div>
</div> </div>
<div class="row">
<div class="col-xs-6">
<label>
<!-- item type: AGENDA_ITEM = 1, HIDDEN_ITEM = 2 -->
<input type="checkbox" ng-model="item.type" ng-true-value="2" ng-false-value="1">
<translate> Hidden item</translate>
</label>
</div>
<div class="col-xs-6">
</div>
</div>
<div class="spacer"> <div class="spacer">
<button ng-click="item.quickEdit=false" class="btn btn-default pull-left" translate> <button ng-click="item.quickEdit=false" class="btn btn-default pull-left" translate>
Cancel Cancel

View File

@ -5,7 +5,7 @@
<!-- item type: AGENDA_ITEM = 1, HIDDEN_ITEM = 2 --> <!-- item type: AGENDA_ITEM = 1, HIDDEN_ITEM = 2 -->
<p ng-repeat="item in items | filter: {type: 1}" ng-class="{ 'spacer-top': !item.parent_id }"> <p ng-repeat="item in items | filter: {type: 1}" ng-class="{ 'spacer-top': !item.parent_id }">
<span ng-repeat="n in [].constructor(item.parentCount) track by $index">&nbsp;&nbsp;</span> <span ng-repeat="n in [].constructor(item.parentCount) track by $index">&nbsp;&nbsp;</span>
{{ item.getTitle() }} {{ item.getListViewTitle() }}
<i ng-if="item.closed" class="fa fa-check"></i> <i ng-if="item.closed" class="fa fa-check"></i>
</p> </p>
</div> </div>

View File

@ -3,6 +3,7 @@ from collections import OrderedDict
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy, ugettext_noop from django.utils.translation import ugettext_lazy, ugettext_noop
from openslides.agenda.models import Item, Speaker from openslides.agenda.models import Item, Speaker
@ -268,6 +269,15 @@ class Assignment(RESTModelMixin, models.Model):
def get_agenda_title(self): def get_agenda_title(self):
return str(self) return str(self)
def get_agenda_list_view_title(self):
"""
Return a title string for the agenda list view.
Contains agenda item number, title and assignment verbose name.
Note: It has to be the same return value like in JavaScript.
"""
return '%s (%s)' % (self.title, _(self._meta.verbose_name))
@property @property
def agenda_item(self): def agenda_item(self):
""" """

View File

@ -73,18 +73,21 @@ angular.module('OpenSlidesApp.assignments', [])
'AssignmentPoll', 'AssignmentPoll',
'jsDataModel', 'jsDataModel',
'gettext', 'gettext',
function ($http, DS, AssignmentRelatedUser, AssignmentPoll, jsDataModel, gettext) { 'gettextCatalog',
function ($http, DS, AssignmentRelatedUser, AssignmentPoll, jsDataModel, gettext, gettextCatalog) {
var name = 'assignments/assignment'; var name = 'assignments/assignment';
var phases; var phases;
return DS.defineResource({ return DS.defineResource({
name: name, name: name,
useClass: jsDataModel, useClass: jsDataModel,
verboseName: gettext('Election'), verboseName: gettext('Election'),
agendaSupplement: gettext('Election'),
phases: phases, phases: phases,
getPhases: function () { getPhases: function () {
if (!this.phases) { if (!this.phases) {
this.phases = $http({ 'method': 'OPTIONS', 'url': '/rest/assignments/assignment/' }); this.phases = $http({ 'method': 'OPTIONS', 'url': '/rest/assignments/assignment/' })
.then(function(phases) {
return phases.data.actions.POST.phase.choices;
});
} }
return this.phases; return this.phases;
}, },

View File

@ -22,8 +22,16 @@ 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;
Assignment.find(id);
// load assignemt object and related agenda item
Assignment.find(id).then(function(assignment) {
Assignment.loadRelations(assignment, 'agenda_item');
});
Assignment.bindOne(id, $scope, 'assignment'); Assignment.bindOne(id, $scope, 'assignment');
Assignment.getPhases().then(function(phases) {
$scope.phases = phases;
});
// load all users
User.findAll(); User.findAll();
User.bindAll({}, $scope, 'users'); User.bindAll({}, $scope, 'users');
} }

View File

@ -36,19 +36,21 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
assignments: function(Assignment) { assignments: function(Assignment) {
return Assignment.findAll(); return Assignment.findAll();
}, },
items: function(Agenda) {
return Agenda.findAll();
},
phases: function(Assignment) { phases: function(Assignment) {
return Assignment.getPhases(); return Assignment.getPhases();
}, }
users: function(User) {
return User.findAll();
},
} }
}) })
.state('assignments.assignment.detail', { .state('assignments.assignment.detail', {
controller: 'AssignmentDetailCtrl', controller: 'AssignmentDetailCtrl',
resolve: { resolve: {
assignment: function(Assignment, $stateParams) { assignment: function(Assignment, $stateParams) {
return Assignment.find($stateParams.id); return Assignment.find($stateParams.id).then(function(assignment) {
return Assignment.loadRelations(assignment, 'agenda_item');
});
}, },
users: function(User) { users: function(User) {
return User.findAll(); return User.findAll();
@ -172,8 +174,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
'phases', 'phases',
function($scope, ngDialog, AssignmentForm, Assignment, phases) { function($scope, ngDialog, AssignmentForm, Assignment, phases) {
Assignment.bindAll({}, $scope, 'assignments'); Assignment.bindAll({}, $scope, 'assignments');
// get all item types via OPTIONS request $scope.phases = phases;
$scope.phases = phases.data.actions.POST.phase.choices;
$scope.alert = {}; $scope.alert = {};
// setup table sorting // setup table sorting
@ -266,8 +267,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
Assignment.bindOne(assignment.id, $scope, 'assignment'); Assignment.bindOne(assignment.id, $scope, 'assignment');
Assignment.loadRelations(assignment, 'agenda_item'); Assignment.loadRelations(assignment, 'agenda_item');
$scope.candidateSelectBox = {}; $scope.candidateSelectBox = {};
// get all item types via OPTIONS request $scope.phases = phases;
$scope.phases = phases.data.actions.POST.phase.choices;
$scope.alert = {}; $scope.alert = {};
// open edit dialog // open edit dialog

View File

@ -28,13 +28,9 @@
<i class="fa fa-pencil"></i> <i class="fa fa-pencil"></i>
</a> </a>
</div> </div>
<h1>{{ assignment.title }}</h1> <h1>{{ assignment.agenda_item.getTitle() }}</h1>
<h2> <h2>
<translate>Election</translate> <translate>Election</translate>
<span ng-if="assignment.agenda_item.item_number">
&ndash;
<translate>Agenda</translate>: {{ assignment.agenda_item.item_number }}
</span>
</h2> </h2>
</div> </div>
</div> </div>

View File

@ -80,19 +80,33 @@
<tr> <tr>
<!-- projector column --> <!-- projector column -->
<th ng-show="!isDeleteMode" os-perms="core.can_manage_projector" class="minimum"> <th ng-show="!isDeleteMode" os-perms="core.can_manage_projector" class="minimum">
<!-- delete selection column --> <!-- delete selection column -->
<th ng-show="isDeleteMode" os-perms="assignments.can_manage" class="minimum deleteColumn"> <th ng-show="isDeleteMode" os-perms="assignments.can_manage" class="minimum deleteColumn">
<input type="checkbox" ng-model="$parent.selectedAll" ng-change="checkAll()"> <input type="checkbox" ng-model="$parent.selectedAll" ng-change="checkAll()">
<!-- agenda item column -->
<th ng-click="toggleSort('agenda_item.item_number')" class="sortable">
<translate translate-comment="short form of agenda item">Item</translate>
<i class="pull-right fa" ng-show="sortColumn === 'agenda_item.item_number' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<!-- title column -->
<th ng-click="toggleSort('title')" class="sortable"> <th ng-click="toggleSort('title')" class="sortable">
<translate>Title</translate> <translate>Title</translate>
<i class="pull-right fa" ng-show="sortColumn === 'title' && header.sortable != false" <i class="pull-right fa" ng-show="sortColumn === 'title' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'"> ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i> </i>
<!-- candicates / posts column -->
<th ng-click="toggleSort('open_posts')" class="sortable optional"> <th ng-click="toggleSort('open_posts')" class="sortable optional">
<translate>Candidates</translate> &middot; <translate>Posts</translate> <translate>Candidates</translate> &middot; <translate>Posts</translate>
<i class="pull-right fa" ng-show="sortColumn === 'open_posts' && header.sortable != false" <i class="pull-right fa" ng-show="sortColumn === 'open_posts' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'"> ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i> </i>
<!-- phase column -->
<th ng-click="toggleSort('phase')" class="sortable optional"> <th ng-click="toggleSort('phase')" class="sortable optional">
<translate>Phase</translate> <translate>Phase</translate>
<i class="pull-right fa" ng-show="sortColumn === 'phase' && header.sortable != false" <i class="pull-right fa" ng-show="sortColumn === 'phase' && header.sortable != false"
@ -103,7 +117,8 @@
filter: {phase: phaseFilter} | orderBy: sortColumn:reverse)" filter: {phase: phaseFilter} | orderBy: sortColumn:reverse)"
class="animate-item" class="animate-item"
ng-class="{ 'activeline': assignment.isProjected(), 'selected': assignment.selected }"> ng-class="{ 'activeline': assignment.isProjected(), 'selected': assignment.selected }">
<!-- projector column -->
<!-- projector -->
<td ng-show="!isDeleteMode" os-perms="core.can_manage_projector"> <td ng-show="!isDeleteMode" os-perms="core.can_manage_projector">
<a class="btn btn-default btn-sm" <a class="btn btn-default btn-sm"
ng-class="{ 'btn-primary': assignment.isProjected() }" ng-class="{ 'btn-primary': assignment.isProjected() }"
@ -111,10 +126,16 @@
title="{{ 'Project election' | translate }}"> title="{{ 'Project election' | translate }}">
<i class="fa fa-video-camera"></i> <i class="fa fa-video-camera"></i>
</a> </a>
<!-- delete selection column -->
<!-- delete selection -->
<td ng-show="isDeleteMode" os-perms="assignments.can_manage" class="deleteColumn"> <td ng-show="isDeleteMode" os-perms="assignments.can_manage" class="deleteColumn">
<input type="checkbox" ng-model="assignment.selected"> <input type="checkbox" ng-model="assignment.selected">
<!-- assignment data colums -->
<!-- agenda item number -->
<td ng-if="!assignment.quickEdit">
{{ assignment.agenda_item.item_number }}
<!-- title -->
<td ng-if="!assignment.quickEdit" ng-mouseover="assignment.hover=true" ng-mouseleave="assignment.hover=false"> <td ng-if="!assignment.quickEdit" ng-mouseover="assignment.hover=true" ng-mouseleave="assignment.hover=false">
<strong><a ui-sref="assignments.assignment.detail({id: assignment.id})">{{ assignment.title }}</a></strong> <strong><a ui-sref="assignments.assignment.detail({id: assignment.id})">{{ assignment.title }}</a></strong>
<div os-perms="assignments.can_manage" class="hoverActions" ng-class="{'hiddenDiv': !assignment.hover}"> <div os-perms="assignments.can_manage" class="hoverActions" ng-class="{'hiddenDiv': !assignment.hover}">
@ -125,16 +146,21 @@
<b>{{ assignment.title }}</b>" <b>{{ assignment.title }}</b>"
ng-bootbox-confirm-action="delete(assignment)" translate>Delete</a> ng-bootbox-confirm-action="delete(assignment)" translate>Delete</a>
</div> </div>
<!-- candidates / posts -->
<td ng-if="!assignment.quickEdit" class="optional"> <td ng-if="!assignment.quickEdit" class="optional">
<span class="badge">{{ assignment.assignment_related_users.length }}</span> <span class="badge">{{ assignment.assignment_related_users.length }}</span>
/ /
<span class="badge">{{ assignment.open_posts }}</span> <span class="badge">{{ assignment.open_posts }}</span>
<!-- phase -->
<td ng-if="!assignment.quickEdit" class="optional"> <td ng-if="!assignment.quickEdit" class="optional">
<span class="label" ng-class="{'label-primary': assignment.phase == 0, <span class="label" ng-class="{'label-primary': assignment.phase == 0,
'label-warning': assignment.phase == 1, 'label-warning': assignment.phase == 1,
'label-success': assignment.phase == 2 }"> 'label-success': assignment.phase == 2 }">
{{ phases[assignment.phase].display_name }} {{ phases[assignment.phase].display_name }}
</span> </span>
<!-- quickEdit columns --> <!-- quickEdit columns -->
<td ng-if="assignment.quickEdit" colspan="3"> <td ng-if="assignment.quickEdit" colspan="3">
<h4>{{ assignment.title }} <span class="text-muted">&ndash; Quick Edit</span></h4> <h4>{{ assignment.title }} <span class="text-muted">&ndash; Quick Edit</span></h4>

View File

@ -1,9 +1,28 @@
<div ng-controller="SlideAssignmentCtrl" class="content scrollcontent"> <div ng-controller="SlideAssignmentCtrl" class="content scrollcontent">
<h1>{{ assignment.title }}</h1>
<div id="sidebox">
<!-- Phase -->
<h3 translate>State</h3>
{{ phases[assignment.phase].display_name }}
<!-- Posts -->
<h3 translate>Posts</h3>
{{ assignment.open_posts }}
</div>
<!-- Title -->
<div id="title">
<h1>{{ assignment.agenda_item.getTitle() }}</h1>
<h2>
<translate>Election</translate>
</h2>
</div>
<!-- Description -->
<div class="white-space-pre-line">{{ assignment.description }}</div> <div class="white-space-pre-line">{{ assignment.description }}</div>
<!-- candidates --> <!-- Candidates -->
<h2 translate>Candidates</h2> <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() }}

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('core', '0002_customslide_attachments'),
]
operations = [
migrations.AlterModelOptions(
name='projector',
options={'default_permissions': (), 'permissions': (
('can_see_projector', 'Can see the projector'),
('can_manage_projector', 'Can manage the projector'),
('can_see_frontpage', 'Can see the front page'))},
),
]

View File

@ -66,7 +66,7 @@ class Projector(RESTModelMixin, models.Model):
permissions = ( permissions = (
('can_see_projector', ugettext_noop('Can see the projector')), ('can_see_projector', ugettext_noop('Can see the projector')),
('can_manage_projector', ugettext_noop('Can manage the projector')), ('can_manage_projector', ugettext_noop('Can manage the projector')),
('can_see_dashboard', ugettext_noop('Can see the dashboard'))) ('can_see_frontpage', ugettext_noop('Can see the front page')))
@property @property
def elements(self): def elements(self):
@ -159,6 +159,9 @@ class CustomSlide(RESTModelMixin, models.Model):
def get_agenda_title(self): def get_agenda_title(self):
return self.title return self.title
def get_agenda_list_view_title(self):
return self.title
def get_search_index_string(self): def get_search_index_string(self):
""" """
Returns a string that can be indexed for the search. Returns a string that can be indexed for the search.

View File

@ -102,7 +102,7 @@ h2 {
} }
h3 { h3 {
color: #222; color: #222;
margin-bottom: 2px margin-bottom: 10px
} }
#title { #title {
width: calc(100% - 230px); width: calc(100% - 230px);

View File

@ -89,7 +89,9 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
// 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;
Customslide.find(id); Customslide.find(id).then(function(customslide) {
Customslide.loadRelations(customslide, 'agenda_item');
});
Customslide.bindOne(id, $scope, 'customslide'); Customslide.bindOne(id, $scope, 'customslide');
} }
]) ])

View File

@ -68,7 +68,7 @@ angular.module('OpenSlidesApp.core.site', [
'gettext', 'gettext',
function (mainMenuProvider, gettext) { function (mainMenuProvider, gettext) {
mainMenuProvider.register({ mainMenuProvider.register({
'ui_sref': 'dashboard', 'ui_sref': 'home',
'img_class': 'home', 'img_class': 'home',
'title': gettext('Home'), 'title': gettext('Home'),
'weight': 100, 'weight': 100,
@ -193,9 +193,9 @@ angular.module('OpenSlidesApp.core.site', [
function($stateProvider, $locationProvider) { function($stateProvider, $locationProvider) {
// Core urls // Core urls
$stateProvider $stateProvider
.state('dashboard', { .state('home', {
url: '/', url: '/',
templateUrl: 'static/templates/dashboard.html' templateUrl: 'static/templates/home.html'
}) })
.state('projector', { .state('projector', {
url: '/projector', url: '/projector',
@ -556,11 +556,11 @@ angular.module('OpenSlidesApp.core.site', [
} }
}, },
{ {
key: 'showOnAgenda', key: 'showAsAgendaItem',
type: 'checkbox', type: 'checkbox',
templateOptions: { templateOptions: {
label: gettextCatalog.getString('Show on agenda'), label: gettextCatalog.getString('Show as agenda item'),
description: gettextCatalog.getString('If deactivated it appears as internal item.') description: gettextCatalog.getString('If deactivated it appears as internal item on agenda.')
} }
}, },
]; ];
@ -791,6 +791,8 @@ angular.module('OpenSlidesApp.core.site', [
'Agenda', 'Agenda',
function($scope, $state, Customslide, CustomslideForm, Agenda) { function($scope, $state, Customslide, CustomslideForm, Agenda) {
$scope.customslide = {}; $scope.customslide = {};
$scope.model = {};
$scope.model.showAsAgendaItem = true;
// get all form fields // get all form fields
$scope.formFields = CustomslideForm.getFormFields(); $scope.formFields = CustomslideForm.getFormFields();
@ -798,14 +800,16 @@ angular.module('OpenSlidesApp.core.site', [
$scope.save = function (customslide) { $scope.save = function (customslide) {
Customslide.create(customslide).then( Customslide.create(customslide).then(
function(success) { function(success) {
// show as agenda item // find related agenda item
if (customslide.showOnAgenda) { Agenda.find(success.agenda_item_id).then(function(item) {
Agenda.find(success.agenda_item_id).then(function(item) { // check form element and set item type (AGENDA_ITEM = 1, HIDDEN_ITEM = 2)
// set item type to AGENDA_ITEM = 1 (default is HIDDEN_ITEM = 2) var type = customslide.showAsAgendaItem ? 1 : 2;
item.type = 1; // save only if agenda item type is modified
if (item.type != type) {
item.type = type;
Agenda.save(item); Agenda.save(item);
}); }
} });
$scope.closeThisDialog(); $scope.closeThisDialog();
} }
); );
@ -818,14 +822,21 @@ angular.module('OpenSlidesApp.core.site', [
'$state', '$state',
'Customslide', 'Customslide',
'CustomslideForm', 'CustomslideForm',
'Agenda',
'customslide', 'customslide',
function($scope, $state, Customslide, CustomslideForm, customslide) { function($scope, $state, Customslide, CustomslideForm, Agenda, customslide) {
$scope.alert = {}; $scope.alert = {};
// set initial values for form model by create deep copy of customslide object // set initial values for form model by create deep copy of customslide object
// so list/detail view is not updated while editing // so list/detail view is not updated while editing
$scope.model = angular.copy(customslide); $scope.model = angular.copy(customslide);
// get all form fields // get all form fields
$scope.formFields = CustomslideForm.getFormFields(); $scope.formFields = CustomslideForm.getFormFields();
for (var i = 0; i < $scope.formFields.length; i++) {
if ($scope.formFields[i].key == "showAsAgendaItem") {
// get state from agenda item (hidden/internal or agenda item)
$scope.formFields[i].defaultValue = !customslide.agenda_item.is_hidden;
}
}
// save form // save form
$scope.save = function (customslide) { $scope.save = function (customslide) {
@ -834,6 +845,12 @@ angular.module('OpenSlidesApp.core.site', [
// save change customslide object on server // save change customslide object on server
Customslide.save(customslide).then( Customslide.save(customslide).then(
function(success) { function(success) {
// save agenda specific stuff
var type = customslide.showAsAgendaItem ? 1 : 2;
if (customslide.agenda_item.type != type) {
customslide.agenda_item.type = type;
Agenda.save(customslide.agenda_item);
}
$scope.closeThisDialog(); $scope.closeThisDialog();
}, },
function (error) { function (error) {

View File

@ -8,7 +8,7 @@
<i class="fa toggle-icon" ng-class="isLiveViewClosed ? 'fa-angle-up' : 'fa-angle-down'"></i> <i class="fa toggle-icon" ng-class="isLiveViewClosed ? 'fa-angle-up' : 'fa-angle-down'"></i>
<h4 translate>Live view</h4> <h4 translate>Live view</h4>
</a> </a>
<div uib-collapse="isLiveViewClosed"> <div uib-collapse="isLiveViewClosed" ng-cloak>
<a ui-sref="projector" target="_blank"> <a ui-sref="projector" target="_blank">
<div id="iframewrapper"> <div id="iframewrapper">
<iframe id="iframe" src="/projector" frameborder="0"></iframe> <iframe id="iframe" src="/projector" frameborder="0"></iframe>
@ -61,7 +61,7 @@
<i class="fa toggle-icon" ng-class="isCountdowns ? 'fa-angle-up' : 'fa-angle-down'"></i> <i class="fa toggle-icon" ng-class="isCountdowns ? 'fa-angle-up' : 'fa-angle-down'"></i>
<h4 translate>Countdowns</h4> <h4 translate>Countdowns</h4>
</a> </a>
<div uib-collapse="!isCountdowns"> <div uib-collapse="!isCountdowns" ng-cloak>
<div ng-repeat="countdown in countdowns | orderBy: 'index'" id="{{countdown.uuid}}" <div ng-repeat="countdown in countdowns | orderBy: 'index'" id="{{countdown.uuid}}"
class="countdown panel panel-default"> class="countdown panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
@ -153,7 +153,7 @@
<i class="fa toggle-icon" ng-class="isMessages ? 'fa-angle-up' : 'fa-angle-down'"></i> <i class="fa toggle-icon" ng-class="isMessages ? 'fa-angle-up' : 'fa-angle-down'"></i>
<h4 translate>Messages</h4> <h4 translate>Messages</h4>
</a> </a>
<div uib-collapse="!isMessages"> <div uib-collapse="!isMessages" ng-cloak>
<div ng-repeat="message in messages | orderBy: 'index'" id="{{message.uuid}}" class="message panel panel-default"> <div ng-repeat="message in messages | orderBy: 'index'" id="{{message.uuid}}" class="message panel panel-default">
<div class="panel-body" <div class="panel-body"
ng-class="{ 'projected': message.visible }"> ng-class="{ 'projected': message.visible }">

View File

@ -1,4 +1,4 @@
<div ng-controller="SlideCustomSlideCtrl" class="content scrollcontent"> <div ng-controller="SlideCustomSlideCtrl" class="content scrollcontent">
<h1>{{ customslide.title }}</h1> <h1>{{ customslide.agenda_item.getTitle() }}</h1>
<div ng-bind-html="customslide.text"></div> <div ng-bind-html="customslide.text"></div>
</div> </div>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="no-js"> <!-- TODO: make lang dynamic --> <html ng-controller="LanguageCtrl" lang="{{ selectedLanguage[0].code }}" class="no-js">
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<base href="/"> <base href="/">
@ -11,17 +11,17 @@
<link rel="icon" href="/static/img/favicon.png"> <link rel="icon" href="/static/img/favicon.png">
<script src="static/js/openslides-libs.js"></script> <script src="static/js/openslides-libs.js"></script>
<script src="static/ckeditor/ckeditor.js"></script> <script src="static/ckeditor/ckeditor.js"></script>
<div id="wrapper">
<div id="wrapper" ng-cloak>
<!-- Header --> <!-- Header -->
<div id="header"> <div id="header">
<div class="containerOS"> <div class="containerOS">
<!-- Logo --> <!-- Logo -->
<div class="title"> <div class="title">
<a ui-sref="dashboard"> <a ui-sref="home">
<img src="/static/img/openslides-logo-dark.png" alt="Logo" height="35"> <img src="/static/img/openslides-logo-dark.png" alt="Logo" height="35">
</a> </a>
<!-- TODO: <span class="navbar-text optional">{{ config('general_event_name') }}</span>-->
</div> </div>
<!-- user specific header (chat, user settings / login, language)--> <!-- user specific header (chat, user settings / login, language)-->
@ -104,7 +104,7 @@
</div> </div>
<!-- language switcher --> <!-- language switcher -->
<span ng-controller="LanguageCtrl" uib-dropdown> <span uib-dropdown>
| <a href class="headerlink" uib-dropdown-toggle> | <a href class="headerlink" uib-dropdown-toggle>
<i class="fa fa-flag"></i> <i class="fa fa-flag"></i>
{{ selectedLanguage[0].name | translate }} {{ selectedLanguage[0].name | translate }}
@ -157,7 +157,7 @@
<div class="containerOS"> <div class="containerOS">
<div class="col1" ng-class="isProjectorSidebar ? 'min' : 'max'"> <div class="col1" ng-class="isProjectorSidebar ? 'min' : 'max'">
<!-- dynamic views --> <!-- dynamic views -->
<div ui-view></div> <div ui-view ng-cloak></div>
<!-- footer --> <!-- footer -->
<div id="footer"> <div id="footer">
&copy; Copyright by <a href="http://www.openslides.org" target="_blank">OpenSlides</a> | &copy; Copyright by <a href="http://www.openslides.org" target="_blank">OpenSlides</a> |

View File

@ -0,0 +1,23 @@
<div class="header">
<div class="title">
<h1 translate>Search results</h1>
</div>
</div>
<div class="details">
<form class="input-group" ng-submit="search(query)">
<input type="text" ng-model="query" class="form-control">
<span class="input-group-btn">
<button type="submit" class="btn btn-default" translate>Search</button>
</span>
</form>
<div class="searchresults spacer-top-lg">
<ol ng-show="results">
<li ng-repeat="result in results">
<a ui-sref="{{ result.urlState }}({{ result.urlParam }})">{{ result.getSearchResultName() }}</a><br>
<span class="grey">{{ result.getSearchResultSubtitle() | translate }}</span>
</ol>
<p ng-show="!results" translate>No results.</p>
</div>
</div>

View File

@ -125,13 +125,9 @@ class Motion(RESTModelMixin, models.Model):
def __str__(self): def __str__(self):
""" """
Return a human readable name of this motion. Return the title of this motion.
""" """
if self.identifier: return self.title
string = '%s: %s' % (self.identifier, self.title)
else:
string = self.title
return string
# TODO: Use transaction # TODO: Use transaction
def save(self, use_version=None, *args, **kwargs): def save(self, use_version=None, *args, **kwargs):
@ -464,11 +460,25 @@ class Motion(RESTModelMixin, models.Model):
def get_agenda_title(self): def get_agenda_title(self):
""" """
Return a title for the agenda. Return a simple title string for the agenda.
Contains only agenda item number and title.
""" """
# There has to be a function with the same return value in javascript.
return str(self) return str(self)
def get_agenda_list_view_title(self):
"""
Return a title string for the agenda list view.
Contains agenda item number, title and motion identifier.
Note: It has to be the same return value like in JavaScript.
"""
if self.identifier:
string = '%s (%s %s)' % (self.title, _(self._meta.verbose_name), self.identifier)
else:
string = '%s (%s)' % (self.title, _(self._meta.verbose_name))
return string
@property @property
def agenda_item(self): def agenda_item(self):
""" """

View File

@ -161,15 +161,15 @@ angular.module('OpenSlidesApp.motions', ['OpenSlidesApp.users'])
'MotionPoll', 'MotionPoll',
'jsDataModel', 'jsDataModel',
'gettext', 'gettext',
'gettextCatalog',
'operator', 'operator',
'Config', 'Config',
function(DS, MotionPoll, jsDataModel, gettext, operator, Config) { function(DS, MotionPoll, jsDataModel, gettext, gettextCatalog, operator, Config) {
var name = 'motions/motion'; var name = 'motions/motion';
return DS.defineResource({ return DS.defineResource({
name: name, name: name,
useClass: jsDataModel, useClass: jsDataModel,
verboseName: gettext('Motion'), verboseName: gettext('Motion'),
agendaSupplement: gettext('Motion'),
methods: { methods: {
getResourceName: function () { getResourceName: function () {
return name; return name;
@ -195,16 +195,9 @@ angular.module('OpenSlidesApp.motions', ['OpenSlidesApp.users'])
getReason: function (versionId) { getReason: function (versionId) {
return this.getVersion(versionId).reason; return this.getVersion(versionId).reason;
}, },
getAgendaTitle: function () {
var value = '';
if (this.identifier) {
value = ' ' + this.identifier;
}
return "Motion " + value + ': ' + this.getTitle();
},
// link name which is shown in search result // link name which is shown in search result
getSearchResultName: function () { getSearchResultName: function () {
return this.getAgendaTitle(); return this.getTitle();
}, },
// subtitle of search result // subtitle of search result
getSearchResultSubtitle: function () { getSearchResultSubtitle: function () {

View File

@ -22,9 +22,16 @@ angular.module('OpenSlidesApp.motions.projector', ['OpenSlidesApp.motions'])
// 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;
Motion.find(id);
User.findAll(); // load motion object and related agenda item
Motion.find(id).then(function(motion) {
Motion.loadRelations(motion, 'agenda_item');
});
Motion.bindOne(id, $scope, 'motion'); Motion.bindOne(id, $scope, 'motion');
// load all users
User.findAll();
User.bindAll({}, $scope, 'users');
} }
]); ]);

View File

@ -34,7 +34,12 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
.state('motions.motion.list', { .state('motions.motion.list', {
resolve: { resolve: {
motions: function(Motion) { motions: function(Motion) {
return Motion.findAll(); return Motion.findAll().then(function(motions) {
angular.forEach(motions, function(motion) {
Motion.loadRelations(motion, 'agenda_item');
});
});
}, },
categories: function(Category) { categories: function(Category) {
return Category.findAll(); return Category.findAll();
@ -53,7 +58,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
.state('motions.motion.detail', { .state('motions.motion.detail', {
resolve: { resolve: {
motion: function(Motion, $stateParams) { motion: function(Motion, $stateParams) {
return Motion.find($stateParams.id); return Motion.find($stateParams.id).then(function(motion) {
return Motion.loadRelations(motion, 'agenda_item');
});
}, },
categories: function(Category) { categories: function(Category) {
return Category.findAll(); return Category.findAll();
@ -85,7 +92,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
resolve: { resolve: {
motion: function() { motion: function() {
return Motion.find($stateParams.id).then(function(motion) { return Motion.find($stateParams.id).then(function(motion) {
return Motion.loadRelations(motion, 'agenda_item'); Motion.loadRelations(motion, 'agenda_item');
}); });
}, },
}, },

View File

@ -28,7 +28,7 @@
<i class="fa fa-pencil"></i> <i class="fa fa-pencil"></i>
</a> </a>
</div> </div>
<h1>{{ motion.getTitle(version) }}</h1> <h1>{{ motion.agenda_item.getTitle() }}</h1>
<h2> <h2>
<translate>Motion</translate> {{ motion.identifier }} <translate>Motion</translate> {{ motion.identifier }}
<span ng-if="motion.versions.length > 1" >| Version {{ motion.getVersion(version).version_number }}</span> <span ng-if="motion.versions.length > 1" >| Version {{ motion.getVersion(version).version_number }}</span>
@ -36,10 +36,6 @@
<i class="fa fa-exclamation-triangle"></i> <i class="fa fa-exclamation-triangle"></i>
<translate>This version is not permitted.</translate> <translate>This version is not permitted.</translate>
</span> </span>
<span ng-if="motion.agenda_item.item_number">
&ndash;
<translate>Agenda</translate>: {{ motion.agenda_item.item_number }}
</span>
</h2> </h2>
</div> </div>
</div> </div>

View File

@ -89,26 +89,42 @@
<!-- delete selection column --> <!-- delete selection column -->
<th ng-show="$parent.isDeleteMode" os-perms="motions.can_manage" class="minimum deleteColumn"> <th ng-show="$parent.isDeleteMode" os-perms="motions.can_manage" class="minimum deleteColumn">
<input type="checkbox" ng-model="$parent.selectedAll" ng-change="checkAll()"> <input type="checkbox" ng-model="$parent.selectedAll" ng-change="checkAll()">
<!-- agenda item column -->
<th ng-click="toggleSort('agenda_item.item_number')" class="sortable">
<translate translate-comment="short form of agenda item">Item</translate>
<i class="pull-right fa" ng-show="sortColumn === 'agenda_item.item_number' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<!-- identifier column -->
<th ng-click="toggleSort('identifier')" class="sortable minimum"> <th ng-click="toggleSort('identifier')" class="sortable minimum">
<translate>Identifier</translate> <translate>Identifier</translate>
<i class="pull-right fa" ng-show="sortColumn === 'identifier' && header.sortable != false" <i class="pull-right fa" ng-show="sortColumn === 'identifier' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'"> ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i> </i>
<!-- title column -->
<th ng-click="toggleSort('getTitle()')" class="sortable"> <th ng-click="toggleSort('getTitle()')" class="sortable">
<translate>Title</translate> <translate>Title</translate>
<i class="pull-right fa" ng-show="sortColumn === 'getTitle()' && header.sortable != false" <i class="pull-right fa" ng-show="sortColumn === 'getTitle()' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'"> ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i> </i>
<!-- submitters column -->
<th ng-click="toggleSort('submitters')" class="sortable optional"> <th ng-click="toggleSort('submitters')" class="sortable optional">
<translate>Submitters</translate> <translate>Submitters</translate>
<i class="pull-right fa" ng-show="sortColumn === 'submitters' && header.sortable != false" <i class="pull-right fa" ng-show="sortColumn === 'submitters' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'"> ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i> </i>
<!-- category column -->
<th ng-click="toggleSort('category')" class="sortable optional"> <th ng-click="toggleSort('category')" class="sortable optional">
<translate>Category</translate> <translate>Category</translate>
<i class="pull-right fa" ng-show="sortColumn === 'category' && header.sortable != false" <i class="pull-right fa" ng-show="sortColumn === 'category' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'"> ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i> </i>
<!-- state column -->
<th ng-click="toggleSort('state.name')" class="sortable optional"> <th ng-click="toggleSort('state.name')" class="sortable optional">
<translate>State</translate> <translate>State</translate>
<i class="pull-right fa" ng-show="sortColumn === 'state.name' && header.sortable != false" <i class="pull-right fa" ng-show="sortColumn === 'state.name' && header.sortable != false"
@ -119,7 +135,8 @@
filter: {state_id: stateFilter} | orderBy: sortColumn:reverse)" filter: {state_id: stateFilter} | orderBy: sortColumn:reverse)"
class="animate-item" class="animate-item"
ng-class="{ 'activeline': motion.isProjected(), 'selected': motion.selected }"> ng-class="{ 'activeline': motion.isProjected(), 'selected': motion.selected }">
<!-- projector column -->
<!-- projector -->
<td ng-show="!isDeleteMode" os-perms="core.can_manage_projector"> <td ng-show="!isDeleteMode" os-perms="core.can_manage_projector">
<a class="btn btn-default btn-sm" <a class="btn btn-default btn-sm"
ng-class="{ 'btn-primary': motion.isProjected() }" ng-class="{ 'btn-primary': motion.isProjected() }"
@ -127,11 +144,18 @@
title="{{ 'Project motion' | translate }}"> title="{{ 'Project motion' | translate }}">
<i class="fa fa-video-camera"></i> <i class="fa fa-video-camera"></i>
</a> </a>
<!-- delete selection column -->
<!-- delete selection -->
<td ng-show="isDeleteMode" os-perms="motions.can_manage" class="deleteColumn"> <td ng-show="isDeleteMode" os-perms="motions.can_manage" class="deleteColumn">
<input type="checkbox" ng-model="motion.selected"> <input type="checkbox" ng-model="motion.selected">
<!-- motion data colums -->
<!-- agenda item number -->
<td ng-if="!motion.quickEdit">{{ motion.agenda_item.item_number }}
<!-- identifier -->
<td ng-if="!motion.quickEdit">{{ motion.identifier }} <td ng-if="!motion.quickEdit">{{ motion.identifier }}
<!-- title -->
<td ng-if="!motion.quickEdit" ng-mouseover="motion.hover=true" ng-mouseleave="motion.hover=false"> <td ng-if="!motion.quickEdit" ng-mouseover="motion.hover=true" ng-mouseleave="motion.hover=false">
<strong><a ui-sref="motions.motion.detail({id: motion.id})">{{ motion.getTitle() }}</a></strong> <strong><a ui-sref="motions.motion.detail({id: motion.id})">{{ motion.getTitle() }}</a></strong>
<span ng-repeat="tag in motion.tags" class="label label-default"> <span ng-repeat="tag in motion.tags" class="label label-default">
@ -151,16 +175,23 @@
ng-bootbox-confirm-action="delete(motion)" translate>Delete</a> ng-bootbox-confirm-action="delete(motion)" translate>Delete</a>
</span> </span>
</div> </div>
<!-- submitters -->
<td ng-if="!motion.quickEdit" class="optional"> <td ng-if="!motion.quickEdit" class="optional">
<div ng-repeat="submitter in motion.submitters"> <div ng-repeat="submitter in motion.submitters">
{{ submitter.get_full_name() }}<br> {{ submitter.get_full_name() }}<br>
</div> </div>
<!-- category -->
<td ng-if="!motion.quickEdit" class="optional"> <td ng-if="!motion.quickEdit" class="optional">
{{ motion.category.name }} {{ motion.category.name }}
<!-- state -->
<td ng-if="!motion.quickEdit" class="optional"> <td ng-if="!motion.quickEdit" class="optional">
<span class="label" ng-class="'label-'+motion.state.css_class"> <span class="label" ng-class="'label-'+motion.state.css_class">
{{ motion.state.name | translate }} {{ motion.state.name | translate }}
</span> </span>
<!-- quickEdit columns --> <!-- quickEdit columns -->
<td ng-if="motion.quickEdit && motion.isAllowed('quickedit')" colspan="5"> <td ng-if="motion.quickEdit && motion.isAllowed('quickedit')" colspan="5">
<h4>{{ motion.getTitle() }} <span class="text-muted">&ndash; <translate>QuickEdit</translate></span></h4> <h4>{{ motion.getTitle() }} <span class="text-muted">&ndash; <translate>QuickEdit</translate></span></h4>

View File

@ -14,7 +14,7 @@
<!-- Title --> <!-- Title -->
<div id="title"> <div id="title">
<h1>{{ motion.getTitle() }}</h1> <h1>{{ motion.agenda_item.getTitle() }}</h1>
<h2> <h2>
<translate>Motion</translate> {{ motion.identifier }} <translate>Motion</translate> {{ motion.identifier }}
<span ng-if="motion.versions.length > 1" >| Version {{ motion.getVersion().version_number }}</span> <span ng-if="motion.versions.length > 1" >| Version {{ motion.getVersion().version_number }}</span>
@ -25,6 +25,6 @@
<div ng-bind-html="motion.getText()"></div> <div ng-bind-html="motion.getText()"></div>
<!-- Reason --> <!-- Reason -->
<h2 ng-if="motion.getReason()" translate>Reason</h2> <h3 ng-if="motion.getReason()" translate>Reason</h3>
<div ng-bind-html="motion.getReason()"></div> <div ng-bind-html="motion.getReason()"></div>
</div> </div>

View File

@ -113,7 +113,7 @@ def create_builtin_groups_and_admin(**kwargs):
'core.can_manage_config', 'core.can_manage_config',
'core.can_manage_projector', 'core.can_manage_projector',
'core.can_manage_tags', 'core.can_manage_tags',
'core.can_see_dashboard', 'core.can_see_frontpage',
'core.can_see_projector', 'core.can_see_projector',
'core.can_use_chat', 'core.can_use_chat',
'mediafiles.can_manage', 'mediafiles.can_manage',
@ -143,7 +143,7 @@ def create_builtin_groups_and_admin(**kwargs):
permission_dict['agenda.can_see'], permission_dict['agenda.can_see'],
permission_dict['agenda.can_see_hidden_items'], permission_dict['agenda.can_see_hidden_items'],
permission_dict['assignments.can_see'], permission_dict['assignments.can_see'],
permission_dict['core.can_see_dashboard'], permission_dict['core.can_see_frontpage'],
permission_dict['core.can_see_projector'], permission_dict['core.can_see_projector'],
permission_dict['mediafiles.can_see'], permission_dict['mediafiles.can_see'],
permission_dict['motions.can_see'], permission_dict['motions.can_see'],

View File

@ -130,7 +130,7 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
closeByEscape: $stateParams.guest_enabled, closeByEscape: $stateParams.guest_enabled,
closeByDocument: $stateParams.guest_enabled, closeByDocument: $stateParams.guest_enabled,
preCloseCallback: function() { preCloseCallback: function() {
$state.go('dashboard'); $state.go('home');
return true; return true;
} }
}); });

View File

@ -128,7 +128,7 @@ class ModelTest(TestCase):
motion.active_version = None motion.active_version = None
motion.save(update_fields=['active_version']) motion.save(update_fields=['active_version'])
# motion.__unicode__() raised an AttributeError # motion.__unicode__() raised an AttributeError
self.assertEqual(str(motion), 'test_identifier_VohT1hu9uhiSh6ooVBFS: test_title_Koowoh1ISheemeey1air') self.assertEqual(str(motion), 'test_title_Koowoh1ISheemeey1air')
def test_is_amendment(self): def test_is_amendment(self):
config['motions_amendments_enabled'] = True config['motions_amendments_enabled'] = True