基于 flask 的 CRUD 操作

个人拙见,web 后端就是一个对数据库进行 CRUD (增删改查)的操作过程。
本章内容就是基于 flask 这个前后端一体化的架构,使用 flask-wtf 插件,打通前后端的联系,来讲解一下 CRUD 操作。

配置 flask 项目

参照 你应该会玩儿 flask的前几章教程,使用大型项目的结构即蓝图模式来配置项目。

项目结构如下:

flask-wtf-crud/
|-- env/
    |-- <python虚拟环境>
|-- app/ <项目的模块名称>
    |-- crud/ <前端蓝图>
        |-- __init__.py
        |-- views.py <路由和视图函数文件>
        |-- forms.py <表单类文件, wtforms插件必须项>
        |-- templates <HTML模板>
            |-- static <静态文件夹>
    |-- XXXXXX/ <其它蓝图>
    |-- __init__.py
    |-- models.py <数据库模型文件>
|-- migrations/ <数据库表关系文件夹,Flask-Migrate迁移数据库时使用>
|-- config.py <项目的配置文件>
|-- manage.py <用于启动程序以及其它程序任务>

对于 crud 蓝图的配置,不再赘述,如果想了解,可以查看源码

作品内容:

image

image

配置数据库示例

创建一个 User 数据库字段。

# -*- coding:utf-8 -*-
__author__ = '东方鹗'
__blog__ = u'http://www.os373.cn'

from . import db


class User(db.Model):
    '''Example for crud
    以用户为例,来展示 CRUD 操作!
    '''
    __tablename__ = 'crud'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), unique=True, index=True)
    email = db.Column(db.String(64), unique=True, index=True)
    status = db.Column(db.Boolean, default=False)
    role = db.Column(db.Boolean, default=False)

    def __repr__(self):
        return '<User %r>' % self.username

创建视图函数 views.py

# -*- coding:utf-8 -*-
__author__ = '东方鹗'
__blog__ = u'http://www.os373.cn'

from flask import render_template, redirect, request, current_app, url_for, flash
from . import crud
from ..models import User
from .forms import AddUserForm, DeleteUserForm, EditUserForm
from ..import db

@crud.route('/basic', methods=['GET', 'POST'])
def basic():
    add_user_form = AddUserForm(prefix='add_user')
    delete_user_form = DeleteUserForm(prefix='delete_user')
    if add_user_form.validate_on_submit():
        if add_user_form.role.data == u'True':
            role = True
        else:
            role = False
        if add_user_form.status.data == u'True':
            status = True
        else:
            status = False
        u = User(username=add_user_form.username.data.strip(), email=add_user_form.email.data.strip(),
                 role=role, status=status)
        db.session.add(u)
        flash({'success': u'添加用户<%s>成功!' % add_user_form.username.data.strip()})
    if delete_user_form.validate_on_submit():
        u = User.query.get_or_404(int(delete_user_form.user_id.data.strip()))
        db.session.delete(u)
        flash({'success': u'删除用户<%s>成功!' % u.username})

    users = User.query.all()


    return render_template('basic.html', users=users, addUserForm=add_user_form,
                           deleteUserForm=delete_user_form)

@crud.route('/basic-edit/<user_id>', methods=['GET', 'POST'])
def user_edit(user_id):
    user = User.query.get_or_404(user_id)
    edit_user_form = EditUserForm(prefix='edit_user', obj=user)
    if edit_user_form.validate_on_submit():
        user.username = edit_user_form.username.data.strip()
        user.email = edit_user_form.email.data.strip()
        if edit_user_form.role.data == u'True':
            user.role = True
        else:
            user.role = False
        if edit_user_form.status.data == u'True':
            user.status = True
        else:
            user.status = False
        flash({'success': u'用户资料已修改成功!'})
        return redirect(url_for('.basic'))


    return render_template('edit_basic.html', editUserForm=edit_user_form, user=user)

知识点:

  1. 视图函数中实现了增加功能和删除功能。
  2. 多个删除功能也只是需要一个 form 类表单来实现,主要的难点在于前端设计。
  3. 实现了修改功能的视图函数,初看是一个简单的单 form 表单提交功能而已,但是,请记住这行代码edit_user_form = EditUserForm(prefix='edit_user', obj=user),特别是obj=user,其中 user 是 User 数据库类的实例,这样的功能将实现修改功能的模板能够显示出原有的实例的内容
    image

