... Flask/Grinberg – na této stránce se budeme učit Flask dle Miguela Grinberga: The Flask Mega-Tutorial Part I: Hello, World!...
Tato stránka je součástí projektu:
<a href='/wiki/Kategorie:Flask' title='Kategorie:Flask'>Flask</a>
Příslušnost: všeobecná

Flask/Grinberg – na této stránce se budeme učit Flask dle Miguela Grinberga: The Flask Mega-Tutorial Part I: Hello, World!

Tato stránka shlukuje všech 23 kapitol dohromady. Chceme-li si prohlížet jednotlivé kapitoly odděleně, jejich seznam najdeme na stránce: Flask/Grinberg


01 - Nazdárek!

Vše je na GitHubu:

Chceme-li si stáhnout zdrojáky pro určitou kapitolu:

  1. na liště klikneme na 24 releases
  2. jsou označené v0.0v0.23 – to jsou odpovídající čísla kapitol
  3. můžeme si stáhnout příslušné zdrojáky jako .zip anebo .tar.gz

Takže věci pro tuto 01. kapitolu jsou zde:

… a tak podobně i v dalších kapitolách

Nicméně Miguel Grinberg doporučuje, aby si každý příslušné příklady naťukal na klávesnici sám, z didaktických důvodů.

U každého balíčku je MIT licence, která umožňuje tyto zdrojáky volně používat i různě modifikovat – za předpokladu, že tato licence bude všude uváděna. Proto ji uvádíme i zde:

The MIT License (MIT)  Copyright (c) 2017 Miguel Grinberg  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 

Installing Python

sudo apt install python3 python3 

Installing Flask

sudo pip3 install flask 

Nicméně Ginberg tento přímý způsob instalace do systému nedoporučuje – a to z toho důvodu, že když vyvineme nějakou aplikaci pod jednou verzí a pak upgradujeme flask, že nám pak s tou starou verzí naší aplikace můžeme mít problémy.

Jako řešení navrhuje vyvíjet každou aplikaci v jejím vlastním virtuálním prostředí a teprve v tomto prostředí pak nainstalovat flask a další věci:

python3 python3 -m venv venv virtualenv venv source venv/bin/activate pip3 install flask 

Výhoda toho je také ta, že nemusíme mít právo roota, abychom si vše instalovali přímo do operačního systému – vše děláme v našem uživatelském prostoru.

Ovšem nevýhoda toho je, že pro každou aplikaci pak musíme mít nainstalované vše znova, což zabírá místo na disku atd. Proto si to virtuální prostředí prozatím můžeme odpustit. Kdo ale chce, ať postupuje dle Ginbergoav návodu.

A "Hello, World" Flask Application

Zjistíme, že na vytvoření jednoduché webové aplikace potřebujeme tři soubory v adresářové stuktuře (lomítko na začátku neoznačuje systémový kořen, ale nějaký náš kořenový adresář, ve kterém budeme aplikaci vyvíjet):

  • /mojeaplikace.py
    • /app/__init__.py
    • /app/routes.py


/app/__init__.py

# app/__init__.py: Flask application instance  from flask import Flask  app = Flask(__name__)  from app import routes  # modul routes si vytvoříme hned za malou chvílku; tady ho ale musíme importovat až na konci skriptu, abychom zamezili vzájemným referencím 

Tento skript vytváří proměnnou (= aplikační objekt) app jakožto instanci třídy Flask importovanou z nainstalovaného balíku flask. Proměnna __name__ je predefinovanou proměnnou, obsahující jméno modulu, ve kterém je použita. Flask tuto lokaci použije, aby věděl, kde má hledat další soubory k naší aplikaci – šablony atd.

/app/routes.py: Zde si nadefinujeme, co se má zobrazovat na jednotlivých webových stránkách:

from app import app  # @ označuje tzv. dekorátor, modifikující funkci za ním následující – registruje ji jakožto něco @app.route('/')      # tento dekorátor vyvolá následující view-funkci při požadavku kořenového URL '/' @app.route('/index') # tento dekorátor vyvolá tu samou funkci, když klient bude chtít '/index' def index():         # to je tzv. view-funkce, mapovaná do jednoho či více URL     return "Nazdáreček, hi hej!" 

/mojeaplikace.py – soubor v nejvyšším patře našeho adresáře

from app import app  # z balíku app importujeme proměnnou app 

(Ginberg zde namísto mojeaplikace.py píše microblog.py, my jsme tu použili obecnější název)

