基于Python的IT设备管理系统

7 篇文章 0 订阅
2 篇文章 1 订阅

基于Python的IT设备管理系统

近年来,越来越多的企事业单位把业务迁移到Web上完成,随之而来的是数据量的爆炸式增长。数据信息的繁杂,必然要求一个有序的管理系统,而数据库则是管理大量信息最有效的方式,用户通过WEB向服务端程序递交查询请求,服务端程序在数据库中检索,将结果生成的页面展现在用户面前,直观而有效。如何让系统提供安全、稳定、高效的服务,无疑是Web应用系统开发人员必须再三思考的问题,因为这关系到用户的体验,进而关系到系统的成败。

本文以基于Python的IT设备管理系统为例,使用Flask框架,结合MySQL数据库,针对管理系统登录功能、查询功能、信息分页显示功能等,展开研究,进而采用合理的技术,以达到对IT设备查询,增加,修改和删除的目的。

在这里插入图片描述

RBAC(Role Based Access Control,基于角色的访问控制)通过建立“权限<->角色”的关联实现将权限赋予给角色,并通过建立“角色<->用户”的关联实现为用户指定角色,从而使用户获得相应角色所具有的权限。RBAC的基本思想就是给用户指定角色,这些角色中定义了允许用户操作哪些系统功能以及资源对象。

由于权限与用户的分离,RBAC具有以下优势:

· 管理员不需要针对用户去逐一指定权限,只需要预先定义具有相应权限的角色,再将角色赋予用户即可。因此RBAC更能适应用户的变化,提高了用户权限分配的灵活性。

· 由于角色与用户的关系常常会发生变化,但是角色和权限的关系相对稳定,因此利用这种稳定的关联可减小用户授权管理的复杂性,降低管理开销。

系统流程:用户注册,分配默认角色(注册用户),登录,按类别查询,按部门查询,按存放位置查询,模糊查询,修改用户信息,修改密码,忘记密码(系统发送邮件到用户邮箱),修改用户角色(需要管理员审核)为管理员,管理员对数据增加,修改,删除。

初始数据:数据库角色表添加用户和管理员两个角色,类别表添加各个类别,部门表添加各个部门,位置表添加各个存放位置。

目录结构:

在这里插入图片描述

在这里插入图片描述

MySQL数据库

信息管理是一项复杂、系统的工作,需要加强对各种信息管理技术的应用,而计算机数据库技术在信息管理方面发挥着重要的作用。数据库技术适于对大量的数据进行组织管理,Web技术拥有较好的信息发布途径,这两种技术天然的互补性,决定其相互融合成为技术发展的必然趋势。将Web技术与数据库技术相结合,开发动态的Web数据库应用,已成为当今Web技术研究的热点所在。MySQL数据库功能强大、处理速度快、占用资源小、跨平台、安全性好、运行稳定可靠,已被全世界范围各领域用户广泛应用,另外支持它的资源极其丰富如各种库、可视管理工具等。

数据库表

数据库设计是系统开发中非常重要的一个环节,之所以强调数据库的重要性,是因为数据库设计就像在建设高楼大厦的根基一样,如果设计不好,在后来的系统维护、变更和功能扩充时,将会引起比较大的问题,大量工作将会重新进行。通过分析系统功能,再结合IT办公设备,可以建立以下几个表。

办公设备表:设备名称,规格型号,主要参数,IP地址,使用人,所属类别,所属部门,存放位置,设备编号,设备状态,购买日期,购买成本

部门表:部门名称,部门负责人

类别表:类别名称

位置表:位置名称

用户表:用户名,密码,邮箱

角色表:角色名

数据库E-R图

根据数据库表中的实体和它们之间的关系,可以画出E-R图。

设备:

在这里插入图片描述

用户:

在这里插入图片描述

数据库模型类

