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

iplweb / bpp / ba6f9e1f-4683-40a1-aae1-40dd0fcb64e3

25 Aug 2025 06:57PM UTC coverage: 43.284% (+0.6%) from 42.715%
ba6f9e1f-4683-40a1-aae1-40dd0fcb64e3

push

circleci

mpasternak
Merge branch 'release/v202508.1208'

77 of 961 new or added lines in 27 files covered. (8.01%)

731 existing lines in 54 files now uncovered.

17273 of 39906 relevant lines covered (43.28%)

0.78 hits per line

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

84.28
src/bpp/admin/core.py
1
from decimal import Decimal
2✔
2

3
from dal import autocomplete
2✔
4
from dal_select2.fields import Select2ListChoiceField, Select2ListCreateChoiceField
2✔
5
from django import forms
2✔
6
from django.conf import settings
2✔
7
from django.db.models.fields import BLANK_CHOICE_DASH
2✔
8
from django.forms import NullBooleanField
2✔
9
from django.forms.widgets import HiddenInput
2✔
10

11
from django.contrib import admin
2✔
12

13
from bpp.admin.crossref_api_helpers import KorzystaZCrossRefAPIAutorInlineMixin
2✔
14
from bpp.admin.zglos_publikacje_helpers import KorzystaZNumeruZgloszeniaInlineMixin
2✔
15
from bpp.jezyk_polski import warianty_zapisanego_nazwiska
2✔
16
from bpp.models import (
2✔
17
    Autor,
18
    Dyscyplina_Naukowa,
19
    Jednostka,
20
    Kierunek_Studiow,
21
    Typ_Odpowiedzialnosci,
22
    Uczelnia,
23
)
24

25
UPOWAZNIENIE_PBN = "upowaznienie_pbn"
2✔
26

27

28
# Proste tabele
29

30

31
class BaseBppAdminMixin:
2✔
32
    """Ta klasa jest potrzebna, (XXXżeby działały sygnały post_commit.XXX)
33

34
    Ta klasa KIEDYŚ była potrzebna, obecnie niespecjalnie. Aczkolwiek,
35
    zostawiam ją z przyczyn historycznych, w ten sposób można łatwo
36
    wyłowić klasy edycyjne, które grzebią COKOLWIEK w cache.
37
    """
38

39
    # Mój dynks do grappelli
40
    auto_open_collapsibles = True
2✔
41

42
    # ograniczenie wielkosci listy
43
    list_per_page = 50
2✔
44

45

46
def get_first_typ_odpowiedzialnosci():
2✔
47
    return Typ_Odpowiedzialnosci.objects.filter(skrot="aut.").first()
2✔
48

49

50
def generuj_formularz_dla_autorow(
2✔
51
    baseModel,
52
    include_rekord=False,
53
    include_dyscyplina=True,
54
):
55
    class baseModel_AutorForm(forms.ModelForm):
2✔
56
        if include_rekord:
2✔
57
            rekord = forms.ModelChoiceField(
1✔
58
                widget=HiddenInput, queryset=baseModel.rekord.get_queryset()
59
            )
60

61
        autor = forms.ModelChoiceField(
2✔
62
            queryset=Autor.objects.all(),
63
            widget=autocomplete.ModelSelect2(url="bpp:autor-autocomplete"),
64
        )
65

66
        jednostka = forms.ModelChoiceField(
2✔
67
            queryset=Jednostka.objects.all(),
68
            widget=autocomplete.ModelSelect2(url="bpp:jednostka-autocomplete"),
69
        )
70

71
        kierunek_studiow = forms.ModelChoiceField(
2✔
72
            label="Kierunek studiów",
73
            help_text="W przypadku autorów-studentów, możesz wpisać tu kierunek studiów, na jakim się znajdują. "
74
            "Pole opcjonalne. ",
75
            queryset=Kierunek_Studiow.objects.all(),
76
            required=False,
77
        )
78

79
        if include_dyscyplina:
2✔
80
            dyscyplina_naukowa = forms.ModelChoiceField(
2✔
81
                queryset=Dyscyplina_Naukowa.objects.all(),
82
                widget=autocomplete.ModelSelect2(
83
                    forward=["autor", "rok"],
84
                    url="bpp:dyscyplina-naukowa-przypisanie-autocomplete",
85
                ),
86
                required=False,
87
            )
88

89
        zapisany_jako = Select2ListChoiceField(
2✔
90
            widget=autocomplete.Select2(
91
                url="bpp:zapisany-jako-autocomplete", forward=["autor"]
92
            ),
93
        )
94

95
        typ_odpowiedzialnosci = forms.ModelChoiceField(
2✔
96
            queryset=Typ_Odpowiedzialnosci.objects.all(),
97
            initial=get_first_typ_odpowiedzialnosci,
98
        )
99

100
        oswiadczenie_ken = forms.NullBooleanField(
2✔
101
            label="Oświadczenie KEN",
102
            help_text="Oświadczenie Komisji Ewaluacji Nauki (Uniwersytet Medyczny w Lublinie). "
103
            "Wyklucza Upoważnienie PBN. ",
104
        )
105

106
        def __init__(self, *args, **kwargs):
2✔
107
            super().__init__(*args, **kwargs)
2✔
108

109
            # Ustaw inicjalną wartość dla pola 'afiliuje'
110
            domyslnie_afiliuje = True
2✔
111
            uczelnia = Uczelnia.objects.first()
2✔
112
            if uczelnia is not None:
2✔
113
                domyslnie_afiliuje = uczelnia.domyslnie_afiliuje
1✔
114
            self.fields["afiliuje"].initial = domyslnie_afiliuje
2✔
115

116
            # Nowy rekord
117
            instance = kwargs.get("instance")
2✔
118
            data = kwargs.get("data")
2✔
119
            if not data and not instance:
2✔
120
                if kwargs.get("initial"):
2✔
121
                    self.initial = kwargs.get("initial")
1✔
122
                    autor = self.initial.get("autor")
1✔
123
                else:
124
                    try:
2✔
125
                        autor = int(args[0]["autor"][0])
2✔
126
                    except (TypeError, ValueError, IndexError):
2✔
127
                        autor = None
2✔
128

129
                if autor is not None:
2✔
130
                    if isinstance(autor, int):
1✔
131
                        try:
1✔
132
                            autor = Autor.objects.get(pk=int(autor))
1✔
133
                        except Autor.DoesNotExist:
1✔
134

135
                            class autor:
1✔
136
                                imiona = "TakiAutor"
1✔
137
                                nazwisko = "NieIstnieje"
1✔
138
                                poprzednie_nazwiska = ""
1✔
139

140
                    warianty = warianty_zapisanego_nazwiska(
1✔
141
                        autor.imiona, autor.nazwisko, autor.poprzednie_nazwiska
142
                    )
143
                    warianty = list(warianty)
1✔
144

145
                    if self.initial.get("zapisany_jako", "") not in warianty:
1✔
146
                        warianty.append(self.initial.get("zapisany_jako"))
1✔
147

148
                    self.fields["zapisany_jako"] = Select2ListCreateChoiceField(
1✔
149
                        choice_list=list(warianty),
150
                        initial=self.initial.get("zapisany_jako"),
151
                        widget=autocomplete.Select2(
152
                            url="bpp:zapisany-jako-autocomplete", forward=["autor"]
153
                        ),
154
                    )
155

156
                return
2✔
157

158
            initial = None
1✔
159

160
            if instance:
1✔
161
                autor = instance.autor
1✔
162
                initial = instance.zapisany_jako
1✔
163

164
            if data:
1✔
165
                # "Nowe" dane z formularza przyszły
UNCOV
166
                zapisany_jako = data.get(kwargs["prefix"] + "-zapisany_jako")
×
UNCOV
167
                if not zapisany_jako:
×
168
                    return
×
169

UNCOV
170
                try:
×
UNCOV
171
                    autor = Autor.objects.get(pk=int(data[kwargs["prefix"] + "-autor"]))
×
172
                except Autor.DoesNotExist:
×
173

174
                    class autor:
×
175
                        imiona = "TakiAutor"
×
176
                        nazwisko = "NieIstnieje"
×
177
                        poprzednie_nazwiska = ""
×
178

179
            warianty = warianty_zapisanego_nazwiska(
1✔
180
                autor.imiona, autor.nazwisko, autor.poprzednie_nazwiska
181
            )
182
            warianty = list(warianty)
1✔
183

184
            if initial not in warianty and instance is not None:
1✔
185
                warianty.append(instance.zapisany_jako)
1✔
186

187
            self.initial["zapisany_jako"] = initial
1✔
188

189
            self.fields["zapisany_jako"] = Select2ListCreateChoiceField(
1✔
190
                choice_list=list(warianty),
191
                initial=initial,
192
                widget=autocomplete.Select2(
193
                    url="bpp:zapisany-jako-autocomplete", forward=["autor"]
194
                ),
195
            )
196

197
            include_oswiadczenie_ken = getattr(
1✔
198
                settings, "BPP_POKAZUJ_OSWIADCZENIE_KEN", False
199
            )
200
            if not include_oswiadczenie_ken:
1✔
201
                self.fields["oswiadczenie_ken"] = NullBooleanField(widget=HiddenInput())
1✔
202

203
        class Media:
2✔
204
            js = ["/static/bpp/js/autorform_dependant.js"]
2✔
205

206
        class Meta:
2✔
207
            DATA_OSWIADCZENIA = "data_oswiadczenia"
2✔
208

209
            fields = [
2✔
210
                "autor",
211
                "jednostka",
212
                "kierunek_studiow",
213
                "typ_odpowiedzialnosci",
214
                "zapisany_jako",
215
                "afiliuje",
216
                "zatrudniony",
217
                UPOWAZNIENIE_PBN,
218
                "oswiadczenie_ken",
219
                "procent",
220
                "profil_orcid",
221
                DATA_OSWIADCZENIA,
222
                "kolejnosc",
223
            ]
224

225
            if include_dyscyplina:
2✔
226
                data_oswiadczenia_idx = fields.index(DATA_OSWIADCZENIA)
2✔
227
                fields = (
2✔
228
                    fields[:data_oswiadczenia_idx]
229
                    + [
230
                        "dyscyplina_naukowa",
231
                        "przypieta",
232
                    ]
233
                    + fields[data_oswiadczenia_idx:]
234
                )
235

236
            if include_rekord:
2✔
237
                fields = [
1✔
238
                    "rekord",
239
                ] + fields
240

241
            model = baseModel
2✔
242
            widgets = {"kolejnosc": HiddenInput, "rekord": HiddenInput}
2✔
243

244
    return baseModel_AutorForm
2✔
245

246

247
def generuj_inline_dla_autorow(baseModel, include_dyscyplina=True):
2✔
248
    MAKSYMALNA_ILOSC_AUTOROW_W_FORMULARZU = 25
2✔
249

250
    class baseModel_AutorFormset(forms.BaseInlineFormSet):
2✔
251
        def get_queryset(self):
2✔
252
            qs = super().get_queryset()
2✔
253
            return qs[:MAKSYMALNA_ILOSC_AUTOROW_W_FORMULARZU]
2✔
254

255
        def clean(self):
2✔
256
            # get forms that actually have valid data
UNCOV
257
            percent = Decimal("0.00")
1✔
UNCOV
258
            for form in self.forms:
1✔
UNCOV
259
                try:
×
UNCOV
260
                    if form.cleaned_data:
×
UNCOV
261
                        percent += form.cleaned_data.get(
×
262
                            "procent", Decimal("0.00")
263
                        ) or Decimal("0.00")
264
                except AttributeError:
×
265
                    # annoyingly, if a subform is invalid Django explicity raises
266
                    # an AttributeError for cleaned_data
