【Flask微电影】15.电影内容管理:增删查改
个人博客,欢迎查看:https://blog.starmeow.cn/
Github地址:https://github.com/xyliurui/FlaskMovie
电影管理
电影添加
创建电影添加表单
app/admin/forms.py
class MovieForm(FlaskForm):
title = StringField(
label='片名',
validators=[
DataRequired('请输入片名!')
],
description='片名',
render_kw={
'class': "form-control",
'placeholder': "请输入标签名称!"
}
)
url = FileField(
label='电影文件',
validators=[
DataRequired('请上传电影文件!')
],
description='电影文件',
)
info = TextAreaField(
label='简介',
validators=[
DataRequired('请输入简介!')
],
description='简介',
render_kw={
'class': "form-control",
'rows': "10",
}
)
logo = FileField(
label='封面',
validators=[
DataRequired('请上传封面!')
],
description='封面',
)
star = SelectField(
label='星级',
validators=[
DataRequired('请选择星级!')
],
description='星级',
coerce=int,
choices=[(1, '1星'), (2, '2星'), (3, '3星'), (4, '4星'), (5, '5星')],
render_kw={
'class': "form-control"
}
)
tag_id = SelectField(
label='标签',
validators=[
DataRequired('请选择标签!')
],
coerce=int,
choices=[(tag.id, tag.name) for tag in Tag.query.all()],
description='标签',
render_kw={
'class': "form-control"
}
)
area = StringField(
label='上映地区',
validators=[
DataRequired('请输入上映地区!')
],
description='上映地区',
render_kw={
'class': "form-control",
'placeholder': "请输入上映地区!"
}
)
length = StringField(
label='播放时长(分钟)',
validators=[
DataRequired('请输入播放时长!')
],
description='播放时长',
render_kw={
'class': "form-control",
'placeholder': "请输入播放时长!",
}
)
release_time = StringField(
label='上映时间',
validators=[
DataRequired('请选择上映时间!')
],
description='上映时间',
render_kw={
'class': "form-control",
'placeholder': "请选择上映时间!",
'id': "input_release_time" # 由于使用了时间控件,需要指定id
}
)
submit = SubmitField(
label='提交',
render_kw={
'class': "btn btn-primary"
}
)
修改movie_add电影添加视图
url
和logo
不能通过form.data
直接获取,后面再增加
@admin.route("/movie/add/", methods=['GET', 'POST'])
@admin_login_require
def movie_add():
form = MovieForm()
if form.validate_on_submit():
data = form.data
url = '' # 待增加
logo = ''
movie = Movie(
title=data['title'],
url=url,
info=data['info'],
logo=logo,
star=data['star'],
play_num=0,
comment_num=0,
tag_id=data['tag_id'],
area=data['area'],
release_time=data['release_time'],
length=data['length']
)
db.session.add(movie)
db.session.commit()
flash('添加电影成功', 'ok')
return redirect(url_for('admin.movie_add'))
return render_template('admin/movie_add.html', form=form)
修改movie_add.html增加表单显示
增加每个字段的验证错误信息显示,以及提交表单后flash
提示信息
<form role="form" method="post" enctype="multipart/form-data">
<div class="box-body">
{% with msgs = get_flashed_messages(category_filter=['ok']) %}
{% if msgs %}
<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>
{% for msg in msgs %}
<p>{{ msg }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% with msgs = get_flashed_messages(category_filter=['err']) %}
{% if msgs %}
<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>
{% for msg in msgs %}
<p>{{ msg }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="form-group">
<label for="input_title">{{ form.title.label }}</label>
{{ form.title }}
{% for err in form.title.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_url">{{ form.url.label }}</label>
{{ form.url }}
{% for err in form.url.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
<div style="margin-top:5px;">
<div id="moviecontainer"></div>
</div>
</div>
<div class="form-group">
<label for="input_info">{{ form.info.label }}</label>
{{ form.info }}
{% for err in form.info.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_logo">{{ form.logo.label }}</label>
{{ form.logo }}
{% for err in form.logo.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
<img data-src="holder.js/262x166" style="margin-top:5px;" class="img-responsive"
alt="">
</div>
<div class="form-group">
<label for="input_star">{{ form.star.label }}</label>
{{ form.star }}
{% for err in form.star.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_tag_id">{{ form.tag_id.label }}</label>
{{ form.tag_id }}
{% for err in form.tag_id.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_area">{{ form.area.label }}</label>
{{ form.area }}
{% for err in form.area.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_length">{{ form.length.label }}</label>
{{ form.length }}
{% for err in form.length.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_release_time">{{ form.release_time.label }}</label>
{{ form.release_time }}
{% for err in form.release_time.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
</div>
{{ form.csrf_token }}
<div class="box-footer">
{{ form.submit }}
</div>
</form>
image.png
进行表单文件的上传保存操作
上传表单、文件上传: http://www.pythondoc.com/flask-wtf/form.html
url
和logo
如何获取?
定义文件上传保存的路径。
修改app/__init__.py增加文件保存路径
# 定义文件上传保存的路径,在__init__.py文件所在目录创建media文件夹,用于保存上传的文件
app.config['UP_DIR'] = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'static/media/')
上传文件重命名,以时间字符串+随机字符串+文件后缀的名称进行重命名
import os
import uuid # 生成唯一字符串
import datetime # 生成时间
# 修改文件名称
def change_filename(filename):
fileinfo = os.path.splitext(filename) # 分离包含路径的文件名与包含点号的扩展名
filename = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + str(uuid.uuid4().hex + fileinfo[-1])
return filename
上传文件保存的逻辑操作
@admin.route("/movie/add/", methods=['GET', 'POST'])
@admin_login_require
def movie_add():
form = MovieForm()
if form.validate_on_submit():
data = form.data
# 提交的片名在数据库中已存在
if Movie.query.filter_by(title=data['title']).count() == 1:
flash('电影片名已存在,请检查', category='err')
return redirect(url_for('admin.movie_add'))
# 获取上传文件的名称
file_url = secure_filename(form.url.data.filename)
file_logo = secure_filename(form.logo.data.filename)
# 文件保存路径操作
file_save_path = app.config['UP_DIR'] # 文件上传保存路径
if not os.path.exists(file_save_path):
os.makedirs(file_save_path) # 如果文件保存路径不存在,则创建一个多级目录
import stat
os.chmod(file_save_path, stat.S_IRWXU) # 授予可读写权限
# 对上传的文件进行重命名
url = change_filename(file_url)
logo = change_filename(file_logo)
# 保存文件,需要给文件的保存路径+文件名
form.url.data.save(file_save_path + url)
form.logo.data.save(file_save_path + logo)
movie = Movie(
title=data['title'],
url=url,
info=data['info'],
logo=logo,
star=data['star'],
play_num=0,
comment_num=0,
tag_id=data['tag_id'],
area=data['area'],
release_time=data['release_time'],
length=data['length']
)
db.session.add(movie)
db.session.commit()
flash('添加电影成功', 'ok')
return redirect(url_for('admin.movie_add'))
return render_template('admin/movie_add.html', form=form)
image.png
将会在static/media
文件夹下保存电信的视频和封面内容
电影列表
修改movie_list视图增加查询和分页
@admin.route("/movie/list/<int:page>/", methods=['GET'])
@admin_login_require
def movie_list(page=None):
if page is None:
page = 1
# 查询的时候关联标签Tag进行查询:使用join(Tag)
# 单表过滤使用filter_by,多表关联使用filter,将Tag.id与Movie的tag_id进行关联
page_movies = Movie.query.join(Tag).filter(
Tag.id == Movie.tag_id
).order_by(
Movie.add_time.desc()
).paginate(page=page, per_page=10)
return render_template('admin/movie_list.html', page_movies=page_movies)
修改movie_list.html显示电影列表和分页
修改base.html中的电影列表增加page参数,如果不增加,后面使用会报错
<a href="{{ url_for('admin.movie_list', page=1) }}">
<i class="fa fa-circle-o"></i> 电影列表
</a>
直接使用之前创建的分页模块,获取电影的标签名称,通过movie.tag.name
电影外键关系关联获取
<table class="table table-hover">
<tbody>
<tr>
<th>编号</th>
<th>片名</th>
<th>片长</th>
<th>标签</th>
<th>地区</th>
<th>星级</th>
<th>播放数量</th>
<th>评论数量</th>
<th>上映时间</th>
<th>操作事项</th>
</tr>
{% for movie in page_movies.items %}
<tr>
<td>{{ movie.id }}</td>
<td>{{ movie.title }}</td>
<td>{{ movie.length }} 分钟</td>
<td>{{ movie.tag.name }}</td>
<td>{{ movie.area }}</td>
<td>{{ movie.star }} 星</td>
<td>{{ movie.comment_num }}</td>
<td>{{ movie.play_num }}</td>
<td>{{ movie.release_time }}</td>
<td>
<a class="label label-success">编辑</a>
<a class="label label-danger">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!--增加分页模块到页面底部,参考标签的分页-->
<!--页码模块-->
{% import 'admin/pagination.html' as pg %}
{{ pg.render_pagination(page_movies, 'admin.movie_list') }}
image.png
电影删除
增加movie_delete删除电影视图
从数据库中查询到该电影,然后进行删除,同事需要从磁盘删除电影文件和封面文件
@admin.route("/movie/delete/<int:delete_id>/", methods=['GET'])
@admin_login_require
def movie_delete(delete_id=None):
if delete_id:
movie = Movie.query.filter_by(id=delete_id).first_or_404()
print(movie.logo)
# 删除电影同时要从磁盘中删除电影的文件和封面文件
file_save_path = app.config['UP_DIR'] # 文件上传保存路径
# 如果存在将进行删除,不判断,如果文件不存在删除会报错
if os.path.exists(os.path.join(file_save_path, movie.url)):
os.remove(os.path.join(file_save_path, movie.url))
if os.path.exists(os.path.join(file_save_path, movie.logo)):
os.remove(os.path.join(file_save_path, movie.logo))
# 删除数据库,提交修改,注意后面要把与电影有关的评论都要删除
db.session.delete(movie)
db.session.commit()
# 删除后闪现消息
flash('删除电影成功!', category='ok')
return redirect(url_for('admin.movie_list', page=1))
修改movie_list.html删除电影链接和提示
{% with msgs = get_flashed_messages(category_filter=['ok']) %}
{% if msgs %}
<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>
{% for msg in msgs %}
<p>{{ msg }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<a class="label label-danger" href="{{ url_for('admin.movie_delete', delete_id=movie.id) }}">删除</a>
image.png
编辑电影
创建movie_update电影编辑试图
- 初始化表单,并增加除了文件外的初始值
- 因为在
MovieForm
中要求url
和logo
不能为空,所以在修改视图中,需要允许上传文件为空,直接定义required
为False
没这样在前端表单中不会要求上传文件 - 提交表单,检查片名是否存在,如果不存在才添加数据库
- 保存提交的修改
- 如果电影文件和封面文件存在,先删除旧文件,然后保存新文件到数据库
@admin.route("/movie/update/<int:update_id>/", methods=['GET', 'POST'])
@admin_login_require
def movie_update(update_id=None):
movie = Movie.query.get_or_404(int(update_id))
# print(movie)
# 给表单赋初始值,文件表单不处理
form = MovieForm(
title=movie.title,
# url=movie.url, # 上传文件,这样赋初始值无效,在前端可以通过上传路径+movie.url来获取文件的保存路径,显示在页面上
info=movie.info,
# logo=movie.logo, # 上传图片和文件类似
star=movie.star,
tag_id=movie.tag_id,
area=movie.area,
release_time=movie.release_time,
length=movie.length,
)
# 对于修改数据,电影文件和封面图已存在,可以非必填:按照教程上测试了validators参数,但始终不行,最终修改required的值就可以了
form.url.validators = []
print(form.url) # <input id="url" name="url" required type="file">
if form.url.render_kw:
form.url.render_kw['required'] = False
else:
form.url.render_kw = {'required': False}
print(form.url) # <input id="url" name="url" type="file">
form.logo.validators = [] # 验证列表为空
form.logo.render_kw = {'required': False} # 直接修改required为False表明不要求输入
if form.validate_on_submit():
data = form.data
# 提交的片名在数据库中已存在,且不是当前的电影名称
if Movie.query.filter_by(title=data['title']).count() == 1 and movie.title != data['title']:
flash('电影片名已存在,请检查', category='err')
return redirect(url_for('admin.movie_update', update_id=update_id))
# 以下和直接修改的数据
movie.title = data['title']
movie.info = data['info']
movie.star = data['star']
movie.tag_id = data['tag_id']
movie.area = data['area']
movie.release_time = data['release_time']
movie.length = data['length']
# 文件保存路径操作
file_save_path = app.config['UP_DIR'] # 文件上传保存路径
if not os.path.exists(file_save_path):
os.makedirs(file_save_path) # 如果文件保存路径不存在,则创建一个多级目录
import stat
os.chmod(file_save_path, stat.S_IRWXU) # 授予可读写权限
print(form.url.data, type(form.url.data))
# <FileStorage: 'ssh.jpg' ('image/jpeg')> <class 'werkzeug.datastructures.FileStorage'>
# 处理电影文件逻辑:先从磁盘中删除旧文件,然后保存新文件
if form.url.data: # 上传文件不为空,才进行保存
# 删除以前的文件
if os.path.exists(os.path.join(file_save_path, movie.url)):
os.remove(os.path.join(file_save_path, movie.url))
# 获取上传文件的名称
file_url = secure_filename(form.url.data.filename)
# 对上传的文件进行重命名
movie.url = change_filename(file_url)
# 保存文件,需要给文件的保存路径+文件名
form.url.data.save(file_save_path + movie.url)
# 处理封面图
if form.logo.data:
if os.path.exists(os.path.join(file_save_path, movie.logo)):
os.remove(os.path.join(file_save_path, movie.logo))
file_logo = secure_filename(form.logo.data.filename)
movie.logo = change_filename(file_logo)
form.logo.data.save(file_save_path + movie.logo)
db.session.merge(movie) # 调用merge方法,此时Movie实体状态并没有被持久化,但是数据库中的记录被更新了(暂时不明白)
db.session.commit()
flash('修改电影成功', 'ok')
return redirect(url_for('admin.movie_update', update_id=update_id))
return render_template('admin/movie_update.html', form=form, movie=movie)
创建movie_update.html电影编辑模板
由于在视图中已经初始化好表单的值,显示在输入框中,在页面上显示通过file: "{{ url_for('static',filename="media/"+ movie.url) }}",
渲染视频的播放,以及使用<img src="{{ url_for('static', filename='media/' + movie.logo) }}" style="margin-top:5px;" class="img-responsive" alt="">
来显示封面图
form role="form" method="post" enctype="multipart/form-data">
<div class="box-body">
{% with msgs = get_flashed_messages(category_filter=['ok']) %}
{% if msgs %}
<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>
{% for msg in msgs %}
<p>{{ msg }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% with msgs = get_flashed_messages(category_filter=['err']) %}
{% if msgs %}
<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>
{% for msg in msgs %}
<p>{{ msg }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<div class="form-group">
<label for="input_title">{{ form.title.label }}</label>
{{ form.title }}
{% for err in form.title.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_url">{{ form.url.label }}</label>
{{ form.url }}
{% for err in form.url.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
<div style="margin-top:5px;">
<div id="moviecontainer"></div>
</div>
</div>
<div class="form-group">
<label for="input_info">{{ form.info.label }}</label>
{{ form.info }}
{% for err in form.info.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_logo">{{ form.logo.label }}</label>
{{ form.logo }}
{% for err in form.logo.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
<img src="{{ url_for('static', filename='media/' + movie.logo) }}" style="margin-top:5px;" class="img-responsive" alt="">
</div>
<div class="form-group">
<label for="input_star">{{ form.star.label }}</label>
{{ form.star }}
{% for err in form.star.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_tag_id">{{ form.tag_id.label }}</label>
{{ form.tag_id }}
{% for err in form.tag_id.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_area">{{ form.area.label }}</label>
{{ form.area }}
{% for err in form.area.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_length">{{ form.length.label }}</label>
{{ form.length }}
{% for err in form.length.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_release_time">{{ form.release_time.label }}</label>
{{ form.release_time }}
{% for err in form.release_time.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
</div>
{{ form.csrf_token }}
<div class="box-footer">
{{ form.submit }}
</div>
</form>
<!--播放页面-->
<script src="{{ url_for('static',filename='jwplayer/jwplayer.js') }}"></script>
<script type="text/javascript">
jwplayer.key = "P9VTqT/X6TSP4gi/hy1wy23BivBhjdzVjMeOaQ==";
</script>
<script type="text/javascript">
jwplayer("moviecontainer").setup({
flashplayer: "{{ url_for('static',filename='jwplayer/jwplayer.flash.swf') }}",
playlist: [{
file: "{{ url_for('static',filename="media/"+ movie.url) }}",
title: "{{ movie.title }}"
}],
modes: [{
type: "html5"
}, {
type: "flash",
src: "{{ url_for('static',filename='jwplayer/jwplayer.flash.swf') }}"
}, {
type: "download"
}],
skin: {
name: "vapor"
},
"playlist.position": "left",
"playlist.size": 200,
height: 250,
width: 387,
});
</script>
<script>
$(document).ready(function () {
$('#input_release_time').datepicker({
autoclose: true,
format: 'yyyy-mm-dd',
language: 'zh-CN',
});
});
</script>
修改movie_list.html
增加编辑按钮的链接
<a class="label label-success" href="{{ url_for('admin.movie_update', update_id=movie.id) }}">编辑</a>
当输入一个已存在的片名就会提示已存在。
image.png image.png如果有上传电影文件或者是封面图片,那么将会删除旧文件,并保存新的文件到数据库,可以查看/static/media/
下的文件变动。