API

Audit decorator

alliance_platform.audit.create_audit_model_base(model, *, meta_base=<class 'object'>, manual_events=None, events=None, registry=<alliance_platform.audit.registry.AuditRegistry object>, list_perm=None, fields=None, exclude=None, related_name=Unset.token, **kwargs)

Given model, returns a base to be used for creating a table to attach audit log to:

class UserAuditEvent(
    create_audit_model_base(User, exclude=["password", "last_login"], manual_events=["LOGIN", "LOGOUT"])
):
    class Meta:
        db_table = "xenopus_frog_user_auditevent"

Should you wish to add events that are manually-triggered eg a pdf file had been downloaded, just specify those events in manual_events; to then trigger a manual event use create_audit_event().

events refers to the RowEvent to be used which defaults to AuditSnapshot. This audits CREATE, UPDATE and DELETE events and has handling for many to many fields. You most likely do not need to change this option.

If you need to monitor other database events, such as BeforeInsert, you could do so by passing in events; this will supersede AuditSnapshot which if you intend to keep can be done by adding AuditSnapshot(label="your label") to the list. See AuditSnapshot to see what you need to be aware of before doing this.

One noteworthy kwarg is fields, where you can ask the audit module to only watch changes made to your selected list of fields. By default, all fields are tracked, and should be kept this way unless you have a good reason to change this behavior. If you wish to record all fields but only display some of them on frontend (eg, sensitive fields), then define audit_fields_to_display on your model.

FileField and ImageField are recorded as string (URL), AutoField is recorded as IntegerField and any ManyToManyField is recorded only on the sourcing side as an ArrayField with same field name (eg. bug=ManyToManyField(Bug) on Coder will result in an array of bug’s ids being recorded in coder.events.bug, but this field will not be created on the Bug model even if its also audited)

See pghistory.core.create_event_model() for the supported kwargs and more details

Usage:

class PDFAuditEvent(create_audit_model_base(PDFFile, manual_events=["accessed"])):
    audit_fields_to_display=['id', 'uploader', 'file']
    class Meta:
        db_table = "pdf_file_audit_log"

# You must pass the record the event is logged against. Acting user is also tracked (by default)
# by AuditMiddleware and stored in context.
create_audit_event(pdf, "accessed")
Parameters:
  • model (type[AuditableModelProtocol]) – The model to create audit event model for

  • meta_base (type) – The base class for Meta, eg. NoDefaultPermissionsMeta

  • manual_events (list[str] | None) – List of manual events supported for this model

  • events (list[Tracker] | None) – Database events to monitor. See above comments for more details.

  • registry (AuditRegistry) – The audit registry to add to. You most likely don’t need this; the default suffices for most cases

  • list_perm (str | None) – The permission to use when showing audit events in list view. This should be a global permission (ie. doesn’t accept a specific object). If not specified uses resolve_perm_name() with an action of ap_audit_settings.LIST_PERM_ACTION (which defaults to audit) for the model model (ie. the source model).

  • fields (list[str] | None) – The fields to track. If None, all fields on model are tracked.

  • exclude (list[str] | None) – Exclude these fields from tracking. Only one of fields and exclude should be specified.

  • related_name (str) – The primary way to identify the relation of the created model and the tracked model. If fields or exclude are not provided this defaults to auditevents otherwise a name is generated based on the provided fields (see pghistory.core.create_event_model()).

Return type:

type[Model]

alliance_platform.audit.with_audit_model(meta_base=<class 'allianceutils.auth.permission.NoDefaultPermissionsMeta'>, audit_fields_to_display=None, **kwargs)

Model class decorator to create an associated audit model

Wraps create_audit_model_base() in decorator form and adds some sensible defaults

Parameters:
  • meta_base (type)

  • audit_fields_to_display (Iterable[str] | None)

Return type:

Callable[[type[Model]], Model]

Events

class alliance_platform.audit.events.PatchedEvent(*, name=None, operation=None, condition=None, label=None, event_model=None, when=None, row=None, snapshot=None, level=None)[source]

Patched pghistory.trigger.event.

This fixes the issue as seen here: https://github.com/jyveapp/django-pghistory/issues/9 and also handles many-to-many (by copying values from the previous record if available).

Limitation: because we copy the m2m values from prev. record, the value is not Guaranteed to be correct: it will be most of times, but would be empty if this gets instlalled on an existing model and no changes to m2m had been made since.

Parameters:
  • name (str | None)

  • operation (Operation | None)

  • condition (Condition | None)

  • event_model (AuditEventProtocol)

  • when (When | None)

  • level (Level | None)

class alliance_platform.audit.events.AuditSnapshot(label=None)[source]

our Snapshot event. Audits AfterInsert, AfterUpdate and BeforeDelete. In the case of update, compares before<->after to see if any value gets modified; this means a instance.save() without any actual changes will NOT trigger an UPDATE snapshot.

This also writes a self-referencing pgh_previous_id that points to last previous record for the same object: effectively a pgh_previous=ForeignKey(‘self’, null=True) that can be used to find out what values have changed.

