Skip to content

Feature/multi hosted config#189

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

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

Conversation

@mpasternak
Copy link
Copy Markdown
Member

No description provided.

mpasternak and others added 30 commits April 28, 2026 18:00
Add infrastructure for multi-hosted BPP configuration where one server
can serve multiple universities on different domains.

- Uczelnia: add OneToOne to django.contrib.sites.Site + theme_name field
- BppUser: add M2M accessible_sites for per-user university access control
- SiteResolutionMiddleware: resolves hostname → Site → Uczelnia on every request
- Context processors: site-aware cache keys, per-uczelnia theme selection
- Admin: site/theme fields in UczelniaAdmin, accessible_sites in BppUserAdmin
- Data migration: links existing Uczelnia to Site(pk=1), grants staff access

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move all per-uczelnia settings from django-constance to fields on the
Uczelnia model, enabling per-university configuration in multi-hosted mode.

- Add 8 new fields to Uczelnia: google_analytics_property_id,
  google_verification_code, pokazuj_oswiadczenie_ken,
  skrot_wydzialu_w_nazwie_jednostki, wydruk_margines_* (4 fields)
- Context processor reads from Uczelnia instead of constance.config
- Admin mixin reads scoring settings from Uczelnia instead of constance
- Empty CONSTANCE_CONFIG (all settings migrated to Uczelnia)
- Data migration copies existing Constance values to Uczelnia
- UczelniaAdmin: add new fieldsets for Google, structure, margins

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add SiteFilteredAdminMixin to filter admin querysets by current uczelnia.
Regular admins see only their university's data, superusers see all.

- New SiteFilteredAdminMixin in src/bpp/admin/helpers/site_filtered.py
- Applied to JednostkaAdmin (uczelnia), WydzialAdmin (uczelnia),
  AutorAdmin (aktualna_jednostka__uczelnia), UczelniaAdmin (pk filter)
- FK dropdowns for wydzial/jednostka filtered per-uczelnia
- Middleware blocks admin access for staff with accessible_sites
  configured but missing current site (backward compat: no sites = allow)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add uczelnia ForeignKey to PBN models that store per-institution data,
enabling multi-tenant PBN operations.

- Add uczelnia FK to: OsobaZInstytucji, PublikacjaInstytucji,
  PublikacjaInstytucji_V2, OswiadczenieInstytucji, SentData
- Data migration links all existing records to first Uczelnia
- Apply SiteFilteredAdminMixin to all 5 PBN admin classes
- Fix PublikacjaInstytucji_V2.link_do_pi() to use self.uczelnia

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add site_cache_key utility and update admin filter count cache to include
site_id, preventing cross-tenant cache pollution.

- New src/bpp/cache_utils.py with site_cache_key() utility
- Admin filter_count_view cache key includes site.pk
- Fix test cache invalidation for per-site keys

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Uczelnia.objects.get_default() with proper multi-site patterns:
- Views: use get_for_request(request) or self.request
- Celery tasks: add uczelnia_id parameter with fallback to get_default()
- Management commands: add --uczelnia-id argument
- Refactor pbn_integrator handle() to reduce complexity (C901 33→<10)
- Fix UP031 percent format → f-strings in ranking_autorow
- Refactor ranking get_queryset() to reduce complexity (C901 13→<10)

28 files updated across tasks, commands, views, admin helpers, and menu.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Uczelnia.objects.get_default() in model/utility code that has
no request context, by adding uczelnia parameter with fallback.

bpp models:
- jednostka.py: use self.uczelnia in get_default_ordering
- abstract/pbn.py: add uczelnia param to link_do_pbn, _format_link_pi
- abstract/disciplines.py: add uczelnia param to przelicz_punkty_dyscyplin
- multiseek_registry/fields: add uczelnia param to option_enabled
- admin/helpers/pbn_api/cli.py: add uczelnia param, fix B904 raise from

PBN import/integrator utilities:
- pbn_import/utils: add uczelnia param to all importer classes
- pbn_integrator/utils: add uczelnia param to scientists and institutions
- pbn_import/templatetags: use request from template context

Other:
- zglos_publikacje: forms accept uczelnia kwarg, model uses _uczelnia attr
- importer_publikacji: add uczelnia param to helpers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SITE_ID configurable via DJANGO_BPP_SITE_ID env var (default=1)
- Add DJANGO_BPP_ENABLE_SITEMAPS env var to disable static-sitemaps
  in multi-hosted mode (static-sitemaps generates for one domain only)
- django_countdown already multi-site friendly (uses get_current_site)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.
mpasternak and others added 7 commits May 21, 2026 12:56
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant