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.

Komentarze

  1. 24 marca 2010 | #

    DRY! http://pastie.org/883994
    A co do treści: dobrze wiedzieć, gorzej jak przyjdzie nowa wersja Django i pozmieniają kolejność argumentów ;)

  2. 25 marca 2010 | #

    takie dodatkowe uproszczenie
    zamiast
    pow = Powiaty.objects.filter(wojewodztwo=post['wojewodztwo']).all()
    self.fields['powiat'].choices = [('','-----'),] + map(lambda x: (x.id, x.nazwa), pow)

    self.fields['powiat'].choices = [('','-----'),] + list(Powiaty.objects.filter(wojewodztwo=post['wojewodztwo']).values_list('id','nazwa'))

    i oczywiście DRY :)

  3. 26 marca 2010 | #

    @Pyetras: ten formularz jest okrojony, oryginalnie ma więcej pól, więc Twój kod nie miałby zastosowania w oryginale. Ale przyznam, że na potrzeby wpisu mogłem przerobić kod. Dzięki za konstruktywny komentarz.

  4. 26 marca 2010 | #

    Wróć, z oryginalnym formularzem, też zadziała.

  5. 26 marca 2010 | #

    Ha! :D

  6. Dreamwalker
    11 sierpnia 2010 | #

    @Pyetras
    Można i tak:
    http://pastie.org/1085508

    :D

Napisz komentarz