如果使用原生SQL的方式来操作数据库,随着项目代码量的提升会发现有大量操作数据库的冗余代码,以及大量SQL语句与逻辑代码杂糅在一起不利于核心代码的读写,并且SQL语句的利用率不高。当更换数据库产品时有些特定SQL语句需要重写,而使用ORM框架能很好的避免上述问题。ORM(Object Relational Mapping),叫作对象关系映射,它把一个类映射成数据库里的一张表,而属性映射成数据库表的列,每一个实例对象对应数据库表里的一行数据。通过它我们可以直接使用面向对象的方式来编写程序,而不再直接书写原生的SQL语句,而且大部分ORM框架支持多种数据库,只需很少的配置即可完成数据库产品的更换。

在Python中,最有名的ORM框架是SQLAlchemy。 而在Flask框架中可以使用Flask-SQLAlchemyFlask-SQLAlchemy是为了简化SQLAlchemy进行的封装。

设备模型类:

# office/models.py
from ..exts import db

# 部门和办公设备的关系 一个部门可以使用多个办公设备 一个办公设备只能被一个部门使用 一对多的关系
class Department(db.Model):
    __tablename__ = "department"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(20), nullable=False, index=True, unique=True)
    leader = db.Column(db.String(10), nullable=False, index=True)

    def __init__(self, name, leader):
        self.name = name
        self.leader = leader

    def __repr__(self):
        return "<Department(name:%s,leader:%s)>" % (self.name, self.leader)


# 类别和办公设备的关系 一个类别可以有多个办公设备 一个办公设备只能属于一个类别 一对多的关系
class Category(db.Model):
    __tablename__ = "category"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(20), nullable=False, index=True, unique=True)

    def __init__(self, name):
        self.name = name

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


# 存放位置和办公设备的关系 一对多的关系
class Location(db.Model):
    __tablename__ = "location"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(20), nullable=False, index=True, unique=True)

    def __init__(self, name):
        self.name = name

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


class Equipment(db.Model):
    __tablename__ = "equipment"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(10), nullable=False, index=True)
    model = db.Column(db.String(20))
    parameter = db.Column(db.String(30))
    user = db.Column(db.String(30))
    IPaddress = db.Column(db.String(20))
    asset_number = db.Column(db.String(20), nullable=False, unique=True)
    asset_status = db.Column(db.Enum("在用", "闲置", "报废", "转移"), nullable=False)
    purchase_date = db.Column(db.Date)
    purchase_cost = db.Column(db.Float)
    department_id = db.Column(db.Integer, db.ForeignKey(
        'department.id'), nullable=False)
    category_id = db.Column(db.Integer, db.ForeignKey(
        'category.id'), nullable=False)
    location_id = db.Column(db.Integer, db.ForeignKey(
        'location.id'), nullable=False)

    department = db.relationship("Department", backref="equipments")
    category = db.relationship("Category", backref="equipments")
    location = db.relationship("Location", backref="equipments")

    def __init__(self, name, asset_number, asset_status, department_id, category_id, location_id):
        self.name = name
        self.asset_number = asset_number
        self.asset_status = asset_status
        self.department_id = department_id
        self.category_id = category_id
        self.location_id = location_id

    def __repr__(self):
        return "<Equipment(name:%s,asset_number:%s,asset_status:%s)>" % (self.name, self.asset_number, self.asset_status)

用户模型类:

# user/models.py
from ..exts import db
from flask_login import UserMixin
# mysql 日期设置默认值必须使用timestamp类型
from sqlalchemy.sql.sqltypes import TIMESTAMP
from sqlalchemy.sql import func


class User(db.Model, UserMixin):
    __tablename__ = "user"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(18), nullable=False, unique=True)
    password = db.Column(db.String(64), nullable=False)
    email = db.Column(db.String(60), nullable=False)
    create_time = db.Column(
        TIMESTAMP, server_default=func.now(), nullable=False)
    role_id = db.Column(db.Integer, db.ForeignKey(
        'role.id'), nullable=False, default=1)

    role = db.relationship("Role", backref="users")

    def __init__(self, username, password, email):
        self.username = username
        self.password = password
        self.email = email

    def __repr__(self):
        return "<User(username: %s,email:%s,role:%s)>" % (self.username, self.email, self.role.name)


class Role(db.Model):
    __tablename__ = "role"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(18), nullable=False, unique=True, index=True)

    def __init__(self, name):
        self.name = name

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

