from django.conf import settings from django.db import models from apps.core.models import Audience, TimeStampedModel def library_upload_path(instance, filename): return f"library/{instance.owner_id}/originals/{filename}" def rendition_upload_path(instance, filename): return f"library/{instance.library_item.owner_id}/renditions/{instance.kind}/{filename}" class LibraryItem(TimeStampedModel): """A single photo or video backed up to a user's private library. Nothing here is visible to anyone but the owner until it is attached to a Moment or a shared Album.""" class MediaType(models.TextChoices): PHOTO = "photo", "Photo" VIDEO = "video", "Video" class ProcessingStatus(models.TextChoices): PENDING = "pending", "Pending" PROCESSING = "processing", "Processing" READY = "ready", "Ready" FAILED = "failed", "Failed" owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="library_items", on_delete=models.CASCADE) file = models.FileField(upload_to=library_upload_path) media_type = models.CharField(max_length=10, choices=MediaType.choices) original_filename = models.CharField(max_length=255) file_size = models.PositiveBigIntegerField(default=0) width = models.PositiveIntegerField(null=True, blank=True) height = models.PositiveIntegerField(null=True, blank=True) duration_seconds = models.FloatField(null=True, blank=True) captured_at = models.DateTimeField(null=True, blank=True) latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) camera_make = models.CharField(max_length=100, blank=True) camera_model = models.CharField(max_length=100, blank=True) checksum = models.CharField(max_length=64, db_index=True, help_text="sha256 of the original file, used for dedup") processing_status = models.CharField( max_length=12, choices=ProcessingStatus.choices, default=ProcessingStatus.PENDING ) class Meta: unique_together = ("owner", "checksum") ordering = ["-captured_at", "-created_at"] def __str__(self): return f"{self.original_filename} ({self.owner})" class MediaRendition(models.Model): """A derived, smaller version of a LibraryItem (thumbnail/feed/large), generated asynchronously so the original never has to be served to the browser just to render a grid.""" class Kind(models.TextChoices): THUMBNAIL = "thumbnail", "Thumbnail" FEED = "feed", "Feed" LARGE = "large", "Large / Fullscreen" library_item = models.ForeignKey(LibraryItem, related_name="renditions", on_delete=models.CASCADE) kind = models.CharField(max_length=10, choices=Kind.choices) file = models.FileField(upload_to=rendition_upload_path) width = models.PositiveIntegerField() height = models.PositiveIntegerField() created_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ("library_item", "kind") def __str__(self): return f"{self.library_item_id} - {self.kind}" class Album(TimeStampedModel): owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="albums", on_delete=models.CASCADE) name = models.CharField(max_length=150) description = models.TextField(blank=True) cover_item = models.ForeignKey( LibraryItem, related_name="+", on_delete=models.SET_NULL, null=True, blank=True ) audience = models.CharField(max_length=10, choices=Audience.choices, default=Audience.ONLY_ME) def __str__(self): return self.name class AlbumItem(models.Model): album = models.ForeignKey(Album, related_name="items", on_delete=models.CASCADE) library_item = models.ForeignKey(LibraryItem, related_name="album_items", on_delete=models.CASCADE) added_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="+", on_delete=models.CASCADE) added_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ("album", "library_item") ordering = ["-added_at"] class AlbumCollaborator(models.Model): class Role(models.TextChoices): VIEWER = "viewer", "Viewer" CONTRIBUTOR = "contributor", "Contributor" ADMIN = "admin", "Admin" class Status(models.TextChoices): PENDING = "pending", "Pending" ACCEPTED = "accepted", "Accepted" album = models.ForeignKey(Album, related_name="collaborators", on_delete=models.CASCADE) user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="album_invites", on_delete=models.CASCADE) role = models.CharField(max_length=11, choices=Role.choices, default=Role.VIEWER) invited_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="+", on_delete=models.CASCADE) status = models.CharField(max_length=8, choices=Status.choices, default=Status.PENDING) class Meta: unique_together = ("album", "user")