Files
thrillwiki_django_no_react/core/history.py
2025-08-15 12:24:20 -04:00

93 lines
3.3 KiB
Python

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
from django.conf import settings
from typing import Any, Dict, Optional
from django.db.models import QuerySet
class DiffMixin:
"""Mixin to add diffing capabilities to models"""
def get_prev_record(self) -> Optional[Any]:
"""Get the previous record for this instance"""
try:
return type(self).objects.filter(
pgh_created_at__lt=self.pgh_created_at,
pgh_obj_id=self.pgh_obj_id
).order_by('-pgh_created_at').first()
except (AttributeError, TypeError):
return None
def diff_against_previous(self) -> Dict:
"""Compare this record against the previous one"""
prev_record = self.get_prev_record()
if not prev_record:
return {}
skip_fields = {
'pgh_id', 'pgh_created_at', 'pgh_label',
'pgh_obj_id', 'pgh_context_id', '_state',
'created_at', 'updated_at'
}
changes = {}
for field, value in self.__dict__.items():
# Skip internal fields and those we don't want to track
if field.startswith('_') or field in skip_fields or field.endswith('_id'):
continue
try:
old_value = getattr(prev_record, field)
new_value = value
if old_value != new_value:
changes[field] = {
"old": str(old_value) if old_value is not None else "None",
"new": str(new_value) if new_value is not None else "None"
}
except AttributeError:
continue
return changes
class TrackedModel(models.Model):
"""Abstract base class for models that need history tracking"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
def get_history(self) -> QuerySet:
"""Get all history records for this instance in chronological order"""
event_model = self.events.model # pghistory provides this automatically
if event_model:
return event_model.objects.filter(
pgh_obj_id=self.pk
).order_by('-pgh_created_at')
return self.__class__.objects.none()
class HistoricalSlug(models.Model):
"""Track historical slugs for models"""
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
slug = models.SlugField(max_length=255)
created_at = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='historical_slugs'
)
class Meta:
unique_together = ('content_type', 'slug')
indexes = [
models.Index(fields=['content_type', 'object_id']),
models.Index(fields=['slug']),
]
def __str__(self) -> str:
return f"{self.content_type} - {self.object_id} - {self.slug}"