Feature/multi hosted config#189
Open
mpasternak wants to merge 258 commits into
Open
Conversation
New test fixtures (conftest_multisite.py): - site1/site2, uczelnia1/uczelnia2, staff users per-site - wydzial/jednostka/autor per-uczelnia - make_request_for_site() helper for simulating domain requests Middleware tests (test_site_resolution.py, 9 tests): - Hostname→Site→Uczelnia resolution - Fallback to SITE_ID for unknown hosts - Staff blocked from wrong site's admin (403) - Superuser allowed everywhere - Anonymous allowed on public pages - Backward compat: staff with no sites configured Admin filtering tests (test_site_filtered.py, 5 tests): - Jednostka/Wydzial filtered per-uczelnia for staff - UczelniaAdmin shows only own uczelnia for non-superuser - Superuser sees all data Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Article model: add M2M uczelnie field (default: all universities) - ArticleAdmin: filter_horizontal for uczelnie, auto-assign all on create - Browse view: filter articles, recently_updated, abstracts, total count by authors from current uczelnia's units - Root view: use get_for_request instead of .first() - Data migration: assign existing articles to all uczelnie - Fix get_absolute_url to use self.uczelnie.first() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PBN_Export_Queue: add uczelnia FK + SiteFilteredAdminMixin in admin Data migration links existing records to first Uczelnia - Deduplikator autorów/publikacji: has_module_permission = superuser only (deduplikacja jest operacją globalną, nie per-uczelnia) - Rozbieżności IF/PK/dyscyplin: TODO markers for per-uczelnia filtering Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rozbieżności dyscyplin: - RozbieznosciViewAdmin/RozbieznosciZrodelViewAdmin: filter by autor__aktualna_jednostka__uczelnia for non-superusers Rozbieżności IF/PK: - RozbieznosciIfLogAdmin/RozbieznosciPkLogAdmin: filter by rekord__autorzy_set__jednostka__uczelnia with distinct() - IgnorujRozbieznoscIf/PkAdmin: superuser-only (GenericFK) Autocomplete: - AutorAutocompleteBase: filter by aktualna_jednostka__uczelnia when request._uczelnia is set (admin context) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 integration tests verifying multi-site data isolation: - Article visible only on assigned uczelnia - Article on both uczelnie when both assigned - Staff cannot see other uczelnia's jednostki in admin - Staff gets 403 on wrong uczelnia's admin - Browse record count scoped per uczelnia Fix: browse view queryset used invalid `original__autorzy_set` path (original is a cached_property, not a DB field). Changed to `autorzy__jednostka__in` which is the correct ORM path for Rekord materialized view. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per Ultraplan review: change BppUser M2M from Site to Uczelnia for clearer semantics — user has access to universities, not domains. - BppUser.accessible_sites (M2M→Site) → accessible_uczelnie (M2M→Uczelnia) - Migration: add new field, copy data (Site→Uczelnia via OneToOne), remove old - Middleware: check access by uczelnia instead of site - Admin: update fieldset reference - Fixtures + tests: updated to use accessible_uczelnie Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Łączy leafy grafu migracji powstałe po rebase feature/multi-hosted-config: - bpp: 0413_bppuser_autor_onetoone (dev) + 0415_rename_accessible_sites_to_uczelnie (feature) - miniblog: 0003_alter_article_article_body (dev) + 0004_assign_articles_to_all_uczelnie (feature)
Dwie pozostałości po Phase 4 — Uczelnia.objects.first() i .all().first() w widoku rankingu autorów. W multi-site zwracały losową uczelnię zamiast tej z bieżącego requestu, przez co podgląd "uzywaj_wydzialow" i "pokazuj_liczbe_cytowan_w_rankingu" nie respektował ustawień uczelni hostującej daną stronę.
Code: - autocomplete/authors.py: getattr(getattr(self, "request", None), ...) zamiast self.request — view'y są instancjonowane bezpośrednio w testach bez routingu HTTP. - browse.py:JednostkiView.get_paginate_by: użyj None-safe Uczelnia.objects.get_for_request, zamiast hasattr-ochrony zwracającej static fallback. Testy zaktualizowane do nowego API: - test_handlers.test_handler403_permission_denied: @pytest.mark.django_db (SiteResolutionMiddleware sięga do DB jak handler403/404/500). - pbn_export_queue test_admin: patch admin.ModelAdmin.response_change zamiast __bases__[1] (po dodaniu SiteFilteredAdminMixin baza ModelAdmin przesunęła się na index 2). - test_browse: a.uczelnie.set([uczelnia]) — Article jest M2M-przypisany do uczelni od Phase: Miniblog M2M. - test_oai, test_ewaluacja_no_queries: bump query budgetu o +3 (Site.get + site.uczelnia + cache lookup z SiteResolutionMiddleware). ImportError w django_pg_baseline/tests/test_rebuild.py jest pre-existing na dev (eb1a124), nie regresja tej gałęzi.
Bugfixy (request był dostępny, ale używano get_default()/first()): - bpp/context_processors/orcid.py — orcid_login_enabled flag. - orcid_integration/backends.py — auth backend's authenticate(request) ignorował request. Realny problem bezpieczeństwa: w multi-site uczelnia.orcid_tylko_dla_pracownikow rozstrzygane było po losowej uczelni, nie tej z hosta. - bpp/admin/jednostka.py — get_changeform_initial_data(self, request). - ranking_autorow: refactor RankingAutorowForm — sygnatura __init__(self, lata, *args, request=None, **kwargs), klasowa lambda w polu rozbij_na_jednostki przeniesiona do __init__. View przekazuje request przez get_form_kwargs. Site OneToOne obowiązkowe: - Model: usunięto null=True, blank=True z Uczelnia.site. - Migracja 0417_ensure_uczelnia_site_not_null: data migration fail-loudly dla niejednoznacznych przypadków, AlterField NOT NULL. - Setup wizard (UczelniaSetupForm.save) — auto-link do get_current_site(request). - Admin (UczelniaAdmin.save_model) — auto-link przy tworzeniu nowej Uczelni. - Test util any_uczelnia + fixture uczelnia w conftest_models — get_or_create Site(domain="testserver") jeśli nie podano. - test_views_browse: zamiana Uczelnia.objects.create(...) na any_uczelnia(...). Pełny suite: 3682 passed, 0 failed.
Zmiana semantyki przypisania artykułu do uczelni: - Niepusty M2M ``Article.uczelnie`` = artykuł widoczny tylko na wybranych uczelniach (bez zmian). - Pusty M2M = artykuł widoczny na WSZYSTKICH uczelniach (lazy resolution zamiast eager-assignment z ArticleAdmin.save_model). Zalety vs. poprzednia implementacja (admin save_model assign all): - Nowo utworzona Uczelnia automatycznie widzi artykuły z pustym M2M (przed zmianą trzeba było ręcznie edytować artykuły dodane przed nową uczelnią). - Edycja artykułu z czyszczeniem M2M = "pokazuj wszędzie" (przed: artykuł znikał wszędzie, bo save_model sprawdzał `not change`). Implementacja: - ``Article.objects.visible_on(uczelnia)`` manager method z ``Q(uczelnie=uczelnia) | Q(uczelnie__isnull=True)``. - ``bpp.views.browse.get_uczelnia_context_data`` używa ``visible_on`` zarówno dla listy ostatnich artykułów, jak i dla pojedynczego artykułu (``get_object_or_404``). - Usunięto ``ArticleAdmin.save_model`` (eager-assignment do wszystkich). Tests: - ``test_article_with_empty_m2m_visible_on_all_uczelnie`` — nowy test weryfikujący lazy resolution. - Istniejące testy isolation/explicit-assignment zostają zielone. Brak migracji — zgodnie z decyzją, brak istniejących instalacji do zaktualizowania.
Plik .docker-build juz nie istnieje (skasowany w poprzednim commicie), wiec elif sprawdzajacy `[ -f ".docker-build" ]` byl dormantnym kodem. Zastapione: push na non-master (czyli feature/fix/hotfix przez restrykcje triggera) → buduj zawsze. Realizuje user-intent "auto-build na feature branches" — bez tego push na feature spadalby na else (skip), a `.docker-build` flag nie istnieje. Komentarze i opisy aktualizowane — bez wzmianek o pliku flagi. Pozostale `docker-build` w workflow to label PR-a (mechanizm zostaje). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wycofuje gating labelem .docker-build/docker-build na rzecz prostszej zasady: master/main push i workflow_dispatch buduja zawsze (release flow + manual override), pozostale (PR sync, feature/fix/hotfix push bez PR) — tylko gdy actor=mpasternak. Inni contributorzy nie pala Docker Cloud minutek; jesli trzeba zbudowac obraz dla cudzego PR-a: `gh workflow run build-docker-images.yml --ref <branch>`. Dev branch dopisany jawnie do komentarza w pushu jako "intentionally excluded" — push do dev nie odpala buildu (intermediate state nie zasluguje na obraz, release leci przez master). Dodany main do triggerow obok master (gdyby kiedys repo zmienilo default branch — single source of truth). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Multi-hosted deployment (jedna instalacja BPP, wiele uczelni/domen)
nie mieścił się w pojedynczym DJANGO_BPP_HOSTNAME. Wprowadzona zmienna
DJANGO_BPP_HOSTNAMES (CSV) rozwiązuje to bez breaking change:
- Jeśli ustawisz DJANGO_BPP_HOSTNAMES, jest source-of-truth dla
ALLOWED_HOSTS i CSRF_TRUSTED_ORIGINS. Pierwszy element listy staje się
canonical hostname (settings.DJANGO_BPP_HOSTNAME) — wykorzystywany przez
Rollbar do identyfikacji deployment'u w raportach błędów.
- Jeśli HOSTNAMES jest puste, używamy single DJANGO_BPP_HOSTNAME jak
wcześniej. Existujące deployments nie wymagają zmian konfiguracji.
Zmienione pliki:
- settings/base.py: parsing CSV w DJANGO_BPP_HOSTNAMES, derive HOSTNAME
z pierwszego elementu listy.
- settings/local.py, production.py: ALLOWED_HOSTS rozszerzony o pełną
listę zamiast pojedynczego env('DJANGO_BPP_HOSTNAME').
- .env.docker, .env.example: udokumentowano obie zmienne i ich relację.
Tests: 3683 passed, 0 failed (full suite).
Walidacja konfiguracji (base.py): - DJANGO_BPP_HOSTNAME i DJANGO_BPP_HOSTNAMES ustawione naraz → ImproperlyConfigured (intencja niejasna). - DJANGO_BPP_HOSTNAME zawiera przecinek → ImproperlyConfigured (na multi-host używaj HOSTNAMES). - DJANGO_BPP_HOSTNAMES bez przecinka lub tylko jeden host po sparsowaniu → ImproperlyConfigured (na single-host używaj HOSTNAME). Custom Rollbar middleware (bpp/middleware.py): - Dotychczasowy DJANGO_BPP_HOSTNAME (canonical/installation identity) zostaje. - Dodatkowo per-request: request_host (vhost gdzie padło zgłoszenie) + uczelnia_skrot/uczelnia_pk z request._uczelnia (ustawiane przez SiteResolutionMiddleware). - DisallowedHost przy request.get_host() łapany ostrożnie i raportowany jako sentinel "<DisallowedHost>" — Rollbar handler nie powinien failować przy raportowaniu błędu, który sam jest DisallowedHost. Tests: 3683 passed, 0 failed.
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.
…długich tagów (#331) Import publikacji z PBN wywalał się na dwóch klasach danych: 1. Słowa kluczowe (taggit Tag.name/slug = varchar(100)) — PBN bywa sklejony w jeden bardzo długi ciąg bez separatorów, przekraczał limit i wywracał import na DataError. Teraz: za długie tagi są pomijane, logowane i zapisywane do `adnotacje` pod znacznikiem `tagsTooLong` (do ręcznej korekty), a rekord się importuje. 2. Tytuły w językach innych niż eng/pol — importer brał tylko eng/pol do `tytul` i asertował pusty słownik `titles`, więc deu/rus/lit wywalały AssertionError, a brak eng/pol — KeyError. Teraz tytuły w pozostałych językach trafiają do nowych wierszy `Wydawnictwo_{Ciagle,Zwarte}_Tytul` (analogicznie do streszczeń), z dowiązaniem do słownika `Jezyk` jeśli się da (`pbn_uid_id` → fallback `skrot`), a surowy kod PBN zachowany w `kod_jezyka_pbn` nawet gdy języka nie ma w słowniku. `tytul` nadal dostaje eng→pol (bez regresji wyświetlania/wyszukiwania). - nowy abstrakt BazaModeluTytulow + 2 modele konkretne + migracja 0430 - inline w adminie obu wydawnictw (edytowalne ręcznie) - przetworz_tytuly(pbn_json, ret, klasa_tytulu) woła się po ret.save() - testy: tagi (pomijanie/adnotacje) + tytuły (eng/pol, obce języki, brak eng/pol, dowiązanie Jezyk, nieznany język, unikalność) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…334) * docs(oidc): spec naprawy logowania OIDC + menu instytucjonalne Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(oidc): napraw logowanie OIDC (claimy mail->email, easyaudit) + menu instytucjonalne per-uczelnia Naprawa logowania OIDC (Keycloak, realm KA): * Claimy: realm wystawia adres pod kluczem \`mail\`, a mozilla-django-oidc oczekuje \`email\` (verify_claims/filter_users_by_claims/create_user) - normalizujemy w jednym chokepoincie get_userinfo() (mail->email). Username z preferred_username, imie/nazwisko z given/family_name. * KeyError: 'username' z easyaudit: jego handler user_login_failed robi twardo credentials[USERNAME_FIELD], a callback OIDC wola authenticate() bez username -> 500 przy PROPAGATE_EXCEPTIONS=True. Guard (apps.ready, gdy easyaudit zainstalowany) podmienia receiver na odporny wariant, delegujacy do oryginalnej logiki easyaudit. * Banner [OIDC] z claimami: stderr -> logger.debug. Menu "logowanie instytucjonalne" jak Microsoft, ale PER-UCZELNIA: * oidc_integration/access.py: oidc_enabled_for_request() - wspolne zrodlo prawdy dla menu i routingu. OIDC to jeden realm na proces, wiec gateujemy po skrocie uczelni (request._uczelnia.skrot == OIDC_LOGIN_SKROT); bez skrotu = instalacja jedno-uczelniana -> globalnie. Precedencja: OIDC (per-uczelnia) > Microsoft (globalny) > formularz BPP. * InstitutionalLoginView jako login_form; local_login_form = formularz BPP. * top_bar.html: instytucjonalne (OIDC/Microsoft) + logowanie BPP w menu. * registration/login.html: usuniety przycisk OIDC spod formularza. Testy: backendy (normalizacja, debug log), easyaudit guard, per-uczelnia gating, dyspozytor logowania. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…microsoft_auth W trybie mieszanym (microsoft_auth + oidc_integration) /logout/ obsługuje MicrosoftLogoutView dla wszystkich backendów. Nie był on świadomy OIDC, więc sesja zalogowana przez Keycloaka szła na logout Microsoftu i zostawała żywa sesja SSO w Keycloaku (cicha re-autoryzacja przy kolejnym logowaniu). MicrosoftLogoutView rozpoznaje teraz sesję OIDC po BACKEND_SESSION_KEY i kieruje ją na RP-Initiated Logout Keycloaka (URL budowany przed logout(), bo czyta oidc_id_token z sesji). Sesje Microsoft/lokalne/ORCID bez zmian. Dodatkowo: jawny TODO „GATE PRZED PRODUKCJĄ" w BppOIDCBackend — provisioning fazy 2a (konto każdemu userowi realmu) nie może wejść na produkcję bez gate'u. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ring sanitization' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Naprawia 4 klasy błędów wywalających import PBN (z logu produkcyjnego): - AssertionError na rozdziałach: sub-słownik chapters niesie redundantne `year`/`originalLanguage`, których nic nie konsumowało — popujemy je (tripwire assert_dictionary_empty zostaje ścisły dla realnie nowych kluczy). - Jezyk.DoesNotExist (książki): surowy Jezyk.objects.get bez fallbacku → przejście na odporny pobierz_jezyk. - KeyError 'mainLanguage' (artykuły): pop bez default → pop(..., None). - KeyError 'publisher' (książki/rozdziały): brak wydawcy w PBN → wydawca=None (FK jest nullable). Domyślny język gdy PBN nie poda języka albo poda kod spoza słownika: - get_jezyk_polski() + pobierz_jezyk(..., domyslny_jezyk) — deterministycznie polski zamiast "pierwszego rekordu w tabeli". - Parametr domyslny_jezyk przewleczony przez importuj_publikacje_po_pbn_uid_id do artykułów/książek/rozdziałów. - Wybór języka na formularzu nowego importu: pole "Domyślny język publikacji" → config["default_jezyk_id"] → resolve_default_jezyk(session) → PublicationImporter.default_jezyk (analogicznie do default_jednostka). TDD: 6 testów resolvera języka, 3 resolve_default_jezyk, 1 wiring + aktualizacja asercji przekazywania kwargs. Zielono: pbn_integrator (121), pbn_import (325). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…picker aktualnie zatrudnionych Powielanie nagłówka "✅ Autorzy z naszej uczelni" brało się z optgroupów emitowanych per strona AJAX Select2 (przy przewijaniu nagłówek się dublował). - PublicAutorAutocomplete (raporty, ranking, multiseek, raport slotów): płaska lista bez optgroup, WYŁĄCZNIE autorzy z aktualnej uczelni (bieżąca lub historyczna afiliacja) — także w single-install, gdzie mixin scope'ujący jest no-op. Staffowy AutorAutocomplete (admin) zostaje z grupami i autorami zewnętrznymi (potrzebne przy dodawaniu współautorów). - AutorZUczelniAutocopmlete → AutorAktualnieZatrudnionyNaUczelni: zawęża WYŁĄCZNIE po aktualna_jednostka__uczelnia (aktualnie zatrudnieni), płasko, bez żadnych innych warunków. Nazwa klasy oddaje semantykę; URL (autor-z-uczelni-autocomplete) bez zmian, więc admin prac doktorskich/ habilitacyjnych działa bez modyfikacji. TDD: 3 nowe testy (publiczny płaski, single-install tylko-uczelnia, aktualnie zatrudnieni). Zielono: 143 testy autocomplete, manage.py check, ruff. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e publikacji Usuwa Wydawnictwo_Ciagle i/lub Wydawnictwo_Zwarte z pod-rekordami, zostawiając słowniki (Zrodlo/Wydawca/Jezyk). Flagi --ciagle / --zwarte (domyślnie oba), --dry-run (podgląd planu), oraz bezpiecznik nieinteraktywny --yes-i-am-sure --confirm-db <nazwa_bazy>. Sprząta też generyczne referencje (GFK) i loguje kaskady Django. Zastępuje dawną komendę wyczysc_baze. TDD: 3 testy (kasowanie publikacji + dzieci, zachowanie słowników, dry-run). Zielono, ruff czysty. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gejt "Lint changed files" padał na 2 błędach ruff, przez co krok nie dochodził do ruff-format (sekwencyjny run:), maskując latentne błędy formatowania. - jednostka.py: usunięty nieużywany import DjangoQLSearchMixin (admin używa BppDjangoQLSearchMixin) — F401. - models/abstract/__init__.py: posortowany blok importów (.titles na właściwe miejsce alfabetyczne) — I001. - 8 plików testowych pbn_import: ruff-format (zawijanie do 88 znaków), czysto kosmetyczne, bez zmian logiki. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… CI)
test_consumers.py (SYNC testy) woła korutyny konsumera przez async_to_sync.
asgiref odmawia, gdy wątek wołającego ma już DZIAŁAJĄCY event loop. Na sharded
CI wcześniejszy test async w tym samym shardzie zostawia działający loop, więc
shardy 2 i 3 deterministycznie wywalały test_connect_* / test_*has_permission
("You cannot use AsyncToSync in the same thread as an async event loop").
Lokalnie, w izolacji, nie reprodukowało się.
Fix: module-local shim async_to_sync uruchamia wywołanie w świeżym wątku (bez
działającego loopa), drop-in dla istniejących miejsc wywołań. Zweryfikowane
symulacją (wywołanie z wnętrza działającego loopa: raw rzuca, shim działa) +
8/8 testów konsumera lokalnie.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dwa błędy z importu PBN (sesja #10) zatrzymywały całą sesję importu: 1. KeyError 'mode' w importuj_openaccess — blok openAccess z PBN bywa niekompletny (potrafi nie zawierać license/mode/releaseDateMode/ textVersion). Pola docelowe to nullable FK, więc brak klucza zostawia pole puste zamiast wywalać import. Wartość OBECNA, lecz nieznana w słowniku BPP, dalej zgłasza błąd (realna luka konfiguracji). 2. AssertionError {'originalLanguage': 'pol'} w importuj_artykul — adapter eksportu zapisuje originalLanguage z jezyk_orig, ale importer nigdy tego klucza nie odczytywał → leftover wywalał assert_dictionary_empty. Nowy helper ustaw_jezyk_oryginalny mapuje originalLanguage → jezyk_orig (round-trip eksportu), wpięty w import artykułów i książek. Nieznany kod języka → None (jezyk_orig jest nullable, dotyczy tłumaczeń). Refactor: blok dat OA wydzielony do _importuj_openaccess_daty (złożoność cyklomatyczna importuj_openaccess wracała ponad limit ruff C901). Testy: test_openaccess_i_jezyk_orig.py — 10 testów (komplet pól, brak mode, pusty blok, brak openAccess, nieznana licencja → ValueError, daty ISO i rok+miesiąc, mapowanie/nieznany kod/brak originalLanguage). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…acja site (#336) * fix(multihosted): create_from_string czyta widoczność z uczelni z requestu AutorManager.create_from_string przyjmuje teraz jawną uczelnię i czyta z niej nowy_autor_z_formularza_pokazuj; AutorAutocomplete.create_object przekazuje uczelnię z requestu (get_for_request). Bez uczelni fallback na get_single_uczelnia_or_none zamiast first() (pierwsza-z-brzegu). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(multihosted): admin AutorForm afiliuje-default bez first() Inline-owy formularz autora (generuj_formularz_dla_autorow) nie ma requestu, więc domyślne 'afiliuje' bierze z get_single_uczelnia_or_none (single → jej ustawienie; 0/>1 → neutralny default True) zamiast Uczelnia.objects.first(). Sprząta też nieaktualne wpisy bpp/admin/core.py i bpp/models/autor.py z APPROVED_FIRST w guard-teście (nie ma tam już first()). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(multihosted): multiseek IndexCopernicus respektuje uczelnię z requestu Ujednolicono sygnaturę option_enabled(request=None) w hierarchii BppMultiseekVisibilityMixin; enabled() przekazuje request niżej. IndexCopernicusQueryObject ustala uczelnię przez get_for_request(request) (sam degraduje do jedyna-albo-None), więc pole 'Index Copernicus' honoruje pokazuj_index_copernicus oglądanej uczelni zamiast zawsze być widoczne. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(multihosted): single GUI path kolejki PBN zapisuje uczelnię z requestu sprobuj_utworzyc_zlecenie_eksportu_do_PBN_gui przekazuje teraz uczelnia=get_for_request(request) do sprobuj_utowrzyc_wpis (jak batch path), żeby wysyłka w tle użyła właściwej konfiguracji PBN zamiast zgadywać. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(multihosted): link 'Otwórz w PBN' używa uczelni z requestu sprobuj_wyslac_do_pbn przekazuje uczelnia=uczelnia do obu wywołań link_do_pbn() (common.py:146,168). Wcześniej bez argumentu przy >1 uczelni link degradował do None (get_single_uczelnia_or_none → None). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(multihosted): PublikacjaInstytucji_V2.link_do_pi nie wybucha przy >1 uczelni Fallback self.uczelnia or Uczelnia.objects.get() → get_single_uczelnia_or_none. Martwy guard 'if uczelnia is not None' ożywiony: przy 0/>1 uczelni link się nie renderuje zamiast rzucać MultipleObjectsReturned / DoesNotExist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(multihosted): admin UczelniaForm wymaga site z przyjaznym komunikatem site jest NOT NULL od migracji 0417; forma dostarcza teraz komunikat dziedzinowy (domena/Site wiąże uczelnię z adresem) zamiast generycznego 'To pole jest wymagane.'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(pbn-import): spec rozdzielenia pobierania od przetwarzania
Projekt rozbicia kroków importu PBN na niezależne fazy download/process
(6 krokow rozdzielanych + nowa integracja konferencji), z dwukolumnowym
formularzem, zgodnoscia wsteczna configu i miekkim ostrzezeniem przy
pustym lustrze.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(pbn-import): plan implementacyjny rozdzielenia pobierania od przetwarzania
15 taskow TDD: fazy w ImportStepBase, integruj_konferencje, split 6 importerow,
model faz w step_definitions + resolver zgodnosci wstecznej, ImportManager per
faza, CLI granular+legacy, presety, dwukolumnowy formularz.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(pbn-import): auto-review — popraw blockery w spec i planie
- integruj_konferencje: value_or_none zamiast value() (sentinel '[brak k]'),
savepoint + except IntegrityError, filter().first() (nie get()).
- Task 7: test ustawia default_jednostka, process() guard na nazwa.
- Task 9: nota bloku migracji kontraktu (9-13 przejsciowo czerwone),
przepisane 4 zlamane testy step_definitions (16 faz), policzona licznosc.
- Task 12: reverse('pbn_import:presets'); presety wylacznie granularne.
- Spec: results -> jedyny konsument _display_results.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pbn-import): ImportStepBase — fazy download/process i __call__(method)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(pbn-import): walidacja method w __call__ + nota o atomowości faz
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pbn-integrator): integruj_konferencje — lustro Conference → BPP Konferencja
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(pbn-integrator): integruj_konferencje — wyklucz DELETED z total, komentarze
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pbn-import): ConferenceImporter — split download/process
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pbn-import): SourceImporter — split download/process
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pbn-import): PublisherImporter — split download/process
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pbn-import): AuthorImporter — split download/process
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(pbn-import): ConferenceImporter.process zwraca conferences_imported (spójność) + napraw wrapper test po splicie
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pbn-import): PublicationImporter — split download/process
Zastąp monolityczne run() dwoma metodami:
- download(): pobieranie v1+v2 z PBN do lustra
- process(): import z lustra do BPP (opcjonalnie z kasowaniem)
Zaktualizuj test_publication_import.py — stare testy run() zastąpiono
odpowiednikami dla download()/process().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(pbn-import): usuń redundantny inner import Publication w process()
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pbn-import): StatementImporter — split download/process
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pbn-import): model faz w step_definitions + resolver zgodnosci wstecznej
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pbn-import): ImportManager wykonuje fazy per-metoda, results po result_key
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pbn-import): CLI — granularne flagi faz + legacy aliasy
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style(pbn-import): myślniki w nazwach flag --disable-<encja>-<faza>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(pbn-import): presety na kluczach granularnych faz
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pbn-import): dwukolumnowy formularz importu (pobieranie/przetwarzanie)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style(pbn-import): tabela kroków importu — wyrównanie kolumn faz
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(pbn-integrator): integruj_konferencje — przycinanie zbyt długich pól (odporność importu)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…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>
…nto feature/multi-hosted-config
…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>
…nto feature/multi-hosted-config
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.