Feature/multi hosted config#189
Open
mpasternak wants to merge 275 commits into
Open
Conversation
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>
…#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>
…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>
…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
…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>
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.