基于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:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}首页{% endblock %} | cooffee</title>
<link rel="stylesheet" href="../static/css/bootstrap.min.css">
<link rel="stylesheet" href="../static/css/main.css">
<script src="../static/js/bootstrap.min.js"></script>
</head>
<body>
<!--导航栏-->
<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 class="active" style="padding-bottom: 15px;"><a href="index.html">首页<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><span class="glyphicon glyphicon-user"></span></a></li>-->
<li><a href="#">{{session.user}}</a></li>
<li><a href="/logout/">注销</a></li>
{% else %}
<li><a href="/login/">登陆</a></li>
<li><a href="/register/">注册</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
{% block content %}
{% endblock %}
<div class="footer">
京ICP备11008151号京公网安备11010802014853
</div>
</body>
</html>
index.html:
{% extends "base.html" %}
{% block title %}
{{super()}}
{% endblock %}
{% block content %}
<div class="container container-fluid">
<div class="row">
<!--左侧导航-->
<div class="col-xs-2">
<div class="list-group left-side">
<a class="list-group-item left-side-active" href="#">综合</a>
<a class="list-group-item" href="#">电影</a>
<a class="list-group-item" href="#">电视剧</a>
<a class="list-group-item" href="#"> 明星</a>
<a class="list-group-item" href="#">娱乐</a>
</div>
</div>
<!--中间新闻-->
<div class="col-xs-7">
<div class="media">
<div class="media-left">
<a href="news.html">
<img class="media-object news-png"
src="../static/img/index1.jpg"
alt="新闻图片"></a>
</div>
<div class="media-body">
<h4 class="media-heading"><b>2年前他为教育和高圆圆分手,今成这般,高圆圆:我有一句MMP如哽在喉</b></h4>
<div class="news-info">
<img class="news-logo" src="../static/img/u=1271327272,62771227&fm=26&gp=0.jpg">
<span>王花花</span>.
<span>25K评论</span>.
<span>7分钟前</span>
</div>
</div>
</div>
<div class="media">
<div class="media-left">
<a href="news.html">
<img class="media-object news-png"
src="../static/img/2.2.jpg"
alt="新闻图片"></a>
</div>
<div class="media-body">
<h4 class="media-heading"><b>2年前他为教育和高圆圆分手,今成这般,高圆圆:我有一句MMP如哽在喉</b></h4>
<div class="news-info">
<img class="news-logo" src="../static/img/u=1271327272,62771227&fm=26&gp=0.jpg">
<span>王花花</span>.
<span>25K评论</span>.
<span>7分钟前</span>
</div>
</div>
</div>
<div class="media">
<div class="media-left">
<a href="news.html">
<img class="media-object news-png"
src="../static/img/2.jpg"
alt="新闻图片"></a>
</div>
<div class="media-body">
<h4 class="media-heading"><b>2年前他为教育和高圆圆分手,今成这般,高圆圆:我有一句MMP如哽在喉</b></h4>
<div class="news-info">
<img class="news-logo" src="../static/img/u=1271327272,62771227&fm=26&gp=0.jpg">
<span>王花花</span>.
<span>25K评论</span>.
<span>7分钟前</span>
</div>
</div>
</div>
<div class="media">
<div class="media-left">
<a href="news.html">
<img class="media-object news-png"
src="../static/img/2.2.jpg"
alt="新闻图片"></a>
</div>
<div class="media-body">
<h4 class="media-heading"><b>2年前他为教育和高圆圆分手,今成这般,高圆圆:我有一句MMP如哽在喉</b></h4>
<div class="news-info">
<img class="news-logo" src="../static/img/u=1271327272,62771227&fm=26&gp=0.jpg">
<span>王花花</span>.
<span>25K评论</span>.
<span>7分钟前</span>
</div>
</div>
</div>
<div class="media">
<div class="media-left">
<a href="news.html">
<img class="media-object news-png"
src="../static/img/index1.jpg"
alt="新闻图片"></a>
</div>
<div class="media-body">
<h4 class="media-heading"><b>2年前他为教育和高圆圆分手,今成这般,高圆圆:我有一句MMP如哽在喉</b></h4>
<div class="news-info">
<img class="news-logo" src="../static/img/u=1271327272,62771227&fm=26&gp=0.jpg">
<span>王花花</span>.
<span>25K评论</span>.
<span>7分钟前</span>
</div>
</div>
</div>
<div class="media">
<div class="media-left">
<a href="news.html">
<img class="media-object news-png"
src="../static/img/2.2.jpg"
alt="新闻图片"></a>
</div>
<div class="media-body">
<h4 class="media-heading"><b>2年前他为教育和高圆圆分手,今成这般,高圆圆:我有一句MMP如哽在喉</b></h4>
<div class="news-info">
<img class="news-logo" src="../static/img/u=1271327272,62771227&fm=26&gp=0.jpg">
<span>王花花</span>.
<span>25K评论</span>.
<span>7分钟前</span>
</div>
</div>
</div>
<!--页角-->
<nav aria-label="Page navigation">
<ul class="pagination">
<li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">上一页</span></a>
</li>
<li class="active"><a href="#">1 <span class="sr-only">(current)</span></a></li>
<li><a href="#">2 <span class="sr-only">(current)</span></a></li>
<li><a href="#">3 <span class="sr-only">(current)</span></a></li>
<li><a href="#">4 <span class="sr-only">(current)</span></a></li>
<li><a href="#">... <span class="sr-only">(current)</span></a></li>
<li><a href="#">10 <span class="sr-only">(current)</span></a></li>
<li class="disabled"><a href="#" aria-label="Previous"><span aria-hidden="true">下一页</span></a>
</li>
</ul>
</nav>
</div>
<div class="col-xs-3">
<!--搜索栏-->
<div class="input-group input-info">
<input type="text" class="form-control" placeholder="搜一下">
</div>
<!--有害信息-->
<div class="bad-infomation">
<table id="bad-table">
<tr>
<td rowspan="2">
<img class="bad-jpg" src="../static/img/4.png">
</td>
<td id="bad-font1">有害信息举报专区</td>
</tr>
<tr>
<td id="bad-font2">
举报电话12377
</td>
</tr>
</table>
</div>
<!--新闻推荐-->
<div class="list-group hot-news">
<a href="#" class="list-group-item">
<span style="font-size: larger"><b>24小时热闻</b></span>
</a>
<a href="#" class="list-group-item"><span style="font-size: large"> Lorem ipsum dolor sit amet,consectetur adipisicing</span><div class="news-info">
<span>25K评论</span>
<span>7分钟前</span></div></a>
<a href="#" class="list-group-item"><span style="font-size: large"> Lorem ipsum dolor sit amet,consectetur adipisicing</span><div class="news-info">
<span>25K评论</span>
<span>7分钟前</span></div></a>
<a href="#" class="list-group-item"><span style="font-size: large"> Lorem ipsum dolor sit amet,consectetur adipisicing</span><div class="news-info">
<span>25K评论</span>
<span>7分钟前</span></div></a>
<a href="#" class="list-group-item"><span style="font-size: large"> Lorem ipsum dolor sit amet,consectetur adipisicing</span><div class="news-info">
<span>25K评论</span>
<span>7分钟前</span></div></a>
<a href="#" class="list-group-item"><span style="font-size: large"> Lorem ipsum dolor sit amet,consectetur adipisicing</span><div class="news-info">
<span>25K评论</span>
<span>7分钟前</span></div></a>
</div>
</div>
</div>
</div>
{% endblock %}
login.html:
{% extends "base.html" %}
{% block title %}
登陆
{% endblock %}
{% block content %}
<!--登陆界面-->
<div class="container container-small login">
<h1>登录
<small>没有账号?<a href="/register/">注册</a></small>
</h1>
<form action="/login/" method="post">
<!--<div class="form-group">-->
<!--<label>用户名/手机/邮箱</label>-->
<!--<input name="user" type="text" class="form-control username-text">-->
<!--</div>-->
<!--<div class="form-group">-->
<!--<label>密码</label>-->
<!--<input name="passwd" type="password" class="form-control username-text">-->
<!--</div>-->
{% import 'macro.html' as macro %}
{{macro.input('text','user','用户名/手机/邮箱')}}
{{macro.input('password','passwd','密码')}}
<div class="form-group">
<button class="btn btn-primary btn-block block-sure" type="submit">登录</button>
</div>
<div class="form-group">
<a href="#">忘记密码?</a>
</div>
{% if message %}
<p style="color: red">{{message}}</p>
{% endif %}
</form>
</div>
{% endblock %}
singnup.html:
{% extends "base.html" %}
{% block title %}
注册
{% endblock %}
{% block content %}
<!--注册界面-->
<div class="container container-small login">
<h1>注册
<small>没有账号?<a href="/register/">注册</a></small>
</h1>
<form action="/register/" method="post">
{% import 'macro.html' as macro %}
{{macro.input('text','user','用户名/手机/邮箱')}}
{{macro.input('password','passwd','密码')}}
<!--<div class="form-group">-->
<!--<label>手机</label>-->
<!--<input type="text" class="form-control username-text">-->
<!--</div>-->
<!--<!–<div class="form-group username-text">–>-->
<!--<!–<label>验证码</label>–>-->
<!--<!–<!–<div class="input-group">–>–>-->
<!--<!–<!–<input type="text" class="form-control">–>–>-->
<!--<!–<!–<div class="input-group-btn">–>–>-->
<!--<!–<!–<div class="btn btn-default">获取验证码</div>–>–>-->
<!--<!–<!–</div>–>–>-->
<!--<!–<!–</div>–>–>-->
<!--<!–</div>–>-->
<!--<div class="form-group">-->
<!--<label>密码</label>-->
<!--<input type="password" class="form-control username-text">-->
<!--</div>-->
<div class="form-group">
<button class="btn btn-primary btn-block block-sure" type="submit">注册</button>
</div>
<div class="form-group">
注册咖啡或浮云即代表您同意<a href="#">咖啡或浮云服务条款</a>
</div>
{% if message %}
<p style="color: red">{{message}}</p>
{% endif %}
</form>
</div>
{% endblock %}
macro.html:
{% macro input(type,name,text) %}
<div class="form-group">
<label>{{text}}</label>
<input name={{name}} type={{type}} class="form-control username-text">
</div>
{% endmacro %}
sysinfo.html:
{% extends 'base.html' %}
{% block title %} 系统信息 {% endblock %}
{% block content %}
{# 表格内容----bootstrap #}
<table class="table table-striped" style="width: 50%; margin: auto " >
<tr>
<td><b style="font-size: large">系统信息</b></td>
<td></td>
</tr>
<tr>
<td>主机名</td>
<td>{{ hostname }}</td>
</tr>
<tr>
<td>内核名称</td>
<td>{{ sysname }}</td>
</tr>
<tr>
<td>发行版本号</td>
<td>{{ release }}</td>
</tr>
<tr>
<td>内核版本</td>
<td>{{ version}}</td>
</tr>
<tr>
<td>系统构架</td>
<td>{{ machine }}</td>
</tr>
<tr>
<td>现在时间</td>
<td>{{ now_time }}</td>
</tr>
<tr>
<td>开机时间</td>
<td>{{ boot_time }}</td>
</tr>
<tr>
<td>运行时间</td>
<td>{{ delta_time }}</td>
</tr>
</table>
{% endblock %}
404.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2 style="color: red;">404: 页面未找到</h2>
</body>
</html>
程序
models.py:
连接数据库
import pymysql
conn = pymysql.connect(
host='172.25.254.78',
user='cooffee',
password='cooffee',
charset='utf8',
db='cooffee'
)
cur = conn.cursor()
def isUserExist(username):
sqli = "select * from flaskdata where name='%s'" %(username)
res = cur.execute(sqli)
if res == 0:
return False
else:
return True
def isPasswdOk(username,passwd):
sqli = sqli = "select * from flaskdata where name='%s' and passwd='%s'" %(username, passwd)
res = cur.execute(sqli)
if res == 0:
return False
else:
return True
def addUser(username,passwd):
sqli = "insert into flaskdata (name, passwd) values('%s', '%s')" %(username, passwd)
try:
res= cur.execute(sqli)
conn.commit()
except Exception as e:
conn.rollback()
return e
if __name__ == "__main__":
addUser('root','root')
print(isUserExist('root'))
print(isPasswdOk('root','root'))
run.py:
import random
import functools
import os
from datetime import datetime
import psutil as psutil
from flask import Flask, request, render_template, redirect, url_for, abort, session
from models import isPasswdOk, isUserExist, addUser
app = Flask(__name__)
app.config['SECRET_KEY'] = random._urandom(24)
def is_login(f):
"""判断用户是否登陆的装饰器"""
@functools.wraps(f)
def wrapper(*args,**kwargs):
# run函数代码里面, 如果登陆, session加入user, passwd两个key值;
# run函数代码里面, 如果注销, session删除user, passwd两个key值;
# 如果没有登陆成功, 则跳转到登陆界面
if 'user' not in session:
return redirect(url_for('login'))
# 如果用户是登陆状态, 则访问哪个路由, 就执行哪个路由对应的视图函数;
return f(*args,**kwargs)
return wrapper
# 用户主页
@app.route('/')
def index():
return render_template('index.html')
# 用户登陆按钮
@app.route('/login/',methods=['GET','POST'])
def login():
if request.method == 'POST':
print(request.form)
user = request.form['user']
passwd = request.form['passwd']
if isPasswdOk(user,passwd):
# 将用户名和密码信息存储到session中;
session['user']=user
session['passwd']=passwd
return redirect(url_for('index'))
else:
return render_template('login.html',message='用户名或者密码错误')
else:
return render_template('login.html')
# 用户注销
@app.route('/logout/')
def logout():
session.pop('user',None)
session.pop('passwd',None)
return redirect(url_for('index'))
# 用户注册
@app.route('/register/',methods=['GET','POST'])
def register():
if request.method == 'POST':
user = request.form['user']
passwd = request.form['passwd']
if isUserExist(user):
message = "用户已经存在"
return render_template('signup.html',message=message)
else:
addUser(user,passwd)
return redirect(url_for('login'))
else:
return render_template('signup.html')
@app.route('/sysinfo/')
@is_login
def sysinfo():
info = os.uname()
boot_time = psutil.boot_time()
boot_time = datetime.fromtimestamp(boot_time)
now_time = datetime.now()
delta_time = now_time - boot_time
delta_time = str(delta_time).split('.')[0]
return render_template('sysinfo.html',
hostname=info.nodename,
sysname=info.sysname,
release=info.release,
version= info.version,
machine=info.machine,
now_time=str(now_time).split('.')[0],
boot_time=boot_time,
delta_time=delta_time
)
# 404异常处理: 类似于捕获异常
@app.errorhandler(404)
def not_found(e):
return render_template('404.html')
app.run(port=9002)