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