Package: django-field-audit
Audit Field Changes on Django ModelsA Django app for auditing field changes on database models.Installationpip install django-field-auditDocumentationDjango SettingsTo enable the app, add it to your DjangoINSTALLED_APPSconfiguration and run
migrations. Settings example:INSTALLED_APPS=[# ..."field_audit",]The "auditor chain" (seeFIELD_AUDIT_AUDITORSin theCustom settingstable
below) is configured out of the box with the default auditors. Ifchange_contextauditing is desired for authenticated Django requests, add the
app middleware to your DjangoMIDDLEWAREconfiguration. For example:MIDDLEWARE=[# ..."field_audit.middleware.FieldAuditMiddleware",]The audit chain can be updated to use custom auditors (subclasses offield_audit.auditors.BaseAuditor). Ifchange_contextauditing is not
desired, the audit chain can be cleared to avoid extra processing:FIELD_AUDIT_AUDITORS=[]Custom settings detailsNameDescriptionDefault value when unsetFIELD_AUDIT_AUDITEVENT_MANAGERA custom manager to use for theAuditEventModel.field_audit.models.DefaultAuditEventManagerFIELD_AUDIT_AUDITORSA custom list of auditors for acquiringchange_contextinfo.["field_audit.auditors.RequestAuditor", "field_audit.auditors.SystemUserAuditor"]Model AuditingTo begin auditing Django models, import thefield_audit.audit_fieldsdecorator
and decorate models specifying which fields should be audited for changes.
Example code:# flight/models.pyfromdjango.dbimportmodelsfromfield_auditimportaudit_fields@audit_fields("tail_number","make_model","operated_by")classAircraft(models.Model):id=AutoField(primary_key=True)tail_number=models.CharField(max_length=32,unique=True)make_model=models.CharField(max_length=64)operated_by=models.CharField(max_length=64)Audited DB write operationsBy default, Model and QuerySet methods are audited, with the exception of four
"special" QuerySet methods:DB Write MethodAuditedModel.delete()YesModel.save()YesQuerySet.bulk_create()NoQuerySet.bulk_update()NoQuerySet.create()Yes (viaModel.save())QuerySet.delete()NoQuerySet.get_or_create()Yes (viaQuerySet.create())QuerySet.update()NoQuerySet.update_or_create()Yes (viaQuerySet.get_or_create()andModel.save())Auditing Special QuerySet WritesAuditing for the four "special" QuerySet methods that perform DB writes (labeledNoin the table above)canbe enabled. This requires three extra usage
details:WarningEnabling auditing on these QuerySet methods might have significant
performance implications, especially on large datasets, since audit events are
constructed in memory and bulk written to the database.Enable the feature by calling the audit decorator specifying@audit_fields(..., audit_special_queryset_writes=True).Configure the model class so its default manager is an instance offield_audit.models.AuditingManager.All calls to the four "special" QuerySet write methods require an extraaudit_actionkeyword argument whose value is one of:field_audit.models.AuditAction.AUDITfield_audit.models.AuditAction.IGNOREImportant NotesSpecifyingaudit_special_queryset_writes=True(step1above) without
setting the default manager to an instance ofAuditingManager(step2above) will raise an exception when the model class is evaluated.At this time,QuerySet.delete(),QuerySet.update(),
andQuerySet.bulk_create()"special" write methods can actually perform
change auditing when called withaudit_action=AuditAction.AUDIT.QuerySet.bulk_update()is not currently implemented and will raiseNotImplementedErrorif called with that action. Implementing this remaining
method remains a task for the future, seeTODObelow. All four methods do
supportaudit_action=AuditAction.IGNOREusage, however.All audited methods use transactions to ensure changes to audited models
are only committed to the database if audit events are successfully created
and saved as well.Bootstrap events for models with existing recordsIn the scenario where auditing is enabled for a model with existing data, it can
be valuable to generate "bootstrap" audit events for all of the existing model
records in order to ensure that there is at least one audit event record for
every model instance that currently exists.  There is a migration utility for
performing this bootstrap operation. Example code:# flight/migrations/0002_bootstrap_aircarft_auditing.pyfromdjango.dbimportmigrations,modelsfromfield_audit.utilsimportrun_bootstrapfromflight.modelsimportAircraftclassMigration(migrations.Migration):dependencies=[('flight','0001_initial'),]operations=[run_bootstrap(Aircraft,["tail_number","make_model","operated_by"])]Bootstrap events via management commandIf bootstrapping is not suitable during migrations, there is a management command for
performing the same operation.  The management command does not accept arbitrary
field names for bootstrap records, and uses the fields configured by the
existingaudit_fields(...)decorator on the model. Example (analogous to
migration action shown above):manage.pybootstrap_field_audit_eventsinitAircraftAdditionally, if a post-migration bootstrap "top up" action is needed, the
the management command can also perform this action. A "top up" operation
creates bootstrap audit events for any existing model records which do not have
a "create" or "bootstrap"AuditEventrecord. Note that the management command
is currently the only way to "top up" bootstrap audit events. Example:manage.pybootstrap_field_audit_eventstop-upAircraftUsing with SQLiteThis app uses Django'sJSONFieldwhich means if you intend to use the app with
a SQLite database, the SQLiteJSON1extension is required. If your system's
Pythonsqlite3library doesn't ship with this extension enabled, seethis articlefor details
on how to enable it.ContributingAll feature and bug contributions are expected to be covered by tests.Setup for developersCreate/activate a python virtualenv and install the required dependencies.cddjango-field-audit
mkvirtualenvdjango-field-audit# or however you choose to setup your environmentpipinstalldjangopynoseflake8coverageRunning testsNote: By default, local tests use an in-memory SQLite database. Ensure that
your local Python'ssqlite3library ships with theJSON1extension enabled
(seeUsing with SQLite).TestsnosetestsStyle checkflake8--config=setup.cfgCoveragecoveragerun-mnose
coveragereport-mAdding migrationsThe examplemanage.pyis available for making new migrations.pythonexample/manage.pymakemigrationsfield_auditUploading to PyPIFirst bump the package version in thefield_audit/__init__.pyfile. Then create a changelog entry in the CHANGELOG.md
file. After these changes are merged, you should tag the main branch with the new version. Then, package and upload the generated files to PyPI.pipinstall-rpkg-requires.txt

pythonsetup.pysdistbdist_wheel
twineuploaddist/*TODOImplement auditing for the remaining "special" QuerySet write operations:bulk_update()Write full library documentation using github.io.Switch topytestto support Python 3.10.BacklogAdd to optimization forinstance.save(save_fields=[...])[maybe].Support adding new audit fields on the same model at different times (instead
of raisingAlreadyAudited) [maybe].