Also audits many-to-many fields by placing triggers on the through table.

Parameters:

label (str)

alliance_platform.audit.events.create_event(obj, registration, *, label, using='default')[source]

Patched pghistory.core.create_event.

Dropped event registration check as it’s already done in create_audit_event, and also adds M2M fields handling support.

alliance_platform.audit.create_audit_event(object, label)

Manually logs an event against a preset manual-event for object’s class, eg: create_audit_event(pdf, "accessed").

The event must be registered on the specified model (eg. should be passed in manual_events to create_audit_model_base()).

All manual log entries are tied to objects (ie, you can’t have object-less events such as create_audit_event("system shutdown") ). By default, AuditMiddleware() tracks and records the current user and URL in pgh_context for the created log event, and additional info can be added by wrapping create_audit_event in with pghistory.context(**kwargs). See pghistory.context for more details about how context works.

Returns created event model.

Parameters:
  • object (Model)

  • label (str)

Return type:

Model

Views

class alliance_platform.audit.api.AuditLogView(**kwargs)[source]

This viewset handles returning data from the appropriate model Event table based on the model request parameter. If model="all" then all audited models are returned using a database UNION.

In most apps there will be a single AuditRegistry and you never need to explicitly define it. In cases where multiple registries are desired (for example to split between different apps - admin vs public app) you can pass the registry argument:

path("api/auditlog/", AuditLogView.as_view(registry=my_registry))

If you wish to restrict the queryset for any Events in some way override get_single_queryset().

Note that in the case that all models are being shown get_queryset will return a EventUnion instead of a QuerySet. Each queryset in the union.querysets will be filtered in filter_queryset before being combined with a .union() call. If you override get_queryset you should handle this (eg. call super().get_queryset()) and handle the case where a EventUnion is returned. It is recommended you override get_single_queryset() or get_multiple_queryset() instead.

All fields available on the source model will be available on on the queryset as well regardless of whether they exist in audit_fields_to_display; you can do something like return qs.filter(owner=request.user.org) if all audited models has an “owner” attribute; or you could refine based on model: if qs.model==FooEvent: return qs.filter(bar=request.user) or if you want to refer to the original audited model if qs.pgh_tracked_model.model==Foo: return qs.filter(bar=request.user)

get_single_queryset(qs)[source]

Called on each event queryset. You can override this to add filters you want applied to all event querysets. Note that adding annotations here won’t work when multiple event querysets are being returned. For advanced cases you will need to override get_multiple_queryset.

Parameters:

qs (QuerySet)

Return type:

QuerySet

get_multiple_queryset()[source]

Called when model request param is ‘all’.

Each model queryset is passed to get_single_queryset.

Filtering occurs in filter_queryset and union is applied after filtering.

Return type:

EventUnion

get_queryset()[source]

Get the queryset or EventUnion to use.

In the case of handling multiple models (when model is ‘all’) this will return an EventUnion.

To override the handling of logic for each type of model override get_single_queryset. This will be called by get_queryset on the single model or on each model when returning all models. You can add conditional logic there for specific models. Note that you cannot add annotations as they will not work with a union. If you need to support this override get_multiple_queryset as well.

Return type:

QuerySet | EventUnion

class alliance_platform.audit.api.AuditUserChoicesView(**kwargs)[source]

This view provides a paginated list of users for audit views to filter lists

class alliance_platform.audit.api.EventUnion(querysets)[source]

List of querysets that will have union taken after filtering occurs

Parameters:

querysets (list[QuerySet])

Registry

class alliance_platform.audit.registry.AuditRegistry[source]

Middleware

class alliance_platform.audit.middleware.AuditMiddleware(get_response)[source]

Tracks POST/PUT/PATCH/DELETE requests and annotates a few fields in the pghistory context.

By default tracks user id, impersonatinguser id and url visited. IP address tracking can be turned on by enabling TRACK_IP_ADDRESS in audit package settings - make sure you take GDPR into consideration (recording without disclosure is a violation; ie. minimal: your site need to have a privacy statement somewhere.)

Templatetags

render_audit_list

Renders an audit log list in one of three modes:

  • supply model but not pk, and model is not all:

    renders a table that lists all events related to all instances of model

  • supply model="all":

    renders a table that lists all changes accessible by user across all audited models

  • supply model with pk, or object:

    renders a table that lists only events recorded for that object

Requires alliance_platform.frontend to be installed, and the AUDIT_LOG_COMPONENT_PATH to point to a react component in your frontend source folder that renders the audit log component.

Argument

Description

context

django context for the purpose of accessing user. provided by default.

model

the string name of a model either being “all” or in the format of app.model eg admin.user

object

an instance of object being audited, if you have one,

pk

or the object’s pk (use in conjuration with model) if you don’t have the instance

registry

registry to use. you most likely don’t need to touch this one.

limit_to_user

restrict events to only those made by the user, where supplied user is either the actor or hijacker

**kwargs

Any other props to pass through to the audit component