Flask开发微电影网站(六)

时间:2022-03-26 12:33:55

1. 后台管理登录功能实现

1.1 后台管理页面登录表单LoginForm

在app的admin目录下创建forms.py文件,用来保存admin蓝图中需要使用到的表单

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, FileField, TextAreaField, SelectField, SelectMultipleField
from wtforms.validators import DataRequired, ValidationError, EqualTo
from app.models import Admin, Tag, Auth, Role

tags = Tag.query.all()             # 获取数据库中所有的电影标签
auth_list = Auth.query.all()       # 获取数据库中所有的权限列表
role_list = Role.query.all()       # 获取数据库中所有的角色列表

# 登录表单
class LoginForm(FlaskForm):
    """
    管理员登录表单
    """
    account = StringField(
        label="账号",
        validators=[
            DataRequired("请输入账号!")
        ],
        description='账号',
        render_kw={
            'class': 'form-control',
            'placeholder': '请输入账号!',
            'required': 'required'
        }
    )

    pwd = PasswordField(
        label="密码",
        validators=[
            DataRequired("请输入密码!")
        ],
        description='密码',
        render_kw={
            'class': 'form-control',
            'placeholder': '请输入密码!',
            'required': 'required'
        }
    )

    submit = SubmitField(
        "登录",
        render_kw={
            'class': 'btn btn-primary btn-block btn-flat'
        }
    )

    def validate_account(self, field):
        account = field.data
        user = Admin.query.filter_by(name=account).count()
        if user == 0:
            raise ValidationError("账号不存在!请重新输入!!")

1.2 后台管理登录页面视图函数

在登录视图函数中调用登录表单LoginForm

