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