Rename product blueprint URL param slug → product_slug
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 57s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 57s
The app-level url_value_preprocessor pops "slug" and "page_slug" into g.post_slug, with page_slug overwriting the product slug. Renaming the blueprint param to product_slug eliminates the collision entirely. Views now use g.product_slug (set by blueprint preprocessor) and have clean signatures with no **_kw hacks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,20 +22,12 @@ from .services.product_operations import toggle_product_like, massage_full_produ
|
|||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bp = Blueprint("product", __name__, url_prefix="/product/<slug>")
|
bp = Blueprint("product", __name__, url_prefix="/product/<product_slug>")
|
||||||
@bp.url_value_preprocessor
|
@bp.url_value_preprocessor
|
||||||
def pull_blog(endpoint, values):
|
def pull_product_slug(endpoint, values):
|
||||||
# App-level preprocessor pops both "slug" and "page_slug" into g.post_slug,
|
# product_slug is distinct from the app-level "slug"/"page_slug" params,
|
||||||
# with page_slug overwriting slug. We need the original product slug which
|
# so it won't be popped by the app-level preprocessor in app.py.
|
||||||
# the app preprocessor popped first — but it's gone. Instead, extract the
|
g.product_slug = values.pop("product_slug", None)
|
||||||
# product slug from the request path: .../product/<slug>/...
|
|
||||||
from quart import request as req
|
|
||||||
parts = req.path.rstrip("/").split("/")
|
|
||||||
try:
|
|
||||||
idx = parts.index("product")
|
|
||||||
g.product_slug = parts[idx + 1] if idx + 1 < len(parts) else None
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
g.product_slug = None
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
# BEFORE REQUEST: Slug or numeric ID resolver
|
# BEFORE REQUEST: Slug or numeric ID resolver
|
||||||
@@ -71,7 +63,7 @@ def register():
|
|||||||
if not is_post:
|
if not is_post:
|
||||||
canon = canonical_html_slug(product["slug"])
|
canon = canonical_html_slug(product["slug"])
|
||||||
return redirect(
|
return redirect(
|
||||||
host_url(url_for("market.browse.product.product_detail", slug=canon))
|
host_url(url_for("market.browse.product.product_detail", product_slug=canon))
|
||||||
)
|
)
|
||||||
|
|
||||||
g.item_data = {"d": product, "slug": product["slug"], "liked": False}
|
g.item_data = {"d": product, "slug": product["slug"], "liked": False}
|
||||||
@@ -84,7 +76,7 @@ def register():
|
|||||||
canon = canonical_html_slug(raw_slug)
|
canon = canonical_html_slug(raw_slug)
|
||||||
if canon != raw_slug and not is_post:
|
if canon != raw_slug and not is_post:
|
||||||
return redirect(
|
return redirect(
|
||||||
host_url(url_for("market.browse.product.product_detail", slug=canon))
|
host_url(url_for("market.browse.product.product_detail", product_slug=canon))
|
||||||
)
|
)
|
||||||
|
|
||||||
# hydrate full product
|
# hydrate full product
|
||||||
@@ -111,7 +103,7 @@ def register():
|
|||||||
# ─────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────
|
||||||
@bp.get("/")
|
@bp.get("/")
|
||||||
@cache_page(tag="browse")
|
@cache_page(tag="browse")
|
||||||
async def product_detail(slug: str):
|
async def product_detail():
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
|
|
||||||
# Determine which template to use based on request type
|
# Determine which template to use based on request type
|
||||||
@@ -126,9 +118,8 @@ def register():
|
|||||||
|
|
||||||
@bp.post("/like/toggle/")
|
@bp.post("/like/toggle/")
|
||||||
@clear_cache(tag="browse", tag_scope="user")
|
@clear_cache(tag="browse", tag_scope="user")
|
||||||
async def like_toggle(slug):
|
async def like_toggle():
|
||||||
# Use slug from URL parameter (set by url_prefix="/product/<slug>")
|
product_slug = g.product_slug
|
||||||
product_slug = slug
|
|
||||||
|
|
||||||
if not g.user:
|
if not g.user:
|
||||||
html = await render_template(
|
html = await render_template(
|
||||||
@@ -157,7 +148,7 @@ def register():
|
|||||||
|
|
||||||
|
|
||||||
@bp.get("/admin/")
|
@bp.get("/admin/")
|
||||||
async def admin(slug: str):
|
async def admin():
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
|
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
@@ -177,7 +168,7 @@ def register():
|
|||||||
|
|
||||||
@bp.post("/cart/")
|
@bp.post("/cart/")
|
||||||
@clear_cache(tag="browse", tag_scope="user")
|
@clear_cache(tag="browse", tag_scope="user")
|
||||||
async def cart(**_kw):
|
async def cart():
|
||||||
slug = g.product_slug
|
slug = g.product_slug
|
||||||
# make sure product exists (we *allow* deleted_at != None later if you want)
|
# make sure product exists (we *allow* deleted_at != None later if you want)
|
||||||
product_id = await g.s.scalar(
|
product_id = await g.s.scalar(
|
||||||
|
|||||||
2
shared
2
shared
Submodule shared updated: 2a9dfaa749...0d40dfaeca
@@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
{% if not quantity %}
|
{% if not quantity %}
|
||||||
<form
|
<form
|
||||||
action="{{ url_for('market.browse.product.cart', slug=slug) }}"
|
action="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
|
||||||
method="post"
|
method="post"
|
||||||
hx-post="{{ url_for('market.browse.product.cart', slug=slug) }}"
|
hx-post="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
|
||||||
hx-target="#cart-mini"
|
hx-target="#cart-mini"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
class="rounded flex items-center"
|
class="rounded flex items-center"
|
||||||
@@ -38,9 +38,9 @@
|
|||||||
<div class="rounded flex items-center gap-2">
|
<div class="rounded flex items-center gap-2">
|
||||||
<!-- minus -->
|
<!-- minus -->
|
||||||
<form
|
<form
|
||||||
action="{{ url_for('market.browse.product.cart', slug=slug) }}"
|
action="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
|
||||||
method="post"
|
method="post"
|
||||||
hx-post="{{ url_for('market.browse.product.cart', slug=slug) }}"
|
hx-post="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
|
||||||
hx-target="#cart-mini"
|
hx-target="#cart-mini"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
>
|
>
|
||||||
@@ -80,9 +80,9 @@
|
|||||||
|
|
||||||
<!-- plus -->
|
<!-- plus -->
|
||||||
<form
|
<form
|
||||||
action="{{ url_for('market.browse.product.cart', slug=slug) }}"
|
action="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
|
||||||
method="post"
|
method="post"
|
||||||
hx-post="{{ url_for('market.browse.product.cart', slug=slug) }}"
|
hx-post="{{ url_for('market.browse.product.cart', product_slug=slug) }}"
|
||||||
hx-target="#cart-mini"
|
hx-target="#cart-mini"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
>
|
>
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
<div class="flex flex-col sm:flex-row sm:items-start justify-between gap-2 sm:gap-3">
|
<div class="flex flex-col sm:flex-row sm:items-start justify-between gap-2 sm:gap-3">
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<h2 class="text-sm sm:text-base md:text-lg font-semibold text-stone-900">
|
<h2 class="text-sm sm:text-base md:text-lg font-semibold text-stone-900">
|
||||||
{% set href=url_for('market.browse.product.product_detail', slug=p.slug) %}
|
{% set href=url_for('market.browse.product.product_detail', product_slug=p.slug) %}
|
||||||
<a
|
<a
|
||||||
href="{{ href }}"
|
href="{{ href }}"
|
||||||
hx_get="{{href}}"
|
hx_get="{{href}}"
|
||||||
@@ -189,9 +189,9 @@
|
|||||||
<div class="flex items-center gap-2 text-xs sm:text-sm text-stone-700">
|
<div class="flex items-center gap-2 text-xs sm:text-sm text-stone-700">
|
||||||
<span class="text-[0.65rem] sm:text-xs uppercase tracking-wide text-stone-500">Quantity</span>
|
<span class="text-[0.65rem] sm:text-xs uppercase tracking-wide text-stone-500">Quantity</span>
|
||||||
<form
|
<form
|
||||||
action="{{ url_for('market.browse.product.cart', slug=p.slug) }}"
|
action="{{ url_for('market.browse.product.cart', product_slug=p.slug) }}"
|
||||||
method="post"
|
method="post"
|
||||||
hx-post="{{ url_for('market.browse.product.cart', slug=p.slug) }}"
|
hx-post="{{ url_for('market.browse.product.cart', product_slug=p.slug) }}"
|
||||||
hx-target="#cart-mini"
|
hx-target="#cart-mini"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
>
|
>
|
||||||
@@ -212,9 +212,9 @@
|
|||||||
{{ item.quantity }}
|
{{ item.quantity }}
|
||||||
</span>
|
</span>
|
||||||
<form
|
<form
|
||||||
action="{{ url_for('market.browse.product.cart', slug=p.slug) }}"
|
action="{{ url_for('market.browse.product.cart', product_slug=p.slug) }}"
|
||||||
method="post"
|
method="post"
|
||||||
hx-post="{{ url_for('market.browse.product.cart', slug=p.slug) }}"
|
hx-post="{{ url_for('market.browse.product.cart', product_slug=p.slug) }}"
|
||||||
hx-target="#cart-mini"
|
hx-target="#cart-mini"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user