Skip to content

DEV: formalize program case field contract - #728

Merged
TThanos3000 merged 2 commits into
devfrom
feature/dev-program-case-field-foundation
Sep 6, 2026
Merged

DEV: formalize program case field contract#728
TThanos3000 merged 2 commits into
devfrom
feature/dev-program-case-field-foundation

Conversation

@TThanos3000

@TThanos3000 TThanos3000 commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Основа

  • Base: dev, a12e36bc4cca5934e68c309681f7ff1df757ca35 (актуальный при повторном fetch).
  • Head: e83004cba98695f5f4bcb2e437c07f797bc49992.
  • Ветка: feature/dev-program-case-field-foundation.
  • Feature commit + follow-up исправления admin validation; всего 20 файлов,
    +1820 / −115. Follow-up отдельно: 7 файлов, +462 / −12.

Follow-up: неизменённый inline при сдаче через Admin

Bug воспроизведён реальным POST в admin change view с parent form и inline
formset для generic и case. До исправления оба позитивных теста возвращали
форму (200) вместо успешного redirect (302).

  • PartnerProgramFieldValue.clean() теперь сравнивает сохранённые
    program_project_id, field_id, value_text с текущим instance. Повторная
    validation неизменённой строки разрешена; новый объект/перенос/смена поля или
    текста считаются мутацией.
  • Freeze учитывает и persisted parent, и текущую несохранённую admin-форму.
    Одновременная сдача + mutation запрещена. Устаревший parent, перенос из
    сданной связи и снятие checkbox вместе с mutation не обходят правило.
  • При case-save блокировка родителя сохранена, но его экземпляр из admin-формы
    больше не заменяется копией из БД, теряющей pending submitted=true.
  • Отдельный inline formset запрещает DELETE после сдачи и при сдаче тем же
    POST. Обычная model validation недостаточна: Django игнорирует её ошибки
    на помеченных DELETE формах.
  • 26 реальных admin-сценариев: generic и case, неизменённый rgdftb + сдача,
    A→B + сдача, edit/add/delete после сдачи и одновременно со сдачей, неизменённый
    inline + несвязанная metadata, сохранение обычного редактирования до сдачи.
  • 12 model/API-тестов проверяют DB comparison, source/target link, stale parent,
    case validation и запрет даже идентичного PUT после сдачи.

update_program_link_fields(), submit_program_project(), case-validator,
serializers, link-scoped API и URL не изменены follow-up коммитом.

Что изменено

Отдельной Case-модели нет: существующие PartnerProgramField / PartnerProgramFieldValue
уже хранят определение и выбор для конкретной связи Project × Program.

  • Только точное name="case" является системным; используется единая константа.
  • Конфигурация: select, required, show_filter, непустые варианты; дубли проверяются
    без учёта регистра и внешних пробелов. Label произвольный. Generic fields не
    становятся системными по названию/подписи/типу и не получают новых ограничений.
  • На initial apply case можно не присылать; первый вариант не выбирается.
    Остальные required fields остаются обязательными. Явный case валидируется.
  • Submit проверяет выбор именно текущей связи и текущие options. Пустой/устаревший
    выбор даёт контролируемую 400 без изменения флага или даты сдачи. Нет case —
    прежний сценарий. После сдачи конкурсной связи все field values заблокированы.
  • PUT / submit используют transaction + row lock. Запись case и редактирование
    definition согласованы блокировкой definition. Два новых PostgreSQL-теста
    проверяют реальную конкуренцию записи со сдачей и удаления option с выбором.
  • Использованные варианты нельзя удалить/переименовать; использованный case field
    нельзя переименовать или перенести. Добавление, удаление неиспользованного option,
    изменение label/help_text допустимы. Admin ModelForm использует те же правила;
    стандартные inline и delete confirmation защищают использованный case field.

API и доступ

Новый канонический endpoint:

GET/PUT /programs/partner-program-projects/{programLinkId}/fields/

GET возвращает program_link_id, program_id, project_id, submitted,
definitions по pk с options: string[] и value: string|null.

PUT принимает прежний массив {field_id, value_text}, допускает partial update,
отклоняет чужие/повторные IDs и неверные значения атомарно. Пишет только лидер;
manager/expert/staff override не добавлен. GET использует существующий restricted
read helper, с ограничением program-ролей конкретной программой из URL.

Legacy PUT /projects/{projectId}/program-fields/: одна связь работает; 0 → 400;
несколько → 409, без выбора первой и без 500. Регрессии проверяют один Project
с разными case values в двух программах.

Существующий GET /programs/{id}/filters/ и manager / expert rate filters
переиспользованы без изменения. A, B и A+B возвращают правильные проекты;
значения другой программы не подмешиваются. Scoring не менялся.

SQL-бюджет

Link-scoped GET для лидера: ровно 3 SQL-запроса при 1 и 20 полях:
join link/program/project + definitions + values. Для остальных ролей только
фиксированное число дополнительных EXISTS проверок доступа; N+1 по fields нет.

Проверки нового head

  • Targeted admin/model/program fields + весь case suite + apply/submit:
    124 теста, OK, 2 PostgreSQL-only skip локально (122 passed).
  • Full backend локально: 804 теста, OK, 4 PostgreSQL-only skip (800 passed).
  • Backend PostgreSQL CI:
    804/804 passed, без skip, включая новые admin/model tests и case locking;
    отдельный locking/vacancy stage 39/39 passed; PostgreSQL vendor и
    select_for_update подтверждены, migrations применены на пустой БД.
  • Обычный CI:
    804 теста, OK, 4 PostgreSQL-only skip (800 passed); полный lint passed.
  • Scoped Flake8 и полный Flake8 в GitHub CI: passed.
  • Black: 17 Python-файлов полностью passed; изменённый код в admin.py и
    services/project_apply.py passed. Четыре существующих formatting-hunk в этих
    двух файлах оставлены как в origin/dev; новых замечаний Black нет.
  • manage.py check, check --tag models: passed.
  • makemigrations --check --dry-run: No changes detected.
  • git diff --check и проверка diff против origin/dev: passed.

Локальный Windows suite использует существующий внешний stub WeasyPrint из-за
недоступного Pango; он не входит в репозиторий/PR. PostgreSQL CI на Linux прошёл
с реальными зависимостями, без этого локального обхода.

Документация и ограничения v1

Полный контракт: docs/program-case-field.md.

  • Нет автоматического создания case и миграции исторических полей/значений.
  • Нет отдельного массового переименования: будущая операция должна транзакционно
    обновлять options и все values.
  • Model/form validation не является DB trigger: прямые SQL / bulk operations,
    удаление всей родительской программы администратором и административное ручное
    изменение submitted требуют отдельного контроля. Пользовательский lifecycle
    использует защищённые endpoints. Исторические невалидные значения автоматически
    не исправляются, а отклоняются при submit с понятным сообщением.
  • Analytics по кейсам и переход Angular на link-scoped API — отдельные этапы.

Границы

Angular, React, analytics contract, scoring semantics, dependencies, workflows,
Docker и deploy не изменены. Новых моделей/колонок/FK и migrations нет.

Draft PR. Не merge / не deploy.

@TThanos3000
TThanos3000 marked this pull request as ready for review September 6, 2026 18:05
@TThanos3000
TThanos3000 merged commit f932586 into dev Sep 6, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant