• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

iplweb / bpp / d739ad7a-8bce-4087-b0c8-83f9ea367ad1

18 Feb 2025 12:47AM UTC coverage: 48.187% (+0.7%) from 47.492%
d739ad7a-8bce-4087-b0c8-83f9ea367ad1

push

circleci

mpasternak
Merge branch 'release/v202502.1157'

172 of 381 new or added lines in 31 files covered. (45.14%)

802 existing lines in 49 files now uncovered.

17072 of 35429 relevant lines covered (48.19%)

1.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

42.41
src/bpp/admin/helpers.py
1
from urllib.parse import parse_qs
3✔
2
from urllib.parse import quote as urlquote
3✔
3

4
from django import forms
3✔
5
from django.db import models
3✔
6
from django.db.models import Q
3✔
7
from django.forms import BaseInlineFormSet
3✔
8
from django.forms.widgets import Textarea
3✔
9
from django.urls import reverse
3✔
10
from sentry_sdk import capture_exception
3✔
11

12
from import_common.normalization import normalize_isbn
3✔
13
from pbn_api.exceptions import (
3✔
14
    AccessDeniedException,
15
    AlreadyEnqueuedError,
16
    BrakZdefiniowanegoObiektuUczelniaWSystemieError,
17
    CharakterFormalnyNieobslugiwanyError,
18
    NeedsPBNAuthorisationException,
19
    PKZeroExportDisabled,
20
    PraceSerwisoweException,
21
    SameDataUploadedRecently,
22
)
23
from pbn_api.models import PBN_Export_Queue, SentData
3✔
24
from pbn_api.tasks import task_sprobuj_wyslac_do_pbn
3✔
25

26
from django.contrib import messages
3✔
27
from django.contrib.admin.utils import quote
3✔
28
from django.contrib.contenttypes.models import ContentType
3✔
29

30
from django.utils.html import format_html
3✔
31
from django.utils.safestring import mark_safe
3✔
32

33
from bpp import const
3✔
34
from bpp.const import PBN_MAX_ROK, PBN_MIN_ROK
3✔
35
from bpp.models import Status_Korekty, Uczelnia
3✔
36
from bpp.models.sloty.core import ISlot
3✔
37
from bpp.models.sloty.exceptions import CannotAdapt
3✔
38

39
CHARMAP_SINGLE_LINE = forms.TextInput(
3✔
40
    attrs={"class": "charmap", "style": "width: 500px"}
41
)
42

43

44
# Pomocnik dla klasy ModelZMetryczka
45

46

47
class ZapiszZAdnotacjaMixin:
3✔
48
    readonly_fields = ("ostatnio_zmieniony",)
3✔
49

50

51
class AdnotacjeZDatamiMixin:
3✔
52
    readonly_fields = ("utworzono", "ostatnio_zmieniony", "id")
3✔
53

54

55
class AdnotacjeZDatamiOrazPBNMixin:
3✔
56
    readonly_fields = (
3✔
57
        "utworzono",
58
        "ostatnio_zmieniony",
59
        "id",
60
        "pbn_id",
61
    )
62

63

64
ADNOTACJE_FIELDSET = (
3✔
65
    "Adnotacje",
66
    {
67
        "classes": ("grp-collapse grp-closed",),
68
        "fields": (ZapiszZAdnotacjaMixin.readonly_fields + ("adnotacje",)),
69
    },
70
)
71

72
ADNOTACJE_Z_DATAMI_FIELDSET = (
3✔
73
    "Adnotacje",
74
    {
75
        "classes": ("grp-collapse grp-closed",),
76
        "fields": AdnotacjeZDatamiMixin.readonly_fields + ("adnotacje",),
77
    },
78
)
79

80
ADNOTACJE_Z_DATAMI_ORAZ_PBN_FIELDSET = (
3✔
81
    "Adnotacje",
82
    {
83
        "classes": ("grp-collapse grp-closed",),
84
        "fields": AdnotacjeZDatamiOrazPBNMixin.readonly_fields + ("adnotacje",),
85
    },
86
)
87

88
OPENACCESS_FIELDSET = (
3✔
89
    "OpenAccess",
90
    {
91
        "classes": ("grp-collapse grp-closed",),
92
        "fields": (
93
            "openaccess_tryb_dostepu",
94
            "openaccess_licencja",
95
            "openaccess_wersja_tekstu",
96
            "openaccess_czas_publikacji",
97
            "openaccess_ilosc_miesiecy",
98
        ),
99
    },
100
)
101

102
DWA_TYTULY = (
3✔
103
    "tytul_oryginalny",
104
    "tytul",
105
)
106

