Merge pull request #4652 from FinnStutzenstein/sortMotionInCategories
Sort motions in categories
This commit is contained in:
commit
524ff4a981
@ -70,24 +70,28 @@ export class CategoryRepositoryService extends BaseRepository<ViewCategory, Cate
|
||||
return viewCategory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the category for the ID
|
||||
* @param category_id category ID
|
||||
*/
|
||||
public getCategoryByID(category_id: number): Category {
|
||||
return this.DS.find<Category>(Category, cat => cat.id === category_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a categories numbering.
|
||||
*
|
||||
* @param category the category it should be updated in
|
||||
* @param motionList the list of motions on this category
|
||||
* @param motionIds the list of motion ids on this category
|
||||
*/
|
||||
public async numberMotionsInCategory(category: Category, motionIds: number[]): Promise<void> {
|
||||
const collectionString = 'rest/motions/category/' + category.id + '/numbering/';
|
||||
await this.httpService.post(collectionString, { motions: motionIds });
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the sorting of motions in a category.
|
||||
*
|
||||
* @param category the category it should be updated in
|
||||
* @param motionIds the list of motion ids on this category
|
||||
*/
|
||||
public async sortMotionsInCategory(category: Category, motionIds: number[]): Promise<void> {
|
||||
const collectionString = 'rest/motions/category/' + category.id + '/sort/';
|
||||
await this.httpService.post(collectionString, { motions: motionIds });
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers an update for the sort function responsible for the default sorting of data items
|
||||
*/
|
||||
|
@ -28,6 +28,7 @@ export class Motion extends BaseModel {
|
||||
public modified_final_version: string;
|
||||
public parent_id: number;
|
||||
public category_id: number;
|
||||
public category_weight: number;
|
||||
public motion_block_id: number;
|
||||
public origin: string;
|
||||
public submitters: MotionSubmitter[];
|
||||
|
@ -128,6 +128,10 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
|
||||
return this._category;
|
||||
}
|
||||
|
||||
public get category_weight(): number {
|
||||
return this.motion.category_weight;
|
||||
}
|
||||
|
||||
public get submitters(): ViewUser[] {
|
||||
return this._submitters;
|
||||
}
|
||||
|
16
openslides/motions/migrations/0025_motion_category_weight.py
Normal file
16
openslides/motions/migrations/0025_motion_category_weight.py
Normal file
@ -0,0 +1,16 @@
|
||||
# Generated by Django 2.2 on 2019-04-30 11:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("motions", "0024_state_restriction_3")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="motion",
|
||||
name="category_weight",
|
||||
field=models.IntegerField(default=10000),
|
||||
)
|
||||
]
|
@ -187,6 +187,12 @@ class Motion(RESTModelMixin, models.Model):
|
||||
ForeignKey to one category of motions.
|
||||
"""
|
||||
|
||||
category_weight = models.IntegerField(default=10000)
|
||||
"""
|
||||
Sorts the motions inside a category. Default is 10000 so new motions
|
||||
in a category will be added on the end of the list.
|
||||
"""
|
||||
|
||||
motion_block = models.ForeignKey(
|
||||
"MotionBlock", on_delete=SET_NULL_AND_AUTOUPDATE, null=True, blank=True
|
||||
)
|
||||
|
@ -428,6 +428,7 @@ class MotionSerializer(ModelSerializer):
|
||||
"reason",
|
||||
"parent",
|
||||
"category",
|
||||
"category_weight",
|
||||
"comments",
|
||||
"motion_block",
|
||||
"origin",
|
||||
@ -455,6 +456,8 @@ class MotionSerializer(ModelSerializer):
|
||||
read_only_fields = (
|
||||
"state",
|
||||
"recommendation",
|
||||
"weight",
|
||||
"category_weight",
|
||||
) # Some other fields are also read_only. See definitions above.
|
||||
|
||||
def validate(self, data):
|
||||
@ -527,16 +530,34 @@ class MotionSerializer(ModelSerializer):
|
||||
def update(self, motion, validated_data):
|
||||
"""
|
||||
Customized method to update a motion.
|
||||
- If the workflow changes, the state of the motions is resetted to
|
||||
the initial state of the new workflow.
|
||||
- If the category changes, the category_weight is reset to the default value.
|
||||
"""
|
||||
workflow_id = None
|
||||
if "workflow_id" in validated_data:
|
||||
workflow_id = validated_data.pop("workflow_id")
|
||||
|
||||
old_category_id = motion.category.pk if motion.category is not None else None
|
||||
new_category_id = (
|
||||
validated_data["category"].pk
|
||||
if validated_data.get("category") is not None
|
||||
else None
|
||||
)
|
||||
|
||||
result = super().update(motion, validated_data)
|
||||
|
||||
# Check for changed workflow
|
||||
if workflow_id is not None and workflow_id != motion.workflow_id:
|
||||
motion.reset_state(workflow_id)
|
||||
motion.save()
|
||||
motion.save(skip_autoupdate=True)
|
||||
|
||||
# Check for changed category
|
||||
if old_category_id != new_category_id:
|
||||
motion.category_weight = 10000
|
||||
motion.save(skip_autoupdate=True)
|
||||
|
||||
inform_changed_data(motion)
|
||||
|
||||
return result
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import re
|
||||
from typing import List
|
||||
from typing import List, Set
|
||||
|
||||
import jsonschema
|
||||
from django.conf import settings
|
||||
@ -1158,6 +1158,7 @@ class CategoryViewSet(ModelViewSet):
|
||||
"partial_update",
|
||||
"update",
|
||||
"destroy",
|
||||
"sort",
|
||||
"numbering",
|
||||
):
|
||||
result = has_perm(self.request.user, "motions.can_see") and has_perm(
|
||||
@ -1167,6 +1168,52 @@ class CategoryViewSet(ModelViewSet):
|
||||
result = False
|
||||
return result
|
||||
|
||||
@detail_route(methods=["post"])
|
||||
@transaction.atomic
|
||||
def sort(self, request, pk=None):
|
||||
"""
|
||||
Endpoint to sort all motions in the category.
|
||||
|
||||
Send POST {'motions': [<list of motion ids>]} to sort the given
|
||||
motions in the given order. Ids of motions with another category or
|
||||
non existing motions are ignored, but all motions of this category
|
||||
have to be send.
|
||||
"""
|
||||
category = self.get_object()
|
||||
|
||||
ids = request.data.get("motions", None)
|
||||
if not isinstance(ids, list):
|
||||
raise ValidationError("The ids must be a list.")
|
||||
|
||||
motions = []
|
||||
motion_ids: Set[int] = set() # To detect duplicated
|
||||
for id in ids:
|
||||
if not isinstance(id, int):
|
||||
raise ValidationError("All ids must be int.")
|
||||
|
||||
if id in motion_ids:
|
||||
continue # Duplicate id
|
||||
|
||||
try:
|
||||
motion = Motion.objects.get(pk=id)
|
||||
except Motion.DoesNotExist:
|
||||
continue # Ignore invalid ids.
|
||||
|
||||
if motion.category is not None and motion.category.pk == category.pk:
|
||||
motions.append(motion)
|
||||
motion_ids.add(id)
|
||||
|
||||
if Motion.objects.filter(category=category).count() != len(motions):
|
||||
raise ValidationError("Not all motions for this category are given")
|
||||
|
||||
# assign the category_weight field:
|
||||
for weight, motion in enumerate(motions, start=1):
|
||||
motion.category_weight = weight
|
||||
motion.save(skip_autoupdate=True)
|
||||
|
||||
inform_changed_data(motions)
|
||||
return Response()
|
||||
|
||||
@detail_route(methods=["post"])
|
||||
def numbering(self, request, pk=None):
|
||||
"""
|
||||
|
@ -582,6 +582,26 @@ class UpdateMotion(TestCase):
|
||||
self.assertEqual(motion.title, "test_title_aeng7ahChie3waiR8xoh")
|
||||
self.assertEqual(motion.workflow_id, 2)
|
||||
|
||||
def test_patch_category(self):
|
||||
"""
|
||||
Tests to only update the category of a motion. Expects the
|
||||
category_weight to be resetted.
|
||||
"""
|
||||
category = Category.objects.create(
|
||||
name="test_category_name_FE3jO(Fm83doqqlwcvlv",
|
||||
prefix="test_prefix_w3ofg2mv79UGFqjk3f8h",
|
||||
)
|
||||
self.motion.category_weight = 1
|
||||
self.motion.save()
|
||||
response = self.client.patch(
|
||||
reverse("motion-detail", args=[self.motion.pk]),
|
||||
{"category_id": category.pk},
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
motion = Motion.objects.get()
|
||||
self.assertEqual(motion.category, category)
|
||||
self.assertEqual(motion.category_weight, 10000)
|
||||
|
||||
def test_patch_supporters(self):
|
||||
supporter = get_user_model().objects.create_user(
|
||||
username="test_username_ieB9eicah0uqu6Phoovo",
|
||||
|
Loading…
Reference in New Issue
Block a user