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
-->
- -
- {{ (users | filter: {id: speaker.user})[0].get_full_name() }}
+
-
+ {{ 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 }}