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

iplweb / bpp / 58b9a630-8512-44e6-b730-daac96d1c4d7

29 Aug 2025 07:21AM UTC coverage: 47.493% (+2.5%) from 45.008%
58b9a630-8512-44e6-b730-daac96d1c4d7

push

circleci

mpasternak
Fix tests

6 of 27 new or added lines in 2 files covered. (22.22%)

1342 existing lines in 64 files now uncovered.

19323 of 40686 relevant lines covered (47.49%)

1.51 hits per line

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

69.96
src/bpp/models/abstract.py
1
"""
2
Klasy abstrakcyjne
3
"""
4

5
import re
4✔
6
from decimal import Decimal
4✔
7

8
from django.conf import settings
4✔
9
from django.core.exceptions import ValidationError
4✔
10
from django.core.validators import MinValueValidator, URLValidator
4✔
11
from django.db import models
4✔
12
from django.db.models import CASCADE, SET_NULL, Q, Sum
4✔
13
from django.urls.base import reverse
4✔
14
from taggit.managers import TaggableManager
4✔
15

16
from django.contrib.postgres.fields import ArrayField, HStoreField
4✔
17
from django.contrib.postgres.search import SearchVectorField as VectorField
4✔
18

19
from bpp import const
4✔
20
from bpp.fields import DOIField, YearField
4✔
21
from bpp.models.dyscyplina_naukowa import Autor_Dyscyplina, Dyscyplina_Naukowa
4✔
22
from bpp.models.util import ModelZOpisemBibliograficznym, dodaj_autora
4✔
23
from bpp.util import safe_html
4✔
24

25
ILOSC_ZNAKOW_NA_ARKUSZ = 40000.0
4✔
26

27

28
def get_liczba_arkuszy_wydawniczych(liczba_znakow_wydawniczych):
4✔
29
    return round(liczba_znakow_wydawniczych / ILOSC_ZNAKOW_NA_ARKUSZ, 2)
×
30

31

32
class ModelZeZnakamiWydawniczymi(models.Model):
4✔
33
    liczba_znakow_wydawniczych = models.IntegerField(
4✔
34
        "Liczba znaków wydawniczych", blank=True, null=True, db_index=True
35
    )
36

37
    def ma_wymiar_wydawniczy(self):
4✔
38
        return self.liczba_znakow_wydawniczych is not None
×
39

40
    def wymiar_wydawniczy_w_arkuszach(self):
4✔
41
        return "%.2f" % get_liczba_arkuszy_wydawniczych(self.liczba_znakow_wydawniczych)
×
42

43
    class Meta:
4✔
44
        abstract = True
4✔
45

46

47
class ModelZAdnotacjami(models.Model):
4✔
48
    """Zawiera adnotację  dla danego obiektu, czyli informacje, które
49
    użytkownik może sobie dowolnie uzupełnić.
50
    """
51

52
    ostatnio_zmieniony = models.DateTimeField(auto_now=True, null=True, db_index=True)
4✔
53

54
    adnotacje = models.TextField(
4✔
55
        help_text="""Pole do użytku wewnętrznego -
56
        wpisane tu informacje nie są wyświetlane na stronach WWW dostępnych
57
        dla użytkowników końcowych.""",
58
        default="",
59
        blank=True,
60
        null=False,
61
        db_index=True,
62
    )
63

64
    class Meta:
4✔
65
        abstract = True
4✔
66

67

68
class ModelZPBN_ID(models.Model):
4✔
69
    """Zawiera informacje o PBN_ID"""
70

71
    pbn_id = models.IntegerField(
4✔
72
        verbose_name="[Przestarzałe] Identyfikator PBN",
73
        help_text="[Pole o znaczeniu historycznym] Identyfikator w systemie Polskiej Bibliografii Naukowej (PBN)",
74
        null=True,
75
        blank=True,
76
        unique=True,
77
        db_index=True,
78
    )
79

80
    class Meta:
4✔
81
        abstract = True
4✔
82

83

84
class ModelZNazwa(models.Model):
4✔
85
    """Nazwany model."""
86

87
    nazwa = models.CharField(max_length=512, unique=True)
4✔
88

89
    def __str__(self):
4✔
90
        return self.nazwa
2✔
91

92
    class Meta:
4✔
93
        abstract = True
4✔
94
        ordering = ["nazwa"]
4✔
95

96

97
class NazwaISkrot(ModelZNazwa):
4✔
98
    """Model z nazwą i ze skrótem"""
99

100
    skrot = models.CharField(max_length=128, unique=True)
4✔
101

102
    class Meta:
4✔
103
        abstract = True
4✔
104

105

106
class NazwaWDopelniaczu(models.Model):
4✔
107
    nazwa_dopelniacz_field = models.CharField(
4✔
108
        "Nazwa w dopełniaczu", max_length=512, null=True, blank=True
109
    )
110

111
    class Meta:
4✔
112
        abstract = True
4✔
113

114
    def nazwa_dopelniacz(self):
4✔
115
        if not hasattr(self, "nazwa"):
×
116
            return self.nazwa_dopelniacz_field
×
117
        if self.nazwa_dopelniacz_field is None or self.nazwa_dopelniacz_field == "":
×
118
            return self.nazwa
×
119
        return self.nazwa_dopelniacz_field
×
120

121

122
class ModelZISSN(models.Model):
4✔
123
    """Model z numerem ISSN oraz E-ISSN"""
124

125
    issn = models.CharField("ISSN", max_length=32, blank=True, null=True)
4✔
126
    e_issn = models.CharField("e-ISSN", max_length=32, blank=True, null=True)
4✔
127

128
    class Meta:
4✔
129
        abstract = True
4✔
130

131

132
class ModelZISBN(models.Model):
4✔
133
    """Model z numerem ISBN oraz E-ISBN"""
134

135
    isbn = models.CharField("ISBN", max_length=64, blank=True, null=True, db_index=True)
4✔
136
    e_isbn = models.CharField(
4✔
137
        "E-ISBN", max_length=64, blank=True, null=True, db_index=True
138
    )
139

140
    class Meta:
4✔
141
        abstract = True
4✔
142

143

144
class ModelZInformacjaZ(models.Model):
4✔
145
    """Model zawierający pole 'Informacja z' - czyli od kogo została
146
    dostarczona informacja o publikacji (np. od autora, od redakcji)."""
147

148
    informacja_z = models.ForeignKey(
4✔
149
        "Zrodlo_Informacji", SET_NULL, null=True, blank=True
150
    )
151

152
    class Meta:
4✔
153
        abstract = True
4✔
154

155

156
class DwaTytuly(models.Model):
4✔
157
    """Model zawierający dwa tytuły: tytuł oryginalny pracy oraz tytuł
158
    przetłumaczony."""
159

160
    tytul_oryginalny = models.TextField("Tytuł oryginalny", db_index=True)