创建 forms.py 表单类

# -*- coding:utf-8 -*-
__author__ = '东方鹗'
__blog__ = u'http://www.os373.cn'

from flask_wtf import Form
from wtforms import StringField, SelectField, SubmitField
from wtforms.validators import DataRequired, Length, Email, Regexp
from wtforms import ValidationError
from ..models import User


class AddUserForm(Form):
    username = StringField(u'用户名', validators=[DataRequired(), Length(1, 64, message=u'姓名长度要在1和64之间'),
                       Regexp(ur'^[\u4E00-\u9FFF]+$', flags=0, message=u'用户名必须为中文')])
    email = StringField(u'邮箱', validators=[DataRequired(), Length(6, 64, message=u'邮件长度要在6和64之间'),
                        Email(message=u'邮件格式不正确!')])
    role = SelectField(u'权限', choices=[(u'True', u'管理员'), (u'False', u'一般用户') ])
    status = SelectField(u'状态', choices=[(u'True', u'正常'), (u'False', u'注销') ])
    submit = SubmitField(u'添加用户')

    def validate_username(self, field):
        if User.query.filter_by(username=field.data).first():
            raise ValidationError(u'用户名已被注册!')

    def validate_email(self, field):
        if User.query.filter_by(email=field.data).first():
            raise ValidationError(u'邮箱已被注册!')


class DeleteUserForm(Form):
    user_id = StringField()


class EditUserForm(Form):
    username = StringField(u'用户名', validators=[DataRequired(), Length(1, 64, message=u'姓名长度要在1和64之间'),
                       Regexp(ur'^[\u4E00-\u9FFF]+$', flags=0, message=u'用户名必须为中文')])
    email = StringField(u'邮箱', validators=[DataRequired(), Length(6, 64, message=u'邮件长度要在6和64之间'),
                        Email(message=u'邮件格式不正确!')])
    role = SelectField(u'权限', choices=[(u'True', u'管理员'), (u'False', u'一般用户') ])
    status = SelectField(u'状态', choices=[(u'True', u'正常'), (u'False', u'注销')])
    submit = SubmitField(u'修改用户')

知识点:

  1. 进行了表单的验证,查看 flask-wtf 官方文档即可。
  2. 实现了用户名和邮箱的唯一性验证。此处留一个彩蛋,在模板中,我没有实现唯一性的提示功能,你可以自己添加,欢迎留言。

创建前段模板

首先创建 basic.html 加载 bootstrap 前端框架,不再赘述。

basic.html 模板内容

{% extends 'common/base.html' %}<!-- 加载基础模板,主要是bootstrap 框架及一些个性化的静态文件-->
{% block content %}
{% include 'common/alert.html' %}<!-- 加载提示模板-->
<h3 class="page-header"> CRUD 基本示例</h3>
<table class ="table table-hover">
    <thead>
        <tr>
            <th>序号</th>
            <th>用户名</th>
            <th>邮箱</th>
            <th>权限</th>
            <th>状态</th>
            <th>
                <button type="button" class="btn btn-primary pull-right" data-toggle="modal" data-target=".bs-example-modal-sm">增加</button>
            </th>
        </tr>
    </thead>
    <tbody class="small">
        <tr>
            {% for user in users %}
                <tr>
                    <th scope="row">{{ loop.index }}</th>
                    <td>{{ user.username}}</td>
                    <td>{{ user.email}}</td>                    
                    {% if user.role %}
                        <td>管理员</td>
                    {% else %}
                        <td>一般用户</td>
                    {% endif %}
                    {% if user.status %}
                        <td>正常</td>
                    {% else %}
                        <td>注销</td>
                    {% endif %}
                    <td><a href="{{ url_for('.user_edit', user_id=user.id) }}"> 修改</a><!-- 修改功能需跳转到另外一个页面 --> |
                        <a href="javascript:delete_user_{{ user.id }}()">删除</a>
                        <form method="post" role="form" id="delete_user_{{ user.id }}"><!-- 实现了删除功能的 form-->
                            {{ deleteUserForm.hidden_tag() }}
                            {{ deleteUserForm.user_id(class="hidden", value=user.id) }}
                        </form><!-- 实现了删除功能的 form 结束-->
                        <script type="text/javascript">
                           function delete_user_{{ user.id }}(){
                               $("#delete_user_{{ user.id }}").submit() ;
                           }
                        </script><!-- 删除 功能用到一些 js 知识 和 jinja2 知识 -->
                    </td>
                </tr>
                {% endfor %}
        </tr>
    </tbody>