# 登录
@admin.route('/login/', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        data = form.data
        admin = Admin.query.filter_by(name=data['account']).first()

        if not admin.check_pwd(data.get("pwd")):
            flash("旧密码错误,请重新输入!", "err")
            return redirect(url_for("admin.login"))

        session['admin'] = data.get("account")
        session['admin_id'] = admin.id
        adminlog = Adminlog(
            admin_id=admin.id,
            ip=request.remote_addr
        )
        db.session.add(adminlog)
        db.session.commit()
        return redirect(request.args.get("next") or url_for('admin.index'))
    return render_template("admin/login.html", form=form)

1.3 后台管理登录页面前端页面

登录页面中调用后台登录视图函数返回来的变量form

form变量中包含管理员登录需要的字段

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>微电影管理系统</title>
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
    <link rel="shortcut icon" href="{{ url_for('static',filename='base/images/logo.png') }}">
    <link rel="stylesheet" href="{{ url_for('static',filename='admin/bootstrap/css/bootstrap.min.css') }}">
    <link rel="stylesheet" href="{{ url_for('static',filename='fonts/css/font-awesome.min.css') }}">
    <link rel="stylesheet" href="{{ url_for('static',filename='ionicons/css/ionicons.min.css') }}">
    <link rel="stylesheet" href="{{ url_for('static',filename='admin/dist/css/AdminLTE.min.css') }}">
    <link rel="stylesheet" href="{{ url_for('static',filename='admin/plugins/iCheck/square/blue.css') }}">
</head>
<body class="hold-transition login-page">
<div class="login-box">
    <div class="login-logo">
        <a href=""><b>微电影管理系统</b></a>
    </div>
    <div class="login-box-body">
        {% for msg in get_flashed_messages(category_filter=['err']) %}
            <p class="login-box-msg" style="color:red">{{ msg }}</p>
        {% endfor %}
        {% for msg in get_flashed_messages(category_filter=['ok']) %}
            <p class="login-box-msg" style="color:green">{{ msg }}</p>
        {% endfor %}

        <form method="POST" id="form-data">
            {{ form.csrf_token }}
            <div class="form-group has-feedback">
                {{ form.account }}
                <span class="glyphicon glyphicon-envelope form-control-feedback"></span>
                {% for err in form.account.errors %}
                    <div class="col-md-12">
                        <font style="color:red">{{ err }}</font>
                    </div>
                {% endfor %}
            </div>
            <div class="form-group has-feedback">
                {{ form.pwd }}
                <span class="glyphicon glyphicon-lock form-control-feedback"></span>

                {% for err in form.pwd.errors %}
                    <div class="col-md-12">
                        <font style="color:red">{{ err }}</font>
                    </div>
                {% endfor %}
            </div>
            <div class="row">
                <div class="col-xs-8">
                </div>
                <div class="col-xs-4">
                    {{ form.submit }}
                </div>
            </div>
        </form>
    </div>
</div>
<script src="{{ url_for('static',filename='admin/plugins/jQuery/jQuery-2.2.0.min.js') }}"></script>
<script src="{{ url_for('static',filename='admin/bootstrap/js/bootstrap.min.js') }}"></script>
<script src="{{ url_for('static',filename='admin/plugins/iCheck/icheck.min.js') }}"></script>
</body>
</html>

2. 后台管理页面退出功能实现

2.1 后台管理页面退出视图函数

退出功能必须要用户登录才能有效,所以要用到登录控制装饰器

@admin.route('/logout/')
@admin_login_req
def logout():
    session.pop("admin", None)
    session.pop("admin_id", None)
    return redirect(url_for("admin.login"))

3. 后台管理页面主页功能实现

3.1 后台管理页面主页视图函数

后台管理员必须要登录进行密码验证才能打开后台管理页面,同时用户要用相应的权限才能打开后台管理页面的主页面

所以后台管理主页视图函数必须被登录控制装饰器和权限控制装饰器同时装饰

@admin.route('/')
@admin_login_req
@admin_auth
def index():
    return render_template("admin/index.html")

3.2 后台管理页面主页前端页面

由于在admin.html页面中已经导入左侧菜单部分,所以后台管理页面主页继承admin.html页面实现顶端部分和左侧菜单栏

{% extends "admin/admin.html" %}

{% block content %}
    <section class="content-header">
        <h1>微电影管理系统</h1>
        <ol class="breadcrumb">
            <li><a href="#"><i class="fa fa-dashboard"></i> 首页</a></li>
            <li class="active">控制面板</li>
        </ol>
    </section>
    <section class="content" id="showcontent">
        <div class="row">
            <div class="col-md-6">
                <div class="box box-primary">
                    <div class="box-header with-border">
                        <h3 class="box-title">内存使用率</h3>
                    </div>
                    <div class="box-body" id="meminfo" style="height:600px;"></div>
                </div>
            </div>
            <div class="col-md-6">
                <div class="box box-primary">
                    <div class="box-header with-border">
                        <h3 class="box-title">系统设置</h3>
                    </div>
                    <form role="form">
                        <div class="box-body" style="height:600px;">
                            <div class="form-group">
                                <label for="input_speed">限制速率大小</label>
                                <input type="text" class="form-control" id="input_speed" placeholder="请输入限制速率!"
                                       value="512">
                            </div>
                            <div class="form-group">
                                <label for="input_mem">限制内存大小</label>
                                <input type="text" class="form-control" id="input_mem" placeholder="请输入限制内存!"
                                       value="10m">
                            </div>
                            <div class="form-group">
                                <label for="input_num">限制客户端数量</label>
                                <input type="text" class="form-control" id="input_num" placeholder="请输入限制客户端数量!"
                                       value="4">
                            </div>
                            <div class="form-group">
                                <button type="submit" class="btn btn-primary">保存并重启服务</button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </section>
{% endblock %}

{% block js %}
    <script src="{{ url_for('static',filename='js/echarts.min.js') }}"></script>
    <script>
        var myChart = echarts.init(document.getElementById('meminfo'));
        option = {
            backgroundColor: "white",
            tooltip: {
                formatter: "{a} <br/>{b} : {c}%"
            },
            series: [{
                name: '内存使用率',
                type: 'gauge',
                detail: {
                    formatter: '{value}%'
                },
                data: [{
                    value: 50,
                    name: '内存使用率'
                }]
            }]
        };
        setInterval(function () {
            option.series[0].data[0].value = (Math.random() * 100).toFixed(2) - 0;
            myChart.setOption(option, true);
        }, 2000);
    </script>
    <script>
    $(document).ready(function () {
        $("#g-1").addClass('active');
        $("#g-1-1").addClass('active');
    });
    </script>
{% endblock %}

4. 后台管理页面修改密码功能实现

4.1 后台管理页面修改密码表单

在app的admin目录下的forms.py文件中,定义修改密码的表单

# 修改密码表单
class PwdForm(FlaskForm):
    old_pwd = PasswordField(
        label="旧密码",
        validators=[
            DataRequired("请输入旧密码!")
        ],
        description="旧密码",
        render_kw={
            "class": "form-control",
            "placeholder": "请输入旧密码!"
        },
    )

    new_pwd = PasswordField(
        label="新密码",
        validators=[
            DataRequired("请输入新密码!")
        ],
        description="新密码",
        render_kw={
            "class": "form-control",
            "placeholder": "请输入新密码!"
        }
    )
    summit = SubmitField(
        "编辑",
        render_kw={
            "class": "btn btn-primary",
        }
    )

    def validate_old_pwd(self, field):
        from flask import session
        pwd = field.data
        name = session.get("admin")
        admin = Admin.query.filter_by(name=name).first()

        if not admin.check_pwd(pwd):
            raise ValidationError("旧密码错误!")

4.2 后台管理页面修改密码视图函数

修改密码必须要登录,所以要用登录控制装饰器装饰修改密码的视图函数权限控制装饰器进行管理用户权限判断

# 修改密码
@admin.route('/pwd/', methods=['GET', 'POST'])
@admin_login_req
@admin_auth
def pwd():
    form = PwdForm()
    if form.validate_on_submit():
        data = form.data
        admin = Admin.query.filter_by(name=session.get("admin")).first()

        from werkzeug.security import generate_password_hash
        admin.pwd = generate_password_hash(data.get("new_pwd"))
        db.session.add(admin)
        db.session.commit()
        flash("修改密码成功,请重新登录!", "ok")
        return redirect(url_for("admin.logout"))
    return render_template("admin/pwd.html", form=form)

4.3 后台管理页面前端页面

由于在admin.html页面中已经导入左侧菜单部分,所以后台管理页面主页继承admin.html页面实现顶端部分和左侧菜单栏

{% extends "admin/admin.html" %}

{% block content %}
    <!--内容-->
    <section class="content-header">
        <h1>微电影管理系统</h1>
        <ol class="breadcrumb">
            <li><a href="#"><i class="fa fa-dashboard"></i> 个人资料</a></li>
            <li class="active">修改密码</li>
        </ol>
    </section>
    <section class="content" id="showcontent">
        <div class="row">
            <div class="col-md-12">
                <div class="box box-primary">
                    <div class="box-header with-border">
                        <h3 class="box-title">修改密码</h3>
                    </div>
                    <form role="form" method="post">
                        <div class="box-body">
                            <div class="form-group">
                                <label for="input_pwd">{{ form.old_pwd.label }}</label>
                                {{ form.old_pwd }}
                                <!--报错信息-->
                                {% for err in form.old_pwd.errors %}
                                    <div class="col-md-12">
                                        <font style="color:red">{{ err }}</font>
                                    </div>
                                {% endfor %}
                            </div>
                            <div class="form-group">
                                <label for="input_newpwd">{{ form.new_pwd.label }}</label>
                                {{ form.new_pwd }}
                                <!--报错信息-->
                                {% for err in form.new_pwd.errors %}
                                    <div class="col-md-12">
                                        <font style="color:red">{{ err }}</font>
                                    </div>
                                {% endfor %}
                            </div>
                        </div>
                        <div class="box-footer">
                            {{ form.submit }}
                            {{ form.csrf_token }}
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </section>
    <!--内容-->
{% endblock %}

5. 后台管理之电影标签管理

5.1 后台管理页面标签表单

在app的admin目录下的forms.py文件中创建TagForm表单,用来保存标签的表单信息

# 标签表单
class TagForm(FlaskForm):
    name = StringField(
        label="名称",
        validators=[
            DataRequired("请输入标签!")
        ],
        description="标签",
        render_kw={
            "class": "form-control",
            "id": 'input_name',
            'placeholder': '请输入标签名称!'
        }
    )

    submit = SubmitField(
        "编辑",
        render_kw={
            'class': 'btn btn-primary'
        }
    )

5.2 前端页面分页组件

在templates目录下创建ui目录,在ui目录下创建admin_page.html页面,用来保存后台管理页面分页组件代码

{% macro page(data,url) -%}
    {% if data %}
        <ul class="pagination pagination-sm no-margin pull-right">
            <li><a href="{{ url_for(url,page=1) }}">首页</a></li>
            {% if data.has_prev %}
                <li><a href="{{ url_for(url,page=data.prev_num) }}">上一页</a></li>
            {% else %}
                <li class="disabled"><a href="#">上一页</a></li>
            {% endif %}

            {% for v in data.iter_pages() %}
                {% if v==data.page %}
                    <li class="active"><a href="#">{{ v }}</a></li>
                {% else %}
                    <li><a href="{{ url_for(url,page=v) }}">{{ v }}</a></li>
                {% endif %}
            {% endfor %}

            {% if data.has_next %}
                <li><a href="{{ url_for(url,page=data.next_num) }}">下一页</a></li>
            {% else %}
                <li class="disabled"><a href="#">下一页</a></li>
            {% endif %}
            <li><a href="{{ url_for(url,page=data.pages) }}">尾页</a></li>
        </ul>
    {% endif %}
{%- endmacro %}

5.3 电影标签管理之标签列表功能实现

5.3.1 电影标签管理标签列表页面视图函数

用控制装饰器来进行登录管理,用权限控制装饰器来进行后台管理用户判断

@admin.route("/tag/list/<int:page>/", methods=["GET"])
@admin_login_req
@admin_auth
def tag_list(page=None):
    """
    标签列表
    """
    if page is None:
        page = 1
    page_data = Tag.query.order_by(
        Tag.addtime.desc()
    ).paginate(page=page, per_page=1)
    return render_template("admin/tag_list.html", page_data=page_data)

5.3.2 电影标签管理标签列表页面前端页面

由于在admin.html页面中已经导入左侧菜单部分,所以后台管理页面主页继承admin.html页面实现顶端部分和左侧菜单栏

导入admin_page.html来实现电影标签的分页展示

{% extends 'admin/admin.html' %}
{% import 'ui/admin_page.html' as pg %}

{% block content %}
    <!--内容-->
    <section class="content-header">
        <h1>微电影管理系统</h1>
        <ol class="breadcrumb">
            <li><a href="#"><i class="fa fa-dashboard"></i> 标签管理</a></li>
            <li class="active">标签列表</li>
        </ol>
    </section>
    <section class="content" id="showcontent">
        <div class="row">
            <div class="col-md-12">
                <div class="box box-primary">
                    <div class="box-header">
                        <h3 class="box-title">标签列表</h3>
                        <div class="box-tools">
                            <div class="input-group input-group-sm" style="width: 150px;">
                                <input type="text" name="table_search" class="form-control pull-right"
                                       placeholder="请输入关键字...">

                                <div class="input-group-btn">
                                    <button type="submit" class="btn btn-default"><i class="fa fa-search"></i>
                                    </button>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="box-body table-responsive no-padding">
                        {% for msg in get_flashed_messages(category_filter=["ok"]) %}
                            <div class="alert alert-success alert-dismissible">
                                <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
                                </button>
                                <h4><i class="icon fa fa-check"></i>操作成功</h4>
                                {{ msg }}
                            </div>
                        {% endfor %}
                        <table class="table table-hover">
                            <tbody>
                            <tr>
                                <th>编号</th>
                                <th>名称</th>
                                <th>添加时间</th>
                                <th>操作事项</th>
                            </tr>

                            {% for v in page_data.items %}
                                <tr>
                                    <td>{{ v.id }}</td>
                                    <td>{{ v.name }}</td>
                                    <td>{{ v.addtime }}</td>
                                    <td>
                                        <a href="{{ url_for("admin.tag_edit",id=v.id) }}" class="label label-success">编辑</a>
                                        &nbsp;
                                        <a href="{{ url_for("admin.tag_del",id=v.id) }}" class="label label-danger">删除</a>
                                    </td>
                                </tr>
                            {% endfor %}
                            </tbody>
                        </table>
                    </div>
                    <div class="box-footer clearfix">
                        {{ pg.page(page_data,"admin.tag_list") }}
                    </div>
                </div>
            </div>
        </div>
    </section>
    <!--内容-->
{% endblock %}

{% block js %}
<script>
    $(document).ready(function () {
        $("#g-2").addClass('active');
        $("#g-2-2").addClass('active');
    });
</script>
{% endblock %}

5.4 电影标签管理之添加电影标签功能实现

5.4.1 后台管理页面添加标签视图函数

# 添加标签
@admin.route('/tag/add/', methods=['GET', 'POST'])
@admin_login_req
@admin_auth
def tag_add():
    form = TagForm()
    if form.validate_on_submit():
        data = form.data
        tag = Tag.query.filter_by(name=data.get("name")).count()
        if tag == 1:
            flash("标签名称已存在,请重新输入!", "err")
            return redirect(url_for("admin.tag_add"))
        # tag = Tag(name=data["name"])
        tag = Tag(name=data.get("name"))
        db.session.add(tag)
        db.session.commit()
        flash("添加标签成功!", "ok")
        oplog = Oplog(
            admin_id=session.get("admin_id"),
            ip=request.remote_addr,
            reason="添加标签:%s" % data.get("name")
        )
        db.session.add(oplog)
        db.session.commit()
        return redirect(url_for("admin.tag_list", page=1))
    return render_template("admin/tag_add.html", form=form)

5.2.3 后台管理页面添加标签前端页面

{% extends 'admin/admin.html' %}

{% block content %}
    <!--内容-->
    <section class="content-header">
        <h1>微电影管理系统</h1>
        <ol class="breadcrumb">
            <li><a href="#"><i class="fa fa-dashboard"></i> 标签管理</a></li>
            <li class="active">添加标签</li>
        </ol>
    </section>
    <section class="content" id="showcontent">
        <div class="row">
            <div class="col-md-12">
                <div class="box box-primary">
                    <div class="box-header with-border">
                        <h3 class="box-title">添加标签</h3>
                    </div>
                    <form role="form" method="post">
                        <div class="box-body">
                            {% for msg in get_flashed_messages(category_filter=["ok"]) %}
                                <div class="alert alert-success alert-dismissible">
                                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
                                    </button>
                                    <h4><i class="icon fa fa-check"></i>操作成功</h4>
                                    {{ msg }}
                                </div>
                            {% endfor %}
                            {% for msg in get_flashed_messages(category_filter=["err"]) %}
                                <div class="alert alert-danger alert-dismissible">
                                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
                                    </button>
                                    <h4><i class="icon fa fa-ban"></i>操作失败</h4>{{ msg }}
                                </div>
                            {% endfor %}
                            <div class="form-group">
                                <label for="input_name">{{ form.name.label }}</label>
                                {{ form.name }}
                                {% for err in form.name.errors %}
                                    <div class="col-md-2">
                                        <font style="color:red">{{ err }}</font>
                                    </div>
                                {% endfor %}
                            </div>
                        </div>
                        <div class="box-footer">
                            {{ form.csrf_token }}
                            {{ form.submit }}
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </section>
    <!--内容-->
{% endblock %}

{% block js %}
<script>
    $(document).ready(function () {
    $("#g-2").addClass('active');
    $("#g-2-1").addClass('active');
    });
</script>
{% endblock %}

5.3 电影标签管理之编辑电影标签功能实现

5.3.1 电影标签管理之编辑电影标签视图函数

# 编辑标签
@admin.route('/tag/edit/<int:id>/', methods=["GET", "POST"])
@admin_login_req
@admin_auth
def tag_edit(id=None):
    form = TagForm()
    tag = Tag.query.get_or_404(id)
    if form.validate_on_submit():
        data = form.data
        tag_count = Tag.query.filter_by(name=data.get("name")).count()
        if tag.name != data.get("name") and tag_count == 1:
            flash("标签名称已存在,请重新输入!", "err")
            return redirect(url_for("admin.tag_edit", id=id))
        # tag = Tag(name=data["name"])
        tag.name = data.get("name")
        db.session.add(tag)
        db.session.commit()
        flash("修改标签成功!", "ok")
        return redirect(url_for("admin.tag_list", page=1))
    return render_template("admin/tag_edit.html", form=form, tag=tag)

5.3.1 电影标签管理之编辑电影标签前端页面

{% extends 'admin/admin.html' %}

{% block content %}
    <!--内容-->
    <section class="content-header">
        <h1>微电影管理系统</h1>
        <ol class="breadcrumb">
            <li><a href="#"><i class="fa fa-dashboard"></i> 标签管理</a></li>
            <li class="active">修改标签</li>
        </ol>
    </section>
    <section class="content" id="showcontent">
        <div class="row">
            <div class="col-md-12">
                <div class="box box-primary">
                    <div class="box-header with-border">
                        <h3 class="box-title">修改标签</h3>
                    </div>
                    <form role="form" method="post" action="{{ url_for("admin.tag_edit",id=tag.id) }}">
                        <div class="box-body">
                            {% for msg in get_flashed_messages(category_filter=["ok"]) %}
                                <div class="alert alert-success alert-dismissible">
                                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
                                    </button>
                                    <h4><i class="icon fa fa-check"></i>操作成功</h4>
                                    {{ msg }}
                                </div>
                            {% endfor %}
                            {% for msg in get_flashed_messages(category_filter=["err"]) %}
                                <div class="alert alert-danger alert-dismissible">
                                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
                                    </button>
                                    <h4><i class="icon fa fa-ban"></i>操作失败</h4>{{ msg }}
                                </div>
                            {% endfor %}
                            <div class="form-group">
                                <label for="input_name">{{ form.name.label }}</label>
                                {{ form.name(value=tag.name) }}
                                <input name="id" value="{{ tag.id }}" type="hidden">
                                {% for err in form.name.errors %}
                                    <div class="col-md-2">
                                        <font style="color:red">{{ err }}</font>
                                    </div>
                                {% endfor %}
                            </div>
                        </div>
                        <div class="box-footer">
                            {{ form.csrf_token }}
                            {{ form.submit }}
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </section>
    <!--内容-->
{% endblock %}

{% block js %}
<script>
    $(document).ready(function () {
    $("#g-2").addClass('active');
    $("#g-2-2").addClass('active');
    });
</script>
{% endblock %}

5.3 电影标签管理之删除电影标签功能实现

5.3.1 电影标签管理之删除电影标签视图函数

    # 标签删除
    @admin.route('/tag/del/<int:id>/', methods=["GET"])
    @admin_login_req
    @admin_auth
    def tag_del(id=None):
        tag = Tag.query.filter_by(id=id).first_or_404()
        db.session.delete(tag)
        db.session.commit()
        flash("删除标签成功!", "ok")
        return redirect(url_for("admin.tag_list", page=1))