4✔
161
    tytul = models.TextField("Tytuł", null=True, blank=True, db_index=True)
4✔
162

163
    def clean(self):
4✔
164
        self.tytul_oryginalny = safe_html(self.tytul_oryginalny)
2✔
165
        self.tytul = safe_html(self.tytul)
2✔
166

167
    class Meta:
4✔
168
        abstract = True
4✔
169

170

171
class ModelZeStatusem(models.Model):
4✔
172
    """Model zawierający pole statusu korekty, oraz informację, czy
173
    punktacja została zweryfikowana."""
174

175
    status_korekty = models.ForeignKey("Status_Korekty", CASCADE)
4✔
176

177
    class Meta:
4✔
178
        abstract = True
4✔
179

180

181
class ModelZAbsolutnymUrl:
4✔
182
    def get_absolute_url(self):
4✔
183
        from django.contrib.contenttypes.models import ContentType
×
184

185
        if hasattr(self, "slug") and self.slug:
×
186
            return reverse("bpp:browse_praca_by_slug", args=(self.slug,))
×
187

188
        return reverse(
×
189
            "bpp:browse_praca",
190
            args=(ContentType.objects.get_for_model(self).pk, self.pk),
191
        )
192

193

194
class ModelZRokiem(models.Model):
4✔
195
    """Model zawierający pole "Rok" """
196

197
    rok = YearField(
4✔
198
        help_text="""Rok uwzględniany przy wyszukiwaniu i raportach
199
        KBN/MNiSW)""",
200
        db_index=True,
201
        validators=[MinValueValidator(0)],
202
    )
203

204
    class Meta:
4✔
205
        abstract = True
4✔
206

207

208
def nie_zawiera_adresu_doi_org(v):
4✔
209
    if v is None:
2✔
210
        return
2✔
211
    v = v.lower().strip()
×
212
    if v.find("doi.org") >= 0:
×
213
        raise ValidationError(
×
214
            "Pole nie powinno zawierać odnośnika do serwisu doi.org. Identyfikator DOI wpisz "
215
            "do innego pola. Odnosniki do serwisu DOI, zawierające w swojej tresci numer DOI "
216
            "są już tworzone przez system. "
217
        )
218

219

220
class ModelZWWW(models.Model):
4✔
221
    """Model zawierający adres strony WWW"""
222

223
    www = models.URLField(
4✔
224
        const.WWW_FIELD_LABEL,
225
        max_length=1024,
226
        blank=True,
227
        null=True,
228
    )
229
    dostep_dnia = models.DateField(
4✔
230
        "Dostęp dnia (płatny dostęp)",
231
        blank=True,
232
        null=True,
233
        help_text="""Data dostępu do strony WWW.""",
234
    )
235

236
    public_www = models.URLField(
4✔
237
        const.PUBLIC_WWW_FIELD_LABEL,
238
        max_length=2048,
239
        blank=True,
240
        null=True,
241
    )
242
    public_dostep_dnia = models.DateField(
4✔
243
        "Dostęp dnia (wolny dostęp)",
244
        blank=True,
245
        null=True,
246
        help_text="""Data wolnego dostępu do strony WWW.""",
247
    )
248

249
    class Meta:
4✔
250
        abstract = True
4✔
251

252

253
class ModelZPubmedID(models.Model):
4✔
254
    pubmed_id = models.BigIntegerField(
4✔
255
        "PubMed ID", blank=True, null=True, help_text="Identyfikator PubMed (PMID)"
256
    )
257
    pmc_id = models.CharField("PubMed Central ID", max_length=32, blank=True, null=True)
4✔
258

259
    class Meta:
4✔
260
        abstract = True
4✔
261

262

263
def nie_zawiera_http_https(v):
4✔
264
    if v is None:
2✔
265
        return
2✔
266
    v = v.lower().strip()
×
267
    if v.startswith("http") or v.startswith("https") or v.find("doi.org") >= 0:
×
268
        raise ValidationError(
×
269
            "Pole nie powinno zawierać adresu URL (adresu strony WWW) - wpisz tu wyłącznie "
270
            "identyfikator DOI; lokalizacją położenia rekordu czyli translacją adresu DOI "
271
            "na adres URL zajmuje się usługa serwisu doi.org i nie ma potrzeby, aby go "
272
            "tu wpisywać. "
273
        )
274

275

276
class ModelZDOI(models.Model):
4✔
277
    doi = DOIField(const.DOI_FIELD_LABEL, null=True, blank=True, db_index=True)
4✔
278

279
    class Meta:
4✔
280
        abstract = True
4✔
281

282

283
class ModelRecenzowany(models.Model):
4✔
284
    """Model zawierający informacje o afiliowaniu/recenzowaniu pracy."""
285

286
    recenzowana = models.BooleanField(default=False, db_index=True)
4✔
287

288
    class Meta:
4✔
289
        abstract = True
4✔
290

291

292
IF_MAX_DIGITS = 6
4✔
293
IF_DECIMAL_PLACES = 3
4✔
294

295

296
def ImpactFactorField(*args, **kw):
4✔
297
    return models.DecimalField(
4✔
298
        max_digits=IF_MAX_DIGITS,
299
        decimal_places=IF_DECIMAL_PLACES,
300
        default=Decimal("0.000"),
301
        *args,
302
        **kw,
303
    )
304

305

306
class ModelPunktowanyBaza(models.Model):
4✔
307
    impact_factor = ImpactFactorField(
4✔
308
        db_index=True,
309
    )
310
    punkty_kbn = models.DecimalField(
4✔
311
        "punkty MNiSW/MEiN",
312
        max_digits=6,
313
        decimal_places=2,
314
        default=Decimal("0.00"),
315
        db_index=True,
316
    )
317
    index_copernicus = models.DecimalField(
4✔
318
        "Index Copernicus",
319
        max_digits=6,
320
        decimal_places=2,
321
        default=Decimal("0.00"),
322
        db_index=True,
323
    )
324
    punktacja_wewnetrzna = models.DecimalField(
4✔
325
        "Punktacja wewnętrzna",
326
        max_digits=6,
327
        decimal_places=2,
328
        default=Decimal("0.00"),
329
        db_index=True,
330
    )
331
    punktacja_snip = models.DecimalField(
4✔
332
        "Punktacja SNIP",
333
        max_digits=6,
334
        decimal_places=3,
335
        default=Decimal("0.000"),
336
        db_index=True,
337
        help_text="""CiteScore SNIP (Source Normalized Impact per Paper)""",
338
    )
339

340
    class Meta:
4✔
341
        abstract = True
4✔
342

343

344
class ModelPunktowany(ModelPunktowanyBaza):
4✔
345
    """Model zawiereający informację o punktacji."""
346

347
    weryfikacja_punktacji = models.BooleanField(default=False)
4✔
348

349
    class Meta:
4✔
350
        abstract = True
4✔
351

