mirror of
https://gitverse.ru/kpa39l/vesti.git
synced 2026-09-29 09:55:03 +00:00
feat(candidates): флаг прочитанности (is_read), жирные непрочитанные, голубая заливка выбранного, дефолтная группировка по дате
This commit is contained in:
+23
-3
@@ -75,6 +75,21 @@ def _db() -> sqlite3.Connection:
|
||||
return conn
|
||||
|
||||
|
||||
def _migrate():
|
||||
"""Идемпотентные миграции при старте веб-сервиса."""
|
||||
conn = sqlite3.connect(DB_PATH, timeout=10)
|
||||
try:
|
||||
cols = [r[1] for r in conn.execute("PRAGMA table_info(posts)").fetchall()]
|
||||
if "is_read" not in cols:
|
||||
conn.execute("ALTER TABLE posts ADD COLUMN is_read INTEGER DEFAULT 0")
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
_migrate()
|
||||
|
||||
|
||||
def _session_ok(request: Request) -> bool:
|
||||
return request.cookies.get(SESSION_COOKIE) == os.getenv("VESTI_WEB_SESSION", "")
|
||||
|
||||
@@ -277,7 +292,7 @@ def _fetch_candidates(conn, direction: str, status: str, own: str, q: str, group
|
||||
|
||||
@app.get("/candidates", response_class=HTMLResponse)
|
||||
def candidates(request: Request, direction: str = "", status: str = "", own: str = "",
|
||||
q: str = "", group_by: str = "source", selected: int = 0, error: str = ""):
|
||||
q: str = "", group_by: str = "date", selected: int = 0, error: str = ""):
|
||||
_require_auth(request)
|
||||
conn = _db()
|
||||
grouped, counts, status = _fetch_candidates(conn, direction, status, own, q, group_by)
|
||||
@@ -294,6 +309,11 @@ def candidates(request: Request, direction: str = "", status: str = "", own: str
|
||||
else:
|
||||
continue
|
||||
break
|
||||
# открытие поста = прочтение (как в почте): сразу помечаем, чтобы карточка перестала быть жирной
|
||||
if sel is not None and sel["id"]:
|
||||
conn.execute("UPDATE posts SET is_read=1 WHERE id=? AND is_read=0", (sel["id"],))
|
||||
sel["is_read"] = 1
|
||||
conn.commit()
|
||||
conn.close()
|
||||
# строка параметров фильтров для ссылок (выбор поста, смена группировки)
|
||||
qs = []
|
||||
@@ -313,7 +333,7 @@ def candidates(request: Request, direction: str = "", status: str = "", own: str
|
||||
|
||||
@app.post("/candidates/select")
|
||||
def candidates_select(request: Request, id: int = Form(...), direction: str = Form(""),
|
||||
status: str = Form(""), own: str = Form(""), q: str = Form(""), group_by: str = Form("source")):
|
||||
status: str = Form(""), own: str = Form(""), q: str = Form(""), group_by: str = Form("date")):
|
||||
"""Выбор кандидата левой панели (обычная форма без JS)."""
|
||||
_require_auth(request)
|
||||
url = f"/candidates?selected={id}&group_by={group_by}"
|
||||
@@ -326,7 +346,7 @@ def candidates_select(request: Request, id: int = Form(...), direction: str = Fo
|
||||
@app.post("/candidates/bulk")
|
||||
def candidates_bulk(request: Request, action: str = Form(...), ids: list[int] = Form(default_factory=list),
|
||||
direction: str = Form(""), status: str = Form(""), own: str = Form(""),
|
||||
q: str = Form(""), group_by: str = Form("source")):
|
||||
q: str = Form(""), group_by: str = Form("date")):
|
||||
"""Bulk-операции: approve/reject/reject-old/process по отмеченным ids."""
|
||||
_require_auth(request)
|
||||
back = f"/candidates?group_by={group_by}"
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
body { padding-top: 4.5rem; background: #f5f6fa; }
|
||||
.card { box-shadow: 0 1px 4px rgba(0,0,0,.08); }
|
||||
.post-text { white-space: pre-wrap; max-height: 140px; overflow-y: auto; font-size: .9rem; }
|
||||
/* список кандидатов: выбранный пост — светло-голубая заливка карточки */
|
||||
.list-group-item.active-row { background-color: #dcecfc; }
|
||||
.list-group-item.active-row:hover { background-color: #d0e4f8; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -19,6 +22,8 @@
|
||||
<a class="nav-link" href="/candidates">Кандидаты</a>
|
||||
<a class="nav-link" href="/published">Опубликовано</a>
|
||||
<a class="nav-link" href="/metrics">Метрики</a>
|
||||
<a class="nav-link" href="/crawlers">Краулеры</a>
|
||||
<a class="nav-link" href="/sources">Источники</a>
|
||||
<a class="nav-link text-danger" href="/logout">Выйти</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -75,8 +75,8 @@
|
||||
</summary>
|
||||
<div class="list-group list-group-flush small">
|
||||
{% for p in g.posts %}
|
||||
<div class="list-group-item d-flex align-items-start">
|
||||
<a class="flex-grow-1 text-decoration-none{% if selected and selected.id == p.id %} fw-bold text-primary{% endif %}"
|
||||
<div class="list-group-item d-flex align-items-start{% if selected and selected.id == p.id %} active-row{% endif %}{% if not p.is_read %} fw-bold{% endif %}">
|
||||
<a class="flex-grow-1 text-decoration-none"
|
||||
href="/candidates?selected={{ p.id }}&group_by={{ group_by }}{% if filters_qs %}&{{ filters_qs }}{% endif %}">
|
||||
<span class="d-block text-truncate">{{ (p.text or '')[:90] }}</span>
|
||||
<span class="d-block text-muted">
|
||||
|
||||
Reference in New Issue
Block a user