107
MODEL_ZE_SZCZEGOLAMI = (
3✔
108
    "informacje",
109
    "szczegoly",
110
    "uwagi",
111
    "slowa_kluczowe",
112
    "slowa_kluczowe_eng",
113
    "strony",
114
    "tom",
115
)
116

117
MODEL_Z_ISSN = (
3✔
118
    "issn",
119
    "e_issn",
120
)
121

122
MODEL_Z_PBN_UID = ("pbn_uid",)
3✔
123

124
MODEL_Z_OPLATA_ZA_PUBLIKACJE = (
3✔
125
    "opl_pub_cost_free",
126
    "opl_pub_research_potential",
127
    "opl_pub_research_or_development_projects",
128
    "opl_pub_other",
129
    "opl_pub_amount",
130
)
131

132
MODEL_Z_OPLATA_ZA_PUBLIKACJE_FIELDSET = (
3✔
133
    "Opłata za publikację",
134
    {"classes": ("grp-collapse grp-closed",), "fields": MODEL_Z_OPLATA_ZA_PUBLIKACJE},
135
)
136

137
MODEL_Z_ISBN = (
3✔
138
    "isbn",
139
    "e_isbn",
140
)
141

142
MODEL_Z_WWW = (
3✔
143
    "www",
144
    "dostep_dnia",
145
    "public_www",
146
    "public_dostep_dnia",
147
)
148

149
MODEL_Z_PUBMEDID = ("pubmed_id", "pmc_id")
3✔
150

151
MODEL_Z_DOI = ("doi",)
3✔
152

153
MODEL_Z_LICZBA_CYTOWAN = ("liczba_cytowan",)
3✔
154

155
MODEL_Z_MIEJSCEM_PRZECHOWYWANIA = ("numer_odbitki",)
3✔
156

157
MODEL_Z_ROKIEM = ("rok",)
3✔
158

159
MODEL_TYPOWANY = (
3✔
160
    "jezyk",
161
    "jezyk_alt",
162
    "jezyk_orig",
163
    "typ_kbn",
164
)
165

166
MODEL_PUNKTOWANY_BAZA = (
3✔
167
    "punkty_kbn",
168
    "impact_factor",
169
    "index_copernicus",
170
    "punktacja_snip",
171
    "punktacja_wewnetrzna",
172
)
173

174
MODEL_PUNKTOWANY = MODEL_PUNKTOWANY_BAZA + ("weryfikacja_punktacji",)
3✔
175

176
MODEL_PUNKTOWANY_Z_KWARTYLAMI_BAZA = MODEL_PUNKTOWANY_BAZA + (
3✔
177
    "kwartyl_w_scopus",
178
    "kwartyl_w_wos",
179
)
180

181
MODEL_PUNKTOWANY_Z_KWARTYLAMI = MODEL_PUNKTOWANY_Z_KWARTYLAMI_BAZA + (
3✔
182
    "weryfikacja_punktacji",
183
)
184

185
MODEL_Z_INFORMACJA_Z = ("informacja_z",)
3✔
186

187
MODEL_Z_LICZBA_ZNAKOW_WYDAWNICZYCH = ("liczba_znakow_wydawniczych",)
3✔
188

189
MODEL_ZE_STATUSEM = ("status_korekty",)
3✔
190

191
MODEL_RECENZOWANY = ("recenzowana",)
3✔
192

193
MODEL_TYPOWANY_BEZ_CHARAKTERU_FIELDSET = (
3✔
194
    "Typ",
195
    {"classes": ("",), "fields": MODEL_TYPOWANY},
196
)
197

198
MODEL_TYPOWANY_FIELDSET = (
3✔
199
    "Typ",
200
    {"classes": ("",), "fields": ("charakter_formalny",) + MODEL_TYPOWANY},
201
)
202

203
MODEL_PUNKTOWANY_FIELDSET = (
3✔
204
    "Punktacja",
205
    {"classes": ("",), "fields": MODEL_PUNKTOWANY},
206
)
207

208
MODEL_PUNKTOWANY_WYDAWNICTWO_CIAGLE_FIELDSET = (
3✔
209
    "Punktacja",
210
    {
211
        "classes": ("",),
212
        "fields": MODEL_PUNKTOWANY_Z_KWARTYLAMI + ("uzupelnij_punktacje",),
213
    },
214
)
215

216
MODEL_OPCJONALNIE_NIE_EKSPORTOWANY_DO_API_FIELDSET = (
3✔
217
    "Eksport do API",
218
    {"classes": ("grp-collapse grp-closed",), "fields": ("nie_eksportuj_przez_api",)},
219
)
220

221
POZOSTALE_MODELE_FIELDSET = (
3✔
222
    "Pozostałe informacje",
223
    {
224
        "classes": ("",),
225
        "fields": MODEL_Z_INFORMACJA_Z + MODEL_ZE_STATUSEM + MODEL_RECENZOWANY,
226
    },
227
)
228

