Menu

  • Home
  • Flask Tutorial
  • WebGL
  • Kontakt

Alicja | Theme by Theme in Progress | Proudly powered by WordPress

Alicja & IT
  • Home
  • Flask Tutorial
  • WebGL
  • Kontakt

Aplikacja webowa w Pythonie – Flask – Routing – #3

December 28, 2016Flask Standard

W poprzednich odcinkach tutorialu stworzyliśmy strukturę aplikacji, do której podłączony został layout strony głównej. Jak sprawić, aby mogło zaistnieć więcej podstron niż jedna? Zaraz się dowiecie :)

Ładne URL-e

Dla twórców Flaska od początku ważne były estetyczne URL-e, dlatego utworzenie ich jest zaskakująco proste. Tak naprawdę wystarczy nam teraz dostęp do pliku views.py.

Skrypt, który pozwalał nam wyświetlić zawartość strony głównej wyglądał mniej więcej tak:

1
2
3
@app.route('/')
def index():
    return 'Strona główna'

Skupmy się teraz na pierwszej linijce tego krótkiego kodu. Czym jest  route()? Według dokumentacji – dekoratorem. Jeśli chciałbyś dowiedzieć się, czym są we Flasku dekoratory, to ta sama dokumentacja ma dość dziwną ich definicję, a mianowicie:

Python has a really interesting feature called function decorators. This allows some really neat things for web applications. Because each view in Flask is a function, decorators can be used to inject additional functionality to one or more functions.

Czyli krótko mówiąc, chodzi o wstrzykiwanie pewnych dodatkowych opcji w funkcję wyświetlającą konkretny view. Funkcją wyświetlającą jest w tym przypadku oczywiście index(), a użyte wcześniej app.route("/") pozwala przekierować to, co jest zwracane przez funkcję pod konkretny adres – w tym wypadku macierzysty. Co w takim razie wydarzy się, jeżeli "/" zamienimy na "/post"? Jak łatwo się domyślić, zawartość strony głównej zniknie (wyświetli się 404 Not Found), a napis “Strona główna” pojawi się dopiero, gdy dopiszemy do urla adres podstrony.

Wykorzystajmy to w sensowny sposób.

Weźmy pod lupę kod, który stworzyliśmy w poprzednim wpisie. W layoucie wyświetliliśmy menu z dwoma obiektami – stroną “o mnie” oraz kontaktem. Postarajmy się, aby każda z nich mogła zostać samodzielnie wyświetlona.

Wcześniej plik views.py wyglądał tak:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import render_template
from hello_world import app
 
@app.route('/')
def index():
    user = {'name': 'Alicja'}
    navigation = [
        {
            'href': 'omnie',
            'caption': 'O mnie'
        },
        {
            'href': 'kontakt',
            'caption': 'Kontakt'
        }
    ]
    return render_template('index.html', user=user, navigation=navigation)

Musimy do niego dopisać dwa kolejne route’y.

Pierwszy z nich, ten dla strony o autorze, mógłby wyglądać tak:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@app.route('/omnie')
def about():
    user = {'name': 'Alicja'}
    navigation = [
        {
            'href': 'omnie',
            'caption': 'O mnie'
        },
        {
            'href': 'kontakt',
            'caption': 'Kontakt'
        }
    ]
    return render_template('about.html', user=user, navigation=navigation)

Od razu można zauważyć, że tego typu kod będzie działać (jeśli tylko stworzymy template about.html oparty na base.html), ale niestety jest sprzeczny z zasadą DRY. Jak go ulepszyć? Flask proponuje ciekawe rozwiązanie – context processors. Zdefiniowane w nich zmienne (w postaci słownika) zostają automatycznie dodane podczas tworzenia się każdego z widoków. Warto pamiętać, że wykonuje się to przy generowaniu każdego template’u – w tym przypadku będzie to jednak ich zaleta.

Kod naszego procesora dodajmy w pliku views.py (pod routingiem) i usuńmy definicję zmiennych ze skryptów dla poszczególnych podstron. Całość powinna się teraz prezentować tak:

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
from flask import render_template
from hello_world import app
 
@app.route('/')
def index():
    return render_template('index.html')
 
@app.route('/omnie')
def about():
    return render_template('about.html')
 
@app.route('/kontakt')
def contact():
    return render_template('contact.html')
 
@app.context_processor
def inject_variables():
    return dict(
        user = {'name': 'Alicja'},
        navigation = [
            {
                'href': '/',
                'caption': 'Home'
            },
            {
                'href': 'omnie',
                'caption': 'O mnie'
            },
            {
                'href': 'kontakt',
                'caption': 'Kontakt'
            }
        ])

Jak widać, context processor, zdefiniowany funkcją inject_variables() pozwala nam uniknąć powtarzania tego samego kodu przy każdej podstronie. Zauważyłeś zapewne, że dodałam do nawigacji również zmienną z opisem “Home”, która przekierowuje nas na stronę główną – gdybyśmy nie skorzystali z context processora, taki dodatek musielibyśmy dopisać aż trzy razy – przy każdej podstronie.

Stwórz teraz template’y dla pozostałych podstron.

about.html:

1
2
3
4
5
6
7
8
9
10
11
{% extends "base.html" %}
{% block title %}O mnie{% endblock %}
{% block content %}
    <h2>O mnie</h2>
    <p>
      Witaj na podstronie o autorze
      {% if user %}
        {{ user.name | striptags }}
      {% endif %}
    </p>
{% endblock %}

contact.html:

1
2
3
4
5
6
7
8
9
10
11
{% extends "base.html" %}
{% block title %}Kontakt{% endblock %}
{% block content %}
    <h2>Kontakt</h2>
    <p>
      Witaj na podstronie kontakt
      {% if user %}
        {{ user.name | striptags }}
      {% endif %}
    </p>
{% endblock %}

Nadszedł czas na uruchomienie aplikacji – wszystko działa jak trzeba, a flaskowy routing pozwala na wyświetlenie konkretnego layoutu na każdej podstronie :).

URL-e w HTMLu

Zwróciłeś już pewnie uwagę, że urle w naszej nawigacji nie są zahardkodowane, tylko umieszczone w zmiennych, które wywołujemy w bazowej templatce:

1
2
3
4
5
<ul id="navigation">
  {% for item in navigation %}
      <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
  {% endfor %}
</ul>

To dobre podejście, ale Flask proponuje również inne. Możemy skorzystać z tego, że w views.py każdy routing ma swoją funkcję i w base.html odwołać się do niej przez url_for:

1
2
3
4
5
<ul id="navigation">
      <li><a href="{{url_for('index')}}">Home</a></li>
      <li><a href="{{url_for('about')}}">O mnie</a></li>
      <li><a href="{{url_for('contact')}}">Kontakt</a></li>
</ul>

Wtedy na spokojnie możemy pozbyć się definicji navigation z context processora…

1
2
3
4
5
@app.context_processor
def inject_variables():
    return dict(
        user = {'name': 'Alicja'}
        )

… i całość wciąż będzie śmigać :)

Zmienne w adresie URL?

Ostatnim tematem, który poruszę w tym poście są zmienne w adresie URL. O co mi chodzi? Wyobraź sobie, że tworzysz bloga, w którym wyświetlane będą posty. Niezależnie od tego, ile tych postów masz, chciałbyś móc wyświetlić każdy z nich, bez tworzenia osobnych routingów. Tutaj przydadzą się właśnie zmienne.

Przyjmijmy, że na naszej stronie chcemy wyświetlić właśnie posty. W views.py potrzebujemy dodać routing dla strony z listą postów:

1
2
3
@app.route('/posty')
def posts():
    return render_template('posts.html')

Oraz dla pojedynczego posta:

1
2
3
@app.route('/post/<int:post_id>')
def post(post_id):
    return render_template('post.html', post_id = post_id)

Jak widzisz, w funkcji route()  można zdefiniować również zmienne z URL-a. Wykorzystujemy do tego nawiasy trójkątne: <variable>. Opcjonalny jest również tak zwany konwerter, czyli w tym przypadku int:. Możliwe konwertery to:

  • string – domyślny, akceptuje dowolny tekst bez “/”
  • int – akceptuje integery
  • float – liczby zmiennoprzecinkowe
  • path – podobny do stringa, ale akceptuje również “/”
  • UUID – dla łańcuchów UUID

Jeżeli wybierzemy, tak jak w przykładzie, konwerter int, to wpisując do URL-a coś innego niż integera (np. /post/fajny-post), wywołamy 404.

Aby nasza aplikacja mogła działać poprawnie, musimy zdefiniować również same posty. Dodajmy je do context processora:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@app.context_processor
def inject_variables():
    return dict(
        user = {'name': 'Alicja'},
        posts = [
        {
            'post_id': 0,
            'title': 'Post numer 0'
        },
        {
            'post_id': 1,
            'title': 'Post numer 1'
        },
        {
            'post_id': 2,
            'title': 'Post numer 2'
        }]
        )

Utwórzmy również template’y, czyli post.html, wyświetlający pojedynczy post:

1
2
3
4
5
6
7
8
9
10
11
12
{% extends "base.html" %}
{% block title %}Posty{% endblock %}
{% block content %}
    <h2>Post</h2>
    <p>
        {% if posts[post_id] %}
            Oto post - {{ posts[post_id].title }}!
        {% else %}
            Posta nie znaleziono :(
        {% endif %}
    </p>
{% endblock %}

Oraz posts.html, wyświetlający listę postów:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{% extends "base.html" %}
{% block title %}Posty{% endblock %}
{% block content %}
    <h2>Posty</h2>
    <p>
      Witaj na podstronie o postach
      {% if user %}
        {{ user.name | striptags }}
      {% endif %}
      {% for item in posts %}
        <li><a href="{{url_for('post', post_id = item.post_id)}}">{{ item.title }}</a></li>
      {% endfor %}
    </p>
{% endblock %}

Zauważ ciekawą konstrukcją związaną z url_for, a mianowicie: {{url_for('post', post_id = item.post_id)}}. Okazuje się, że funkcja ta również może przyjmować parametry, dzięki czemu podlinkowanie wszystkich postów po kolei staje się bardzo proste i czytelne. Do każdego wywołania pętli for tworzymy odpowiedni link, prowadzący do konkretnego posta.

Na koniec wystarczy dodać do nawigacji w base.html link do podstrony ze wszystkimi postami:

1
2
3
4
5
6
<ul id="navigation">
      <li><a href="{{url_for('index')}}">Home</a></li>
      <li><a href="{{url_for('posts')}}">Posty</a></li>
      <li><a href="{{url_for('about')}}">O mnie</a></li>
      <li><a href="{{url_for('contact')}}">Kontakt</a></li>
</ul>

Wtedy całość prezentuje się poprawnie :). Kolejny odcinek – o upiększaniu naszej aplikacji – już wkrótce!

7

Flask:

  • Aplikacja webowa w Pythonie – Flask – Hello World – #1
  • Aplikacja webowa w Pythonie – Flask – Template’y – #2
  • Aplikacja webowa w Pythonie – Flask na Windowsie
  • Aplikacja webowa w Pythonie – Flask – Routing – #3
  • Aplikacja webowa w Pythonie – Flask – Static Files – #4
  • Aplikacja webowa w Pythonie – Flask – Łączenie z bazą danych – #5
  • Aplikacja webowa w Pythonie – Flask – Formularze – #6
  • Aplikacja webowa w Pythonie – Flask – Struktura większej aplikacji, czyli Blueprint – #7
  • Aplikacja webowa w Pythonie – Flask – Logowanie – #8
  • Aplikacja webowa w Pythonie – Flask – Obsługa błędów – #9
  • Aplikacja webowa w Pythonie – Flask – Migracja z localhosta na serwer, czyli co może pójść nie tak z bazą danych – #10
  • Aplikacja webowa w Pythonie – Flask – Deploy na Azure’a – #11

Facebook

Kontakt

kontakt[at]alicja.it

Kategorie

  • Flask (12)
  • Luźne (2)
  • PyGame (5)
  • Relacje z wydarzeń (3)
  • WebGL (3)