Advanced Usage ============== Verbose mode ------------ By default, when you use ``get_dirty_fields()`` function, if there are dirty fields, only the saved value is returned. You can use the ``verbose`` option to return the saved and current in-memory value: .. code-block:: pycon >>> model = ExampleModel.objects.create(characters="first value") >>> model.characters = "second value" >>> model.get_dirty_fields() {'characters': 'first_value'} >>> model.get_dirty_fields(verbose=True) {'characters': {'saved': 'first value', 'current': 'second value'}} Checking foreign key fields. ---------------------------- By default, dirty functions are not checking foreign keys. If you want to also take these relationships into account, use ``check_relationship`` parameter: .. code-block:: python class ForeignKeyModel(DirtyFieldsMixin, models.Model): fkey = models.ForeignKey(AnotherModel, on_delete=models.CASCADE) .. code-block:: pycon >>> model = ForeignKeyModel.objects.create(fkey=obj1) >>> model.is_dirty() False >>> model.fkey = obj2 >>> model.is_dirty() False >>> model.is_dirty(check_relationship=True) True >>> model.get_dirty_fields() {} >>> model.get_dirty_fields(check_relationship=True) {'fkey': 1} Saving dirty fields. -------------------- If you want to only save dirty fields from an instance in the database (only these fields will be involved in SQL query), you can use ``save_dirty_fields()`` method. If the model instance has not been persisted yet, it will be saved in full. Warning: This calls the ``save()`` method internally so will trigger the same signals as normally calling the ``save()`` method. .. code-block:: pycon >>> model.is_dirty() True >>> model.save_dirty_fields() >>> model.is_dirty() False Performance Impact ------------------ Using ``DirtyFieldsMixin`` in your Model will have a (normally small) performance impact even when you don't call any of ``DirtyFieldsMixin``'s methods. This is because ``DirtyFieldsMixin`` needs to capture the state of the Model when it is initialized and when it is saved, so that ``DirtyFieldsMixin`` can later determine if the fields are dirty. Using a Proxy Model to reduce Performance Impact ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you only use ``DirtyFieldsMixin``'s methods in some places of you project but not all, you can eliminate the performance impact in the places you don't use them by inheriting from ``DirtyFieldsMixin`` in a `Proxy Model`_. .. _Proxy Model: https://docs.djangoproject.com/en/dev/topics/db/models/#proxy-models For example define your Model without ``DirtyFieldsMixin``: .. code-block:: python class FooModel(models.Model): ... Use this Model class when you don't need to track dirty fields. It is a regular Model so there will be no performance impact, but ``is_dirty()`` and ``get_dirty_fields()`` can't be used. Then define a Proxy Model for that Model which includes ``DirtyFieldsMixin``: .. code-block:: python class FooModelWithDirtyFields(DirtyFieldsMixin, FooModel): class Meta: proxy = True Use this Model class when you do want dirty fields to be tracked. There will be a performance impact but ``is_dirty()`` and ``get_dirty_fields()`` can be used. Database Transactions Limitations --------------------------------- There is currently a limitation when using dirtyfields and database transactions. If your code saves Model instances inside a ``transaction.atomic()`` block, and the transaction is rolled back, then the Model instance's ``is_dirty()`` method will return ``False`` when it should return ``True``. The ``get_dirty_fields()`` method will also return the wrong thing in the same way. This is because after the ``save()`` method is called, the instance's dirty state is reset because it thinks it has successfully saved to the database. Then when the transaction rolls back, the database is reset back to the original value. At this point this Model instance thinks it is not dirty when it actually is. Here is a code example to illustrate the problem: .. code-block:: python # first create a model model = ExampleModel.objects.create(characters="first") # then make an edit in-memory, model becomes dirty model.characters = "second" assert model.is_dirty() # then attempt to save the model in a transaction try: with transaction.atomic(): model.save() # no longer dirty because save() has been called, # BUT we are still in a transaction ... assert not model.is_dirty() # force a transaction rollback raise DatabaseError("pretend something went wrong") except DatabaseError: pass # Here is the problem: # value in DB is still "first" but model does not think its dirty, # because in-memory value is still "second" assert model.characters == "second" assert not model.is_dirty() This simplest workaround to this issue is to call ``model.refresh_from_db()`` if the transaction is rolled back. Or you can manually restore the fields that were edited in-memory.