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
Open
feat(zapytanie): wachlarz wyjść dla wyszukiwania zapytaniem — postać wyniku i eksport (CSV/XLSX/HTML/DOCX/BibTeX)#685mpasternak wants to merge 17 commits into
mpasternak wants to merge 17 commits into
Conversation
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>
Member
Author
|
CI jest w komplecie zielone: 12/12 shardów To domyka zastrzeżenie z opisu PR-a: commity dołożone po ostatnim lokalnym przebiegu ( |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:rekordy(dotychczasowa tabela redakcyjna, nadal domyślna),list,table,pkt_wewn,pkt_wewn_bez,bibtex,.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żą:
wykonaj_zapytanie(model_key, query)ZapytanieView.render_results.distinct()document_export_response(...)MyMultiseekExport._export_documentselfpoza odczytemreport_type(feature envy); teraz oba wejścia wołają jedną funkcjęhide_chrome/pokaz_edycjeFlagi 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:
HttpResponseBadRequestma domyślnieContent-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 wymuszatext/plain, plusescape()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łówekContent-Dispositioni nazwa arkusza XLSX) idzie przez istniejące sanitizery,postac/model/wariant/export_formatprzez 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.pyplus rozszerzonytest_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.py21/21,test_zapytanie*.py110/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ę navirtio_sndw pierwszej sekundzie bootu). CI jest tu właściwym gejtem — proszę nie mergować przed zielonymiBuild test-runner imagei wszystkimi shardamiTests (sharded).🤖 Generated with Claude Code