229
POZOSTALE_MODELE_WYDAWNICTWO_CIAGLE_FIELDSET = (
3✔
230
    "Pozostałe informacje",
231
    {
232
        "classes": ("",),
233
        "fields": MODEL_Z_LICZBA_ZNAKOW_WYDAWNICZYCH
234
        + MODEL_Z_INFORMACJA_Z
235
        + MODEL_ZE_STATUSEM
236
        + MODEL_RECENZOWANY,
237
    },
238
)
239

240
POZOSTALE_MODELE_WYDAWNICTWO_ZWARTE_FIELDSET = (
3✔
241
    "Pozostałe informacje",
242
    {
243
        "classes": ("",),
244
        "fields": MODEL_Z_LICZBA_ZNAKOW_WYDAWNICZYCH
245
        + MODEL_Z_INFORMACJA_Z
246
        + MODEL_ZE_STATUSEM
247
        + MODEL_RECENZOWANY,
248
    },
249
)
250

251
SERIA_WYDAWNICZA_FIELDSET = (
3✔
252
    "Seria wydawnicza",
253
    {
254
        "classes": ("grp-collapse grp-closed",),
255
        "fields": ("seria_wydawnicza", "numer_w_serii"),
256
    },
257
)
258

259
PRACA_WYBITNA_FIELDSET = (
3✔
260
    "Praca wybitna",
261
    {
262
        "classes": ("grp-collapse grp-closed",),
263
        "fields": ("praca_wybitna", "uzasadnienie_wybitnosci"),
264
    },
265
)
266

267
PRZED_PO_LISCIE_AUTOROW_FIELDSET = (
3✔
268
    "Dowolny tekst przed lub po liście autorów",
269
    {
270
        "classes": ("grp-collapse grp-closed",),
271
        "fields": ("tekst_przed_pierwszym_autorem", "tekst_po_ostatnim_autorze"),
272
    },
273
)
274

275
EKSTRA_INFORMACJE_WYDAWNICTWO_CIAGLE_FIELDSET = (
3✔
276
    "Ekstra informacje",
277
    {
278
        "classes": ("grp-collapse grp-closed",),
279
        "fields": MODEL_Z_PBN_UID
280
        + MODEL_Z_ISSN
281
        + MODEL_Z_WWW
282
        + MODEL_Z_PUBMEDID
283
        + MODEL_Z_DOI
284
        + MODEL_Z_LICZBA_CYTOWAN
285
        + MODEL_Z_MIEJSCEM_PRZECHOWYWANIA,
286
    },
287
)
288

289
EKSTRA_INFORMACJE_WYDAWNICTWO_ZWARTE_FIELDSET = (
3✔
290
    "Ekstra informacje",
291
    {
292
        "classes": ("grp-collapse grp-closed",),
293
        "fields": MODEL_Z_PBN_UID
294
        + MODEL_Z_ISSN
295
        + MODEL_Z_WWW
296
        + MODEL_Z_PUBMEDID
297
        + MODEL_Z_DOI
298
        + MODEL_Z_LICZBA_CYTOWAN
299
        + MODEL_Z_MIEJSCEM_PRZECHOWYWANIA,
300
    },
301
)
302

303
EKSTRA_INFORMACJE_DOKTORSKA_HABILITACYJNA_FIELDSET = (
3✔
304
    "Ekstra informacje",
305
    {
306
        "classes": ("grp-collapse grp-closed",),
307
        "fields": MODEL_Z_WWW
308
        + MODEL_Z_PUBMEDID
309
        + MODEL_Z_DOI
310
        + MODEL_Z_LICZBA_CYTOWAN
311
        + MODEL_Z_MIEJSCEM_PRZECHOWYWANIA,
312
    },
313
)
314

315

316
def js_openwin(url, handle, options):
3✔
317
    options = ",".join([f"{a}={b}" for a, b in list(options.items())])
×
318
    d = dict(url=url, handle=handle, options=options)
×
319
    return "window.open('%(url)s','\\%(handle)s','%(options)s')" % d
×
320

321

322
NIZSZE_TEXTFIELD_Z_MAPA_ZNAKOW = {
3✔
323
    models.TextField: {
324
        "widget": Textarea(attrs={"rows": 2, "cols": 90, "class": "charmap"})
325
    },
326
}
327

328

329
class DomyslnyStatusKorektyMixin:
3✔
330
    status_korekty = forms.ModelChoiceField(
3✔
331
        required=True,
332
        queryset=Status_Korekty.objects.all(),
333
        initial=lambda: Status_Korekty.objects.first(),
334
    )
335

336

337
class Wycinaj_W_z_InformacjiMixin:
3✔
338
    def clean_informacje(self):