映射到数据库

有了数据库模型类,并没有在数据库中生成表,这时候需要启动app,用SQLAlchemy()将模型类生成表。

# run.py
import os
from flask import request, render_template
from settings import DevelopConfig, ProductConfig, LOG_PATH
from apps import create_app
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from apps.exts import db
import logging
from logging.handlers import RotatingFileHandler
logger = logging.getLogger()
logger.setLevel(level=logging.DEBUG)
handler = RotatingFileHandler(
    LOG_PATH+os.sep+"app.log", mode='a', maxBytes=10485760, backupCount=2, encoding="utf-8")
handler.setLevel(logging.INFO)
formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y/%m/%d %H:%M:%S')
handler.setFormatter(formatter)
logger.addHandler(handler)

app = create_app(DevelopConfig)
logger.info("app初始化成功")


@app.errorhandler(404)
def page404(e):
    logger.warning("用户在请求%s页面时出错", request.url)
    return render_template('errors/404.html'), 404


@app.errorhandler(500)
def page500(e):
    logger.warning("用户在请求%s页面时出错", request.url)
    return render_template('errors/500.html'), 500


manager = Manager(app)
migrate = Migrate(app, db)  #  db=SQLAlchemy()
manager.add_command("db", MigrateCommand)

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

通过命令初始化数据库,生成表。

python run.py db init

python run.py db migrate

python run.py db upgrade

登录功能

登录功能对于任何管理系统都是至关重要的。登录页面最基本的元素就是两个输入框,输入用户名和密码,一个提交按钮。当用户输入完成后,点击提交按钮,通过验证后实现登录。验证就是检验用户输入的用户名和密码是否正确,包括检验用户输入的字符长度,是否含有非法字符,是否符合数据库字段的要求等,可以通过正则表达式写验证规则。

用户名验证

验证可以前端验证,也可以后端验证。前端验证可以减少后端服务器的压力。前端验证就是使用javascript脚本,比如验证字符串长度,非法字符,这些验证是否通过,前端可以直接处理,再把消息显示在页面上,让用户知道哪里出错。在考虑用户使用方面,前端验证可以在用户输入每一项完成时就验证,而不是将所有信息输入完成后,再点击提交按钮时验证。这样做的好处就是可以实时的给用户反馈,以便用户做出更改。这里就可以通过输入框的获得和失去焦点事件处理。

// 用户名输入框获得焦点
$("#login-username").focus(function () {
	// 清空输入框的占位文字
    $("#login-username").attr("placeholder", "");
    // 清空上一次输入错误的提示文字
    $("#uname_tips").text("");
    // 移除上一次输入错误的样式
    $("#login-username").removeClass("form-control is-invalid")
})
// 用户名输入框失去焦点
$("#login-username").blur(function () {
    username = $("#login-username").val();
    // 用户名未输入,显示占位文字
    if (username.length == 0) {
        $("#login-username").attr("placeholder", "请输入用户名/手机号");
        flagName = false;
        // 用户名已输入,对用户名判断验证
    } else {
        if (/[\ \'\"\%\#\^\*\@\!]/g.test(username)) {
            $("#uname_tips").text("用户名含有非法字符")
            flagName = false;
        } else if (username.length > 18 || username.length < 6) {
            $("#uname_tips").text("请输入6~18个字符")
            flagName = false;
        } else {
            // 向后端服务器发送用户名,后端服务器查询数据库
            $.post("{{url_for('user.verifi_username')}}", { username: username }, function (data) {
                // 用户不存在
                if (data.code == 4000) {
                    $("#uname_tips").text(data.msg)
                    flagName = false;
                    // 用户存在
                } else {
                    flagName = true;
                }
            })
        }
    }
})

密码验证

只做了个密码长度验证。

/*验证密码*/
$("#login-password").focus(function () {
    // 清空输入框的占位字符
    $("#login-password").attr("placeholder", "");
    $("#pwd_tips").text("")
    $("#login-password").removeClass("form-control is-invalid")
})
$("#login-password").blur(function () {
    password = $("#login-password").val();
    if (password.length == 0) {
        $("#login-password").attr("placeholder", "请输入密码");
        flagPas = false;
    } else {
        if (password.length < 6 || password.length > 18) {
            $("#pwd_tips").text("请输入6~18个字符")
            flagPas = false;
        } else {

            flagPas = true;
        }
    }

})

提交按钮功能

输入正确的前提下,再通过ajax技术,把用户名和密码传给后端服务器,后端服务器可以做进一步的验证,通过查询数据库,判断用户是否存在,如果存在则通过用户名在数据库中查询密码,将密码和前端传来的密码比较,检测密码通过与否,将消息传回前端,前端再根据消息判断是否跳转页面。如果用户不存在,也将消息传回前端,前端根据消息,做出决策。

用户密码的输入要考虑安全问题,除了输入框的type属性改成password外,在网络传输时还要考虑加密,以防被网络抓包。将用户输入的密码,先通过md5加密,再通过ajax的post()方法,将用户名和加密的密码传到后端。

// 用户名和密码都通过验证
if (flagName && flagPas) {
    // 用md5加密
    var password_md5 = $.md5(password);
    // 网络请求,传送用户名和密码密文到后端服务器
    $.post("{{url_for('user.login')}}", { username: username, password: password_md5 }, function (data) {
        // 密码正确,登录成功
        if (data.code == 200) {
            window.location.href = "{{url_for('office.detail')}}"
            // 密码错误,错误消息显示在页面上
        } else {
            $("#pwd_tips").text(data.msg)
        }
    })
} else {
    if (!flagName) {
        $("#login-username").addClass("form-control is-invalid");
        return;
    }
    if (!flagPas) {
        $("#register-password").addClass("form-control is-invalid");
        return;
    }
}

记录用户登录状态

Web应用程序是使用HTTP协议传输数据的,因为 HTTP 请求是无状态的,所以为了把一个 HTTP 请求跟其他 HTTP 请求关联起来,让服务器端知道当前在跟它对话的客户端是谁,我们就需要有一种机制来保存这些请求之间的用户数据,这就是会话。 “记录用户登录状态”其实就是“保持一个会话”。

在Flask中可以使用第三方库flask-login记录登录状态,用户名和密码正确后,将用户对象作为参数传入login_user()中。

@user_bp.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")
        user = User.query.filter(User.username == username).first()
        if user.password == password:
            # 记录用户登录状态
            login_user(user)
            # session['_user_id'] = user_id-> user_id=getattr(user, current_app.login_manager.id_attribute)()->LoginManager(object)中self.id_attribute = ID_ATTRIBUTE
            # ID_ATTRIBUTE = 'get_id'-> getattr(user, "get_id")()=user.get_id()->用户模型类中的get_id() return text_type(self.id)->text_type(self.id)=str(user.id)->session["_user_id"]="id值"
            RET = {
                "code": 200,
                "msg": "登录成功",
            }
            logger.info("%s登录成功", current_user.username)
        else:

            RET = {
                "code": 201,
                "msg": "密码错误"
            }
            logger.info("%s登录失败", username)
        return jsonify(RET)

    return render_template("user/login.html")

要使用login_user(),在用户模型类中要有get_id()方法,用户模型类可以继承UserMixin(),用户表字段还要有id。

login_user()执行后,将user.id,fresh=True,生成的id放到session中。

在这里插入图片描述

查询和分页显示

当我们查询的数据信息条目非常多时,页面就会变得很长,浏览它们将会非常不方便,同时查询所用的时间也会很长,对数据库压力也很大。这时如果我们采用分页显示技术就能很好的解决这个问题。

分页显示技术即将要显示的数据信息分页显示,分批查询,在每个页面显示一定数量的数据信息,这样用户查看起来将会非常方便,同时数据库一次查询条目也不大,页面响应速度快,用户体验更好。分页技术有很多种,这里采用的是flask_sqlalchemy中的paginate()方法。Paginate()方法可以接受两个参数,第一个参数指示返回第几页的内容,第二个参数表示每页展示的对象数量。返回结果是一个pagination类对象,遍历pagination.items,可以得到每一条查询结果。

按类别、部门、位置查询

用户登录成功后,跳转到第一个页面,默认显示第一个类别的详细数据。

在这里插入图片描述

类别、部门、位置数据是建立数据库的初始数据,设备详情列表是通过系统增加功能添加的。后端服务器将查询结果,以关键字参数的形式传到前端,前端通过模板渲染到页面上,Flask使用的是Jinja2模板引擎。

# office/view.py
...
@office_bp.route("/detail")
def detail():
    category_list = Category.query.order_by(Category.id).all()
    dep_list = Department.query.all()
    loc_list = Location.query.all()
    category_id_list = []
    for category in category_list:
        category_id_list.append(category.id)
    category_id = request.args.get("category", 1)
    try:
        if int(category_id) not in category_id_list:
            return redirect(url_for('office.detail'))
    except:
        return redirect(url_for('office.detail'))
    else:
        page = request.args.get("page", 1)

        pagination = db.session.query(Equipment).filter(
            Equipment.category_id == int(category_id)).paginate(int(page), 15)

        cat_name = db.session.query(Category.name).filter(
            Category.id == int(category_id)).first()
        return render_template("office/detail.html", pagination=pagination, category_list=category_list, dep_list=dep_list, loc_list=loc_list,
                               category_id=int(category_id), category_id_list=category_id_list, cat_name=cat_name[0])
...

正常查询,从url参数中取到类别和页数,如果没有类别和页数参数,就设置个默认值。正常的url有http://127.0.0.1:5000/detail,http://127.0.0.1:5000/detail?category=2,http://127.0.0.1:5000/detail?category=2&page=2。如果url为http://127.0.0.1:5000/detail?category=244没有这个类别,或者http://127.0.0.1:5000/detail?category=ad时,就重定向到http://127.0.0.1:5000/detail。

前端需要什么数据,后端就传什么数据。

<div class="design" id="leftcolumn">

    <h2 class="left">所属类别<span class="left_h2"></span></h2>
    {% for cat in category_list %}
    <a href="{{url_for('office.detail')}}?category={{cat.id}}" {% if cat_name==cat.name
       %}style="color:red;" {%endif%}>
        {{cat.name}}</a>
    {% endfor %}
    <h2 class="left">所属部门<span class="left_h2"></span></h2>
    {% for dep in dep_list %}
    <a href="{{url_for('office.department')}}?department={{dep.id}}">
        {{dep.name}}</a>
    {% endfor %}

    <h2 class="left" style="margin-top:4px">存放位置<span class="left_h2"></span></h2>
    {% for loc in loc_list %}
    <a href="{{url_for('office.location')}}?location={{loc.id}}">
        {{loc.name}}</a>
    {% endfor %}

</div>

同一个页面,可以显示不同的样式,也可以显示不同的格式。

{%for item in pagination.items%}
<tr {%if item.asset_status=='闲置' %}class="warning" {%elif item.asset_status=='报废'
                                %}class="danger" {%endif%}>
    <td>{{loop.index}}</td>
    <td>{{item.name}}</td>
    <td>{{item.model}}</td>
    {%if category_id==8%}
    <td>{{item.parameter}}</td>
    {%endif%}
    <td>{{item.department.name}}</td>
    <td>{{item.location.name}}</td>
    {%if category_id==8%}
    <td>{{item.user}}</td>
    <td>{{item.IPaddress}}</td>
    {%endif%}
    <td>{{item.asset_number}}</td>
    <td>{{item.asset_status}}</td>
    <td>{%if item.purchase_date != None%}{{get_months(item.purchase_date)}}{%endif%}</td>

    <td><a title="修改" href="{{url_for('office.mod',asset_number=item.asset_number)}}"><img
                                                                                           alt="修改" style="width: 16px;height: 16px; margin-left: 10px;"
                                                                                           src="{{url_for('static',filename='img/修改.png')}}" />
        </a>&nbsp;<a title="删除"
                     href="{{url_for('office.delete',asset_number=item.asset_number)}}"
                     onclick="javascript:return del();"><img alt="删除"
                                                             style="width: 16px;height: 16px; margin-left: 10px;"
                                                             src="{{url_for('static',filename='img/删除.png')}}" />
        </a></td>
</tr>
...

分页显示

Pagination类对象又封装了很多有用的属性和方法,比如pagination.has_prev和pagination.has_next可以判断是否有上一页和下一页,根据返回值,我们可以限制能否向上或向下翻页。Pagination.prev_num和pagination.next_num属性值是当前页的上一页和下一页的数值。Pagination.pages的属性值是查询结果总的页数。Pagination.iter_pages()方法可以迭代出页码数,并不是每一页。默认参数保证左边缘有两页,右边缘有两页,当前页的左边有两页,当前页的右边有四页。 left_edge=2 , left_current=2, right_current=5, right_edge=2

在这里插入图片描述

<ul class="pagination">
    {% if pagination.has_prev %}
    <li><a
           href="{{url_for('office.detail')}}?category={{category_id}}&page={{pagination.prev_num}}">&laquo;</a>
    </li>
    {%else%}
    <li class="disabled"><span>&laquo;</span></li>
    {%endif%}
    {% for i in pagination.iter_pages() %}
    {%if i%}
    {%if pagination.page==i%}
    <li class="active">
        <span>{{i}} <span class="sr-only">(current)</span></span>
    </li>
    {%else%}
    <li><a href="{{url_for('office.detail')}}?category={{category_id}}&page={{i}}">{{i}} <span
                                                                                               class="sr-only">(current)</span></a>
    </li>
    {%endif%}
    {%else%}
    <li><span>...</span></li>
    {%endif%}
    {%endfor%}
    {% if pagination.has_next %}
    <li><a
           href="{{url_for('office.detail')}}?category={{category_id}}&page={{pagination.next_num}}">&raquo;</a>
    </li>
    {%else%}
    <li class="disabled"><span>&raquo;</span></li>
    {%endif%}
</ul>

模糊查询

搜索输入框输入查找内容,通过GET或POST方式,将查找内容传到后端,后端将查询结果显示到新的页面。

from sqlalchemy import or_
...
@office_bp.route("/search", methods=["GET", "POST"])
def search():
    if request.method == "POST":
        category_list = Category.query.order_by(Category.id).all()
        dep_list = Department.query.all()
        loc_list = Location.query.all()
        find_content = request.form.get("search")
        pagination = db.session.query(Equipment).filter(or_(Equipment.asset_number.like(
            f"%{find_content}%"), Equipment.name.like(f"%{find_content}%"))).paginate(1, 15)
        return render_template("office/search.html", pagination=pagination, category_list=category_list, find_content=find_content, dep_list=dep_list, loc_list=loc_list)
    category_list = Category.query.order_by(Category.id).all()
    dep_list = Department.query.all()
    loc_list = Location.query.all()
    find_content = request.args.get("find_content")
    page = request.args.get("page", 1)
    pagination = db.session.query(Equipment).filter(or_(Equipment.asset_number.like(
        f"%{find_content}%"), Equipment.name.like(f"%{find_content}%"))).paginate(int(page), 15)
    return render_template("office/search.html", pagination=pagination, category_list=category_list, find_content=find_content, dep_list=dep_list, loc_list=loc_list)
...

输入框name=“search”,Flask通过request.form.get("search")获得前端传来的数据,将查找内容与数据库中的一个或多个字段值匹配,or_(Equipment.asset_number.like(f"%{find_content}%"), Equipment.name.like(f"%{find_content}%")),前后%表示以查找内容为开头和结尾的都可以。sqlalchemyor_方法表示或者关系,and_表示并且关系。查询结果还是pagination对象,数据比较多时,前端好处理。

增加数据功能

增加功能的链接在每个类别的最后一页,最后一条数据后面。

{%if pagination.page==pagination.pages and loop.last%}
<tr>
    <td><a title="增加" href="{{url_for('office.add')}}?category={{category_id}}"><img
                                                                                     alt="增加" style="width: 16px;height: 16px;"
                                                                                     src="{{url_for('static',filename='img/加号.png')}}" /></a></td>
    ...

在这里插入图片描述

增加功能是管理系统的一个很重要的功能,也是很复杂的一个功能。增加功能大致分为三个步骤,第一用户从前端页面提交数据,第二服务器接收并处理数据,第三处理完成写入数据库。这也符合MVC模式,M是指业务模型,对应的是第三步,处理完成写入数据库。C是控制器,对应的是第二步,服务器接收并处理数据,Flask框架主要做是就是这一层的事务。V是指用户界面,对应的是第一步,用户从前端页面提交数据。

增加数据页面

为用户提供一种交互式操作的界面,接收用户输入的数据。用户输入数据,有些为必填项,对应数据库中不能为空的字段,有些为选填项,对应数据库中可以为空的字段。用户输入的所有数据,都要做验证处理,以适应数据库中字段类型要求,否则不能写入数据库。

在这里插入图片描述

设备编号是唯一的,当用户输入完成,需要查询数据库中是否有这个编号,若数据库中没有,则可以使用。通过输入框失去焦点事件处理,发送ajax请求。

$.post("/asset_number_verifi/" + asset_number, {}, function (data) {
    if (data.code == 0) {

    } else {
        // 资产编号已存在

        $("#asset_number_tips").text(data.msg)
        return
    }
})
# 增加前先验证资产编号是否唯一
@office_bp.route("/asset_number_verifi/<asset_number>", methods=["POST"])
def asset_number_verifi(asset_number):
    equ_obj = db.session.query(Equipment).filter(
        Equipment.asset_number == asset_number).first()
    if equ_obj:
        RET = {
            "code": 1,
            "msg": "资产编号已存在"
        }
        return jsonify(RET)
    else:
        RET = {
            "code": 0,
            "msg": "资产编号可以使用"
        }
        return jsonify(RET)

下拉选择框的选中值select_loc = $("[name='select_loc'] option:checked").val();,单选框的选中值asset_status = $("[name='asset_status']:checked").val(),购买日期和购买成本数据库中是date和float类型,前端为空值时是字符串类型,为避免错误,设置相应类型的默认值。

function getDateStr(date) {
    var month = date.getMonth() + 1;
    var strDate = date.getFullYear() + '-' + month + '-' + date.getDate();
    return strDate;
}
var d = new Date();
purchase_date = $("[name='purchase_date']").val() ? $("[name='purchase_date']").val() : getDateStr(d);
purchase_cost_float = $("[name='purchase_cost']").val() ? parseFloat($("[name='purchase_cost']").val()) : 0;

Flask接收数据并写入数据库

增加功能是管理员角色才能使用的功能,进入视图函数前要检查用户的角色,可以用装饰器的方法。角色装饰器要放在login_required后,只有登录后的用户才有角色属性。

def role_required(func):
    @wraps(func)
    def decorated_view(*args, **kwargs):
        role_id = current_user.role.id
        if role_id >= 2:
            return func(*args, **kwargs)
        else:
            return redirect(url_for("user.role"))
    return decorated_view

前端传输数据比较多时,建议用POST方式,Flask用request.form.get()取出数据。验证数据并写入数据库。

@office_bp.route("/add", methods=["GET", "POST"])
@login_required
@role_required
def add():
    if request.method == "POST":
        # print(request.form.to_dict())
        name = request.form.get("name")
        asset_number = request.form.get("asset_number")
        asset_status = request.form.get("asset_status")
        department_id = request.form.get("department")
        category_id = request.form.get("category")
        location_id = request.form.get("location")
        equ = Equipment(name, asset_number, asset_status,
                        department_id, category_id, location_id)
        equ.model = request.form.get("model")
        equ.parameter = request.form.get("parameter")
        equ.user = request.form.get("user")
        equ.IPaddress = request.form.get("IPaddress")
        equ.purchase_date = request.form.get("purchase_date")
        equ.purchase_cost = request.form.get("purchase_cost")
        try:
            db.session.add(equ)

            RET = {
                "code": 0,
                "msg": "添加成功"
            }
            # print(current_user.username)
            logger.info("%s成功添加%s", current_user.username, asset_number)
        except:
            db.session.rollback()
            RET = {
                "code": 1,
                "msg": "添加失败,请重新提交。"
            }
            logger.info("%s添加%s失败", current_user.username, asset_number)
        db.session.commit()
        return jsonify(RET)

Equipment()实例化时传入的参数是由Equipment类的__init__()方法决定的,另外也支持动态添加属性。数据库增加数据用db.session.add(),最后要db.session.commit()提交。

修改功能

修改功能都做在了每一条数据的后面,当点击修改时,就是对这个条目数据的修改。由于设备编号是唯一的,不可以修改,其它信息可以修改。修改页面要先显示出当前设备的信息,修改完成再提交到后端服务器。

在这里插入图片描述

修改页面的url采用的是动态参数,视图函数要接受url中的动态部分,这样就可以使用一个视图函数修改不同设备的信息。修改信息需要管理员角色,要加装饰器@role_required。提交到数据库使用update(),也是flask_sqlalchemy中封装的方法。

# 修改信息
@office_bp.route("/mod/<asset_number>", methods=["GET", "POST"])
@login_required
@role_required
def mod(asset_number):
    if request.method == "POST":
        # print(request.form.to_dict())
        name = request.form.get("name")
        model = request.form.get("model")
        parameter = request.form.get("parameter")
        department_id = request.form.get("department")
        location_id = request.form.get("location")
        user = request.form.get("user")
        IPaddress = request.form.get("IPaddress")
        purchase_date = request.form.get("purchase_date")
        purchase_cost = request.form.get('purchase_cost')
        asset_status = request.form.get("asset_status")
        try:
            Equipment.query.filter(Equipment.asset_number == asset_number).update({"name": name, "model": model, "parameter": parameter, "user": user, "IPaddress": IPaddress,
                                                                                   "asset_status": asset_status, "department_id": department_id, "location_id": location_id, "purchase_date": purchase_date, "purchase_cost": purchase_cost})

            RET = {
                "code": 0,
                "msg": "修改成功"
            }
            logger.info("%s成功修改%s", current_user.username, asset_number)
        except:
            db.session.rollback()
            RET = {
                "code": 1,
                "msg": "修改失败"
            }
            logger.info("%s修改%s失败", current_user.username, asset_number)
        db.session.commit()
        return jsonify(RET)

删除功能

删除的链接也在每一个条目的后面,表示删除的是这个条目,url也是采用动态参数形式。

在这里插入图片描述

在这里插入图片描述

删除功能是一个很危险的功能,应该有一个再确定的过程,点击删除链接时,先弹出提示框,点击确定才删除。

function del() {
    var msg = "您真的要删除吗?\n\n请确认!";
    if (confirm(msg) == true) {
        return true;
    } else {
        return false;
    }
}

删除完成,再返回到当前类别的详情页面。

@office_bp.route("/delete/<asset_number>")
@login_required
@role_required
def delete(asset_number):
    category_id = db.session.query(Equipment.category_id).filter(
        Equipment.asset_number == asset_number).first()
    Equipment.query.filter(Equipment.asset_number == asset_number).delete()
    db.session.commit()
    logger.warning("%s删除了%s", current_user.username, asset_number)
    return redirect("/detail"+"?category="+str(category_id[0]))

总结

通过开发此管理系统,掌握WEB管理系统的开发流程,常用功能,使用的技术,常见问题的解决办法,对管理系统有了更深的理解。

Flask框架的使用,app=Flask() app.run(),路由和视图函数@app.route(“/”) def 函数名,路由接受的请求方法methods=[“GET”,“POST”],装饰器的定义和使用,前端到后端的数据传输,url参数,动态参数,ajax。后端接收request.agrs.get(),request.form.get(),@app.route("/<>"),后端到前端的数据传输return render_template(".html",关键字参数),return jsonify()。异常处理,日志记录。Flask蓝图,session,第三方模块,flask_sqlalchemy,flask_login,flask_bootstrap,功能函数模块utils自定义的功能,添加全局模板app.add_template_global(get_months,“get_months”)等等。Flask可以根据需要,添加很多功能。

需要改进的地方也有很多,比如session放到radis数据库中,页面缓存,查询数据缓存等,在以后的时间会继续完善。

  • 3
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

崇赛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值