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-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"
|
||||
}
|
||||
}
|
||||
|
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
|
||||
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.
|
||||
"""
|
||||
|
@ -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',)
|
||||
|
@ -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) {
|
||||
|
@ -55,8 +55,8 @@
|
||||
* check permissions
|
||||
-->
|
||||
<ol>
|
||||
<li ng-repeat="speaker in item.speaker_set">
|
||||
{{ (users | filter: {id: speaker.user})[0].get_full_name() }}
|
||||
<li ng-repeat="speaker in item.speakers">
|
||||
{{ speaker.user.get_full_name() }}
|
||||
<button os-perms="agenda.can_manage" ng-click="removeSpeaker(speaker.id)"
|
||||
class="btn btn-default btn-xs">
|
||||
<i class="fa fa-times"></i>
|
||||
@ -65,7 +65,7 @@
|
||||
</ol>
|
||||
|
||||
<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">
|
||||
<ui-select ng-model="speaker.selected" ng-change="addSpeaker(speaker.selected.id)">
|
||||
<ui-select-match placeholder="{{ 'Select or search a participant...' | translate }}">
|
||||
|
@ -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'])
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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'])
|
||||
|
@ -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
|
||||
|
@ -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');
|
||||
|
@ -2,9 +2,10 @@
|
||||
{{ motion.getTitle() }}
|
||||
<small>
|
||||
<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>
|
||||
</h1>
|
||||
{{ motion.tags }}
|
||||
|
||||
<div id="submenu">
|
||||
<a ui-sref="motions.motion.list" class="btn btn-sm btn-default">
|
||||
@ -16,7 +17,7 @@
|
||||
<translate>PDF</translate>
|
||||
</a>
|
||||
<!-- 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-click="motion.project()"
|
||||
title="{{ 'Project motion' | translate }}">
|
||||
@ -43,11 +44,11 @@
|
||||
<div class="well">
|
||||
<h3 translate>Submitters</h3>
|
||||
<div ng-repeat="submitter in motion.submitters">
|
||||
{{ (users | filter: {id: submitter})[0].get_full_name() }}<br>
|
||||
{{ submitter.get_full_name() }}<br>
|
||||
</div>
|
||||
|
||||
<h3 translate>Category</h3>
|
||||
{{ (categories | filter: {id: motion.category})[0].name }}</a>
|
||||
{{ motion.category.name }}</a>
|
||||
|
||||
<h3 translate>Voting result</h3>
|
||||
-
|
||||
|
@ -22,7 +22,7 @@
|
||||
<div class="form-group">
|
||||
<label for="selectSubmitter" translate>Submitter</label>
|
||||
<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>
|
||||
<!-- TODO: use modern ui-select component with more information per user
|
||||
<ui-select multipe ng-model="motion.submitter" theme="bootstrap" name="selectSubmitter">
|
||||
@ -50,25 +50,25 @@
|
||||
<div class="form-group">
|
||||
<label for="selectCategory" translate>Category</label>
|
||||
<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>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="selectTags" translate>Tags</label>
|
||||
<select ng-options="tag.id as tag.name for tag in tags"
|
||||
ng-model="motion.tags" class="form-control" name="selectTags">
|
||||
<select multiple ng-options="tag.id as tag.name for tag in tags"
|
||||
ng-model="motion.tags_id" class="form-control" name="selectTags">
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="selectAttachments" translate>Attachments</label>
|
||||
<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>
|
||||
</div>
|
||||
<!-- TODO: show only if supporters is enabled -->
|
||||
<div class="form-group">
|
||||
<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 }}">
|
||||
{{ $select.selected.get_short_name() }}
|
||||
</ui-select-match>
|
||||
|
@ -66,18 +66,17 @@
|
||||
<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})">
|
||||
<!-- TODO: make it easier to get active version -->
|
||||
{{ (motion.versions | filter: {id: motion.active_version})[0].title }}
|
||||
{{ motion.getTitle() }}
|
||||
</a>
|
||||
<td class="optional">
|
||||
<div ng-repeat="submitter in motion.submitters">
|
||||
{{ (users | filter: {id: submitter})[0].get_full_name() }}<br>
|
||||
{{ submitter.get_full_name() }}<br>
|
||||
</div>
|
||||
<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">
|
||||
<!-- 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-click="motion.project()"
|
||||
title="{{ 'Project motion' | translate }}">
|
||||
|
@ -1,10 +1,11 @@
|
||||
"use strict";
|
||||
|
||||
angular.module('OpenSlidesApp.users', [])
|
||||
|
||||
.factory('User', function(DS, Group, jsDataModel) {
|
||||
.factory('User', ['DS', 'Group', 'jsDataModel', function(DS, Group, jsDataModel) {
|
||||
var name = 'users/user'
|
||||
return DS.defineResource({
|
||||
name: name,
|
||||
endpoint: '/rest/users/user/',
|
||||
useClass: jsDataModel,
|
||||
methods: {
|
||||
getResourceName: function () {
|
||||
@ -52,7 +53,7 @@ angular.module('OpenSlidesApp.users', [])
|
||||
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);
|
||||
var group = Group.get(groupId);
|
||||
if (group) {
|
||||
_.forEach(group.permissions, function(perm) {
|
||||
allPerms.push(perm);
|
||||
@ -63,16 +64,15 @@ angular.module('OpenSlidesApp.users', [])
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
}])
|
||||
|
||||
.factory('Group', function(DS) {
|
||||
.factory('Group', ['DS', function(DS) {
|
||||
return DS.defineResource({
|
||||
name: 'users/group',
|
||||
endpoint: '/rest/users/group/'
|
||||
});
|
||||
})
|
||||
}])
|
||||
|
||||
.run(function(User, Group) {});
|
||||
.run(['User', 'Group', function(User, Group) {}]);
|
||||
|
||||
|
||||
angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users'])
|
||||
|
@ -148,8 +148,9 @@ class Speak(TestCase):
|
||||
def test_begin_speach_next_speaker(self):
|
||||
speaker = Speaker.objects.add(self.user, 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]))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
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')
|
||||
|
||||
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(
|
||||
reverse('motion-list'),
|
||||
{'title': 'test_title_OoCoo3MeiT9li5Iengu9',
|
||||
'text': 'test_text_thuoz0iecheiheereiCi'})
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
motion = Motion.objects.get()
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(motion.title, 'test_title_OoCoo3MeiT9li5Iengu9')
|
||||
self.assertEqual(motion.identifier, '1')
|
||||
self.assertTrue(motion.submitters.exists())
|
||||
|
Loading…
Reference in New Issue
Block a user