Performance: Increases workflow related M2M prefetching (#12618)

This commit is contained in:
Trenton H
2026-04-21 15:01:51 -07:00
committed by GitHub
parent c669c3416e
commit 89a9e7f190
3 changed files with 78 additions and 18 deletions

View File

@@ -3352,13 +3352,13 @@ class WorkflowSerializer(serializers.ModelSerializer[Workflow]):
ManyToMany fields dont support e.g. on_delete so we need to discard unattached
triggers and actions manually
"""
for trigger in WorkflowTrigger.objects.all():
if trigger.workflows.all().count() == 0:
trigger.delete()
WorkflowTrigger.objects.annotate(
workflow_count=Count("workflows"),
).filter(workflow_count=0).delete()
for action in WorkflowAction.objects.all():
if action.workflows.all().count() == 0:
action.delete()
WorkflowAction.objects.annotate(
workflow_count=Count("workflows"),
).filter(workflow_count=0).delete()
WorkflowActionEmail.objects.filter(action=None).delete()
WorkflowActionWebhook.objects.filter(action=None).delete()
@@ -3387,16 +3387,6 @@ class WorkflowSerializer(serializers.ModelSerializer[Workflow]):
return instance
def to_representation(self, instance: Workflow) -> dict[str, Any]:
data = super().to_representation(instance)
actions = instance.actions.order_by("order", "pk")
data["actions"] = WorkflowActionSerializer(
actions,
many=True,
context=self.context,
).data
return data
class TrashSerializer(SerializerWithPerms):
documents = serializers.ListField(

View File

@@ -99,6 +99,40 @@ class TestApiWorkflows(DirectoriesMixin, APITestCase):
self.action.assign_correspondent.pk,
)
def test_api_get_workflow_actions_ordered(self) -> None:
"""
GIVEN:
- A workflow with two actions added in reverse order (order=1 before order=0)
WHEN:
- API is called to get workflows
THEN:
- Actions are returned sorted by order ascending
"""
# Created before action_first so its pk is lower — ensures pk order
# disagrees with the order field, catching regressions if order_by is removed.
action_second = WorkflowAction.objects.create(
assign_title="Second action",
order=1,
)
action_first = WorkflowAction.objects.create(
assign_title="First action",
order=0,
)
self.workflow.actions.add(action_second)
self.workflow.actions.add(action_first)
response = self.client.get(self.ENDPOINT, format="json")
self.assertEqual(response.status_code, status.HTTP_200_OK)
resp_actions = response.data["results"][0]["actions"]
action_ids = [a["id"] for a in resp_actions]
self.assertIn(action_first.id, action_ids)
self.assertIn(action_second.id, action_ids)
self.assertLess(
action_ids.index(action_first.id),
action_ids.index(action_second.id),
)
def test_api_create_workflow(self) -> None:
"""
GIVEN:

View File

@@ -4485,8 +4485,44 @@ class WorkflowViewSet(ModelViewSet[Workflow]):
Workflow.objects.all()
.order_by("order")
.prefetch_related(
"triggers",
"actions",
Prefetch(
"triggers",
queryset=WorkflowTrigger.objects.prefetch_related(
"filter_has_tags",
"filter_has_all_tags",
"filter_has_not_tags",
"filter_has_any_correspondents",
"filter_has_not_correspondents",
"filter_has_any_document_types",
"filter_has_not_document_types",
"filter_has_any_storage_paths",
"filter_has_not_storage_paths",
),
),
Prefetch(
"actions",
queryset=WorkflowAction.objects.order_by(
"order",
"pk",
).prefetch_related(
"assign_tags",
"assign_view_users",
"assign_view_groups",
"assign_change_users",
"assign_change_groups",
"assign_custom_fields",
"remove_tags",
"remove_correspondents",
"remove_document_types",
"remove_storage_paths",
"remove_custom_fields",
"remove_owners",
"remove_view_users",
"remove_view_groups",
"remove_change_users",
"remove_change_groups",
),
),
)
)