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

iplweb / bpp / 69252c38-21b1-49f8-98a5-a21d59416a66

17 Feb 2025 01:27AM UTC coverage: 47.492% (+0.7%) from 46.838%
69252c38-21b1-49f8-98a5-a21d59416a66

push

circleci

mpasternak
Merge branch 'release/v202502.1156'

2 of 8 new or added lines in 3 files covered. (25.0%)

2233 existing lines in 114 files now uncovered.

16671 of 35103 relevant lines covered (47.49%)

1.2 hits per line

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

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

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

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

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

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

25
ILOSC_ZNAKOW_NA_ARKUSZ = 40000.0
3✔
26

27

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

31

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

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

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

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

46

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

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

67

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

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

83

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

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

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

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

96

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

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

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

105

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

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

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

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

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

131

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

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

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

143

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

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

155

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

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

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

170

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

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

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

180

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

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

207

208
def nie_zawiera_adresu_doi_org(v):
3✔
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):
3✔
221
    """Model zawierający adres strony WWW"""
222

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

252

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

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

262

263
def nie_zawiera_http_https(v):
3✔
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):
3✔
277
    doi = DOIField(const.DOI_FIELD_LABEL, null=True, blank=True, db_index=True)
3✔
278

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

282

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

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

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

291

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

295

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

343

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

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

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

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

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

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

396
    class Meta:
3✔
397
        abstract = True
3✔
398

399

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

409
    kwartyl_w_wos = models.IntegerField(
3✔
410
        "Kwartyl w WoS", choices=const.KWARTYLE, default=None, blank=True, null=True
411
    )
412

413
    class Meta:
3✔
414
        abstract = True
3✔
415

416

417
POLA_PUNKTACJI = [
3✔
418
    x.name for x in ModelPunktowany._meta.fields if x.name != "weryfikacja_punktacji"
419
] + [x.name for x in ModelZKwartylami._meta.fields]
420

421

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

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

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

457
    dyscyplina_naukowa = models.ForeignKey(
3✔
458
        Dyscyplina_Naukowa, on_delete=SET_NULL, null=True, blank=True
459
    )
460

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

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

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

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

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

496
    class Meta:
3✔
497
        abstract = True
3✔
498
        ordering = ("kolejnosc", "typ_odpowiedzialnosci__skrot")
3✔
499

500
    def __str__(self):
3✔
UNCOV
501
        return str(self.autor) + " - " + str(self.jednostka.skrot)
1✔
502

503
    def okresl_dyscypline(self):
3✔
504
        return self.dyscyplina_naukowa
×
505

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

512
        # # Jeżeli nie, sprawdź, czy dla danego autora jest określona dyscyplina
513
        # # na dany rok:
514
        # try:
515
        #     ad = Autor_Dyscyplina.objects.get(
516
        #         autor_id=self.autor_id,
517
        #         rok=self.rekord.rok,
518
        #     )
519
        # except Autor_Dyscyplina.DoesNotExist:
520
        #     return
521
        #
522
        # # Zwróć przypisaną dyscyplinę naukową tylko w sytuacji, gdy jest
523
        # # określona jedna. Jeżeli są dwie, to nie można określić z automatu
524
        # if ad.subdyscyplina_naukowa is None:
525
        #     return ad.dyscyplina_naukowa
526

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

529
    def clean(self):
3✔
530
        # --- Walidacja dyscypliny ---
531
        # Czy jest określona dyscyplina? Jeżeli tak, to:
532
        # - rekord nadrzędny musi być określony i mieć jakąś wartość w polu 'Rok'
533
        # - musi istnieć takie przypisanie autora do dyscypliny dla danego roku
UNCOV
534
        if self.dyscyplina_naukowa is not None:
1✔
UNCOV
535
            if self.rekord_id is None:
1✔
536
                # Może nie ustalono rekordu nadrzędnego... a moze dodajemy nowy
537
                # rekord do bazy?
538

UNCOV
539
                if self.rekord is None:
1✔
540
                    raise ValidationError(
×
541
                        {
542
                            "dyscyplina_naukowa": "Określono dyscyplinę naukową, ale brak publikacji nadrzędnej. "
543
                        }
544
                    )
545

UNCOV
546
            if self.rekord is not None and self.rekord.rok is None:
1✔
547
                raise ValidationError(
×
548
                    {
549
                        "dyscyplina_naukowa": "Publikacja nadrzędna nie ma określonego roku."
550
                    }
551
                )
552

UNCOV
553
            try:
1✔
UNCOV
554
                Autor_Dyscyplina.objects.get(
1✔
555
                    Q(dyscyplina_naukowa=self.dyscyplina_naukowa)
556
                    | Q(subdyscyplina_naukowa=self.dyscyplina_naukowa),
557
                    autor=self.autor,
558
                    rok=self.rekord.rok,
559
                )
560
            except Autor_Dyscyplina.DoesNotExist:
×
561
                raise ValidationError(
×
562
                    {
563
                        "dyscyplina_naukowa": "Autor nie ma przypisania na dany rok do takiej dyscypliny."
564
                    }
565
                )
566

567
        # Na tym etapie rekord musi być ustalony:
UNCOV
568
        ValErrRek = ValidationError(
1✔
569
            "Rekord nadrzędny (pole: rekord) musi być ustalone. "
570
        )
UNCOV
571
        if hasattr(self, "rekord"):
1✔
UNCOV
572
            if self.rekord is None:
1✔
573
                raise ValErrRek
×
574
        else:
575
            if self.rekord_id is None:
×
576
                raise ValErrRek
×
577

578
        # --- Walidacja procentów ---
579
        # Znajdź inne obiekty z tego rekordu, które są już w bazie danych, ewentualnie
580
        # utrudniając ich zapisanie w sytuacji, gdyby ilość procent
581
        # przekroczyła 100:
UNCOV
582
        inne = self.__class__.objects.filter(rekord=self.rekord)
1✔
UNCOV
583
        if self.pk:
1✔
UNCOV
584
            inne = inne.exclude(pk=self.pk)
1✔
UNCOV
585
        suma = inne.aggregate(Sum("procent"))["procent__sum"] or Decimal("0.00")
1✔
UNCOV
586
        procent = self.procent or Decimal("0.00")
1✔
587

UNCOV
588
        if suma + procent > Decimal("100.00"):
1✔
589
            raise ValidationError(
×
590
                {
591
                    "procent": "Suma podanych odpowiedzialności przekracza 100. "
592
                    "Jeżeli edytujesz rekord, spróbuj zrobić to w dwóch etapach. W pierwszym "
593
                    "zmniejsz punkty procentowe innym, zapisz, w następnym zwiększ punkty "
594
                    "procentowe i zapisz ponownie. Rekordy nie zostały zapisane. "
595
                }
596
            )
597

598
        # --- Walidacja afiliacji ---
599
        # Jeżeli autor afiliuje na jednostkę która jest obca (skupia_pracownikow=False),
600
        # to zgłoś błąd
601

UNCOV
602
        if (
1✔
603
            self.afiliuje
604
            and self.jednostka_id is not None
605
            and self.jednostka.skupia_pracownikow is False
606
            and getattr(settings, "BPP_WALIDUJ_AFILIACJE_AUTOROW", True)
607
        ):
608
            raise ValidationError(
×
609
                {
610
                    "afiliuje": "Jeżeli autor opracował tą pracę w obcej jednostce, to pole "
611
                    "'Afiliuje' nie powinno być zaznaczone."
612
                }
613
            )
614

615
        # --- Walidacja oświadczeń KEN (Uniwersytet Medyczny w Lublinie) ---
616
        # Jeżeli jednocześnie włączone upowaznienie_pbn oraz oświadczenie_ken to zgłoś błąd
UNCOV
617
        if self.upowaznienie_pbn and self.oswiadczenie_ken:
1✔
UNCOV
618
            msg = (
1✔
619
                "Pola 'Upoważnienie PBN' oraz 'Oświadczenie KEN' nie mogą być jednocześnie wybrane. "
620
                "Odznacz jedno lub drugie. "
621
            )
UNCOV
622
            raise ValidationError(
1✔
623
                {
624
                    "upowaznienie_pbn": msg,
625
                    "oswiadczenie_ken": msg,
626
                }
627
            )
628

629
    def save(self, *args, **kw):
3✔
UNCOV
630
        if "__disable_bmoa_clean_method" in kw:
