Dzień pierwszy - formularz HTML cd.

Jednak jeszcze nie nadszedł czas na Formularze Django - kontynuujemy poznawanie samego Formularza HTML. Uznałem, że skoro już, choć po trochu korzystamy z Bootstrapa, to czemu by nie pokazać przy użyciu dokumentacji Bootstrapa pozostałych elementów formularzy.

SelectList, czyli pole wyboru z listy zdefiniowanych opcji (także wielokrotnego wyboru multiple=multiple):

    <select class="form-control" name=Lista multiple=multiple>
        <option value=""> -- wybierz opcję -- </option>
        <option value=Kasia>1 </option>
        <option value=Basia>2</option>
        <option value=Gabrysia>3</option>
        <option value=Asia>4</option>
        <option valu=Ania>5</option>
    </select>

Checkboxy i Radiobuttony:

    <label>
        <input type="checkbox" value="mama" name=check> Can't check this
    </label>
    <div class="radio">
        <label>
            <input type="radio" name="optionsRadios" id="optionsRadios1" value="option1" checked>
            Option one is this and that&mdash;be sure to include why it's great
        </label>
    </div>
    <div class="radio">
        <label>
            <input type="radio" name="optionsRadios" id="optionsRadios2" value="option2">
            Option two can be something else and selecting it will deselect option one
        </label>
    </div>
    <div class="radio disabled">
        <label>
            <input type="radio" name="optionsRadios" id="optionsRadios3" value="option3" disabled>
            Option three is disabled
        </label>
    </div>

Wszystkie dane z formularze były dostępne w odpowiedniej tablicy/słowniku, czyli request.POST dla <form method="post"> i request.GET dla <form method="get">.

    def cats(request):
        if request.GET:
            raise Exception(request.GET)
        elif request.POST:
            raise Exception(request.POST)

Dla rozróżnienia metody post i get, można uprościć, że get to ta metoda, gdzie dane są widocznie/przesyłane w pasku url, a post to ta metoda, w której wszystko idzie utajone. Oczywiście należy pamiętać, że przy post w Django konieczne jest jeszcze dodanie {% csrf_token %}, co zabezpiecza nasz formularz przed potencjalnymi atakami i przed ogladaniem błędu na ekranie.

Input do wysyłki plików. Pliki będą widocznie w request.FILES, ale tylko gdy formularz, będzie miał odpowiedni atrybut enctype <form method=post enctype="multipart/form-data">:

<input type="file" name=my_file id="exampleInputFile">

W międzyczasie pozamienialiśmy wszystkie render_to_response() na render(). Pokazałem także, tak jakbym się chciał usprawiedliwić, że w mojej aplikacji z trochę starszym Django, to ciągle działa bez zarzutu, a w dla tej wersji Django to po prostu działa niepełnie i jak sie okazało jest opisany jako Depreciated (zdeprecjonowany, zdewaluowany) w dokumentacji.

Co ciekawe, po całej lekcji z użyciem Bootstrapa, rekrutce bardziej spodobał się domyślny html, niż ten ostylowany przez Bootstrap. Pewnie dlatego, że nie rozlewał się tak na stronie.


Dzień drugi - pierwszy formularz z modelu (ModelForm)

