Files
thrillwiki_django_no_react/core/models.py
2024-10-28 17:09:57 -04:00

93 lines
3.2 KiB
Python

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.utils.text import slugify
class SlugHistory(models.Model):
"""
Model for tracking slug changes across all models that use slugs.
Uses generic relations to work with any model.
"""
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.CharField(max_length=50) # Using CharField to work with our custom IDs
content_object = GenericForeignKey('content_type', 'object_id')
old_slug = models.SlugField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
indexes = [
models.Index(fields=['content_type', 'object_id']),
models.Index(fields=['old_slug']),
]
verbose_name_plural = 'Slug histories'
ordering = ['-created_at']
def __str__(self):
return f"Old slug '{self.old_slug}' for {self.content_object}"
class SluggedModel(models.Model):
"""
Abstract base model that provides slug functionality with history tracking.
"""
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
class Meta:
abstract = True
def save(self, *args, **kwargs):
# Get the current instance from DB if it exists
if self.pk:
try:
old_instance = self.__class__.objects.get(pk=self.pk)
# If slug has changed, save the old one to history
if old_instance.slug != self.slug:
SlugHistory.objects.create(
content_type=ContentType.objects.get_for_model(self),
object_id=getattr(self, self.get_id_field_name()),
old_slug=old_instance.slug
)
except self.__class__.DoesNotExist:
pass
# Generate slug if not set
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
def get_id_field_name(self):
"""
Returns the name of the read-only ID field for this model.
Should be overridden by subclasses.
"""
raise NotImplementedError(
"Subclasses of SluggedModel must implement get_id_field_name()"
)
@classmethod
def get_by_slug(cls, slug):
"""
Get an object by its current or historical slug.
Returns (object, is_old_slug) tuple.
"""
try:
# Try to get by current slug first
return cls.objects.get(slug=slug), False
except cls.DoesNotExist:
# Try to find in slug history
history = SlugHistory.objects.filter(
content_type=ContentType.objects.get_for_model(cls),
old_slug=slug
).order_by('-created_at').first()
if history:
return cls.objects.get(
**{cls.get_id_field_name(): history.object_id}
), True
raise cls.DoesNotExist(
f"{cls.__name__} with slug '{slug}' does not exist"
)