Updated js-data to 2.0
Added js-data relation for the motion and agenda app Added improved load function
This commit is contained in:
parent
ec50b6e67f
commit
6674ea85b7
@ -18,11 +18,11 @@
|
|||||||
"angular-gettext": "~2.0.2",
|
"angular-gettext": "~2.0.2",
|
||||||
"angular-sanitize": "~1.3.15",
|
"angular-sanitize": "~1.3.15",
|
||||||
"angular-xeditable": "~0.1.9",
|
"angular-xeditable": "~0.1.9",
|
||||||
"js-data": "~1.8.0",
|
|
||||||
"js-data-angular": "~2.1.0",
|
|
||||||
"ng-fab-form": "~1.2.7",
|
"ng-fab-form": "~1.2.7",
|
||||||
"ngBootbox": "~0.0.5",
|
"ngBootbox": "~0.0.5",
|
||||||
"sockjs": "~0.3.4",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
openslides/agenda/migrations/0003_auto_20150904_1732.py
Normal file
15
openslides/agenda/migrations/0003_auto_20150904_1732.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
@ -235,11 +235,10 @@ class Item(RESTModelMixin, models.Model):
|
|||||||
dictionary contains a prefix, the speaker and its type. Types
|
dictionary contains a prefix, the speaker and its type. Types
|
||||||
are old_speaker, actual_speaker and coming_speaker.
|
are old_speaker, actual_speaker and coming_speaker.
|
||||||
"""
|
"""
|
||||||
speaker_query = Speaker.objects.filter(item=self) # TODO: Why not self.speaker_set?
|
|
||||||
list_of_speakers = []
|
list_of_speakers = []
|
||||||
|
|
||||||
# Parse old 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:
|
if old_speakers_count is None:
|
||||||
old_speakers_count = old_speakers.count()
|
old_speakers_count = old_speakers.count()
|
||||||
last_old_speakers_count = max(0, 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
|
# Parse actual speaker
|
||||||
try:
|
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:
|
except Speaker.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@ -272,7 +271,7 @@ class Item(RESTModelMixin, models.Model):
|
|||||||
'last_in_group': True})
|
'last_in_group': True})
|
||||||
|
|
||||||
# Parse coming speakers
|
# 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:
|
if coming_speakers_count is None:
|
||||||
coming_speakers_count = coming_speakers.count()
|
coming_speakers_count = coming_speakers.count()
|
||||||
coming_speakers = coming_speakers[:max(0, 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.
|
Returns the speaker object of the user who is next.
|
||||||
"""
|
"""
|
||||||
try:
|
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:
|
except IndexError:
|
||||||
# The list of speakers is empty.
|
# The list of speakers is empty.
|
||||||
return None
|
return None
|
||||||
@ -366,7 +365,7 @@ class Speaker(RESTModelMixin, models.Model):
|
|||||||
ForeinKey to the user who speaks.
|
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.
|
ForeinKey to the AgendaItem to which the user want to speak.
|
||||||
"""
|
"""
|
||||||
|
@ -21,7 +21,9 @@ class SpeakerSerializer(ModelSerializer):
|
|||||||
'user',
|
'user',
|
||||||
'begin_time',
|
'begin_time',
|
||||||
'end_time',
|
'end_time',
|
||||||
'weight')
|
'weight',
|
||||||
|
'item', # js-data needs the item-id in the nested object to define relations.
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RelatedItemRelatedField(RelatedField):
|
class RelatedItemRelatedField(RelatedField):
|
||||||
@ -47,7 +49,7 @@ class ItemSerializer(ModelSerializer):
|
|||||||
get_title_supplement = CharField(read_only=True)
|
get_title_supplement = CharField(read_only=True)
|
||||||
content_object = RelatedItemRelatedField(read_only=True)
|
content_object = RelatedItemRelatedField(read_only=True)
|
||||||
item_no = CharField(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:
|
class Meta:
|
||||||
model = Item
|
model = Item
|
||||||
@ -63,7 +65,7 @@ class ItemSerializer(ModelSerializer):
|
|||||||
'closed',
|
'closed',
|
||||||
'type',
|
'type',
|
||||||
'duration',
|
'duration',
|
||||||
'speaker_set',
|
'speakers',
|
||||||
'speaker_list_closed',
|
'speaker_list_closed',
|
||||||
'content_object',
|
'content_object',
|
||||||
'tags',)
|
'tags',)
|
||||||
|
@ -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'
|
var name = 'agenda/item'
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: name,
|
name: name,
|
||||||
endpoint: '/rest/agenda/item/',
|
|
||||||
useClass: jsDataModel,
|
useClass: jsDataModel,
|
||||||
methods: {
|
methods: {
|
||||||
getResourceName: function () {
|
getResourceName: function () {
|
||||||
return name;
|
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.
|
// Make sure that the Agenda resource is loaded.
|
||||||
.run(function(Agenda) {});
|
.run(['Agenda', function(Agenda) {}]);
|
||||||
|
|
||||||
|
|
||||||
angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
||||||
@ -130,7 +157,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
|
|||||||
$scope.isAgendaProjected = function () {
|
$scope.isAgendaProjected = function () {
|
||||||
// Returns true if there is a projector element with the same
|
// Returns true if there is a projector element with the same
|
||||||
// name and agenda is active.
|
// name and agenda is active.
|
||||||
var projector = Projector.get(id=1);
|
var projector = Projector.get(1);
|
||||||
if (typeof projector === 'undefined') return false;
|
if (typeof projector === 'undefined') return false;
|
||||||
var self = this;
|
var self = this;
|
||||||
return _.findIndex(projector.elements, function(element) {
|
return _.findIndex(projector.elements, function(element) {
|
||||||
|
@ -55,8 +55,8 @@
|
|||||||
* check permissions
|
* check permissions
|
||||||
-->
|
-->
|
||||||
<ol>
|
<ol>
|
||||||
<li ng-repeat="speaker in item.speaker_set">
|
<li ng-repeat="speaker in item.speakers">
|
||||||
{{ (users | filter: {id: speaker.user})[0].get_full_name() }}
|
{{ speaker.user.get_full_name() }}
|
||||||
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
|
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
|
||||||
class="btn btn-default btn-xs">
|
class="btn btn-default btn-xs">
|
||||||
<i class="fa fa-times"></i>
|
<i class="fa fa-times"></i>
|
||||||
@ -65,7 +65,7 @@
|
|||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<div os-perms="agenda.can_manage" class="form-group col-sm-6">
|
<div os-perms="agenda.can_manage" class="form-group col-sm-6">
|
||||||
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">{{alert.msg}}</alert>
|
<alert ng-show="alert.show" type="{{ alert.type }}" ng-click="alert={}" close="alert={}">{{ alert.msg }}</alert>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<ui-select ng-model="speaker.selected" ng-change="addSpeaker(speaker.selected.id)">
|
<ui-select ng-model="speaker.selected" ng-change="addSpeaker(speaker.selected.id)">
|
||||||
<ui-select-match placeholder="{{ 'Select or search a participant...' | translate }}">
|
<ui-select-match placeholder="{{ 'Select or search a participant...' | translate }}">
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
angular.module('OpenSlidesApp.assignments', [])
|
angular.module('OpenSlidesApp.assignments', [])
|
||||||
|
|
||||||
.factory('Assignment', function(DS, jsDataModel) {
|
.factory('Assignment', ['DS', 'jsDataModel', function(DS, jsDataModel) {
|
||||||
var name = 'assignments/assignment'
|
var name = 'assignments/assignment'
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: name,
|
name: name,
|
||||||
endpoint: '/rest/assignments/assignment/',
|
|
||||||
useClass: jsDataModel,
|
useClass: jsDataModel,
|
||||||
methods: {
|
methods: {
|
||||||
getResourceName: function () {
|
getResourceName: function () {
|
||||||
@ -12,9 +13,9 @@ angular.module('OpenSlidesApp.assignments', [])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
}])
|
||||||
|
|
||||||
.run(function(Assignment) {});
|
.run(['Assignment', function(Assignment) {}]);
|
||||||
|
|
||||||
|
|
||||||
angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
// The core module used for the OpenSlides site and the projector
|
// The core module used for the OpenSlides site and the projector
|
||||||
angular.module('OpenSlidesApp.core', [
|
angular.module('OpenSlidesApp.core', [
|
||||||
'angular-loading-bar',
|
'angular-loading-bar',
|
||||||
@ -8,11 +10,12 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
'ui.tree',
|
'ui.tree',
|
||||||
])
|
])
|
||||||
|
|
||||||
.config(function(DSProvider) {
|
.config(['DSProvider', 'DSHttpAdapterProvider', function(DSProvider, DSHttpAdapterProvider) {
|
||||||
// Reloads everything after 5 minutes.
|
// Reloads everything after 5 minutes.
|
||||||
// TODO: * find a way only to reload things that are still needed
|
// TODO: * find a way only to reload things that are still needed
|
||||||
DSProvider.defaults.maxAge = 5 * 60 * 1000; // 5 minutes
|
DSProvider.defaults.maxAge = 5 * 60 * 1000; // 5 minutes
|
||||||
DSProvider.defaults.reapAction = 'none';
|
DSProvider.defaults.reapAction = 'none';
|
||||||
|
DSProvider.defaults.basePath = '/rest';
|
||||||
DSProvider.defaults.afterReap = function(model, items) {
|
DSProvider.defaults.afterReap = function(model, items) {
|
||||||
if (items.length > 5) {
|
if (items.length > 5) {
|
||||||
model.findAll({}, {bypassCache: true});
|
model.findAll({}, {bypassCache: true});
|
||||||
@ -22,44 +25,8 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
DSHttpAdapterProvider.defaults.forceTrailingSlash = true;
|
||||||
|
}])
|
||||||
.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();
|
|
||||||
})
|
|
||||||
|
|
||||||
.factory('autoupdate', function() {
|
.factory('autoupdate', function() {
|
||||||
var url = location.origin + "/sockjs";
|
var url = location.origin + "/sockjs";
|
||||||
@ -89,7 +56,43 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
return Autoupdate;
|
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() {};
|
var BaseModel = function() {};
|
||||||
BaseModel.prototype.project = function() {
|
BaseModel.prototype.project = function() {
|
||||||
return $http.post(
|
return $http.post(
|
||||||
@ -100,7 +103,7 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
BaseModel.prototype.isProjected = function() {
|
BaseModel.prototype.isProjected = function() {
|
||||||
// Returns true if there is a projector element with the same
|
// Returns true if there is a projector element with the same
|
||||||
// name and the same id.
|
// name and the same id.
|
||||||
var projector = Projector.get(id=1);
|
var projector = Projector.get(1);
|
||||||
if (typeof projector === 'undefined') return false;
|
if (typeof projector === 'undefined') return false;
|
||||||
var self = this;
|
var self = this;
|
||||||
return _.findIndex(projector.elements, function(element) {
|
return _.findIndex(projector.elements, function(element) {
|
||||||
@ -111,13 +114,12 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
}) > -1;
|
}) > -1;
|
||||||
}
|
}
|
||||||
return BaseModel;
|
return BaseModel;
|
||||||
})
|
}])
|
||||||
|
|
||||||
.factory('Customslide', function(DS, jsDataModel) {
|
.factory('Customslide', ['DS', 'jsDataModel', function(DS, jsDataModel) {
|
||||||
var name = 'core/customslide'
|
var name = 'core/customslide'
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: name,
|
name: name,
|
||||||
endpoint: '/rest/core/customslide/',
|
|
||||||
useClass: jsDataModel,
|
useClass: jsDataModel,
|
||||||
methods: {
|
methods: {
|
||||||
getResourceName: function () {
|
getResourceName: function () {
|
||||||
@ -125,31 +127,29 @@ angular.module('OpenSlidesApp.core', [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
}])
|
||||||
|
|
||||||
.factory('Tag', function(DS) {
|
.factory('Tag', ['DS', function(DS) {
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: 'core/tag',
|
name: 'core/tag',
|
||||||
endpoint: '/rest/core/tag/'
|
|
||||||
});
|
});
|
||||||
})
|
}])
|
||||||
|
|
||||||
.factory('Config', function(DS) {
|
.factory('Config', ['DS', function(DS) {
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: 'core/config',
|
name: 'core/config',
|
||||||
idAttribute: 'key',
|
idAttribute: 'key',
|
||||||
endpoint: '/rest/core/config/'
|
|
||||||
});
|
});
|
||||||
})
|
}])
|
||||||
|
|
||||||
.factory('Projector', function(DS) {
|
.factory('Projector', ['DS', function(DS) {
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: 'core/projector',
|
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
|
// The core module for the OpenSlides site
|
||||||
|
@ -94,12 +94,33 @@ class AppsJsView(utils_views.View):
|
|||||||
static=settings.STATIC_URL,
|
static=settings.STATIC_URL,
|
||||||
path=path)
|
path=path)
|
||||||
for path in app_js_files]
|
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(
|
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});"
|
"angular.module('OpenSlidesApp.{app}', {angular_modules});"
|
||||||
"var deferres = [];"
|
"var deferres = [];"
|
||||||
"{js_files}.forEach(function(js_file)deferres.push($.getScript(js_file)));"
|
"{js_files}.forEach(function(js_file)deferres.push(loadScript(js_file)));"
|
||||||
"$.when.apply(this, deferres).done(function()angular.bootstrap(document,['OpenSlidesApp.{app}']));"
|
"$.when.apply(this,deferres).done(function()angular.bootstrap(document,['OpenSlidesApp.{app}']));"
|
||||||
.format(
|
.format(
|
||||||
app=kwargs.get('openslides_app'),
|
app=kwargs.get('openslides_app'),
|
||||||
angular_modules=angular_modules,
|
angular_modules=angular_modules,
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
angular.module('OpenSlidesApp.mediafiles', [])
|
angular.module('OpenSlidesApp.mediafiles', [])
|
||||||
|
|
||||||
.factory('Mediafile', function(DS) {
|
.factory('Mediafile', ['DS', function(DS) {
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: 'mediafiles/mediafile',
|
name: 'mediafiles/mediafile',
|
||||||
endpoint: '/rest/mediafiles/mediafile/'
|
|
||||||
});
|
});
|
||||||
})
|
}])
|
||||||
|
|
||||||
.run(function(Mediafile) {});
|
.run(['Mediafile', function(Mediafile) {}]);
|
||||||
|
|
||||||
|
|
||||||
angular.module('OpenSlidesApp.mediafiles.site', ['OpenSlidesApp.mediafiles'])
|
angular.module('OpenSlidesApp.mediafiles.site', ['OpenSlidesApp.mediafiles'])
|
||||||
|
@ -218,13 +218,13 @@ class MotionSerializer(ModelSerializer):
|
|||||||
motion.category = validated_data.get('category')
|
motion.category = validated_data.get('category')
|
||||||
motion.reset_state(validated_data.get('workflow', int(config['motions_workflow'])))
|
motion.reset_state(validated_data.get('workflow', int(config['motions_workflow'])))
|
||||||
motion.save()
|
motion.save()
|
||||||
if validated_data['submitters']:
|
if validated_data.get('submitters'):
|
||||||
motion.submitters.add(*validated_data['submitters'])
|
motion.submitters.add(*validated_data['submitters'])
|
||||||
else:
|
else:
|
||||||
motion.submitters.add(validated_data['request_user'])
|
motion.submitters.add(validated_data['request_user'])
|
||||||
motion.supporters.add(*validated_data['supporters'])
|
motion.supporters.add(*validated_data.get('supporters', []))
|
||||||
motion.attachments.add(*validated_data['attachments'])
|
motion.attachments.add(*validated_data.get('attachments', []))
|
||||||
motion.tags.add(*validated_data['tags'])
|
motion.tags.add(*validated_data.get('tags', []))
|
||||||
return motion
|
return motion
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
angular.module('OpenSlidesApp.motions', [])
|
angular.module('OpenSlidesApp.motions', [])
|
||||||
|
|
||||||
.factory('Motion', function(DS, jsDataModel) {
|
.factory('Motion', ['DS', 'jsDataModel', function(DS, jsDataModel) {
|
||||||
var name = 'motions/motion'
|
var name = 'motions/motion'
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: name,
|
name: name,
|
||||||
endpoint: '/rest/motions/motion/',
|
|
||||||
useClass: jsDataModel,
|
useClass: jsDataModel,
|
||||||
methods: {
|
methods: {
|
||||||
getResourceName: function () {
|
getResourceName: function () {
|
||||||
@ -12,6 +13,7 @@ angular.module('OpenSlidesApp.motions', [])
|
|||||||
},
|
},
|
||||||
getVersion: function(versionId) {
|
getVersion: function(versionId) {
|
||||||
versionId = versionId || this.active_version;
|
versionId = versionId || this.active_version;
|
||||||
|
var index;
|
||||||
if (versionId == -1) {
|
if (versionId == -1) {
|
||||||
index = this.versions.length - 1;
|
index = this.versions.length - 1;
|
||||||
} else {
|
} else {
|
||||||
@ -30,23 +32,47 @@ angular.module('OpenSlidesApp.motions', [])
|
|||||||
getReason: function(versionId) {
|
getReason: function(versionId) {
|
||||||
return this.getVersion(versionId).reason;
|
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({
|
return DS.defineResource({
|
||||||
name: 'motions/category',
|
name: 'motions/category',
|
||||||
endpoint: '/rest/motions/category/'
|
|
||||||
});
|
});
|
||||||
})
|
}])
|
||||||
.factory('Workflow', function(DS) {
|
|
||||||
|
.factory('Workflow', ['DS', function(DS) {
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: 'motions/workflow',
|
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'])
|
angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
||||||
@ -107,7 +133,10 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
|||||||
},
|
},
|
||||||
users: function(User) {
|
users: function(User) {
|
||||||
return User.findAll();
|
return User.findAll();
|
||||||
}
|
},
|
||||||
|
tags: function(Tag) {
|
||||||
|
return Tag.findAll();
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.state('motions.motion.detail.update', {
|
.state('motions.motion.detail.update', {
|
||||||
@ -211,11 +240,6 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
|
|||||||
|
|
||||||
$scope.motion = {};
|
$scope.motion = {};
|
||||||
$scope.save = function (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(
|
Motion.create(motion).then(
|
||||||
function(success) {
|
function(success) {
|
||||||
$state.go('motions.motion.list');
|
$state.go('motions.motion.list');
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
{{ motion.getTitle() }}
|
{{ motion.getTitle() }}
|
||||||
<small>
|
<small>
|
||||||
<translate>Motion</translate> {{ motion.identifier }}
|
<translate>Motion</translate> {{ motion.identifier }}
|
||||||
<span ng-if="motion.versions.length > 1" >| Version {{ (motion.versions | filter: {id: motion.active_version})[0].version_number }}</span>
|
<span ng-if="motion.versions.length > 1" >| Version {{ motion.active_version }}</span>
|
||||||
</small>
|
</small>
|
||||||
</h1>
|
</h1>
|
||||||
|
{{ motion.tags }}
|
||||||
|
|
||||||
<div id="submenu">
|
<div id="submenu">
|
||||||
<a ui-sref="motions.motion.list" class="btn btn-sm btn-default">
|
<a ui-sref="motions.motion.list" class="btn btn-sm btn-default">
|
||||||
@ -16,7 +17,7 @@
|
|||||||
<translate>PDF</translate>
|
<translate>PDF</translate>
|
||||||
</a>
|
</a>
|
||||||
<!-- project -->
|
<!-- project -->
|
||||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||||
ng-class="{ 'btn-primary': motion.isProjected() }"
|
ng-class="{ 'btn-primary': motion.isProjected() }"
|
||||||
ng-click="motion.project()"
|
ng-click="motion.project()"
|
||||||
title="{{ 'Project motion' | translate }}">
|
title="{{ 'Project motion' | translate }}">
|
||||||
@ -43,11 +44,11 @@
|
|||||||
<div class="well">
|
<div class="well">
|
||||||
<h3 translate>Submitters</h3>
|
<h3 translate>Submitters</h3>
|
||||||
<div ng-repeat="submitter in motion.submitters">
|
<div ng-repeat="submitter in motion.submitters">
|
||||||
{{ (users | filter: {id: submitter})[0].get_full_name() }}<br>
|
{{ submitter.get_full_name() }}<br>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 translate>Category</h3>
|
<h3 translate>Category</h3>
|
||||||
{{ (categories | filter: {id: motion.category})[0].name }}</a>
|
{{ motion.category.name }}</a>
|
||||||
|
|
||||||
<h3 translate>Voting result</h3>
|
<h3 translate>Voting result</h3>
|
||||||
-
|
-
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="selectSubmitter" translate>Submitter</label>
|
<label for="selectSubmitter" translate>Submitter</label>
|
||||||
<select multiple size="3" ng-options="user.id as user.get_short_name() for user in users"
|
<select multiple size="3" ng-options="user.id as user.get_short_name() for user in users"
|
||||||
ng-model="motion.submitters" class="form-control" name="selectSubmitter" required>
|
ng-model="motion.submitters_id" class="form-control" name="selectSubmitter" required>
|
||||||
</select>
|
</select>
|
||||||
<!-- TODO: use modern ui-select component with more information per user
|
<!-- TODO: use modern ui-select component with more information per user
|
||||||
<ui-select multipe ng-model="motion.submitter" theme="bootstrap" name="selectSubmitter">
|
<ui-select multipe ng-model="motion.submitter" theme="bootstrap" name="selectSubmitter">
|
||||||
@ -50,25 +50,25 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="selectCategory" translate>Category</label>
|
<label for="selectCategory" translate>Category</label>
|
||||||
<select ng-options="category.id as category.name for category in categories"
|
<select ng-options="category.id as category.name for category in categories"
|
||||||
ng-model="motion.category" class="form-control" name="selectCategory">
|
ng-model="motion.category_id" class="form-control" name="selectCategory">
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="selectTags" translate>Tags</label>
|
<label for="selectTags" translate>Tags</label>
|
||||||
<select ng-options="tag.id as tag.name for tag in tags"
|
<select multiple ng-options="tag.id as tag.name for tag in tags"
|
||||||
ng-model="motion.tags" class="form-control" name="selectTags">
|
ng-model="motion.tags_id" class="form-control" name="selectTags">
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="selectAttachments" translate>Attachments</label>
|
<label for="selectAttachments" translate>Attachments</label>
|
||||||
<select ng-options="file.id as file.title for file in mediafiles"
|
<select ng-options="file.id as file.title for file in mediafiles"
|
||||||
ng-model="motion.attachments" class="form-control" name="selectAttachments">
|
ng-model="motion.attachments_id" class="form-control" name="selectAttachments">
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<!-- TODO: show only if supporters is enabled -->
|
<!-- TODO: show only if supporters is enabled -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="selectSupporter" translate>Supporters</label>
|
<label for="selectSupporter" translate>Supporters</label>
|
||||||
<ui-select ng-model="motion.supporter" theme="bootstrap" name="selectSupporter">
|
<ui-select multiple ng-model="motion.supporters_id" theme="bootstrap" name="selectSupporter">
|
||||||
<ui-select-match placeholder="{{ 'Select or search a participant...' | translate }}">
|
<ui-select-match placeholder="{{ 'Select or search a participant...' | translate }}">
|
||||||
{{ $select.selected.get_short_name() }}
|
{{ $select.selected.get_short_name() }}
|
||||||
</ui-select-match>
|
</ui-select-match>
|
||||||
|
@ -66,18 +66,17 @@
|
|||||||
<td> <!--TOOD: add agenda item reference -->
|
<td> <!--TOOD: add agenda item reference -->
|
||||||
<td><a ui-sref="motions.motion.detail({id: motion.id})">{{ motion.identifier }}</a>
|
<td><a ui-sref="motions.motion.detail({id: motion.id})">{{ motion.identifier }}</a>
|
||||||
<td><a ui-sref="motions.motion.detail({id: motion.id})">
|
<td><a ui-sref="motions.motion.detail({id: motion.id})">
|
||||||
<!-- TODO: make it easier to get active version -->
|
{{ motion.getTitle() }}
|
||||||
{{ (motion.versions | filter: {id: motion.active_version})[0].title }}
|
|
||||||
</a>
|
</a>
|
||||||
<td class="optional">
|
<td class="optional">
|
||||||
<div ng-repeat="submitter in motion.submitters">
|
<div ng-repeat="submitter in motion.submitters">
|
||||||
{{ (users | filter: {id: submitter})[0].get_full_name() }}<br>
|
{{ submitter.get_full_name() }}<br>
|
||||||
</div>
|
</div>
|
||||||
<td class="optional">
|
<td class="optional">
|
||||||
{{ (categories | filter: {id: motion.category})[0].name }}
|
{{ motion.category.name }}
|
||||||
<td os-perms="motions.can_manage core.can_manage_projector" class="nobr">
|
<td os-perms="motions.can_manage core.can_manage_projector" class="nobr">
|
||||||
<!-- project -->
|
<!-- project -->
|
||||||
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
<a os-perms="core.can_manage_projector" class="btn btn-default btn-sm"
|
||||||
ng-class="{ 'btn-primary': motion.isProjected() }"
|
ng-class="{ 'btn-primary': motion.isProjected() }"
|
||||||
ng-click="motion.project()"
|
ng-click="motion.project()"
|
||||||
title="{{ 'Project motion' | translate }}">
|
title="{{ 'Project motion' | translate }}">
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
angular.module('OpenSlidesApp.users', [])
|
angular.module('OpenSlidesApp.users', [])
|
||||||
|
|
||||||
.factory('User', function(DS, Group, jsDataModel) {
|
.factory('User', ['DS', 'Group', 'jsDataModel', function(DS, Group, jsDataModel) {
|
||||||
var name = 'users/user'
|
var name = 'users/user'
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: name,
|
name: name,
|
||||||
endpoint: '/rest/users/user/',
|
|
||||||
useClass: jsDataModel,
|
useClass: jsDataModel,
|
||||||
methods: {
|
methods: {
|
||||||
getResourceName: function () {
|
getResourceName: function () {
|
||||||
@ -52,7 +53,7 @@ angular.module('OpenSlidesApp.users', [])
|
|||||||
Group.find(groupId);
|
Group.find(groupId);
|
||||||
// But do not work with the returned promise, because in
|
// But do not work with the returned promise, because in
|
||||||
// this case this method can not be called in $watch
|
// this case this method can not be called in $watch
|
||||||
group = Group.get(groupId);
|
var group = Group.get(groupId);
|
||||||
if (group) {
|
if (group) {
|
||||||
_.forEach(group.permissions, function(perm) {
|
_.forEach(group.permissions, function(perm) {
|
||||||
allPerms.push(perm);
|
allPerms.push(perm);
|
||||||
@ -63,16 +64,15 @@ angular.module('OpenSlidesApp.users', [])
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
})
|
}])
|
||||||
|
|
||||||
.factory('Group', function(DS) {
|
.factory('Group', ['DS', function(DS) {
|
||||||
return DS.defineResource({
|
return DS.defineResource({
|
||||||
name: 'users/group',
|
name: 'users/group',
|
||||||
endpoint: '/rest/users/group/'
|
|
||||||
});
|
});
|
||||||
})
|
}])
|
||||||
|
|
||||||
.run(function(User, Group) {});
|
.run(['User', 'Group', function(User, Group) {}]);
|
||||||
|
|
||||||
|
|
||||||
angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
||||||
|
@ -148,8 +148,9 @@ class Speak(TestCase):
|
|||||||
def test_begin_speach_next_speaker(self):
|
def test_begin_speach_next_speaker(self):
|
||||||
speaker = Speaker.objects.add(self.user, self.item)
|
speaker = Speaker.objects.add(self.user, self.item)
|
||||||
Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item)
|
Speaker.objects.add(get_user_model().objects.get(username='admin'), self.item)
|
||||||
self.assertTrue(Speaker.objects.get(pk=speaker.pk).begin_time is None)
|
|
||||||
response = self.client.put(reverse('item-speak', args=[self.item.pk]))
|
response = self.client.put(reverse('item-speak', args=[self.item.pk]))
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertFalse(Speaker.objects.get(pk=speaker.pk).begin_time is None)
|
self.assertFalse(Speaker.objects.get(pk=speaker.pk).begin_time is None)
|
||||||
|
|
||||||
|
@ -17,12 +17,19 @@ class CreateMotion(TestCase):
|
|||||||
self.client.login(username='admin', password='admin')
|
self.client.login(username='admin', password='admin')
|
||||||
|
|
||||||
def test_simple(self):
|
def test_simple(self):
|
||||||
|
"""
|
||||||
|
Tests that a motion is created with a specific title and text.
|
||||||
|
|
||||||
|
The created motion should have an identifier and the admin user should
|
||||||
|
be the submitter.
|
||||||
|
"""
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse('motion-list'),
|
reverse('motion-list'),
|
||||||
{'title': 'test_title_OoCoo3MeiT9li5Iengu9',
|
{'title': 'test_title_OoCoo3MeiT9li5Iengu9',
|
||||||
'text': 'test_text_thuoz0iecheiheereiCi'})
|
'text': 'test_text_thuoz0iecheiheereiCi'})
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
||||||
motion = Motion.objects.get()
|
motion = Motion.objects.get()
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
self.assertEqual(motion.title, 'test_title_OoCoo3MeiT9li5Iengu9')
|
self.assertEqual(motion.title, 'test_title_OoCoo3MeiT9li5Iengu9')
|
||||||
self.assertEqual(motion.identifier, '1')
|
self.assertEqual(motion.identifier, '1')
|
||||||
self.assertTrue(motion.submitters.exists())
|
self.assertTrue(motion.submitters.exists())
|
||||||
|
Loading…
Reference in New Issue
Block a user