</table>    
<div class="modal fade bs-example-modal-sm" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel">
    <div class="modal-dialog modal-sm" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                <h4 class="modal-title" id="gridSystemModalLabel">增加信息</h4>
            </div>
            <div class="modal-body">
                <form method="post" role="form"><!-- 实现了增加功能的 form-->
                    {{ addUserForm.hidden_tag() }}                    
                    <div class="input-group">
                        <span class="input-group-addon"><i class="glyphicon glyphicon-user"></i> </span>
                        {{ addUserForm.username(class="form-control", placeholder="用户名",required="", autofocus="") }}
                    </div>
                    <div class="input-group">
                        <span class="input-group-addon"><i class="glyphicon glyphicon-envelope"></i> </span>
                        {{ addUserForm.email(class="form-control", placeholder="邮 箱", required="") }}
                    </div>
                    <div class="input-group">
                        <span class="input-group-addon"><i class="glyphicon glyphicon-briefcase"></i> </span>
                        {{ addUserForm.role(class="form-control", required="") }}
                    </div>
                    <div class="input-group">
                        <span class="input-group-addon"><i class="glyphicon glyphicon-tree-deciduous"></i> </span>
                        {{ addUserForm.status(class="form-control", required="") }}
                    </div>
                    <div class="modal-footer">
                        <input class="btn btn-default" type="reset" value="重 置">
                        {{ addUserForm.submit(class="btn btn-primary") }}
                    </div>
                </form><!-- 实现了增加功能的 form 结束-->
            </div>
        </div>
    </div>
</div>
{% endblock %}

知识点

  1. 一个页面里有多个 form,包括 modal 里实现的增加功能的 form 和 实现了删除功能的 form。
  2. 实现了删除功能的 form 并不是单独的存在,二是通过 jinja2 的循环功能,生成了不同 id 的具有删除功能的 form。
  3. 修改功能是通过另外一个单独的页面实现。

edit_basic.html 模板内容

{% extends 'common/base.html' %}
{% block content %}
<div class="row">
    <div class="col-xs-12 col-sm-offset-2">
        <form method="post" role="form">
            {{ editUserForm.hidden_tag() }}
            <div class="col-xs-12 col-sm-8">              
                <div class="input-group">
                    <span class="input-group-addon"><i class="glyphicon glyphicon-user"></i> </span>
                    {{ editUserForm.username(class="form-control", placeholder="用户名",required="", autofocus="") }}
                </div>
                <div class="input-group">
                    <span class="input-group-addon"><i class="glyphicon glyphicon-envelope"></i> </span>
                    {{ editUserForm.email(class="form-control", placeholder="邮 箱", required="") }}
                </div>
                <div class="input-group">
                    <span class="input-group-addon"><i class="glyphicon glyphicon-briefcase"></i> </span>
                    {{ editUserForm.role(class="form-control", required="") }}
                </div>
                <div class="input-group">
                    <span class="input-group-addon"><i class="glyphicon glyphicon-tree-deciduous"></i> </span>
                    {{ editUserForm.status(class="form-control", required="") }}
                </div>
                <div class="modal-footer">
                    <input class="btn btn-default" type="reset" value="重 置">
                    {{ editUserForm.submit(class="btn btn-primary") }}
                </div>
            </div>
        </form>
    </div>
</div>


{% endblock %}

知识点: 无。参照 flask-wtf 的官方文档即可。

源码下载地址:https://github.com/eastossifrage/flask-wtf-crud/archive/basic.zip

查看原文

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容

  • 22年12月更新:个人网站关停,如果仍旧对旧教程有兴趣参考 Github 的markdown内容[https://...
    tangyefei阅读 35,174评论 22 257
  • 本文首发于Gevin的博客 原文链接:Flask 入门指南 未经 Gevin 授权,禁止转载 1. 初识Flask...
    Gevin阅读 16,765评论 10 237
  • 第二部分 Blog例子 第八章 用户验证 大部分程序需要追踪用户身份。当用户连接到程序,通过一系列步骤使自己的身份...
    易木成华阅读 1,288评论 0 4
  • 第4章 Web表单 我们在第二章介绍过请求对象,它包含有客户端请求的全部信息。尤其是,可以通过request.fo...
    易木成华阅读 1,023评论 0 1