diff --git a/bower.json b/bower.json index 8439bd0c9..20ff31753 100644 --- a/bower.json +++ b/bower.json @@ -18,11 +18,11 @@ "angular-gettext": "~2.0.2", "angular-sanitize": "~1.3.15", "angular-xeditable": "~0.1.9", - "js-data": "~1.8.0", - "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" + "font-awesome-bower": "4.3.0", + "js-data": "~2.3.0", + "js-data-angular": "~3.0.0" } } diff --git a/openslides/agenda/migrations/0003_auto_20150904_1732.py b/openslides/agenda/migrations/0003_auto_20150904_1732.py new file mode 100644 index 000000000..dfd8e0252 --- /dev/null +++ b/openslides/agenda/migrations/0003_auto_20150904_1732.py @@ -0,0 +1,15 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('agenda', '0002_auto_20150630_0144'), + ] + + operations = [ + migrations.AlterField( + model_name='speaker', + name='item', + field=models.ForeignKey(related_name='speakers', to='agenda.Item'), + ), + ] diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index 705e47983..b61324e05 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -235,11 +235,10 @@ class Item(RESTModelMixin, models.Model): dictionary contains a prefix, the speaker and its type. Types are old_speaker, actual_speaker and coming_speaker. """ - speaker_query = Speaker.objects.filter(item=self) # TODO: Why not self.speaker_set? list_of_speakers = [] # Parse old speakers - old_speakers = speaker_query.exclude(begin_time=None).exclude(end_time=None).order_by('end_time') + old_speakers = self.speakers.exclude(begin_time=None).exclude(end_time=None).order_by('end_time') if old_speakers_count is None: old_speakers_count = old_speakers.count() last_old_speakers_count = max(0, old_speakers.count() - old_speakers_count) @@ -260,7 +259,7 @@ class Item(RESTModelMixin, models.Model): # Parse actual speaker try: - actual_speaker = speaker_query.filter(end_time=None).exclude(begin_time=None).get() + actual_speaker = self.speakers.filter(end_time=None).exclude(begin_time=None).get() except Speaker.DoesNotExist: pass else: @@ -272,7 +271,7 @@ class Item(RESTModelMixin, models.Model): 'last_in_group': True}) # Parse coming speakers - coming_speakers = speaker_query.filter(begin_time=None).order_by('weight') + coming_speakers = self.speakers.filter(begin_time=None).order_by('weight') if coming_speakers_count is None: coming_speakers_count = coming_speakers.count() coming_speakers = coming_speakers[:max(0, coming_speakers_count)] @@ -296,7 +295,7 @@ class Item(RESTModelMixin, models.Model): Returns the speaker object of the user who is next. """ try: - return self.speaker_set.filter(begin_time=None).order_by('weight')[0] + return self.speakers.filter(begin_time=None).order_by('weight')[0] except IndexError: # The list of speakers is empty. return None @@ -366,7 +365,7 @@ class Speaker(RESTModelMixin, models.Model): ForeinKey to the user who speaks. """ - item = models.ForeignKey(Item) + item = models.ForeignKey(Item, related_name='speakers') """ ForeinKey to the AgendaItem to which the user want to speak. """ diff --git a/openslides/agenda/serializers.py b/openslides/agenda/serializers.py index c8c423d4e..611a10520 100644 --- a/openslides/agenda/serializers.py +++ b/openslides/agenda/serializers.py @@ -21,7 +21,9 @@ class SpeakerSerializer(ModelSerializer): 'user', 'begin_time', 'end_time', - 'weight') + 'weight', + 'item', # js-data needs the item-id in the nested object to define relations. + ) class RelatedItemRelatedField(RelatedField): @@ -47,7 +49,7 @@ class ItemSerializer(ModelSerializer): get_title_supplement = CharField(read_only=True) content_object = RelatedItemRelatedField(read_only=True) item_no = CharField(read_only=True) - speaker_set = SpeakerSerializer(many=True, read_only=True) + speakers = SpeakerSerializer(many=True, read_only=True) class Meta: model = Item @@ -63,7 +65,7 @@ class ItemSerializer(ModelSerializer): 'closed', 'type', 'duration', - 'speaker_set', + 'speakers', 'speaker_list_closed', 'content_object', 'tags',) diff --git a/openslides/agenda/static/js/agenda/agenda.js b/openslides/agenda/static/js/agenda/agenda.js index 42ec9be35..c9f710a65 100644 --- a/openslides/agenda/static/js/agenda/agenda.js +++ b/openslides/agenda/static/js/agenda/agenda.js @@ -1,21 +1,48 @@ -angular.module('OpenSlidesApp.agenda', []) +"use strict"; -.factory('Agenda', function(DS, jsDataModel) { +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, - endpoint: '/rest/agenda/item/', useClass: jsDataModel, methods: { getResourceName: function () { return name; } + }, + relations: { + hasMany: { + 'core/tag': { + localField: 'tags', + localKeys: 'tags_id', + }, + 'agenda/speaker': { + localField: 'speakers', + foreignKey: 'item_id', + } + } } }); -}) +}]) // Make sure that the Agenda resource is loaded. -.run(function(Agenda) {}); +.run(['Agenda', function(Agenda) {}]); angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) @@ -130,7 +157,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) $scope.isAgendaProjected = function () { // Returns true if there is a projector element with the same // name and agenda is active. - var projector = Projector.get(id=1); + var projector = Projector.get(1); if (typeof projector === 'undefined') return false; var self = this; return _.findIndex(projector.elements, function(element) { diff --git a/openslides/agenda/static/templates/agenda/item-detail.html b/openslides/agenda/static/templates/agenda/item-detail.html index 208cde250..44e9374dc 100644 --- a/openslides/agenda/static/templates/agenda/item-detail.html +++ b/openslides/agenda/static/templates/agenda/item-detail.html @@ -55,8 +55,8 @@ * check permissions -->
    -
  1. - {{ (users | filter: {id: speaker.user})[0].get_full_name() }} +
  2. + {{ speaker.user.get_full_name() }}
