"""Category model. Self-referential (parent_id) for subcategories. `field_schema` is admin-editable JSON describing category-specific listing fields. Shape: { "fields": [ {"name":"condition","label":"Condition","type":"select", "required":true,"options":["new","like_new","good","fair"],"hot":true}, {"name":"brand","label":"Brand","type":"text","required":false,"max":80}, {"name":"salary_min","label":"Min salary","type":"number","required":false,"hot":true} ] } Supported types: text, number, select, bool. `hot:true` marks a field whose value is denormalized onto an indexed Listing column for fast filtering. """ from datetime import datetime from app.utils.time import utcnow from sqlalchemy import JSON from app.extensions import db class Category(db.Model): __tablename__ = "categories" id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"), primary_key=True, autoincrement=True) slug = db.Column(db.String(60), unique=True, nullable=False, index=True) name = db.Column(db.String(80), nullable=False) parent_id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"), db.ForeignKey("categories.id"), nullable=True, index=True) field_schema = db.Column(JSON, nullable=False, default=dict) icon = db.Column(db.String(40), nullable=True) sort_order = db.Column(db.Integer, nullable=False, default=0) sponsor_id = db.Column(db.BigInteger, nullable=True) # FK wired in Phase 5 is_active = db.Column(db.Boolean, nullable=False, default=True) created_at = db.Column(db.DateTime, default=utcnow, nullable=False) updated_at = db.Column(db.DateTime, default=utcnow, onupdate=utcnow, nullable=False) children = db.relationship("Category", backref=db.backref("parent", remote_side=[id]), lazy="selectin") listings = db.relationship("Listing", back_populates="category", lazy="dynamic") @property def is_subcategory(self): return self.parent_id is not None def fields(self): return (self.field_schema or {}).get("fields", []) def hot_fields(self): return [f for f in self.fields() if f.get("hot")] def __repr__(self): return f""