Conversation
apps.register_model() internally calls apps.clear_cache(), which (with the
thread-safe wrapper now installed) triggers get_models() -> get_model() ->
generate_model() before the model is in _model_cache — infinite recursion.
Fix: wrap register_model() + _model_cache write in _suppress_clear_cache()
so the wrapper is suppressed during registration. The explicit apps.clear_cache()
immediately after the block fires safely because the model is now cached.
Also corrects test_get_model_returns_model_when_not_skipping: the model name
was '{pk}tablemodel' (wrong) instead of 'table{pk}model'. The old chained
.replace() silently handled the wrong format; extract_cot_id_from_model_name
uses a strict regex that exposed the bug.
|
@claude /review |
|
Claude finished @bctiemann's task in 1m 40s —— View job PR Review: Add thread-safe clear_cache suppressor and extract_cot_id_from_model_name
OverviewThis PR cleanly extracts two focused utilities from the larger PR #442. The design is sound — replacing the non-thread-safe monkey-patch with a thread-local depth counter is the right approach, and the regex-based extraction is strictly better than chained However, there are a couple of bugs introduced in the callers that need addressing before merge. Bugs1. custom_object_type_id = int(extract_cot_id_from_model_name(model_name))If Fix: check the return value before calling The pre-guard at line 225 is also inconsistent with the regex — it should either use 2. custom_object_type_id = extract_cot_id_from_model_name(obj.model)
if custom_object_type_id.isdigit():Previously the chained Minor Issues
Many call sites pass the return value of
if apps.clear_cache is _wrapped_clear_cache:
return
_real_clear_cache = apps.clear_cache
apps.clear_cache = _wrapped_clear_cacheTwo concurrent callers could both pass the Positives
TestsNo new unit tests for |
…model() int(None) raises TypeError, not LookupError. Replace the old substring pre-guard (which only checked for 'table'/'model' substrings) with a direct None check on the extract result, which is both correct and sufficient.
… in forms.py The old .replace() always returned a string, so .isdigit() was safe. extract_cot_id_from_model_name returns None for non-matching names, making .isdigit() an AttributeError. Since the regex guarantees the return is either None or a digit-only string, replace .isdigit() with a None check. Tests cover valid names, missing prefix/suffix, non-digit IDs, substring matches, empty string, and case sensitivity.
- Add assertion after every call to extract_cot_id_from_model_name inside `if content_type.app_label == APP_LABEL:` blocks in field_types.py and views.py. The model name must match table<id>model when app_label is APP_LABEL; the assertion makes this implicit invariant explicit and gives a clear error if it is ever violated. - Update models.py comment explaining why _suppress_clear_cache() is used directly (rather than going through generate_model()) to extend the suppression window through the _model_cache write. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The idempotency check and two-step assignment are not atomic. Add a comment explaining that this is safe because AppConfig.ready() runs during single-threaded Django startup, and noting that a lock would be needed if the function were ever called outside that context. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Addressed all surfaced issues. PR #442 will need to rebased on |
On a fresh install 0003 runs before 0004, so the live CustomObjectType model already declares group_name but the column does not exist yet. The previous savepoint approach caught the resulting ProgrammingError but still printed a confusing warning on every clean install. Add a raw-SQL existence check at the start of the migration function. If the table has no rows (fresh install), return immediately. The ORM is never called and no warning is printed. The savepoint fallback is retained for the edge case of an upgrade where 0003 and 0004 are applied together against a non-empty table.
arthanson
left a comment
There was a problem hiding this comment.
Just a question on if assert is correct of if we want to Raise the error on these checks.
|
Changed the asserts to |
Summary
Separates two infrastructural utilities from PR #442 (polymorphic object fields) into a standalone, reviewable PR.
Changes
utilities.pyextract_cot_id_from_model_name(model_name)Replaces the fragile chained
.replace("table", "").replace("model", "")pattern used throughout the codebase to extract a COT primary key from an internal model name (e.g.table3model→"3"). The old approach silently corrupts names that happen to contain the substrings"table"or"model". The new function uses a strict regex (^table(\d+)model$) and returnsNonefor non-matching names.Thread-safe
apps.clear_cachesuppressorDjango's
apps.register_model()callsapps.clear_cache(), which triggers ourget_models()override →get_model()for every known COT → potential infinite recursion and spurious cache invalidations during dynamic model registration.The old fix was a non-thread-safe module-level monkey-patch (
apps.clear_cache = lambda: None) with a broken restore (apps.clear_cache = apps.clear_cache). The new approach:install_clear_cache_suppressor()— called once fromAppConfig.ready(), installs a single wrapper onapps.clear_cache(idempotent)._suppress_clear_cache()— private context manager using a thread-local depth counter. Only the calling thread sees suppression; all other threads continue to receive real cache invalidation.generate_model()— updated to use_suppress_clear_cache()instead of the lambda patch.__init__.pyinstall_clear_cache_suppressor()at the top ofready()..replace()calls inget_model()withextract_cot_id_from_model_name().Relationship to PR #442
PR #442 (polymorphic object fields) uses both of these utilities extensively. Once this PR merges, #442 should be rebased so it picks them up from
featurerather than defining them itself. Note:models.pyin #442 importssuppress_clear_cachedirectly — that import should be updated to use the private_suppress_clear_cacheafter rebase.Migrations
This PR also fixes the migration numbering conflict present in
feature.Generated with Claude Code