• 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

56.63
src/bpp/models/autor.py
1
"""
2
Autorzy
3
"""
4

5
from __future__ import annotations
4✔
6

7
from datetime import date, datetime, timedelta
4✔
8

9
from autoslug import AutoSlugField
4✔
10
from django.core.exceptions import ValidationError
4✔
11
from django.core.validators import RegexValidator
4✔
12
from django.db import IntegrityError, models, transaction
4✔
13
from django.db.models import CASCADE, SET_NULL, Count, Q, Sum, UniqueConstraint
4✔
14
from django.urls.base import reverse
4✔
15
from tinymce.models import HTMLField
4✔
16

17
from django.contrib.postgres.search import SearchVectorField as VectorField
4✔
18

19
from django.utils import timezone
4✔
20

21
from bpp import const
4✔
22
from bpp.core import zbieraj_sloty
4✔
23
from bpp.models import LinkDoPBNMixin, ModelZAdnotacjami, ModelZNazwa, NazwaISkrot
4✔
24
from bpp.models.abstract import ModelZPBN_ID
4✔
25
from bpp.util import FulltextSearchMixin
4✔
26

27

28
class Tytul(NazwaISkrot):
4✔
29
    class Meta:
4✔
30
        verbose_name = "tytuł"
4✔
31
        verbose_name_plural = "tytuły"
4✔
32
        app_label = "bpp"
4✔
33
        ordering = ("skrot",)
4✔
34

35

36
class Plec(NazwaISkrot):
4✔
37
    class Meta:
4✔
38
        verbose_name = "płeć"
4✔
39
        verbose_name_plural = "płcie"
4✔
40
        app_label = "bpp"
4✔
41

42

43
def autor_split_string(text):
4✔
44
    text = text.strip().replace("\t", " ").replace("\n", " ").replace("\r", " ")
×
45
    while text.find("  ") >= 0:
×
46
        text = text.replace("  ", " ")
×
47

48
    text = [x.strip() for x in text.split(" ", 1)]
×
49
    if len(text) != 2:
×
50
        raise ValueError(text)
×
51

52
    if not text[0] or not text[1]:
×
53
        raise ValueError(text)
×
54

55
    return text[0], text[1]
×
56

57

58
class AutorManager(FulltextSearchMixin, models.Manager):
4✔
59
    # Nie włączaj websearch gdy podano minus (podwójne nazwiska z myślnikiem)
60
    fts_enable_websearch_on_minus_or_quote = False
4✔
61

62
    def create_from_string(self, text):
4✔
63
        """Tworzy rekord autora z ciągu znaków. Używane, gdy dysponujemy
64
        wpisanym ciągiem znaków z np AutorAutocomplete i chcemy utworzyć
65
        autora z nazwiskiem i imieniem w poprawny sposób."""
66

67
        text = autor_split_string(text)
×
68

69
        return self.create(
×
70
            **dict(nazwisko=text[0].title(), imiona=text[1].title(), pokazuj=False)
71
        )
72

73
    def fulltext_annotate(self, search_query, normalization):
4✔
74
        return {self.fts_field + "__rank": Count("wydawnictwo_ciagle")}
2✔
75

76

77
class Autor(LinkDoPBNMixin, ModelZAdnotacjami, ModelZPBN_ID):
4✔
78
    url_do_pbn = const.LINK_PBN_DO_AUTORA
4✔
79

80
    imiona = models.CharField(max_length=512, db_index=True)
4✔
81
    nazwisko = models.CharField(max_length=256, db_index=True)
4✔
82
    tytul = models.ForeignKey(Tytul, CASCADE, blank=True, null=True)
4✔
83
    pseudonim = models.CharField(
4✔
84
        max_length=300,
85
        blank=True,
86
        null=True,
87
        help_text="""
88
    Jeżeli w bazie danych znajdują się autorzy o zbliżonych imionach, nazwiskach i tytułach naukowych,
89
    skorzystaj z tego pola aby ułatwić ich rozróżnienie. Pseudonim pokaże się w polach wyszukiwania
90
    oraz na podstronie autora, po nazwisku i tytule naukowym.""",
91
    )
92

93
    aktualna_jednostka = models.ForeignKey(
4✔
94
        "Jednostka", CASCADE, blank=True, null=True, related_name="aktualna_jednostka"
95
    )