Naše první aplikace je hotová, ale aby flask věděl, kde ji najít, musíme mu nastavit proměnnou:

export FLASK_APP=mojeaplikace.py 

Poté už můžeme flask spustit jednoduchým příkazem:

flask run 

Flask spustí webový server, který nám naši aplikaci obslouží na portu 5000, takže ji najdeme na URL http://127.0.0.1:5000/ neboli http:localhost:5000/

Při instalaci na produkční web pak aplikace bude čekat na portu 443 (případně 80, pokud nebudeme implementovat šifrování)

Abychom nemuseli při každém sezení znova exportovat FLASK_APP, nainstalujeme si balík:

sudo pip3 install python-dotenv 

A pak na vrchol svého adresáře s naší aplikací umístíme soubor: .flaskenv

FLASK_APP=mojeaplikace.py 

Ale to asi bude fungovat jen v tom virtuálním prostředí, které jsme se rozhodli nevyužívat.

02 - Šablony

Webové stránky budeme psát pomocí šablon, ve kterých můžeme používat výrazy Jinja2 ve dvojitých složených závorkách. Šablony mohou být do sebe vnořeny, takže si uděláme např. jednu základní base.html, na základě které pak vytvoříme výchozí stránku index.html:

/app/templates/base.html

<html>     <head>       <title>Naše aplikace</title>     </head>     <body>         <div>Aplikace: <a href="/index">Domů</a></div>         <hr>         {% block content %}{% endblock %}     </body> </html> 

/app/templates/index.html

{% extends "base.html" %}  {% block content %}     <h1>Hi, {{ user.username }}!</h1>     {% for post in posts %}     <div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>     {% endfor %} {% endblock %} 

03 - Formuláře

Chapter 3: Web Forms

Introduction to Flask-WTF

Configuration

User Login Form

Form Templates

Naučíme se vyplňovat formuláře metodou POST v souboru login.html:

{% extends "base.html" %}  {% block content %}     <h1>Sign In</h1>     <form method="post" novalidate>  <!-- validation will be done on the server side -->         {{ form.hidden_tag() }}	<!-- protect the form against CSRF attacks, see SECRET_KEY -->         <p>             {{ form.username.label }}<br>             {{ form.username(size=32) }}         </p>         <p>             {{ form.password.label }}<br>             {{ form.password(size=32) }}         </p>         <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>         <p>{{ form.submit() }}</p>     </form> {% endblock %} 

Přitom dostávám chybu Method Not Allowed – The method is not allowed for the requested URL (chyba 405).

Co je to za chybu a jak jí čelit? Viz:

Rovněž dle w:Stavové kódy HTTP Pochopíme, že chyby se dělí m.j. na:

  • 5xx – server error responses – například známá 500 Internal server error
  • 4xx – client error responses – například 404 Page Not Found (je to chyba klienta, že chce něco, co není!)

O.K., dočetli jsme se, že odhalit chybu 405 je často dost obtížné – neodhalil jsem.

Někde na nějakém videu jsem si všiml, že v souboru routes.py při definici webové stránky, která používá jinou metodu, než POST, to musí být explicitně uvedeno. Tedy:

@app.route('/login', methods=['GET','POST']) 

A vida! To je to, co nakonec pomohlo. Divím se, že Grinberg to tam neměl uvedeno. AHA! Má to tam uvedeno. Jenže kousek dále, než jsem dočetl. Dokonce i s tou chybou, která tak vznikne. Byl jsem zkrátka rychlejší...

Nicméně jsem přitom také odkoukal, že ten chlapík ten problém řeší v jakémsi prostředí:

Zajímavé věci...

Form Views

Receiving Form Data

Vybrání dat z formuláře je jednoduché – vrací je metoda:

form.jméno_pole.data
@app.route('/login', methods=['GET', 'POST']) def login():     form = LoginForm()     if form.validate_on_submit():                                       # receives the form by POST                                                                                               flash('Login requested for user {}, remember_me={}'.format(             form.username.data, form.remember_me.data))         return redirect('/index')     return render_template('login.html', title='Sign In', form=form)   # sends the form to the client – GET 

V tomto oddílu se také naučíme používat funkci flash, která kumuluje různé hlášky do svého seznamu, ke kterému je pak možno přistoupit (a zároveň jej vyprázdnit) funkcí

get_flashed_messages()

