flask视频网站(后台管理)

news/2024/5/9 16:03:27/文章来源:https://blog.csdn.net/Roy_Allen/article/details/121058585

文章目录

  • 简介
  • 管理员登录
  • 标签管理
  • 电影管理
  • 电影预告管理
  • 会员管理
  • 评论管理
  • 电影收藏
  • 管理员密码修改
  • 日志管理
    • 操作日志
    • 管理员登录日志
    • 会员登录日志
  • 小结

简介

  • 这一部分要实现具体的后台管理逻辑
  • 基本逻辑如下:
    1

管理员登录

  • 将之前models中数据库的认证部分移动到app初始化文件中
  • 这一节的大部分内容都是参考前端页面进行的,这也是为什么上一节先搭建页面在这里插入图片描述
  • flask中所有表单提交验证使用flask_wtf,可以安装一下先
    • 激活虚拟环境,pip install flask-wtf
    • 这个扩展里定义好了很多表单要用的字段和验证器,例如字符串、密码、提交等等,也属于模型
    • 同样的,在我的基础笔记中有较为详细的解释
  • forms.py
    from flask_sqlalchemy import SQLAlchemy
    from flask import Flask
    from flask_wtf import FlaskForm
    from wtforms import StringField, PasswordField, SubmitField
    from wtforms.validators import DataRequired, ValidationErrorclass LoginForm(FlaskForm):"""管理员登录表单"""account = StringField(label='账号',validators=[DataRequired('请输入账号!')],description='账号',render_kw={"class":"from-control","placeholder":"请输入账号","required":"required"})pwd = StringField(label='密码',validators=[DataRequired('请输入账号!')],description='账号',render_kw={"class": "from-control","placeholder": "请输入账号","required": "required"}   # 传递给前端标签的属性,直接从模板中拷贝过来)submit = SubmitField(label='登录',render_kw={"class": "btn btn-primary btn-block btn-flat",})
    
  • 将模型渲染到模板中
    • 模板的操作要经过视图,在views.py
    from app.admin.forms import LoginForm# 后台登录
    @admin.route('/login/', methods=["GET", "POST"])
    def login():form = LoginForm()# 反过来执行判断if form.validate_on_submit():data = form.dataadmin = Admin.query.filter_by(name=data['account']).first() # 这是一条包含信息的对象if not admin.check_pwd(data['pwd']):flash("密码错误!")return redirect(url_for('admin.login'))# 密码正确,保存账号session['admin'] = data['account']  # 账号return redirect(request.args.get('next') or url_for('admin.index'))return render_template('admin/login.html', form=form)
    
    • 到模板中替换,login.html
    <input name="user" type="text" class="form-control" placeholder="请输入账号!">
    {{ form.account }}
    <input name="pwd" type="password" class="form-control" placeholder="请输入密码!">
    {{ form.pwd }}
    <a id="btn-sub" type="submit" class="btn btn-primary btn-block btn-flat">登录</a>
    {{ form.submit }}
    
  • 这里会报需要csrf字段,这是一种保护策略(跨站请求伪造),我们可以查看官方文档使用
    • 我们给app配置一个SECRET_KEY,可以使用uuid模块生成
    • 然后只需在模板中加入{{ form.csrf_token }}即可生成隐藏的csrf标签
  • 既然有了验证,如何在模板提示验证时的错误信息?在每个表单字段下修改:
    {% for err in form.account.errors %}
    <div class="col-md-12"><font style="color:red">{{err}}</font>
    </div>
    {% endfor %}
    
    • 这个没生效,无伤大雅,后面再看!
    • OK,这个不是没生效,而是账户密码都输入了才会验证并显示错误信息!
  • 表单验证的结果以及接收数据在视图中处理(前->后)
    • 当然,这个验证方法也放在表单模型中管理
    def validate_account(self, field):account = field.dataadmin = Admin.query.filter_by(name=account).count() # 使用数据库模型查询用户信息if admin == 0:raise ValidationError("账号不存在!")
    
    • 密码经过了hash运算,我们在admin的模型类中定义检验方法
    # models.py
    # class Admin
    def check_pwd(self, pwd):from werkzeug.security import check_password_hashreturn check_password_hash(self.pwd, pwd)
    
    • 然后在视图中定义校验密码(账号已存在并传递表单数据给视图)
    from flask import Flask, render_template, redirect, url_for, flash, session, request
    # 后台登录
    @admin.route('/login/', methods=["GET", "POST"])
    def login():form = LoginForm()if form.validate_on_submit():data = form.dataadmin = Admin.query.filter_by(name=data['account']).first() # 这是一条包含信息的对象if not admin.check_pwd(data['pwd']):flash("密码错误!")return redirect(url_for('admin.login'))# 密码正确,保存账号session['admin'] = data['account']  # 账号return redirect(request.args.get('next') or url_for('admin.index'))return render_template('admin/login.html', form=form)
    
    • 这里用到了消息闪现flash,需要前端加点东西
    {% for msg in get_flashed_messages() %}
    <p class="login-box-msg" style="color:red;">{{ msg }}</p>
    {% endfor %}
    
  • 很多页面需要登录才能访问,使用装饰器限制视图函数
    # admin/views.py
    from functools import wraps	# 作用是不改变被装饰函数的信息def admin_login(f):
    @wraps(f)
    def inner(*args, **kwargs):if "admin" not in session:	# 不能使用 session['admin'] is Nonereturn redirect(url_for('admin.login', next=request.url))	# next参数表示继续之前请求的地址return f(*args, **kwargs)	# 返回,不调用
    return inner	# 然后我们给每个视图函数加上这个装饰器语法糖,例如
    @admin.route('/logout')
    @admin_login
    def logout():session.pop('admin', None)  # 清除sessionreturn redirect(url_for('admin.login'))
    # 注意admin.html布局文件中,退出是路由到logout
    
  • session会将用户名和密码都保存,logout之后会将其清除!
    • session和cookie的区别是?

标签管理

  • 理一下管理员登录的逻辑,前面四步是通用的!
    • forms中定义表单模型
    • 在视图中传递
    • 在模板中渲染
    • 添加csrf验证
    • 在表单模型中定义验证器,判断用户是否存在
    • 在数据库模型中定义密码校验方法
    • 使用装饰器限制页面访问
  • 这里在贴一遍逻辑
    class TagForm(FlaskForm):"""标签添加表单"""name = StringField(label='标签名称',validators=[DataRequired("请输入标签名称!")],description="标签",render_kw={"class" : "form-control","id" : "input_name","placeholder" : "请输入标签名称!"})submit = SubmitField(label="添加",render_kw={"class" : "btn btn-primary"})
    
  • 定义入库方法
    # 标签的添加和列表
    @admin.route('/tag/add', methods=["GET", "POST"])   # 这个方法必须定义,否则validator不显示
    @admin_login
    def tag_add():form = TagForm()print("aaaaaaaaaaaaaaaa")if form.validate_on_submit():   # 没过验证data = form.datatag = Tag.query.filter_by(name=data['name']).count()if tag == 1:flash("标签已存在!", "err")return redirect(url_for('admin.tag_add'))tag = Tag(name= data['name'])db.session.add(tag)db.session.commit()flash("标签添加成功", 'ok')   # 第二个参数是固定的一些值,应用到模板 category_filter=["ok"]return render_template('admin/tagadd.html', form=form)
    
    • 我这里因为{{form.csrf_token}}这玩意儿写错了搞了半天,要细心!
  • 在前端找个模板提示信息
    {% 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">
    
    3
  • 然后在标签列表中分页显示并处理编辑删除操作
    • 分页显示在路由中用到正则匹配:<int:page>,前端就要传递页码哦!
    @admin.route('/tag/list/<int:page>/', methods=['GET'])
    @admin_login
    def tag_list(page=None):if page is None:page = 1page_data = Tag.query.order_by(Tag.addtime.desc()).paginate(page=page, per_page=2)return render_template('admin/taglist.html', page_data=page_data)
    
    • 处理分页也是用到扩展SQLAlchemy,templates中新建ui/admin_page.html,使用macro的语法,可以参考官方文档
    • 注意这里前端传递参数的方式,还是在url_for中使用/拼接,自动放入url,再正则解析,还不同于查询参数?x=x
    • 例如:{{url_for('admin.tag_edit', id=v.id)}}
    # menu.html,初始化第一页
    <li><a href="{{url_for('admin.tag_list', page=1)}}"><i class="fa fa-circle-o"></i> 标签列表</a>
    </li><!--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 %}<!--taglist.html-->
    {% import "ui/admin_page.html" as pg%}
    <div class="box-footer clearfix"><!--调用macro函数-->{{pg.page(page_data, 'admin.tag_list')}}
    </div>
    
  • 列表删除和编辑
    • 编辑和添加同样,涉及到表单提交,视图中都是先渲染到前端,提交表单,再返回验证并入库
    • 如果是删除,只需要传递参数(拼接url,正则捕获),修改数据库
    • 如果是查询,只需要查询数据库,分页
    • 增删改查到这来就都涉及到了!规律是 参数传递 + 视图-前端-视图;还有查询参数没有涉及
      • 这个参数传递可能在先也可能在后,这里删除就是先传递id参数,如果列表就是后面传递请求的page
    # 标签删除
    @admin.route('/tag/del/<int:id>/', methods=['GET'])
    @admin_login
    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))
    # html
    <a href="{{url_for('admin.tag_del', id=v.id)}}" class="label label-danger">删除</a># 标签编辑
    @admin.route('/tag/edit/<int:id>/', methods=['GET', 'POST'])
    @admin_login
    def tag_edit(id=None):# 编辑相当于重新提交一个表单form = TagForm()	# 和tagadd共用表单模型tag = Tag.query.get_or_404(id)  # 要修改的标签名if form.validate_on_submit():data = form.data    # 提交的标签名tag_count = Tag.query.filter_by(name=data['name']).count()if tag_count == 1:  # tag.name != data['name'] andflash("名称已存在", 'err')tag.name = data['name']db.session.add(tag)db.session.commit()flash("编辑成功!", "ok")return redirect(url_for('admin.tag_list', page=1))return render_template("admin/tagedit.html", form=form, tag=tag)	# 先渲染到前端,再返回验证
    
  • 要新建编辑页面,复制tagadd.html即可,注意使用tag变量的方法:{{form.name(value=tag.name)}}
    {% 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(value=tag.name)}}{% for err in form.name.errors %}<div class="col-md-12"><span style="color: red">{{ err }}</span></div>{% endfor %}<!--<input type="text" class="form-control" id="input_name" placeholder="请输入标签名称!">--></div></div><div class="box-footer">{{form.submit}}{{form.csrf_token}}<!--<button type="submit" class="btn btn-primary">添加</button>--></div></form></div></div></div>
    </section>
    {% endblock %}{% block js%}
    <script>$(document).ready(function () {$('#m-2').addClass("active");})
    </script>
    {% endblock %}
    
  • 表单模型不修改,统一叫编辑(添加/修改)
    4
    5

