Audit history & anvil doctor
Audit history
Wraps django-simple-history
rather than reinventing change tracking. Set audited = True on a Resource:
class CouponResource(Resource):
model = Coupon
...
audited = True
and add the history field to the model yourself — Anvil never edits model
files for you, the same policy as TenantScopedModel:
from simple_history.models import HistoricalRecords
class Coupon(TenantScopedModel):
...
history = HistoricalRecords()
Anvil wires up the admin integration: the generated ModelAdmin uses
SimpleHistoryAdmin instead of admin.ModelAdmin, giving a full "History"
button and change log in the Django admin UI automatically.
django-simple-history is already installed as part of django-anvil — just add
"simple_history" to INSTALLED_APPS.
Verified for real: create a row, change it twice,
instance.history.all() shows three entries — + (created),
~, ~ (two updates) — each with its own timestamp.
anvil doctor
Static checks, no database needed — safe to run in CI before migrate:
python manage.py anvil doctor
| Check | Level |
|---|---|
DEBUG=True | Warning |
Unreplaced default SECRET_KEY | Warning |
ALLOWED_HOSTS empty with DEBUG=False | Error |
A tenant_scoped Resource whose model isn't really TenantScopedModel | Error |
A mixin used without the field it needs (SoftDeleteViewSetMixin → is_deleted, OwnerScopedViewSetMixin → owner) | Error |
| A permission codename that doesn't match any real permission on the model | Warning |
audited = True but the model has no history field yet | Warning |
| A field filtered/sorted on a lot with no database index | Info |
The permission-codename check exists specifically because RBAC fails closed: a typo'd
codename doesn't raise an error anywhere — it just silently locks everyone out of
that action forever, with nothing in the logs to point at the cause. This is the one check
most worth running after any change to a Resource's permissions.
[WARN] security: DEBUG=True -- fine for local development, never deploy with this on.
[WARN] security: SECRET_KEY is still Django's auto-generated insecure default -- replace it before deploying.
[INFO] performance: ProductResource (products.Product): 'price' is filtered/sorted on but has no db_index -- consider adding one if the table gets large.
0 error(s), 2 warning(s), 1 suggestion(s)