1. Django

Django to napisany w Pythonie framework przeznaczony do szybkiego tworzenia aplikacji internetowych. Został zaprojektowany przez zespół doświadczonych praktyków w taki sposób, żeby odciążyć programistę od wykonywania typowych, a jednocześnie uciążliwych czynności. Zalety Django to szybkość, bezpieczeństwo i skalowalność. Inne cechy wymienione są na polskiej stronie Wikipedii: Django (framework).

1.1. Przygotowanie środowiska

Uwaga

Zanim zaczniesz zwróć uwagę, że:

  • podane niżej polecenia zakładają, że pracujesz w systemie Linux;
  • / znak slash jest separatorem katalogów i plików;
  • ~ – oznacza katalog domowy użytkownika w Linuksie;
  • ~/Django – oznacza katalog, w którym należy wydawać polecenia;
  • $ – oznacza znak zachęty, po nim następują właściwe polecenia;
  • .pve – kropka poprzedzająca nazwę katalogu lub pliku oznacza, że jest on ukryty;
  • Linux rozróżnia małe i duże litery w nazwach katalogów i plików!
  • ścieżki do katalogów i plików w treści podawane są względnie, np. pizza/urls.py;

Do pracy z Django potrzebny jest przede wszystkim interpreter Pythona 2.7.x. Jest on domyślnie obecny w systemach Linux. Poza Pythonem potrzebny jest również instalator pakietów Pythona pip. W systemach Linux opartych na Debianie (Ubuntu, Linux Mint) wystarczy wydać w terminalu polecenie:

~$ sudo apt install python-pip

Następnie instalujemy narzędzie virtualenv:

~$ sudo pip install virtualenv

Posłuży nam ono do przygotowania wyizolowanego środowiska Pythona, zawierającego wybraną wersję Django. Wydajemy polecenia:

~$ mkdir Django; cd Django
~/Django$ virtualenv .pve
~/Django$ source .pve/bin/activate
(.pve) ~/Django$ pip install Django==1.10.4 django-registration

Na początku tworzymy katalog do przechowywania projektu i wchodzimy do niego. Katalog nie jest niezbędny, jednak ułatwi utrzymanie porządku na dysku. Polecenie virtualenv .pve tworzy ukryty katalog o umownej nazwie .pve. Zawiera on najważniejsze komponenty Pythona. Aby skorzystać z przygotowanego środowiska, należy go zawsze na początku aktywować za pomocą polecenia source .pve/bin/activate. Opuszczenie środowiska umożliwia komenda deactivate.

Polecenie pip install ... instaluje wskazaną wersję Django oraz dodatkową aplikację ułatwiającą zarządzanie użytkownikami. Tak zainstalowane moduły będą dostępne tylko w środowisku wirtualnym.

Wskazówka

W systemie Windows:

  • separatorem katalogów i plików jest znak \ (backslash),
  • projekt lepiej budować w katalogu położonym na partycji innej niż systemowa,
  • wielkość liter w nazwach katalogów i plików nie jest brana pod uwagę,
  • instalacja Pythona: Interpreter Pythona,
  • instalacja virtualenv: pip,
  • aktywacja środowiska wirtualnego: .pve\Scripts\activate.bat

1.1.1. Ćwiczenie

Zgodnie z powyższym opisem przygotuj samodzielnie wirtualne środowisko do pracy z Django.

Wskazówka

Projektując aplikację będziemy często korzystać z linii poleceń. Nie zamykaj więc okna terminala. Jednak jeżeli przypadkowo zamkniesz terminal, uruchom go ponownie, wejdź do katalogu nadrzędnego środowiska wirtualnego (cd ~/Django), wydaj polecenie source .pve/bin/activate, a na koniec przejdź do katalogu projektu (cd malybar), który zaraz utworzymy.

1.2. Projekt

Upewnij się, że wirtualne środowisko Pythona jest aktywne. Utworzymy teraz projekt i uruchomimy serwer deweloperski. Wydajemy polecenia:

(.pve) ~/Django$ django-admin stratproject malybar
(.pve) ~/Django$ cd malybar
(.pve) ~/Django/malybar$ python manage.py runserver

Pierwsze polecenie tworzy szkielet serwisu, ostatnie uruchomia serwer deweloperski, który możemy wywołać wpisując w przeglądarce adres: 127.0.0.1:8000. Większość zmian w kodzie nie wymaga restartowania serwera. W razie potrzeby serwer zatrzymujemy naciskając w terminalu skrót CTRL+C.

../_images/django_01.jpg

Struktura plików projektu – w terminalu wydajemy jedno z poleceń:

(.pve) ~/Django/malybar$ tree
[lub]
(.pve) ~/Django/malybar$ ls -R
../_images/django_02.jpg

Nazwa zewnętrznego katalogu malybar nie ma znaczenia, można ją dowolnie zmieniać, to tylko pojemnik na projekt. Zawiera on:

  • manage.py – skrypt Pythona do zarządzania projektem;
  • db.sqlite3 – bazę danych w domyślnym formacie SQLite3.

Katlog projektu malybar/malybar zawiera:

  • settings.py – konfiguracja projektu;
  • urls.py – swego rodzaju “menu” naszego projektu, a więc lista wpisów definiująca adresy URL, które będziemy obsługiwać;
  • wsgi.py – plik konfiguracyjny wykorzystywany przez serwery WWW.

Plik __init__.py obecny w danym katalogu wskazuje, że dany katalog jest modułem Pythona.

1.3. Aplikacja

W ramach jednego projektu (serwisu internetowego) może działać wiele aplikacji. Utworzymy teraz naszą aplikację pizza i zbadamy jej strukturę plików:

(.pve) ~/Django/malybar$ python manage.py startapp pizza
(.pve) ~/Django/malybar$ tree pizza
lub:
(.pve) ~/Django/malybar$ ls -R pizza
../_images/django_03.jpg

Katalog aplikacji malybar/pizza zawiera:

  • apps.py – ustawienia aplikacji;
  • admin.py – konfigurację panelu administracyjnego;
  • models.py – plik definiujący modele danych przechowywanych w bazie;
  • views.py – plik zawierający funkcje lub klasy definiujące tzw. widoki (ang. views), obsługujące żądania klienta przychodzące do serwera;

1.4. Ustawienia projektu

Otwieramy i edytujemy plik malybar/settings.py.

Dostępne w projekcie aplikacje znajdują się w liście INSTALLED_APPS. Domyślnie Django udostępnia kilka obsługujących podstawowe funkcjonalności serwisu internetowego. Na początku tej listy dodamy konfigurację aplikacji pizza, na końcu zainstalowanej wcześniej django-registration:

Kod nr : malybar/settings.py
33
34
35
36
37
38
39
40
41
42
INSTALLED_APPS = [
    'pizza.apps.PizzaConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_registration',
]

Lokalizacja projektu obejmuje ustawienie języka i strefy czasowej:

Kod nr : malybar/settings.py
108
109
110
LANGUAGE_CODE = 'pl'

TIME_ZONE = 'Europe/Warsaw'

Po zapisaniu zmian, uruchomieniu serwera i otwarciu adresu 127.0.0.1:8000 w przeglądarce, zobaczymy:

../_images/django_04.jpg

1.5. Widok domyślny

Mapowanie adresów URL aplikacji tworzymy w nowym pliku pizza/urls.py, który wypełniamy następującym kodem:

Kod nr
1
2
3
4
5
6
7
from django.urls import path
from pizza import views  # import widoków aplikacji

app_name = 'pizza'  # przestrzeń nazw aplikacji
urlpatterns = [
    path('', views.index, name='pizza_index'),
]

Zmienna app-name – pozwala określić przestrzeń nazw, w której dostępne będą adresy URL obsługujące aplikację.

Lista urlpatterns zawiera powiązania między adresami URL a obsługującymi je widokami zapisanymi w pliku views.py, który importujemy w drugiej linii.

Funkcja path() przyporządkowuje adresowi URL widok, który go obsługuje. Pierwszy parametr to wyrażenie regularne, do którego Django próbuje dopasować adres otrzymany w żądaniu od klienta. Drugi to nazwa widoku. Trzeci to unikalna nazwa, dzięki której można odwoływać się w aplikacji do zdefiniowanego adresu.

Konfiguracja adresów URL projektu zawarta jest w pliku malybar/urls.py. W tym miejscu dołączamy listy adresów URL zdefiniowane przez poszczególne aplikacje.

Kod nr
1
2
3
4
5
6
7
from django.contrib import admin
from django.conf.urls import path, include

urlpatterns = [
    path('', include('pizza.urls')),
    path('admin/', admin.site.urls),
]

Funkcja include() jako parametr przyjmuje ścieżkę dostępu do konfiguracji adresów danej aplikacji. W praktyce jest to nazwa katalogu, w którym znajduje się aplikacja, operator . (kropka) oraz domyślna nazwa pliku konfiguracyjnego urls.py bez rozszerzenia.

Widok definiuje jakiś typ strony WWW, za pomocą którego użytkownik wykonuje w aplikacji jakieś operacje, np. wyświetla zestawienie danych. Technicznie widok zazwyczaj składa się z funkcji otrzymującej żądanie klienta i jakiegoś szablonu służącego prezentowaniu danych.

Widok domyślny obsługujący żądania typu GET przychodzące na adres podstawowy serwera zdefiniujemy w pliku pizza/views.py:

Kod nr
1
2
3
4
5
6
7
8
9
# -*- coding: utf-8 -*-

from django.shortcuts import render


def index(request):
    """Strona główna"""
    kontekst = {'komunikat': 'Witaj w aplikacji Pizza!'}
    return render(request, 'pizza/index.html', kontekst)

Uwaga

Zapamiętaj:

  • linia # -*- coding: utf-8 -*- to określenie kodowania znaków. Należy umieszczać je w pierwszej linii każdego pliku, w którym zamierzamy używać polskich znaków, czy to w komentarzach czy w kodzie.
  • napisy zawierające polskie znaki poprzedzamy literą u, np. u'składnik'.

Nazwa funkcji – index() – jest umowna. Każdy widok otrzymuje szczegóły żądania wysłanego przez klienta (obiekt typu HttpRequest) i powinien zwrócić jakąś odpowiedź (HttpResponse). W tym wypadku zwracamy funkcję render() wywołującą wskazany jako drugi parametr szablon, który otrzymuje dane w postaci słownika kontekst (nazwa umowna).

Szablon (ang. template) – to plik tekstowy, służący generowaniu najczęściej plików HTML. Oprócz tagów HTML-a, zawiera zmienne oraz tagi sterujące języka szablonów Django.

Informacja

Szablony umieszczamy w katalogu: pizza/templates/pizza!

Zawartość szablonu pizza/templates/pizza/index.html:

Kod nr
1
2
<h1>Mały Bar</h1>
<p>{{ komunikat }}</p>

Zobacz, jak w znaczniku <p> wstawiamy przekazaną do szablonu zmienną komunikat, używamy podwójnych nawiasów sześciennych: {{ nazwa_zmiennej }}.

Wskazówka

W tym miejscu warto usystematyzować dodawanie kolejnych funkcji do naszej aplikacji. Zazwyczaj proces ten przebiega wg. schematu:

  1. w pliku urls.py: przyporządkowujemy adres widokowi;
  2. w pliku views.py: definiujemy widok, który najczęściej zwraca szablon połączony z przekazanymi do nego danymi;
  3. w katalogu templates/nazwa_aplikacji: tworzymy szablon, który łączy znaczniki HTML-a i dane.

1.5.1. Ćwiczenie

W tym momencie powinieneś przetestować działanie aplikacji. Sprawdź, czy działa serwer. Jeżeli nie, uruchom go. W przeglądarce odśwież lub wpisz adres domyślny serwera testowego, tj.: 127.0.0.1:8000. Powinieneś zobaczyć nazwę projektu i powitanie.

../_images/django_06.jpg

Wskazówka

Programowanie to sztuka wykrywania i poprawiania błędów! W przypadku błędów Django wyświetla obszerne informacje, które na pierwszy rzut oka są bardzo skomplikowane. Nie musisz studiować całości, żeby zrozumieć, co poszło nie tak. Skup się na początku komunikatu!

../_images/django_05.jpg

1.6. Model danych

Podstawą użytecznej aplikacji są dane. Django realizuje obiektowy wzorzec programowania, więc dane definiujemy jako klasy opisujące tzw. modele. Model danych – to kompletne źródło informacji o jakimś obiekcie, zawiera jego właściwości (pola) oraz metody działań na nich.

W pliku pizza/models.py definiujemy klasy opisujące źródła danych naszej aplikacji:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models


class Pizza(models.Model):
    LARGE = 'L'
    MEDIUM = 'M'
    SMALL = 'S'
    ROZMIARY = (
        (LARGE, 'duża'),
        (MEDIUM, 'średnia'),
        (SMALL, 'mała'),
    )
    nazwa = models.CharField(verbose_name='Pizza', max_length=30)
    opis = models.TextField(blank=True, help_text='Opis Pizzy')
    rozmiar = models.CharField(max_length=1, choices=ROZMIARY, default=LARGE)
    cena = models.DecimalField(max_digits=5, decimal_places=2)
    data = models.DateField('dodano', auto_now_add=True)


class Skladnik(models.Model):
    pizza = models.ForeignKey(Pizza,
                              on_delete=models.CASCADE,
                              related_name='skladniki')
    nazwa = models.CharField(verbose_name=u"składnik", max_length=30)
    jarski = models.BooleanField(
        default=False,
        verbose_name=u"jarski?",
        help_text=u"Zaznacz, jeżeli składnik jest odpowiedni dla"
        u" wegetarian")

