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

iplweb / bpp / 0950478e-207f-4389-967f-fb3a6c1090d4

01 Apr 2025 12:57PM UTC coverage: 43.279% (-3.3%) from 46.628%
0950478e-207f-4389-967f-fb3a6c1090d4

push

circleci

mpasternak
Merge branch 'release/v202504.1175'

1 of 19 new or added lines in 5 files covered. (5.26%)

1780 existing lines in 123 files now uncovered.

15876 of 36683 relevant lines covered (43.28%)

0.79 hits per line

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

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

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

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

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

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

25
ILOSC_ZNAKOW_NA_ARKUSZ = 40000.0
2✔
26

27

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

31

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

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

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

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

46

47
class ModelZAdnotacjami(models.Model):
2✔
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)
2✔
53

54
    adnotacje = models.TextField(
2✔
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:
2✔
65
        abstract = True
2✔
66

67

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

71
    pbn_id = models.IntegerField(
2✔
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:
2✔
81
        abstract = True
2✔
82

83

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

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

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

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

96

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

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

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

105

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

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

114
    def nazwa_dopelniacz(self):
2✔
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):
2✔
123
    """Model z numerem ISSN oraz E-ISSN"""
124

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

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

131

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

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

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

143

144
class ModelZInformacjaZ(models.Model):
2✔
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(
2✔
149
        "Zrodlo_Informacji", SET_NULL, null=True, blank=True
150
    )
151

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

155

156
class DwaTytuly(models.Model):
2✔
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)
2✔
161
    tytul = models.TextField("Tytuł", null=True, blank=True, db_index=True)
2✔
162

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

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

170

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

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

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

180

181
class ModelZAbsolutnymUrl:
2✔
182
    def get_absolute_url(self):
2✔
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):
2✔
195
    """Model zawierający pole "Rok" """
196

197
    rok = YearField(
2✔
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:
2✔
205
        abstract = True
2✔
206

207

208
def nie_zawiera_adresu_doi_org(v):
2✔
UNCOV
209
    if v is None:
1✔
UNCOV
210
        return
1✔
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):
2✔
221
    """Model zawierający adres strony WWW"""
222

223
    www = models.URLField(
2✔
224
        const.WWW_FIELD_LABEL,
225
        max_length=1024,
226
        blank=True,
227
        null=True,
228
    )
