Ponadpodstawowe szablony HTML w Django
Wróćmy do naszej aplikacji dotyczącej ankiet internetowych. Zaczniemy od stworzenia bardziej funkcjonalnych i użytecznych widoków HTML.
Rozszerzanie szablonów w Django
Póki co nasza aplikacja korzystała z jednego szablonu dla wszystkich widoków aplikacji, zakładając że wszystkie widoki przygotują jeden ciąg znaków, który będzie wyświetlany jako treśc strony.
Wady używania jednego szablonu
Co niektórzy bardziej ciekawscy uczestnicy kursu na pewno spróbowali wstawiać do treści strony elementy języka HTML, żeby strona wyglądała lepiej. Szybko okazywało się jednak, że coś przeszkadza nam w takim podejściu.
Django domyślnie nie pozwala na podawanie do szablonów treści napisanej w HTML. Taka decyzja jest podyktowana względami bezpieczeństwa - dokładniejszym omówieniem tego zajmiemy się w jednym z przyszłych rozdziałów.
Z założenia wszystko co związane jest z prezentacją aplikacji, czyli HTML, CSS i kod JavaScript jest obsługiwane przez szablony Django. Funkcje widoków mają tylko zgromadzić dane, ewentualnie przetworzyć je na potrzeby szablonu, np. posortować albo przefiltrować, a za sposób prezentacji danych jest odpowiedzialny system renderowania szablonów.
W programowaniu istnieje koncept Rozdzielenia Odpowiedzialności (ang. Separation of Concerns). Jest to koncept popularny wśród programistów języków “obiektowych”, ale dotyczy zasadniczo całego spektrum tworzenia oprogramowania. Jeżeli program jest podzielony na osobne moduły/komponenty/klasy, to poszczególne warstwy powinny mieć jasno zdefiniowane odpowiedzialności i, co najważniejsze, odpowiedzialności te powinny być unikatowe dla danego komponentu. To pozwala nam ograniczyć możliwości projektowania aplikacji.
Nawet w przypadku prostego przykładu funckji widoku i szablonu w Django. Gdybyśmy założyli, że przygotowywanie fragmentow kodu HTML było jedną z opcji dla funkcji widoku, to już daje nam do podjęcia decyzję: jaką częśc dokumentu HTML będzie tworzyć kod widoku? Cały dokument? Pojedynczą tabelę? Tylko wiersze tabeli? A może pozostaniemy przy opcji “zero HTML”? A co jeżeli kolega z zespołu podejmie inne decyzje? Co jeżeli sami uznamy podczas pracy nad kolejnym widokiem, że jednak wolelibyśmy inne podejście? Jak dużo czasu minie, zanim nasz kod stanie się nieczytelną mieszaniną różnych podejść?
Różne widoki, różne szablony
Skoro mamy różne funkcje widoków, które mają pokazywać różne dane zawarte w naszej aplikacji, to możemy też stworzyć różne szablony, osobny dla każdego widoku. Zacznijmy od stworzenia szablonu dla widoku index, który ma wyświetlać nam listę obecnie otwartych ankiet. Skopiuj bazowy szablon templates/polls/base.html
i stwórz nowy plik templates/polls/index.html
. Zmodyfikuj treśc szablonu w następujący sposób:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Awesome polls page</title>
<link rel="stylesheet" href="{% static 'polls/style.css' %}">
</head>
<body>
<div id="content" class="container">
Currently open polls are:
<ul>
{% for name, values in polls.items %}
<li><a href="/polls/{{ name }}/">{{ values.question }}</a></li>
{% endfor %}
</ul>
</div>
<script src="{% static 'polls/app.js' %}"></script>
</body>
</html>
Do tego musimy zmodyfikować funkcję widoku index
w pliku mysite/views.py
. Zacznijmy od takich zmian:
def index(request):
template = loader.get_template("polls/index.html")
context = {
"polls": open_polls,
}
return HttpResponse(template.render(context, request))
Przy odrobinie zmian w naszym szablonie CSS możemy otrzymać taki oto wynik:
Zadanie praktyczne: widok ankiety
Teraz stwórz nowy szablon dla widoku pojedynczej ankiety, tak żeby wyświetlał się następująco:
Wiele szablonów, wiele problemów
Gdybyśmy teraz postanowili usunąć nasz skrypt JavaScript app.js
ze wszystkich widoków na naszej stronie, musimy pamiętać, żeby usunąć go z co najmniej trzech szablonów. W prawdziwej aplikacji możemy spodziewać się o wielu więcej widoków. System szablonów Django pozwala nam usprawnić pracę poprzez współdzielenie częsci wspólnych dla szablonu.
Konkretnie ta metoda jest nazywana dziedziczeniem szablonów i możesz o niej przeczytać w dokumentacji.
Zmieńmy treść naszego pliku templates/polls/base.html
na następującą:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Awesome polls page</title>
<link rel="stylesheet" href="{% static 'polls/style.css' %}">
</head>
<body>
<div id="content" class="container">
{% block content %}
<p>There should be content here!</p>
{% endblock %}
</div>
</body>
</html>
A treść pliku templates/polls/index.html
na:
{% extends "polls/base.html" %}
{% block content %}
Currently open polls are:
<ul>
{% for name, values in polls.items %}
<li><a href="/polls/{{ name }}/">{{ values.question }}</a></li>
{% endfor %}
</ul>
{% endblock %}
Zadanie praktyczne: dziedziczący widok pojedynczej ankiety
Zmodyfikuj widok ankiety tak, aby dziedziczył widok polls/base.html
Zadanie praktyczne: sprzątanie i eksperymenty
- Usuń plik skrypt
app.js
z aplikacji i ze wszystkich szablonów, upewnij się, że nie jest ładowany sprawdzając w konsoli narzędzi deweloperskich przeglądarki. - Co się stanie, jeżeli dziedziczący szablon nie zdefiniuje treści jakiegoś bloku zdefiniowanego w szablonie bazowym?
- Zdefiniuj treść tagu
<title>
z nagłówka HTML jako blok szablonu i spróbuj zmodyfikować go z poziomu widoku.
Skróty w Django
Jeżeli istnieje jakaś często powtarzana sekwencja wywołań funkcji, albo inny powtarzalny wzorzec w kodzie, developerzy Django czasem tworzą skróty (ang. shortcuts), które pozwalają trochę przyspieszyć pisanie kodu.
Potencjalnym kosztem jest utrata przejrzystości tego, co nasz kod robi, z jakich elementów frameworka korzysta i niemożliwość wykorzystania mniej popularnych opcji w niektórych interfejsach, ale skróty istnieją dla tych 90% przypadków, kiedy nie mamy takiej potrzeby.
Skrótem, którego już możemy użyć w naszej aplikacji jest funkcja render
, która pozwoli nam skrócić następujący kod:
from django.template import loader
from django.http import HttpResponse
# ...
def index(request):
template = loader.get_template("polls/index.html")
context = {
"polls": open_polls,
}
return HttpResponse(template.render(context, request))
Do takiej wersji:
from django.shortcuts import render
# ...
def index(request):
context = { "polls": open_polls }
return render(request, "polls/index.html", context)
Zadanie praktyczne: użyj skrótu “render” w swojej aplikacji
Gdzie to możliwe użyj skrótu render
w swojej aplikacji.
Generowanie odnośników do widoków
Chwilowo w naszym szablonie polls/index.html
tworzymy linki do ankiet “ręcznie”
{% for name, values in polls.items %}
<li><a href="/polls/{{ name }}/">{{ values.question }}</a></li>
{% endfor %}
Nie jest to zalecana metoda, ponieważ nasza aplikacja może zmienić na jakimś etapie ścieżkę bazową, albo format całej ścieżki, dlatego w Django zaleca się tworzyć odnośniki używając tagu szablonu {% url %}
.
W przypadku naszej aplikacji jego użycie może wyglądać tak:
{% for name, values in polls.items %}
<li><a href="{% url 'poll' name %}">{{ values.question }}</a></li>
{% endfor %}
Ważne jest żeby odnosić się do naszego widoku po nazwie, którą nadaliśmy mu w pliku polls/urls.py
:
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name="index"),
path('<str:poll_name>/', views.by_name, name="poll") # ważny jest ostatni parametr 'name'
]
W tym przypadku nie ma tego problemu, ale dla naszego widoku index
jest spore ryzyko, że ta nazwa pokryje się z widokami innych aplikacji, dlatego dobrym pomysłem jest nadanie naszej aplikacji osobnej przestrzeni nazw dla jej widoków. Możemy to zrobić dodając do pliku polls/urls.py
jedną linijkę kodu.
from django.urls import path
from . import views
# poniżej dodaliśmy zmienną app_name!
app_name = "polls"
urlpatterns = [
path('', views.index, name="index"),
path('<str:poll_name>/', views.by_name, name="poll")
]
A potem w widoku możemy już odnosić się do nazwy widoku razem z nazwą aplikacji, z której pochodzi:
{% for name, values in polls.items %}
<li><a href="{% url 'polls:poll' name %}">{{ values.question }}</a></li>
{% endfor %}
Zadanie praktyczne: popraw tworzenie linków w swojej aplikacji
- Do tworzenia linków w swojej aplikacji użyj tagu szablonu {% url %}
- Zmień ścieżkę bazową swojej aplikacji i sprawdź, czy linki dalej działają.
Inne zadania praktyczne
- Dodaj do widoku pojedynczej ankiety przycisk/link pozwalający wrócić do widoku
index