Added view for bulk category set and bulk motion block set.
Due to rebasing this PR, this are the appropriate authorships: Server: @normanjaeckel and @FinnStutzenstein Client: @MaximilianKrambach
This commit is contained in:
parent
8983f6aef3
commit
cf29f97613
@ -40,10 +40,10 @@ Motions:
|
|||||||
[#4235, #4518, #4521].
|
[#4235, #4518, #4521].
|
||||||
- Allowed submitters to set state of new motions in complex and customized
|
- Allowed submitters to set state of new motions in complex and customized
|
||||||
workflow [#4236].
|
workflow [#4236].
|
||||||
- Added multi select action to manage submitters, tags, states and
|
- Added multi select action to manage submitters, categories, motion blocks,
|
||||||
recommendations [#4037, #4132].
|
tags, states and recommendations [#4037, #4132, #4702].
|
||||||
- Added timestampes for motions [#4134].
|
- Added timestampes for motions [#4134].
|
||||||
- New config option to set reason as required field [#4232]
|
- New config option to set reason as required field [#4232].
|
||||||
|
|
||||||
User:
|
User:
|
||||||
- Added new admin group which grants all permissions. Users of existing group
|
- Added new admin group which grants all permissions. Users of existing group
|
||||||
|
@ -350,6 +350,34 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
|
|||||||
await this.httpService.post(restPath, { motions: motionsIdMap });
|
await this.httpService.post(restPath, { motions: motionsIdMap });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the motion blocks of motions in bulk
|
||||||
|
*
|
||||||
|
* @param viewMotion target motion
|
||||||
|
* @param motionblockId the number that indicates the motion block
|
||||||
|
*/
|
||||||
|
public async setMultiMotionBlock(viewMotions: ViewMotion[], motionblockId: number): Promise<void> {
|
||||||
|
const restPath = `/rest/motions/motion/manage_multiple_motion_block/`;
|
||||||
|
const motionsIdMap: { id: number; motion_block: number }[] = viewMotions.map(motion => {
|
||||||
|
return { id: motion.id, motion_block: motionblockId };
|
||||||
|
});
|
||||||
|
await this.httpService.post(restPath, { motions: motionsIdMap });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the category of motions in bulk
|
||||||
|
*
|
||||||
|
* @param viewMotion target motion
|
||||||
|
* @param categoryId the number that indicates the category
|
||||||
|
*/
|
||||||
|
public async setMultiCategory(viewMotions: ViewMotion[], categoryId: number): Promise<void> {
|
||||||
|
const restPath = `/rest/motions/motion/manage_multiple_category/`;
|
||||||
|
const motionsIdMap: { id: number; category: number }[] = viewMotions.map(motion => {
|
||||||
|
return { id: motion.id, category: categoryId };
|
||||||
|
});
|
||||||
|
await this.httpService.post(restPath, { motions: motionsIdMap });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the recommenders state of a motion
|
* Set the recommenders state of a motion
|
||||||
*
|
*
|
||||||
|
@ -179,20 +179,12 @@ export class MotionMultiselectService {
|
|||||||
clearChoice
|
clearChoice
|
||||||
);
|
);
|
||||||
if (selectedChoice) {
|
if (selectedChoice) {
|
||||||
let i = 0;
|
const message = this.translate.instant(this.messageForSpinner);
|
||||||
for (const motion of motions) {
|
|
||||||
++i;
|
|
||||||
const message =
|
|
||||||
this.translate.instant(this.messageForSpinner) +
|
|
||||||
`\n${i} ` +
|
|
||||||
this.translate.instant('of') +
|
|
||||||
` ${motions.length}`;
|
|
||||||
this.spinnerService.setVisibility(true, message);
|
this.spinnerService.setVisibility(true, message);
|
||||||
await this.repo.update(
|
await this.repo.setMultiCategory(motions, selectedChoice.items as number).catch(error => {
|
||||||
{ category_id: selectedChoice.action ? null : (selectedChoice.items as number) },
|
this.spinnerService.setVisibility(false);
|
||||||
motion
|
throw error;
|
||||||
);
|
});
|
||||||
}
|
|
||||||
this.spinnerService.setVisibility(false);
|
this.spinnerService.setVisibility(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -315,18 +307,13 @@ export class MotionMultiselectService {
|
|||||||
clearChoice
|
clearChoice
|
||||||
);
|
);
|
||||||
if (selectedChoice) {
|
if (selectedChoice) {
|
||||||
let i = 0;
|
const message = this.translate.instant(this.messageForSpinner);
|
||||||
for (const motion of motions) {
|
|
||||||
++i;
|
|
||||||
const message =
|
|
||||||
this.translate.instant(this.messageForSpinner) +
|
|
||||||
`\n${i} ` +
|
|
||||||
this.translate.instant('of') +
|
|
||||||
` ${motions.length}`;
|
|
||||||
this.spinnerService.setVisibility(true, message);
|
this.spinnerService.setVisibility(true, message);
|
||||||
const blockId = selectedChoice.action ? null : (selectedChoice.items as number);
|
const blockId = selectedChoice.action ? null : (selectedChoice.items as number);
|
||||||
await this.repo.update({ motion_block_id: blockId }, motion);
|
await this.repo.setMultiMotionBlock(motions, blockId).catch(error => {
|
||||||
}
|
this.spinnerService.setVisibility(false);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
this.spinnerService.setVisibility(false);
|
this.spinnerService.setVisibility(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,8 @@ class MotionViewSet(TreeSortMixin, ModelViewSet):
|
|||||||
result = has_perm(self.request.user, "motions.can_see")
|
result = has_perm(self.request.user, "motions.can_see")
|
||||||
# The rest of the check is done in the respective method. See below.
|
# The rest of the check is done in the respective method. See below.
|
||||||
elif self.action in (
|
elif self.action in (
|
||||||
|
"manage_multiple_category",
|
||||||
|
"manage_multiple_motion_block",
|
||||||
"manage_multiple_state",
|
"manage_multiple_state",
|
||||||
"set_recommendation",
|
"set_recommendation",
|
||||||
"manage_multiple_recommendation",
|
"manage_multiple_recommendation",
|
||||||
@ -523,6 +525,166 @@ class MotionViewSet(TreeSortMixin, ModelViewSet):
|
|||||||
# Initiate response.
|
# Initiate response.
|
||||||
return Response({"detail": message})
|
return Response({"detail": message})
|
||||||
|
|
||||||
|
@list_route(methods=["post"])
|
||||||
|
@transaction.atomic
|
||||||
|
def manage_multiple_category(self, request):
|
||||||
|
"""
|
||||||
|
Set categories of multiple motions.
|
||||||
|
|
||||||
|
Send POST {"motions": [... see schema ...]} to changed the categories.
|
||||||
|
"""
|
||||||
|
motions = request.data.get("motions")
|
||||||
|
|
||||||
|
schema = {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Motion manage multiple categories schema",
|
||||||
|
"description": "An array of motion ids with the respective category id that should be set as category.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {"description": "The id of the motion.", "type": "integer"},
|
||||||
|
"category": {
|
||||||
|
"description": "The id for the category that should become the new category.",
|
||||||
|
"anyOf": [{"type": "number", "minimum": 1}, {"type": "null"}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["id", "category"],
|
||||||
|
},
|
||||||
|
"uniqueItems": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate request data.
|
||||||
|
try:
|
||||||
|
jsonschema.validate(motions, schema)
|
||||||
|
except jsonschema.ValidationError as err:
|
||||||
|
raise ValidationError({"detail": str(err)})
|
||||||
|
|
||||||
|
motion_result = []
|
||||||
|
for item in motions:
|
||||||
|
# Get motion.
|
||||||
|
try:
|
||||||
|
motion = Motion.objects.get(pk=item["id"])
|
||||||
|
except Motion.DoesNotExist:
|
||||||
|
raise ValidationError({"detail": f"Motion {item['id']} does not exist"})
|
||||||
|
|
||||||
|
# Get category
|
||||||
|
category = None
|
||||||
|
if item["category"] is not None:
|
||||||
|
try:
|
||||||
|
category = Category.objects.get(pk=item["category"])
|
||||||
|
except Category.DoesNotExist:
|
||||||
|
raise ValidationError(
|
||||||
|
{"detail": f"Category {item['category']} does not exist"}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set category
|
||||||
|
motion.category = category
|
||||||
|
|
||||||
|
# Save motion.
|
||||||
|
motion.save(
|
||||||
|
update_fields=["category", "last_modified"], skip_autoupdate=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fire autoupdate again to save information to OpenSlides history.
|
||||||
|
information = (
|
||||||
|
["Category removed"]
|
||||||
|
if category is None
|
||||||
|
else ["Category set to {arg1}", category.name]
|
||||||
|
)
|
||||||
|
inform_changed_data(
|
||||||
|
motion, information=information, user_id=request.user.pk
|
||||||
|
)
|
||||||
|
|
||||||
|
# Finish motion.
|
||||||
|
motion_result.append(motion)
|
||||||
|
|
||||||
|
# Send response.
|
||||||
|
return Response(
|
||||||
|
{"detail": f"Category of {len(motion_result)} motions successfully set."}
|
||||||
|
)
|
||||||
|
|
||||||
|
@list_route(methods=["post"])
|
||||||
|
@transaction.atomic
|
||||||
|
def manage_multiple_motion_block(self, request):
|
||||||
|
"""
|
||||||
|
Set motion blocks of multiple motions.
|
||||||
|
|
||||||
|
Send POST {"motions": [... see schema ...]} to changed the motion blocks.
|
||||||
|
"""
|
||||||
|
motions = request.data.get("motions")
|
||||||
|
|
||||||
|
schema = {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Motion manage multiple motion blocks schema",
|
||||||
|
"description": "An array of motion ids with the respective motion block id that should be set as motion block.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {"description": "The id of the motion.", "type": "integer"},
|
||||||
|
"motion_block": {
|
||||||
|
"description": "The id for the motion block that should become the new motion block.",
|
||||||
|
"anyOf": [{"type": "number", "minimum": 1}, {"type": "null"}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["id", "motion_block"],
|
||||||
|
},
|
||||||
|
"uniqueItems": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate request data.
|
||||||
|
try:
|
||||||
|
jsonschema.validate(motions, schema)
|
||||||
|
except jsonschema.ValidationError as err:
|
||||||
|
raise ValidationError({"detail": str(err)})
|
||||||
|
|
||||||
|
motion_result = []
|
||||||
|
for item in motions:
|
||||||
|
# Get motion.
|
||||||
|
try:
|
||||||
|
motion = Motion.objects.get(pk=item["id"])
|
||||||
|
except Motion.DoesNotExist:
|
||||||
|
raise ValidationError({"detail": f"Motion {item['id']} does not exist"})
|
||||||
|
|
||||||
|
# Get motion block
|
||||||
|
motion_block = None
|
||||||
|
if item["motion_block"] is not None:
|
||||||
|
try:
|
||||||
|
motion_block = MotionBlock.objects.get(pk=item["motion_block"])
|
||||||
|
except MotionBlock.DoesNotExist:
|
||||||
|
raise ValidationError(
|
||||||
|
{"detail": f"MotionBlock {item['motion_block']} does not exist"}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set motion bock
|
||||||
|
motion.motion_block = motion_block
|
||||||
|
|
||||||
|
# Save motion.
|
||||||
|
motion.save(
|
||||||
|
update_fields=["motion_block", "last_modified"], skip_autoupdate=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fire autoupdate again to save information to OpenSlides history.
|
||||||
|
information = (
|
||||||
|
["Motion block removed"]
|
||||||
|
if motion_block is None
|
||||||
|
else ["Motion block set to {arg1}", motion_block.title]
|
||||||
|
)
|
||||||
|
inform_changed_data(
|
||||||
|
motion, information=information, user_id=request.user.pk
|
||||||
|
)
|
||||||
|
|
||||||
|
# Finish motion.
|
||||||
|
motion_result.append(motion)
|
||||||
|
|
||||||
|
# Send response.
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"detail": f"Motion block of {len(motion_result)} motions successfully set."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@detail_route(methods=["put"])
|
@detail_route(methods=["put"])
|
||||||
def set_state(self, request, pk=None):
|
def set_state(self, request, pk=None):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user