229
    dostep_dnia = models.DateField(
2✔
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(
2✔
237
        const.PUBLIC_WWW_FIELD_LABEL,
238
        max_length=2048,
239
        blank=True,
240
        null=True,
241
    )
242
    public_dostep_dnia = models.DateField(
2✔
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:
2✔
250
        abstract = True
2✔
251

252

253
class ModelZPubmedID(models.Model):
2✔
254
    pubmed_id = models.BigIntegerField(
2✔
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)
2✔
258

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

262

263
def nie_zawiera_http_https(v):
2✔
UNCOV
264
    if v is None:
1✔
UNCOV
265
        return
1✔
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):
2✔
277
    doi = DOIField(const.DOI_FIELD_LABEL, null=True, blank=True, db_index=True)
2✔
278

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

282

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

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

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

291

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

295

296
def ImpactFactorField(*args, **kw):
2✔
297
    return models.DecimalField(
2✔
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):
2✔
307
    impact_factor = ImpactFactorField(
2✔
308
        db_index=True,
309
    )
310
    punkty_kbn = models.DecimalField(
2✔
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(
2✔
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(
2✔
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(
2✔
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:
2✔
341
        abstract = True
2✔
342

343

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

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

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

352
    def ma_punktacje(self):
2✔
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):
2✔
373
    """Model zawierający typ MNiSW/MEiN oraz język."""
374

375
    typ_kbn = models.ForeignKey("Typ_KBN", CASCADE, verbose_name="typ MNiSW/MEiN")
2✔
376
    jezyk = models.ForeignKey(
2✔
377
        "Jezyk", CASCADE, verbose_name="Język", limit_choices_to={"widoczny": True}
378
    )
379
    jezyk_alt = models.ForeignKey(
2✔
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(
2✔
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:
2✔
399
        abstract = True
2✔
400

401

402
class ModelZKwartylami(models.Model):
2✔
403
    kwartyl_w_scopus = models.IntegerField(
2✔
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(
2✔
412
        "Kwartyl w WoS", choices=const.KWARTYLE, default=None, blank=True, null=True
413
    )
414

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

418

419
POLA_PUNKTACJI = [
2✔
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):
2✔
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)
2✔
431
    jednostka = models.ForeignKey("bpp.Jednostka", CASCADE)
2✔
432
    kierunek_studiow = models.ForeignKey(
2✔
433
        "bpp.Kierunek_Studiow", SET_NULL, blank=True, null=True
434
    )
435
    kolejnosc = models.IntegerField("Kolejność", default=0)
2✔
436
    typ_odpowiedzialnosci = models.ForeignKey(
2✔
437
        "bpp.Typ_Odpowiedzialnosci", CASCADE, verbose_name="Typ odpowiedzialności"
438
    )
439
    zapisany_jako = models.CharField(max_length=512)
2✔
440
    afiliuje = models.BooleanField(
2✔
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(
2✔
446
        default=False,
447
        help_text="""Pracownik
448
    jednostki podanej w przypisaniu""",
449
    )
450

451
    procent = models.DecimalField(
2✔
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(
2✔
460
        Dyscyplina_Naukowa, on_delete=SET_NULL, null=True, blank=True
461
    )
462

463
    przypieta = models.BooleanField(
2✔
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(
2✔
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(
2✔
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(
2✔
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(
2✔
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:
2✔
499
        abstract = True
2✔
500
        ordering = ("kolejnosc", "typ_odpowiedzialnosci__skrot")
2✔
501

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

505
    def rodzaj_autora_uwzgledniany_w_kalkulacjach_slotow(self):
2✔
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):
2✔
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):
2✔
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:
2✔
UNCOV
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

UNCOV
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

UNCOV
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

UNCOV
565
            try:
1✔
UNCOV
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(
2✔
581
            "Rekord nadrzędny (pole: rekord) musi być ustalone. "
582
        )
583
        if hasattr(self, "rekord"):
2✔
584
            if self.rekord is None:
2✔
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)
2✔
595
        if self.pk:
2✔
596
            inne = inne.exclude(pk=self.pk)
2✔
597
        suma = inne.aggregate(Sum("procent"))["procent__sum"] or Decimal("0.00")
2✔
598
        procent = self.procent or Decimal("0.00")
2✔
599

600
        if suma + procent > Decimal("100.00"):
2✔
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 (
2✔
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:
2✔
630
            msg = (
×
631
                "Pola 'Upoważnienie PBN' oraz 'Oświadczenie KEN' nie mogą być jednocześnie wybrane. "
632
                "Odznacz jedno lub drugie. "
633
            )
634
            raise ValidationError(
×
635
                {
636
                    "upowaznienie_pbn": msg,
637
                    "oswiadczenie_ken": msg,
638
                }
639
            )
640

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

648
        if (
2✔
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(
2✔
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)
2✔
661

662

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

667
    slowa_kluczowe = TaggableManager(
2✔
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(
2✔
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):
2✔
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)
2✔
686

687
    szczegoly = models.CharField(
2✔
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)
2✔
692

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

697
    strony = models.CharField(
2✔
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(
2✔
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:
2✔
723
        abstract = True
2✔
724

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

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

734

735
class ModelZNumeremZeszytu(models.Model):
2✔
736
    nr_zeszytu = models.CharField(
2✔
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:
2✔
748
        abstract = True
2✔
749

750

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

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

759

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

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

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

770

771
class ModelZLegacyData(models.Model):
2✔
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)
2✔
778

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

782

783
class RekordBPPBaza(
2✔
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:
2✔
790
        abstract = True
2✔
791

792

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

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

802

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

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

810
    def link_do_pbn(self):
2✔
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

823
class ModelZPBN_UID(LinkDoPBNMixin, models.Model):
2✔
824
    pbn_uid = models.OneToOneField(
2✔
825
        "pbn_api.Publication",
826
        verbose_name=const.PBN_UID_FIELD_LABEL,
827
        null=True,
828
        blank=True,
829
        on_delete=models.SET_NULL,
830
        unique=True,
831
    )
832

833
    url_do_pbn = const.LINK_PBN_DO_PUBLIKACJI
2✔
834

835
    class Meta:
2✔
836
        abstract = True
2✔
837

838

839
class ManagerModeliZOplataZaPublikacjeMixin:
2✔
840
    def rekordy_z_oplata(self):
2✔
841
        return self.exclude(opl_pub_cost_free=None)
×
842

843

844
class ModelZOplataZaPublikacje(models.Model):
2✔
845
    opl_pub_cost_free = models.BooleanField(
2✔
846
        verbose_name="Publikacja bezkosztowa", null=True
847
    )
848
    opl_pub_research_potential = models.BooleanField(
2✔
849
        verbose_name="Środki finansowe art. 365 pkt 2 ustawy",
850
        null=True,
851
        help_text="Środki finansowe, o których mowa w art. 365 pkt 2 ustawy",
852
    )
853
    opl_pub_research_or_development_projects = models.BooleanField(
2✔
854
        verbose_name="Środki finansowe na realizację projektu",
855
        null=True,
856
        help_text="Środki finansowe przyznane na realizację projektu "
857
        "w zakresie badań naukowych lub prac rozwojowych",
858
    )
859

860
    opl_pub_other = models.BooleanField(
2✔
861
        verbose_name="Inne środki finansowe", null=True, blank=True, default=None
862
    )
863

864
    opl_pub_amount = models.DecimalField(
2✔
865
        max_digits=20,
866
        decimal_places=2,
867
        verbose_name="Kwota brutto (zł)",
868
        null=True,
869
        blank=True,
870
    )
871

872
    class Meta:
2✔
873
        abstract = True
2✔
874

875
    def clean(self):
2✔
UNCOV
876
        if self.opl_pub_cost_free:
1✔
877
            # Publikacja bezkosztowa...
878

879
            if self.opl_pub_amount is not None and self.opl_pub_amount > 0:
×
880
                # ... musi mieć kwotę za publikację równą zero
881
                raise ValidationError(
×
882
                    {
883
                        "opl_pub_amount": "Publikacja bezkosztowa, ale kwota opłaty za publikację większa od zera."
884
                        "Proszę o skorygowanie"
885
                    }
886
                )
887

888
            if (
×
889
                self.opl_pub_research_potential
890
                or self.opl_pub_research_or_development_projects
891
                or self.opl_pub_other
892
            ):
893
                # ... oraz odznaczone pozostałe pola
894

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

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

901
                errdct = {
×
902
                    "opl_pub_cost_free": errmsg,
903
                }
904

905
                if self.opl_pub_research_potential:
×
906
                    errdct["opl_pub_research_potential"] = errmsg2
×
907

908
                if self.opl_pub_research_or_development_projects:
×
909
                    errdct["opl_pub_research_or_development_projects"] = errmsg2
×
910

911
                if self.opl_pub_other:
×
912
                    errdct["opl_pub_other"] = errmsg2
×
913

914
                raise ValidationError(errdct)
×
915
        else:
916
            # Publikacja kosztowa z kolei (self.opl_pub_cost_free jest None
917
            # albo False) ...
UNCOV
918
            if self.opl_pub_amount is not None and self.opl_pub_amount > 0:
1✔
919
                # ...jeżeli ma wpisany koszt, musi miec zaznaczony któreś z pól:
920
                if (
×
921
                    not self.opl_pub_research_or_development_projects
922
                    and not self.opl_pub_research_potential
923
                    and not self.opl_pub_other
924
                ):
925
                    errmsg = (
×
926
                        "Jeżeli wpisano opłatę za publikację, należy dodatkowo zaznaczyć, z jakich środków"
927
                        " została ta opłata zrealizowana. Przejrzyj pola dotyczące środków finansowych "
928
                        "i ustaw wartość na 'TAK' przynajmniej w jednym z nich - np w tym ... "
929
                    )
930

931
                    errmsg2 = "... lub w tym ..."
×
932
                    errmsg3 = "... lub tutaj. "
×
933

934
                    raise ValidationError(
×
935
                        {
936
                            "opl_pub_research_potential": errmsg,
937
                            "opl_pub_research_or_development_projects": errmsg2,
938
                            "opl_pub_other": errmsg3,
939
                        }
940
                    )
941

942
            else:
943
                # ... jeżeli nie ma wpisanego kosztu a ma zaznaczone któreś z pól to też źle
UNCOV
944
                if (
1✔
945
                    self.opl_pub_research_or_development_projects
946
                    or self.opl_pub_research_potential
947
                    or self.opl_pub_other
948
                ):
949
                    errdct = {"opl_pub_amount": "Tu należy uzupełnić kwotę. "}
×
950

951
                    errmsg = (
×
952
                        "Jeżeli wybrano pola dotyczące opłaty za publikację, należy dodatkowo wpisać kwotę... "
953
                        "lub od-znaczyć te pola. "
954
                    )
955

956
                    if self.opl_pub_research_potential:
×
957
                        errdct["opl_pub_research_potential"] = errmsg
×
958

959
                    if self.opl_pub_research_or_development_projects:
×
960
                        errdct["opl_pub_research_or_development_projects"] = errmsg
×
961

962
                    if self.opl_pub_other:
×
963
                        errdct["opl_pub_other"] = errmsg
×
964

965
                    raise ValidationError(errdct)
×
966
                else:
UNCOV
967
                    if self.opl_pub_cost_free is not None:
1✔
968
                        # jeżeli nie ma wpisanego kosztu i nie ma zaznaczonego
969
                        # zadnego z pól to tym bardziej źle
970
                        errdct = {"opl_pub_amount": "Tu należy uzupełnić kwotę. "}
×
971

972
                        errmsg = (
×
973
                            "Jeżeli publikacja nie była bezkosztowa, należy zaznaczyć "
974
                            "przynajmniej jedno z tych pól"
975
                        )
976

977
                        errdct["opl_pub_research_potential"] = errmsg
×
978
                        errdct["opl_pub_research_or_development_projects"] = errmsg
×
979
                        errdct["opl_pub_other"] = errmsg
×
980

981
                        raise ValidationError(errdct)
×
982

983

984
class Wydawnictwo_Baza(RekordBPPBaza):
2✔
985
    """Klasa bazowa wydawnictw (prace doktorskie, habilitacyjne, wydawnictwa
986
    ciągłe, zwarte -- bez patentów)."""
987

988
    def __str__(self):
2✔
989
        return self.tytul_oryginalny
2✔
990

991
    class Meta:
2✔
992
        abstract = True
2✔
993

994

995
url_validator = URLValidator()
2✔
996

997

998
strony_regex = re.compile(
2✔
999
    r"(?P<parametr>s{1,2}\.)\s*"
1000
    r"(?P<poczatek>(\w*\d+|\w+|\d+))"
1001
    r"((-)(?P<koniec>(\w*\d+|\w+|\d+))|)",
1002
    flags=re.IGNORECASE,
1003
)
1004

1005
alt_strony_regex = re.compile(
2✔
1006
    r"(?P<poczatek>\d+)(-(?P<koniec>\d+)|)(\s*s.|)", flags=re.IGNORECASE
1007
)
1008

1009
BRAK_PAGINACJI = ("[b. pag.]", "[b.pag.]", "[b. pag]", "[b. bag.]")
2✔
1010

1011

1012
def wez_zakres_stron(szczegoly):
2✔
1013
    """Funkcja wycinająca informacje o stronach z pola 'Szczegóły'"""
1014
    if not szczegoly:
×
1015
        return
×
1016

1017
    for bp in BRAK_PAGINACJI:
×
1018
        if szczegoly.find(bp) >= 0:
×
1019
            return "brak"
×
1020

1021
    def ret(res):
×
1022
        d = res.groupdict()
×
1023
        if "poczatek" in d and "koniec" in d and d["koniec"] is not None:
×
1024
            return "{}-{}".format(d["poczatek"], d["koniec"])
×
1025

1026
        return "%s" % d["poczatek"]
×
1027

1028
    res = strony_regex.search(szczegoly)
×
1029
    if res is not None:
×
1030
        return ret(res)
×
1031

1032
    res = alt_strony_regex.search(szczegoly)
×
1033
    if res is not None:
×
1034
        return ret(res)
×
1035

1036

1037
parsed_informacje_regex = re.compile(
2✔
1038
    r"(\[online\])?\s*"
1039
    r"(?P<rok>\d\d+)"
1040
    r"(\s*(vol|t|r|bd)\.*\s*\[?(?P<tom>[A-Za-z]?\d+)\]?)?"
1041
    # To poniżej to była kiedyś jedna długa linia
1042
    r"(\s*(iss|nr|z|h|no)?\.*\s*(?P<numer>((\d+\w*([\/-]\d*\w*)?)\s*((e-)?(suppl|supl)?\.?(\s*\d+|\w+)?)|"
1043
    r"((e-)?(suppl|supl)?\.?\s*\d+(\/\d+)?)|(\d+\w*([\/-]\d*\w*)?))|\[?(suppl|supl)\.\]?))?",
1044
    flags=re.IGNORECASE,
1045
)
1046

1047

1048
def parse_informacje_as_dict(
2✔
1049
    informacje, parsed_informacje_regex=parsed_informacje_regex
1050
):
1051
    """Wycina z pola informacje informację o tomie lub numerze lub roku.
1052

1053
    Jeśli mamy zapis "Vol.60 supl.3" - to "supl.3";
1054
    jeśli mamy zapis "Vol.61 no.2 suppl.2" - to optymalnie byłoby, żeby do pola numeru trafiało "2 suppl.2",
1055
    jeśli zapis jest "Vol.15 no.5 suppl." - "5 suppl."
1056
    """
1057
    if not informacje:
×
1058
        return {}
×
1059

1060
    p = parsed_informacje_regex.search(informacje)
×
1061
    if p is not None:
×
1062
        return p.groupdict()
×
1063
    return {}
×
1064

1065

1066
def parse_informacje(informacje, key):
2✔
1067
    "Wstecznie kompatybilna wersja funkcji parse_informacje_as_dict"
1068
    return parse_informacje_as_dict(informacje).get(key)
×
1069

1070

1071
class ModelZSeria_Wydawnicza(models.Model):
2✔
1072
    seria_wydawnicza = models.ForeignKey(
2✔
1073
        "bpp.Seria_Wydawnicza", CASCADE, blank=True, null=True
1074
    )
1075

1076
    numer_w_serii = models.CharField(max_length=512, blank=True, null=True)
2✔
1077

1078
    class Meta:
2✔
1079
        abstract = True
2✔
1080

1081

1082
class ModelZKonferencja(models.Model):
2✔
1083
    konferencja = models.ForeignKey("bpp.Konferencja", CASCADE, blank=True, null=True)
2✔
1084

1085
    class Meta:
2✔
1086
        abstract = True
2✔
1087

1088

1089
class ModelZOpenAccess(models.Model):
2✔
1090
    openaccess_wersja_tekstu = models.ForeignKey(
2✔
1091
        "Wersja_Tekstu_OpenAccess",
1092
        CASCADE,
1093
        verbose_name="OpenAccess: wersja tekstu",
1094
        blank=True,
1095
        null=True,
1096
    )
1097

1098
    openaccess_licencja = models.ForeignKey(
2✔
1099
        "Licencja_OpenAccess",
1100
        CASCADE,
1101
        verbose_name="OpenAccess: licencja",
1102
        blank=True,
1103
        null=True,
1104
    )
1105

1106
    openaccess_czas_publikacji = models.ForeignKey(
2✔
1107
        "Czas_Udostepnienia_OpenAccess",
1108
        CASCADE,
1109
        verbose_name="OpenAccess: czas udostępnienia",
1110
        blank=True,
1111
        null=True,
1112
    )
1113

1114
    openaccess_ilosc_miesiecy = models.PositiveIntegerField(
2✔
1115
        "OpenAccess: ilość miesięcy",
1116
        blank=True,
1117
        null=True,
1118
        help_text="Ilość miesięcy jakie upłynęły od momentu opublikowania do momentu udostępnienia",
1119
    )
1120

1121
    openaccess_data_opublikowania = models.DateField(
2✔
1122
        "OpenAccess: data publikacji",
1123
        blank=True,
1124
        null=True,
1125
    )
1126

1127
    class Meta:
2✔
1128
        abstract = True
2✔
1129

1130

1131
class ModelZLiczbaCytowan(models.Model):
2✔
1132
    liczba_cytowan = models.PositiveIntegerField(
2✔
1133
        verbose_name="Liczba cytowań",
1134
        null=True,
1135
        blank=True,
1136
        help_text="""Wartość aktualizowana jest automatycznie raz na kilka dni w przypadku
1137
        skonfigurowania dostępu do API WOS AMR (przez obiekt 'Uczelnia'). Możesz również
1138
        czaktualizować tą wartość ręcznie, naciskając przycisk. """,
1139
    )
1140

1141
    class Meta:
2✔
1142
        abstract = True
2✔
1143

1144

1145
class ModelZMiejscemPrzechowywania(models.Model):
2✔
1146
    numer_odbitki = models.CharField(max_length=50, null=True, blank=True)
2✔
1147

1148
    class Meta:
2✔
1149
        abstract = True
2✔
1150

1151

1152
class MaProcentyMixin:
2✔
1153
    def ma_procenty(self):
2✔
1154
        for autor in self.autorzy_set.all():
×
1155
            if autor.procent:
×
1156
                return True
×
1157
        return False
×
1158

1159

1160
class NieMaProcentowMixin:
2✔
1161
    def ma_procenty(self):
2✔
1162
        return False
×
1163

1164

1165
class DodajAutoraMixin:
2✔
1166
    """Funkcja pomocnicza z dodawaniem autora do rekordu, raczej na 99%
1167
    używana tylko i wyłącznie przez testy. Musisz określić self.autor_rekordu_class
1168
    czyli np dla Wydawnictwo_Zwarte ta zmienna powinna przyjąć wartość
1169
    Wydawnictwo_Zwarte_Autor."""
1170

1171
    autor_rekordu_klass = None
2✔
1172

1173
    def dodaj_autora(
2✔
1174
        self,
1175
        autor,
1176
        jednostka,
1177
        zapisany_jako=None,
1178
        typ_odpowiedzialnosci_skrot="aut.",
1179
        kolejnosc=None,
1180
        dyscyplina_naukowa=None,
1181
        afiliuje=True,
1182
    ):
1183
        """
1184
        :rtype: bpp.models.abstract.BazaModeluOdpowiedzialnosciAutorow
1185
        """
1186
        return dodaj_autora(
2✔
1187
            klass=self.autor_rekordu_klass,
1188
            rekord=self,
1189
            autor=autor,
1190
            jednostka=jednostka,
1191
            zapisany_jako=zapisany_jako,
1192
            typ_odpowiedzialnosci_skrot=typ_odpowiedzialnosci_skrot,
1193
            kolejnosc=kolejnosc,
1194
            dyscyplina_naukowa=dyscyplina_naukowa,
1195
            afiliuje=afiliuje,
1196
        )
1197

1198

1199
class ModelOpcjonalnieNieEksportowanyDoAPI(models.Model):
2✔
1200
    nie_eksportuj_przez_api = models.BooleanField(
2✔
1201
        "Nie eksportuj przez API",
1202
        default=False,
1203
        db_index=True,
1204
        help_text="Jeżeli zaznaczone, to ten rekord nie będzie dostępny przez JSON REST API",
1205
    )
1206

1207
    class Meta:
2✔
1208
        abstract = True
2✔
1209

1210

1211
class ModelZPrzeliczaniemDyscyplin(models.Model):
2✔
1212
    def przelicz_punkty_dyscyplin(self):
2✔
1213
        from bpp.models.sloty.core import IPunktacjaCacher
2✔
1214
        from bpp.models.uczelnia import Uczelnia
2✔
1215

1216
        ipc = IPunktacjaCacher(self, Uczelnia.objects.get_default())
2✔
1217
        ipc.removeEntries()
2✔
1218
        if ipc.canAdapt():
2✔
1219
            ipc.rebuildEntries()
1✔
1220
        return ipc.serialize()
2✔
1221

1222
    def odpiete_dyscypliny(self):
2✔
1223
        return self.autorzy_set.exclude(dyscyplina_naukowa=None).exclude(przypieta=True)
×
1224

1225
    def wszystkie_dyscypliny_rekordu(self):
2✔
1226
        """Ta funkcja zwraca każdą dyscyplinę przypiętą do pracy w postaci listy."""
1227
        if not self.pk:
2✔
1228
            return []
2✔
1229

UNCOV
1230
        return (
1✔
1231
            self.autorzy_set.exclude(dyscyplina_naukowa=None)
1232
            .filter(przypieta=True)
1233
            .values_list("dyscyplina_naukowa")
1234
            .distinct()
1235
        )
1236

1237
    class Meta:
2✔
1238
        abstract = True
2✔
1239

1240

1241
class BazaModeluStreszczen(models.Model):
2✔
1242
    jezyk_streszczenia = models.ForeignKey(
2✔
1243
        "bpp.Jezyk", null=True, blank=True, on_delete=models.SET_NULL
1244
    )
1245
    streszczenie = models.TextField(blank=True, null=True)
2✔
1246

1247
    class Meta:
2✔
1248
        abstract = True
2✔
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