Install & Compatibility
Where this runs
tested against v5.0.0 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslpy 3.10–3.95 runs
installs and imports cleanly · install 0.0s · import 0.762s · 66.6MB
glibcpy 3.10–3.95 runs
installs and imports cleanly · install 3.5s · import 0.668s · 67MB
66MB installed
● package 66MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
FieldTracker
✓ from model_utils import FieldTracker
Used for tracking changes to model fields.
StatusField
✓ from model_utils.fields import StatusField
A CharField subclass for models with predefined states.
TimeStampedModel
✓ from model_utils.models import TimeStampedModel
Abstract base class providing 'created' and 'modified' DateTimeFields.
SoftDeletableModel
✓ from model_utils.models import SoftDeletableModel
Abstract base class for models with logical deletion instead of actual removal.
Choices
✓ from model_utils import Choices
Utility for defining model field choices with conveniences.
This quickstart demonstrates how to use `FieldTracker` to monitor changes in a model's fields. The `tracker` instance on the model allows you to check if a specific field has changed (`has_changed()`), retrieve its previous value (`previous()`), or get a dictionary of all changed fields and their old values (`changed()`). The example shows how to integrate this logic within a custom `save` method.
from django.db import models
from model_utils import FieldTracker
class Post(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
tracker = FieldTracker()
def save(self, *args, **kwargs):
# Example of using FieldTracker in a save method
super().save(*args, **kwargs)
if self.tracker.has_changed('title'):
print(f"Title changed from '{self.tracker.previous('title')}' to '{self.title}'")
# Example usage (requires Django setup and database interaction)
# post = Post.objects.create(title='Initial Title', body='Some body content')
# post.title = 'Updated Title'
# post.save()
# print(post.tracker.changed()) # Shows all changed fields and their previous values
Debug
Known issues
breakingThe `SaveSignalHandlingModel` has been removed. If you used this abstract base class to temporarily disable signals during a save operation, you will need to refactor your code. This model was removed in version 4.4.0 due to maintainability concerns with its modified `Model.save_base()` method.fixConsider using Django's built-in `bulk_create` or `update` methods which bypass signals, or implement custom signal dispatching logic. For more complex scenarios, you might need to temporarily disconnect signals before saving and reconnect them afterwards.
affects: >=4.4.0
breakingThe `JoinQueryset.get_quoted_query()` method was removed in version 4.5.1. If your code directly invoked this method, it will now fail.fixUpdate your code to no longer rely on `get_quoted_query()`. If you were using `JoinManager` or `JoinManagerMixin`, these were deprecated in 5.0.0; migrate to `JoinQueryset.as_manager()` instead.
affects: >=4.5.1
breakingVersion 4.0.0 dropped support for Python 2.7 and Django 1.11. Additionally, it removed internal usage of the `six` compatibility library.fixEnsure your project runs on Python 3.x (specifically `>=3.8` for `django-model-utils` 5.0.0) and uses Django 2.2 or later (preferably >=3.2 as per 4.3.1 requirements).
affects: >=4.0.0
breakingBehavioral changes to `FieldTracker` in version 4.1.0 mean it now correctly marks fields as not changed after `refresh_from_db` and respects `update_fields` when saving. It also resets states after `pre_save()` signals instead of `save()` signals.fixReview existing code using `FieldTracker` within custom `save()` methods, signal handlers, or after `refresh_from_db()` to ensure the new behavior aligns with expectations. Specifically, logic relying on `tracker.changed()` within `post_save` might need adjustment if it expects changes that occurred during `pre_save` to be re-evaluated.
affects: >=4.1.0
deprecatedThe `ModelTracker` was deprecated in favor of `FieldTracker` due to serious flaws in handling `ForeignKey` fields, leading to potentially many extra database queries.fixReplace all instances of `ModelTracker` with `FieldTracker` to avoid performance issues and leverage improved `ForeignKey` handling.
affects: All versions (deprecated in 1.3.0)
deprecatedFor `SoftDeletableModel`, relying on the default `objects` manager to filter out not-deleted instances is deprecated. The `objects` manager will include deleted objects in a future release.fixAlways use the `available_objects` manager to retrieve only non-deleted instances, or `all_objects` when you explicitly want to include deleted instances.
affects: All versions (deprecated since 4.1.0, mentioned in 5.0.0 docs)
gotchaIn version 5.0.0, the `MonitorField` now defaults to `None` when nullable and no default is provided, instead of `django.utils.timezone.now`.fixIf your `MonitorField` relies on `django.utils.timezone.now` for nullable fields without an explicit default, you must now set `default=django.utils.timezone.now` explicitly to maintain the previous behavior.
affects: >=5.0.0
Errors
Common errors & fixes
ModuleNotFoundError: No module named 'model_utils'
The `django-model-utils` package is not installed in your Python environment or the virtual environment is not activated.
fixRun `pip install django-model-utils` to install the library.
AssertionError: To use StatusField, the model 'MyModel' must have a STATUS choices class attribute.
The `StatusField` requires a `STATUS` class attribute to be defined on the model to provide the available choices for the field.
fixDefine a `STATUS` class attribute (e.g., using `model_utils.Choices` or a list of two-tuples) within your model class. Example: `from model_utils import Choices; class MyModel(models.Model): STATUS = Choices('draft', 'published'); status = StatusField()`. AttributeError: 'FieldTracker' object has no attribute 'attname'
This error typically occurs when an older version of `django-model-utils` (e.g., 3.1.2) is used with a newer Django version (e.g., 3.2+), or when attempting to dynamically attach `FieldTracker` to a third-party model in a way that bypasses its proper initialization hooks.
fixEnsure `django-model-utils` is updated to a version compatible with your Django version (e.g., 4.2.0+ for Django 3.2+). If attaching to a third-party model, ensure `FieldTracker` is properly initialized, often requiring a subclass or using `contribute_to_class` carefully.
TypeError: unsupported operand type(s) for +=: 'set' and 'list'
This error arises when multiple `django-model-utils` mixins (like `TimeStampedModel` and `StatusModel`) are inherited, and their `save()` methods handle the `update_fields` argument inconsistently (e.g., one converting it to a `set` and another expecting or modifying a `list`). This was a known bug in `django-model-utils` 4.1.0.
fixUpgrade `django-model-utils` to version 4.1.1 or newer, as this issue was addressed in subsequent releases.
Upgrade
Version history
5.0.0latest on PyPI · released Sep 4, 2024
Audit
Dependencies
DjangorequiredCore framework dependency. Supports Django >=3.2 and Python >=3.8. Version 5.0.0 formally supports Django up to 5.1.