Skip to content

feat(#438): Faza C — usunięcie modelu Wydzial i całej jego powierzchni#465

Open
mpasternak wants to merge 20 commits into
devfrom
feat/438-faza-C
Open

feat(#438): Faza C — usunięcie modelu Wydzial i całej jego powierzchni#465
mpasternak wants to merge 20 commits into
devfrom
feat/438-faza-C

Conversation

@mpasternak

Copy link
Copy Markdown
Member

Faza C (#438): usunięcie modelu Wydzial i całej jego powierzchni

Domknięcie konsolidacji Wydział → Jednostka. Po Fazie B (jedno drzewo MPTT,
wszystkie zewnętrzne FK przepięte na Jednostka) „wydział" to po prostu
jednostka top-level (parent IS NULL). Faza C usuwa martwy już model
Wydzial
wraz z całą powierzchnią API/UI/importu oraz scaffoldingiem
konwersji.

Migracje (0466 → 0469, zależność liniowa od 0465_merge)

# migracja co robi
0466 faza_c_backfill_poprzednie_nazwy dopisuje nazwy dawnych wydziałów do poprzednie_nazwy korzeni (idempotentnie) — historia nazw przeżywa drop modelu
0467 faza_c_drop_wydzial DeleteModel("Wydzial") (cross-app deps: import_dyscyplin 0024, zglos_publikacje 0026)
0468 faza_c_drop_legacy_markery RemoveField × 2: legacy_wydzial_id, jest_lustrem (markery konwersji Fazy B — runtime ich nie używa)
0469 faza_c_czysc_contenttype_wydzial kasuje osierocony ContentType + Permission po Wydzial (Django nie robi tego sam w migracjach)

Usunięta powierzchnia

  • Model + sygnały: bpp/models/wydzial.py, bpp/models/struktura_konwersja.py
    (znajdz_lub_utworz_wezel_wydzialu).
  • API v1: /api/v1/wydzial/ (serializer + viewset + router).
  • Autocomplete: WydzialAutocomplete, PublicWydzialAutocomplete.
  • Komendy: konwertuj_wydzialy_na_jednostki, waliduj_konwersje_wydzialow.
  • Settings/cacheops/dashboard: referencje bpp.Wydzial.
  • Browse: /wydzial/<slug>/ → trwały (301) redirect na
    bpp:browse_jednostka po tym samym slug-u (Opcja A, mapowanie 1:1).
  • Martwa fixture: um_lublin_wydzial.json.

Przepięcia runtime (bez modelu)

  • Jednostka.wydzial_dnia() zwraca węzeł-rodzic (Jednostka) wprost.
  • imports/egeria_2012.py — lookup wydziału po Jednostka top-level.
  • Onboarding pbn_import, importer XLSX, demo-data — tworzą top-level Jednostka.
  • Metody węzła dla strukturalnego stylu browse liczą poddrzewo po MPTT
    (get_descendants) / zdenorm. self-FK wydzial — nigdy po markerach.

Co ZOSTAJE (audyt T14)

Konfigurowalna etykieta top-level jest niezależna od modelu:

  • Uczelnia.uzywaj_wydzialow (flaga ekspozycji pojęcia „Wydział"),
  • nazwa_wydzialu = lemat("WYDZIAL") (etykieta z lematów gramatycznych).

Testy

  • Grep-gate: zero żywych referencji Wydzial w kodzie (poza komentarzami
    historycznymi i zamrożonymi migracjami).
  • ~35 plików testów + fixtury przepięte na jednostkę top-level.
  • Nowe testy: test_wydzial_usuniety (model/tabela/markery/kolumny znikły),
    test_migration_0469_czysc_contenttype (niepusty — seeduje CT+Permission).
  • Pełna lokalna suita make tests-without-playwright — zielona.

⚠️ Uwaga dla mergującego: baseline

baseline.sql na dev jest nieświeży (bpp @ 0442, 27 migracji wstecz — stan
współdzielony, nie z Fazy C). Zgodnie z CLAUDE.md NIE dołączam re-dumpa do
tego PR-a (byłby to ~4,6k-liniowy diff mieszający migracje innych feature'ów).
Przy scalaniu puść make baseline-update RAZ. Łańcuch 0443→0469
zwalidowany end-to-end lokalnie (baseline_update zaaplikował go na czystym
kontenerze bez błędów).

Closes część #438 (Faza C).

🤖 Generated with Claude Code

mpasternak and others added 20 commits July 7, 2026 08:20
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
D1=Opcja A (redirect po slugu jednostki), D2=nie (pole wydzial bez renamu),
D3=odlozone (rename modelu poza zakresem).

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

Kluczowe: pbn_import/import_dyscyplin/egeria to zywi konsumenci Wydzial
(pominieci w pierwotnej inwentaryzacji); cross-app deps migracji DeleteModel;
backfill poprzednie_nazwy przed dropem; migracja ~25 plikow testow; MPTT
per-object create; kolizja Jednostka.nazwa w widgecie; non-vacuous test CT.

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

T1: matchuj_wydzial/_wydzial_filtr (import_common) + _matchuj_wydzial_z_cache
(import_dyscyplin) konsumuja Jednostke top-level zamiast modelu Wydzial.
Match po nazwie LUB poprzednie_nazwy (promowany 1-jedn. wydzial ma nazwe
jednostki; dawna nazwa wydzialu w poprzednie_nazwy — backfill 0466). Usuwa
zaleznosc od struktura_konwersja i pulapke cichego filter(Jednostka.pk jako
Wydzial.pk) (Fable F3/F6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
T2: WydzialGetOrCreateWidget → get-or-create root-Jednostka (parent=None)
zamiast tworzyc model Wydzial + wezel-lustro. Lookup po nazwie BEZ
parent__isnull (Jednostka.nazwa unique globalnie — Fable F8): kolizja z
jednostka podrzedna = blad walidacji, nie ciche zle drzewo. Dodane testy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
T3: create_wydzialy tworzy rooty Jednostka (rodzaj Wydzial, MPTT rebuild po
bulk_create — F7 zaadresowane presetem 0+rebuild), create_jednostki wisza
pod nimi wprost (drop znajdz_lub_utworz_wezel_wydzialu). manifest: bpp.Wydzial
usuniety z CLEANUP_ORDER (rooty ida jako bpp.Jednostka); orchestrator cleanup
toleruje nieznane modele w starym manifescie (F11). Testy przepiete
(legacy_wydzial_id/Wydzial.count -> parent__isnull).

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

T4: zaloz_jednostki_domyslne, mapuj_kierunki_studiow, prace_do_rozliczenia,
rebuild_slugs, look_for_unused_fields — Wydzial.objects -> Jednostka
parent__isnull; site_filtered scope 'wydzial' FK jak 'jednostka' (oba celuja
w Jednostka po B). Usunieto jednorazowy import_jednostki_ipis (hardkod sciezki).
Test zaloz odsprzezony od fixture 'wydzial' (root Jednostka).

Uwaga: konwertuj_wydzialy_na_jednostki + waliduj_konwersje_wydzialow zostaja
do T11 (wola je test_faza_b_i2) — usuwane razem ze struktura_konwersja+model.

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

Faza C usuwa model Wydzial. Onboarding importu PBN (dashboard + kreator
InstitutionImporter) przestaje tworzyć/czytać Wydzial i węzły-lustra —
„wydział domyślny" to teraz zwykła jednostka top-level (parent IS NULL),
a jednostka domyślna wisi wprost pod nią (MPTT parent).

- znajdz_lub_utworz_wydzial_domyslny: tworzy/wyszukuje root-Jednostkę
  (parent__isnull) zamiast Wydzial; skrot [:128].
- znajdz_lub_utworz_obca_jednostke / InstitutionImporter.run: podpinają
  jednostki wprost pod root-wydział (bez znajdz_lub_utworz_wezel_wydzialu).
- views dashboard: listy „wydziałów" = Jednostka.filter(parent__isnull);
  POST czyta root-Jednostkę po pk; default-creation odświeża root przed
  MPTT-move (in-memory lft/rght nieaktualne po utworzeniu jednostki →
  inaczej InvalidMove).
- testy: baker.make(Wydzial) → root-Jednostka; usunięto helper _wezel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Model Wydzial znika w Fazie C, więc znika też jego endpoint REST.
„Wydział" = top-level Jednostka, dostępny przez /api/v1/jednostka/.
Hiperłącza ``wydzial`` na Jednostce i Patencie już wskazują
jednostka-detail (Faza B), więc zostają bez zmian.

- WydzialSerializer + WydzialViewSet: usunięte.
- router: wyrejestrowany r"wydzial".
- api-index: „wydzial" zdjęte z listy authors_and_units.
- test: /api/v1/wydzial/ → NoReverseMatch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WydzialAutocomplete i PublicWydzialAutocomplete odchodzą razem z modelem
Wydzial. Rolę pickera „wydziału" przejął w Fazie B
PublicJednostkaToplevelAutocomplete (jednostki parent IS NULL), więc żadne
formularze/multiseek już z nich nie korzystają — usuwamy martwy kod.

- simple.py: obie klasy + nieużywane już importy (Wydzial,
  NazwaLubSkrotMixin, UczelniaScopedAutocompleteMixin).
- __init__.py / urls.py: eksporty i trasy wydzial-autocomplete oraz
  public-wydzial-autocomplete.
- testy: zdjęto public-wydzial-autocomplete z listy security-endpointów,
  usunięto per-uczelnia test Wydzialu i asercję WydzialAutocomplete;
  dodano test potwierdzający NoReverseMatch obu tras.

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

Opcja A: legacy redirect przestaje szukać modelu Wydzial i węzła-lustra
(legacy_wydzial_id). „Wydział" to jednostka top-level o tym samym slugu
(mapowanie 1:1 zachowane od Fazy B), więc /wydzial/<slug>/ kierujemy
wprost na /jednostka/<slug>/; brak jednostki → 404. Odcina browse.py od
modelu Wydzial przed jego usunięciem (T11).

- browse.py: uproszczony browse_wydzial_redirect + zdjęty import Wydzial.
- testy: happy-path na jednostce top-level + 404 bez jednostki;
  usunięto testy oparte na węźle-lustrze.

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

Model Wydzial znika w T11 — jego etykieta w listach ładowanych na starcie
(``apps.get_model``) rzucałaby wtedy LookupError. Usuwamy referencje z
wyprzedzeniem:

- DJANGO_EASY_AUDIT_REGISTERED_CLASSES: bez "bpp.Wydzial".
- CACHEOPS (production): bez "bpp.wydzial".

Flagi behawioralne (DJANGO_BPP_SKROT_WYDZIALU_W_NAZWIE_JEDNOSTKI,
uzywaj_wydzialow) zostają — dotyczą etykiet/układu, nie modelu.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PRZED dropem modelu Wydzial (0467) i markera legacy_wydzial_id (0468)
utrwalamy nazwę każdego wydziału w poprzednie_nazwy jego węzła-korzenia.
Dzięki temu matchuj_wydzial (import, Faza C) dalej odnajduje root po dawnej
nazwie wydziału — także gdy root został PROMOWANY z realnej jednostki i nosi
jej nazwę, nie nazwę wydziału (węzeł-lustro nadal matchuje po nazwie).

Idempotentne (nie dubluje; pomija gdy nazwa roota == nazwa wydziału),
reversible = no-op. Test: promowany root, idempotencja, węzeł-lustro
(skip), brak legacy_wydzial_id (skip), zachowanie istniejących nazw.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Faza C, sedno: model ``Wydzial`` znika. „Wydział" to jednostka top-level
(``parent IS NULL``); wszyscy konsumenci FK zostali przepięci na ``Jednostka``
w Fazie B, a nazwy dawnych wydziałów utrwalił backfill 0466.

Model / kod:
- Usunięto ``bpp/models/wydzial.py`` (model + sygnały + JednostkaCreateManager)
  i ``bpp/models/struktura_konwersja.py`` (znajdz_lub_utworz_wezel_wydzialu).
- ``struktura.py`` / ``jednostka.py``: zdjęto importy z ``wydzial``.
- ``Jednostka.wydzial_dnia()``: zwraca teraz węzeł-rodzic (Jednostka) wprost,
  a nie odtwarzany ``Wydzial`` po ``legacy_wydzial_id`` (F4).
- ``imports/egeria_2012.py``: lookup wydziału → ``Jednostka`` top-level,
  bez węzła-lustra (F3).
- Usunięto komendy ``konwertuj_wydzialy_na_jednostki`` i
  ``waliduj_konwersje_wydzialow``.
- ``autor.py``: przywrócono import ``Q`` (był zgubiony w bazie brancha
  względem origin/dev — forward-port jednoliniowej poprawki).

Migracja:
- ``0467_faza_c_drop_wydzial``: ``DeleteModel("Wydzial")`` z cross-app
  dependencies (import_dyscyplin 0024, zglos_publikacje 0026) — F1.
- Doprecyzowano 0466: czysta funkcja ``nowa_poprzednie_nazwy`` (testowalna
  po dropie modelu, bez ORM).

Testy:
- Usunięto testy martwej mechaniki konwersji (test_wydzial,
  test_konwertuj_wydzialy, test_waliduj_konwersje_wydzialow, faza_b_i2/i3/i4)
  oraz kroku „odkryj_widoczna"/lazy-lustro w faza_b_iv1.
- Przepięto ~30 plików testów + fixtury (any_wydzial, conftest_models,
  conftest_multisite) na jednostkę top-level; ``baker.make(Wydzial)`` →
  ``baker.make(Jednostka, parent=None)``, węzeł-lustro → root wprost.
- Strukturalny styl browse: root dostaje rodzaj „Wydział"
  (``pokazuj_strukture_podjednostek=True``) w teście.
- ``test_wydzial_usuniety``: model nie w rejestrze, tabela ``bpp_wydzial``
  nie istnieje.

Grep-gate: zero żywych referencji ``Wydzial`` w kodzie (poza komentarzami
historycznymi i migracjami). Wszystkie 313 przepiętych testów zielone;
jeden pre-existing fail (test_konfigurowalne_platnosci_artykul — brak
fixture ``typy_odpowiedzialnosci``, identycznie na origin/dev) poza zakresem.

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

Faza C: po dropie modelu Wydzial (0467) markery TOŻSAMOŚCI KONWERSJI na
``Jednostka`` przestały być do czegokolwiek potrzebne. Były scaffoldingiem
Fazy B (mapowanie starych FK po ``legacy_wydzial_id``, filtr logiki
lustra/widoczności po ``jest_lustrem``). Runtime już ich nie dotyka —
predykaty poddrzewa poszły na strukturę MPTT (``get_descendants``) i zdenorm.
self-FK ``wydzial`` (patrz metody węzła w jednostka.py).

- ``jednostka.py``: usunięto pola ``legacy_wydzial_id`` i ``jest_lustrem``;
  przeredagowano komentarze metod węzła, które opisowo odwoływały się do
  ``*_legacy_wydzial_id`` (kod i tak używał ``wydzial=self`` / MPTT).
- Migracja ``0468_faza_c_drop_legacy_markery``: ``RemoveField`` × 2
  (dep: 0467). ``makemigrations --check`` → „No changes detected".
- ``test_wydzial_usuniety``: parametryzowane testy, że oba markery zniknęły
  z modelu (``FieldDoesNotExist``) i z tabeli ``bpp_jednostka``.
- ``test_jednostka_pola_faza_a``: zdjęto asercję o ``legacy_wydzial_id``.
- ``test_migration_0463``: usunięto 3 testy end-to-end
  ``remap_saved_searches`` — mapowały stare pk wydziału PO usuniętym polu,
  więc nie da się ich odpalić na żywych modelach (w realnym łańcuchu 0463
  działa dalej — używa zamrożonego ``apps``). Logikę remapu wartości
  pokrywają czyste testy słownikowe.

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

``DeleteModel("Wydzial")`` (0467) usuwa tabelę, ale zostawia wiersz
``ContentType(app_label="bpp", model="wydzial")`` i jego 4 auto-uprawnienia
(``add/change/delete/view_wydzial``). Django nie kasuje stale content-type'ów
w migracjach — robi to tylko interaktywna ``remove_stale_contenttypes`` — więc
na realnym upgrade DB, która MIAŁA ten CT, śmieci zostałyby na zawsze
(orphaned Permission mylące w adminie/ACL). Migracja 0469 (RunPython, reverse
= noop) czyści je deterministycznie i idempotentnie; na świeżej bazie CT nie
powstaje, więc to bezpieczny no-op.

Test NIEPUSTY: seeduje CT+Permission ręcznie (na świeżej bazie CT ``wydzial``
nie istnieje, sam łańcuch nic by nie udowodnił) i sprawdza, że funkcja je
kasuje, jest idempotentna i NIE rusza innych content-type'ów.

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

Audyt T14 (#438): konfigurowalna etykieta „wydziału" na poziomie top-level
przeżywa Fazę C bez zmian, bo NIE zależy od usuniętego modelu:
- ``Uczelnia.uzywaj_wydzialow`` (BooleanField) — flaga sterująca ekspozycją
  pojęcia „Wydział" (setup wizard, multiseek ``option_enabled``, nowe_raporty,
  context processor ``UCZELNIA_UZYWA_WYDZIALOW``, admin) — zostaje.
- ``nazwa_wydzialu`` = ``lemat("WYDZIAL")`` — etykieta z lematów gramatycznych,
  niezależna od modelu — zostaje.
- ``domyslna_nazwa_wydzialu`` — czysty string ``f"Wydział {skrot}"``.

Jedyna zmiana kodu: docstring ``JednostkaImportResource`` mówił, że widget
tworzy ``Wydzial`` (model) — po Fazie C tworzy top-level ``Jednostka``
(``parent IS NULL``). Poprawiono.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F12 higiena: fixture zawierała ``"model": "bpp.wydzial"`` (usunięty model) i
była referencjonowana już tylko w ZAKOMENTOWANEJ linii migracji 0035 —
loaddata nigdy jej nie ładuje, a po dropie modelu i tak by się wywaliła.

Uwaga nt. baseline: baseline.sql na dev jest nieświeży (bpp @ 0442, 27 migracji
wstecz — stan współdzielony z origin/dev, nie wprowadzony przez Fazę C).
Zgodnie z CLAUDE.md („nie odświeżaj baseline w równoległych feature-branchach —
rób to raz, przy scalaniu") NIE dołączam 4,6k-liniowego re-dumpa do tego PR-a;
baseline-update trzeba puścić RAZ przy merge. Łańcuch 0443→0469 zwalidowany
end-to-end lokalnie (baseline_update zaaplikował go na czystym kontenerze).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Self-review planu/implementacji Fazy C subagentem Fable — naprawione
zweryfikowane findingi:

- **[wysoki] matchuj_wydzial**: pusty string (osiągalny z pustej kolumny XLS
  przez ``matchuj_jednostke(..., wydzial="")``) trafiał w
  ``poprzednie_nazwy__icontains=""`` = ``LIKE '%%'`` i zwracał LOSOWY root.
  Dodatkowo brak ``order_by`` (niedeterminizm) i ``icontains`` łapał substring.
  Fix: guard pustego → None; exact ``nazwa`` (globalnie unikatowa) ma priorytet;
  fallback po ``poprzednie_nazwy`` dopasowuje CAŁĄ linię (weryfikacja w Pythonie),
  deterministyczny ``order_by("pk")``. + 2 testy regresji.
- **dashboard.py**: usunięto martwe ``"bpp.models.wydzial"`` w ModelList
  „Struktura" (miss w T9 — dangling ref do usuniętego modelu).
- **ruff format**: 4 pliki testów zmienione w tym branchu miały drift
  formatowania (ryzyko czerwonego „Lint changed files" w CI).
- **design-spec**: zdanie „usunięcie uzywaj_wydzialow" było sprzeczne z decyzją
  T14 (flaga ZOSTAJE) i z Kryterium sukcesu #15 — doprecyzowano; odnotowano
  odłożone rename'y (D2/D3).
- **menu.py**: stały komentarz „Wydzial jako MODEL żyje do Fazy C".

Świadomie pominięte: log overflow w 0466 (modyfikacja migracji + przypadek
skrajnie nieprawdopodobny — do checklisty „zweryfikuj backfill na dumpie prod"),
egeria_2012 (martwy importer), semantyka wydzial_dnia (zero konsumentów poza
testami).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Scala 88 commitów z dev (importer: sugerowanie punktacji, import POLON
zapis do bazy, kasowanie pustych jednostek, zwijanie list autorów, PBN
import hardening, kolumna uczelni w adminie jednostek, ...) do gałęzi
Fazy C (#438), która usuwa model Wydzial.

Konflikty (2) rozwiązane pod kątem świata Fazy C ("wydział" = jednostka
top-level z parent IS NULL; model Wydzial i moduł struktura_konwersja
usunięte):

- src/import_polon/tests/test_multi_uczelnia_scoping.py — zachowano nowe
  z dev helpery _jednostka()/_autor_historyczny(), zaadaptowane do
  root-Jednostki zamiast baker.make(Wydzial) + znajdz_lub_utworz_wezel_
  wydzialu.
- src/pbn_import/utils/institution_import.py — złożono semantykę dev
  (podpinaj obcą jednostkę do wydziału TYLKO gdy caller poda jawny
  wydzial; bez niego zostaje węzłem-root, gate sprawdz_obca_jednostka
  nie wymaga linku) z mechaniką Fazy C (parent=wydzial wprost, bez
  węzła-lustra).

Graf migracji: dwa liście 0466 (dev 0466_bppuser_zwijaj... oraz tip
łańcucha Fazy C 0469_faza_c_czysc_contenttype_wydzial) połączone
migracją 0470_merge_20260708_1616.

Baseline odświeżony in-place (make baseline-update) — potwierdza, że
migracje Fazy C usuwające Wydzial aplikują się czysto na schemacie dev
(bpp 0465 -> 0470).

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