Skip to content

Commit 1a4e7a8

Browse files
pghistory: add finding.reviewers to tracked models
1 parent 94fb6de commit 1a4e7a8

4 files changed

Lines changed: 91 additions & 20 deletions

File tree

dojo/auditlog.py

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -220,26 +220,6 @@ def register_django_pghistory_models():
220220
},
221221
)(Finding)
222222

223-
# # Track the reviewers ManyToMany relationship through table
224-
# # This tracks additions/removals of reviewers from findings
225-
# reviewers_through = Finding._meta.get_field("reviewers").remote_field.through
226-
# if reviewers_through:
227-
# logger.info(f"Tracking reviewers M2M through table: {reviewers_through} (db_table: {reviewers_through._meta.db_table})")
228-
# pghistory.track(
229-
# pghistory.InsertEvent(),
230-
# pghistory.DeleteEvent(),
231-
# meta={
232-
# "indexes": [
233-
# models.Index(fields=["pgh_created_at"]),
234-
# models.Index(fields=["pgh_label"]),
235-
# models.Index(fields=["pgh_context_id"]),
236-
# ],
237-
# },
238-
# )(reviewers_through)
239-
# logger.info("Successfully registered pghistory tracking for reviewers through table")
240-
# else:
241-
# logger.warning("Could not find reviewers through table for Finding model!")
242-
243223
pghistory.track(
244224
pghistory.InsertEvent(),
245225
pghistory.UpdateEvent(condition=pghistory.AnyChange(exclude_auto=True)),
@@ -354,6 +334,31 @@ def register_django_pghistory_models():
354334
},
355335
)(Notification_Webhooks)
356336

337+
# Track Finding.reviewers ManyToMany relationship
338+
# Create a proxy model for the through table as per pghistory docs:
339+
# https://django-pghistory.readthedocs.io/en/2.4.2/tutorial.html#tracking-many-to-many-events
340+
# Note: For auto-generated through models, we don't specify obj_fk/obj_field
341+
# as Django doesn't allow foreign keys to auto-generated through models
342+
reviewers_through = Finding._meta.get_field("reviewers").remote_field.through
343+
344+
class FindingReviewers(reviewers_through):
345+
class Meta:
346+
proxy = True
347+
348+
pghistory.track(
349+
pghistory.InsertEvent(),
350+
pghistory.DeleteEvent(),
351+
pghistory.ManualEvent(label="initial_import"),
352+
meta={
353+
"db_table": "dojo_finding_reviewersevent",
354+
"indexes": [
355+
models.Index(fields=["pgh_created_at"]),
356+
models.Index(fields=["pgh_label"]),
357+
models.Index(fields=["pgh_context_id"]),
358+
],
359+
},
360+
)(FindingReviewers)
361+
357362
# Only log during actual application startup, not during shell commands
358363
if "shell" not in sys.argv:
359364
logger.info("Successfully registered models with django-pghistory")
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Generated by Django 5.1.13 on 2025-11-01 17:04
2+
3+
import django.db.models.deletion
4+
import pgtrigger.compiler
5+
import pgtrigger.migrations
6+
from django.db import migrations, models
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
("dojo", "0246_endpoint_idx_ep_product_lower_host_and_more")
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name="FindingReviewers",
18+
fields=[
19+
],
20+
options={
21+
"proxy": True,
22+
"indexes": [],
23+
"constraints": [],
24+
},
25+
bases=("dojo.finding_reviewers",),
26+
),
27+
migrations.CreateModel(
28+
name="FindingReviewersEvent",
29+
fields=[
30+
("pgh_id", models.AutoField(primary_key=True, serialize=False)),
31+
("pgh_created_at", models.DateTimeField(auto_now_add=True)),
32+
("pgh_label", models.TextField(help_text="The event label.")),
33+
("id", models.IntegerField()),
34+
("dojo_user", models.ForeignKey(db_constraint=False, db_index=False, db_tablespace="", on_delete=django.db.models.deletion.DO_NOTHING, related_name="+", related_query_name="+", to="dojo.dojo_user")),
35+
("finding", models.ForeignKey(db_constraint=False, db_index=False, db_tablespace="", on_delete=django.db.models.deletion.DO_NOTHING, related_name="+", related_query_name="+", to="dojo.finding")),
36+
("pgh_context", models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name="+", to="pghistory.context")),
37+
("pgh_obj", models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, related_name="events", to="dojo.findingreviewers")),
38+
],
39+
options={
40+
"abstract": False,
41+
"db_table": "dojo_finding_reviewersevent",
42+
},
43+
),
44+
migrations.AddIndex(
45+
model_name="findingreviewersevent",
46+
index=models.Index(fields=["pgh_created_at"], name="dojo_findin_pgh_cre_d5e5b4_idx"),
47+
),
48+
migrations.AddIndex(
49+
model_name="findingreviewersevent",
50+
index=models.Index(fields=["pgh_label"], name="dojo_findin_pgh_lab_5517f9_idx"),
51+
),
52+
migrations.AddIndex(
53+
model_name="findingreviewersevent",
54+
index=models.Index(fields=["pgh_context_id"], name="dojo_findin_pgh_con_06229b_idx"),
55+
),
56+
pgtrigger.migrations.AddTrigger(
57+
model_name="findingreviewers",
58+
trigger=pgtrigger.compiler.Trigger(name="insert_insert", sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "dojo_finding_reviewersevent" ("dojo_user_id", "finding_id", "id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id") VALUES (NEW."dojo_user_id", NEW."finding_id", NEW."id", _pgh_attach_context(), NOW(), \'insert\', NEW."id"); RETURN NULL;', hash="5c1fd440159e49c929122cbb590f96983a1c934e", operation="INSERT", pgid="pgtrigger_insert_insert_0808c", table="dojo_finding_reviewers", when="AFTER")),
59+
),
60+
pgtrigger.migrations.AddTrigger(
61+
model_name="findingreviewers",
62+
trigger=pgtrigger.compiler.Trigger(name="delete_delete", sql=pgtrigger.compiler.UpsertTriggerSql(func='INSERT INTO "dojo_finding_reviewersevent" ("dojo_user_id", "finding_id", "id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id") VALUES (OLD."dojo_user_id", OLD."finding_id", OLD."id", _pgh_attach_context(), NOW(), \'delete\', OLD."id"); RETURN NULL;', hash="23a4e01eaea469f708679392a6a92a6e16b21181", operation="DELETE", pgid="pgtrigger_delete_delete_40083", table="dojo_finding_reviewers", when="AFTER")),
63+
),
64+
]

dojo/management/commands/pghistory_backfill.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ def handle(self, *args, **options):
164164
"Dojo_User", "Endpoint", "Engagement", "Finding", "Finding_Group",
165165
"Product_Type", "Product", "Test", "Risk_Acceptance",
166166
"Finding_Template", "Cred_User", "Notification_Webhooks",
167+
"FindingReviewers", # M2M through table for Finding.reviewers
167168
]
168169

169170
specific_model = options.get("model")

dojo/management/commands/pghistory_backfill_fast.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ def handle(self, *args, **options):
490490
"Dojo_User", "Endpoint", "Engagement", "Finding", "Finding_Group",
491491
"Product_Type", "Product", "Test", "Risk_Acceptance",
492492
"Finding_Template", "Cred_User", "Notification_Webhooks",
493+
"FindingReviewers", # M2M through table for Finding.reviewers
493494
]
494495

495496
specific_model = options.get("model")

0 commit comments

Comments
 (0)