Skip to content

Commit bf6fb97

Browse files
mpasternakclaude
andauthored
fix(eksport-autora): eksport bez ukrytych statusów także dla redakcji i bez uczelni (#921)
Uzupełnienie #901. Zakres ukrytych statusów w eksporcie BibTeX/RIS liczyła ukryte_statusy_podgladu(request), czyli reguła strony rekordu: - redakcja dostawała pustą listę — także w zadaniu w tle, którego plik jest publiczny po UUID, więc zlecenie redaktora publikowało prace o ukrytych statusach, - przy nieustalonej uczelni lista była pusta (fail-open). Nowy helper ukryte_statusy_eksportu(request): reguła publiczna dla każdego (plik wychodzi poza stronę), a bez uczelni — statusy ukryte w kanale "podglad" w dowolnej uczelni. Używany w eksporcie synchronicznym i przy starcie zadania (zakres i deduplikacja). Claude-Session: https://claude.ai/code/session_01A6vyhS6eoLYPN5ceSPJ1sh Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 09fa171 commit bf6fb97

4 files changed

Lines changed: 73 additions & 11 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Eksport prac autora do BibTeX/RIS (synchroniczny i w tle) nie zawiera prac o statusach korekty ukrytych na stronach publicznych także wtedy, gdy zleca go redaktor (plik wychodzi poza stronę) oraz gdy nie da się ustalić uczelni.

‎src/bpp/tests/test_profil/test_widocznosc_profilu.py‎

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,28 @@ def test_eksport_sync_bez_ukrytych_dla_zwyklego_usera(
215215
assert "Praca ukryta" not in body
216216

217217

218-
def test_eksport_sync_redakcja_widzi_ukryte(admin_client, dorobek_z_ukryta):
219-
url = reverse("bpp:autor_eksport_bibtex", args=(dorobek_z_ukryta.pk,))
218+
@pytest.mark.parametrize("nazwa", ["autor_eksport_bibtex", "autor_eksport_ris"])
219+
def test_eksport_sync_redakcja_tez_bez_ukrytych(admin_client, dorobek_z_ukryta, nazwa):
220+
"""Plik wychodzi poza stronę — redakcja też nie dostaje ukrytych prac."""
221+
url = reverse(f"bpp:{nazwa}", args=(dorobek_z_ukryta.pk,))
220222
body = admin_client.get(url).content.decode("utf-8")
221-
assert "Praca ukryta" in body
223+
assert "Praca widoczna" in body
224+
assert "Praca ukryta" not in body
225+
226+
227+
def test_eksport_sync_bez_uczelni_bez_ukrytych(
228+
client, django_user_model, dorobek_z_ukryta
229+
):
230+
"""Bez ustalonej uczelni eksport nie jest fail-open: wyklucza statusy
231+
ukryte w dowolnej uczelni."""
232+
client.force_login(_zwykly_user(django_user_model))
233+
url = reverse("bpp:autor_eksport_bibtex", args=(dorobek_z_ukryta.pk,))
234+
with mock.patch(
235+
"bpp.models.Uczelnia.objects.get_for_request", return_value=None
236+
):
237+
body = client.get(url).content.decode("utf-8")
238+
assert "Praca widoczna" in body
239+
assert "Praca ukryta" not in body
222240

223241

224242
def test_eksport_sync_ukryty_autor_404(client, admin_client, django_user_model):
@@ -250,10 +268,13 @@ def test_eksport_sync_limit_zadan(client, django_user_model):
250268
# --- eksport asynchroniczny --------------------------------------------------
251269

252270

253-
def test_eksport_async_zapisuje_zakres_i_rozdziela_deduplikacje(
271+
def test_eksport_async_zakres_publiczny_takze_dla_redakcji(
254272
client, admin_client, uczelnia, dorobek_z_ukryta
255273
):
256-
ukryte = list(uczelnia.ukryte_statusy("podglad"))
274+
"""Plik zadania jest publiczny po UUID — zlecenie redakcji dostaje ten
275+
sam (publiczny) zakres, więc anonim reużywa tego samego zadania."""
276+
ukryte = sorted(uczelnia.ukryte_statusy("podglad"))
277+
assert ukryte
257278
url = reverse(
258279
"bpp:autor_eksport_start", kwargs={"pk": dorobek_z_ukryta.pk, "format": "bib"}
259280
)
@@ -265,7 +286,22 @@ def test_eksport_async_zapisuje_zakres_i_rozdziela_deduplikacje(
265286
client.post(url, {"captcha": "eyJjaGFsbGVuZ2UiOiAieCJ9"})
266287

267288
zadania = AutorEksportTask.objects.filter(autor=dorobek_z_ukryta)
268-
assert sorted(list(t.ukryte_statusy) for t in zadania) == sorted([[], ukryte])
289+
assert [list(t.ukryte_statusy) for t in zadania] == [ukryte]
290+
291+
292+
def test_eksport_async_bez_uczelni_bez_ukrytych(admin_client, dorobek_z_ukryta):
293+
"""Bez ustalonej uczelni zadanie dostaje statusy ukryte w dowolnej
294+
uczelni (nie pustą listę)."""
295+
url = reverse(
296+
"bpp:autor_eksport_start", kwargs={"pk": dorobek_z_ukryta.pk, "format": "ris"}
297+
)
298+
with mock.patch("bpp.tasks.generuj_eksport_autora.delay"), mock.patch(
299+
"bpp.models.Uczelnia.objects.get_for_request", return_value=None
300+
):
301+
admin_client.post(url)
302+
task = AutorEksportTask.objects.get(autor=dorobek_z_ukryta)
303+
ukryty = Status_Korekty.objects.get(nazwa="ukryty-test")
304+
assert list(task.ukryte_statusy) == [ukryty.pk]
269305

270306

271307
def test_eksport_async_wyklucza_ukryte(dorobek_z_ukryta, uczelnia):

‎src/bpp/util/ukryte_statusy.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,26 @@ def wyklucz_ukryte_statusy(
4242
if not statusy:
4343
return qs
4444
return qs.exclude(**{f"{lookup}__in": statusy})
45+
46+
47+
def ukryte_statusy_eksportu(request, kanal="podglad"):
48+
"""Lista PK statusów korekty wykluczanych z eksportu plików (BibTeX/RIS).
49+
50+
W odróżnieniu od :func:`ukryte_statusy_dla` reguła jest publiczna dla
51+
KAŻDEGO, także redaktora: plik wychodzi poza stronę (można go przekazać
52+
dalej, a plik z zadania w tle jest dostępny po UUID). Gdy nie da się
53+
ustalić uczelni, nie jest fail-open — zwraca statusy ukryte w danym
54+
kanale w KTÓREJKOLWIEK uczelni.
55+
"""
56+
from bpp.models import Uczelnia, Ukryj_Status_Korekty
57+
58+
uczelnia = Uczelnia.objects.get_for_request(request)
59+
if uczelnia is not None:
60+
return sorted(set(uczelnia.ukryte_statusy(kanal)))
61+
return sorted(
62+
set(
63+
Ukryj_Status_Korekty.objects.filter(**{kanal: True}).values_list(
64+
"status_korekty_id", flat=True
65+
)
66+
)
67+
)

‎src/bpp/views/eksport_autora.py‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from bpp.export.ris import export_to_ris
3131
from bpp.models import Autor, AutorEksportTask, Rekord
3232
from bpp.permissions import moze_wprowadzac_dane
33-
from bpp.profil_autora_dane import ukryte_statusy_podgladu
33+
from bpp.util.ukryte_statusy import ukryte_statusy_eksportu
3434

3535
#: Maksymalna liczba eksportowanych rekordów. Zabezpiecza przed OOM przy
3636
#: autorach z gigantyczną liczbą prac (eksport materializuje instancje
@@ -99,7 +99,8 @@ def get(self, request, pk):
9999
)
100100

101101
qs = Rekord.objects.prace_autora(autor)
102-
ukryte = ukryte_statusy_podgladu(request)
102+
# Plik wychodzi poza stronę — reguła publiczna także dla redakcji.
103+
ukryte = ukryte_statusy_eksportu(request)
103104
if ukryte:
104105
qs = qs.exclude(status_korekty_id__in=ukryte)
105106
wszystkich = qs.count()
@@ -204,9 +205,10 @@ def post(self, request, pk, format):
204205
return self._render(request, autor, format, form)
205206

206207
# Zakres widoczności (ukryte statusy) zapisujemy w zadaniu — worker
207-
# Celery nie ma requestu. Wchodzi też do deduplikacji, żeby zadania
208-
# o różnym zakresie (redakcja vs. pozostali) nie były współdzielone.
209-
ukryte = sorted(ukryte_statusy_podgladu(request))
208+
# Celery nie ma requestu. Plik jest publiczny po UUID, więc zakres
209+
# jest publiczny także dla redakcji; wchodzi do deduplikacji, żeby
210+
# zadania różnych uczelni (inne ukryte statusy) nie były współdzielone.
211+
ukryte = ukryte_statusy_eksportu(request)
210212

211213
od = timezone.now() - timedelta(minutes=DEDUPE_MINUTES)
212214
istniejacy = (

0 commit comments

Comments
 (0)