Уеб програмиране. Flask#
Съдържание:
Какво е Flask ?
Просто приложение
Шаблони
Routing
Работа с ресурси
Работа със заявки
JSON API
Накратко за Web:#
Какво е Flask ?#
Flask е библиотека за Python, която ни позволява да създаваме уеб приложения. Тя е написана на Python и е свободен софтуер, който може да се използва безплатно.
Можем да си я инсталираме чрез pip install flask
.
Просто приложение#
Първото нещо, което трябва да направим е да заредим Flask модула. Това става с from flask import Flask
. След това трябва да създадем обект от тип Flask
. Това става с app = Flask(__name__)
. Като първи аргумент на конструктора на Flask
подаваме името на модула, в който се намираме. Това е необходимо, за да Flask знае къде да търси файловете, които са свързани с приложението.
След това трябва да дефинираме функции, които ще се изпълняват, при поискване на даден път. За целта използваме декоратора @app.route
. Това е декоратор, който се използва за да се определи къде да се изпълни функцията. Нека дефинираме функция, която да връща текста Hello world
, обвит като <p>
таг. Това става с return "<p>Hello world</p>"
.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello_world():
return "<p>Hello, World!</p>"
За да стартираме нашето уеб приложение, трябва да изпълним следната команда: python3 -m flask --app simple_app run
. Това ще стартира нашето приложение на адрес http://127.0.0.1:5000
!python3 -m flask --app examples/simple_app run
Друг начин да стартираме нашето приложение е като добавим app.run()
в main частта от кода ни.
Шаблони#
Flask поддържа и т.нар. HTML шаблони - това са HTML страници, които могат да получават данни от нашия Python код. Flask търси тези template-и в специална папка на име templates
.
Шаблоните във Flask са реализирани с помощта на Jinja.
Ще създадем един базов шаблон, който ще съдържа общата част на всяка наша страница:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
{% block styles %}
{% endblock %}
{% block scripts %}
{% endblock %}
</head>
<body>
<div id="main-content">
{% block content %}
{% endblock %}
</div>
</body>
</html>
Забелязваме някои нестандартни за HTML елементи - всичко, което е оградено от {}
скоби е израз, който ще бъде изчислен от Python кода. Това са {{ title }}
и {% block content %}{% endblock %}
. Първият израз ще бъде заменен със стойността на променливата title
, а вторият израз ще бъде заменен със съдържанието на блока content
.
Нека разширим нашия базов шаблон, като направим началната ни страница - тя ще е с името index.html
{% extends "base.html" %}
{% block content %}
<h1>This is the home page</h1>
<h2>Hi, {{user}}</h2>
{% endblock %}
Остава единствено да кажем на Flask да зареди нашия шаблон - това става с помощта на метода `render_template”.
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def home():
return render_template('index.html', title='Home', user='Lyubo')
Освен името на шаблона, към render_template
можем да подадем и стойности, с които да бъдат заместени изразите в шаблона.
!python3 -m flask --app examples/simple_templates run
Routing#
Дотук работехме само с една страница - как може да добавим допълнителни страници ?
Можем да използваме @app.route
декоратора, но с друг път, за да можем да създадем други страници. Нека направим примитивна логин страница.
Нека създадем нов template, който да бъде използван за логин страница.
{% extends "base.html" %}
{% block content %}
<h1>This is the login page</h1>
<form action="/login_action" method="POST">
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<input type="submit" value="Login">
</form>
{% if message %}
<h2>{{ message }}</h2>
{% endif %}
{% endblock %}
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def home():
return render_template('index.html', title='Home', user='Lyubo')
@app.route("/login")
def login():
return render_template('login.html', title='Login')
!python3 -m flask --app examples/simple_login run
Друга особеност на Flask е възможността за работа с динамични адреси - адреси, които се състоят от части, които се дефинират по време на изпълнение. Нека направим такъв адрес за потребителската страница.
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def home():
return render_template('index.html', title='Home', user='Lyubo')
@app.route("/login")
def login():
return render_template('login.html', title='Login')
@app.route("/user/<username>")
def user_page(username):
return render_template('index.html', title='User', user=username)
Каквото бъде подадено като адрес след /user/
ще бъде записано в променливата username
.
!python3 -m flask --app examples/simple_user_page run
Бележка: За работа с по-сложни логин страници, може да погледнете тук.
Работа с ресурси#
Почти винаги ще ни се наложи да работим с някакви статични ресурси в нашето уеб приложение - било то CSS файлове, JS файлове или изображения. За да можем да достъпваме тези ресурси, ще трябва да ги копираме в папка на нашето приложение.
Нека създадем файла style.css
в папката static
.
#main-content {
width: 100%;
height: 100%;
background-color: #333333;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 0 0 10px #ccc;
}
h1 {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: white;
}
За да достъпим такъв статичен ресурс през нашия HTML код, можем да използваме {{ url_for('static', filename='img/logo.png') }}
. Това ще ни върне пътя до файла logo.png
в папката img
в папката static
.
!python3 -m flask --app examples/fancy_page run
Работа със заявки#
Във Flask можем да зададем метода на достъп до страницата, който да бъде използван. По подразбиране, методът е GET
, но можем да го променим с @app.route('/login', methods=['POST'])
.
Нека създадем една нова страница, която да бъде достъпна само с POST заявка. Тя ще има за цел да обработва данните, които са подадени от логин формата.
Цялата информация около подадената заявка се намира в специалния request
обект. Чрез него можем да проверим метода, с който е поискана страницата. В случай, че това не е POST
метод, можем да върнем грешка.
Освен това, можем да достъпим данни подадени през нашата логин форма - данните от нея се намират в request.form
.
Ако се въвели правилната парола за администраторския профил, ще бъдем пренасочени към страницата /user/admin
.
Ако пък имаме грешна парола или потребителско име, ще покажем подходящо съобщение.
from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)
@app.route("/")
def home():
return render_template('index.html', title='Home', user='Guest')
@app.route("/login_action", methods=['POST'])
def login_action():
if request.method != 'POST':
return redirect(url_for('/login', message='Invalid method'))
if request.form['username'] == 'admin' and request.form['password'] == 'admin':
username = request.form['username']
return redirect(url_for('user_page', name=username))
else:
return redirect(url_for('login', message='Invalid username or password'))
@app.route("/login")
def login(message=None):
if 'message' in request.args:
message = request.args['message']
print(message)
return render_template('login.html', title='Login', message=message)
@app.route("/user_page/<name>")
def user_page(name):
return render_template('user.html', title='User', user=name)
app.run()
Можем да забележим обаче, че администраторската ни страница е достъпна и без да трябва да сме се логнали. За достъп до нея, можем да изискваме даден ключ, или token. Него можем да подадем като параметър на заявката.
from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)
@app.route("/")
def home():
return render_template('index.html', title='Home', user='Guest')
@app.route("/login_action", methods=['POST'])
def login_action():
if request.method != 'POST':
return redirect(url_for('/login', message='Invalid method'))
if request.form['username'] == 'admin' and request.form['password'] == 'admin':
username = request.form['username']
return redirect(url_for('user_page', name=username, token='123456'))
else:
return redirect(url_for('login', message='Invalid username or password'))
@app.route("/login")
def login(message=None):
if 'message' in request.args:
message = request.args['message']
print(message)
return render_template('login.html', title='Login', message=message)
@app.route("/user_page/<name>")
def user_page(name):
if 'token' not in request.args:
return redirect(url_for('login', message='You must login first'))
return render_template('user.html', title='User', user=name)
Естествено, това е доста прост начин за аутентикация. Повече по темата може да прочетете тук.
JSON API#
С помощта на Flask можем и да направим API, който ни връща отговорите в JSON формат. За целта ще разгледаме някои полезни методи на Flask.
jsonify
#
Методът jsonify
ни позволява лесно да превърнем дадени променливи, в отговор на заявка в JSON формат.
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def home():
return jsonify(status='success', message='Hello, World!')
Резултатът от извикването на jsonify
ще е Response
обект, с mimetype application/json
. Подадените аргументи пък, ще бъдат върнати в JSON формат.
!python3 -m flask --app examples/api_jsonify run
Има два прости начина да “тестваме” нашето API. Първият е да използваме браузъра, като отворим адреса на нашата страница, като добавим /api
в края. Вторият начин е да използваме curl
командата.
curl http://127.0.0.1:5000
!curl "http://127.0.0.1:5000"
make_response#
Понякога се налага да промени нещо по Response
обекта - било то response code, header-и или други елементи.
За тази цел, можем да използваме make_response
функцията.
Освен текст, можем да зададем и статус кода на нашия отговор като аргумент на make_response
функцията.
Друг полезен аргумент е headers
, който приема речник с header-и, които да бъдат добавени към отговора.
from flask import Flask, make_response
app = Flask(__name__)
@app.route("/")
def home():
return make_response("Hello World!", 200, {"Debug": "Hello World!"})
@app.route("/error")
def error():
return make_response("Error!", 404, {"Debug": "Error!"})
!python3 -m flask --app examples/make_response_example run
Можем да видим върнатия ни header, като използваме curl
командата с опция -i
.
curl -i http://127.0.0.1:5000/error
Достъп до header-и#
Видяхме как можем да изпращаме header-и към нашия отговор. Но как можем да ги прочетем?
За да прочетем header-и, можем да използваме request.headers
атрибут. Той е речник, който съдържа всички header-и, които са изпратени от клиента.
Нека разгледаме пример, в който ще очакваме от клиента да ни изпрати в header-а стойност за user
.
from flask import Flask, render_template, request, redirect, url_for
app = Flask(__name__)
@app.route("/")
def home():
if 'user' in request.headers:
user = request.headers['user']
return render_template('index.html', title='Home', user=user)
return render_template('index.html', title='Home', user='Guest')
!python3 -m flask --app examples/accessing_headers run
Можем да тестваме примера отново чрез командата curl
. Ако искаме да изпратим header, можем да използваме опция -H
.
!curl "http://127.0.0.1:5000"
!echo ""
!curl -H "user:Lyubo" "http://127.0.0.1:5000"
Работа с конфигурационни файлове#
Flask поддържа няколко настройки. Те се намират в config
атрибута на Flask
обекта.
Някой от най-използваните са ENV
, DEBUG
, TESTING
и SECRET_KEY
.
ENV
- средата, в която работи Flask. Може да бъдеdevelopment
,production
илиtesting
.DEBUG
- ако еTrue
, Flask ще бъде стартиран в режим на debug. Това означава, че при грешка, Flask ще покаже по-подробна информация за грешката. Друга удобна функционалност е, че когато променим нашия код, Flask сървъра ще се рестартира автоматично.TESTING
- ако еTrue
, Flask няма да хваща грешки. Използва се при тестване.SECRTE_KEY
- таен ключ, който се използва за криптиране на данни
Пълен списък може да намерите в документацията.
Като пример, можем да създадем страница, която да е достъпна, само като ENV
е равна на development
.
from flask import Flask, make_response
app = Flask(__name__)
# app.config['ENV'] = 'development'
@app.route("/")
def home():
return make_response("Hello World!", 200, {"Debug": "Hello World!"})
if app.config['ENV'] == 'development':
@app.route("/dev")
def dev():
return make_response("This is the development panel !", 200)
!python3 -m flask --app examples/config_example run
Освен в кода, можем да задаваме настройки и във файлове. Това е удобно, когато искаме да съхраняваме тайни данни, като пароли, ключове и т.н.
Нека създадем файл на име dev.conf
, със следното съдържание:
TESTING = True
ENV = 'development'
Освен това, ще зададем стойността на средата FLASK_CONFIG
на пътя към нашия конфигурационен файл.
from flask import Flask, make_response
app = Flask(__name__)
app.config.from_envvar('FLASK_CONFIG')
@app.route("/")
def home():
return make_response("Hello World!", 200, {"Debug": "Hello World!"})
if app.config['ENV'] == 'development':
@app.route("/dev")
def dev():
return make_response("This is the development panel !", 200)
С app.config.from_envvar
казваме на Flask да зареди настройките от конфигурационния файл, намиращ се на пътя записан в FLASK_CONFIG
.
!export FLASK_CONFIG='dev.conf' && python3 -m flask --app examples/external_config_example run
Обработка на грешки#
Понякога бихме искали да обработим грешки, които са възникнали в нашето приложение. Например, ако някой от нашите view-та върне 404
, можем да направим нещо по-интересно, отколкото да покажем само текста Not Found
.
С помощта на errorhandler
декоратора, можем да обработим грешки, които са възникнали в нашето приложение.
Ще добавим една нова страница, която ще връща 404
код. Така ще можем да видим как се обработва грешката.
{% extends "base.html" %}
{% block styles %}
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
{% endblock %}
{% block content %}
<h1>Page was not found</h1>
<img src="{{ url_for('static', filename='logo.png') }}"/>
{% endblock %}
from flask import Flask, request, make_response, render_template
app = Flask(__name__)
@app.route("/")
def home():
if 'user' in request.headers:
user = request.headers['user']
return make_response(f"Hi, {user}", 200)
return make_response("Hi, Guest", 200)
@app.errorhandler(404)
def page_not_found(e):
return make_response(render_template('custom_404.html'), 404)
!python3 -m flask --app examples/custom_404 run