W końcu naszeszła ta wiekopomna chwila, czyli nasz pierwszy formularz z modelu (bazujący na modelu). Użycie klasy ModelForm, do tworzenia formularzy, jest jedną z tych funkcjonalności Django, która mocno przekonuje do siebie, a także jest zgodna z uwielbianą przeze mnie regułą DRY (Don't Repeat Yourself - nie powtarzaj się). W Django sporo dzieje się w koło modeli. Definiujemy model, a potem:

  • robimy migrację, uruchamiamy migrację i już mamy tabelę w bazie danych, zgodną ze strukturą zawartą w naszym modelu
  • zmieniamy coś w modelu? nie ma problemu - tworzymy i uruchamiamy migrację, i w tej samej chwili struktura naszej bazy danych zostanie zmieniona/uaktualniona
  • automatyczna walidacja danych zapisywanych za pomoca modelu
  • (w końcu) formularz na bazie modelu, wygenerowany automatycznie

Zaczynam od zaimportowania naszego ModelForm, następnie musimy zaimportować model, dla którego chcemy utworzyć formularz.

from django.forms import ModelForm
from .models import Cabaret, City, Show

Tworzymy klasę formularza. Zwyczajowo może się ona składać z nazwy modelu połączonego z 'Form', czyli w naszym przypadku CabaretForm. Skoro wcześniej już zaimportowaliśmy ModelForm, to wypada teraz rozszerzyć ten model naszą klasą, czyli użyć dziedziczenia po ModelForm. Dzięki dziedziczeniu, możemy w ekspresowym tempie zdefiniować dwa podstawowe parametry formularz. Przy pomocy klasy Meta, definiujemy model bazowy dla naszego formularze i exclude = [] (nie najlepszy, ale najszybszy sposób na zdefiniowanie pól, takim zapisem mówimy, że chcemy wszystkie pola).

class CabaretForm(ModelForm):
    class Meta:
        model = Cabaret
        exclude = []

No to teraz importujemy from .forms import CabaretForm, inicjujemy formularz CabaretForm():

        "form": CabaretForm(),
    }
    response = render(request, 'index.html', args)

i wyświetlamy w szablonie:

{{ form }}

Lekcję zakończyliśmy pokazaniem jak się wysyła formularze za pomoca AJAX-u (Asynchronous JavaScript and XML), czyli w jaki sposób lata temu znaleziono sposób by wysyłać/odbierać dane bez przeładowania całej strony.

<button type="button" onclick="$.post('', $('form').serialize(), function(response) { alert(response)})" >Send by ajax </button>

Po wciśnięciu przycisku 'Send by ajax', dane z formularza zostaną zserializowane (utworzenie ciągu tekstowego z danych w formularzu), wysłane AJAX-em, a wynik będzie wyświetlony za pomocą JavaScript-owej funkcji alert().

    if request.is_ajax():
        return HttpResponse("This was sent by AJAX, so without page reload.")
    elif request.POST:
        return HttpResponse('Not an AJAX request.')

Wynik oczywiście będzie zwracany przez nasz widok, a którym możemy zidentyfikować czy żądanie przyszło za pomocą AJAX-u. Jesli tak, wyświetlimy na ekran, za pomocą prostego HttpReponse() komunikat. Będzie on (komunikat) potem wyświetlany za pomocą, już wspomnianej funkcji alert(). Oczywiście nie zabrakło też omówienia przykładów użycia AJAX-u, choćby w tak popularnym Gmailu czy na Facebooku.


Dzień trzeci - fields/exclude, wyświetlanie i obsługa formularza

Jako, że ledwo co zdążyliśmy, przed zakończeniem wczorajszego dnia, dodać nasz pierwszy formularz bazujący na modelu, nowy dzień rozpoczęliśmy od powrotu do naszego CabaretForm(ModelForm). Zaczęliśmy do tematu exclude = [] vs fields = ['field1', 'field2'].

  • exclude - najszybszą opcją jest zazwyczaj wybranie opcji z exclude, czyli z deklaracją pól/kolumn, które nie powinny się pojawić w formularzu, szczególnie, że często exclude będzie, po prostu pustą listą. Może się też zdarzyć, że będziemy mieć Model z dwudziestoma kolumnami/polami i dzięki exclude nie będziemy musieli ich wszystkich wypisywać.
  • fields - Niemniej jednak to opcja z deklaracją fields, czyli zadeklarowaniem pól/column, które powinny się pojawić w formularzu, jest bardziej bezpieczna (a co za tym idzie zalecana), gdyż w każdej chwili model może zmienić swoją strukturę, a wtedy w formularzu mogą pojawić się przypadkowo pola, które nie powinny. Dodatkowym atutem opcji z fields, jest to, że pola formularza będą pojawiać się w kolejności ustalonej w fields.

