Files
thrillwiki_django_no_react/history_tracking/mixins.py

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