- {{alert.msg}} + {{ alert.msg }}
diff --git a/openslides/assignments/static/js/assignments/assignments.js b/openslides/assignments/static/js/assignments/assignments.js index 970b402a4..818320adf 100644 --- a/openslides/assignments/static/js/assignments/assignments.js +++ b/openslides/assignments/static/js/assignments/assignments.js @@ -1,10 +1,11 @@ +"use strict"; + angular.module('OpenSlidesApp.assignments', []) -.factory('Assignment', function(DS, jsDataModel) { +.factory('Assignment', ['DS', 'jsDataModel', function(DS, jsDataModel) { var name = 'assignments/assignment' return DS.defineResource({ name: name, - endpoint: '/rest/assignments/assignment/', useClass: jsDataModel, methods: { getResourceName: function () { @@ -12,9 +13,9 @@ angular.module('OpenSlidesApp.assignments', []) } } }); -}) +}]) -.run(function(Assignment) {}); +.run(['Assignment', function(Assignment) {}]); angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) diff --git a/openslides/core/static/js/core/core.js b/openslides/core/static/js/core/core.js index ef605bd1c..fc735eda6 100644 --- a/openslides/core/static/js/core/core.js +++ b/openslides/core/static/js/core/core.js @@ -1,3 +1,5 @@ +"use strict"; + // The core module used for the OpenSlides site and the projector angular.module('OpenSlidesApp.core', [ 'angular-loading-bar', @@ -8,11 +10,12 @@ angular.module('OpenSlidesApp.core', [ 'ui.tree', ]) -.config(function(DSProvider) { +.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}); @@ -22,44 +25,8 @@ 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. - 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, Projector) { - // 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 '' - } - } - }); - - // Loads all projector data - Projector.findAll(); -}) + DSHttpAdapterProvider.defaults.forceTrailingSlash = true; +}]) .factory('autoupdate', function() { var url = location.origin + "/sockjs"; @@ -89,7 +56,43 @@ angular.module('OpenSlidesApp.core', [ return Autoupdate; }) -.factory('jsDataModel', function($http, Projector) { +.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 + }); +}]) + +.run(['$rootScope', 'Config', 'Projector', function($rootScope, Config, Projector) { + // 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(); +}]) + +.factory('jsDataModel', ['$http', 'Projector', function($http, Projector) { var BaseModel = function() {}; BaseModel.prototype.project = function() { return $http.post( @@ -100,7 +103,7 @@ angular.module('OpenSlidesApp.core', [ BaseModel.prototype.isProjected = function() { // Returns true if there is a projector element with the same // name and the same id. - var projector = Projector.get(id=1); + var projector = Projector.get(1); if (typeof projector === 'undefined') return false; var self = this; return _.findIndex(projector.elements, function(element) { @@ -111,13 +114,12 @@ angular.module('OpenSlidesApp.core', [ }) > -1; } return BaseModel; -}) +}]) -.factory('Customslide', function(DS, jsDataModel) { +.factory('Customslide', ['DS', 'jsDataModel', function(DS, jsDataModel) { var name = 'core/customslide' return DS.defineResource({ name: name, - endpoint: '/rest/core/customslide/', useClass: jsDataModel, methods: { getResourceName: function () { @@ -125,31 +127,29 @@ angular.module('OpenSlidesApp.core', [ }, }, }); -}) +}]) -.factory('Tag', function(DS) { +.factory('Tag', ['DS', function(DS) { return DS.defineResource({ name: 'core/tag', - endpoint: '/rest/core/tag/' }); -}) +}]) -.factory('Config', function(DS) { +.factory('Config', ['DS', function(DS) { return DS.defineResource({ name: 'core/config', idAttribute: 'key', - endpoint: '/rest/core/config/' }); -}) +}]) -.factory('Projector', function(DS) { +.factory('Projector', ['DS', function(DS) { return DS.defineResource({ name: 'core/projector', - endpoint: '/rest/core/projector/', }); -}) +}]) -.run(function(Projector, Config, Tag, Customslide){}); +// Make sure that the DS factories are loaded by making them a dependency +.run(['Projector', 'Config', 'Tag', 'Customslide', function(Projector, Config, Tag, Customslide){}]); // The core module for the OpenSlides site diff --git a/openslides/core/views.py b/openslides/core/views.py index 48e2b0211..da2f36ebb 100644 --- a/openslides/core/views.py +++ b/openslides/core/views.py @@ -94,12 +94,33 @@ class AppsJsView(utils_views.View): static=settings.STATIC_URL, path=path) for path in app_js_files] - + # Use javascript loadScript function from + # http://balpha.de/2011/10/jquery-script-insertion-and-its-consequences-for-debugging/ return HttpResponse( + """ + var loadScript = function (path) { + var result = $.Deferred(), + script = document.createElement("script"); + script.async = "async"; + script.type = "text/javascript"; + script.src = path; + script.onload = script.onreadystatechange = function(_, isAbort) { + if (!script.readyState || /loaded|complete/.test(script.readyState)) { + if (isAbort) + result.reject(); + else + result.resolve(); + } + }; + script.onerror = function () { result.reject(); }; + $("head")[0].appendChild(script); + return result.promise(); + }; + """ + "angular.module('OpenSlidesApp.{app}', {angular_modules});" "var deferres = [];" - "{js_files}.forEach(function(js_file)deferres.push($.getScript(js_file)));" - "$.when.apply(this, deferres).done(function()angular.bootstrap(document,['OpenSlidesApp.{app}']));" + "{js_files}.forEach(function(js_file)deferres.push(loadScript(js_file)));" + "$.when.apply(this,deferres).done(function()angular.bootstrap(document,['OpenSlidesApp.{app}']));" .format( app=kwargs.get('openslides_app'), angular_modules=angular_modules, diff --git a/openslides/mediafiles/static/js/mediafiles/mediafiles.js b/openslides/mediafiles/static/js/mediafiles/mediafiles.js index e95ed0b80..4d2b74377 100644 --- a/openslides/mediafiles/static/js/mediafiles/mediafiles.js +++ b/openslides/mediafiles/static/js/mediafiles/mediafiles.js @@ -1,13 +1,14 @@ +"use strict"; + angular.module('OpenSlidesApp.mediafiles', []) -.factory('Mediafile', function(DS) { +.factory('Mediafile', ['DS', function(DS) { return DS.defineResource({ name: 'mediafiles/mediafile', - endpoint: '/rest/mediafiles/mediafile/' }); -}) +}]) -.run(function(Mediafile) {}); +.run(['Mediafile', function(Mediafile) {}]); angular.module('OpenSlidesApp.mediafiles.site', ['OpenSlidesApp.mediafiles']) diff --git a/openslides/motions/serializers.py b/openslides/motions/serializers.py index dd36f98a8..ca0fdc019 100644 --- a/openslides/motions/serializers.py +++ b/openslides/motions/serializers.py @@ -218,13 +218,13 @@ class MotionSerializer(ModelSerializer): motion.category = validated_data.get('category') motion.reset_state(validated_data.get('workflow', int(config['motions_workflow']))) motion.save() - if validated_data['submitters']: + if validated_data.get('submitters'): motion.submitters.add(*validated_data['submitters']) else: motion.submitters.add(validated_data['request_user']) - motion.supporters.add(*validated_data['supporters']) - motion.attachments.add(*validated_data['attachments']) - motion.tags.add(*validated_data['tags']) + motion.supporters.add(*validated_data.get('supporters', [])) + motion.attachments.add(*validated_data.get('attachments', [])) + motion.tags.add(*validated_data.get('tags', [])) return motion @transaction.atomic diff --git a/openslides/motions/static/js/motions/motions.js b/openslides/motions/static/js/motions/motions.js index e095c235b..7a29df7e8 100644 --- a/openslides/motions/static/js/motions/motions.js +++ b/openslides/motions/static/js/motions/motions.js @@ -1,10 +1,11 @@ +"use strict"; + angular.module('OpenSlidesApp.motions', []) -.factory('Motion', function(DS, jsDataModel) { +.factory('Motion', ['DS', 'jsDataModel', function(DS, jsDataModel) { var name = 'motions/motion' return DS.defineResource({ name: name, - endpoint: '/rest/motions/motion/', useClass: jsDataModel, methods: { getResourceName: function () { @@ -12,6 +13,7 @@ angular.module('OpenSlidesApp.motions', []) }, getVersion: function(versionId) { versionId = versionId || this.active_version; + var index; if (versionId == -1) { index = this.versions.length - 1; } else { @@ -30,23 +32,47 @@ angular.module('OpenSlidesApp.motions', []) getReason: function(versionId) { return this.getVersion(versionId).reason; } + }, + relations: { + belongsTo: { + 'motions/category': { + localField: 'category', + localKey: 'category_id', + }, + }, + hasMany: { + 'core/tag': { + localField: 'tags', + localKeys: 'tags_id', + }, + 'users/user': [ + { + localField: 'submitters', + localKeys: 'submitters_id', + }, + 'supporters': { + localField: 'supporters', + localKeys: 'supporters_id', + } + ], + } } }); -}) -.factory('Category', function(DS) { +}]) + +.factory('Category', ['DS', function(DS) { return DS.defineResource({ name: 'motions/category', - endpoint: '/rest/motions/category/' }); -}) -.factory('Workflow', function(DS) { +}]) + +.factory('Workflow', ['DS', function(DS) { return DS.defineResource({ name: 'motions/workflow', - endpoint: '/rest/motions/workflow/' }); -}) +}]) -.run(function(Motion, Category, Workflow) {}); +.run(['Motion', 'Category', 'Workflow', function(Motion, Category, Workflow) {}]); angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) @@ -107,7 +133,10 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) }, users: function(User) { return User.findAll(); - } + }, + tags: function(Tag) { + return Tag.findAll(); + }, } }) .state('motions.motion.detail.update', { @@ -211,11 +240,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions']) $scope.motion = {}; $scope.save = function (motion) { - motion.tags = []; // TODO: REST API should do it! (Bug in Django REST framework) - motion.attachments = []; // TODO: REST API should do it! (Bug in Django REST framework) - if (!motion.supporters) { - motion.supporters = []; // TODO: REST API should do it! (Bug in Django REST framework) - } Motion.create(motion).then( function(success) { $state.go('motions.motion.list'); diff --git a/openslides/motions/static/templates/motions/motion-detail.html b/openslides/motions/static/templates/motions/motion-detail.html index 9d1a0a95a..7cbd0b459 100644 --- a/openslides/motions/static/templates/motions/motion-detail.html +++ b/openslides/motions/static/templates/motions/motion-detail.html @@ -2,9 +2,10 @@ {{ motion.getTitle() }} Motion {{ motion.identifier }} - | Version {{ (motion.versions | filter: {id: motion.active_version})[0].version_number }} + | Version {{ motion.active_version }} +{{ motion.tags }}