96
    aktualna_funkcja = models.ForeignKey(
4✔
97
        "Funkcja_Autora",
98
        CASCADE,
99
        blank=True,
100
        null=True,
101
        related_name="aktualna_funkcja",
102
    )
103

104
    pokazuj = models.BooleanField(
4✔
105
        default=True, help_text="Pokazuj autora na stronach jednostek oraz w rankingu. "
106
    )
107

108
    email = models.EmailField("E-mail", max_length=128, blank=True, null=True)
4✔
109
    www = models.URLField("WWW", max_length=1024, blank=True, null=True)
4✔
110

111
    plec = models.ForeignKey(Plec, CASCADE, null=True, blank=True)
4✔
112

113
    urodzony = models.DateField(blank=True, null=True)
4✔
114
    zmarl = models.DateField(blank=True, null=True)
4✔
115

116
    opis = HTMLField(blank=True, null=True)  # models.TextField(blank=True, null=True)
4✔
117
    pokazuj_opis = models.BooleanField(
4✔
118
        default=False, help_text="""Czy pokazywać tekst z pola 'Opis' na stronie?"""
119
    )
120
    poprzednie_nazwiska = models.CharField(
4✔
121
        max_length=1024,
122
        blank=True,
123
        null=True,
124
        help_text="""Jeżeli ten
125
        autor(-ka) posiada nazwisko panieńskie, pod którym ukazywały
126
        się publikacje lub zmieniał nazwisko z innych powodów, wpisz tutaj
127
        wszystkie poprzednie nazwiska, oddzielając je przecinkami.""",
128
        db_index=True,
129
    )
130
    pokazuj_poprzednie_nazwiska = models.BooleanField(
4✔
131
        default=True,
132
        help_text="Jeżeli odznaczone, poprzednie nazwiska nie będą się wyświetlać na podstronie autora "
133
        "dla użytkowników niezalogowanych. Użytkownicy zalogowani widzą je zawsze. Wyszukiwanie po poprzednich "
134
        "nazwiskach będzie nadal możliwe. ",
135
    )
136
    orcid = models.CharField(
4✔
137
        "Identyfikator ORCID",
138
        max_length=19,
139
        blank=True,
140
        null=True,
141
        unique=True,
142
        help_text="Open Researcher and Contributor ID, " "vide http://www.orcid.org",
143
        validators=[
144
            RegexValidator(
145
                regex=r"^\d\d\d\d-\d\d\d\d-\d\d\d\d-\d\d\d(\d|X)$",
146
                message="Identyfikator ORCID to 4 grupy po 4 cyfry w każdej, "
147
                "oddzielone myślnikami",
148
                code="orcid_invalid_format",
149
            ),
150
        ],
151
        db_index=True,
152
    )
153
    orcid_w_pbn = models.BooleanField(
4✔
154
        "ORCID jest w bazie PBN?",
155
        help_text="""Jeżeli ORCID jest w bazie PBN, to pole powinno być zaznaczone. Zaznaczenie następuje
156
        automatycznie, przez procedury integrujące bazę danych z PBNem w nocy. Można też zaznaczyć ręcznie.
157
        Pole wykorzystywane jest gdy autor nie ma odpowiednika w PBN (pole 'PBN UID' rekordu autora jest puste,
158
        zaś eksport danych powoduje komunikat zwrotny z PBN o nieistniejącym w ich bazie ORCID). W takich sytuacjach
159
        należy w polu wybrać "Nie". """,
160
        null=True,
161
    )
162

163
    expertus_id = models.CharField(
4✔
164
        "Identyfikator w bazie Expertus",
165
        max_length=10,
166
        null=True,
167
        blank=True,
168
        db_index=True,
169
        unique=True,
170
    )
171

172
    system_kadrowy_id = models.PositiveIntegerField(
4✔
173
        "Identyfikator w systemie kadrowym",
174
        help_text="""Identyfikator cyfrowy, używany do matchowania autora z danymi z systemu kadrowego Uczelni""",
175
        null=True,
176
        blank=True,
177
        db_index=True,
178
        unique=True,
179
    )
180

181
    pbn_uid = models.ForeignKey(
4✔
182
        "pbn_api.Scientist", null=True, blank=True, on_delete=SET_NULL
183
    )
184

185
    search = VectorField()
4✔
186

187
    objects = AutorManager()
4✔
188

189
    slug = AutoSlugField(populate_from="get_full_name", unique=True, max_length=1024)
4✔
190

191
    sort = models.TextField()
4✔
192

193
    jednostki = models.ManyToManyField("bpp.Jednostka", through="Autor_Jednostka")
4✔
194

195
    def get_absolute_url(self):
4✔
UNCOV
196
        return reverse("bpp:browse_autor", args=(self.slug,))
1✔
197

198
    class Meta:
4✔
199
        verbose_name = "autor"
4✔
200
        verbose_name_plural = "autorzy"
4✔
201
        ordering = ["sort"]
4✔
202
        app_label = "bpp"
4✔
203

204
    def aktualna_dyscyplina(self, pole="dyscyplina_naukowa"):
4✔
205
        from bpp.models import Autor_Dyscyplina
×
206

207
        try:
×
208
            # Spróbuj pobrać wpis Autor_Dyscyplina dla obecnego roku
209
            ret = Autor_Dyscyplina.objects.get(
×
210
                autor=self, rok=timezone.now().date().year
211
            )
212
        except Autor_Dyscyplina.DoesNotExist:
×
213
            return
×
214

215
        return getattr(ret, pole)
×
216

217
    def aktualna_subdyscyplina(self):
4✔
218
        return self.aktualna_dyscyplina(pole="subdyscyplina_naukowa")
×
219

220
    def __str__(self):
4✔
221
        buf = f"{self.nazwisko} {self.imiona}"
3✔
222

223
        if self.poprzednie_nazwiska and self.pokazuj_poprzednie_nazwiska:
3✔
224
            buf += " (%s)" % self.poprzednie_nazwiska
×
225

226
        if self.tytul is not None:
3✔
227
            buf += ", " + self.tytul.skrot
2✔
228

229
        if self.pseudonim is not None:
3✔
230
            buf += " (" + self.pseudonim + ")"
×
231

232
        return buf
3✔
233

234
    def dodaj_jednostke(
4✔
235
        self, jednostka, rok=None, funkcja=None
236
    ) -> Autor_Jednostka | None:
237
        start_pracy = None
×
238
        koniec_pracy = None
×
239

240
        if rok is not None:
×
241
            start_pracy = date(rok, 1, 1)
×
242
            koniec_pracy = date(rok, 12, 31)
×
243

244
        czy_juz_istnieje = Autor_Jednostka.objects.filter(
×
245
            autor=self,
246
            jednostka=jednostka,
247
            rozpoczal_prace__lte=start_pracy or date(1, 1, 1),
248
            zakonczyl_prace__gte=koniec_pracy or date(999, 12, 31),
249
        )
250

251
        if czy_juz_istnieje.exists():
×
252
            # Ten czas jest już pokryty
253
            return czy_juz_istnieje.first()
×
254

255
        try:
×
256
            ret = Autor_Jednostka.objects.create(
×
257
                autor=self,
258
                jednostka=jednostka,
259
                funkcja=funkcja,
260
                rozpoczal_prace=start_pracy,
261
                zakonczyl_prace=koniec_pracy,
262
            )
263
        except IntegrityError:
×
264
            return
×
265
        self.defragmentuj_jednostke(jednostka)
×
266

267
        return ret
×
268

269
    def defragmentuj_jednostke(self, jednostka):
4✔
270
        Autor_Jednostka.objects.defragmentuj(autor=self, jednostka=jednostka)
×
271

272
    def save(self, *args, **kw):
4✔
273
        self.sort = (self.nazwisko.lower().replace("von ", "") + self.imiona).lower()
4✔
274
        ret = super().save(*args, **kw)
4✔
275

276
        for jednostka in self.jednostki.all():
4✔
277
            self.defragmentuj_jednostke(jednostka)
×
278

279
        return ret
4✔
280

281
    def afiliacja_na_rok(self, rok, wydzial, rozszerzona=False):
4✔
282
        """
283
        Czy autor w danym roku był w danym wydziale?
284

285
        :param rok:
286
        :param wydzial:
287
        :return: True gdy w danym roku był w danym wydziale
288
        """
289
        start_pracy = date(rok, 1, 1)
×
290
        koniec_pracy = date(rok, 12, 31)
×
291

292
        if Autor_Jednostka.objects.filter(
×
293
            autor=self,
294
            rozpoczal_prace__lte=start_pracy,
295
            zakonczyl_prace__gte=koniec_pracy,
296
            jednostka__wydzial=wydzial,
297
        ):
298
            return True
×
299

300
        # A może ma wpisaną tylko datę początku pracy? W takiej sytuacji
301
        # stwierdzamy, że autor NADAL tam pracuje, bo nie ma końca, więc:
302
        if Autor_Jednostka.objects.filter(
×
303
            autor=self,
304
            rozpoczal_prace__lte=start_pracy,
305
            zakonczyl_prace=None,
306
            jednostka__wydzial=wydzial,
307
        ):
308
            return True
×
309

310
        # Jeżeli nie ma takiego rekordu z dopasowaniem z datami, to może jest
311
        # rekord z dopasowaniem JAKIMKOLWIEK innym?
312
        # XXX po telefonie p. Małgorzaty Zając dnia 2013-03-25 o godzinie 11:55
313
        # dostałem informację, że NIE interesują nas tacy autorzy, zatem:
314

315
        if not rozszerzona:
×
316
            return
×
317

318
        # ... aczkolwiek, sprawdzanie afiliacji do wydziału dla niektórych autorów może
319
        # być przydatne np przy importowaniu imion i innych rzeczy, więc sprawdźmy w sytuacj
320
        # gdy jest rozszerzona afiliacja:
321

322
        if Autor_Jednostka.objects.filter(autor=self, jednostka__wydzial=wydzial):
×
323
            return True
×
324

325
    def get_full_name(self):
4✔
326
        buf = f"{self.imiona} {self.nazwisko}"
3✔
327
        if self.poprzednie_nazwiska:
3✔
328
            buf += " (%s)" % self.poprzednie_nazwiska
×
329
        return buf
3✔
330

331
    def get_full_name_surname_first(self):
4✔
UNCOV
332
        buf = "%s" % self.nazwisko
1✔
UNCOV
333
        if self.poprzednie_nazwiska:
1✔
334
            buf += " (%s)" % self.poprzednie_nazwiska
×
UNCOV
335
        buf += " %s" % self.imiona
1✔
UNCOV
336
        return buf
1✔
337

338
    def prace_w_latach(self):
4✔
339
        """Zwraca lata, w których ten autor opracowywał jakiekolwiek prace."""
340
        from bpp.models.cache import Rekord
×
341

342
        return (
×
343
            Rekord.objects.prace_autora(self)
344
            .values_list("rok", flat=True)
345
            .distinct()
346
            .order_by("rok")
347
        )
348

349
    def liczba_cytowan(self):
4✔
350
        """Zwraca liczbę cytowań prac danego autora"""
351
        from bpp.models.cache import Rekord
×
352

353
        return (
×
354
            Rekord.objects.prace_autora(self)
355
            .distinct()
356
            .aggregate(s=Sum("liczba_cytowan"))["s"]
357
        )
358

359
    def liczba_cytowan_afiliowane(self):
4✔
360
        """Zwraca liczbę cytowań prac danego autora tam,
361
        gdzie została podana afiliacja na jednostkę uczelni"""
362
        from bpp.models.cache import Rekord
×
363

364
        return (
×
365
            Rekord.objects.prace_autora_z_afiliowanych_jednostek(self)
366
            .distinct()
367
            .aggregate(s=Sum("liczba_cytowan"))["s"]
368
        )
369

370
    def jednostki_gdzie_ma_publikacje(self):
4✔
371
        from bpp.models import Autorzy, Jednostka
×
372

373
        return Jednostka.objects.filter(
×
374
            pk__in=Autorzy.objects.filter(autor_id=self.pk)
375
            .values("jednostka_id")
376
            .distinct()
377
        )
378

379
    def zbieraj_sloty(
4✔
380
        self,
381
        zadany_slot,
382
        rok_min,
383
        rok_max,
384
        minimalny_pk=None,
385
        dyscyplina_id=None,
386
        jednostka_id=None,
387
        akcja=None,
388
    ):
389
        return zbieraj_sloty(
×
390
            autor_id=self.pk,
391
            zadany_slot=zadany_slot,
392
            rok_min=rok_min,
393
            rok_max=rok_max,
394
            minimalny_pk=minimalny_pk,
395
            dyscyplina_id=dyscyplina_id,
396
            jednostka_id=jednostka_id,
397
            akcja=akcja,
398
        )