Nazwa każdego modelu (klasy) powinna zaczynać się dużą literą. Każdy model jest potomkiem klasy Models (dziedziczenie). Definicja każdej zmiennej (właściwości) zawiera wywołanie metody tworzącej pole wymaganego typu. Za pomocą nazwanych argumentów określamy dodatkowe cechy pól.

Informacja

Najczęstsze typy pól:

  • CharField – pole znakowe, przechowuje niezbyt długie napisy, np. nazwy;
  • TextField – pole tekstowe, dla długich tekstów, np. opisów;
  • DecimalField – pole dziesiętne, nadaje się do przechowywania liczb rzeczywistych, np. cen;
  • Date(Time)Field – pole daty (i czasu);
  • BooleanField – pole logiczne, przechowuje wartość True lub False;
  • ForeignKey – pole klucza obcego, czyli relacji; wymaga nazwy powiązanego modelu jako pierwszego argumentu.

Właściwości pól:

  • verbose_name lub napis podany jako pierwszy argument – przyjazna nazwa pola;
  • max_length – maksymalna długość pola znakowego;
  • blank = True – pole może zawierać ciąg pusty;
  • help_text – tekst podpowiedzi;
  • max_digits, decimal_places – określenie maksymalnej ilości cyfr i ilości miejsc po przecinku liczby rzeczywistej;
  • auto_now_add = True – data (i czas) wstawione zostaną automatycznie;
  • default – określenie wartości domyślnej pola;
  • choices – wskazuje listę wartości dopuszczalnych dla danego pola;
  • on_delete – określa, co ma się stać w przypadku usunięcia obiektu nadrzędnego (pizzy), na który wskazuje klucz obcy, opcja models.CASCADE wymusza usunięcie obiektów zależnych (składników);
  • realted_name – nazwa używana w relacji zwrotnej, kiedy z obiektu nadrzędnego (pizzy) chcemy odwołać się do obiektów zależnych (składników), np. pizza.skladniki.

W bazie chcemy przechowywać dane o pizzach. Każda z nich składać się może z wielu składników. Tak więc między modelami Pizza i Skladnik istnieje relacja jeden-do-wielu.

Po dokonaniu zmian w bazie tworzymy tzw. migrację, w terminalu wydajemy polecenia:

Kod nr
(.pve) ~/Django/malybar$ python manage.py makemigrations pizza
(.pve) ~/Django/malybar$ python manage.py migrate

Migracja – tworzona przez pierwsze polecenie, to informacje o zmianie w bazy danych zapisywana przez Django w języku SQL w katalogu pizza/migrations.

Drugie polecenie na podstawie migracji wszystkich zarejestrowanych aplikacji (w tym domyślnych) buduje lub aktualizuje bazę danych. Z nazw modeli Django utworzy odpowiednie tabele, w oparciu o zdefiniowane właściwości – odpowiednie kolumny.

../_images/django_07.jpg

1.6.1. Zmiany modeli

Modele można zmieniać.

  1. Do modelu Pizza dodamy pole przechowujące użytkownika, który dodał ją do bazy.

    • przed definicjami klas dodaj import from django.contrib.auth.models import User
    • dodaj klucz obcy o nazwie autor wskazujący na model User: autor = models.ForeignKey(User, on_delete=models.CASCADE)
  2. Dodamy możliwość “autoprezentacji” modeli, czyli wyświetlania ich znakowej reprezentacji.

    • do każdej klasy dodaj następującą metodę:
def __unicode__(self):
    return u'%s' % (self.nazwa)
  1. W panelu administracyjnym przydatna jest forma liczby mnogiej służąca nazywaniu egzemplarzy danego modelu.

    • w każdym modelu umieść dodatkową klasę Meta z odpowiednią formą liczby mnogiej, np.:
class Meta:
    verbose_name_plural = 'pizze'

Uwaga

Zapamiętaj: po zmianie modelu należy utworzyć migrację aplikacji i zaktualizować bazę danych projektu (polecenia makemigrations i migrate)!

Wskazówka

Jeżeli z jakichś powodów kolejnej migracji nie da się zastosować, można:

  • usunąć bazę db.sqlite3;
  • usunąć katalog migrations aplikacji;
  • ponownie utworzyć migrację i zaktualizować bazę projektu.

1.7. Strona administracyjna

Zarządzanie treściami czy użytkownikami wymaga panelu administracyjnego, Django dostarcza nam go automatycznie.

Konto administratora tworzymy, wydając w terminalu polecenie:

~/Django/malybar$ python manage.py createsuperuser

Django zapyta o nazwę, e-mail i hasło. Podajemy: admin, “” (pomijamy), q1w2e3r4.

Aplikacja w panelu administratora: uzupełniamy plik pizza/admin.py:

Kod nr
1
2
3
4
5
6
7
# -*- coding: utf-8 -*-
from django.contrib import admin
from . import models

# rejestrujemy modele w panelu administracyjnym
admin.site.register(models.Pizza)
admin.site.register(models.Skladnik)

Po zaimportowaniu modeli danych rejestrujemy je w panelu, dzięki temu będziemy mogli dodawać i modyfikować dane użytkowników i aplikacji.

../_images/django_08.jpg

1.7.1. Zarządzanie danymi

  1. Uruchom serwer i wywołaj w przeglądarce adres: 127.0.0.1:8000/admin.
  2. Zaloguj się jako administrator, dodaj pizzę i przynajmniej jeden składnik.
  3. Utwórz konto dla użytkownika “uczen” z hasłem “q1w2e3r4”. Przydziel mu prawa do dodawania, modyfikowania i usuwania pizz i składników. Uwaga: nie zapomnij zaznaczyć opcji “W zespole”!
  4. Zaloguj się na konto “uczen” i dodaj jeszcze jedną pizzę z co najmniej jednym składnikiem.

Informacja

Obsługa panelu administracyjnego jest dobrą okazją, żeby zobaczyć jak wygląda komunikacja między klientem a serwerem w aplikacjach sieciowych wykorzystujących protokół http. Serwer testowy wyświetla pełen zapis sesji w oknie terminala.

../_images/django_09.jpg

1.7.2. Lepszy panel

W podstawowej konfiguracji modele Pizza i Skladnik rejestrowane i obsługiwane są osobno. Z logicznego i praktycznego punktu widzenia dobrze byłoby, gdyby pizza i jej składniki stanowiły całość, również podczas dodawania. W tym celu zmienimy treść pliku pizza/admin.py na:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# -*- coding: utf-8 -*-
from django.contrib import admin
from . import models
from django.forms import Textarea
from django.db.models.fields import TextField


class SkladnikInline(admin.TabularInline):
    model = models.Skladnik
    fields = ['nazwa', 'jarski']
    extra = 3
    max_num = 6


