FossilRepo

fossilrepo / core / models.py
Source Blame History 74 lines
4ce269c… ragelink 1 import uuid
4ce269c… ragelink 2
4ce269c… ragelink 3 from django.conf import settings
4ce269c… ragelink 4 from django.db import models
4ce269c… ragelink 5 from django.utils import timezone
4ce269c… ragelink 6 from django.utils.text import slugify
4ce269c… ragelink 7 from simple_history.models import HistoricalRecords
4ce269c… ragelink 8
4ce269c… ragelink 9
4ce269c… ragelink 10 class Tracking(models.Model):
4ce269c… ragelink 11 """Abstract base providing audit trails and soft deletes for all business models."""
4ce269c… ragelink 12
4ce269c… ragelink 13 version = models.PositiveIntegerField(default=1, editable=False)
4ce269c… ragelink 14 created_at = models.DateTimeField(auto_now_add=True)
4ce269c… ragelink 15 created_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="+")
4ce269c… ragelink 16 updated_at = models.DateTimeField(auto_now=True)
4ce269c… ragelink 17 updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="+")
4ce269c… ragelink 18 deleted_at = models.DateTimeField(null=True, blank=True)
4ce269c… ragelink 19 deleted_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL, related_name="+")
4ce269c… ragelink 20 history = HistoricalRecords(inherit=True)
4ce269c… ragelink 21
4ce269c… ragelink 22 class Meta:
4ce269c… ragelink 23 abstract = True
4ce269c… ragelink 24
4ce269c… ragelink 25 def save(self, *args, **kwargs):
4ce269c… ragelink 26 if self.pk:
4ce269c… ragelink 27 self.version += 1
4ce269c… ragelink 28 super().save(*args, **kwargs)
4ce269c… ragelink 29
4ce269c… ragelink 30 def soft_delete(self, user=None):
4ce269c… ragelink 31 self.deleted_at = timezone.now()
4ce269c… ragelink 32 self.deleted_by = user
4ce269c… ragelink 33 self.save(update_fields=["deleted_at", "deleted_by", "updated_at", "version"])
4ce269c… ragelink 34
4ce269c… ragelink 35 @property
4ce269c… ragelink 36 def is_deleted(self):
4ce269c… ragelink 37 return self.deleted_at is not None
4ce269c… ragelink 38
4ce269c… ragelink 39
4ce269c… ragelink 40 class BaseCoreModel(Tracking):
4ce269c… ragelink 41 """Abstract base for named, addressable entities with UUID external identifiers."""
4ce269c… ragelink 42
4ce269c… ragelink 43 guid = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, db_index=True)
4ce269c… ragelink 44 name = models.CharField(max_length=200)
4ce269c… ragelink 45 slug = models.SlugField(max_length=200, unique=True, db_index=True)
4ce269c… ragelink 46 description = models.TextField(blank=True, default="")
4ce269c… ragelink 47
4ce269c… ragelink 48 class Meta:
4ce269c… ragelink 49 abstract = True
4ce269c… ragelink 50
4ce269c… ragelink 51 def __str__(self):
4ce269c… ragelink 52 return self.name
4ce269c… ragelink 53
4ce269c… ragelink 54 def save(self, *args, **kwargs):
4ce269c… ragelink 55 if not self.slug:
4ce269c… ragelink 56 base_slug = slugify(self.name)
4ce269c… ragelink 57 slug = base_slug
4ce269c… ragelink 58 counter = 1
4ce269c… ragelink 59 model_class = type(self)
4ce269c… ragelink 60 while model_class.objects.filter(slug=slug).exclude(pk=self.pk).exists():
4ce269c… ragelink 61 slug = f"{base_slug}-{counter}"
4ce269c… ragelink 62 counter += 1
4ce269c… ragelink 63 self.slug = slug
4ce269c… ragelink 64 super().save(*args, **kwargs)
4ce269c… ragelink 65
4ce269c… ragelink 66 def get_absolute_url(self):
4ce269c… ragelink 67 return f"/{self._meta.app_label}/{self.slug}/"
4ce269c… ragelink 68
4ce269c… ragelink 69
4ce269c… ragelink 70 class ActiveManager(models.Manager):
4ce269c… ragelink 71 """Manager that excludes soft-deleted records by default."""
4ce269c… ragelink 72
4ce269c… ragelink 73 def get_queryset(self):
4ce269c… ragelink 74 return super().get_queryset().filter(deleted_at__isnull=True)

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button