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
« 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
4from sqlalchemy import BigInteger, DateTime, ForeignKey, String, UniqueConstraint, func
5from sqlalchemy.orm import Mapped, column_property, mapped_column, relationship
7from couchers.models.base import Base, communities_seq
9if TYPE_CHECKING:
10 from couchers.models import Cluster, User
13class Discussion(Base, kw_only=True):
14 """
15 forum board
16 """
18 __tablename__ = "discussions"
20 id: Mapped[int] = mapped_column(
21 BigInteger, communities_seq, primary_key=True, server_default=communities_seq.next_value(), init=False
22 )
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)
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)
32 slug: Mapped[str] = column_property(func.slugify(title))
34 thread: Mapped[Thread] = relationship(init=False, backref="discussion", uselist=False)
36 subscribers: Mapped[list[User]] = relationship(
37 init=False, backref="discussions", secondary="discussion_subscriptions", viewonly=True
38 )
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)
46class DiscussionSubscription(Base, kw_only=True):
47 """
48 users subscriptions to discussions
49 """
51 __tablename__ = "discussion_subscriptions"
52 __table_args__ = (UniqueConstraint("discussion_id", "user_id"),)
54 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False)
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)
61 user: Mapped[User] = relationship(init=False, backref="discussion_subscriptions")
62 discussion: Mapped[Discussion] = relationship(init=False, backref="discussion_subscriptions")
65class Thread(Base, kw_only=True):
66 """
67 Thread
68 """
70 __tablename__ = "threads"
72 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False)
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)
78class Comment(Base, kw_only=True):
79 """
80 Comment
81 """
83 __tablename__ = "comments"
85 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False)
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)
93 thread: Mapped[Thread] = relationship(init=False, backref="comments")
96class Reply(Base, kw_only=True):
97 """
98 Reply
99 """
101 __tablename__ = "replies"
103 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False)
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)
111 comment: Mapped[Comment] = relationship(init=False, backref="replies")
114class ClusterDiscussionAssociation(Base, kw_only=True):
115 """
116 discussions related to clusters
117 """
119 __tablename__ = "cluster_discussion_associations"
120 __table_args__ = (UniqueConstraint("discussion_id", "cluster_id"),)
122 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False)
124 discussion_id: Mapped[int] = mapped_column(ForeignKey("discussions.id"), index=True)
125 cluster_id: Mapped[int] = mapped_column(ForeignKey("clusters.id"), index=True)
127 discussion: Mapped[Discussion] = relationship(init=False, backref="cluster_discussion_associations")
128 cluster: Mapped[Cluster] = relationship(init=False, backref="cluster_discussion_associations")