from django.conf import settings from django.db import models from django.db.models import Q from infrasynth.tenancy.mixins import GlobalOrTenantModel, TenantOwnedModel class StoredFile(TenantOwnedModel): storage_backend = models.CharField(max_length=50) storage_key = models.CharField(max_length=500) original_filename = models.CharField(max_length=500) mime_type = models.CharField(max_length=255) size_bytes = models.BigIntegerField() checksum_sha256 = models.CharField(max_length=64) is_public = models.BooleanField(default=False) category = models.ForeignKey( "FileCategory", on_delete=models.SET_NULL, null=True, blank=True, related_name="+", ) metadata = models.JSONField(default=dict) uploaded_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="+", ) created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = "files_stored_file" indexes = [models.Index(fields=["tenant_id", "created_at"])] class FileCategory(TenantOwnedModel): slug = models.SlugField(max_length=100) name = models.CharField(max_length=200) description = models.TextField(blank=True) storage_path = models.CharField(max_length=500) allowed_extensions = models.TextField(blank=True) max_size_bytes = models.BigIntegerField(null=True, blank=True) is_active = models.BooleanField(default=True) storage_backend_override = models.CharField(max_length=50, blank=True) class Meta: db_table = "files_category" constraints = [ models.UniqueConstraint(fields=["tenant", "slug"], name="uniq_file_category_slug_per_tenant"), ] class ProcessingPipeline(GlobalOrTenantModel): name = models.CharField(max_length=200) slug = models.SlugField(max_length=100) steps = models.JSONField() is_active = models.BooleanField(default=True) class Meta: db_table = "files_pipeline" constraints = [ models.UniqueConstraint(fields=["tenant", "slug"], name="uniq_pipeline_slug_per_tenant"), models.UniqueConstraint( fields=["slug"], condition=Q(tenant__isnull=True), name="uniq_global_pipeline_slug", ), ] class PipelineExecution(TenantOwnedModel): class Status(models.TextChoices): PENDING = "pending", "Pending" RUNNING = "running", "Running" COMPLETED = "completed", "Completed" FAILED = "failed", "Failed" file = models.ForeignKey(StoredFile, on_delete=models.CASCADE, related_name="pipeline_executions") pipeline = models.ForeignKey( ProcessingPipeline, on_delete=models.SET_NULL, null=True, blank=True, related_name="+", ) status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING) started_at = models.DateTimeField(null=True, blank=True) completed_at = models.DateTimeField(null=True, blank=True) output_file = models.ForeignKey( StoredFile, on_delete=models.SET_NULL, null=True, blank=True, related_name="+", ) error = models.TextField(blank=True) class Meta: db_table = "files_pipeline_execution" indexes = [models.Index(fields=["tenant_id", "status"])]