1✔
631
            del kw["__disable_bmoa_clean_method"]
×
632
        else:
UNCOV
633
            self.clean()
1✔
UNCOV
634
        from bpp.models import Autor_Jednostka
1✔
635

UNCOV
636
        if (
1✔
637
            getattr(settings, "BPP_DODAWAJ_JEDNOSTKE_PRZY_ZAPISIE_PRACY", True)
638
            and not Autor_Jednostka.objects.filter(
639
                autor_id=self.autor_id, jednostka_id=self.jednostka_id
640
            ).exists()
641
        ):
UNCOV
642
            Autor_Jednostka.objects.create(
1✔
643
                autor_id=self.autor_id,
644
                jednostka_id=self.jednostka_id,
645
            )
646
            # olewamy refresh_from_db i autor.aktualna_jednostka
647

UNCOV
648
        return super().save(*args, **kw)
1✔
649

650

651
class ModelZeSlowamiKluczowymi(models.Model):
3✔
652
    class Meta:
3✔
653
        abstract = True
3✔
654

655
    slowa_kluczowe = TaggableManager(
3✔
656
        "Słowa kluczowe -- język polski",
657
        help_text="Lista słów kluczowych -- język polski.",
658
        blank=True,
659
    )
660

661
    slowa_kluczowe_eng = ArrayField(
3✔
662
        base_field=models.CharField(max_length=255, blank=True),
663
        verbose_name="Słowa kluczowe -- język angielski",
664
        help_text="Lista słów kluczowych -- język angielski",
665
        null=True,
666
        blank=True,
667
    )
668

669

670
class ModelZeSzczegolami(models.Model):
3✔
671
    """Model zawierający pola: informacje, szczegóły, uwagi, słowa kluczowe."""
672

673
    informacje = models.TextField("Informacje", null=True, blank=True, db_index=True)
3✔
674

675
    szczegoly = models.CharField(
3✔
676
        "Szczegóły", max_length=512, null=True, blank=True, help_text="Np. str. 23-45"
677
    )
678

679
    uwagi = models.TextField(null=True, blank=True, db_index=True)
3✔
680

681
    utworzono = models.DateTimeField(
3✔
682
        "Utworzono", auto_now_add=True, blank=True, null=True
683
    )
684

685
    strony = models.CharField(
3✔
686
        max_length=250,
687
        null=True,
688
        blank=True,
689
        help_text="""Jeżeli uzupełnione, to pole będzie eksportowane do
690
        danych PBN. Jeżeli puste, informacja ta będzie ekstrahowana z
691
        pola "Szczegóły" w chwili generowania eksportu PBN. Aby uniknąć
692
        sytuacji, gdy wskutek błędnego wprowadzenia tekstu do pola
693
        "Szczegóły" informacja ta nie będzie mogła być wyekstrahowana
694
        z tego pola, kliknij przycisk "Uzupełnij", aby spowodować uzupełnienie
695
        tego pola na podstawie pola "Szczegóły".
696
        """,
697
    )
698

699
    tom = models.CharField(
3✔
700
        max_length=50,
701
        null=True,
702
        blank=True,
703
        help_text="""Jeżeli uzupełnione, to pole będzie eksportowane do
704
        danych PBN. Jeżeli puste, informacja ta będzie ekstrahowana z
705
        pola 'Informacje'. Kliknięcie przycisku "Uzupełnij" powoduje
706
        również automatyczne wypełnienie tego pola, o ile do formularza
707
        zostały wprowadzone odpowiednie informacje. """,
708
    )
709

710
    class Meta:
3✔
711
        abstract = True
3✔
712

713
    def pierwsza_strona(self):
3✔
714
        return self.strony.split("-")[0]
×
715

716
    def ostatnia_strona(self):
3✔
717
        try:
×
718
            return self.strony.split("-")[1]
×
719
        except IndexError:
×
720
            return self.pierwsza_strona()
×
721

722

723
class ModelZNumeremZeszytu(models.Model):
3✔
724
    nr_zeszytu = models.CharField(
3✔
725
        max_length=50,
726
        null=True,
727
        blank=True,
728
        help_text="""Jeżeli uzupełnione, to pole będzie eksportowane do
729
        danych PBN. Jeżeli puste, informacja ta będzie ekstrahowana z
730
        pola 'Informacje'. Kliknięcie przycisku "Uzupełnij" powoduje
731
        również automatyczne wypełnienie tego pola, o ile do formularza
732
        zostały wprowadzone odpowiednie informacje. """,
733
    )
