Coverage for app/backend/src/couchers/models/public_trips.py: 100%
31 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 00:40 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 00:40 +0000
1import enum
2from datetime import date, datetime
3from typing import TYPE_CHECKING
5from sqlalchemy import BigInteger, Boolean, CheckConstraint, Date, DateTime, Enum, ForeignKey, Index, String, func
6from sqlalchemy.orm import Mapped, mapped_column, relationship
7from sqlalchemy.sql import expression
9from couchers.models.base import Base
10from couchers.models.moderation import ModerationObjectType
12if TYPE_CHECKING:
13 from couchers.models import Node, User
14 from couchers.models.host_requests import HostRequest
15 from couchers.models.moderation import ModerationState
18class PublicTripStatus(enum.Enum):
19 searching_for_host = enum.auto()
20 closed = enum.auto()
23class PublicTrip(Base, kw_only=True):
24 """
25 A public trip posted by a traveler looking for a host in a community.
26 """
28 __tablename__ = "public_trips"
29 __moderation_author_column__ = "user_id"
30 __moderation_object_type__ = ModerationObjectType.public_trip
31 __moderation_has_own_visibility_mechanism__ = False
33 id: Mapped[int] = mapped_column(BigInteger, primary_key=True, init=False)
35 # The traveler posting the trip
36 user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True)
38 # The community/location (city-level node)
39 node_id: Mapped[int] = mapped_column(ForeignKey("nodes.id"), index=True)
41 # Trip dates
42 from_date: Mapped[date] = mapped_column(Date)
43 to_date: Mapped[date] = mapped_column(Date)
45 # User's message about their trip
46 description: Mapped[str] = mapped_column(String)
48 # Current status
49 status: Mapped[PublicTripStatus] = mapped_column(
50 Enum(PublicTripStatus), default=PublicTripStatus.searching_for_host
51 )
53 # If true, only users with the same gender as the poster can see this trip
54 same_gender_only: Mapped[bool] = mapped_column(Boolean, default=False, server_default=expression.false())
56 moderation_state_id: Mapped[int] = mapped_column(ForeignKey("moderation_states.id"), index=True)
58 # Timestamps
59 created: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), init=False)
61 # Relationships
62 user: Mapped[User] = relationship(init=False, back_populates="public_trips")
63 node: Mapped[Node] = relationship(init=False, back_populates="public_trips")
64 host_requests: Mapped[list[HostRequest]] = relationship(init=False, back_populates="public_trip")
65 moderation_state: Mapped[ModerationState] = relationship(init=False)
67 __table_args__ = (
68 # Ensure from_date is not after to_date
69 CheckConstraint("from_date <= to_date", name="valid_date_range"),
70 # Index for querying active trips in a community
71 # Using partial index since we mostly query for active trips
72 Index(
73 "ix_public_trips_node_from_date_active",
74 node_id,
75 from_date,
76 postgresql_where=status == PublicTripStatus.searching_for_host,
77 ),
78 )