mirror of
https://gitverse.ru/kpa39l/vesti.git
synced 2026-09-29 09:55:03 +00:00
feat(web): архив опубликованных — дерево навигации по годам/месяцам + пагинация (openspec archive-navigation)
This commit is contained in:
+92
-8
@@ -110,6 +110,24 @@ tpl.filters["safe_html"] = safe_html_filter
|
||||
tpl.filters["dt"] = dt_filter
|
||||
|
||||
|
||||
def _archive_href(year: str = "", month: str = "", nodate: bool = False, page: int = 1) -> str:
|
||||
"""URL ссылки архива: '/published' без параметров или с query (страница 1 не пишется)."""
|
||||
parts = []
|
||||
if year:
|
||||
parts.append(f"year={year}")
|
||||
if month:
|
||||
parts.append(f"month={month}")
|
||||
if nodate:
|
||||
parts.append("nodate=1")
|
||||
if page and page > 1:
|
||||
parts.append(f"page={page}")
|
||||
qs = "&".join(parts)
|
||||
return f"/published?{qs}" if qs else "/published"
|
||||
|
||||
|
||||
tpl.globals["_archive_href"] = _archive_href
|
||||
|
||||
|
||||
def _db() -> sqlite3.Connection:
|
||||
conn = sqlite3.connect(DB_PATH, timeout=10)
|
||||
conn.row_factory = sqlite3.Row
|
||||
@@ -136,6 +154,41 @@ def _migrate():
|
||||
_migrate()
|
||||
|
||||
|
||||
# --- Архив опубликованных: дерево навигации по годам/месяцам ---
|
||||
MONTH_NAMES_RU = ("", "Янв", "Фев", "Мар", "Апр", "Май", "Июн",
|
||||
"Июл", "Авг", "Сен", "Окт", "Ноя", "Дек")
|
||||
ARCHIVE_PAGE_SIZE = 10
|
||||
|
||||
|
||||
def _archive_tree(conn: sqlite3.Connection) -> dict:
|
||||
"""Дерево навигации архива: {years: [{year, months:[{m, label, n}], n}], no_date: n}.
|
||||
Строится только из существующих периодов (GROUP BY); пустые месяцы отсутствуют —
|
||||
как в Hugo-архивах, где дерево порождается постами, а не календарём."""
|
||||
rows = conn.execute(
|
||||
"""SELECT strftime('%Y', published_at) y, strftime('%m', published_at) m,
|
||||
COUNT(*) n
|
||||
FROM posts WHERE status='published'
|
||||
AND published_at IS NOT NULL AND published_at != ''
|
||||
GROUP BY y, m ORDER BY y DESC, m DESC"""
|
||||
).fetchall()
|
||||
years, cur = [], None
|
||||
for r in rows:
|
||||
if cur is None or cur["year"] != r["y"]:
|
||||
cur = {"year": r["y"], "months": [], "n": 0}
|
||||
years.append(cur)
|
||||
cur["months"].append({
|
||||
"m": r["m"],
|
||||
"label": f"{MONTH_NAMES_RU[int(r['m'])]}",
|
||||
"n": r["n"],
|
||||
})
|
||||
cur["n"] += r["n"]
|
||||
no_date = conn.execute(
|
||||
"""SELECT COUNT(*) FROM posts
|
||||
WHERE status='published' AND (published_at IS NULL OR published_at='')"""
|
||||
).fetchone()[0]
|
||||
return {"years": years, "no_date": no_date}
|
||||
|
||||
|
||||
def _session_ok(request: Request) -> bool:
|
||||
return request.cookies.get(SESSION_COOKIE) == os.getenv("VESTI_WEB_SESSION", "")
|
||||
|
||||
@@ -733,19 +786,50 @@ def post_translate(post_id: int, request: Request,
|
||||
|
||||
|
||||
@app.get("/published", response_class=HTMLResponse)
|
||||
def published(request: Request):
|
||||
def published(request: Request, year: str = "", month: str = "", nodate: str = "", page: int = 1):
|
||||
"""Архив опубликованных: дерево навигации по годам/месяцам (правая колонка),
|
||||
посты за выбранный период, пагинация по 10."""
|
||||
_require_auth(request)
|
||||
page = max(1, int(page or 1))
|
||||
conn = _db()
|
||||
tree = _archive_tree(conn)
|
||||
|
||||
filters, params = [], []
|
||||
filters.append("p.status='published'")
|
||||
if nodate:
|
||||
filters.append("(p.published_at IS NULL OR p.published_at='')")
|
||||
else:
|
||||
filters.append("(p.published_at IS NOT NULL AND p.published_at != '')")
|
||||
if year:
|
||||
filters.append("strftime('%Y', p.published_at) = ?")
|
||||
params.append(year)
|
||||
if month:
|
||||
filters.append("strftime('%m', p.published_at) = ?")
|
||||
params.append(month)
|
||||
where = " AND ".join(filters)
|
||||
|
||||
total = conn.execute(f"SELECT COUNT(*) FROM posts p WHERE {where}", params).fetchone()[0]
|
||||
pages = max(1, (total + ARCHIVE_PAGE_SIZE - 1) // ARCHIVE_PAGE_SIZE)
|
||||
page = min(page, pages)
|
||||
|
||||
rows = conn.execute(
|
||||
"""SELECT p.*, s.name source_name,
|
||||
pub.tg_message_id, pub.views pub_views, pub.bundle_path, pub.distributed_dirs
|
||||
FROM posts p
|
||||
LEFT JOIN sources s ON s.id=p.source_id
|
||||
LEFT JOIN published pub ON pub.post_id=p.id
|
||||
WHERE p.status='published' ORDER BY p.published_at DESC LIMIT 100"""
|
||||
f"""SELECT p.*, s.name source_name,
|
||||
pub.tg_message_id, pub.views pub_views, pub.bundle_path, pub.distributed_dirs
|
||||
FROM posts p
|
||||
LEFT JOIN sources s ON s.id=p.source_id
|
||||
LEFT JOIN (SELECT post_id, MAX(id) mid FROM published GROUP BY post_id) lastpub
|
||||
ON lastpub.post_id=p.id
|
||||
LEFT JOIN published pub ON pub.id=lastpub.mid
|
||||
WHERE {where}
|
||||
ORDER BY p.published_at DESC LIMIT ? OFFSET ?""",
|
||||
params + [ARCHIVE_PAGE_SIZE, (page - 1) * ARCHIVE_PAGE_SIZE]
|
||||
).fetchall()
|
||||
conn.close()
|
||||
html = tpl.get_template("published.html").render(posts=rows)
|
||||
|
||||
html = tpl.get_template("published.html").render(
|
||||
posts=rows, tree=tree, year=year, month=month, nodate=bool(nodate),
|
||||
page=page, pages=pages, total=total,
|
||||
)
|
||||
return HTMLResponse(html)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user