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

1import enum 

2from datetime import date, datetime 

3from typing import TYPE_CHECKING 

4 

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 

8 

9from couchers.models.base import Base 

10from couchers.models.moderation import ModerationObjectType 

11 

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 

16 

17 

18class PublicTripStatus(enum.Enum): 

19 searching_for_host = enum.auto() 

20 closed = enum.auto() 

21 

22 

23class PublicTrip(Base, kw_only=True): 

24 """ 

25 A public trip posted by a traveler looking for a host in a community. 

26 """ 

27 

28 __tablename__ = "public_trips" 

29 __moderation_author_column__ = "user_id" 

30 __moderation_object_type__ = ModerationObjectType.public_trip 

31 __moderation_has_own_visibility_mechanism__ = False 

32 

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

34 

35 # The traveler posting the trip 

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

37 

38 # The community/location (city-level node) 

39 node_id: Mapped[int] = mapped_column(ForeignKey("nodes.id"), index=True) 

40 

41 # Trip dates 

42 from_date: Mapped[date] = mapped_column(Date) 

43 to_date: Mapped[date] = mapped_column(Date) 

44 

45 # User's message about their trip 

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

47 

48 # Current status 

49 status: Mapped[PublicTripStatus] = mapped_column( 

50 Enum(PublicTripStatus), default=PublicTripStatus.searching_for_host 

51 ) 

52 

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()) 

55 

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

57 

58 # Timestamps 

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

60 

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) 

66 

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 )