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 – Struktura większej aplikacji, czyli Blueprint – #7

January 7, 2017Flask Standard

Z każdym odcinkiem tutorialu nasza aplikacja coraz bardziej się rozrastała. Podłączyliśmy bazę, layouty i kilka routingów, które zaczęły powiększać plik views.py. Łatwo zauważyć, że z każdą następną linijką, staje się on coraz mniej czytelny. Wraz z rozwojem apki, nasze routingi przestaną wyglądać tak:

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

…a zaczną osiągać wielkości nawet kilkuset wersów. Powalczmy więc z tym zanim będzie za późno :)

Blueprinty

Zacznijmy od wprowadzenia kolejnego flaskowego pojęcia – blueprintu. Dokumentacja mówi, że:

Flask uses a concept of blueprints for making application components and supporting common patterns within an application or across applications.

oraz:

A blueprint in Flask is not a pluggable app because it is not actually an application – it’s a set of operations which can be registered on an application, even multiple times.

Nie jest to najbardziej jasna definicja na świecie, ale można się domyślić, że blueprinty pozwalają nam zaimplementować pewną modułowość i definiować zbiór operacji, do których będziemy się odwoływać w aplikacji.

Podzielmy views!

Blueprinty są bardzo przydatne, gdy views.py zacznie za bardzo się rozrastać. W naszym pliku mamy póki co jedną dużą funkcję, database_posts() – spróbujmy ją wyodrębnić.

W folderze głównym hello_world utwórz plik database_posts.py i uzupełnij go treścią odpowiedniego routingu z views.py (usuwając ją z pierwotnej lokalizacji), czyli:

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
@app.route("/wszystkie-posty", methods=['POST', 'GET'])
def database_posts():
    message = ""
    if request.method == 'POST':
        if request.form["action"] == "add":
            name = request.form['tytul']
            author = request.form['autor']
            content = request.form['tresc']
            conn = mysql.connect()
            cursor = conn.cursor()
            to_db = ("", name, content, author)
            cursor.execute("INSERT INTO posty VALUES (%s, %s, %s, %s)", to_db)
            conn.commit()
            cursor.close()
            message = "Dodano!"
        if request.form["action"] == "delete":
            id = request.form['id-delete']
            conn = mysql.connect()
            cursor = conn.cursor()
            cursor.execute("DELETE from posty where ID='" + id + "'")
            conn.commit()
            cursor.close()
            message = "Usunięto post numer " + id + "!"
    cursor = mysql.connect().cursor()
    cursor.execute("SELECT * from posty")
    data = cursor.fetchall()
    cursor.close()
    return render_template('database_posts.html', data = data, message = message)

Na początku pliku zaimportuj odpowiedni pakiet z flask i zdefiniuj nowy blueprint:

1
2
3
from flask import Blueprint
 
db_posts_blueprint = Blueprint('db_posts_blueprint', __name__)

To właśnie db_posts_blueprint będzie jego nową nazwą. Pozostaje jeszcze edycja routingu, aby nie odwoływać się do aplikacji, tylko samego blueprintu:

1
@db_posts_blueprint.route("/wszystkie-posty", methods=['POST', 'GET'])

…oraz zarejestrowanie nowo utworzonej części aplikacji w views.py:

1
2
3
from .database_posts import db_posts_blueprint
 
app.register_blueprint(db_posts_blueprint)

Na początku database_posts.py zaimportuj jeszcze niezbędne pakiety:

1
2
3
from flask import request
from flask import render_template
from hello_world import mysql

I uruchom program.

Działa? Prawdopodobnie nie :)

error

Zapomnieliśmy edytować linki w base.html oraz w database_posts.html, które prowadziły do funkcji database_posts – “mądry” Flask podpowiada nam, na co powinniśmy je zmienić:

1
<li><a href="{{url_for('db_posts_blueprint.database_posts')}}">Posty z bazy danych</a></li>

oraz podwójne:

1
<form class="col s12" action="{{ url_for('db_posts_blueprint.database_posts')}}" method="post">

Po podmienieniu url_for na właściwą konstrukcję, aplikacja powinna śmigać tak jak wcześniej – na froncie oczywiście nic się nie zmieniło, ale opanowaliśmy rozrastający się backend, co na pewno pomoże nam w przyszłości :)

Inne podejście do modułowości

Jeżeli nasza aplikacja robi się naprawdę spora i chcielibyśmy, aby nasze blueprinty były osobnymi modułami, możemy jeszcze trochę zmodyfikować strukturę aplikacji.

blueprint

Zawartość database_posts.py, który wcześniej znajdował się w hello_world, przenieśmy to __init__.py, umieszczonego w strukturze hello_world/blueprints/db_post_blueprint, a database_posts.py usuńmy. Wtedy w views.py wystarczy zmienić jedną linijkę ( from .blueprints.db_posts_blueprint import db_posts_blueprint), aby aplikacja działała poprawnie.

Zauważ, że tego typu rozwiązanie daje Ci o wiele więcej możliwości. Możesz zdefiniować własne template’y czy pliki statyczne, które zostaną wykorzystywane jedynie w konkretnym blueprincie. Będzie to przydatne na przykład w sytuacji, gdy nasza apka posiada front widoczny dla każdego i panel administratora, do którego mogą dostać się jedynie zalogowani użytkownicy.

Powiedzmy, że chcemy, aby nasz panel administratora (czyli database_posts) znajdowało się w całości w blueprincie db_posts_blueprint. Jak to powinno wyglądać i działać?

Najpierw struktura:

blueprint2
W folderze db_posts_blueprint stworzyłam dwa foldery: posts_static i posts_templates. W posts_templates powstał jeszcze jeden folder db_posts_blueprint i dopiero w nim umieściłam index.html. Dlaczego tak? Wyjaśni nam to dokumentacja Flaska:

The reason for the extra admin folder is to avoid getting our template overridden by a template named index.html in the actual application template folder.

Po prostu nie chcemy, aby jakiekolwiek pliki ze sobą kolidowały – nawet takie o identycznych nazwach.

Kodowanie zacznijmy od pliku __init__.py w naszym blueprincie. Jak można zauważyć na screnshocie, zmieniły się dwie rzeczy – definicja blueprintu oraz routing. Pierwsze z nich wzbogaciło się o informację o folderach:

1
db_posts_blueprint = Blueprint('db_posts_blueprint', __name__, static_folder='posts_static', template_folder='posts_templates')

A drugie w odpowiedni url:

1
    return render_template('db_posts_blueprint/index.html', data = data, message = message)

Resztę pozostawiamy bez zmian :)

Czas na views.py – musimy zmienić wywołanie blueprinta, aby działało, gdy znajduje się on w osobnym folderze (jeśli nie zrobiliśmy tego wcześniej):

1
from .blueprints.db_posts_blueprint import db_posts_blueprint

Następnie zajmijmy się wyglądem – weźmy pod uwagę index.html w blueprincie. Usuńmy database_posts.html (nie będzie już potrzebne), a jego treść, w połączeniu z bazową, przenieśmy do index.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
<html>
<head>
    <title>{% block title %}{% endblock %} - moja aplikacja </title>
    <link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/materialize.css') }}">
    <link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/style.css') }}">
    <link rel=stylesheet type=text/css href="{{ url_for('db_posts_blueprint.static', filename='style.css') }}">
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
  <nav class="light-blue">
  <div class="nav-wrapper">
    <div class="container">
      <a href="#" class="brand-logo">Logo</a>
      <ul id="nav-mobile" class="right hide-on-med-and-down">
        <li><a href="{{url_for('index')}}">Home</a></li>
        <li><a href="{{url_for('posts')}}">Posty</a></li>
        <li><a href="{{url_for('db_posts_blueprint.database_posts')}}">Posty z bazy danych</a></li>
        <li><a href="{{url_for('about')}}">O mnie</a></li>
        <li><a href="{{url_for('contact')}}">Kontakt</a></li>
      </ul>
    </div>
  </div>
</nav>
<main>
<div class="section no-pad-bot" id="index-banner">
    <div class="container">
      <h1 class="header center">Moja aplikacja</h1>
      <div class="row center">
        <div id="content">
          <p class="red white-text">{{message}}</p>
            <h2>Dodaj wpis</h2>
            <div class="row">
              <form class="col s12" action="{{ url_for('db_posts_blueprint.database_posts')}}" method="post">
                <div class="row">
                  <div class="input-field col s6">
                   <input name="tytul" id="tytul" type="text" class="validate">
                   <label for="tytul">Tytuł</label>
                 </div>
                 <div class="input-field col s6">
                   <input name="autor" id="autor" type="text" class="validate">
                   <label for="autor">Autor</label>
                 </div>
                  <div class="input-field col s12">
                    <textarea id="tresc" name="tresc" class="materialize-textarea"></textarea>
                    <label for="tresc">Treść</label>
                  </div>
                </div>
                <button class="btn waves-effect waves-light custom-green" type="submit" name="action" value="add">Zapisz</button>
              </form>
            </div>
            <h2>Posty</h2>
            <p>
              Witaj na podstronie, na ktorej wyświetlone są posty z bazy danych
              {% for row in data %}
                <article>
                  <h3>{{ row[0] }}. {{ row[1] }}</h3>
                  <p>{{ row[2] }}</p>
                  <form class="col s12" action="{{ url_for('db_posts_blueprint.database_posts')}}" method="post">
                    <input id='{{row[0]}}' type='hidden' value='{{row[0]}}' name='id-delete'>
                    <button class="btn waves-effect waves-light red" type="submit" name="action" value="delete">Usuń mnie</button>
                  </form>
                  <p><i>Autor: {{ row[3] }}</i></p>
                </article>
              {% endfor %}
            </p>
        </div>
      </div>
    </div>
</div>
</main>
<footer class="page-footer orange">
    <div class="container">
      <div class="row">
      </div>
    </div>
    <div class="footer-copyright">
      <div class="container">
      Copyright
      </div>
    </div>
</footer>
 
 
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/materialize.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/script.js') }}"></script>
</body>
</html>

Póki co nie odwołujemy się w nim do base.html, tylko hardkodujemy całość (załóżmy, że nasz panel admina będzie miał osobną bazę, którą zdefiniujemy w przyszłości).

Interesująca jest ta linijka:

1
  <link rel=stylesheet type=text/css href="{{ url_for('db_posts_blueprint.static', filename='style.css') }}">

Jest ona odwołaniem do folderu statycznego blueprintu i pozwala zdefiniować własne style tylko dla niego. Treść style.css (znajdującego się w posts_static) wygląda obecnie tak:

1
2
3
main {
  background-color: #e1e1e1;
}

Zmieniając tylko kolor tła na szary (ale wystarczy na potrzeby testu).

Pora na sprawdzenie, czy wszystko działa jak trzeba – powinno :)

Tego typu podejście sprawdza się przede wszystkim przy nieco większych aplikacjach, ale gdy chcemy oddzielić od siebie części serwisu, również możemy z niego skorzystać. Modyfikacja nie jest bardzo skomplikowana (szczególnie, gdy dopiero zaczynamy), a bardzo pomaga uporządkować kod i sprawia, że łatwiej się w nim później zorientować. Jednym słowem – warto :)

admin

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)