Merge pull request #3713 from FinnStutzenstein/oneRequestProjection
One request for each projection. Added some validation for clear_elem…
This commit is contained in:
commit
198e19d3d1
@ -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].
|
||||
|
@ -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 () {
|
||||
|
@ -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) {
|
||||
|
@ -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!";
|
||||
};
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
requestData.prune = {
|
||||
id: projectorId,
|
||||
element: {
|
||||
name: name,
|
||||
id: this.id,
|
||||
mode: mode}]
|
||||
);
|
||||
mode: mode,
|
||||
},
|
||||
};
|
||||
}
|
||||
return $http.post('/rest/core/projector/project/', requestData);
|
||||
},
|
||||
isProjected: function (mode) {
|
||||
var self = this;
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user