352
    def ma_punktacje(self):
4✔
353
        """Zwraca 'True', jeżeli ten rekord ma jakąkolwiek punktację,
354
        czyli jeżeli dowolne z jego pól ma wartość nie-zerową"""
355

356
        for pole in POLA_PUNKTACJI:
×
357
            f = getattr(self, pole)
×
358

359
            if f is None:
×
360
                continue
×
361

362
            if isinstance(f, Decimal):
×
363
                if not f.is_zero():
×
364
                    return True
×
365
            else:
366
                if f != 0:
×
367
                    return True
×
368

369
        return False
×
370

371

372
class ModelTypowany(models.Model):
4✔
373
    """Model zawierający typ MNiSW/MEiN oraz język."""
374

375
    typ_kbn = models.ForeignKey("Typ_KBN", CASCADE, verbose_name="typ MNiSW/MEiN")
4✔
376
    jezyk = models.ForeignKey(
4✔
377
        "Jezyk", CASCADE, verbose_name="Język", limit_choices_to={"widoczny": True}
378
    )
379
    jezyk_alt = models.ForeignKey(
4✔
380
        "Jezyk",
381
        SET_NULL,
382
        verbose_name="Język alternatywny",
383
        null=True,
384
        blank=True,
385
        related_name="+",
386
    )
387

388
    jezyk_orig = models.ForeignKey(
4✔
389
        "Jezyk",
390
        SET_NULL,
391
        verbose_name="Język oryginalny",
392
        help_text="Dla tłumaczeń. Wartość eksportowana do PBN. ",
393
        related_name="+",
394
        null=True,
395
        blank=True,
396
    )
397

398
    class Meta:
4✔
399
        abstract = True
4✔
400

401

402
class ModelZKwartylami(models.Model):
4✔
403
    kwartyl_w_scopus = models.IntegerField(
4✔
404
        "Kwartyl w SCOPUS",
405
        choices=const.KWARTYLE,
406
        default=None,
407
        blank=True,
408
        null=True,
409
    )
410

411
    kwartyl_w_wos = models.IntegerField(
4✔
412
        "Kwartyl w WoS", choices=const.KWARTYLE, default=None, blank=True, null=True
413
    )
414

415
    class Meta:
4✔
416
        abstract = True
4✔
417

418

419
POLA_PUNKTACJI = [
4✔
420
    x.name for x in ModelPunktowany._meta.fields if x.name != "weryfikacja_punktacji"
421
] + [x.name for x in ModelZKwartylami._meta.fields]
422

423

424
class BazaModeluOdpowiedzialnosciAutorow(models.Model):
4✔
425
    """Bazowa klasa dla odpowiedzialności autorów (czyli dla przypisania
426
    autora do czegokolwiek innego). Zawiera wszystkie informacje dla autora,
427
    czyli: powiązanie ForeignKey, jednostkę, rodzaj zapisu nazwiska, ale
428
    nie zawiera podstawowej informacji, czyli powiązania"""
429

430
    autor = models.ForeignKey("bpp.Autor", CASCADE)
4✔
431
    jednostka = models.ForeignKey("bpp.Jednostka", CASCADE)
4✔
432
    kierunek_studiow = models.ForeignKey(
4✔
433
        "bpp.Kierunek_Studiow", SET_NULL, blank=True, null=True
434
    )
435
    kolejnosc = models.IntegerField("Kolejność", default=0)
4✔
436
    typ_odpowiedzialnosci = models.ForeignKey(
4✔
437
        "bpp.Typ_Odpowiedzialnosci", CASCADE, verbose_name="Typ odpowiedzialności"
438
    )
439
    zapisany_jako = models.CharField(max_length=512)
4✔
440
    afiliuje = models.BooleanField(
4✔
441
        default=True,
442
        help_text="""Afiliuje
443
    się do jednostki podanej w przypisaniu. Jednostka nie może być obcą. """,
444
    )
445
    zatrudniony = models.BooleanField(
4✔
446
        default=False,
447
        help_text="""Pracownik
448
    jednostki podanej w przypisaniu""",
449
    )
450

451
    procent = models.DecimalField(
4✔
452
        "Udział w opracowaniu (procent)",
453
        max_digits=5,
454
        decimal_places=2,
455
        null=True,
456
        blank=True,
457
    )
458

459
    dyscyplina_naukowa = models.ForeignKey(
4✔
460
        Dyscyplina_Naukowa, on_delete=SET_NULL, null=True, blank=True
461
    )
462

463
    przypieta = models.BooleanField(
4✔
464
        default=True,
465
        db_index=True,
466
        help_text="""Możesz odznaczyć, żeby "odpiąć" dyscyplinę od tego autora. Dyscyplina "odpięta" nie będzie
467
        wykazywana do PBN oraz nie będzie używana do liczenia punktów dla danej pracy.""",
468
    )
469

470
    upowaznienie_pbn = models.BooleanField(
4✔
471
        "Upoważnienie PBN",
472
        default=False,
473
        help_text='Tik w polu "upoważnienie PBN" oznacza, że dany autor upoważnił '
474
        "Uczelnię do sprawozdania tej publikacji w ocenie parametrycznej Uczelni",
475
    )
476

477
    oswiadczenie_ken = models.BooleanField(
4✔
478
        "Oświadczenie KEN",
479
        null=True,
480
        blank=True,
481
        default=None,
482
        help_text="Oświadczenie Komisji Ewaluacji Nauki (Uniwersytet Medyczny w Lublinie)",
483
    )
484

485
    profil_orcid = models.BooleanField(
4✔
486
        "Praca w profilu ORCID autora",
487
        default=False,
488
        help_text="Zaznacz, jeżeli praca znajdje się na profilu ORCID autora",
489
    )
490

491
    data_oswiadczenia = models.DateField(
4✔
492
        "Data oświadczenia",
493
        null=True,
494
        blank=True,
495
        help_text="Informacja eksportowana do PBN, gdy uzupełniono",
496
    )
497

498
    class Meta:
4✔
499
        abstract = True
4✔
500
        ordering = ("kolejnosc", "typ_odpowiedzialnosci__skrot")
4✔
501

502
    def __str__(self):
4✔
503
        return str(self.autor) + " - " + str(self.jednostka.skrot)
2✔
504

505
    def rodzaj_autora_uwzgledniany_w_kalkulacjach_slotow(self):
4✔
506
        return self.autor.autor_dyscyplina_set.filter(
×
507
            rok=self.rekord.rok,
508
            rodzaj_autora__in=[
509
                Autor_Dyscyplina.RODZAJE_AUTORA.N,
510
                Autor_Dyscyplina.RODZAJE_AUTORA.D,
511
                Autor_Dyscyplina.RODZAJE_AUTORA.Z,
512
            ],
513
        ).exists()
514

515
    def okresl_dyscypline(self):
4✔
516
        return self.dyscyplina_naukowa
