Zapamiętywanie stanu AJAX-owych formularzy w Django

Problem, który chciałbym poruszyć w tym wpisie, dotyczy formularzy, których wartości ładowane są AJAX-em i których stan ma być zapamiętany po przeładowaniu strony. Załóżmy, że mamy formularz do wyszukiwania jakichś elementów przypisanych do jednostek terytorialnych. Możemy wybrać województwo, powiat, gminę, miasto. Wszystkie te wartości są słownikowe (zaimplementowany TERYT). Ogólnie w Polsce jest dużo gmin, a jeszcze więcej miast. Ładowanie wszystkich miast do select-a nie wchodzi zatem w rachubę, więc naturalnym rozwiązaniem jest stosowanie AJAX-a - jak wybiorę województwo, ładują się powiaty do selecta, jak wybiorę powiat, ładują się gminy do selecta itd. Jednak co, jeżeli wybiorę już sobie województwo, powiat, gminę i miasto i kliknę szukaj? Po przeładowaniu strony formularz wyszukiwania będzie zawierał tylko listę województw, a reszta się wyczyści. Jest jednak metoda, którą można sobie z tym poradzić.

Formularz w pierwotnej postaci wygląda tak:

class SzukajFirme(forms.Form):  
        wojewodztwo = forms.ChoiceField(choices=pobierz_wojewodztwa(), required=False, label="Województwo")
        powiat = forms.ChoiceField(choices=(('', '-----'),), required=False, label="Powiat")
        gmina = forms.ChoiceField(choices=(('', '-----'),), required=False, label="Gmina")
        miejscowosc = forms.ChoiceField(choices=(('', '-----'),), required=False, label="Miejscowość")

Całość można rozwiązać na poziomie formularza (widziałem już rozwiązania w których używano widoku). Aby załadować wybrane wartości, musimy dostać się do danych przesyłanych przez POST. Jest to możliwe poprzez odczytanie w metodzie __init__() argumentu przekazanego do formularza. Jak już mamy dane z post-a, to reszta jest prosta - wystarczy pobrać kolejno poszczególne poziomy struktury podziału terytorialnego. A oto jak to wygląda:

class SzukajFirme(forms.Form):  
        def __init__(self, *args, **kwargs):
                super(SzukajFirme, self).__init__(*args, **kwargs)
                try:
                        post = args[0]
                        if post['wojewodztwo']:
                                pow = Powiaty.objects.filter(wojewodztwo=post['wojewodztwo']).all()
                                self.fields['powiat'].choices = [('','-----'),] + map(lambda x: (x.id, x.nazwa), pow)           
                        else:
                                self.fields['powiat'].choices = (('','-----'),)
                        if post['powiat']:
                                gmi = Gminy.objects.filter(powiat=post['powiat']).all()
                                self.fields['gmina'].choices = [('','-----'),] + map(lambda x: (x.id, x.nazwa), gmi)    
                        else:
                                self.fields['gmina'].choices = (('','-----'),)          
                        
                        if post['gmina']:
                                miej = Miejscowosci.objects.filter(gmina=post['gmina']).all()
                                self.fields['miejscowosc'].choices = [('','-----'),] + map(lambda x: (x.id, x.nazwa), miej)     
                        else:
                                self.fields['miejscowosc'].choices = (('','-----'),)
                except:
                        pass            
                        
        wojewodztwo = forms.ChoiceField(choices=pobierz_wojewodztwa(), required=False, label="Województwo")
        powiat = forms.ChoiceField(choices=(('', '-----'),), required=False, label="Powiat")
        gmina = forms.ChoiceField(choices=(('', '-----'),), required=False, label="Gmina")
        miejscowosc = forms.ChoiceField(choices=(('', '-----'),), required=False, label="Miejscowość")

Po przeładowaniu strony, w formularzu załadują się wybrane wcześniej pozycje, wraz z elementami podrzędnymi (np. jak wybrano województwo, to będą załadowane powiaty). Osobiście najbardziej podoba mi się to, że nie trzeba angażować do tego żadnego widoku, wszystko można zrobić na poziomie formularza, to formularz sam martwi się o swoje dane.

Debugowanie Django w Winpdb

Django posiada wbudowaną stronę służącą do zgłaszania błędów, dzięki której można zidentyfikować źródło błędu. Pojawia się zawsze gdy wystąpi nieobsłużony wyjątek, pod warunkiem, że w ustawieniach opcja DEBUG przyjmuje wartość True. Każdy developer Django pewnie widział ją nie raz. Jednak błędy oprogramowania, to nie tylko wyjątki, ale także niepoprawnie wyświetlane dane - tego Django już nie wyłapie. Czasami też zdaża się, że informacja ze strony błędu, to za mało, żeby zidentyfikować błąd. Można wtedy posiłkować się funkcją print, której zwróconą wartość widać w konsoli, na której został uruchomiony serwer developerski. Jest to jednak przydatne przy naprawie drobnych błędów, gdzie szybko można zorientować się co może być ich przyczyną. Korzystanie z print na pewno nie jest debugowaniem z prawdziwego zdarzenia. Żeby dokładnie prześledzić działanie stworzonego skryptu, najlepiej posłużyć się narzędziem przeznaczonym do tego. Takim narzędziem jest Winpdb - graficzna wersja pdb (Python Debugger).

Debugowanie aplikacji stworzonych w Django jest prostsze, niż mogłoby się to wydawać. Do prezentacji posłużyłem się bardzo prostym, testowym projektem:

test_project/
|-- __init__.py
|-- manage.py
|-- settings.py
|-- test_app
|   |-- __init__.py
|   |-- models.py
|   `-- views.py
`-- urls.py

Kod który chcę prześledzić w poszukiwaniu błędów znajduje się w pliku test_app/views.py

def test_view(request):
        a = 1
        b = 3
        c = a + b

        print c

Pierwszym krokiem do prześledzenia skryptu, jest uruchomienie developerskiego serwera Django z poziomu Winpdb. Najpierw wybieramy "File -> Launch" W oknie które się pojawi,trzeba wpisać polecenie, które uruchomi serwer developerski.

Launch command

W moim przypadku było to:

/home/jaro/strony/test_project/manage.py runserver --noreload

Opcja noreload umożliwia debugowanie i zatrzymywanie się wykonywania skryptu w breakpointach. Skrypt zostaje załadowany i żeby go uruchomić trzeba kliknąć na ikonkę play. Spowoduje to uruchomienie serwera developerskiego Django. W konsoli wyświetli się komunikat serwera.

Konsola Winpdb

Do zatrzymania wykonywania skryptu w wybranym miejscu, można posłużyć się breakpointem. W pierwszej kolejności trzeba otworzyć plik, w którym breakpoint będzie ustawiony (File -> Open Source). Plik załaduje się do okna "Source".

Breakpoint

Breakpoint ustawia się poprzez kliknięcie na linię kodu, która z kolei zostanie wyróżniona czerwonym kolorem, tak jak jest to widoczne na powyższym rysunku.

Już wszystko jest gotowe do debugowania, teraz wystarczy wejść w przeglądarce na stronę, która uruchomi interesujący nas widok (np. http://localhost:8000/). Strona nie załaduje się, a jak przejdziemy do debuggera, to zobaczymy skrypt zatrzymany we wskazanej wcześniej linii.

Debugowanie skryptu

W widocznych oknach można zobaczyć aktualne wartości zmiennych, uruchomione wątki, stos, konsolę oraz kod źródłowy, którego kolejne linie można wykonywać interaktywnie, korzystając z przycisków w pasku narzędzi.