3✔
339
        i = self.cleaned_data.get("informacje")
2✔
340
        if i:
2✔
341
            x = i.lower()
×
342
            n = 0
×
343
            if x.startswith("w:"):
×
344
                n = 2
×
345
            if x.startswith("w :"):
×
346
                n = 3
×
347
            if n:
×
348
                return i[n:].strip()
×
349
        return i
2✔
350

351

352
class LimitingFormset(BaseInlineFormSet):
3✔
353
    def get_queryset(self):
3✔
354
        if not hasattr(self, "_queryset_limited"):
×
355
            qs = super().get_queryset()
×
356
            self._queryset_limited = qs[:100]
×
357
        return self._queryset_limited
×
358

359

360
def link_do_obiektu(obj, friendly_name=None):
3✔
361
    opts = obj._meta
2✔
362
    obj_url = reverse(
2✔
363
        f"admin:{opts.app_label}_{opts.model_name}_change",
364
        args=(quote(obj.pk),),
365
        # current_app=self.admin_site.name,
366
    )
367
    # Add a link to the object's change form if the user can edit the obj.
368
    if friendly_name is None:
2✔
369
        friendly_name = mark_safe(obj)
2✔
370
    return format_html('<a href="{}">{}</a>', urlquote(obj_url), friendly_name)
2✔
371

372

373
def sprobuj_policzyc_sloty(request, obj):
3✔
374
    if obj.rok >= PBN_MIN_ROK and obj.rok <= PBN_MAX_ROK:
2✔
375
        try:
1✔
376
            ISlot(obj)
1✔
377
            messages.success(
×
378
                request,
379
                'Punkty dla dyscyplin dla "%s" będą mogły być obliczone.'
380
                % link_do_obiektu(obj),
381
            )
382
        except CannotAdapt as e:
1✔
383
            messages.error(
1✔
384
                request,
385
                'Nie można obliczyć punktów dla dyscyplin dla "%s": %s'
386
                % (link_do_obiektu(obj), e),
387
            )
388
    else:
389
        messages.warning(
1✔
390
            request,
391
            'Punkty dla dyscyplin dla "%s" nie będą liczone - rok poza zakresem (%i)'
392
            % (link_do_obiektu(obj), obj.rok),
393
        )
394

395

396
def sprawdz_czy_ustawiono_wysylke_tego_charakteru_formalnego(charakter_formalny):
3✔
NEW
397
    if charakter_formalny.rodzaj_pbn is None:
×
NEW
398
        raise CharakterFormalnyNieobslugiwanyError(
×
399
            "ten rekord nie może być wyeksportowany do PBN, gdyż ustawienia jego charakteru formalnego "
400
            "po stronie bazy BPP na to nie pozwalają"
401
        )
402

403

404
def sprawdz_czy_ustawiono_wysylke_tego_charakteru_formalnego_gui(request, obj):
3✔
NEW
405
    try:
×
NEW
406
        sprawdz_czy_ustawiono_wysylke_tego_charakteru_formalnego(obj.charakter_formalny)
×
NEW
407
    except CharakterFormalnyNieobslugiwanyError:
×
UNCOV
408
        messages.info(
×
409
            request,
410
            'Rekord "%s" nie będzie eksportowany do PBN zgodnie z ustawieniem dla charakteru formalnego.'
411
            % link_do_obiektu(obj),
412
        )
NEW
413
        return False
×
NEW
414
    return True
×
415

416

417
def sprawdz_wysylke_do_pbn_w_parametrach_uczelni(uczelnia):
3✔
418
    """
419
    :param uczelnia:
420
    :return: zwraca False jeżeli integracja wyłączona lub aktualizowanie na bieżąco wyłączone;
421
    zwraca obiekt uczelnia jeżeli jest OK,
422
    podnosi wyjątek jeżeli brak obiektu Uczelnia
423
    """
424
    if uczelnia is None:
×
NEW
425
        raise BrakZdefiniowanegoObiektuUczelniaWSystemieError()
×
426

NEW
427
    if not uczelnia.pbn_integracja or not uczelnia.pbn_aktualizuj_na_biezaco:
×
NEW
428
        return False
×
429

NEW
430
    return uczelnia
×
431

432

433
def sprawdz_wysylke_do_pbn_w_parametrach_uczelni_gui(request, obj):
3✔
NEW
434
    from bpp.models.uczelnia import Uczelnia
×
435

NEW
436
    uczelnia = Uczelnia.objects.get_for_request(request)
×
NEW
437
    try:
×
NEW
438
        res = sprawdz_wysylke_do_pbn_w_parametrach_uczelni(uczelnia)
×
NEW
439
    except BrakZdefiniowanegoObiektuUczelniaWSystemieError:
