diff --git a/bower.json b/bower.json
index 1f9d474fb..44c77e312 100644
--- a/bower.json
+++ b/bower.json
@@ -3,14 +3,24 @@
"private": true,
"dependencies": {
"lodash": "~3.0.1",
- "jquery": "~1.11.2",
+ "jquery": "~2.1.4",
"jquery.cookie": "~1.4.1",
- "bootstrap": "~3.3.1",
- "datatables-bootstrap3-plugin": "~0.2.0",
- "angular": "~1.3.13",
+ "bootstrap-css-only": "~3.3.4",
+ "angular": "~1.3.15",
+ "angular-bootstrap": "~0.13.0",
+ "angular-messages": "~1.3.15",
+ "angular-animate": "~1.3.15",
+ "angular-csv-import": "~0.0.15",
+ "angular-loading-bar": "~0.7.1",
"angular-ui-router": "~0.2.13",
+ "angular-ui-select": "~0.11.2",
+ "angular-ui-tree": "~2.2.0",
"angular-gettext": "~2.0.2",
+ "angular-sanitize": "~1.3.15",
+ "angular-xeditable": "~0.1.9",
"js-data-angular": "~2.1.0",
+ "ng-fab-form": "~1.2.7",
+ "ngBootbox": "~0.0.5",
"sockjs": "~0.3.4",
"font-awesome-bower": "4.3.0"
}
diff --git a/openslides/agenda/main_menu.py b/openslides/agenda/main_menu.py
index db50655a6..f97d00804 100644
--- a/openslides/agenda/main_menu.py
+++ b/openslides/agenda/main_menu.py
@@ -10,5 +10,5 @@ class AgendaMainMenuEntry(MainMenuEntry):
verbose_name = ugettext_lazy('Agenda')
required_permission = 'agenda.can_see'
default_weight = 20
- pattern_name = 'item_overview'
+ pattern_name = '/agenda' # TODO: use generic solution, see issue #1469
icon_css_class = 'glyphicon-calendar'
diff --git a/openslides/agenda/static/img/glyphicons_300_microphone.png b/openslides/agenda/static/img/glyphicons_300_microphone.png
deleted file mode 100644
index bde033ca3..000000000
Binary files a/openslides/agenda/static/img/glyphicons_300_microphone.png and /dev/null differ
diff --git a/openslides/agenda/static/js/agenda.js b/openslides/agenda/static/js/agenda.js
deleted file mode 100644
index bda780b12..000000000
--- a/openslides/agenda/static/js/agenda.js
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * JavaScript functions for agenda.
- *
- */
-
-function hideClosedSlides(hide) {
- if (hide) {
- $('#hidelink').attr('title', 'show');
- $('#hidelink').removeClass('hide').addClass('show');
- $('.agenda_list .icon-checked-new').each(function() {
- $(this).parents("li").first().hide();
- });
- var hidden = $(".agenda_list li:hidden").length;
- $('#hiddencount').text(interpolate(gettext(', of which %s are hidden.'), [hidden]));
- } else {
- $('.agenda_list li').show();
- $('#hidelink').attr('title','hide');
- $('#hidelink').removeClass('show').addClass('hide');
- $('#hiddencount').text('');
- }
- return false;
-}
-
-$('#coming_speakers_changed_form').submit(function() {
- $('#sort_order').val($('#coming_speakers').sortable("toArray"));
-});
-
-$(function() {
- // change participant status (on/off)
- $('.close_link').click(function(event) {
- event.preventDefault();
- var link = $(this);
- $.ajax({
- type: 'GET',
- url: $(this).attr('href'),
- dataType: 'json',
- success: function(data) {
- if (data.closed) {
- newclass = 'icon-checked-new';
- link.parent().parent().addClass('offline');
- link.addClass('btn-success');
- } else {
- newclass = 'icon-unchecked-new';
- link.parent().parent().removeClass('offline');
- link.removeClass('btn-success');
- }
- link.children('i').removeClass('icon-checked-new icon-unchecked-new').addClass(newclass);
- link.attr('href', data.link);
- }
- });
- });
- // filter to show/hide closed items
- $('#hide_closed_items').click(function(event) {
- // show all items
- if ($.cookie('Slide.HideClosed') == 1) {
- $.cookie('Slide.HideClosed', 0);
- hideClosedSlides(false);
- $('#hide_closed_items').attr('checked', false);
- }
- else { // hide closed items
- $.cookie('Slide.HideClosed', 1);
- hideClosedSlides(true);
- $('#hide_closed_items').attr('checked', true);
- }
- });
-
- // TODO: Fix this code and reactivate it again
- //# if ($.cookie('Slide.HideClosed') === null) {
- //# $('#hide_closed_items').attr('checked', false);
- //# $.cookie('Slide.HideClosed', 0);
- //# } else if ($.cookie('Slide.HideClosed') == 1) {
- //# hideClosedSlides(true);
- //# $('#hide_closed_items').attr('checked', true);
- //# }
-
- if ($('#coming_speakers').length > 0) {
- $('#coming_speakers').sortable({axis: "y", containment: "parent", update: function(event, ui) {
- $('#coming_speakers_changed_form').show();
- }});
- $('#coming_speakers').disableSelection();
- }
-
-
- /*
- * function for sorting agenda items
- *
- */
- var $agenda_list = $('ol.agenda_list');
- var rebuildOpenersClosers = function ( ) {
- $agenda_list.find("li").each(function() {
- var $li = $(this);
- if ($li.find("> ol").length > 0) $li.find("> div .opener_closer").show();
- else $li.find("> div .opener_closer").hide();
- });
- }
- var rebuildNesting = function( $root, level, parent_id ) {
- var $children = $root.find('> li'),
- curr_weight = -50;
-
- $children.each(function() {
- var $child = $(this),
- $curr_element = $child.find('> div'),
- my_id = $curr_element.find('.menu-mlid').val();
- $curr_element.find('.menu-plid').val(parent_id);
- $curr_element.find('.menu-weight').val(curr_weight);
- curr_weight++;
- $child.find('> ol').each(function() {
- rebuildNesting( $(this), level + 1, my_id );
- });
- });
- };
- if ($agenda_list.hasClass("sortable")) $agenda_list.nestedSortable({
- forcePlaceholderSize: true,
- handle: 'div',
- helper: 'clone',
- items: 'li',
- opacity: .6,
- placeholder: 'placeholder',
- revert: 250,
- tabSize: 25,
- tolerance: 'pointer',
- toleranceElement: '> div',
- isTree: true,
- expandOnHover: 700,
- startCollapsed: true,
- update: function (event, ui) {
- var $this = $(this);
- rebuildNesting($this, 0, 0);
- $('#changed-order-message').show();
- rebuildOpenersClosers();
- }
- });
- rebuildOpenersClosers();
- $agenda_list.find(".opener_closer .opener").click(function(ev) {
- ev.preventDefault();
- $(this).parents("li").first().removeClass("closed");
- });
- $agenda_list.find(".opener_closer .closer").click(function(ev) {
- ev.preventDefault();
- $(this).parents("li").first().addClass("closed");
- });
-});
diff --git a/openslides/agenda/static/js/agenda/agenda.js b/openslides/agenda/static/js/agenda/agenda.js
index bf7c5387c..763adcdce 100644
--- a/openslides/agenda/static/js/agenda/agenda.js
+++ b/openslides/agenda/static/js/agenda/agenda.js
@@ -15,21 +15,56 @@ angular.module('OpenSlidesApp.agenda', [])
resolve: {
items: function(Agenda) {
return Agenda.findAll();
+ },
+ tree: function($http) {
+ return $http.get('/rest/agenda/item/tree/');
+ }
+ }
+ })
+ .state('agenda.item.create', {
+ resolve: {
+ types: function($http) {
+ // get all item types
+ return $http({ 'method': 'OPTIONS', 'url': '/rest/agenda/item/' });
}
}
})
- .state('agenda.item.create', {})
.state('agenda.item.detail', {
resolve: {
item: function(Agenda, $stateParams) {
return Agenda.find($stateParams.id);
+ },
+ users: function(User) {
+ return User.findAll();
}
}
})
.state('agenda.item.detail.update', {
views: {
'@agenda.item': {}
+ },
+ resolve: {
+ types: function($http) {
+ // get all item types
+ return $http({ 'method': 'OPTIONS', 'url': '/rest/agenda/item/' });
+ }
}
+ })
+ .state('agenda.item.sort', {
+ resolve: {
+ items: function(Agenda) {
+ return Agenda.findAll();
+ },
+ tree: function($http) {
+ return $http.get('/rest/agenda/item/tree/');
+ }
+ },
+ url: '/sort',
+ controller: 'AgendaSortCtrl',
+ })
+ .state('agenda.item.csv-import', {
+ url: '/csv-import',
+ controller: 'AgendaCSVImportCtrl',
});
})
@@ -40,30 +75,84 @@ angular.module('OpenSlidesApp.agenda', [])
});
})
-.controller('ItemListCtrl', function($scope, Agenda, i18n) {
+.controller('ItemListCtrl', function($scope, Agenda, tree) {
Agenda.bindAll({}, $scope, 'items');
- $scope.test_plural = i18n.ngettext('test', 'tests', 2);
- $scope.test_singular = i18n.ngettext('test', 'tests', 1);
-})
-.controller('ItemDetailCtrl', function($scope, Agenda, item) {
- Agenda.bindOne($scope, 'item', item.id);
-})
+ // get a 'flat' (ordered) array of agenda tree to display in table
+ $scope.flattenedTree = buildTree(tree.data);
+ function buildTree(tree, level = 0) {
+ var nodes = [];
+ var defaultlevel = level;
+ _.each(tree, function(node) {
+ level = defaultlevel;
+ if (node.id) {
+ nodes.push({ id: node.id, level: level });
+ }
+ if (node.children) {
+ level++;
+ var child = buildTree(node.children, level);
+ if (child.length) {
+ nodes = nodes.concat(child);
+ }
+ }
+ });
+ return nodes;
+ }
-.controller('ItemCreateCtrl', function($scope, Agenda) {
- $scope.item = {};
- $scope.save = function (item) {
- item.weight = 0; // TODO: the rest_api should do this
- item.tags = []; // TODO: the rest_api should do this
- Agenda.create(item);
- // TODO: redirect to list-view
- };
-})
-
-.controller('ItemUpdateCtrl', function($scope, Agenda, item) {
- $scope.item = item; // do not use Agenda.binOne(...) so autoupdate is not activated
+ // save changed item
$scope.save = function (item) {
Agenda.save(item);
- // TODO: redirect to list-view
};
+ // delete selected item
+ $scope.delete = function (id) {
+ Agenda.destroy(id);
+ };
+})
+
+.controller('ItemDetailCtrl', function($scope, Agenda, User, item) {
+ Agenda.bindOne(item.id, $scope, 'item');
+ User.bindAll({}, $scope, 'users');
+})
+
+.controller('ItemCreateCtrl', function($scope, $state, Agenda, types) {
+ $scope.types = types.data.actions.POST.type.choices; // get all item types
+ $scope.save = function (item) {
+ if (!item)
+ return null;
+ item.weight = 0; // TODO: the rest_api should do this
+ item.tags = []; // TODO: the rest_api should do this
+ Agenda.create(item).then(
+ function(success) {
+ $state.go('agenda.item.list');
+ }
+ );
+ };
+})
+
+.controller('ItemUpdateCtrl', function($scope, $state, Agenda, types, item) {
+ $scope.types = types.data.actions.POST.type.choices; // get all item types
+ $scope.item = item;
+ $scope.save = function (item) {
+ Agenda.save(item).then(
+ function(success) {
+ $state.go('agenda.item.list');
+ }
+ );
+ };
+})
+
+.controller('AgendaSortCtrl', function($scope, $http, Agenda, tree) {
+ Agenda.bindAll({}, $scope, 'items');
+ $scope.tree = tree.data;
+
+ // set changed agenda tree
+ $scope.treeOptions = {
+ dropped: function(e) {
+ $http.put('/rest/agenda/item/tree/', {tree: $scope.tree});
+ }
+ };
+})
+
+.controller('AgendaCSVImportCtrl', function($scope, Agenda) {
+ // TODO
});
diff --git a/openslides/agenda/static/js/agenda_current_list_of_speakers_projector.js b/openslides/agenda/static/js/agenda_current_list_of_speakers_projector.js
deleted file mode 100644
index 432c4bb6d..000000000
--- a/openslides/agenda/static/js/agenda_current_list_of_speakers_projector.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * JavaScript functions for agenda CurrentListOfSpeakersProjectorView
- */
-
-function reloadListOfSpeakers() {
- $.ajax({
- url: '',
- success: function (data) {
- updater.updateProjector(data);
- setTimeout('reloadListOfSpeakers()', 2000);
- },
- dataType: 'json'
- });
-}
diff --git a/openslides/agenda/static/templates/agenda/item-csv-import.html b/openslides/agenda/static/templates/agenda/item-csv-import.html
new file mode 100644
index 000000000..18b95ea27
--- /dev/null
+++ b/openslides/agenda/static/templates/agenda/item-csv-import.html
@@ -0,0 +1,39 @@
+
Import agenda items
+
+
+
+Select a CSV file to import agenda items!
+
+
Please note:
+
+
+
+
diff --git a/openslides/agenda/static/templates/agenda/item-detail.html b/openslides/agenda/static/templates/agenda/item-detail.html
index 09811fd77..a7267d2f5 100644
--- a/openslides/agenda/static/templates/agenda/item-detail.html
+++ b/openslides/agenda/static/templates/agenda/item-detail.html
@@ -1,2 +1,56 @@
{{ item.get_title }}
+
+
+
{{ item.text }}
+
+
+
Duration
+ {{ item.duration }}
+
+
+
+
Comment
+ {{ item.comment }}
+
+
+
+List of speakers
+
+
+
diff --git a/openslides/agenda/static/templates/agenda/item-form.html b/openslides/agenda/static/templates/agenda/item-form.html
index f2330908c..c34dc158b 100644
--- a/openslides/agenda/static/templates/agenda/item-form.html
+++ b/openslides/agenda/static/templates/agenda/item-form.html
@@ -1,8 +1,51 @@
-{{ item.title }}
-Neuer Eintrag
+Edit agenda item
+New agenda item
-
diff --git a/openslides/agenda/static/templates/agenda/item-list.html b/openslides/agenda/static/templates/agenda/item-list.html
index b98d3da53..2ea8db77b 100644
--- a/openslides/agenda/static/templates/agenda/item-list.html
+++ b/openslides/agenda/static/templates/agenda/item-list.html
@@ -1,9 +1,85 @@
-
-{{ gettext('New') }}
-
{{ test_singular }}
-
{{ test_plural }}
+Agenda
+
+
+
+
+
+
diff --git a/openslides/agenda/static/templates/agenda/item-sort.html b/openslides/agenda/static/templates/agenda/item-sort.html
new file mode 100644
index 000000000..934c047d4
--- /dev/null
+++ b/openslides/agenda/static/templates/agenda/item-sort.html
@@ -0,0 +1,27 @@
+Sort agenda
+
+
+
+Drag and drop items to change the order of the agenda. Your modification will be saved directly.
+
+
+
+
+
diff --git a/openslides/agenda/templates/agenda/edit.html b/openslides/agenda/templates/agenda/edit.html
deleted file mode 100644
index 1d50f8fa9..000000000
--- a/openslides/agenda/templates/agenda/edit.html
+++ /dev/null
@@ -1,44 +0,0 @@
-{% extends "base.html" %}
-
-{% load i18n %}
-{% load tags %}
-{% load staticfiles %}
-
-{% block title %}
- {% if item %}
- {{ item }} – {% trans "Edit item" %}
- {% else %}
- {% trans "New item" %}
- {% endif %}
- – {{ block.super }}
-{% endblock %}
-
-{% block content %}
-
- {% if item %}
- {% trans "Edit item" %}
- {% else %}
- {% trans "New item" %}
- {% endif %}
-
- {% trans "Back to overview" %}
-
-
-
- {% if item.content_object %}
-
- {% blocktrans with type=item.content_type.name|trans name=item.content_object %}Edit {{ type }} {{ name }}{% endblocktrans %}
-
- {% endif %}
-
-
-{% endblock %}
diff --git a/openslides/agenda/templates/agenda/item_form_csv_import.html b/openslides/agenda/templates/agenda/item_form_csv_import.html
deleted file mode 100644
index c346a3a3e..000000000
--- a/openslides/agenda/templates/agenda/item_form_csv_import.html
+++ /dev/null
@@ -1,47 +0,0 @@
-{% extends 'base.html' %}
-
-{% load i18n %}
-
-{% block title %}{% trans 'Import agenda items' %} – {{ block.super }}{% endblock %}
-
-{% block content %}
-
-
- {% trans 'Select a CSV file to import agenda items' %}.
-
- {% trans 'Please note' %}:
-
- -
- {% trans 'Required comma separated values' %}:
- {% trans 'title, text, duration' %}
-
- -
- {% trans 'Text and duration are optional and may be empty' %}.
-
- - {% trans 'The first line (header) is ignored' %}.
- -
- {% trans 'Required CSV file encoding is UTF-8' %}.
-
- -
- {% trans 'Use the CSV example file from OpenSlides Wiki.' %}
-
-
-
-
-{% endblock %}
diff --git a/openslides/agenda/templates/agenda/item_row.html b/openslides/agenda/templates/agenda/item_row.html
deleted file mode 100644
index dde29ad09..000000000
--- a/openslides/agenda/templates/agenda/item_row.html
+++ /dev/null
@@ -1,90 +0,0 @@
-{% load i18n %}
-{% load tags %}
-
-
- {% if perms.agenda.can_manage or perms.core.can_manage_projector %}
-
-
- {% if perms.core.can_manage_projector %}
-
-
-
-
-
-
- {% endif %}
- {% if perms.agenda.can_manage %}
-
-
-
-
-
-
-
-
-
-
-
- {% if not node.is_leaf_node %}
-
-
-
- {% endif %}
- {% endif %}
-
-
- {% endif %}
-
- {% if perms.agenda.can_see_orga_items %}
-
- {% if node.duration %}
- {{ node.duration }} h
- {% if node.tooltip %}
-
-
- {% endif %}
- {% endif %}
-
- {% endif %}
-
- {% if perms.agenda.can_manage %}
-
- {% if node.comment %}
- {{ node.comment|first_line }}
-
-
-
- {% endif %}
-
- {% endif %}
-
-
-
-
-
diff --git a/openslides/agenda/templates/agenda/overview.html b/openslides/agenda/templates/agenda/overview.html
deleted file mode 100644
index e4dd49b42..000000000
--- a/openslides/agenda/templates/agenda/overview.html
+++ /dev/null
@@ -1,149 +0,0 @@
-{% extends "base.html" %}
-
-{% load i18n %}
-{% load mptt_tags %}
-{% load staticfiles %}
-
-{% block title %}{% trans "Agenda" %} – {{ block.super }}{% endblock %}
-
-{% block header %}
-
-{% endblock %}
-
-{% block javascript %}
-
-{% endblock %}
-
-{% block content %}
- {% trans "Agenda" %}
-
- {% if perms.agenda.can_manage %}
-
-
- {% trans "New" %}
-
- {% endif %}
- {% if perms.core.can_manage_tags %}
-
-
- {% trans 'Tags' %}
-
- {% endif %}
- {% if perms.agenda.can_manage %}
-
-
- {% trans "Import" %}
-
- {% endif %}
-
-
- PDF
-
- {% if perms.core.can_see_projector %}
-
-
- {% trans 'List of speakers' %}
-
- {% endif %}
-
-
-
-
-
- {% if perms.agenda.can_manage %}
-
- {% trans 'Number agenda items' %}
-
-
- {% endif %}
-
-
- {{ items|length }}
- {% blocktrans count counter=items|length %}item{% plural %}items{% endblocktrans %}
-
-
-
- {% if perms.agenda.can_see_orga_items %}
- {% if start and end %}
-
-
- {% trans "Start of event" %}: |
- {{ start|date:"DATETIME_FORMAT" }} |
-
-
- {% trans "Estimated end" %}: |
- {{ end|date:"DATETIME_FORMAT" }} |
-
-
- {% elif perms.config.can_manage %}
-
- {% trans 'Set start time of event' %}
-
- {% endif %}
- {% endif %}
-
-
-
- {% if items %}
-
- {% recursetree items %}
- -
- {% include "agenda/item_row.html" %}
- {% if not node.is_leaf_node %}
-
- {{ children }}
-
- {% endif %}
-
- {% endrecursetree %}
-
- {% else %}
- {% trans "No items available." %}
- {% endif %}
-{% endblock %}
diff --git a/openslides/agenda/templates/agenda/sort.html b/openslides/agenda/templates/agenda/sort.html
deleted file mode 100644
index 1bb85f3bd..000000000
--- a/openslides/agenda/templates/agenda/sort.html
+++ /dev/null
@@ -1,151 +0,0 @@
-{% extends "base.html" %}
-
-{% load i18n %}
-{% load mptt_tags %}
-{% load staticfiles %}
-
-{% block title %}{% trans "Agenda" %} – {{ block.super }}{% endblock %}
-
-{% block header %}
-
-{% endblock %}
-
-{% block javascript %}
-
- {% if perms.agenda.can_manage %}
-
-
-
-
- {% endif %}
-{% endblock %}
-
-{% block content %}
-
-{% endblock %}
diff --git a/openslides/agenda/templates/agenda/view.html b/openslides/agenda/templates/agenda/view.html
deleted file mode 100644
index 93730488b..000000000
--- a/openslides/agenda/templates/agenda/view.html
+++ /dev/null
@@ -1,173 +0,0 @@
-{% extends "base.html" %}
-
-{% load i18n %}
-{% load tags %}
-{% load staticfiles %}
-
-{% block title %}{{ item }} – {{ block.super }}{% endblock %}
-
-{% block header %}
-
-{% endblock %}
-
-{% block javascript %}
-
-
-{% endblock %}
-
-{% block content %}
-
- {{ item }}
-
- {% trans "Back to overview" %}
- {% if perms.core.can_manage_projector %}
-
-
-
- {% endif %}
- {% if perms.agenda.can_manage %}
-
-
-
-
-
-{% for tag in item.tags.all %}
- {{ tag }}
-{% endfor %}
-
-
-
- {% if not item.content_object %}
- {{ item.text|safe }}
- {% else %}
- {% trans item.content_type.name %}: {{ item.content_object }}
- {% endif %}
-
-
-
-{% if perms.agenda.can_manage %}
- {% if item.comment %}
- {% trans "Comment" %}
- {{ item.comment|linebreaks }}
- {% endif %}
-{% endif %}
-
-
-{% trans "List of speakers" %} {% if item.speaker_list_closed %}{% trans 'closed' %}{% endif %}
-
-{% if perms.agenda.can_manage %}
- {% if item.speaker_list_closed %}
- {% trans 'Open list' %}
- {% else %}
- {% trans 'Close list' %}
- {% endif %}
-{% endif %}
-{% if perms.core.can_manage_projector %}
-
-
- {% trans 'Show list' %}
-
-{% endif %}
-
-
-{% if perms.agenda.can_manage %}
-
-{% endif %}
-
-
- {% for speaker_dict in list_of_speakers %}
- {% if speaker_dict.first_in_group %}
- {% if speaker_dict.type == 'old_speaker' %}
-
{% trans "Last speakers" %}:
-
-
-
- {% elif speaker_dict.type == 'actual_speaker' %}
-
{% trans 'Current speaker' %}:
- {% else %}
-
{% trans "Next speakers" %}:
- {% endif %}
-
- {% endif %}
- -
- {% if speaker_dict.type == 'coming_speaker' %}
-
- {{ speaker_dict.prefix }}.
- {% else %}
- [{{ speaker_dict.speaker.begin_time }}{% if speaker_dict.type == 'old_speaker' %} – {{ speaker_dict.speaker.end_time }}{% endif %}]
- {% endif %}
- {{ speaker_dict.speaker }}
- {% if perms.agenda.can_manage %}
- {% if speaker_dict.type == 'actual_speaker' %}
- {% trans 'End speach' %}
- {% elif speaker_dict.type == 'coming_speaker' %}
- {% trans "Begin speach" %}
- {% endif %}
-
-
-
- {% endif %}
-
- {% if speaker_dict.last_in_group %}
-
- {% endif %}
- {% endfor %}
-
-
- {% if is_on_the_list_of_speakers %}
- {% trans "Remove me from the list" %}
- {% elif not object.speaker_list_closed and perms.agenda.can_be_speaker %}
- {% trans "Put me on the list" %}
- {% endif %}
-
-
- {% if perms.agenda.can_manage %}
-
- {% endif %}
-
-
-{% endblock %}
diff --git a/openslides/agenda/templates/agenda/widget_item.html b/openslides/agenda/templates/agenda/widget_item.html
index 905b8bf2b..e69de29bb 100644
--- a/openslides/agenda/templates/agenda/widget_item.html
+++ b/openslides/agenda/templates/agenda/widget_item.html
@@ -1,62 +0,0 @@
-{% extends 'core/widget.html' %}
-
-{% load i18n %}
-{% load tags %}
-
-{% block content %}
-
- -
-
-
-
-
-
-
- {% trans "Agenda" %}
-
-
-
-
-{% endblock %}
diff --git a/openslides/agenda/templates/agenda/widget_list_of_speakers.html b/openslides/agenda/templates/agenda/widget_list_of_speakers.html
index 98fdd3844..e69de29bb 100644
--- a/openslides/agenda/templates/agenda/widget_list_of_speakers.html
+++ b/openslides/agenda/templates/agenda/widget_list_of_speakers.html
@@ -1,16 +0,0 @@
-{% extends 'core/widget.html' %}
-
-{% load i18n %}
-
-{% block content %}
-{% if perms.agenda.can_be_speaker %}
- {% trans 'Put me on the current list of speakers' %}
-{% endif %}
-{% if perms.agenda.can_manage %}
-
- {% trans 'Next speaker' %}
- {% trans 'End speach' %}
-
-{% endif %}
-{% trans 'Go to current list of speakers' %} ...
-{% endblock %}
diff --git a/openslides/agenda/urls.py b/openslides/agenda/urls.py
index 9e82d5909..922cdef70 100644
--- a/openslides/agenda/urls.py
+++ b/openslides/agenda/urls.py
@@ -4,97 +4,14 @@ from . import views
urlpatterns = patterns(
'',
- url(r'^$',
- views.Overview.as_view(),
- name='item_overview'), # TODO: Rename this to item_list
-
- url(r'^(?P\d+)/$',
- views.AgendaItemView.as_view(),
- name='item_view'),
-
- url(r'^(?P\d+)/close/$',
- views.SetClosed.as_view(),
- {'closed': True},
- name='item_close'),
-
- url(r'^(?P\d+)/open/$',
- views.SetClosed.as_view(),
- {'closed': False},
- name='item_open'),
-
- url(r'^(?P\d+)/edit/$',
- views.ItemUpdate.as_view(),
- name='item_edit'),
-
- url(r'^new/$',
- views.ItemCreate.as_view(),
- name='item_new'),
-
- url(r'^(?P\d+)/del/$',
- views.ItemDelete.as_view(),
- name='item_delete'),
+ # PDF
url(r'^print/$',
views.AgendaPDF.as_view(),
- name='print_agenda'),
-
- url(r'^numbering/$',
- views.AgendaNumberingView.as_view(),
- name='agenda_numbering'),
-
- # List of speakers
- url(r'^(?P\d+)/speaker/$',
- views.SpeakerAppendView.as_view(),
- name='agenda_speaker_append'),
-
- url(r'^(?P\d+)/speaker/close/$',
- views.SpeakerListCloseView.as_view(),
- name='agenda_speaker_close'),
-
- url(r'^(?P\d+)/speaker/reopen/$',
- views.SpeakerListCloseView.as_view(reopen=True),
- name='agenda_speaker_reopen'),
-
- url(r'^(?P\d+)/speaker/del/$',
- views.SpeakerDeleteView.as_view(),
- name='agenda_speaker_delete'),
-
- url(r'^(?P\d+)/speaker/(?P\d+)/del/$',
- views.SpeakerDeleteView.as_view(),
- name='agenda_speaker_delete'),
-
- url(r'^(?P\d+)/speaker/(?P[^/]+)/speak/$',
- views.SpeakerSpeakView.as_view(),
- name='agenda_speaker_speak'),
-
- url(r'^(?P\d+)/speaker/end_speach/$',
- views.SpeakerEndSpeachView.as_view(),
- name='agenda_speaker_end_speach'),
-
- url(r'^(?P\d+)/speaker/change_order/$',
- views.SpeakerChangeOrderView.as_view(),
- name='agenda_speaker_change_order'),
-
- url(r'^list_of_speakers/$',
- views.CurrentListOfSpeakersView.as_view(),
- name='agenda_current_list_of_speakers'),
-
- url(r'^list_of_speakers/add/$',
- views.CurrentListOfSpeakersView.as_view(set_speaker=True),
- name='agenda_add_to_current_list_of_speakers'),
-
- url(r'^list_of_speakers/next/$',
- views.CurrentListOfSpeakersView.as_view(next_speaker=True),
- name='agenda_next_on_current_list_of_speakers'),
-
- url(r'^list_of_speakers/end_speach/$',
- views.CurrentListOfSpeakersView.as_view(end_speach=True),
- name='agenda_end_speach_on_current_list_of_speakers'),
+ name='agenda_pdf'),
+ # TODO: remove it after implement projector rest api
url(r'^list_of_speakers/projector/$',
views.CurrentListOfSpeakersProjectorView.as_view(),
name='agenda_current_list_of_speakers_projector'),
-
- url(r'^csv_import/$',
- views.ItemCSVImportView.as_view(),
- name='item_csv_import'))
+)
diff --git a/openslides/agenda/views.py b/openslides/agenda/views.py
index bca70602f..a61f38e67 100644
--- a/openslides/agenda/views.py
+++ b/openslides/agenda/views.py
@@ -2,15 +2,10 @@
from cgi import escape
from collections import defaultdict
-from datetime import datetime, timedelta
from json import dumps
-from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
from django.contrib.staticfiles.templatetags.staticfiles import static
-from django.core.urlresolvers import reverse
-from django.db import transaction, IntegrityError
-from django.db.models import Model
from django.template.loader import render_to_string
from django.utils.datastructures import SortedDict
from django.utils.safestring import mark_safe
@@ -21,304 +16,21 @@ from reportlab.platypus import Paragraph
from openslides.config.api import config
from openslides.projector.api import (
get_active_object,
- get_active_slide,
get_projector_overlays_js,
get_overlays)
-from openslides.utils.exceptions import OpenSlidesError
from openslides.utils.pdf import stylesheet
from openslides.utils.rest_api import ModelViewSet, list_route, Response
-from openslides.utils.utils import html_strong
from openslides.utils.views import (
AjaxMixin,
- CreateView,
- CSVImportView,
- DeleteView,
- FormView,
PDFView,
- QuestionView,
RedirectView,
SingleObjectMixin,
- TemplateView,
- UpdateView)
+ TemplateView)
-from .csv_import import import_agenda_items
-from .forms import AppendSpeakerForm, ItemForm, ItemOrderForm, RelatedItemForm
-from .models import Item, Speaker
+from .models import Item
from .serializers import ItemSerializer
-class Overview(TemplateView):
- """
- Show all agenda items, and update their range via post.
- """
- required_permission = 'agenda.can_see'
- template_name = 'agenda/overview.html'
-
- def get_context_data(self, **kwargs):
- context = super(Overview, self).get_context_data(**kwargs)
-
- if self.request.user.has_perm('agenda.can_see_orga_items'):
- items = Item.objects.all()
- else:
- items = Item.objects.filter(type__exact=Item.AGENDA_ITEM)
-
- # Save the items as a list (not a queryset). This is important,
- # because in other case, django-mtpp reloads the items in the
- # template. But we add some attributes (in this function), which are
- # not in the database and would be lost if the items were reloaded.
- # TODO: Try to remove this line in later versions of django-mptt
- items = list(items)
-
- start = config['agenda_start_event_date_time']
- if start is None or len(start) == 0:
- start = None
- else:
- start = datetime.strptime(start, '%d.%m.%Y %H:%M')
-
- duration = timedelta()
-
- for item in items:
- if item.duration is not None and len(item.duration) > 0:
- if ':' in item.duration:
- duration_list = item.duration.split(':')
- duration += timedelta(hours=int(duration_list[0]),
- minutes=int(duration_list[1]))
- else:
- hours = int(item.duration) / 60
- minutes = int(item.duration) - (60 * hours)
- duration += timedelta(hours=hours,
- minutes=minutes)
- if minutes < 10:
- minutes = "%s%s" % (0, minutes)
- item.duration = "%s:%s" % (hours, minutes)
- if start is not None:
- item.tooltip = start + duration
-
- if start is None:
- end = None
- else:
- end = start + duration
-
- duration = u'%d:%02d' % (
- (duration.days * 24 + duration.seconds / 3600.0),
- (duration.seconds / 60.0 % 60))
-
- active_slide = get_active_slide()
- if active_slide['callback'] == 'agenda':
- agenda_is_active = active_slide.get('pk', 'agenda') == 'agenda'
- active_type = active_slide.get('type', 'text')
- else:
- agenda_is_active = None
- active_type = None
-
- context.update({
- 'items': items,
- 'agenda_is_active': agenda_is_active,
- 'duration': duration,
- 'start': start,
- 'end': end,
- 'active_type': active_type})
- return context
-
- def post(self, request, *args, **kwargs):
- if not request.user.has_perm('agenda.can_manage'):
- messages.error(
- request,
- _('You are not authorized to manage the agenda.'))
- context = self.get_context_data(**kwargs)
- return self.render_to_response(context)
-
- # Use transaction.atomic() to change all items at once.
- # Raise IntegrityError if some data is invalid.
- # In this case, django does not commit anything, else, the mptt-tree is
- # rebuild.
- try:
- with transaction.atomic():
- for item in Item.objects.all():
- form = ItemOrderForm(request.POST, prefix="i%d" % item.id)
- if form.is_valid():
- try:
- parent = Item.objects.get(id=form.cleaned_data['parent'])
- except Item.DoesNotExist:
- parent = None
- else:
- if item.type == item.AGENDA_ITEM and parent.type == item.ORGANIZATIONAL_ITEM:
- messages.error(
- request, _('Agenda items can not be child elements of an organizational item.'))
- raise IntegrityError
- item.parent = parent
- item.weight = form.cleaned_data['weight']
- Model.save(item)
- else:
- messages.error(
- request, _('Errors when reordering of the agenda'))
- raise IntegrityError
- except IntegrityError:
- pass
- else:
- Item.objects.rebuild()
- # TODO: assure, that it is a valid tree
-
- context = self.get_context_data(**kwargs)
- return self.render_to_response(context)
-
-
-class AgendaItemView(SingleObjectMixin, FormView):
- """
- Show an agenda item.
- """
- # TODO: use 'SingleObjectTemplateResponseMixin' to choose the right template name
- template_name = 'agenda/view.html'
- model = Item
- context_object_name = 'item'
- form_class = AppendSpeakerForm
-
- def check_permission(self, request, *args, **kwargs):
- """
- Checks if the user has the required permission.
- """
- if self.get_object().type == Item.ORGANIZATIONAL_ITEM:
- check_permission = request.user.has_perm('agenda.can_see_orga_items')
- else:
- check_permission = request.user.has_perm('agenda.can_see')
- return check_permission
-
- def get_context_data(self, **kwargs):
- list_of_speakers = self.get_object().get_list_of_speakers()
- active_slide = get_active_slide()
- active_type = active_slide.get('type', None)
- kwargs.update({
- 'object': self.get_object(),
- 'list_of_speakers': list_of_speakers,
- 'is_on_the_list_of_speakers': Speaker.objects.filter(
- item=self.get_object(), begin_time=None, user=self.request.user).exists(),
- 'active_type': active_type,
- })
- return super(AgendaItemView, self).get_context_data(**kwargs)
-
- def form_valid(self, form):
- Speaker.objects.add(user=form.cleaned_data['speaker'], item=self.get_object())
- return self.render_to_response(self.get_context_data(form=form))
-
- def get_form_kwargs(self):
- kwargs = super(AgendaItemView, self).get_form_kwargs()
- kwargs['item'] = self.get_object()
- return kwargs
-
-
-class SetClosed(SingleObjectMixin, RedirectView):
- """
- Close or open an item.
- """
- required_permission = 'agenda.can_manage'
- allow_ajax = True
- url_name = 'item_overview'
- model = Item
-
- def get_ajax_context(self, **kwargs):
- closed = self.kwargs['closed']
- if closed:
- link = reverse('item_open', args=[self.get_object().pk])
- else:
- link = reverse('item_close', args=[self.get_object().pk])
- return super(SetClosed, self).get_ajax_context(closed=closed, link=link)
-
- def pre_redirect(self, request, *args, **kwargs):
- closed = kwargs['closed']
- # TODO: use update here
- self.get_object().closed = closed
- self.get_object().save()
- return super(SetClosed, self).pre_redirect(request, *args, **kwargs)
-
- def get_url_name_args(self):
- return []
-
-
-class ItemUpdate(UpdateView):
- """
- Update an existing item.
- """
- required_permission = 'agenda.can_manage'
- template_name = 'agenda/edit.html'
- model = Item
- context_object_name = 'item'
- success_url_name = 'item_overview'
- url_name_args = []
-
- def get_form_class(self):
- if self.get_object().content_object:
- form = RelatedItemForm
- else:
- form = ItemForm
- return form
-
-
-class ItemCreate(CreateView):
- """
- Create a new item.
- """
- required_permission = 'agenda.can_manage'
- template_name = 'agenda/edit.html'
- model = Item
- context_object_name = 'item'
- form_class = ItemForm
- success_url_name = 'item_overview'
- url_name_args = []
-
-
-class ItemDelete(DeleteView):
- """
- Delete an item.
- """
- required_permission = 'agenda.can_manage'
- model = Item
- question_url_name = 'item_overview'
- success_url_name = 'item_overview'
- url_name_args = []
-
- def get_answer_options(self):
- """
- Returns the possible answers to the delete view.
-
- 'all' is a possible answer, when the item has children.
- """
- # Cache the result in the request, so when the children are deleted, the
- # result does not change
- try:
- options = self.item_delete_answer_options
- except AttributeError:
- if self.get_object().children.exists():
- options = [('all', _("Yes, with all child items."))] + self.answer_options
- else:
- options = self.answer_options
- self.item_delete_answer_options = options
- return options
-
- def on_clicked_yes(self):
- """
- Deletes the item but not its children.
- """
- self.get_object().delete(with_children=False)
-
- def on_clicked_all(self):
- """
- Deletes the item and its children.
- """
- self.get_object().delete(with_children=True)
-
- def get_final_message(self):
- """
- Prints the success message to the user.
- """
- # OpenSlidesError (invalid answer) should never be raised here because
- # this method should only be called if the answer is 'yes' or 'all'.
- if self.get_answer() == 'yes':
- message = _('Item %s was successfully deleted.') % html_strong(self.get_object())
- else:
- message = _('Item %s and its children were successfully deleted.') % html_strong(self.get_object())
- return message
-
-
class CreateRelatedAgendaItemView(SingleObjectMixin, RedirectView):
"""
View to create and agenda item for a related object.
@@ -337,22 +49,6 @@ class CreateRelatedAgendaItemView(SingleObjectMixin, RedirectView):
self.item = Item.objects.create(content_object=self.get_object())
-class AgendaNumberingView(QuestionView):
- required_permission = 'agenda.can_manage'
- question_url_name = 'item_overview'
- url_name = 'item_overview'
- question_message = ugettext_lazy('Do you really want to generate agenda numbering? Manually added item numbers will be overwritten!')
- url_name_args = []
-
- def on_clicked_yes(self):
- for item in Item.objects.all():
- item.item_number = item.calc_item_no()
- item.save()
-
- def get_final_message(self):
- return ugettext_lazy('The agenda has been numbered.')
-
-
class AgendaPDF(PDFView):
"""
Create a full agenda-PDF.
@@ -373,297 +69,6 @@ class AgendaPDF(PDFView):
story.append(Paragraph(escape(item.get_title()), stylesheet['Item']))
-class SpeakerAppendView(SingleObjectMixin, RedirectView):
- """
- Set the request.user to the speaker list.
- """
- required_permission = 'agenda.can_be_speaker'
- url_name = 'item_view'
- model = Item
-
- def pre_redirect(self, request, *args, **kwargs):
- if self.get_object().speaker_list_closed:
- messages.error(request, _('The list of speakers is closed.'))
- else:
- try:
- Speaker.objects.add(item=self.get_object(), user=request.user)
- except OpenSlidesError as e:
- messages.error(request, e)
- else:
- messages.success(request, _('You were successfully added to the list of speakers.'))
-
-
-class SpeakerDeleteView(DeleteView):
- """
- Delete the request.user or a specific user from the speaker list.
- """
- success_url_name = 'item_view'
- question_url_name = 'item_view'
-
- def check_permission(self, request, *args, **kwargs):
- """
- Check the permission to delete a speaker.
- """
- if 'speaker' in kwargs:
- return request.user.has_perm('agenda.can_manage')
- else:
- # Any user who is on the list of speakers can delete himself from the list.
- return True
-
- def get(self, *args, **kwargs):
- if self.get_object() is None:
- return super(RedirectView, self).get(*args, **kwargs)
- else:
- return super().get(*args, **kwargs)
-
- def get_object(self):
- """
- Returns the speaker object.
-
- If 'speaker' is in kwargs, this speaker object is returnd. Else, a speaker
- object with the request.user as speaker.
- """
- try:
- speaker = self._object
- except AttributeError:
- speaker_pk = self.kwargs.get('speaker')
- if speaker_pk is not None:
- queryset = Speaker.objects.filter(pk=speaker_pk)
- else:
- queryset = Speaker.objects.filter(
- item=self.kwargs['pk'], user=self.request.user).exclude(weight=None)
- try:
- speaker = queryset.get()
- except Speaker.DoesNotExist:
- speaker = None
- if speaker_pk is not None:
- messages.error(self.request, _('You are not on the list of speakers.'))
- self._object = speaker
- return speaker
-
- def get_url_name_args(self):
- return [self.kwargs['pk']]
-
- def get_question(self):
- if 'speaker' in self.kwargs:
- return super(SpeakerDeleteView, self).get_question()
- else:
- return _('Do you really want to remove yourself from the list of speakers?')
-
-
-class SpeakerSpeakView(SingleObjectMixin, RedirectView):
- """
- Mark the speaking user.
- """
- required_permission = 'agenda.can_manage'
- url_name = 'item_view'
- model = Item
-
- def pre_redirect(self, *args, **kwargs):
- try:
- speaker = Speaker.objects.filter(
- user=kwargs['user_id'],
- item=self.get_object(),
- begin_time=None).get()
- except Speaker.DoesNotExist: # TODO: Check the MultipleObjectsReturned error here?
- messages.error(
- self.request,
- _('%(user)s is not on the list of %(item)s.')
- % {'user': kwargs['user_id'], 'item': self.get_object()})
- else:
- speaker.begin_speach()
-
- def get_url_name_args(self):
- return [self.get_object().pk]
-
-
-class SpeakerEndSpeachView(SingleObjectMixin, RedirectView):
- """
- The speach of the actual speaker is finished.
- """
- required_permission = 'agenda.can_manage'
- url_name = 'item_view'
- model = Item
-
- def pre_redirect(self, *args, **kwargs):
- try:
- speaker = Speaker.objects.filter(
- item=self.get_object(),
- end_time=None).exclude(begin_time=None).get()
- except Speaker.DoesNotExist:
- messages.error(
- self.request,
- _('There is no one speaking at the moment according to %(item)s.')
- % {'item': self.get_object()})
- else:
- speaker.end_speach()
-
- def get_url_name_args(self):
- return [self.get_object().pk]
-
-
-class SpeakerListCloseView(SingleObjectMixin, RedirectView):
- """
- View to close and reopen a list of speakers.
- """
- required_permission = 'agenda.can_manage'
- model = Item
- reopen = False
- url_name = 'item_view'
-
- def pre_redirect(self, *args, **kwargs):
- self.get_object().speaker_list_closed = not self.reopen
- self.get_object().save()
-
- def get_url_name_args(self):
- return [self.get_object().pk]
-
-
-class SpeakerChangeOrderView(SingleObjectMixin, RedirectView):
- """
- Change the order of the speakers.
-
- Has to be called as post-request with the new order of the speaker ids.
- """
- required_permission = 'agenda.can_manage'
- model = Item
- url_name = 'item_view'
-
- def pre_post_redirect(self, request, *args, **kwargs):
- """
- Reorder the list of speaker.
-
- Take the string 'sort_order' from the post-data, and use this order.
- """
- try:
- with transaction.atomic():
- for (counter, speaker) in enumerate(self.request.POST['sort_order'].split(',')):
- try:
- speaker_pk = int(speaker.split('_')[1])
- except IndexError:
- raise IntegrityError
- try:
- speaker = Speaker.objects.filter(item=self.get_object()).get(pk=speaker_pk)
- except Speaker.DoesNotExist:
- raise IntegrityError
- speaker.weight = counter + 1
- speaker.save()
- except IntegrityError:
- messages.error(request, _('Could not change order. Invalid data.'))
-
- def get_url_name_args(self):
- return [self.get_object().pk]
-
-
-class CurrentListOfSpeakersView(RedirectView):
- """
- Redirect to the current list of speakers and set the request.user on it,
- begins speach of the next speaker or ends the speach of the current speaker.
- """
- set_speaker = False
- next_speaker = False
- end_speach = False
-
- def get_item(self):
- """
- Returns the current Item, or None, if the current Slide is not an Agenda Item.
- """
- slide = get_active_object()
- if slide is None or isinstance(slide, Item):
- # No Slide or an agenda item is active
- item = slide
- else:
- # A related Item is active
- try:
- item = Item.objects.filter(
- content_type=ContentType.objects.get_for_model(slide),
- object_id=slide.pk)[0]
- except IndexError:
- item = None
-
- return item
-
- def get_redirect_url(self):
- """
- Returns the URL to the item_view if:
-
- * the current slide is an item,
- * the user has the permission to see the item,
- * the user who wants to be a speaker has this permission and
- * the list of speakers of the item is not closed,
-
- in other case, it returns the URL to the dashboard.
-
- This method also adds the request.user to the list of speakers if he
- has the right permissions and the list is not closed.
-
- This method also begins the speach of the next speaker if the flag
- next_speaker is given.
-
- This method also ends the speach of the current speaker if the flag
- end_speach is given.
- """
- item = self.get_item()
- request = self.request
-
- if item is None:
- messages.error(request, _(
- 'There is no list of speakers for the current slide. '
- 'Please choose the agenda item manually from the agenda.'))
- return reverse('core_dashboard')
-
- if self.set_speaker:
- if item.speaker_list_closed:
- messages.error(request, _('The list of speakers is closed.'))
- reverse_to_dashboard = True
- else:
- if self.request.user.has_perm('agenda.can_be_speaker'):
- try:
- Speaker.objects.add(self.request.user, item)
- except OpenSlidesError as e:
- messages.error(request, e)
- else:
- messages.success(request, _('You were successfully added to the list of speakers.'))
- finally:
- reverse_to_dashboard = False
- else:
- messages.error(request, _('You can not put yourself on the list of speakers.'))
- reverse_to_dashboard = True
- else:
- reverse_to_dashboard = False
-
- if self.next_speaker:
- next_speaker_object = item.get_next_speaker()
- if next_speaker_object:
- next_speaker_object.begin_speach()
- messages.success(request, _('%s is now speaking.') % next_speaker_object)
- else:
- messages.error(request, _('The list of speakers is empty.'))
- if not self.set_speaker:
- reverse_to_dashboard = True
-
- if self.end_speach:
- try:
- current_speaker = item.speaker_set.filter(end_time=None).exclude(begin_time=None).get()
- except Speaker.DoesNotExist:
- messages.error(request, _('There is no one speaking at the moment.'))
- else:
- current_speaker.end_speach()
- messages.success(request, _('%s is now finished.') % current_speaker)
- reverse_to_dashboard = True
-
- if item.type == Item.ORGANIZATIONAL_ITEM:
- if reverse_to_dashboard or not self.request.user.has_perm('agenda.can_see_orga_items'):
- return reverse('core_dashboard')
- else:
- return reverse('item_view', args=[item.pk])
- else:
- if reverse_to_dashboard or not self.request.user.has_perm('agenda.can_see'):
- return reverse('core_dashboard')
- else:
- return reverse('item_view', args=[item.pk])
-
-
class CurrentListOfSpeakersProjectorView(AjaxMixin, TemplateView):
"""
View with the current list of speakers depending on the active slide.
@@ -766,16 +171,6 @@ class CurrentListOfSpeakersProjectorView(AjaxMixin, TemplateView):
**context)
-class ItemCSVImportView(CSVImportView):
- """
- Imports agenda items from an uploaded csv file.
- """
- import_function = staticmethod(import_agenda_items)
- required_permission = 'agenda.can_manage'
- success_url_name = 'item_overview'
- template_name = 'agenda/item_form_csv_import.html'
-
-
class ItemViewSet(ModelViewSet):
"""
API endpoint to list, retrieve, create, update and destroy agenda items.
diff --git a/openslides/assignments/main_menu.py b/openslides/assignments/main_menu.py
index 16a53f285..6e7eeb517 100644
--- a/openslides/assignments/main_menu.py
+++ b/openslides/assignments/main_menu.py
@@ -10,5 +10,5 @@ class AssignmentMainMenuEntry(MainMenuEntry):
verbose_name = ugettext_lazy('Elections')
required_permission = 'assignments.can_see'
default_weight = 40
- pattern_name = 'assignment_list'
+ pattern_name = '/assignments' # TODO: use generic solution, see issue #1469
icon_css_class = 'icon-assignment'
diff --git a/openslides/assignments/static/img/glyphicons_041_charts.png b/openslides/assignments/static/img/glyphicons_041_charts.png
deleted file mode 100644
index 556b7cce5..000000000
Binary files a/openslides/assignments/static/img/glyphicons_041_charts.png and /dev/null differ
diff --git a/openslides/assignments/static/img/glyphicons_041_charts_white.png b/openslides/assignments/static/img/glyphicons_041_charts_white.png
deleted file mode 100644
index 83cd93b8c..000000000
Binary files a/openslides/assignments/static/img/glyphicons_041_charts_white.png and /dev/null differ
diff --git a/openslides/assignments/static/js/assignment.js b/openslides/assignments/static/js/assignment.js
deleted file mode 100644
index bc8d860cb..000000000
--- a/openslides/assignments/static/js/assignment.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * JavaScript functions for assignment.
- *
- */
-
-$(function() {
- $('a.elected').parent().parent().children('td').addClass('elected');
-
- $('.election_link').click(function(event) {
- event.preventDefault();
- line = $(this);
- $.ajax({
- type: 'GET',
- url: line.attr('href'),
- dataType: 'json',
- success: function(data) {
- if (line.hasClass('elected') && !data.elected) {
- line.removeClass('elected')
- line.parent().parent().children('td').removeClass('elected')
- } else if (!line.hasClass('elected') && data.elected) {
- line.addClass('elected')
- line.parent().parent().children('td').addClass('elected')
- }
- line.attr('href', data.link);
- }
- });
- });
- $('.close_link').click(function(event) {
- event.preventDefault();
- slide = $(this);
- $.ajax({
- type: 'GET',
- url: slide.attr('href'),
- dataType: 'json',
- success: function(data) {
- if (data.closed) {
- newclass = 'closed';
- } else {
- newclass = 'open';
- }
- slide.removeClass('closed open').addClass(newclass);
- slide.attr('href', data.link);
- }
- });
- });
- // change publish status of ballot
- $('.publish_link').click(function(event) {
- event.preventDefault();
- var link = $(this);
- $.ajax({
- type: 'GET',
- url: $(this).attr('href'),
- dataType: 'json',
- success: function(data) {
- if (data.published) {
- newclass = 'icon-checked-new_white';
- link.addClass('btn-primary');
- } else {
- newclass = 'icon-unchecked-new';
- link.removeClass('btn-primary');
- }
- link.children('i').removeClass('icon-checked-new_white icon-unchecked-new').addClass(newclass);
- link.attr('href', data.link);
- }
- });
- });
-});
diff --git a/openslides/assignments/static/js/assignments/assignments.js b/openslides/assignments/static/js/assignments/assignments.js
index e3cc60972..b65ec19b1 100644
--- a/openslides/assignments/static/js/assignments/assignments.js
+++ b/openslides/assignments/static/js/assignments/assignments.js
@@ -3,7 +3,7 @@ angular.module('OpenSlidesApp.assignments', [])
.config(function($stateProvider) {
$stateProvider
.state('assignments', {
- url: '/assignment',
+ url: '/assignments',
abstract: true,
template: "",
})
@@ -15,6 +15,9 @@ angular.module('OpenSlidesApp.assignments', [])
resolve: {
assignments: function(Assignment) {
return Assignment.findAll();
+ },
+ phases: function($http) {
+ return $http({ 'method': 'OPTIONS', 'url': '/rest/assignments/assignment/' });
}
}
})
@@ -41,29 +44,58 @@ angular.module('OpenSlidesApp.assignments', [])
});
})
-.controller('AssignmentListCtrl', function($scope, Assignment) {
+.controller('AssignmentListCtrl', function($scope, Assignment, phases) {
Assignment.bindAll({}, $scope, 'assignments');
+ // get all item types via OPTIONS request
+ $scope.phases = phases.data.actions.POST.phase.choices;
+
+ // setup table sorting
+ $scope.sortColumn = 'title';
+ $scope.filterPresent = '';
+ $scope.reverse = false;
+ // function to sort by clicked column
+ $scope.toggleSort = function ( column ) {
+ if ( $scope.sortColumn === column ) {
+ $scope.reverse = !$scope.reverse;
+ }
+ $scope.sortColumn = column;
+ };
+
+ // delete assignment
+ $scope.delete = function (assignment) {
+ //TODO: add confirm message
+ Assignment.destroy(assignment.id).then(
+ function(success) {
+ //TODO: success message
+ }
+ );
+ };
})
.controller('AssignmentDetailCtrl', function($scope, Assignment, assignment) {
Assignment.bindOne(assignment.id, $scope, 'assignment')
})
-.controller('AssignmentCreateCtrl', function($scope, Assignment) {
+.controller('AssignmentCreateCtrl', function($scope, $state, Assignment) {
$scope.assignment = {};
$scope.save = function(assignment) {
- assignment.open_posts = 1;
assignment.tags = []; // TODO: the rest_api should do this
- Assignment.create(assignment);
- // TODO: redirect to list-view
+ Assignment.create(assignment).then(
+ function(success) {
+ $state.go('assignments.assignment.list');
+ }
+ );
};
})
-.controller('AssignmentUpdateCtrl', function($scope, Assignment, assignment) {
+.controller('AssignmentUpdateCtrl', function($scope, $state, Assignment, assignment) {
$scope.assignment = assignment; // do not use .binOne(...) so autoupdate is not activated
$scope.save = function (assignment) {
- Assignment.save(assignment);
- // TODO: redirect to list-view
+ Assignment.save(assignment).then(
+ function(success) {
+ $state.go('assignments.assignment.list');
+ }
+ );
};
});
diff --git a/openslides/assignments/static/templates/assignments/assignment-detail.html b/openslides/assignments/static/templates/assignments/assignment-detail.html
index b3c623b6e..592646e05 100644
--- a/openslides/assignments/static/templates/assignments/assignment-detail.html
+++ b/openslides/assignments/static/templates/assignments/assignment-detail.html
@@ -1,2 +1,31 @@
{{ assignment.title }}
+
+
+
+Description
{{ assignment.description }}
+
+Candidates
+
+
+Election result
+
diff --git a/openslides/assignments/static/templates/assignments/assignment-form.html b/openslides/assignments/static/templates/assignments/assignment-form.html
index fe8700e69..75e6b7e75 100644
--- a/openslides/assignments/static/templates/assignments/assignment-form.html
+++ b/openslides/assignments/static/templates/assignments/assignment-form.html
@@ -1,8 +1,31 @@
-{{ assignment.title }}
-Neue Assignment
+Edit election
+New election
-
diff --git a/openslides/assignments/static/templates/assignments/assignment-list.html b/openslides/assignments/static/templates/assignments/assignment-list.html
index dc67383a2..9a5acd83f 100644
--- a/openslides/assignments/static/templates/assignments/assignment-list.html
+++ b/openslides/assignments/static/templates/assignments/assignment-list.html
@@ -1,7 +1,76 @@
-
-Neu
+Elections
+
+
+
+
+
+
+
+
+
+ Title
+
+
+ |
+ Posts
+
+
+ |
+ State
+
+
+ |
+ Actions
+ |
+
+ {{ assignment.title }}
+ | {{ assignment.open_posts }}
+ |
+
+ {{ phases[assignment.phase].display_name }}
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ |
diff --git a/openslides/assignments/templates/assignments/assignment_detail.html b/openslides/assignments/templates/assignments/assignment_detail.html
index 947f1a1dc..2601333c3 100644
--- a/openslides/assignments/templates/assignments/assignment_detail.html
+++ b/openslides/assignments/templates/assignments/assignment_detail.html
@@ -22,7 +22,7 @@
{% trans "Back to overview" %}
-
PDF
diff --git a/openslides/assignments/templates/assignments/assignment_form.html b/openslides/assignments/templates/assignments/assignment_form.html
deleted file mode 100644
index 0888ba10a..000000000
--- a/openslides/assignments/templates/assignments/assignment_form.html
+++ /dev/null
@@ -1,47 +0,0 @@
-{% extends "base.html" %}
-
-{% load i18n %}
-{% load tags %}
-
-{% block title %}
- {% if assignment %}
- {% trans "Edit election" %}
- {% else %}
- {% trans "New election" %}
- {% endif %}
- – {{ block.super }}
-{% endblock %}
-
-{% block content %}
-
- {% if assignment %}
- {% trans "Edit election" %}
- {% else %}
- {% trans "New election" %}
- {% endif %}
-
- {% if assignment %}
-
-
- {% trans "Back to election" %}
-
- {% else %}
-
-
- {% trans "Back to overview" %}
-
- {% endif %}
-
-
-
-
-{% endblock %}
diff --git a/openslides/assignments/templates/assignments/assignment_list.html b/openslides/assignments/templates/assignments/assignment_list.html
deleted file mode 100644
index 5653f6964..000000000
--- a/openslides/assignments/templates/assignments/assignment_list.html
+++ /dev/null
@@ -1,94 +0,0 @@
-{% extends "base.html" %}
-
-{% load i18n %}
-{% load staticfiles %}
-{% load tags %}
-
-{% block title %}{% trans "Elections" %} – {{ block.super }}{% endblock %}
-
-{% block content %}
- {% trans "Elections" %}
-
- {% if perms.assignments.can_manage %}
-
-
- {% trans 'New' %}
-
- {% endif %}
- {% if perms.core.can_manage_tags %}
-
-
- {% trans 'Tags' %}
-
- {% endif %}
- {% if perms.assignments.can_see %}
-
-
- PDF
-
- {% endif %}
-
-
-
-
-
-
- {% trans "Election" %} |
- {% trans "Candidates" %} |
- {% trans "Phase" %} |
- {% if perms.assignments.can_manage or perms.core.can_manage_projector %}
- {% trans "Actions" %} |
- {% endif %}
-
-
- {% for object in object_list %}
-
- {{ object }}
- {% for tag in object.tags.all %}
- {{ tag }}
- {% endfor %}
- |
-
-
- {% trans "Posts" context "Number of searched candidates for an election" %}:
- {{ object.open_posts }}
-
- {% if object.phase != object.PHASE_FINISHED %}
- | {% trans "Candidates" %}: {{ object.candidates.count }}
- {% endif %}
-
- | {% trans "Elected" %}: {{ object.elected.count }}
- |
- {{ object.get_phase_display }} |
- {% if perms.assignments.can_manage or perms.core.can_manage_projector %}
-
-
- {% if perms.core.can_manage_projector %}
-
-
-
- {% endif %}
- {% if perms.assignments.can_manage %}
-
-
-
-
-
-
- {% endif %}
-
- |
- {% endif %}
-
- {% endfor %}
-
-{% endblock %}
diff --git a/openslides/assignments/templates/assignments/widget_assignment.html b/openslides/assignments/templates/assignments/widget_assignment.html
index 7c28f131a..e69de29bb 100644
--- a/openslides/assignments/templates/assignments/widget_assignment.html
+++ b/openslides/assignments/templates/assignments/widget_assignment.html
@@ -1,30 +0,0 @@
-{% extends 'core/widget.html' %}
-
-{% load i18n %}
-{% load tags %}
-
-{% block content %}
-
- {% for assignment in assignments %}
- -
-
-
-
- {% if perms.assignments.can_manage %}
-
-
-
- {% endif %}
-
-
-
- {{ assignment }}
-
- {% empty %}
- - {% trans 'No elections available.' %}
- {% endfor %}
-
-{% endblock %}
diff --git a/openslides/assignments/urls.py b/openslides/assignments/urls.py
index b2a31e2ed..cad5efc48 100644
--- a/openslides/assignments/urls.py
+++ b/openslides/assignments/urls.py
@@ -4,87 +4,17 @@ from . import views
urlpatterns = patterns(
'',
- url(r'^$',
- views.AssignmentListView.as_view(),
- name='assignment_list'),
-
- url(r'^(?P\d+)/$',
- views.AssignmentDetail.as_view(),
- name='assignment_detail'),
-
- url(r'^new/$',
- views.AssignmentCreateView.as_view(),
- name='assignment_create'),
-
- url(r'^(?P\d+)/edit/$',
- views.AssignmentUpdateView.as_view(),
- name='assignment_update'),
-
- url(r'^(?P\d+)/del/$',
- views.AssignmentDeleteView.as_view(),
- name='assignment_delete'),
-
- url(r'^(?P\d+)/set_phase/(?P\d+)/$',
- views.AssignmentSetPhaseView.as_view(),
- name='assignment_set_phase'),
-
- url(r'^(?P\d+)/candidate/$',
- views.AssignmentCandidateView.as_view(),
- name='assignment_candidate'),
-
- url(r'^(?P\d+)/delete_candidate/$',
- views.AssignmentDeleteCandidateshipView.as_view(),
- name='assignment_del_candidate'),
-
- url(r'^(?P\d+)/delother/(?P[^/]+)/$',
- views.AssignmentDeleteCandidateshipOtherView.as_view(),
- name='assignment_del_candidate_other'),
-
- url(r'^(?P\d+)/agenda/$',
- views.CreateRelatedAgendaItemView.as_view(),
- name='assignment_create_agenda'),
+ # PDF
url(r'^print/$',
views.AssignmentPDF.as_view(),
- name='assignment_list_pdf'),
+ name='assignments_pdf'),
url(r'^(?P\d+)/print/$',
views.AssignmentPDF.as_view(),
- name='assignment_pdf'),
-
- url(r'^(?P\d+)/create_poll/$',
- views.PollCreateView.as_view(),
- name='assignmentpoll_create'),
-
- url(r'^poll/(?P\d+)/edit/$',
- views.PollUpdateView.as_view(),
- name='assignmentpoll_update'),
-
- url(r'^poll/(?P\d+)/del/$',
- views.AssignmentPollDeleteView.as_view(),
- name='assignmentpoll_delete'),
+ name='assignments_single_pdf'),
url(r'^poll/(?P\d+)/print/$',
views.AssignmentPollPDF.as_view(),
name='assignmentpoll_pdf'),
-
- url(r'^poll/(?P\d+)/pub/$',
- views.SetPublishPollView.as_view(),
- {'publish': True},
- name='assignmentpoll_publish_poll'),
-
- url(r'^poll/(?P\d+)/unpub/$',
- views.SetPublishPollView.as_view(),
- {'publish': False},
- name='assignmentpoll_unpublish_poll'),
-
- url(r'^(?P\d+)/elected/(?P[^/]+)/$',
- views.SetElectedView.as_view(),
- {'elected': True},
- name='assignment_user_elected'),
-
- url(r'^(?P\d+)/notelected/(?P[^/]+)/$',
- views.SetElectedView.as_view(),
- {'elected': False},
- name='assignment_user_not_elected')
)
diff --git a/openslides/assignments/views.py b/openslides/assignments/views.py
index 5c4d3eafe..bb6a1d173 100644
--- a/openslides/assignments/views.py
+++ b/openslides/assignments/views.py
@@ -1,7 +1,5 @@
from cgi import escape
-from django.contrib import messages
-from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
from reportlab.lib import colors
@@ -9,212 +7,16 @@ from reportlab.lib.units import cm
from reportlab.platypus import (PageBreak, Paragraph, SimpleDocTemplate, Spacer,
LongTable, Table, TableStyle)
-from openslides.agenda.views import CreateRelatedAgendaItemView as _CreateRelatedAgendaItemView
from openslides.config.api import config
from openslides.users.models import Group, User # TODO: remove this
-from openslides.poll.views import PollFormView
from openslides.utils.pdf import stylesheet
from openslides.utils.rest_api import ModelViewSet, Response, ValidationError, detail_route
-from openslides.utils.utils import html_strong
-from openslides.utils.views import (CreateView, DeleteView, DetailView,
- ListView, PDFView,
- QuestionView, RedirectView,
- SingleObjectMixin, UpdateView)
+from openslides.utils.views import PDFView
-from .forms import AssignmentForm, AssignmentRunForm
from .models import Assignment, AssignmentPoll
from .serializers import AssignmentFullSerializer, AssignmentShortSerializer
-class AssignmentListView(ListView):
- """
- Lists all assignments.
- """
- required_permission = 'assignments.can_see'
- model = Assignment
-
-
-class AssignmentDetail(DetailView):
- """
- Shows one assignment.
- """
- # TODO: use another view as 'run form' when updating this to angular
- required_permission = 'assignments.can_see'
- model = Assignment
- form_class = AssignmentRunForm
-
- def get_context_data(self, *args, **kwargs):
- context = super().get_context_data(*args, **kwargs)
- assignment = self.get_object()
- if self.request.method == 'POST':
- context['form'] = self.form_class(self.request.POST)
- else:
- context['form'] = self.form_class()
-
- polls = assignment.polls.all()
- if not self.request.user.has_perm('assignments.can_manage'):
- polls = polls.filter(published=True)
- vote_results = assignment.vote_results(only_published=True)
- else:
- polls = self.get_object().polls.all()
- vote_results = assignment.vote_results(only_published=False)
-
- context['polls'] = polls
- context['vote_results'] = vote_results
- context['blocked_candidates'] = assignment.blocked
- context['user_is_candidate'] = assignment.is_candidate(self.request.user)
- return context
-
- def post(self, *args, **kwargs):
- if self.request.user.has_perm('assignments.can_nominate_other'):
- assignment = self.get_object()
- form = self.form_class(self.request.POST)
- if form.is_valid():
- user = form.cleaned_data['candidate']
- if (assignment.phase == assignment.PHASE_SEARCH or
- self.request.user.has_perm('assignments.can_manage')):
- if (assignment.is_blocked(user) and
- not self.request.user.has_perm('assignments.can_manage')):
- messages.error(
- self.request,
- _("User %s does not want to be an candidate") % user)
- elif assignment.is_elected(user):
- messages.error(
- self.request,
- _("User %s is already elected") % html_strong(user))
- elif assignment.is_candidate(user):
- messages.error(
- self.request,
- _("User %s is already an candidate") % html_strong(user))
- else:
- assignment.set_candidate(user)
- messages.success(
- self.request,
- _("User %s was nominated successfully.") % html_strong(user))
- else:
- messages.error(
- self.request,
- _("You can not add candidates to this assignment"))
- return super(AssignmentDetail, self).get(*args, **kwargs)
-
-
-class AssignmentCreateView(CreateView):
- required_permission = 'assignments.can_manage'
- model = Assignment
- form_class = AssignmentForm
-
-
-class AssignmentUpdateView(UpdateView):
- required_permission = 'assignments.can_manage'
- model = Assignment
- form_class = AssignmentForm
-
-
-class AssignmentDeleteView(DeleteView):
- required_permission = 'assignments.can_manage'
- model = Assignment
- success_url_name = 'assignment_list'
-
-
-class AssignmentSetPhaseView(SingleObjectMixin, RedirectView):
- required_permission = 'assignments.can_manage'
- model = Assignment
- url_name = 'assignment_detail'
-
- def pre_redirect(self, *args, **kwargs):
- phase = int(kwargs.get('phase'))
- assignment = self.get_object()
- try:
- assignment.set_phase(phase)
- except ValueError as e:
- messages.error(self.request, e)
- else:
- assignment.save()
- messages.success(
- self.request,
- _('Election status was set to: %s.') %
- html_strong(assignment.get_phase_display()))
-
-
-class AssignmentCandidateView(SingleObjectMixin, RedirectView):
- required_permission = 'assignments.can_nominate_self'
- model = Assignment
- url_name = 'assignment_detail'
-
- def pre_redirect(self, *args, **kwargs):
- assignment = self.get_object()
- if (assignment.phase == assignment.PHASE_SEARCH or
- self.request.user.has_perm('assignments.can_manage')):
- user = self.request.user
- if assignment.is_elected(user):
- messages.error(
- self.request,
- _("You are already elected"))
- elif assignment.is_candidate(user):
- messages.error(
- self.request,
- _("You are already an candidate"))
- else:
- assignment.set_candidate(user)
- messages.success(
- self.request,
- _("You were nominated successfully."))
- else:
- messages.error(
- self.request,
- _("You can not candidate to this assignment"))
-
-
-class AssignmentDeleteCandidateshipView(SingleObjectMixin, RedirectView):
- required_permission = None # Any user can withdraw his candidature
- model = Assignment
- url_name = 'assignment_detail'
-
- def pre_redirect(self, *args, **kwargs):
- assignment = self.get_object()
- if (assignment.phase == assignment.PHASE_SEARCH or
- self.request.user.has_perm('assignments.can_manage')):
- user = self.request.user
- assignment.set_blocked(user)
- messages.success(self.request, _(
- 'You have withdrawn your candidature successfully. '
- 'You can not be nominated by other participants anymore.'))
- else:
- messages.error(self.request, _('The candidate list is already closed.'))
-
-
-class AssignmentDeleteCandidateshipOtherView(SingleObjectMixin, QuestionView):
- required_permission = 'assignments.can_manage'
- model = Assignment
-
- def get_question_message(self):
- self.user = User.objects.get(pk=self.kwargs.get('user_pk'))
- assignment = self.get_object()
- if assignment.is_blocked:
- question = _("Do you really want to unblock %s for the election?") % html_strong(self.user)
- else:
- question = _("Do you really want to withdraw %s from the election?") % html_strong(self.user)
- return question
-
- def on_clicked_yes(self):
- self.user = User.objects.get(pk=self.kwargs.get('user_pk'))
- assignment = self.get_object()
- if not assignment.is_elected(self.user):
- assignment.delete_related_user(self.user)
- self.error = False
- else:
- self.error = _("User %s is already elected") % html_strong(self.user)
-
- def create_final_message(self):
- if self.error:
- messages.error(self.request, self.error)
- else:
- messages.success(self.request, self.get_final_message())
-
- def get_final_message(self):
- return _("Candidate %s was withdrawn successfully.") % html_strong(self.user)
-
-
class AssignmentViewSet(ModelViewSet):
"""
API endpoint to list, retrieve, create, update and destroy assignments and
@@ -377,104 +179,6 @@ class AssignmentViewSet(ModelViewSet):
return Response({'detail': message})
-class PollCreateView(SingleObjectMixin, RedirectView):
- required_permission = 'assignments.can_manage'
- model = Assignment
- url_name = 'assignment_detail'
-
- def pre_redirect(self, *args, **kwargs):
- self.get_object().create_poll()
- messages.success(self.request, _("New ballot was successfully created."))
-
-
-class PollUpdateView(PollFormView):
- required_permission = 'assignments.can_manage'
- poll_class = AssignmentPoll
- template_name = 'assignments/assignmentpoll_form.html'
-
- def get_context_data(self, **kwargs):
- context = super(PollUpdateView, self).get_context_data(**kwargs)
- self.assignment = self.poll.get_assignment()
- context['assignment'] = self.assignment
- context['poll'] = self.poll
- context['polls'] = self.assignment.polls.all()
- context['ballotnumber'] = self.poll.get_ballot()
- return context
-
- def get_success_url(self):
- if 'apply' not in self.request.POST:
- return_url = reverse('assignment_detail', args=[self.poll.assignment.id])
- else:
- return_url = ''
- return return_url
-
-
-class SetPublishPollView(SingleObjectMixin, RedirectView):
- required_permission = 'assignments.can_manage'
- model = AssignmentPoll
- url_name = 'assignment_detail'
- allow_ajax = True
- publish = False
-
- def get_ajax_context(self, **context):
- return super().get_ajax_context(
- published=self.object.published,
- **context)
-
- def pre_redirect(self, *args, **kwargs):
- poll = self.get_object()
- poll.set_published(kwargs['publish'])
-
-
-class SetElectedView(SingleObjectMixin, RedirectView):
- required_permission = 'assignments.can_manage'
- model = Assignment
- url_name = 'assignment_detail'
- allow_ajax = True
-
- def pre_redirect(self, *args, **kwargs):
- self.person = User.objects.get(pk=kwargs['user_id'])
- self.elected = kwargs['elected']
- # TODO: un-elect users if self.elected is False
- self.get_object().set_elected(self.person)
-
- def get_ajax_context(self, **kwargs):
- if self.elected:
- link = reverse('assignment_user_not_elected',
- args=[self.get_object().id, self.person.person_id])
- text = _('not elected')
- else:
- link = reverse('assignment_user_elected',
- args=[self.get_object().id, self.person.person_id])
- text = _('elected')
- return {'elected': self.elected, 'link': link, 'text': text}
-
-
-class AssignmentPollDeleteView(DeleteView):
- """
- Delete an assignment poll object.
- """
- required_permission = 'assignments.can_manage'
- model = AssignmentPoll
-
- def pre_redirect(self, request, *args, **kwargs):
- self.set_assignment()
- super().pre_redirect(request, *args, **kwargs)
-
- def pre_post_redirect(self, request, *args, **kwargs):
- self.set_assignment()
- super().pre_post_redirect(request, *args, **kwargs)
-
- def set_assignment(self):
- self.assignment = self.get_object().assignment
-
- def get_redirect_url(self, **kwargs):
- return reverse('assignment_detail', args=[self.assignment.id])
-
- def get_final_message(self):
- return _('Ballot was successfully deleted.')
-
-
class AssignmentPDF(PDFView):
required_permission = 'assignments.can_see'
top_space = 0
@@ -665,13 +369,6 @@ class AssignmentPDF(PDFView):
stylesheet['Paragraph']))
-class CreateRelatedAgendaItemView(_CreateRelatedAgendaItemView):
- """
- View to create and agenda item for an assignment.
- """
- model = Assignment
-
-
class AssignmentPollPDF(PDFView):
required_permission = 'assignments.can_manage'
top_space = 0
diff --git a/openslides/core/main_menu.py b/openslides/core/main_menu.py
index 165ea7b25..2d4e5850d 100644
--- a/openslides/core/main_menu.py
+++ b/openslides/core/main_menu.py
@@ -11,4 +11,4 @@ class DashboardMainMenuEntry(MainMenuEntry):
required_permission = 'core.can_see_dashboard'
default_weight = 10
icon_css_class = 'glyphicon-home'
- pattern_name = 'core_dashboard'
+ pattern_name = '/' # TODO: use generic solution, see issue #1469
diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css
index 64854327d..ca427631b 100644
--- a/openslides/core/static/css/app.css
+++ b/openslides/core/static/css/app.css
@@ -6,6 +6,103 @@ body {
background-color: #FBFBFB;
}
+/* TODO: Move only used custom style for angular templates in new css file */
+.angular-ui-tree-handle {
+ background: none repeat scroll 0 0 #f8faff;
+ border: 1px solid #dae2ea;
+ color: #7c9eb2;
+ padding: 10px;
+}
+.angular-ui-tree-handle:hover {
+ background: none repeat scroll 0 0 #f4f6f7;
+ border-color: #dce2e8;
+ color: #438eb9;
+}
+.angular-ui-tree-placeholder {
+ background: none repeat scroll 0 0 #f0f9ff;
+ border: 2px dashed #bed2db;
+ box-sizing: border-box;
+}
+
+/* TODO: used by ng-fab-forms */
+.validation-success {
+ opacity: 1;
+ display: block;
+ position: absolute;
+ right: -7.2px;
+ bottom: -7.2px;
+ font-size: 28.8px;
+ width: 36px;
+ height: 36px;
+ line-height: 36px;
+ text-align: center;
+ border-radius: 36px;
+ color: #62b14c;
+ transition: all ease-out 0.32s; }
+ .validation-success:after {
+ display: block;
+ content: '\e013';
+ font-family: 'Glyphicons Halflings'; }
+ .validation-success.ng-hide {
+ transition-delay: 0s;
+ transition: all ease-out 0.12s;
+ opacity: 0;
+ transform: rotate(360deg); }
+
+.ng-hide-remove li {
+ opacity: 0; }
+
+.validation {
+ color: #fff;
+ margin: 0;
+ position: relative;
+ font-size: 14px;
+ overflow: visible;
+ background: #c00640; }
+ .validation ul {
+ display: block;
+ overflow: hidden; }
+ .validation li {
+ display: block;
+ line-height: 1;
+ background: #c00640;
+ position: absolute;
+ right: -4px;
+ top: -10px;
+ text-align: center;
+ font-weight: bold;
+ padding: 2px 10px;
+ color: #fff;
+ transform: rotate(0deg);
+ transition: all ease-in 0.2s;
+ opacity: 1;
+ transition-delay: 0s; }
+ .validation li.ng-enter {
+ opacity: 0;
+ top: 0; }
+ .validation li.ng-leave {
+ transition: all ease-in 0s;
+ opacity: 0; }
+ *:focus + .validation li {
+ background-color: #63bff8 !important; }
+
+input.ng-touched.ng-invalid:not(.ng-valid), textarea.ng-touched.ng-invalid:not(.ng-valid), select.ng-touched.ng-invalid:not(.ng-valid) {
+ border-color: #c00640; }
+input:focus, input:focus.ng-touched.ng-invalid:not(.ng-valid), textarea:focus, textarea:focus.ng-touched.ng-invalid:not(.ng-valid), select:focus, select:focus.ng-touched.ng-invalid:not(.ng-valid) {
+ border-color: #63bff8; }
+input.ng-valid-required.ng-valid:not(.ng-invalid), textarea.ng-valid-required.ng-valid:not(.ng-invalid), select.ng-valid-required.ng-valid:not(.ng-invalid) {
+ border-color: #62b14c; }
+
+form[class*="ng-invalid"] button.btn[type=submit] {
+ background: #63bff8;
+ transition: none; }
+
+form button.btn[type=submit] {
+ transition: all ease-in 0.5s;
+ background: #62b14c; }
+
+
+
/* Header */
#header {
background-color: #333333;
@@ -86,12 +183,28 @@ a:hover {
line-height: 45px;
}
+/* List tables */
+th.sortable:hover {
+ cursor: pointer;
+}
+
+
/* Log */
#log {
padding-left: 14px;
}
/** Utils **/
+div.import .label {
+ color: #333 !important;
+ font-size: 100%;
+ font-weight: normal;
+}
+div.import > div > input[type="text"] {
+ width: 30px;
+}
+
+
tr.offline td, li.offline {
background-color: #EAEAEA !important;
}
@@ -116,7 +229,7 @@ tr.total td {
.indentation {
margin-left: 12px;
}
-.mini_width {
+.minimum, .mini_width {
width: 1px;
}
/* show optional column */
@@ -201,7 +314,7 @@ table.cke_dialog_contents textarea {
.leftmenu ul li {
display: block;
width: 100%;
- line-height: 30px;
+ line-height: 22px;
}
.leftmenu ul li a {
border-style: none solid solid;
@@ -216,14 +329,15 @@ table.cke_dialog_contents textarea {
.leftmenu ul li:first-child a {
border-top: 1px solid #DDDDDD;
}
-.leftmenu ul li a .glyphicon {
+.leftmenu ul li a span.ico {
display: inline-block;
background: #f9f9f9;
padding: 8px 10px 6px;
margin: 0 5px 0 0;
border-right: 1px solid #dddddd;
+ width: 35px;
}
-.leftmenu ul li a, .leftmenu ul li a .glyphicon {
+.leftmenu ul li a, .leftmenu ul li a span.ico {
-webkit-transition: background 0.2s ease-in-out;
-moz-transition: background 0.2s ease-in-out;
-ms-transition: background 0.2s ease-in-out;
@@ -234,14 +348,14 @@ table.cke_dialog_contents textarea {
background-color: #f5f5f5;
color: #000000;
}
-.leftmenu ul li a:hover .glyphicon {
+.leftmenu ul li a:hover span.ico {
background-color: #efefef;
}
.leftmenu ul li.active a {
background-color: #333333;
color: #ffffff;
}
-.leftmenu ul li.active a .glyphicon {
+.leftmenu ul li.active a span.ico {
background-color: #333333;
border-right: 1px solid #444444;
}
@@ -258,10 +372,10 @@ table.cke_dialog_contents textarea {
margin-top: -34px;
}
.leftmenu.lefticon > ul > li > a > span.text {
- display: none;
+ /*display: none;*/
}
-.leftmenu.lefticon ul ul > li > a {
- min-width: 200px !important;
+.leftmenu.lefticon ul > li > a {
+ min-width: 150px !important;
}
.leftmenu.lefticon span.text {
padding-right: 15px;
@@ -269,14 +383,6 @@ table.cke_dialog_contents textarea {
/** Icons **/
/* TODO: Move some of them to the respective apps. */
-.icon-assignment {
- background: url("../img/glyphicons_041_charts.png") no-repeat !important;
- width: 25px;
- margin-left: 10px !important;
-}
-.leftmenu ul li.active a .glyphicon.icon-assignment {
- background-image: url("../img/glyphicons_041_charts_white.png") !important;
-}
.status_link .icon-on, .icon-checked-new {
background-image: url("../img/glyphicons_152_check.png");
background-position: 0;
diff --git a/openslides/core/static/js/app.js b/openslides/core/static/js/app.js
index 1fe78a0bc..f84121985 100644
--- a/openslides/core/static/js/app.js
+++ b/openslides/core/static/js/app.js
@@ -1,11 +1,24 @@
angular.module('OpenSlidesApp', [
'ui.router',
+ 'angular-loading-bar',
'js-data',
'gettext',
+ 'ngBootbox',
+ 'ngFabForm',
+ 'ngMessages',
+ 'ngAnimate',
+ 'ngCsvImport',
+ 'ngSanitize',
+ 'ui.bootstrap',
+ 'ui.select',
+ 'ui.tree',
+ 'xeditable',
'OpenSlidesApp.core',
'OpenSlidesApp.agenda',
+ 'OpenSlidesApp.motions',
'OpenSlidesApp.assignments',
'OpenSlidesApp.users',
+ 'OpenSlidesApp.mediafiles',
])
.config(function($urlRouterProvider, $locationProvider) {
diff --git a/openslides/core/static/js/core.js b/openslides/core/static/js/core.js
index 6d132be6e..f32b52483 100644
--- a/openslides/core/static/js/core.js
+++ b/openslides/core/static/js/core.js
@@ -75,10 +75,35 @@ angular.module('OpenSlidesApp.core', [])
.config(function($stateProvider, $locationProvider) {
// Core urls
- $stateProvider.state('dashboard', {
- url: '/',
- templateUrl: 'static/templates/dashboard.html'
- });
+ $stateProvider
+ .state('dashboard', {
+ url: '/',
+ templateUrl: 'static/templates/dashboard.html'
+ })
+ .state('core', {
+ url: '/core',
+ abstract: true,
+ template: "",
+ })
+ // tag
+ .state('core.tag', {
+ url: '/tag',
+ abstract: true,
+ template: "",
+ })
+ .state('core.tag.list', {
+ resolve: {
+ tags: function(Tag) {
+ return Tag.findAll();
+ }
+ }
+ })
+ .state('core.tag.create', {})
+ .state('core.tag.detail.update', {
+ views: {
+ '@core.tag': {}
+ }
+ });
$locationProvider.html5Mode(true);
})
@@ -99,6 +124,13 @@ angular.module('OpenSlidesApp.core', [])
};
})
+// config for ng-fab-form
+.config(function(ngFabFormProvider) {
+ ngFabFormProvider.extendConfig({
+ setAsteriskForRequiredLabel: true
+ });
+})
+
.provider('runtimeStates', function($stateProvider) {
this.$get = function($q, $timeout, $state) {
return {
@@ -150,6 +182,11 @@ angular.module('OpenSlidesApp.core', [])
});
})
+//options for angular-xeditable
+.run(function(editableOptions) {
+ editableOptions.theme = 'bs3';
+})
+
.factory('autoupdate', function() {
//TODO: use config here
var url = "http://" + location.host + "/sockjs";
@@ -179,6 +216,20 @@ angular.module('OpenSlidesApp.core', [])
return Autoupdate;
})
+.factory('Customslide', function(DS) {
+ return DS.defineResource({
+ name: 'core/customslide',
+ endpoint: '/rest/core/customslide/'
+ });
+})
+
+.factory('Tag', function(DS) {
+ return DS.defineResource({
+ name: 'core/tag',
+ endpoint: '/rest/core/tag/'
+ });
+})
+
.factory('Config', function(DS) {
return DS.defineResource({
name: 'config/config',
@@ -201,6 +252,91 @@ angular.module('OpenSlidesApp.core', [])
}
})
+.controller("LoginFormCtrl", function ($scope, $modal) {
+ $scope.open = function () {
+ var modalInstance = $modal.open({
+ animation: true,
+ templateUrl: 'LoginForm.html',
+ controller: 'LoginFormModalCtrl',
+ size: 'sm',
+ });
+ }
+})
+
+.controller('LoginFormModalCtrl', function ($scope, $modalInstance, $http, operator) {
+ $scope.login = function () {
+ $http.post(
+ '/users/login/',
+ {'username': $scope.username, 'password': $scope.password}
+ ).success(function(data) {
+ if (data.success) {
+ operator.setUser(data.user_id);
+ $scope.loginFailed = false;
+ $modalInstance.close();
+ } else {
+ $scope.loginFailed = true;
+ }
+ });
+ };
+ $scope.guest = function () {
+ $modalInstance.dismiss('cancel');
+ };
+ $scope.cancel = function () {
+ $modalInstance.dismiss('cancel');
+ };
+})
+
+.controller('TagListCtrl', function($scope, Tag) {
+ Tag.bindAll({}, $scope, 'tags');
+
+ // setup table sorting
+ $scope.sortColumn = 'name';
+ $scope.filterPresent = '';
+ $scope.reverse = false;
+ // function to sort by clicked column
+ $scope.toggleSort = function ( column ) {
+ if ( $scope.sortColumn === column ) {
+ $scope.reverse = !$scope.reverse;
+ }
+ $scope.sortColumn = column;
+ };
+
+ // save changed tag
+ $scope.save = function (tag) {
+ Tag.save(tag);
+ };
+ $scope.delete = function (tag) {
+ //TODO: add confirm message
+ Tag.destroy(tag.id).then(
+ function(success) {
+ //TODO: success message
+ }
+ );
+ };
+})
+
+.controller('TagCreateCtrl', function($scope, $state, Tag) {
+ $scope.tag = {};
+ $scope.save = function (tag) {
+ Tag.create(tag).then(
+ function(success) {
+ $state.go('core.tag.list');
+ }
+ );
+ };
+})
+
+.controller('TagUpdateCtrl', function($scope, $state, Tag, tag) {
+ $scope.tag = tag;
+ $scope.save = function (tag) {
+ Tag.save(tag).then(
+ function(success) {
+ $state.go('core.tag.list');
+ }
+ );
+ };
+})
+
.directive('osFocusMe', function ($timeout) {
return {
link: function (scope, element, attrs, model) {
@@ -210,8 +346,3 @@ angular.module('OpenSlidesApp.core', [])
}
};
});
-
-// some general JavaScript functions used in all OpenSlides apps
-$(function () {
- $('[data-toggle="tooltip"]').tooltip({'placement': 'bottom'})
-});
diff --git a/openslides/core/static/templates/core/tag-form.html b/openslides/core/static/templates/core/tag-form.html
new file mode 100644
index 000000000..6af1eb4c7
--- /dev/null
+++ b/openslides/core/static/templates/core/tag-form.html
@@ -0,0 +1,23 @@
+Edit tag
+New tag
+
+
+
+
diff --git a/openslides/core/static/templates/core/tag-list.html b/openslides/core/static/templates/core/tag-list.html
new file mode 100644
index 000000000..88043fd3c
--- /dev/null
+++ b/openslides/core/static/templates/core/tag-list.html
@@ -0,0 +1,44 @@
+Tags
+
+
+
+
+
+
+
+
+
+ Name
+
+
+ |
+ Actions
+ |
+
+ {{ tag.name }}
+ |
+
+
+
+
+
+
+
+
+ |
diff --git a/openslides/core/static/templates/dashboard.html b/openslides/core/static/templates/dashboard.html
index af2eeeb59..1fb083194 100644
--- a/openslides/core/static/templates/dashboard.html
+++ b/openslides/core/static/templates/dashboard.html
@@ -1,6 +1,7 @@
Dashboard
diff --git a/openslides/core/static/templates/index.html b/openslides/core/static/templates/index.html
index 093686ba0..c5431459e 100644
--- a/openslides/core/static/templates/index.html
+++ b/openslides/core/static/templates/index.html
@@ -26,75 +26,76 @@
-
-
+
+
+
+
+
-
+