基于Flask与Mariadb实现任务清单管理

目标

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

项目介绍

本应用修改自 TodoMVC 的 todo list 应用,使用 Mariadb 作为数据库后端,Bootstrap 作为前端的 Flask 应用。先给它起个好听的名字吧,方便之后称呼。
todo list => (自定义,随便起名称) => todoest
就像一般的 todo list 应用一样,todoest 实现了以下功能:

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

技术分析

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

目录结构

在这里插入图片描述

templates

base.html:
{% extends "bootstrap/base.html" %}

{% block styles %}
    {{ super() }}
    <link rel="stylesheet" href="../static/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="index.html"></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><a href="#"></a></li>
                     <li><a href="#"></a></li>
                    <li><a href="#">首页<span class="sr-only">(current)</span></a></li>
                    <li><a href="#">新闻</a></li>
                    <li><a href="#">国际</a></li>
                    <li><a href="#">国内</a></li>
                    <li><a href="/sysinfo/">系统信息</a></li>
                    <li><a href="#">登陆用户</a></li>
                </ul>
                <ul class="nav navbar-nav navbar-right">


                    {% if 'user' in session %}
                        <li><a href="login.html"><span class="glyphicon glyphicon-user"></span>
                            &nbsp;&nbsp; {{ session.user }}</a></li>
                        <li><a href="/logout/"><span class="glyphicon glyphicon-log-in"></span>
                            &nbsp;&nbsp; 注销 </a></li>


                    {% else %}

                        <li><a href="/login/"><span class="glyphicon glyphicon-log-in"></span>
                            &nbsp;&nbsp;登陆</a></li>
                    {% endif %}

                </ul>
            </div><!-- /.navbar-collapse -->
        </div><!-- /.container-fluid -->
    </nav>
{% endblock %}

{% block content %}
    {% block newcontent %}

    {% endblock %}

    {% block footer %}
        <div class="footer">


                京ICP备11008151号京公网安备11010802014853

        </div>
    {% endblock %}
{% endblock %}
list.html:
{% extends 'base.html' %}
{% block newcontent %}
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            {# /*添加任务*/#}
            <form class="form-horizontal" action="/todo/add/" method="post">
                <div class="form-group">
                    {#  添加框                   #}
                    <div class="col-sm-9">
                        <input type="text" class="form-control" placeholder="请添加任务" required="required"
                               name="todo_name">
                    </div>
                    {#    选择框              #}
                    <div class="col-sm-2">
                        <select class="form-control" name="part">
                            {% for part in parts %}
                                <option value="{{ part.id }}">{{ part.name }}</option>
                            {% endfor %}
                        </select>
                    </div>

                    {#   添加的按钮              #}
                    <div class="col-sm-1">
                        <input type="submit" class="btn btn-success" value="添加任务">
                    </div>
                </div>

            </form>
            {# /*任务显示*/#}
            <h1>添加任务</h1>
            <table class="table table-bordered">
                <tr>
                    <td>任务内容</td>
                    <td>创建时间</td>
                    <td>状态</td>
                    <td>所属部门</td>
                    <td>操作</td>
                </tr>
                {% for todo in todos.items %}
                    <tr>
                        <td>{{ todo.name }}</td>
                        <td>{{ todo.add_time }}</td>
                        {#        #}
                        <td>
                            {% if todo.status %}
                                 <a href="/todo/undo/{{  todo.id }}/" class="btn btn-sm btn-success" role="button"><span
                            class="glyphicon glyphicon-ok"></span> 已完成</a>

                            {% else %}
                                <a href="/todo/done/{{  todo.id }}/" class="btn btn-sm btn-warning" role="button"><span
                            class="glyphicon glyphicon-remove"></span> 未完成</a>

                            {% endif %}

                        </td>
                        <td>{{ todo.department.name }}</td>
                        <td><a href="/todo/delete/{{  todo.id }}/" class="btn btn-sm btn-danger" role="button"><span
                            class="glyphicon glyphicon-ice-lolly"></span>&nbsp;&nbsp; 删除</a></td>
                    </tr>

                {% endfor %}


            </table>
            {% from 'macro_page.html' import paginate %}
            {{ paginate('todo_list',todos)}}

        </div>
    </div>

{% endblock %}
login.html:
{% extends 'bootstrap/base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block content %}
<div class="col-md-6 col-md-offset-3">
    <form method="post" action="/login/">
        {{ wtf.quick_form(form) }}
        {% if message %}
         <p style="color: red">{{ message }}</p>
        {% endif %}
    </form>
</div>
{% endblock %}
macro_page.html:
{% macro paginate(fname,todos) %}
    <ul class="pagination">
    {#        判断是否由上一页, #}
        {% if todos.has_prev %}
            <li><a href="{{ url_for(fname,page=todos.prev_num) }}">前一页</a></li>
        {% else %}
            <li class="disabled"><a href="#">前一页</a></li>
        {% endif %}
        {#   根据从数据库中查询的数据, 来确定分页的个数, 使用for循环   #}
        {#  返回一个迭代器, 如果有100页, 薄嗯不会返回100个数字, 而是1 2 。。。。 99 100        #}
        {% for page in todos.iter_pages(right_current=2) %}
            {# page: 要生成html代码的页数,  todos.page: 用户希望显示数据的页数            #}
    {% for page in todos.iter_pages(right_current=2) %}
        {% if page == todos.page %}
            <li class="active"><a href="{{ url_for(fname,page=page) }}">{{ page }}</a></li>
        {% elif page == None %}
            <li><a href="{{ url_for(fname,page=todos.next_num)}}">...</a></li>
        {% else %}
            <li><a href="{{ url_for(fname,page=page) }}">{{ page }}</a></li>
        {% endif %}
    {% endfor %}
    {% if todos.has_next %}
        <li><a href="{{ url_for(fname,page=todos.next_num) }}">后一页</a></li>
    {% else %}
        <li class="disabled"><a href="#">后一页</a></li>
    {% endif %}
</ul><br>
{% endmacro %}

py文件

forms.py:
from flask_wtf import FlaskForm
from wtforms import StringField,PasswordField,SubmitField
from wtforms.validators import DataRequired,Length
class LoginForm(FlaskForm):
    user = StringField(
        label="用户名/邮箱/手机号",
        validators=[
            DataRequired(message="请输入用户名"),
            Length(7,9,message="位数为7-9位")
        ]
    )
    passwd = PasswordField(
        label="密码",
        validators=[
            DataRequired(),
            Length(7,message="位数为7")
        ]
    )
    sumbit = SubmitField(
        label="登陆"
    )
models.py:
import random
from datetime import datetime
from  flask import Flask
from  flask_sqlalchemy import SQLAlchemy
from  flask_bootstrap import Bootstrap

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]='mysql+pymysql://cooffee:cooffee@172.25.254.78/Todo'
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
db = SQLAlchemy(app)
bootstrap = Bootstrap(app)
app.config["SECRET_KEY"] = random._urandom(24)
#  1). 面向对象方式创建表
#  2). 分析关系:
#       部门和用户: 一对多
#       用户和任务: 一对多的关系
#       用户和用户登录日志: 一对多的关系
#  实现一对多(Role(1): User(n))的关系
#   - 多的一端写外键
#   - 少的一端写反向引用
class User(db.Model):
    id = db.Column(db.Integer,autoincrement=True,primary_key=True)
    name= db.Column(db.String(50),unique=True,nullable=Flask)
    pwd = db.Column(db.String(100))
    email = db.Column(db.String(30), unique=True)
    phone = db.Column(db.String(30), unique=True)
    info = db.Column(db.Text)
    add_time = db.Column(db.DateTime, default=datetime.now())
    department_id = db.Column(db.Integer, db.ForeignKey('department.id'))
    todos = db.relationship('Todo', backref='user')
    log = db.relationship('Userlog',backref='user')

class Department(db.Model):
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    name = db.Column(db.String(50), unique=True, nullable=Flask)
    users = db.relationship('User', backref='department')
    todos = db.relationship('Todo', backref='department')

class Todo(db.Model):
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    name = db.Column(db.String(200), nullable=False)
    add_time = db.Column(db.DateTime, default=datetime.now())
    status = db.Column(db.Boolean, default=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    department_id = db.Column(db.Integer, db.ForeignKey('department.id'))

class Userlog(db.Model):
    id = db.Column(db.Integer, autoincrement=True, primary_key=True)
    add_time = db.Column(db.DateTime, default=datetime.now())
    ip = db.Column(db.String(200), nullable=False)
    area = db.Column(db.String(200))
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

if __name__ == "__main__":
    db.create_all()
    parts = ['开发部', '运维部', '人事部']
    partObj = [Department(name=part) for part in parts]
    db.session.add_all(partObj)
    db.session.commit()
    u1 = User(name="cooffee1", pwd='cooffee', department_id=1)
    db.session.add(u1)
    db.session.commit()
manage.py:
from flask_migrate import Migrate,MigrateCommand
from  flask_script import  Shell,Manager
from models import app,db,User,Department
manager = Manager(app)
migrate = Migrate(app,db)
manager.add_command('db',MigrateCommand)

@manager.command
def showUser():
    """显示所有的用户"""
    users = User.query.all()
    print(users[:5])

@manager.command
def showDepart():
    """显示所有的部门"""
    deaprts  = Department.query.all()
    print(deaprts)

@manager.option('-n', '--name', help="部门名称")
def add_depart(name):
    try:
        depart1 = Department(name=name)
        db.session.add(depart1)
        db.session.commit()
    except Exception:
        print("创建部门失败!")
    else:
        print("创建部门%s成功!" %(name))
if __name__=="__main__":
    manager.run()
views.py:
import functools
from flask import render_template, request, redirect, url_for, session

from forms import LoginForm
from  models import app, Todo, db, Department, User


def is_login(f):
    """判断用户是否登陆的装饰器"""
    @functools.wraps(f)
    def wrapper(*args,**kwargs):
        if 'user' not in session:
            return redirect(url_for('login'))
        return f(*args,**kwargs)
    return wrapper

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

@app.route('/login/', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = form.data['user']
        passwd = form.data['passwd']
        u = User.query.filter_by(name=user).first()
        if u and passwd == u.pwd:
            session['user'] = user
            session['passwd'] = passwd
            return redirect(url_for('todo_list'))
        else:
            return render_template('login.html', message='用户名或者密码错误',form=form)
    else:
        return render_template('login.html',form=form)

@app.route('/logout/')
def logout():
    session.pop('user',None)
    session.pop('passwd',None)
    return redirect(url_for('index'))


@app.route('/todo/add/',methods=['POST'])
def todo_add():
    name = request.form['todo_name']
    part = request.form['part']
    todo = Todo(name=name,department_id=part,user_id=1)
    db.session.add(todo)
    db.session.commit()
    return redirect(url_for("todo_list"))

@app.route('/list/')
@app.route('/list/<int:page>/')
@is_login
def todo_list(page=1):
    todos = Todo.query.paginate(page,5)
    parts = Department.query.all()
    return render_template('list.html',
                           todos = todos,
                           parts=parts)

@app.route('/todo/undo/<int:id>/')
def undo(id):
    todo = Todo.query.filter_by(id=id).first()
    todo.status = False
    db.session.commit()
    return redirect(url_for('todo_list'))

@app.route('/todo/done/<int:id>/')
def done(id):
    todo = Todo.query.filter_by(id=id).first()
    todo.status = True
    db.session.commit()
    return redirect(url_for('todo_list'))

@app.route('/todo/delete/<int:id>/')
def delete(id):
    todo = Todo.query.filter_by(id=id).first()
    db.session.delete(todo)
    db.session.commit()
    return redirect(url_for('todo_list'))import functools
from flask import render_template, request, redirect, url_for, session

from forms import LoginForm
from  models import app, Todo, db, Department, User


def is_login(f):
    """判断用户是否登陆的装饰器"""
    @functools.wraps(f)
    def wrapper(*args,**kwargs):
        if 'user' not in session:
            return redirect(url_for('login'))
        return f(*args,**kwargs)
    return wrapper

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

@app.route('/login/', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = form.data['user']
        passwd = form.data['passwd']
        u = User.query.filter_by(name=user).first()
        if u and passwd == u.pwd:
            session['user'] = user
            session['passwd'] = passwd
            return redirect(url_for('todo_list'))
        else:
            return render_template('login.html', message='用户名或者密码错误',form=form)
    else:
        return render_template('login.html',form=form)

@app.route('/logout/')
def logout():
    session.pop('user',None)
    session.pop('passwd',None)
    return redirect(url_for('index'))


@app.route('/todo/add/',methods=['POST'])
def todo_add():
    name = request.form['todo_name']
    part = request.form['part']
    todo = Todo(name=name,department_id=part,user_id=1)
    db.session.add(todo)
    db.session.commit()
    return redirect(url_for("todo_list"))

@app.route('/list/')
@app.route('/list/<int:page>/')
@is_login
def todo_list(page=1):
    todos = Todo.query.paginate(page,5)
    parts = Department.query.all()
    return render_template('list.html',
                           todos = todos,
                           parts=parts)

@app.route('/todo/undo/<int:id>/')
def undo(id):
    todo = Todo.query.filter_by(id=id).first()
    todo.status = False
    db.session.commit()
    return redirect(url_for('todo_list'))

@app.route('/todo/done/<int:id>/')
def done(id):
    todo = Todo.query.filter_by(id=id).first()
    todo.status = True
    db.session.commit()
    return redirect(url_for('todo_list'))

@app.route('/todo/delete/<int:id>/')
def delete(id):
    todo = Todo.query.filter_by(id=id).first()
    db.session.delete(todo)
    db.session.commit()
    return redirect(url_for('todo_list'))
Todo.py:
from  views import *
if __name__ == '__main__':
    app.run()

在这里插入图片描述

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页