python
50 lines · 8 steps
Tracking field changes in a Django model
An Order model snapshots audited fields on load so save() can log exactly what changed.
Explained by
highlit
1from django.db import models
2
3
4class Order(models.Model):
5 STATUS_CHOICES = [
6 ("pending", "Pending"),
7 ("paid", "Paid"),
8 ("shipped", "Shipped"),
9 ("cancelled", "Cancelled"),
10 ]
11
12 reference = models.CharField(max_length=32, unique=True)
13 status = models.CharField(max_length=16, choices=STATUS_CHOICES, default="pending")
14 total = models.DecimalField(max_digits=10, decimal_places=2)
15
16 AUDITED_FIELDS = ("status", "total")
17
18 def __init__(self, *args, **kwargs):
19 super().__init__(*args, **kwargs)
20 self._snapshot = self._current_state()
21
22 def _current_state(self):
23 return {field: getattr(self, field) for field in self.AUDITED_FIELDS}
24
25 def changed_fields(self):
26 current = self._current_state()
27 return {
28 field: (self._snapshot[field], current[field])
29 for field in self.AUDITED_FIELDS
30 if self._snapshot[field] != current[field]
31 }
32
33 def save(self, *args, **kwargs):
34 is_new = self._state.adding
35 changes = self.changed_fields()
36
37 super().save(*args, **kwargs)
38
39 if not is_new and changes:
40 AuditLog.objects.bulk_create([
41 AuditLog(
42 content_object=self,
43 field=field,
44 old_value=str(old),
45 new_value=str(new),
46 )
47 for field, (old, new) in changes.items()
48 ])
49
50 self._snapshot = self._current_state()
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Snapshotting values at load time lets you diff against the current state later without extra queries.
- 2Overriding save() around super().save() is the natural hook for before/after side effects.
- 3Comparing the snapshot to current state only after a successful save keeps your audit log honest.
Related explainers
python
from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI()
Building a WebSocket chat with FastAPI
websockets
broadcast
connection-management
Intermediate
9 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
python
import random from typing import Iterator, List
How reservoir sampling picks k items
reservoir-sampling
streaming
randomness
Intermediate
5 steps
python
import secrets from django.contrib.auth import authenticate, login from django.core.cache import cache
Two-factor login with OTP in Django
two-factor-auth
one-time-passwords
caching
Intermediate
9 steps
python
import re from functools import total_ordering from typing import Optional
Parsing and comparing semantic versions
regex
operator-overloading
sorting
Intermediate
7 steps
python
from typing import Any, Sequence, Mapping def render_markdown_table(
Rendering an aligned Markdown table in Python
string formatting
data transformation
closures
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/tracking-field-changes-in-a-django-model-explained-python-1425/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.