×
517

518
        # Ponizej wykomentowane automatyczne zachowanie, obecne w systemie do wersji 1.0.30-dev2,
519
        # którego po tej wersji NIE chcemy. Chcemy mieć explicte określoną dyscyplinę naukową.
520
        # Jednakże, gdyby się okazało, że należy powrócić do jakiejś automatyki w tym temacie,
521
        # API .okresl_dyscyplinę na ten moment zostaje, jak i resztka z tego kodu, któro
522
        # zapewniało zachowanie automatyczne:
523

524
        # # Jeżeli nie, sprawdź, czy dla danego autora jest określona dyscyplina
525
        # # na dany rok:
526
        # try:
527
        #     ad = Autor_Dyscyplina.objects.get(
528
        #         autor_id=self.autor_id,
529
        #         rok=self.rekord.rok,
530
        #     )
531
        # except Autor_Dyscyplina.DoesNotExist:
532
        #     return
533
        #
534
        # # Zwróć przypisaną dyscyplinę naukową tylko w sytuacji, gdy jest
535
        # # określona jedna. Jeżeli są dwie, to nie można określić z automatu
536
        # if ad.subdyscyplina_naukowa is None:
537
        #     return ad.dyscyplina_naukowa
538

539
    # XXX TODO sprawdzanie, żęby nie było dwóch autorów o tej samej kolejności
540

541
    def clean(self):
4✔
542
        # --- Walidacja dyscypliny ---
543
        # Czy jest określona dyscyplina? Jeżeli tak, to:
544
        # - rekord nadrzędny musi być określony i mieć jakąś wartość w polu 'Rok'
545
        # - musi istnieć takie przypisanie autora do dyscypliny dla danego roku
546
        if self.dyscyplina_naukowa is not None:
3✔
547
            if self.rekord_id is None:
1✔
548
                # Może nie ustalono rekordu nadrzędnego... a moze dodajemy nowy
549
                # rekord do bazy?
550

551
                if self.rekord is None:
1✔
552
                    raise ValidationError(
×
553
                        {
554
                            "dyscyplina_naukowa": "Określono dyscyplinę naukową, ale brak publikacji nadrzędnej. "
555
                        }
556
                    )
557

558
            if self.rekord is not None and self.rekord.rok is None:
1✔
559
                raise ValidationError(
×
560
                    {
561
                        "dyscyplina_naukowa": "Publikacja nadrzędna nie ma określonego roku."
562
                    }
563
                )
564

565
            try:
1✔
566
                Autor_Dyscyplina.objects.get(
1✔
567
                    Q(dyscyplina_naukowa=self.dyscyplina_naukowa)
568
                    | Q(subdyscyplina_naukowa=self.dyscyplina_naukowa),
569
                    autor=self.autor,
570
                    rok=self.rekord.rok,
571
                )
572
            except Autor_Dyscyplina.DoesNotExist:
×
573
                raise ValidationError(
×
574
                    {
575
                        "dyscyplina_naukowa": "Autor nie ma przypisania na dany rok do takiej dyscypliny."
576
                    }
577
                )
578

579
        # Na tym etapie rekord musi być ustalony:
580
        ValErrRek = ValidationError(
3✔
581
            "Rekord nadrzędny (pole: rekord) musi być ustalone. "
582
        )
583
        if hasattr(self, "rekord"):
3✔
584
            if self.rekord is None:
3✔
585
                raise ValErrRek
×
586
        else:
587
            if self.rekord_id is None:
×
588
                raise ValErrRek
×
589

590
        # --- Walidacja procentów ---
591
        # Znajdź inne obiekty z tego rekordu, które są już w bazie danych, ewentualnie
592
        # utrudniając ich zapisanie w sytuacji, gdyby ilość procent
593
        # przekroczyła 100:
594
        inne = self.__class__.objects.filter(rekord=self.rekord)
3✔
595
        if self.pk:
3✔
596
            inne = inne.exclude(pk=self.pk)
3✔
597
        suma = inne.aggregate(Sum("procent"))["procent__sum"] or Decimal("0.00")
3✔
598
        procent = self.procent or Decimal("0.00")
3✔
599

600
        if suma + procent > Decimal("100.00"):
3✔
601
            raise ValidationError(
×
602
                {
603
                    "procent": "Suma podanych odpowiedzialności przekracza 100. "
604
                    "Jeżeli edytujesz rekord, spróbuj zrobić to w dwóch etapach. W pierwszym "
605
                    "zmniejsz punkty procentowe innym, zapisz, w następnym zwiększ punkty "
606
                    "procentowe i zapisz ponownie. Rekordy nie zostały zapisane. "
607
                }
608
            )
609

610
        # --- Walidacja afiliacji ---
611
        # Jeżeli autor afiliuje na jednostkę która jest obca (skupia_pracownikow=False),
612
        # to zgłoś błąd
613

614
        if (
3✔
615
            self.afiliuje
616
            and self.jednostka_id is not None
617
            and self.jednostka.skupia_pracownikow is False
618
            and getattr(settings, "BPP_WALIDUJ_AFILIACJE_AUTOROW", True)
619
        ):
620
            raise ValidationError(
×
621
                {
622
                    "afiliuje": "Jeżeli autor opracował tą pracę w obcej jednostce, to pole "
623
                    "'Afiliuje' nie powinno być zaznaczone."
624
                }
625
            )
626

627
        # --- Walidacja oświadczeń KEN (Uniwersytet Medyczny w Lublinie) ---
628
        # Jeżeli jednocześnie włączone upowaznienie_pbn oraz oświadczenie_ken to zgłoś błąd
629
        if self.upowaznienie_pbn and self.oswiadczenie_ken:
3✔
630
            msg = (
1✔
631
                "Pola 'Upoważnienie PBN' oraz 'Oświadczenie KEN' nie mogą być jednocześnie wybrane. "
632
                "Odznacz jedno lub drugie. "
633
            )
634
            raise ValidationError(
1✔
635
                {
636
                    "upowaznienie_pbn": msg,
637
                    "oswiadczenie_ken": msg,
638
                }
639
            )
640

641
    def save(self, *args, **kw):
4✔
642
        if "__disable_bmoa_clean_method" in kw:
3✔
643
            del kw["__disable_bmoa_clean_method"]
×
644
        else:
645
            self.clean()
3✔
646
        from bpp.models import Autor_Jednostka
3✔
647

648
        if (
3✔
649
            getattr(settings, "BPP_DODAWAJ_JEDNOSTKE_PRZY_ZAPISIE_PRACY", True)
650
            and not Autor_Jednostka.objects.filter(
651
                autor_id=self.autor_id, jednostka_id=self.jednostka_id
652
            ).exists()
653
        ):
654
            Autor_Jednostka.objects.create(
3✔
655
                autor_id=self.autor_id,
656
                jednostka_id=self.jednostka_id,
657
            )