@admin.register(models.Pizza)
class PizzaAdmin(admin.ModelAdmin):
    exclude = ('autor',)
    inlines = [SkladnikInline]
    search_fields = ['nazwa']
    list_per_page = 10
    formfield_overrides = {
        TextField: {'widget': Textarea(attrs={'rows': 2, 'cols': 100})},
    }

    def save_model(self, request, obj, form, change):
        if not change:
            obj.autor = request.user
        obj.save()

Formularze generowane automatycznie w panelu administracyjnym obsługiwane są przez klasę ModelAdmin. Dostosujemy ją do naszych potrzeb. Na początku używamy klasy TabularInline pozwalającej edytować kilka modeli na jednej stronie. Nam chodzi o składniki, dlatego tworzymy klasę SkladnikiInline i ustawiamy odpowiednie opcje:

  • model – nazwa modelu, dla którego modyfikujemy formularz;
  • fields – lista pól, dla których mają być generowane formularze;
  • extra – ilość pustych formularzy umożliwiających wprowadzanie danych;
  • max_num – maksymalna ilość obiektów możliwych do dodania za jednym razem.

W klasie PizzaAdmin projektujemy wygląd całego formularza dodawania pizz. Używamy następujących opcji:

  • exclude – lista pól wykluczonych z formularza;
  • inlines – nazwa klasy definiującej sposób wyświetlania formularzy dla innych modeli;
  • search_fields – lista pól, które będą przeglądane podczas wyszukiwania obiektów;
  • list_per_page – maksymalna ilość obiektów pokazywanych na stronie;
  • formfield_overrides – słownik, w którym kluczami są klasy pól formularza; służy modyfikacji ich wyświetlania, w naszym przypadku ustalamy tu właściwości pola tekstowego.

Utworzenie swojej klasy administracyjnej pozwala również na modyfikację zachowań panelu, np. zapisywania danych. Metoda save_model() pozwala nam przypisać zalogowanego użytkownika jako autora dodawanego obiektu. Dzięki temu użytkownik nie musi wybierać autora (czyli siebie) z listy.

Do rejestrowania klas modyfikujących domyślną klasę ModelAdmin używamy dekoratora w postaci @admin.register(models.Pizza).

../_images/django_10.jpg

1.8. Użytkownicy

Do zarządzania użytkownikami użyjemy zainstalowanej na początku aplikacji django-registration. W pliku malybar/settings.py dodaliśmy ją już do listy aplikacji INSTALLED_APPS. Teraz na końcu tego pliku dodamy kilka ustawień:

Kod nr : malybar/settings.py
124
125
126
127
# django-registration
REGISTRATION_AUTO_LOGIN = True  # automatyczne logowanie po rejestracji
LOGIN_REDIRECT_URL = '/pizza/'  # strona docelowa po zalogowaniu
LOGIN_URL = '/konta/login/'  # strona logowania

Wskazówka

Uwaga: komentarze w powyższym kodzie zawierają polskie znaki, jeżeli wstawisz je do pliku, pamiętaj o dodaniu informacji o kodowaniu znaków w pierwszej linii!

Następnie włączamy konfigurację adresów URL aplikacji do pliku malybar/urls.py:

Kod nr
20
21
22
23
24
25
urlpatterns = [
    url(r'^', include('pizza.urls')),
    url(r'^pizza/', include('pizza.urls')),
    url(r'^konta/', include('registration.backends.simple.urls')),
    url(r'^admin/', admin.site.urls),
]

Teraz możemy zobaczyć, jakie adresy udostępnia aplikacja django-registration, wpisując w przeglądarce adres 127.0.0.1:8000/konta/:

../_images/django_11.jpg

Jak widać, mamy do dyspozycji m.in następujące adresy:

  • /konta/register o nazwie registration_register – do tworzenia konta;
  • /konta/login o nazwie auth_login – do logowania;
  • /konta/logout o nazwie auth_logout – do wylogowywania.

1.8.1. Szablony

Na początku utworzymy szablon służący do rejestracji w pliku pizza/templates/registration/registration_form.html:

Kod nr
1
2
3
4
5
6
<h1>Tworzenie konta</h1>
<form action="." method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Utwórz konto">
</form>

W powyższym kodzie widać, w jaki sposób używamy przygotowanych wcześniej formularzy w szablonach. Znacznik HTML-a <form> i przycisk typu submit musimy wstawić sami, resztę może za nas zrobić Django:

  • {% csrf_token %} – ten tag dodaje ukryte pole zabezpieczające formularz przed atakami typu CSRF;
  • {{ form.as_p }} – metoda as_p renderuje przekazany do szablonu w zmiennej form formularz przy użyciu znaczników akapitów <p>.

Potrzebujemy również szablonu logowania, który umieszczamy w pliku pizza/templates/registration/login.html:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<h1>Logowanie</h1>
  <form method="post" action=".">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Zaloguj się">
  </form>
<hr>
<p>
  Nie masz konta?<br>
  <a href="{% url 'registration_register' %}">Utwórz konto!</a>
</p>

Adresy URL w szablonach wstawiamy za pomocą tagu url, który jako pierwszy obowiązkowy argument przyjmuje nazwę adresu zdefiniowaną w argumencie name w plikach urls.py.

Uwaga

Zapamiętaj: nawiasy {{ zmienna }} służą do wstawiania wartości zmiennych, nawiasów {% tag %} używamy do tagów języka szablonów.

Na koniec szablon wyświetlany po wylogowaniu, czyli plik pizza/templates/registration/logout.html:

Kod nr
1
2
<h1>Wylogowanie</h1>
<p>Zostałeś wylogowany!</p>

1.8.2. Ćwiczenie

Po dodaniu szablonów można już przetestować tworzenie konta, logowanie i komunikat po wylogowaniu wpisując w przeglądarce po nazwie serwera 127.0.0.1:8000 adresy:

  • /konta/register – tworzenie nowego konta; utwórz konto ewa z hasłem q1w2e3r4;
  • /konta/login – logowanie; zaloguj się na utworzone wcześniej konto uczen;
  • /konta/logout – potwierdzenie wylogowania;

Spróbuj wstawić do szablonu templates/pizza/index.html odnośniki do powyższych adresów. Na końcu pliku umieść kod:

<ul>
  {% if not user.is_authenticated %}
    <li><a href="{% url 'nazwa_adresu' %}">Zaloguj się</a></li>
    <li><a href="{% url 'nazwa_adresu' %}">Utwórz konto</a></li>
  {% else %}
    <li><a href="{% url 'nazwa_adresu' %}">Wyloguj się</a></li>
  {% endif %}
</ul>

– i zamień tekst nazwa_adresu na właściwe nazwy adresów URL.

Uwaga

Zapamiętaj: w szablonach dostępne są konstrukcje warunkowe wstawiane za pomocą tagów {% if warunek %} ... {% else %} ... {% endif %}. W szablonach dostępny jest obiekt user zawierający informacje o użytkowniku. Metoda is_authenticated zwraca prawdę, jeżeli użytkownik został zalogowany.

../_images/django_12.jpg

1.9. ListView

