python-flask(七)基于Flask的任务清单管理系统

1.目标

本项目将学习 Mariadb 作为数据库后端,Bootstrap 作为前端的技术栈,并实现一个清单应用。从中我们可以学习 Flask Web 应用框架,及 Mariadb 关系型数据库和 BootStrap web开发框架。

2.项目介绍

本应用修改自 TodoMVC 的 todo list 应用,使用 Mariadb 作为数据库后端,Bootstrap 作为前端的 Flask 应用。先给它起个好听的名字吧,方便之后称呼。

todo list => (自定义,随便起名称) => todoest

就像一般的 todo list 应用一样,todoest 实现了以下功能:

  • 管理数据库连接
  • 列出所有的 todo 项
  • 创建新的 todo
  • 检索单个 todo
  • 编辑单个 todo 或将其标记为已完成
  • 删除单个 todo

3.项目效果

在这里插入图片描述

4.技术分析

  • 为什么选择Flask?

    Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。Flask使用 BSD 授权。
    Flask也被称为 “microframework” ,因为它使用简单的核心,用 extension 增加其他功能。Flask没有默认使用的数据库、窗体验证工具。
    因此Flask是一个使用Python编写的轻量级Web应用框架。轻巧易扩展,而且够主流,有问题不怕找不到人问,最适合 todoest 这种轻应用了。

  • 为什么选择Mariadb?

    MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可 MariaDB的目的是完全兼容MySQL,包括API和命令行,使之能轻松成为MySQL的代替品。MariaDB虽然被视为MySQL数据库的替代品,但它在扩展功能、存储引擎以及一些新的功能改进方面都强过MySQL。而且从MySQL迁移到MariaDB也是非常简单的.

  • 为什么选择Bootstrap?

    Bootstrap是美国Twitter公司的设计师Mark Otto和Jacob Thornton合作基于HTML、CSS、JavaScript 开发的简洁、直观、强悍的前端开发框架,使得 Web 开发更加快捷。

    Bootstrap中包含了丰富的Web组件,根据这些组件,可以快速的搭建一个漂亮、功能完备的网站。其中包括以下组件:下拉菜单、按钮组、按钮下拉菜单、导航、导航条、路径导航、分页、排版、缩略图、警告对话框、进度条、媒体对象等

5.设计结构

1.架构

在这里插入图片描述

首先在mysql里新建数据库

 create database Todo DEFAULT CHARSET utf8

config.py

SQLALCHEMY_DATABASE_URI = 'mysql://root:redhat@localhost/Todo'
SQLALCHEMY_TRACK_MODIFICATIONS = True

manage.py

from app import  manager
if __name__ == '__main__':
    manager.run()

init.py

from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
import pymysql

pymysql.install_as_MySQLdb()


app = Flask(__name__)
app.config.from_pyfile('../config.py')
db = SQLAlchemy(app)
manager = Manager(app)

models.py
数据库:
存储三类 用户/Todo/Todo的类型

"""
# 存储数据库相关的操作
"""
from app import db
from werkzeug.security import  generate_password_hash, check_password_hash
from datetime import  datetime



# 用户和任务的关系: 一对多, 用户是一, 任务是多,
# 用户和分类的关系:
class User(db.Model):
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    username = db.Column(db.String(20), unique=True)
    password_hash = db.Column(db.String(100), nullable=False)
    email = db.Column(db.String(30), unique=True)
    add_time = db.Column(db.DateTime, default=datetime.now()) # 账户创建时间
    # 1).  User添加属性todos; 2). Todo添加属性user;
    todos = db.relationship('Todo', backref="user")
    categories = db.relationship('Category', backref='user')

    @property
    def password(self):
        """u.password"""
        raise  AttributeError("密码属性不可以读取")

    @password.setter
    def password(self, password):
        """u.password = xxxxx """
        self.password_hash = generate_password_hash(password)


    def verify_password(self, password):
        """验证密码是否正确"""
        return  check_password_hash(self.password_hash, password)

    def __repr__(self):
        return  "<User %s>" %(self.username)

# 任务和分类的关系: 一对多
# 分类是一, 任务是多, 外键写在多的一端
class Todo(db.Model):
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    content = db.Column(db.String(100)) # 任务内容
    status = db.Column(db.Boolean, default=False) # 任务的状态
    add_time = db.Column(db.DateTime, default=datetime.now())  # 任务创建时间
    # 任务的类型,关联另外一个表的id
    category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
    # 任务所属用户;
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

    def __repr__(self):
        return  "<Todo %s>" %(self.content[:6])


class Category(db.Model):
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    name = db.Column(db.String(20), unique=True)
    add_time = db.Column(db.DateTime, default=datetime.now())  # 任务创建时间
    # 1). Category添加一个属性todos, 2). Todo添加属性category;
    todos = db.relationship('Todo', backref='category')

    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

    def __repr__(self):
        return  "<Category %s>" %(self.name)

# 初始化,用于测试数据库,实际代码不用写,因为把初始化数据库写在models.py文件中更为方便
def init():
    db.drop_all()
    db.create_all()
    u = User(username='admin',email='[email protected]')
    u.password='admin'
    db.session.add(u)
    db.session.commit()
    print('用户%s创建成功' %(u.username))
    c = Category(name='学习',user_id=1)
    db.session.add(c)
    print('分类%s创建成功' %(c.name))

    t = Todo(content='学习flask',category_id=1,user_id=1)
    db.session.add(t)
    print('任务%s添加成功' %(t.content))

    db.session.commit()


if __name__ == '__main__':
    init()

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

view.py–视图函数(先实现登陆注册页面,再写出todo页面框架)

from app import app, db

# 网站首页
from app.forms import RegisterForm, LoginForm
from flask import render_template, flash, redirect, url_for, session

from app.models import User


@app.route('/')
def index():
    return 'index'


# 注册页面
@app.route('/register/', methods=['POST', 'GET'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        # 1. 从前端获取用户输入的值;
        email = form.email.data
        username = form.username.data
        password = form.password.data

        # 2. 判断用户是否已经存在? 如果返回位None,说明可以注册;
        u = User.query.filter_by(username=username).first()
        if u:
            flash("用户%s已经存在" % (u.username))
            return redirect(url_for('register'))
        else:
            u = User(username=username, email=email)
            u.password = password
            db.session.add(u)
            db.session.commit()
            flash("注册用户%s成功" % (u.username))
            return redirect(url_for('login'))
    return render_template('register.html',
                           form=form)


# 登录页面
@app.route('/login/', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        username = form.username.data
        password = form.password.data

        # 1. 判断用户是否存在?
        u = User.query.filter_by(username=username).first()
        if u and u.verify_password( password):
            session['user_id'] = u.id
            session['user'] = u.username
            flash("登录成功!")
            return  redirect(url_for('index'))
        else:
            flash("用户名或者密码错误!")
            return  redirect(url_for('login'))

    return  render_template('login.html',
                            form=form)

@app.route('/logout/')
def logout():
    session.pop('user_id', None)
    session.pop('user', None)

    return  redirect(url_for('login'))


# 添加任务
@app.route('/todo/add/')
def todo_add():
    return 'todo_add'


# 编辑任务
@app.route('/todo/edit/<int:id>/')
def todo_modify(id):
    return "todo_modify %s" % (id)


# 删除任务
@app.route('/todo/delete/<int:id>/')
def todo_delete(id):
    return "todo_delete"


# 查看任务
@app.route('/todo/list/')
@app.route('/todo/list/<int:page>')
def list(page):
    return "list"


# 修改任务状态为完成
@app.route('/todo/done/<int:id>/')
def done(id):
    return 'done'


# 修改任务状态为未完成
@app.route('/todo/undo/<int:id>')
def undo(id):
    return 'undo'

forms.py–表单文件

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

# 注册表单
from app.models import User


class RegisterForm(FlaskForm):
    email = StringField(
        label="邮箱",
        validators=[
            DataRequired(),
            Email(),

        ]
    )
    username = StringField(
        label="用户名",
        validators=[
            DataRequired(),

        ],
    )
    password = PasswordField(
        label='密码',
        validators=[
            DataRequired(),
            Length(6, 12, "密码必须是6-12位")
        ]
    )

    repassword = PasswordField(
        label='确认密码',
        validators=[
            EqualTo("password", "密码与确认密码不一致")
        ]
    )

    submit = SubmitField(
        label="注册"
    )

    # *****************************************************
    # 默认情况下validate_username会验证用户名是否正确, 验证的规则, 写在函数里面
    def validate_username(self, field):
        # filed.data ==== username表单提交的内容
        u = User.query.filter_by(username=field.data).first()
        if u:
            raise ValidationError("用户名%s已经注册" % (u.username))

    def validate_email(self, filed):
        u = User.query.filter_by(email=filed.data).first()
        if u:
            raise ValidationError("邮箱%s已经注册" % (u.email))


# 登录表单
class LoginForm(FlaskForm):
    username = StringField(
        label="用户名",
        validators=[
            DataRequired(),

        ],
    )
    password = PasswordField(
        label='密码',
        validators=[
            DataRequired(),
            # Length(6, 12, "密码必须是6-12位")
        ]
    )
    submit = SubmitField(
        label="登录"
    )

css样式:
/static/css/main.css 存储css样式 用于继承

.navbar {
    font-size: 130%;
    background: whitesmoke;
    margin-top: 10px;
    padding-top: 5px;
    box-shadow: 2px 2px 2px 2px lightgray;
    height: 60px;
}

templates
base.html --基模板(copy之前的flask注册页面
这里继承的是bootstrap的base模板(之前讲过继承bootstrap框架

{% extends 'bootstrap/base.html' %}

{% block styles %}
    {#  先继承父类的css样式导入   #}
    {{ super() }}
    <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
{% endblock %}

{% block navbar %}
    <nav class="navbar navbar-default">
        <div class="container-fluid">
            <!-- Brand and toggle get grouped for better mobile display -->
            <div class="navbar-header">
                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                        data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="#">任务管理</a>
            </div>

            <!-- Collect the nav links, forms, and other content for toggling -->
            <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                <ul class="nav navbar-nav">
                    <li class="active"><a href="#">主页 <span class="sr-only">(current)</span></a></li>
                    <li><a href="{{ url_for('list') }}">用户信息</a></li>
                    <li><a href="#">监控</a></li>
                    <li><a href="#">新闻</a></li>
                    <li><a href="#">音乐</a></li>
                    <li><a href="#">电影</a></li>

                </ul>

                <ul class="nav navbar-nav navbar-right">
                    {#       分类讨论:
                            1. 如果没有登录, 显示登录和注册按钮;
                            2. 如果登录成功, 显示用户名称和注销按钮

            #}
                    {% if not session.user %}
                        <li><a href="{{ url_for('login') }}">登录</a></li>
                        <li><a href="{{ url_for('register') }}">注册</a></li>
                    {% else %}
                        <li class="dropdown">
                            <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button"
                               aria-haspopup="true"
                               aria-expanded="false">当前用户:{{ session.user }} <span class="caret"></span></a>
                            <ul class="dropdown-menu">
                                <li role="separator" class="divider"></li>
                                <li><a href="{{ url_for('logout') }}">注销</a></li>
                            </ul>
                        </li>

                    {% endif %}
                </ul>


            </div><!-- /.navbar-collapse -->
        </div><!-- /.container-fluid -->
    </nav>


    {#让每个页面都可以获取闪现信息闪现#}
    {% for item in get_flashed_messages() %}

        <div class="alert alert-warning alert-dismissible" role="alert">
            <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span
                    aria-hidden="true">&times;</span></button>
            {{ item }}
        </div>
    {% endfor %}
{% endblock %}

login.html – 登陆页面
wtf.quick_form(form) # 快速生成表单

{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block title %}登录页面{% endblock %}
{% block content %}
    <div class="container">
        <div class="col-lg-8 col-lg-offset-2">
            <div class="page-header">
                <h1>
                    登录
                    <small>
                        没有帐号
                        {#                <a href="/login/">登录</a>#}
                        <a href="{{ url_for('register') }}">注册</a>
                    </small>

                </h1>

            </div>

            {{ wtf.quick_form(form) }}
        </div>


    </div>
{% endblock %}

register.html --注册页面与login.html相似

{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block title %}注册页面{% endblock %}
{% block content %}
    <div class="container">
        <div class="col-lg-8 col-lg-offset-2">
            <div class="page-header">
                <h1>
                    注册
                    <small>
                        已有帐号
                        {#                <a href="/login/">登录</a>#}
                        <a href="{{ url_for('login') }}">登录</a>
                    </small>

                </h1>

            </div>

            {{ wtf.quick_form(form) }}
        </div>


    </div>
{% endblock %}

config.py

SQLALCHEMY_DATABASE_URI = 'mysql://root:redhat@localhost/Todo'
SQLALCHEMY_TRACK_MODIFICATIONS = True
SECRET_KEY = 'westos'

manage.py

from app import manager, db
from app.views import  *

# 添加数据库操作的命令信息;
from app.models import User, Category, Todo

@manager.command
def dbinit():
    """数据库初始化信息"""
    db.drop_all()
    db.create_all()
    u = User(username='admin', email="[email protected]")
    u.password = 'admin'
    db.session.add(u)
    db.session.commit()
    print("用户%s创建成功......." % (u.username))

    c = Category(name="学习", user_id=1)
    db.session.add(c)
    print("分类%s创建成功...." % (c.name))

    t = Todo(content="学习Flask", category_id=1, user_id=1)
    db.session.add(t)
    print("任务%s添加成功....." % (t.content))
    db.session.commit()


if __name__ == '__main__':
    manager.run()

在这里插入图片描述在这里插入图片描述

2.todo

1.添加任务


class AddTodoForm(FlaskForm):
    content = StringField(
        label='任务内容',
        validators=[DataRequired()]
    )
    category = SelectField(
        label='任务类型',
        coerce=int,  # 存的是id整形
        choices=[(item.id,item.name) for item in Category.query.all()]
    )
    submit = SubmitField(
        label='添加任务',

    )

在views.py视图函数里添加add视图函数

# 添加任务
@app.route('/todo/add/')
def todo_add():
    form = AddTodoForm()
    if form.validate_on_submit():
        pass
    return render_template('todo/add_todo.html',form=form)

在template 下新建todo目录,把login登陆的html复制过来简单修改

{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block title %}添加任务{% endblock %}
{% block content %}
    <div class="container">
        <div class="col-lg-8 col-lg-offset-2">
            <div class="page-header">
                <h1>
                    添加任务
                </h1>

            </div>

            {{ wtf.quick_form(form) }}
        </div>


    </div>
{% endblock %}

运行python manager.py runserver
在这里插入图片描述forms.py的@app.route(’/todo/add/’),post方法补充完整

@app.route('/todo/add/',methods=['GET','POST'])
def todo_add():
    form = AddTodoForm()
    if form.validate_on_submit():
        # 获取用户提交的内容
        content = form.content.data
        category_id = form.category.data

    #     添加到数据库中
        todo = Todo(content=content,
             category_id=category_id,
             user_id=session.get('user_id'))
        db.session.add(todo)
        db.session.commit()
        flash('添加任务成功')
        return redirect(url_for('todo_add'))
    return render_template('todo/add_todo.html',form=form)

重新运行 测试一下:
在这里插入图片描述
检查数据库是否保存
在这里插入图片描述

2.任务显示

填写任务显示的视图函数

# 查看任务
@app.route('/todo/list/')
@app.route('/todo/list/<int:page>')
def list(page=1):
    # 任务需要分页显示
    todoPageObj=    Todo.query.paginate(page,per_page=app.config['PER_PAGE'])
    return render_template('todo/list_todo.html',todoPageObj=todoPageObj)

list_todo.html

{% extends 'base.html' %}
{% block title %}
    任务显示
{% endblock %}

{% block content %}
    <div class="col-lg-8 col-lg-offset-2">
        <div class="page-header">
            <h1>任务显示</h1>

        </div>
    <table class="table table-hover">
        <tr>
            <td>任务内容</td>
            <td>任务状态</td>
            <td>任务分类</td>
            <td>所属用户</td>
        </tr>
        {% for todo in todoPageObj.items %}
        <tr>
            <td>{{ todo.content }}</td>
            <td>{{ todo.status }}</td>
            <td>{{ todo.category. name}}</td>
            <td>{{ todo.user.username}}</td>

        </tr>

    </table>
    </div>
{% endblock %}

在config中添加每页的页数per_page

SQLALCHEMY_DATABASE_URI = 'mysql://root:redhat@localhost/Todo'
SQLALCHEMY_TRACK_MODIFICATIONS = True
SECRET_KEY = 'westos'
PER_PAGE = 5

运行一下:
在这里插入图片描述
todo是一个可以交互的页面,所以我们需要加上按钮可以删除和编辑,以及对勾和叉叉
bootstrap按钮的css样式

任意选择几个按钮样式复制到任务列表的html代码里,按钮样式举例:
<!-- Standard button -->
<button type="button" class="btn btn-default">(默认样式)Default</button>

<!-- Provides extra visual weight and identifies the primary action in a set of buttons -->
<button type="button" class="btn btn-primary">(首选项)Primary</button>

<!-- Indicates a successful or positive action -->
<button type="button" class="btn btn-success">(成功)Success</button>

<!-- Contextual button for informational alert messages -->
<button type="button" class="btn btn-info">(一般信息)Info</button>

<!-- Indicates caution should be taken with this action -->
<button type="button" class="btn btn-warning">(警告)Warning</button>

<!-- Indicates a dangerous or potentially negative action -->
<button type="button" class="btn btn-danger">(危险)Danger</button>

<!-- Deemphasize a button by making it look like a link while maintaining button behavior -->
<button type="button" class="btn btn-link">(链接)Link</button>

list_todo.html

{% extends 'base.html' %}
{% block title %}
    任务显示
{% endblock %}

{% block content %}
    <div class="col-lg-8 col-lg-offset-2">
        <div class="page-header">
            <h1>任务显示</h1>

        </div>
    <table class="table table-hover">
        <tr>
            <td>任务内容</td>
            <td>任务状态</td>
            <td>任务分类</td>
            <td>所属用户</td>
        </tr>
        {% for todo in todoPageObj.items %}
        <tr>
            <td>{{ todo.content }}</td>
            <td>{{ todo.status }}</td>
            <td>{{ todo.category. name}}</td>
            <td>{{ todo.user.username}}</td>
            <td>
				{# 按钮#}
                <a href="#" class="btn btn-success" role="button">编辑</a>
                <a href="#" class="btn btn-danger" role="button">删除</a>

            </td>
        </tr>
        {% endfor %}
    </table>
    </div>
{% endblock %}

再网页上找对勾和叉叉的样式图标显示
在这里插入图片描述
在状态status这里添加判断:

{% extends 'base.html' %}
{% block title %}
    任务显示
{% endblock %}

{% block content %}
    <div class="col-lg-8 col-lg-offset-2">
        <div class="page-header">
            <h1>任务显示</h1>

        </div>
    <table class="table table-hover">
        <tr>
            <td>任务内容</td>
            <td>任务状态</td>
            <td>任务分类</td>
            <td>所属用户</td>
            <td>操作</td>
        </tr>
        {% for todo in todoPageObj.items %}
        <tr>
            <td>{{ todo.content }}</td>
            <td>{% if todo.status %}
            <span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
                {% else %}
                <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
            {% endif %}
            </td>
            <td>{{ todo.category. name}}</td>
            <td>{{ todo.user.username}}</td>
            <td>
{#                按钮#}
                <a href="#" class="btn btn-success" role="button">编辑</a>
                <a href="#" class="btn btn-danger" role="button">删除</a>

            </td>
        </tr>
        {% endfor %}
    </table>
    </div>
{% endblock %}

在这里插入图片描述
在页面上显示分页:
要让分页栏显示在表格下面,所以写在table标签后面

            {#        # Day36 templates/list.html#}

            <nav aria-label="Page navigation">
                <ul class="pagination">
                    {#
1. 上一页的显示url获取
    /list/2/ ===== url_for('list', todoPageObj.prev_num)

2. 上一页信息逻辑判断
    1). 判断是否有上一页信息;
    2). 如果有, 创建链接;
    3). 如果没有, 该链接设为不可点击的链接

3. 上一页显示使用的类属性和方法:
    1). dataObj.has_prev:
        判断用户是否有上一页?
        如果有,返回True; 如果没有,返回False;

    2).dataObj.prev_num:
        获取上一页的页数编号;
#}
                    {% if todoPageObj.has_prev %}
                        <li>

                            <a href="{{ url_for('list', page=todoPageObj.prev_num) }}" aria-label="Previous">
                                <span aria-hidden="true">&laquo;</span>
                            </a>
                        </li>
                    {% else %}
                        <li class="disabled">
                            <a href="#" aria-label="Previous">
                                <span aria-hidden="true">&laquo;</span>
                            </a>
                        </li>

                    {% endif %}


                    {#

详细页的显示
依次创建每个分页表框:
    1). 是否为none, 设置类名为diabled;
    2). 是否为当前页, 设置类名为active;
    3).其他,正常设置;
#}

                    {% for page in todoPageObj.iter_pages() %}
                        {% if page is none %}
                            <li class="disabled"><a href="#">......</a></li>
                        {% elif page == todoPageObj.page %}

                            <li class="active"><a href="{{ url_for('list', page=page) }}">{{ page }}</a></li>
                        {% else %}
                            <li><a href="{{ url_for('list', page=page) }}">{{ page }}</a></li>
                        {% endif %}
                    {% endfor %}


                    {#
1.下一页信息判断逻辑
    1). 判断是否有下一页信息;
    2). 如果有, 创建链接;
    3). 如果没有, 该链接设为不可点击的链接

2. 使用的方法:
    1). dataObj.has_next:
        判断用户是否有下一页?
        如果有,返回True; 如果没有,返回False;
    2).dataObj.next_num: 获取下一页的页数编号;
#}
                    {% if todoPageObj.has_next %}
                        <li>
                            <a href="{{ url_for('list', page=todoPageObj.next_num) }}" aria-label="Next">
                                <span aria-hidden="true">&raquo;</span>
                            </a>
                        </li>
                    {% else %}
                        <li class="disabled">
                            <a href="#" aria-label="Next">
                                <span aria-hidden="true">&raquo;</span>
                            </a>
                        </li>
                    {% endif %}

                </ul>
            </nav>

在这里插入图片描述
添加删除的视图函数

# 删除任务
@app.route('/todo/delete/<int:id>/')
def todo_delete(id):
    todo = Todo.query.filter_by(id=id).first()
    db.session.delete(todo)
    db.session.commit()
    return redirect(url_for('list'))

修改list_to.html里删除按钮,a标签里加上连接地址

  <a href="{{ url_for('todo_delete',id=todo.id) }}" class="btn btn-danger" role="button">删除</a>

测试一下删除按钮:
在这里插入图片描述

3.编辑任务清单

将任务内容和任务类型作为一个任务的基类,其他任务的类全继承于这个基类
forms.py

# 关于任务的基类
class TodoForm(FlaskForm):
    content = StringField(
        label="任务内容",
        validators=[
            DataRequired()
        ]
    )
    # 任务类型
    category = SelectField(
        label="任务类型",
        coerce=int,
        choices=[(item.id, item.name) for item in Category.query.all()]
    )
class AddTodoForm(TodoForm):
    finish_time = DateTimeField(
        label="任务终止日期"
    )
    submit = SubmitField(
        label="添加任务",
    )

class EditTodoForm(TodoForm):
    submit = SubmitField(
        label="编辑任务",
    )

当get方法时的视图函数

# 编辑任务
@app.route('/todo/edit/<int:id>/',methods=['GET','POST'])
def todo_edit(id):
    form = EditTodoForm()
    if form.validate_on_submit():
        pass
    return render_template('todo/edit_todo.html',form=form)

edit_todo.html修改之前添加任务的页面

{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block title %}编辑任务{% endblock %}
{% block content %}
    <div class="container">
        <div class="col-lg-8 col-lg-offset-2">
            <div class="page-header">
                <h1>
                    编辑任务
                </h1>

            </div>

            {{ wtf.quick_form(form) }}
        </div>
    </div>
{% endblock %}

然后把list_todo.html里面编辑按钮的链接加上

 <a href="{{ url_for('todo_edit',id=todo.id) }}" class="btn btn-success" role="button">编辑</a>

测试一下编辑按钮能否跳转
在这里插入图片描述最后完善一下post方法的视图函数:

# 编辑任务
@app.route('/todo/edit/<int:id>/', methods=['GET', 'POST'])
def todo_edit(id):
    form = EditTodoForm()
    # *****重要: 编辑时需要获取原先任务的信息, 并显示到表单里面;
    todo = Todo.query.filter_by(id=id).first()
    form.content.data = todo.content
    form.category.data = todo.category_id
    if form.validate_on_submit():
        # 更新时获取表单数据一定要使用request.form方法获取, 而form.content.data并不能获取用户更新后提交的表单内容;
        # print(request.form)
        # content = form.content.data   # error
        # category_id = form.category.data   # error
        content = request.form.get('content')
        category_id = request.form.get('category')
        # 更新到数据库里面
        todo.content = content
        todo.category_id = category_id
        db.session.add(todo)
        db.session.commit()
        flash("更新任务成功")
        return redirect(url_for('list'))
    return render_template('todo/edit_todo.html',
                           form=form)

在这里插入图片描述在这里插入图片描述

4.任务显示界面中状态修改

修改任务状态的视图函数
1.状态为对勾

# 修改任务状态为完成
@app.route('/todo/done/<int:id>/')
def done(id):
    todo = Todo.query.filter_by(id=id).first()
    todo.status = True
    db.session.add(todo)
    db.session.commit()
    flash('修改状态成功')
    return redirect(url_for('list'))

修改html代码

<td>{% if todo.status %}
            <a href="#"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></a>
                {% else %}
                <a href="{{ url_for('done',id=todo.id) }}"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></a>
            {% endif %}
               </td>

在这里插入图片描述2.状态为叉叉

# 修改任务状态为未完成
@app.route('/todo/undo/<int:id>')
def undo(id):
    todo = Todo.query.filter_by(id=id).first()
    todo.status = False
    db.session.add(todo)
    db.session.commit()
    flash('修改状态成功')
    return redirect(url_for('list'))
       <td>{% if todo.status %}
            <a href="{{ url_for('undo',id=todo.id) }}"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></a>
                {% else %}
                <a href="{{ url_for('done',id=todo.id) }}"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></a>
            {% endif %}
            </td>

在这里插入图片描述
为了更有好显示,可以先判断状态,把状态为叉的任务内容划去后再显示:用del标签

            {% for todo in todoPageObj.items %}
                <tr>
                    {% if todo.status %}
                        <td>{{ todo.content }}</td>
                    {% else %}

                        <td><del>{{ todo.content }}</del></td>
                    {% endif %}
                    <td>{% if todo.status %}
                        <a href="{{ url_for('undo',id=todo.id) }}"><span class="glyphicon glyphicon-ok"
                                                                         aria-hidden="true"></span></a>
                    {% else %}
                        <a href="{{ url_for('done',id=todo.id) }}"><span class="glyphicon glyphicon-remove"
                                                                         aria-hidden="true"></span></a>
                    {% endif %}
                    </td>
                    <td>{{ todo.category. name }}</td>
                    <td>{{ todo.user.username }}</td>
                    <td>
                        {#                按钮#}
                        <a href="{{ url_for('todo_edit',id=todo.id) }}" class="btn btn-success" role="button">编辑</a>
                        <a href="{{ url_for('todo_delete',id=todo.id) }}" class="btn btn-danger" role="button">删除</a>

                    </td>
                </tr>
            {% endfor %}

在这里插入图片描述

/home/kiosk/Documents/python/python0310/day37_TodoProject

0316 P156

猜你喜欢

转载自blog.csdn.net/weixin_43067754/article/details/88580601
今日推荐