Jak już wspomniałem, zamykaliśmy wczorajszy dzień w pośpiechu, więc jedyne co zdążyliśmy, to w ekspresowym tempie utworzyć formularz bazujący na modelu Cabaret i wyświetlić 'goły' {{ form }} w szablonie, a co za tym idzie, na tym etapie nasz formularz był ciągle niekompletny i bezużyteczny. Rekrut dostał podpowiedź, że jeśli będziemy wysyłać formularz za pomocą method="post", to brakuje nam trzech elementów, w przeciwnym razie brakuje nam tylko dwóch. Przyjrzeliśmy się więc trochę mocniej dokumentacji i wywołanie {{ form }}:

  • opakowaliśmy w <form></form>, jednocześnie określając method="post",
  • dodalismy {% csrf_token %}, który jest wymagany przy methodzie post,
  • wstawilismy przycisk <input type="submit" value="Submit" />
    <form action="" method="post"> 
        {% csrf_token %}
        {{ form }}
        <input type="submit" value="Submit" />
    </form>

Obsłużenie formularza w widoku:

  • sprawdzamy czy mamy dane w słowniku request.POST (tak, możemy policzyć ilość elementów słownika), jeśli są dane to znaczy, że fomularz został wysłany
  • jeśli został wyslany, to wstawiamy nasze dane z request.POST do CabaretForm i przypisujemy do zmiennej form
    • następnie za pomocą .is_valid() sprawdzamy czy mamy poprawne dane (fajnie, że nie musimy tego robić sami, ModelForm sam to za nas zrobi)
    • jeśli dane są prawidłowe, zapiszemy formularz .save(), co będzie równoznaczne z dodaniem do bazy nowego kabaretu
  • jesli okaże się, że nie wysłaliśmy formularza, to zainicujujemy formularz bez danych i także przypiszemy go do zmiennej form
    if len(request.POST) > 0:
        form = CabaretForm(request.POST)
        if form.is_valid():
            form.save()
    else:
        form = CabaretForm()

Pozostało jedynie przekazać do szablonu zmienną form i już mamy pełną obsługę formularza:

        # "form": CabaretForm(),
        "form": form,        
     }
     response = render(request, 'index.html', args)

Dzień czwarty - locals(), globals(), skrócony if

Błąd niezdefiniowanej zmiennej. W views.py zakomentowaliśmy kod obsługujący nasz pierwszy formularz z modelu (CabaretForm), aby narazie bez sprawdzania, z jakiego formularza przychodzą dane, obsłużyć następny formularz, który dopiero powstanie. Jako, że zniknęła w ten sposób deklaracja zmiennej form =, można było spodziewać się, że nasza aplikacja 'walnie' błędem:

W języku Python (odwrotnie niż w języku Szablonów Django), odwołanie się do zmiennej, która nie została jeszcze zadeklarowana powoduje błąd. Ma to sens, gdyż obecność tych danych może być, nawet nie tylko niezbędna, ale brak tych danych, może być niebezpieczny dla samej aplikacji i bezpieczeństwa danych. W Django Templates (Szablonach Django) domyślnie, brak zmiennej po prostu zostanie zignorowany.

Wracając do tematu. Oczywiście najlepszym rozwiązaniem jest zadeklarowanie wszystkich zmienny już na poczatku, co w niektórych językach programowania jest wręcz niezbędne, w Pythonie jest uznawane za dobrą praktykę. Możemy też nasz problem rozwiązać w inny sposób, czyli sprawdzić czy mamy w naszej aplikacji zmienną. W języku Python mamy do dyspozycji dwie wbudowane funkcje:

  • locals() - funkcja, która zwróci nam słownik, którego kluczami locals().keys(), będą wszystkie nazwy zmiennych, które do tej pory zostały przez nas zadeklarowane w danym fragmencie kodu,
  • globals() - funkcja, która zwraca nam słownik, który zawiera wszystkie (także te nie zadeklarowane przez nas) zmienne, m.in. zaimportowane moduły albo __doc__

W jaki sposób możemy użyć naszych nowo poznanych funkcji, by ustrzec się przed 'wyrzuceniem błędu'? Wystarczy sprawdzić, czy mamy naszą zmienną (lokalnie lub globalnie). Jako, że nasze funkcje generują słownik, zrobimy po prostu sprawdzając czy zmienne znajdują się w słowniku, czyli: 'form' in locals() # True if form variable exists locally

Można byłoby, wywołać poniższy kod przed każdym użyciem zmiennej form, ale...

    if 'form' in locals():
        form = form
    else:
        form = None

