Django Dirty Fields
django-dirtyfields is a small library for tracking 'dirty' fields on a Django model instance. A field is considered dirty if its in-memory value differs from the value currently saved in the database. The library is currently at version 1.9.9 and is actively maintained with regular updates to support new Django and Python versions.
Warnings
- breaking With Django 6.0+, fields that are expressions (e.g., `F()` objects) are now refreshed or deferred after `Model.save()`. This means such a field may incorrectly appear as 'dirty' if subsequently changed in-memory, even if its actual database value didn't change based on your application logic, potentially leading to unexpected behavior in `is_dirty()` or `get_dirty_fields()`.
- gotcha There's a limitation when using `django-dirtyfields` within database transactions. If a model instance is saved inside a `transaction.atomic()` block and the transaction is subsequently rolled back, the `is_dirty()` method might incorrectly return `False` (and `get_dirty_fields()` yield wrong results) because the instance's dirty state is reset after `save()` is called, irrespective of the transaction's eventual outcome.
- gotcha Using `DirtyFieldsMixin` introduces a small performance overhead as it needs to capture the model's state during initialization and saving to track changes. For models where dirty field tracking is not consistently needed, this overhead can be avoided by using a Proxy Model that inherits from `DirtyFieldsMixin` only when tracking is required.
- gotcha The `save_dirty_fields()` method, while only persisting the changed fields, still internally calls the standard `save()` method on the model instance. This means that any `pre_save` or `post_save` signals registered for your model will still be triggered.
Install
-
pip install django-dirtyfields
Imports
- DirtyFieldsMixin
from dirtyfields import DirtyFieldsMixin
Quickstart
from django.db import models
from dirtyfields import DirtyFieldsMixin
class ExampleModel(DirtyFieldsMixin, models.Model):
characters = models.CharField(max_length=80, default='initial value')
boolean = models.BooleanField(default=True)
def __str__(self):
return self.characters
# Example Usage:
# Assuming an active Django environment and database
# model = ExampleModel.objects.create(characters='first value', boolean=True)
# print(f'Is dirty initially? {model.is_dirty()}') # Expected: False
# print(f'Dirty fields initially: {model.get_dirty_fields()}') # Expected: {}
# model.characters = 'second value'
# print(f'Is dirty after change? {model.is_dirty()}') # Expected: True
# print(f'Dirty fields after change: {model.get_dirty_fields()}') # Expected: {'characters': 'first value'}
# model.save()
# print(f'Is dirty after save? {model.is_dirty()}') # Expected: False
# model.boolean = False
# print(f'Is dirty after another change? {model.is_dirty()}') # Expected: True