Fixes #20587: Handle stale ContentTypes in has_feature() #1055

Closed
opened 2026-04-05 20:06:57 +02:00 by MrUnknownDE · 0 comments
Owner

Originally created by @jnovinger on 10/15/2025

Fixes: #20587

Fixes #20587 (and its duplicate #20588) by adding a null check when handling stale ContentTypes in has_feature().

When running remove_stale_contenttypes (executed by upgrade.sh), the management command fails with TypeError: issubclass() arg 1 must be a class. This occurs when deleting ContentTypes that no longer have corresponding model classes—referred to as "stale" ContentTypes. These arise when models are renamed/removed between versions or when plugins are uninstalled.

The error path:

  1. Django's pre_delete signal fires when deleting the stale ContentType
  2. notify_object_changed() signal handler calls has_feature(instance, 'notifications')
  3. has_feature() calls model_class() on the ContentType, which returns None for stale types
  4. This None gets passed directly to the feature test lambda: issubclass(None, NotificationsMixin) → TypeError

Root Cause:

Commit 5ceb6a6 (which fixed #20290) changed the ContentType code path in has_feature() to use direct feature registry lookups in addition to ObjectType lookups. However, this refactoring inadvertently removed the null safety check that existed in the previous implementation.

https://gist.github.com/jnovinger/35948fc653c0425a5f9b207f9b77883f is available as a reproduction script to verify this fix.

*Originally created by @jnovinger on 10/15/2025* ### Fixes: #20587 Fixes #20587 (and its duplicate #20588) by adding a null check when handling stale `ContentType`s in `has_feature()`. When running `remove_stale_contenttypes` (executed by `upgrade.sh`), the management command fails with `TypeError: issubclass() arg 1 must be a class`. This occurs when deleting ContentTypes that no longer have corresponding model classes—referred to as "stale" ContentTypes. These arise when models are renamed/removed between versions or when plugins are uninstalled. The error path: 1. Django's `pre_delete` signal fires when deleting the stale ContentType 2. `notify_object_changed()` signal handler calls `has_feature(instance, 'notifications')` 3. `has_feature()` calls `model_class()` on the ContentType, which returns `None` for stale types 4. This `None` gets passed directly to the feature test lambda: `issubclass(None, NotificationsMixin)` → TypeError Root Cause: Commit 5ceb6a6 (which fixed #20290) changed the ContentType code path in `has_feature()` to use direct feature registry lookups in addition to ObjectType lookups. However, this refactoring inadvertently removed the null safety check that existed in the previous implementation. https://gist.github.com/jnovinger/35948fc653c0425a5f9b207f9b77883f is available as a reproduction script to verify this fix.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/netbox#1055