上映预告
- 模型: Preview
- 表单: 无
- 请求方法: GET
- 访问控制: 无
views中进行业务逻辑的实现
@home.route("/animation/")
def animation():
"""
首页轮播动画
"""
data = Preview.query.all()
return render_template("home/animation.html", data=data)
标签筛选 - 电影分页
- 模型: Tag Movie
- 表单: 无
- 请求方法: GET
- 访问控制: 无
编写首页的views
@home.route("/<int:page>/", methods=["GET"])
@home.route("/", methods=["GET"])
def index(page=None):
"""
首页电影列表
"""
tags = Tag.query.all()
page_data = Movie.query
# 标签
tid = request.args.get("tid", 0)
if int(tid) != 0:
page_data = page_data.filter_by(tag_id=int(tid))
# 星级
star = request.args.get("star", 0)
if int(star) != 0:
page_data = page_data.filter_by(star=int(star))
# 时间
time = request.args.get("time", 0)
if int(time) != 0:
if int(time) == 1:
page_data = page_data.order_by(
Movie.addtime.desc()
)
else:
page_data = page_data.order_by(
Movie.addtime.asc()
)
# 播放量
pm = request.args.get("pm", 0)
if int(pm) != 0:
if int(pm) == 1:
page_data = page_data.order_by(
Movie.playnum.desc()
)
else:
page_data = page_data.order_by(
Movie.playnum.asc()
)
# 评论量
cm = request.args.get("cm", 0)
if int(cm) != 0:
if int(cm) == 1:
page_data = page_data.order_by(
Movie.commentnum.desc()
)
else:
page_data = page_data.order_by(
Movie.commentnum.asc()
)
if page is None:
page = 1
page_data = page_data.paginate(page=page, per_page=8)
p = dict(
tid=tid,
star=star,
time=time,
pm=pm,
cm=cm,
)
return render_template(
"home/index.html",
tags=tags,
p=p,
page_data=page_data)
电影标签,电影星级,上映时间,播放数量,评论数量
-
tid = request.args.get("tid", 0)
获取到问号传参中的tid
没有获取到时置零 - 同理获取star
star = request.args.get("star", 0)
-
time = request.args.get("time", 0)
同理获取到time - 其他同理pm cm 这里的数量排序通过前台传过来是0还是1决定数量正序或倒序
碎碎念杂谈,将用户已选择的分类细节展示给用户,可以点击x去除。
![mark](https://i-blog.csdnimg.cn/blog_migrate/00b83d2406ffd7a012c0e6639aefa9b5.png)
可以使用jquery实现,清除组合的时候移除dom节点,显示的时候添加dom节点,然后在添加和清除的时候注意参数的添加和删除,组装成url,通过触发事件触发搜素!
查询出所有的tag,然后经模板渲染出菜单。
tags = Tag.query.all()
传递到模板进行填充。
![mark](https://i-blog.csdnimg.cn/blog_migrate/e4ce93982fb4387ae365f61a2de94ded.png)
p = dict(
tid=tid,
star=star,
time=time,
pm=pm,
cm=cm,
)
构建一个参数的字典。
![mark](https://i-blog.csdnimg.cn/blog_migrate/b917868f560b36b88dfc7247b597232d.png)
处理标签的链接
href="{{ url_for('home.index') }}?tid={{ v.id }}&star={{ p['star'] }}&time={{ p['time'] }}&pm={{ p['pm'] }}&cm={{ p['cm'] }}"
上面为标签添加href值。其中的tid是取自当前循环
href="{{ url_for('home.index') }}?tid={{ p['tid'] }}&star={{ v.star }}&time={{ p['time'] }}&pm={{ p['pm'] }}&cm={{ p['cm'] }}"
上面为star添加href值。其中的star是取自当前循环
其他项同理可得,将当前值取实际值,其他值取p['']
分页电影
if int(tid) != 0:
page_data = page_data.filter_by(tag_id=int(tid))
其他字段做相同的过滤操作。
进行分页效果展示。
![mark](https://i-blog.csdnimg.cn/blog_migrate/22855cec71d073938bfb4e14e1f94aba.png)
为所有的标签分类添加page=1
![mark](https://i-blog.csdnimg.cn/blog_migrate/732dc532c6a35cbcc69539713946a469.png)
电影搜索,搜索分页
- 模型: Movie
- 表单: 无
- 请求方法: GET
- 访问控制: 无
views中书写search相关的逻辑处理
@home.route("/search/<int:page>/")
def search(page=None):
"""
搜索
"""
if page is None:
page = 1
key = request.args.get("key", "")
movie_count = Movie.query.filter(
Movie.title.ilike('%' + key + '%')
).count()
page_data = Movie.query.filter(
Movie.title.ilike('%' + key + '%')
).order_by(
Movie.addtime.desc()
).paginate(page=page, per_page=10)
page_data.key = key
return render_template("home/search.html", movie_count=movie_count, key=key, page_data=page_data)
![mark](https://i-blog.csdnimg.cn/blog_migrate/0b4ce8025109ae39d55e184623af7335.png)
为layout中的搜索框添加id
添加id = do_search
![mark](https://i-blog.csdnimg.cn/blog_migrate/d7427c72ec08d99e1b046cc3f33f7e97.png)
![mark](https://i-blog.csdnimg.cn/blog_migrate/b745162b2b31fbd3c86f262a7f2f0a4e.png)
home页面中script标签中使用jQuery获取搜索框内key值。然后location跳转到search路由,并携带page key 参数
将这段代码再粘贴到其他需要搜索的页面
- layout home 都需要修改id
ilike
进行模糊匹配
在search页面进行最终结果的填充与分页。
又是这种重复的填充操作,自己瞎做吧。跟以前没什么两样。
新建一个s_page.html,把home_page复制过去就行。
然后依然万年不变import 调用page方法。
碎碎念积累,回车直接搜索:
$("...").keydown(function(e){
if(e.keyCode==13){
//处理事件
}
});
电影详情 电影播放 电影评论 电影收藏
- 模型: Movie
- 表单: 无
- 请求方法: GET
- 访问控制: 无
评论统计:
- 模型: Movie User Comment
- 表单: CommentForm
- 请求方法: GET POST
- 访问控制: 需登录
![mark](https://i-blog.csdnimg.cn/blog_migrate/8a76e081fc2994d4eed641a3f514c753.png)
如果当前未登录则显示请先登录才可以提交。
![mark](https://i-blog.csdnimg.cn/blog_migrate/824e0a92642c739e68df8421cb52de40.png)
只有用户登录状态才会显示评论框。
forms中添加commentsform
class CommentForm(FlaskForm):
content = TextAreaField(
label="内容",
validators=[
DataRequired("请输入内容!"),
],
description="内容",
render_kw={
"id": "input_content"
}
)
submit = SubmitField(
'提交评论',
render_kw={
"class": "btn btn-success",
"id": "btn-sub"
}
)
注意内容的id input_content
views中处理播放页面的数据业务逻辑,以及评论的业务逻辑
@home.route("/play/<int:id>/<int:page>/", methods=["GET", "POST"])
def play(id=None, page=None):
"""
播放电影
"""
# 查询出相关联的标签
movie = Movie.query.join(Tag).filter(
Tag.id == Movie.tag_id,
Movie.id == int(id)
).first_or_404()
if page is None:
page = 1
page_data = Comment.query.join(
Movie
).join(
User
).filter(
Movie.id == movie.id,
User.id == Comment.user_id
).order_by(
Comment.addtime.desc()
).paginate(page=page, per_page=10)
movie.playnum = movie.playnum + 1
form = CommentForm()
# 必须登录
if "user" in session and form.validate_on_submit():
data = form.data
comment = Comment(
content=data["content"],
movie_id=movie.id,
user_id=session["user_id"]
)
db.session.add(comment)
db.session.commit()
movie.commentnum = movie.commentnum + 1
db.session.add(movie)
db.session.commit()
flash("添加评论成功!", "ok")
return redirect(url_for('home.play', id=movie.id, page=1))
db.session.add(movie)
db.session.commit()
return render_template("home/play.html", movie=movie, form=form, page_data=page_data)
index以及search页面中的播放按钮,都添加上v.id 以及page
前往play中填充movie的具体数据。使用form填充评论框,使用pagedata填充已有评论
- 当然flash的位置,报错信息。submit之前的csrf都不要忘记,还有form的method
![mark](https://i-blog.csdnimg.cn/blog_migrate/961262f85471f0a839742a1891228a9b.png)
修改js中需要修改的player file title 等
为评论分页新建一个html ui/comment_page.html
复制s_page的内容
为commentspage添加id参数。url中也添加id
然后又是重复的引入和page方法调用。pagedata填充
为了安全,默认不会显示html的内容,添加safe
![mark](https://i-blog.csdnimg.cn/blog_migrate/5b3f495251cbd82d9c5fde6d1aa2076c.png)
查询出自己的评论记录显示在自己个人中心
@home.route("/comments/<int:page>/")
@user_login_req
def comments(page=None):
"""
个人中心评论记录
"""
if page is None:
page = 1
page_data = Comment.query.join(
Movie
).join(
User
).filter(
Movie.id == Comment.movie_id,
User.id == session["user_id"]
).order_by(
Comment.addtime.desc()
).paginate(page=page, per_page=10)
return render_template("home/comments.html", page_data=page_data)
将comments.html进行pagedata填充以及分页处理。
menu中评论记录url添加page=1
![mark](https://i-blog.csdnimg.cn/blog_migrate/854068a8faf335d5b739b9efabca0751.png)
收藏电影
- 模型: Movie User Moviecol
- 表单: 无
- 请求方法: GET
- 访问控制: 需登录
通过ajax异步方法添加电影收藏。
![mark](https://i-blog.csdnimg.cn/blog_migrate/326bb97233aebec81bf72d1b0f37ad89.png)
ajax的提示信息无处安放,添加上图id 为show_col_msg
play页面中有一个按钮是收藏电影,id为btn-col
![mark](https://i-blog.csdnimg.cn/blog_migrate/0315b6a80959123b0e399e8e7570ac4e.png)
为btn-col 设置点击事件,获取当前的movieid,获取当前的用户。
使用ajax,提交的地址。type:get,提交的数据,返回的数据类型json。
成功之后的处理函数res接收返回的json对象
@home.route("/moviecol/add/", methods=["GET"])
@user_login_req
def moviecol_add():
"""
添加电影收藏
"""
uid = request.args.get("uid", "")
mid = request.args.get("mid", "")
moviecol = Moviecol.query.filter_by(
user_id=int(uid),
movie_id=int(mid)
).count()
# 已收藏
if moviecol == 1:
data = dict(ok=0)
# 未收藏进行收藏
if moviecol == 0:
moviecol = Moviecol(
user_id=int(uid),
movie_id=int(mid)
)
db.session.add(moviecol)
db.session.commit()
data = dict(ok=1)
import json
return json.dumps(data)
处理个人中心中电影收藏的view
@home.route("/moviecol/<int:page>/")
@user_login_req
def moviecol(page=None):
"""
电影收藏
"""
if page is None:
page = 1
page_data = Moviecol.query.join(
Movie
).join(
User
).filter(
Movie.id == Moviecol.movie_id,
User.id == session["user_id"]
).order_by(
Moviecol.addtime.desc()
).paginate(page=page, per_page=10)
return render_template("home/moviecol.html", page_data=page_data)
page_data进行填充,进行分页
menus中将收藏电影
![mark](https://i-blog.csdnimg.cn/blog_migrate/375069f2490fe26d2004b762b32f19ec.png)
![mark](https://i-blog.csdnimg.cn/blog_migrate/45f2c58f7d655fada369f3283a2ea10a.png)
修改这里配置菜单显示哪些按钮。
- 换不存在的账号登录出现报错
builtins.AttributeError
AttributeError: 'NoneType' object has no attribute 'check_pwd'
解决方案:
user = User.query.filter_by(name=data["name"]).first()
if user:
if not user.check_pwd(data["pwd"]):
flash("密码错误!", "err")
return redirect(url_for("home.login"))
else:
flash("账户不存在!", "err")
return redirect(url_for("home.login"))
FLask 结合Redis消息队列实现电影弹幕
- 模型: Movie
- 表单: 无
- 请求方法: GET POST
- 访问控制: 无
- 消息队列: Redis
- Flask第三方扩展: Flask-Redis
- 弹幕播放器插件: dplayer.js(开源)
redis自行前往官网下载最新稳定版本
pip install flask-redis
前面的版本使用的是jwplayer
yum -y install gcc gcc-c++
tar zxf redis-4.0.1.tar.gz
cd redis-4.0.1/
可以看到当前目录有Makefile文件,安装。
make && make install
启动服务
./utils/install_server.sh
vim /etc/redis/6379.conf
修改bind的地址
redis-cli -h 192.x.x.x
弹幕播放器views实现
@home.route("/video/<int:id>/<int:page>/", methods=["GET", "POST"])
def video(id=None, page=None):
"""
弹幕播放器
"""
movie = Movie.query.join(Tag).filter(
Tag.id == Movie.tag_id,
Movie.id == int(id)
).first_or_404()
if page is None:
page = 1
page_data = Comment.query.join(
Movie
).join(
User
).filter(
Movie.id == movie.id,
User.id == Comment.user_id
).order_by(
Comment.addtime.desc()
).paginate(page=page, per_page=10)
movie.playnum = movie.playnum + 1
form = CommentForm()
if "user" in session and form.validate_on_submit():
data = form.data
comment = Comment(
content=data["content"],
movie_id=movie.id,
user_id=session["user_id"]
)
db.session.add(comment)
db.session.commit()
movie.commentnum = movie.commentnum + 1
db.session.add(movie)
db.session.commit()
flash("添加评论成功!", "ok")
return redirect(url_for('home.video', id=movie.id, page=1))
db.session.add(movie)
db.session.commit()
return render_template("home/video.html", movie=movie, form=form, page_data=page_data)
templates目录创建一个video.html把原本play中的代码粘贴过来
将原本的jwplayer部分的script可以直接全部去掉了
![mark](https://i-blog.csdnimg.cn/blog_migrate/c11470eeea88346f8f28ada0ff2300e5.png)
将评论这里的分页中视图改为video
将jwplayer的css去掉
进入播放需要的静态资源
<link rel="stylesheet" href="{{ url_for('static',filename='dplayer/dist/DPlayer.min.css') }}">
<script src="{{ url_for('static',filename='dplayer/plugin/flv.min.js') }}"></script>
<script src="{{ url_for('static',filename='dplayer/plugin/hls.min.js') }}"></script>
<script src="{{ url_for('static',filename='dplayer/dist/DPlayer.min.js') }}"></script>
修改样式
.dplayer-comment-setting-type>label{
display: inline;
定义播放器的样式
<div id="dplayer1" style="height:500px;width: 774px;"></div>
播放页面
<script>
var dp1 = new DPlayer({
element: document.getElementById('dplayer1'),
video: {
url: "{{ url_for('static',filename='uploads/'+movie.url) }}",
},
danmaku: {
id: '{{ movie.id }}',
api: "/tm/"
}
});
</script>
在官方网站的demo上开发者工具xhr可以看到弹幕发送及加载数据的接口。
发送的格式是json的字符串。
定义弹幕相关的处理方法
init文件中
from flask_redis import FlaskRedis
定义我们redis的url格式
app.config["REDIS_URL"] = "redis://127.0.0.1:6379/0"
rd = FlaskRedis(app)
views.py中引入
@home.route("/tm/", methods=["GET", "POST"])
def tm():
"""
弹幕消息处理
"""
import json
if request.method == "GET":
# 获取弹幕消息队列
id = request.args.get('id')
# 存放在redis队列中的键值
key = "movie" + str(id)
if rd.llen(key):
msgs = rd.lrange(key, 0, 2999)
res = {
"code": 1,
"danmaku": [json.loads(v) for v in msgs]
}
else:
res = {
"code": 1,
"danmaku": []
}
resp = json.dumps(res)
if request.method == "POST":
# 添加弹幕
data = json.loads(request.get_data())
msg = {
"__v": 0,
"author": data["author"],
"time": data["time"],
"text": data["text"],
"color": data["color"],
"type": data['type'],
"ip": request.remote_addr,
"_id": datetime.datetime.now().strftime("%Y%m%d%H%M%S") + uuid.uuid4().hex,
"player": [
data["player"]
]
}
res = {
"code": 1,
"data": msg
}
resp = json.dumps(res)
# 将添加的弹幕推入redis的队列中
rd.lpush("movie" + str(data["player"]), json.dumps(msg))
return Response(resp, mimetype='application/json')
原理:
![mark](https://i-blog.csdnimg.cn/blog_migrate/71242a2f6b8fa035cfff0740490dfbb5.png)
代码优化及bug处理
- 头像判断
- 关键字搜索分页
- 电影右侧播放页面滚动条
builtins.TypeError
TypeError: must be str, not NoneType
![mark](https://i-blog.csdnimg.cn/blog_migrate/72e8cad624e1265a24dab2f9bffadd99.png)
如果有头像,显示头像,没有头像就显示holder的空头像。
评论记录中的头像部分做相同处理
后台的会员列表也做同样的处理
![mark](https://i-blog.csdnimg.cn/blog_migrate/af73543e7583f15eba7688ef5faa7ae3.png)
后台的评论列表也做同样的处理
![mark](https://i-blog.csdnimg.cn/blog_migrate/8a5fb1e1169bc375eaa43baf20080c13.png)
![mark](https://i-blog.csdnimg.cn/blog_migrate/f883293a4a8d2b1c07b38bf5f242882a.png)
会员详情中的会员头像也要判断templates/admin/user_view.html
![mark](https://i-blog.csdnimg.cn/blog_migrate/de740fbfb2a5311851de4c62598314f7.png)
关键字搜索分页
搜索分页中当前key会丢失掉
![mark](https://i-blog.csdnimg.cn/blog_migrate/5990f1f34205d16318d2aeb7e0785f97.png)
spage中的key要传递进去。
当影片内容过长设置滚动条
![mark](https://i-blog.csdnimg.cn/blog_migrate/d59ae498f2d5c417f60a0f03ece21c95.png)
play 和 video中都做同样处理