×
UNCOV
440
        messages.info(
×
441
            request,
442
            'Rekord "%s" nie zostanie wyeksportowany do PBN, ponieważ w systemie brakuje obiektu "Uczelnia", a'
443
            " co za tym idzie, jakchkolwiek ustawień" % link_do_obiektu(obj),
444
        )
445
        return
×
446

NEW
447
    return res
×
448

449

450
def sprobuj_utworzyc_zlecenie_eksportu_do_PBN_gui(request, obj):
3✔
NEW
451
    if not sprawdz_czy_ustawiono_wysylke_tego_charakteru_formalnego_gui(request, obj):
×
NEW
452
        return
×
453

NEW
454
    try:
×
NEW
455
        res = sprawdz_wysylke_do_pbn_w_parametrach_uczelni_gui(request, obj)
×
NEW
456
    except BrakZdefiniowanegoObiektuUczelniaWSystemieError:
×
NEW
457
        messages.error("Brak zdefiniowanego w systemie obiektu Uczelnia.")
×
458

NEW
459
    if res is False:
×
NEW
460
        messages.error("Wysyłka do PBN nie skonfigurowana w obiektu Uczelnia.")
×
461

NEW
462
    try:
×
NEW
463
        ret = PBN_Export_Queue.objects.sprobuj_utowrzyc_wpis(request.user, obj)
×
NEW
464
    except AlreadyEnqueuedError:
×
NEW
465
        messages.warning(
×
466
            request, f"Rekord {obj} jest już w kolejce do eksportu do PBN."
467
        )
NEW
468
        return
×
469

NEW
470
    link_do_kolejki = reverse("admin:pbn_api_pbn_export_queue_change", args=(ret.pk,))
×
471

NEW
472
    messages.info(
×
473
        request,
474
        f"Utworzono zlecenie wysyłki rekordu {obj} w tle do PBN. <a href={link_do_kolejki}>"
475
        f"Kliknij tutaj, aby śledzić stan.</a>",
476
    )
477

NEW
478
    task_sprobuj_wyslac_do_pbn.delay_on_commit(ret.pk)
×
479

480

481
class TextNotificator:
3✔
482
    def __init__(self, *args, **kw):
3✔
NEW
483
        self.output = []
×
484

485
    def info(self, msg, level="INFO"):
3✔
NEW
486
        self.output.append(f"{level} {msg}")
×
487

488
    def warning(self, msg):
3✔
NEW
489
        self.info(msg, level="WARNING")
×
490

491
    def error(self, msg):
3✔
NEW
492
        self.info(msg, level="WARNING")
×
493

494
    def success(self, msg):
3✔
NEW
495
        self.info(msg, level="WARNING")
×
496

497
    def as_text(self):
3✔
NEW
498
        return "\n".join(self.output)
×
499

500

501
class MessagesNotificator:
3✔
502
    def __init__(self, request):
3✔
NEW
503
        self.request = request
×
504

505
    def info(self, msg):
3✔
NEW
506
        messages.info(self.request, msg)
×
507

508
    def warning(self, msg):
3✔
NEW
509
        messages.warning(self.request, msg)
×
510

511
    def error(self, msg):
3✔
NEW
512
        messages.error(self.request, msg)
×
513

514
    def success(self, msg):
3✔
NEW
515
        messages.success(self.request, msg)
×
516

517

518
def sprobuj_wgrac_do_pbn_gui(request, obj, force_upload=False, pbn_client=None):
3✔
NEW
519
    if not sprawdz_czy_ustawiono_wysylke_tego_charakteru_formalnego_gui(request, obj):
×
NEW
520
        return
×
521

NEW
522
    uczelnia = sprawdz_wysylke_do_pbn_w_parametrach_uczelni_gui(request, obj)
×
NEW
523
    if uczelnia is None:
×
NEW
524
        return
×
525

NEW
526
    if uczelnia is False:
×
NEW
527
        messages.error(request, "Wysyłka do PBN nie skonfigurowana w obiektu Uczelnia.")
×
UNCOV
528
        return
×
529

530
    if pbn_client is None:
×
531
        pbn_client = uczelnia.pbn_client(request.user.pbn_token)
×
532

NEW
533
    sprobuj_wgrac_do_pbn(
×
534
        obj=obj,
535
        uczelnia=uczelnia,
536
        force_upload=force_upload,
537
        pbn_client=pbn_client,
538
        notificator=MessagesNotificator(request),
539
    )
540

541

542
def sprobuj_wgrac_do_pbn_celery(user, obj, force_upload=False, pbn_client=None):
3✔
NEW
543
    try:
×
NEW
544
        sprawdz_czy_ustawiono_wysylke_tego_charakteru_formalnego(obj.charakter_formalny)