734

735
    class Meta:
3✔
736
        abstract = True
3✔
737

738

739
class ModelZCharakterem(models.Model):
3✔
740
    charakter_formalny = models.ForeignKey(
3✔
741
        "bpp.Charakter_Formalny", CASCADE, verbose_name="Charakter formalny"
742
    )
743

744
    class Meta:
3✔
745
        abstract = True
3✔
746

747

748
class ModelPrzeszukiwalny(models.Model):
3✔
749
    """Model zawierający pole pełnotekstowego przeszukiwania
750
    'search_index'"""
751

752
    search_index = VectorField()
3✔
753
    tytul_oryginalny_sort = models.TextField(db_index=True, default="")
3✔
754

755
    class Meta:
3✔
756
        abstract = True
3✔
757

758

759
class ModelZLegacyData(models.Model):
3✔
760
    """Model zawierający informacje zaimportowane z poprzedniego systemu,
761
    nie mające odpowiednika w nowych danych, jednakże pozostawione na
762
    rekordzie w taki sposób, aby w razie potrzeby w przyszłości można było
763
    z nich skorzystać"""
764

765
    legacy_data = HStoreField(blank=True, null=True)
3✔
766

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

770

771
class RekordBPPBaza(
3✔
772
    ModelZPBN_ID, ModelZOpisemBibliograficznym, ModelPrzeszukiwalny, ModelZLegacyData
773
):
774
    """Klasa bazowa wszystkich rekordów (patenty, prace doktorskie,
775
    habilitacyjne, wydawnictwa zwarte i ciągłe)"""
776

777
    class Meta:
3✔
778
        abstract = True
3✔
779

780

781
class ModelWybitny(models.Model):
3✔
782
    praca_wybitna = models.BooleanField(default=False)
3✔
783
    uzasadnienie_wybitnosci = models.TextField(
3✔
784
        "Uzasadnienie wybitności", default="", blank=True
785
    )
786

787
    class Meta:
3✔
788
        abstract = True
3✔
789

790

791
class LinkDoPBNMixin:
3✔
792
    url_do_pbn = None
3✔
793
    atrybut_dla_url_do_pbn = "pbn_uid_id"
3✔
794

795
    def link_do_pbn_wartosc_id(self):
3✔
796
        return getattr(self, self.atrybut_dla_url_do_pbn)
×
797

798
    def link_do_pbn(self):
3✔
799
        assert self.url_do_pbn, "Określ parametr self.url_do_pbn"
×
800

801
        from bpp.models import Uczelnia
×
802

803
        uczelnia = Uczelnia.objects.get_default()
×
804
        if uczelnia is not None:
×
805
            return self.url_do_pbn.format(
×
806
                pbn_api_root=uczelnia.pbn_api_root,
807
                pbn_uid_id=self.link_do_pbn_wartosc_id(),
808
            )
809

810

811
class ModelZPBN_UID(LinkDoPBNMixin, models.Model):
3✔
812
    pbn_uid = models.ForeignKey(
3✔
813
        "pbn_api.Publication",
814
        verbose_name=const.PBN_UID_FIELD_LABEL,
815
        null=True,
816
        blank=True,
817
        on_delete=models.SET_NULL,
818
    )
819

820
    url_do_pbn = const.LINK_PBN_DO_PUBLIKACJI
3✔
821

822
    class Meta:
3✔
823
        abstract = True
3✔
824

825

826
class ManagerModeliZOplataZaPublikacjeMixin:
3✔
827
    def rekordy_z_oplata(self):
3✔
828
        return self.exclude(opl_pub_cost_free=None)
×
829

830

831
class ModelZOplataZaPublikacje(models.Model):
3✔
832
    opl_pub_cost_free = models.BooleanField(
3✔
833
        verbose_name="Publikacja bezkosztowa", null=True
834
    )
835
    opl_pub_research_potential = models.BooleanField(
3✔
836
        verbose_name="Środki finansowe art. 365 pkt 2 ustawy",
837
        null=True,
838
        help_text="Środki finansowe, o których mowa w art. 365 pkt 2 ustawy",
839
    )
