fix: hide council editing controls from read-only viewers
CI / test (pull_request) Successful in 10m44s
CI / Fingerprint diagnostics (production deployment temporarily disabled) (pull_request) Skipped

This commit is contained in:
codex committed 2026-11-12 09:45:12 +00:00
1 parent 508aae4f8a
commit 3fb174899d
10 files changed
+26 -26

No files matched your search

+2 -2
View File
@@ -8,8 +8,7 @@
if (names.includes(previous)) branch.value = previous;
});
const dialog = document.querySelector('[data-council-dialog]');
if (!dialog) return;
const form = dialog.querySelector('[data-edit-form]');
const form = dialog?.querySelector('[data-edit-form]');
const autoSpecialties = form?.querySelector('[data-specialty-auto]');
const specialtyCheckboxes = form ? Array.from(form.querySelectorAll('[name="specialty_codes"]')) : [];
autoSpecialties?.addEventListener('change', () => {
@@ -149,6 +148,7 @@
}, true);
window.addEventListener('resize', repositionTooltips);
if (!dialog) return;
dialog.querySelectorAll('[data-edit-close]').forEach(button => {
button.addEventListener('click', () => { if (!saving) dialog.close(); });
});
@@ -7,9 +7,11 @@
{% if candidate.ambiguous %}<p class="council__hint">Есть сотрудники с одинаковым ФИО. Публикации из данных ВШЭ не присвоены автоматически; показаны записи из личного кабинета этого сотрудника.</p>{% endif %}
<ol class="council__articles">
{% for article in candidate.articles %}
<li class="council__article" tabindex="0" data-council-article aria-label="{{ article.title }}. Нажмите, чтобы показать редактирование">
<li class="council__article"{% if can_edit %} tabindex="0" data-council-article aria-label="{{ article.title }}. Нажмите, чтобы показать редактирование"{% endif %}>
{% if can_edit %}
{% set employee_id = candidate.employee_id %}
{% include "council_edit_button.html" %}
{% endif %}
<div class="council__article-heading">
<strong>{{ article.title }}</strong>
<span>{{ article.year or 'Год не указан' }}{% set field = 'year' %}{% set label = 'год публикации' %}{% include 'council_warning.html' %}</span>
+2 -5
View File
@@ -1,10 +1,10 @@
{% if can_edit %}
<dialog class="columns-modal" data-council-dialog aria-labelledby="article-edit-title">
<div class="columns-modal__panel">
<div class="columns-modal__header">
<h3 class="columns-modal__title" id="article-edit-title">Редактирование статьи</h3>
<button class="button button--ghost" type="button" data-edit-close aria-label="Закрыть редактирование">✕</button>
</div>
{% if can_edit %}
<form class="council-editor" data-edit-form>
<label class="directory__field" for="article-edit-name">Название статьи
<textarea class="directory__input" id="article-edit-name" name="title" required maxlength="2000" rows="3"></textarea>
@@ -37,9 +37,6 @@
</div>
</form>
<datalist id="council-journals">{% for name in journal_names|default([]) %}<option value="{{ name }}">{% endfor %}</datalist>
{% else %}
<p>Для редактирования статей и квартилей войдите в систему.</p>
<a class="button" href="/admin/login">Войти в систему</a>
{% endif %}
</div>
</dialog>
{% endif %}
+2 -3
View File
@@ -18,11 +18,10 @@
<div class="council__message" role="alert"><h3>Не удалось загрузить публикации</h3><p>Попробуйте ещё раз позже.</p><a class="admin__link" href="{{ request.url.path }}?{{ request.url.query }}">Повторить загрузку</a></div>
{% else %}
<p class="council__hint" role="status">Найдено статей: {{ matched }}{% if matched %} · Показаны {{ offset + 1 }}–{{ [offset + limit, matched]|min }}{% endif %}</p>
{% if not can_edit %}<p class="council__hint">Просмотр доступен без входа. Для сохранения изменений <a class="admin__link" href="/admin/login">войдите в систему</a>.</p>{% endif %}
<ol class="council__articles council__articles--editor" start="{{ offset + 1 }}">
{% for article in articles %}
<li class="council__article" tabindex="0" data-council-article aria-label="{{ article.title }}. Нажмите, чтобы показать редактирование">
{% set employee_id = article.employee_id %}{% include "council_edit_button.html" %}
<li class="council__article"{% if can_edit %} tabindex="0" data-council-article aria-label="{{ article.title }}. Нажмите, чтобы показать редактирование"{% endif %}>
{% if can_edit %}{% set employee_id = article.employee_id %}{% include "council_edit_button.html" %}{% endif %}
<p class="council__hint">{{ article.employee_name }}</p>
<div class="council__article-heading"><strong>{{ article.title }}</strong><span>{{ article.year or 'Год не указан' }}{% set field = 'year' %}{% set label = 'год публикации' %}{% include 'council_warning.html' %}</span></div>
<p class="council__hint">{{ article.display_type }}{% set field = 'type' %}{% set label = 'тип материала' %}{% include 'council_warning.html' %}</p>
+3 -3
View File
@@ -1,3 +1,3 @@
APP_VERSION = "2.0.0"
FRONTEND_VERSION = "2.0.0"
BACKEND_VERSION = "2.0.0"
APP_VERSION = "2.0.1"
FRONTEND_VERSION = "2.0.1"
BACKEND_VERSION = "2.0.1"
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "miem-workers"
version = "2.0.0"
version = "2.0.1"
description = "MIEM employees parser, admin API, and web admin"
requires-python = ">=3.11"
dependencies = [
+3 -8
View File
@@ -66,16 +66,11 @@ def main():
page.on('pageerror', lambda error: errors.append(str(error)))
page.goto('http://miem.test/admin/council')
page.locator('.council__candidate-summary').click()
expect(page.locator('.council__article strong')).to_have_text('Исследование передачи данных')
expect(page.locator('[data-council-edit]')).to_have_count(0)
expect(page.locator('[data-council-dialog]')).to_have_count(0)
article = page.locator('[data-council-article]')
pencil = article.locator('[data-council-edit]')
expect(pencil).to_be_hidden()
article.locator('strong').click()
expect(pencil).to_be_visible()
pencil.click()
expect(page.get_by_role('dialog')).to_be_visible()
expect(page.get_by_role('link', name='Войти в систему', exact=True)).to_be_visible()
page.keyboard.press('Escape')
expect(pencil).to_be_focused()
client.cookies.set(SESSION_COOKIE, sign_session('admin', settings))
page.reload()
page.locator('.council__candidate-summary').click()
+1 -1
View File
@@ -18,7 +18,7 @@ def test_health_returns_versions():
response = TestClient(app).get("/api/health")
assert response.status_code == 200
assert response.json()["backend_version"] == "2.0.0"
assert response.json()["backend_version"] == "2.0.1"
def test_api_employees_and_stats_require_admin_session():
+1 -1
View File
@@ -230,7 +230,7 @@ def test_public_page_data_minimization_and_safe_links(db_session, export):
assert "javascript:" not in fragment.text and "private@example.org" not in fragment.text
assert "javascript:" not in response.text and "private@example.org" not in response.text
assert "/admin/employees/" not in response.text and "/admin/logout" not in response.text
assert "Backend 2.0.0 · Frontend 2.0.0" in response.text
assert "Backend 2.0.1 · Frontend 2.0.1" in response.text
assert "Вход в систему" in response.text
assert client.get("/admin/directory", follow_redirects=False).status_code in (302, 303, 401, 403)
assert "Сотрудники не найдены" in client.get("/admin/council?q=missing").text
+8 -1
View File
@@ -82,8 +82,15 @@ def test_editor_permissions_csrf_validation_and_missing_article(editor, db_sessi
assert db_session.get(CouncilCorrections, 1) is None
client.cookies.clear()
assert save(client, payload).status_code == 401
original_title = db_session.scalar(select(EmployeePublication.title).where(
EmployeePublication.employee_id == payload['employee_id']))
response = client.get('/admin/council/publications')
assert response.status_code == 200 and 'Для редактирования' in response.text
assert response.status_code == 200
assert original_title in response.text
assert 'data-council-edit' not in response.text and 'data-council-dialog' not in response.text
fragment = client.get(f"/admin/council/employees/{payload['employee_id']}/articles")
assert fragment.status_code == 200 and original_title in fragment.text
assert 'data-council-article' not in fragment.text and 'data-council-edit' not in fragment.text
def test_editor_journal_reassignment_year_scope_and_unknown_year(editor, db_session):