×
NEW
545
    except CharakterFormalnyNieobslugiwanyError:
×
NEW
546
        return (
×
547
            None,
548
            [
549
                "Charakter formalny tego rekordu nie jest ustawiony jako wysyłany do PBN. Zmień konfigurację "
550
                "bazy BPP, Redagowanie -> Dane systemowe -> Charaktery formalne"
551
            ],
552
        )
553

NEW
554
    try:
×
NEW
555
        uczelnia = sprawdz_wysylke_do_pbn_w_parametrach_uczelni(
×
556
            Uczelnia.objects.get_default()
557
        )
NEW
558
    except BrakZdefiniowanegoObiektuUczelniaWSystemieError:
×
NEW
559
        return (None, ["W systemie brak obiektu Uczelnia."])
×
560

NEW
561
    if uczelnia is False:
×
NEW
562
        return (None, ["Wysyłka do PBN nie skonfigurowana w obiekcie Uczelnia"])
×
563

NEW
564
    if uczelnia is None:
×
NEW
565
        return (None, ["Brak obiektu uczelnia w systemie"])
×
566

NEW
567
    if user.pbn_token is None:
×
NEW
568
        return (
×
569
            None,
570
            [
571
                "Nie masz autoryzacji w PBN. Wykonaj autoryzację w PBN przez główną stronę serwisu. "
572
            ],
573
        )
574

NEW
575
    if pbn_client is None:
×
NEW
576
        pbn_client = uczelnia.pbn_client(user.pbn_token)
×
577

NEW
578
    try:
×
NEW
579
        pbn_client.get_institution_by_id(uczelnia.pbn_uid_id)
×
NEW
580
    except AccessDeniedException:
×
NEW
581
        return (
×
582
            None,
583
            [
584
                "Nie masz autoryzacji w PBN. Wykonaj autoryzację w PBN przez główną stronę serwisu. "
585
            ],
586
        )
587

NEW
588
    notificator = TextNotificator()
×
589

NEW
590
    sent_data = sprobuj_wgrac_do_pbn(
×
591
        obj=obj,
592
        uczelnia=uczelnia,
593
        force_upload=force_upload,
594
        pbn_client=pbn_client,
595
        notificator=notificator,
596
    )
597

NEW
598
    return (sent_data, notificator)
×
599

600

601
def sprobuj_wgrac_do_pbn(obj, pbn_client, uczelnia, notificator, force_upload=False):
3✔
602

603
    # Sprawdź, czy wydawnictwo nadrzędne ma odpowoednik PBN:
604
    if (
×
605
        hasattr(obj, "wydawnictwo_nadrzedne_id")
606
        and obj.wydawnictwo_nadrzedne_id is not None
607
    ):
608
        wn = obj.wydawnictwo_nadrzedne
×
609
        if wn.pbn_uid_id is None:
×
NEW
610
            notificator.info(
×
611
                "Wygląda na to, że wydawnictwo nadrzędne tego rekordu nie posiada odpowiednika "
612
                "w PBN, spróbuję go pobrać.",
613
            )
614
            udalo = False
×
615
            if wn.isbn:
×
616
                ni = normalize_isbn(wn.isbn)
×
617
                if ni:
×
618
                    from pbn_api.integrator import _pobierz_prace_po_elemencie
×
619

620
                    res = None
×
621
                    try:
×
622
                        res = _pobierz_prace_po_elemencie(pbn_client, "isbn", ni)
×
623
                    except PraceSerwisoweException:
×
NEW
624
                        notificator.warning(
×
625
                            "Pobieranie z PBN odpowiednika wydawnictwa nadrzędnego pracy po ISBN nie powiodło się "
626
                            "-- trwają prace serwisowe po stronie PBN. ",
627
                        )
628
                    except NeedsPBNAuthorisationException:
×
NEW
629
                        notificator.warning(
×
630
                            "Wyszukanie PBN UID wydawnictwa nadrzędnego po ISBN nieudane - "
631
                            "autoryzuj się najpierw w PBN. ",
632
                        )
633

634
                    if res:
×
NEW
635
                        notificator.info(
×
636
                            f"Udało się dopasować PBN UID wydawnictwa nadrzędnego po ISBN "
637
                            f"({', '.join([x.tytul_oryginalny for x in res])}). ",
638
                        )
639
                        udalo = True
×
640

641
            elif wn.doi:
×
642
                nd = normalize_isbn(wn.doi)
×
643
                if nd:
×
644
                    from pbn_api.integrator import _pobierz_prace_po_elemencie
×
645

646
                    res = None
×
647
                    try:
×
648
                        res = _pobierz_prace_po_elemencie(pbn_client, "doi", nd)
×
649
                    except PraceSerwisoweException:
×
NEW
650
                        notificator.warning(
×
651
                            "Pobieranie z PBN odpowiednika wydawnictwa nadrzędnego pracy po DOI nie powiodło się "
652
                            "-- trwają prace serwisowe po stronie PBN. ",
653
                        )
654
                    except NeedsPBNAuthorisationException:
×
NEW
655
                        notificator.warning(
×
656
                            "Wyszukanie PBN UID wydawnictwa nadrzędnego po DOI nieudane - "
657
                            "autoryzuj się najpierw w PBN. ",
658
                        )
659

660
                    if res:
×
NEW
661
                        notificator.info(
×
662
                            f"Udało się dopasować PBN UID wydawnictwa nadrzędnego po DOI. "
663
                            f"({', '.join([x.tytul_oryginalny for x in res])}). ",
664
                        )
665
                        udalo = True
×
666

667
            if not udalo:
×
NEW
668
                notificator.warning(
×
669
                    "Wygląda na to, że nie udało się dopasować rekordu nadrzędnego po ISBN/DOI do rekordu "
670
                    "po stronie PBN. Jeżeli jednak dokonano autoryzacji w PBN, to pewne rekordy z PBN "
671
                    "zostały teraz pobrane i możesz spróbować ustawić odpowiednik "
672
                    "PBN dla wydawnictwa nadrzędnego ręcznie. ",
673
                )
674

675
    try:
×
676
        pbn_client.sync_publication(
×
677
            obj,
678
            force_upload=force_upload,
679
            delete_statements_before_upload=uczelnia.pbn_api_kasuj_przed_wysylka,
680
            export_pk_zero=not uczelnia.pbn_api_nie_wysylaj_prac_bez_pk,
681
            always_affiliate_to_uid=(
682
                uczelnia.pbn_uid_id
683
                if uczelnia.pbn_api_afiliacja_zawsze_na_uczelnie
684
                else None
685
            ),
686
        )
687

688
    except SameDataUploadedRecently as e:
×
689
        link_do_wyslanych = reverse(
×
690
            "admin:pbn_api_sentdata_change",
691
            args=(SentData.objects.get_for_rec(obj).pk,),
692
        )
693

NEW
694
        notificator.info(
×
695
            f'Identyczne dane rekordu "{link_do_obiektu(obj)}" zostały wgrane do PBN w dniu {e}. '
696
            f"Nie aktualizuję w PBN API. Jeżeli chcesz wysłać ten rekord do PBN, musisz dokonać jakiejś zmiany "
697
            f"danych rekodu lub "
698
            f'usunąć informacje o <a target=_blank href="{link_do_wyslanych}">wcześniej wysłanych danych do PBN</a> '
699
            f"(Redagowanie -> PBN API -> Wysłane informacje). "
700
            f'<a target=_blank href="{obj.link_do_pbn()}">Kliknij tutaj, aby otworzyć w PBN</a>. ',
701
        )
702
        return
×
703

704
    except AccessDeniedException as e:
×
NEW
705
        notificator.warning(
×
706
            f'Nie można zsynchronizować obiektu "{link_do_obiektu(obj)}" z PBN pod adresem '
707
            f"API {e.url}. Brak dostępu -- najprawdopodobniej użytkownik nie posiada odpowiednich uprawnień "
708
            f"po stronie PBN/POLON. ",
709
        )
710
        return
×
711

712
    except PKZeroExportDisabled:
×
NEW
713
        notificator.warning(
×
714
            f"Eksport prac z PK=0 jest wyłączony w konfiguracji. Próba wysyłki do PBN rekordu "
715
            f'"{link_do_obiektu(obj)}" nie została podjęta z uwagi na konfigurację systemu. ',
716
        )
717
        return
×
718

719
    except NeedsPBNAuthorisationException as e:
×
NEW
720
        notificator.warning(
×
721
            f'Nie można zsynchronizować obiektu "{link_do_obiektu(obj)}" z PBN pod adresem '
722
            f"API {e.url}. Brak dostępu z powodu nieprawidłowego tokena -- wymagana autoryzacja w PBN. "
723
            f'<a target=_blank href="{reverse("pbn_api:authorize")}">Kliknij tutaj, aby autoryzować sesję</a>.',
724
        )
725
        return
×
726

727
    except Exception as e:
×
728
        try:
×
729
            link_do_wyslanych = reverse(
×
730
                "admin:pbn_api_sentdata_change",
731
                args=(SentData.objects.get_for_rec(obj).pk,),
732
            )
733
        except SentData.DoesNotExist:
×
734
            link_do_wyslanych = None
×
735

736
        extra = ""
×
737
        if link_do_wyslanych:
×
738
            extra = (
×
739
                '<a target=_blank href="%s">Kliknij, aby otworzyć widok wysłanych danych</a>.'
740
                % link_do_wyslanych
741
            )
