django-easy-audit: a Django audit log whose settings file is the real documentation
Yet another Django audit log app, hopefully the simplest one.
At a glance
- What is it?
- django-easy-audit records every create, update and delete, every login and every requested URL into models you can query from the Django admin, and it does it with one app entry, one middleware and one migration. The interesting part of the project is the long settings list, which covers filtering, read-only events, split databases and a pluggable logging backend.
- Who is it for?
- Adopt django-easy-audit if you need an audit trail in a Django project and want it recorded without writing your own signal handlers, since the setup is three steps and the events land in models the admin can already show. Do not adopt it if you need the events to be tamper-proof, because the admin interface makes them editable by default unless you change a setting.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 73 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Three steps and every event is recorded
django-easy-audit describes itself as yet another Django audit log app, hopefully the simplest one, and the quickstart is the argument for that claim.
You install it with pip, and the README notes an alternative for people who would rather not add a dependency, downloading the latest release from GitHub, unzipping it and placing the easyaudit folder in the root of the project. Then you add the app to the installed applications:
INSTALLED_APPS = [
...
'easyaudit',
]And you add the middleware:
MIDDLEWARE = (
...
'easyaudit.middleware.easyaudit.EasyAuditMiddleware',
)Then you create the tables:
python manage.py migrate easyauditAnd that is it. From that point on every CRUD event in the project is registered in the audit models, you can query them from the Django admin, and the app also logs all authentication events and all requested URLs.
The three event families are the design. Model events come from Django signals, which the README starts to explain at the point where it is cut off, and the middleware is what supplies the request events. So a project gets change history, who signed in and who asked for what, from one mechanism, and the admin interface is the interface.
The licence is GPL-3.0, the package is on PyPI as django-easy-audit, and the last push was on 2026-08-11, with a release candidate for the next minor version published the same day as v1.3.9 in June.
The settings decide what is recorded, and one of them is dynamic
The settings section is long, and the README points to a wiki page for the exhaustive list. The ones worth reading are the ones that change what ends up in the database.
Three switches turn off entire categories of logging: the setting to watch model events, the one for authentication events and the one for request events, each of which stops that family being logged when set to false.
Two lists exclude specific things. There is a setting for models to ignore, whose items can be classes or strings in the application name and model name format, and a setting for URLs to ignore, whose items are regular expressions matched against the URL path. Both exist because a project usually has a few models and endpoints that generate noise.
The third is the interesting one, because it is not a list. The CRUD difference callbacks setting may point to a list of callables or string paths to functions and classes, in which the application code decides, for each individual CRUD event, whether to create it or not. The README is explicit that this differs from the registered and unregistered settings: it is meant for dynamic cases where the application inspects the current save, create or delete and chooses.
That is a real difference in kind. The list-based settings are configuration, decided once at startup, while the callbacks are code, consulted per event, and they can look at the change itself. A project that wants to keep audit rows only for changes that touch a financial field, for instance, needs the callbacks, and the README's phrasing, on a per CRUD event whether or not the application chooses to create the event, is a good description of the contract.
So the tiering of the settings is: switches to silence a whole category, lists to exclude known noise, and callbacks for decisions that depend on the data.
Admin filters and the question of who can edit an audit row
The admin side of the package has three list filters, one per event family, and the README gives the defaults so you know what you are getting before you change anything.
For CRUD events the filter list is the event type, the content type, the user and the datetime. For login events it is the login type, the user and the datetime. For request events it is the method, the user and the datetime. The purpose of these filters, as the README puts it, is to remove filters when the corresponding list of data would be too long, which is a performance decision rather than a usability one.
Then there is the setting that matters most for anyone treating the audit trail as evidence. The events visible through the admin interface are editable by default by a superuser, and the read-only events setting makes the recorded events read-only through the admin interface when set to true.
That default is a deliberate choice, and a defensible one, because audit data that staff can correct is more useful than audit data that staff cannot correct at all. But it is the opposite of what an organisation with a compliance requirement usually wants, where the log is evidence and the evidence should be immutable. The setting is the difference between the two, and it is one boolean.
It is also worth noting what the package is not. It records events, it does not sign them, and it does not ship them anywhere. If the requirement is tamper-evidence rather than history, this is the wrong layer, and the read-only setting only gets you as far as the admin interface.
The performance settings are where a real deployment is decided
Three settings in the list are about cost rather than behaviour, and they are the ones a team running this in production will care about.
The first is the user existence check, which is on by default. When it is on, the app fetches the request user for audit event creation and checks the database, and setting it to false means only the request user is fetched with no database check, which the README describes as speeding up event creation and saving database calls. It also names the case where you want it off, an application with soft deletes or no delete on the users model, where the user row may not exist in the way the check expects.
The second is the changed fields skip, which is off by default. When on, the app will not save a CRUD event if the changed fields as determined by the pre-save handler show there are none. The README explains the default choice: it is kept off so that projects that want fewer events can turn it on, and projects that do not want it, yet or ever, or that do not follow the release notes closely, have one less thing to worry about when upgrading. That is a considerate default, and it is also a warning that this behaviour may change between versions.
The third is the database alias, which defaults to the Django default alias. For projects that have split databases, the README says this is necessary to keep database atomicity concerns in check during the signal handlers, and clarifies that it is only truly necessary for the model signals. The reason is structural rather than stylistic: the audit table and the user table may live in different databases, and a write that touches both has to be atomic or it does not.
And then there is the exception setting, which is off by default, so that easyaudit does not propagate exceptions raised in its own signal handlers. The recommended approach is to tie it to Django's debug flag so errors surface in development and stay quiet in production:
DJANGO_EASY_AUDIT_PROPAGATE_EXCEPTIONS = DEBUGThat is a good default for an audit package, because an exception in the audit path should not take down the request it was recording.
A pluggable backend means the events can go somewhere else
The logging backend setting makes the destination of the events a pluggable piece, and the default is the model backend in the package. The README specifies the contract: the class has to have three methods, one for logins, one for CRUD events and one for requests, and each takes a dictionary containing the information about the event.
The example in the README is a backend that writes to the standard Python logging module, which is the shape most projects will want to start from. It configures the root logger, creates a logger with a name of your choosing, and then each of the three methods logs the information dictionary as extra data and returns it.
import logging
class PythonLoggerBackend:
logging.basicConfig()
logger = logging.getLogger('your-kibana-logger')
logger.setLevel(logging.DEBUG)
def request(self, request_info):
return request_info # if you don't need it
def login(self, login_info):
self.logger.info(msg='your message', extra=login_info)
return login_infoThe log messages in the example point at a log aggregation service, and that is the point of the interface. The model backend writes rows you query from the admin, and a custom backend can write to a log pipeline, to a message broker, to a file, or to a store with retention rules, without changing the app that produces the events. The dictionary is the contract, and the README is explicit that returning the information is allowed even when you do not need it, which keeps the chain simple.
For a project that already runs a log pipeline, this is the feature that turns the audit log into a feed, and it is a small interface: three methods that take a dictionary.
Supported Django versions, and how the package is tested
The project metadata says more about the audience than the README does.
Django is required from 4.2 up to but not including 7, and the classifiers name Django 4.2, 5.0, 5.1, 5.2 and 6.0 individually, which is a project that tracks new Django releases as they land rather than lagging a version behind. Python is 3.9 and later, with classifiers up to 3.14.
The build is Poetry, with the package configuration listing the easyaudit package, and the repository carries a poetry lock file, a poetry configuration file, a manage.py and a tests directory, so the project is laid out as a Django project as well as a distributable library.
The tooling is modern and deliberate. The development group carries django-upgrade, which rewrites your code for newer Django versions, djlint for linting templates, pre-commit and ruff. The test group carries pytest with the Django plugin, the asyncio plugin in auto mode, and coverage. The pytest configuration pins the settings module to the test settings, so the test run never touches a developer's own database configuration.
The version scheme is worth a note. The releases listed are v1.3.9 in June 2026, a beta of the same version earlier, and then v1.4.0.a1 in August, and the project metadata marks the current version as a pre-release of 1.4.0. The letters after the numbers are the alpha and beta markers, so a user pinning a version is choosing between a stable point release and a pre-release of the next minor, and the changelog in the repository is where the difference is described.
The licence is GPL-3.0, and the README's last line points to the project wiki, which is where the full settings reference lives.
Editorial conclusion
Adopt django-easy-audit if you need an audit trail in a Django project and want it recorded without writing your own signal handlers, since the setup is three steps and the events land in models the admin can already show. Do not adopt it if you need the events to be tamper-proof, because the admin interface makes them editable by default unless you change a setting. Before you turn it on in production, read the performance settings, in particular the one that adds a user lookup per event, and decide where the audit tables live if your project splits its databases, since the default alias assumes they are together.
Frequently asked questions
How do I set up django-easy-audit?
Install it with pip install django-easy-audit, or unzip the release into the project root, then add easyaudit to INSTALLED_APPS, add the EasyAuditMiddleware to MIDDLEWARE, and run python manage.py migrate easyaudit. After that every CRUD event in the project is registered in the audit models, along with all authentication events and all requested URLs.
What does django-easy-audit record?
Three families of events. Model events, which are the create, update and delete operations on your models, come from Django signals. Authentication events come from the login flow, and request events record the URLs that were requested. All three end up as models you can query from the Django admin.
Can I stop django-easy-audit from recording certain things?
Yes. Three settings switch off whole families of events, one each for model, authentication and request events. Two more take lists, one of models and one of URLs, where the URL items are regular expressions matched against the path. And the CRUD difference callbacks setting takes callables that let your application decide per event whether to record it.
Can audit events be edited in the admin?
By default they can, by a superuser. The read-only events setting makes the recorded events read-only through the admin interface when set to true, which matters if the audit trail has to be evidence rather than a working note.
Can I send django-easy-audit events to another system?
Yes, through the logging backend setting. The backend is a class with three methods, one each for logins, CRUD events and requests, and each takes a dictionary of event information. The README shows an example backend that writes to the standard Python logging module, which is a starting point for sending the events to a log pipeline.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/soynatan-django-easy-audit)Was this useful?