mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 19:51:08 -05:00
Refactor comments app to use mixins for comment functionality; update admin interfaces and add historical model fixes
This commit is contained in:
61
history_tracking/custom_history.py
Normal file
61
history_tracking/custom_history.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from django.db import models
|
||||
from simple_history.models import HistoricalRecords
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.utils.timezone import now
|
||||
|
||||
class CustomHistoricalRecords(HistoricalRecords):
|
||||
"""Custom historical records that properly handle generic relations."""
|
||||
|
||||
def copy_fields(self, model):
|
||||
"""
|
||||
Copy fields from the model to the historical record model,
|
||||
excluding GenericRelation fields.
|
||||
"""
|
||||
fields = {}
|
||||
for field in model._meta.concrete_fields:
|
||||
if not isinstance(field, GenericRelation) and field.name not in [
|
||||
'comments', 'comment_threads', 'photos', 'reviews'
|
||||
]:
|
||||
fields[field.name] = field.clone()
|
||||
return fields
|
||||
|
||||
def create_history_model(self, model, inherited):
|
||||
"""
|
||||
Override to ensure we don't create duplicate auto fields.
|
||||
"""
|
||||
attrs = {
|
||||
'__module__': model.__module__,
|
||||
'_history_excluded_fields': ['comments', 'comment_threads', 'photos', 'reviews'],
|
||||
}
|
||||
|
||||
app_module = '%s.models' % model._meta.app_label
|
||||
|
||||
if inherited:
|
||||
# inherited use models.AutoField instead of models.IntegerField
|
||||
attrs.update({
|
||||
'id': models.AutoField(primary_key=True),
|
||||
'history_id': models.AutoField(primary_key=True),
|
||||
'history_date': models.DateTimeField(default=now),
|
||||
'history_change_reason': models.CharField(max_length=100, null=True),
|
||||
'history_type': models.CharField(max_length=1, choices=(
|
||||
('+', 'Created'),
|
||||
('~', 'Changed'),
|
||||
('-', 'Deleted'),
|
||||
)),
|
||||
'history_user': models.ForeignKey(
|
||||
'accounts.User',
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='+'
|
||||
),
|
||||
})
|
||||
|
||||
# Convert field to point to historical model
|
||||
fields = self.copy_fields(model)
|
||||
attrs.update(fields)
|
||||
|
||||
return type(
|
||||
str('Historical%s' % model._meta.object_name),
|
||||
(models.Model,),
|
||||
attrs
|
||||
)
|
||||
49
history_tracking/historical_fields.py
Normal file
49
history_tracking/historical_fields.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey
|
||||
from typing import List, Type
|
||||
|
||||
def get_trackable_fields(model_class: Type[models.Model]) -> List[models.Field]:
|
||||
"""Get fields that should be tracked in history."""
|
||||
if getattr(model_class, '_is_historical_model', False):
|
||||
# For historical models, only return core history fields
|
||||
return [
|
||||
models.BigAutoField(name='id', primary_key=True),
|
||||
models.DateTimeField(name='history_date'),
|
||||
models.CharField(name='history_change_reason', max_length=100, null=True),
|
||||
models.CharField(name='history_type', max_length=1),
|
||||
models.ForeignKey(
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
name='history_user',
|
||||
null=True,
|
||||
on_delete=models.SET_NULL
|
||||
)
|
||||
]
|
||||
|
||||
trackable_fields = []
|
||||
excluded_fields = {
|
||||
'comment_threads', 'comments', 'photos', 'reviews',
|
||||
'thread', 'content_type', 'object_id', 'content_object'
|
||||
}
|
||||
|
||||
for field in model_class._meta.get_fields():
|
||||
# Skip fields we don't want to track
|
||||
if any([
|
||||
isinstance(field, (GenericRelation, GenericForeignKey)),
|
||||
field.name in excluded_fields,
|
||||
field.is_relation and hasattr(field.remote_field.model, '_meta') and
|
||||
'commentthread' in field.remote_field.model._meta.model_name.lower()
|
||||
]):
|
||||
continue
|
||||
|
||||
trackable_fields.append(field)
|
||||
|
||||
return trackable_fields
|
||||
|
||||
class HistoricalFieldsMixin:
|
||||
"""Mixin that controls which fields are copied to historical models."""
|
||||
|
||||
@classmethod
|
||||
def get_fields_to_track(cls) -> List[models.Field]:
|
||||
"""Get fields that should be tracked in history."""
|
||||
return get_trackable_fields(cls)
|
||||
@@ -1,10 +1,13 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||
from django.db.models.fields.related import RelatedField
|
||||
from django.contrib.auth import get_user_model
|
||||
from simple_history.models import HistoricalRecords
|
||||
from .mixins import HistoricalChangeMixin
|
||||
from typing import Any, Type, TypeVar, cast, Optional
|
||||
from .historical_fields import HistoricalFieldsMixin
|
||||
from typing import Any, Type, TypeVar, cast, Optional, List
|
||||
from django.db.models import QuerySet
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import timezone
|
||||
@@ -13,13 +16,28 @@ T = TypeVar('T', bound=models.Model)
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
class HistoricalModel(models.Model):
|
||||
class HistoricalModel(models.Model, HistoricalFieldsMixin):
|
||||
"""Abstract base class for models with history tracking"""
|
||||
id = models.BigAutoField(primary_key=True)
|
||||
history: HistoricalRecords = HistoricalRecords(
|
||||
|
||||
@classmethod
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
"""Initialize subclass with proper configuration."""
|
||||
super().__init_subclass__(**kwargs)
|
||||
# Mark historical models
|
||||
if cls.__name__.startswith('Historical'):
|
||||
cls._is_historical_model = True
|
||||
# Remove any inherited generic relations
|
||||
for field in list(cls._meta.private_fields):
|
||||
if isinstance(field, GenericRelation):
|
||||
cls._meta.private_fields.remove(field)
|
||||
else:
|
||||
cls._is_historical_model = False
|
||||
history = HistoricalRecords(
|
||||
inherit=True,
|
||||
bases=(HistoricalChangeMixin,),
|
||||
excluded_fields=['comments', 'photos', 'reviews'] # Exclude all generic relations
|
||||
bases=[HistoricalChangeMixin],
|
||||
excluded_fields=['comments', 'comment_threads', 'photos', 'reviews'],
|
||||
use_base_model_db=True # Use base model's db
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
Reference in New Issue
Block a user