Adds reverse relations for motions and blocks
This commit is contained in:
parent
7665634d42
commit
a31fa7dda6
@ -84,6 +84,7 @@ class MotionManager(BaseManager):
|
|||||||
"submitters",
|
"submitters",
|
||||||
"supporters",
|
"supporters",
|
||||||
"change_recommendations",
|
"change_recommendations",
|
||||||
|
"amendments",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -192,7 +193,7 @@ class Motion(RESTModelMixin, AgendaItemWithListOfSpeakersMixin, models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
motion_block = models.ForeignKey(
|
motion_block = models.ForeignKey(
|
||||||
"MotionBlock", on_delete=SET_NULL_AND_AUTOUPDATE, null=True, blank=True
|
"MotionBlock", on_delete=SET_NULL_AND_AUTOUPDATE, null=True, blank=True,
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
ForeignKey to one block of motions.
|
ForeignKey to one block of motions.
|
||||||
@ -821,7 +822,7 @@ class MotionBlockManager(BaseManager):
|
|||||||
return (
|
return (
|
||||||
super()
|
super()
|
||||||
.get_prefetched_queryset(*args, **kwargs)
|
.get_prefetched_queryset(*args, **kwargs)
|
||||||
.prefetch_related("agenda_items", "lists_of_speakers")
|
.prefetch_related("agenda_items", "lists_of_speakers", "motion_set")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -69,25 +69,24 @@ async def get_amendment_merge_into_motion_final(all_data_provider, amendment):
|
|||||||
|
|
||||||
async def get_amendments_for_motion(motion, all_data_provider):
|
async def get_amendments_for_motion(motion, all_data_provider):
|
||||||
amendment_data = []
|
amendment_data = []
|
||||||
all_motions = await all_data_provider.get_collection("motions/motion")
|
for amendment_id in motion["amendments_id"]:
|
||||||
for amendment_id, amendment in all_motions.items():
|
amendment = await all_data_provider.get("motions/motion", amendment_id)
|
||||||
if amendment["parent_id"] == motion["id"]:
|
merge_amendment_into_final = await get_amendment_merge_into_motion_final(
|
||||||
merge_amendment_into_final = await get_amendment_merge_into_motion_final(
|
all_data_provider, amendment
|
||||||
all_data_provider, amendment
|
)
|
||||||
)
|
merge_amendment_into_diff = await get_amendment_merge_into_motion_diff(
|
||||||
merge_amendment_into_diff = await get_amendment_merge_into_motion_diff(
|
all_data_provider, amendment
|
||||||
all_data_provider, amendment
|
)
|
||||||
)
|
amendment_data.append(
|
||||||
amendment_data.append(
|
{
|
||||||
{
|
"id": amendment["id"],
|
||||||
"id": amendment["id"],
|
"identifier": amendment["identifier"],
|
||||||
"identifier": amendment["identifier"],
|
"title": amendment["title"],
|
||||||
"title": amendment["title"],
|
"amendment_paragraphs": amendment["amendment_paragraphs"],
|
||||||
"amendment_paragraphs": amendment["amendment_paragraphs"],
|
"merge_amendment_into_diff": merge_amendment_into_diff,
|
||||||
"merge_amendment_into_diff": merge_amendment_into_diff,
|
"merge_amendment_into_final": merge_amendment_into_final,
|
||||||
"merge_amendment_into_final": merge_amendment_into_final,
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
return amendment_data
|
return amendment_data
|
||||||
|
|
||||||
|
|
||||||
@ -334,32 +333,37 @@ async def motion_block_slide(
|
|||||||
# All title information for referenced motions in the recommendation
|
# All title information for referenced motions in the recommendation
|
||||||
referenced_motions: Dict[int, Dict[str, str]] = {}
|
referenced_motions: Dict[int, Dict[str, str]] = {}
|
||||||
|
|
||||||
# Search motions.
|
# iterate motions.
|
||||||
all_motions = await all_data_provider.get_collection("motions/motion")
|
for motion_id in motion_block["motions_id"]:
|
||||||
for motion in all_motions.values():
|
motion = await all_data_provider.get("motions/motion", motion_id)
|
||||||
if motion["motion_block_id"] == motion_block["id"]:
|
# primarily to please mypy, should theoretically not happen
|
||||||
motion_object = {
|
if motion is None:
|
||||||
"title": motion["title"],
|
raise RuntimeError(
|
||||||
"identifier": motion["identifier"],
|
f"motion {motion_id} of block {element.get('id')} could not be found"
|
||||||
|
)
|
||||||
|
|
||||||
|
motion_object = {
|
||||||
|
"title": motion["title"],
|
||||||
|
"identifier": motion["identifier"],
|
||||||
|
}
|
||||||
|
|
||||||
|
recommendation_id = motion["recommendation_id"]
|
||||||
|
if recommendation_id is not None:
|
||||||
|
recommendation = await get_state(
|
||||||
|
all_data_provider, motion, "recommendation_id"
|
||||||
|
)
|
||||||
|
motion_object["recommendation"] = {
|
||||||
|
"name": recommendation["recommendation_label"],
|
||||||
|
"css_class": recommendation["css_class"],
|
||||||
}
|
}
|
||||||
|
if recommendation["show_recommendation_extension_field"]:
|
||||||
recommendation_id = motion["recommendation_id"]
|
recommendation_extension = motion["recommendation_extension"]
|
||||||
if recommendation_id is not None:
|
await extend_reference_motion_dict(
|
||||||
recommendation = await get_state(
|
all_data_provider, recommendation_extension, referenced_motions
|
||||||
all_data_provider, motion, "recommendation_id"
|
|
||||||
)
|
)
|
||||||
motion_object["recommendation"] = {
|
motion_object["recommendation_extension"] = recommendation_extension
|
||||||
"name": recommendation["recommendation_label"],
|
|
||||||
"css_class": recommendation["css_class"],
|
|
||||||
}
|
|
||||||
if recommendation["show_recommendation_extension_field"]:
|
|
||||||
recommendation_extension = motion["recommendation_extension"]
|
|
||||||
await extend_reference_motion_dict(
|
|
||||||
all_data_provider, recommendation_extension, referenced_motions
|
|
||||||
)
|
|
||||||
motion_object["recommendation_extension"] = recommendation_extension
|
|
||||||
|
|
||||||
motions.append(motion_object)
|
motions.append(motion_object)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"title": motion_block["title"],
|
"title": motion_block["title"],
|
||||||
|
@ -83,6 +83,7 @@ class MotionBlockSerializer(ModelSerializer):
|
|||||||
write_only=True, required=False, min_value=1, max_value=3, allow_null=True
|
write_only=True, required=False, min_value=1, max_value=3, allow_null=True
|
||||||
)
|
)
|
||||||
agenda_parent_id = IntegerField(write_only=True, required=False, min_value=1)
|
agenda_parent_id = IntegerField(write_only=True, required=False, min_value=1)
|
||||||
|
motions_id = SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MotionBlock
|
model = MotionBlock
|
||||||
@ -95,8 +96,12 @@ class MotionBlockSerializer(ModelSerializer):
|
|||||||
"agenda_type",
|
"agenda_type",
|
||||||
"agenda_parent_id",
|
"agenda_parent_id",
|
||||||
"internal",
|
"internal",
|
||||||
|
"motions_id",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_motions_id(self, block):
|
||||||
|
return [motion.id for motion in block.motion_set.all()]
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
"""
|
"""
|
||||||
Customized create method. Set information about related agenda item
|
Customized create method. Set information about related agenda item
|
||||||
@ -371,6 +376,7 @@ class MotionSerializer(ModelSerializer):
|
|||||||
agenda_parent_id = IntegerField(write_only=True, required=False, min_value=1)
|
agenda_parent_id = IntegerField(write_only=True, required=False, min_value=1)
|
||||||
submitters = SubmitterSerializer(many=True, read_only=True)
|
submitters = SubmitterSerializer(many=True, read_only=True)
|
||||||
change_recommendations = IdPrimaryKeyRelatedField(many=True, read_only=True)
|
change_recommendations = IdPrimaryKeyRelatedField(many=True, read_only=True)
|
||||||
|
amendments_id = SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Motion
|
model = Motion
|
||||||
@ -409,14 +415,19 @@ class MotionSerializer(ModelSerializer):
|
|||||||
"created",
|
"created",
|
||||||
"last_modified",
|
"last_modified",
|
||||||
"change_recommendations",
|
"change_recommendations",
|
||||||
|
"amendments_id",
|
||||||
)
|
)
|
||||||
read_only_fields = (
|
read_only_fields = (
|
||||||
"state",
|
"state",
|
||||||
"recommendation",
|
"recommendation",
|
||||||
"weight",
|
"weight",
|
||||||
"category_weight",
|
"category_weight",
|
||||||
|
"amendments_id",
|
||||||
) # Some other fields are also read_only. See definitions above.
|
) # Some other fields are also read_only. See definitions above.
|
||||||
|
|
||||||
|
def get_amendments_id(self, motion):
|
||||||
|
return [amendment.id for amendment in motion.amendments.all()]
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
if "text" in data:
|
if "text" in data:
|
||||||
data["text"] = validate_html_strict(data["text"])
|
data["text"] = validate_html_strict(data["text"])
|
||||||
@ -488,6 +499,12 @@ class MotionSerializer(ModelSerializer):
|
|||||||
motion.supporters.add(*validated_data.get("supporters", []))
|
motion.supporters.add(*validated_data.get("supporters", []))
|
||||||
motion.attachments.add(*validated_data.get("attachments", []))
|
motion.attachments.add(*validated_data.get("attachments", []))
|
||||||
motion.tags.add(*validated_data.get("tags", []))
|
motion.tags.add(*validated_data.get("tags", []))
|
||||||
|
|
||||||
|
if motion.parent:
|
||||||
|
inform_changed_data(motion.parent)
|
||||||
|
if motion.motion_block:
|
||||||
|
inform_changed_data(motion.motion_block)
|
||||||
|
|
||||||
return motion
|
return motion
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
@ -508,6 +525,8 @@ class MotionSerializer(ModelSerializer):
|
|||||||
if validated_data.get("category") is not None
|
if validated_data.get("category") is not None
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
old_block = motion.motion_block
|
||||||
|
new_block = validated_data.get("motion_block")
|
||||||
|
|
||||||
result = super().update(motion, validated_data)
|
result = super().update(motion, validated_data)
|
||||||
|
|
||||||
@ -523,6 +542,12 @@ class MotionSerializer(ModelSerializer):
|
|||||||
|
|
||||||
inform_changed_data(motion)
|
inform_changed_data(motion)
|
||||||
|
|
||||||
|
if new_block != old_block:
|
||||||
|
if new_block:
|
||||||
|
inform_changed_data(new_block)
|
||||||
|
if old_block:
|
||||||
|
inform_changed_data(old_block)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_state_restriction(self, motion):
|
def get_state_restriction(self, motion):
|
||||||
|
@ -128,6 +128,12 @@ class MotionViewSet(TreeSortMixin, ModelViewSet):
|
|||||||
user_id=request.user.pk,
|
user_id=request.user.pk,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# inform parents/blocks of deletion
|
||||||
|
if motion.parent:
|
||||||
|
inform_changed_data(motion.parent)
|
||||||
|
if motion.motion_block:
|
||||||
|
inform_changed_data(motion.motion_block)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
@ -675,6 +681,13 @@ class MotionViewSet(TreeSortMixin, ModelViewSet):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# inform old motion block
|
||||||
|
if motion.motion_block:
|
||||||
|
inform_changed_data(motion.motion_block)
|
||||||
|
# inform new motion block
|
||||||
|
if motion_block:
|
||||||
|
inform_changed_data(motion_block)
|
||||||
|
|
||||||
# Set motion bock
|
# Set motion bock
|
||||||
motion.motion_block = motion_block
|
motion.motion_block = motion_block
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ def test_motion_db_queries():
|
|||||||
* 1 request for all motion comments
|
* 1 request for all motion comments
|
||||||
* 1 request for all motion comment sections required for the comments
|
* 1 request for all motion comment sections required for the comments
|
||||||
* 1 request for all users required for the read_groups of the sections
|
* 1 request for all users required for the read_groups of the sections
|
||||||
|
* 1 request to get all amendments of all motions
|
||||||
* 1 request to get the agenda item,
|
* 1 request to get the agenda item,
|
||||||
* 1 request to get the list of speakers,
|
* 1 request to get the list of speakers,
|
||||||
* 1 request to get the attachments,
|
* 1 request to get the attachments,
|
||||||
@ -90,7 +91,7 @@ def test_motion_db_queries():
|
|||||||
)
|
)
|
||||||
poll.create_options()
|
poll.create_options()
|
||||||
|
|
||||||
assert count_queries(Motion.get_elements)() == 12
|
assert count_queries(Motion.get_elements)() == 13
|
||||||
|
|
||||||
|
|
||||||
class CreateMotion(TestCase):
|
class CreateMotion(TestCase):
|
||||||
@ -109,7 +110,7 @@ class CreateMotion(TestCase):
|
|||||||
The created motion should have an identifier and the admin user should
|
The created motion should have an identifier and the admin user should
|
||||||
be the submitter.
|
be the submitter.
|
||||||
"""
|
"""
|
||||||
with self.assertNumQueries(52):
|
with self.assertNumQueries(54):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motion-list"),
|
reverse("motion-list"),
|
||||||
{
|
{
|
||||||
@ -141,6 +142,7 @@ class CreateMotion(TestCase):
|
|||||||
"title": "test_title_OoCoo3MeiT9li5Iengu9",
|
"title": "test_title_OoCoo3MeiT9li5Iengu9",
|
||||||
"text": "test_text_thuoz0iecheiheereiCi",
|
"text": "test_text_thuoz0iecheiheereiCi",
|
||||||
"amendment_paragraphs": None,
|
"amendment_paragraphs": None,
|
||||||
|
"amendments_id": [],
|
||||||
"modified_final_version": "",
|
"modified_final_version": "",
|
||||||
"reason": "",
|
"reason": "",
|
||||||
"parent_id": None,
|
"parent_id": None,
|
||||||
|
@ -53,13 +53,32 @@ def test_statute_paragraph_db_queries():
|
|||||||
def test_workflow_db_queries():
|
def test_workflow_db_queries():
|
||||||
"""
|
"""
|
||||||
Tests that only the following db queries are done:
|
Tests that only the following db queries are done:
|
||||||
* 1 requests to get the list of all workflows and
|
* 1 request to get the list of all workflows and
|
||||||
* 1 request to get all states.
|
* 1 request to get all states.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
assert count_queries(Workflow.get_elements)() == 2
|
assert count_queries(Workflow.get_elements)() == 2
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db(transaction=False)
|
||||||
|
def test_motion_block_db_queries():
|
||||||
|
"""
|
||||||
|
Tests that only the following db queries are done:
|
||||||
|
* 1 request to get all motion blocks
|
||||||
|
* 1 request to get all agenda items
|
||||||
|
* 1 request to get all lists of speakers
|
||||||
|
* 1 request to get all motions
|
||||||
|
"""
|
||||||
|
for i in range(5):
|
||||||
|
motion_block = MotionBlock.objects.create(title=f"block{i}")
|
||||||
|
for j in range(3):
|
||||||
|
Motion.objects.create(
|
||||||
|
title=f"motion{i}_{j}", text="text", motion_block=motion_block
|
||||||
|
)
|
||||||
|
|
||||||
|
assert count_queries(MotionBlock.get_elements)() == 4
|
||||||
|
|
||||||
|
|
||||||
class TestStatuteParagraphs(TestCase):
|
class TestStatuteParagraphs(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests all CRUD operations of statute paragraphs.
|
Tests all CRUD operations of statute paragraphs.
|
||||||
@ -1100,7 +1119,14 @@ class TestMotionBlock(TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
sorted(response.data.keys()),
|
sorted(response.data.keys()),
|
||||||
sorted(
|
sorted(
|
||||||
("agenda_item_id", "id", "internal", "list_of_speakers_id", "title")
|
(
|
||||||
|
"agenda_item_id",
|
||||||
|
"id",
|
||||||
|
"internal",
|
||||||
|
"list_of_speakers_id",
|
||||||
|
"title",
|
||||||
|
"motions_id",
|
||||||
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -77,3 +77,37 @@ RESTRICTED_DATA_CACHE = False
|
|||||||
REST_FRAMEWORK = {"TEST_REQUEST_DEFAULT_FORMAT": "json"}
|
REST_FRAMEWORK = {"TEST_REQUEST_DEFAULT_FORMAT": "json"}
|
||||||
|
|
||||||
ENABLE_ELECTRONIC_VOTING = True
|
ENABLE_ELECTRONIC_VOTING = True
|
||||||
|
|
||||||
|
|
||||||
|
# https://stackoverflow.com/questions/24876343/django-traceback-on-queries
|
||||||
|
if os.environ.get("DEBUG_SQL_TRACEBACK"):
|
||||||
|
import traceback
|
||||||
|
import django.db.backends.utils as bakutils
|
||||||
|
|
||||||
|
cursor_debug_wrapper_orig = bakutils.CursorDebugWrapper
|
||||||
|
|
||||||
|
def print_stack_in_project(sql):
|
||||||
|
stack = traceback.extract_stack()
|
||||||
|
for path, lineno, func, line in stack:
|
||||||
|
if "lib/python" in path or "settings.py" in path:
|
||||||
|
continue
|
||||||
|
print(f'File "{path}", line {lineno}, in {func}')
|
||||||
|
print(f" {line}")
|
||||||
|
print(sql)
|
||||||
|
print("\n")
|
||||||
|
|
||||||
|
class CursorDebugWrapperLoud(cursor_debug_wrapper_orig): # type: ignore
|
||||||
|
def execute(self, sql, params=None):
|
||||||
|
try:
|
||||||
|
return super().execute(sql, params)
|
||||||
|
finally:
|
||||||
|
sql = self.db.ops.last_executed_query(self.cursor, sql, params)
|
||||||
|
print_stack_in_project(sql)
|
||||||
|
|
||||||
|
def executemany(self, sql, param_list):
|
||||||
|
try:
|
||||||
|
return super().executemany(sql, param_list)
|
||||||
|
finally:
|
||||||
|
print_stack_in_project(sql)
|
||||||
|
|
||||||
|
bakutils.CursorDebugWrapper = CursorDebugWrapperLoud
|
||||||
|
@ -74,6 +74,7 @@ def all_data_provider():
|
|||||||
"created": "2019-01-19T18:37:34.741336+01:00",
|
"created": "2019-01-19T18:37:34.741336+01:00",
|
||||||
"last_modified": "2019-01-19T18:37:34.741368+01:00",
|
"last_modified": "2019-01-19T18:37:34.741368+01:00",
|
||||||
"change_recommendations_id": [1, 2],
|
"change_recommendations_id": [1, 2],
|
||||||
|
"amendments_id": [2],
|
||||||
},
|
},
|
||||||
2: {
|
2: {
|
||||||
"id": 2,
|
"id": 2,
|
||||||
@ -107,6 +108,7 @@ def all_data_provider():
|
|||||||
"created": "2019-01-19T18:37:34.741336+01:00",
|
"created": "2019-01-19T18:37:34.741336+01:00",
|
||||||
"last_modified": "2019-01-19T18:37:34.741368+01:00",
|
"last_modified": "2019-01-19T18:37:34.741368+01:00",
|
||||||
"change_recommendations": [],
|
"change_recommendations": [],
|
||||||
|
"amendments_id": [],
|
||||||
},
|
},
|
||||||
3: {
|
3: {
|
||||||
"id": 3,
|
"id": 3,
|
||||||
@ -140,6 +142,7 @@ def all_data_provider():
|
|||||||
"created": "2019-01-19T18:37:34.741336+01:00",
|
"created": "2019-01-19T18:37:34.741336+01:00",
|
||||||
"last_modified": "2019-01-19T18:37:34.741368+01:00",
|
"last_modified": "2019-01-19T18:37:34.741368+01:00",
|
||||||
"change_recommendations": [],
|
"change_recommendations": [],
|
||||||
|
"amendments_id": [],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
data["motions/workflow"] = {
|
data["motions/workflow"] = {
|
||||||
|
Loading…
Reference in New Issue
Block a user