# Ticket #36846: Handling of ContentType for deleted models

**URL:** <https://forum.djangoproject.com/t/ticket-36846-handling-of-contenttype-for-deleted-models/44090>\
**Category:** Django Internals\
**Created:** [February 4, 2026, 2:34pm UTC](https://forum.djangoproject.com/t/ticket-36846-handling-of-contenttype-for-deleted-models/44090 "2026-02-04T14:34:02Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![mthuurne](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/mthuurne/32/31449_2.png) [@mthuurne](https://forum.djangoproject.com/u/mthuurne)\
**Post date:** [February 4, 2026, 2:34pm UTC](https://forum.djangoproject.com/t/ticket-36846-handling-of-contenttype-for-deleted-models/44090/1 "2026-02-04T14:34:03Z")

</div>

In [ticket #36846](https://code.djangoproject.com/ticket/36846), the central question is what the expected behavior is when `ContentType.get_object_for_this_type()` is called when the `ContentType` represents a model that doesn’t exist in the code base anymore.

There is a `remove_stale_contenttypes` admin command to remove stale `ContentType` records. The documentation does not mention whether the use of this command is an optional way to clean up the database or a required step to be run on each deployment to ensure correct operation:

- If running `remove_stale_contenttypes` is required, I’d argue that there should be a system check on startup that issues a warning if there are stale content types in the database, similar to the warning about migrations that haven’t run.
- If running `remove_stale_contenttypes` is optional, the question is what should happen at runtime when a stale content type is accessed.

The current behavior is that an `AttributeError` is raised when a manager attribute is looked up on a `None` object. This behavior is undocumented and seems accidental rather than designed, so it is probably not the desired behavior. The impact of the current behavior is that the admin site crashes if one record in an overview table is linked to a model that no longer exists via a `GenericForeignKey`.

The behavior that I expected was for `ObjectDoesNotExist` to be raised, similar to the situation where the model does exist but the object does not. This exception is caught by the admin site and the result is that `-` is displayed instead of the linked object.

In the ticket discussion, Jacob Walls mentioned a concern that using the same exception type (`ObjectDoesNotExist`) for non-existing instances and non-existing models would deprive developers of useful information. The underlying question is whether the removal of the model itself is meaningfully different from the removal of all instances of that model (no table vs empty table).

Maybe a compromise is possible by introducing a `ModelDoesNotExist` exception type that either inherits from `ObjectDoesNotExist` or both inheriting from the same base class. Then it’s possible to catch only one exception type when the difference is relevant or catch both when the difference is irrelevant.

The unhandled `None` is returned by `ContentType.model_class()`, the current implementation of which dates back to commit [​ba7206cd](https://github.com/django/django/commit/ba7206cd81458865bace85914905392291b05829) from late 2013 (Django 1.7). It preserves the old “return `None`” behavior when `apps.get_model()` was changed to raise `LookupError` instead. Having `ContentType.model_class()` raise `LookupError` as well would be consistent, but this was considered too invasive of a change even 12 years ago (as evidenced by `model_class()` retaining the old behavior for compatibility), so it would likely require a deprecation cycle.

To summarize, the discussed behavior options for `ContentType.get_object_for_this_type()` when called for a model that doesn’t exist anymore are:

- require that the database never contains stale content types
- raise `AttributeError`: current behavior, a bug in my opinion
- raise `LookupError`: the new behavior of `apps.get_model()`
- raise `ObjectDoesNotExist`: handle a missing model like a missing instance
- raise `ModelDoesNotExist`: introduce a new exception type

Note that `ObjectDoesNotExist` does not inherit `LookupError`, even though it is raised when a lookup fails. Maybe it should?

In all cases except forbidding stale content types and raising `ObjectDoesNotExist` the admin site code would have to be updated to handle the selected behavior.

It would be good to get some more perspectives on what the expected/desired behavior would be.
