Skip to content

feat(zapytanie): wachlarz wyjść dla wyszukiwania zapytaniem — postać wyniku i eksport (CSV/XLSX/HTML/DOCX/BibTeX)#685

Open
mpasternak wants to merge 17 commits into
devfrom
feat/zapytanie-wyjscia
Open

feat(zapytanie): wachlarz wyjść dla wyszukiwania zapytaniem — postać wyniku i eksport (CSV/XLSX/HTML/DOCX/BibTeX)#685
mpasternak wants to merge 17 commits into
devfrom
feat/zapytanie-wyjscia

Conversation

@mpasternak

Copy link
Copy Markdown
Member

Co to robi

Strona „Wyszukiwanie zapytaniem" (/zapytanie/, DjangoQL) umiała dotąd jedno: pokazać stronicowaną tabelę opis / ID / akcje. Nie dało się z niej wyeksportować niczego. Ten PR daje jej wachlarz wyjść multiwyszukiwarki:

  • wybór postaci wynikurekordy (dotychczasowa tabela redakcyjna, nadal domyślna), list, table, pkt_wewn, pkt_wewn_bez, bibtex,
  • eksport — CSV, XLSX (warianty „dane" i „opis"), HTML, DOCX oraz .bib.

Postacie renderują się partialami multiseeka, eksport idzie jego funkcjami serializacji. Nic nie jest pisane od nowa.

Dlaczego akurat tak

Teza: warstwa wyjścia (eksport, render raportu) nie jest funkcją multiseeka — jest funkcją querysetu. Siedziała pod multiseekiem tylko dlatego, że był pierwszym konsumentem. PR dokłada drugie wejście (DjangoQL) i przesuwa granice tam, gdzie należą:

ekstrakcja z czego po co
wykonaj_zapytanie(model_key, query) ZapytanieView.render_results strona i eksport muszą liczyć ten sam queryset, razem z .distinct()
document_export_response(...) MyMultiseekExport._export_document metoda nie używała self poza odczytem report_type (feature envy); teraz oba wejścia wołają jedną funkcję
flagi hide_chrome / pokaz_edycje partiale multiseeka wyciszenie sesyjnego widgetu ❌ i przycisku kopiowania BibTeX-a poza multiseekiem, plus opcjonalny link do admina

Flagi szablonowe są negatywne / domyślnie wyłączone celowo: multiseek ich nie przekazuje, a w Django brakująca zmienna jest falsy — więc jego zachowanie nie może się zmienić.

Bezpieczeństwo

Review wyłapało reflected XSS w komunikacie o błędnym zapytaniu: HttpResponseBadRequest ma domyślnie Content-Type: text/html, a treść błędu z djangoql odbija literały z zapytania użytkownika dosłownie. Wektor: spreparowany link GET podesłany redaktorowi albo superuserowi (tylko oni mają tu dostęp).

Naprawione dwiema niezależnymi warstwami: wszystkie odpowiedzi 400 idą przez _blad(), które wymusza text/plain, plus escape() na tekście od użytkownika. Test regresyjny sprawdza obie — weryfikowany przez cofanie każdej warstwy z osobna.

Prześledzono też każdy parametr GET od wejścia do odpowiedzi: ?tytul= (nagłówek Content-Disposition i nazwa arkusza XLSX) idzie przez istniejące sanitizery, postac/model/wariant/export_format przez allow-listy, CSV/formula injection przez reużyte _sanitize_spreadsheet_row. Innych ścieżek user input → odpowiedź bez obróbki nie znaleziono.

Kompatybilność z multiseekiem

Zmieniane są trzy współdzielone partiale i wyciągany kod z mymultiseek.py. Zero zmian w plikach testowych multiseeka — to celowe kryterium neutralności; jego suita (313+ testów, w tym 25 testów pivota) miała przejść bez modyfikacji i przechodzi.

Zakres

To faza 1 z czterofazowego planu. Poza tym PR-em zostają: tabela krzyżowa na stronie zapytania (rekordowa i autorska) oraz eksport autorów z metrykami dorobku — mają gotową specyfikację i plan, wejdą osobno.

Spec i plan: docs/superpowers/specs/2026-07-26-zapytanie-wachlarz-wyjsc-design.md, docs/superpowers/plans/2026-07-26-zapytanie-wachlarz-wyjsc.md.

Testy

~40 nowych testów w test_zapytanie_postac.py, test_zapytanie_export.py, test_multiseek_export.py plus rozszerzony test_zapytanie.py: render każdej postaci, wszystkie formaty eksportu (content-type, nazwa pliku, treść), limity, uprawnienia osobno na eksporcie (anonim i staff spoza grupy), sanityzacja tytułu, regresja XSS, neutralność multiseeka.

Uwaga o weryfikacji: ostatni pełny lokalny przebieg suity odbył się na commicie 5e40e87ca (test_zapytanie_postac.py 21/21, test_zapytanie*.py 110/110, multiseek 293/293). Późniejsze commity z poprawkami po końcowym review nie mają lokalnego przebiegu — środowisko dockerowe na maszynie deweloperskiej padło (stos wirtualizacji macOS-a, jądro gościa zawiesza się na virtio_snd w pierwszej sekundzie bootu). CI jest tu właściwym gejtem — proszę nie mergować przed zielonymi Build test-runner image i wszystkimi shardami Tests (sharded).

🤖 Generated with Claude Code

