diff --git a/.gitignore b/.gitignore index ddf502c38..cfe1cb267 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # General *.pyc *.swp +*.swo *~ # Virtual Environment diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index 02cc156b9..517716099 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -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 diff --git a/openslides/agenda/serializers.py b/openslides/agenda/serializers.py index 0b0aeba38..0e975e33c 100644 --- a/openslides/agenda/serializers.py +++ b/openslides/agenda/serializers.py @@ -53,6 +53,7 @@ class ItemSerializer(ModelSerializer): 'id', 'item_number', 'title', + 'list_view_title', 'comment', 'closed', 'type', diff --git a/openslides/agenda/static/js/agenda/base.js b/openslides/agenda/static/js/agenda/base.js index 916764013..a45826445 100644 --- a/openslides/agenda/static/js/agenda/base.js +++ b/openslides/agenda/static/js/agenda/base.js @@ -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; diff --git a/openslides/agenda/static/js/agenda/site.js b/openslides/agenda/static/js/agenda/site.js index 1f6639153..af6cad44a 100644 --- a/openslides/agenda/static/js/agenda/site.js +++ b/openslides/agenda/static/js/agenda/site.js @@ -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) { diff --git a/openslides/agenda/static/templates/agenda/item-detail.html b/openslides/agenda/static/templates/agenda/item-detail.html index 1ca45ef01..e19ca9bb7 100644 --- a/openslides/agenda/static/templates/agenda/item-detail.html +++ b/openslides/agenda/static/templates/agenda/item-detail.html @@ -7,7 +7,7 @@ - {{ item.getContentResource().verboseName }} + {{ item.getContentResource().verboseName | translate }} - {{ item.getContentResource().verboseName }} + {{ item.getContentResource().verboseName | translate }}

{{ item.getTitle() }}

@@ -68,7 +68,7 @@

Last speakers

