feat: move article generation to background worker queue
- Add generate_articles task handler to worker.py - template_generate and template_regenerate now enqueue tasks instead of running inline (was blocking HTTP request for seconds with 1k articles) - rebuild_all enqueues per-template + inline rebuilds manual articles - Update tests to check task enqueue instead of immediate article creation Subtask 4 of CMS admin improvement. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1314,7 +1314,7 @@ async def template_preview(slug: str, row_key: str):
|
||||
@csrf_protect
|
||||
async def template_generate(slug: str):
|
||||
"""Generate articles from template + DuckDB data."""
|
||||
from ..content import fetch_template_data, generate_articles, load_template
|
||||
from ..content import fetch_template_data, load_template
|
||||
|
||||
try:
|
||||
config = load_template(slug)
|
||||
@@ -1330,15 +1330,20 @@ async def template_generate(slug: str):
|
||||
start_date_str = form.get("start_date", "")
|
||||
articles_per_day = int(form.get("articles_per_day", 3) or 3)
|
||||
|
||||
if not start_date_str:
|
||||
start_date = date.today()
|
||||
else:
|
||||
start_date = date.fromisoformat(start_date_str)
|
||||
start_date = date.fromisoformat(start_date_str) if start_date_str else date.today()
|
||||
|
||||
generated = await generate_articles(
|
||||
slug, start_date, articles_per_day, limit=500,
|
||||
from ..worker import enqueue
|
||||
await enqueue("generate_articles", {
|
||||
"template_slug": slug,
|
||||
"start_date": start_date.isoformat(),
|
||||
"articles_per_day": articles_per_day,
|
||||
"limit": 500,
|
||||
})
|
||||
await flash(
|
||||
f"Article generation queued for '{config['name']}'. "
|
||||
"The worker will process it in the background.",
|
||||
"success",
|
||||
)
|
||||
await flash(f"Generated {generated} articles with staggered publish dates.", "success")
|
||||
return redirect(url_for("admin.articles"))
|
||||
|
||||
return await render_template(
|
||||
@@ -1354,7 +1359,7 @@ async def template_generate(slug: str):
|
||||
@csrf_protect
|
||||
async def template_regenerate(slug: str):
|
||||
"""Re-generate all articles for a template with fresh DuckDB data."""
|
||||
from ..content import generate_articles, load_template
|
||||
from ..content import load_template
|
||||
|
||||
try:
|
||||
load_template(slug)
|
||||
@@ -1362,9 +1367,14 @@ async def template_regenerate(slug: str):
|
||||
await flash("Template not found.", "error")
|
||||
return redirect(url_for("admin.templates"))
|
||||
|
||||
# Use today as start date, keep existing publish dates via upsert
|
||||
generated = await generate_articles(slug, date.today(), articles_per_day=500)
|
||||
await flash(f"Regenerated {generated} articles from fresh data.", "success")
|
||||
from ..worker import enqueue
|
||||
await enqueue("generate_articles", {
|
||||
"template_slug": slug,
|
||||
"start_date": date.today().isoformat(),
|
||||
"articles_per_day": 500,
|
||||
"limit": 500,
|
||||
})
|
||||
await flash("Regeneration queued. The worker will process it in the background.", "success")
|
||||
return redirect(url_for("admin.template_detail", slug=slug))
|
||||
|
||||
|
||||
@@ -1877,13 +1887,29 @@ async def article_rebuild(article_id: int):
|
||||
@role_required("admin")
|
||||
@csrf_protect
|
||||
async def rebuild_all():
|
||||
"""Re-render all articles."""
|
||||
articles = await fetch_all("SELECT id FROM articles")
|
||||
count = 0
|
||||
for a in articles:
|
||||
"""Re-render all articles via background worker."""
|
||||
from ..content import discover_templates
|
||||
from ..worker import enqueue
|
||||
|
||||
templates = discover_templates()
|
||||
for t in templates:
|
||||
await enqueue("generate_articles", {
|
||||
"template_slug": t["slug"],
|
||||
"start_date": date.today().isoformat(),
|
||||
"articles_per_day": 500,
|
||||
"limit": 500,
|
||||
})
|
||||
|
||||
# Manual articles still need inline rebuild
|
||||
manual = await fetch_all("SELECT id FROM articles WHERE template_slug IS NULL")
|
||||
for a in manual:
|
||||
await _rebuild_article(a["id"])
|
||||
count += 1
|
||||
await flash(f"Rebuilt {count} articles.", "success")
|
||||
|
||||
await flash(
|
||||
f"Queued rebuild for {len(templates)} templates"
|
||||
f" + rebuilt {len(manual)} manual articles.",
|
||||
"success",
|
||||
)
|
||||
return redirect(url_for("admin.articles"))
|
||||
|
||||
|
||||
|
||||
@@ -709,6 +709,22 @@ async def handle_cleanup_seo_metrics(payload: dict) -> None:
|
||||
print(f"[WORKER] Cleaned up {deleted} old SEO metric rows")
|
||||
|
||||
|
||||
@task("generate_articles")
|
||||
async def handle_generate_articles(payload: dict) -> None:
|
||||
"""Generate articles from a template in the background."""
|
||||
from datetime import date as date_cls
|
||||
|
||||
from .content import generate_articles
|
||||
|
||||
slug = payload["template_slug"]
|
||||
start_date = date_cls.fromisoformat(payload["start_date"])
|
||||
articles_per_day = payload.get("articles_per_day", 3)
|
||||
limit = payload.get("limit", 500)
|
||||
|
||||
count = await generate_articles(slug, start_date, articles_per_day, limit=limit)
|
||||
print(f"[WORKER] Generated {count} articles for template '{slug}'")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Worker Loop
|
||||
# =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user