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:
parent
741cae028c
commit
23503eb4ba
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
# General
|
||||
*.pyc
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Virtual Environment
|
||||
|
@ -299,6 +299,17 @@ class Item(RESTModelMixin, models.Model):
|
||||
raise NotImplementedError('You have to provide a get_agenda_title '
|
||||
'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):
|
||||
"""
|
||||
Returns True if the type of this object itself is a hidden item or any
|
||||
|
@ -53,6 +53,7 @@ class ItemSerializer(ModelSerializer):
|
||||
'id',
|
||||
'item_number',
|
||||
'title',
|
||||
'list_view_title',
|
||||
'comment',
|
||||
'closed',
|
||||
'type',
|
||||
|
@ -48,12 +48,24 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
|
||||
try {
|
||||
title = this.getContentObject().getAgendaTitle();
|
||||
} catch (e) {
|
||||
// Only use this.title when the content object is not
|
||||
// in the DS store.
|
||||
// when the content object is not in the DS store.
|
||||
title = this.title;
|
||||
}
|
||||
if (this.getContentResource().agendaSupplement) {
|
||||
title = gettextCatalog.getString(this.getContentResource().agendaSupplement) + ' ' + title;
|
||||
if (this.item_number) {
|
||||
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) {
|
||||
title = this.item_number + ' · ' + title;
|
||||
|
@ -280,6 +280,14 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||
$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
|
||||
$scope.treeOptions = {
|
||||
dropped: function (event) {
|
||||
|
@ -7,7 +7,7 @@
|
||||
</a>
|
||||
<a href="" ng-click="open(item)" class="btn btn-sm btn-default">
|
||||
<i class="fa fa-angle-double-left fa-lg"></i>
|
||||
{{ item.getContentResource().verboseName }}
|
||||
{{ item.getContentResource().verboseName | translate }}
|
||||
</a>
|
||||
<!-- project list of speakers -->
|
||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||
@ -22,7 +22,7 @@
|
||||
ng-click="item.project()"
|
||||
title="{{ 'Project item' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
{{ item.getContentResource().verboseName }}
|
||||
{{ item.getContentResource().verboseName | translate }}
|
||||
</a>
|
||||
</div>
|
||||
<h1>{{ item.getTitle() }}</h1>
|
||||
@ -68,7 +68,7 @@
|
||||
<div class="spacer">
|
||||
<h3 translate>Last speakers</h3>
|
||||
<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">Hide</translate>
|
||||
</button>
|
||||
@ -77,8 +77,9 @@
|
||||
<li ng-repeat="speaker in item.speakers | filter: {end_time: '!!'}">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
<small class="grey">
|
||||
[{{ speaker.begin_time | date:'yyyy-MM-dd HH:mm:ss' }} –
|
||||
{{ speaker.end_time | date:'yyyy-MM-dd HH:mm:ss' }}]
|
||||
{{ getDuration(speaker) | osSecondsToTime }} <translate>minutes</translate>
|
||||
(<translate>Start time</translate>:
|
||||
{{ speaker.begin_time | date:'yyyy-MM-dd HH:mm:ss' }})
|
||||
</small>
|
||||
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
|
||||
class="btn btn-default btn-xs" title="{{ 'Remove' | translate }}">
|
||||
@ -94,11 +95,11 @@
|
||||
filter: {end_time: null, begin_time: '!!'}">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
<button os-perms="agenda.can_manage" ng-click="endSpeech()"
|
||||
class="btn btn-default btn-xs" title="{{ 'End speech' | translate }}">
|
||||
<i class="fa fa-microphone-slash"></i>
|
||||
class="btn btn-default btn-sm" title="{{ 'End speech' | translate }}">
|
||||
<i class="fa fa-microphone-slash"></i> <translate>Stop</translate>
|
||||
</button>
|
||||
<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>
|
||||
</button>
|
||||
</strong>
|
||||
@ -113,11 +114,11 @@
|
||||
{{ $index + 1 }}.
|
||||
{{ speaker.user.get_full_name() }}
|
||||
<button os-perms="agenda.can_manage" ng-click="beginSpeech(speaker.id)"
|
||||
class="btn btn-default btn-xs" title="{{ 'Begin speech' | translate }}">
|
||||
<i class="fa fa-microphone"></i>
|
||||
class="btn btn-default btn-sm" title="{{ 'Begin speech' | translate }}">
|
||||
<i class="fa fa-microphone"></i> <translate>Start</translate>
|
||||
</button>
|
||||
<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>
|
||||
</button>
|
||||
</ol>
|
||||
|
@ -28,19 +28,18 @@
|
||||
|
||||
<div class="details">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<form class="form-inline">
|
||||
<!-- delete mode -->
|
||||
<button os-perms="agenda.can_manage" class="btn"
|
||||
ng-class="$parent.isDeleteMode ? 'btn-primary' : 'btn-default'"
|
||||
ng-click="$parent.isDeleteMode = !$parent.isDeleteMode; uncheckAll()">
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
<translate>Select ...</translate>
|
||||
</button>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-7">
|
||||
<div class="form-inline">
|
||||
<!-- delete mode -->
|
||||
<button os-perms="agenda.can_manage" class="btn"
|
||||
ng-class="$parent.isDeleteMode ? 'btn-primary' : 'btn-default'"
|
||||
ng-click="$parent.isDeleteMode = !$parent.isDeleteMode; uncheckAll()">
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
<translate>Select ...</translate>
|
||||
</button>
|
||||
<!-- project agenda button -->
|
||||
<a os-perms="core.can_manage_projector"
|
||||
class="btn btn-default form-control"
|
||||
class="btn btn-default"
|
||||
title="{{ 'Project agenda' | translate }}"
|
||||
ng-click="projectAgenda()"
|
||||
ng-class="{ 'btn-primary': isAgendaProjected() }">
|
||||
@ -49,15 +48,14 @@
|
||||
</a>
|
||||
<!-- auto numbering button -->
|
||||
<a os-perms="core.can_manage_projector"
|
||||
class="btn btn-default form-control"
|
||||
class="btn btn-default"
|
||||
ng-click="autoNumbering()">
|
||||
<i class="fa fa-sort-numeric-asc"></i>
|
||||
<translate>Number agenda</translate>
|
||||
<translate>Numbering</translate>
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="col-sm-5">
|
||||
<div class="form-inline text-right">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
@ -138,29 +136,34 @@
|
||||
style="padding-left: calc(8px + {{ item.parentCount }}*15px)">
|
||||
<strong>
|
||||
<a href="" ng-click="open(item)">
|
||||
{{ item.getTitle() }}
|
||||
{{ item.getListViewTitle() }}
|
||||
</a>
|
||||
</strong>
|
||||
<span ng-if="item.is_hidden" title="{{ 'Internal item' | translate }}"><i class="fa fa-ban"></i></span>
|
||||
<div ng-if="item.comment">
|
||||
<small><i class="fa fa-info-circle"></i> {{ item.comment }}</small>
|
||||
</div>
|
||||
<div os-perms="agenda.can_manage" class="hoverActions" ng-class="{'hiddenDiv': !item.hover}">
|
||||
<a ui-sref="agenda.item.detail({id: item.id})" translate>List of speakers</a> |
|
||||
<a href="" ng-click="editDialog(item)" translate>Edit</a> |
|
||||
<a href="" ng-click="item.quickEdit=true" translate>QuickEdit</a>
|
||||
<span ng-if="item.content_object.collection == 'core/customslide'"> |
|
||||
<a href="" class="text-danger"
|
||||
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
|
||||
<b>{{ item.getTitle() }}</b>"
|
||||
ng-bootbox-confirm-action="deleteRelatedItem(item)" translate>Delete</a>
|
||||
<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>
|
||||
<span os-perms="agenda.can_manage"> |
|
||||
<a href="" ng-click="editDialog(item)" translate>Edit</a> |
|
||||
<a href="" ng-click="item.quickEdit=true" translate>QuickEdit</a>
|
||||
<span ng-if="item.content_object.collection == 'core/customslide'"> |
|
||||
<a href="" class="text-danger"
|
||||
ng-bootbox-confirm="{{ 'Are you sure you want to delete this entry?' | translate }}<br>
|
||||
<b>{{ item.getTitle() }}</b>"
|
||||
ng-bootbox-confirm-action="deleteRelatedItem(item)" translate>Delete</a>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<td ng-show="!item.quickEdit" os-perms="agenda.can_see_hidden_items" class="optional">
|
||||
{{ item.duration }}
|
||||
<span ng-if="item.duration" translate-comment="'h' means time in hours" translate>h</span>
|
||||
<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 -->
|
||||
<td ng-show="item.quickEdit" os-perms="agenda.can_manage" colspan="3">
|
||||
<form ng-submit="save(item)">
|
||||
@ -170,8 +173,8 @@
|
||||
</uib-alert>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<label for="inputTitle" translate>Title</label>
|
||||
<input type="text" ng-model="item.title" class="form-control input-sm" id="inputTitle">
|
||||
<label for="inputItemNumber" translate>Item number</label>
|
||||
<input type="text" ng-model="item.item_number" class="form-control input-sm" id="inputItemNumber">
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<label for="inputComment" translate>Comment</label>
|
||||
@ -180,25 +183,15 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6">
|
||||
<label for="inputItemNumber" translate>Item number</label>
|
||||
<input type="text" ng-model="item.item_number" class="form-control input-sm" id="inputItemNumber">
|
||||
<!-- item type: AGENDA_ITEM = 1, HIDDEN_ITEM = 2 -->
|
||||
<input type="checkbox" ng-model="item.type" ng-true-value="1" ng-false-value="2">
|
||||
<translate>Show as agenda item</translate>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<label for="inputDuration" translate>Duration</label>
|
||||
<input type="text" ng-model="item.duration" class="form-control input-sm" id="inputDuration">
|
||||
</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">
|
||||
<button ng-click="item.quickEdit=false" class="btn btn-default pull-left" translate>
|
||||
Cancel
|
||||
|
@ -5,7 +5,7 @@
|
||||
<!-- item type: AGENDA_ITEM = 1, HIDDEN_ITEM = 2 -->
|
||||
<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"> </span>
|
||||
{{ item.getTitle() }}
|
||||
{{ item.getListViewTitle() }}
|
||||
<i ng-if="item.closed" class="fa fa-check"></i>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -3,6 +3,7 @@ from collections import OrderedDict
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy, ugettext_noop
|
||||
|
||||
from openslides.agenda.models import Item, Speaker
|
||||
@ -268,6 +269,15 @@ class Assignment(RESTModelMixin, models.Model):
|
||||
def get_agenda_title(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
|
||||
def agenda_item(self):
|
||||
"""
|
||||
|
@ -73,18 +73,21 @@ angular.module('OpenSlidesApp.assignments', [])
|
||||
'AssignmentPoll',
|
||||
'jsDataModel',
|
||||
'gettext',
|
||||
function ($http, DS, AssignmentRelatedUser, AssignmentPoll, jsDataModel, gettext) {
|
||||
'gettextCatalog',
|
||||
function ($http, DS, AssignmentRelatedUser, AssignmentPoll, jsDataModel, gettext, gettextCatalog) {
|
||||
var name = 'assignments/assignment';
|
||||
var phases;
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
verboseName: gettext('Election'),
|
||||
agendaSupplement: gettext('Election'),
|
||||
phases: phases,
|
||||
getPhases: function () {
|
||||
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;
|
||||
},
|
||||
|
@ -22,8 +22,16 @@ angular.module('OpenSlidesApp.assignments.projector', ['OpenSlidesApp.assignment
|
||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||
// class.
|
||||
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.getPhases().then(function(phases) {
|
||||
$scope.phases = phases;
|
||||
});
|
||||
// load all users
|
||||
User.findAll();
|
||||
User.bindAll({}, $scope, 'users');
|
||||
}
|
||||
|
@ -36,19 +36,21 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
assignments: function(Assignment) {
|
||||
return Assignment.findAll();
|
||||
},
|
||||
items: function(Agenda) {
|
||||
return Agenda.findAll();
|
||||
},
|
||||
phases: function(Assignment) {
|
||||
return Assignment.getPhases();
|
||||
},
|
||||
users: function(User) {
|
||||
return User.findAll();
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('assignments.assignment.detail', {
|
||||
controller: 'AssignmentDetailCtrl',
|
||||
resolve: {
|
||||
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) {
|
||||
return User.findAll();
|
||||
@ -172,8 +174,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
'phases',
|
||||
function($scope, ngDialog, AssignmentForm, Assignment, phases) {
|
||||
Assignment.bindAll({}, $scope, 'assignments');
|
||||
// get all item types via OPTIONS request
|
||||
$scope.phases = phases.data.actions.POST.phase.choices;
|
||||
$scope.phases = phases;
|
||||
$scope.alert = {};
|
||||
|
||||
// setup table sorting
|
||||
@ -266,8 +267,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||
Assignment.bindOne(assignment.id, $scope, 'assignment');
|
||||
Assignment.loadRelations(assignment, 'agenda_item');
|
||||
$scope.candidateSelectBox = {};
|
||||
// get all item types via OPTIONS request
|
||||
$scope.phases = phases.data.actions.POST.phase.choices;
|
||||
$scope.phases = phases;
|
||||
$scope.alert = {};
|
||||
|
||||
// open edit dialog
|
||||
|
@ -28,13 +28,9 @@
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</div>
|
||||
<h1>{{ assignment.title }}</h1>
|
||||
<h1>{{ assignment.agenda_item.getTitle() }}</h1>
|
||||
<h2>
|
||||
<translate>Election</translate>
|
||||
<span ng-if="assignment.agenda_item.item_number">
|
||||
–
|
||||
<translate>Agenda</translate>: {{ assignment.agenda_item.item_number }}
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -80,19 +80,33 @@
|
||||
<tr>
|
||||
<!-- projector column -->
|
||||
<th ng-show="!isDeleteMode" os-perms="core.can_manage_projector" class="minimum">
|
||||
|
||||
<!-- delete selection column -->
|
||||
<th ng-show="isDeleteMode" os-perms="assignments.can_manage" class="minimum deleteColumn">
|
||||
<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">
|
||||
<translate>Title</translate>
|
||||
<i class="pull-right fa" ng-show="sortColumn === 'title' && header.sortable != false"
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
|
||||
<!-- candicates / posts column -->
|
||||
<th ng-click="toggleSort('open_posts')" class="sortable optional">
|
||||
<translate>Candidates</translate> · <translate>Posts</translate>
|
||||
<i class="pull-right fa" ng-show="sortColumn === 'open_posts' && header.sortable != false"
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
|
||||
<!-- phase column -->
|
||||
<th ng-click="toggleSort('phase')" class="sortable optional">
|
||||
<translate>Phase</translate>
|
||||
<i class="pull-right fa" ng-show="sortColumn === 'phase' && header.sortable != false"
|
||||
@ -103,7 +117,8 @@
|
||||
filter: {phase: phaseFilter} | orderBy: sortColumn:reverse)"
|
||||
class="animate-item"
|
||||
ng-class="{ 'activeline': assignment.isProjected(), 'selected': assignment.selected }">
|
||||
<!-- projector column -->
|
||||
|
||||
<!-- projector -->
|
||||
<td ng-show="!isDeleteMode" os-perms="core.can_manage_projector">
|
||||
<a class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': assignment.isProjected() }"
|
||||
@ -111,10 +126,16 @@
|
||||
title="{{ 'Project election' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<!-- delete selection column -->
|
||||
|
||||
<!-- delete selection -->
|
||||
<td ng-show="isDeleteMode" os-perms="assignments.can_manage" class="deleteColumn">
|
||||
<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">
|
||||
<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}">
|
||||
@ -125,16 +146,21 @@
|
||||
<b>{{ assignment.title }}</b>"
|
||||
ng-bootbox-confirm-action="delete(assignment)" translate>Delete</a>
|
||||
</div>
|
||||
|
||||
<!-- candidates / posts -->
|
||||
<td ng-if="!assignment.quickEdit" class="optional">
|
||||
<span class="badge">{{ assignment.assignment_related_users.length }}</span>
|
||||
/
|
||||
<span class="badge">{{ assignment.open_posts }}</span>
|
||||
|
||||
<!-- phase -->
|
||||
<td ng-if="!assignment.quickEdit" class="optional">
|
||||
<span class="label" ng-class="{'label-primary': assignment.phase == 0,
|
||||
'label-warning': assignment.phase == 1,
|
||||
'label-success': assignment.phase == 2 }">
|
||||
{{ phases[assignment.phase].display_name }}
|
||||
</span>
|
||||
|
||||
<!-- quickEdit columns -->
|
||||
<td ng-if="assignment.quickEdit" colspan="3">
|
||||
<h4>{{ assignment.title }} <span class="text-muted">– Quick Edit</span></h4>
|
||||
|
@ -1,9 +1,28 @@
|
||||
<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>
|
||||
|
||||
<!-- candidates -->
|
||||
<h2 translate>Candidates</h2>
|
||||
<!-- Candidates -->
|
||||
<h3 translate>Candidates</h3>
|
||||
<ol>
|
||||
<li ng-repeat="related_user in assignment.assignment_related_users">
|
||||
{{ related_user.user.get_full_name() }}
|
||||
|
21
openslides/core/migrations/0003_auto_20160125_2138.py
Normal file
21
openslides/core/migrations/0003_auto_20160125_2138.py
Normal 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'))},
|
||||
),
|
||||
]
|
@ -66,7 +66,7 @@ class Projector(RESTModelMixin, models.Model):
|
||||
permissions = (
|
||||
('can_see_projector', ugettext_noop('Can see 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
|
||||
def elements(self):
|
||||
@ -159,6 +159,9 @@ class CustomSlide(RESTModelMixin, models.Model):
|
||||
def get_agenda_title(self):
|
||||
return self.title
|
||||
|
||||
def get_agenda_list_view_title(self):
|
||||
return self.title
|
||||
|
||||
def get_search_index_string(self):
|
||||
"""
|
||||
Returns a string that can be indexed for the search.
|
||||
|
@ -102,7 +102,7 @@ h2 {
|
||||
}
|
||||
h3 {
|
||||
color: #222;
|
||||
margin-bottom: 2px
|
||||
margin-bottom: 10px
|
||||
}
|
||||
#title {
|
||||
width: calc(100% - 230px);
|
||||
|
@ -89,7 +89,9 @@ angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core'])
|
||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||
// class.
|
||||
var id = $scope.element.id;
|
||||
Customslide.find(id);
|
||||
Customslide.find(id).then(function(customslide) {
|
||||
Customslide.loadRelations(customslide, 'agenda_item');
|
||||
});
|
||||
Customslide.bindOne(id, $scope, 'customslide');
|
||||
}
|
||||
])
|
||||
|
@ -68,7 +68,7 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
'gettext',
|
||||
function (mainMenuProvider, gettext) {
|
||||
mainMenuProvider.register({
|
||||
'ui_sref': 'dashboard',
|
||||
'ui_sref': 'home',
|
||||
'img_class': 'home',
|
||||
'title': gettext('Home'),
|
||||
'weight': 100,
|
||||
@ -193,9 +193,9 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
function($stateProvider, $locationProvider) {
|
||||
// Core urls
|
||||
$stateProvider
|
||||
.state('dashboard', {
|
||||
.state('home', {
|
||||
url: '/',
|
||||
templateUrl: 'static/templates/dashboard.html'
|
||||
templateUrl: 'static/templates/home.html'
|
||||
})
|
||||
.state('projector', {
|
||||
url: '/projector',
|
||||
@ -556,11 +556,11 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'showOnAgenda',
|
||||
key: 'showAsAgendaItem',
|
||||
type: 'checkbox',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Show on agenda'),
|
||||
description: gettextCatalog.getString('If deactivated it appears as internal item.')
|
||||
label: gettextCatalog.getString('Show as agenda item'),
|
||||
description: gettextCatalog.getString('If deactivated it appears as internal item on agenda.')
|
||||
}
|
||||
},
|
||||
];
|
||||
@ -791,6 +791,8 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
'Agenda',
|
||||
function($scope, $state, Customslide, CustomslideForm, Agenda) {
|
||||
$scope.customslide = {};
|
||||
$scope.model = {};
|
||||
$scope.model.showAsAgendaItem = true;
|
||||
// get all form fields
|
||||
$scope.formFields = CustomslideForm.getFormFields();
|
||||
|
||||
@ -798,14 +800,16 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
$scope.save = function (customslide) {
|
||||
Customslide.create(customslide).then(
|
||||
function(success) {
|
||||
// show as agenda item
|
||||
if (customslide.showOnAgenda) {
|
||||
Agenda.find(success.agenda_item_id).then(function(item) {
|
||||
// set item type to AGENDA_ITEM = 1 (default is HIDDEN_ITEM = 2)
|
||||
item.type = 1;
|
||||
// find related agenda item
|
||||
Agenda.find(success.agenda_item_id).then(function(item) {
|
||||
// check form element and set item type (AGENDA_ITEM = 1, HIDDEN_ITEM = 2)
|
||||
var type = customslide.showAsAgendaItem ? 1 : 2;
|
||||
// save only if agenda item type is modified
|
||||
if (item.type != type) {
|
||||
item.type = type;
|
||||
Agenda.save(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
$scope.closeThisDialog();
|
||||
}
|
||||
);
|
||||
@ -818,14 +822,21 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
'$state',
|
||||
'Customslide',
|
||||
'CustomslideForm',
|
||||
'Agenda',
|
||||
'customslide',
|
||||
function($scope, $state, Customslide, CustomslideForm, customslide) {
|
||||
function($scope, $state, Customslide, CustomslideForm, Agenda, customslide) {
|
||||
$scope.alert = {};
|
||||
// set initial values for form model by create deep copy of customslide object
|
||||
// so list/detail view is not updated while editing
|
||||
$scope.model = angular.copy(customslide);
|
||||
// get all form fields
|
||||
$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
|
||||
$scope.save = function (customslide) {
|
||||
@ -834,6 +845,12 @@ angular.module('OpenSlidesApp.core.site', [
|
||||
// save change customslide object on server
|
||||
Customslide.save(customslide).then(
|
||||
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();
|
||||
},
|
||||
function (error) {
|
||||
|
@ -8,7 +8,7 @@
|
||||
<i class="fa toggle-icon" ng-class="isLiveViewClosed ? 'fa-angle-up' : 'fa-angle-down'"></i>
|
||||
<h4 translate>Live view</h4>
|
||||
</a>
|
||||
<div uib-collapse="isLiveViewClosed">
|
||||
<div uib-collapse="isLiveViewClosed" ng-cloak>
|
||||
<a ui-sref="projector" target="_blank">
|
||||
<div id="iframewrapper">
|
||||
<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>
|
||||
<h4 translate>Countdowns</h4>
|
||||
</a>
|
||||
<div uib-collapse="!isCountdowns">
|
||||
<div uib-collapse="!isCountdowns" ng-cloak>
|
||||
<div ng-repeat="countdown in countdowns | orderBy: 'index'" id="{{countdown.uuid}}"
|
||||
class="countdown panel panel-default">
|
||||
<div class="panel-heading">
|
||||
@ -153,7 +153,7 @@
|
||||
<i class="fa toggle-icon" ng-class="isMessages ? 'fa-angle-up' : 'fa-angle-down'"></i>
|
||||
<h4 translate>Messages</h4>
|
||||
</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 class="panel-body"
|
||||
ng-class="{ 'projected': message.visible }">
|
||||
|
@ -1,4 +1,4 @@
|
||||
<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>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!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 http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<base href="/">
|
||||
@ -11,17 +11,17 @@
|
||||
<link rel="icon" href="/static/img/favicon.png">
|
||||
<script src="static/js/openslides-libs.js"></script>
|
||||
<script src="static/ckeditor/ckeditor.js"></script>
|
||||
<div id="wrapper">
|
||||
|
||||
<div id="wrapper" ng-cloak>
|
||||
|
||||
<!-- Header -->
|
||||
<div id="header">
|
||||
<div class="containerOS">
|
||||
<!-- Logo -->
|
||||
<div class="title">
|
||||
<a ui-sref="dashboard">
|
||||
<a ui-sref="home">
|
||||
<img src="/static/img/openslides-logo-dark.png" alt="Logo" height="35">
|
||||
</a>
|
||||
<!-- TODO: <span class="navbar-text optional">{{ config('general_event_name') }}</span>-->
|
||||
</div>
|
||||
|
||||
<!-- user specific header (chat, user settings / login, language)-->
|
||||
@ -104,7 +104,7 @@
|
||||
</div>
|
||||
|
||||
<!-- language switcher -->
|
||||
<span ng-controller="LanguageCtrl" uib-dropdown>
|
||||
<span uib-dropdown>
|
||||
| <a href class="headerlink" uib-dropdown-toggle>
|
||||
<i class="fa fa-flag"></i>
|
||||
{{ selectedLanguage[0].name | translate }}
|
||||
@ -157,7 +157,7 @@
|
||||
<div class="containerOS">
|
||||
<div class="col1" ng-class="isProjectorSidebar ? 'min' : 'max'">
|
||||
<!-- dynamic views -->
|
||||
<div ui-view></div>
|
||||
<div ui-view ng-cloak></div>
|
||||
<!-- footer -->
|
||||
<div id="footer">
|
||||
© Copyright by <a href="http://www.openslides.org" target="_blank">OpenSlides</a> |
|
||||
|
23
openslides/core/static/templates/search.html
Normal file
23
openslides/core/static/templates/search.html
Normal 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>
|
@ -125,13 +125,9 @@ class Motion(RESTModelMixin, models.Model):
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Return a human readable name of this motion.
|
||||
Return the title of this motion.
|
||||
"""
|
||||
if self.identifier:
|
||||
string = '%s: %s' % (self.identifier, self.title)
|
||||
else:
|
||||
string = self.title
|
||||
return string
|
||||
return self.title
|
||||
|
||||
# TODO: Use transaction
|
||||
def save(self, use_version=None, *args, **kwargs):
|
||||
@ -464,11 +460,25 @@ class Motion(RESTModelMixin, models.Model):
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
def agenda_item(self):
|
||||
"""
|
||||
|
@ -161,15 +161,15 @@ angular.module('OpenSlidesApp.motions', ['OpenSlidesApp.users'])
|
||||
'MotionPoll',
|
||||
'jsDataModel',
|
||||
'gettext',
|
||||
'gettextCatalog',
|
||||
'operator',
|
||||
'Config',
|
||||
function(DS, MotionPoll, jsDataModel, gettext, operator, Config) {
|
||||
function(DS, MotionPoll, jsDataModel, gettext, gettextCatalog, operator, Config) {
|
||||
var name = 'motions/motion';
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
useClass: jsDataModel,
|
||||
verboseName: gettext('Motion'),
|
||||
agendaSupplement: gettext('Motion'),
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
return name;
|
||||
@ -195,16 +195,9 @@ angular.module('OpenSlidesApp.motions', ['OpenSlidesApp.users'])
|
||||
getReason: function (versionId) {
|
||||
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
|
||||
getSearchResultName: function () {
|
||||
return this.getAgendaTitle();
|
||||
return this.getTitle();
|
||||
},
|
||||
// subtitle of search result
|
||||
getSearchResultSubtitle: function () {
|
||||
|
@ -22,9 +22,16 @@ angular.module('OpenSlidesApp.motions.projector', ['OpenSlidesApp.motions'])
|
||||
// Add it to the coresponding get_requirements method of the ProjectorElement
|
||||
// class.
|
||||
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');
|
||||
|
||||
// load all users
|
||||
User.findAll();
|
||||
User.bindAll({}, $scope, 'users');
|
||||
}
|
||||
]);
|
||||
|
||||
|
@ -34,7 +34,12 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
.state('motions.motion.list', {
|
||||
resolve: {
|
||||
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) {
|
||||
return Category.findAll();
|
||||
@ -53,7 +58,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
.state('motions.motion.detail', {
|
||||
resolve: {
|
||||
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) {
|
||||
return Category.findAll();
|
||||
@ -85,7 +92,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||
resolve: {
|
||||
motion: function() {
|
||||
return Motion.find($stateParams.id).then(function(motion) {
|
||||
return Motion.loadRelations(motion, 'agenda_item');
|
||||
Motion.loadRelations(motion, 'agenda_item');
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@ -28,7 +28,7 @@
|
||||
<i class="fa fa-pencil"></i>
|
||||
</a>
|
||||
</div>
|
||||
<h1>{{ motion.getTitle(version) }}</h1>
|
||||
<h1>{{ motion.agenda_item.getTitle() }}</h1>
|
||||
<h2>
|
||||
<translate>Motion</translate> {{ motion.identifier }}
|
||||
<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>
|
||||
<translate>This version is not permitted.</translate>
|
||||
</span>
|
||||
<span ng-if="motion.agenda_item.item_number">
|
||||
–
|
||||
<translate>Agenda</translate>: {{ motion.agenda_item.item_number }}
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -89,26 +89,42 @@
|
||||
<!-- delete selection column -->
|
||||
<th ng-show="$parent.isDeleteMode" os-perms="motions.can_manage" class="minimum deleteColumn">
|
||||
<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">
|
||||
<translate>Identifier</translate>
|
||||
<i class="pull-right fa" ng-show="sortColumn === 'identifier' && header.sortable != false"
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
|
||||
<!-- title column -->
|
||||
<th ng-click="toggleSort('getTitle()')" class="sortable">
|
||||
<translate>Title</translate>
|
||||
<i class="pull-right fa" ng-show="sortColumn === 'getTitle()' && header.sortable != false"
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
|
||||
<!-- submitters column -->
|
||||
<th ng-click="toggleSort('submitters')" class="sortable optional">
|
||||
<translate>Submitters</translate>
|
||||
<i class="pull-right fa" ng-show="sortColumn === 'submitters' && header.sortable != false"
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
|
||||
<!-- category column -->
|
||||
<th ng-click="toggleSort('category')" class="sortable optional">
|
||||
<translate>Category</translate>
|
||||
<i class="pull-right fa" ng-show="sortColumn === 'category' && header.sortable != false"
|
||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||
</i>
|
||||
|
||||
<!-- state column -->
|
||||
<th ng-click="toggleSort('state.name')" class="sortable optional">
|
||||
<translate>State</translate>
|
||||
<i class="pull-right fa" ng-show="sortColumn === 'state.name' && header.sortable != false"
|
||||
@ -119,7 +135,8 @@
|
||||
filter: {state_id: stateFilter} | orderBy: sortColumn:reverse)"
|
||||
class="animate-item"
|
||||
ng-class="{ 'activeline': motion.isProjected(), 'selected': motion.selected }">
|
||||
<!-- projector column -->
|
||||
|
||||
<!-- projector -->
|
||||
<td ng-show="!isDeleteMode" os-perms="core.can_manage_projector">
|
||||
<a class="btn btn-default btn-sm"
|
||||
ng-class="{ 'btn-primary': motion.isProjected() }"
|
||||
@ -127,11 +144,18 @@
|
||||
title="{{ 'Project motion' | translate }}">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</a>
|
||||
<!-- delete selection column -->
|
||||
|
||||
<!-- delete selection -->
|
||||
<td ng-show="isDeleteMode" os-perms="motions.can_manage" class="deleteColumn">
|
||||
<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 }}
|
||||
|
||||
<!-- title -->
|
||||
<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>
|
||||
<span ng-repeat="tag in motion.tags" class="label label-default">
|
||||
@ -151,16 +175,23 @@
|
||||
ng-bootbox-confirm-action="delete(motion)" translate>Delete</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- submitters -->
|
||||
<td ng-if="!motion.quickEdit" class="optional">
|
||||
<div ng-repeat="submitter in motion.submitters">
|
||||
{{ submitter.get_full_name() }}<br>
|
||||
</div>
|
||||
|
||||
<!-- category -->
|
||||
<td ng-if="!motion.quickEdit" class="optional">
|
||||
{{ motion.category.name }}
|
||||
|
||||
<!-- state -->
|
||||
<td ng-if="!motion.quickEdit" class="optional">
|
||||
<span class="label" ng-class="'label-'+motion.state.css_class">
|
||||
{{ motion.state.name | translate }}
|
||||
</span>
|
||||
|
||||
<!-- quickEdit columns -->
|
||||
<td ng-if="motion.quickEdit && motion.isAllowed('quickedit')" colspan="5">
|
||||
<h4>{{ motion.getTitle() }} <span class="text-muted">– <translate>QuickEdit</translate></span></h4>
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
<!-- Title -->
|
||||
<div id="title">
|
||||
<h1>{{ motion.getTitle() }}</h1>
|
||||
<h1>{{ motion.agenda_item.getTitle() }}</h1>
|
||||
<h2>
|
||||
<translate>Motion</translate> {{ motion.identifier }}
|
||||
<span ng-if="motion.versions.length > 1" >| Version {{ motion.getVersion().version_number }}</span>
|
||||
@ -25,6 +25,6 @@
|
||||
<div ng-bind-html="motion.getText()"></div>
|
||||
|
||||
<!-- 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>
|
||||
|
@ -113,7 +113,7 @@ def create_builtin_groups_and_admin(**kwargs):
|
||||
'core.can_manage_config',
|
||||
'core.can_manage_projector',
|
||||
'core.can_manage_tags',
|
||||
'core.can_see_dashboard',
|
||||
'core.can_see_frontpage',
|
||||
'core.can_see_projector',
|
||||
'core.can_use_chat',
|
||||
'mediafiles.can_manage',
|
||||
@ -143,7 +143,7 @@ def create_builtin_groups_and_admin(**kwargs):
|
||||
permission_dict['agenda.can_see'],
|
||||
permission_dict['agenda.can_see_hidden_items'],
|
||||
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['mediafiles.can_see'],
|
||||
permission_dict['motions.can_see'],
|
||||
|
@ -130,7 +130,7 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
||||
closeByEscape: $stateParams.guest_enabled,
|
||||
closeByDocument: $stateParams.guest_enabled,
|
||||
preCloseCallback: function() {
|
||||
$state.go('dashboard');
|
||||
$state.go('home');
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
@ -128,7 +128,7 @@ class ModelTest(TestCase):
|
||||
motion.active_version = None
|
||||
motion.save(update_fields=['active_version'])
|
||||
# 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):
|
||||
config['motions_amendments_enabled'] = True
|
||||
|
Loading…
Reference in New Issue
Block a user