658
            # olewamy refresh_from_db i autor.aktualna_jednostka
659

660
        return super().save(*args, **kw)
3✔
661

662

663
class ModelZeSlowamiKluczowymi(models.Model):
4✔
664
    class Meta:
4✔
665
        abstract = True
4✔
666

667
    slowa_kluczowe = TaggableManager(
4✔
668
        "Słowa kluczowe -- język polski",
669
        help_text="Lista słów kluczowych -- język polski.",
670
        blank=True,
671
    )
672

673
    slowa_kluczowe_eng = ArrayField(
4✔
674
        base_field=models.CharField(max_length=255, blank=True),
675
        verbose_name="Słowa kluczowe -- język angielski",
676
        help_text="Lista słów kluczowych -- język angielski",
677
        null=True,
678
        blank=True,
679
    )
680

681

682
class ModelZeSzczegolami(models.Model):
4✔
683
    """Model zawierający pola: informacje, szczegóły, uwagi, słowa kluczowe."""
684

685
    informacje = models.TextField("Informacje", null=True, blank=True, db_index=True)
4✔
686

687
    szczegoly = models.CharField(
4✔
688
        "Szczegóły", max_length=512, null=True, blank=True, help_text="Np. str. 23-45"
689
    )
690

691
    uwagi = models.TextField(null=True, blank=True, db_index=True)
4✔
692

693
    utworzono = models.DateTimeField(
4✔
694
        "Utworzono", auto_now_add=True, blank=True, null=True
695
    )
696

697
    strony = models.CharField(
4✔
698
        max_length=250,
699
        null=True,
700
        blank=True,
701
        help_text="""Jeżeli uzupełnione, to pole będzie eksportowane do
702
        danych PBN. Jeżeli puste, informacja ta będzie ekstrahowana z
703
        pola "Szczegóły" w chwili generowania eksportu PBN. Aby uniknąć
704
        sytuacji, gdy wskutek błędnego wprowadzenia tekstu do pola
705
        "Szczegóły" informacja ta nie będzie mogła być wyekstrahowana
706
        z tego pola, kliknij przycisk "Uzupełnij", aby spowodować uzupełnienie
707
        tego pola na podstawie pola "Szczegóły".
708
        """,
709
    )
710

711
    tom = models.CharField(
4✔
712
        max_length=50,
713
        null=True,
714
        blank=True,
715
        help_text="""Jeżeli uzupełnione, to pole będzie eksportowane do
716
        danych PBN. Jeżeli puste, informacja ta będzie ekstrahowana z
717
        pola 'Informacje'. Kliknięcie przycisku "Uzupełnij" powoduje
718
        również automatyczne wypełnienie tego pola, o ile do formularza
719
        zostały wprowadzone odpowiednie informacje. """,
720
    )
721

722
    class Meta:
4✔
723
        abstract = True
4✔
724

725
    def pierwsza_strona(self):
4✔
726
        return self.strony.split("-")[0]
×
727

728
    def ostatnia_strona(self):
4✔
729
        try:
×
730
            return self.strony.split("-")[1]
×
731
        except IndexError:
×
732
            return self.pierwsza_strona()
×
733

734

735
class ModelZNumeremZeszytu(models.Model):
4✔
736
    nr_zeszytu = models.CharField(
4✔
737
        max_length=50,
738
        null=True,
739
        blank=True,
740
        help_text="""Jeżeli uzupełnione, to pole będzie eksportowane do
741
        danych PBN. Jeżeli puste, informacja ta będzie ekstrahowana z
742
        pola 'Informacje'. Kliknięcie przycisku "Uzupełnij" powoduje
743
        również automatyczne wypełnienie tego pola, o ile do formularza
744
        zostały wprowadzone odpowiednie informacje. """,
745
    )
746

747
    class Meta:
4✔
748
        abstract = True
4✔
749

750

751
class ModelZCharakterem(models.Model):
4✔
752
    charakter_formalny = models.ForeignKey(
4✔
753
        "bpp.Charakter_Formalny", CASCADE, verbose_name="Charakter formalny"
754
    )
755

756
    class Meta:
4✔
757
        abstract = True
4✔
758

759

760
class ModelPrzeszukiwalny(models.Model):
4✔
761
    """Model zawierający pole pełnotekstowego przeszukiwania
762
    'search_index'"""
763

764
    search_index = VectorField()
4✔
765
    tytul_oryginalny_sort = models.TextField(db_index=True, default="")
4✔
766

767
    class Meta:
4✔
768
        abstract = True
4✔
769

770

771
class ModelZLegacyData(models.Model):
4✔
772
    """Model zawierający informacje zaimportowane z poprzedniego systemu,
773
    nie mające odpowiednika w nowych danych, jednakże pozostawione na
774
    rekordzie w taki sposób, aby w razie potrzeby w przyszłości można było
775
    z nich skorzystać"""
776

777
    legacy_data = HStoreField(blank=True, null=True)
4✔
778

779
    class Meta:
4✔
780
        abstract = True
4✔
781

782

783
class RekordBPPBaza(
4✔
784
    ModelZPBN_ID, ModelZOpisemBibliograficznym, ModelPrzeszukiwalny, ModelZLegacyData
785
):
786
    """Klasa bazowa wszystkich rekordów (patenty, prace doktorskie,
787
    habilitacyjne, wydawnictwa zwarte i ciągłe)"""
788

789
    class Meta:
4✔
790
        abstract = True
4✔
791

792

793
class ModelWybitny(models.Model):
4✔
794
    praca_wybitna = models.BooleanField(default=False)
4✔
795
    uzasadnienie_wybitnosci = models.TextField(
4✔
796
        "Uzasadnienie wybitności", default="", blank=True
797
    )
798

799
    class Meta:
4✔
800
        abstract = True
4✔
801

802

803
class LinkDoPBNMixin:
4✔
804
    url_do_pbn = None
4✔
805
    atrybut_dla_url_do_pbn = "pbn_uid_id"
4✔
806

807
    def link_do_pbn_wartosc_id(self):
4✔
808
        return getattr(self, self.atrybut_dla_url_do_pbn)
×
809

810
    def link_do_pbn(self):
4✔
811
        assert self.url_do_pbn, "Określ parametr self.url_do_pbn"
×
812

813
        from bpp.models import Uczelnia
×
814

815
        uczelnia = Uczelnia.objects.get_default()
×
816
        if uczelnia is not None:
×
817
            return self.url_do_pbn.format(
×
818
                pbn_api_root=uczelnia.pbn_api_root,
819
                pbn_uid_id=self.link_do_pbn_wartosc_id(),
820
            )
821

822
    def link_do_pi(self):
4✔
823
        pbn_uid_id = self.link_do_pbn_wartosc_id()
×
824

825
        if not pbn_uid_id:
×
826
            return
×
827

828
        versionHash = None
