From 32b23df763112ea1cb16e8e47ca471c5f72b2dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Thu, 22 Oct 2015 00:01:51 +0200 Subject: [PATCH] Split and cleaned up JS files. Added test. --- openslides/agenda/apps.py | 2 +- openslides/agenda/static/js/agenda/base.js | 122 ++++++ .../agenda/static/js/agenda/projector.js | 55 +++ .../static/js/agenda/{agenda.js => site.js} | 175 +------- openslides/assignments/apps.py | 2 +- .../assignments/static/js/assignments/base.js | 22 + .../static/js/assignments/projector.js | 22 + .../assignments/{assignments.js => site.js} | 39 +- openslides/core/apps.py | 2 +- openslides/core/static/js/core/base.js | 218 ++++++++++ openslides/core/static/js/core/projector.js | 136 +++++++ .../core/static/js/core/{core.js => site.js} | 376 +----------------- openslides/mediafiles/apps.py | 2 +- .../mediafiles/static/js/mediafiles/base.js | 28 ++ .../static/js/mediafiles/projector.js | 7 + .../js/mediafiles/{mediafiles.js => site.js} | 70 ++-- .../migrations/0003_auto_20151021_2320.py | 38 ++ tests/unit/utils/test_utils.py | 11 + 18 files changed, 713 insertions(+), 614 deletions(-) create mode 100644 openslides/agenda/static/js/agenda/base.js create mode 100644 openslides/agenda/static/js/agenda/projector.js rename openslides/agenda/static/js/agenda/{agenda.js => site.js} (66%) create mode 100644 openslides/assignments/static/js/assignments/base.js create mode 100644 openslides/assignments/static/js/assignments/projector.js rename openslides/assignments/static/js/assignments/{assignments.js => site.js} (73%) create mode 100644 openslides/core/static/js/core/base.js create mode 100644 openslides/core/static/js/core/projector.js rename openslides/core/static/js/core/{core.js => site.js} (67%) create mode 100644 openslides/mediafiles/static/js/mediafiles/base.js create mode 100644 openslides/mediafiles/static/js/mediafiles/projector.js rename openslides/mediafiles/static/js/mediafiles/{mediafiles.js => site.js} (79%) create mode 100644 openslides/users/migrations/0003_auto_20151021_2320.py create mode 100644 tests/unit/utils/test_utils.py diff --git a/openslides/agenda/apps.py b/openslides/agenda/apps.py index 60f9aecab..8c7813be9 100644 --- a/openslides/agenda/apps.py +++ b/openslides/agenda/apps.py @@ -6,7 +6,7 @@ class AgendaAppConfig(AppConfig): verbose_name = 'OpenSlides Agenda' angular_site_module = True angular_projector_module = True - js_files = ['js/agenda/agenda.js'] + js_files = ['js/agenda/base.js', 'js/agenda/site.js', 'js/agenda/projector.js'] def ready(self): # Load projector elements. diff --git a/openslides/agenda/static/js/agenda/base.js b/openslides/agenda/static/js/agenda/base.js new file mode 100644 index 000000000..89c40a96e --- /dev/null +++ b/openslides/agenda/static/js/agenda/base.js @@ -0,0 +1,122 @@ +(function () { + +'use strict'; + +angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users']) + +.factory('Speaker', [ + 'DS', + function(DS) { + return DS.defineResource({ + name: 'agenda/speaker', + relations: { + belongsTo: { + 'users/user': { + localField: 'user', + localKey: 'user_id', + } + } + } + }); + } +]) + +.factory('Agenda', [ + 'DS', + 'Speaker', + 'jsDataModel', + function(DS, Speaker, jsDataModel) { + var name = 'agenda/item'; + return DS.defineResource({ + name: name, + useClass: jsDataModel, + methods: { + getResourceName: function () { + return name; + } + }, + relations: { + hasMany: { + 'core/tag': { + localField: 'tags', + localKeys: 'tags_id', + }, + 'agenda/speaker': { + localField: 'speakers', + foreignKey: 'item_id', + } + } + } + }); + } +]) + +.factory('AgendaTree', [ + function () { + return { + getTree: function (items) { + // Sort items after there weight + items.sort(function(itemA, itemB) { + return itemA.weight - itemB.weight; + }); + + // Build a dict with all children (dict-value) to a specific + // item id (dict-key). + var itemChildren = {}; + + _.each(items, function (item) { + if (item.parent_id) { + // Add item to his parent. If it is the first child, then + // create a new list. + try { + itemChildren[item.parent_id].push(item); + } catch (error) { + itemChildren[item.parent_id] = [item]; + } + } + + }); + + // Recursive function that generates a nested list with all + // items with there children + function getChildren(items) { + var returnItems = []; + _.each(items, function (item) { + returnItems.push({ + item: item, + children: getChildren(itemChildren[item.id]), + id: item.id, + }); + }); + return returnItems; + } + + // Generates the list of root items (with no parents) + var parentItems = items.filter(function (item) { + return !item.parent_id; + }); + return getChildren(parentItems); + }, + // Returns a list of all items as a flat tree the attribute parentCount + getFlatTree: function(items) { + var tree = this.getTree(items); + var flatItems = []; + + function generateFatTree(tree, parentCount) { + _.each(tree, function (item) { + item.item.parentCount = parentCount; + flatItems.push(item.item); + generateFatTree(item.children, parentCount + 1); + }); + } + generateFatTree(tree, 0); + return flatItems; + }, + }; + } +]) + +// Make sure that the Agenda resource is loaded. +.run(['Agenda', function(Agenda) {}]); + +}()); diff --git a/openslides/agenda/static/js/agenda/projector.js b/openslides/agenda/static/js/agenda/projector.js new file mode 100644 index 000000000..47aa97343 --- /dev/null +++ b/openslides/agenda/static/js/agenda/projector.js @@ -0,0 +1,55 @@ +(function () { + +'use strict'; + +angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda']) + +.config([ + 'slidesProvider', + function(slidesProvider) { + slidesProvider.registerSlide('agenda/item', { + template: 'static/templates/agenda/slide-item-detail.html', + }); + slidesProvider.registerSlide('agenda/item-list', { + template: 'static/templates/agenda/slide-item-list.html', + }); + } +]) + +.controller('SlideItemDetailCtrl', [ + '$scope', + 'Agenda', + 'User', + function($scope, Agenda, User) { + // Attention! Each object that is used here has to be dealt on server side. + // Add it to the coresponding get_requirements method of the ProjectorElement + // class. + var id = $scope.element.id; + Agenda.find(id); + User.findAll(); + Agenda.bindOne(id, $scope, 'item'); + // get flag for list-of-speakers-slide (true/false) + $scope.is_list_of_speakers = $scope.element.list_of_speakers; + } +]) + +.controller('SlideItemListCtrl', [ + '$scope', + '$http', + 'Agenda', + 'AgendaTree', + function($scope, $http, Agenda, AgendaTree) { + // Attention! Each object that is used here has to be dealt on server side. + // Add it to the coresponding get_requirements method of the ProjectorElement + // class. + Agenda.findAll(); + // Bind agenda tree to the scope + $scope.$watch(function () { + return Agenda.lastModified(); + }, function () { + $scope.items = AgendaTree.getFlatTree(Agenda.getAll()); + }); + } +]); + +}()); diff --git a/openslides/agenda/static/js/agenda/agenda.js b/openslides/agenda/static/js/agenda/site.js similarity index 66% rename from openslides/agenda/static/js/agenda/agenda.js rename to openslides/agenda/static/js/agenda/site.js index e4cb5ce68..a62cd102f 100644 --- a/openslides/agenda/static/js/agenda/agenda.js +++ b/openslides/agenda/static/js/agenda/site.js @@ -1,122 +1,6 @@ -"use strict"; - -angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users']) - -.factory('Speaker', [ - 'DS', - function(DS) { - return DS.defineResource({ - name: 'agenda/speaker', - relations: { - belongsTo: { - 'users/user': { - localField: 'user', - localKey: 'user_id', - } - } - } - }); - } -]) - -.factory('Agenda', [ - 'DS', - 'Speaker', - 'jsDataModel', - function(DS, Speaker, jsDataModel) { - var name = 'agenda/item' - return DS.defineResource({ - name: name, - useClass: jsDataModel, - methods: { - getResourceName: function () { - return name; - } - }, - relations: { - hasMany: { - 'core/tag': { - localField: 'tags', - localKeys: 'tags_id', - }, - 'agenda/speaker': { - localField: 'speakers', - foreignKey: 'item_id', - } - } - } - }); - } -]) - -.factory('AgendaTree', [ - function () { - return { - getTree: function (items) { - // Sort items after there weight - items.sort(function(itemA, itemB) { - return itemA.weight - itemB.weight; - }); - - // Build a dict with all children (dict-value) to a specific - // item id (dict-key). - var itemChildren = {}; - - _.each(items, function (item) { - if (item.parent_id) { - // Add item to his parent. If it is the first child, then - // create a new list. - try { - itemChildren[item.parent_id].push(item); - } catch (error) { - itemChildren[item.parent_id] = [item]; - } - } - - }); - - // Recursive function that generates a nested list with all - // items with there children - function getChildren(items) { - var returnItems = []; - _.each(items, function (item) { - returnItems.push({ - item: item, - children: getChildren(itemChildren[item.id]), - id: item.id, - }); - }); - return returnItems; - } - - // Generates the list of root items (with no parents) - var parentItems = items.filter(function (item) { - return !item.parent_id; - }); - return getChildren(parentItems); - }, - // Returns a list of all items as a flat tree the attribute parentCount - getFlatTree: function(items) { - var tree = this.getTree(items); - var flatItems = []; - - function generateFatTree(tree, parentCount) { - _.each(tree, function (item) { - item.item.parentCount = parentCount; - flatItems.push(item.item); - generateFatTree(item.children, parentCount + 1); - }); - } - generateFatTree(tree, 0); - return flatItems; - }, - } - } -]) - -// Make sure that the Agenda resource is loaded. -.run(['Agenda', function(Agenda) {}]); +(function () { +'use strict'; angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) @@ -421,7 +305,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) } ); }); - } + }; // import from csv file $scope.csv = { @@ -447,60 +331,11 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) ); } $scope.csvimported = true; - } + }; $scope.clear = function () { $scope.csv.result = null; }; } ]); - -angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda']) - -.config([ - 'slidesProvider', - function(slidesProvider) { - slidesProvider.registerSlide('agenda/item', { - template: 'static/templates/agenda/slide-item-detail.html', - }); - slidesProvider.registerSlide('agenda/item-list', { - template: 'static/templates/agenda/slide-item-list.html', - }); - } -]) - -.controller('SlideItemDetailCtrl', [ - '$scope', - 'Agenda', - 'User', - function($scope, Agenda, User) { - // Attention! Each object that is used here has to be dealt on server side. - // Add it to the coresponding get_requirements method of the ProjectorElement - // class. - var id = $scope.element.id; - Agenda.find(id); - User.findAll(); - Agenda.bindOne(id, $scope, 'item'); - // get flag for list-of-speakers-slide (true/false) - $scope.is_list_of_speakers = $scope.element.list_of_speakers; - } -]) - -.controller('SlideItemListCtrl', [ - '$scope', - '$http', - 'Agenda', - 'AgendaTree', - function($scope, $http, Agenda, AgendaTree) { - // Attention! Each object that is used here has to be dealt on server side. - // Add it to the coresponding get_requirements method of the ProjectorElement - // class. - Agenda.findAll(); - // Bind agenda tree to the scope - $scope.$watch(function () { - return Agenda.lastModified(); - }, function () { - $scope.items = AgendaTree.getFlatTree(Agenda.getAll()); - }); - } -]); +}()); diff --git a/openslides/assignments/apps.py b/openslides/assignments/apps.py index d00028cce..e967a3784 100644 --- a/openslides/assignments/apps.py +++ b/openslides/assignments/apps.py @@ -6,7 +6,7 @@ class AssignmentsAppConfig(AppConfig): verbose_name = 'OpenSlides Assignments' angular_site_module = True angular_projector_module = True - js_files = ['js/assignments/assignments.js'] + js_files = ['js/assignments/base.js', 'js/assignments/site.js', 'js/assignments/projector.js'] def ready(self): # Load projector elements. diff --git a/openslides/assignments/static/js/assignments/base.js b/openslides/assignments/static/js/assignments/base.js new file mode 100644 index 000000000..8aac18039 --- /dev/null +++ b/openslides/assignments/static/js/assignments/base.js @@ -0,0 +1,22 @@ +(function () { + +'use strict'; + +angular.module('OpenSlidesApp.assignments', []) + +.factory('Assignment', ['DS', 'jsDataModel', function(DS, jsDataModel) { + var name = 'assignments/assignment'; + return DS.defineResource({ + name: name, + useClass: jsDataModel, + methods: { + getResourceName: function () { + return name; + } + } + }); +}]) + +.run(['Assignment', function(Assignment) {}]); + +}()); diff --git a/openslides/assignments/static/js/assignments/projector.js b/openslides/assignments/static/js/assignments/projector.js new file mode 100644 index 000000000..cef9a1a0f --- /dev/null +++ b/openslides/assignments/static/js/assignments/projector.js @@ -0,0 +1,22 @@ +(function () { + +'use strict'; + +angular.module('OpenSlidesApp.assignments.projector', ['OpenSlidesApp.assignments']) + +.config(function(slidesProvider) { + slidesProvider.registerSlide('assignments/assignment', { + template: 'static/templates/assignments/slide_assignment.html', + }); +}) + +.controller('SlideAssignmentCtrl', function($scope, Assignment) { + // Attention! Each object that is used here has to be dealt on server side. + // Add it to the coresponding get_requirements method of the ProjectorElement + // class. + var id = $scope.element.id; + Assignment.find(id); + Assignment.bindOne(id, $scope, 'assignment'); +}); + +}()); diff --git a/openslides/assignments/static/js/assignments/assignments.js b/openslides/assignments/static/js/assignments/site.js similarity index 73% rename from openslides/assignments/static/js/assignments/assignments.js rename to openslides/assignments/static/js/assignments/site.js index cd179a984..57361a927 100644 --- a/openslides/assignments/static/js/assignments/assignments.js +++ b/openslides/assignments/static/js/assignments/site.js @@ -1,22 +1,6 @@ -"use strict"; - -angular.module('OpenSlidesApp.assignments', []) - -.factory('Assignment', ['DS', 'jsDataModel', function(DS, jsDataModel) { - var name = 'assignments/assignment' - return DS.defineResource({ - name: name, - useClass: jsDataModel, - methods: { - getResourceName: function () { - return name; - } - } - }); -}]) - -.run(['Assignment', function(Assignment) {}]); +(function () { +'use strict'; angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) @@ -94,7 +78,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) }) .controller('AssignmentDetailCtrl', function($scope, Assignment, assignment) { - Assignment.bindOne(assignment.id, $scope, 'assignment') + Assignment.bindOne(assignment.id, $scope, 'assignment'); }) .controller('AssignmentCreateCtrl', function($scope, $state, Assignment) { @@ -119,19 +103,4 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) }; }); -angular.module('OpenSlidesApp.assignments.projector', ['OpenSlidesApp.assignments']) - -.config(function(slidesProvider) { - slidesProvider.registerSlide('assignments/assignment', { - template: 'static/templates/assignments/slide_assignment.html', - }); -}) - -.controller('SlideAssignmentCtrl', function($scope, Assignment) { - // Attention! Each object that is used here has to be dealt on server side. - // Add it to the coresponding get_requirements method of the ProjectorElement - // class. - var id = $scope.element.id; - Assignment.find(id); - Assignment.bindOne(id, $scope, 'assignment'); -}); +}()); diff --git a/openslides/core/apps.py b/openslides/core/apps.py index 08f80bcc2..6a9af519b 100644 --- a/openslides/core/apps.py +++ b/openslides/core/apps.py @@ -6,7 +6,7 @@ class CoreAppConfig(AppConfig): verbose_name = 'OpenSlides Core' angular_site_module = True angular_projector_module = True - js_files = ['js/core/core.js'] + js_files = ['js/core/base.js', 'js/core/site.js', 'js/core/projector.js'] def ready(self): # Load projector elements. diff --git a/openslides/core/static/js/core/base.js b/openslides/core/static/js/core/base.js new file mode 100644 index 000000000..7a092c0e2 --- /dev/null +++ b/openslides/core/static/js/core/base.js @@ -0,0 +1,218 @@ +(function () { + +'use strict'; + +// The core module used for the OpenSlides site and the projector +angular.module('OpenSlidesApp.core', [ + 'angular-loading-bar', + 'js-data', + 'gettext', + 'ngAnimate', + 'ui.bootstrap', + 'ui.tree', + 'uiSwitch', +]) + +.config(['DSProvider', 'DSHttpAdapterProvider', function(DSProvider, DSHttpAdapterProvider) { + // Reloads everything after 5 minutes. + // TODO: * find a way only to reload things that are still needed + DSProvider.defaults.maxAge = 5 * 60 * 1000; // 5 minutes + DSProvider.defaults.reapAction = 'none'; + DSProvider.defaults.basePath = '/rest'; + DSProvider.defaults.afterReap = function(model, items) { + if (items.length > 5) { + model.findAll({}, {bypassCache: true}); + } else { + _.forEach(items, function (item) { + model.refresh(item[model.idAttribute]); + }); + } + }; + DSHttpAdapterProvider.defaults.forceTrailingSlash = true; +}]) + +.factory('autoupdate', function() { + var url = location.origin + "/sockjs"; + + var Autoupdate = { + socket: null, + message_receivers: [], + connect: function() { + var autoupdate = this; + this.socket = new SockJS(url); + + this.socket.onmessage = function(event) { + _.forEach(autoupdate.message_receivers, function(receiver) { + receiver(event.data); + }); + }; + + this.socket.onclose = function() { + setTimeout(autoupdate.connect, 5000); + }; + }, + on_message: function(receiver) { + this.message_receivers.push(receiver); + } + }; + Autoupdate.connect(); + return Autoupdate; +}) + +.run(['DS', 'autoupdate', function(DS, autoupdate) { + autoupdate.on_message(function(data) { + // TODO: when MODEL.find() is called after this + // a new request is fired. This could be a bug in DS + + // TODO: Do not send the status code to the client, but make the decission + // on the server side. It is an implementation detail, that tornado + // sends request to wsgi, which should not concern the client. + console.log("Received object: " + data.collection + ", " + data.id); + if (data.status_code == 200) { + DS.inject(data.collection, data.data); + } else if (data.status_code == 404) { + DS.eject(data.collection, data.id); + } + // TODO: handle other statuscodes + }); +}]) + +.factory('loadGlobalData', [ + '$rootScope', + '$http', + 'Config', + 'Projector', + function ($rootScope, $http, Config, Projector) { + return function () { + // Puts the config object into each scope. + Config.findAll().then(function() { + $rootScope.config = function(key) { + try { + return Config.get(key).value; + } + catch(err) { + console.log("Unkown config key: " + key); + return ''; + } + }; + }); + + // Loads all projector data + Projector.findAll(); + + // Loads server time and calculates server offset + $http.get('/core/servertime/').then(function(data) { + $rootScope.serverOffset = Math.floor( Date.now() / 1000 - data.data ); + }); + }; + } +]) + +// Load the global data on startup +.run([ + 'loadGlobalData', + function(loadGlobalData, operator) { + loadGlobalData(); + } +]) + +.factory('jsDataModel', ['$http', 'Projector', function($http, Projector) { + var BaseModel = function() {}; + BaseModel.prototype.project = function() { + return $http.post( + '/rest/core/projector/1/prune_elements/', + [{name: this.getResourceName(), id: this.id}] + ); + }; + BaseModel.prototype.isProjected = function() { + // Returns true if there is a projector element with the same + // name and the same id. + var projector = Projector.get(1); + if (typeof projector === 'undefined') return false; + var self = this; + var predicate = function (element) { + return element.name == self.getResourceName() && + typeof element.id !== 'undefined' && + element.id == self.id; + }; + return typeof _.findKey(projector.elements, predicate) === 'string'; + }; + return BaseModel; +}]) + +.factory('Customslide', ['DS', 'jsDataModel', function(DS, jsDataModel) { + var name = 'core/customslide'; + return DS.defineResource({ + name: name, + useClass: jsDataModel, + methods: { + getResourceName: function () { + return name; + }, + }, + }); +}]) + +.factory('Tag', ['DS', function(DS) { + return DS.defineResource({ + name: 'core/tag', + }); +}]) + +.factory('Config', ['DS', function(DS) { + return DS.defineResource({ + name: 'core/config', + idAttribute: 'key', + }); +}]) + +/* Model for a projector. + * + * At the moment we use only one projector, so there will be only one object + * in this model. It has the id 1. For later releases there will be multiple + * projector objects. + * + * This model uses onConfilict: 'replace' instead of 'merge'. This is necessary + * because the keys of the projector objects can change and old keys have to + * be removed. See http://www.js-data.io/docs/dsdefaults#onconflict for + * more information. + */ +.factory('Projector', ['DS', function(DS) { + return DS.defineResource({ + name: 'core/projector', + onConflict: 'replace', + }); +}]) + +/* Converts number of seconds into string "hh:mm:ss" or "mm:ss" */ +.filter('osSecondsToTime', [ + function () { + return function (totalseconds) { + var time; + var total = Math.abs(totalseconds); + if (parseInt(totalseconds)) { + var hh = Math.floor(total / 3600); + var mm = Math.floor(total % 3600 / 60); + var ss = Math.floor(total % 60); + var zero = "0"; + // Add leading "0" for double digit values + hh = (zero+hh).slice(-2); + mm = (zero+mm).slice(-2); + ss = (zero+ss).slice(-2); + if (hh == "00") + time = mm + ':' + ss; + else + time = hh + ":" + mm + ":" + ss; + if (totalseconds < 0) + time = "-"+time; + } else { + time = "--:--"; + } + return time; + }; + } +]) +// Make sure that the DS factories are loaded by making them a dependency +.run(['Projector', 'Config', 'Tag', 'Customslide', function(Projector, Config, Tag, Customslide){}]); + +}()); diff --git a/openslides/core/static/js/core/projector.js b/openslides/core/static/js/core/projector.js new file mode 100644 index 000000000..e6e3acd8c --- /dev/null +++ b/openslides/core/static/js/core/projector.js @@ -0,0 +1,136 @@ +(function () { + +'use strict'; + +// The core module for the OpenSlides projector +angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core']) + +// Provider to register slides in a .config() statement. +.provider('slides', function() { + var slidesMap = {}; + + this.registerSlide = function(name, config) { + slidesMap[name] = config; + return this; + }; + + this.$get = function($templateRequest, $q) { + var self = this; + return { + getElements: function(projector) { + var elements = []; + var factory = this; + _.forEach(projector.elements, function(element) { + if (element.name in slidesMap) { + element.template = slidesMap[element.name].template; + elements.push(element); + } else { + console.log("Unknown slide: " + element.name); + } + }); + return elements; + } + }; + }; +}) + +.config(function(slidesProvider) { + slidesProvider.registerSlide('core/customslide', { + template: 'static/templates/core/slide_customslide.html', + }); + + slidesProvider.registerSlide('core/clock', { + template: 'static/templates/core/slide_clock.html', + }); + + slidesProvider.registerSlide('core/countdown', { + template: 'static/templates/core/slide_countdown.html', + }); + + slidesProvider.registerSlide('core/message', { + template: 'static/templates/core/slide_message.html', + }); +}) + +.controller('ProjectorCtrl', function($scope, Projector, slides) { + Projector.find(1).then(function() { + $scope.$watch(function () { + return Projector.lastModified(1); + }, function () { + $scope.elements = []; + _.forEach(slides.getElements(Projector.get(1)), function(element) { + if (!element.error) { + $scope.elements.push(element); + } else { + console.error("Error for slide " + element.name + ": " + element.error); + } + }); + $scope.scroll = -10 * Projector.get(1).scroll; + $scope.scale = 100 + 20 * Projector.get(1).scale; + }); + }); +}) + +.controller('SlideCustomSlideCtrl', [ + '$scope', + 'Customslide', + function($scope, Customslide) { + // Attention! Each object that is used here has to be dealt on server side. + // Add it to the coresponding get_requirements method of the ProjectorElement + // class. + var id = $scope.element.id; + Customslide.find(id); + Customslide.bindOne(id, $scope, 'customslide'); + } +]) + +.controller('SlideClockCtrl', [ + '$scope', + function($scope) { + // Attention! Each object that is used here has to be dealt on server side. + // Add it to the coresponding get_requirements method of the ProjectorElement + // class. + $scope.servertime = ( Date.now() / 1000 - $scope.serverOffset ) * 1000; + } +]) + +.controller('SlideCountdownCtrl', [ + '$scope', + '$interval', + function($scope, $interval) { + // Attention! Each object that is used here has to be dealt on server side. + // Add it to the coresponding get_requirements method of the ProjectorElement + // class. + $scope.seconds = Math.floor( $scope.element.countdown_time - Date.now() / 1000 + $scope.serverOffset ); + $scope.status = $scope.element.status; + $scope.visible = $scope.element.visible; + $scope.index = $scope.element.index; + $scope.description = $scope.element.description; + // start interval timer if countdown status is running + var interval; + if ($scope.status == "running") { + interval = $interval( function() { + $scope.seconds = Math.floor( $scope.element.countdown_time - Date.now() / 1000 + $scope.serverOffset ); + }, 1000); + } else { + $scope.seconds = $scope.element.countdown_time; + } + $scope.$on('$destroy', function() { + // Cancel the interval if the controller is destroyed + $interval.cancel(interval); + }); + } +]) + +.controller('SlideMessageCtrl', [ + '$scope', + function($scope) { + // Attention! Each object that is used here has to be dealt on server side. + // Add it to the coresponding get_requirements method of the ProjectorElement + // class. + $scope.message = $scope.element.message; + $scope.visible = $scope.element.visible; + } +]); + +}()); diff --git a/openslides/core/static/js/core/core.js b/openslides/core/static/js/core/site.js similarity index 67% rename from openslides/core/static/js/core/core.js rename to openslides/core/static/js/core/site.js index 60f01f7c4..2d9e214f6 100644 --- a/openslides/core/static/js/core/core.js +++ b/openslides/core/static/js/core/site.js @@ -1,218 +1,6 @@ -"use strict"; - -// The core module used for the OpenSlides site and the projector -angular.module('OpenSlidesApp.core', [ - 'angular-loading-bar', - 'js-data', - 'gettext', - 'ngAnimate', - 'ui.bootstrap', - 'ui.tree', - 'uiSwitch', -]) - -.config(['DSProvider', 'DSHttpAdapterProvider', function(DSProvider, DSHttpAdapterProvider) { - // Reloads everything after 5 minutes. - // TODO: * find a way only to reload things that are still needed - DSProvider.defaults.maxAge = 5 * 60 * 1000; // 5 minutes - DSProvider.defaults.reapAction = 'none'; - DSProvider.defaults.basePath = '/rest'; - DSProvider.defaults.afterReap = function(model, items) { - if (items.length > 5) { - model.findAll({}, {bypassCache: true}); - } else { - _.forEach(items, function (item) { - model.refresh(item[model.idAttribute]); - }); - } - }; - DSHttpAdapterProvider.defaults.forceTrailingSlash = true; -}]) - -.factory('autoupdate', function() { - var url = location.origin + "/sockjs"; - - var Autoupdate = { - socket: null, - message_receivers: [], - connect: function() { - var autoupdate = this; - this.socket = new SockJS(url); - - this.socket.onmessage = function(event) { - _.forEach(autoupdate.message_receivers, function(receiver) { - receiver(event.data); - }); - } - - this.socket.onclose = function() { - setTimeout(autoupdate.connect, 5000); - } - }, - on_message: function(receiver) { - this.message_receivers.push(receiver); - } - }; - Autoupdate.connect(); - return Autoupdate; -}) - -.run(['DS', 'autoupdate', function(DS, autoupdate) { - autoupdate.on_message(function(data) { - // TODO: when MODEL.find() is called after this - // a new request is fired. This could be a bug in DS - - // TODO: Do not send the status code to the client, but make the decission - // on the server side. It is an implementation detail, that tornado - // sends request to wsgi, which should not concern the client. - console.log("Received object: " + data.collection + ", " + data.id); - if (data.status_code == 200) { - DS.inject(data.collection, data.data); - } else if (data.status_code == 404) { - DS.eject(data.collection, data.id); - } - // TODO: handle other statuscodes - }); -}]) - -.factory('loadGlobalData', [ - '$rootScope', - '$http', - 'Config', - 'Projector', - function ($rootScope, $http, Config, Projector) { - return function () { - // Puts the config object into each scope. - Config.findAll().then(function() { - $rootScope.config = function(key) { - try { - return Config.get(key).value; - } - catch(err) { - console.log("Unkown config key: " + key); - return '' - } - } - }); - - // Loads all projector data - Projector.findAll(); - - // Loads server time and calculates server offset - $http.get('/core/servertime/').then(function(data) { - $rootScope.serverOffset = Math.floor( Date.now() / 1000 - data.data ); - }); - } - } -]) - -// Load the global data on startup -.run([ - 'loadGlobalData', - function(loadGlobalData, operator) { - loadGlobalData(); - } -]) - -.factory('jsDataModel', ['$http', 'Projector', function($http, Projector) { - var BaseModel = function() {}; - BaseModel.prototype.project = function() { - return $http.post( - '/rest/core/projector/1/prune_elements/', - [{name: this.getResourceName(), id: this.id}] - ); - }; - BaseModel.prototype.isProjected = function() { - // Returns true if there is a projector element with the same - // name and the same id. - var projector = Projector.get(1); - if (typeof projector === 'undefined') return false; - var self = this; - var predicate = function (element) { - return element.name == self.getResourceName() && - typeof element.id !== 'undefined' && - element.id == self.id; - }; - return typeof _.findKey(projector.elements, predicate) === 'string'; - }; - return BaseModel; -}]) - -.factory('Customslide', ['DS', 'jsDataModel', function(DS, jsDataModel) { - var name = 'core/customslide' - return DS.defineResource({ - name: name, - useClass: jsDataModel, - methods: { - getResourceName: function () { - return name; - }, - }, - }); -}]) - -.factory('Tag', ['DS', function(DS) { - return DS.defineResource({ - name: 'core/tag', - }); -}]) - -.factory('Config', ['DS', function(DS) { - return DS.defineResource({ - name: 'core/config', - idAttribute: 'key', - }); -}]) - -/* Model for a projector. - * - * At the moment we use only one projector, so there will be only one object - * in this model. It has the id 1. For later releases there will be multiple - * projector objects. - * - * This model uses onConfilict: 'replace' instead of 'merge'. This is necessary - * because the keys of the projector objects can change and old keys have to - * be removed. See http://www.js-data.io/docs/dsdefaults#onconflict for - * more information. - */ -.factory('Projector', ['DS', function(DS) { - return DS.defineResource({ - name: 'core/projector', - onConflict: 'replace', - }); -}]) - -/* Converts number of seconds into string "hh:mm:ss" or "mm:ss" */ -.filter('osSecondsToTime', [ - function () { - return function (totalseconds) { - var time; - var total = Math.abs(totalseconds); - if (parseInt(totalseconds)) { - var hh = Math.floor(total / 3600); - var mm = Math.floor(total % 3600 / 60); - var ss = Math.floor(total % 60); - var zero = "0"; - // Add leading "0" for double digit values - hh = (zero+hh).slice(-2); - mm = (zero+mm).slice(-2); - ss = (zero+ss).slice(-2); - if (hh == "00") - time = mm + ':' + ss; - else - time = hh + ":" + mm + ":" + ss; - if (totalseconds < 0) - time = "-"+time; - } else { - time = "--:--"; - } - return time; - }; - } -]) -// Make sure that the DS factories are loaded by making them a dependency -.run(['Projector', 'Config', 'Tag', 'Customslide', function(Projector, Config, Tag, Customslide){}]); +(function () { +'use strict'; // The core module for the OpenSlides site angular.module('OpenSlidesApp.core.site', [ @@ -243,7 +31,7 @@ angular.module('OpenSlidesApp.core.site', [ var that = this; this.scope = scope; this.updateMainMenu(); - operator.onOperatorChange(function () {that.updateMainMenu()}); + operator.onOperatorChange(function () {that.updateMainMenu();}); }, updateMainMenu: function () { this.scope.elements = this.getElements(); @@ -258,7 +46,7 @@ angular.module('OpenSlidesApp.core.site', [ }); return elements; } - } + }; }]; } ]) @@ -318,7 +106,7 @@ angular.module('OpenSlidesApp.core.site', [ .config(function($stateProvider, $urlMatcherFactoryProvider) { // Make the trailing slash optional - $urlMatcherFactoryProvider.strictMode(false) + $urlMatcherFactoryProvider.strictMode(false); // Use stateProvider.decorator to give default values to our states $stateProvider.decorator('views', function(state, parent) { @@ -346,7 +134,7 @@ angular.module('OpenSlidesApp.core.site', [ if (_.last(patterns).match(/(create|update)/)) { // When state_patterns is in the form "app.module.create" or // "app.module.update", use the form template. - templateUrl = 'static/templates/' + patterns[0] + '/' + patterns[1] + '-form.html' + templateUrl = 'static/templates/' + patterns[0] + '/' + patterns[1] + '-form.html'; } else { // Replaces the first point through a slash (the app name) var appName = state.name.replace('.', '/'); @@ -385,7 +173,7 @@ angular.module('OpenSlidesApp.core.site', [ } state.url = state.url || defaultUrl; - return parent(state) + return parent(state); }); }) @@ -487,8 +275,8 @@ angular.module('OpenSlidesApp.core.site', [ addState: function(name, state) { $stateProvider.state(name, state); } - } - } + }; + }; }) // Load the django url patterns @@ -540,7 +328,7 @@ angular.module('OpenSlidesApp.core.site', [ $scope.value = config.value; $scope.help_text = field.help_text; } - } + }; }) .controller("MainMenuCtrl", [ @@ -562,7 +350,7 @@ angular.module('OpenSlidesApp.core.site', [ if (lang != 'en') { gettextCatalog.loadRemote("static/i18n/" + lang + ".json"); } - } + }; }) .controller("LoginFormCtrl", function ($scope, $modal) { @@ -573,7 +361,7 @@ angular.module('OpenSlidesApp.core.site', [ controller: 'LoginFormModalCtrl', size: 'sm', }); - } + }; }) .controller('LoginFormModalCtrl', [ @@ -626,7 +414,7 @@ angular.module('OpenSlidesApp.core.site', [ $scope.save = function(key, value) { Config.get(key).value = value; Config.save(key); - } + }; }) // Customslide Controller @@ -698,7 +486,7 @@ angular.module('OpenSlidesApp.core.site', [ // *** countdown functions *** $scope.calculateCountdownTime = function (countdown) { countdown.seconds = Math.floor( countdown.countdown_time - Date.now() / 1000 + $scope.serverOffset ); - } + }; $scope.rebuildAllElements = function () { $scope.countdowns = []; $scope.messages = []; @@ -721,7 +509,7 @@ angular.module('OpenSlidesApp.core.site', [ }); $scope.scrollLevel = Projector.get(1).scroll; $scope.scaleLevel = Projector.get(1).scale; - } + }; // get initial values for $scope.countdowns, $scope.messages, $scope.scrollLevel // and $scope.scaleLevel (after page reload) @@ -762,7 +550,7 @@ angular.module('OpenSlidesApp.core.site', [ }; $scope.editCountdown = function (countdown) { var data = {}; - data[countdown.uuid] = { + data[countdown.uuid] = { "description": countdown.description, "default": parseInt(countdown.default) }; @@ -943,134 +731,4 @@ angular.module('OpenSlidesApp.core.site', [ }; }); - -// The core module for the OpenSlides projector -angular.module('OpenSlidesApp.core.projector', ['OpenSlidesApp.core']) - -// Provider to register slides in a .config() statement. -.provider('slides', function() { - var slidesMap = {}; - - this.registerSlide = function(name, config) { - slidesMap[name] = config; - return this; - }; - - this.$get = function($templateRequest, $q) { - var self = this; - return { - getElements: function(projector) { - var elements = []; - var factory = this; - _.forEach(projector.elements, function(element) { - if (element.name in slidesMap) { - element.template = slidesMap[element.name].template; - elements.push(element); - } else { - console.log("Unknown slide: " + element.name); - } - }); - return elements; - } - } - }; -}) - -.config(function(slidesProvider) { - slidesProvider.registerSlide('core/customslide', { - template: 'static/templates/core/slide_customslide.html', - }); - - slidesProvider.registerSlide('core/clock', { - template: 'static/templates/core/slide_clock.html', - }); - - slidesProvider.registerSlide('core/countdown', { - template: 'static/templates/core/slide_countdown.html', - }); - - slidesProvider.registerSlide('core/message', { - template: 'static/templates/core/slide_message.html', - }); -}) - -.controller('ProjectorCtrl', function($scope, Projector, slides) { - Projector.find(1).then(function() { - $scope.$watch(function () { - return Projector.lastModified(1); - }, function () { - $scope.elements = []; - _.forEach(slides.getElements(Projector.get(1)), function(element) { - if (!element.error) { - $scope.elements.push(element); - } else { - console.error("Error for slide " + element.name + ": " + element.error) - } - }); - $scope.scroll = -10 * Projector.get(1).scroll; - $scope.scale = 100 + 20 * Projector.get(1).scale; - }); - }); -}) - -.controller('SlideCustomSlideCtrl', [ - '$scope', - 'Customslide', - function($scope, Customslide) { - // Attention! Each object that is used here has to be dealt on server side. - // Add it to the coresponding get_requirements method of the ProjectorElement - // class. - var id = $scope.element.id; - Customslide.find(id); - Customslide.bindOne(id, $scope, 'customslide'); - } -]) - -.controller('SlideClockCtrl', [ - '$scope', - function($scope) { - // Attention! Each object that is used here has to be dealt on server side. - // Add it to the coresponding get_requirements method of the ProjectorElement - // class. - $scope.servertime = ( Date.now() / 1000 - $scope.serverOffset ) * 1000; - } -]) - -.controller('SlideCountdownCtrl', [ - '$scope', - '$interval', - function($scope, $interval) { - // Attention! Each object that is used here has to be dealt on server side. - // Add it to the coresponding get_requirements method of the ProjectorElement - // class. - $scope.seconds = Math.floor( $scope.element.countdown_time - Date.now() / 1000 + $scope.serverOffset ); - $scope.status = $scope.element.status; - $scope.visible = $scope.element.visible; - $scope.index = $scope.element.index; - $scope.description = $scope.element.description; - // start interval timer if countdown status is running - var interval; - if ($scope.status == "running") { - interval = $interval( function() { - $scope.seconds = Math.floor( $scope.element.countdown_time - Date.now() / 1000 + $scope.serverOffset ); - }, 1000); - } else { - $scope.seconds = $scope.element.countdown_time; - } - $scope.$on('$destroy', function() { - // Cancel the interval if the controller is destroyed - $interval.cancel(interval); - }); - } -]) - -.controller('SlideMessageCtrl', [ - '$scope', - function($scope) { - // Attention! Each object that is used here has to be dealt on server side. - // Add it to the coresponding get_requirements method of the ProjectorElement - // class. - $scope.message = $scope.element.message; - $scope.visible = $scope.element.visible; - } -]); +}()); diff --git a/openslides/mediafiles/apps.py b/openslides/mediafiles/apps.py index 4d6d46042..78c4d673a 100644 --- a/openslides/mediafiles/apps.py +++ b/openslides/mediafiles/apps.py @@ -6,7 +6,7 @@ class MediafilesAppConfig(AppConfig): verbose_name = 'OpenSlides Mediafiles' angular_site_module = True angular_projector_module = True - js_files = ['js/mediafiles/mediafiles.js'] + js_files = ['js/mediafiles/base.js', 'js/mediafiles/site.js', 'js/mediafiles/projector.js'] def ready(self): # Load projector elements. diff --git a/openslides/mediafiles/static/js/mediafiles/base.js b/openslides/mediafiles/static/js/mediafiles/base.js new file mode 100644 index 000000000..e876de418 --- /dev/null +++ b/openslides/mediafiles/static/js/mediafiles/base.js @@ -0,0 +1,28 @@ +(function () { + +'use strict'; + +angular.module('OpenSlidesApp.mediafiles', []) + +.factory('Mediafile', ['DS', function(DS) { + return DS.defineResource({ + name: 'mediafiles/mediafile', + computed: { + is_presentable: ['filetype', function (filetype) { + var PRESENTABLE_FILE_TYPES = ['application/pdf']; + return _.contains(PRESENTABLE_FILE_TYPES, filetype); + }], + filename: [function () { + var filename = this.mediafile.name; + return /\/(.+?)$/.exec(filename)[1]; + }], + title_or_filename: ['title', 'mediafile', function (title) { + return title || this.filename; + }] + } + }); +}]) + +.run(['Mediafile', function(Mediafile) {}]); + +}()); diff --git a/openslides/mediafiles/static/js/mediafiles/projector.js b/openslides/mediafiles/static/js/mediafiles/projector.js new file mode 100644 index 000000000..846aa34ef --- /dev/null +++ b/openslides/mediafiles/static/js/mediafiles/projector.js @@ -0,0 +1,7 @@ +(function () { + +'use strict'; + +angular.module('OpenSlidesApp.mediafiles.projector', ['OpenSlidesApp.mediafiles']); + +}()); diff --git a/openslides/mediafiles/static/js/mediafiles/mediafiles.js b/openslides/mediafiles/static/js/mediafiles/site.js similarity index 79% rename from openslides/mediafiles/static/js/mediafiles/mediafiles.js rename to openslides/mediafiles/static/js/mediafiles/site.js index 8288841d8..97ba38319 100644 --- a/openslides/mediafiles/static/js/mediafiles/mediafiles.js +++ b/openslides/mediafiles/static/js/mediafiles/site.js @@ -1,49 +1,6 @@ -"use strict"; +(function () { -angular.module('OpenSlidesApp.mediafiles', []) - -.factory('Mediafile', ['DS', function(DS) { - return DS.defineResource({ - name: 'mediafiles/mediafile', - computed: { - is_presentable: ['filetype', function (filetype) { - var PRESENTABLE_FILE_TYPES = ['application/pdf'] - return _.contains(PRESENTABLE_FILE_TYPES, filetype); - }], - filename: [function () { - var filename = this.mediafile.name; - return /\/(.+?)$/.exec(filename)[1]; - }], - title_or_filename: ['title', 'mediafile', function (title) { - return title || this.filename; - }] - } - }); -}]) - -.run(['Mediafile', function(Mediafile) {}]); - -function uploadFile($timeout, $scope, $state, Upload, mediafile) { - return function(file) { - file.upload = Upload.upload({ - url: '/rest/mediafiles/mediafile/' + (mediafile ? mediafile.id : ''), - method: mediafile ? 'PUT' : 'POST', - fields: {title: file.title}, - file: file.mediafile, - fileFormDataName: 'mediafile' - }); - - file.upload.then(function (response) { - $timeout(function () { - file.result = response.data; - $state.go('mediafiles.mediafile.list'); - }); - }, function (response) { - if (response.status > 0) - $scope.errorMsg = response.status + ': ' + response.data; - }); - } -} +'use strict'; angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.mediafiles']) @@ -134,5 +91,26 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp. $scope.save = uploadFile($timeout, $scope, $state, Upload, mediafile); }); +function uploadFile($timeout, $scope, $state, Upload, mediafile) { + return function(file) { + file.upload = Upload.upload({ + url: '/rest/mediafiles/mediafile/' + (mediafile ? mediafile.id : ''), + method: mediafile ? 'PUT' : 'POST', + fields: {title: file.title}, + file: file.mediafile, + fileFormDataName: 'mediafile' + }); -angular.module('OpenSlidesApp.mediafiles.projector', ['OpenSlidesApp.mediafiles']); + file.upload.then(function (response) { + $timeout(function () { + file.result = response.data; + $state.go('mediafiles.mediafile.list'); + }); + }, function (response) { + if (response.status > 0) + $scope.errorMsg = response.status + ': ' + response.data; + }); + }; +} + +}()); diff --git a/openslides/users/migrations/0003_auto_20151021_2320.py b/openslides/users/migrations/0003_auto_20151021_2320.py new file mode 100644 index 000000000..59ec2aeed --- /dev/null +++ b/openslides/users/migrations/0003_auto_20151021_2320.py @@ -0,0 +1,38 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_auto_20150630_0143'), + ] + + operations = [ + migrations.AlterModelOptions( + name='user', + options={ + 'ordering': ('last_name', 'first_name', 'username'), + 'permissions': ( + ('can_see_name', 'Can see names of users'), + ('can_see_extra_data', 'Can see extra data of users'), + ('can_manage', 'Can manage users'))}, + ), + migrations.AlterField( + model_name='user', + name='about_me', + field=models.TextField(blank=True, help_text='Profile text.', default='', verbose_name='About me'), + ), + migrations.AlterField( + model_name='user', + name='is_active', + field=models.BooleanField( + help_text='Designates whether this user should be treated as active. Unselect this instead of deleting the account.', + default=True, + verbose_name='Active'), + ), + migrations.AlterField( + model_name='user', + name='is_present', + field=models.BooleanField(help_text='Designates whether this user is in the room or not.', default=False, verbose_name='Present'), + ), + ] diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py new file mode 100644 index 000000000..f5349a3e1 --- /dev/null +++ b/tests/unit/utils/test_utils.py @@ -0,0 +1,11 @@ +from unittest import TestCase + +from openslides.utils import utils + + +class ToRomanTest(TestCase): + def test_to_roman_result(self): + self.assertEqual(utils.to_roman(3), 'III') + + def test_to_roman_none(self): + self.assertTrue(utils.to_roman(-3) is None)