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 607d1224b..53edbb894 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 @@ -47,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 new file mode 100644 index 000000000..bdcc1c8af --- /dev/null +++ b/src/bpp/newsfragments/+bibtex-klucz-doktoratu.bugfix.rst @@ -0,0 +1,5 @@ +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 new file mode 100644 index 000000000..6140235e6 --- /dev/null +++ b/src/bpp/tests/test_export/test_bibtex_klucz_doktoratu.py @@ -0,0 +1,62 @@ +"""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 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 +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}" + + +@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}"