|
18 | 18 |
|
19 | 19 | logger = logging.getLogger(__name__) |
20 | 20 |
|
| 21 | +# FindingReviewers proxy model will be created lazily in register_django_pghistory_models() |
| 22 | +# Cannot be defined at module level because Finding.reviewers.through requires |
| 23 | +# Django's app registry to be ready (AppRegistryNotReady error) |
| 24 | +# The function is called from DojoAppConfig.ready() which guarantees the registry is ready |
| 25 | + |
21 | 26 |
|
22 | 27 | def _flush_models_in_batches(models_to_flush, timestamp_field: str, retention_period: int, batch_size: int, max_batches: int, *, dry_run: bool = False) -> tuple[int, int, bool]: |
23 | 28 | """ |
@@ -339,25 +344,41 @@ def register_django_pghistory_models(): |
339 | 344 | # https://django-pghistory.readthedocs.io/en/2.4.2/tutorial.html#tracking-many-to-many-events |
340 | 345 | # Note: For auto-generated through models, we don't specify obj_fk/obj_field |
341 | 346 | # 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) |
| 347 | + # |
| 348 | + # We must create the proxy model here (not at module level) because: |
| 349 | + # 1. Finding.reviewers.through requires Django's app registry to be ready |
| 350 | + # 2. This function is called from DojoAppConfig.ready() which guarantees registry is ready |
| 351 | + # 3. We check if it already exists to avoid re-registration warnings |
| 352 | + # |
| 353 | + # Note: This pattern is not explicitly documented in Django's official documentation. |
| 354 | + # Django docs mention AppRegistryNotReady and AppConfig.ready() in general terms, but |
| 355 | + # don't specifically cover proxy models for auto-generated ManyToMany through tables. |
| 356 | + # This is a common pattern used by libraries like django-pghistory and is necessary |
| 357 | + # because accessing Model.field.through at module import time triggers AppRegistryNotReady. |
| 358 | + try: |
| 359 | + FindingReviewers = apps.get_model("dojo", "FindingReviewers") |
| 360 | + except LookupError: |
| 361 | + # Model doesn't exist yet, create it |
| 362 | + # Note: Finding is imported above, and apps registry is ready when this runs |
| 363 | + reviewers_through = Finding._meta.get_field("reviewers").remote_field.through |
| 364 | + |
| 365 | + class FindingReviewers(reviewers_through): |
| 366 | + class Meta: |
| 367 | + proxy = True |
| 368 | + |
| 369 | + pghistory.track( |
| 370 | + pghistory.InsertEvent(), |
| 371 | + pghistory.DeleteEvent(), |
| 372 | + pghistory.ManualEvent(label="initial_import"), |
| 373 | + meta={ |
| 374 | + "db_table": "dojo_finding_reviewersevent", |
| 375 | + "indexes": [ |
| 376 | + models.Index(fields=["pgh_created_at"]), |
| 377 | + models.Index(fields=["pgh_label"]), |
| 378 | + models.Index(fields=["pgh_context_id"]), |
| 379 | + ], |
| 380 | + }, |
| 381 | + )(FindingReviewers) |
361 | 382 |
|
362 | 383 | # Only log during actual application startup, not during shell commands |
363 | 384 | if "shell" not in sys.argv: |
|
0 commit comments