Skip to main content

Django Wicked Historian

example workflow Coverage Status

A complete solution for creating automatic history of Django models.

Installation

Package can be installed using PyPi:

$ pip install django-wicked-historian

You can also use extras to ensure some additional dependencies specific for implementation of JSONField which is required for this package to work properly.

$ pip install django-wicked-historian[mysql]
$ pip install django-wicked-historian[postgres]
$ pip install django-wicked-historian[django-jsonfield]

Defining JSONField to be used

Package requires some configuration. You need to specify JSONField implementation which package gonna use to store values of model fields in your settings:

from wicked_historian.encoder import JSONEncoder

WICKED_HISTORIAN_JSON_FIELD_CLASS = 'path.to.JSONField'
WICKED_HISTORIAN_JSON_FIELD_KWARGS = {
    'encoder': JSONEncoder,
}

WICKED_HISTORIAN_JSON_FIELD_CLASS - path to JSON field class to be used WICKED_HISTORIAN_JSON_FIELD_KWARGS - kwargs used for instantiate of supplied class

Remember to always use wicked_historian.encoder.JSONEncoder as an encoder for this field.

Example configuration for common used fields

jsonfield.JSONField

WICKED_HISTORIAN_JSON_FIELD_CLASS = 'jsonfield.JSONField'
WICKED_HISTORIAN_JSON_FIELD_KWARGS = {
    'encoder_class': JSONEncoder,
}

django.contrib.postgres.fields.JSONField

For Django >= 2.1 use builtin field with our encoder.

WICKED_HISTORIAN_JSON_FIELD_CLASS = 'django.contrib.postgres.fields.JSONField'
WICKED_HISTORIAN_JSON_FIELD_KWARGS = {
    'encoder': JSONEncoder,
}

django_mysql.models.fields.JSONField

This field in version 2.2.0 of django-mysql package doesn't support supplying custom json encoder. However this package supplies subclass of this field with support of custom encoders and wicked_historian.encoder.JSONEncoder is default encoder.

Use field wicked_historian.compat.mysql.JSONField instead:

WICKED_HISTORIAN_JSON_FIELD_CLASS = 'wicked_historian.compat.mysql.JSONField'
WICKED_HISTORIAN_JSON_FIELD_KWARGS = {}

Adding history to model of choice

Model for which history is going to be generated should inherit from wicked_historian.models.DiffableHistoryModel and have a class for history entries specified in the Model.Meta class. History class should be created using factory wicked_historian.utils.generate_history_class:

from wicked_historian.models import DiffableHistoryModel
from wicked_historian.utils import generate_history_class


class Book(DiffableHistoryModel):
    title = models.CharField(max_length=100)

    class Meta:
        history_class = 'this_app.models.BookEditHistory'


BookEditHistory = generate_history_class(Book, __name__)

If there is a need for customizing the history model, it can be generated with the abstract option and used as a base model for a custom history model.

class BookEditHistory(generate_history_class(Book, __name__, abstract=True)):
    custom_field = models.IntegerField(default=0)

    def custom_method(self):
        return self.custom_field + 10

Changes in model's fields set

If the set of model fields changes in a non-incremental way (fields were removed or changed their type), old definitions of such fields should be supplied to the generate_history_class factory for handling already existing history entries concerning these fields:

from wicked_historian.utils import ObsoleteFieldDescription

BookEditHistory = generate_history_class(
    Book,
    __name__,
    obsolete_field_choices=[
        ObsoleteFieldDescription('title', models.TextField()),
        ObsoleteFieldDescription('number_of_pages', models.IntegerField()),
        ObsoleteFieldDescription('age', models.IntegerField(choices=[
            (1, 'XV'),
            (2, 'XIX'),
            (3, 'XX'),
        ])),
    ],
)

Excluding fields from history

If there is no need for generating history for some fields, they can be excluded by supplying list of unwanted fields name to generate_history_class. History for these will not be generated, but any already existing history can be read.

from wicked_historian.utils import ObsoleteFieldDescription

BookEditHistory = generate_history_class(
    Book,
    __name__,
    excluded_fields=['title'],
)

Choices in model fields

Please note that when the set of choices in model fields changes in a non-incremental way, some values may be impossible to restore from history entries. That's why you should always have a superset of all choices ever used in this fields declared in the field.

Reading/accessing history

Instance history should be accessed by history model.

Retrieving whole history

To retrieve whole history use method get_for, e.g. BookEditHistory.get_for(book) will return list of whole Book instance history as dicts.

Filtering and searching history

To filter history use history model manager (e.g. BookEditHistory.objects.filter(user=some_user, model=book)) and transform history entry to dict form using get_entry_for method.

history_entry_instances = BookEditHistory.objects.filter(user=some_user, model=book)
history_entries = BookEditHistory.get_history_entry(history_entry_instance) for history_entry_instance in history_entry_instances

Troubleshooting custom m2m handling

When there is risk of sending by Django both signals model related (pre_save, post_delete etc.) and m2m related use wicked_historian.signals_exclusion.signal_exclusion and make those changes in signal_exclusion.model_signals_exclusion_context context. When in context calling signal_exclusion.are_model_signals_excluded with the same arguments context was created returns True.

License

The Django Wicked Historian package is licensed under the FreeBSD License.

Release files for django-wicked-historian 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-wicked-historian 1.0.0
File Size Uploaded
django-wicked-historian-1.0.0.tar.gz 18.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-wicked-historian 1.0.0
File Interpreter ABI Platform
django_wicked_historian-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 40.5 kB

Release files / django-wicked-historian-1.0.0.tar.gz

Download URL django-wicked-historian-1.0.0.tar.gz
Size 18.1 kB
Tags Source
SHA-256 checksum
How to use checksums
3dac0c456b13f42eec906ab5a73127df1ec394309dba5b412cd429c60fe3e4fc
BLAKE2b-256 checksum
How to use checksums
312315cbc79ed95ffc757e9a652815ccaabaa1340902723e6507778b843650c3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.8.0 pkginfo/1.8.2 readme-renderer/32.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.62.3 importlib-metadata/4.11.1 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.9.10

Release files / django_wicked_historian-1.0.0-py3-none-any.whl

Download URL django_wicked_historian-1.0.0-py3-none-any.whl
Size 22.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e61c377f4b3023d2d7481b4b1c39176eaf854efae8a246dbe0987f1793f8ec9d
BLAKE2b-256 checksum
How to use checksums
949c06a638ddb8466343fc13ee93ebd0b729f3480dc30e7ec7c36b4fef6e3524
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.8.0 pkginfo/1.8.2 readme-renderer/32.0 requests/2.27.1 requests-toolbelt/0.9.1 urllib3/1.26.8 tqdm/4.62.3 importlib-metadata/4.11.1 keyring/23.5.0 rfc3986/2.0.0 colorama/0.4.4 CPython/3.9.10

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page