Skip to content

feat(raport_slotow): migracja z long_running na django-liveops#618

Open
mpasternak wants to merge 3 commits into
devfrom
feat/raport-slotow-liveops
Open

feat(raport_slotow): migracja z long_running na django-liveops#618
mpasternak wants to merge 3 commits into
devfrom
feat/raport-slotow-liveops

Conversation

@mpasternak

Copy link
Copy Markdown
Member

Cel

Przeniesienie RaportSlotowUczelnia (jedynej long-running operacji w
raport_slotow) z long_running.Report (+ ASGINotificationMixin) na
django-liveops 0.4 (LiveOperation + run(self, p) + WebProgress po
WebSocket/HTMX), analogicznie do zmigrowanego import_pracownikow. To jest
Report (generuje wiersze do bazy), więc wynik to osobna tabela (-results),
a nie panel inline — Ścieżka A (get_success_url()).

Co się zmieniło

Model / migracja (Faza 2)

  • RaportSlotowUczelnia(LiveOperation); create_report()run(self, p)
    owinięte w transaction.atomic() (liveops task_run nie owija; long_running
    owijało — zachowujemy all-or-nothing generacji). Postęp przez p.percent().
  • on_reset()on_restart(); dodano get_success_url() (po FINISHED_OK
    przejście na tabelę wyników). Jawne host_template_name/result_template_name
    (kolizja class_to_snake z istniejącym raport_slotow_uczelnia.html).
  • Nowa migracja 0022_liveops — delta pól stanu liveops + RemoveField last_updated_on + owner related_name="+". pk pozostaje UUID (bez
    migracji pk), URL-e już <uuid:pk>.

API v1 (druga powierzchnia — pękała razem z modelem)

  • Usunięto last_updated_on z serializera (po RemoveField każde żądanie do
    viewsetu → ImproperlyConfigured).
  • create() (@transaction.atomic) kolejkuje teraz przez
    transaction.on_commit(inst.enqueue) zamiast martwego
    perform_generic_long_running_tasktask_perform (którego LiveOperation
    nie ma). Enqueue MUSI iść przez on_commit (create jest atomic).

Widoki / URL-e / szablony (Faza 3)

  • UtworzCreateLiveOperationView (owner + uczelnia z requestu + enqueue).
  • Lista → owner-scoped ListView (bez auto-kasowania starych operacji).
  • RegenRestartView POST-only + owner-scope + bramka BaseRaportAuthMixin;
    guard „reset tylko gdy stan terminalny" (chroni przed wyścigiem regenu
    w połowie runu z workerem — raport slotów nie ma maszyny stanów).
  • Results odcięte od LongRunningResultsView — własne parent_object
    (owner-scope + superuser-exempt) i get_queryset; zachowany django_tables2 +
    eksport XLSX; kontekst nadal niesie object.
  • Usunięto widoki/URL-e -router i -details (rolę przejął centralny
    liveops:live) oraz martwe szablony. Nowy host-template z wrapperem
    hx-headers X-CSRFToken (bo CSRF_COOKIE_HTTPONLY=True) + minimalny
    result-fragment fallback. Regen w tabeli wyników → formularz POST.
  • _LiveopsNoPermissionCompatMixin godzi braces AccessMixin (wymaga
    request) z liveops BaseLiveOperationMixin (Django-style bez request) —
    anonim dostaje 302 na login zamiast 500.

Zachowany scoping per-uczelnia

  • uczelnia FK zostaje na modelu; form_valid nadal ustawia
    uczelnia = uczelnia_dla_odczytu(request) PRZED save().
  • run() zachowuje filtr jednostka__uczelnia_id oraz przekazanie
    uczelnia_id/uczelnia do zbieraj_sloty/autorzy_zerowi — bez zmian.
  • Świadoma decyzja (LOW-4): ścieżka API create() NIE ustawia uczelnia
    (raport z API jest nie-zawężony) — to stan OBECNY sprzed migracji; owner-scope
    i tak izoluje odczyt. Udokumentowane w komentarzu serializera.
  • Guardy zielone: test_uczelnia_helper, test_per_uczelnia_uczelnia,
    tests_models/test_uczelnia (przepięte na run(MockProgress(...))).