... będzie to mocno niepraktyczne.

Z pomoca przychodzi nam skrócona wersja warunku if else. Dość ciekawa konstrukcja, czyli:

<zrób coś> if <jakiś warunek zostanie spełniony> else <zrób coś innego(bo warunek nie został spełniony>

W naszym przypadku, 'zrobieniem czegoś` będzie wstawienie form albo None, w zależności do tego czy warunek został spełniony warunku, czyli:

    args = {
        "form": form if "form" in locals() else None,
    }

Na koniec zdążyliśmy jeszcze napoknąć o The Zen of Python i omówić na szybko 2-3 losowe reguły.


Dzień piąty - rekrutka samodzielnie tworzy kolejny formularz wraz z zapisem do bazy

Dzisiaj prawie samodzielna praca, czyli powtórka z czwartku. Zadanie do wykonania - dodać i obsłużyć kolejny formularz bazujący na modelu.

Co trzeba było zrobić:

  • wybrać model - zostały City i Show
  • w pliku forms.py dodać nową klasę np. CityForm, dziedziczącą po ModelForm, określić model i pola (fields lub exclude)
  • zaimportować nasz nowy formularz, tudzież zamportować wszystkie formularze za pomocą gwiazdki(*)
  • w widoku utworzyć obiekt formularza, wypełnić danymi jeśli mamy request.POST, przypisać do zmiennej, zwalidować i zapisać wynik za pomocą .save()
    elif len(request.POST) > 0:
        form_city = CityForm(request.POST)
        if form_city.is_valid():
             form_city.save()
    else:
        form_city = CityForm()
  • dodać nasz formularz jako argument do szablonu
        "form_city": form_city if "form_city" in locals() else None,
    }
  • w szablonie dodać formularz, który wysłaliśmy tego szablonu, a także dodać brakujące elementy
    <form action="" method="post">
        {% csrf_token %}
        {{ form_city }}
        <input type="submit" value="Submit" />
    </form>

Dzień szósty - redirect-y i czas na zmiany w kursie

Ostatni dzień tygodnia był bardzo owocny, lecz nie dlatego, że dużo zrobiliśmy. Było wręcz odwrotnie. To był dzień pełnego blackout-u. Ręce opadały, gdy kilka elementów podstawowych było barierą nie do przekroczenie i gdy rekrutka pewne elementy chciała łączyć bez ładu i skladu. Nie mniej jednak, był to niezwykle owocny dzień, gdyż od tego dnia nie dało się, już ukryć faktu, że coś trzeba zmienić w naszym szkoleniu.

Udało się jedynie zrealizować temat przekierowań, a także omówić rodzaje przekierowań, a także ich wpływ na SEO:

  • przekierowanie jako zabezpieczenie przed ponownym wysłaniem już obsłużonego formularza, czyli przekierowanie na ten sam url (request.path)
    if form_city.is_valid():
        form_city.save()
        return HttpResponseRedirect(request.path + "#redirected")
  • przekierowanie używając tylko nazwy urla (z urls.py)
    return redirect("counters")
  • przekierowanie z użyciem reverse(), który jest w stanie wygenerować url dla przekierowania, razem w wymaganymi parametrami, np. cabaretId
    return redirect(reverse("counters"))

Podsumowanie

To był bardzo ważny tydzień z dwóch powodów:

  • 1) ogarnialiśmy formularze, zarówno te czyste HTML-owe jak i te tworzone z poziomu kodu
  • 2) okazało się, że bez samodzielnych zadań, nie da się wprawić w programowanie. Za dużo było prowadzenia za rękę, za mało było zostawienia rekrutki samej ze sobą, dokumentacją i Google. Rekrutka, mimo że zdolna, za rzadko miała okazję sama rozwiązywać zadania i problemy, co jest esencją programowania, czyli samodzielność i rozwiązywanie problemów. W ostatnich 3 z zaplanowanych 8 tygodni na pewno będzie dużo więcej samodzielności. Wiedzy już sporo, zostało przyswojonej, teraz trzeba nauczyć się to wszystko zastosować, poskaładać wszystkie informacje w jedną całość i wykonać taski (zadania).

Poprzedni wpis