| """ |
| Core abstract base models for the entire SaaS platform. |
| |
| All application models should inherit from BaseModel or SoftDeleteModel |
| to ensure consistent UUID primary keys, timestamps, and soft-delete behavior. |
| """ |
| import uuid |
| from typing import Optional |
|
|
| from django.db import models |
| from django.utils import timezone |
|
|
|
|
| class BaseManager(models.Manager): |
| """Default manager — returns all rows.""" |
| pass |
|
|
|
|
| class ActiveManager(models.Manager): |
| """Manager that excludes soft-deleted rows.""" |
|
|
| def get_queryset(self) -> models.QuerySet: |
| return super().get_queryset().filter(deleted_at__isnull=True) |
|
|
|
|
| class BaseModel(models.Model): |
| """ |
| Abstract base providing UUID primary key and timestamps. |
| |
| Every model in the project should inherit from this. |
| """ |
|
|
| id: models.UUIDField = models.UUIDField( |
| primary_key=True, |
| default=uuid.uuid4, |
| editable=False, |
| ) |
| created_at: models.DateTimeField = models.DateTimeField( |
| default=timezone.now, |
| editable=False, |
| ) |
| updated_at: models.DateTimeField = models.DateTimeField(auto_now=True) |
|
|
| class Meta: |
| abstract = True |
| ordering = ["-created_at"] |
|
|
| def __str__(self) -> str: |
| return str(self.id) |
|
|
|
|
| class SoftDeleteModel(BaseModel): |
| """ |
| Abstract base adding soft-delete capability. |
| |
| Uses `deleted_at` timestamp instead of hard deletion. |
| Access `objects` for active-only, `all_objects` for everything. |
| """ |
|
|
| deleted_at: models.DateTimeField = models.DateTimeField( |
| null=True, |
| blank=True, |
| default=None, |
| ) |
|
|
| objects = ActiveManager() |
| all_objects = BaseManager() |
|
|
| class Meta(BaseModel.Meta): |
| abstract = True |
|
|
| def soft_delete(self) -> None: |
| """Mark this record as deleted.""" |
| self.deleted_at = timezone.now() |
| self.save(update_fields=["deleted_at", "updated_at"]) |
|
|
| def restore(self) -> None: |
| """Restore a soft-deleted record.""" |
| self.deleted_at = None |
| self.save(update_fields=["deleted_at", "updated_at"]) |
|
|
| @property |
| def is_deleted(self) -> bool: |
| return self.deleted_at is not None |
|
|