mirror of
https://gitverse.ru/kpa39l/vesti.git
synced 2026-09-29 09:55:03 +00:00
candidates: фикс смены группировки (422 при сабмите bulk-формы)
- Группировка вынесена из onchange-сабмита bulk-формы в GET-ссылки (/candidates?group_by=...&filters...&selected=...) - Чекбоксы bulk привязаны к форме атрибутом form='bulk-form' (без JS, HTML5) — устранены вложенные формы (невалидный HTML) - Выбор поста — ссылкой GET (selected=id) вместо POST /candidates/select - app.py: добавлен filters_qs (query-string фильтров для ссылок), импорт urllib.parse.quote; /candidates/select оставлен для обратной совместимости (сабмит старых форм) - Тест: смена группировки, bulk reject, выбор поста, фильтры — зелёные
This commit is contained in:
+10
-1
@@ -8,6 +8,7 @@ import secrets
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
|
||||
import markdown as md_lib
|
||||
from fastapi import FastAPI, Form, HTTPException, Request, Response
|
||||
@@ -250,9 +251,17 @@ def candidates(request: Request, direction: str = "", status: str = "", own: str
|
||||
continue
|
||||
break
|
||||
conn.close()
|
||||
# строка параметров фильтров для ссылок (выбор поста, смена группировки)
|
||||
qs = []
|
||||
for k, v in (("direction", direction), ("status", status), ("own", own), ("q", q)):
|
||||
if v:
|
||||
qs.append(f"{k}={quote(v)}")
|
||||
if not selected:
|
||||
selected = sel["id"] if sel else 0
|
||||
filters_qs = "&".join(qs)
|
||||
html = tpl.get_template("candidates.html").render(
|
||||
posts=grouped, counts=counts, status=status, direction=direction, own=own,
|
||||
q=q, group_by=group_by, selected=sel, error=error,
|
||||
q=q, group_by=group_by, selected=sel, error=error, filters_qs=filters_qs,
|
||||
directions=DIRECTIONS, STATUS_LABELS=STATUS_LABELS, STATUS_COLORS=STATUS_COLORS,
|
||||
)
|
||||
return HTMLResponse(html)
|
||||
|
||||
@@ -45,30 +45,24 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/candidates/bulk" id="bulk-form"
|
||||
onsubmit="return confirm('Выполнить групповое действие над выбранными?')">
|
||||
<input type="hidden" name="direction" value="{{ direction }}">
|
||||
<input type="hidden" name="status" value="{{ status }}">
|
||||
<input type="hidden" name="own" value="{{ own }}">
|
||||
<input type="hidden" name="q" value="{{ q }}">
|
||||
<input type="hidden" name="group_by" value="{{ group_by }}">
|
||||
{% set selected_qs = '&selected=' ~ selected.id if selected else '' %}
|
||||
|
||||
<div class="row g-0">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-5">
|
||||
<div class="border rounded p-2" style="max-height: 78vh; overflow-y:auto; background:#fff;">
|
||||
<div class="border rounded p-2 d-flex flex-column" style="max-height: 78vh; background:#fff;">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="me-2 text-muted small">Группировка:</span>
|
||||
<select name="group_by" class="form-select form-select-sm d-inline" style="width:auto;"
|
||||
onchange="this.form.submit()">
|
||||
{% for val in ['source', 'date', 'status'] %}
|
||||
<option value="{{ val }}" {% if group_by == val %}selected{% endif %}>
|
||||
{% if val == 'source' %}по источнику{% elif val == 'date' %}по дате{% else %}по статусу{% endif %}
|
||||
</option>
|
||||
{% for val, label in [('source', 'по источнику'), ('date', 'по дате'), ('status', 'по статусу')] %}
|
||||
{% if group_by == val %}
|
||||
<span class="badge bg-primary text-white me-1">{{ label }}</span>
|
||||
{% else %}
|
||||
<a class="badge bg-light text-dark border me-1 text-decoration-none"
|
||||
href="/candidates?group_by={{ val }}{% if filters_qs %}&{{ filters_qs }}{% endif %}{{ selected_qs }}">{{ label }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline-secondary ms-2" type="submit">➡️</button>
|
||||
</div>
|
||||
|
||||
<div style="overflow-y:auto; flex:1 1 auto;">
|
||||
{% for g in posts %}
|
||||
<details {% if loop.first or (selected and selected.id in g.posts|map(attribute='id')|list) %}open{% endif %} class="mb-2">
|
||||
<summary class="small text-secondary">
|
||||
@@ -81,25 +75,17 @@
|
||||
<div class="list-group list-group-flush small">
|
||||
{% for p in g.posts %}
|
||||
<div class="list-group-item d-flex align-items-start">
|
||||
<input class="form-check-input me-2" type="checkbox" name="ids" value="{{ p.id }}"
|
||||
id="cb-{{ p.id }}" style="vertical-align:middle;">
|
||||
<form class="d-inline flex-grow-1" method="post" action="/candidates/select">
|
||||
<input type="hidden" name="id" value="{{ p.id }}">
|
||||
<input type="hidden" name="group_by" value="{{ group_by }}">
|
||||
<input type="hidden" name="direction" value="{{ direction }}">
|
||||
<input type="hidden" name="status" value="{{ status }}">
|
||||
<input type="hidden" name="own" value="{{ own }}">
|
||||
<input type="hidden" name="q" value="{{ q }}">
|
||||
<button type="submit" class="btn btn-link p-0 text-start w-100 text-decoration-none
|
||||
{% if selected and selected.id == p.id %}fw-bold text-primary{% endif %}">
|
||||
<a class="flex-grow-1 text-decoration-none{% if selected and selected.id == p.id %} fw-bold text-primary{% endif %}"
|
||||
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">
|
||||
{{ p.source_name }} · {{ (p.published_at or p.fetched_at) | dt }}
|
||||
{% if p.is_own %} · ⭐{% endif %}
|
||||
{% if p.direction %}<span class="badge bg-light">{{ p.direction }}</span>{% endif %}
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</a>
|
||||
<input class="form-check-input ms-auto mt-1" type="checkbox" name="ids" value="{{ p.id }}"
|
||||
form="bulk-form" {% if selected and selected.id == p.id %}checked{% endif %}>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -108,6 +94,25 @@
|
||||
<div class="alert alert-info py-2">Нет кандидатов по фильтру</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<form method="post" action="/candidates/bulk" id="bulk-form"
|
||||
onsubmit="return confirm('Выполнить групповое действие над выбранными?')">
|
||||
<input type="hidden" name="direction" value="{{ direction }}">
|
||||
<input type="hidden" name="status" value="{{ status }}">
|
||||
<input type="hidden" name="own" value="{{ own }}">
|
||||
<input type="hidden" name="q" value="{{ q }}">
|
||||
<input type="hidden" name="group_by" value="{{ group_by }}">
|
||||
<div class="d-flex align-items-center gap-2 mt-2">
|
||||
<select name="action" class="form-select form-select-sm" style="width:auto;">
|
||||
<option value=""></option>
|
||||
<option value="reject">🚫 Отклонить</option>
|
||||
<option value="reject-old">🗑 Отклонить старьё</option>
|
||||
<option value="approve">✅ Опубликовать</option>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-primary" type="submit">Выполнить</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-7">
|
||||
@@ -188,6 +193,5 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user