Skip to content

Commit c09b987

Browse files
committed
fix: eliminate redundant workflow DB queries and admin list N+1 (v0.37.14)
tasks.py: - send_rejection/approval/submission/withdrawal_notification: read hide_approval_history from already-resolved local workflow var instead of calling _get_hide_approval_history(submission) which issued a duplicate workflows.first() DB query - send_submission_form_field_notifications: resolve workflow once before the notification loop; pre-compute hide_approval_history - send_workflow_definition_notifications: same pattern, resolve workflow and hide_approval_history once before the loop admin.py: - WorkflowNotificationAdmin: add list_select_related = (workflow__form_definition,) to eliminate the 2-query N+1 on the list page caused by workflow_form() traversing the FK chain Bump 0.37.13 to 0.37.14
1 parent 0572ec6 commit c09b987

4 files changed

Lines changed: 29 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.37.14] - 2026-03-27
11+
12+
### Fixed
13+
- **Double `workflow` DB query in 4 notification tasks**`send_rejection_notification`, `send_approval_notification`, `send_submission_notification`, and `send_withdrawal_notification` each already resolve `workflow = getattr(submission.form_definition, "workflow", None)` for the toggle check. The subsequent `_get_hide_approval_history(submission)` call was redundantly calling `workflows.first()` a second time. The flag is now read directly from the local `workflow` variable with `bool(getattr(workflow, "hide_approval_history", False))`.
14+
- **Extra `workflow` fetch in `send_submission_form_field_notifications` and `send_workflow_definition_notifications`** — neither task had a local `workflow` variable, so every iteration of their notification loop called `_get_hide_approval_history(submission)``workflows.first()`. Both tasks now resolve `workflow` once at the top and pre-compute `hide_approval_history` before the loop; the per-notif context dict reads the pre-computed value.
15+
- **`WorkflowNotificationAdmin` list page N+1**`workflow_form()` accessed `obj.workflow.form_definition.name` without any join, causing 2 extra queries per row. Added `list_select_related = ("workflow__form_definition",)` so the list page is served in a single query.
16+
1017
## [0.37.13] - 2026-03-27
1118

1219
### Fixed

django_forms_workflows/admin.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,7 @@ class WorkflowNotificationAdmin(admin.ModelAdmin):
15361536
"subject_template_truncated",
15371537
)
15381538
list_filter = ("notification_type", "workflow__form_definition")
1539+
list_select_related = ("workflow__form_definition",)
15391540
search_fields = (
15401541
"workflow__form_definition__name",
15411542
"email_field",

django_forms_workflows/tasks.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,9 @@ def send_rejection_notification(submission_id: int) -> None:
329329
"submission": submission,
330330
"task": task,
331331
"submission_url": submission_url,
332-
"hide_approval_history": _get_hide_approval_history(submission),
332+
"hide_approval_history": bool(
333+
getattr(workflow, "hide_approval_history", False)
334+
),
333335
}
334336
subject = (
335337
f"Submission Rejected: {submission.form_definition.name} (ID {submission.id})"
@@ -374,7 +376,9 @@ def send_approval_notification(submission_id: int) -> None:
374376
context = {
375377
"submission": submission,
376378
"submission_url": submission_url,
377-
"hide_approval_history": _get_hide_approval_history(submission),
379+
"hide_approval_history": bool(
380+
getattr(workflow, "hide_approval_history", False)
381+
),
378382
}
379383
subject = (
380384
f"Submission Approved: {submission.form_definition.name} (ID {submission.id})"
@@ -417,7 +421,9 @@ def send_submission_notification(submission_id: int) -> None:
417421
context = {
418422
"submission": submission,
419423
"submission_url": submission_url,
420-
"hide_approval_history": _get_hide_approval_history(submission),
424+
"hide_approval_history": bool(
425+
getattr(workflow, "hide_approval_history", False)
426+
),
421427
}
422428
subject = (
423429
f"Submission Received: {submission.form_definition.name} (ID {submission.id})"
@@ -955,6 +961,9 @@ def send_submission_form_field_notifications(
955961
form_data = submission.form_data or {}
956962
form_name = submission.form_definition.name
957963
submission_url, _ = _build_form_field_notification_context(submission)
964+
# Resolve once — reused for hide_approval_history below.
965+
workflow = getattr(submission.form_definition, "workflow", None)
966+
hide_approval_history = bool(getattr(workflow, "hide_approval_history", False))
958967

959968
notifications = StageFormFieldNotification.objects.filter(
960969
stage__workflow__form_definition=submission.form_definition,
@@ -997,7 +1006,7 @@ def send_submission_form_field_notifications(
9971006
context = {
9981007
"submission": submission,
9991008
"submission_url": submission_url,
1000-
"hide_approval_history": _get_hide_approval_history(submission),
1009+
"hide_approval_history": hide_approval_history,
10011010
}
10021011
_send_html_email(
10031012
subject,
@@ -1029,7 +1038,9 @@ def send_withdrawal_notification(submission_id: int) -> None:
10291038
context = {
10301039
"submission": submission,
10311040
"submission_url": submission_url,
1032-
"hide_approval_history": _get_hide_approval_history(submission),
1041+
"hide_approval_history": bool(
1042+
getattr(workflow, "hide_approval_history", False)
1043+
),
10331044
}
10341045
subject = (
10351046
f"Submission Withdrawn: {submission.form_definition.name} (ID {submission.id})"
@@ -1094,6 +1105,9 @@ def send_workflow_definition_notifications(
10941105
form_data = submission.form_data or {}
10951106
form_name = submission.form_definition.name
10961107
submission_url, _ = _build_form_field_notification_context(submission)
1108+
# Resolve once — reused for hide_approval_history in every email context below.
1109+
workflow = getattr(submission.form_definition, "workflow", None)
1110+
hide_approval_history = bool(getattr(workflow, "hide_approval_history", False))
10971111

10981112
default_subjects = {
10991113
"submission_received": f"Submission Received: {form_name} (ID {submission.id})",
@@ -1150,7 +1164,7 @@ def send_workflow_definition_notifications(
11501164
context = {
11511165
"submission": submission,
11521166
"submission_url": submission_url,
1153-
"hide_approval_history": _get_hide_approval_history(submission),
1167+
"hide_approval_history": hide_approval_history,
11541168
}
11551169

11561170
# Send one email per recipient so each message is independent

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "django-forms-workflows"
3-
version = "0.37.13"
3+
version = "0.37.14"
44
description = "Enterprise-grade, database-driven form builder with approval workflows and external data integration"
55
license = "LGPL-3.0-only"
66
readme = "README.md"

0 commit comments

Comments
 (0)