Testy

  • uv run pytest src/raport_slotow src/api_v1/tests/test_raport_slotow_uczelnia.py
    98 passed (świeża baza, bez REUSE).
  • Regresja sąsiadów long_running + import_pracownikow + api_v1843
    passed
    (2 „failures" w test_wydawnictwo_*_no_queries to artefakt
    PYTEST_TESTCONTAINERS_REUSE — przechodzą na świeżej bazie).
  • Nowe testy: model liveops (test_liveops_uczelnia), guard restartu
    „nie-skończony nie resetuje", regen POST, anon 302 bez efektu ubocznego,
    API create-enqueue.
  • makemigrations --check --dry-run raport_slotow api_v1No changes
    detected
    (czysto).

Baseline

Baseline NIE był odświeżany w tym branchu (reguła: raz, przy scalaniu) —
wymaga make baseline-update przy merge (dodano migrację 0022 zmieniającą
schemat).

🤖 Generated with Claude Code

mpasternak and others added 3 commits July 16, 2026 21:00
…veops

Faza 2 (cutover bazy): RaportSlotowUczelnia dziedziczy teraz po
liveops.LiveOperation zamiast long_running.Report.

- create_report() -> run(self, p); ciało owinięte w transaction.atomic()
  (liveops.task_run nie owija; long_running owijało — zachowujemy
  all-or-nothing generacji). Postęp przez p.percent() zamiast send_progress.
- on_reset() -> on_restart(); dodano get_success_url() (Ścieżka A: po
  FINISHED_OK przejście na tabelę -results). Jawne host/result_template_name
  (kolizja class_to_snake z istniejącym szablonem wyników).
- Scoping per-uczelnia bez zmian: filtr jednostka__uczelnia_id +
  przekazanie uczelnia_id/uczelnia do zbieraj_sloty/autorzy_zerowi.
- Nowa migracja 0022_liveops (delta pól stanu liveops, usunięcie
  last_updated_on, owner related_name="+"). pk pozostaje UUID.
- API v1 (druga powierzchnia, pękała razem z modelem): usunięto
  last_updated_on z serializera; create() enqueue przez
  transaction.on_commit(inst.enqueue) zamiast martwego
  perform_generic_long_running_task -> task_perform.
- Testy modelu/guardy per-uczelnia: run(MockProgress(...)); nowy
  test_liveops_uczelnia; test create-enqueue w API.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…iveops

Faza 3+4 (cutover UI + sprzątanie):

- UtworzRaportSlotowUczelnia na CreateLiveOperationView; form_valid nadal
  ustawia uczelnia=uczelnia_dla_odczytu(request) PRZED save() (scoping
  per-uczelnia §8.1), owner + enqueue() bezpośrednio, redirect na live.
- ListaRaportSlotowUczelnia: zwykły owner-scoped ListView (bez auto-delete
  starych operacji — housekeeping to nie zadanie list-view).
- WygenerujPonownieRaportSlotowUczelnia: RestartView POST-only + owner-scope +
  bramka BaseRaportAuthMixin. Guard §8.4: reset/re-enqueue TYLKO gdy stan
  terminalny (chroni przed wyścigiem regen-w-trakcie-runu z workerem).
- SzczegolyRaportSlotowUczelniaListaRekordow: odcięte od LongRunningResultsView;
  własne parent_object (owner-scope + superuser-exempt) i get_queryset;
  zachowany django_tables2 + eksport XLSX; kontekst nadal niesie object.
- Usunięto widoki/URL-e router i details (rolę przejął centralny liveops:live);
  usunięto martwe szablony detail/wiersz_list.
- Nowe szablony: host (raport_slotow_uczelnia_live.html) z wrapperem
  hx-headers X-CSRFToken (CSRF_COOKIE_HTTPONLY) + minimalny result-fragment.
  Link listy → get_absolute_url; regen w tabeli wyników → formularz POST.
- _LiveopsNoPermissionCompatMixin: godzi braces AccessMixin (wymaga request)
  z liveops BaseLiveOperationMixin (Django-style bez request) — anonim dostaje
  302 na login zamiast 500.
- Testy widoków przepięte na nowy kontrakt (regen POST, lista bez kasowania,
  strona live, guard nie-skończonego, anon 302 bez efektu). Newsfragment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review PR #618:
- _generuj() wołało tylko p.percent() (bez check_cancelled), więc przycisk
  „Anuluj" był no-op mid-run, a newsfragment obiecywał anulowanie. Dodane
  p.check_cancelled() na szczycie pętli → OperationCancelled leci z bloku
  atomic w run() → rollback już-zapisanych wierszy (all-or-nothing). Test
  test_run_anulowanie_rollback to weryfikuje.
- Dodany test anon→302 dla UtworzRaportSlotowUczelnia (luka pokrycia —
  dotąd tylko Regen miał test compat-mixinu handle_no_permission).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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