Package: django-field-history
A Django app to track changes to a model field. For Python 2.7/3.4+ and Django 1.11/2.0+.Other similar apps aredjango-reversionanddjango-simple-history, which trackallmodel fields.Projectdjango-field-historydjango-reversiondjango-simple-historyAdmin IntegrationN/AYesYesAll/Some fieldsSomeSomeAllObject HistoryNoYesYesModel HistoryN/ANoYesMulti-object RevisionsN/AYesNoExtra Model ManagerYesNoYesModel RegistryNoYesNoDjango View HelpersNoYesNoManager Helper MethodsN/AYesYes (as_of,most_recent)MySQL SupportExtra configCompleteCompleteDocumentationThe full documentation is athttps://django-field-history.readthedocs.io.FeaturesKeeps a history of all changes to a particular model’s field.Stores the field’s name, value, date and time of change, and the user that changed it.Works with all model field types (exceptManyToManyField).QuickstartInstall django-field-history:pip install django-field-historyBe sure to put it in INSTALLED_APPS.INSTALLED_APPS=[# other apps...'field_history',]Then add it to your models.fromfield_history.trackerimportFieldHistoryTrackerclassPizzaOrder(models.Model):STATUS_CHOICES=(('ORDERED','Ordered'),('COOKING','Cooking'),('COMPLETE','Complete'),)status=models.CharField(max_length=64,choices=STATUS_CHOICES)field_history=FieldHistoryTracker(['status'])Now each time you change the order’s status field information about that change will be stored in the database.fromfield_history.modelsimportFieldHistory# No FieldHistory objects yetassertFieldHistory.objects.count()==0# Creating an object will make onepizza_order=PizzaOrder.objects.create(status='ORDERED')assertFieldHistory.objects.count()==1# This object has some fields on ithistory=FieldHistory.objects.get()asserthistory.object==pizza_orderasserthistory.field_name=='status'asserthistory.field_value=='ORDERED'asserthistory.date_createdisnotNone# You can query FieldHistory using the get_{field_name}_history()# method added to your modelhistories=pizza_order.get_status_history()assertlist(FieldHistory.objects.all())==list(histories)# Or using the custom FieldHistory managerhistories2=FieldHistory.objects.get_for_model_and_field(pizza_order,'status')assertlist(histories)==list(histories2)# Updating that particular field creates a new FieldHistorypizza_order.status='COOKING'pizza_order.save()assertFieldHistory.objects.count()==2updated_history=histories.latest()assertupdated_history.object==pizza_orderassertupdated_history.field_name=='status'assertupdated_history.field_value=='COOKING'assertupdated_history.date_createdisnotNoneManagement Commandsdjango-field-history comes with a few management commands.createinitialfieldhistoryThis command will inspect all of the models in your application and createFieldHistoryobjects for the models that have aFieldHistoryTracker. Run this the first time you install django-field-history.python manage.py createinitialfieldhistoryrenamefieldhistoryUse this command after changing a model field name of a field you track withFieldHistoryTracker:python manage.py renamefieldhistory --model=app_label.model_name --from_field=old_field_name --to_field=new_field_nameFor instance, if you have this model:classPerson(models.Model):username=models.CharField(max_length=255)field_history=FieldHistoryTracker(['username'])And you change theusernamefield name tohandle:classPerson(models.Model):handle=models.CharField(max_length=255)field_history=FieldHistoryTracker(['handle'])You will need to also update thefield_namevalue in allFieldHistoryobjects that point to this model:python manage.py renamefieldhistory --model=myapp.Person --from_field=username --to_field=handleStoring Which User Changed the FieldThere are two ways to store the user that changed your model field. The simplest way is to usethe logged in userthat made the request. To do this, add theFieldHistoryMiddlewareclass to yourMIDDLEWAREsetting.MIDDLEWARE=['django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','field_history.middleware.FieldHistoryMiddleware',]Alternatively, you can add a_field_history_userproperty to the model that has fields you are tracking. This property should return the user you would like stored onFieldHistorywhen your field is updated.classPizza(models.Model):name=models.CharField(max_length=255)updated_by=models.ForeignKey('auth.User')field_history=FieldHistoryTracker(['name'])@propertydef_field_history_user(self):returnself.updated_byWorking with MySQLIf you’re using MySQL, the default configuration will throw an exception when you run migrations. (By default,FieldHistory.object_idis implemented as aTextFieldfor flexibility, but indexed columns in MySQL InnoDB tables may be a maximum of 767 bytes.) To fix this, you can setFIELD_HISTORY_OBJECT_ID_TYPEin settings.py to override the default field type with one that meets MySQL’s constraints.FIELD_HISTORY_OBJECT_ID_TYPEmay be set to either:the Django model field class you wish to use, ora tuple(field_class, kwargs), wherefield_classis a Django model field class andkwargsis a dict of arguments to pass to the field class constructor.To approximate the default behavior for Postgres when using MySQL, configureobject_idto use aCharFieldby adding the following to settings.py:fromdjango.dbimportmodelsFIELD_HISTORY_OBJECT_ID_TYPE=(models.CharField,{'max_length':100})FIELD_HISTORY_OBJECT_ID_TYPEalso allows you to use a field type that’s more efficient for your use case, even if you’re using Postgres (or a similarly unconstrained database). For example, if you always let Django auto-create anidfield (implemented internally as anAutoField), settingFIELD_HISTORY_OBJECT_ID_TYPEtoIntegerFieldwill result in efficiency gains (both in time and space). This would look like:fromdjango.dbimportmodelsFIELD_HISTORY_OBJECT_ID_TYPE=models.IntegerFieldRunning TestsDoes the code actually work?source <YOURVIRTUALENV>/bin/activate
(myenv) $ pip install -r requirements-test.txt
(myenv) $ python runtests.pyHistory0.8.0 (January 5, 2020)Added support for Django 2.2 and 3.0Added support for Python 3.80.7.0 (September 3, 2018)Added support for Django 2.0 and 2.1Added support for Python 3.7Dropped support for Django 1.7 through 1.10Dropped support for Python 3.2 and 3.3Fixed generic primary key bug withcreateinitialfieldhistorycommand (#20)0.6.0 (December 22, 2016)Added Django 1.10 compatibility.Added MySQL compatibility.Fixed issue that would duplicate tracked fields.0.5.0 (April 16, 2016)Added the ability to track field history of parent models.Added Django 1.7 compatibility.0.4.0 (February 24, 2016)Added a way to automatically store the logged in user onFieldHistory.user.0.3.0 (February 20, 2016)FieldHistoryobjects are now created usingbulk_create, which means only one query will be executed, even when changing multiple fields at the same time.Added a way to store which user updated a field.Addedget_latest_bytoFieldHistoryMeta options so.latest()and.earliest()can be used.Addedcreateinitialfieldhistorymanagement command.Addedrenamefieldhistorymanagement command.0.2.0 (February 17, 2016)First release on PyPI.