@@ -77,8 +77,9 @@
  • {{ speaker.user.get_full_name() }} - [{{ speaker.begin_time | date:'yyyy-MM-dd HH:mm:ss' }} – - {{ speaker.end_time | date:'yyyy-MM-dd HH:mm:ss' }}] + {{ getDuration(speaker) | osSecondsToTime }} minutes + (Start time: + {{ speaker.begin_time | date:'yyyy-MM-dd HH:mm:ss' }}) @@ -113,11 +114,11 @@ {{ $index + 1 }}. {{ speaker.user.get_full_name() }} diff --git a/openslides/agenda/static/templates/agenda/item-list.html b/openslides/agenda/static/templates/agenda/item-list.html index 8655e0dc5..122ded0be 100644 --- a/openslides/agenda/static/templates/agenda/item-list.html +++ b/openslides/agenda/static/templates/agenda/item-list.html @@ -28,19 +28,18 @@
    -
    -
    - - -
    -
    +
    @@ -138,29 +136,34 @@ style="padding-left: calc(8px + {{ item.parentCount }}*15px)"> - {{ item.getTitle() }} + {{ item.getListViewTitle() }}
    {{ item.comment }}
    -
    - List of speakers | - Edit | - QuickEdit - | - Delete +
    + List of speakers + | + Edit | + QuickEdit + | + Delete +
    {{ item.duration }} h - + + + +
    @@ -170,8 +173,8 @@
    - - + +
    @@ -180,25 +183,15 @@
    - - + + + Show as agenda item
    -
    -
    - -
    -
    -
    -
    diff --git a/openslides/assignments/models.py b/openslides/assignments/models.py index 3b3d0c242..b4e56e74e 100644 --- a/openslides/assignments/models.py +++ b/openslides/assignments/models.py @@ -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): """ diff --git a/openslides/assignments/static/js/assignments/base.js b/openslides/assignments/static/js/assignments/base.js index b55a03da0..bcd83a9d1 100644 --- a/openslides/assignments/static/js/assignments/base.js +++ b/openslides/assignments/static/js/assignments/base.js @@ -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; }, diff --git a/openslides/assignments/static/js/assignments/projector.js b/openslides/assignments/static/js/assignments/projector.js index 89cb71e2d..6c348f978 100644 --- a/openslides/assignments/static/js/assignments/projector.js +++ b/openslides/assignments/static/js/assignments/projector.js @@ -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'); } diff --git a/openslides/assignments/static/js/assignments/site.js b/openslides/assignments/static/js/assignments/site.js index 801513087..28358bab1 100644 --- a/openslides/assignments/static/js/assignments/site.js +++ b/openslides/assignments/static/js/assignments/site.js @@ -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 diff --git a/openslides/assignments/static/templates/assignments/assignment-detail.html b/openslides/assignments/static/templates/assignments/assignment-detail.html index ac3b15606..2d4e1ef26 100644 --- a/openslides/assignments/static/templates/assignments/assignment-detail.html +++ b/openslides/assignments/static/templates/assignments/assignment-detail.html @@ -28,13 +28,9 @@
    -

    {{ assignment.title }}

    +

    {{ assignment.agenda_item.getTitle() }}

    Election - - – - Agenda: {{ assignment.agenda_item.item_number }} -

    diff --git a/openslides/assignments/static/templates/assignments/assignment-list.html b/openslides/assignments/static/templates/assignments/assignment-list.html index 9846e1106..5da6398cf 100644 --- a/openslides/assignments/static/templates/assignments/assignment-list.html +++ b/openslides/assignments/static/templates/assignments/assignment-list.html @@ -80,19 +80,33 @@ + + + + + Item + + + + Title + + Candidates · Posts + + Phase - + + - + + - + + + + {{ assignment.agenda_item.item_number }} + + {{ assignment.title }}
    @@ -125,16 +146,21 @@ {{ assignment.title }}" ng-bootbox-confirm-action="delete(assignment)" translate>Delete
    + + {{ assignment.assignment_related_users.length }} / {{ assignment.open_posts }} + + {{ phases[assignment.phase].display_name }} +

    {{ assignment.title }} – Quick Edit

    diff --git a/openslides/assignments/static/templates/assignments/slide_assignment.html b/openslides/assignments/static/templates/assignments/slide_assignment.html index 341633bcd..f0c9afb41 100644 --- a/openslides/assignments/static/templates/assignments/slide_assignment.html +++ b/openslides/assignments/static/templates/assignments/slide_assignment.html @@ -1,9 +1,28 @@
    -

    {{ assignment.title }}

    + + + + +
    +

    {{ assignment.agenda_item.getTitle() }}

    +

    + Election +

    +
    + +
    {{ assignment.description }}
    - -

    Candidates

    + +

    Candidates

    1. {{ related_user.user.get_full_name() }} diff --git a/openslides/core/migrations/0003_auto_20160125_2138.py b/openslides/core/migrations/0003_auto_20160125_2138.py new file mode 100644 index 000000000..dd7bae88e --- /dev/null +++ b/openslides/core/migrations/0003_auto_20160125_2138.py @@ -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'))}, + ), + ] diff --git a/openslides/core/models.py b/openslides/core/models.py index 7ebb84df6..53098c144 100644 --- a/openslides/core/models.py +++ b/openslides/core/models.py @@ -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. diff --git a/openslides/core/static/css/projector.css b/openslides/core/static/css/projector.css index c2b481ed5..4460cace3 100644 --- a/openslides/core/static/css/projector.css +++ b/openslides/core/static/css/projector.css @@ -102,7 +102,7 @@ h2 { } h3 { color: #222; - margin-bottom: 2px + margin-bottom: 10px } #title { width: calc(100% - 230px); diff --git a/openslides/core/static/js/core/projector.js b/openslides/core/static/js/core/projector.js index 17207d867..dc907fc7b 100644 --- a/openslides/core/static/js/core/projector.js +++ b/openslides/core/static/js/core/projector.js @@ -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'); } ]) diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js index e0f4fc54e..ae2f41375 100644 --- a/openslides/core/static/js/core/site.js +++ b/openslides/core/static/js/core/site.js @@ -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) { diff --git a/openslides/core/static/templates/core/projector-controls.html b/openslides/core/static/templates/core/projector-controls.html index 6bb7db1fd..4dd40f422 100644 --- a/openslides/core/static/templates/core/projector-controls.html +++ b/openslides/core/static/templates/core/projector-controls.html @@ -8,7 +8,7 @@

      Live view

      -
      +
      @@ -61,7 +61,7 @@

      Countdowns

      -
      +
      @@ -153,7 +153,7 @@

      Messages

      -
      +
      diff --git a/openslides/core/static/templates/core/slide_customslide.html b/openslides/core/static/templates/core/slide_customslide.html index 47237a810..693ecddfc 100644 --- a/openslides/core/static/templates/core/slide_customslide.html +++ b/openslides/core/static/templates/core/slide_customslide.html @@ -1,4 +1,4 @@
      -

      {{ customslide.title }}

      +

      {{ customslide.agenda_item.getTitle() }}

      diff --git a/openslides/core/static/templates/dashboard.html b/openslides/core/static/templates/home.html similarity index 100% rename from openslides/core/static/templates/dashboard.html rename to openslides/core/static/templates/home.html diff --git a/openslides/core/static/templates/index.html b/openslides/core/static/templates/index.html index f7f534fa7..1b0d7de86 100644 --- a/openslides/core/static/templates/index.html +++ b/openslides/core/static/templates/index.html @@ -1,5 +1,5 @@ - + @@ -11,17 +11,17 @@ -
      + +
      + +
      {{ submitter.get_full_name() }}
      + + {{ motion.category.name }} + + {{ motion.state.name | translate }} +

      {{ motion.getTitle() }} QuickEdit

      diff --git a/openslides/motions/static/templates/motions/slide_motion.html b/openslides/motions/static/templates/motions/slide_motion.html index 5fa284951..4cace9c04 100644 --- a/openslides/motions/static/templates/motions/slide_motion.html +++ b/openslides/motions/static/templates/motions/slide_motion.html @@ -14,7 +14,7 @@
      -

      {{ motion.getTitle() }}

      +

      {{ motion.agenda_item.getTitle() }}

      Motion {{ motion.identifier }} | Version {{ motion.getVersion().version_number }} @@ -25,6 +25,6 @@
      -

      Reason

      +

      Reason

      diff --git a/openslides/users/signals.py b/openslides/users/signals.py index 62dc8074f..156b9a122 100644 --- a/openslides/users/signals.py +++ b/openslides/users/signals.py @@ -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'], diff --git a/openslides/users/static/js/users/site.js b/openslides/users/static/js/users/site.js index d7134b80b..71039b200 100644 --- a/openslides/users/static/js/users/site.js +++ b/openslides/users/static/js/users/site.js @@ -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; } }); diff --git a/tests/old/motions/test_models.py b/tests/old/motions/test_models.py index 998acd78e..bc8c9b3e7 100644 --- a/tests/old/motions/test_models.py +++ b/tests/old/motions/test_models.py @@ -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