Flask Basics
Flask Basics#
The app receives all the requests. The requests are sent to the correct function or view.
The function/view is found through the route.
Installing Flask#
pip install flask
Simple App#
from flask import Flask
Now we need an app instance and we want it named the same as the current namespace
app = Flask(__name__)
This means use whatever our current namespace is.
Run the app and set debug=True so it auto refreshes
app.run(debug=True)
You can run the site now with
python simple_app.py
But you will get a 404 as there is no route
Routes should be defined before app.run()
Creating a Route#
A route is a function and a routing decorator directive
@app.route('/')
def index():
return "Hello from treehouse"
URL Parameters#
If you want to add parameters to the route you need the request object
from flask import request
It is a global object, so it is always available but python neckbeards hate it.
The function is given a default and the name is acquired with request.args.get('name', name)
@app.route('/')
def index(name="surfer190"):
name = request.args.get('name', name)
return f'Hello from { name }'
Neat Routes#
Views can have more than 1 route
@app.route('/<name>')
This acquired the name parameter and the request.args.get() is no longer required
You can even specify the data type in the route.
If a different data type is given the route will 404:
@app.route('/add/<int:num1>/<int:num2>')
def add(num1, num2):
return '{} + {} = {}'.format(num1, num2, num1 + num2)
You need to return a stirng, you cannot return a string.
If you want to accept both int and float use multiple routes:
@app.route('/add/<float:num1>/<float:num2>')
@app.route('/add/<int:num1>/<int:num2>')
@app.route('/add/<int:num1>/<float:num2>')
@app.route('/add/<float:num1>/<int:num2>')
def add(num1, num2):
return '{} + {} = {}'.format(num1, num2, num1 + num2)
Templates#
You can render html with a multiline string:
def add(num1, num2):
return '''
<!doctype html>
<html>
<head><title>Adding!</title></head>
<body>
<h1>{} + {} = {}</h1>
</body>
</html>
'''.format(num1, num2, num1 + num2)
But the better way is use templates
-
Create a
templatesfolder -
Add the template file
base.html:<!doctype html> <html> <head> <title>Adding!</title> </head> <body> <h1>Num1 + Num2 = Sum-thing</h1> </body> </html> -
Import
render_templatefromflaskfrom flask import render_template -
In your
view functionreturn render_template('base.html')
Flask uses jinja2 to template and in jinja2 you print variables with {{ }}.Ensure that the view is passed the variable in render_template.
def add(num1, num2):
return render_template('add.html', num1=num1, num2=num2)
We can also use ** to unpack a context dictionary
def add(num1, num2):
context = {'num1': num1, 'num2': num2}
return render_template('add.html', **context)
Template Inheritance#
Removes duplicate html and makes use of blocks that can be swapped out
Create a layout.html file in templates, copy generic code and add blocks for changable context:
{% block content %}
{% endblock %}
To use it in other template files you need to extend:
{% extends 'layout.html' %}
To add the content from the parent block use the super() function in the block
Static Files#
Static files like images, js and css are stored in the static directory
You can then reference the file with /static/*
<link rel="stylesheet" href="/static/styles.css">
Forms#
There are many libraries available for making forms.
You can set a route only applicable to certain methods
@app.route('/save', methods=['POST'])
Then set the url of the form:
<form action="{{ url_for('save') }}" method="POST">
Redirect#
To redirect import url_for and redirect from flask
return redirect(url_for('index'))
Form Data#
When posting to a url the request has a form attribute
Cookies#
Cookies are good for when you don’t have a database and don’t want to use javascript local storage.
In flask cookies are set on the response, which you don’t have access to until after the return.
So you have to fake the response with make_response
response = make_response(redirect(url_for('index')))
return response
To set the cookie as a json object of the form data use:
response.set_cookie('character', json.dumps(dict(request.form.items())))
Remember
json.dumps()stands fordump string
Retrieval#
request.cookies.get('character')
Flash Messages#
from flask import flash
Use the user’s session, for that you need a secret_key
app.secret_key = 'KJHSADOABSLKDNknkjsfhoi768HDIu76'
Setting flash message inthe view
flash("Saved changes!")
Can set a category:
flash("Yay! you registered", "success")
Displaying messags in the template
<div class="wrap no-bottom messages">
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class="flashes">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
</div>
Remember flashes only happen once (persist till next time page is viewed)
Database#
Connecting to and disconnecting from the db responsibility.
We can run certain things before and after a request.
With decorators @app.before_request
Remember the function for
@app.after_requesttakes in a response object
The Global object#
g is a global object that gets passed around.
We can use to set up things we want available everywhere.
from flask import g
Then you can set the database:
@app.before_request
def before_request():
'''Connect to the database before each request'''
g.db = models.DATABASE
g.db.connect()
and close the database after:
@app.after_request
def after_request(response):
'''Close the database connection after each request'''
g.db.close()
return response
Run the app#
if __name__ == '__main__':
app.run(debug=DEBUG)
Login#
To get the login sorted you need the LoginManager
from flask_login import LoginManager
You also need to enable sessions so you need the secret_key
app.secret_key = 'djkh7dhYYGSHhhsbjdyd'
You need to create the login mnager and initilaise the app and give it the view t redirect anonymous users to:
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
Add the user_loader method:
@login_manager.user_loader
def load_user(userid):
try:
return models.User.get(models.User.id == userid)
except models.DoesNotExist:
return None
Forms#
Forms are all about validation and a bit about display
The defactor package is flask_wtf build on wtforms
pip install flask-wtf
Example form with validation
from flask_wtf import Form
from wtforms import StringField, PasswordField
from wtforms.validators import (
DataRequired, Email, Regexp, ValidationError
Length, EqualTo)
from models import User
def name_exists(form, field):
if User.select().where(User.username == field.data).exists():
raise ValidationError('Username already exists')
def email_exists(form, field):
if User.select().where(User.email == field.data).exists():
raise ValidationError('Email already exists')
class RegisterForm(Form):
username = StringField(
'Username',
validators=[
DataRequired(),
Regexp(
r'^[a-zA-Z0-9_]+$',
messsage=("Username should be one word; letters, "
"numbers or underscores only")
),
name_exists
]
)
email = StringField(
'Email',
validators=[
DataRequired(),
Email(),
email_exists
]
)
password = PasswordField(
'Password',
validators=[
DataRequired(),
Length(min=2),
EqualTo('password2', message='Passwords must match'),
]
)
password2 = PasswordField(
'Confirm Password',
validators=[
DataRequired()
]
)
Handling forms in the view#
@app.route('/register', methods=('GET', 'POST',))
def register():
form = forms.RegisterForm()
if form.validate_on_submit():
flash("Yay! you registered", "success")
models.User.create_user(
username=form.username.data,
email=form.email.data,
password=form.password.data
)
return redirect(url_for('index'))
return render_template('register.html', form=form)
Showing the view#
<form method="POST" action="" class="form">
{{ form.hidden_tag() }}
{% for field in form %}
<div class="field">
{% if field.errors %}
{% for error in field.errors %}
<div class="notification error">{{ error }}</div>
{% endfor %}
{% endif %}
{{ field(placeholder=field.label.text) }}
</div>
</form>
Macros#
Macros are pieces of reusabel tremplate code
Create the macro in templates/macros.html:
{% macro render_field(field) %}
<div class="field">
{% if field.errors %}
{% for error in field.errors %}
<div class="notification error">{{ error }}</div>
{% endfor %}
{% endif %}
{{ field(placeholder=field.label.text) }}
</div>
{% endmacro %}
Then use the macro with:
{% from 'macros.html' import render_field %}
<form method="POST" action="" class="form">
{{ form.hidden_tag() }}
{% for field in form %}
{{ render_field(field) }}
{% endfor %}
</form>
Template Layout#
Setting variables for flash messages:
{% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %}
{% for category, message in messages %}
<div class="notification {{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
Checking logged in:
<!-- Log in/Log out -->
{% if current_user.is_authenticated() %}
<a href="{{ url_for('logout') }}" class="icon-power" title="Log out"></a>
{% else %}
<a href="{{ url_for('login') }}" class="icon-power" title="Log in"></a>
<a href="{{ url_for('register') }}" class="icon-profile" title="Register"></a>
{% endif %}
Returning a 404#
To return a 404 we need abort
from flask import abort
Usage:
abort(404)
Setting a custom404 page#
@app.errorhandler(404)
def not_found(error):
return render_template('404.html'), 404