Skip to content

feat(import_polon): migracja z long_running na django-liveops#617

Open
mpasternak wants to merge 4 commits into
devfrom
feat/import-polon-liveops
Open

feat(import_polon): migracja z long_running na django-liveops#617
mpasternak wants to merge 4 commits into
devfrom
feat/import-polon-liveops

Conversation

@mpasternak

Copy link
Copy Markdown
Member

Co się zmienia

Migracja obu importerów w import_polon — importu z POLON
(ImportPlikuPolon) oraz importu absencji (ImportPlikuAbsencji) — ze
starego mechanizmu long_running (Operation + ASGINotificationMixin,
kanały ?extraChannels=[pk]) na django-liveops 0.4: kanały liveop.<pk>,
podpisany subscription_token, snapshot-on-connect, throttlowany progress,
runner celery. Wzorem był scalony wcześniej import_pracownikow.

Zachowane (bez regresji)

  • Wielohostowe zawężanie POLON — walidacja pola ZATRUDNIENIE do uczelni
    importu, raport niezmatchowanych autorów, guard mutacji autora obcej
    uczelni oraz zawężenie listy importów do uczelni z requestu
    (test_multi_uczelnia_scoping.py bez zmian merytorycznych).
  • Domknięcie dry-run → „zapisz do bazy" — flip flagi + reset stanu +
    ponowne uruchomienie z zapisem (ZapiszDoBazyMixin, owner-scope bez
    long_running; enqueue odroczony przez transaction.on_commit).
  • Etykieta uczelni na liście importów.

Bugfix

Strona „import absencji" (index-absencji) pokazywała dotąd listę
importów POLON zamiast absencji — teraz ma własny widok
PokazImportyAbsencji + szablon importplikuabsencji_list.html.

Fazy

  1. Faza 1 — rdzeń (analyze_file_import_polon/absencji) przyjmuje
    liveops Progress (p.track + p.log); model wciąż legacy, więc
    perform() forwarduje przez lokalny _LegacyProgressBridge
    (check_cancelled no-op — legacy Operation nie ma cancel_requested).
  2. Faza 2 — atomowy cutover: model (migracja 0017_liveops) + widoki +
    URL-e + szablony (host/result z obowiązkowym wrapperem CSRF, bo
    CSRF_COOKIE_HTTPONLY=True) + przepisany test_zapisz_do_bazy.py (11
    testów). ResultsView z paginate_by=25 + object=parent_object. Restart
    gejtowany grupą „wprowadzanie danych" (_PkOwnerRestartMixin, precedens
    feat(import_pracownikow): Faza 0 — migracja na django-liveops + dry-run #508 F4).
  3. Faza 4 — sprzątanie + newsfragmenty (feature + bugfix). Faza 3
    (polish live-UI: stages) pominięta jako opcjonalna.

Testy

PYTEST_TESTCONTAINERS_REUSE=1 uv run pytest src/import_polon/59
passed
(w tym multi-uczelnia, zapisz-do-bazy ×11, nowe test_liveops.py:
finalizacja run(), strona live + wrapper CSRF, restart gejtowany grupą z
asercją po efekcie ubocznym — 302→login, nie 403).

DJANGO_BPP_SKIP_DOTENV=1 uv run python src/manage.py makemigrations --check --dry-runczysto dla aplikacji BPP (import_polon bez dryfu; listują
się tylko pakiety third-party: favicon/flexible_reports/siteblog).

Baseline

Nie odświeżany w tym branchu (świadomie) — wymaga make baseline-update
przy merge (nowa migracja 0017_liveops zmienia schemat obu tabel).

🤖 Generated with Claude Code

mpasternak and others added 4 commits July 16, 2026 21:03
…ress

Rdzeń analyze_file_import_polon / analyze_file_import_absencji dostaje
wymagany parametr ``p`` (liveops.Progress): pętle przechodzą na
``p.track(...)`` (throttlowany pasek + check_cancelled per wiersz), a
błędy wczytania pliku raportują ``p.log(...)`` zamiast ręcznych
``send_progress`` / ``send_notification``.

Model wciąż jest legacy ``Operation``, więc ``perform()`` buduje LOKALNY
``_LegacyProgressBridge`` forwardujący emisje do starego API
``ASGINotificationMixin`` (pasek postępu na dotychczasowej stronie details
działa dalej między Fazą 1 a 2). ``check_cancelled`` jest no-op — legacy
Operation nie ma pola ``cancel_requested``, więc realny track w celery nie
rzuci ValueError.

Call-site'y testów rdzenia przechodzą na ``MockProgress(model)``.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Oba importery (POLON + absencji) przechodzą z long_running
(Operation + ASGINotificationMixin) na liveops.LiveOperation: kanały
liveop.<pk>, podpisany subscription_token, snapshot-on-connect,
throttlowany progress, runner celery. Cutover jest atomowy — model,
widoki, URL-e, szablony oraz domknięcie dry-run→„zapisz do bazy" muszą
przejść w jednym zielonym commicie (wspólna klasa bazowa, jedna migracja,
wspólny ZapiszDoBazyMixin, jeden urls.py).

Model (0017_liveops): AlterModelOptions ordering=-created_on, usunięcie
last_updated_on, +10 pól liveops, owner related_name="+". run(self, p)
woła rdzeń z Progress i finalizuje p.result({"total": ...}); on_restart()
kasuje wiersze-dzieci; reset_liveops_state() zeruje stan przed re-enqueue.

Widoki: PokazImporty/PokazImportyAbsencji = owner-scoped ListView
(zachowane zawężenie multi-hosted + per-uczelnia trim dla POLON);
UtworzImport* = CreateLiveOperationView (POLON przypina uczelnię z
requestu); *ResultsView = ListView z paginate_by=25 + object=parent_object
(inaczej NoReverseMatch / cicha utrata paginacji), zachowany filter_form +
raport niezmatchowanych; ZapiszDoBazyMixin owner-scope bez long_running,
POST: flip flagi + on_restart + reset + transaction.on_commit(enqueue),
redirect na get_absolute_url; restart gejtowany grupą
(_PkOwnerRestartMixin(GroupRequiredMixin, RestartView), precedens #508 F4).

Szablony: nowe host/result (import_pliku_polon[_result].html + absencji) z
OBOWIĄZKOWYM wrapperem CSRF (CSRF_COOKIE_HTTPONLY=True); liczniki z
operation.get_details_set() (stare importy mają result_context=NULL); nowy
importplikuabsencji_list.html (bugfix index-absencji); linki na
get_absolute_url; usunięte *_detail.html + include operation_details.

Testy: przepisany test_zapisz_do_bazy.py (11) na liveops enqueue; nowe
test_liveops.py (run finalizuje, strona live + wrapper CSRF, restart
gejtowany grupą — asercja po efekcie ubocznym, 302→login nie 403).

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

Review PR #617: dodane testy potwierdzające, że anonim na utworz-import
(CreateLiveOperationView) i restart (_PkOwnerRestartMixin/RestartView)
dostaje 302→login, nie 500. braces GroupRequiredMixin.dispatch jest
najbardziej z lewej w MRO i short-circuituje anona przed liveops
BaseLiveOperationMixin (który woła bez-argumentowe handle_no_permission) —
POLON nie ma kolizji, którą raport_slotow rozwiązuje compat-mixinem.

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