Tvorba internetových aplikací

Jakub Klinkovský

:: České vysoké učení technické v Praze
:: Fakulta jaderná a fyzikálně inženýrská
:: Katedra softwarového inženýrství

Akademický rok 2023-2024

Framework Django

Django – přehled

Django je framework pro vývoj webových aplikací postavený na jazyku Python.

Poskytované funkce:

  • mapování URL (routing)
  • šablony (templates)
  • formuláře (forms)
  • databáze (models)
  • ověřování uživatelů, administrátorské rozhraní (authentication and authorization)
  • tvorba vícejazyčných stránek (internationalization)
  • bezpečnostní prvky (security)

Django – instalace

Jeden z následujících příkazů (v závislosti na instalaci Pythonu):

  • pip install django – pokud je k dispozici příkaz pip
  • pip3 install django – pokud je k dispozici příkaz pip3
  • python -m pip install django – pomocí modulu pip
  • py -m pip install django – pokud je Python interpreter dostupný jako py

Ověření:

  • příkaz django-admin --version v terminálu,
  • nebo příkaz python -m django --version – pomocí modulu django,
  • nebo příkaz py -m django --version – pokud je Python interpreter dostupný jako py namísto python

Poznámka: po instalaci může být nutné zavřít terminál a otevřít nový (např. Windows)

Django – vytvoření projektu

V adresáři, kde chcete vytvořit projekt, spusťte příkaz:

django-admin startproject mysite

Poznámka: Pokud není k dispozici příkaz django-admin, totéž lze provést pomocí modulu django:

python -m django startproject mysite

Případně použijte python3 nebo py místo python.

Struktura projektu Django

Přikaz startproject vytvoří následující strukturu souborů:

mysite/
├── manage.py
└── mysite
    ├── asgi.py
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

Poznámka k názvům adresářů:

  • Vnější adresář mysite představuje celý projekt a název můžete bez obav kdykoliv změnit.
  • Vnitřní adresář mysite obsahuje konfiguraci projektu a název po vygenerování není snadné změnit (je použit ve vytvořených souborech).

Význam defaultních souborů v projektu

  • manage.py: program/skript pro obsluhu projektu
  • mysite/__init__.py: prázdný soubor, který označuje daný adresář jako balíček (package) pro jazyk Python
  • mysite/settings.py: soubor s konfigurací projektu
  • mysite/urls.py: soubor s deklaracemi, které mapují URL na funkce v Pythonu
  • mysite/asgi.py: vstupní skript pro web server využívající standard ASGI
  • mysite/wsgi.py: vstupní skript pro web server využívající standard WSGI

Přidání aplikace do projektu

Projekt Django je tvořen několika aplikacemi (app), které můžou představovat např. ucelené komponenty webové aplikace.

Aplikaci s názvem pokus vytvoříme pomocí skriptu manage.py:

python manage.py startapp pokus

Případně použijte python3 nebo py místo python.

Poznámka: příkaz je nutné spustit v adresáři projektu, tj. vnější adresář mysite.

Struktura projektu s aplikací

Předchozí příkaz vytvoří adresář pokus ve vnějším adresáři mysite:

mysite/
├── manage.py
├── mysite
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── pokus
    ├── admin.py
    ├── apps.py
    ├── __init__.py
    ├── migrations
    │   └── __init__.py
    ├── models.py
    ├── tests.py
    └── views.py

Spuštění vývojového serveru

Pomocí skriptu manage.py spustíme podpříkaz runserver:

python manage.py runserver

Web server běží na pozadí a prázdná aplikace je dostupná na defaultní adrese http://localhost:8000/

Poznámka: Tento web server je určený pro vývoj a ne k produkčnímu nasazení. Není příliš efektivní, ale poskytuje užitečné funkce usnadňující programování aplikace (např. automatické obnovení/restartování aplikace po změnách v kódu).

Poznámka: Po spuštění vznikne v projektu adresář __pycache__, který obsahuje předkompilované soubory (bytecode) pro efektivnější běh aplikace.

Komponenty frameworku Django

Diagram zpracování požadavku a vytvoření odpovědi

center

Dále přístup přímo na disk (uploadované soubory), statické soubory (CSS, JS), ...

Django middleware

Middleware ve frameworku Django označuje kus kódu, který stojí mezi vlastním kódem aplikace a světem. Stará se o pre-processing a post-processing požadavků / odpovědí.

Jednotlivé middleware jsou seřazené a zpracování probíhá následovně:

center

Více o middleware

Důležité informace z dokumentace k Middleware:

  • konfigurace v souboru settings.py – seznam MIDDLEWARE
  • záleží na pořadí – viz doporučení k built-in middleware
  • middleware může v metodě process_view přerušit sekvenční procházení a vrátit HttpResponse – potom se další middleware ani view function nevolají a pokračuje se metodou process_response
  • metoda process_exception se volá pouze pokud ve view vznikla výjimka
  • metoda process_template_response se volá pouze pokud objekt vrácený z view function má metodu render – např. TemplateResponse (umožňuje modifikovat renderování v dekorátorech nebo v middleware)
  • lze definovat vlastní middleware (pokud nestačí dekorátory pro funkci view...)

