From a4c00d5ee3f5f15e55fd12b2eca00df4f2d09909 Mon Sep 17 00:00:00 2001 From: Oskar Hahn Date: Wed, 17 Jun 2015 09:45:00 +0200 Subject: [PATCH] Angular Client * Split angular app into a site- and a projector app * Created client slide api and slides for customslide and user * JS-function to activate a slide --- openslides/agenda/static/js/agenda/agenda.js | 20 +- .../static/js/assignments/assignments.js | 19 +- openslides/core/models.py | 2 +- openslides/core/projector.py | 17 +- openslides/core/serializers.py | 2 +- openslides/core/static/js/app.js | 34 +- openslides/core/static/js/core.js | 304 ++++++++++++------ .../static/templates/core/slide_clock.html | 3 + .../templates/core/slide_customslide.html | 4 + openslides/core/static/templates/index.html | 4 +- .../core/static/templates/projector.html | 25 ++ openslides/core/urls.py | 7 + openslides/core/views.py | 11 + .../static/js/mediafiles/mediafiles.js | 20 +- .../motions/static/js/motions/motions.js | 43 +-- openslides/urls.py | 6 +- openslides/users/projector.py | 34 +- openslides/users/static/js/users/users.js | 121 ++++--- .../static/templates/users/slide_user.html | 3 + .../static/templates/users/user-list.html | 3 +- openslides/utils/projector.py | 19 +- tests/integration/core/test_views.py | 11 +- 22 files changed, 455 insertions(+), 257 deletions(-) create mode 100644 openslides/core/static/templates/core/slide_clock.html create mode 100644 openslides/core/static/templates/core/slide_customslide.html create mode 100644 openslides/core/static/templates/projector.html create mode 100644 openslides/users/static/templates/users/slide_user.html diff --git a/openslides/agenda/static/js/agenda/agenda.js b/openslides/agenda/static/js/agenda/agenda.js index 8037d3676..9955ee880 100644 --- a/openslides/agenda/static/js/agenda/agenda.js +++ b/openslides/agenda/static/js/agenda/agenda.js @@ -1,5 +1,18 @@ angular.module('OpenSlidesApp.agenda', []) +.factory('Agenda', function(DS) { + return DS.defineResource({ + name: 'agenda/item', + endpoint: '/rest/agenda/item/' + }); +}) + +// Make sure that the Agenda resource is loaded. +.run(function(Agenda) {}); + + +angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) + .config(function($stateProvider) { $stateProvider .state('agenda', { @@ -68,13 +81,6 @@ angular.module('OpenSlidesApp.agenda', []) }); }) -.factory('Agenda', function(DS) { - return DS.defineResource({ - name: 'agenda/item', - endpoint: '/rest/agenda/item/' - }); -}) - .controller('ItemListCtrl', function($scope, Agenda, tree) { Agenda.bindAll({}, $scope, 'items'); diff --git a/openslides/assignments/static/js/assignments/assignments.js b/openslides/assignments/static/js/assignments/assignments.js index b65ec19b1..d24389b1e 100644 --- a/openslides/assignments/static/js/assignments/assignments.js +++ b/openslides/assignments/static/js/assignments/assignments.js @@ -1,5 +1,17 @@ angular.module('OpenSlidesApp.assignments', []) +.factory('Assignment', function(DS) { + return DS.defineResource({ + name: 'assignments/assignment', + endpoint: '/rest/assignments/assignment/' + }); +}) + +.run(function(Assignment) {}); + + +angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) + .config(function($stateProvider) { $stateProvider .state('assignments', { @@ -37,13 +49,6 @@ angular.module('OpenSlidesApp.assignments', []) }); }) -.factory('Assignment', function(DS) { - return DS.defineResource({ - name: 'assignments/assignment', - endpoint: '/rest/assignments/assignment/' - }); -}) - .controller('AssignmentListCtrl', function($scope, Assignment, phases) { Assignment.bindAll({}, $scope, 'assignments'); // get all item types via OPTIONS request diff --git a/openslides/core/models.py b/openslides/core/models.py index e7a9c36a3..ac203d4fa 100644 --- a/openslides/core/models.py +++ b/openslides/core/models.py @@ -41,7 +41,7 @@ class Projector(RESTModelMixin, models.Model): ('can_use_chat', ugettext_noop('Can use the chat'))) @property - def projector_elements(self): + def elements(self): """ A generator to retrieve all projector elements given in the config field. For every element the method get_data() is called and its diff --git a/openslides/core/projector.py b/openslides/core/projector.py index 0ba63fa37..0083d19ce 100644 --- a/openslides/core/projector.py +++ b/openslides/core/projector.py @@ -13,24 +13,16 @@ class CustomSlideSlide(ProjectorElement): Slide definitions for custom slide model. """ name = 'core/customslide' - scripts = 'core/customslide_slide.js' def get_context(self): pk = self.config_entry.get('id') if not CustomSlide.objects.filter(pk=pk).exists(): raise ProjectorException(_('Custom slide does not exist.')) - return [{ - 'collection': 'core/customslide', - 'id': pk}] + return {'id': pk} def get_requirements(self, config_entry): - self.config_entry = config_entry - try: - pk = self.get_context()[0]['id'] - except ProjectorException: - # Custom slide does not exist so just do nothing. - pass - else: + pk = config_entry.get('id') + if pk is not None: yield ProjectorRequirement( view_class=CustomSlideViewSet, view_action='retrieve', @@ -42,7 +34,6 @@ class Clock(ProjectorElement): Clock on the projector. """ name = 'core/clock' - scripts = 'core/clock.js' def get_context(self): return {'server_time': now().timestamp()} @@ -73,7 +64,6 @@ class Countdown(ProjectorElement): To hide a running countdown add {"hidden": true}. """ name = 'core/countdown' - scripts = 'core/countdown.js' def get_context(self): if self.config_entry.get('countdown_time') is None: @@ -88,7 +78,6 @@ class Message(ProjectorElement): Short message on the projector. Rendered as overlay. """ name = 'core/message' - scripts = 'core/message.js' def get_context(self): if self.config_entry.get('message') is None: diff --git a/openslides/core/serializers.py b/openslides/core/serializers.py index bd3eef7fe..995f4898a 100644 --- a/openslides/core/serializers.py +++ b/openslides/core/serializers.py @@ -33,7 +33,7 @@ class ProjectorSerializer(ModelSerializer): class Meta: model = Projector - fields = ('config', 'projector_elements', ) + fields = ('id', 'config', 'elements', ) class CustomSlideSerializer(ModelSerializer): diff --git a/openslides/core/static/js/app.js b/openslides/core/static/js/app.js index f84121985..238773954 100644 --- a/openslides/core/static/js/app.js +++ b/openslides/core/static/js/app.js @@ -1,24 +1,38 @@ 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', +]); + +angular.module('OpenSlidesApp.projector', [ + 'OpenSlidesApp', + 'OpenSlidesApp.core.projector', 'OpenSlidesApp.agenda', 'OpenSlidesApp.motions', 'OpenSlidesApp.assignments', - 'OpenSlidesApp.users', + 'OpenSlidesApp.users.projector', 'OpenSlidesApp.mediafiles', +]); + +angular.module('OpenSlidesApp.site', [ + 'OpenSlidesApp', + 'ui.router', + 'ngBootbox', + 'ngFabForm', + 'ngMessages', + 'ngCsvImport', + 'ngSanitize', // TODO: remove this as global dependency + 'ui.select', + 'xeditable', + 'OpenSlidesApp.core.site', + 'OpenSlidesApp.agenda.site', + 'OpenSlidesApp.motions.site', + 'OpenSlidesApp.assignments.site', + 'OpenSlidesApp.users.site', + 'OpenSlidesApp.mediafiles.site', ]) .config(function($urlRouterProvider, $locationProvider) { diff --git a/openslides/core/static/js/core.js b/openslides/core/static/js/core.js index f32b52483..d2826de26 100644 --- a/openslides/core/static/js/core.js +++ b/openslides/core/static/js/core.js @@ -1,5 +1,119 @@ +// The core module used for the OpenSlides site and the projector angular.module('OpenSlidesApp.core', []) +.config(function(DSProvider) { + // 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.afterReap = function(model, items) { + if (items.length > 5) { + model.findAll({}, {bypassCache: true}); + } else { + _.forEach(items, function (item) { + model.refresh(item[model.idAttribute]); + }); + } + }; +}) + +.run(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 + }); +}) + +.run(function($rootScope, Config) { + // Puts the config object into each scope. + // TODO: maybe rootscope.config has to set before findAll() is finished + Config.findAll().then(function() { + $rootScope.config = function(key) { + try { + return Config.get(key).value; + } + catch(err) { + console.log("Unkown config key: " + key); + return '' + } + } + }); +}) + +.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; +}) + +.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', + idAttribute: 'key', + endpoint: '/rest/config/config/' + }); +}) + +.factory('Projector', function(DS) { + return DS.defineResource({ + name: 'core/projector', + endpoint: '/rest/core/projector/', + }); +}) + +.run(function(Projector, Config, Tag, Customslide){}); + + +// The core module for the OpenSlides site +angular.module('OpenSlidesApp.core.site', ['OpenSlidesApp.core']) + .config(function($stateProvider, $urlMatcherFactoryProvider) { // Make the trailing slash optional $urlMatcherFactoryProvider.strictMode(false) @@ -80,6 +194,13 @@ angular.module('OpenSlidesApp.core', []) url: '/', templateUrl: 'static/templates/dashboard.html' }) + .state('projector', { + url: '/projector', + data: {extern: true}, + onEnter: function($window) { + $window.location.href = this.url; + } + }) .state('core', { url: '/core', abstract: true, @@ -108,22 +229,6 @@ angular.module('OpenSlidesApp.core', []) $locationProvider.html5Mode(true); }) -.config(function(DSProvider) { - // 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.afterReap = function(model, items) { - if (items.length > 5) { - model.findAll({}, {bypassCache: true}); - } else { - _.forEach(items, function (item) { - model.refresh(item[model.idAttribute]); - }); - } - }; -}) - // config for ng-fab-form .config(function(ngFabFormProvider) { ngFabFormProvider.extendConfig({ @@ -131,6 +236,8 @@ angular.module('OpenSlidesApp.core', []) }); }) +// Helper to add ui.router states at runtime. +// Needed for the django url_patterns. .provider('runtimeStates', function($stateProvider) { this.$get = function($q, $timeout, $state) { return { @@ -141,6 +248,7 @@ angular.module('OpenSlidesApp.core', []) } }) +// Load the django url patterns .run(function(runtimeStates, $http) { $http.get('/core/url_patterns/').then(function(data) { for (var pattern in data.data) { @@ -155,87 +263,20 @@ angular.module('OpenSlidesApp.core', []) }); }) -.run(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. - 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 - }); -}) - -.run(function($rootScope, Config) { - // Puts the config object into each scope. - // TODO: maybe rootscope.config has to set before findAll() is finished - Config.findAll().then(function() {; - $rootScope.config = function(key) { - return Config.get(key).value; - } - }); -}) - -//options for angular-xeditable +// options for angular-xeditable .run(function(editableOptions) { editableOptions.theme = 'bs3'; }) -.factory('autoupdate', function() { - //TODO: use config here - var url = "http://" + location.host + "/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); - } +// Activate an Element from the Rest-API on the projector +// At the moment it only activates item on projector 1 +.factory('projectorActivate', function($http) { + return function(model, id) { + return $http.post( + '/rest/core/projector/1/prune_elements/', + [{name: model.name, id: id}] + ); }; - Autoupdate.connect(); - 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', - idAttribute: 'key', - endpoint: '/rest/config/config/' - }); }) .controller("LanguageCtrl", function ($scope, gettextCatalog) { @@ -346,3 +387,82 @@ angular.module('OpenSlidesApp.core', []) } }; }); + + +// 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', + }); +}) + +.filter('osServertime',function() { + return function(serverOffset) { + var date = new Date(); + return date.setTime(date.getTime() - serverOffset); + }; +}) + +.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) { + $scope.elements.push(element); + }); + }); + }); +}) + +.controller('SlideCustomSlideCtr', 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.context.id; + Customslide.find(id); + Customslide.bindOne(id, $scope, 'customslide'); +}) + +.controller('SlideClockCtr', 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.serverOffset = Date.parse(new Date().toUTCString()) - $scope.element.context.server_time; +}); diff --git a/openslides/core/static/templates/core/slide_clock.html b/openslides/core/static/templates/core/slide_clock.html new file mode 100644 index 000000000..d74638bac --- /dev/null +++ b/openslides/core/static/templates/core/slide_clock.html @@ -0,0 +1,3 @@ +
+ {{ serverOffset | osServertime | date:'HH:mm' }} Uhr +
diff --git a/openslides/core/static/templates/core/slide_customslide.html b/openslides/core/static/templates/core/slide_customslide.html new file mode 100644 index 000000000..58bd7e3ba --- /dev/null +++ b/openslides/core/static/templates/core/slide_customslide.html @@ -0,0 +1,4 @@ +
+

