### Install django-simple-history Source: https://github.com/django-commons/django-simple-history/blob/master/docs/quick_start.md Install the library using pip. ```bash pip install django-simple-history ``` -------------------------------- ### Initialize Project Requirements Source: https://github.com/django-commons/django-simple-history/blob/master/CONTRIBUTING.rst Installs the necessary dependencies for documentation generation and testing using the Makefile. ```bash make init ``` -------------------------------- ### Install Pre-commit Hooks Source: https://github.com/django-commons/django-simple-history/blob/master/CONTRIBUTING.rst Installs pre-commit, a tool that runs linters and formatters on code commits to maintain code quality. ```bash pre-commit install ``` -------------------------------- ### Run Tests with Single Python/Django Version Source: https://github.com/django-commons/django-simple-history/blob/master/CONTRIBUTING.rst Quickly runs tests against a specific Python and Django version. Ensure Django is installed via pip beforehand. ```bash python runtests.py ``` -------------------------------- ### Getting Previous and Next Historical Records Source: https://github.com/django-commons/django-simple-history/blob/master/docs/querying_history.md Navigate through the history of a model instance using the `prev_record` and `next_record` attributes on a historical record. These will be `None` if there is no older or newer record, respectively. ```python >>> from polls.models import Poll, Choice >>> from datetime import datetime >>> poll = Poll.objects.create(question="what's up?", pub_date=datetime.now()) >>> >>> record = poll.history.first() >>> record.prev_record None >>> record.next_record None >>> poll.question = "what is up?" >>> poll.save() >>> record.next_record ``` -------------------------------- ### Grant View Permission Only (Default Behavior) Source: https://github.com/django-commons/django-simple-history/blob/master/docs/admin.md With `SIMPLE_HISTORY_ENFORCE_HISTORY_MODEL_PERMISSIONS` set to `False`, a user with view permission on the main model implicitly gets view permission on the history model. ```python user.user_permissions.clear() user.user_permissions.add( Permission.objects.get(codename="view_poll"), ) ``` -------------------------------- ### Reuse Model's QuerySet for History Source: https://github.com/django-commons/django-simple-history/blob/master/docs/historical_model.md Reuse an existing model's QuerySet for historical records by inheriting from `HistoricalQuerySet` and the model's QuerySet. This example demonstrates reusing `QuestionQuerySet` for historical data. ```python from datetime import timedelta from django.db import models from django.utils import timezone from simple_history.models import HistoricalRecords from simple_history.manager import HistoryManager, HistoricalQuerySet class QuestionQuerySet(models.QuerySet): def question_prefixed(self): return self.filter(question__startswith="Question: ") class HistoryQuestionQuerySet(QuestionQuerySet, HistoricalQuerySet): """Redefine ``QuerySet`` with base class ``HistoricalQuerySet``.""" class Question(models.Model): pub_date = models.DateTimeField("date published") history = HistoricalRecords(historical_queryset=HistoryQuestionQuerySet) manager = QuestionQuerySet.as_manager() ``` -------------------------------- ### Display Custom Columns in Admin History Source: https://github.com/django-commons/django-simple-history/blob/master/docs/admin.md Add a `history_list_display` attribute to `SimpleHistoryAdmin` to show additional fields in the history log view. This example adds the 'status' field. ```python from django.contrib import admin from simple_history.admin import SimpleHistoryAdmin from .models import Poll, Choice class PollHistoryAdmin(SimpleHistoryAdmin): list_display = ["id", "name", "status"] history_list_display = ["status"] search_fields = ['name', 'user__username'] history_list_per_page = 100 admin.site.register(Poll, PollHistoryAdmin) admin.site.register(Choice, SimpleHistoryAdmin) ``` -------------------------------- ### Custom Save Method to Skip History Source: https://github.com/django-commons/django-simple-history/blob/master/docs/querying_history.md This is an example of a custom `save_without_historical_record` method that automatically handles skipping history creation. It sets `skip_history_when_saving` and ensures it's deleted afterward, even if errors occur. ```python def save_without_historical_record(self, *args, **kwargs): self.skip_history_when_saving = True try: ret = self.save(*args, **kwargs) finally: del self.skip_history_when_saving return ret ``` -------------------------------- ### Generate Documentation Source: https://github.com/django-commons/django-simple-history/blob/master/CONTRIBUTING.rst Regenerates the project's documentation using the Makefile. ```bash make docs ``` -------------------------------- ### Filter Model History by User Involvement Source: https://github.com/django-commons/django-simple-history/blob/master/docs/querying_history.md Establish a relationship to the history to filter changes, for example, to find all data records a specific user was involved in. This example filters `Poll` objects based on their history user. ```python Poll.objects.filter(history__history_user=4) ``` -------------------------------- ### Run Django Migrations Source: https://github.com/django-commons/django-simple-history/blob/master/docs/quick_start.md Create and apply database migrations after making model changes. ```bash python manage.py makemigrations python manage.py migrate ``` -------------------------------- ### Configure Django Settings Source: https://context7.com/django-commons/django-simple-history/llms.txt Add 'simple_history' to INSTALLED_APPS and include HistoryRequestMiddleware in MIDDLEWARE for automatic user tracking. ```python # settings.py INSTALLED_APPS = [ # ... 'simple_history', ] MIDDLEWARE = [ # ... 'simple_history.middleware.HistoryRequestMiddleware', ] ``` -------------------------------- ### Get Most Recent Model Instance Source: https://github.com/django-commons/django-simple-history/blob/master/docs/querying_history.md Use the `most_recent()` method to retrieve the latest version of a model from its history. This is useful for quickly accessing the current state of an object. ```python >>> from datetime import datetime >>> poll.history.most_recent() ``` -------------------------------- ### Populate history for existing projects Source: https://github.com/django-commons/django-simple-history/blob/master/docs/quick_start.md Use the populate_history command to generate an initial change record for pre-existing model instances. The --auto flag enables automatic detection. ```bash python manage.py populate_history --auto ``` -------------------------------- ### Specify Custom History Model Name with Callable Source: https://github.com/django-commons/django-simple-history/blob/master/docs/historical_model.md Use `custom_model_name` with a callable to dynamically set the historical model name. This example changes the prefix from 'Historical' to 'Audit'. ```python class Poll(models.Model): question = models.CharField(max_length=200) history = HistoricalRecords(custom_model_name=lambda x:f'Audit{x}') class Opinion(models.Model): opinion = models.CharField(max_length=2000) register(Opinion, custom_model_name=lambda x:f'Audit{x}') ``` -------------------------------- ### Multi-table Inheritance with History Tracking Source: https://github.com/django-commons/django-simple-history/blob/master/docs/common_issues.md Demonstrates setting up history tracking for models using multi-table inheritance. Each model in the inheritance chain can have its own HistoricalRecords instance, allowing for independent history tracking. ```python class ParentModel(models.Model): parent_field = models.CharField(max_length=255) history = HistoricalRecords() class ChildModel(ParentModel): child_field = models.CharField(max_length=255) history = HistoricalRecords() ``` -------------------------------- ### Bulk Create and Update with History Source: https://context7.com/django-commons/django-simple-history/llms.txt Use `bulk_create_with_history` and `bulk_update_with_history` for efficient bulk operations while maintaining historical records. Specify `batch_size`, `default_user`, and `default_change_reason` for these operations. ```python from datetime import datetime # Assuming Poll model and admin_user are defined elsewhere # Bulk create with history polls = [ Poll(question="Question 1", pub_date=datetime.now()), Poll(question="Question 2", pub_date=datetime.now()), Poll(question="Question 3", pub_date=datetime.now()), ] created_polls = bulk_create_with_history( polls, Poll, batch_size=100, default_user=admin_user, default_change_reason="Bulk import from CSV", ) # Each poll now has an ID and a history record for poll in created_polls: print(f"Poll {poll.id}: {poll.history.count()} history records") # Bulk update with history for poll in created_polls: poll.question = poll.question + " (updated)" rows_updated = bulk_update_with_history( created_polls, Poll, fields=['question'], batch_size=100, default_user=admin_user, default_change_reason="Bulk update via script", ) print(f"Updated {rows_updated} rows") ``` -------------------------------- ### User Lacks History View Permission (Enforced) Source: https://github.com/django-commons/django-simple-history/blob/master/docs/admin.md When `SIMPLE_HISTORY_ENFORCE_HISTORY_MODEL_PERMISSIONS` is `True`, a user with view permission on the main model does not automatically get view permission on the history model. ```python # SIMPLE_HISTORY_ENFORCE_HISTORY_MODEL_PERMISSIONS = True in settings user.user_permissions.clear() user.user_permissions.add( Permission.objects.get(codename="view_poll"), ) ``` -------------------------------- ### Run Pre-commit Checks Source: https://github.com/django-commons/django-simple-history/blob/master/CONTRIBUTING.rst Manually runs all configured pre-commit hooks on the codebase. ```bash pre-commit run ``` -------------------------------- ### Equivalent Query to `as_of` Source: https://github.com/django-commons/django-simple-history/blob/master/docs/querying_history.md Demonstrates an alternative, more verbose way to achieve the same result as the `as_of` method by filtering by `history_date` and using `latest_of_each().as_instances()`. ```python RankedDocument.history.filter(history_date__lte=t1).latest_of_each().as_instances() ``` -------------------------------- ### Accessing Historical Model for Generic Views Source: https://github.com/django-commons/django-simple-history/blob/master/docs/common_issues.md To reference the historical model, for example, in Django's generic views or DRF serializers, access it through the HistoricalRecords manager defined on your base model. ```python class PollHistoryListView(ListView): # or PollHistorySerializer(ModelSerializer): class Meta: model = Poll.history.model # ... ``` -------------------------------- ### Add default user and change reason to bulk functions Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Allows setting a default user and change reason for both bulk_create_with_history and bulk_update_with_history. ```python bulk_create_with_history(..., default_user=None, default_change_reason=None) bulk_update_with_history(..., default_user=None, default_change_reason=None) ``` -------------------------------- ### Create Translation File Source: https://github.com/django-commons/django-simple-history/blob/master/CONTRIBUTING.rst Generates a new translation file for a specified locale in the repository's root directory. ```bash django-admin makemessages -l ``` -------------------------------- ### Use default model manager in bulk_create_with_history Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Uses the default model manager instead of 'objects' for bulk_create_with_history and bulk_update_with_history for better compatibility. ```python bulk_create_with_history(..., manager=None) bulk_update_with_history(..., manager=None) ``` -------------------------------- ### Connect to History Signals Source: https://context7.com/django-commons/django-simple-history/llms.txt Customize behavior during historical record creation by connecting to signals like `pre_create_historical_record` and `post_create_historical_record`. These signals allow modification of the history instance or execution of actions after saving. ```python from django.dispatch import receiver from simple_history.signals import ( pre_create_historical_record, post_create_historical_record, pre_create_historical_m2m_records, post_create_historical_m2m_records, ) @receiver(pre_create_historical_record) def before_history_save(sender, instance, history_instance, history_date, history_user, history_change_reason, using, **kwargs): # Modify history_instance before it's saved if hasattr(history_instance, 'ip_address'): from simple_history.models import HistoricalRecords request = getattr(HistoricalRecords.context, 'request', None) if request: history_instance.ip_address = request.META.get('REMOTE_ADDR') @receiver(post_create_historical_record) def after_history_save(sender, instance, history_instance, **kwargs): # Perform actions after history is saved print(f"History saved: {history_instance.history_id}") # Example: Send audit notification if history_instance.history_type == '-': notify_deletion(instance, history_instance.history_user) @receiver(pre_create_historical_m2m_records) def before_m2m_history(sender, rows, history_instance, instance, field, **kwargs): print(f"About to save {len(rows)} M2M history records for {field.name}") @receiver(post_create_historical_m2m_records) def after_m2m_history(sender, created_rows, **kwargs): print(f"Saved {len(created_rows)} M2M history records") ``` -------------------------------- ### Run Tests and Generate Coverage Report Source: https://github.com/django-commons/django-simple-history/blob/master/CONTRIBUTING.rst Executes all tests using tox and generates an HTML code coverage report in the 'htmlcov' directory. ```bash make test ``` -------------------------------- ### Load Simple History Template Tags Source: https://github.com/django-commons/django-simple-history/blob/master/simple_history/templates/simple_history/object_history_list.html Load necessary template tags for history display and URL generation. ```django {% load i18n %} {% load url from simple_history_compat %} {% load admin_urls %} {% load getattribute from getattributes %} ``` -------------------------------- ### Leverage get_user from HistoricalRecords Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Uses 'get_user' from HistoricalRecords to set a fallback user for bulk updates and creates. ```python HistoricalRecords(get_user=None) ``` -------------------------------- ### Configure History Model in Different App Source: https://github.com/django-commons/django-simple-history/blob/master/docs/historical_model.md Specify a different app label for history models using the 'app' argument. This is useful for database routing or organizing history models separately. ```python class Poll(models.Model): question = models.CharField(max_length=200) history = HistoricalRecords(app="SomeAppName") class Opinion(models.Model): opinion = models.CharField(max_length=2000) register(Opinion, app="SomeAppName") ``` -------------------------------- ### Create History Record on Specific Database Source: https://github.com/django-commons/django-simple-history/blob/master/docs/multiple_dbs.md Use the `using()` method to create a new historical record on a database other than the default. ```python >>> # This will create a new historical record on the 'other' database. poll = Poll.objects.using('other').create(question='Question 1') ``` -------------------------------- ### Register a third-party model with custom app for migrations Source: https://github.com/django-commons/django-simple-history/blob/master/docs/quick_start.md Specify a custom app for migrations when registering a third-party model using the 'app' parameter. ```python register(User, app=__package__) ``` -------------------------------- ### Bulk operations with history Source: https://context7.com/django-commons/django-simple-history/llms.txt Create or update multiple objects while maintaining history records using `bulk_create_with_history` and `bulk_update_with_history` utilities. ```python from polls.models import Poll from simple_history.utils import bulk_create_with_history, bulk_update_with_history from datetime import datetime from django.contrib.auth.models import User admin_user = User.objects.get(username='admin') ``` -------------------------------- ### Add support for using chained manager method Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Adds support for the 'using' chained manager method and the save/delete keyword argument for database routing. ```python MyModel.history.using('other_db').all() instance.save(using='other_db') ``` -------------------------------- ### Add bulk_update_with_history utility function Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Introduces the 'bulk_update_with_history' utility function for efficient bulk updates of historical records. ```python bulk_update_with_history(queryset, change_reason=None, default_date=None, manager=None) ``` -------------------------------- ### Grant View and Change Permissions (Default Behavior) Source: https://github.com/django-commons/django-simple-history/blob/master/docs/admin.md When `SIMPLE_HISTORY_ENFORCE_HISTORY_MODEL_PERMISSIONS` is `False`, granting view and change permissions to the main model also grants them to its history model. ```python user.user_permissions.clear() user.user_permissions.add( Permission.objects.get(codename="view_poll"), Permission.objects.get(codename="change_poll"), ) ``` -------------------------------- ### Using a Custom User Model with HistoricalRecords Source: https://github.com/django-commons/django-simple-history/blob/master/docs/user_tracking.md When using a custom user model, pass the model to `user_model` in `HistoricalRecords`. Ensure `_history_user` is implemented to link historical changes to the correct user. ```python from django.db import models from simple_history.models import HistoricalRecords class PollUser(models.Model): user_id = models.ForeignKey('auth.User') # Only PollUsers should be modifying a Poll class Poll(models.Model): question = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') changed_by = models.ForeignKey(PollUser) history = HistoricalRecords(user_model=PollUser) @property def _history_user(self): return self.changed_by @_history_user.setter def _history_user(self, value): self.changed_by = value ``` -------------------------------- ### Add app parameter to HistoricalRecords Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Introduces the 'app' parameter to the HistoricalRecords constructor, allowing explicit specification of the app label. ```python HistoricalRecords(app='my_app') ``` -------------------------------- ### Register Third-Party Models for History Tracking Source: https://context7.com/django-commons/django-simple-history/llms.txt Use the register() function to add history tracking to models you don't control. Options include specifying a custom app, manager name, table name, and excluded fields. ```python from simple_history import register from django.contrib.auth.models import User # Basic registration register(User) # Registration with custom app for migrations register(User, app='myapp') # Registration with custom options register( User, app='myapp', manager_name='audit_log', table_name='user_audit_history', excluded_fields=['password', 'last_login'], ) # After registration, history is accessible user = User.objects.create_user('john', 'john@example.com', 'password123') print(user.history.all()) ``` ```python # [] ``` -------------------------------- ### Querying History at a Specific Point in Time (`as_of`) Source: https://github.com/django-commons/django-simple-history/blob/master/docs/querying_history.md Use the `as_of` method on the HistoryManager to retrieve the latest historical records or instances as they existed at a specific point in time. This method can be called on an instance's history manager or a model's history manager. ```python >>> t0 = datetime.now() >>> document1 = RankedDocument.objects.create(rank=42) >>> document2 = RankedDocument.objects.create(rank=84) >>> t1 = datetime.now() >>> RankedDocument.history.as_of(t1) , ]> >>> RankedDocument.history.as_of(t1).filter(rank__lte=50) ]> ``` -------------------------------- ### Diffing Many-to-Many Relationship Changes Source: https://github.com/django-commons/django-simple-history/blob/master/docs/historical_model.md Demonstrates how to retrieve and compare historical records to see changes in many-to-many relationships, showing the state before and after modifications. ```python informal = Category.objects.create(name="informal questions") official = Category.objects.create(name="official questions") p = Poll.objects.create(question="what's up?") p.categories.add(informal, official) p.categories.remove(informal) last_record = p.history.latest() previous_record = last_record.prev_record delta = last_record.diff_against(previous_record) for change in delta.changes: print("{} changed from {} to {}".format(change.field, change.old, change.new)) ``` -------------------------------- ### Add simple_history to INSTALLED_APPS Source: https://github.com/django-commons/django-simple-history/blob/master/docs/quick_start.md Add 'simple_history' to your Django project's INSTALLED_APPS setting. ```python INSTALLED_APPS = [ # ... 'simple_history', ] ``` -------------------------------- ### Registering Custom User Model for History Tracking Source: https://github.com/django-commons/django-simple-history/blob/master/docs/common_issues.md To track changes made by a custom user model, use the register() method instead of directly setting HistoricalRecords on the model. This is necessary because HistoricalRecords needs to establish a foreign key relationship to the user model. ```default ERRORS: custom_user.HistoricalCustomUser.history_user: (fields.E300) Field defines a relation with model 'custom_user.CustomUser', which is either not installed, or is abstract. ``` -------------------------------- ### Import ContentType using get_model Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Imports the 'ContentType' model using 'django_apps.get_model' in SimpleHistoryAdmin to prevent 'AppRegistryNotReady' exceptions. ```python from django.apps import apps ContentType = apps.get_model('contenttypes', 'ContentType') ``` -------------------------------- ### Add default date to bulk_create_with_history Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Adds a default date parameter to bulk_create_with_history and bulk_update_with_history for easier historical record management. ```python bulk_create_with_history(..., default_date=None) bulk_update_with_history(..., default_date=None) ``` -------------------------------- ### Control history_date indexing Source: https://github.com/django-commons/django-simple-history/blob/master/docs/historical_model.md Configure history_date indexing behavior using SIMPLE_HISTORY_DATE_INDEX in settings.py. Options include disabling indexing, enabling default indexing, or enabling composite indexing with the model's primary key. ```python # disable indexing on history_date SIMPLE_HISTORY_DATE_INDEX = False ``` ```python # enable indexing on history_date (default setting) SIMPLE_HISTORY_DATE_INDEX = True ``` ```python # enable composite indexing on history_date and model pk (to improve as_of queries) # the string is case-insensitive SIMPLE_HISTORY_DATE_INDEX = "Composite" ``` -------------------------------- ### Compile Translation Messages Source: https://github.com/django-commons/django-simple-history/blob/master/CONTRIBUTING.rst Compiles the translation source files (.po) into binary files (.mo) that Django can use. ```bash django-admin compilemessages ``` -------------------------------- ### Basic Model Diffing Source: https://github.com/django-commons/django-simple-history/blob/master/docs/history_diffing.md Use diff_against() to compare two historical records and iterate through the changes. This basic usage shows the direct field value differences. ```python poll = Poll.objects.create(question="what's up?") poll.question = "what's up, man?" poll.save() new_record, old_record = poll.history.all() delta = new_record.diff_against(old_record) for change in delta.changes: print(f"'{change.field}' changed from '{change.old}' to '{change.new}'") ``` -------------------------------- ### Defining Base Model with History Tracking Source: https://github.com/django-commons/django-simple-history/blob/master/docs/common_issues.md Define a base model that includes HistoricalRecords to enable history tracking. A corresponding historical model will be automatically created, including extra fields and methods. ```python class BaseModel(models.Model): history = HistoricalRecords() ``` -------------------------------- ### Enable Inherited History Tracking Source: https://github.com/django-commons/django-simple-history/blob/master/docs/historical_model.md Use `inherit=True` with `register()` or `HistoricalRecords` to enable history tracking for child models that inherit from a registered model. Be aware of `MultipleRegistrationsError`. ```python from django.contrib.auth.models import User from django.db import models from simple_history import register from simple_history.models import HistoricalRecords # register() example register(User, inherit=True) ``` -------------------------------- ### Add simple filtering to clean_duplicate_history Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Enables simple filtering for the 'clean_duplicate_history' command when a 'minutes' argument is provided. ```python clean_duplicate_history(..., minutes=None) ``` -------------------------------- ### Add Makemigrations for Auth in BitBucket Pipelines Source: https://github.com/django-commons/django-simple-history/blob/master/docs/common_issues.md When using BitBucket Pipelines, include this command before tests to resolve missing migration errors for the auth app's historical User model. ```yaml python manage.py makemigrations auth ``` -------------------------------- ### Fix ModuleNotFound for six Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Addresses a 'ModuleNotFoundError' related to the 'six' library, ensuring proper compatibility. ```python import six ``` -------------------------------- ### Revert model instance to a previous version Source: https://context7.com/django-commons/django-simple-history/llms.txt Restore a model instance to any previous state by accessing the historical record's `instance` property and saving it. A new history record is created for the revert action. ```python from polls.models import Poll from datetime import datetime poll = Poll.objects.create(question="Original", pub_date=datetime.now()) poll.question = "Modified" poll.save() poll.question = "Modified again" poll.save() for record in poll.history.all(): print(f"{record.history_date}: {record.question} (type: {record.history_type})") earliest_record = poll.history.earliest() earliest_record.instance.save() poll.refresh_from_db() print(poll.question) print(poll.history.count()) ``` -------------------------------- ### User Has Both Model and History View Permissions (Enforced) Source: https://github.com/django-commons/django-simple-history/blob/master/docs/admin.md Explicitly grant both view permissions when `SIMPLE_HISTORY_ENFORCE_HISTORY_MODEL_PERMISSIONS` is `True` to allow access to both the main model and its history. ```python # SIMPLE_HISTORY_ENFORCE_HISTORY_MODEL_PERMISSIONS = True in settings user.user_permissions.clear() user.user_permissions.add( Permission.objects.get(codename="view_poll"), Permission.objects.get(codename="view_historicalpoll"), ) ``` -------------------------------- ### Fix bulk_create_with_history support for relation_name Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Resolves an issue with 'bulk_create_with_history' when the 'relation_name' attribute is used. ```python HistoricalRecords(relation_name='related_history') ``` -------------------------------- ### Query History from Specific Database Source: https://github.com/django-commons/django-simple-history/blob/master/docs/multiple_dbs.md Utilize the `using()` method on the history manager to retrieve historical records from a particular database. ```python >>> # This will return a QuerySet from the 'other' database. Poll.history.using('other').all() ``` -------------------------------- ### Display Change Reason Source: https://github.com/django-commons/django-simple-history/blob/master/simple_history/templates/simple_history/object_history_list.html Display the reason provided for the historical change. ```django {{ record.history_change_reason }} ``` -------------------------------- ### Track Model Changes with HistoricalRecords Source: https://context7.com/django-commons/django-simple-history/llms.txt Add HistoricalRecords to a model to automatically track create, update, and delete operations. Each change generates a historical record. ```python from django.db import models from simple_history.models import HistoricalRecords class Poll(models.Model): question = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') history = HistoricalRecords() class Choice(models.Model): poll = models.ForeignKey(Poll, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0) history = HistoricalRecords() ``` ```python # Usage from datetime import datetime poll = Poll.objects.create(question="What's your favorite color?", pub_date=datetime.now()) # Creates: poll record + HistoricalPoll record with history_type='+' poll.question = "What's your favorite programming language?" poll.save() # Creates: HistoricalPoll record with history_type='~' poll.delete() # Creates: HistoricalPoll record with history_type='-' ``` -------------------------------- ### Configure History Tracking in Separate Database Source: https://github.com/django-commons/django-simple-history/blob/master/docs/multiple_dbs.md Set `use_base_model_db=False` in `HistoricalRecords` to direct migrations and audit events to a database specified by the database router, rather than the base model's database. The default value is False. ```python class MyModel(models.Model): ... history = HistoricalRecords(use_base_model_db=False) ``` -------------------------------- ### Reverting a Model to a Previous State Source: https://github.com/django-commons/django-simple-history/blob/master/docs/querying_history.md Programmatically revert a model instance to a previous historical state by accessing the earliest historical record and calling `.instance.save()` on it. This creates a new historical record reflecting the reverted state. ```python >>> earliest_poll = poll.history.earliest() >>> earliest_poll.instance.save() ``` -------------------------------- ### Use _change_reason instead of changeReason Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Switches from using 'changeReason' to '_change_reason' for adding change reasons to historical objects. 'changeReason' is deprecated. ```python instance._change_reason = 'New reason' ``` -------------------------------- ### Track history for Poll and Choice models Source: https://github.com/django-commons/django-simple-history/blob/master/docs/quick_start.md Add HistoricalRecords to your models to automatically track changes. Ensure necessary imports are included. ```python from django.db import models from simple_history.models import HistoricalRecords class Poll(models.Model): question = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') history = HistoricalRecords() class Choice(models.Model): poll = models.ForeignKey(Poll) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0) history = HistoricalRecords() ``` -------------------------------- ### Track users making model changes Source: https://context7.com/django-commons/django-simple-history/llms.txt Track the user responsible for each change using the `HistoryRequestMiddleware`, setting the `_history_user` property on the model, or using a `get_user` callback with `register()`. ```python from django.db import models from simple_history.models import HistoricalRecords from simple_history import register class Article(models.Model): title = models.CharField(max_length=200) author = models.ForeignKey('auth.User', on_delete=models.CASCADE) history = HistoricalRecords() @property def _history_user(self): return self.author @_history_user.setter def _history_user(self, value): self.author = value def get_document_user(instance, **kwargs): return instance.last_editor register(Document, get_user=get_document_user) Article.history.filter(history_user__username='admin') ``` -------------------------------- ### Point-in-Time Queries with as_of() Source: https://context7.com/django-commons/django-simple-history/llms.txt Retrieve the state of a model instance or queryset at a specific point in time using the `as_of()` method. This is useful for reconstructing past states. ```python from datetime import datetime, timedelta from polls.models import Poll # Create a poll and make changes over time t0 = datetime.now() poll = Poll.objects.create(question="Version 1", pub_date=t0) t1 = datetime.now() poll.question = "Version 2" poll.save() t2 = datetime.now() poll.question = "Version 3" poll.save() # Get instance state at a specific time poll_at_t1 = poll.history.as_of(t1) print(poll_at_t1.question) # "Version 1" poll_at_t2 = poll.history.as_of(t2) print(poll_at_t2.question) # "Version 2" ``` -------------------------------- ### Track User with get_user Function for Registration Source: https://github.com/django-commons/django-simple-history/blob/master/docs/user_tracking.md Provide a get_user function to the register method to specify how the user associated with a model change should be identified. This function receives the instance and request (if available). ```python from django.db import models from simple_history.models import HistoricalRecords class Poll(models.Model): question = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') changed_by = models.ForeignKey('auth.User') def get_poll_user(instance, **kwargs): return instance.changed_by register(Poll, get_user=get_poll_user) ``` -------------------------------- ### Modify pre_create_historical_record signature Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Updates the 'pre_create_historical_record' method to pass the 'history_instance' for easier customization. ```python def pre_create_historical_record(self, instance, history_instance): # Custom logic here ``` -------------------------------- ### Add optional manager argument to bulk_update_with_history Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Allows specifying an optional manager for bulk_update_with_history to use instead of the default. ```python bulk_update_with_history(..., manager=None) ``` -------------------------------- ### Save History Record on Specific Database Source: https://github.com/django-commons/django-simple-history/blob/master/docs/multiple_dbs.md Use the `save()` method with the `using()` argument to save changes to a specific database, creating a new historical record there. ```python >>> # This will also create a new historical record on the 'other' database. poll.save(using='other') ``` -------------------------------- ### Add Additional Fields to Historical Models Source: https://github.com/django-commons/django-simple-history/blob/master/docs/historical_model.md Combine `bases` functionality with the `pre_create_historical_record` signal to add fields to historical models that do not exist on the source model. ```python # in models.py class IPAddressHistoricalModel(models.Model): """ Abstract model for history models tracking the IP address. """ ip_address = models.GenericIPAddressField(_('IP address')) class Meta: abstract = True class PollWithExtraFields(models.Model): question = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') history = HistoricalRecords(bases=[IPAddressHistoricalModel,]) ``` ```python # define your signal handler/callback anywhere outside of models.py def add_history_ip_address(sender, **kwargs): history_instance = kwargs['history_instance'] # context.request for use only when the simple_history middleware is on and enabled history_instance.ip_address = HistoricalRecords.context.request.META['REMOTE_ADDR'] ``` ```python # in apps.py from django.apps import AppConfig from simple_history.signals import pre_create_historical_record class TestsConfig(AppConfig): def ready(self): from simple_history.tests.models \ import HistoricalPollWithExtraFields pre_create_historical_record.connect( add_history_ip_address, sender=HistoricalPollWithExtraFields ) ``` -------------------------------- ### Register a third-party model for history tracking Source: https://github.com/django-commons/django-simple-history/blob/master/docs/quick_start.md Use the simple_history.register function to track history for models you do not control, such as the Django User model. ```python from simple_history import register from django.contrib.auth.models import User register(User) ``` -------------------------------- ### Basic HistoricalRecords Usage Source: https://github.com/django-commons/django-simple-history/blob/master/docs/historical_model.md Inherit historical tracking for a model by adding a HistoricalRecords field. This enables tracking of changes to the model's instances. ```python class Poll(models.Model): history = HistoricalRecords(inherit=True) ``` -------------------------------- ### Custom History Manager and QuerySet Source: https://github.com/django-commons/django-simple-history/blob/master/docs/historical_model.md Customize the history manager and historical queryset by providing subclasses of `HistoryManager` and `HistoricalQuerySet`. This allows for custom filtering and data manipulation on historical records. ```python from datetime import timedelta from django.db import models from django.utils import timezone from simple_history.manager import HistoryManager, HistoricalQuerySet from simple_history.models import HistoricalRecords class HistoryQuestionManager(HistoryManager): def published(self): return self.filter(pub_date__lte=timezone.now()) class HistoryQuestionQuerySet(HistoricalQuerySet): def question_prefixed(self): return self.filter(question__startswith="Question: ") class Question(models.Model): pub_date = models.DateTimeField("date published") history = HistoricalRecords( history_manager=HistoryQuestionManager, historical_queryset=HistoryQuestionQuerySet, ) # This is now possible: queryset = Question.history.published().question_prefixed() ``` -------------------------------- ### Query historical instances at a specific time Source: https://context7.com/django-commons/django-simple-history/llms.txt Retrieve all instances of a model as they existed at a particular datetime. This can be useful for auditing or reproducing past states. You can also filter these historical querysets. ```python all_polls_at_t1 = Poll.history.as_of(t1) old_polls = Poll.history.as_of(t1).filter(pub_date__lt=t0) ``` -------------------------------- ### Display History Date and Type Source: https://github.com/django-commons/django-simple-history/blob/master/simple_history/templates/simple_history/object_history_list.html Display the date and time of the historical change and its type. ```django {{ record.history_date }} ``` ```django {{ record.get_history_type_display }} ``` -------------------------------- ### Adjust batch size for populate_history command Source: https://github.com/django-commons/django-simple-history/blob/master/docs/quick_start.md Modify the batch size for the populate_history command using the --batchsize option, useful for large tables. ```bash python manage.py populate_history --batchsize 500 ``` -------------------------------- ### Iterate Through History List Display Source: https://github.com/django-commons/django-simple-history/blob/master/simple_history/templates/simple_history/object_history_list.html Iterate through a list of columns to display for historical records. ```django {% for column in history_list_display %} {% endfor %} ``` -------------------------------- ### Custom history table name with register() Source: https://github.com/django-commons/django-simple-history/blob/master/docs/historical_model.md Use the register() function to specify a custom table name for historical records using the table_name parameter. This provides an alternative way to manage historical table naming. ```python from simple_history.utils import register from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') register(Question, table_name='polls_question_history') ``` -------------------------------- ### Add clean_old_history management command Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Introduces a management command 'clean_old_history' to remove old historical records. ```python python manage.py clean_old_history --days=30 ``` -------------------------------- ### Display Total Entry Count Source: https://github.com/django-commons/django-simple-history/blob/master/simple_history/templates/simple_history/object_history_list.html Display the total number of historical entries and their pluralized label. ```django {{ page_obj.paginator.count }} {% blocktranslate count counter=page_obj.paginator.count %}entry{% plural %}entries{% endblocktranslate %} ``` -------------------------------- ### Add clean_duplicate_history management command Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Introduces the 'clean_duplicate_history' management command to remove duplicate historical entries. ```python python manage.py clean_duplicate_history --model=myapp.MyModel ``` -------------------------------- ### HistoricForeignKey and HistoricOneToOneField Source: https://context7.com/django-commons/django-simple-history/llms.txt Use `HistoricForeignKey` and `HistoricOneToOneField` to ensure temporal consistency when navigating relationships from historical instances. These fields link to historical versions of related objects. ```python from django.db import models from simple_history.models import HistoricalRecords, HistoricForeignKey, HistoricOneToOneField class Author(models.Model): name = models.CharField(max_length=100) history = HistoricalRecords() class Book(models.Model): title = models.CharField(max_length=200) author = HistoricForeignKey(Author, on_delete=models.CASCADE) history = HistoricalRecords() class AuthorProfile(models.Model): author = HistoricOneToOneField(Author, on_delete=models.CASCADE) bio = models.TextField() history = HistoricalRecords() # When you query a Book at a point in time, the author relationship # also resolves to that same point in time from datetime import datetime, timedelta author = Author.objects.create(name="John Smith") book = Book.objects.create(title="My Book", author=author) timestamp = datetime.now() author.name = "John Q. Smith" author.save() # Get book as it was before the author name change historical_book = book.history.as_of(timestamp) print(historical_book.author.name) # "John Smith" (not "John Q. Smith") ``` -------------------------------- ### Render History Pagination Source: https://github.com/django-commons/django-simple-history/blob/master/simple_history/templates/simple_history/object_history_list.html Display pagination controls for navigating through historical records. Includes page numbers, ellipsis, and the current page indicator. ```django {% if pagination_required %} {% for i in page_range %} {% if i == page_obj.paginator.ELLIPSIS %} {{ page_obj.paginator.ELLIPSIS }} {% elif i == page_obj.number %} {{ i }} {% else %} [{{ i }}](?{{ page_var }}={{ i }}) {% endif %} {% endfor %} {% endif %} ``` -------------------------------- ### Convert FileField to CharField setting Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Adds a setting to convert FileFields to CharFields instead of TextFields in historical records. ```python settings.SIMPLE_HISTORY_FILEFIELD_AS_CHARFIELD = True ``` -------------------------------- ### Querying History on a Model Instance Source: https://github.com/django-commons/django-simple-history/blob/master/docs/querying_history.md Access historical records for a specific model instance using the `history` attribute. This works similarly to a model manager. ```python >>> from polls.models import Poll, Choice >>> from datetime import datetime >>> poll = Poll.objects.create(question="what's up?", pub_date=datetime.now()) >>> >>> poll.history.all() [] ``` ```python >>> poll.pub_date = datetime(2007, 4, 1, 0, 0) >>> poll.save() >>> poll.history.all() [, ] ``` -------------------------------- ### Prefetch Related History with Ordering Source: https://github.com/django-commons/django-simple-history/blob/master/docs/querying_history.md Prefetch related historical objects, ordering them by `history_date` in descending order. The prefetched results are stored in the `ordered_histories` attribute. ```python Poll.objects.filter(something).prefetch_related(Prefetch('history', queryset=Poll.history.order_by('-history_date'), to_attr='ordered_histories') ``` -------------------------------- ### Register Models with SimpleHistoryAdmin Source: https://github.com/django-commons/django-simple-history/blob/master/docs/admin.md Inherit from `SimpleHistoryAdmin` when registering models to enable history tracking and viewing in the Django admin site. ```python from django.contrib import admin from simple_history.admin import SimpleHistoryAdmin from .models import Poll, Choice admin.site.register(Poll, SimpleHistoryAdmin) admin.site.register(Choice, SimpleHistoryAdmin) ``` -------------------------------- ### Add HistoryRequestMiddleware Source: https://github.com/django-commons/django-simple-history/blob/master/docs/quick_start.md Include HistoryRequestMiddleware in your Django settings to automatically populate the history user. ```python MIDDLEWARE = [ # ... 'simple_history.middleware.HistoryRequestMiddleware', ] ``` -------------------------------- ### Use TextField for History Change Reason Source: https://github.com/django-commons/django-simple-history/blob/master/docs/historical_model.md Configure `django-simple-history` to use a `TextField` for `history_change_reason` either globally via settings or per-model in the constructor. This is useful for supporting longer change histories. ```python SIMPLE_HISTORY_HISTORY_CHANGE_REASON_USE_TEXT_FIELD=True ``` ```python class TextFieldExample(models.Model): greeting = models.CharField(max_length=100) history = HistoricalRecords( history_change_reason_field=models.TextField(null=True) ) ``` -------------------------------- ### Diffing Many-to-Many Fields (Default vs. Object) Source: https://github.com/django-commons/django-simple-history/blob/master/docs/history_diffing.md Illustrates the difference in diffing many-to-many fields with foreign_keys_are_objs=True. The default shows primary keys in a list of dicts, while True shows related objects or DeletedObject instances. ```python # --- Effect on many-to-many fields --- informal = Category.objects.create(pk=63, name="informal questions") whats_up.categories.add(informal) new = whats_up.history.latest() old = new.prev_record default_delta = new.diff_against(old) # Printing the changes of `default_delta` will output: # 'categories' changed from [] to [{'poll': 15, 'category': 63}] delta_with_objs = new.diff_against(old, foreign_keys_are_objs=True) # Printing the changes of `delta_with_objs` will output: # 'categories' changed from [] to [{'poll': , 'category': }] # Deleting all the categories: Category.objects.all().delete() delta_with_objs = new.diff_against(old, foreign_keys_are_objs=True) # Printing the changes of `delta_with_objs` will now output: # 'categories' changed from [] to [{'poll': , 'category': DeletedObject(model=, pk=63)}] ``` -------------------------------- ### Add user_db_constraint parameter Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Introduces the 'user_db_constraint' parameter to history to prevent circular reference errors on delete. ```python HistoricalRecords(user_db_constraint=True) ``` -------------------------------- ### Manage History Operations on Specific Database Source: https://github.com/django-commons/django-simple-history/blob/master/docs/multiple_dbs.md Employ the `db_manager()` method to execute manager methods, such as `as_of()`, against a specified database. ```python >>> # This will call a manager method on the 'other' database. poll.history.db_manager('other').as_of(datetime(2010, 10, 25, 18, 4, 0)) ``` -------------------------------- ### Skip History Creation on Save Source: https://context7.com/django-commons/django-simple-history/llms.txt Learn how to prevent history records from being created for specific save operations. This can be done using the `skip_history_when_saving` attribute, the `save_without_historical_record` method, or by disabling history globally. ```python from polls.models import Poll from datetime import datetime poll = Poll.objects.create(question="Test", pub_date=datetime.now()) # Method 1: Use skip_history_when_saving attribute poll.question = "Updated without history" poll.skip_history_when_saving = True poll.save() del poll.skip_history_when_saving # Clean up # Method 2: Use save_without_historical_record method poll.question = "Another update without history" poll.save_without_historical_record() # Verify history count print(poll.history.count()) # Only 1 record (the create) ``` -------------------------------- ### Custom history table name with HistoricalRecords Source: https://github.com/django-commons/django-simple-history/blob/master/docs/historical_model.md Specify a custom table name for historical records by using the table_name parameter within HistoricalRecords(). This allows for more organized database schema management. ```python from django.db import models from simple_history.models import HistoricalRecords class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') history = HistoricalRecords(table_name='polls_question_history') ``` -------------------------------- ### Django Management Commands for History Source: https://context7.com/django-commons/django-simple-history/llms.txt Utilize built-in management commands for maintaining and cleaning historical data. Commands include populating history for existing records, cleaning duplicate history entries, and removing old history records. ```bash # Populate history for existing records (useful when adding history to existing models) python manage.py populate_history --auto python manage.py populate_history --auto --batchsize 500 python manage.py populate_history myapp.MyModel # Remove duplicate history records (when save() was called without actual changes) python manage.py clean_duplicate_history --auto python manage.py clean_duplicate_history --auto -m 60 # Last 60 minutes only python manage.py clean_duplicate_history --auto --excluded_fields last_modified python manage.py clean_duplicate_history --auto --base-manager # Use base manager # Remove old history records python manage.py clean_old_history --auto # Default: older than 30 days python manage.py clean_old_history --auto --days 90 python manage.py clean_old_history myapp.MyModel --days 180 ``` -------------------------------- ### Add possibility to create relation to original model Source: https://github.com/django-commons/django-simple-history/blob/master/CHANGES.rst Introduces the ability to create a direct relation from the historical model back to the original model. ```python class HistoricalRecords(models.Model): history_related_name = 'original_model' ``` -------------------------------- ### Track change reasons for model updates Source: https://context7.com/django-commons/django-simple-history/llms.txt Record the reason for changes by setting the `_change_reason` attribute before saving or using the `update_change_reason()` utility. Change reasons are accessible in the history records. ```python from polls.models import Poll from simple_history.utils import update_change_reason from datetime import datetime poll = Poll(question="New poll", pub_date=datetime.now()) poll._change_reason = "Initial creation via admin" poll.save() poll.question = "Updated poll" poll._change_reason = "Fixed typo in question" poll.save() poll.question = "Another update" poll.save() update_change_reason(poll, "Updated for clarity") for record in poll.history.all(): print(f"{record.history_date}: {record.history_change_reason}") ``` -------------------------------- ### Bulk Create Models with History Source: https://github.com/django-commons/django-simple-history/blob/master/docs/common_issues.md Use `bulk_create_with_history` to create multiple model instances and save their history simultaneously. This function is available from `django-simple-history` 2.2.0. ```python >>> from simple_history.utils import bulk_create_with_history >>> from simple_history.tests.models import Poll >>> from django.utils.timezone import now >>> >>> data = [Poll(id=x, question='Question ' + str(x), pub_date=now()) for x in range(1000)] >>> objs = bulk_create_with_history(data, Poll, batch_size=500) >>> Poll.objects.count() 1000 >>> Poll.history.count() 1000 ``` ```python >>> for poll in data: poll._change_reason = 'reason' poll._history_user = my_user poll._history_date = some_date >>> objs = bulk_create_with_history(data, Poll, batch_size=500) >>> Poll.history.get(id=data[0].id).history_change_reason 'reason' ``` ```python >>> user = User.objects.create_user("tester", "tester@example.com") >>> objs = bulk_create_with_history(data, Poll, batch_size=500, default_user=user) >>> Poll.history.get(id=data[0].id).history_user == user True ``` ```python >>> from simple_history.tests.models import PollWithHistoricalSessionAttr >>> data = [ PollWithHistoricalSessionAttr(id=x, question=f'Question {x}') for x in range(10) ] >>> objs = bulk_create_with_history( data, PollWithHistoricalSessionAttr, custom_historical_attrs={'session': 'training'} ) >>> data[0].history.get().session 'training' ``` -------------------------------- ### Compare two historical records using diff_against() Source: https://context7.com/django-commons/django-simple-history/llms.txt Compare two historical records to identify changes between them. The `diff_against` method returns a `ModelDelta` object detailing changed fields, and can optionally handle foreign keys as objects and exclude/include specific fields. ```python from polls.models import Poll from datetime import datetime poll = Poll.objects.create(question="What's up?", pub_date=datetime.now()) poll.question = "What's up, doc?" poll.save() new_record, old_record = poll.history.all()[:2] delta = new_record.diff_against(old_record) print(f"Changed fields: {delta.changed_fields}") for change in delta.changes: print(f"'{change.field}' changed from '{change.old}' to '{change.new}'") delta = new_record.diff_against(old_record, foreign_keys_are_objs=True) delta = new_record.diff_against(old_record, excluded_fields=['pub_date']) delta = new_record.diff_against(old_record, included_fields=['question']) ``` -------------------------------- ### Clean Old History Command Source: https://github.com/django-commons/django-simple-history/blob/master/docs/utils.md Schedule this command to remove historical records older than a specified number of days. The `--auto` flag applies the cleanup to all models with HistoricalRecords. Use the `--days` parameter to set the retention period; the default is 30 days. ```bash python manage.py clean_old_history --auto ``` ```bash python manage.py clean_old_history --days 60 --auto ``` -------------------------------- ### User Has History View Permission Only (Enforced) Source: https://github.com/django-commons/django-simple-history/blob/master/docs/admin.md If `SIMPLE_HISTORY_ENFORCE_HISTORY_MODEL_PERMISSIONS` is `True`, a user with only history view permission can access the history page directly via URL, but the model won't appear in admin lists. ```python # SIMPLE_HISTORY_ENFORCE_HISTORY_MODEL_PERMISSIONS = True in settings user.user_permissions.clear() user.user_permissions.add( Permission.objects.get(codename="view_historicalpoll"), ) ```