mpasternak and others added 17 commits July 26, 2026 16:22
Eksporty (CSV/XLSX/HTML/DOCX/BibTeX) i tabela krzyżowa na stronie
/zapytanie/ w parytecie z multiseekiem, plus nowy rejestr wymiarów
tabeli krzyżowej dla modelu Autor (bazy: kadrowa / prace / udziały).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Część IV: usunięta sprzeczność (jeden annotate vs osobne zapytania);
  metryki dorobku liczone dwoma zapytaniami, bo dwa JOIN-y do relacji
  do-wielu w jednym annotate zawyżają sumy
- postać dla model=autor to 'rekordy' + 'pivot' (nie 'lista')
- doprecyzowany routing eksportu przy postac=pivot i sanityzacja ?tytul=

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cztery fazy: wspólny silnik wyjścia + eksporty rekordów, pivot rekordów,
pakiet bpp/pivot/ + rejestr autorski (baza kadrowa), metryki
bibliometryczne + eksport autorów.

Self-review planu wykrył trzy blokery reużycia report-body-pivot.html
(formularz GET gubiący model/query, relatywne linki eksportu, selektory
nieświadome baz agregacji) — wszystkie rozpisane jako konkretne edycje.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pre-flight scan planu: testy dotykające Rekordu wymagają fixture denorms
i denorms.flush() (Rekord to zdenormalizowany cache), fixture 'tytul' nie
istnieje (jest 'tytuly'), a dwie asercje były zbyt słabe (dowolne '2'
w wierszu CSV; brak '5000' w komunikacie) — zamienione na parsowanie CSV
i sprawdzenie treści komunikatu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Task 2 wykrył, że plan brał drugi argument ReportType('list', 'lista') —
etykietę UI — za wartość report_type. postac=tabela nigdy nie trafiłaby
w TABLE_REPORT_TYPES, więc tabela renderowałaby się jako lista, bez błędu.
Poprawione w planie (zadania 2-6, 12) i w specyfikacji.

Druga poprawka: nh3 w sanitize_export_html zdejmuje atrybuty class, więc
asercje testów eksportu HTML/DOCX nie mogą opierać się na klasach CSS —
tylko render na stronie je zachowuje.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review Task 3: postać 'tabela krzyżowa' była wybieralna, a renderowała
placeholder listy (dla autorów: puste wiersze, bo Autor nie ma
js_safe_pk ani opisu bibliograficznego). Zadanie 3 usuwa ją z list
postaci, zadania 6 i 9 dopisują ją z powrotem wraz z implementacją.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…a bibtex, ikona fi-page-edit

- POSTACIE_REKORD/POSTACIE_AUTOR: wyrzucone "pivot" (bez dedykowanego
  partiala renderowal sie jako zepsuta lista rekordow/autorow).
- postac="bibtex" renderuje teraz wlasny partial multiseeka
  (report-body-bibtex.html) zamiast placeholder-owej listy opisow.
- ikona edycji ✎ -> fi-page-edit (Foundation Icons, zgodnie z konwencja
  repo dla frontendu publicznego) w obu partialach (list/table).
- test_postac_tabela_ma_sumy: asercja na realnej wartosci sumy (dwa
  rekordy, 40+10=50,00), nie tylko na obecnosci kontenera.
- nowe testy regresyjne: pivot niewybieralny/degradujacy, bibtex uzywa
  wlasnego partiala, pager zachowuje postac miedzy stronami, stopka
  "Suma:" tylko na ostatniej stronie tabeli.
…ekiem

Przycisk "Skopiuj wszystko do schowka" w report-body-bibtex.html ma
handler JS tylko inline w multiseekowym common-results.html — na
/zapytanie/ (hide_chrome=True) byl martwy (klik bez efektu). Ten sam
wzorzec co widget usuwania (❌): {% if not hide_chrome %} wokol
.bibtex-actions. Multiseek nie przekazuje hide_chrome, wiec jego
przycisk dziala jak dotad; /zapytanie/ dostaje sam <pre> z BibTeX-em
(w pelni zaznaczalny/kopiowalny recznie).
Zadanie 5 celowo zwróciło pustą krotkę formatów dla model=autor, bo
backend 400-ował każdy format i pasek pokazywałby martwe linki. Po
wdrożeniu eksportu autorów gałąź ma wrócić do (csv, xlsx).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eksporty, komunikat nieznanego modelu

Test uprawnien testuje teraz sztywno 403 (raise_exception=True gwarantuje
to niezaleznie od stanu logowania — potwierdzone w kodzie mixinow Django).
Usuniete szesc martwych re-eksportow z mymultiseek.py (niepotwierdzone
grepem uzycie poza tym plikiem). Rozdzielony komunikat 400 dla eksportu
autorow i dla realnie nieznanego modelu. Dwa testy degradacji postaci dla
autora dostaly pozytywna asercje na "rekord-id-cell" zamiast samego
"not in".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mpasternak

Copy link
Copy Markdown
Member Author

CI jest w komplecie zielone: 12/12 shardów Tests (sharded) oraz Build test-runner image — czyli oba realne gejty tego repo. Razem 23 pass, 1 skipping (Deploy to GitHub Pages), mergeStateStatus: CLEAN.

To domyka zastrzeżenie z opisu PR-a: commity dołożone po ostatnim lokalnym przebiegu (5e40e87ca) — w tym ed411c4f5 z poprawkami po końcowym review — nie miały weryfikacji na maszynie deweloperskiej, bo padło tam środowisko dockerowe. Zweryfikowało je CI.

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