Praktycznie w każdym serwisie występują strony zawierające zestawienie danych. Utworzymy więc widok prezentujący listę pizz.

Definicja adresu – w pliku pizza/urls.py dodajemy importy:

Kod nr
3
4
5
from django.contrib.auth.decorators import login_required
from django.views.generic.list import ListView
from .models import Pizza

Następnie przyporządkujemy adres lista/ o nazwie lista widokowi ListView. Dodajemy kod:

Kod nr
 8
 9
10
11
12
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^lista/', login_required(ListView.as_view(model=Pizza)),
        name='lista'),
]

Widoki generyczne (ang. generic views), udostępniane przez Django, służą przygotowywaniu typowych stron WWW. ListView – jak wskazuje nazwa – tworzy stronę z listą obiektów. Najważniejszym argumentem widoku jest model, czyli nazwa modelu obiektów, które mają być wyświetlane.

Lista obiektów będzie dostępna w szablonie w zmiennej o domyślnej nazwie object_list.

Informacja

Widoki generyczne są klasami. Jeżeli używamy ich w pliku urls.py, musimy użyć ich metody as_view(), aby potraktowane zostały jak funkcje.

Jeżeli chcemy, aby jakiś adres dostępny był tylko dla zalogowanych użytkowników, wywołanie widoku umieszczamy w funkcji login_required().

Szablon dla widoku generycznego ma schematyczną nazwę, w tym wypadku nazwa_modelu_list.html. Tworzymy więc plik templates/pizza/pizza_list.html o zawartości:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<h1>Lista pizz</h1>

<table class="table">
<tr><th>Lp.</th><th>Nazwa</th><th>Info</th><th>Operacje</th></tr>
{% for p in object_list %}
<tr>
  <td>{{ forloop.counter }}</td>
  <td>{{ p.nazwa }}</td>
  <td>{{ p.autor.username }} ({{ p.data }})</td>
  <td>
    {% if p.autor.username == user.username %}
      <a href="#">Edytuj</a> &bull;
      <a href="#">Usuń</a>
    {% endif %}
  </td>
</tr>
{% endfor %}
</table>

Konstrukcję for p in object_list należy rozumieć następująco: dla każdego obiektu pobranego z listy object_list do zmiennej p wykonaj:. W pętli wyświetlamy kolejne zmienne: numer iteracji (forloop.counter), nazwę, autora i datę dodania pizzy. Jeżeli zalogowany użytkownik dodał daną pizzę, wyświetlamy odnośniki umożliwiające edycję i usuwanie obiektów.

Uwaga

Zapamiętaj: tagów {% for zmienna in lista %} ... {% endfor %} używamy w szablonach, jeżeli potrzebujemy pętli.

Na koniec dodaj do szablonu index.html odnośnik do listy. W atrybucie href odnośnika użyj kodu:

{% url 'pizza:lista' %}

Zwróć uwagę, że nazwa URL-a poprzedzona została nazwą przestrzeni nazw, którą zdefiniowaliśmy w parametrze namespace podczas włączania listy adresów naszej aplikacji do listy projektu.

../_images/django_13.jpg

1.10. Create View

Zajmiemy się teraz możliwością dodawania danych, czyli pizz i składników. Na początku utworzymy nowy plik pizza/forms.py, z następującą zawartością:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# -*- coding: utf-8 -*-

from django.forms import ModelForm, Textarea
from . import models
from django.forms.models import inlineformset_factory


class PizzaForm(ModelForm):

    class Meta:
        model = models.Pizza
        exclude = ('data', 'autor')
        widgets = {'opis': Textarea(attrs={'rows': 2, 'cols': 80})}


SkladnikiFormSet = inlineformset_factory(
    parent_model=models.Pizza,
    model=models.Skladnik,
    max_num=6,
    min_num=1,
    validate_max=True,
    validate_min=True,
    extra=2,
    fields=('nazwa', 'jarski')
)

Definicje formularzy umieszczamy w plikach o nazwie forms.py. Co prawda Django potrafi automatycznie tworzyć formularze na podstawie modeli, ale wymagają one dostosowania. Dlatego tworzymy klasę PizzaForm, w której definiujemy formularz do dodawania i edytowania pizz. Właściwe opcje umieszczamy w podklasie Meta:

  • model – model, dla którego dostosowujemy formularz;
  • exclude – tupla z polami, które wykluczamy z formularza;
  • widgets – opcjonalny słownik, w którym ustalamy właściwości widżetów HTML.

Django automatycznie generuje widżety HTML odpowiadających typom pól modelu. Np. pola CharField reprezentowane są przez tagi <input>, a pola TextField przez <textarea>. Możemy zmienić domyślne ustawienia. W powyższym przykładzie określiliśmy rozmiar pola tekstowego na 2 wiersze i 80 kolumn.

Zestaw (pod)formularzy – wyświetlany razem z formularzem nadrzędnym, definiowany jest jako tzw. formset przy użyciu funkcji inlineformset_factory():

  • parent_model – model nadrzędny dla składników, czyli Pizza;
  • model – model, dla którego definiujemy zestaw formularzy;
  • max_num, min_num – maksymalna i minimalna ilość obiektów, które można dodać;
  • validate_max, validate_min – podczas walidacji sprawdzana będzie minimalna i maksymalna ilość obiektów;
  • extra – ilość początkowych formularzy do dodawania obiektów;
  • fields – lista pól, dla których wygenerowane zostaną widżety.

Klasę SkladnikiFormSet wykorzystamy po to, aby można było dodawać dane pizzy i składników w obrębie jednej strony, podobnie jak w panelu administracyjnym.

Wskazówka

Definiowanie formularzy używanych w panelu administracyjnym, czy na stronach, w tym formularzy inline, wymaga określania podobnych lub identycznych opcji, np. model, fields, extra itd.

Jeżeli określimy właściwość fields, nie musimy podawać extra. Działa to również w drugą stronę.

Dodanie adresu – w pliku pizza/urls.py tworzymy adres /dodaj:

Kod nr
13
    url(r'^dodaj/$', views.PizzaCreate.as_view(), name='dodaj'),

CreateView – to kolejny widok generyczny, który posłuży zgodnie z nazwą do dodawania danych. Użyjemy go w pliku pizza/views.py. Na początku dodajemy, jak zwykle, importy:

Kod nr
 4
 5
 6
 7
 8
 9
10
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from . import models
from . import forms
from django.views.generic.edit import CreateView
from django.urls import reverse_lazy
from django.http import HttpResponseRedirect

Następnie na końcu pliku pizza/views.py umieszczamy kod:

Kod nr
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@method_decorator(login_required, 'dispatch')
class PizzaCreate(CreateView):
    """Widok dodawania pizzy i skladników."""

    model = models.Pizza
    form_class = forms.PizzaForm
    success_url = reverse_lazy('pizza:lista')  # '/pizza/lista'

    def get_context_data(self, **kwargs):
        context = super(PizzaCreate, self).get_context_data(**kwargs)
        if self.request.POST:
            context['skladniki'] = forms.SkladnikiFormSet(self.request.POST)
        else:
            context['skladniki'] = forms.SkladnikiFormSet()
        return context

    def post(self, request, *args, **kwargs):
        self.object = None
        form = self.get_form()
        skladniki = forms.SkladnikiFormSet(self.request.POST)
        if form.is_valid() and skladniki.is_valid():
            return self.form_valid(form, skladniki)
        else:
            return self.form_invalid(form, skladniki)

    def form_valid(self, form, skladniki):
        form.instance.autor = self.request.user
        self.object = form.save()
        skladniki.instance = self.object
        skladniki.save()
        return HttpResponseRedirect(self.get_success_url())

    def form_invalid(self, form, skladniki):
        return self.render_to_response(
            self.get_context_data(form=form, skladniki=skladniki)
        )

Widok PizzaCreate to klasa dziedzicząca i dostosowująca właściwości i metody klasy rodzica, czyli CreateView. Właściwości:

  • model – pozwala określić model, dla którego tworzymy widok;
  • form_class – klasa formularza do dodawania obiektu, którą zdefiniowaliśmy w forms.py;
  • success_url – adres URL, pod który zostaniemy przekierowani po poprawnym obsłużeniu formularza; aby nie wstawiać adresu literalnie, używamy funkcji reverse_lazy().

Informacja

GET i POST – to dwa podstawowe typy żądań zdefiniowane w protokole HTTP:

  1. GET – to żądanie klienta (przeglądarki), które dotyczy zazwyczaj pobrania zasobu z serwera bez zmieniania danych, innymi słowy są to operacje odczytu;
  2. POST – to żądania klienta wysyłające dane na serwer, aby zmienić dane po jego stronie: utworzyć nowe, zaktualizować lub usunąć.

Zadaniem widoku jest wygenerowanie pustego formularza, kiedy użytkownik wyświetla go po raz pierwszy (żądanie typu GET), później sprawdzenie przesłanych danych (żądanie typu POST) i ich zapisanie w bazie. Ponieważ chcemy dodawać pizze (obiekt nadrzędny) i składniki (obiekty zależne) razem, musimy widok dostosować do obsługi zestawu formularzy (ang. formset) składników.

Kontekst widoku – zawiera słownik z danymi, metoda get_context_data() domyślnie dopisuje do niego formularz główny dla pizzy. My wykorzystujemy ją, aby dodać formset dla składników. W zależności od typu żądania tworzymy pusty (GET) lub wypełniony przesłanymi danymi zestaw (POST).

Walidacja danych – to sprawdzanie poprawności przesłanych danych. Przeprowadzamy ją w metodzie post(), którą nadpisujemy. Na podstawie przesłanych danych tworzymy:

  • form = self.get_form() – obiekt formularza głównego;
  • skladniki = forms.SkladnikiFormSet(self.request.POST)formset składników.

Metoda is_valid() sprawdza poprawność danych, np. to, czy wartości pól wymaganych zostały podane.

Zapisanie danych ma miejsce w metodzie form_valid() wywoływanej po pozytywnej walidacji. W metodzie uzupełniamy pole autor, które wykluczyliśmy z formularza głównego. Po zapisaniu przekierowujemy użytkownika na zdefiniowany wcześniej adres.

Jeżeli walidacja nie powiedzie się, wywoływana jest metoda fomr_invalid(). Nadpisujemy ją po to, aby zwrócić błędy nie tylko formularza głównego, ale również formularzy zależnych.

Szablon dodawania – dla widoku typu CreateView ma nazwę tworzoną wg schematu nazwa_modelu_form.html. Tworzymy więc plik pizza/templates/pizza/pizza_form.html:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<h1>Dodaj pizzę</h1>

<form action="." method="post" id="pform">
  {% csrf_token %}
  {{ form.as_p }}

  <h3>Składniki:</h3>
  {{ skladniki.management_form }}
  {{ skladniki.non_form_errors }}
  {% for skl in skladniki %}
  {{ skl.id }}
    <div class="{{ skladniki.prefix }}">
      <span>{{ forloop.counter }})&nbsp;</span>
      {{ skl.nazwa.errors }}
      {{ skl.jarski.errors }}
      {{ skl.nazwa.label_tag }} {{ skl.nazwa }}
      {{ skl.jarski.label_tag }} {{ skl.jarski }}
    </div>
  {% endfor %}
  <br /><input type="submit" value="Zapisz">
</form>

Wygenerowanie HTML-owej wersji formularza głównego pozostawiamy Django. Natomiast formularze dla składników renderujemy ręcznie.

Uwaga

Podczas ręcznego renderowania zestawów formularzy formset nie wolno zapomnieć o polu management_form i polach id (identyfikatorów) kolejnych formularzy.

Po zdefiniowaniu formularzy, utworzeniu adresu, widoku i szablonu możemy dodawać nowe pizze! Nie zapomnij o dodaniu odnośnika na stronie głównej!

../_images/django_14.jpg

1.11. UpdateView

UpdateView – to widok umożliwiający edycję utworzonych danych, współdzieli z widokiem dodawania formularz, formset i szablon.

Import – na początku, jak zwykle, importujemy klasę UpdateView. Dodajemy ją po przecinku za widokiem CreateView.

Adres edycji definiujemy w pliku pizza/urls.py:

Kod nr
14
    url(r'^edytuj/(?P<pk>\d+)/', views.PizzaUpdate.as_view(), name='edytuj'),

Adres składać się będzie z części /edytuj/, po której podany powinien zostać argument o nazwie pk będący liczbą. Przykładowy poprawny adres może mieć więc postać /edytuj/2. Nazwa argumentu pk nie jest przypadkowa, to skrót od słów ang. primary key (klucz podstawowy). Jest on automatycznie przekazywany do klas widoków opartych na modelach.

Sam widok umieszczamy na końcu pliku pizza/views.py:

Kod nr
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
@method_decorator(login_required, 'dispatch')
class PizzaUpdate(UpdateView):
    """Widok aktualizuacji"""

    model = models.Pizza
    form_class = forms.PizzaForm
    success_url = reverse_lazy('pizza:lista')  # '/pizza/lista'

    def get_context_data(self, **kwargs):
        context = super(PizzaUpdate, self).get_context_data(**kwargs)
        if self.request.POST:
            context['skladniki'] = forms.SkladnikiFormSet(
                self.request.POST,
                instance=self.object)
        else:
            context['skladniki'] = forms.SkladnikiFormSet(instance=self.object)
        return context

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        form = self.get_form()
        skladniki = forms.SkladnikiFormSet(
            self.request.POST,
            instance=self.object)
        if form.is_valid() and skladniki.is_valid():
            return self.form_valid(form, skladniki)
        else:
            return self.form_invalid(form, skladniki)

    def form_valid(self, form, skladniki):
        form.instance.autor = self.request.user
        self.object = form.save()
        skladniki.save()
        return HttpResponseRedirect(self.get_success_url())

    def form_invalid(self, form, skladniki):
        return self.render_to_response(
            self.get_context_data(form=form, skladniki=skladniki)
        )

Jak widać większość kodu jest identyczna z widokiem dodawania. Są jednak ważne różnice:

  1. W metodzie post() instrukcja self.object = self.get_object() – utworzenie instancji edytowanego obiektu;
  2. Argument instance zestawu formularzy, zawierający dane edytowanych składników, przyjmuje wartości z self.object już w metodzie post(), a nie w metodzie form_valid().

Wskazówka

W szablonie pizza_list.html warto uzupełnić odnośnik do edycji. Użyj kodu {% url 'pizza:edytuj' p.id %}.

Na koniec warto wspomnieć, że zapisywanie edytowanych danych dochodzi do skutku, o ile dane zostały zmienione.

1.12. DeleteView

DeleteView – służy do usuwania danych, których identyfikator przesłany jest za pomocą żądania POST, w przypadku GET wyświetla formularz potwierdzenia.

Import – importujemy klasę DeleteView. Dodajemy ją po przecinku za widokiem UpdateView.

Adres widoku będzie podobny, jak dla edycji danych, tzn. przekażemy w nim identyfikator obiektu, który chcemy usunąć. W pliku pizza/urls.py dopisujemy:

Kod nr
15
    url(r'^usun/(?P<pk>\d+)/', views.PizzaDelete.as_view(), name='usun'),

Sam widok umieszczamy na końcu pliku pizza/views.py:

Kod nr
 98
 99
100
101
102
103
104
105
106
107
@method_decorator(login_required, 'dispatch')
class PizzaDelete(DeleteView):
    model = models.Pizza
    success_url = reverse_lazy('pizza:lista')  # '/pizza/lista'

    def get_context_data(self, **kwargs):
        context = super(PizzaDelete, self).get_context_data(**kwargs)
        skladniki = models.Skladnik.objects.filter(pizza=self.object)
        context['skladniki'] = skladniki
        return context

Uzupełniamy kontekst, ponieważ chcemy w szablonie potwierdzenia wyświetlić również listę składników pizzy. W metodzie get_context_data() pobieramy listę składników w zapytaniu skladniki = models.Skladnik.objects.filter(pizza=self.object). Warto zwrócić uwagę na kryterium filtrowania rekordów. Używamy klucza obcego (pola pizza z modelu Skladnik), który musi odpowiadać obiektowi pizzy przypisanemu do właściwości self.object widoku.

Szablon widoku nazywamy wg domyślnego schematu model_confirm_delete.html, czyli tworzymy plik pizza/templates/pizza/pizza_confirm_delete.html z następującą zawartością:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<h1>Usuwanie pizzy</h1>
<form action="." method="post">
  {% csrf_token %}
  <p>Czy na pewno chcesz usunąć pizzę:<br />"{{ object }}"?</p>
    <ol>Składniki:
    {% for s in skladniki %}
      <li>{{ s.nazwa }}</li>
    {% endfor %}
    </ol>
  <button type="submit">Usuń</button>
</form>

Obiekt pizzy, który usuwamy, dostępny jest w zmiennej object. Dodatkowo w pętli wyświetlamy przekazane przez kontekst składniki.

../_images/django_16.jpg

1.13. DetailView

DetailView – widok szczegółowy służy prezentowaniu wszystkich informacji dotyczących jakiegoś obiektu na jednej stronie.

Import – importujemy klasę dopisując w pliku pizza/views.py: from django.views.generic import DetailView.

Adres widoku jest zbudowany na takiej samej zasadzie, jak w przypadku edycji i usuwania danych, czyli zawiera identyfikator obiektu. W pliku pizza/urls.py dopisujemy:

Kod nr
16
    url(r'^info/(?P<pk>\d+)/', views.PizzaDetailView.as_view(), name='info'),

Widok umieszczamy na końcu pliku pizza/views.py:

Kod nr
111
112
113
114
115
116
117
118
119
@method_decorator(login_required, 'dispatch')
class PizzaDetailView(DetailView):
    model = models.Pizza

    def get_context_data(self, **kwargs):
        context = super(PizzaDetailView, self).get_context_data(**kwargs)
        skladniki = models.Skladnik.objects.filter(pizza=self.object)
        context['skladniki'] = skladniki
        return context

W kontekście widoku dodajemy, podobnie jak w przypadku widoku usuwania, listę składników danej pizzy.

Informacja

Widoki UpdateView, DeleteView oraz DetailView na podstawie przekazanego w zmiennej pk identyfikatora automatycznie pobierają odpowiedni obiekt z bazy przy użyciu metody get_object().

Szablon widoku nazywamy wg domyślnego schematu model_detail.html, czyli tworzymy plik pizza/templates/pizza/pizza_detail.html z następującą zawartością:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<h2>{{ object.nazwa }}</h2>
<table class="table">
  <tr><td>Opis:</td><td>{{ object.opis }}</td></tr>
  <tr><td>Rozmiar:</td><td>{{ object.rozmiar }}</td></tr>
  <tr><td>Cena:</td><td>{{ object.cena }}</td></tr>
  <tr><td>Dodano:</td><td>{{ object.data }}</td></tr>
  <tr><td>Autor:</td><td>{{ object.autor }}</td></tr>
</table>
<h3>Składniki:</h3>
<ol>
{% for skl in skladniki %}
    <li>{{ skl.nazwa }} ({{ skl.czy_jarski }})</li>
{% endfor %}

1.13.1. Ćwiczenie

Dodaj do szablonu listy obiektów (pizz) link o nazwie np. “Szczegóły”, który wyświetli dodatkowe inforamcje o danej pizzy.

1.14. Szablon bazowy

Ponieważ o atrakcyjności serwisu w dużej mierze decyduje jego wygląd, a także interaktywny interfejs, zobaczymy, jak względnie łatwo dodać do projektu framework Bootstrap dostarczający gotowe elementy HTML, CSS i JavaScript przeznaczone do projektowania mobilnych i responsywnych stron WWW.

Szablon bazowy – to szkielet stron w naszym serwisie; zawiera powtarzające się elementy, np. menu, jak również bloki, które można wypełniać dostosowaną treścią w szablonach dziedziczących. Tworzymy plik pizza/templates/pizza/base.html:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
{% load static %}<!DOCTYPE html>
<html lang="pl">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>{% block tytul_head %}Ciasto + składniki = Pizza{% endblock %}</title>

  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

  <link href="{% static 'pizza/css/pizza.css' %}" rel="stylesheet">
</head>
<body>

