Merge pull request #3713 from FinnStutzenstein/oneRequestProjection

One request for each projection. Added some validation for clear_elem…
This commit is contained in:
Emanuel Schütze 2018-04-24 11:15:04 +02:00 committed by GitHub
commit 198e19d3d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 150 additions and 66 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -213,8 +213,8 @@ class ProjectorViewSet(ModelViewSet):
elif self.action in (
'create', 'update', 'partial_update', 'destroy',
'activate_elements', 'prune_elements', 'update_elements', 'deactivate_elements', 'clear_elements',
'control_view', 'set_resolution', 'set_scroll', 'control_blank', 'broadcast',
'set_projectiondefault',
'project', 'control_view', 'set_resolution', 'set_scroll', 'control_blank',
'broadcast', 'set_projectiondefault',
):
result = (has_perm(self.request.user, 'core.can_see_projector') and
has_perm(self.request.user, 'core.can_manage_projector'))
@ -271,24 +271,39 @@ class ProjectorViewSet(ModelViewSet):
if not isinstance(request.data, list):
raise ValidationError({'detail': 'Data must be a list.'})
projector_instance = self.get_object()
# reset scroll level
if (projector_instance.scroll != 0):
projector_instance.scroll = 0
projector_instance.save()
projector_config = {}
for key, value in projector_instance.config.items():
if value.get('stable'):
projector_config[key] = value
for element in request.data:
projector = self.get_object()
elements = request.data
if not isinstance(elements, list):
raise ValidationError({'detail': _('The data has to be a list.')})
for element in elements:
if not isinstance(element, dict):
raise ValidationError({'detail': _('All elements have to be dicts.')})
if element.get('name') is None:
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
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.save()
return Response(serializer.data)
return serializer.data
@detail_route(methods=['post'])
def update_elements(self, request, pk):
@ -370,16 +385,73 @@ class ProjectorViewSet(ModelViewSet):
entries with stable == True. It expects a POST request to
/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 = {}
for key, value in projector_instance.config.items():
for key, value in projector.config.items():
if value.get('stable'):
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.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'])
def set_resolution(self, request, pk):

View File

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

View File

@ -181,7 +181,7 @@ def get_win32_app_data_dir() -> str:
"""
Returns the directory of Windows' AppData directory.
"""
shell32 = ctypes.WinDLL("shell32.dll")
shell32 = ctypes.WinDLL('shell32.dll') # type: ignore
SHGetFolderPath = shell32.SHGetFolderPathW
SHGetFolderPath.argtypes = (
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
raise Exception("Could not determine Windows' APPDATA path")
return buf.value
return buf.value.decode('utf-8')
def get_win32_portable_dir() -> str: