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

iplweb / bpp / #820

19 Oct 2025 06:59PM UTC coverage: 65.093% (+5.3%) from 59.791%
#820

push

coveralls-python

Michał Pasternak
Fixes

4215 of 9430 branches covered (44.7%)

Branch coverage included in aggregate %.

27562 of 39388 relevant lines covered (69.98%)

0.7 hits per line

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

96.02
src/pbn_export_queue/models.py
1
import sys
1✔
2
import traceback
1✔
3
from enum import Enum
1✔
4

5
import rollbar
1✔
6
from django.contrib.contenttypes.fields import GenericForeignKey
1✔
7
from django.contrib.contenttypes.models import ContentType
1✔
8
from django.core.exceptions import ObjectDoesNotExist
1✔
9
from django.db import models
1✔
10
from django.db.models import PositiveIntegerField
1✔
11
from django.urls import reverse
1✔
12
from django.utils import timezone
1✔
13

14
from django_bpp.settings.base import AUTH_USER_MODEL
1✔
15
from pbn_api.exceptions import (
1✔
16
    AccessDeniedException,
17
    AlreadyEnqueuedError,
18
    CharakterFormalnyNieobslugiwanyError,
19
    NeedsPBNAuthorisationException,
20
    PKZeroExportDisabled,
21
    PraceSerwisoweException,
22
    ResourceLockedException,
23
)
24

25

26
class PBN_Export_QueueManager(models.Manager):
1✔
27
    def filter_rekord_do_wysylki(self, rekord):
1✔
28
        return self.filter(
1✔
29
            content_type=ContentType.objects.get_for_model(rekord),
30
            object_id=rekord.pk,
31
            wysylke_zakonczono=None,
32
        )
33

34
    def sprobuj_utowrzyc_wpis(self, user, rekord):
1✔
35
        if self.filter_rekord_do_wysylki(rekord).exists():
1✔
36
            raise AlreadyEnqueuedError("ten rekord jest już w kolejce do wysyłki")
1✔
37

38
        return self.create(
1✔
39
            rekord_do_wysylki=rekord,
40
            zamowil=user,
41
        )
42

43

44
class SendStatus(Enum):
1✔
45
    RETRY_SOON = 0  # few seconds, 423 Locked
1✔
46
    RETRY_LATER = 1  # few minutes
1✔
47
    RETRY_MUCH_LATER = 2  # few hours, PraceSerwisoweExecption
1✔
48

49
    RETRY_AFTER_USER_AUTHORISED = 3  # when user logs in + authorizes
1✔
50

51
    FINISHED_OKAY = 5
1✔
52
    FINISHED_ERROR = 6
1✔
53

54

55
def model_table_exists(model):
1✔
56
    """Check if a model's table exists"""
57
    from django.db import connection
1✔
58

59
    table_name = model._meta.db_table
1✔
60
    return table_name in connection.introspection.table_names()
1✔
61

62

63
class PBN_Export_Queue(models.Model):
1✔
64
    objects = PBN_Export_QueueManager()
1✔
65

66
    object_id = PositiveIntegerField()
1✔
67
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
1✔
68
    rekord_do_wysylki = GenericForeignKey()
1✔
69

70
    zamowil = models.ForeignKey(AUTH_USER_MODEL, on_delete=models.CASCADE)
1✔
71

72
    zamowiono = models.DateTimeField(auto_now_add=True, db_index=True)
1✔
73

74
    wysylke_podjeto = models.DateTimeField(null=True, blank=True)
1✔
75
    wysylke_zakonczono = models.DateTimeField(null=True, blank=True, db_index=True)
1✔
76

77
    ilosc_prob = models.PositiveSmallIntegerField(default=0)
1✔
78
    zakonczono_pomyslnie = models.BooleanField(null=True, default=None, db_index=True)
1✔
79
    komunikat = models.TextField(null=True, blank=True)
1✔
80