267
                    pass
×
UNCOV
268
            if percent > Decimal("100.00"):
1✔
269
                raise forms.ValidationError(
×
270
                    "Liczba podanych procent odpowiedzialności przekracza 100.0"
271
                )
272

273
    baseClass = admin.StackedInline
2✔
274
    extraRows = 0
2✔
275

276
    from django.conf import settings
2✔
277

278
    if getattr(settings, "INLINE_DLA_AUTOROW", "stacked") == "tabular":
2✔
279
        baseClass = admin.TabularInline
×
280
        extraRows = 1
×
281

282
    class baseModel_AutorInline(
2✔
283
        KorzystaZNumeruZgloszeniaInlineMixin,
284
        KorzystaZCrossRefAPIAutorInlineMixin,
285
        baseClass,
286
    ):
287
        model = baseModel
2✔
288
        extra = extraRows
2✔
289
        form = generuj_formularz_dla_autorow(
2✔
290
            baseModel,
291
            include_rekord=False,
292
            include_dyscyplina=include_dyscyplina,
293
        )
294
        formset = baseModel_AutorFormset
2✔
295
        sortable_field_name = "kolejnosc"
2✔
296
        sortable_excludes = [
2✔
297
            "typ_odpowiedzialnosci",
298
            "zapisany_jako",
299
            "afiliuje",
300
        ]
301

302
        # Maksymalna ilosć autorów edytowanych w ramach formularza. Pozostałych
303
        # autorów nalezy edytować przez opcję "Edycja autorów"
304
        max_num = MAKSYMALNA_ILOSC_AUTOROW_W_FORMULARZU
2✔
305
        verbose_name_plural = (
2✔
306
            f"Powiązania autorów z rekordem - jeżeli potrzebujesz więcej, jak {max_num}, "
307
            f"edytuj je przy pomocy przycisku 'Autorzy' na górze formularza"
308
        )
309

310
    return baseModel_AutorInline
2✔
311

312

313
#
314
# Kolumny ze skrótami
315
#
316

317

318
class KolumnyZeSkrotamiMixin:
2✔
319
    def charakter_formalny__skrot(self, obj):
2✔
320
        return obj.charakter_formalny.skrot
×
321

322
    charakter_formalny__skrot.short_description = "Char. form."
2✔
323
    charakter_formalny__skrot.admin_order_field = "charakter_formalny__skrot"
2✔
324

325
    def typ_kbn__skrot(self, obj):
2✔
326
        return obj.typ_kbn.skrot
×
327

328
    typ_kbn__skrot.short_description = "typ MNiSW/MEiN"
2✔
329
    typ_kbn__skrot.admin_order_field = "typ_kbn__skrot"
2✔
330

331

332
class RestrictDeletionToAdministracjaGroupMixin:
2✔
333
    def get_action_choices(self, request, default_choices=BLANK_CHOICE_DASH):
2✔
334
        if "administracja" in [x.name for x in request.user.cached_groups]:
×
335
            return admin.ModelAdmin.get_action_choices(self, request, default_choices)
×
336
        return []
×
337

338
    def has_delete_permission(self, request, obj=None):
2✔
339
        if "administracja" in [x.name for x in request.user.cached_groups]:
2✔
340
            return admin.ModelAdmin.has_delete_permission(self, request, obj=obj)
×
341
        return False
2✔
342

343

344
class RestrictDeletionToAdministracjaGroupAdmin(
2✔
345
    RestrictDeletionToAdministracjaGroupMixin, admin.ModelAdmin
346
):
347
    pass
2✔
348

349

350
class PreventDeletionMixin:
2✔
351
    def has_delete_permission(self, request, obj=None):
2✔
352
        return False
2✔
353

354
    def get_action_choices(self, request, default_choices=BLANK_CHOICE_DASH):
2✔
355
        return []
×
356

357

358
class PreventDeletionAdmin(PreventDeletionMixin, admin.ModelAdmin):
2✔
359
    pass
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