电影管理

  • 围绕着“参数传递+视前视”的核心处理思路,开始后台电影管理
  • 添加电影
    • 视图渲染表单,先根据数据库模型定义表单模型
      class MovieForm(FlaskForm):"""电影添加表单"""title = StringField(label='电影名称',validators=[DataRequired("请输入电影名称!")],description="标签",render_kw={"class": "form-control","id": "input_title","placeholder": "请输入片名!"})url = FileField(label='文件',validators=[DataRequired("请上传文件")],description="文件")info = TextAreaField(label='简介',validators=[DataRequired("请输入电影简介")],description="简介",render_kw={"class": "form-control","rows":"10","id": "input_info"})logo = FileField(label='logo图片',validators=[DataRequired("请上传文件")],description="logo")star = SelectField(label='星级',validators=[DataRequired("请选择星级")],description="星级",coerce=int,choices=[(1,"1星"),(2,"2星"),(3,"3星"),(4,"4星"),(5,"5星")],render_kw={"class": "form-control","id": "input_star"})tag_id = SelectField(label='标签',validators=[DataRequired("请选择标签")],description="标签",coerce=int,choices=[(v.id, v.name) for v in tags], # 列表推导式,传递到前端只显示v.name,给到视图是v.idrender_kw={"class": "form-control","id": "input_tag_id"})area = StringField(label='地区',validators=[DataRequired("请输入地区")],description="地区",render_kw={"class": "form-control","id": "input_area","placeholder" : "请输入地区!"})length = StringField(label='片长',validators=[DataRequired("请输入片长")],description="地区",render_kw={"class": "form-control","id": "input_length","placeholder" : "请输入片长!"})release = StringField(label='上映时间',validators=[DataRequired("请输入上映时间")],description="上映时间",render_kw={"class": "form-control","id": "input_release_time","placeholder" : "请输入上映时间!"})submit = SubmitField(label="添加",render_kw={"class": "btn btn-primary"})
      
    • 视图中导入数据库和表单模型,准备到前端
      <!--从流程来讲,不应该忘了这句,限定方法-->
      <form role="form" method="POST" enctype="multipart/form-data">
      
    • 前端POST之后,回到视图验证,注意文件的保存
      # 电影的添加
      from werkzeug.utils import secure_filename@admin.route('/movie/add', methods=['GET','POST'])
      @admin_login
      def movie_add():form = MovieForm()if form.validate_on_submit():data = form.data# 准备一条数据插入# 过滤数据名file_url = secure_filename(form.url.data.filename)file_logo = secure_filename(form.logo.data.filename)# 准备存储路径if not os.path.exists(app.config['UP_DIR']):os.mkdir(app.config['UP_DIR'])os.chmod(app.config['UP_DIR'], "rw")# 更改文件名file_url = changeFileName(file_url)file_logo = changeFileName(file_logo)# 保存文件form.url.data.save(app.config['UP_DIR'] + file_url)form.logo.data.save(app.config['UP_DIR'] + file_logo)movie = Movie(title = data['title'],url = file_url,info = data["info"],logo = file_logo,star = int(data["star"]),playnum = 0,commentnum = 0,tag_id = int(data['tag_id']),	# 给到视图的是v.id ,和表单模型的choices属性有关area = data['area'],release_time = data['release'],length = data["length"])db.session.add(movie)db.session.commit()flash("添加电影成功!", "ok")return render_template('admin/movieadd.html', form=form)
      
  • 这里要上传文件,需要在__init__配置了,并做处理
    app.config['UP_DIR'] = os.path.join(os.path.abspath(__file__),"/static/uploads/") # 以当前文件为参考,拼接上传文件的保存路径
    
  • 视图中定义函数,改变上传文件的名称
    from werkzeug.utils import secure_filename
    import os
    from datetime import datetimedef changeFileName(filename):'''修改传入文件的名称:return:'''file = os.path.splitext(filename)   # 将名称和后缀分开filename = datetime.now().strftime("%Y%m%d%H%M%S")+str(uuid.uuid4().hex)+str(file[-1])return filename
    
    • 视频图片都是直接存在服务器文件夹,数据库中存储的都是路径
  • 这里注意个问题
    # 保存文件时使用save方法
    form.url.data.save(app.config['UP_DIR'] + file_url)	# 是加号,不是逗号
    # app中添加配置时
    app.config['UP_DIR'] = os.path.join(os.path.abspath(os.path.dirname(__file__)),"static/uploads/") # uploads后面这个 / 必须写# 情况当前表中数据
    delete from movie;	# truncate 有外键约束
    
  • 电影列表
    • 更新菜单栏链接,加上page=1参数
    • 流程还是一样的,视图(查询分页数据)——前端——视图(返回请求页码);这里视图函数只需支持GET方法
    @admin.route('/movie/list/<int:page>', methods=['GET'])
    @admin_login
    def movie_list(page=None):if page==None:page = 1# 一个个电影挨着查page_data = Movie.query.join(Tag).filter(Tag.id == Movie.tag_id  # 多表关联时使用  movie为多端  这里就是查到tag表对应的整条记录,使用时用v.tag.name).order_by(Movie.addtime.desc()).paginate(page=page, per_page=10)return render_template('admin/movielist.html', page_data=page_data)
    
  • 电影删除,常规操作
    @admin.route('/movie/del/<int:id>', methods=['GET'])
    @admin_login
    def movie_del(id):movie = Movie.query.get_or_404(int(id)) # 如果没有直接跳到404db.session.delete(movie)    # 评论等数据和movie关联,movie是主表,所以会连带一起删除db.session.commit()flash("电影删除成功!", "ok")  # 小写 okreturn redirect(url_for("admin.movie_list", page=1))    # 重定向,需要模板渲染有add edit 和 list
    
    • 需要模板渲染有add edit 和 list,删除只需要重定向
  • 电影编辑:参数—视图—前端——视图(保存)
    # 编辑电影
    @admin.route('/movie/edit/<int:id>', methods=['GET', "POST"])
    @admin_login
    def movie_edit(id):form = MovieForm()form.url.validators = []# print(form.url.validators)form.logo.validators = []   # 跳过未选择文件的验证# 问题:required字段搞不掉?打印发现这么写没问题,解决方案:# render_kw = {#             "required": False#         }# 但是这样会在添加的时候不提示选择文件# 更好的方案是就该edit.html  {{form.logo(required=False)}}movie = Movie.query.get_or_404(int(id)) # 如果没有直接跳到404# 处理movie某些原信息不显示的问题,通过请求方法区分if request.method=="GET":form.info.data = movie.infoform.star.data = movie.starform.tag_id.data = movie.tag_id # 模板中就不需要 value=movie.infoif form.validate_on_submit():   # POSTdata = form.datam = Movie.query.filter_by(title=data['title']).count()if m==1:flash("电影已存在","err")return  redirect(url_for("admin.movie_edit", id=id))movie.title = data["title"]movie.info = data["info"]movie.star = data["star"]movie.tag_id = data["tag_id"]movie.area = data["area"]movie.length = data["length"]movie.release_time = data["release"]    # name属性# 文件重传if not os.path.exists(app.config["UP_DIR"]):os.mkdir(app.config["UP_DIR"])os.chmod(app.config["UP_DIR"], "rw")if form.url.data.filename != "":    # 问选择文件就是空,现在有值说明重传了(跟显示原文件无关)file_url = secure_filename(form.url.data.filename)movie.url = changeFileName(file_url)    # 更新原数据信息form.url.data.save(app.config["UP_DIR"] + movie.url)if form.logo.data.filename != "":file_logo = secure_filename(form.logo.data.filename)movie.logo = changeFileName(file_logo)form.logo.data.save(app.config["UP_DIR"] + movie.logo)db.session.add(movie)   # 修改db.session.commit()flash("修改成功", "pk")return redirect(url_for('admin.movie_list', page=1))return render_template("admin/movieedit.html", form=form, movie=movie)  # 渲染出原信息
    

电影预告管理

  • 添加和列表,查数据库发现表单模型字段就两个
  • 视图—前端—视图;视图—分页(后台数据—前台渲染);参数传递;文件保存
    # 预告的添加和列表
    @admin.route('/preview/add', methods=['GET', 'POST'])
    @admin_login
    def preview_add():'''和电影添加一样的过程'''form = PreviewForm()if form.validate_on_submit():# 过滤file_logo = secure_filename(form.logo.data.filename)# 准备存储路径if not os.path.exists(app.config["UP_DIR"]):os.mkdir(app.config["UP_DIR"])os.chmod(app.config["UP_DIR"], "rw")# 更改文件名file_logo = changeFileName(file_logo)# 保存文件form.logo.data.save(app.config["UP_DIR"] + file_logo)data = form.data# 准备一条数据入库preview = Preview(title = data["title"],logo = file_logo    # 存名字即可)db.session.add(preview)db.session.commit()flash("添加预告成功!", "ok")return redirect(url_for('admin.preview_add'))return render_template('admin/previewadd.html', form=form)@admin.route('/preview/list/<int:page>')
    @admin_login
    def preview_list(page=None):if page==None:page = 1page_data = Preview.query.order_by(Preview.addtime.desc()).paginate(page=page, per_page=10) # 视图部分提供数据,字典形式;前台部分使用macro渲染return render_template('admin/previewlist.html', page_data=page_data)@admin.route('/preview/del/<int:id>')
    @admin_login
    def preview_del(id=None):preview = Preview.query.get_or_404(int(id))  # 如果没有直接跳到404db.session.delete(preview)  # 评论等数据和movie关联,movie是主表,所以会连带一起删除db.session.commit()flash("预告删除成功!", "ok")  # 小写 okreturn redirect(url_for("admin.preview_list", page=1))  # 重定向,模板渲染有add edit 和 list@admin.route('/preview/edit/<int:id>', methods=['GET', 'POST'])
    @admin_login
    def preview_edit(id=None):form = PreviewForm()form.logo.validators = []preview = Preview.query.get_or_404(int(id))  # 如果没有直接跳到404if form.validate_on_submit():  # POSTdata = form.datam = Preview.query.filter_by(title=data['title']).count()if m == 1 and form.logo.data.filename == "":flash("预告已存在", "err")return redirect(url_for("admin.preview_edit", id=id))preview.title = data["title"]# 准备路径if not os.path.exists(app.config["UP_DIR"]):os.mkdir(app.config["UP_DIR"])os.chmod(app.config["UP_DIR"], "rw")if form.logo.data.filename != "":file_logo = secure_filename(form.logo.data.filename)preview.logo = changeFileName(file_logo)form.logo.data.save(app.config["UP_DIR"] + preview.logo)db.session.add(preview)  # 修改db.session.commit()flash("修改成功", "ok")return redirect(url_for('admin.preview_list', page=1))return render_template("admin/previewedit.html", form=form, preview=preview)  # 渲染出原信息
    

会员管理

  • 不知为啥启动网页时连接数据库出问题,需要pip install cryptography
    pip install -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com cryptography
    
  • 向user表中插入一些准备好的数据
    -- uuid需要系统生成
    insert into user(name,pwd,email,phone,info,face,uuid,addtime) values('鼠','1231','1231@123.com','13888888881','鼠','1f401.png','d32a72bdac524478b7e4f6dfc8394fc0',now());
    insert into user(name,pwd,email,phone,info,face,uuid,addtime) values('牛','1232','1232@123.com','13888888882','牛','1f402.png','d32a72bdac524478b7e4f6dfc8394fc1',now());
    insert into user(name,pwd,email,phone,info,face,uuid,addtime) values('虎','1233','1233@123.com','13888888883','虎','1f405.png','d32a72bdac524478b7e4f6dfc8394fc2',now());
    insert into user(name,pwd,email,phone,info,face,uuid,addtime) values('兔','1234','1234@123.com','13888888884','兔','1f407.png','d32a72bdac524478b7e4f6dfc8394fc3',now());
    insert into user(name,pwd,email,phone,info,face,uuid,addtime) values('龙','1235','1235@123.com','13888888885','龙','1f409.png','d32a72bdac524478b7e4f6dfc8394fc4',now());
    insert into user(name,pwd,email,phone,info,face,uuid,addtime) values('蛇','1236','1236@123.com','13888888886','蛇','1f40d.png','d32a72bdac524478b7e4f6dfc8394fc5',now());
    insert into user(name,pwd,email,phone,info,face,uuid,addtime) values('马','1237','1237@123.com','13888888887','马','1f434.png','d32a72bdac524478b7e4f6dfc8394fc6',now());
    insert into user(name,pwd,email,phone,info,face,uuid,addtime) values('羊','1238','1238@123.com','13888888888','羊','1f411.png','d32a72bdac524478b7e4f6dfc8394fc7',now());
    insert into user(name,pwd,email,phone,info,face,uuid,addtime) values('猴','1239','1239@123.com','13888888889','猴','1f412.png','d32a72bdac524478b7e4f6dfc8394fc8',now());
    insert into user(name,pwd,email,phone,info,face,uuid,addtime) values('鸡','1240','1240@123.com','13888888891','鸡','1f413.png','d32a72bdac524478b7e4f6dfc8394fc9',now());
    insert into user(name,pwd,email,phone,info,face,uuid,addtime) values('狗','1241','1241@123.com','13888888892','狗','1f415.png','d32a72bdac524478b7e4f6dfc8394fd0',now());
    insert into user(name,pwd,email,phone,info,face,uuid,addtime) values('猪','1242','1242@123.com','13888888893','猪','1f416.png','d32a72bdac524478b7e4f6dfc8394fd1',now());
    
  • 图片也是准备好的,复制到uplaods下
  • 这里添加user是前端触发的,属于home的逻辑;需要编写list/edit/del操作
    # 分页:后台准备数据,前台macro渲染
    # 查询参数可有可无,定义 /<int:page> 后必须每次都传递page
    #会员列表
    @admin.route('/user/list/<int:page>', methods=['GET'])
    @admin_login
    def user_list(page=None):if page is None:page = 1page_data = User.query.order_by(User.addtime.desc()).paginate(page=page, per_page=4)return render_template('admin/userlist.html', page_data=page_data)# 会员详细信息查看
    @admin.route('/user/view/<int:id>')
    @admin_login
    def user_view(id):user = User.query.get_or_404(int(id))return render_template('admin/userview.html', user=user)@admin.route('/user/del/<int:id>', methods=['GET'])
    @admin_login
    def user_del(id):user = User.query.get_or_404(int(id)) # 如果没有直接跳到404db.session.delete(user)    # 评论等数据和movie关联,movie是主表,所以会连带一起删除db.session.commit()flash("会员删除成功!", "ok")  # 小写 okreturn redirect(url_for("admin.user_list", page=1))    # 重定向,模板渲染有add edit 和 list
    
  • 状态(正常/冻结)先不设置

评论管理

  • 同样的,初始化一些数据
    -- 清空表后复位自增起点
    ALTER TABLE comment auto_increment=1;
    -- comment表外键约束movie表,user表
    insert into comment(movie_id,user_id,content,addtime) values(7,1,"好看",now());
    insert into comment(movie_id,user_id,content,addtime) values(7,2,"不错",now());
    insert into comment(movie_id,user_id,content,addtime) values(7,3,"经典",now());
    insert into comment(movie_id,user_id,content,addtime) values(7,4,"给力",now());
    insert into comment(movie_id,user_id,content,addtime) values(8,5,"难看",now());
    insert into comment(movie_id,user_id,content,addtime) values(8,6,"无聊",now());
    insert into comment(movie_id,user_id,content,addtime) values(8,7,"乏味",now());
    insert into comment(movie_id,user_id,content,addtime) values(8,8,"无感",now());
    
  • 注意要关联查询,包括评论列表和删除
    # 评论列表
    @admin.route('/comment/list/<int:page>')
    @admin_login
    def comment_list(page=None):if page==None:page=1page_data = Comment.query.join(Movie).join(User).filter(Movie.id == Comment.movie_id,User.id == Comment.user_id).order_by(Comment.addtime.desc()).paginate(page=page, per_page=5)# {{v.movie.title}}return render_template('admin/commentlist.html', page_data=page_data)@admin.route('/comment/del/<int:id>', methods=['GET'])
    @admin_login
    def comment_del(id):comment = Comment.query.get_or_404(int(id)) # 如果没有直接跳到404db.session.delete(comment)    # 评论等数据和movie关联,movie是主表,所以会连带一起删除db.session.commit()flash("评论删除成功!", "ok")  # 小写 okreturn redirect(url_for("admin.comment_list", page=1))
    

电影收藏

  • 之前疏忽数据库多加了一个字段:alter table movcollec drop content;
  • 电影收藏列表,删除收藏
    # 收藏列表
    @admin.route('/collect/list/<int:page>')
    @admin_login
    def collect_list(page=None):if page==None:page=1page_data = MovCollection.query.join(Movie).join(User).filter(Movie.id == MovCollection.movie_id,User.id == MovCollection.user_id).order_by(MovCollection.addtime.desc()).paginate(page=page, per_page=8)return render_template('admin/collectlist.html', page_data=page_data)@admin.route('/collect/del/<int:id>', methods=['GET'])
    @admin_login
    def collect_del(id):collect = MovCollection.query.get_or_404(int(id)) # 如果没有直接跳到404db.session.delete(collect)db.session.commit()flash("收藏删除成功!", "ok")return redirect(url_for("admin.collect_list", page=1))
    

管理员密码修改

  • 提交新密码,终于要定义新表单模型了,这里也要写验证旧密码的方法
    @admin.route('/cpwd', methods=['GET', 'POST'])
    @admin_login
    def cpwd():form = PwdForm()if form.validate_on_submit():data = form.data# 这里需要传递命名参数,对应字段名;否则会参数异常admin = Admin.query.filter_by(name=session['admin']).first()from werkzeug.security import generate_password_hashadmin.pwd = generate_password_hash(data['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)
    
  • 密码要经过加密后存储,user中密码的需要在home的后台加密处理
  • 显示管理员上线时间
    @admin.context_processor
    def tpl_extra():'''上下文处理器:return:'''# 创建全局变量,直接在admin.html渲染data = dict(online_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    

日志管理

  • 根据具体的日志需求,处理逻辑不同

操作日志

  • 这部分需要在各种操作时产生数据,例如添加标签日志,添加电影日志等等
    # 例如在添加标签时:
    def tag_add():form = TagForm()if form.validate_on_submit():   # 没过验证data = form.datatag = Tag.query.filter_by(name=data['name']).count()if tag == 1:flash("标签已存在!", "err")return redirect(url_for('admin.tag_add'))tag = Tag(name= data['name'])db.session.add(tag)db.session.commit()flash("标签添加成功", 'ok')  # 第二个参数是固定的一些值,应用到模板 category_filter=["ok"]# 添加操作日志---------------------oplog = OperateLog(admin_id=session['admin_id'],ip=request.remote_addr,reason="添加标签 %s" % data['name'])db.session.add(oplog)db.session.commit()return render_template('admin/tagadd.html', form=form)
    
  • 后续部分需要自己完善,在各种操作后记录操作日志
  • 有了操作日志,当然要展示了,登录的管理员目前能看到所有的操作日志
    # 操作日志
    @admin.route('/oplog/list/<int:page>')
    @admin_login
    def oplog_list(page=None):if page==None:page=1page_data = OperateLog.query.join(Admin).filter(Admin.id == OperateLog.admin_id).order_by(OperateLog.addtime.desc()).paginate(page=page,per_page=3)return render_template('admin/oploglist.html', page_data=page_data)
    

管理员登录日志

  • 这部分需要在登录的时候产生数据
    def login():xxx# 添加登录日志-----------------admin = AdminLog(admin_id = admin.id,ip=request.remote_addr)db.session.add(admin)db.session.commit()xxx
    
  • list视图逻辑
    # 管理员登录日志
    @admin.route('/adminlog/list/<int:page>')
    @admin_login
    def adminlog_list(page=None):if page==None:page=1page_data = AdminLog.query.join(Admin).filter(Admin.id == AdminLog.admin_id).order_by(AdminLog.addtime.desc()).paginate(page=page,per_page=2)return render_template('admin/adminloglist.html', page_data=page_data)
    
  • 前端分页渲染即可

会员登录日志

  • 因为这部分属于home前后台管理,只能直接初始化几条数据
    insert into userlog(user_id,ip,addtime) values(1,"192.168.4.1",now());
    insert into userlog(user_id,ip,addtime) values(2,"192.168.4.2",now());
    insert into userlog(user_id,ip,addtime) values(3,"192.168.4.3",now());
    insert into userlog(user_id,ip,addtime) values(4,"192.168.4.4",now());
    insert into userlog(user_id,ip,addtime) values(5,"192.168.4.5",now());
    insert into userlog(user_id,ip,addtime) values(6,"192.168.4.6",now());
    insert into userlog(user_id,ip,addtime) values(7,"192.168.4.7",now());
    insert into userlog(user_id,ip,addtime) values(8,"192.168.4.8",now());
    insert into userlog(user_id,ip,addtime) values(9,"192.168.4.9",now());
    
  • 视图逻辑
    # 会员登录日志
    @admin.route('/userlog/list/<int:page>')
    @admin_login
    def userlog_list(page=None):if page==None:page=1page_data = UserLog.query.join(User).filter(User.id == UserLog.user_id).order_by(UserLog.addtime.desc()).paginate(page=page,per_page=4)return render_template('admin/userloglist.html', page_data=page_data)
    

小结

  • admin前后台的管理员、会员、电影、标签、日志等逻辑定义完成
    • 主要分两类逻辑:查看(分页)和编辑(增删改)
    • 至于细节部分:表单模型——视图——前端——视图,或者其他吧
  • 下一节主要是基于角色的访问控制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_826678.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

flask视频网站(权限控制)

文章目录AuthRoleAdmin权限控制Auth 基于角色的访问权限控制 有的管理员只能访问日志&#xff0c;而有的能访问会员列表&#xff0c;有的管理电影这部分还属于admin 从创建表单模型开始&#xff0c;别着急class AuthForm(FlaskForm):"""访问权限控制"&quo…

ASP.NET Core 网站在Docker中运行

Docker作为新一代的虚拟化方式&#xff0c;未来肯定会得到广泛的应用&#xff0c;传统虚拟机的部署方式要保证开发环境、测试环境、UAT环境、生产环境的依赖一致性&#xff0c;需要大量的运维人力&#xff0c;使用Docker我们可以实现一次部署&#xff0c;到处运行。 本文介绍如…

给你介绍一个假的苹果网站,能肉眼看出来算我输!

这或许是用肉眼最难分辨的钓鱼网站&#xff0c;没有之一&#xff0c;不信你试试&#xff0c;能看出端倪吗&#xff1f; 网站的 URL 地址显示的是苹果官网&#xff0c;网址旁边是安全字样和绿色小锁&#xff0c;表示网站信息基于 https 加密传输&#xff0c;完全没什么问题&…

新手如何掌握制作和提交网站地图?

新手如何掌握制作和提交网站地图?网站地图作为根据网站的结构&#xff0c;框架&#xff0c;内容生成的导航网页文件。大多数人都知道网站地图对于提高用户体验有好处&#xff1a;它们为网站访问者指明方向&#xff0c;并帮助迷失的访问者找到他们想看的页面。那么什么是网站地…

006-网站统计中的数据收集原理及实现

网站数据统计分析工具是网站站长和运营人员经常使用的一种工具&#xff0c;比较常用的有谷歌分析、百度统计和腾讯分析等等。所有这些统计分析工具的第一步都是网站访问数据的收集。目前主流的数据收集方式基本都是基于javascript的。本文将简要分析这种数据收集的原理&#xf…

漫谈聚类--网站

http://blog.pluskid.org/?page_id78 转载于:https://www.cnblogs.com/lm3306/p/9347665.html

json在线解析及格式化验证网站

2019独角兽企业重金招聘Python工程师标准>>> https://www.json.cn/ 转载于:https://my.oschina.net/u/3766116/blog/1861799

成都SEO网站优化与新媒体流量互通_成都辰星建站

为什么80%的码农都做不了架构师&#xff1f;>>> 成都SEO企业网站优化流量来源途径与互联网新媒体列表及流量互通实施方案,新媒体运营在当下的互联网流量运营方式占据着举足轻重的作用,企业网站流量导入外链建设已经逐渐削弱,星的流量运营导入方式已经诞生&#xff…

电商网站架构探索|SOA分布式架构详解

目前很多的企业都有自己的电商网站&#xff0c;但随着业务量的增长&#xff0c;并发量高了。由于平台架构的一些不足&#xff0c;会导致一系列严重的问题&#xff0c;电子商务平台的安全性&#xff0c;承受能力也经受着严峻的考验&#xff0c;而市面上绝大多数的电商网站是业务…

百度关键词模拟发包php程序,PHP可视化百度小程序平台微信狗源码OEM招商加盟版(多套网站风格模板+一键搭建)...

【温馨提示】源码包解压密码&#xff1a;www.youhutong.com资源描述PHP可视化百度小程序平台微信狗源码OEM招商加盟版(多套网站风格模板一键搭建)源码介绍&#xff1a;它拥有以下几个特点&#xff1a;1、系统终身使用&#xff0c;可以生成无数小程序&#xff1b;2、自带多种行业…

log4j mysql 详细日志_log4j将日志存储到数据库_太平洋学习网|一个最全的javaweb,js,css,html5,csdn,android,linux的学习网站。...

log4j是javaEE日志输出文件&#xff0c;通常情况下我们都是把log日志输出到指定的日志文件中&#xff0c;在这儿我们使用log4j将日志存储到mysql&#xff0c;oracle数据库表中&#xff0c;使用log4j前必须引入log4j.jar 和commons-logging.jar这两个jar包。一&#xff1a;配置l…

获取手机号_网站获取手机号的方法

很多人疑惑网站和app中的手机电话号码数据是怎么抓取的&#xff0c;是如何实现的&#xff0c;我在这里说下。一、数据的来源现在数据的来源有很多种&#xff0c;我给大家说下常见的几种数据来源方式和抓取方式。1、运营商数据&#xff0c;这种来源方式的话是运营商会有一个http…

WebMatrixRazor建站系列之WebMatrix介绍

WebMatrix介绍 WebMatrix是微软开发的一个免费的&#xff0c;轻量级Web开发工具。提供了一种简单的方式让我们创建一个站点。它包括IIS Express&#xff08;Web服务器&#xff09;&#xff0c;ASP.NET&#xff08;Web框架&#xff09;&#xff0c;和SQL Server Compact&#xf…

如何扫描网站的php文件在哪里,PHP实现的网站目录扫描索引工具

代码很简单&#xff0c;这里就不多废话了&#xff0c;本代码来至一位网友的投稿&#xff0c;经测试可用error_reporting(E_ALL & ~E_NOTICE);ignore_user_abort();set_time_limit(0);if ($_GET[act] op) {$data_url $_GET[url] . /;$hz $_GET[type];list($fw1, $fw2) e…

为您的IIS6下的网站配置Rewrite伪静态组件

首先我们下载Rewrite伪静态组件到服务器&#xff0c;点击下载&#xff0c;然后解压到D:\Rewrite下&#xff0c;解压后如下图&#xff1a; 温馨提示&#xff1a;ReWrite组件所在目录要有Users或者Everyone默认访问权限。您可以点击该目录右键&#xff0c;属性&#xff0c;安全&a…

超实用的54套ASP网站设计源码

2019独角兽企业重金招聘Python工程师标准>>> ASP是一种服务器端脚本编写环境&#xff0c;可以用来创建和运行动态网页或Web应用程序。ASP网页可以包含HTML标记、普通文本、脚本命令以及COM组件等。利用ASP可以向网页中添加交互式内容。以下是则会54套ASP网站设计源码…

网站相关技术探究keepalive_timeout:

网站相关技术探究keepalive设多少&#xff1a; /proc/$PID/fd/$number0:标准输入 1:标准输出2:标准错误Test:[rootKTQT ~]# ll /proc/12857/fdtotal 0 lrwx------ 1 root root 64 Apr 4 17:49 0 -> /dev/nulllrwx------ 1 root root 64 Apr 4 17:49 1 -> /dev/nulllrwx…

Google的网站统计、分析系统

http://www.google.com/analytics/功能全&#xff0c;比较酷&#xff01;转载于:https://www.cnblogs.com/huobazi/archive/2005/11/16/277830.html

SharePoint Server 2013 Step By Step之管理网站导航

管理网站导航SharePoint网站导航包括两个区域的导航选项&#xff0c;快速启动和导航栏。默认情况下&#xff0c;快速启动位于网站内容的左侧&#xff0c;通常用于列出当前站点&#xff1a;如列表、类别&#xff0c;库&#xff0c;子网站等等。而导航栏&#xff0c;默认情况下&a…

ZT 如何使XP自动启动IIS默认网站

如何使XP自动启动IIS默认网站 [转贴 2007-08-27 21:33:24 ] 发表者: yl_wen 在服务里&#xff0c;把下面两个服务设置为自动即可&#xff1a; 1. IIS Admin 2. World Wide Web Publishing分类: 系统维护 转载于:https://www.cnblogs.com/Jasper-Wang/article…