119 lines
4.6 KiB
Python
119 lines
4.6 KiB
Python
"""Ad and Sponsor models.
|
|
|
|
Ad: internal ad server. Slots: header, sidebar, inline, footer.
|
|
Targeting: lang (nullable = all), geo_state (nullable = all).
|
|
Impression/click counters on the model; Redis batching added in Phase 7.
|
|
|
|
Sponsor: paid directory listing + optional category sponsorship.
|
|
Category sponsor FK wired to categories.sponsor_id (set separately).
|
|
|
|
PromotedKeyword: pinned listing for a search keyword (promoted search).
|
|
"""
|
|
from datetime import datetime
|
|
from app.utils.time import utcnow
|
|
from app.extensions import db
|
|
|
|
|
|
class Ad(db.Model):
|
|
__tablename__ = "ads"
|
|
|
|
SLOTS = ["header", "sidebar", "inline", "footer"]
|
|
|
|
id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"),
|
|
primary_key=True, autoincrement=True)
|
|
advertiser_name = db.Column(db.String(120), nullable=False)
|
|
slot = db.Column(db.Enum("header", "sidebar", "inline", "footer",
|
|
name="ad_slot"), nullable=False)
|
|
creative_path = db.Column(db.String(255), nullable=True) # image file rel path
|
|
target_url = db.Column(db.String(512), nullable=False)
|
|
alt_text = db.Column(db.String(200), nullable=True)
|
|
|
|
# targeting (null = match all)
|
|
lang = db.Column(db.String(5), nullable=True)
|
|
geo_state = db.Column(db.String(2), nullable=True)
|
|
|
|
starts_at = db.Column(db.DateTime, nullable=False, index=True)
|
|
ends_at = db.Column(db.DateTime, nullable=False, index=True)
|
|
impressions = db.Column(db.Integer, nullable=False, default=0)
|
|
clicks = db.Column(db.Integer, nullable=False, default=0)
|
|
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)
|
|
|
|
@property
|
|
def ctr(self):
|
|
if not self.impressions:
|
|
return 0.0
|
|
return round(self.clicks / self.impressions * 100, 2)
|
|
|
|
@property
|
|
def is_running(self):
|
|
now = utcnow()
|
|
return self.is_active and self.starts_at <= now <= self.ends_at
|
|
|
|
def __repr__(self):
|
|
return f"<Ad {self.id} slot={self.slot} adv={self.advertiser_name[:20]}>"
|
|
|
|
|
|
class Sponsor(db.Model):
|
|
__tablename__ = "sponsors"
|
|
|
|
TIERS = ["directory", "category"]
|
|
|
|
id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"),
|
|
primary_key=True, autoincrement=True)
|
|
name = db.Column(db.String(120), nullable=False)
|
|
logo_path = db.Column(db.String(255), nullable=True)
|
|
url = db.Column(db.String(512), nullable=False)
|
|
tagline = db.Column(db.String(160), nullable=True)
|
|
tier = db.Column(db.Enum("directory", "category", name="sponsor_tier"),
|
|
nullable=False, default="directory")
|
|
category_id = db.Column(
|
|
db.BigInteger().with_variant(db.Integer, "sqlite"),
|
|
db.ForeignKey("categories.id"), nullable=True)
|
|
starts_at = db.Column(db.DateTime, nullable=False)
|
|
ends_at = db.Column(db.DateTime, nullable=False)
|
|
is_active = db.Column(db.Boolean, nullable=False, default=True)
|
|
created_at = db.Column(db.DateTime, default=utcnow, nullable=False)
|
|
|
|
category = db.relationship("Category",
|
|
backref=db.backref("sponsors", lazy="selectin"))
|
|
|
|
@property
|
|
def is_running(self):
|
|
now = utcnow()
|
|
return self.is_active and self.starts_at <= now <= self.ends_at
|
|
|
|
def __repr__(self):
|
|
return f"<Sponsor {self.name} tier={self.tier}>"
|
|
|
|
|
|
class PromotedKeyword(db.Model):
|
|
"""A listing pinned to the top of search results for a given keyword."""
|
|
__tablename__ = "promoted_keywords"
|
|
__table_args__ = (
|
|
db.UniqueConstraint("keyword", "listing_id", name="uq_pk_kw_listing"),
|
|
)
|
|
|
|
id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"),
|
|
primary_key=True, autoincrement=True)
|
|
keyword = db.Column(db.String(80), nullable=False, index=True)
|
|
listing_id = db.Column(db.BigInteger().with_variant(db.Integer, "sqlite"),
|
|
db.ForeignKey("listings.id"), nullable=False)
|
|
priority = db.Column(db.Integer, nullable=False, default=0)
|
|
expires_at = db.Column(db.DateTime, nullable=False, index=True)
|
|
created_at = db.Column(db.DateTime, default=utcnow, nullable=False)
|
|
|
|
listing = db.relationship("Listing",
|
|
backref=db.backref("promoted_keywords",
|
|
lazy="selectin"))
|
|
|
|
@property
|
|
def is_active(self):
|
|
return self.expires_at > utcnow()
|
|
|
|
def __repr__(self):
|
|
return f"<PromotedKeyword '{self.keyword}' L{self.listing_id}>"
|