项目视频下载地址
链接: https://pan.baidu.com/s/11ROsvoKukAUAiEnztvS55A 密码: qsj2
项目代码github地址
链接: https://github.com/cafeUncle/movie-mooc.git
mysql命令
mysql命令行下,\s命令可以查看简单信息
pip操作
pip 下载时如果很慢,可以使用-i命令指定下载源 使用--trusted-host指定信任的主机
pip install -i http://pypi.douban.com/simple/ --trustrt-host pypi.douban.com virtualenv
pip | description |
---|---|
freeze | Output installed packages in requirements format. |
list | List installed packages. |
pip安装包的方式中,有如下两种安装方式:
pip install flask
pip install -r requirements.txt
- 文件requirement.txt里面内容的格式和pip freeze的格式完全一样。因此我们可以将pip freeze的内容输出至requirements.txt。其他机器可以根据导出的requirements.txt进行包安装。
pip freeze > requirements.txt # 输出本地包环境至文件
pip install -r requirements.txt # 根据文件进行包安装
- pip freeze 为什么比 pip list 的包少几个呢?
因为pip , wheel , setuptools 等包,是自带的而无法(un)install的。考虑到pip freeze的用途,所以这些包并没有显示。
如果一定要用pip freeze来显示所有包,可以加上参数-all,即pip freeze -all
蓝图
flask blueprint
- 定义蓝图
app/admin/__init__.py
from flask import Blueprint
admin = Blueprint('admin', name)
import views- 调用蓝图
app/admin/views.py
from . import admin
@admin.route("/")- 注册蓝图
app/__init__.py
from flask import admin as admin_blueprint
app.register_blueprint(admin_blueprint, url_prefix="/admin")
必须先调用蓝图设置好路由,然后再注册蓝图
前台布局搭建
- 静态文件引入: {{ url_for('static', filename='文件路径') }}
- 定义路由: {{ url_for('模块名.视图名',变量=参数) }}
模块名.视图名对应view.py中的方法名,而不是html文件名,个人认为写"/login"这样的路径更方便,学到后面如果发现url_for的特别用法,再来更新 - 定义数据块: {% block 数据块名称 %} ... {% endblock %}
一般定义在base页面中,类似freemarker中的macro,可以在子模板中引用同名block块。
- 一般首先定义一个base模板,在内容中允许定义多个block块,由子模板引用,子模板决定替换哪些block块,没有定义的块即不替换,直接使用父模板的block块 。
- 通过{{ block.super }},可以引用上级代码块并在其基础上进行一些修改
{% block name%}
{{ block.super }}
<p>...
{% endblock %}
- 引用时首先要在子模板中{% extends "base.html" %}
extends只能写在子模板第一行,参数一般为字符串,也可为变量,可带路径或相对路径,以TEMPLATE_DIRS的模板目录为基准
- 其它
- {% include 'includes/nav.html' %}
可带路径或相对路径,以TEMPLATE_DIRS的模板目录为基准
也可以使用变量名 {% include template_name %},包含的变量都会统一处理,不区分是第几层模板
被包含在(include)的页面里面不能用{%block%},类似jsp的静态导入<%@ include file="fileurl" %>和动态导入<jsp:include page="menu.jsp"/>的区别
- {% import 'ui/page_admin.html' as pg %}
引用后可以使用别名调用。
page_admin.html 内容如下,类似freemarker的macro
{% 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><a class="disabled">上一页</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><a class="disabled">下一页</a></li>
{% endif %}
<li><a href="{{url_for(url, page=data.pages)}}">尾页</a></li>
</ul>
{% endif %}
{%- endmacro %}
调用时
<div class="box-footer clearfix">
{{ pg.page(page_data, 'admin.tag_list') }}
</div>
- {% for x in range(1,5)%}{{x}}{% endfor %}
<div class="box-body">
{% for msg in get_flashed_messages(category_filter=['ok']) %} # 过滤显示flashed信息
<h4><i class="icon fa fa-warning"></i> 操作成功!</h4>
{{msg}}
</div>
{% endfor %}
{% for msg in get_flashed_messages(category_filter=['err']) %}
<h4><i class="icon fa fa-warning"></i> 操作失败!</h4>
{{msg}}
</div>
{% endfor %}
<div class="form-group">
<label for="input_title">{{form.title.label}}</label>
{{form.title}}
{% for err in form.title.errors %} # 显示validators的错误信息
<div class="col-md-12">
<font style="color: red;">{{err}}</font>
</div>
{% endfor %}
</div>
后台
-
编写wtforms.validators的ValidationError自定义验证器时,方法名后缀一定要和参数名相同,否则不会触发该验证
FlaskForm
from flask_wtf import FlaskForm
class RoleForm(FlaskForm): # 继承FlaskForm类
name = StringField( # 表单类型 StringField, PasswordField, SubmitField, FileField, TextAreaField, SelectField, SelectMultipleField
label="角色名称", # {{form.label}}
validators=[ # 验证器
DataRequired("请输入角色名称")
],
description="角色名称",
render_kw={ # 元素属性
"class": "form-control",
"id": "input_name",
"placeholder": "请输入角色名称!"
}
)
star = SelectField(
label="星级",
validators=[
DataRequired("请选择星级!")
],
description="星级",
coerce=int,
choices=[(1, "1星"), (2, "2星"), (3, "3星"), (4, "4星"), (5, "5星")],
render_kw={
"class": "form-control"
}
)
auths = SelectMultipleField(
label="权限列表",
validators=[
DataRequired("请选择权限")
],
coerce=int,
choices=[(v.id, v.name) for v in auth_list],
description="权限列表",
render_kw={
"class": "form-control"
}
)
submit = SubmitField(
"编辑",
render_kw={
"class": "btn btn-primary"
}
)
def validate_account(self, field): # 自定义验证器,第一个参数必须是self,方法名称必须是validate_变量名,否则无法匹配生效
account = field.data
admin = Admin.query.filter_by(name=account).count()
if admin == 0:
raise ValidationError("账号不存在!")
def __repr__(self): # 打印方法
return "<Admin %r>" % self.name
- 登录验证装饰器
def admin_login_req(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'admin' not in session:
return redirect(url_for('admin.login', next=request.url))
return f(*args, **kwargs)
return decorated_function
使用时将@admin_login_req注解写在方法上
- 权限控制装饰器
def admin_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
admin = Admin.query.join(
Role
).filter(
Admin.role_id == Role.id,
Admin.id == session['admin_id']
).first()
auths = admin.role.auths
auths = list(map(lambda v: int(v), auths.split(',')))
auth_list = Auth.query.all()
urls = [v.url for v in auth_list for val in auths if val == v.id]
rule = request.url_rule
print(urls)
print(rule)
if str(rule) not in urls:
abort(404)
return f(*args, **kwargs)
return decorated_function
使用时将@admin_auth注解写在方法上
- 上下文应用处理器 封装全局变量,展现到模板中
@admin.context_processor
def tpl_extra():
data = dict(
online_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
)
return data
使用时在模板中用{{online_time}}调用
- 获取当前日期并格式化,及生成uuid
werkzeug库
from werkzeug.security import generate_password_hash
from werkzeug.security import check_password_hash
def check_pwd(self, pwd): # 检查密码
from werkzeug.security import check_password_hash
return check_password_hash(self.pwd, pwd)Gists
Gist | 作用 |
---|---|
request.url_rule | 获取请求url |
request.remote_addr | 获取请求地址 |
Auth.query.all() | 查询数据 |
Admin.query.filter_by(name=data['account']).first() | 查询数据 |
db.session.add(admin_log) | 插入数据 |
db.session.delete(movie) | 删除数据 |
db.session.commit() | 提交数据 |
session.pop('admin', None) | 删除session |
Tag.query.filter_by(name=data['name']).count() | 统计 |
page_data = Tag.query.order_by(Tag.add_time.desc()).paginate(page=page, per_page=10) | 排序 分页 |
Tag.query.filter_by(id=id).first_or_404() | 找不到则返回404 |
@admin.route('/tag/del/<int:id>/', methods=['GET']) | 路径参数 |
flash('添加电影成功', 'ok') | {% for msg in get_flashed_messages(category_filter=['ok']) %} |
page_data = Movie.query.join(Tag).filter(Movie.tag_id == Tag.id).order_by(Movie.add_time.desc()).paginate(page=page, per_page=10) | 多表关联,排序分页 |
Movie.query.filter_by(title=data['title']).count() | 筛选查询 |
if form.validate_on_submit() | 用自定义的验证器验证表单 |
json.dumps({"t": str(datetime.datetime.now()), 'action': 'getJson'}) | 格式化为json |
request.args.get("next") | 获取转发链接 |
page_data = MovieCol.query.join(Movie).join(User).filter(Movie.id == MovieCol.movie_id,User.id == 1).filter_by(Movie.title == '战狼').order_by(Movie.add_time.desc()).paginate(page=1,per_page=10) | N个表关联 |