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
.
Struktura plików projektu – w terminalu wydajemy jedno z poleceń:
(.pve) ~/Django/malybar$ tree
[lub]
(.pve) ~/Django/malybar$ ls -R
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
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:
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:
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:
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:
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.
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
:
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
:
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:
- w pliku
urls.py
: przyporządkowujemy adres widokowi; - w pliku
views.py
: definiujemy widok, który najczęściej zwraca szablon połączony z przekazanymi do nego danymi; - 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.
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!
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:
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
lubFalse
;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, opcjamodels.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:
(.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.
1.6.1. Zmiany modeli¶
Modele można zmieniać.
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 modelUser
:autor = models.ForeignKey(User, on_delete=models.CASCADE)
- przed definicjami klas dodaj import
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)
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
:
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.
1.7.1. Zarządzanie danymi¶
- Uruchom serwer i wywołaj w przeglądarce adres:
127.0.0.1:8000/admin
. - Zaloguj się jako administrator, dodaj pizzę i przynajmniej jeden składnik.
- 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”!
- 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.
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:
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)
.
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ń:
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
:
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/
:
Jak widać, mamy do dyspozycji m.in następujące adresy:
/konta/register
o nazwieregistration_register
– do tworzenia konta;/konta/login
o nazwieauth_login
– do logowania;/konta/logout
o nazwieauth_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
:
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 }}
– metodaas_p
renderuje przekazany do szablonu w zmiennejform
formularz przy użyciu znaczników akapitów<p>
.
Potrzebujemy również szablonu logowania, który umieszczamy w pliku
pizza/templates/registration/login.html
:
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
:
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.
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:
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:
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:
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> •
<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.
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ą:
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
:
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:
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:
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 wforms.py
;success_url
– adres URL, pod który zostaniemy przekierowani po poprawnym obsłużeniu formularza; aby nie wstawiać adresu literalnie, używamy funkcjireverse_lazy()
.
Informacja
GET i POST – to dwa podstawowe typy żądań zdefiniowane w protokole HTTP:
- GET – to żądanie klienta (przeglądarki), które dotyczy zazwyczaj pobrania zasobu z serwera bez zmieniania danych, innymi słowy są to operacje odczytu;
- 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
:
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 }}) </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!
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
:
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
:
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:
- W metodzie
post()
instrukcjaself.object = self.get_object()
– utworzenie instancji edytowanego obiektu; - Argument
instance
zestawu formularzy, zawierający dane edytowanych składników, przyjmuje wartości zself.object
już w metodziepost()
, a nie w metodzieform_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:
15 | url(r'^usun/(?P<pk>\d+)/', views.PizzaDelete.as_view(), name='usun'),
|
Sam widok umieszczamy na końcu pliku pizza/views.py
:
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ą:
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.
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:
16 | url(r'^info/(?P<pk>\d+)/', views.PizzaDetailView.as_view(), name='info'),
|
Widok umieszczamy na końcu pliku pizza/views.py
:
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ą:
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
:
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>© 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:
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:
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.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:
- menu;
- kontener zawierający jeden wiersz podzielony na dwie kolumny;
- kontener zawierający stopkę dokumentu.
Informacja
Kompletny kod aplikacji dostępny jest w repozytorium: https://github.com/xinulsw/malybar.