<!-- Nawigacja -->
<nav class="navbar navbar-default navbar-fixed-top">
  <div class="container">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
        <span class="sr-only">Przełącz nawigację</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="{% url 'pizza:index' %}">
        <img src="{% static 'pizza/images/pizza.jpg' %}" class="img-thumbnail">
      </a>
    </div>
    <div id="navbar" class="navbar-collapse collapse">
      <ul class="nav navbar-nav">
        {% if not user.is_authenticated %}
        <li><a href="{% url 'auth_login' %}">Zaloguj</a></li>
        <li><a href="{% url 'registration_register' %}">Utwórz konto</a></li>
        {% else %}
        <li><a href="{% url 'pizza:dodaj' %}">Dodaj pizzę</a></li>
        <li><a href="{% url 'pizza:lista' %}">Lista pizz</a></li>
        <li><a href="{% url 'auth_logout' %}">Wyloguj: {{ user.username }}</a></li>
        {% endif %}
      </ul>
    </div><!--/.nav-collapse -->
  </div>
</nav>

<!-- Treść główna -->
<div class="container">

  <div class="row">

    <div class="col-sm-8">

      <noscript><p>Uaktywnij bsługę JavaScript.</p></noscript>

      <!-- blok tytułu strony -->
      <h1>{% block tytul_h1 %}{% endblock %}</h1>

      <!-- blok treści -->
      <div class="tresc">
        {% block tresc %}{% endblock %}
      </div>

    </div>

    <div class="col-sm-3 col-sm-offset-1">
      <div class="well">
        <h4>Przydatne linki</h4>
        <ul>
          {% block linki_ul %}{% endblock %}
        </ul>
      </div> <!--/well-->
    </div>

  </div> <!--/row -->
</div> <!--/container -->

<div class="container footer">
  <footer>
    <div class="row">
      <div class="col-sm-12 text-center">
        <div class="well">Django web framework – kurs błyskawiczny!<br>&copy; by Koduj z klasą 2017</div>
      </div>
    </div>
  </footer>
</div>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

</body>
</html>

Zasoby statyczne, czyli rzadko zmieniane, to arkusze stylów CSS, skrypty JS, pliki graficzne, ewentualnie czcionki i ikony, umieszcza się w podkatalogu pizza/static/pizza. Dla przejrzystości grupuje się je zazwyczaj w folderach css, js czy images – ich nazwy są umowne.

Wstawianie zasobów statycznych do szablonu wymaga umieszczenia na początku dokumentu tagu {% load static %}. Dzięki temu możemy generować poprawne adresy URL dla atrybutów href czy src za pomocą tagu {% static 'względna_ścieżka_do_zasobu' %}, np.: - href="{% static 'pizza/css/pizza.css' %}"; - src="{% static 'pizza/images/pizza.jpg' %}".

Bloki szablonu – to miejsca, które szablony dla poszczególnych stron serwisu mogą wypełniać własną zawartością. Blok definiujemy przy użyciu tagów: {% block nazwa_bloku %} treść_domyślna {% endblock %}.

1.14.1. Rozszerzanie szablonu

Szablony poszczególnych widoków rozszerzają szablon bazowy dziedzicząc elementy powtarzalne i wypełniając bloki własną zawartością. Dotychczasową zawartość szablonu pizza/index.html zastępujemy kodem:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{% extends "pizza/base.html" %}

{% block tytul_h1 %}
  Mały Bar
{% endblock %}

{% block tresc %}
  <p>{{ komunikat }}</p>
{% endblock %}

{% block linki_ul %}
  <li><a href="http://django-maly-bar.rtfd.io">Dokumentacja aplikacji</a></li>
  <li><a href="https://www.djangoproject.com/">Django – web framework</a></li>
  <li><a href="http://django.pl/">Polska strona Django</a></li>
  <li><a href="http://getbootstrap.com/">Bootstrap</a></li>
  <li><a href="http://www.w3schools.com/bootstrap/">Bootstrap Tutorial
  </a></li>
  <li><a href="https://www.python.org/">Python</a></li>
  <li><a href="http://python101.readthedocs.io/">Materiały Python 101</a></li>
{% endblock %}

Tag {% extendes "pizza/base.html" %} informuje, że korzystamy z podanego szablonu podstawowego. Sekwencje tagów {% block nazwa_bloku %} ... {% endblock %} wypełniają zdefiniowane w szablonie bazowym bloki odpowiednią zawartością.

Jeszcze jeden przykład. Szablon registration/login.html będzie wyglądał następująco:

Kod nr
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{% extends "pizza/base.html" %}

{% block tytul_h1 %}
  Logowanie
{% endblock %}

{% block tresc %}
  <form method="post" action=".">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Zaloguj się">
  </form>
<hr>
<p>
  Nie masz konta?<br>
  <a href="{% url 'registration_register' %}">Utwórz konto!</a>
</p>
{% endblock %}

1.14.2. Ćwiczenie

Dostosuj pozostałe szablony, tak aby korzystały z szablonu bazowego.

../_images/django_17.jpg

1.15. Bootstrap

Szablon bazowy oparty został na przykładowym layoucie dostępnym na stronach Bootstrapa. W nagłówku strony w znaczniku <link> ładowany jest podstawowy komponent frameworka, tzn. arkusz stylów CSS bootstrap.min.css. Na końcu szablonu w znacznikach <script> dołączamy skrypty JavaScript: bibliotekę JQuery i komponent JS Bootstrapa, plik bootstrap.min.js.

Podstawą Bootsrapa jest system podziału strony na 12 części o tej samej szerokości zwany grid system. Tworząc układ strony ustalamy szerokość kolumn łącząc części w grupy za pomocą klas CSS, np.:

<div class="container">
  <div class="row">
    <div class="col-sm-8">
      <!-- zawartość -->
    </div>
    <div class="col-sm-4">
      <!-- zawartość -->
    </div>
  </div>
</div>

Powyższy kod daje nam podział na 1. kolumnę o szerokości 8 części i 2. kolumnę o szerokości 4. Przyrostek -sm informuje, że podział ten obowiązuje dla rozdzielczości >= 768px. Podziałem części na kolumny możesz manipulować, pamiętać tylko trzeba, żeby ich suma dawała 12.

Znacznik <div class="container">, wyodrębnia sekcję strony, <div class="row"> tworzy podział na wiersze.

Kolejnym często używanym elementem Bootstrapa jest menu umieszczane w znaczniku <nav>. W szablonie wykorzystujemy menu przytwierdzone na stałe u góry strony, widoczne również w trakcie jej przewijania. Decyduje o tym klasa navbar-fixed-top.

Menu składa się z 2 części. Pierwsza wyznaczona klasą navbar-header zawiera przycisk mobilny i obrazek, druga z identyfikatorem id=navbar zawiera właściwe odnośniki w postaci elementów listy <ul>.

Cały layout ma więc trzy części:

  1. menu;
  2. kontener zawierający jeden wiersz podzielony na dwie kolumny;
  3. kontener zawierający stopkę dokumentu.

Informacja

Kompletny kod aplikacji dostępny jest w repozytorium: https://github.com/xinulsw/malybar.