One request for each projection. Added some validation for clear_elements and prune_elements
This commit is contained in:
parent
f1b87af623
commit
10038b782f
@ -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].
|
||||||
|
@ -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 () {
|
||||||
|
@ -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) {
|
||||||
|
@ -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!";
|
||||||
};
|
};
|
||||||
|
@ -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):
|
||||||
|
@ -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: {
|
||||||
|
name: name,
|
||||||
id: this.id,
|
id: this.id,
|
||||||
mode: mode}]
|
mode: mode,
|
||||||
);
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
return $http.post('/rest/core/projector/project/', requestData);
|
||||||
},
|
},
|
||||||
isProjected: function (mode) {
|
isProjected: function (mode) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user