基于Flask框架的任务管理系统

基于Flask框架的任务管理系统

基本功能:

  • 任务的添加、删除、查看、编辑、点击完成
  • 用户登录、登出

数据库层

任务:

  • 任务id号
  • 任务名
  • 任务添加时间
  • 任务的状态(完成/未完成)
  • 任务所属部门(外键关联部门department.id)

部门

  • 部门id编号
  • 部门名称
  • 部门任务(关联Todo)
  • 部门用户(关联User)

用户

  • 用户id编号
  • 用户名
  • 用户密码
  • 邮箱地址
  • 联系方式
  • 用户简介
  • 注册时间
  • 所属部门
  • 用户操作日志

用户登录日志

  • 日志编号
  • 用户(关联user)
  • 登录主机ip
  • 登录时间
  • 登录地区
    models.py
import os
from flask import Flask
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime


#将models绑定app
app=Flask(__name__)

#配置ORM
app.config['SQLALCHEMY_DATABASE_URI']='mysql+pymysql://root:@localhost/TodoProject'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=True
#CSRF加密密钥验证,字符变量由24个随机字符组成
app.config['SECRET_KEY']=os.urandom(24)

#绑定Bootstrap前端框架,可以使用框架内简洁的样式
Bootstrap(app)

#实例化db对象
db=SQLAlchemy(app)


class Todo(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.now())  #创建任务的时间
    status=db.Column(db.Boolean,default=False) #任务的状态
    #外键关联部门的id
    department_id=db.Column(db.Integer,db.ForeignKey('department.id'))

class Department(db.Model):
    """一个关于部门的类"""
    id=db.Column(db.Integer,autoincrement=True,primary_key=True) #任务编号
    name=db.Column(db.String(50),unique=True)
    todos=db.relationship('Todo',backref='department')
    users=db.relationship('User',backref='department')

class User(db.Model):
    """一个关于用户的类"""
    id=db.Column(db.Integer,autoincrement=True,primary_key=True) #任务编号
    name=db.Column(db.String(20),unique=True)
    pwd=db.Column(db.String(100))
    email=db.Column(db.String(50),unique=True)
    phone=db.Column(db.String(20),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'))
    userlog=db.relationship('Userlog',backref='user')

class Userlog(db.Model):
    """一个关于用户登录日志的类"""
    id=db.Column(db.Integer,autoincrement=True,primary_key=True) #任务编号
    user_id=db.Column(db.Integer,db.ForeignKey('user.id'))
    ip=db.Column(db.String(100))
    add_time=db.Column(db.DateTime,default=datetime.now())
    areas=db.Column(db.String(100))

if __name__=='__main__':
    #创建数据库表
    db.create_all()

form表单

  • 用户登录表单和任务编辑表单

forms.py

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


class LoginForm(FlaskForm):
    name=StringField(
        label="用户名",
        validators=[
            DataRequired("请输入用户名"),
            Length(6,10)
        ],
        render_kw={
            'placeholder':"请输入用户名"
        }
    )


    passwd=PasswordField(
        label="用户密码",
        validators=[
            DataRequired("请输入用户密码"),
            Length(6,10)
        ],
        render_kw={
            'placeholder':"请输入用户密码"
        }
    )

    submit=SubmitField(
        render_kw={
            'value':'登陆',
            'class':'btn btn-success pull-right'
        }
    )

#任务编辑
class EditForm(FlaskForm):
    name=StringField(
        label='任务名称',
        validators=[
        DataRequired()
        ]
    )

    department=StringField(
        label="所属部门",
        validators=[
            DataRequired()
        ]
    )

    submit=SubmitField(
        render_kw={
            'value':'完成',
            'class':'btn btn-success pull-right'
        }
    )

视图函数层

基本内容:

  • 首页
  • 用户登录
  • 用户退出
  • 任务列表
  • 任务添加
  • 任务删除
  • 任务编辑
    views.py
import json
import time
from functools import wraps
from urllib.request import urlopen

from models import app, Userlog, User, Todo, Department, db
from flask import render_template, redirect, request, url_for, flash, session
from forms import LoginForm,EditForm
from werkzeug.security import check_password_hash,generate_password_hash


#首页
@app.route('/')
def headpage():
    return render_template('base.html')


#获取用户登陆主机ip的所在地
def get_ip_area(ip):
    url='http://ip.taobao.com/service/getIpInfo.php?ip=%s' %(ip)
    json_data=urlopen(url).read().decode('utf-8')
    s_data=json.loads(json_data)
    country=s_data['data']['country']
    if country=='xx':
        country==''
    city=s_data['data']['city']
    if city=='xx':
        city==''
    return country+city


#判断用户是否登陆,检测会话中有无用户信息
def islogin(f):
    @wraps(f)
    def wrapper(*args,**kwargs):
        if not 'user' in session:
            return redirect(url_for('login'))
        return f(*args,**kwargs)
    return wrapper


#登陆页面
@app.route('/login/',methods=['POST','GET'])
def login():
    forms=LoginForm()
    if request.method=='POST':
        u=User.query.filter_by(name=forms.name.data).first()
        pwd=forms.passwd.data
        if u and check_password_hash(u.pwd,pwd):
            session['user']=u.name
            session['user_id']=u.id
            userlog=Userlog(
                user_id=u.id,
                ip=request.remote_addr,
                areas=get_ip_area(request.remote_addr)
            )
            db.session.add(userlog)
            db.session.commit()
            flash('登陆成功','ok')
            time.sleep(2)
            return redirect('/list/')
        else:
            flash('用户名或密码错误','error')
    return  render_template('login.html',forms=forms)


#任务列表
@app.route('/list/<int:page>',methods=['GET','POST'])
@app.route('/list/')
@islogin
def list(page=1):
    todos = Todo.query.paginate(page,per_page=4)
    parts = Department.query.all()
    return render_template('list.html',todos=todos,parts=parts)


#点击完成
@app.route('/todoup/<int:page>/<int:id>')
@app.route('/todoup/')
@islogin
def todoup(page,id):
    todo=Todo.query.filter_by(id=id).first()
    todo.status=1
    db.session.add(todo)
    db.session.commit()
    return redirect(url_for('list',page=page))

#点击未完成
@app.route('/tododown/<int:page>/<int:id>')
@app.route('/tododown/')
@islogin
def tododown(page,id):
    todo=Todo.query.filter_by(id=id).first()
    todo.status=0
    db.session.add(todo)
    db.session.commit()
    return redirect(url_for('list',page=page))


#删除操作
@app.route('/delete/<int:page>/<int:id>')
@islogin
def delete(page,id):
    todo=Todo.query.filter_by(id=id).first()
    db.session.delete(todo)
    db.session.commit()
    return redirect(url_for('list',page=page))


#编辑操作
@app.route('/edit/<int:page>/<int:id>',methods=['GET','POST'])
@islogin
def edit(page,id):
    forms=EditForm()
    todo = Todo.query.filter_by(id=id).first()
    oldname = todo.name
    oldpart=todo.department_id
    if request.method == 'POST':
        todo.name=forms.name.data
        todo.department_id=forms.department.data
        db.session.add(todo)
        db.session.commit()
        return redirect(url_for('list', page=page))
    forms.name.data=oldname
    forms.department.data=oldpart
    return render_template('edit.html',forms=forms)

#添加任务
@app.route('/add/',methods=['POST'])
@islogin
def add():
    todoname=request.form['name']
    part=request.form['part']
    todo=Todo(name=todoname,department_id=part)
    db.session.add(todo)
    db.session.commit()
    return redirect(url_for('list'))

#用户注销
@app.route('/logout/')
@islogin
def logout():
    session.pop('user',None)
    session.pop('user_id',None)
    return redirect(url_for('login'))

#用户注销
@app.route('/backhead/')
@islogin
def backhead():
    session.pop('user',None)
    session.pop('user_id',None)
    return redirect(url_for('headpage'))



app.run(port='9000')

模板层

templates

├── base.html  #基础模版:导航栏
├── edit.html   #编辑任务模版(继承base.html)
├── list.html   #任务列表模版(继承base.html)
├── login.html  #用户登录模版(继承base.html)
└── macro   #自定义的宏,用于分页列表的操作
    └── page.html

base.html

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

{% block titie %}
    首页
{% 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="/"><span style="font-size: x-large">用户管理</span></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>{% block headpage %}
                 {% endblock %}</li>
            </ul>

            <ul class="nav navbar-nav navbar-right">
                <form class="navbar-form navbar-left">
                    <div class="form-group">
                        <input type="text" class="form-control" placeholder="Search">
                    </div>
                    <button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-search"></span>
                    </button>
                </form>
                <li><a href="/login/" style="font-size: x-large">登录</a></li>
                {% block loginout %}

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

{% endblock %}

{% block content %}

{% endblock %}

login.html

{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}

{% block titie %}
    用户登录
{% endblock %}

{% block content %}
    <div class="col-md-4 col-md-offset-4">
        <h1 align="center">用户登录</h1>

        {% for msg in get_flashed_messages(category_filter='ok') %}
            <div class="alert alert-success alert-dismissable">
                <button type="button" class="close" data-dismiss="alert"
                        aria-hidden="true">
                    &times;
                </button>
                {{ msg }}
            </div>
        {% endfor %}
        {% for msg in get_flashed_messages(category_filter='error') %}
            <div class="alert alert-success alert-dismissable">
                <button type="button" class="close" data-dismiss="alert"
                        aria-hidden="true">
                    &times;
                </button>
                {{ msg }}
            </div>
        {% endfor %}

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


{% endblock %}

list.html

{% extends 'base.html' %}

{% block titie %}
    处理任务
{% 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="/"><span style="font-size: x-large">用户管理</span></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>{% block headpage %}
                 {% endblock %}</li>
            </ul>

            <ul class="nav navbar-nav navbar-right">
                <form class="navbar-form navbar-left">
                    <div class="form-group">
                        <input type="text" class="form-control" placeholder="Search">
                    </div>
                    <button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-search"></span>
                    </button>
                </form>
                <li><a href="/logout/" style="font-size: x-large">注销</a></li>
                <li><a href="/backhead/" style="font-size: x-large">返回首页</a></li>
                {% block loginout %}

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

{% block content %}
    <br>
    <br>
    <form action="{{ url_for('add') }}" class="form-horizontal" method="post">
        <div class="form-group">
            <div class="col-lg-offset-3 col-lg-4 col-sm-5">
                <input type="text" class="form-control" placeholder="清添加任务" required="required" name="name">
            </div>
            <div class="col-lg3 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-lg3 col-sm-2">
                <input type="submit" class="btn btn-primary" value="完成">
            </div>
        </div>
    </form>


    <div class="col-lg-offset-3 col-lg-6">
    <table class="table table-striped">
      <caption><h1>任务列表</h1></caption>
      <thead>
        <tr>
            <th>任务编号</th>
            <th>任务名称</th>
            <th>创建时间</th>
            <th>所属部门</th>
            <th>任务状态</th>
            <th>操作</th>
        </tr>
      </thead>
      <tbody>
      {% for todo in todos.items %}
        <tr>
        <td>{{ todo.id }}</td>
          <td>{{ todo.name }}</td>
          <td>{{ todo.add_time }}</td>
          <td>{{ todo.department.name }}</td>

          <td>
            {% if todo.status %}
                <a href="{{ url_for('tododown',id=todo.id,page=todos.page) }}" class="btn btn-success" role="button">已完成</a>
            {% else %}
                <a href="{{ url_for('todoup',id=todo.id,page=todos.page) }}" class="btn btn-warning" role="button">未完成</a>
            {% endif %}
          </td>
            <td>
                <a href="{{ url_for('delete',id=todo.id,page=todos.page) }}" class="btn btn-warning" role="button">删除</a>
                <a href="{{ url_for('edit',id=todo.id,page=todos.page) }}" class="btn btn-primary" role="button">编辑</a>
            </td>
        </tr>
      {% endfor %}
      </tbody>
    </table>
    {% from 'macro/page.html' import paginate %}
        {{ paginate('list',todos) }}
</div>
{% endblock %}

macro/page.html

{% macro paginate(funame,dataObj) %}
    <ul class="pagination pull-right">


{#      点击前一页   #}
    <li>
        {% if dataObj.has_prev %}
            <li><a href="{{ url_for(funame,page=dataObj.prev_num) }}">&laquo;</a></li>
        {% else %}
            <li class="disabled"><a href="#">&laquo;</a></li>
        {% endif %}
    </li>
{#            点击首页#}
        <li><a href="{{ url_for(funame,page=1) }}">首页</a></li>
    <li>
{#        中间页列表,如果页数太多无法显示,则用...代替#}
        {% for v in dataObj.iter_pages() %}
            {% if v==dataObj.page %}
                <li class="active"><a href="{{ url_for(funame,page=v) }}">{{ v }}</a></li>
            {% elif v==None %}
                <li class="disabled"><a href="#">...</a></li>
            {% else %}
                <li><a href="{{ url_for(funame,page=v) }}">{{ v }}</a></li>
            {% endif %}
        {% endfor %}
    </li>

    {#        点击尾页#}
{#        <li><a href="{{ url_for(funame,page=dataObj.) }}">首页</a></li>#}
    <li>
        {#      点击下一页   #}
        {% if dataObj.has_prev %}
            <li><a href="{{ url_for(funame,page=dataObj.next_num) }}">&raquo;</a></li>
        {% else %}
            <li class="disabled"><a href="#">&raquo;</a></li>
        {% endif %}
    </li>
    </ul>

{% endmacro %}

edit.html

{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}

{% block titie %}
编辑任务
{% endblock %}

{% block content %}
    <div class="col-md-4 col-md-offset-4">
        <h1 align="center">编辑任务</h1>

        {% for msg in get_flashed_messages(category_filter='ok') %}
            <div class="alert alert-success alert-dismissable">
                <button type="button" class="close" data-dismiss="alert"
                        aria-hidden="true">
                    &times;
                </button>
                {{ msg }}
            </div>
        {% endfor %}
        {% for msg in get_flashed_messages(category_filter='error') %}
            <div class="alert alert-success alert-dismissable">
                <button type="button" class="close" data-dismiss="alert"
                        aria-hidden="true">
                    &times;
                </button>
                {{ msg }}
            </div>
        {% endfor %}

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

猜你喜欢

转载自blog.csdn.net/mashaokang1314/article/details/81272462