mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 12:51:09 -05:00
136 lines
5.0 KiB
Python
136 lines
5.0 KiB
Python
# history_tracking/mixins.py
|
|
from django.db import models
|
|
from django.conf import settings
|
|
from django.contrib.contenttypes.fields import GenericRelation
|
|
|
|
class HistoricalChangeMixin(models.Model):
|
|
"""Mixin for historical models to track changes"""
|
|
comments = GenericRelation('CommentThread', related_query_name='historical_record')
|
|
id = models.BigIntegerField(db_index=True, auto_created=True, blank=True)
|
|
history_date = models.DateTimeField()
|
|
history_id = models.AutoField(primary_key=True)
|
|
history_type = models.CharField(max_length=1)
|
|
history_user = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL,
|
|
null=True,
|
|
on_delete=models.SET_NULL,
|
|
related_name='+'
|
|
)
|
|
history_change_reason = models.CharField(max_length=100, null=True)
|
|
|
|
class Meta:
|
|
abstract = True
|
|
ordering = ['-history_date', '-history_id']
|
|
|
|
@property
|
|
def prev_record(self):
|
|
"""Get the previous record for this instance"""
|
|
try:
|
|
return self.__class__.objects.filter(
|
|
history_date__lt=self.history_date,
|
|
id=self.id
|
|
).order_by('-history_date').first()
|
|
except (AttributeError, TypeError):
|
|
return None
|
|
|
|
@property
|
|
def diff_against_previous(self):
|
|
"""Get enhanced diff with syntax highlighting and metadata"""
|
|
prev_record = self.prev_record
|
|
if not prev_record:
|
|
return {}
|
|
|
|
changes = {}
|
|
for field in self.__dict__:
|
|
if field not in [
|
|
"history_date",
|
|
"history_id",
|
|
"history_type",
|
|
"history_user_id",
|
|
"history_change_reason",
|
|
"history_type",
|
|
"id",
|
|
"_state",
|
|
"_history_user_cache"
|
|
] and not field.startswith("_"):
|
|
try:
|
|
old_value = getattr(prev_record, field)
|
|
new_value = getattr(self, field)
|
|
if old_value != new_value:
|
|
field_type = self._meta.get_field(field).get_internal_type()
|
|
syntax_type = self._get_syntax_type(field_type)
|
|
changes[field] = {
|
|
"old": str(old_value),
|
|
"new": str(new_value),
|
|
"syntax_type": syntax_type,
|
|
"metadata": {
|
|
"field_type": field_type,
|
|
"comment_anchor_id": f"{self.history_id}_{field}",
|
|
"line_numbers": self._compute_line_numbers(old_value, new_value)
|
|
}
|
|
}
|
|
except AttributeError:
|
|
continue
|
|
return changes
|
|
|
|
def _get_syntax_type(self, field_type):
|
|
"""Map Django field types to syntax highlighting types"""
|
|
syntax_map = {
|
|
'TextField': 'text',
|
|
'JSONField': 'json',
|
|
'FileField': 'path',
|
|
'ImageField': 'path',
|
|
'URLField': 'url',
|
|
'EmailField': 'email',
|
|
'CodeField': 'python' # Custom field type for code
|
|
}
|
|
return syntax_map.get(field_type, 'text')
|
|
|
|
def _compute_line_numbers(self, old_value, new_value):
|
|
"""Compute line numbers for diff navigation"""
|
|
old_lines = str(old_value).count('\n') + 1
|
|
new_lines = str(new_value).count('\n') + 1
|
|
return {
|
|
"old": list(range(1, old_lines + 1)),
|
|
"new": list(range(1, new_lines + 1))
|
|
}
|
|
|
|
def get_structured_diff(self, other_version=None):
|
|
"""Get structured diff between two versions with enhanced metadata"""
|
|
compare_to = other_version or self.prev_record
|
|
if not compare_to:
|
|
return None
|
|
|
|
diff_data = self.diff_against_previous
|
|
return {
|
|
"changes": diff_data,
|
|
"metadata": {
|
|
"timestamp": self.history_date.isoformat(),
|
|
"user": self.history_user_display,
|
|
"change_type": self.history_type,
|
|
"reason": self.history_change_reason,
|
|
"performance": {
|
|
"computation_time": None # To be filled by frontend
|
|
}
|
|
},
|
|
"navigation": {
|
|
"next_id": None, # To be filled by frontend
|
|
"prev_id": None, # To be filled by frontend
|
|
"current_position": None # To be filled by frontend
|
|
}
|
|
}
|
|
|
|
@property
|
|
def history_user_display(self):
|
|
"""Get a display name for the history user"""
|
|
if hasattr(self, 'history_user') and self.history_user:
|
|
return str(self.history_user)
|
|
return None
|
|
|
|
def get_instance(self):
|
|
"""Get the model instance this history record represents"""
|
|
try:
|
|
return self.__class__.objects.get(id=self.id)
|
|
except self.__class__.DoesNotExist:
|
|
return None
|