URL dispatcher

Modul URLconf se stará o mapování URL na správnou view funkci.

  • soubor settings.py – parametr ROOT_URLCONF (u nás mysite.urls)
  • z nastaveného modulu se použije proměnná urlpatterns, např.:
    urlpatterns = [
        path("articles/2003/", views.special_case_2003),
        path("articles/<int:year>/", views.year_archive),
        path("articles/<int:year>/<int:month>/", views.month_archive),
        path("articles/<int:year>/<int:month>/<slug:slug>/", views.article_detail),
    ]
    
  • uvedené vzory se zpracovávají sekvenčně, první úspěšný se použije
  • porovnává se pouze část URL (cesta, path) – bez domény a bez query stringu
  • funkce path() zpracovává jednoduché vzory,
    funkce re_path() zpracovává regulární výrazy
  • lze se rekurzivně odkazovat na další urls moduly (např. v aplikacích)

URL dispatcher – příklad

views.py:

from django.http import HttpResponse

def index(request):
    return HttpResponse("<b>Hello, world!</b>")

urls.py:

from django.contrib import admin
from django.urls import path

from pokus import views

urlpatterns = [
    path("", views.index),
    path('admin/', admin.site.urls),
]

Úkol 1

Upravte view index v aplikaci pokus tak, že se v textu zobrazí aktuální datum a čas (dle zpracování požadavku serverem).

Bonus: Zobrazte také svátek připadající na daný den.

Protokol HTTP ve frameworku Django

Objekty HttpRequest a HttpResponse

Funkce index ze slajdu 16 má parametr request (objekt typu HttpRequest), a vrací objekt typu HttpResponse.

Objekty slouží pro abstrakci dat přenášených pomocí protokolu HTTP(S).

Více informací o objektech v dokumentaci Django.

Atributy objektu HttpRequest

Pouze read-only atributy:

  • HttpRequest.scheme – schéma požadavku (typicky "http" nebo "https")
  • HttpRequest.method – metoda protokolu HTTP (např. "GET" nebo "POST")
  • HttpRequest.path – část URL odpovídající cestě k danému dokumentu
  • HttpRequest.GET – slovník obsahující parametry odeslané metodou GET
    (odpovídá části URL, tzv. query string: např. ?a=123&b=4)
  • HttpRequest.POST – slovník obsahující parametry odeslané metodou POST
    (typicky kódované v těle požadavku, Django se stará o dekódování dle hlaviček)
  • HttpRequest.headers – slovník obsahující hlavičky (klíče jsou case-insensitive)
  • další: HttpRequest.COOKIES, HttpRequest.FILES, HttpRequest.META

Parametry objektu HttpResponse

Tvar konstruktoru objektu:

HttpResponse.__init__(content=b'', content_type=None, status=200, reason=None, charset=None, headers=None)
  • content – tělo odpovědi (typicky string obsahující dokument ve formátu HTML)
  • content_type – defaultní hodnota "text/html; charset=utf-8"
  • status – stavový kód (číslo nebo objekt http.HTTPStatus)
  • reason – fráze stavu odpovědi (defaultně se vybere podle stavového kódu)
  • charset – kódování odpovědi (defaultní hodnota se parsuje z content_type)
  • headers – slovník s hlavičkami odpovědi

Metody objektu HttpResponse

  • HttpResponse.write() – slouží k postupnému sestavování těla odpovědi, např.:
    response = HttpResponse()
    response.write("<p>Here's the text of the web page.</p>")
    response.write("<p>Here's another paragraph.</p>")
    
  • operátor [] – slouží k manipulaci s hlavičkami odpovědi, např.:
    response = HttpResponse()
    if "Age" not in response:
        response["Age"] = 120
    if "Expires" in response:
        del response["Expires"]
    
  • další: viz dokumentace

Demo: zobrazení informací o požadavku

Funkce, která v odpovědi odešle data přijatá v požadavku.

Šablonový systém frameworku Django

Šablonový systém pro tvorbu dokumentů

  • tvorba dokumentu HTML pomocí postupné konstrukce stringu je nepřehledná a náchylná k chybám (v předchozích příkladech jsme navíc nekonstruovali celý dokument HTML, ale jen část <body>)
  • pro zjednodušení webové frameworky typicky poskytují nějaký způsob tvorby dokumentů na základě šablon, do kterých se doplní dynamický obsah
  • ve frameworku Django lze použít buď Django template language nebo Jinja2
  • princip: oddělení prezentace (struktury dokumentu) od logiky programu

Přidání šablony do vlastní aplikace

  1. Vytvořit adresář pro šablony: pro aplikaci pokus je to pokus/templates/pokus/
    (vnitřní adresář pokus umožňuje jednoznačně identifikovat šablony dané aplikace pomocí relativní cesty vzhledem k templates, např. pokus/base.html)
  2. Vytvořit šablony, např. base.html a some_page.html:
    └── pokus
        ├── admin.py
        ├── apps.py
        ├── __init__.py
        ├── migrations
        │   ├── __init__.py
        ├── models.py
        ├── templates
        │   └── pokus
        │       ├── base.html
        │       └── some_page.html
        ├── tests.py
        └── views.py
    

Přidání šablony do vlastní aplikace

  1. V souboru settings.py registrovat danou aplikaci, např.
    INSTALLED_APPS = [
        "pokus",
        ...
    ]
    
  2. V souboru views.py použít danou šablonu pomocí funkce render(), např.
    from django.shortcuts import render
    
    def some_page(request):
        context = {
            "some_parameters": {"key": "value"},
        }
        return render(request, "pokus/some_page.html", context)
    

Příklad – šablona base.html

{% load static %}

<!DOCTYPE html>
<html lang="{{ lang | default:'en' }}">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>{% block title %}{% endblock %}</title>
        <link rel="stylesheet" type="text/css" href="{% static "pokus/pokus.css" %}">
        {% block head %}
        {% endblock %}
    </head>
    <body>
        <header>
            <nav>
                <a href="{% url 'some_page' %}">Home</a>
                <a href="{% url 'some_page' %}/subpage-1">Subpage 1</a>
                <a href="{% url 'some_page' %}/subpage-2">Subpage 2</a>
            </nav>
        </header>
        <main id="content">
            {% block content %}
            <article id="content-left">
                {% block content_left %}
                {% endblock %}
            </article>
            <aside id="sidebar">
                {% block sidebar %}
                {% endblock %}
            </aside>
            {% endblock %}
        </main>
        <footer>
            <p>Powered by <a href="https://www.djangoproject.com/">Django</a>.</p>
        </footer>
    </body>
</html>

Příklad – šablona some_page.html

{% extends "pokus/base.html" %}
{% load static %}

{% block title %}
Some page title
{% endblock %}

{% block head %}
    <link rel="stylesheet" type="text/css" href="{% static 'pokus/some_page.css' %}">
{% endblock %}

{% block content %}
<article>
<section>
    <p>Li Europan lingues es membres del sam familie. Lor separat existentie es un myth. ...</p>
    <p>At solmen va esser necessi far uniform grammatica, pronunciation e plu commun paroles. ...</p>
</section>
<section>
    <h2>Použití proměnných v šabloně</h2>
    <dl>
    {% for key, value in some_parameters.items %}
        <dt>{{ key }}</dt>
        <dd>{{ value }}</dd>
    {% endfor %}
    </dl>
</section>
</article>
{% endblock %}

Syntaxe šablonového jazyka Django

  1. Proměnné: {{ variable }}, {{ object.attribute }}, atd.
  2. Filtry: {{ value|default:"nothing" }}, {{ value|filesizeformat }}, atd.
    Viz seznam vestavěných filtrů
  3. Tagy: {% tag %}, kde tag je např. extends, loads, block, for, if, ...
    Viz seznam vestavěných tagů
  4. Komentáře: {# tohle je komentář #}
  5. Dědičnost: pomocí tagů extends a block (viz příklady na předchozích slajdech)
  6. Automatické escapování HTML – lze vypnout pomocí filtru safe ({{var|safe}})
    nebo tagu autoescape ({% autoescape off %} ... {% endautoescape %})

Podrobnosti najdete v dokumentaci.

Přidání statických souborů do vlastní aplikace

  1. Vytvořit adresář pro statické soubory: podobně jako v případě šablon, pro aplikaci pokus je to pokus/static/pokus/ (vnitřní podadresář pokus umožňuje jednoznačně identifikovat statické soubory dané aplikace pomocí relativní cesty vzhledem k static, např. pokus/some_page.css)
  2. Vytvořit statické soubory, např. pokus/static/pokus/some_page.css
  3. Použít statické soubory v příslušných šablonách, např.
    {% load static %}
    
    ...
    <link rel="stylesheet" type="text/css" href="{% static "pokus/some_page.css" %}">
    ...
    
  4. Pro nasazení webu v praxi jsou potřeba další kroky, kterými se zatím nebudeme zabývat.

Úkol 2

Přidejte do Django aplikace pokus:

  • alespoň 2 různé funkce ve views.py s příslušným mapováním v urls.py, přičemž alespoň 1 funkce získává parametry z URL pomocí jednoduchých vzorů nebo regulárních výrazů
  • alespoň 2 šablony propojené pomocí dědičnosti
  • všechny stránky obsahují navigační menu s odkazem na hlavní stránku ("home") pomocí tagu url: např. <a href="{% url 'some_page' %}">Home</a>
  • všechny stránky používají sémantické značky (main, article, section, atd.)
  • všechny prvky vhodně nastylujte pomocí CSS

Úkol 3

Na jedné z předchozích stránek si procvičte generování "dynamického" obsahu:

  • ve funkci v souboru views.py vytvořte vhodné datové struktury, např. seznam stringů, slovník, seznam slovníků, atd.
  • tyto struktury předejte jako proměnné pro určitou šablonu
  • v šabloně převeďte data do formátu HTML – vytvořte alespoň jeden číslovaný seznam, alespoň jeden seznam definic (značka <dl>), a alespoň jednu tabulku
  • všechny prvky vhodně nastylujte pomocí CSS