Flask表单processing and datalibrarymodel

overview

in WebapplicationDevelopmentin, 表单processing and datalibrary交互 is corefunctions. Flaskproviding了flexible scaleecosystem, 让我们可以easilyimplementation这些functions. 本章节将介绍such as何usingWTFormsprocessing表单, 以及such as何usingSQLAlchemyfordatalibraryoperation.

1. Flask-WTF表单processing

Flask-WTF is Flask 一个scale, 它集成了WTFormslibrary, providing了表单verification, CSRF保护etc.functions.

1.1 installationFlask-WTF

pip install flask-wtf

1.2 creation表单class

我们可以throughcreation一个inheritance自FlaskForm class来定义表单:

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, TextAreaField
from wtforms.validators import DataRequired, Length, Email, EqualTo

class RegistrationForm(FlaskForm):
    username = StringField('user名', validators=[DataRequired(), Length(min=2, max=20)])
    email = StringField('邮箱', validators=[DataRequired(), Email()])
    password = PasswordField('password', validators=[DataRequired()])
    confirm_password = PasswordField('确认password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('register')

class LoginForm(FlaskForm):
    email = StringField('邮箱', validators=[DataRequired(), Email()])
    password = PasswordField('password', validators=[DataRequired()])
    submit = SubmitField('login')

1.3 in 视graphinprocessing表单

in 视graphfunctionin, 我们可以instance化表单object并processing表单submitting:

from flask import render_template, flash, redirect, url_for
from app import app
from app.forms import RegistrationForm, LoginForm

@app.route("/register", methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        flash(f'账号creation成功!欢迎 {form.username.data}', 'success')
        return redirect(url_for('home'))
    return render_template('register.html', title='register', form=form)

@app.route("/login", methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        # 这里应该添加practical userverification逻辑
        if form.email.data == 'admin@example.com' and form.password.data == 'password':
            flash('login成功!', 'success')
            return redirect(url_for('home'))
        else:
            flash('login失败, 请check邮箱 and password', 'danger')
    return render_template('login.html', title='login', form=form)

1.4 in 模板in渲染表单

in HTML模板in, 我们可以usingFlask-WTFproviding 辅助function来渲染表单:

<form method="POST" action="">
    {{ form.hidden_tag() }}
    <fieldset class="form-group">
        <legend class="border-bottom mb-4">register</legend>
        <div class="form-group">
            {{ form.username.label(class="form-control-label") }}
            {% if form.username.errors %}
                {{ form.username(class="form-control form-control-lg is-invalid") }}
                <div class="invalid-feedback">
                    {% for error in form.username.errors %}
                        <span>{{ error }}</span>
                    {% endfor %}
                </div>
            {% else %}
                {{ form.username(class="form-control form-control-lg") }}
            {% endif %}
        </div>
        <div class="form-group">
            {{ form.email.label(class="form-control-label") }}
            {% if form.email.errors %}
                {{ form.email(class="form-control form-control-lg is-invalid") }}
                <div class="invalid-feedback">
                    {% for error in form.email.errors %}
                        <span>{{ error }}</span>
                    {% endfor %}
                </div>
            {% else %}
                {{ form.email(class="form-control form-control-lg") }}
            {% endif %}
        </div>
        <div class="form-group">
            {{ form.password.label(class="form-control-label") }}
            {% if form.password.errors %}
                {{ form.password(class="form-control form-control-lg is-invalid") }}
                <div class="invalid-feedback">
                    {% for error in form.password.errors %}
                        <span>{{ error }}</span>
                    {% endfor %}
                </div>
            {% else %}
                {{ form.password(class="form-control form-control-lg") }}
            {% endif %}
        </div>
        <div class="form-group">
            {{ form.confirm_password.label(class="form-control-label") }}
            {% if form.confirm_password.errors %}
                {{ form.confirm_password(class="form-control form-control-lg is-invalid") }}
                <div class="invalid-feedback">
                    {% for error in form.confirm_password.errors %}
                        <span>{{ error }}</span>
                    {% endfor %}
                </div>
            {% else %}
                {{ form.confirm_password(class="form-control form-control-lg") }}
            {% endif %}
        </div>
    </fieldset>
    <div class="form-group mt-4">
        {{ form.submit(class="btn btn-primary btn-lg") }}
    </div>
</form>

2. Flask-SQLAlchemydatalibraryoperation

SQLAlchemy is Pythonin最流行 ORM (objectrelationshipsmap) library之一. Flask-SQLAlchemy is Flask 一个scale, 它简化了SQLAlchemy in Flaskapplicationin using.

2.1 installationFlask-SQLAlchemy

pip install flask-sqlalchemy

2.2 configurationdatalibrary

in Flaskapplicationin, 我们需要configurationdatalibraryURI:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'  # SQLitedatalibrary
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

2.3 creationdatalibrarymodel

我们可以throughcreation一个inheritance自db.Model class来定义datalibrarymodel:

from datetime import datetime
from app import db

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
    password = db.Column(db.String(60), nullable=False)
    posts = db.relationship('Post', backref='author', lazy=True)

    def __repr__(self):
        return f"<User {self.username}>"

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

    def __repr__(self):
        return f"<Post {self.title}>"

2.4 creationdatalibrary

in Flask shellin, 我们可以creationdatalibrary and 表:

>>> from app import db
>>> db.create_all()

2.5 datalibraryoperation

我们可以usingSQLAlchemyproviding API来执行datalibraryoperation:

添加data

from app import db, User, Post

# creationuser
user = User(username='john', email='john@example.com', password='password')
db.session.add(user)
db.session.submitting()

# creation文章
post = Post(title='First Post', content='Hello World!', author=user)
db.session.add(post)
db.session.submitting()

querydata

# query所 has user
users = User.query.all()

# query单个user
user = User.query.filter_by(username='john').first()

# queryuser 所 has 文章
posts = user.posts

# query所 has 文章
posts = Post.query.all()

updatedata

user = User.query.filter_by(username='john').first()
user.username = 'john_doe'
db.session.submitting()

deletedata

user = User.query.filter_by(username='john_doe').first()
db.session.delete(user)
db.session.submitting()

3. datalibrarymigration

in Development过程in, 我们经常需要modifydatalibrarymodel. Flask-Migrate is a scale, 它providing了datalibrarymigrationfunctions, 让我们可以security地updatedatalibrarystructure.

3.1 installationFlask-Migrate

pip install flask-migrate

3.2 初始化migration

in applicationinconfigurationFlask-Migrate:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'

.db = SQLAlchemy(app)
migrate = Migrate(app, db)

3.3 creationmigration仓library

flask db init

3.4 creationmigration脚本

flask db migrate -m "Initial migration"

3.5 applicationmigration

flask db upgrade

3.6 rollbackmigration

flask db downgrade

4. 综合example: 博客application

让我们creation一个 simple 博客application, 综合运用表单processing and datalibraryoperation:

4.1 projectstructure

blog_app/
├── app/
│   ├── __init__.py
│   ├── forms.py
│   ├── models.py
│   ├── routes.py
│   ├── static/
│   └── templates/
│       ├── base.html
│       ├── home.html
│       ├── login.html
│       ├── register.html
│       └── post.html
└── run.py

4.2 implementation要点

  1. usingFlask-WTFcreationregister, login and 文章表单
  2. usingSQLAlchemy定义User and Postmodel
  3. implementationuserregister, loginfunctions
  4. implementation文章 creation, 查看functions
  5. using模板inheritance保持页面风格一致

4.3 runapplication

flask run

5. best practices

  • 始终usingFlask-WTF form.hidden_tag()来package含CSRFtoken
  • for userpasswordfor哈希processing, 不要store明文password (可以usingFlask-Bcrypt)
  • usingdatalibrarymigration来managementdatalibrarystructure变更
  • in produceenvironmentinusing强password and security datalibraryconfiguration
  • usingSQLAlchemy queryoptimizationfunctions, such aslazy='dynamic'来processing big 量data

summarized

本章节介绍了Flaskin 表单processing and datalibraryoperation, including:

  • usingFlask-WTFcreation and verification表单
  • usingFlask-SQLAlchemyfordatalibraryoperation
  • usingFlask-Migratefordatalibrarymigration
  • 综合example: 博客application

这些functions is 构建现代Webapplication Basics, Master它们将helping你Development出functions完整, security reliable Flaskapplication.

codeexample

以 under is a 完整 Flask表单 and datalibraryexample:

from flask import Flask, render_template, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, TextAreaField
from wtforms.validators import DataRequired, Length, Email, EqualTo
from datetime import datetime

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

# datalibrarymodel
class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(60), nullable=False)
    posts = db.relationship('Post', backref='author', lazy=True)

    def __repr__(self):
        return f"<User {self.username}>"

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

    def __repr__(self):
        return f"<Post {self.title}>"

# 表单class
class RegistrationForm(FlaskForm):
    username = StringField('user名', validators=[DataRequired(), Length(min=2, max=20)])
    email = StringField('邮箱', validators=[DataRequired(), Email()])
    password = PasswordField('password', validators=[DataRequired()])
    confirm_password = PasswordField('确认password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('register')

class LoginForm(FlaskForm):
    email = StringField('邮箱', validators=[DataRequired(), Email()])
    password = PasswordField('password', validators=[DataRequired()])
    submit = SubmitField('login')

class PostForm(FlaskForm):
    title = StringField('标题', validators=[DataRequired()])
    content = TextAreaField(' in 容', validators=[DataRequired()])
    submit = SubmitField('release')

# routing
@app.route("/")
def home():
    posts = Post.query.all()
    return render_template('home.html', posts=posts)

@app.route("/register", methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        user = User(username=form.username.data, email=form.email.data, password=form.password.data)
        db.session.add(user)
        db.session.submitting()
        flash('register成功!', 'success')
        return redirect(url_for('home'))
    return render_template('register.html', form=form)

@app.route("/login", methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user and user.password == form.password.data:
            flash('login成功!', 'success')
            return redirect(url_for('home'))
        else:
            flash('login失败, 请check邮箱 and password', 'danger')
    return render_template('login.html', form=form)

@app.route("/post/new", methods=['GET', 'POST'])
def new_post():
    form = PostForm()
    if form.validate_on_submit():
        # 这里应该获取当 before loginuser, 简化起见using第一个user
        user = User.query.first()
        post = Post(title=form.title.data, content=form.content.data, author=user)
        db.session.add(post)
        db.session.submitting()
        flash('文章release成功!', 'success')
        return redirect(url_for('home'))
    return render_template('create_post.html', form=form)

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

练习题

  1. creation一个Flaskapplication, usingFlask-WTFimplementation一个联系表单, including姓名, 邮箱 and 留言字段
  2. usingSQLAlchemycreation一个 simple 博客model, includingUser and Post表
  3. implementationuserregister and loginfunctions
  4. 添加文章creation, 编辑 and deletefunctions
  5. usingFlask-Migrateimplementationdatalibrarymigration