mirror of
https://gitverse.ru/kpa39l/vesti.git
synced 2026-09-29 09:55:03 +00:00
fix(candidates): после Отклонить остаёмся на кандидатах, кнопки действий над комментарием
- reject/reject-old (single + bulk): редирект на /candidates с текущей группировкой вместо /candidates?status=rejected — не прерываем отбор. Отклонённые доступны через фильтр статуса. - candidates.html: Обработать/Переписать/Отклонить — один ряд над полем комментария (после переноса Опубликовать в approve-форму кнопки съехали). changes: candidates-buttons-order, reject-stay-candidates (skip_specs, валидны).
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
schema: spec-driven
|
||||
skip_specs: true
|
||||
created: 2026-09-15
|
||||
@@ -0,0 +1,106 @@
|
||||
## Design
|
||||
|
||||
Файл: `/opt/vesti/web/templates/candidates.html`, правая панель
|
||||
(`{% if selected %}`), блок действий (строки ~150–184).
|
||||
|
||||
### Текущая разметка
|
||||
|
||||
```html
|
||||
<div class="mb-3 d-flex flex-wrap gap-2">
|
||||
{% set hid %}...{% endset %}
|
||||
{% if selected.status != 'published' %}
|
||||
<form method="post" action="/posts/{{ selected.id }}/approve" id="approve-form">
|
||||
{{ hid }}
|
||||
<label class="form-label small text-muted mb-0">💬 Мой комментарий...</label>
|
||||
<textarea class="form-control form-control-sm mt-1" name="comment" rows="2"
|
||||
placeholder="Комментарий/анонс…">{{ selected.comment or '' }}</textarea>
|
||||
<div class="mt-2 d-flex gap-2">
|
||||
<button class="btn btn-success" type="submit" title="...">✅ Опубликовать</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" type="submit"
|
||||
formaction="/posts/{{ selected.id }}/comment" title="...">💾 Сохранить комментарий</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if selected.status != 'published' %}
|
||||
<form method="post" action="/posts/{{ selected.id }}/reclassify">
|
||||
{{ hid }}
|
||||
<button class="btn btn-info" type="submit" title="...">🤖 Обработать моделью</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if selected.status != 'published' %}
|
||||
<form method="post" action="/posts/{{ selected.id }}/rewrite">
|
||||
{{ hid }}
|
||||
<button class="btn btn-outline-primary" type="submit" title="...">✍️ Переписать</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if selected.status != 'rejected' and selected.status != 'published' %}
|
||||
<form method="post" action="/posts/{{ selected.id }}/reject">
|
||||
{{ hid }}
|
||||
<button class="btn btn-outline-danger" type="submit">🚫 Отклонить</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Новая разметка
|
||||
|
||||
Кнопки действий выносятся в ОДИН flex-ряд НАД полем комментария; approve-форма
|
||||
остаётся обёрткой только для «Опубликовать» + «Сохранить комментарий» + textarea
|
||||
(поля `hid` нужны каждой форме; внутри approve-формы они уже есть).
|
||||
|
||||
```html
|
||||
<div class="mb-3 d-flex flex-wrap gap-2">
|
||||
{% set hid %}...{% endset %}
|
||||
{% if selected.status != 'published' %}
|
||||
<form method="post" action="/posts/{{ selected.id }}/reclassify">
|
||||
{{ hid }}
|
||||
<button class="btn btn-info" type="submit" title="...">🤖 Обработать моделью</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if selected.status != 'published' %}
|
||||
<form method="post" action="/posts/{{ selected.id }}/rewrite">
|
||||
{{ hid }}
|
||||
<button class="btn btn-outline-primary" type="submit" title="...">✍️ Переписать</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if selected.status != 'rejected' and selected.status != 'published' %}
|
||||
<form method="post" action="/posts/{{ selected.id }}/reject">
|
||||
{{ hid }}
|
||||
<button class="btn btn-outline-danger" type="submit">🚫 Отклонить</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if selected.status != 'published' %}
|
||||
<form method="post" action="/posts/{{ selected.id }}/approve" id="approve-form">
|
||||
{{ hid }}
|
||||
<div class="mt-0 d-flex gap-2">
|
||||
<button class="btn btn-success" type="submit" title="...">✅ Опубликовать</button>
|
||||
</div>
|
||||
<label class="form-label small text-muted mb-0 mt-2">💬 Мой комментарий...</label>
|
||||
<textarea class="form-control form-control-sm mt-1" name="comment" rows="2"
|
||||
placeholder="Комментарий/анонс…">{{ selected.comment or '' }}</textarea>
|
||||
<div class="mt-2">
|
||||
<button class="btn btn-sm btn-outline-secondary" type="submit"
|
||||
formaction="/posts/{{ selected.id }}/comment" title="...">💾 Сохранить комментарий</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
```
|
||||
|
||||
Порядок в ряду: «🤖 Обработать моделью» → «✍️ Переписать» → «🚫 Отклонить» →
|
||||
«✅ Опубликовать» (кнопка публикации — правая, главная). Затем — поле
|
||||
комментария и «💾 Сохранить комментарий».
|
||||
|
||||
Все кнопки остаются `type="submit"` в своих формах; `formaction` у
|
||||
«Сохранить комментарий» сохраняется; логика видимости не меняется.
|
||||
|
||||
## Верификация
|
||||
|
||||
- `openspec validate candidates-buttons-order` — чисто.
|
||||
- Рендер шаблона проверяется только ч/з сам сервис (TestClient/живой GET),
|
||||
т.к. прямой рендер `tpl.get_template(...)` блокируется политикой.
|
||||
- Перезапуск: `sudo systemctl restart vesti-web` (Jinja2-кеш!).
|
||||
- Ручная проверка: `GET /candidates` → выбран неопубликованный пост → в
|
||||
правой панели НАД полем комментария в одном ряду кнопки
|
||||
Обработать/Переписать/Отклонить/Опубликовать, под textarea — «Сохранить
|
||||
комментарий».
|
||||
@@ -0,0 +1,46 @@
|
||||
## Why
|
||||
|
||||
В прошлой сессии кнопку «Опубликовать» перенесли в approve-форму (вместе с
|
||||
полем комментария). В результате в правой панели деталей кандидата кнопки
|
||||
«Обработать моделью», «Переписать», «Отклонить» оказались ПОСЛЕ блока
|
||||
комментария отдельным flex-рядом — визуально «съехали» и разорвали основной
|
||||
ряд действий.
|
||||
|
||||
Пользователь: действия модератора должны быть над полем комментария, в одном
|
||||
ряду с «Опубликовать».
|
||||
|
||||
## What Changes
|
||||
|
||||
- В `web/templates/candidates.html`, правая панель выбранного поста:
|
||||
- Перенести кнопки «🤖 Обработать моделью», «✍️ Переписать», «🚫 Отклонить»
|
||||
из отдельного flex-ряда ПОСЛЕ approve-формы в ОДИН ряд с кнопкой
|
||||
«✅ Опубликовать» — НАД полем комментария.
|
||||
- Итоговая структура блока действий (когда пост не опубликован/не отклонён):
|
||||
|
||||
```
|
||||
[✅ Опубликовать] [🤖 Обработать моделью] [✍️ Переписать] [🚫 Отклонить]
|
||||
💬 Мой комментарий (будет первым в канале):
|
||||
[textarea комментария]
|
||||
[💾 Сохранить комментарий]
|
||||
```
|
||||
|
||||
- Логика видимости кнопок не меняется:
|
||||
- «Опубликовать» — статус != published;
|
||||
- «Обработать моделью», «Переписать» — статус != published;
|
||||
- «Отклонить» — статус != rejected и != published.
|
||||
|
||||
## Why Not
|
||||
|
||||
- Не объединять все кнопки в одну approve-форму: «Сохранить комментарий»
|
||||
использует отдельный эндпоинт (`/posts/{id}/comment`), а «Обработать
|
||||
моделью»/«Переписать»/«Отклонить» — свои POST-эндпоинты; в одну форму их
|
||||
не собрать без JS (а htmx/JS-сложная логика в проекте запрещены).
|
||||
- Оставлять текущий порядок нельзя — кнопки визуально разорваны.
|
||||
|
||||
## Impact
|
||||
|
||||
- Файл: `web/templates/candidates.html` (правка только разметки, без JS и
|
||||
эндпоинтов).
|
||||
- Данные: без изменений.
|
||||
- Сервис: `vesti-web` (:8400) — нужен перезапуск (Jinja2-кеш без --reload).
|
||||
- Rollback: вернуть разметку и перезапустить сервис.
|
||||
@@ -0,0 +1,10 @@
|
||||
# candidates-buttons-order
|
||||
|
||||
- [x] Создан OpenSpec change (proposal/design) — `skip_specs: true`, без delta-спеки (правка только разметки, поведение кнопок не меняется)
|
||||
- [x] `openspec validate candidates-buttons-order` — чисто
|
||||
- [x] candidates.html: кнопки Обработать/Переписать/Отклонить перенесены над полем комментария, в одном ряду с Опубликовать
|
||||
- [x] Проверка: рендер правой панели (живой GET :8400 после рестарта) — ряд кнопок над textarea комментария
|
||||
- [x] Перезапуск `sudo systemctl restart vesti-web`
|
||||
- [x] Ручная проверка в браузере: порядок кнопок над комментарием
|
||||
- [x] Обновить STATUS.md / TODO.md
|
||||
- [x] Бэкап (крон 2:45 делает сам, вручную НЕ запускать)
|
||||
@@ -0,0 +1,3 @@
|
||||
schema: spec-driven
|
||||
skip_specs: true
|
||||
created: 2026-09-15
|
||||
@@ -0,0 +1,57 @@
|
||||
## Design
|
||||
|
||||
Файл: `/opt/vesti/web/app.py`.
|
||||
|
||||
### 1. `reject()` — строка 582
|
||||
|
||||
Сейчас:
|
||||
|
||||
```python
|
||||
return RedirectResponse(url=_cand_back(group_by, direction, own, q, status="rejected"), status_code=302)
|
||||
```
|
||||
|
||||
Стало:
|
||||
|
||||
```python
|
||||
return RedirectResponse(url=_cand_back(group_by, direction, own, q), status_code=302)
|
||||
```
|
||||
|
||||
`_cand_back` без `status` строит `/candidates` с сохранением
|
||||
`group_by/direction/own/q` — очереди кандидатов (`status=new` по умолчанию).
|
||||
|
||||
### 2. `candidates_bulk()` — строки 345, 358
|
||||
|
||||
Сейчас:
|
||||
|
||||
```python
|
||||
if action == "reject":
|
||||
...
|
||||
return RedirectResponse(url=back + "&status=rejected", status_code=302)
|
||||
...
|
||||
if action == "reject-old":
|
||||
...
|
||||
return RedirectResponse(url=back + "&status=rejected", status_code=302)
|
||||
```
|
||||
|
||||
Стало: редирект на `back` (уже содержит `group_by` + фильтры), без
|
||||
`&status=rejected`.
|
||||
|
||||
```python
|
||||
if action == "reject":
|
||||
...
|
||||
return RedirectResponse(url=back, status_code=302)
|
||||
...
|
||||
if action == "reject-old":
|
||||
...
|
||||
return RedirectResponse(url=back, status_code=302)
|
||||
```
|
||||
|
||||
## Верификация
|
||||
|
||||
- `openspec validate reject-stay-candidates` — чисто.
|
||||
- Юнит (TestClient): login → POST `/posts/{id}/reject` с `group_by=date`
|
||||
→ ожидаем `Location: /candidates?group_by=date` (БЕЗ `status=rejected`).
|
||||
- Перезапуск: `sudo systemctl restart vesti-web`.
|
||||
- Ручная проверка: отклонить пост из правой панели → остаёмся на
|
||||
«Кандидатах» в той же группировке, пост пропал из списка; список
|
||||
отклонённых доступен через фильтр «🗑 Отклонённые».
|
||||
@@ -0,0 +1,37 @@
|
||||
## Why
|
||||
|
||||
После нажатия «🚫 Отклонить» в правой панели кандидата пользователь
|
||||
попадает на страницу `/candidates?status=rejected` (отдельный список
|
||||
отклонённых), а не остаётся в текущем списке кандидатов. Это разрывает
|
||||
поток отбора: после отклонения одного поста нужно вернуться в очередь
|
||||
кандидатов и продолжить.
|
||||
|
||||
Same для массового отклонения через bulk-форму (левая панель,
|
||||
«🚫 Отклонить» / «🗑 Старьё»): редирект уводит на `status=rejected`.
|
||||
|
||||
## What Changes
|
||||
|
||||
- `web/app.py`, `reject()` (`POST /posts/{id}/reject`): редирект после
|
||||
отклонения — на `/candidates` с сохранением текущей группировки и
|
||||
фильтров, БЕЗ `status=rejected` (чтобы остаться в очереди кандидатов).
|
||||
Т.е. `_cand_back(group_by, direction, own, q)` без `status`.
|
||||
- `web/app.py`, `candidates_bulk()`: для `action == "reject"` и
|
||||
`action == "reject-old"` — редирект на `back` (текущая группировка +
|
||||
фильтры) БЕЗ `&status=rejected`.
|
||||
|
||||
Текущее поведение (reject → список отклонённых) доступно вручную через
|
||||
фильтр статуса «🗑 Отклонённые» или `?status=rejected`.
|
||||
|
||||
## Why Not
|
||||
|
||||
- Не менять approve: после публикации переход на опубликованные —
|
||||
осознанное поведение (посмотреть результат), менять не просили.
|
||||
- Не добавлять параметр «куда вернуться»: редирект на текущую группировку
|
||||
достаточен и предсказуем.
|
||||
|
||||
## Impact
|
||||
|
||||
- Файл: `web/app.py` (две точки редиректа).
|
||||
- Данные: без изменений.
|
||||
- Сервис: `vesti-web` (:8400) — перезапуск.
|
||||
- Rollback: вернуть `status="rejected"` в редиректах и перезапустить.
|
||||
@@ -0,0 +1,11 @@
|
||||
# reject-stay-candidates
|
||||
|
||||
- [x] Создан OpenSpec change (proposal/design) — `skip_specs: true`
|
||||
- [x] `openspec validate reject-stay-candidates` — чисто
|
||||
- [x] web/app.py `reject()`: редирект на `/candidates` без `status=rejected`
|
||||
- [x] web/app.py `candidates_bulk()`: reject/reject-old → `back` без `status=rejected`
|
||||
- [x] Тест (TestClient): Location после reject содержит group_by, не содержит `status=rejected`
|
||||
- [x] Перезапуск `sudo systemctl restart vesti-web`
|
||||
- [x] Ручная проверка: после отклонения остаёмся на кандидатах (живой URL `/candidates?group_by=date`)
|
||||
- [x] Обновить STATUS.md / TODO.md
|
||||
- [x] Бэкап (крон 2:45 делает сам, вручную НЕ запускать)
|
||||
Reference in New Issue
Block a user