Collecting Context

By default, all django-pghistory event models come with a nullable pgh_context foreign key to the pghistory.models.Context model. This model has a metadata JSONField that is populated by calling pghistory.context.

Using pghistory.context

pghistory.context can be used as a decorator or context manager. When it is entered the first time, it creates a session with a UUID, collects metadata, and stores this metadata in the metadata field of the pghistory.models.Context model. Any nested calls will add or override the metadata for that session.

For example, here we’ve started context collection and added a few keys and values in the midst of database queries:

with pghistory.context(key1="val1"):
    # Do changes...

    with pghistory.context(key2="val2"):
        # Do more changes...

In the above example, all events will reference the same Context object with metadata of:

{
    "key1": "val1",
    "key2": "val2",
}

Remember, context collection allows you to not only track free-form metadata, but also group events together. Normally an application will group events for each request. Tasks like management commands or Celery jobs can also be instrumented. We’ll cover these examples later.

If you’d like to avoid starting a context session and only attach context to a pre-existing session, call pghistory.context as a function. If pghistory.context hasn’t been previously entered as a decorator or context manager, the context will not be stored.

Tip

If you’re attaching context that cannot be serialized to JSON, override the default JSON encoder class with settings.PGHISTORY_JSON_ENCODER. It defaults to django.core.serializers.json.DjangoJSONEncoder.

Middleware

django-pghistory comes with the pghistory.middleware.HistoryMiddleware middleware to add context to requests. It adds the URL of the request to the url attribute and the authenticated user ID to the user attribute.

Since the middleware starts context collection at the beginning of the request, all tracked changes in the request will reference the same context.

Use settings.PGHISTORY_MIDDLEWARE_METHODS to configure the requests that are tracked. It defaults to ("GET", "POST", "PUT", "PATCH", "DELETE").

Management Commands

To capture all events issued under a management command, instrument manage.py like so:

#!/usr/bin/env python
import contextlib
import os
import sys

import pghistory


if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
    from django.core.management import execute_from_command_line

    if (
        len(sys.argv) > 1
        and not sys.argv[1].startswith("runserver")
    ):
        # Group history context under the same management command if
        # we aren't running a server.
        history_context = pghistory.context(command=sys.argv[1])
    else:
        history_context = contextlib.ExitStack()

    with history_context:
        execute_from_command_line(sys.argv)

Above we ignore tracking context for runserver commands. Otherwise every single change in a development session would be grouped under the same context.

Celery Tasks

Override the Celery base task to group all task events:

import celery
import pghistory


class Task(celery.Task):
    def __call__(self, *args, **kwargs):
        with pghistory.context(task=self.name):
            return super().__call__(*args, **kwargs)


# Override the celery task decorator for your application
app = create_celery_app('my-app')
task = app.task(base=Task)