399

400

401
class Funkcja_Autora(NazwaISkrot):
4✔
402
    """Funkcja autora w jednostce"""
403

404
    pokazuj_za_nazwiskiem = models.BooleanField(
4✔
405
        default=False,
406
        help_text="""Zaznaczenie tego pola sprawi, że ta funkcja
407
        będzie wyświetlana na stronie autora, za nazwiskiem.""",
408
    )
409

410
    class Meta:
4✔
411
        verbose_name = "funkcja w jednostce"
4✔
412
        verbose_name_plural = "funkcje w jednostkach"
4✔
413
        ordering = ["nazwa"]
4✔
414
        app_label = "bpp"
4✔
415

416

417
class Grupa_Pracownicza(ModelZNazwa):
4✔
418
    class Meta:
4✔
419
        verbose_name = "grupa pracownicza"
4✔
420
        verbose_name_plural = "grupy pracownicze"
4✔
421
        ordering = [
4✔
422
            "nazwa",
423
        ]
424
        app_label = "bpp"
4✔
425

426

427
class Wymiar_Etatu(ModelZNazwa):
4✔
428
    class Meta:
4✔
429
        verbose_name = "wymiar etatu"
4✔
430
        verbose_name_plural = "wymiary etatów"
4✔
431
        ordering = ["nazwa"]
4✔
432
        app_label = "bpp"
4✔
433

434

435
class Autor_Jednostka_Manager(models.Manager):
4✔
436
    def defragmentuj(self, autor, jednostka):
4✔
437
        poprzedni_rekord = None
×
438
        usun = []
×
439
        for rec in Autor_Jednostka.objects.filter(
×
440
            autor=autor, jednostka=jednostka
441
        ).order_by("rozpoczal_prace"):
442
            if poprzedni_rekord is None:
×
443
                poprzedni_rekord = rec
×
444
                continue
×
445

446
            if rec.rozpoczal_prace is None and rec.zakonczyl_prace is None:
×
447
                # Nic nie wnosi tutaj taki rekord ORAZ nie jest to 'poprzedni'
448
                # rekord, więc:
449
                usun.append(rec)
×
450
                continue
×
451

452
            # Przy imporcie danych z XLS na dane ze starego systemu - obydwa pola są None
453
            if (
×
454
                poprzedni_rekord.zakonczyl_prace is None
455
                and poprzedni_rekord.rozpoczal_prace is None
456
            ):
457
                usun.append(poprzedni_rekord)
×
458
                poprzedni_rekord = rec
×
459
                continue
×
460

461
            # Nowy system - przy imporcie danych z XLS do nowego systemu jest sytuacja, gdy autor
462
            # zaczął kiedyśtam prace ALE jej nie zakończył:
463
            if poprzedni_rekord.zakonczyl_prace is None:
×
464
                if (
×
465
                    rec.rozpoczal_prace is None
466
                    and poprzedni_rekord.rozpoczal_prace is not None
467
                ):
468
                    if rec.zakonczyl_prace == poprzedni_rekord.rozpoczal_prace:
×
469
                        usun.append(rec)
×
470
                        poprzedni_rekord.rozpoczal_prace = rec.rozpoczal_prace
×
471
                        poprzedni_rekord.save()
×
472

473
                    continue
×
474

475
                if rec.rozpoczal_prace >= poprzedni_rekord.rozpoczal_prace:
×
476
                    usun.append(rec)
×
477
                    poprzedni_rekord.zakonczyl_prace = rec.zakonczyl_prace
×
478
                    poprzedni_rekord.save()
×
479
                    continue
×
480

481
            if rec.rozpoczal_prace == poprzedni_rekord.zakonczyl_prace + timedelta(
×
482
                days=1
483
            ):
484
                usun.append(rec)
×
485
                poprzedni_rekord.zakonczyl_prace = rec.zakonczyl_prace
×
486
                poprzedni_rekord.save()
×
487
            else:
488
                poprzedni_rekord = rec
×
489

490
        for aj in usun:
×
491
            aj.delete()
×
492

493

494
class Autor_Jednostka(models.Model):
4✔
495
    """Powiązanie autora z jednostką"""