742

NEW
743
        notificator.warning(
×
744
            'Nie można zsynchronizować obiektu "%s" z PBN. Kod błędu: %r. %s'
745
            % (link_do_obiektu(obj), e, extra),
746
        )
747

748
        # Zaloguj problem do Sentry, bo w sumie nie wiadomo, co to za problem na tym etapie...
749
        capture_exception(e)
×
750

751
        return
×
752

753
    sent_data = SentData.objects.get(
×
754
        content_type=ContentType.objects.get_for_model(obj), object_id=obj.pk
755
    )
756
    sent_data_link = link_do_obiektu(
×
757
        sent_data, "Kliknij tutaj, aby otworzyć widok wysłanych danych. "
758
    )
759

760
    publication_link = link_do_obiektu(
×
761
        sent_data.pbn_uid,
762
        "Kliknij tutaj, aby otworzyć zwrotnie otrzymane z PBN dane o rekordzie. ",
763
    )
NEW
764
    notificator.success(
×
765
        f"Dane w PBN dla rekordu {link_do_obiektu(obj)} zostały zaktualizowane. "
766
        f'<a target=_blank href="{obj.link_do_pbn()}">Kliknij tutaj, aby otworzyć w PBN</a>. '
767
        f"{sent_data_link}{publication_link}",
768
    )
769

NEW
770
    return sent_data
×
771

772

773
def get_rekord_id_from_GET_qs(request):
3✔
774
    flt = request.GET.get("_changelist_filters", "?")
3✔
775
    data = parse_qs(flt)  # noqa
3✔
776
    if "rekord__id__exact" in data:
3✔
777
        try:
3✔
778
            return int(data.get("rekord__id__exact")[0])
3✔
779
        except (ValueError, TypeError):
×
780
            pass
×
781

782

783
class OptionalPBNSaveMixin:
3✔
784
    def render_change_form(
3✔
785
        self, request, context, add=False, change=False, form_url="", obj=None
786
    ):
787
        from bpp.models import Uczelnia
3✔
788

789
        uczelnia = Uczelnia.objects.get_default()
3✔
790
        if uczelnia is not None:
3✔
791
            if uczelnia.pbn_integracja and uczelnia.pbn_aktualizuj_na_biezaco:
2✔
792
                context.update({"show_save_and_pbn": True})
×
793

794
        return super().render_change_form(request, context, add, change, form_url, obj)
3✔
795

796
    def response_post_save_change(self, request, obj):
3✔
797
        if "_continue_and_pbn" in request.POST:
×
NEW
798
            sprobuj_wgrac_do_pbn_gui(request, obj)
×
799

NEW
800
        elif "_continue_and_pbn_later" in request.POST:
×
NEW
801
            sprobuj_utworzyc_zlecenie_eksportu_do_PBN_gui(request, obj)
×
802

803
        else:
804
            # Otherwise, use default behavior
805
            return super().response_post_save_change(request, obj)
×
806

807
        # Przekieruj użytkownika na formularz zmian
NEW
808
        opts = self.model._meta
×
NEW
809
        route = f"admin:{opts.app_label}_{opts.model_name}_change"
×
810

NEW
811
        post_url = reverse(route, args=(obj.pk,))
×
812

NEW
813
        from django.http import HttpResponseRedirect
×
814

NEW
815
        return HttpResponseRedirect(post_url)
×
816

817

818
def sprawdz_duplikaty_www_doi(request, obj):
3✔
819
    from bpp.models.cache import Rekord
2✔
820

821
    IEXACT = "__iexact"
2✔
822
    for field, operator, label in [
2✔
823
        ("www", IEXACT, const.WWW_FIELD_LABEL),
824
        ("public_www", IEXACT, const.PUBLIC_WWW_FIELD_LABEL),
825
        ("doi", IEXACT, const.DOI_FIELD_LABEL),
826
        ("pbn_uid_id", "", const.PBN_UID_FIELD_LABEL),
827
    ]:
828
        if not hasattr(obj, field):
2✔
829
            continue
×
830

831
        v = getattr(obj, field)
2✔
832
        if v in [None, ""]:
2✔
833
            continue
2✔
834

835
        rekord_pk = (ContentType.objects.get_for_model(obj).pk, obj.pk)
×
836

837
        query = Q(**{field + operator: v})
×
838

839
        if field == "www":
×
840
            query |= Q(public_www__iexact=v)
×
841
        elif field == "public_www":
×
842
            query |= Q(www__iexact=v)
×
843

844
        if Rekord.objects.filter(query).exclude(pk=rekord_pk).exists():
×
845
            messages.warning(
×
846
                request, const.ZDUBLOWANE_POLE_KOMUNIKAT.format(label=label)
847
            )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc