One request for each projection. Added some validation for clear_elements and prune_elements

This commit is contained in:
FinnStutzenstein 2018-04-17 10:05:27 +02:00
parent f1b87af623
commit 10038b782f
7 changed files with 150 additions and 66 deletions

View File

@ -132,6 +132,7 @@ Core:
- Reset scroll level for each new projection [#3686]. - Reset scroll level for each new projection [#3686].
- Scroll to top on every state change [#3689]. - Scroll to top on every state change [#3689].
- Added pagination on top of lists [#3698]. - Added pagination on top of lists [#3698].
- New api route to project items with just one request needed [#3713].
Mediafiles: Mediafiles:
- New form for uploading multiple files [#3650]. - New form for uploading multiple files [#3650].

View File

@ -129,23 +129,24 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
project: function (projectorId, tree) { project: function (projectorId, tree) {
if (tree) { if (tree) {
var isProjectedIds = this.isProjected(tree); var isProjectedIds = this.isProjected(tree);
_.forEach(isProjectedIds, function (id) { var requestData = {
$http.post('/rest/core/projector/' + id + '/clear_elements/'); clear_ids: isProjectedIds,
}); };
// Activate, if the projector_id is a new projector. // Activate, if the projector_id is a new projector.
if (_.indexOf(isProjectedIds, projectorId) == -1) { if (_.indexOf(isProjectedIds, projectorId) == -1) {
return $http.post( requestData.prune = {
'/rest/core/projector/' + projectorId + '/prune_elements/', id: projectorId,
[{ element: {
name: 'agenda/item-list', name: 'agenda/item-list',
tree: true, tree: true,
id: this.id id: this.id,
}] },
); };
} }
return $http.post('/rest/core/projector/project/', requestData);
} else { // Project the content object } else { // Project the content object
var contentObject = DS.get(this.content_object.collection, this.content_object.id); var contentObject = DS.get(this.content_object.collection, this.content_object.id);
contentObject.project(projectorId); return contentObject.project(projectorId);
} }
}, },
// override isProjected function of jsDataModel factory // override isProjected function of jsDataModel factory
@ -185,15 +186,19 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
// project list of speakers // project list of speakers
projectListOfSpeakers: function(projectorId) { projectListOfSpeakers: function(projectorId) {
var isProjectedIds = this.isListOfSpeakersProjected(); var isProjectedIds = this.isListOfSpeakersProjected();
_.forEach(isProjectedIds, function (id) { var requestData = {
$http.post('/rest/core/projector/' + id + '/clear_elements/'); clear_ids: isProjectedIds,
}); };
if (_.indexOf(isProjectedIds, projectorId) == -1) { if (_.indexOf(isProjectedIds, projectorId) == -1) {
return $http.post( requestData.prune = {
'/rest/core/projector/' + projectorId + '/prune_elements/', id: projectorId,
[{name: 'agenda/list-of-speakers', id: this.id}] element: {
); name: 'agenda/list-of-speakers',
id: this.id,
},
};
} }
return $http.post('/rest/core/projector/project/', requestData);
}, },
// check if list of speakers is projected // check if list of speakers is projected
isListOfSpeakersProjected: function () { isListOfSpeakersProjected: function () {

View File

@ -356,15 +356,20 @@ angular.module('OpenSlidesApp.assignments', [])
// override project function of jsDataModel factory // override project function of jsDataModel factory
project: function (projectorId, pollId) { project: function (projectorId, pollId) {
var isProjectedIds = this.isProjected(pollId); var isProjectedIds = this.isProjected(pollId);
_.forEach(isProjectedIds, function (id) { var requestData = {
$http.post('/rest/core/projector/' + id + '/clear_elements/'); clear_ids: isProjectedIds,
}); };
if (_.indexOf(isProjectedIds, projectorId) == -1) { if (_.indexOf(isProjectedIds, projectorId) == -1) {
return $http.post( requestData.prune = {
'/rest/core/projector/' + projectorId + '/prune_elements/', id: projectorId,
[{name: 'assignments/assignment', id: this.id, poll: pollId}] element: {
); name: 'assignments/assignment',
id: this.id,
poll: pollId
},
};
} }
return $http.post('/rest/core/projector/project/', requestData);
}, },
// override isProjected function of jsDataModel factory // override isProjected function of jsDataModel factory
isProjected: function (poll_id, anyPoll) { isProjected: function (poll_id, anyPoll) {

View File

@ -733,16 +733,17 @@ angular.module('OpenSlidesApp.core', [
BaseModel.prototype.project = function(projectorId) { BaseModel.prototype.project = function(projectorId) {
// if this object is already projected on projectorId, delete this element from this projector // if this object is already projected on projectorId, delete this element from this projector
var isProjectedIds = this.isProjected(); var isProjectedIds = this.isProjected();
_.forEach(isProjectedIds, function (id) { var requestData = {
$http.post('/rest/core/projector/' + id + '/clear_elements/'); clear_ids: isProjectedIds,
}); };
// Show the element, if it was not projected before on the given projector // Show the element, if it was not projected before on the given projector
if (_.indexOf(isProjectedIds, projectorId) == -1) { if (_.indexOf(isProjectedIds, projectorId) == -1) {
return $http.post( requestData.prune = {
'/rest/core/projector/' + projectorId + '/prune_elements/', id: projectorId,
[{name: this.getResourceName(), id: this.id}] element: {name: this.getResourceName(), id: this.id},
); };
} }
return $http.post('/rest/core/projector/project/', requestData);
}; };
BaseModel.prototype.isProjected = function() { BaseModel.prototype.isProjected = function() {
// Returns the ids of all projectors if there is a projector element // Returns the ids of all projectors if there is a projector element
@ -761,7 +762,7 @@ angular.module('OpenSlidesApp.core', [
}); });
return isProjectedIds; return isProjectedIds;
}; };
// Override this method to get object spzific behavior // Override this method to get object specific behavior
BaseModel.prototype.isRelatedProjected = function() { BaseModel.prototype.isRelatedProjected = function() {
throw "needs to be implemented!"; throw "needs to be implemented!";
}; };

View File

@ -213,8 +213,8 @@ class ProjectorViewSet(ModelViewSet):
elif self.action in ( elif self.action in (
'create', 'update', 'partial_update', 'destroy', 'create', 'update', 'partial_update', 'destroy',
'activate_elements', 'prune_elements', 'update_elements', 'deactivate_elements', 'clear_elements', 'activate_elements', 'prune_elements', 'update_elements', 'deactivate_elements', 'clear_elements',
'control_view', 'set_resolution', 'set_scroll', 'control_blank', 'broadcast', 'project', 'control_view', 'set_resolution', 'set_scroll', 'control_blank',
'set_projectiondefault', 'broadcast', 'set_projectiondefault',
): ):
result = (has_perm(self.request.user, 'core.can_see_projector') and result = (has_perm(self.request.user, 'core.can_see_projector') and
has_perm(self.request.user, 'core.can_manage_projector')) has_perm(self.request.user, 'core.can_manage_projector'))
@ -271,24 +271,39 @@ class ProjectorViewSet(ModelViewSet):
if not isinstance(request.data, list): if not isinstance(request.data, list):
raise ValidationError({'detail': 'Data must be a list.'}) raise ValidationError({'detail': 'Data must be a list.'})
projector_instance = self.get_object() projector = self.get_object()
# reset scroll level elements = request.data
if (projector_instance.scroll != 0): if not isinstance(elements, list):
projector_instance.scroll = 0 raise ValidationError({'detail': _('The data has to be a list.')})
projector_instance.save() for element in elements:
projector_config = {} if not isinstance(element, dict):
for key, value in projector_instance.config.items(): raise ValidationError({'detail': _('All elements have to be dicts.')})
if value.get('stable'):
projector_config[key] = value
for element in request.data:
if element.get('name') is None: if element.get('name') is None:
raise ValidationError({'detail': 'Invalid projector element. Name is missing.'}) raise ValidationError({'detail': 'Invalid projector element. Name is missing.'})
return Response(self.prune(projector, elements))
def prune(self, projector, elements):
"""
Prunes all non stable elements from the projector and adds the given elements.
The elements have to a list of dicts, each gict containing at least a name. This
is not validated at this point! Should be done before.
Returns the new serialized data.
"""
# reset scroll level
if (projector.scroll != 0):
projector.scroll = 0
projector.save()
projector_config = {}
for key, value in projector.config.items():
if value.get('stable'):
projector_config[key] = value
for element in elements:
projector_config[uuid.uuid4().hex] = element projector_config[uuid.uuid4().hex] = element
serializer = self.get_serializer(projector_instance, data={'config': projector_config}, partial=False) serializer = self.get_serializer(projector, data={'config': projector_config}, partial=False)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
serializer.save() serializer.save()
return Response(serializer.data) return serializer.data
@detail_route(methods=['post']) @detail_route(methods=['post'])
def update_elements(self, request, pk): def update_elements(self, request, pk):
@ -370,16 +385,73 @@ class ProjectorViewSet(ModelViewSet):
entries with stable == True. It expects a POST request to entries with stable == True. It expects a POST request to
/rest/core/projector/<pk>/clear_elements/. /rest/core/projector/<pk>/clear_elements/.
""" """
projector_instance = self.get_object() projector = self.get_object()
return Response(self.clear(projector))
def clear(self, projector):
projector_config = {} projector_config = {}
for key, value in projector_instance.config.items(): for key, value in projector.config.items():
if value.get('stable'): if value.get('stable'):
projector_config[key] = value projector_config[key] = value
serializer = self.get_serializer(projector_instance, data={'config': projector_config}, partial=False) serializer = self.get_serializer(projector, data={'config': projector_config}, partial=False)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
serializer.save() serializer.save()
return Response(serializer.data) return serializer.data
@list_route(methods=['post'])
def project(self, request, *args, **kwargs):
"""
REST API operation. Does a combination of clear_elements and prune_elements:
In the most cases when projecting an element it first need to be removed from
all projectors where it is projected. In a second step the new element (which
may be not given if the element is just deprojected) needs to be projected on
a maybe different projector. The request data has to have this scheme:
{
clear_ids: [<projector id1>, ...], # May be an empty list
prune: { # May not be given.
id: <projector id>,
element: <projector element to add>
}
}
"""
# Get projector ids to clear
clear_projector_ids = request.data.get('clear_ids', [])
for id in clear_projector_ids:
if not isinstance(id, int):
raise ValidationError({'detail': _('The id "{}" has to be int.').format(id)})
# Get the projector id and validate element to prune. This is optional.
prune = request.data.get('prune')
if prune is not None:
if not isinstance(prune, dict):
raise ValidationError({'detail': _('Prune has to be an object.')})
prune_projector_id = prune.get('id')
if not isinstance(prune_projector_id, int):
raise ValidationError({'detail': _('The prune projector id has to be int.')})
# Get the projector after all clear operations, but check, if it exist.
if not Projector.objects.filter(pk=prune_projector_id).exists():
raise ValidationError({
'detail': _('The projector with id "{}" does not exist').format(prune_projector_id)})
prune_element = prune.get('element', {})
if not isinstance(prune_element, dict):
raise ValidationError({'detail': _('Prune element has to be a dict or not given.')})
if prune_element.get('name') is None:
raise ValidationError({'detail': 'Invalid projector element. Name is missing.'})
# First step: Clear all given projectors
for projector in Projector.objects.filter(pk__in=clear_projector_ids):
self.clear(projector)
# Second step: optionally prune
if prune is not None:
# This get is save. We checked that the projector exists above.
prune_projector = Projector.objects.get(pk=prune_projector_id)
self.prune(prune_projector, [prune_element])
return Response()
@detail_route(methods=['post']) @detail_route(methods=['post'])
def set_resolution(self, request, pk): def set_resolution(self, request, pk):

View File

@ -44,12 +44,10 @@ angular.module('OpenSlidesApp.motions', [
.factory('Workflow', [ .factory('Workflow', [
'DS', 'DS',
'jsDataModel',
'WorkflowState', 'WorkflowState',
function (DS, jsDataModel, WorkflowState) { function (DS, WorkflowState) {
return DS.defineResource({ return DS.defineResource({
name: 'motions/workflow', name: 'motions/workflow',
useClass: jsDataModel,
relations: { relations: {
hasMany: { hasMany: {
'motions/workflowstate': { 'motions/workflowstate': {
@ -652,13 +650,12 @@ angular.module('OpenSlidesApp.motions', [
* Also sets the projection mode if given; If not it projects in 'original' mode. */ * Also sets the projection mode if given; If not it projects in 'original' mode. */
project: function (projectorId, mode) { project: function (projectorId, mode) {
// if this object is already projected on projectorId, delete this element from this projector // if this object is already projected on projectorId, delete this element from this projector
var isProjected = this.isProjectedWithMode(); var requestData = {
_.forEach(isProjected, function (mapping) { clear_ids: this.isProjected(),
$http.post('/rest/core/projector/' + mapping.projectorId + '/clear_elements/'); };
});
// Was there a projector with the same id and mode as the given id and mode? // Was there a projector with the same id and mode as the given id and mode?
// If not, project the motion. // If not, project the motion.
var wasProjectedBefore = _.some(isProjected, function (mapping) { var wasProjectedBefore = _.some(this.isProjectedWithMode(), function (mapping) {
var value = (mapping.projectorId === projectorId); var value = (mapping.projectorId === projectorId);
if (mode) { if (mode) {
value = value && (mapping.mode === mode); value = value && (mapping.mode === mode);
@ -667,13 +664,16 @@ angular.module('OpenSlidesApp.motions', [
}); });
mode = mode || Config.get('motions_recommendation_text_mode').value; mode = mode || Config.get('motions_recommendation_text_mode').value;
if (!wasProjectedBefore) { if (!wasProjectedBefore) {
return $http.post( requestData.prune = {
'/rest/core/projector/' + projectorId + '/prune_elements/', id: projectorId,
[{name: name, element: {
id: this.id, name: name,
mode: mode}] id: this.id,
); mode: mode,
},
};
} }
return $http.post('/rest/core/projector/project/', requestData);
}, },
isProjected: function (mode) { isProjected: function (mode) {
var self = this; var self = this;

View File

@ -181,7 +181,7 @@ def get_win32_app_data_dir() -> str:
""" """
Returns the directory of Windows' AppData directory. Returns the directory of Windows' AppData directory.
""" """
shell32 = ctypes.WinDLL("shell32.dll") shell32 = ctypes.WinDLL('shell32.dll') # type: ignore
SHGetFolderPath = shell32.SHGetFolderPathW SHGetFolderPath = shell32.SHGetFolderPathW
SHGetFolderPath.argtypes = ( SHGetFolderPath.argtypes = (
ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_uint32,
@ -197,7 +197,7 @@ def get_win32_app_data_dir() -> str:
# TODO: Write other exception # TODO: Write other exception
raise Exception("Could not determine Windows' APPDATA path") raise Exception("Could not determine Windows' APPDATA path")
return buf.value return buf.value.decode('utf-8')
def get_win32_portable_dir() -> str: def get_win32_portable_dir() -> str: