Skip to content

Feature/multi hosted config#189

Open
mpasternak wants to merge 275 commits into
devfrom
feature/multi-hosted-config
Open

Feature/multi hosted config#189
mpasternak wants to merge 275 commits into
devfrom
feature/multi-hosted-config

Conversation

@mpasternak

Copy link
Copy Markdown
Member

No description provided.

mpasternak and others added 18 commits April 28, 2026 22:06
Pięć miejsc używało Site.objects.first()/get_current() do budowy URL-i
w eksportach XLSX/BibTeX. W multi-hosted to losowy host — eksport
wygenerowany na uczelnia1 mógł zawierać linki na uczelnia2.

Wspólny helper bpp.util.site_url_for_request(request=None):
- z requestem: f"{scheme}://{host}".
- bez requestu (CLI/Celery): fallback do Uczelnia.objects.get_default()
  .site, dalej Site.objects.first(), ostatecznie "https://localhost".

Naprawione miejsca:
- bpp/admin/xlsx_export/resources.py: Wydawnictwo_ResourceBase trzyma
  request z kwargs (przekazane przez ImportExportModelAdmin).
- rozbieznosci_dyscyplin/admin.py: RozbieznosciViewResource +
  RozbieznosciZrodelViewResource analogicznie.
- deduplikator_autorow/utils/export.py + views.py: export_duplicates_to_xlsx
  bierze request opcjonalnie, propagacja z download_duplicates_xlsx.
- deduplikator_zrodel/utils.py + views.py: analogicznie.
- ewaluacja2021/util.py: output_table_to_xlsx (CLI/Celery context),
  helper fallbackuje do default Uczelnia.site.

Drobne pre-existing fixy w ewaluacja2021/util.py (wymagane przez
ruff hook): rename `a`/`col`/`dirs` na `_`, # noqa: E402 dla
intencjonalnych mid-file imports, # noqa: C901 dla output_table_to_xlsx.

Plus IDE fix w bpp/admin/uczelnia.py:save_model: try/except
ImproperlyConfigured przy obj.pbn_client() (gdy admin ustawi
pbn_integracja=True ale nie wypełni pbn_app_name/token).

Tests: 3683 passed, 0 failed.
Dodaje przycisk „Importuj" w admin/bpp/jednostka/. Plik XLSX (kolumny:
Uczelnia, Wydział, Katedra/Zakład/Klinika) jest parsowany przez nowy
JednostkaImportResource:

- Uczelnie muszą istnieć (lookup po nazwa) — błąd per-wiersz w GUI.
- Brakujące Wydziały tworzone get_or_create przez WydzialGetOrCreateWidget
  z auto-generowanym skrot (max 10) i skrot_nazwy (max 250).
- Puste komórki Wydział/Katedra dostają domyślne nazwy
  („Wydział <skrót uczelni>", „Jednostka Wydziału <X>").
- import_id_fields=("nazwa",) + skip_unchanged → idempotentny re-import.
- before_save_instance auto-generuje Jednostka.skrot i ustawia
  aktualna=True na nowych wierszach.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wcześniej autocomplete twardo filtrował autorów po
aktualna_jednostka.uczelnia == request._uczelnia, przez co nie dało się
wybrać:
- wieloetatowca z aktualną jednostką w innej uczelni federacji
- byłego pracownika (brak aktualnej jednostki, ale Autor_Jednostka u nas)
- autora bez żadnego przypisania (np. świeżo zaimportowanego z PBN)

Zamiast filtrować, autocomplete annotuje każdy wynik etykietą grupy
(Case/When + Exists na Autor_Jednostka) i sortuje po niej. Override
get_results renderuje 3 optgroupy w odpowiedzi Select2 — JS po stronie
klienta nie wymaga zmian (Select2 obsługuje optgroup natywnie):

  ✅ Autorzy z naszej uczelni
  🏛️ Autorzy powiązani historycznie z naszą uczelnią
  🌐 Autorzy zewnętrzni

get_result_label zostaje bez zmian — emoji per-option (📚 PBN,
🏛️ MNISW, [❌ USUNIĘTY]) działa jak wcześniej.

Naprawia 5 testów Playwright padających pre-merge na multi-hosted-config:
test_podpowiedzi_dyscyplin_autor_ma_jedna_uczelnia_podpowiada
(ciagle/zwarte) oraz test_procent_odpowiedzialnosci AutorFormset
jeden_autor (ciagle/zwarte) i dobrze_potem_zle_dwoch_autorow (patent).
Wszystkie 5 używały autorów bez aktualna_jednostka, których stary filtr
odsiewał z autocomplete.
241 commits, ~530 plików. Multi-hosted nadrzędne nad zmianami dev.

Konflikty rozstrzygnięte:

- src/bpp/util.py (UD): dev rozbił monolit na pakiet bpp/util/.
  site_url_for_request() przeniesiona do bpp/util/bpp_specific.py.

- src/bpp_setup_wizard/views.py (UD): dev przepisał setup wizard na
  django-first-run-wizard. Nadpisujemy on_complete() w UczelniaSetupStep
  żeby przekazać request do form.save(request=request).

- src/bpp/admin/helpers/constance_field_mixin.py (UU): dev'owy bool()
  workaround dla constance bezprzedmiotowy (Phase 1 wycofało constance).

- src/bpp/views/browse.py (UU): multi-host filtrowanie news przez
  siteblog.Article.sites M2M (Q(sites=site)|Q(sites__isnull=True)).
  recently_updated/recent_abstracts/total_rekord_count nadal per uczelnia.

- src/miniblog/ (UU): apka to pusta wydmuszka dla historii migracji
  (cutover do siteblog na dev'ie). Nasze migracje 0003-0005 wywalone.

- src/deduplikator_autorow/admin.py (UU): dev przemianował IgnoredAuthor
  → IgnoredScientist + dodał nowy IgnoredAuthor. has_module_permission
  (superuser-only) na obu Admin klasach.

- src/przemapuj_prace_autora/test_integration.py (UU): cache.delete dla
  per-site kluczy (Phase 5) + invalidate_all() (cacheops query cache).

- src/zglos_publikacje/forms.py (UU): łączenie z dev'owym wizard
  rewrite — uczelnia kwarg w nowej sygnaturze __init__.

- src/zglos_publikacje/models.py (UU): clean() używa self._uczelnia
  z fallbackiem (Phase 6.2-6.4).

- src/bpp/tests/test_multisite/test_isolation.py: 3 testy przepisane
  z miniblog.Article.uczelnie → siteblog.Article.sites.

Migracja merge:
- 0418_merge_20260521_1015.py: łączy 0416_rename_dynamic_columns_to_admin
  (dev) i 0417_ensure_uczelnia_site_not_null (multi-host).

Pre-commit fix-ups (ręcznie, manual):
- .github/workflows/docs.yml (z dev): persist-credentials: false
  (zizmor artipacked).
- .pre-commit-config.yaml: mkdocs.yml do check-yaml exclude
  (pymdownx.slugs.slugify python tag); 3 templaty z dev
  (rozbieznosci_if, rozbieznosci_pk, snapshotodpiec_list) do djlint
  exclude (orphan-tag pattern w if/else).
- src/django_bpp/asgi.py: # noqa: E402 na late imports po
  django_asgi_app = get_asgi_application().
- src/bpp/models/konferencja.py: # noqa: DJ001 na 4 CharField'ach z
  null=True (pre-existing tech-debt z dev'a, wymaga migracji w follow-up).
- src/bpp/migrations/0416_rename_dynamic_columns_to_admin.py: usunięty
  nieużywany `from django.apps import apps as django_apps`.
- src/fixtures/conftest_browser.py: usunięty nieużywany
  `from django.core.exceptions import ImproperlyConfigured`.
- src/zglos_publikacje/tests/test_forms.py: usunięty nieużywany
  `from django.core.files.uploadedfile import SimpleUploadedFile`.
- Whitespace fixes: HISTORY.md + 7 docs/*.md (pre-commit auto-fix).
- Ruff import order: drobne reordering w 7 plikach (third-party przed
  first-party).

Smoke: Django check OK, importy bpp.util.site_url_for_request,
bpp.views.browse, zglos_publikacje.forms, bpp_setup_wizard.steps,
siteblog.Article wszystkie działają.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ia.site NOT NULL)

Po mergu origin/dev w branch feature/multi-hosted-config zostało 5 failures
i 3 errors w testach. Tylko wzorce wymagające adaptacji do nowego stanu
po mergu — same testy są poprawne, ale używały API z przed mergea.

- src/bpp/tests/test_views/test_browse/test_browse.py: test_artykuly i
  test_artykul_ze_skrotem używały `a.uczelnie.set([uczelnia])` (M2M na
  starym miniblog.Article). Po mergu Article to siteblog.Article z M2M
  `sites` (do django.contrib.sites.Site). Zamiana na
  `a.sites.set([uczelnia.site])` — fixture uczelnia ma `.site`
  (OneToOne do Site, mandatory po 0417).

- src/bpp/tests/test_views/test_views_browse.py: 3 testy używały
  `Uczelnia.objects.create(nazwa="X", skrot="X")` — to lata przed 0417
  migracją wymuszającą Uczelnia.site NOT NULL. Zamiana na helper
  `any_uczelnia()` (już użyty wcześniej w tym pliku), który auto-tworzy
  Site i przypina go.

- src/deduplikator_autorow/tests/test_xlsx_orcid_and_pbn_url.py: fixture
  `candidate_with_orcid_and_pbn` używała `Uczelnia.objects.get_or_create`
  bez `site=` w defaults. Dodane `site` (get_or_create na testserver).

Wszystkie 468 testów w merge-targeted suite passuje (test_multisite,
test_middleware, test_views, test_admin/test_site_filtered,
bpp_setup_wizard, zglos_publikacje, deduplikator_autorow, miniblog,
przemapuj_prace_autora).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ia.site NOT NULL)

Demo data generator tworzył Uczelnię bez site, co po migracji 0417
(Uczelnia.site mandatory) wywalało NotNullViolation we wszystkich
testach test_demo_data (28 testów: 7 failures + 21 errors w jednej
fixturze jednostki_fixture która tworzy uczelnię przez ensure_uczelnia).

W kontekście CLI/demo nie ma requestu więc get_current_site nie
zadziała — bierzemy pierwszy Site (zwykle django.contrib.sites
fixture 'example.com'), albo tworzymy 'demo.local' jeśli baza pusta.

Tests: 74 passed (test_demo_data full suite + 2 flaky które przy
okazji się przeszły).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…palety (#238)

* feat: dodano trzy nowe zestawy kolorystyczne frontend dla uczelni

Dodano trzy nowe frontend themes dla BPP, nawiązujące kolorystyką do stron uczelni:

1. Uniwersytet VIZJA (vizja.pl):
   - Szary (#3a3a3a) z żółtymi akcentami (#fbb800)
   - Tło: #f8f8f8
   - Buttons: żółte z czarnym text
   - Links: żółte akcenty (#fbb800)

2. MWSLiT Wrocław (mwsl.eu):
   - Granat (#003688) z pomarańczowymi akcentami (#ff6b35)
   - Tło: #f5f8ff
   - Buttons: pomarańczowe z białym text
   - Links: granatowe z pomarańczem na hover

3. UFAM (ufam.edu.pl):
   - Niebieski (#0056b8, #003688)
   - Tło: #f5f8ff
   - Buttons: niebieskie z białym text
   - Links: niebieskie akcenty

Nowe pliki:
- src/bpp/static/scss/_settings_vizja.scss - ustawienia Foundation dla Vizja
- src/bpp/static/scss/_settings_mwsl.scss - ustawienia Foundation dla MWSL
- src/bpp/static/scss/_settings_ufam.scss - ustawienia Foundation dla UFAM
- src/bpp/static/scss/app-vizja.scss - theme Vizja
- src/bpp/static/scss/app-mwsl.scss - theme MWSL
- src/bpp/static/scss/app-ufam.scss - theme UFAM

Każdy theme importuje odpowiedni _settings_*.scss z kolorami,
a resztę ustawień pobiera z domyślnego settings.scss.

Aby użyć nowego theme, w settings/base.py zmień DJANGO_BPP_THEME_NAME
na odpowiedni plik CSS (scss/app-vizja, scss/app-mwsl, scss/app-ufam).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: run_site buduje assets + nowe themes w COMPRESS_OFFLINE_CONTEXT

1. run_site automatycznie buduje frontend assets (make assets)
   - Nowa metoda _build_assets() wywołuje make assets na początku
   - Opcja --skip-assets dla devs którzy mają aktualny CSS
   - Graceful degradation: błędy assets są tylko warningi

2. Dodano nowe frontend themes do COMPRESS_OFFLINE_CONTEXT:
   - scss/app-vizja.css (Uniwersytet VIZJA - szary z żółtymi akcentami)
   - scss/app-mwsl.css (MWSLiT Wrocław - granat z pomarańczem)
   - scss/app-ufam.css (UFAM - niebieski)

Nowe themes są dostępne dla django-compress do offline
kompresji i cachowania.

Aby użyć nowego theme, zmień DJANGO_BPP_THEME_NAME w settings
na odpowiedni plik CSS (scss/app-vizja, scss/app-mwsl, scss/app-ufam).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: dodano nowe uniwersyteckie themes do Gruntfile.js

Dodano trzy nowe frontend themes do konfiguracji Grunt:
- vizja: scss/app-vizja.scss → scss/app-vizja.css
- mwsl: scss/app-mwsl.scss → scss/app-mwsl.css
- ufam: scss/app-ufam.scss → scss/app-ufam.css

Te taski są teraz budowane równolegle z resztą themes przez
grunt concurrent:themes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(university-themes): poprawki kolorów i ikon kalendarza

- App-vizja: przyciemnienie złotego koloru z #fbb800 na #d4a000
  dla lepszej czytelności na szarym tle #f8f8f8
- Ikona kalendarza: dodanie override dla .uczelnia__tile aby
  używała koloru z klasy .uczelnia__tile-icon zamiast
  $primary-color (kafe na głównej stronie mają teraz własne kolory)
- Ptaszki dropdown: zmiana hardcoded koloru rgba(44, 62, 80, 0.6)
  na rgba($anchor-color, 0.6) dla spójności ze theme'ami

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(university-themes): MWSL i UFAM jako samodzielne theme'y Foundation

Rozszerzono _settings_mwsl.scss i _settings_ufam.scss z minimalnej formy
(@import 'settings') do pełnego, samodzielnego setu zmiennych Foundation.
Każdy theme zawiera teraz wszystkie 56 sekcji konfiguracji Foundation
z dostosowanymi kolorami uczelni — dzięki temu zmiany w bazowym
_settings.scss nie wpływają na wygląd theme'ów uczelnianych.

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

* refactor(university-themes): palety zgodne z brandem + rename UFAM→UAFM

- MWSL: primary #ff6b35→#e35b00, secondary #003688→#002b53 (1:1 z mwsl.eu)
- VIZJA: primary #d4a000→#EFA402, secondary #3a3a3a→#01608C (federacjavizja.pl)
- UAFM (poprzednio UFAM): primary #0056b8→#b41906, secondary #003688→#045595,
  alert #cc4b37→#df1a17 (uafm.edu.pl); zmiana nazwy plików, taska Grunta i
  THEME_NAME w base.py
- Usunięto globalną regułę .fi-calendar { color: $primary-color; } z app-vizja,
  app-uafm, app-mwsl, app-green, app-orange — kolor kalendarza wyciekał na
  cały serwis; teraz kolor pochodzi wyłącznie z modyfikatora
  uczelnia__tile-icon--* na kafelku homepage.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Migracja docsow z Sphinxa na MkDocs Material (87a76da) pozostawila
kilka rozsianych odwolan do Sphinxa — sprzatamy je.

- Makefile: live-docs -> mkdocs serve (zamiast sphinx-autobuild)
- docs/SECURITY_PRACTICES.md: wyjatek dla live-docs opisany przez
  docs/requirements.txt (mkdocs-material) zamiast sphinx-autobuild
- SECURITY.md + docs/SECURITY.md: HISTORY.rst -> HISTORY.md (plik
  HISTORY.md istnieje od dawna, RST byl martwym odsylaczem)
- bin/scan-deps.sh: przyklad dev-only paczki w komentarzu sphinx -> mkdocs
- AUTHORS.rst -> AUTHORS.md (jedyny pozostaly .rst w repo, niczego
  nie referowal, tresc juz w docs/authors.md ale plik w roocie
  zostawiamy dla widocznosci GitHuba)

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

1. check-flag hint: na pull_request evencie github.ref_name to
   "<PR>/merge" (np. 189/merge) ktorego workflow_dispatch nie akceptuje
   ("HTTP 422: No ref found for: 189/merge"). Dodane HEAD_REF z
   github.head_ref (nazwa branchu zrodlowego PR-a) + fallback do
   ref_name dla nie-PR eventow.

   Przed: gh workflow run build-docker-images.yml --ref 189/merge  (fail)
   Po:    gh workflow run build-docker-images.yml --ref feature/multi-hosted-config

2. docker/bpp_base/Dockerfile: usuniety martwy COPY z
   src/notifications/static/notifications/js/. src/notifications/ apka
   zostala usunieta na dev w commicie 048c2cf (notifications JS jest
   teraz dostarczane przez pakiet django-channels-broadcast, ktory
   wyladuje pliki z venv w runtime collectstatic). Powodowalo to fail
   docker builda na "failed to compute cache key: ... /src/notifications/
   static/notifications/js: not found".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Podciągnięcie feature/multi-hosted-config do aktualnego origin/dev
(26 commitów od merge-base). Merge bez konfliktów.
Podciągnięcie do origin/dev@a04285362 (17 nowych commitów).

# Conflicts:
#	docker/bpp_base/Dockerfile
Merge dev wniósł 0417_remove_uczelnia_pokazuj_raport_autorow_and_more,
co utworzyło drugą liść obok istniejącej 0418_merge_20260521_1015.
Pusta migracja scalająca unifikuje graf — bez zmian schematu.
Fikstury `site1`/`uczelnia1`/`site2`/`uczelnia2` (i pochodne) były
rejestrowane przez `pytest_plugins` WYŁĄCZNIE w rootdir-owym
`conftest.py`. `conftest_multisite` importuje modele Django na
top-levelu, a rootdir-owe `pytest_plugins` ładuje się w preloadzie
`pytest-testcontainers-django` ZANIM `django.setup()` zapełni rejestr
aplikacji → import wybucha `AppRegistryNotReady`, fikstury cicho się nie
rejestrują, a każdy test ich żądający pada `ERROR at setup:
fixture 'site1' not found`. To wywalało 7 z 8 shardów w CI (testy
multisite/middleware/site-filtered).

Pozostałe model-bearing moduły fikstur (conftest_models, _publications,
_system, _disciplines, pbn_api, wydawnictwa) są już w `src/conftest.py`
(ładowanym PO `django.setup()`) — `conftest_multisite` był jedynym, który
złamał ten wzorzec. Dopisuję go tam, obok sąsiadów.

Domykam też lukę w guardzie `test_conftest_preload_safety.py`: dotąd
sprawdzał tylko eager-chain `from fixtures import *` (przez `import
conftest`), który NIE ładuje `pytest_plugins`. Nowy test importuje każdy
moduł z rootdir-owej `pytest_plugins` przed `django.setup()` i wymaga, by
każdy preload-niebezpieczny (model-bearing) był też w `src/conftest.py`.
Bez fixu nowy test pada wskazując dokładnie `fixtures.conftest_multisite`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mpasternak and others added 11 commits June 1, 2026 13:17
…#189)

Motywy app-vizja, app-mwsl i app-uafm były już zbudowane przez Grunt
i zarejestrowane w COMPRESS_OFFLINE_CONTEXT, a context processor czytał
uczelnia.theme_name — brakowało jedynie wpisów w THEME_CHOICES, więc nie
dało się ich wybrać w menu w adminie uczelni.

- dopisz trzy nowe motywy do THEME_CHOICES (z etykietami nazywającymi
  uczelnię + paletę)
- migracja 0421 (AlterField choices, no-op na poziomie schematu DB)
- per-motyw tinty --admin-hover-bg w admin/base_site.html dla nowych
  motywów (wcześniej spadały na neutralną szarość)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…a z niej (#189)

Lista motywów była zduplikowana ręcznie w COMPRESS_OFFLINE_CONTEXT
(settings) i THEME_CHOICES (model), a każda zmiana choices na modelu
wymuszała migrację. Konsoliduję do jednej stałej settings.BPP_THEMES.

- settings.BPP_THEMES — jedyne źródło prawdy (wartość = nazwa pliku SCSS,
  etykieta kolorystyczna bez nazw własnych uczelni)
- COMPRESS_OFFLINE_CONTEXT wyliczany z BPP_THEMES (koniec drugiej listy)
- Uczelnia.theme_name traci choices= (zwykły CharField); usunięto martwą
  THEME_CHOICES → migracja 0422 (ostatnia migracja motywów, no-op na DB)
- UczelniaAdminForm: theme_name jako ChoiceField z choices z settings,
  walidacja wyboru w formularzu zamiast w modelu
- dodanie motywu = wpis w BPP_THEMES (+ ręcznie target w Gruntfile),
  ZERO migracji

Spec: docs/superpowers/specs/2026-06-02-motywy-z-settings-design.md
Testy: model bez choices, form.choices == settings.BPP_THEMES,
COMPRESS_OFFLINE_CONTEXT pochodny BPP_THEMES.

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

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

# Conflicts:
#	docs/administrator/import-pracownikow.md
#	docs/uzytkownik/jednostki.md
#	docs/uzytkownik/uczelnia.md
#	src/bpp/demo_data/generators/uczelnia.py
#	src/ewaluacja2021/util.py
…onfig

# Conflicts:
#	.github/workflows/build-docker-images.yml
#	src/pbn_api/models/sentdata.py
#	src/pbn_integrator/management/commands/pbn_integrator.py
Po wmergowaniu origin/dev (themes #283 + pbn_api interaktywny #164)
powstaly rozwidlenia grafu migracji:
- pbn_api: 0069_sentdata_api_url (dev) vs 0069_add_uczelnia_fk ->
  0070_link_pbn_to_uczelnia (feature) -> merge 0071
- bpp: 0419_merge (dev) vs 0422_alter_uczelnia_theme_name (feature) ->
  merge 0423

Obie galezie dotykaja roznych pol, wiec merge migracje sa puste
(tylko scalenie lisci). makemigrations --check: brak driftu.

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

W instalacji z wieloma uczelniami PBN-owe zadania Celery (pobieranie
publikacji/osób/źródeł, import) brały konfigurację PBN z
Uczelnia.objects.get_default() (pierwsza-z-brzegu), a nie z uczelni
wybranej w requeście. Gdy PBN był skonfigurowany w innej niż pierwsza
uczelni, zadania dostawały pusty app_token → 403 "token aplikacji null"
oraz "Brak konfiguracji klienta PBN" przy imporcie.

Zmiany:

- UczelniaManager.get_for_pbn_background(uczelnia_id): ścisły resolwer
  dla torów w tle — wymaga jawnego id, ZERO fallbacku do get_default().
- Entrypointy (widoki pbn_downloader_app, pbn_import) przekazują id
  uczelni z requestu (get_for_request) do zadań.
- Zadania pbn_downloader_app (publikacje/osoby/źródła) i pbn_import
  używają get_for_pbn_background; publikacje przekazują --uczelnia-id
  do management-commandów.
- get_pbn_client (pbn_downloader_app) buduje klienta przez kanoniczną
  Uczelnia.pbn_client() — koniec z ręcznym sklejaniem transportu.
- PBNBaseCommand: flaga --uczelnia-id realnie działa (wcześniej martwa).
  get_default() dozwolone TYLKO gdy w systemie jest dokładnie jedna
  uczelnia; przy wielu — CommandError zamiast cichego wyboru pierwszej.
- Dedup: pbn_api.tasks.download_institution_publications to teraz
  re-eksport kanonicznej implementacji z pbn_downloader_app.tasks
  (były dwie rozjeżdżające się kopie).
- Komunikat błędu "Default institution does not have PBN UID" zastąpiony
  wskazaniem konkretnej uczelni.

Testy: nowe (resolwer, entrypointy, kontrakt komendy) pisane TDD —
najpierw RED. Istniejące zaktualizowane do wymaganego uczelnia_id.

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

pbn_wysylka_oswiadczen.get_pbn_client(user) budował klienta z
Uczelnia.objects.get_default() — w multi-hosted brał konfigurację PBN
złej uczelni. Teraz:

- get_pbn_client(user, uczelnia_id) buduje klienta przez kanoniczną
  Uczelnia.pbn_client() i rozwiązuje uczelnię ściśle po id
  (get_for_pbn_background) — bez fallbacku do get_default().
- wysylka_oswiadczen_task przyjmuje uczelnia_id i podaje go do
  get_pbn_client.
- StartTaskView przekazuje id uczelni z requestu do zadania.

Testy: nowe (entrypoint, kontrakt get_pbn_client) pisane TDD (RED→GREEN),
istniejące zaktualizowane do wymaganego uczelnia_id.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mpasternak and others added 30 commits June 12, 2026 12:06
…nie dyscyplin (#348)

* docs(pbn-import): spec — dopasowanie autorów po imieniu/nazwisku + odporne przypisywanie dyscyplin

Projekt rozwiązania trzech wad integracji oświadczeń PBN:
- twardy crash sesji importu na konflikcie dyscyplin → raportowana niespójność,
- przedwczesny raport author_not_found → auto-dopasowanie współautora o tym
  samym imieniu/nazwisku (inne ID) + raport informacyjny,
- auto-zakładanie Autor_Dyscyplina z PBN z procentami (100 / 50:50) i śladem
  w logu/uwagach.

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

* docs(pbn-import): self-review specu — domknięcie 4 ustaleń

- F1: gałąź konfliktu vs bezwarunkowy rec.save() (statements.py:299) — nie
  zostawiać rec.dyscyplina ustawionej na D, by zapis nie utrwalił niespójnej
  pary (autor, dyscyplina) (save() nie woła clean()).
- F2: cykl typów raportów — author_not_found (126) usunięty, author_auto_fixed
  (236) → author_matched_by_name, no_override_without_disciplines/manual_fix
  zostają.
- F3: update_or_create osłonięty savepointem (transaction.atomic).
- F4: konsolidacja powtórzonych elem.get_bpp_discipline().

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

* docs(pbn-import): plan implementacji — dopasowanie autorów + odporne dyscypliny

4 taski TDD: helper przypisz_dyscypline_pbn (Task 1), wpięcie + koniec crasha
(Task 2), raporty dopasowania autora (Task 3), weryfikacja + pre-commit (Task 4).

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

* feat(pbn-import): helper przypisz_dyscypline_pbn — slot-aware, auto-procenty

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

* refactor(pbn-import): code-review helpera dyscyplin — get_or_create, update_fields, typy

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

* fix(pbn-import): konflikt dyscyplin nie wywala importu — raport zamiast raise

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

* test(pbn-import): lock-in — PBN zglasza subdyscypline autora => rec=sub, brak konfliktu

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

* fix(pbn-import): dopasowanie autora po nazwisku zamiast falszywego author_not_found

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

* fix(pbn-import): author_matched_by_name action_taken bez klamliwej podmiany autora

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

* test(pbn-import): lock-in braku swapu autora + ruff format

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

* style(pbn-import): ruff format test_dyscypliny

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

* feat(pbn-import): rejestracja nowych typow niespojnosci w konsumencie + auto-assign test

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

* docs(pbn-import): sprostuj przesłankę save()+clean(), dopisz decyzje (no-swap, Task5, follow-up)

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

* fix(pbn-import): tier-4 znormalizowane dopasowanie bez podmiany autora (spójnie)

Usuwa rec.autor=aut + mid-flow rec.save() (walidujący => ten sam utajony crash)
oraz raport author_replaced; author_matched_by_name leci wspólnie dla tier 2/3/4.
Dodaje odznakę admina dla historycznych author_replaced.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…onfig

# Conflicts:
#	.test_durations
#	src/bpp/admin/jednostka.py
#	src/bpp/admin/wydzial.py
#	src/bpp/views/browse.py
#	src/zglos_publikacje/forms.py
#	src/zglos_publikacje/views.py
…_trigger_v3)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…yjątków)

CodeQL przypisał te alerty do PR #189, bo diff jest duży (507 plików) —
realnie kod istniał już na dev. Hardening przy okazji dotknięcia plików:

- pbn_api: redirect z OAuth state.originalPage walidowany przez
  url_has_allowed_host_and_scheme (jedyny realny open-redirect — state
  round-tripuje przez przeglądarkę, jest attacker-controllable)
- browse: redirect paginacji ograniczony do bieżącego hosta
- base_site.html: encodeURIComponent na wartości selecta w location.href
- importer_autorow_pbn / pbn_wysylka_oswiadczen: pełne szczegóły wyjątku
  do Rollbara, do klienta tylko ogólny komunikat (str(e) nie wycieka)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
url_has_allowed_host_and_scheme jest barrier-guardem tylko dla zwalidowanej
wartości; poprzedni fallback `redirect_url = request.path` ponownie wprowadzał
remote-source, więc CodeQL nadal flagował redirect(). Fallback to teraz stała
"/" — sink widzi wyłącznie zwalidowane-lub-literalne dane. Zachowanie bez
zmian (skonstruowany URL jest same-origin, więc guard i tak zawsze przechodzi).

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

# Conflicts:
#	src/bpp/models/cache/rekord.py
#	src/bpp/tests/test_cache/test_cache_pk_filter.py
…ularza importu

Kolumny Pobieranie/Przetwarzanie oraz colspan=2 kroków niepodzielnych
w modalu Konfiguracja importu — text-align: left zamiast center.

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

Po merge'u dev do feature/multi-hosted-config CI pokazywał 4 grupy błędów —
wszystkie naprawione:

- migracje: dwa liście 0431 (0431_merge_20260612_1504 + dev-owy
  0431_search_index_gin) blokowały migrację testowej bazy → KAŻDY test z DB
  wybuchał. Dodana migracja-merge 0432 (pusta, tylko zależności). To też
  naprawiło dspace_api test_adapter_patent (był ofiarą, nie przyczyną).

- admin eksport jednostki: JednostkaAdmin ma ImportMixin + EksportDanychMixin;
  import_export wybiera import_export_change_list_template po MRO, a ImportMixin
  jest pierwszy → renderował szablon TYLKO z importem (znikał przycisk
  "Eksport"). Wymuszony połączony szablon change_list_import_export.html
  (rozszerza grappelli_mptt jako bazowy → draggable MPTT zachowany).
  Naprawia test_xlsx_export_data[jednostka] + _order_and_freeze_panes.

- playwright test_admin_domyslnie_afiliuje_istniejacy_rekord: baker.make(
  Jednostka) tworzył DRUGĄ uczelnię przez FK → get_single_uczelnia_or_none()
  zwracał None (>1) → default afiliacji degradował do True → asercja padała
  dla expected=False. Jednostka przypięta do uczelni z testu (1 uczelnia).
  Bug testu odsłonięty przez (poprawną) semantykę multi-hosted.

- lint: ruff format na pbn_integrator/tests/test_openaccess_i_jezyk_orig.py
  (jedyny zmieniony plik poza extend-exclude).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pip-audit blokował CI na 7 CVE w 2 runtime-pakietach:
- django 5.2.14 -> 5.2.15: 5x PYSEC-2026 (197-201)
- pypdf 6.10.2 -> 6.13.2: CVE-2026-48155, CVE-2026-48156 (fix >=6.12.0;
  pypdf jest tranzytywne przez xhtml2pdf)

Podniesiony też floor Django w pyproject (>=5.2.15) żeby wymusić patcha
security. pytest 8.4.2 CVE-2025-71176 NIE ruszany — to dev-dep (poza
skanem runtime CI) i jest przypięty <9 przez pytest-testcontainers-django.

Walidacja lokalna: manage.py check czysty, nowe_raporty (ścieżka
xhtml2pdf->pypdf) 61 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Użytkownik zalogowany przez Keycloak (OIDC) widział w menu link
„zmiana hasła" → /password_change/, który kończył się błędem 500.
Przyczyna: SmartPasswordChangeView robił EXTERNAL_PROVIDER_URLS[backend],
a backend OIDC był w EXTERNAL_AUTH_BACKENDS, ale nie w mapie URL-i → KeyError.

- Link pokazujemy per-użytkownik: tylko zalogowanym hasłem BPP (Django
  ModelBackend), nie przez OIDC/Microsoft/ORCID. Nowy context processor
  external_auth_status → zmienna logged_in_via_external_auth; top_bar.html
  używa jej zamiast globalnego microsoft_login_enabled.
- SmartPasswordChangeView._external_provider_info(): odporny lookup zamiast
  [backend]; OIDC dostaje przyjazną stronę z linkiem do panelu konta
  Keycloaka (OIDC_ACCOUNT_CONSOLE_URL = {issuer}/account), szablon pokazuje
  przycisk tylko gdy URL jest ustawiony.

Testy: 11 nowych (context processor + widok, w tym reprodukcja 500 → 200).

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

# Conflicts:
#	.github/workflows/docs.yml
#	uv.lock
…ut claimów

Resolucja adresu e-mail w BppOIDCBackend zamiast sztywnego `mail`:
kolejność email → e-mail → e_mail → mail (pierwszy niepusty wygrywa),
a w ostateczności `preferred_username`, jeśli zawiera domenę (UPN).
Gdy nie da się ustalić adresu → SuspiciousOperation (mozilla_django_oidc
degraduje to do login failure, nie 500). Adres pod `mail` bywa prywatny;
instytucjonalny realm wystawia zwykle pod email/e-mail — stąd `mail` jest
ostatnim fallbackiem, nie pierwszym.

Diagnostyka claimów (`_log_claims_debug`) dotąd była niewidoczna, bo brakowało
loggera `oidc_integration` w LOGGING — DEBUG przepadał na rootcie (WARNING).
Dodaję logger sterowany env-varem DJANGO_BPP_OIDC_DEBUG_CLAIMS=1 (opt-in,
domyślnie WARNING). propagate=True świadomie (root nie ma handlera, więc bez
dublowania, a pytestowy caplog widzi zrzut).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ning DisallowedHost (#379)

Renderuje osobną stronę 500 dla każdego Site ($STATIC_ROOT/500/<domena>.html)
z brandingiem przypisanej uczelni, plus generyczny $STATIC_ROOT/500.html jako
fallback dla nginx.

- Wymusza uczelnię przez request._uczelnia — get_for_request zwraca je ZANIM
  sięgnie po get_host() w _site_dla_requestu. To uodparnia command na
  DisallowedHost, gdy 'testserver' (default RequestFactory) nie jest w
  ALLOWED_HOSTS, i jednocześnie daje właściwy branding per-domena.
- Czyści globalny cache b"bpp_uczelnia" przed każdym renderem — bez tego
  pierwsza uczelnia „zatruwałaby" kolejne domeny (cache nie rozróżnia hostów).
- Pisze do $STATIC_ROOT (autorytatywne miejsce serwowane przez nginx),
  zachowując zapis do gitignored src/bpp/static/500.html (collectstatic na
  buildzie, wsteczny kontrakt).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
No significant changes.
…gnore

Bumpy łatające podatności zgłoszone przez pip-audit, z egzekwowaniem
minimów przez constraint-dependencies (tranzytywne, nie direct deps):
- bleach  6.2.0 -> 6.4.0  (GHSA-8rfp-98v4-mmr6, GHSA-gj48-438w-jh9v)
- daphne  4.2.1 -> 4.2.2  (PYSEC-2026-213, PYSEC-2026-214)
- pypdf   6.12.0 -> 6.13.2 (CVE-2026-48735/49460/49461/54530/54531)

bin/scan-deps.sh: dodaje pip-audit jako 5/5 etap, w parytecie z gate-em
release-u w dependency-audit.yml (lokalny guard testował dotąd tylko
OSV/Grype/Trivy, które w CI są report-only). Leci przez uvx, bez
instalacji; --no-pipaudit żeby pominąć.

Usuwa --ignore-vuln CVE-2026-42304 z workflow i skryptu: twisted jest
teraz na 26.4.0 stable (fix był w 26.4.0rc2), pip-audit już go nie
zgłasza, a martwy ignore tylko maskowałby ewentualny regres twisted.

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

- Zaktualizowano trzy zależności tranzytywne do wersji łatających
  podatności zgłoszone w audycie ``pip-audit``: ``bleach`` do 6.4.0
  (GHSA-8rfp-98v4-mmr6, GHSA-gj48-438w-jh9v), ``daphne`` do 4.2.2
  (PYSEC-2026-213, PYSEC-2026-214) oraz ``pypdf`` do 6.13.2
  (CVE-2026-48735, CVE-2026-49460, CVE-2026-49461, CVE-2026-54530,
  CVE-2026-54531). Minimalne wersje są od teraz egzekwowane przez
  ``constraint-dependencies`` w ``pyproject.toml``, dzięki czemu
  przyszłe przeliczenie ``uv.lock`` nie cofnie się poniżej
  załatanego wydania.
…aprawy poprawności (#360)

* docs(pbn_import): przegląd kodu importu PBN + plan czyszczenia

Read-only review src/pbn_import/ i powiązanego kodu (pbn_integrator,
pbn_api, import_common). Plan implementacji items 1/4/5/6; item 2
pominięty (decyzja usera), item 3 zablokowany (legacy komenda
pbn_integrator NIE jest duplikatem — ma unikalne sync/clear/ORCID/
DOI/ISBN).

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

* refactor(pbn_import): usuń martwą warstwę WebSocket (item 1)

Koperta import_update nie pasowała do handlerów konsumenta ani do
switch(data.type) po stronie klienta — każda wiadomość WS była
porzucana. Cały realtime realizuje polling HTMX. Usuwa consumers,
routing, helpery send_websocket_update, wpięcie ASGI i martwy skrypt
klienta.

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

* perf(pbn_import): ogranicz rozmiar querysetów logów w widokach (item 4)

Endpointy HTMX re-fetchowane co 5 s ładowały WSZYSTKIE wiersze ImportLog
sesji. Wprowadza MAX_LOGS_DISPLAY=200 i slice w 4 miejscach; pełny log
pozostaje pobieralny przez ImportLogDownloadView.

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

* fix(pbn_import): anulowanie HTMX zwraca 200 zamiast 500 (item 5)

components/progress.html odwoływał się do nieistniejącej trasy
pbn_import:stats → NoReverseMatch przy anulowaniu importu. CancelImportView
renderuje teraz progress_compact.html (używany wszędzie indziej);
martwy progress.html usunięty.

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

* chore(pbn_import): usuń no-op SavePresetView + trasę (item 5)

Widok był @csrf_exempt, robił json.loads(request.body) bez zabezpieczenia
(500 na złym body), niczego nie zapisywał i nie był referencjonowany.

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

* fix(pbn_import): zawęź zapisy progress_data, unikaj wyścigu nadpisania (item 5)

Pełne session.save() po mutacji jednego klucza JSONField mogło nadpisać
równoległy zapis throttlowanego TqdmSessionProgress (stale in-memory).
refresh_from_db(fields=[progress_data]) przed mutacją + update_fields przy
zapisie. Bez migracji.

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

* refactor(pbn_import): przenieś sondę autoryzacji PBN z __init__ do run()

ImportManager.__init__ wołał _check_pbn_authorization(), które robi
wywołanie sieciowe client.get_languages() — samo skonstruowanie obiektu
uderzało w API PBN (efekt uboczny w konstruktorze, zły dla testowalności
i zaskakujący). Sonda trafia teraz na początek run(), odpalana dokładnie
raz przez sentinel (pbn_authorized=False & pbn_error_message is None =
"jeszcze nie sondowano"). Publiczne API bez zmian.

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

* docs(pbn_import): oznacz CODEBASE_MAP jako historyczny (item 6)

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

* docs(pbn_import): newsfragments dla czyszczenia importu PBN

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… autora

Warstwa A — mapowanie claimów logowania OIDC:
- Domyślnie `mail` (instytucjonalny) ma pierwszeństwo nad `email`
  (w realmie UAFM bywa prywatny); `email` tylko jako fallback.
- Źródła e-maila i username konfigurowalne env-em wg istniejącej
  precedencji DJANGO_BPP_OIDC_<SKROT>_{EMAIL,USERNAME}_CLAIMS -> bare
  (CSV). Defaulty w conf.py, wystawione jako OIDC_EMAIL_CLAIMS /
  OIDC_USERNAME_CLAIMS, czytane przez BppOIDCBackend.

Warstwa B — naprawa BppUser.sprobuj_dopasowac_autora:
- Pomija autorów już powiązanych z innym kontem (user__isnull=True) ->
  koniec IntegrityError przy OneToOne BppUser.autor (zrodlo tracebacku).
- Zawęża kandydatów do uczelni z accessible_uczelnie
  (aktualna_jednostka.uczelnia); puste = bez ograniczenia (kompat.).
- Matching po person_id/system_kadrowy_id swiadomie NIE uzywany.
- Backend OIDC wola dopasowanie z create_user (po przypisaniu uczelni)
  i update_user -> powiazanie powstaje przy logowaniu, self-healing.

TDD: testy RED->GREEN dla obu warstw (test_conf, test_backends,
nowy test_dopasowanie_autora). 79 passed, manage.py check czysty.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Import PBN wywalał się na kroku institution_setup z błędem triggera
bpp_jednostka_wydzial_sprawdz_uczelnia_id ("Uczelnia jednostki i wydzialu
musi byc identyczna"). Przyczyna: get_or_create(nazwa="Obca jednostka")
szukał po globalnie-unikalnej nazwie (Jednostka.nazwa/skrot są unique=True,
a w multi-hosted uczelnie współdzielą bazę), więc trafiał w obcą jednostkę
INNEJ uczelni i linkował ją do wydziału bieżącej → trigger.

- znajdz_lub_utworz_obca_jednostke(uczelnia, wydzial=None): idempotentny
  helper, FK Uczelnia.obca_jednostka jako źródło prawdy, nazwa/skrót
  sufiksowane skrótem uczelni ("Obca jednostka <SKRÓT>").
- institution_setup woła helper zamiast kolidującego get_or_create.
- sprawdz_obca_jednostka(uczelnia): gate-check (FK ustawiony, ta sama
  uczelnia, skupia_pracownikow=False, podpięta do wydziału). Wpięty w
  StartImportView.post (blokada startu) i ImportDashboardView (baner).
- create_obca_jednostka: polecenie provisionujące/naprawiające obcą
  jednostkę dla wszystkich uczelni (idempotent, --dry-run, opcjonalny arg).
- znajdz_lub_utworz_wydzial_domyslny i _jednostke_domyslna: create-path
  sufiksowany skrótem uczelni (ta sama klasa globalnej kolizji); skróty
  przycinane do limitów kolumn (Wydzial.skrot varchar(10), Jednostka.skrot
  varchar(128)). FIND po prefiksie wciąż matchuje legacy rekordy.

Testy: pełna suita pakietu src/pbn_import/ — 369 passed (Postgres +
trigger przez testcontainers). Repro produkcyjnego błędu w
test_institution_importer_does_not_collide_with_other_uczelnia_obca.

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

CI shard 11 wywalił test_profil_metryki_zaweza_do_uczelni_ogladajacego z
NumericValueOutOfRange (numeric(5,2), < 10^3). Przyczyna: MetrykaAutora.save()
przelicza procent_wykorzystania_slotow = slot_nazbierany/slot_maksymalny*100
(oraz srednia_za_slot_* = punkty/slot) z pól, które baker wypełniał losowymi
DecimalField(10,4) — niezależne wartości dawały iloraz przepełniający pole
procentu (lub średniej). Flaky zależnie od seeda/sharda (lokalnie 4/30).

Pinujemy pola WEJŚCIOWE (slot_*/punkty_*) do zdrowych wartości — test sprawdza
tylko zawężenie metryk po uczelni, nie liczby. 60/60 powtórzeń zielone.

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

Stopka pustego logu sesji była zahardkodowana na „import przebiegł
pomyślnie", co dla sesji `running` przeczyło nagłówkowi „Status: W trakcie".
Brak wpisów (log odtwarzany z błędów/ostrzeżeń) ≠ sukces. Dobieram stopkę
wg statusu: active → „wciąż trwa", completed → „pomyślnie",
failed/cancelled → neutralnie ze statusem.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Na stronie rekordu flaga „nasz autor" zależała wyłącznie od
Jednostka.skupia_pracownikow — bez żadnego odniesienia do oglądającej
uczelni. W konfiguracji multi-hosted ta sama praca pokazywała tego samego
autora jako „naszego" na każdym hoście (objaw zgłoszony przez użytkownika).

autorzy_dla_opisu_skrocony() dostaje opcjonalny parametr `uczelnia`: gdy
podany (ma pk), autor jest „nasz" tylko jeśli jego jednostka należy do TEJ
uczelni (jednostka.uczelnia_id == uczelnia.pk) ORAZ skupia_pracownikow.
Bez parametru (uczelnia=None / niezdefiniowana) zachowanie jak dawniej —
wstecz-kompatybilne dla pozostałych callerów.

Oglądająca uczelnia trafia do metody z context processora
(Uczelnia.objects.get_for_request, per-host) przez nowy simple_tag
`autorzy_skrocony` — model-method nie da się zawołać z argumentem przez
{% with %}.

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.

2 participants