{{ customslide.title }}

+ {{ customslide.text }} +
diff --git a/openslides/core/static/templates/index.html b/openslides/core/static/templates/index.html index b153f71bf..eb218e00c 100644 --- a/openslides/core/static/templates/index.html +++ b/openslides/core/static/templates/index.html @@ -2,7 +2,7 @@ - + @@ -133,7 +133,7 @@ Home
  • - + Projector diff --git a/openslides/core/static/templates/projector.html b/openslides/core/static/templates/projector.html new file mode 100644 index 000000000..d8a44e3ea --- /dev/null +++ b/openslides/core/static/templates/projector.html @@ -0,0 +1,25 @@ + + + + +OpenSlides – Projector + + + + + + +
    +
    +
    + + + + + + + + diff --git a/openslides/core/urls.py b/openslides/core/urls.py index 9bcf92ec4..d9bd83a07 100644 --- a/openslides/core/urls.py +++ b/openslides/core/urls.py @@ -10,4 +10,11 @@ urlpatterns = patterns( url(r'^core/version/$', views.VersionView.as_view(), name='core_version'), + + # View for the projectors are handelt by angular. + url(r'^projector.*$', views.ProjectorView.as_view()), + + # Main entry point for all angular pages. + # Has to be the last entry in the urls.py + url(r'^.*$', views.IndexView.as_view()), ) diff --git a/openslides/core/views.py b/openslides/core/views.py index 3ac43a34b..c2839b651 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -43,6 +43,17 @@ class IndexView(utils_views.CSRFMixin, utils_views.View): return HttpResponse(content) +class ProjectorView(utils_views.View): + """ + Access the projector. + """ + + def get(self, *args, **kwargs): + with open(finders.find('templates/projector.html')) as f: + content = f.read() + return HttpResponse(content) + + class ProjectorViewSet(ReadOnlyModelViewSet): """ API endpoint to list, retrieve and update the projector slide info. diff --git a/openslides/mediafiles/static/js/mediafiles/mediafiles.js b/openslides/mediafiles/static/js/mediafiles/mediafiles.js index 71497e30c..9c0635d4e 100644 --- a/openslides/mediafiles/static/js/mediafiles/mediafiles.js +++ b/openslides/mediafiles/static/js/mediafiles/mediafiles.js @@ -1,5 +1,17 @@ angular.module('OpenSlidesApp.mediafiles', []) +.factory('Mediafile', function(DS) { + return DS.defineResource({ + name: 'mediafiles/mediafile', + endpoint: '/rest/mediafiles/mediafile/' + }); +}) + +.run(function(Mediafile) {}); + + +angular.module('OpenSlidesApp.mediafiles.site', ['OpenSlidesApp.mediafiles']) + .config(function($stateProvider) { $stateProvider .state('mediafiles', { @@ -26,13 +38,6 @@ angular.module('OpenSlidesApp.mediafiles', []) }); }) -.factory('Mediafile', function(DS) { - return DS.defineResource({ - name: 'mediafiles/mediafile', - endpoint: '/rest/mediafiles/mediafile/' - }); -}) - .controller('MediafileListCtrl', function($scope, $http, Mediafile) { Mediafile.bindAll({}, $scope, 'mediafiles'); @@ -80,4 +85,3 @@ angular.module('OpenSlidesApp.mediafiles', []) ); }; }); - diff --git a/openslides/motions/static/js/motions/motions.js b/openslides/motions/static/js/motions/motions.js index 09254891c..6666f2d76 100644 --- a/openslides/motions/static/js/motions/motions.js +++ b/openslides/motions/static/js/motions/motions.js @@ -1,5 +1,29 @@ angular.module('OpenSlidesApp.motions', []) +.factory('Motion', function(DS) { + return DS.defineResource({ + name: 'motions/motion', + endpoint: '/rest/motions/motion/' + }); +}) +.factory('Category', function(DS) { + return DS.defineResource({ + name: 'motions/category', + endpoint: '/rest/motions/category/' + }); +}) +.factory('Workflow', function(DS) { + return DS.defineResource({ + name: 'motions/workflow', + endpoint: '/rest/motions/workflow/' + }); +}) + +.run(function(Motion, Category, Workflow) {}); + + +angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) + .config(function($stateProvider) { $stateProvider .state('motions', { @@ -104,25 +128,6 @@ angular.module('OpenSlidesApp.motions', []) }) }) -.factory('Motion', function(DS) { - return DS.defineResource({ - name: 'motions/motion', - endpoint: '/rest/motions/motion/' - }); -}) -.factory('Category', function(DS) { - return DS.defineResource({ - name: 'motions/category', - endpoint: '/rest/motions/category/' - }); -}) -.factory('Workflow', function(DS) { - return DS.defineResource({ - name: 'motions/workflow', - endpoint: '/rest/motions/workflow/' - }); -}) - .controller('MotionListCtrl', function($scope, Motion) { Motion.bindAll({}, $scope, 'motions'); diff --git a/openslides/urls.py b/openslides/urls.py index 13b630306..b5b23ce22 100644 --- a/openslides/urls.py +++ b/openslides/urls.py @@ -1,17 +1,17 @@ from django.conf.urls import include, patterns, url from django.views.generic import RedirectView -from openslides.core.views import IndexView from openslides.utils.rest_api import router urlpatterns = patterns( '', url(r'^(?P.*[^/])$', RedirectView.as_view(url='/%(url)s/')), url(r'^rest/', include(router.urls)), - url(r'^', include('openslides.core.urls')), url(r'^agenda/', include('openslides.agenda.urls')), url(r'^assignments/', include('openslides.assignments.urls')), url(r'^motions/', include('openslides.motions.urls')), url(r'^users/', include('openslides.users.urls')), - url(r'^.*$', IndexView.as_view()), + + # The urls.py for the core app has to be the last entry in the urls.py + url(r'^', include('openslides.core.urls')), ) diff --git a/openslides/users/projector.py b/openslides/users/projector.py index 8457ded81..16f3718d2 100644 --- a/openslides/users/projector.py +++ b/openslides/users/projector.py @@ -12,33 +12,23 @@ class UserSlide(ProjectorElement): Slide definitions for user model. """ name = 'users/user' - scripts = 'users/user_slide.js' def get_context(self): pk = self.config_entry.get('id') - try: - user = User.objects.get(pk=pk) - except User.DoesNotExist: + if not User.objects.filter(pk=pk).exists(): raise ProjectorException(_('User does not exist.')) - result = [{ - 'collection': 'users/user', - 'id': pk}] - for group in user.groups.all(): - result.append({ - 'collection': 'users/group', - 'id': group.pk}) - return result + return {'id': pk} def get_requirements(self, config_entry): - self.config_entry = config_entry - try: - context = self.get_context() - except ProjectorException: - # User does not exist so just do nothing. - pass - else: - for item in context: + pk = config_entry.get('id') + if pk is not None: + yield ProjectorRequirement( + view_class=UserViewSet, + view_action='retrive', + pk=pk) + + for group in User.objects.get(pk=pk).groups.all(): yield ProjectorRequirement( - view_class=UserViewSet if item['collection'] == 'users/user' else GroupViewSet, + view_class=GroupViewSet, view_action='retrieve', - pk=str(item['id'])) + pk=str(group.pk)) diff --git a/openslides/users/static/js/users/users.js b/openslides/users/static/js/users/users.js index c9f536e6c..aa1c83efb 100644 --- a/openslides/users/static/js/users/users.js +++ b/openslides/users/static/js/users/users.js @@ -1,5 +1,56 @@ angular.module('OpenSlidesApp.users', []) +.factory('User', function(DS, Group) { + return DS.defineResource({ + name: 'users/user', + endpoint: '/rest/users/user/', + methods: { + get_short_name: function() { + // should be the same as in the python user model. + var firstName = _.trim(this.first_name), + lastName = _.trim(this.last_name), + name; + + if (firstName && lastName) { + // TODO: check config + name = [firstName, lastName].join(' '); + } else { + name = firstName || lastName || this.username; + } + return name; + }, + getPerms: function() { + var allPerms = []; + _.forEach(this.groups, function(groupId) { + // Get group from server + Group.find(groupId); + // But do not work with the returned promise, because in + // this case this method can not be called in $watch + group = Group.get(groupId); + if (group) { + _.forEach(group.permissions, function(perm) { + allPerms.push(perm); + }); + } + }); + return _.uniq(allPerms); + }, + }, + }); +}) + +.factory('Group', function(DS) { + return DS.defineResource({ + name: 'users/group', + endpoint: '/rest/users/group/' + }); +}) + +.run(function(User, Group) {}); + + +angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) + .config(function($stateProvider) { $stateProvider .state('users', { @@ -126,52 +177,6 @@ angular.module('OpenSlidesApp.users', []) $rootScope.operator = operator; }) -.factory('User', function(DS, Group) { - return DS.defineResource({ - name: 'users/user', - endpoint: '/rest/users/user/', - methods: { - get_short_name: function() { - // should be the same as in the python user model. - var firstName = _.trim(this.first_name), - lastName = _.trim(this.last_name), - name; - - if (firstName && lastName) { - // TODO: check config - name = [firstName, lastName].join(' '); - } else { - name = firstName || lastName || this.username; - } - return name; - }, - getPerms: function() { - var allPerms = []; - _.forEach(this.groups, function(groupId) { - // Get group from server - Group.find(groupId); - // But do not work with the returned promise, because in - // this case this method can not be called in $watch - group = Group.get(groupId); - if (group) { - _.forEach(group.permissions, function(perm) { - allPerms.push(perm); - }); - } - }); - return _.uniq(allPerms); - }, - }, - }); -}) - -.factory('Group', function(DS) { - return DS.defineResource({ - name: 'users/group', - endpoint: '/rest/users/group/' - }); -}) - /* * Directive to check for permissions * @@ -239,7 +244,7 @@ angular.module('OpenSlidesApp.users', []) }; }]) -.controller('UserListCtrl', function($scope, User) { +.controller('UserListCtrl', function($scope, User, projectorActivate) { User.bindAll({}, $scope, 'users'); // setup table sorting @@ -264,6 +269,10 @@ angular.module('OpenSlidesApp.users', []) $scope.delete = function (user) { User.destroy(user.id); }; + + $scope.project = function(user) { + projectorActivate(User, user.id).error(function() {console.log('success')}); + }; }) .controller('UserDetailCtrl', function($scope, User, user) { @@ -369,9 +378,25 @@ angular.module('OpenSlidesApp.users', []) // DS.flush(); }); }; +}); + + +angular.module('OpenSlidesApp.users.projector', ['OpenSlidesApp.users']) + +.config(function(slidesProvider) { + slidesProvider.registerSlide('users/user', { + template: 'static/templates/users/slide_user.html', + }); }) - +.controller('SlideUserCtr', function($scope, 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.context.id; + User.find(id); + User.bindOne(id, $scope, 'user'); +}); // this is code from angular.js. Find a way to call this function from this file function getBlockNodes(nodes) { diff --git a/openslides/users/static/templates/users/slide_user.html b/openslides/users/static/templates/users/slide_user.html new file mode 100644 index 000000000..0e2237734 --- /dev/null +++ b/openslides/users/static/templates/users/slide_user.html @@ -0,0 +1,3 @@ +
    +

    {{ user.username }}

    +
    diff --git a/openslides/users/static/templates/users/user-list.html b/openslides/users/static/templates/users/user-list.html index b5e9fd77a..08059d803 100644 --- a/openslides/users/static/templates/users/user-list.html +++ b/openslides/users/static/templates/users/user-list.html @@ -106,7 +106,8 @@ {{ user.last_login | date:'yyyy-MM-dd HH:mm:ss'}} - diff --git a/openslides/utils/projector.py b/openslides/utils/projector.py index 4dc805cc4..265f5b4a9 100644 --- a/openslides/utils/projector.py +++ b/openslides/utils/projector.py @@ -8,13 +8,12 @@ class ProjectorElement(object, metaclass=SignalConnectMetaClass): Base class for an element on the projector. Every app which wants to add projector elements has to create classes - subclassing from this base class with different names. The name and - scripts attributes have to be set. The metaclass - (SignalConnectMetaClass) does the rest of the magic. + subclassing from this base class with different names. The name attribute + has to be set. The metaclass (SignalConnectMetaClass) does the rest of the + magic. """ signal = Signal() name = None - scripts = None def __init__(self, **kwargs): """ @@ -45,20 +44,8 @@ class ProjectorElement(object, metaclass=SignalConnectMetaClass): assert self.config_entry.get('name') == self.name, ( 'To get data of a projector element, the correct config entry has to be given.') return { - 'scripts': self.get_scripts(), 'context': self.get_context()} - def get_scripts(self): - """ - Returns ...? - """ - # TODO: Write docstring - if self.scripts is None: - raise NotImplementedError( - 'A projector element class must define either a ' - 'get_scripts method or have a scripts argument.') - return self.scripts - def get_context(self): """ Returns the context of the projector element. diff --git a/tests/integration/core/test_views.py b/tests/integration/core/test_views.py index d7a9efeb1..061963d9e 100644 --- a/tests/integration/core/test_views.py +++ b/tests/integration/core/test_views.py @@ -23,13 +23,11 @@ class ProjectorAPI(TestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(json.loads(response.content.decode()), { + 'id': 1, 'config': [{'name': 'core/customslide', 'id': customslide.id}], - 'projector_elements': [ + 'elements': [ {'name': 'core/customslide', - 'scripts': 'core/customslide_slide.js', - 'context': [ - {'collection': 'core/customslide', - 'id': customslide.id}]}]}) + 'context': {'id': customslide.id}}]}) def test_invalid_slide_on_default_projector(self): self.client.login(username='admin', password='admin') @@ -41,8 +39,9 @@ class ProjectorAPI(TestCase): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(json.loads(response.content.decode()), { + 'id': 1, 'config': [{'name': 'invalid_slide'}], - 'projector_elements': [ + 'elements': [ {'name': 'invalid_slide', 'error': 'Projector element does not exist.'}]})