Zprávy si pak můžeme zobrazit např. HTML kódem:

        {% with messages = get_flashed_messages() %}         {% if messages %}         <hr>          <i>Hlášky:</i>         <ul>             {% for message in messages %}             <li>{{ message }}</li>             {% endfor %}         </ul>         <hr>         {% endif %}         {% endwith %} 

04 - Databáze

Chapter 4: Database

Databases in Flask

Databáze není do Flasku přímo integrovaná, což je dobře – můžeme si zde používat, co chceme – zkrátka použijeme vhodnou extensi.

Databázové systémy zhruba je rozdělujeme do dvou skupin:

SQL je častější, tak se zde budeme zaobírat s ní.

Použijeme dvě extense (ta druhá viz níže)

sudo pip3 install flask-sqlalchemy
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy(app)  #inicializace databáze 

Database Migrations

Extense, která pomáhá řešit migraci databází:

sudo pip3 install flask-migrate
from flask_migrate import Migrate migrate = Migrate(app, db) 

Flask-SQLAlchemy Configuration

Začneme s SQLite:

config.py:

import os basedir = os.path.abspath(os.path.dirname(__file__))  class Config(object):     # ...     SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \         'sqlite:///' + os.path.join(basedir, 'app.db')             # Kde je databáze? Buď řečeno v environmentu anebo 'app.db' v kořenu naší aplikace     SQLALCHEMY_TRACK_MODIFICATIONS = False                         # zakážeme signál při každé změně databáze 

Na začátku je zapotřebí tu databázi inicializovat – takže nejlépe to vše najednou učiníme v našem souboru app/__init__.py, který teď bude vypadat:

from flask import Flask from config import Config from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate  app = Flask(__name__) app.config.from_object(Config) db = SQLAlchemy(app)               # objekt reprezentující databázi migrate = Migrate(app, db)         # objekt reprezentující migrační stroj (migration engine)  from app import routes, models     # importuji také modul "models", který bude definovat strukturu databáze 

Database Models

Struktura databáze (schema)

databázový model = kolekce tříd

objekty těchto tříd ⟹ řádky tabulek

Použijeme WWW SQL Designer tool

Tabulka:

user
idINTEGERprimary_key
usernameVARCHAR (64)unique
emailVARCHAR (120)unique
password_hashVARCHAR (128)

Takovýto návrh přepíšeme do souboru app/models.py:

from app import db             # app dostala db při inicializaci __init__.py  class User(db.Model):          # třída User je inheritována ze základní třídy db.Model     id = db.Column(db.Integer, primary_key=True)   # sloupce jsou instance třídy db.Column     username = db.Column(db.String(64), index=True, unique=True)     email = db.Column(db.String(120), index=True, unique=True)     password_hash = db.Column(db.String(128))      def __repr__(self):        # metoda __repr__ určuje, jak se mají objekty tisknout:         return '<User {}>'.format(self.username) 

Creating The Migration Repository

Během vývoje aplikace se struktura databáze může měnit. Migrační framework Alembic (chladič, vytvářející křivuli – v alchymii) umožňuje provádět takové změny databázových schemat, aniž by se musela znovu vytvářet celá databáze.

Alembic udržuje svůj migrační repozitář, což je adresář, do kterého se ukládají migrační skripty. Po každé změně schematu se sem přidává další skript. Příkazy skriptu jsou příkazy flasku. flask-migrate nám přidá příkaz flask db

Od dřívějška (Chapter 1) bychom měli mít nastavenu proměnnou prostředí:

export FLASK_APP naše_aplikace.py

a pak můžeme z příkazové řádky shellu zadat příkaz:

flask db init 

což nám vytvoří adresář migration:

  • /versions
  • script.py.mako
  • env.py
  • README
  • alembic.ini

The First Database Migration

První databázová migrace, zahrnující mapování tabulky Users do databázového modelu User – možnosti:

  • manuálně
  • automaticky: Alembic porovná schema definované v modelu s aktuální databází a spustí migrační skript

Dosud jsme žádnou předchozí databázi neměli, takže z příkazového řádku spustíme (argument -m přidá komentář):

flask db migrate -m "users table" 
INFO  [alembic.runtime.migration] Context impl SQLiteImpl. INFO  [alembic.runtime.migration] Will assume non-transactional DDL. INFO  [alembic.autogenerate.compare] Detected added table 'user' INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_email' on '['email']' INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_username' on '['username']'   Generating /.../04a-Database/migrations/versions/c7329ba56057_users_table.py ...  done 

A vidíme malý zázrak. Krátce zrekapitulujeme:

  1. V souboru /app/models.py jsme si definovali náš databázový model
  2. Alembic nám vytvořil soubor /migrations/versions/c7329ba56057_users_table.py, což je pythonovský skript, obsahující definice dvou funkcí, jejichž význam je jasný:
    1. def upgrade():
    2. def downgrade():
  3. Alembic nám vytvořil SQLite databázový soubor /app.db

Předpokládám, že máme na linuxu nainstalováno:

sudo apt-get install sqlite3 sudo apt-get install sqlitebrowser 

Podíváme se na tu databázi /app.db grafickým prohlížečem databáze:

sqlitebrowser app.db & 

V případě, že není sqlitebrowser nainstalovaný, spustíme:

sqlite3 app.db 

a pak můžeme spouštět příkazy jako:

sqlite> .help sqlite> .show sqlite> .databases sqlite> .tables sqlite> .fullschema sqlite> select * from user; 

Vidíme ale, že tam zatím máme jen jednu prázdnou tabulku alembic_version – protože samotný příkaz Abychom v té databázi vytvořili naše schema, musíme spustit příkaz:

flask db upgrade 

V Browseru pro SQLite si můžu ověřit, že databáze byla aktualizována.

Database Upgrade and Downgrade Workflow

Database Relationships

Uděláme si další tabulku:

posts
idINTEGERprimary_key
bodyVARCHAR (140)
timestampDATETIME
user_idINTEGERforeign key

Tato relace se nazývá one-to-many, protože jeden uživatel může napsat více zpráv.

Druhou tabulku vytvoříme v souboru app/models.py podobně, jako tu první:

class Post(db.Model):     id = db.Column(db.Integer, primary_key=True)     body = db.Column(db.String(140))     timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) # argument 'default' dostane samotnou funkci, nikoli funkční hodnotu     user_id = db.Column(db.Integer, db.ForeignKey('user.id'))   # tento klíč je 'id' z tabulky 'user'       def __repr__(self):         return '<Post {}>'.format(self.body) 

Do první tabulky user ještě doplníme řádek, popisující nové pole:

    posts = db.relationship('Post', backref='author', lazy='dynamic') 

a na začátek souboru nezapomenu doplnit:

from datetime import datetime 

Teď provedu novou migraci databáze a po ní upgrade:

flask db migrate -m "posts table" flask db upgrade 

Play Time

Hrajeme si s databází v příkazové řádce pythonu.

>>> from app import db >>> from app.models import User, Post 

A dál si zkoušíme různé další příkazy:

u = User(username='john', email='john@example.com') db.session.add(u) db.session.commit()  users = User.query.all() users for u in users:     print(u.id, u.username) u = User.query.get(1) ... atd.  users = User.query.all() for u in users:     db.session.delete(u) db.session.commit() 

Shell Context

Nemusím z příkazové řádky shellu volat python3, ale můžu rovnou zavolat:

flask shell 

a pak nemusím znovu importovat db a ty další věci.

Vytvoříme si ještě shell context, který mi pre-importuje i další věci – instanci databáze a modely. Přidáme to do hlavního souboru microblog.py, takže bude vypadat:

from app import app, db from app.models import User, Post  @app.shell_context_processor def make_shell_context():     return {'db': db, 'User': User, 'Post': Post} 

Dekorátor app.shell_context_processor teď registruje tuhle funkci jakožto kontextovou funkci flaskového shellu: Jakmíle ze shellu operačního systému spustíme příkaz flask shell, tak invokuje tuto funkci, která vrací slovník (dictionary).

Takže teď spustíme

flask shell 

a můžeme klást takové dotazy, jako:

db User Post 

V případě, že bychom dostali chybové výjimky, znamená to, že ta funkce make_shell_context() nebyla Flaskem registrována. Nejčastější chybou je, že v systémovém shellu nemáme exportovanou proměnnou FLASK_APP (jak jsme popisovali v kapitole Flask/Grinberg/01_-_Nazdárek!#A_"Hello,_World"_Flask_Application):

export FLASK_APP=mojeaplikace.py 

05 - Zalogování

Chapter 5: User Logins

Password Hashing

Vytvoření a verifikace hashe – zkusíme si to nejdříve v pythonovském shellu:

>>> from werkzeug.security import generate_password_hash, check_password_hash >>> hash = generate_password_hash('brekeke') >>> hash >>> check_password_hash (hash, 'brekeke') >>> check_password_hash (hash, 'brekek') 

Introduction to Flask-Login

sudo pip3 install flask-login 

(Naštěstí na pythonanywhere je nainstalován: pythonanywhere.com/batteries_included/)

Tato flasková extense obhospodařuje stav uživatelů, kteří jsou zalogováni, včetně funkce remember me, která umožňuje udržovat uživatele zalogovaného i když si zavře okno browseru.

Stejně jako jiné extense ji inicializujeme v souboru app/__init__.py:

# … from flask_login import LoginManager # … app = Flask(__name__) # … login = LoginManager(app) 

Preparing The User Model for Flask-Login

Extense Flask-Login potřebuje tři vlastnosti (property) a jednu metodu:

  • is_authenticated: property je:
    • True = uživatel se zalogoval se správnými kredenciály (loginname, password)
    • False = nikoli
  • is_active: property je:
    • True = uživatelský účet (account) je aktivní
    • False = nikoli
  • is_anonymous: property je:
    • False = uživatel je řádným uživatelem
    • True = speciální anonymní uživatel
  • get_id(): metoda, která vrací jednoznačný identifikátor uživatele jako string

Tyto čtyři věci se implementují celkem snadno, nicméně Flask-Login poskytuje třídu mixin, která v sobě už zahrnuje generickou implementaci, vyhovující většině běžných požadavků – takže stačí do souboru app/models.py přidat:

# … from flask_login import UserMixin  class User(UserMixin, db.Model):     # … 

User Loader Function

user session = prostor, kam Flask-Login ukládá stopu zalogovaných uživatelů (jejich jedinečný identifikátor) – jaké stránky ten uživatel navštíví.

Protože Flask-Login neví nic o (použitých) databázích a modelech, tak si aplikace musí nakonfigurovat funkci user_loader, a to v modulu app/models.py:

from app import login # … @login.user_loader def load_user(id):     return User.query.get(int(id)) 

Funkci user_loader jsme zaregistrovali dekorátorem @login.user_loader.

V databázi je uživatel uložený jako numerické ID a my jej chceme konvertovat na jméno uživatele.

Logging Users In

Nyní již máme přístup k databázi a tím pádem můžeme zkompletovat naši view-funkci v souboru app/routes.py:

# app/routes.py: Login view function logic # … from flask_login import current_user, login_user from app.models import User  # …  @app.route('/login', methods=['GET', 'POST']) def login():     if current_user.is_authenticated:    # kdyby zalogovaný user šel znovu na login,         return redirect(url_for('index'))    # pošleme ho na index     form = LoginForm()     if form.validate_on_submit():         user = User.query.filter_by(username=form.username.data).first() # filtrem vyhledám usera dle username; first() vrací None jestliže neexistuje         if user is None or not user.check_password(form.password.data):  # Když se mu nepovedlo zalogovat …             flash('Invalid username or password')                                     return redirect(url_for('login'))                            # … znovu na login         login_user(user, remember=form.remember_me.data)    # login_user() je fce z Flask-Login: zaregistruje zalogovaného usera, nastaví current_user         return redirect(url_for('index'))                   # vrátíme se na index     return render_template('login.html', title='Sign In', form=form) 

Logging Users Out

Na to má Flask-Login funkci logout_user(), kterou velmi jednoduše použijeme opět v souboru app/routes.py:

# app/routes.py: Logout view function  # … from flask_login import logout_user  # …  @app.route('/logout') def logout():     logout_user()     return redirect(url_for('index')) 

Aby uživatel ten link viděl, tak mu ho přidáme do navigačního řádku hned po tom, co se zaloguje.

Soubor app/templates/base.html:

    <div>         Microblog:         <a href="{{ url_for('index') }}">Home</a>         {% if current_user.is_anonymous %}         <a href="{{ url_for('login') }}">Login</a>         {% else %}         <a href="{{ url_for('logout') }}">Logout</a>         {% endif %}     </div> 

Vlastnost (property) is_anonymous je jedním z atributů, které Flask-Login přidává objektu user skrzevá třídu UserMixin. Výraz current_user.is_anonymous je True pouze v případě, že uživatel není zalogován.

Requiring Users To Login

Některé stránky (protected pages) můžeme ochránit heslem a Flask-Login podporuje možnost, že pro jejich prohlížení pořádá uživatele o přihlášení.

Flask-Login potřebuje vědět, jaká view-funkce zařizuje login, tak mu to řekneme hned na začátku v souboru app/__init__.py:

# … login = LoginManager(app) login.login_view = 'login'  # 'login' = jméno té funkce, která dělá login_view neboli to samé jméno, které také použijeme jako argument url_for() 

Flask-Login ochrání stránky před anonymním přístupem pomocí dekorátoru @login_required. Když ten dekorátor umístíme pod jiné dekorátory, tak ta následná view-funkce bude ochráněna a příslušné stránky budou vyžadovat zalogování. V následujícím příkladu ochráníme již hlavní stránku aplikace v souboru app/routes.py:

#app/routes.py: @login_required decorator  from flask_login import login_required  @app.route('/') @app.route('/index') @login_required def index():     # … 

Po úspěšném zalogování ještě zbývá nasměrovat uživatele na tu stránku, ke které se předtím pokoušel přistoupit. To se udělá tak, že se do URL přidá query string (tj. string s otazníkem), takže v URL se nám pak ukáže:

…/login?next=/puvodni_stranka_na_kterou_chtel_uzivatel_jit

To lomítko ale bude zaencodované %2F, takže ve skutečnosti uvidíme:

…/login?next=%2Fpuvodni_stranka_na_kterou_chtel_uzivatel_jit

app/routes.py pak bude vypadat:

# app/routes.py: Redirect to "next" page  from flask import request               # z flasku importujeme request from werkzeug.urls import url_parse     # z werkzeugu importujeme url_parse: podle netloc nám určí, jestli je URL absolutní nebo relativní  @app.route('/login', methods=['GET', 'POST']) def login():     # …     if form.validate_on_submit():       # atd … už jsme vysvětlovali v sekci 'Logging Users In'         user = User.query.filter_by(username=form.username.data).first()         if user is None or not user.check_password(form.password.data):             flash('Invalid username or password')             return redirect(url_for('login'))         login_user(user, remember=form.remember_me.data)  # user se zaloguje             next_page = request.args.get('next')              # var. request obsahuje request clienta, request.args je slovník. next_page = další stránka za 'next'         if not next_page or url_parse(next_page).netloc != '':  # next_page není anebo obsahuje kompletní URL včetně domain name (= může být pokus o attack!)             next_page = url_for('index')                           # … jdi normálně na hlavní stránku         return redirect(next_page)                                 # jdi na next_page     # … 

Showing The Logged In User in Templates

Namísto fake-usera (kapitola 02) už můžeme v šabloně app/templates/index.html použít skutečného uživatele:

{% extends "base.html" %}  {% block content %}     <h1>Hi, {{ current_user.username }}!</h1>     {% for post in posts %}     <div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>     {% endfor %} {% endblock %} 

A naopak vyndáme user template argument (tj. argument user=user) z view-funkce v souboru app/routes.py:

# app/routes.py: Do not pass user to template anymore  @app.route('/') @app.route('/index') def index():     # …     return render_template("index.html", title='Home Page', posts=posts) 

Teď si můžeme vyzkoušet, jak nám to funguje. Protože ještě nemáme udělanou registraci uživatele, vyzkoušíme si to z flask shell:

>>> u = User(username='Kychot', email='kychot@example.com') >>> u.set_password('kyky') >>> db.session.add(u) >>> db.session.commit() 

Anebo si takový testovací kousek programu, který nám bude vkládat nějaký záznam do databáze, můžeme dočasně strčit na konec souboru models.py

# TEST: password_hash = generate_password_hash('kyky') u = User(username='Kychot', email='kychot@example.com', password_hash = password_hash) db.session.add(u) db.session.commit() 

User Registration

Začneme tím, že si vytvoříme třídu RegistrationForm v souboru app/forms.py:

# app/forms.py: User registration form  from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms.validators import ValidationError, DataRequired, Email, EqualTo from app.models import User  # …  class RegistrationForm(FlaskForm):     username = StringField('Username', validators=[DataRequired()])     email = StringField('Email', validators=[DataRequired(), Email()])  # nový validátor Email() ⇐ WTForms     password = PasswordField('Password', validators=[DataRequired()])     password2 = PasswordField(                                          # heslo napsat dvakrát – pro kontrolu         'Repeat Password', validators=[DataRequired(), EqualTo('password')]) # nový validátor EqualTo()     submit = SubmitField('Register')      def validate_username(self, username):         user = User.query.filter_by(username=username.data).first()         if user is not None:             raise ValidationError('Please use a different username.')      def validate_email(self, email):         user = User.query.filter_by(email=email.data).first()         if user is not None:             raise ValidationError('Please use a different email address.') 

Těm validátorům, které použijeme z WTForms, se říká stock, jako že jsou na skladě. Mimo nich si ale můžeme vytvořit vlastní validátory – zkrátka když si vytvoříme nějakou metodu, kterou nazveme validate_<field_name>, tak to WTForms bude brát jako uživatelský validátor vyvolá ho stejně, jako ostatní validátory, které má „skladem”.

Aby se nám ten registrační formulář zobrazil, musíme si pro něj vytvořit registrační šablonu app/templates/register.html:

{% extends "base.html" %}  {% block content %}     <h1>Register</h1>     <form action="" method="post">         {{ form.hidden_tag() }}         <p>             {{ form.username.label }}<br>             {{ form.username(size=32) }}<br>             {% for error in form.username.errors %}             <span style="color: red;">[{{ error }}]</span>             {% endfor %}         </p>         <p>             {{ form.email.label }}<br>             {{ form.email(size=64) }}<br>             {% for error in form.email.errors %}             <span style="color: red;">[{{ error }}]</span>             {% endfor %}         </p>         <p>             {{ form.password.label }}<br>             {{ form.password(size=32) }}<br>             {% for error in form.password.errors %}             <span style="color: red;">[{{ error }}]</span>             {% endfor %}         </p>         <p>             {{ form.password2.label }}<br>             {{ form.password2(size=32) }}<br>             {% for error in form.password2.errors %}             <span style="color: red;">[{{ error }}]</span>             {% endfor %}         </p>         <p>{{ form.submit() }}</p>     </form> {% endblock %} 

Do souboru app/templates/login.html potřebujeme ještě doplnit link, který pošle uživatele na registrační formulář:

<p>Nový uživatel? <a href="{{ url_for('register') }}">Zaregistruj se!</a></p> 

A nakonec musíme napsat view-funkci, která bude tu registraci uživatele dělat (tj. nakonec ho přidá do databáze) – dáme ji do našeho souboru app/routes.py:

from app import db from app.forms import RegistrationForm  # …  @app.route('/register', methods=['GET', 'POST']) def register():     if current_user.is_authenticated:  # pokud je už uživatel zalogovaný, nebude se registrovat         return redirect(url_for('index'))     form = RegistrationForm()     if form.validate_on_submit():      # vše v pořádku, uživatele uložíme do databáze         user = User(username=form.username.data, email=form.email.data)         user.set_password(form.password.data)         db.session.add(user)         db.session.commit()         flash('Gratulujeme, jsi naším registrovaným uživatelem!')         return redirect(url_for('login'))   # Nově vytvořený uživatel se může rovnou zalogovat     return render_template('register.html', title='Register', form=form) 

Flask/Grinberg/06 - Uživatelská stránka a avatary

Flask/Grinberg/07 - Ošetření chyb

Flask/Grinberg/08 - Followers

Flask/Grinberg/09 - Stránkování

Flask/Grinberg/10 - Podpora e-mailů

Flask/Grinberg/11 - Facelift

Flask/Grinberg/12 - Datum a čas

Flask/Grinberg/13 - I18n and L10n

Flask/Grinberg/14 - Ajax

Flask/Grinberg/15 - Lepší struktura aplikací

Flask/Grinberg/16 - Fulltextové vyhledávání

Flask/Grinberg/17 - Nasazení na Linuxu

Flask/Grinberg/18 - Nasazení na Heroku

Flask/Grinberg/19 - Nasazení na Docker Containers

Flask/Grinberg/20 - Trocha JavaScriptového čarování

Flask/Grinberg/21 - Notifice uživatelů

Flask/Grinberg/22 - Úlohy na pozadí

Flask/Grinberg/23 - Application Programming Interfaces (APIs)





  Go to top  

This article is issued from web site Wikiversity. The original article may be a bit shortened or modified. Some links may have been modified. The text is licensed under "Creative Commons - Attribution - Sharealike" [1] and some of the text can also be licensed under the terms of the "GNU Free Documentation License" [2]. Additional terms may apply for the media files. By using this site, you agree to our Legal pages [3] [4] [5] [6] [7]. Web links: [1] [2]