Coverage for app / backend / src / couchers / models / discussions.py: 100%

60 statements  

« prev     ^ index     » next       coverage.py v7.13.2, created at 2026-02-03 06:18 +0000

1from datetime import datetime 

2from typing import TYPE_CHECKING 

3 

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

5from sqlalchemy.orm import Mapped, column_property, mapped_column, relationship 

6 

7from couchers.models.base import Base, communities_seq 

8 

9if TYPE_CHECKING: 

10 from couchers.models import Cluster, User 

11 

12 

13class Discussion(Base, kw_only=True): 

14 """ 

15 forum board 

16 """ 

17 

18 __tablename__ = "discussions" 

19 

20 id: Mapped[int] = mapped_column( 

21 BigInteger, communities_seq, primary_key=True, server_default=communities_seq.next_value(), init=False 

22 ) 

23 

24 title: Mapped[str] = mapped_column(String) 

25 content: Mapped[str] = mapped_column(String) 

26 thread_id: Mapped[int] = mapped_column(ForeignKey("threads.id"), unique=True) 

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

28 

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

30 owner_cluster_id: Mapped[int] = mapped_column(ForeignKey("clusters.id"), index=True) 

31 

32 slug: Mapped[str] = column_property(func.slugify(title)) 

33 

34 thread: Mapped[Thread] = relationship(init=False, backref="discussion", uselist=False) 

35 

36 subscribers: Mapped[list[User]] = relationship( 

37 init=False, backref="discussions", secondary="discussion_subscriptions", viewonly=True 

38 ) 

39 

40 creator_user: Mapped[User] = relationship( 

41 init=False, backref="created_discussions", foreign_keys="Discussion.creator_user_id" 

42 ) 

43 owner_cluster: Mapped[Cluster] = relationship(init=False, back_populates="owned_discussions", uselist=False) 

44 

45 

46class DiscussionSubscription(Base, kw_only=True): 

47 """ 

48 users subscriptions to discussions 

49 """ 

50 

51 __tablename__ = "discussion_subscriptions" 

52 __table_args__ = (UniqueConstraint("discussion_id", "user_id"),) 

53 

54 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False) 

55 

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

57 discussion_id: Mapped[int] = mapped_column(ForeignKey("discussions.id"), index=True) 

58 joined: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), init=False) 

59 left: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None) 

60 

61 user: Mapped[User] = relationship(init=False, backref="discussion_subscriptions") 

62 discussion: Mapped[Discussion] = relationship(init=False, backref="discussion_subscriptions") 

63 

64 

65class Thread(Base, kw_only=True): 

66 """ 

67 Thread 

68 """ 

69 

70 __tablename__ = "threads" 

71 

72 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False) 

73 

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

75 deleted: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None) 

76 

77 

78class Comment(Base, kw_only=True): 

79 """ 

80 Comment 

81 """ 

82 

83 __tablename__ = "comments" 

84 

85 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False) 

86 

87 thread_id: Mapped[int] = mapped_column(ForeignKey("threads.id"), index=True) 

88 author_user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) 

89 content: Mapped[str] = mapped_column(String) # CommonMark without images 

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

91 deleted: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None) 

92 

93 thread: Mapped[Thread] = relationship(init=False, backref="comments") 

94 

95 

96class Reply(Base, kw_only=True): 

97 """ 

98 Reply 

99 """ 

100 

101 __tablename__ = "replies" 

102 

103 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False) 

104 

105 comment_id: Mapped[int] = mapped_column(ForeignKey("comments.id"), index=True) 

106 author_user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) 

107 content: Mapped[str] = mapped_column(String) # CommonMark without images 

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

109 deleted: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None) 

110 

111 comment: Mapped[Comment] = relationship(init=False, backref="replies") 

112 

113 

114class ClusterDiscussionAssociation(Base, kw_only=True): 

115 """ 

116 discussions related to clusters 

117 """ 

118 

119 __tablename__ = "cluster_discussion_associations" 

120 __table_args__ = (UniqueConstraint("discussion_id", "cluster_id"),) 

121 

122 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False) 

123 

124 discussion_id: Mapped[int] = mapped_column(ForeignKey("discussions.id"), index=True) 

125 cluster_id: Mapped[int] = mapped_column(ForeignKey("clusters.id"), index=True) 

126 

127 discussion: Mapped[Discussion] = relationship(init=False, backref="cluster_discussion_associations") 

128 cluster: Mapped[Cluster] = relationship(init=False, backref="cluster_discussion_associations")