基于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

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

项目结构

.
|-- app
|   |-- forms.py
|   |-- __init__.py
|   |-- managerUtil
|   |   |-- database.py
|   |   `-- __pycache__
|   |       `-- database.cpython-36.pyc
|   |-- models.py
|   |-- __pycache__
|   |   |-- forms.cpython-36.pyc
|   |   |-- __init__.cpython-36.pyc
|   |   |-- models.cpython-36.pyc
|   |   `-- views.cpython-36.pyc
|   |-- static
|   |   |-- css
|   |   |   `-- main.css
|   |   `-- js
|   |       `-- echarts.min.js
|   |-- templates
|   |   |-- base.html
|   |   |-- login.html
|   |   |-- register.html
|   |   `-- todo
|   |       |-- add.html
|   |       |-- edit.html
|   |       |-- list.html
|   |       `-- show.html
|   `-- views.py
|-- config.py
|-- manage.py
`-- migrations
    |-- alembic.ini
    |-- env.py
    |-- __pycache__
    |   `-- env.cpython-36.pyc
    |-- README
    |-- script.py.mako
    `-- versions
        |-- fdc3a3a03441_\346\267\273\345\212\240\347\224\250\346\210\267\346\200\247\345\210\253.py
        `-- __pycache__
            `-- fdc3a3a03441_\346\267\273\345\212\240\347\224\250\346\210\267\346\200\247\345\210\253.cpython-36.pyc

第一步:先导入各个需要的模块,在__init__.py中实例化,下面是全部导入的,其实是按照程序来走到哪个模块导入哪个模块

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate
from flask_bootstrap import Bootstrap
from flask_moment import Moment
import pymysql
pymysql.install_as_MySQLdb()


app=Flask(__name__)

#配置信息
app.config.from_pyfile('../config.py')

#连接数据库
db=SQLAlchemy(app)
# 命令行添加使用数据库操作
manager=Manager(app)
# 命令行查看修改数据库的信息
migrate=Migrate(app,db)
# 导入样式
bt=Bootstrap(app)
# 调整时间
moment=Moment(app)

第二步:配置连接数据库的信息,config.py中

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

第三步:创建数据库中需要的表的结构,models.py中,运用flask-sqlalchemy

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(50),unique=True)
    password_hash=db.Column(db.String(100),nullable=False)
    email=db.Column(db.String(50),unique=True)
    # 添加一列数据,更新数据库又不能删除原来的表,只能通过magrate模块来调整
    #gender=db.Column(db.SmallInteger,default=1)
    # 创建账户时间,默认utc时间,在显示页面moment方法进行转化
    # 使用协调时间时(Coordinated Universal Time,UTC)协调世界各地的时差问题;
    add_time=db.Column(db.DateTime,default=datetime.utcnow())
    todos=db.relationship('Todo',backref='user')

    categorys=db.relationship('Category',backref='user')
    # 密码查看保护
    @property
    def password(self):
        """u.password"""
        raise ArithmeticError('密码不可读取')

    @password.setter
    def password(self,password):
        """将密码进行hash加密"""
        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(50),unique=True)
    # 任务状态,一般刚创建未完成,默认未false
    status=db.Column(db.Boolean,default=False)
    add_time=db.Column(db.DateTime,default=datetime.utcnow())
    # 和用户表关联
    user_id=db.Column(db.Integer,db.ForeignKey('user.id'))
    category_id=db.Column(db.Integer,db.ForeignKey('category.id'))

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


class Category(db.Model):
    id = db.Column(db.Integer,autoincrement=True,primary_key=True)
    name=db.Column(db.String(50),unique=True)
    add_time=db.Column(db.DateTime,default=datetime.utcnow())
    # 关联任务表
    todos=db.relationship('Todo',backref='category')
    # 关联用户表
    user_id=db.Column(db.Integer,db.ForeignKey('user.id'))

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

第三步:创建表,在表中添加数据,删除表等数据库操作,封装在一个文件夹内,ManagerUtil/database.py中,运用flask-manager模块

from flask_script import Manager,prompt_bool,Command
from app import db
from app.models import *
database_manager=Manager(usage='数据库操作详情')


@database_manager.command
def dbinit():

    u1 = User(username='admin', email="[email protected]")
    u1.password = 'admin'
    u2 = User(username='westos', email="[email protected]", password='westos')
    db.session.add_all((u1, u2))
    db.session.commit()
    print("用户%s创建成功......." % (u1.username))
    print("用户%s创建成功......." % (u2.username))

    c1 = Category(name="学习", user_id=1)
    c2 = Category(name="运动", user_id=2)
    db.session.add_all((c1, c2))
    print("分类%s创建成功...." % (c1.name))
    print("分类%s创建成功...." % (c2.name))

    t1 = Todo(content="学习Flask", category_id=1, user_id=1)
    t2 = Todo(content="跑步", category_id=2, user_id=2)
    db.session.add_all((t1,t2))
    print("任务%s添加成功....." % (t1.content))
    print("任务%s添加成功....." % (t2.content))
    db.session.commit()


