from django.db import models from django.utils.translation import gettext_lazy as _ import uuid from accounts.models import CustomUser from games.models import Game class OCRStatus(models.TextChoices): TO_PROCESS = "to_process", _("To Process") PROCESSING = "processing", _("Processing") PROCESSED = "processed", _("Processed") ERROR = "error", _("Error processing") def submission_file_upload_path(instance, filename): ext = filename.split(".")[-1] if "." in filename else "" new_filename = f"{instance.id}_{filename}" if ext else str(instance.id) date = instance.created_at.strftime("%Y/%m/%d") user = instance.submission.user.username return f"{instance.submission.game.path.lstrip('/')}/{date}/{user}/{instance.submission.id}/{new_filename}" class BaseModel(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # Timestamps created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: abstract = True class Submission(BaseModel): game = models.ForeignKey( Game, on_delete=models.CASCADE, null=True, blank=True, related_name="submissions", ) user = models.ForeignKey( CustomUser, on_delete=models.CASCADE, null=True, blank=True, help_text="User who made the submission (null for anonymous)", related_name="user_submissions", ) # Submission metadata notes = models.TextField( null=True, blank=True, help_text="Optional notes about the submission", ) class SubmissionFile(BaseModel): submission = models.ForeignKey( Submission, on_delete=models.CASCADE, related_name="files", ) file = models.FileField( upload_to=submission_file_upload_path, help_text=_("Uploaded file"), ) original_filename = models.CharField( max_length=255, help_text=_("Original filename as uploaded by user"), ) file_size = models.PositiveIntegerField(help_text=_("File size in bytes")) content_type = models.CharField( max_length=100, help_text=_("MIME type of the file"), ) # OCR metadata ocr_status = models.CharField( max_length=20, choices=OCRStatus.choices, default=OCRStatus.TO_PROCESS, help_text=_("OCR processing status for the entire file"), ) ocr_raw_data = models.TextField( blank=True, help_text=_("Raw OCR data for the entire file"), ) class Meta: ordering = ["file", "created_at"] verbose_name = "Submission File" verbose_name_plural = "Submission Files" @property def file_url(self) -> str | None: if self.file: return self.file.url def save(self, *args, **kwargs): if self.file and not self.file_size: self.file_size = self.file.size super().save(*args, **kwargs) class ImageCrop(BaseModel): submission_file = models.ForeignKey( SubmissionFile, on_delete=models.CASCADE, related_name="crops", ) # Crop coordinates (pixels): x1, y1, x2, y2 x1 = models.PositiveIntegerField(help_text=_("Top-left x coordinate")) y1 = models.PositiveIntegerField(help_text=_("Top-left y coordinate")) x2 = models.PositiveIntegerField(help_text=_("Bottom-right x coordinate")) y2 = models.PositiveIntegerField(help_text=_("Bottom-right y coordinate")) # Label/name for the crop label = models.CharField( max_length=255, blank=True, help_text=_("Optional label for this crop region"), ) # OCR results for this crop ocr_status = models.CharField( max_length=20, choices=OCRStatus.choices, default=OCRStatus.TO_PROCESS, help_text=_("OCR processing status for this crop"), ) ocr_raw_data = models.TextField( blank=True, help_text=_("Raw OCR data for this crop"), ) class Meta: ordering = ["created_at"] verbose_name = "Image Crop" verbose_name_plural = "Image Crops" def __str__(self): return f"{self.label or f'Crop ({self.x1}, {self.y1}, {self.x2}, {self.y2})'}"