A reusable Django app for tracking and recording audit and event logs.
Project description
django-eventlog
A reusable Django application for tracking and logging events and model changes. This library provides a structured way to maintain audit trails for user actions and system events.
Installation
You can install the package directly into your project using uv (or pip):
uv add ./eventlog
Add eventlog to your INSTALLED_APPS in settings.py:
INSTALLED_APPS = [
# ...
'eventlog',
# ...
]
Run migrations to create the required database tables:
python manage.py makemigrations eventlog
python manage.py migrate
Core Concepts
The library provides two primary components:
EventLog: The main model that stores the log entry, who performed the action, which object was affected, and a JSON payload of changes.EventLogService: A helper class to automatically generate diffs and create the log entries cleanly.
Usage
1. Logging an Event
Use EventLogService.log to record an event. Here is a practical example of logging when an item is updated or deleted within a ViewSet:
from rest_framework import viewsets
from rest_framework.response import Response
from django.db import transaction
from eventlog.services import EventLogService
from eventlog.choices import EventLogAction
class ExampleViewSet(viewsets.ModelViewSet):
# ...
@transaction.atomic
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
# 1. Store the old data
old_data = self.get_serializer(instance).data
# 2. Perform the update
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
# 3. Store the new data
new_data = serializer.data
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
# 4. Generate the diff schema between old and new data
changes = EventLogService.create_diff_schema(old_data, new_data)
# 5. Log the update event
EventLogService.log(
template_message="Item {item__name} was updated by {user__full_name}.",
values_json={
"item__name": instance,
"user__full_name": self.request.user
},
object=instance,
actor=self.request.user,
tool="example_feature",
action=EventLogAction.UPDATE,
changes=changes,
)
return Response(serializer.data)
@transaction.atomic
def perform_destroy(self, instance):
# Delete the object
super().perform_destroy(instance)
# Log the delete event
EventLogService.log(
template_message="Item {item__name} was deleted by {user__full_name}.",
values_json={
"item__name": instance,
"user__full_name": self.request.user,
},
object=instance,
actor=self.request.user,
tool="example_feature",
action=EventLogAction.DELETE,
)
You can also log events directly from your Serializers, such as when creating a new object:
from rest_framework import serializers
from django.db import transaction
from eventlog.services import EventLogService
from eventlog.choices import EventLogAction
class ExampleSerializer(serializers.ModelSerializer):
# ... your fields ...
@transaction.atomic
def create(self, validated_data):
# 1. Create the object
obj = super().create(validated_data)
# 2. Get the user from the serializer context
user = self.context['request'].user
# 3. Log the create event
EventLogService.log(
template_message="Created a new request",
object=obj,
actor=user,
tool="example_request",
action=EventLogAction.CREATE,
)
return obj
Parameter Breakdown
-
template_message&values_json(Format Strings):
These two parameters work together to create dynamic text. In the example above,{inspection_team__name}is a format string. The prefixinspection_teamis just for readability, but the crucial part is__name. It tells the service to look at thevalues_jsondictionary, find the key"inspection_team__name", take the object provided (instance), and extract itsnameattribute. The same logic applies to{user__full_name}: it looks for"user__full_name"invalues_json, takesself.request.user, and extracts itsfull_nameattribute. -
actor: The user who made the request or caused this event (usuallyrequest.user). -
tool: A string used to categorize logs by app or feature (e.g.,"certificate","user","inspection_team"). This is very helpful when you want to filter logs by feature via the API. -
action: A string indicating what method or action was performed (create,update,delete). We provideEventLogActionchoices, but you can pass custom strings. -
object: The main target object of this event log (e.g., the specific inspection team or certificate being deleted/updated). -
changes: Used mainly for updates. You can generate a diff between the old and new states usingEventLogService.create_diff_schema(old_data, new_data)to record exactly what changed.
2. Generating Diffs
If you have two dictionaries representing the before and after states (e.g., from a DRF serializer), you can generate a schema of what changed:
old_data = {"name": "John", "status": "active"}
new_data = {"name": "John", "status": "inactive"}
diff = EventLogService.create_diff_schema(old_data, new_data, exclude_keys=["updated_at"])
# Returns: {"old_value": {"status": "active"}, "new_value": {"status": "inactive"}}
3. Provided Log ViewSet and Mixin
We provide ready-to-use components to easily expose your event logs via a REST API.
EventLogViewSet
To expose the REST API endpoints for viewing all logs, you can wire up the EventLogViewSet into your project's URL configuration.
This ViewSet is particularly useful when you want to filter logs by tool to see all events that occurred within a specific feature across your application (e.g., GET /api/eventlogs/?tool=example_feature).
In your project's urls.py, import the viewset and register it with a Django REST Framework router:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from eventlog.views import EventLogViewSet
# Create a router and register our viewset
router = DefaultRouter()
router.register(r'eventlogs', EventLogViewSet, basename='eventlog')
urlpatterns = [
# ... your other url patterns ...
path('api/', include(router.urls)),
]
(Note: If your project already uses a router, simply import EventLogViewSet and register it along with your other endpoints.)
EventLogMixin
We also provide a handy EventLogMixin that you can add to any of your existing DRF ViewSets. Once added, it automatically creates a route at GET /your-endpoint/<id>/log/ so you can view all events related to that specific object!
from rest_framework import viewsets
from eventlog.mixins import EventLogMixin
class CertificateViewSet(EventLogMixin, viewsets.ModelViewSet):
queryset = Certificate.objects.all()
serializer_class = CertificateSerializer
# That's it! You can now call GET /certificates/1/log/
Example Response:
{
"count": 2,
"results": [
{
"id": 2,
"actor": "John Doe",
"message": "Item Certificate A was updated by John Doe.",
"timestamp": "2023-11-12T15:26:12.275092Z",
"object_id": 1,
"tool": "example_feature",
"action": "update",
"changes": {
"new_value": {
"status": "inactive"
},
"old_value": {
"status": "active"
}
},
"object_type": 15
},
{
"id": 1,
"actor": "John Doe",
"message": "Created a new request",
"timestamp": "2023-11-12T15:25:23.776353Z",
"object_id": 1,
"tool": "example_request",
"action": "create",
"changes": null,
"object_type": 15
}
]
}
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_simple_eventlog-0.1.0.tar.gz.
File metadata
- Download URL: django_simple_eventlog-0.1.0.tar.gz
- Upload date:
- Size: 7.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0fb5e534e3077bf5d26ddac371ca80fb7d2029f764a62585452f214882a0f057
|
|
| MD5 |
840269ab90fdabb6d22e80dcdeee9947
|
|
| BLAKE2b-256 |
d414c714ea843b47baec0b7808bba592115724128f87a410630314b887aa371c
|
File details
Details for the file django_simple_eventlog-0.1.0-py3-none-any.whl.
File metadata
- Download URL: django_simple_eventlog-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8fc4f236e70fbab70dd50ea88d6a36988392b3f1b762d921cc88e9be2eb6a5a4
|
|
| MD5 |
a141ec8daf68ef5398d9b5f47df66b7a
|
|
| BLAKE2b-256 |
005f345d54c4393160394b09087e4ec0cc4ad1c7a11fe65792c6b8c8f689ddfd
|