From e54cd553ef4bbc685ef4a85f331e342a91193d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sat, 20 Jun 2026 23:57:48 +0200 Subject: [PATCH] =?UTF-8?q?feat(admin):=20rozdzia=C5=82=20=E2=80=94=20dzie?= =?UTF-8?q?dziczenie=20wydawcy=20po=20wydawnictwie=20nadrz=C4=99dnym=20+?= =?UTF-8?q?=20pod=C5=9Bwietlenie=20(Freshdesk=20#385)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Przy wprowadzaniu rozdziału (Wydawnictwo_Zwarte z wydawnictwem nadrzędnym) operatorzy łatwo przeoczali pole "wydawca". Dwie zmiany: 1. Dziedziczenie wydawcy (autorytatywnie po stronie serwera, bez JS): Wydawnictwo_ZwarteForm.clean() podstawia wydawcę z wydawnictwa nadrzędnego, gdy pole "wydawca" pozostaje puste. Jawnie wpisany wydawca NIE jest nadpisywany. Operator dostaje komunikat (warning) o odziedziczeniu. Logika wydzielona do testowalnego helpera _dziedzicz_wydawce_po_nadrzednym(). 2. Podświetlenie pola "wydawca" jako wymaganego przy rozdziale — warstwa wyłącznie wizualna (admin JS + CSS), aktywna gdy wskazano wydawnictwo nadrzędne, a pole "wydawca" jest puste. Dodano też help_text na polu. Brak migracji (zmiany tylko w formularzu/adminie). Testy pytest pokrywają: dziedziczenie przy pustym wydawcy, brak nadpisywania jawnie podanego, nadrzędne bez wydawcy oraz brak nadrzędnego. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/bpp/admin/wydawnictwo_zwarte.py | 34 ++++++++ .../bpp/css/wydawnictwo_zwarte_wydawca.css | 17 ++++ .../bpp/js/wydawnictwo_zwarte_wydawca.js | 71 +++++++++++++++++ .../test_admin/test_wydawnictwo_zwarte.py | 77 ++++++++++++++++++- 4 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 src/bpp/static/bpp/css/wydawnictwo_zwarte_wydawca.css create mode 100644 src/bpp/static/bpp/js/wydawnictwo_zwarte_wydawca.js diff --git a/src/bpp/admin/wydawnictwo_zwarte.py b/src/bpp/admin/wydawnictwo_zwarte.py index fd374e538..644643b41 100644 --- a/src/bpp/admin/wydawnictwo_zwarte.py +++ b/src/bpp/admin/wydawnictwo_zwarte.py @@ -273,6 +273,9 @@ def _sprawdz_isbn_z_nadrzednym( wydawca = forms.ModelChoiceField( required=False, queryset=Wydawca.objects.all(), + help_text="Przy rozdziale (gdy wskazano wydawnictwo nadrzędne) wydawca " + "jest dziedziczony po wydawnictwie nadrzędnym, o ile pozostawisz to pole " + "puste. Możesz wpisać innego wydawcę ręcznie.", widget=autocomplete.ModelSelect2( url="bpp:wydawca-autocomplete", attrs={"class": "bpp-autocomplete-wide"} ), @@ -345,8 +348,32 @@ def clean(self): if message: self._warnings.append(message) + self._dziedzicz_wydawce_po_nadrzednym(cleaned_data, wydawnictwo_nadrzedne) + return cleaned_data + def _dziedzicz_wydawce_po_nadrzednym(self, cleaned_data, wydawnictwo_nadrzedne): + """Dziedziczenie wydawcy po wydawnictwie nadrzędnym (rozdziale). + + Freshdesk #385. Jeżeli rekord ma wskazane wydawnictwo nadrzędne + (czyli jest rozdziałem), a operator nie podał własnego wydawcy — + podstaw wydawcę z wydawnictwa nadrzędnego. Jawnie wpisany wydawca + NIE jest nadpisywany. Mutuje ``cleaned_data`` w miejscu. + """ + if not wydawnictwo_nadrzedne: + return + if cleaned_data.get("wydawca"): + return + if wydawnictwo_nadrzedne.wydawca_id is None: + return + + cleaned_data["wydawca"] = wydawnictwo_nadrzedne.wydawca + self._warnings.append( + "Wydawca został odziedziczony po wydawnictwie nadrzędnym " + f"'{wydawnictwo_nadrzedne.tytul_oryginalny}': " + f"{wydawnictwo_nadrzedne.wydawca}." + ) + class Meta: model = Wydawnictwo_Zwarte fields = [ @@ -427,6 +454,13 @@ class Meta: "slowa_kluczowe": TextareaTagWidget(attrs={"rows": 2}), } + class Media: + # Freshdesk #385: podświetlenie wymaganego pola "wydawca" przy + # rozdziale (rekord z wydawnictwem nadrzędnym). Warstwa wyłącznie + # wizualna — dziedziczenie wydawcy realizuje serwer w clean(). + js = ["/static/bpp/js/wydawnictwo_zwarte_wydawca.js"] + css = {"all": ["/static/bpp/css/wydawnictwo_zwarte_wydawca.css"]} + class Wydawnictwo_Zwarte_Zewnetrzna_Baza_DanychForm(forms.ModelForm): class Meta: diff --git a/src/bpp/static/bpp/css/wydawnictwo_zwarte_wydawca.css b/src/bpp/static/bpp/css/wydawnictwo_zwarte_wydawca.css new file mode 100644 index 000000000..0effc52fc --- /dev/null +++ b/src/bpp/static/bpp/css/wydawnictwo_zwarte_wydawca.css @@ -0,0 +1,17 @@ +/* + * Freshdesk #385 — podświetlenie wymaganego pola "wydawca" przy rozdziale. + * Aktywowane klasą .bpp-wydawca-wymagany dodawaną przez + * wydawnictwo_zwarte_wydawca.js, gdy wskazano wydawnictwo nadrzędne, + * a pole wydawca jest puste. + */ +.bpp-wydawca-wymagany { + border-left: 4px solid #e6a700; + padding-left: 8px; + background-color: rgba(230, 167, 0, 0.08); +} + +.bpp-wydawca-required-marker { + color: #b35900; + font-weight: bold; + font-size: 0.85em; +} diff --git a/src/bpp/static/bpp/js/wydawnictwo_zwarte_wydawca.js b/src/bpp/static/bpp/js/wydawnictwo_zwarte_wydawca.js new file mode 100644 index 000000000..e725501bf --- /dev/null +++ b/src/bpp/static/bpp/js/wydawnictwo_zwarte_wydawca.js @@ -0,0 +1,71 @@ +/* + * Freshdesk #385 — Rozdział: podświetlanie wymaganego pola "wydawca". + * + * Gdy rekord wydawnictwa zwartego ma wskazane wydawnictwo nadrzędne + * (czyli jest rozdziałem), pole "wydawca" jest istotne i łatwo je + * przeoczyć. Ten skrypt podświetla je wizualnie jako wymagane, dopóki + * pozostaje puste. Faktyczne dziedziczenie wydawcy po wydawnictwie + * nadrzędnym realizuje serwer (Wydawnictwo_ZwarteForm.clean) — to jest + * wyłącznie warstwa UI i nie jest wymagana do działania mechanizmu. + */ +(function ($) { + "use strict"; + + function wydawcaInput() { + return $("#id_wydawca"); + } + + function wydawcaRow() { + // Wiersz formularza admina opakowujący pole wydawca (div.form-row / + // div.field-wydawca w zależności od wersji szablonu). + return wydawcaInput().closest(".form-row, .field-wydawca"); + } + + function nadrzedneWybrane() { + var val = $("#id_wydawnictwo_nadrzedne").val(); + return val !== null && val !== undefined && val !== ""; + } + + function wydawcaPusty() { + var val = wydawcaInput().val(); + return val === null || val === undefined || val === ""; + } + + function odswiezPodswietlenie() { + var row = wydawcaRow(); + if (!row.length) { + return; + } + var wymagany = nadrzedneWybrane() && wydawcaPusty(); + row.toggleClass("bpp-wydawca-wymagany", wymagany); + + var label = row.find("label").first(); + if (!label.length) { + return; + } + var marker = label.find(".bpp-wydawca-required-marker"); + if (wymagany) { + if (!marker.length) { + label.append( + ' ' + + "❗ wymagane przy rozdziale" + ); + } + } else { + marker.remove(); + } + } + + $(function () { + odswiezPodswietlenie(); + // django-autocomplete-light (Select2) emituje "change" na ukrytym + //