×
829
        try:
×
830
            # bpp.models.Wydawnictwo_Ciagle, bpp.models.Wydawnictwo_Zwarte
831
            current_version = self.pbn_uid.current_version
×
832
            if current_version is not None:
×
833
                # w testach moze tak byc, ze bedzie None
834
                versionHash = current_version.get("versionHash", None)
×
835
        except AttributeError:
×
836
            try:
×
837
                # pbn_api.models.OswiadczenieInstytucji
838
                versionHash = self.publicationId.current_version.get(
×
839
                    "versionHash", None
840
                )
841
            except AttributeError:
×
842
                # pbn_api.models.Publication
843
                versionHash = self.current_version.get("versionHash", None)
×
844

845
        if versionHash is None:
×
846
            return
×
847

848
        from bpp import const
×
849
        from bpp.models import Uczelnia
×
850

851
        uczelnia = Uczelnia.objects.get_default()
×
852
        if uczelnia is not None:
×
853
            return const.LINK_PI_WSZYSTKO.format(
×
854
                pbn_api_root=uczelnia.pbn_api_root,
855
                pbn_uid_id=pbn_uid_id,
856
                versionHash=versionHash,
857
            )
858

859

860
class ModelZPBN_UID(LinkDoPBNMixin, models.Model):
4✔
861
    pbn_uid = models.OneToOneField(
4✔
862
        "pbn_api.Publication",
863
        verbose_name=const.PBN_UID_FIELD_LABEL,
864
        null=True,
865
        blank=True,
866
        on_delete=models.SET_NULL,
867
        unique=True,
868
    )
869

870
    url_do_pbn = const.LINK_PBN_DO_PUBLIKACJI
4✔
871

872
    def get_pbn_uuid(self):
4✔
873
        """Nazwa tej funkcji to NIE literówka; alias to PBN UID V2
874

875
        get_pbn_uid_v2
876
        get_pbn_uuid_v2
877

878
        Ta funkcja próbuje zwrócić PBN UUID, pod warunkiem, że został zaciągnięty z API oświadczeń instytucji
879
        V2. Oraz, pod warunkiem, że self.pbn_uid_id jest ustawione."""
880

881
        if self.pbn_uid_id is None:
×
882
            return
×
883

884
        from pbn_api.models.publikacja_instytucji import PublikacjaInstytucji_V2
×
885

886
        publicationUuid = PublikacjaInstytucji_V2.objects.filter(
×
887
            objectId=self.pbn_uid_id
888
        ).values_list("uuid", flat=True)[:1]
889

890
        if publicationUuid:
×
891
            return publicationUuid[0]
×
892

893
    class Meta:
4✔
894
        abstract = True
4✔
895

896

897
class ManagerModeliZOplataZaPublikacjeMixin:
4✔
898
    def rekordy_z_oplata(self):
4✔
899
        return self.exclude(opl_pub_cost_free=None)
×
900

901

902
class ModelZOplataZaPublikacje(models.Model):
4✔
903
    opl_pub_cost_free = models.BooleanField(
4✔
904
        verbose_name="Publikacja bezkosztowa", null=True
905
    )
906
    opl_pub_research_potential = models.BooleanField(
4✔
907
        verbose_name="Środki finansowe art. 365 pkt 2 ustawy",
908
        null=True,
909
        help_text="Środki finansowe, o których mowa w art. 365 pkt 2 ustawy",
910
    )
911
    opl_pub_research_or_development_projects = models.BooleanField(
4✔
912
        verbose_name="Środki finansowe na realizację projektu",
913
        null=True,
914
        help_text="Środki finansowe przyznane na realizację projektu "
915
        "w zakresie badań naukowych lub prac rozwojowych",
916
    )
917

918
    opl_pub_other = models.BooleanField(
4✔
919
        verbose_name="Inne środki finansowe", null=True, blank=True, default=None
920
    )
921

922
    opl_pub_amount = models.DecimalField(
4✔
923
        max_digits=20,
924
        decimal_places=2,
925
        verbose_name="Kwota brutto (zł)",
926
        null=True,
927
        blank=True,
928
    )
929

930
    class Meta:
4✔
931
        abstract = True
4✔
932

933
    def clean(self):
4✔
934
        if self.opl_pub_cost_free:
2✔
935
            # Publikacja bezkosztowa...
936

937
            if self.opl_pub_amount is not None and self.opl_pub_amount > 0:
×
938
                # ... musi mieć kwotę za publikację równą zero
939
                raise ValidationError(
×
940
                    {
941
                        "opl_pub_amount": "Publikacja bezkosztowa, ale kwota opłaty za publikację większa od zera."
942
                        "Proszę o skorygowanie"
943
                    }
944
                )
945

946
            if (
×
947
                self.opl_pub_research_potential
948
                or self.opl_pub_research_or_development_projects
949
                or self.opl_pub_other
950
            ):
951
                # ... oraz odznaczone pozostałe pola
952

953
                errmsg = """Jeżeli zaznaczono publikację jako bezkosztową, to pozostałe pola dotyczące
×
954
                środków finansowych nie mogą być zaznaczone na 'TAK', a koszt powinien być równy 0.00 zł. Przejrzyj
955
                te pola i odznacz je. """
956

957
                errmsg2 = """Pole nie może być zaznaczone na 'TAK' dla publikacji bezkosztowej. """
×
958

959
                errdct = {
×
960
                    "opl_pub_cost_free": errmsg,
961
                }
962

963
                if self.opl_pub_research_potential:
×
964
                    errdct["opl_pub_research_potential"] = errmsg2
×
965

966
                if self.opl_pub_research_or_development_projects:
×
967
                    errdct["opl_pub_research_or_development_projects"] = errmsg2
×
968

969
                if self.opl_pub_other:
×
970
                    errdct["opl_pub_other"] = errmsg2
×
971

972
                raise ValidationError(errdct)
×
973
        else:
974
            # Publikacja kosztowa z kolei (self.opl_pub_cost_free jest None
975
            # albo False) ...
976
            if self.opl_pub_amount is not None and self.opl_pub_amount > 0:
2✔
977
                # ...jeżeli ma wpisany koszt, musi miec zaznaczony któreś z pól:
978
                if (
×
979
                    not self.opl_pub_research_or_development_projects
980
                    and not self.opl_pub_research_potential
981
                    and not self.opl_pub_other
982
                ):
983
                    errmsg = (
×
984
                        "Jeżeli wpisano opłatę za publikację, należy dodatkowo zaznaczyć, z jakich środków"
985
                        " została ta opłata zrealizowana. Przejrzyj pola dotyczące środków finansowych "
986
                        "i ustaw wartość na 'TAK' przynajmniej w jednym z nich - np w tym ... "
987
                    )
988

989
                    errmsg2 = "... lub w tym ..."
×
990
                    errmsg3 = "... lub tutaj. "
×
991

