feat(affiliate): tests, ruff cleanup, CHANGELOG + PROJECT.md (commit 9/9)

- 26 tests in web/tests/test_affiliate.py covering hash_ip determinism,
  daily rotation, product CRUD, bake_product_cards marker replacement,
  click redirect (302 + logged), inactive/unknown 404, multi-retailer
- ruff: fix E741 ambiguous var (l → line in _form_to_product), F401 unused
  import, I001 import sort in admin/routes.py
- CHANGELOG: affiliate product system entry
- PROJECT.md: affiliate system moved to Done, Wirecutter backlog item removed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-28 21:06:01 +01:00
parent 1fdd2d07a4
commit 5c22ea9780
4 changed files with 360 additions and 6 deletions

View File

@@ -2499,7 +2499,12 @@ async def article_results():
@csrf_protect
async def article_new():
"""Create a manual article."""
from ..content.routes import BUILD_DIR, bake_product_cards, bake_scenario_cards, is_reserved_path
from ..content.routes import (
BUILD_DIR,
bake_product_cards,
bake_scenario_cards,
is_reserved_path,
)
if request.method == "POST":
form = await request.form
@@ -2562,7 +2567,12 @@ async def article_new():
@csrf_protect
async def article_edit(article_id: int):
"""Edit a manual article."""
from ..content.routes import BUILD_DIR, bake_product_cards, bake_scenario_cards, is_reserved_path
from ..content.routes import (
BUILD_DIR,
bake_product_cards,
bake_scenario_cards,
is_reserved_path,
)
article = await fetch_one("SELECT * FROM articles WHERE id = ?", (article_id,))
if not article:
@@ -3266,8 +3276,8 @@ def _form_to_product(form) -> dict:
pros_raw = form.get("pros", "").strip()
cons_raw = form.get("cons", "").strip()
pros = json.dumps([l.strip() for l in pros_raw.splitlines() if l.strip()])
cons = json.dumps([l.strip() for l in cons_raw.splitlines() if l.strip()])
pros = json.dumps([line.strip() for line in pros_raw.splitlines() if line.strip()])
cons = json.dumps([line.strip() for line in cons_raw.splitlines() if line.strip()])
return {
"slug": form.get("slug", "").strip(),