This repository has been archived on 2026-02-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
market/bp/browse/routes.py
giles 6271a715a1
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
feat: initialize market app with browsing, product, and scraping code
Split from coop monolith. Includes:
- Market/browse/product blueprints
- Product sync API
- Suma scraping pipeline
- Templates for market, browse, and product views
- Dockerfile and CI workflow for independent deployment
2026-02-09 23:16:34 +00:00

162 lines
5.2 KiB
Python

from __future__ import annotations
from quart import (
g,
Blueprint,
abort,
render_template,
render_template_string,
make_response,
current_app,
)
from config import config
from .services.nav import category_context, get_nav
from .services.blacklist.category import is_category_blocked
from .services import (
_hx_fragment_request,
_productInfo,
_vary,
_current_url_without_page,
)
from suma_browser.app.redis_cacher import cache_page
from suma_browser.app.utils.htmx import is_htmx_request
def register():
browse_bp = Blueprint("browse", __name__)
from .. import register_product
browse_bp.register_blueprint(
register_product(),
)
@browse_bp.get("/")
@cache_page(tag="browse")
async def home():
"""
Market landing page.
Shows the Ghost CMS post with slug='market'.
"""
from shared.internal_api import get as api_get
# Fetch the market post from coop internal API
p_data = await api_get("coop", "/internal/post/market")
if not p_data:
abort(404)
# Determine which template to use based on request type
if not is_htmx_request():
# Normal browser request: full page with layout
html = await render_template("_types/market/index.html", **p_data)
else:
# HTMX request: main panel + OOB elements
html = await render_template("_types/market/_oob_elements.html", **p_data)
return await make_response(html)
@browse_bp.get("/all/")
@cache_page(tag="browse")
async def browse_all():
"""
Browse all products across all categories.
Renders full page or just product cards (HTMX pagination fragment).
"""
nav = await get_nav(g.s)
ctx = {
"category_label": "All Products",
"top_slug": "all",
"sub_slug": None,
}
product_info = await _productInfo()
full_context = {**product_info, **ctx}
# Determine which template to use based on request type and pagination
if not is_htmx_request():
# Normal browser request: full page with layout
html = await render_template("_types/browse/index.html", **full_context)
elif product_info["page"] > 1:
# HTMX pagination: just product cards + sentinel
html = await render_template("_types/browse/_product_cards.html", **product_info)
else:
# HTMX navigation (page 1): main panel + OOB elements
html = await render_template("_types/browse/_oob_elements.html", **full_context)
resp = await make_response(html)
resp.headers["Hx-Push-Url"] = _current_url_without_page()
return _vary(resp)
@browse_bp.get("/<top_slug>/")
@cache_page(tag="browse")
async def browse_top(top_slug: str):
"""
Browse by top-level category (e.g. /fruit).
404 if category not in allowed list or is blocked.
"""
REVERSE_CATEGORY = {v: k for k, v in config()["categories"]["allow"].items()}
if top_slug not in REVERSE_CATEGORY:
abort(404)
if is_category_blocked(top_slug):
abort(404)
nav = await get_nav(g.s)
ctx = category_context(top_slug, None, nav)
product_info = await _productInfo(top_slug)
full_context = {**product_info, **ctx}
# Determine which template to use based on request type and pagination
if not is_htmx_request():
# Normal browser request: full page with layout
html = await render_template("_types/browse/index.html", **full_context)
elif product_info["page"] > 1:
# HTMX pagination: just product cards + sentinel
html = await render_template("_types/browse/_product_cards.html", **product_info)
else:
html = await render_template("_types/browse/_oob_elements.html", **full_context)
resp = await make_response(html)
resp.headers["Hx-Push-Url"] = _current_url_without_page()
return _vary(resp)
@browse_bp.get("/<top_slug>/<sub_slug>/")
@cache_page(tag="browse")
async def browse_sub(top_slug: str, sub_slug: str):
"""
Browse by subcategory (e.g. /fruit/citrus).
404 if blocked or unknown.
"""
REVERSE_CATEGORY = {v: k for k, v in config()["categories"]["allow"].items()}
if top_slug not in REVERSE_CATEGORY:
abort(404)
if is_category_blocked(top_slug, sub_slug):
abort(404)
nav = await get_nav(g.s)
ctx = category_context(top_slug, sub_slug, nav)
product_info = await _productInfo(top_slug, sub_slug)
full_context = {**product_info, **ctx}
# Determine which template to use based on request type and pagination
if not is_htmx_request():
# Normal browser request: full page with layout
html = await render_template("_types/browse/index.html", **full_context)
elif product_info["page"] > 1:
# HTMX pagination: just product cards + sentinel
html = await render_template("_types/browse/_product_cards.html", **product_info)
else:
# HTMX navigation (page 1): main panel + OOB elements
html = await render_template("_types/browse/_oob_elements.html", **full_context)
resp = await make_response(html)
resp.headers["Hx-Push-Url"] = _current_url_without_page()
return _vary(resp)
return browse_bp