840
    opl_pub_research_or_development_projects = models.BooleanField(
3✔
841
        verbose_name="Środki finansowe na realizację projektu",
842
        null=True,
843
        help_text="Środki finansowe przyznane na realizację projektu "
844
        "w zakresie badań naukowych lub prac rozwojowych",
845
    )
846

847
    opl_pub_other = models.BooleanField(
3✔
848
        verbose_name="Inne środki finansowe", null=True, blank=True, default=None
849
    )
850

851
    opl_pub_amount = models.DecimalField(
3✔
852
        max_digits=20,
853
        decimal_places=2,
854
        verbose_name="Kwota brutto (zł)",
855
        null=True,
856
        blank=True,
857
    )
858

859
    class Meta:
3✔
860
        abstract = True
3✔
861

862
    def clean(self):
3✔
UNCOV
863
        if self.opl_pub_cost_free:
1✔
864
            # Publikacja bezkosztowa...
865

866
            if self.opl_pub_amount is not None and self.opl_pub_amount > 0:
×
867
                # ... musi mieć kwotę za publikację równą zero
868
                raise ValidationError(
×
869
                    {
870
                        "opl_pub_amount": "Publikacja bezkosztowa, ale kwota opłaty za publikację większa od zera."
871
                        "Proszę o skorygowanie"
872
                    }
873
                )
874

875
            if (
×
876
                self.opl_pub_research_potential
877
                or self.opl_pub_research_or_development_projects
878
                or self.opl_pub_other
879
            ):
880
                # ... oraz odznaczone pozostałe pola
881

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

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

888
                errdct = {
×
889
                    "opl_pub_cost_free": errmsg,
890
                }
891

892
                if self.opl_pub_research_potential:
×
893
                    errdct["opl_pub_research_potential"] = errmsg2
×
894

895
                if self.opl_pub_research_or_development_projects:
×
896
                    errdct["opl_pub_research_or_development_projects"] = errmsg2
×
897

898
                if self.opl_pub_other:
×
899
                    errdct["opl_pub_other"] = errmsg2
×
900

901
                raise ValidationError(errdct)
×
902
        else:
903
            # Publikacja kosztowa z kolei (self.opl_pub_cost_free jest None
904
            # albo False) ...
UNCOV
905
            if self.opl_pub_amount is not None and self.opl_pub_amount > 0:
1✔
906
                # ...jeżeli ma wpisany koszt, musi miec zaznaczony któreś z pól:
907
                if (
×
908
                    not self.opl_pub_research_or_development_projects
909
                    and not self.opl_pub_research_potential
910
                    and not self.opl_pub_other
911
                ):
912
                    errmsg = (
×
913
                        "Jeżeli wpisano opłatę za publikację, należy dodatkowo zaznaczyć, z jakich środków"
914
                        " została ta opłata zrealizowana. Przejrzyj pola dotyczące środków finansowych "
915
                        "i ustaw wartość na 'TAK' przynajmniej w jednym z nich - np w tym ... "
916
                    )
917

918
                    errmsg2 = "... lub w tym ..."
×
919
                    errmsg3 = "... lub tutaj. "
×
920

921
                    raise ValidationError(
×
922
                        {
923
                            "opl_pub_research_potential": errmsg,
924
                            "opl_pub_research_or_development_projects": errmsg2,
925
                            "opl_pub_other": errmsg3,
926
                        }
927
                    )
928

929
            else:
930
                # ... jeżeli nie ma wpisanego kosztu a ma zaznaczone któreś z pól to też źle
UNCOV
931
                if (
1✔
932
                    self.opl_pub_research_or_development_projects
933
                    or self.opl_pub_research_potential
934
                    or self.opl_pub_other
935
                ):
936
                    errdct = {"opl_pub_amount": "Tu należy uzupełnić kwotę. "}
×
937

938
                    errmsg = (
×
939
                        "Jeżeli wybrano pola dotyczące opłaty za publikację, należy dodatkowo wpisać kwotę... "
940
                        "lub od-znaczyć te pola. "
941
                    )
942

943
                    if self.opl_pub_research_potential:
×
944
                        errdct["opl_pub_research_potential"] = errmsg
×
945

946
                    if self.opl_pub_research_or_development_projects:
×
947
                        errdct["opl_pub_research_or_development_projects"] = errmsg
×
948

949
                    if self.opl_pub_other:
×
950
                        errdct["opl_pub_other"] = errmsg
×
951

952
                    raise ValidationError(errdct)
×
953
                else:
UNCOV
954
                    if self.opl_pub_cost_free is not None:
1✔
955
                        # jeżeli nie ma wpisanego kosztu i nie ma zaznaczonego
956
                        # zadnego z pól to tym bardziej źle
957
                        errdct = {"opl_pub_amount": "Tu należy uzupełnić kwotę. "}
×
958

959
                        errmsg = (
×
960
                            "Jeżeli publikacja nie była bezkosztowa, należy zaznaczyć "
961
                            "przynajmniej jedno z tych pól"
962
                        )
963

964
                        errdct["opl_pub_research_potential"] = errmsg
×
965
                        errdct["opl_pub_research_or_development_projects"] = errmsg
×
966
                        errdct["opl_pub_other"] = errmsg
×
967

968
                        raise ValidationError(errdct)
×
969

970

971
class Wydawnictwo_Baza(RekordBPPBaza):
3✔
972
    """Klasa bazowa wydawnictw (prace doktorskie, habilitacyjne, wydawnictwa
973
    ciągłe, zwarte -- bez patentów)."""
974

975
    def __str__(self):
3✔
UNCOV
976
        return self.tytul_oryginalny
1✔
977

978
    class Meta:
3✔
979
        abstract = True
3✔
980

981

982
url_validator = URLValidator()
3✔
983

984

985
strony_regex = re.compile(
3✔
986
    r"(?P<parametr>s{1,2}\.)\s*"
987
    r"(?P<poczatek>(\w*\d+|\w+|\d+))"
988
    r"((-)(?P<koniec>(\w*\d+|\w+|\d+))|)",
989
    flags=re.IGNORECASE,
990
)
991

992
alt_strony_regex = re.compile(
3✔
993
    r"(?P<poczatek>\d+)(-(?P<koniec>\d+)|)(\s*s.|)", flags=re.IGNORECASE
994
)
995

996
BRAK_PAGINACJI = ("[b. pag.]", "[b.pag.]", "[b. pag]", "[b. bag.]")
3✔
997

998

999
def wez_zakres_stron(szczegoly):
3✔
1000
    """Funkcja wycinająca informacje o stronach z pola 'Szczegóły'"""
1001
    if not szczegoly:
×
1002
        return
×
1003

1004
    for bp in BRAK_PAGINACJI:
×
1005
        if szczegoly.find(bp) >= 0:
×
1006
            return "brak"
×
1007

1008
    def ret(res):
×
1009
        d = res.groupdict()
×
1010
        if "poczatek" in d and "koniec" in d and d["koniec"] is not None:
×
1011
            return "{}-{}".format(d["poczatek"], d["koniec"])
×
1012

1013
        return "%s" % d["poczatek"]
×
1014

1015
    res = strony_regex.search(szczegoly)
×
1016
    if res is not None:
×
1017
        return ret(res)
×
1018

1019
    res = alt_strony_regex.search(szczegoly)
×
1020
    if res is not None:
×
1021
        return ret(res)
×
1022

1023

1024
parsed_informacje_regex = re.compile(
3✔
1025
    r"(\[online\])?\s*"
1026
    r"(?P<rok>\d\d+)"
1027
    r"(\s*(vol|t|r|bd)\.*\s*\[?(?P<tom>[A-Za-z]?\d+)\]?)?"
1028
    # To poniżej to była kiedyś jedna długa linia
1029
    r"(\s*(iss|nr|z|h|no)?\.*\s*(?P<numer>((\d+\w*([\/-]\d*\w*)?)\s*((e-)?(suppl|supl)?\.?(\s*\d+|\w+)?)|"
1030
    r"((e-)?(suppl|supl)?\.?\s*\d+(\/\d+)?)|(\d+\w*([\/-]\d*\w*)?))|\[?(suppl|supl)\.\]?))?",
1031
    flags=re.IGNORECASE,
1032
)
1033

1034