992
                    raise ValidationError(
×
993
                        {
994
                            "opl_pub_research_potential": errmsg,
995
                            "opl_pub_research_or_development_projects": errmsg2,
996
                            "opl_pub_other": errmsg3,
997
                        }
998
                    )
999

1000
            else:
1001
                # ... jeżeli nie ma wpisanego kosztu a ma zaznaczone któreś z pól to też źle
1002
                if (
2✔
1003
                    self.opl_pub_research_or_development_projects
1004
                    or self.opl_pub_research_potential
1005
                    or self.opl_pub_other
1006
                ):
1007
                    errdct = {"opl_pub_amount": "Tu należy uzupełnić kwotę. "}
×
1008

1009
                    errmsg = (
×
1010
                        "Jeżeli wybrano pola dotyczące opłaty za publikację, należy dodatkowo wpisać kwotę... "
1011
                        "lub od-znaczyć te pola. "
1012
                    )
1013

1014
                    if self.opl_pub_research_potential:
×
1015
                        errdct["opl_pub_research_potential"] = errmsg
×
1016

1017
                    if self.opl_pub_research_or_development_projects:
×
1018
                        errdct["opl_pub_research_or_development_projects"] = errmsg
×
1019

1020
                    if self.opl_pub_other:
×
1021
                        errdct["opl_pub_other"] = errmsg
×
1022

1023
                    raise ValidationError(errdct)
×
1024
                else:
1025
                    if self.opl_pub_cost_free is not None:
2✔
1026
                        # jeżeli nie ma wpisanego kosztu i nie ma zaznaczonego
1027
                        # zadnego z pól to tym bardziej źle
1028
                        errdct = {"opl_pub_amount": "Tu należy uzupełnić kwotę. "}
×
1029

1030
                        errmsg = (
×
1031
                            "Jeżeli publikacja nie była bezkosztowa, należy zaznaczyć "
1032
                            "przynajmniej jedno z tych pól"
1033
                        )
1034

1035
                        errdct["opl_pub_research_potential"] = errmsg
×
1036
                        errdct["opl_pub_research_or_development_projects"] = errmsg
×
1037
                        errdct["opl_pub_other"] = errmsg
×
1038

1039
                        raise ValidationError(errdct)
×
1040

1041

1042
class Wydawnictwo_Baza(RekordBPPBaza):
4✔
1043
    """Klasa bazowa wydawnictw (prace doktorskie, habilitacyjne, wydawnictwa
1044
    ciągłe, zwarte -- bez patentów)."""
1045

1046
    def __str__(self):
4✔
1047
        return self.tytul_oryginalny
3✔
1048

1049
    class Meta:
4✔
1050
        abstract = True
4✔
1051

1052

1053
url_validator = URLValidator()
4✔
1054

1055

1056
strony_regex = re.compile(
4✔
1057
    r"(?P<parametr>s{1,2}\.)\s*"
1058
    r"(?P<poczatek>(\w*\d+|\w+|\d+))"
1059
    r"((-)(?P<koniec>(\w*\d+|\w+|\d+))|)",
1060
    flags=re.IGNORECASE,
1061
)
1062

1063
alt_strony_regex = re.compile(
4✔
1064
    r"(?P<poczatek>\d+)(-(?P<koniec>\d+)|)(\s*s.|)", flags=re.IGNORECASE
1065
)
1066

1067
BRAK_PAGINACJI = ("[b. pag.]", "[b.pag.]", "[b. pag]", "[b. bag.]")
4✔
1068

1069

1070
def wez_zakres_stron(szczegoly):
4✔
1071
    """Funkcja wycinająca informacje o stronach z pola 'Szczegóły'"""
1072
    if not szczegoly:
×
1073
        return
×
1074

1075
    for bp in BRAK_PAGINACJI:
×
1076
        if szczegoly.find(bp) >= 0:
×
1077
            return "brak"
×
1078

1079
    def ret(res):
×
1080
        d = res.groupdict()
×
1081
        if "poczatek" in d and "koniec" in d and d["koniec"] is not None:
×
1082
            return "{}-{}".format(d["poczatek"], d["koniec"])
×
1083

1084
        return "%s" % d["poczatek"]
×
1085

1086
    res = strony_regex.search(szczegoly)
×
1087
    if res is not None:
×
1088
        return ret(res)
×
1089

1090
    res = alt_strony_regex.search(szczegoly)
×
1091
    if res is not None:
×
1092
        return ret(res)
×
1093

1094

1095
parsed_informacje_regex = re.compile(
4✔
1096
    r"(\[online\])?\s*"
1097
    r"(?P<rok>\d\d+)"
1098
    r"(\s*(vol|t|r|bd)\.*\s*\[?(?P<tom>[A-Za-z]?\d+)\]?)?"
1099
    # To poniżej to była kiedyś jedna długa linia
1100
    r"(\s*(iss|nr|z|h|no)?\.*\s*(?P<numer>((\d+\w*([\/-]\d*\w*)?)\s*((e-)?(suppl|supl)?\.?(\s*\d+|\w+)?)|"
1101
    r"((e-)?(suppl|supl)?\.?\s*\d+(\/\d+)?)|(\d+\w*([\/-]\d*\w*)?))|\[?(suppl|supl)\.\]?))?",
1102
    flags=re.IGNORECASE,
1103
)
1104

1105

1106
def parse_informacje_as_dict(
4✔
1107
    informacje, parsed_informacje_regex=parsed_informacje_regex
1108
):
1109
    """Wycina z pola informacje informację o tomie lub numerze lub roku.
1110

1111
    Jeśli mamy zapis "Vol.60 supl.3" - to "supl.3";
1112
    jeśli mamy zapis "Vol.61 no.2 suppl.2" - to optymalnie byłoby, żeby do pola numeru trafiało "2 suppl.2",
1113
    jeśli zapis jest "Vol.15 no.5 suppl." - "5 suppl."
1114
    """
1115
    if not informacje:
×
1116
        return {}
×
1117

1118
    p = parsed_informacje_regex.search(informacje)
×
1119
    if p is not None:
×
1120
        return p.groupdict()
×
1121
    return {}
×
1122

1123

1124
def parse_informacje(informacje, key):
4✔
1125
    "Wstecznie kompatybilna wersja funkcji parse_informacje_as_dict"
1126
    return parse_informacje_as_dict(informacje).get(key)
×
1127

1128

1129
class ModelZSeria_Wydawnicza(models.Model):
4✔
1130
    seria_wydawnicza = models.ForeignKey(
4✔
1131
        "bpp.Seria_Wydawnicza", CASCADE, blank=True, null=True
1132
    )
1133

1134
    numer_w_serii = models.CharField(max_length=512, blank=True, null=True)
4✔
1135

1136
    class Meta:
4✔
1137
        abstract = True
4✔
1138

1139