#  option装饰器, 可以指定参数
@database_manager.option('-u', '--username', help="指定用户名")
@database_manager.option('-p', '--password', help="指定密码")
def add_user(username, password):
    """添加用户, 指定用户名和密码"""
    if username and password:
        u = User(username=username, password=password)
        db.session.add(u)
        db.session.commit()
        return  "添加用户%s成功" %(u.username)
    else:
        return  "请指定用户名和密码"

@database_manager.command
def dropdb():
    """删除数据库"""
    if prompt_bool("是否删除数据库"):
        db.drop_all()


@database_manager.command
def createdb():
    """删除数据库"""
    if prompt_bool("是否创建数据库"):
        db.create_all()


@database_manager.command
def recreate():
    """重建数据库"""
    if prompt_bool("是否重数据库"):
        dropdb()
        createdb()

第五步:运行manager.py,使整个程序联动,这个时候还没有视图函数和相应的html文件,所以并不能runserver,只能进行数据库的操作

from app import  manager,db
from app.managerUtil.database import database_manager
from app.views import *
from flask_migrate import MigrateCommand
from app.models import *
from flask_script import Command, prompt_bool

# 将模块中的数据库操作添加到命令行
manager.add_command('database',database_manager)
manager.add_command('db',MigrateCommand)

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

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

第六步:数据库信息的转移----当数据库中的表数据已经写入,现在突然想添加一列信息,比如加一个gender属性在User表中,这个时候用到flask_migrate模块

添加前数据库:
在这里插入图片描述
操作:

(virtl) [kiosk@foundation67 Todo]$ python manage.py
usage: manage.py [-?] {database,db,shell,runserver} ...

positional arguments:
  {database,db,shell,runserver}
    database            数据库操作详情
    db                  Perform database migrations
    shell               Runs a Python shell inside Flask application context.
    runserver           Runs the Flask development server i.e. app.run()

optional arguments:
  -?, --help            show this help message and exit
创建一个仓库,里边时需要修改的内容
(virtl) [kiosk@foundation67 Todo]$ python manage.py db init
  Creating directory /home/kiosk/PycharmProjects/Todo/migrations ... done
  Creating directory /home/kiosk/PycharmProjects/Todo/migrations/versions ... done
  Generating /home/kiosk/PycharmProjects/Todo/migrations/README ... done
  Generating /home/kiosk/PycharmProjects/Todo/migrations/alembic.ini ... done
  Generating /home/kiosk/PycharmProjects/Todo/migrations/env.py ... done
  Generating /home/kiosk/PycharmProjects/Todo/migrations/script.py.mako ... done
  Please edit configuration/connection/logging settings in '/home/kiosk/PycharmProjects/Todo/migrations/alembic.ini' before
  proceeding.
向表中添加新加入的属性信息
(virtl) [kiosk@foundation67 Todo]$ python manage.py db migrate -m '添加用户性别'
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added column 'user.gender'
  Generating /home/kiosk/PycharmProjects/Todo/migrations/versions/fdc3a3a03441_添加用户性别.py ... done
更新数据库中的表,不会对原有数据产生影响,保留以前的表,进入到下一个地址
(virtl) [kiosk@foundation67 Todo]$ python manage.py db upgrade
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> fdc3a3a03441, 添加用户性别
查看数据库进行的操作
(virtl) [kiosk@foundation67 Todo]$ python manage.py db history
<base> -> fdc3a3a03441 (head), 添加用户性别
(virtl) [kiosk@foundation67 Todo]$ python manage.py db current
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
fdc3a3a03441 (head)
返回上一级的位置,即表还原
(virtl) [kiosk@foundation67 Todo]$ python manage.py db downgrade base
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running downgrade fdc3a3a03441 -> , 添加用户性别
(virtl) [kiosk@foundation67 Todo]$ python manage.py db upgrade
INFO  [alembic.runtime.migration] Context impl MySQLImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> fdc3a3a03441, 添加用户性别

添加后数据库:
在这里插入图片描述

第七步:对应html和视图函数的表单的编写,forms.py中,运用flask-wtf模块

from flask_wtf import FlaskForm
from wtforms import StringField,PasswordField,SelectField,SubmitField,ValidationError,DateTimeField
from wtforms.validators import Length,DataRequired,EqualTo,Email
from app.models import *


#注册表单
class RegisterForm(FlaskForm):

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

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

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

    email=StringField(
        label='邮箱地址',
        validators=[DataRequired(),
                    Email()]
    )

    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,field):
        u=User.query.filter_by(email=field.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,message='密码为6~12位')]
    )

    submit=SubmitField(
        label='登录'
    )

# 关于任务的基类
class TodoProjectForm(FlaskForm):

    content=StringField(
        label='任务内容',
        validators=[DataRequired(),
                    ]
    )

    # 任务类型
    category=SelectField(
        label='任务类型',
        coerce=int,
        # 找出数据库中存在的所有任务类型
        choices=[(item.id,item.name) for item in Category.query.all()]
    )

# 添加任务
class AddTodoForm(TodoProjectForm):
    finsh_time=DateTimeField(
        label='任务终止时间',
    )

    submit=SubmitField(
        label='添加'
    )

# 编辑任务
class EditTodoForm(TodoProjectForm):
    submit=SubmitField(
        label='编辑'
    )

第八步:视图函数的编写,views.py文件中

import json
from functools import wraps

from flask import session, flash, redirect, url_for, render_template, request

from app import app, db
from app.forms import RegisterForm, LoginForm, AddTodoForm, EditTodoForm
from app.models import *

#装饰器
def is_login(f):
    """用来判断用户是否登录成功"""

    @wraps(f)
    def wrapper(*args, **kwargs):
        if session.get('user', None):
            return f(*args, **kwargs)
        else:
            flash('用户必须登录才能访问%s页面' % (f.__name__))
            return redirect(url_for('login'))

    return wrapper


@app.route('/')
@is_login
def index():
    return redirect(url_for('list'))


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

        u = User.query.filter_by(username=username).first()
        if u and u.verify_password(password):
            session['user'] = u.username
            session['user_id'] = u.id
            flash('登录成功')
            return redirect(url_for('index'))
        else:
            flash('帐号或密码错误')
            return redirect(url_for('login'))
    return render_template('login.html', form=form)


@app.route('/register/', methods=['POST', 'GET'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        username = form.username.data
        password = form.password.data
        email = form.email.data
        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('/logout/')
@is_login
def logout():
    session.pop('user_id', None)
    session.pop('user', None)
    flash('退出登录')
    return redirect(url_for('login'))


# 添加任务
@app.route('/todo/add/', methods=[ 'GET','POST'])
@is_login
def 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('list'))
    return render_template('todo/add.html', form=form)


# 编辑任务
@app.route('/todo/edit/<int:id>/', methods=['GET', 'POST'])
def 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并不能获取用户更新后提交的表单内容;
        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.html', form=form)


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


# 查看任务
@app.route('/todo/list/')
@app.route('/todo/list/<int:page>/')
@is_login
def list(page=1):
    # 任务显示需要分页,每个用户只能查看自己的任务
    todoPageObj = Todo.query.filter_by(
        user_id=session.get('user_id')).paginate(
        # 在config.py文件中有设置;
        page, per_page=app.config['PER_PAGE'])
    return render_template('todo/list.html',todoPageObj=todoPageObj)

# 修改任务状态为完成
@app.route('/todo/done/<int:id>/')
@is_login
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'))

# 修改任务状态为未完成
@app.route('/todo/undo/<int:id>')
@is_login
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'))




@app.route('/get_data/')
@is_login
def get_data():
    done_count = Todo.query.filter_by(status=True).count()
    undone_count = Todo.query.filter_by(status=False).count()
    info = {
        'info': ["已完成", "未完成"],
        'count': [done_count, undone_count]
    }
    # 解决中文编码问题
    return json.dumps(info, ensure_ascii=False)

@app.route('/todo/show/')
@is_login
def newShowTodo():
    return render_template('todo/show.html')

第九步:页面的编写,在templates文件夹中编写,同时在static文件夹中添加css以及js样式

1).添加css以及js样式

css文件是自己编写的main.css:
.navbar {
    font-size: 130%;
    background: whitesmoke;
    margin-top: 10px;
    padding-top: 5px;
    box-shadow: 2px 2px 2px 2px lightgray;
    height: 60px;
}
js文件是从echarts官网下载的文件echarts.min.js

2).编写继承文件,以便使其他页面继承 base.html

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

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


{% block scripts %}

    {{ super() }}
    {#    实质使导入moment.js库的, 通过Flask-moment集成了起来#}
    {#  处理utc时间转换成当地时间的模块  #}
    {{ moment.include_moment() }}
    <script src="{{ url_for('static', filename='js/echarts.min.js') }}"></script>

{% 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="{{ url_for('add') }}">添加任务</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 %}

3). 注册页面的编写 register.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 %}

在这里插入图片描述

4).登录页面的编写 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('register') }}">注册</a>
                    </small>
                </h1>
            </div>
            {{ wtf.quick_form(form) }}
        </div>

    </div>

{% endblock %}

在这里插入图片描述

5).显示自己任务的页面 todo/list.html

{% extends 'base.html' %}
{% block title %}

    任务显示
{% endblock %}

{% block content %}
    <div class="container">
        <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>
                    <td>创建时长</td>
                    <td>操作</td>
                </tr>
                {# 边历每一个任务#}
                {% for todo in todoPageObj.items %}

                    <tr>
                        {# 如果任务以完成,则划一条删除线  #}
                        {% if todo.status %}
                            <td>
                                <del>{{ todo.content }}</del>

                            </td>
                        {% else %}

                            <td>{{ todo.content }}</td>
                        {% endif %}

                        <td>
                            {% if todo.status %}
                                {#如果任务以完成,则状态显示为正确模式#}
                                <a href="{{ url_for('undo', id=todo.id) }}" style="color: green"><span
                                        class="glyphicon glyphicon-ok"
                                        aria-hidden="true">

                                </span></a>

                            {% else %}
                                {#如果任务没完成,则状态显示为错误模式#}
                                <a href="{{ url_for('done', id=todo.id) }}" style="color:mediumvioletred ">
                                    <span class="glyphicon glyphicon-remove"
                                          aria-hidden="true">
                                </span></a>


                            {% endif %}

                        </td>
                        <td>{{ todo.category.name }}</td>
                        <td>{{ todo.user.username }}</td>
                        <td>{{ moment(todo.add_time).format('L') }}</td>
                        <td>{{ moment(todo.add_time).fromNow(refresh=True) }}</td>
                        <td>
                            {#相应按钮进入删除和修改页面#}
                            <a class="btn btn-success" href="{{ url_for('edit', id=todo.id) }}"
                               role="button">修改</a>
                            <a class="btn btn-danger" href="{{ url_for('delete', id=todo.id) }}"
                               role="button">删除</a>

                        </td>
                    </tr>
                {% endfor %}
            </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>


        </div>

    </div>

{% endblock %}

当一个刚注册的用户是没有任务的,所以登录后转到自己的任务页面是空的

在这里插入图片描述

6). 添加任务,登录上帐号后可以给自己添加任务,然后任务列表中能显示自己的任务,todo/add.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 %}

比如我给用户添加一个踢球的运动惹任务
在这里插入图片描述
添加完了后,会在任务清单中显示
在这里插入图片描述
但是其他用户是看不到你的任务的
在这里插入图片描述

7). 编辑任务,删除任务,以及修改任务状态(在list.html中有写),编辑任务的为 todo/edit.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 %}

当前任务信息为

我要修改任务内容或者类型,点击修改
在这里插入图片描述
将其中的任务内容为看数学的为短跑,类型选择运动,点击编辑提交
在这里插入图片描述
当这项任务完成了,点击任务状态的图表,自动跳转到完成效果
在这里插入图片描述
任务完成就可以点击删除按钮删除任务
在这里插入图片描述

8). 通过echarts官网上的可视化图形的样式,用图像显示出数据对比,传入时实字典信息到html文件中的scripts标签下时,需要使用json转化, show.html中

{% extends 'base.html' %}

{% block title %}

    图表分析
{% endblock %}


{% block scripts %}
    {{ super() }}
    <script type="text/javascript">
        // 基于准备好的dom,初始化echarts实例
        var myChart = echarts.init(document.getElementById('main'));
        $.ajax({
            url: '/get_data/',
            success: function (data) {
                json_data = JSON.parse(data)
                // 指定图表的配置项和数据
                var option = {
                    title: {
                        text: '任务完成度分析',

                    },
                    tooltip: {
                        trigger: 'item',
                        formatter: "{a} <br/>{b}: {c} ({d}%)"
                    },
                    legend: {
                        orient: 'vertical',
                        x: 'right',
                        data: [json_data.info[0], json_data.info[1]]
                    },
                    series: [
                        {
                            name: '访问来源',
                            type: 'pie',
                            radius: ['50%', '70%'],
                            avoidLabelOverlap: false,
                            label: {
                                normal: {
                                    show: false,
                                    position: 'center'
                                },
                                emphasis: {
                                    show: true,
                                    textStyle: {
                                        fontSize: '20',
                                        fontWeight: 'bold'
                                    }
                                }
                            },
                            labelLine: {
                                normal: {
                                    show: false
                                }
                            },
                            data: [
                                {value: json_data.count[0], name: json_data.info[0]},
                                {value: json_data.count[1], name: json_data.info[1]},

                            ]
                        }
                    ]

                };

                // 使用刚指定的配置项和数据显示图表。
                myChart.setOption(option);

            }
        })
    </script>

{% endblock %}
{% block content %}
    <!-- 为ECharts准备一个具备大小(宽高)的Dom -->
    <div id="main" style="width: 500px;height:500px;
    border: 1px solid gray"></div>

{% endblock %}

显示的是所有用户的任务完成度
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/GLH_2236504154/article/details/88680255