1035
def parse_informacje_as_dict(
3✔
1036
    informacje, parsed_informacje_regex=parsed_informacje_regex
1037
):
1038
    """Wycina z pola informacje informację o tomie lub numerze lub roku.
1039

1040
    Jeśli mamy zapis "Vol.60 supl.3" - to "supl.3";
1041
    jeśli mamy zapis "Vol.61 no.2 suppl.2" - to optymalnie byłoby, żeby do pola numeru trafiało "2 suppl.2",
1042
    jeśli zapis jest "Vol.15 no.5 suppl." - "5 suppl."
1043
    """
1044
    if not informacje:
×
1045
        return {}
×
1046

1047
    p = parsed_informacje_regex.search(informacje)
×
1048
    if p is not None:
×
1049
        return p.groupdict()
×
1050
    return {}
×
1051

1052

1053
def parse_informacje(informacje, key):
3✔
1054
    "Wstecznie kompatybilna wersja funkcji parse_informacje_as_dict"
1055
    return parse_informacje_as_dict(informacje).get(key)
×
1056

1057

1058
class ModelZSeria_Wydawnicza(models.Model):
3✔
1059
    seria_wydawnicza = models.ForeignKey(
3✔
1060
        "bpp.Seria_Wydawnicza", CASCADE, blank=True, null=True
1061
    )
1062

1063
    numer_w_serii = models.CharField(max_length=512, blank=True, null=True)
3✔
1064

1065
    class Meta:
3✔
1066
        abstract = True
3✔
1067

1068

1069
class ModelZKonferencja(models.Model):
3✔
1070
    konferencja = models.ForeignKey("bpp.Konferencja", CASCADE, blank=True, null=True)
3✔
1071

1072
    class Meta:
3✔
1073
        abstract = True
3✔
1074

1075

1076
class ModelZOpenAccess(models.Model):
3✔
1077
    openaccess_wersja_tekstu = models.ForeignKey(
3✔
1078
        "Wersja_Tekstu_OpenAccess",
1079
        CASCADE,
1080
        verbose_name="OpenAccess: wersja tekstu",
1081
        blank=True,
1082
        null=True,
1083
    )
1084

1085
    openaccess_licencja = models.ForeignKey(
3✔
1086
        "Licencja_OpenAccess",
1087
        CASCADE,
1088
        verbose_name="OpenAccess: licencja",
1089
        blank=True,
1090
        null=True,
1091
    )
1092

1093
    openaccess_czas_publikacji = models.ForeignKey(
3✔
1094
        "Czas_Udostepnienia_OpenAccess",
1095
        CASCADE,
1096
        verbose_name="OpenAccess: czas udostępnienia",
1097
        blank=True,
1098
        null=True,
1099
    )
1100

1101
    openaccess_ilosc_miesiecy = models.PositiveIntegerField(
3✔
1102
        "OpenAccess: ilość miesięcy",
1103
        blank=True,
1104
        null=True,
1105
        help_text="Ilość miesięcy jakie upłynęły od momentu opublikowania do momentu udostępnienia",
1106
    )
1107

1108
    openaccess_data_opublikowania = models.DateField(
3✔
1109
        "OpenAccess: data publikacji",
1110
        blank=True,
1111
        null=True,
1112
    )
1113

1114
    class Meta:
3✔
1115
        abstract = True
3✔
1116

1117

1118
class ModelZLiczbaCytowan(models.Model):
3✔
1119
    liczba_cytowan = models.PositiveIntegerField(
3✔
1120
        verbose_name="Liczba cytowań",
1121
        null=True,
1122
        blank=True,
1123
        help_text="""Wartość aktualizowana jest automatycznie raz na kilka dni w przypadku
1124
        skonfigurowania dostępu do API WOS AMR (przez obiekt 'Uczelnia'). Możesz również
1125
        czaktualizować tą wartość ręcznie, naciskając przycisk. """,
1126
    )
1127

1128
    class Meta:
3✔
1129
        abstract = True
3✔
1130

1131

1132
class ModelZMiejscemPrzechowywania(models.Model):
3✔
1133
    numer_odbitki = models.CharField(max_length=50, null=True, blank=True)
3✔
1134

1135
    class Meta:
3✔
1136
        abstract = True
3✔
1137