1140
class ModelZKonferencja(models.Model):
4✔
1141
    konferencja = models.ForeignKey("bpp.Konferencja", CASCADE, blank=True, null=True)
4✔
1142

1143
    class Meta:
4✔
1144
        abstract = True
4✔
1145

1146

1147
class ModelZOpenAccess(models.Model):
4✔
1148
    openaccess_wersja_tekstu = models.ForeignKey(
4✔
1149
        "Wersja_Tekstu_OpenAccess",
1150
        CASCADE,
1151
        verbose_name="OpenAccess: wersja tekstu",
1152
        blank=True,
1153
        null=True,
1154
    )
1155

1156
    openaccess_licencja = models.ForeignKey(
4✔
1157
        "Licencja_OpenAccess",
1158
        CASCADE,
1159
        verbose_name="OpenAccess: licencja",
1160
        blank=True,
1161
        null=True,
1162
    )
1163

1164
    openaccess_czas_publikacji = models.ForeignKey(
4✔
1165
        "Czas_Udostepnienia_OpenAccess",
1166
        CASCADE,
1167
        verbose_name="OpenAccess: czas udostępnienia",
1168
        blank=True,
1169
        null=True,
1170
    )
1171

1172
    openaccess_ilosc_miesiecy = models.PositiveIntegerField(
4✔
1173
        "OpenAccess: ilość miesięcy",
1174
        blank=True,
1175
        null=True,
1176
        help_text="Ilość miesięcy jakie upłynęły od momentu opublikowania do momentu udostępnienia",
1177
    )
1178

1179
    openaccess_data_opublikowania = models.DateField(
4✔
1180
        "OpenAccess: data publikacji",
1181
        blank=True,
1182
        null=True,
1183
    )
1184

1185
    class Meta:
4✔
1186
        abstract = True
4✔
1187

1188

1189
class ModelZLiczbaCytowan(models.Model):
4✔
1190
    liczba_cytowan = models.PositiveIntegerField(
4✔
1191
        verbose_name="Liczba cytowań",
1192
        null=True,
1193
        blank=True,
1194
        help_text="""Wartość aktualizowana jest automatycznie raz na kilka dni w przypadku
1195
        skonfigurowania dostępu do API WOS AMR (przez obiekt 'Uczelnia'). Możesz również
1196
        czaktualizować tą wartość ręcznie, naciskając przycisk. """,
1197
    )
1198

1199
    class Meta:
4✔
1200
        abstract = True
4✔
1201

1202

1203
class ModelZMiejscemPrzechowywania(models.Model):
4✔
1204
    numer_odbitki = models.CharField(max_length=50, null=True, blank=True)
4✔
1205

1206
    class Meta:
4✔
1207
        abstract = True
4✔
1208

1209

1210
class MaProcentyMixin:
4✔
1211
    def ma_procenty(self):
4✔
1212
        for autor in self.autorzy_set.all():
×
1213
            if autor.procent:
×
1214
                return True
×
1215
        return False
×
1216

1217

1218
class NieMaProcentowMixin:
4✔
1219
    def ma_procenty(self):
4✔
1220
        return False
×
1221

1222

1223
class DodajAutoraMixin:
4✔
1224
    """Funkcja pomocnicza z dodawaniem autora do rekordu, raczej na 99%
1225
    używana tylko i wyłącznie przez testy. Musisz określić self.autor_rekordu_class
1226
    czyli np dla Wydawnictwo_Zwarte ta zmienna powinna przyjąć wartość
1227
    Wydawnictwo_Zwarte_Autor."""
1228

1229
    autor_rekordu_klass = None
4✔
1230

1231
    def dodaj_autora(
4✔
1232
        self,
1233
        autor,
1234
        jednostka,
1235
        zapisany_jako=None,
1236
        typ_odpowiedzialnosci_skrot="aut.",
1237
        kolejnosc=None,
1238
        dyscyplina_naukowa=None,
1239
        afiliuje=True,
1240
    ):
1241
        """
1242
        :rtype: bpp.models.abstract.BazaModeluOdpowiedzialnosciAutorow
1243
        """
1244
        return dodaj_autora(
3✔
1245
            klass=self.autor_rekordu_klass,
1246
            rekord=self,
1247
            autor=autor,
1248
            jednostka=jednostka,
1249
            zapisany_jako=zapisany_jako,
1250
            typ_odpowiedzialnosci_skrot=typ_odpowiedzialnosci_skrot,
1251
            kolejnosc=kolejnosc,
1252
            dyscyplina_naukowa=dyscyplina_naukowa,
1253
            afiliuje=afiliuje,
1254
        )
1255

1256

1257
class ModelOpcjonalnieNieEksportowanyDoAPI(models.Model):
4✔
1258
    nie_eksportuj_przez_api = models.BooleanField(
4✔
1259
        "Nie eksportuj przez API",
1260
        default=False,
1261
        db_index=True,
1262
        help_text="Jeżeli zaznaczone, to ten rekord nie będzie dostępny przez JSON REST API",
1263
    )
1264

1265
    class Meta:
4✔
1266
        abstract = True
4✔
1267

1268

1269
class ModelZPrzeliczaniemDyscyplin(models.Model):
4✔
1270
    def przelicz_punkty_dyscyplin(self):
4✔
1271
        from bpp.models.sloty.core import IPunktacjaCacher
4✔
1272
        from bpp.models.uczelnia import Uczelnia
4✔
1273

1274
        ipc = IPunktacjaCacher(self, Uczelnia.objects.get_default())
4✔
1275
        ipc.removeEntries()
4✔
1276
        if ipc.canAdapt():
4✔
UNCOV
1277
            ipc.rebuildEntries()
1✔
1278
        return ipc.serialize()
4✔
1279

1280
    def odpiete_dyscypliny(self):
4✔
1281
        return self.autorzy_set.exclude(dyscyplina_naukowa=None).exclude(przypieta=True)
×
1282

1283
    def wszystkie_dyscypliny_rekordu(self):
4✔
1284
        """Ta funkcja zwraca każdą dyscyplinę przypiętą do pracy w postaci listy."""
1285
        if not self.pk:
4✔
1286
            return []
4✔
1287

1288
        return (
1✔
1289
            self.autorzy_set.exclude(dyscyplina_naukowa=None)
1290
            .filter(przypieta=True)
1291
            .values_list("dyscyplina_naukowa")
1292
            .distinct()
1293
        )
1294

1295
    class Meta:
4✔
1296
        abstract = True
4✔
1297

1298

1299
class BazaModeluStreszczen(models.Model):
4✔
1300
    jezyk_streszczenia = models.ForeignKey(
4✔
1301
        "bpp.Jezyk", null=True, blank=True, on_delete=models.SET_NULL
1302
    )
1303
    streszczenie = models.TextField(blank=True, null=True)
4✔
1304

1305
    class Meta:
4✔
1306
        abstract = True
4✔
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