From 5bd00e97b0ca5ea9151379a471f390ee9313bf82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Fri, 24 Jul 2026 23:58:17 +0200 Subject: [PATCH 1/2] =?UTF-8?q?fix(bibtex):=20klucz=20doktoratu=20i=20habi?= =?UTF-8?q?litacji=20zn=C3=B3w=20zawiera=20nazwisko=20autora?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Praca_Doktorska.autorzy_dla_opisu() zwraca FakeSet — listę udającą queryset, bo doktorat ma dokładnie jednego autora w polu `autor`, a nie relację M2M. FakeSet nie miał metody first(), więc generate_bibtex_key leciało na AttributeError. Wyjątek jest tam połykany (zaloguj_polkniety_wyjatek), więc eksport nie padał — klucz po prostu gubił nazwisko i zostawało samo `_id`, a do Rollbara szedł item (#447, 6 wystąpień). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01NcAqeqyqBzNEkkVnhpHDaH --- src/bpp/models/praca_doktorska.py | 7 ++++ .../+bibtex-klucz-doktoratu.bugfix.rst | 4 ++ .../test_bibtex_klucz_doktoratu.py | 37 +++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 src/bpp/newsfragments/+bibtex-klucz-doktoratu.bugfix.rst create mode 100644 src/bpp/tests/test_export/test_bibtex_klucz_doktoratu.py diff --git a/src/bpp/models/praca_doktorska.py b/src/bpp/models/praca_doktorska.py index 607d1224b..e89808e37 100644 --- a/src/bpp/models/praca_doktorska.py +++ b/src/bpp/models/praca_doktorska.py @@ -37,6 +37,13 @@ class FakeSet(list): def all(self): return self + def first(self): + # Doktorat/habilitacja ma dokładnie jednego autora (pole + # `autor`, nie relacja M2M), ale kod wołający traktuje wynik + # jak queryset. Bez `first()` leciał AttributeError — m.in. + # w bpp.export.bibtex.generate_bibtex_key. + return self[0] if self else None + def select_related(self, *args, **kw): return self diff --git a/src/bpp/newsfragments/+bibtex-klucz-doktoratu.bugfix.rst b/src/bpp/newsfragments/+bibtex-klucz-doktoratu.bugfix.rst new file mode 100644 index 000000000..05b035cc3 --- /dev/null +++ b/src/bpp/newsfragments/+bibtex-klucz-doktoratu.bugfix.rst @@ -0,0 +1,4 @@ +Klucz cytowania w eksporcie BibTeX prac doktorskich i habilitacyjnych znów +zawiera nazwisko autora. Wcześniej cichcem gubił nazwisko i zostawało samo +``_id`` — wyjątek był połykany, więc eksport działał, tylko +z gorszym kluczem. diff --git a/src/bpp/tests/test_export/test_bibtex_klucz_doktoratu.py b/src/bpp/tests/test_export/test_bibtex_klucz_doktoratu.py new file mode 100644 index 000000000..7a72c5451 --- /dev/null +++ b/src/bpp/tests/test_export/test_bibtex_klucz_doktoratu.py @@ -0,0 +1,37 @@ +"""Klucz BibTeX dla pracy doktorskiej/habilitacyjnej zawiera nazwisko autora. + +Regresja (Rollbar #447): ``Praca_Doktorska.autorzy_dla_opisu()`` zwraca +``FakeSet`` — listę udającą queryset, bo doktorat ma dokładnie jednego autora +w polu ``autor``, a nie relację wiele-do-wielu. ``FakeSet`` nie miał metody +``first()``, więc ``generate_bibtex_key`` leciało na ``AttributeError``. +Wyjątek był połykany (``zaloguj_polkniety_wyjatek``), więc eksport nie padał — +tyle że klucz cichcem gubił nazwisko i zostawało samo ``_id``. +""" + +import pytest + +from bpp.export.bibtex import generate_bibtex_key + + +@pytest.mark.django_db +def test_klucz_bibtex_doktoratu_zawiera_nazwisko_autora(doktorat, autor_jan_kowalski): + doktorat.autor = autor_jan_kowalski + doktorat.rok = 2020 + doktorat.save() + + klucz = generate_bibtex_key(doktorat) + + assert klucz == f"Kowalski_2020_id{doktorat.pk}" + + +@pytest.mark.django_db +def test_klucz_bibtex_habilitacji_zawiera_nazwisko_autora( + habilitacja, autor_jan_kowalski +): + habilitacja.autor = autor_jan_kowalski + habilitacja.rok = 2021 + habilitacja.save() + + klucz = generate_bibtex_key(habilitacja) + + assert klucz == f"Kowalski_2021_id{habilitacja.pk}" From a7fa3c56a8f4e443dfe8867d1617f9621f4abd45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sat, 25 Jul 2026 00:17:43 +0200 Subject: [PATCH 2/2] =?UTF-8?q?fix(bibtex):=20napraw=20przyczyn=C4=99,=20n?= =?UTF-8?q?ie=20objaw=20=E2=80=94=20autorzy=5Fdla=5Fopisu=20bywa=20list?= =?UTF-8?q?=C4=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-review słusznie wytknął, że FakeSet.first() naprawia JEDEN z czterech kształtów zwracanych przez autorzy_dla_opisu(). bpp.models.util zwraca też: queryset, gołą listę z Prefetch(to_attr=...) oraz [] dla obiektu bez pk. Żaden z listowych nie ma first(), więc dokładnie ten sam AttributeError czekał obok — i jest osiągalny: prefetch_dane_strony_rekordu podstawia listę, a nowy test to potwierdza (padał na 2019_id... zamiast Nowak_2019_id...). generate_bibtex_key bierze teraz pierwszy element przez ten sam idiom, co bpp.models.util: first() gdy jest, inaczej next(iter(...), None). FakeSet.first() zostaje — jest potrzebny — a exists() dostaje komentarz wyjaśniający, że twarde False NIE jest sprzecznością: exists() jest osiągalne tylko po exclude(), które w FakeSet ignoruje warunki, więc False znaczy "brak autorów z odpiętą dyscypliną", nie "brak autorów". Zgodnie z uwagą review NIE dodaję do_rollbar=False w tym except — po tej poprawce nadal jest osiągalny (nazwisko None, błędy bazy), a item #447 był jedynym sygnałem, że coś jest nie tak. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01NcAqeqyqBzNEkkVnhpHDaH --- src/bpp/export/bibtex.py | 10 +++++++- src/bpp/models/praca_doktorska.py | 7 ++++++ .../+bibtex-klucz-doktoratu.bugfix.rst | 9 ++++--- .../test_bibtex_klucz_doktoratu.py | 25 +++++++++++++++++++ 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/bpp/export/bibtex.py b/src/bpp/export/bibtex.py index db7a799d9..34ead4037 100644 --- a/src/bpp/export/bibtex.py +++ b/src/bpp/export/bibtex.py @@ -90,7 +90,15 @@ def generate_bibtex_key(wydawnictwo) -> str: key_parts = [] try: - first_author = wydawnictwo.autorzy_dla_opisu().first() + # `autorzy_dla_opisu()` NIE zawsze zwraca queryset. Zwraca też zwykłą + # listę (prefetch przez `Prefetch(to_attr=...)` albo `[]` dla obiektu + # bez `pk`) oraz `FakeSet` doktoratu/habilitacji. Tylko queryset ma + # `first()` — dla pozostałych trzeba wziąć pierwszy element wprost. + # Ten sam idiom stosuje `bpp.models.util`. + autorzy = wydawnictwo.autorzy_dla_opisu() + first_author = ( + autorzy.first() if hasattr(autorzy, "first") else next(iter(autorzy), None) + ) if first_author and hasattr(first_author, "autor"): surname = first_author.autor.nazwisko # Clean surname for key diff --git a/src/bpp/models/praca_doktorska.py b/src/bpp/models/praca_doktorska.py index e89808e37..53edbb894 100644 --- a/src/bpp/models/praca_doktorska.py +++ b/src/bpp/models/praca_doktorska.py @@ -54,6 +54,13 @@ def exclude(self, *args, **kw): return self def exists(self): + # NIE jest to sprzeczność z `first()` powyżej. `exists()` na + # FakeSet jest osiągalne wyłącznie po łańcuchu `exclude(...)` + # (patrz Rekord.ma_odpiete_dyscypliny), a `exclude()` tutaj + # IGNORUJE warunki i zwraca self. Twarde False znaczy więc + # „brak autorów z odpiętą dyscypliną", nie „brak autorów" — + # doktorat nie ma czego odpinać. Gdyby zwracało True, strona + # rekordu renderowałaby pusty box „odpięte dyscypliny". return False ret = FakeAutorDoktoratuHabilitacji() diff --git a/src/bpp/newsfragments/+bibtex-klucz-doktoratu.bugfix.rst b/src/bpp/newsfragments/+bibtex-klucz-doktoratu.bugfix.rst index 05b035cc3..bdcc1c8af 100644 --- a/src/bpp/newsfragments/+bibtex-klucz-doktoratu.bugfix.rst +++ b/src/bpp/newsfragments/+bibtex-klucz-doktoratu.bugfix.rst @@ -1,4 +1,5 @@ -Klucz cytowania w eksporcie BibTeX prac doktorskich i habilitacyjnych znów -zawiera nazwisko autora. Wcześniej cichcem gubił nazwisko i zostawało samo -``_id`` — wyjątek był połykany, więc eksport działał, tylko -z gorszym kluczem. +Klucz cytowania w eksporcie BibTeX znów zawiera nazwisko pierwszego autora — +dotyczyło to prac doktorskich i habilitacyjnych oraz rekordów otwieranych +bezpośrednio ze strony rekordu. Wcześniej klucz cichcem gubił nazwisko +i zostawało samo ``_id``; wyjątek był połykany, więc eksport +działał, tylko z gorszym kluczem. diff --git a/src/bpp/tests/test_export/test_bibtex_klucz_doktoratu.py b/src/bpp/tests/test_export/test_bibtex_klucz_doktoratu.py index 7a72c5451..6140235e6 100644 --- a/src/bpp/tests/test_export/test_bibtex_klucz_doktoratu.py +++ b/src/bpp/tests/test_export/test_bibtex_klucz_doktoratu.py @@ -9,8 +9,11 @@ """ import pytest +from model_bakery import baker from bpp.export.bibtex import generate_bibtex_key +from bpp.models import Autor, Wydawnictwo_Ciagle +from bpp.models.util import prefetch_dane_strony_rekordu @pytest.mark.django_db @@ -35,3 +38,25 @@ def test_klucz_bibtex_habilitacji_zawiera_nazwisko_autora( klucz = generate_bibtex_key(habilitacja) assert klucz == f"Kowalski_2021_id{habilitacja.pk}" + + +@pytest.mark.django_db +def test_klucz_bibtex_dziala_gdy_autorzy_sa_zwykla_lista(typy_odpowiedzialnosci): + """``autorzy_dla_opisu()`` bywa ZWYKŁĄ LISTĄ, nie tylko querysetem. + + ``bpp.models.util`` zwraca stamtąd trzy różne kształty: queryset, ``[]`` + dla obiektu bez ``pk`` oraz listę podstawioną przez + ``prefetch_dane_strony_rekordu`` (``Prefetch(to_attr=...)``). Czwarty to + ``FakeSet`` doktoratu. Żaden z listowych nie ma ``first()``, więc + naprawienie samego ``FakeSet`` zostawiłoby tę samą pułapkę obok. + """ + autor = baker.make(Autor, nazwisko="Nowak", imiona="Anna") + wyd = baker.make(Wydawnictwo_Ciagle, tytul_oryginalny="Artykuł", rok=2019) + wyd.dodaj_autora(autor, baker.make("bpp.Jednostka")) + + prefetch_dane_strony_rekordu(wyd) + assert isinstance(wyd.autorzy_dla_opisu(), list), ( + "prefetch miał podstawić listę — bez tego test nie bada tej ścieżki" + ) + + assert generate_bibtex_key(wyd) == f"Nowak_2019_id{wyd.pk}"