81
    retry_after_user_authorised = models.BooleanField(
1✔
82
        null=True, default=None, db_index=True
83
    )
84

85
    class Meta:
1✔
86
        verbose_name = "Kolejka eksportu do PBN"
1✔
87
        verbose_name_plural = "Kolejka eksportu do PBN"
1✔
88
        ordering = ("-zamowiono", "zamowil")
1✔
89

90
    def __str__(self):
1✔
91
        return f"Zlecenie wysyłki do PBN dla {self.rekord_do_wysylki}"
1✔
92

93
    @property
1✔
94
    def ostatnia_aktualizacja(self):
1✔
95
        """Returns the most recent update timestamp"""
96
        if self.wysylke_zakonczono:
1✔
97
            return self.wysylke_zakonczono
1✔
98
        elif self.wysylke_podjeto:
1✔
99
            return self.wysylke_podjeto
1✔
100
        else:
101
            return self.zamowiono
1✔
102

103
    def check_if_record_still_exists(self):
1✔
104
        if not self.content_type_id:
1✔
105
            return False
1✔
106

107
        try:
1✔
108
            # Check iftable exists for that model...
109
            if not model_table_exists(self.content_type.model_class()):
1✔
110
                return False
1✔
111

112
            try:
1✔
113
                if self.content_type.get_object_for_this_type(pk=self.object_id):
1!
114
                    return True
1✔
115

116
            except ObjectDoesNotExist:
1✔
117
                return False
1✔
118
        except self.content_type.model_class().DoesNotExist:
×
119
            return False
1✔
120

121
    def prepare_for_resend(self, user=None, message_suffix=""):
1✔
122
        """Przygotowuje obiekt do ponownej wysyłki."""
123
        self.refresh_from_db()
1✔
124
        self.wysylke_zakonczono = None
1✔
125
        self.zakonczono_pomyslnie = None
1✔
126
        self.retry_after_user_authorised = None
1✔
127

128
        msg = "Ponownie wysłano"
1✔
129
        if user is not None:
1✔
130
            self.zamowil = user
1✔
131
            msg += f" przez użytkownika: {user}"
1✔
132
        if message_suffix is not None:
1!
133
            msg += f"{message_suffix}"
1✔
134
        msg += ". "
1✔
135
        self.dopisz_komunikat(msg)
1✔
136

137
        self.save()
1✔
138

139
    def sprobuj_wyslac_do_pbn(self):
1✔
140
        from pbn_export_queue.tasks import task_sprobuj_wyslac_do_pbn
1✔
141

142
        task_sprobuj_wyslac_do_pbn.delay(self.pk)
1✔
143

144
    def dopisz_komunikat(self, msg):
1✔
145
        res = str(timezone.now())
1✔
146
        res += "\n" + "==============================================================="
1✔
147
        res += "\n" + msg + "\n"
1✔
148
        if self.komunikat:
1✔
149
            self.komunikat = "\n" + res + "\n" + self.komunikat
1✔
150
        else:
151
            self.komunikat = res
1✔
152

153
    def error(self, msg):
1✔
154
        self.wysylke_zakonczono = timezone.now()
1✔
155
        self.zakonczono_pomyslnie = False
1✔
156
        self.dopisz_komunikat(msg)
1✔
157
        self.save()
1✔
158
        return SendStatus.FINISHED_ERROR
1✔
159

160
    def send_to_pbn(self):
1✔
161
        """
162
        :return: (int : SendStatus,)
163
        """
164

165
        if not self.check_if_record_still_exists():
1✔
166
            if self.wysylke_zakonczono is not None:
1✔
167
                raise Exception(
1✔
168
                    "System próbuje ponownie wysyłać rekordy, których wysyłać nie powinien"
169
                )
170

171
            return self.error("Rekord został usunięty nim wysyłka była możliwa.")
1✔
172

173
        self.wysylke_podjeto = timezone.now()
1✔
174
        if self.retry_after_user_authorised:
1✔
175
            self.retry_after_user_authorised = (
1✔
176
                None  # Zresetuj wartosc tego pola, rekord wysyłany n-ty raz
177
            )
178
        self.ilosc_prob += 1
1✔
179
        self.save()
1✔
180

181
        from bpp.admin.helpers.pbn_api.cli import sprobuj_wyslac_do_pbn_celery
1✔
182

183
        try:
1✔
184
            sent_data, notificator = sprobuj_wyslac_do_pbn_celery(
1✔
185
                user=self.zamowil.get_pbn_user(),
186
                obj=self.rekord_do_wysylki,
187
                force_upload=True,
188
            )
189
        except PraceSerwisoweException:
1✔
190
            self.dopisz_komunikat("Prace serwisowe w PBN, spróbuję za kilka godzin")
1✔
191
            self.save()
1✔
192
            return SendStatus.RETRY_MUCH_LATER
1✔
193

194
        except NeedsPBNAuthorisationException:
1✔
195
            self.dopisz_komunikat(
1✔
196
                "Użytkownik bez autoryzacji w PBN, spróbuję po zalogowaniu do PBN."
197
            )
198
            self.retry_after_user_authorised = True
×
199
            self.save()
×
200
            return SendStatus.RETRY_AFTER_USER_AUTHORISED
×
201

202
        except CharakterFormalnyNieobslugiwanyError:
1✔
203
            self.error(
1✔
204
                "Charakter formalny tego rekordu nie jest ustawiony jako wysyłany do PBN. Zmień konfigurację "
205
                "bazy BPP, Redagowanie -> Dane systemowe -> Charaktery formalne"
206
            )
207
            return SendStatus.FINISHED_ERROR
1✔
208

209
        except AccessDeniedException:
1✔
210
            return self.error(
1✔
211
                "Brak uprawnień, załączam traceback:\n" + traceback.format_exc()
212
            )
213
            return SendStatus.FINISHED_ERROR
214

215
        except PKZeroExportDisabled:
1✔
216
            self.error(
1✔
217
                "Eksport prac bez punktów PK wyłączony w konfiguracji, nie wysłano."
218
            )
219
            return SendStatus.FINISHED_ERROR
1✔
220

221
        except ResourceLockedException as e:
1✔
222
            self.dopisz_komunikat(f"{e}, ponowiam wysyłkę za kilka minut...")
1✔
223
            self.save()
×
224
            return SendStatus.RETRY_LATER
1✔
225

226
        except Exception:
1✔
227
            rollbar.report_exc_info(sys.exc_info())
1✔
228
            return self.error(
1✔
229
                "Wystąpił nieobsługiwany błąd, załączam traceback:\n"
230
                + traceback.format_exc()
231
            )
232

233
        if sent_data is None:
1✔
234
            return self.error(
1✔
235
                "Wystąpił błąd, dane nie zostały wysłane, wyjaśnienie poniżej.\n\n"
236
                + "\n".join(notificator)
237
            )
238

239
        msg = (
1✔
240
            "Wysłano poprawnie. Link do wysłanego kodu JSON <a href="
241
            + reverse("admin:pbn_api_sentdata_change", args=[sent_data.pk])
242
            + ">tutaj</a>. "
243
        )
244
        extra_info = "\n".join(notificator)
1✔
245
        # Jeżeli notyfikator zawiera cokolwiek, a może zawierać np ostrzeżenia czy uwagi do
246
        # wysłanego rekordu to dołącz to
247
        if extra_info:
1✔
248
            msg += "\n\nDodatkowe informacje:\n" + extra_info
1✔
249

250
        self.wysylke_zakonczono = timezone.now()
1✔
251
        self.dopisz_komunikat(msg)
1✔
252
        self.zakonczono_pomyslnie = True
1✔
253
        self.save()
1✔
254

255
        return SendStatus.FINISHED_OKAY
1✔
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