{"id":6597,"library":"django-pghistory","title":"Django PG History","description":"django-pghistory is a Django library that provides simple, powerful, and performant history tracking for Django models using PostgreSQL's event triggers. It leverages database-level features for efficiency, making it suitable for auditing and versioning. The current stable version is 3.9.2, and it maintains a regular release cadence with several minor versions and patches throughout the year.","status":"active","version":"3.9.2","language":"en","source_language":"en","source_url":"https://github.com/AmbitionEng/django-pghistory","tags":["django","history","audit","versioning","postgresql","orm"],"install":[{"cmd":"pip install django-pghistory 'psycopg[binary]' django","lang":"bash","label":"Install with psycopg (for PostgreSQL)"}],"dependencies":[{"reason":"Core framework dependency for the library.","package":"Django","optional":false},{"reason":"PostgreSQL adapter for Django, required for interaction with the database triggers.","package":"psycopg","optional":false}],"imports":[{"symbol":"track","correct":"import pghistory"},{"symbol":"context","correct":"import pghistory"},{"symbol":"get_event_model","correct":"import pghistory"},{"symbol":"ProxyField","correct":"import pghistory.models"},{"note":"pghistory provides its own admin integration for history models, which simplifies registration and adds extra features.","wrong":"from django.contrib import admin; admin.site.register(MyHistoryModel)","symbol":"admin","correct":"import pghistory.admin"}],"quickstart":{"code":"import os\nimport django\nfrom django.conf import settings\nfrom django.db import models\n\nsettings.configure(\n    DEBUG=True,\n    INSTALLED_APPS=[\n        'django.contrib.auth',\n        'django.contrib.contenttypes',\n        'django.contrib.sessions',\n        'django.contrib.sites',\n        'django.contrib.messages',\n        'django.contrib.staticfiles',\n        'pghistory',\n        'pghistory.admin',\n        'your_app_name' # Replace with your actual app name\n    ],\n    DATABASES={\n        'default': {\n            'ENGINE': 'django.db.backends.postgresql',\n            'NAME': os.environ.get('PG_DB_NAME', 'test_db'),\n            'USER': os.environ.get('PG_DB_USER', 'test_user'),\n            'HOST': os.environ.get('PG_DB_HOST', 'localhost'),\n            'PORT': os.environ.get('PG_DB_PORT', '5432'),\n            'PASSWORD': os.environ.get('PG_DB_PASSWORD', 'password'),\n        }\n    },\n    MIDDLEWARE_CLASSES=(\n        'django.contrib.sessions.middleware.SessionMiddleware',\n        'django.middleware.common.CommonMiddleware',\n        'django.middleware.csrf.CsrfViewMiddleware',\n        'django.contrib.auth.middleware.AuthenticationMiddleware',\n        'django.contrib.messages.middleware.MessageMiddleware',\n    ),\n    TEMPLATES=[\n        {\n            'BACKEND': 'django.template.backends.django.DjangoTemplates',\n            'DIRS': [],\n            'APP_DIRS': True,\n            'OPTIONS': {\n                'context_processors': [\n                    'django.template.context_processors.debug',\n                    'django.template.context_processors.request',\n                    'django.contrib.auth.context_processors.auth',\n                    'django.contrib.messages.context_processors.messages',\n                ],\n            },\n        },\n    ],\n    STATIC_URL = '/static/',\n    SECRET_KEY = 'a-very-secret-key',\n)\ndjango.setup()\n\nimport pghistory\n\n# Define your model\n@pghistory.track(fields=['name', 'value'])\nclass MyModel(models.Model):\n    name = models.CharField(max_length=255)\n    value = models.IntegerField(default=0)\n    created_at = models.DateTimeField(auto_now_add=True)\n    updated_at = models.DateTimeField(auto_now=True)\n\n    def __str__(self):\n        return f\"{self.name} - {self.value}\"\n\n    class Meta:\n        app_label = 'your_app_name'\n\n# After running makemigrations and migrate:\n# from your_app_name.models import MyModel\n# obj = MyModel.objects.create(name='Test', value=10)\n# obj.value = 20\n# obj.save()\n#\n# HistoryModel = pghistory.get_event_model(MyModel)\n# for event in HistoryModel.objects.all():\n#     print(f\"ID: {event.pgh_obj_id}, Name: {event.name}, Value: {event.value}, Changed at: {event.pgh_created_at}\")\n","lang":"python","description":"This quickstart demonstrates how to define a Django model and enable history tracking using the `@pghistory.track` decorator. It also shows how to retrieve the historical events through the automatically generated history model. Remember to add `pghistory` and `pghistory.admin` to `INSTALLED_APPS` and run `makemigrations` and `migrate` after defining your models."},"warnings":[{"fix":"Upgrade your Python environment to 3.10 or later. Consider Django 6.0 compatibility, which was also introduced in 3.9.0.","message":"Python 3.9 support was dropped in version 3.9.0. Ensure your project is running Python 3.10 or newer.","severity":"breaking","affected_versions":">=3.9.0"},{"fix":"To avoid automatic registration, remove 'pghistory.admin' from `INSTALLED_APPS` and register history models explicitly with `pghistory.admin.site.register(pghistory.get_event_model(MyModel))`.","message":"Installing `pghistory.admin` in `INSTALLED_APPS` registers the admin automatically for all tracked models. If you need custom admin registration for history models, you should not include `pghistory.admin` and register them manually using `pghistory.admin.site.register`.","severity":"gotcha","affected_versions":">=3.9.2 (documentation warning added), all versions."},{"fix":"Be aware that context events are primarily for DML (INSERT, UPDATE, DELETE) operations. Do not expect context for all SQL statement types. Review the documentation for specific ignored statements.","message":"Context tracking in `django-pghistory` can be ignored for certain SQL statements (e.g., `VACUUM`, `SELECT` without `FOR UPDATE`). This is by design to prevent issues or track irrelevant changes, but it means certain database operations will not generate context events.","severity":"gotcha","affected_versions":">=3.7.0, >=3.8.3"},{"fix":"Refer to the 'Migrating existing tracking models to denormalized context' section in the official documentation for detailed steps on how to migrate your historical data and schema.","message":"Upgrading existing tracking models to denormalized context requires specific migration steps. Simply changing tracking configuration won't automatically update existing history tables.","severity":"gotcha","affected_versions":">=3.8.0"},{"fix":"If adopting statement-level tracking (`@pghistory.track(level=pghistory.Statement)` or `PGHISTORY_LEVEL = pghistory.Statement`), thoroughly test how your history is recorded for bulk actions. Understand the differences in how `new`, `old`, and context data are captured.","message":"Statement-level history tracking (introduced in 3.6.0) significantly changes how triggers work, especially for bulk operations. While it offers performance improvements, it may alter the granularity or content of recorded history events compared to row-level tracking.","severity":"gotcha","affected_versions":">=3.6.0"}],"env_vars":null,"last_verified":"2026-04-15T00:00:00.000Z","next_check":"2026-07-14T00:00:00.000Z","problems":[]}