1138

1139
class MaProcentyMixin:
3✔
1140
    def ma_procenty(self):
3✔
1141
        for autor in self.autorzy_set.all():
×
1142
            if autor.procent:
×
1143
                return True
×
1144
        return False
×
1145

1146

1147
class NieMaProcentowMixin:
3✔
1148
    def ma_procenty(self):
3✔
1149
        return False
×
1150

1151

1152
class DodajAutoraMixin:
3✔
1153
    """Funkcja pomocnicza z dodawaniem autora do rekordu, raczej na 99%
1154
    używana tylko i wyłącznie przez testy. Musisz określić self.autor_rekordu_class
1155
    czyli np dla Wydawnictwo_Zwarte ta zmienna powinna przyjąć wartość
1156
    Wydawnictwo_Zwarte_Autor."""
1157

1158
    autor_rekordu_klass = None
3✔
1159

1160
    def dodaj_autora(
3✔
1161
        self,
1162
        autor,
1163
        jednostka,
1164
        zapisany_jako=None,
1165
        typ_odpowiedzialnosci_skrot="aut.",
1166
        kolejnosc=None,
1167
        dyscyplina_naukowa=None,
1168
        afiliuje=True,
1169
    ):
1170
        """
1171
        :rtype: bpp.models.abstract.BazaModeluOdpowiedzialnosciAutorow
1172
        """
UNCOV
1173
        return dodaj_autora(
1✔
1174
            klass=self.autor_rekordu_klass,
1175
            rekord=self,
1176
            autor=autor,
1177
            jednostka=jednostka,
1178
            zapisany_jako=zapisany_jako,
1179
            typ_odpowiedzialnosci_skrot=typ_odpowiedzialnosci_skrot,
1180
            kolejnosc=kolejnosc,
1181
            dyscyplina_naukowa=dyscyplina_naukowa,
1182
            afiliuje=afiliuje,
1183
        )
1184

1185

1186
class ModelOpcjonalnieNieEksportowanyDoAPI(models.Model):
3✔
1187
    nie_eksportuj_przez_api = models.BooleanField(
3✔
1188
        "Nie eksportuj przez API",
1189
        default=False,
1190
        db_index=True,
1191
        help_text="Jeżeli zaznaczone, to ten rekord nie będzie dostępny przez JSON REST API",
1192
    )
1193

1194
    class Meta:
3✔
1195
        abstract = True
3✔
1196

1197

1198
class ModelZPrzeliczaniemDyscyplin(models.Model):
3✔
1199
    def przelicz_punkty_dyscyplin(self):
3✔
1200
        from bpp.models.sloty.core import IPunktacjaCacher
3✔
1201
        from bpp.models.uczelnia import Uczelnia
3✔
1202

1203
        ipc = IPunktacjaCacher(self, Uczelnia.objects.get_default())
3✔
1204
        ipc.removeEntries()
3✔
1205
        if ipc.canAdapt():
3✔
UNCOV
1206
            ipc.rebuildEntries()
×
1207
        return ipc.serialize()
3✔
1208

1209
    def odpiete_dyscypliny(self):
3✔
1210
        return self.autorzy_set.exclude(dyscyplina_naukowa=None).exclude(przypieta=True)
×
1211

1212
    def wszystkie_dyscypliny_rekordu(self):
3✔
1213
        """Ta funkcja zwraca każdą dyscyplinę przypiętą do pracy w postaci listy."""
1214
        if not self.pk:
3✔
1215
            return []
3✔
1216

UNCOV
1217
        return (
1✔
1218
            self.autorzy_set.exclude(dyscyplina_naukowa=None)
1219
            .filter(przypieta=True)
1220
            .values_list("dyscyplina_naukowa")
1221
            .distinct()
1222
        )
1223

1224
    class Meta:
3✔
1225
        abstract = True
3✔
1226

1227

1228
class BazaModeluStreszczen(models.Model):
3✔
1229
    jezyk_streszczenia = models.ForeignKey(
3✔
1230
        "bpp.Jezyk", null=True, blank=True, on_delete=models.SET_NULL
1231
    )
1232
    streszczenie = models.TextField(blank=True, null=True)
3✔
1233

1234
    class Meta:
3✔
1235
        abstract = True
3✔
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