Django Wicked Historian
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.
Metadata
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)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| django-wicked-historian-1.0.0.tar.gz | 18.1 kB | Without provenance | Details for django-wicked-historian-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| django_wicked_historian-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for django_wicked_historian-1.0.0-py3-none-any.whl |
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
|
Security for django-wicked-historian 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.