/ backend / models / housing_post.py
housing_post.py
  1  """
  2  HousingPost model - represents housing_posts table.
  3  
  4  This file teaches you about ONE-TO-MANY relationships and working with dates.
  5  """
  6  from sqlalchemy import String, DateTime, Text, Date, ForeignKey
  7  from sqlalchemy.orm import Mapped, mapped_column, relationship
  8  from sqlalchemy.dialects.postgresql import UUID
  9  from datetime import datetime, date, timezone
 10  import uuid
 11  
 12  from database import Base
 13  
 14  
 15  class HousingPost(Base):
 16      __tablename__ = "housing_posts"
 17  
 18      # =============================================================================
 19      # LEARNING GUIDE: One-to-Many Relationships
 20      # =============================================================================
 21      #
 22      # This model demonstrates a ONE-TO-MANY relationship:
 23      # - One User can create MANY HousingPosts
 24      # - Each HousingPost belongs to ONE User
 25      #
 26      # How do we represent this?
 27      # → The "many" side (HousingPost) stores a foreign key to the "one" side (User)
 28      #
 29      # Pattern:
 30      #   user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id"), nullable=False)
 31      #   user: Mapped["User"] = relationship("User", back_populates="posts")
 32      #
 33      # This is different from one-to-one because:
 34      # - user_id is NOT the primary key (so multiple posts can have same user_id)
 35      # - Multiple HousingPost records can point to the same User
 36      #
 37      # =============================================================================
 38      # DATE vs DATETIME in SQLAlchemy:
 39      # =============================================================================
 40      #
 41      # Date: Just the day (no time)
 42      #   SQL type: date
 43      #   Python type: date (from datetime module)
 44      #   Example: 2025-10-20
 45      #   Use for: Birth dates, event dates, date ranges
 46      #
 47      # DateTime: Day AND time
 48      #   SQL type: timestamp
 49      #   Python type: datetime (from datetime module)
 50      #   Example: 2025-10-20 14:30:00
 51      #   Use for: Created/updated timestamps, expiration times
 52      #
 53      # =============================================================================
 54  
 55      # =====================
 56      # Identity fields
 57      # =====================
 58      # SQL: "id" uuid PRIMARY KEY
 59      # → Every post needs a unique identifier
 60      id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
 61  
 62      # SQL: "user_id" uuid NOT NULL (with FOREIGN KEY to users.id)
 63      # → This links the post to whoever created it
 64      # → Think: What's the type? Is it nullable? What constraint do we need?
 65      # TODO: user_id: Mapped[?] = mapped_column(ForeignKey(?), nullable=?)
 66  
 67      # =====================
 68      # Timestamp fields
 69      # =====================
 70      # SQL: "created_at" timestamp NOT NULL DEFAULT (now())
 71      # → Auto-set when post is created, never changes
 72      # → Remember: Use timezone.utc for timezone-aware timestamps!
 73      created_at: Mapped[datetime] = mapped_column(
 74          DateTime,
 75          nullable=False,
 76          default=lambda: datetime.now(timezone.utc)
 77      )
 78  
 79      # SQL: "updated_at" timestamp NOT NULL DEFAULT (now())
 80      # → Think: How is this different from created_at?
 81      # → Hint: This should update every time the post is modified
 82      # → For now, just set the default. Later you can add an onupdate parameter
 83      # TODO: updated_at: Mapped[?] = mapped_column(?, nullable=?, default=?)
 84  
 85      # =====================
 86      # Location & content
 87      # =====================
 88      # SQL: "city" varchar(100) NOT NULL
 89      # → Which city they need housing in
 90      # TODO: city: Mapped[?] = mapped_column(?, nullable=?)
 91  
 92      # SQL: "description" text NOT NULL
 93      # → Full description of their housing need
 94      # → Think: text vs varchar? text has unlimited length!
 95      # TODO: description: Mapped[?] = mapped_column(?, nullable=?)
 96  
 97      # =====================
 98      # Date range fields (using Date, not DateTime!)
 99      # =====================
100      # SQL: "dates_start" date NOT NULL
101      # → When they need housing to START
102      # → Think: Do we need time? No! Just the day. So we use Date, not DateTime
103      # TODO: dates_start: Mapped[?] = mapped_column(?, nullable=?)
104  
105      # SQL: "dates_end" date NOT NULL
106      # → When they need housing to END
107      # TODO: dates_end: Mapped[?] = mapped_column(?, nullable=?)
108  
109      # =====================
110      # Metadata fields
111      # =====================
112      # SQL: "urgency" varchar(20) NOT NULL
113      # → Values: "emergency" | "short" | "medium" | "long"
114      # → This affects who gets notified
115      # TODO: urgency: Mapped[?] = mapped_column(?, nullable=?)
116  
117      # SQL: "notification_text" varchar(150) NOT NULL
118      # → Short text for email subject line (max 150 chars)
119      # → Think: What's different about this varchar? It has a length limit!
120      # TODO: notification_text: Mapped[?] = mapped_column(?, nullable=?)
121  
122      # SQL: "status" varchar(20) NOT NULL DEFAULT 'active'
123      # → Values: "active" | "fulfilled" | "expired"
124      # → Has a default value!
125      # TODO: status: Mapped[?] = mapped_column(?, nullable=?, default=?)
126  
127      # =============================================================================
128      # RELATIONSHIPS: How this post connects to other data
129      # =============================================================================
130      #
131      # A HousingPost has TWO relationships:
132      #
133      # 1. MANY-TO-ONE with User (the post creator)
134      #    → Many posts belong to one user
135      #    → Access: post.user gives you the User who created it
136      #    → user = relationship("User", back_populates="posts")
137      #
138      # 2. ONE-TO-MANY with Response (people responding to this post)
139      #    → One post can have many responses
140      #    → Access: post.responses gives you a list of Response objects
141      #    → responses = relationship("Response", back_populates="post")
142      #
143      # TODO: Add both relationships
144      # Hint for user: relationship("User", back_populates="posts")
145      # Hint for responses: relationship("Response", back_populates="post")
146