Coverage for src/couchers/models/uploads.py: 100%

51 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-12-25 10:58 +0000

1from datetime import datetime 

2from typing import TYPE_CHECKING, Any 

3 

4from sqlalchemy import BigInteger, DateTime, Float, ForeignKey, String, UniqueConstraint, func 

5from sqlalchemy.ext.hybrid import hybrid_property 

6from sqlalchemy.orm import Mapped, mapped_column, relationship 

7 

8from couchers import urls 

9from couchers.models.base import Base 

10 

11if TYPE_CHECKING: 

12 from couchers.models.users import User 

13 

14 

15class InitiatedUpload(Base): 

16 """ 

17 Started downloads, not necessarily complete yet. 

18 """ 

19 

20 __tablename__ = "initiated_uploads" 

21 

22 key: Mapped[str] = mapped_column(String, primary_key=True) 

23 

24 # timezones should always be UTC 

25 created: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) 

26 expiry: Mapped[datetime] = mapped_column(DateTime(timezone=True)) 

27 

28 initiator_user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True) 

29 

30 initiator_user: Mapped["User"] = relationship("User") 

31 

32 @hybrid_property 

33 def is_valid(self) -> Any: 

34 return (self.created <= func.now()) & (self.expiry >= func.now()) 

35 

36 

37class Upload(Base): 

38 """ 

39 Completed uploads. 

40 """ 

41 

42 __tablename__ = "uploads" 

43 

44 key: Mapped[str] = mapped_column(String, primary_key=True) 

45 

46 filename: Mapped[str] = mapped_column(String) 

47 created: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) 

48 creator_user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True) 

49 

50 # photo credit, etc 

51 credit: Mapped[str | None] = mapped_column(String, nullable=True) 

52 

53 creator_user: Mapped["User"] = relationship("User", backref="uploads", foreign_keys="Upload.creator_user_id") 

54 

55 def _url(self, size: str) -> str: 

56 return urls.media_url(filename=self.filename, size=size) 

57 

58 @property 

59 def thumbnail_url(self) -> str: 

60 return self._url("thumbnail") 

61 

62 @property 

63 def full_url(self) -> str: 

64 return self._url("full") 

65 

66 

67class PhotoGallery(Base): 

68 """ 

69 Photo galleries for users or other entities. 

70 """ 

71 

72 __tablename__ = "photo_galleries" 

73 

74 id: Mapped[int] = mapped_column(BigInteger, primary_key=True) 

75 

76 # For now, galleries are owned by users, but this could be extended 

77 # in the future for communities, events, etc. 

78 owner_user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True) 

79 

80 created: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) 

81 

82 owner_user: Mapped["User"] = relationship("User", foreign_keys=[owner_user_id], back_populates="galleries") 

83 photos: Mapped[list["PhotoGalleryItem"]] = relationship( 

84 "PhotoGalleryItem", 

85 back_populates="gallery", 

86 order_by="PhotoGalleryItem.position", 

87 ) 

88 

89 

90class PhotoGalleryItem(Base): 

91 """ 

92 Individual photos within a gallery with ordering and captions. 

93 """ 

94 

95 __tablename__ = "photo_gallery_items" 

96 

97 id: Mapped[int] = mapped_column(BigInteger, primary_key=True) 

98 

99 gallery_id: Mapped[int] = mapped_column(ForeignKey("photo_galleries.id"), index=True) 

100 upload_key: Mapped[str] = mapped_column(ForeignKey("uploads.key")) 

101 

102 # Float position for ordering - allows inserting between items without shifting 

103 position: Mapped[float] = mapped_column(Float) 

104 

105 caption: Mapped[str | None] = mapped_column(String) 

106 

107 created: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) 

108 

109 gallery: Mapped["PhotoGallery"] = relationship("PhotoGallery", back_populates="photos") 

110 upload: Mapped["Upload"] = relationship("Upload") 

111 

112 __table_args__ = ( 

113 # Ensure each upload is only in a gallery once 

114 UniqueConstraint("gallery_id", "upload_key", name="uix_gallery_upload"), 

115 )