Open rollbar[bot] opened 2 years ago
To prawdopodobnie będzie wymagać pewnego (ale chyba nie jakiegoś bardzo bolesnego) wgryzienia się w niuanse Django.
Hipoteza do zweryfikowania na początek (a przynajmniej miejsce, od którego można zacząć) jest taka, że domyślna walidacja formularza nie chroni nas przed próbą wstawienia do bazy "nielegalnych" danych dlatego, że tylko pole answer
modelu Preference
pojawia się w definicji klasy PreferenceForm
; można więc spróbować dodać tam więcej pól (ale oczywiście nie chcemy, by w formularzu pojawiły się nowe odpowiadające im kontrolki) bądź przeciążyć metodę is_valid
lub jakąś powiązaną.
Zasugerowana hipoteza była prawdziwa. Formularz PreferenceForm
w definicji klasy używał jedynie pola answer
i tym samym tylko ono podlegało domyślnej walidacji. Aplikacja preferences
opiera się na dynamicznie generowanej klasie tworzonej za pomocą modelformset_factory
. Klasa ta dziedziczy z BaseModelFormSet
, która z kolei dziedziczy z BaseFormSet
.
Podczas generowania klasy za pomocą modelformset_factory
przekazany musi być model dla, którego tworzony będzie formset oraz można przekazać parametr form
, który będzie wzorcem według którego będą tworzone poszczególne formularze.
W naszym przypadku odpowiednio Preference
oraz PreferenceForm
).
Następnie tworząc instancję tak wygenerowanej klasy można przekazać parametr queryset
, który określa wartości jakimi zostaną wypełnione poszczególne formularze (1 formularz per rekord (?)).
W naszym przypadku jest to Preference.objects.filter(employee=employee).order_by('question__proposal')
(patrz prepare_formset
w preference/forms.py
).
To wiąże te formularze z tymi danymi (bound), więc wszystkie operacje na tych danych będą powodować modyfikację istniejących danych a nie tworznie nowych.
Dodatkowo formset wyróżnia dwa rodzaje formualrzy initial
oraz extra
. initial
to lista formularzy, do których dane zostały wczytane z systemu (to są te bound
formularze) a extra
- przeciwnie - to lista formularzy, które zostały wygenerowane jako puste i mają służyć tworzeniu nowych instacji modelu.
Ilość tych formularzy może być dynamicznie zarządzana przez JS na frontendzie dlatego wartości tych zmiennych wyrażone są w DOM'ie poprzez:
<input type="hidden" name="form-TOTAL_FORMS" value="22" id="id_form-TOTAL_FORMS">
<input type="hidden" name="form-INITIAL_FORMS" value="22" id="id_form-INITIAL_FORMS">
Manipulacja wartością form-INITIAL_FORMS
poprzez zmniejszenie jej o n
spowoduje, że django uzna n
ostatnich formularzy jako extra
a tym samym spóbuje stworzyć dla nich nowe instancje.
W tym celu na pewnym etapie wywoła save
na klasie formularza używanego przez formset. W naszym przypadku jest to PreferenceForm
, a ten formularz obsługuje jedynie pole answer
i tylko te pole waliduje, więc podczas próby zapisania brakujące wartości są null'owane co powoduje błąd.
Alternatywne podejście polegające na dodaniu pozostałych pól modelu i ukryciu ich w formularzu poprawnie odrzuci podczas walidacji i zgłosi w tym przypadku błąd łamania ograniczenia unique_together = ('employee', 'question')
z preferences/models.py
. Dzieje się tak dlatego, że tym razem dostępne są jakieś dane do utworzenia nowej instancji, ale ponieważ są to dane wcześniej odczytane z bazy to ich ponowna próba dodania łamie wspomniane ograniczenie.
Z tego co rozumiem, to nie ma opcji żeby wymusić statyczną ilość wpisów - najlepsze co można zrobić to przeciążyć funkcję walidującą żeby porównywała ilość zwracanych formularzy z oczekiwaną ilością wyliczaną z długości quertset'u z pytaniami.
Zamknąłem issue przypadkiem - reopen
View details in Rollbar: https://rollbar.com/iiuni/projektzapisy/items/483/