496

497
    autor = models.ForeignKey("bpp.Autor", CASCADE)
4✔
498
    jednostka = models.ForeignKey("bpp.Jednostka", CASCADE)
4✔
499
    rozpoczal_prace = models.DateField(
4✔
500
        "Rozpoczął pracę", blank=True, null=True, db_index=True
501
    )
502
    zakonczyl_prace = models.DateField(
4✔
503
        "Zakończył pracę", null=True, blank=True, db_index=True
504
    )
505
    funkcja = models.ForeignKey("bpp.Funkcja_Autora", CASCADE, null=True, blank=True)
4✔
506

507
    podstawowe_miejsce_pracy = models.BooleanField(null=True, blank=True, default=None)
4✔
508

509
    grupa_pracownicza = models.ForeignKey(
4✔
510
        "bpp.Grupa_Pracownicza", SET_NULL, null=True, blank=True
511
    )
512
    wymiar_etatu = models.ForeignKey(
4✔
513
        "bpp.Wymiar_Etatu", SET_NULL, null=True, blank=True
514
    )
515

516
    objects = Autor_Jednostka_Manager()
4✔
517

518
    def clean(self, exclude=None):
4✔
519
        if self.rozpoczal_prace is not None and self.zakonczyl_prace is not None:
×
520
            if self.rozpoczal_prace >= self.zakonczyl_prace:
×
521
                raise ValidationError(
×
522
                    "Początek pracy późniejszy lub równy, jak zakończenie"
523
                )
524

525
        if self.zakonczyl_prace is not None:
×
526
            if self.zakonczyl_prace >= datetime.now().date():
×
527
                raise ValidationError(
×
528
                    "Czas zakończenia pracy w jednostce nie może być taki sam"
529
                    " lub większy, jak obecna data"
530
                )
531

532
        if self.podstawowe_miejsce_pracy:
×
533
            czy_istnieje = Autor_Jednostka.objects.filter(
×
534
                autor_id=self.autor_id, podstawowe_miejsce_pracy=True
535
            )
536
            if self.pk:
×
537
                czy_istnieje = czy_istnieje.exclude(pk=self.pk)
×
538

539
            if czy_istnieje.exists():
×
540
                raise ValidationError(
×
541
                    "Ten autor ma już określone podstawowe miejsce pracy. Najpierw usuń podstawowe "
542
                    f"miejsce pracy (ustaw wartość na 'Nie' dla jednostki '{czy_istnieje.first().jednostka.nazwa}'), "
543
                    f"następnie zapisz rekord autora, następnie ustaw ponownie właściwe podstawowe miejsce pracy "
544
                    f"(ustaw wartość na 'Tak') i zapisz po raz kolejny. "
545
                )
546

547
    def __str__(self):
4✔
548
        buf = f"{self.autor} ↔ {self.jednostka.skrot}"
×
549
        if self.funkcja:
×
550
            buf = f"{self.autor} ↔ {self.funkcja.nazwa}, {self.jednostka.skrot}"
×
551
        return buf
×
552

553
    @transaction.atomic
4✔
554
    def ustaw_podstawowe_miejsce_pracy(self):
4✔
555
        """Ustawia to miejsce pracy jako podstawowe i wszystkie pozostałe jako nie-podstawowe"""
556
        Autor_Jednostka.objects.filter(
×
557
            autor=self.autor, podstawowe_miejsce_pracy=True
558
        ).exclude(pk=self.pk).update(podstawowe_miejsce_pracy=False)
559
        self.podstawowe_miejsce_pracy = True
×
560
        self.save()
×
561

562
    class Meta:
4✔
563
        verbose_name = "powiązanie autor-jednostka"
4✔
564
        verbose_name_plural = "powiązania autor-jednostka"
4✔
565
        ordering = ["autor__nazwisko", "rozpoczal_prace", "jednostka__nazwa"]
4✔
566
        unique_together = [("autor", "jednostka", "rozpoczal_prace")]
4✔
567
        app_label = "bpp"
4✔
568
        constraints = [
4✔
569
            UniqueConstraint(
570
                fields=["autor_id"],
571
                condition=Q(podstawowe_miejsce_pracy=True),
572
                name="jedno_podstawowe_miejsce_pracy_na_autora",
573
            )
574
        ]
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