You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

481 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import os.path
import time
from django.contrib import messages
from django.db.models import Max, Count
from django.shortcuts import render, redirect, reverse
from django.views.generic import View, ListView, DetailView
from .forms import RegisterForm, LoginForm, CommentForm
from .models import User, Movie, Movie_rating, Movie_hot
BASE = os.path.dirname(os.path.abspath(__file__))
class IndexView(ListView):
model = Movie
template_name = 'movie/index.html'
paginate_by = 15
context_object_name = 'movies'
ordering = 'imdb_id'
page_kwarg = 'p'
# 返回前1000部电影
def get_queryset(self):
return Movie.objects.filter(imdb_id__lte=1000)
# 获取上下文数据
def get_context_data(self, *, object_list=None, **kwargs):
context = super(IndexView, self).get_context_data(*kwargs)
paginator = context.get('paginator') # 分页器对象
page_obj = context.get('page_obj') # 当前页对象
pagination_data = self.get_pagination_data(paginator, page_obj) # 获取分页数据
context.update(pagination_data) # 返回更新后的上下文数据
return context
def get_pagination_data(self, paginator, page_obj, around_count=2):
current_page = page_obj.number
if current_page <= around_count + 2:
left_pages = range(1, current_page)
left_has_more = False
else:
left_pages = range(current_page - around_count, current_page)
left_has_more = True
if current_page >= paginator.num_pages - around_count - 1:
right_pages = range(current_page + 1, paginator.num_pages + 1)
right_has_more = False
else:
right_pages = range(current_page + 1, current_page + 1 + around_count)
right_has_more = True
return {
'left_pages': left_pages,
'right_pages': right_pages,
'current_page': current_page,
'left_has_more': left_has_more,
'right_has_more': right_has_more
}
# 热门电影视图
class PopularMovieView(ListView):
model = Movie_hot
template_name = 'movie/hot.html'
paginate_by = 15
context_object_name = 'movies'
page_kwarg = 'p'
def get_queryset(self):
# 初始化 计算评分人数最多的100部电影并保存到数据库中不建议每次都运行
movies = Movie.objects.annotate(nums=Count('movie_rating__score')).order_by('-nums')[:100]
for movie in movies:
record = Movie_hot(movie=movie, rating_number=movie.nums)
record.save()
hot_movies = Movie_hot.objects.all().values("movie_id")
movies = Movie.objects.filter(id__in=hot_movies).annotate(nums=Max('movie_hot__rating_number')).order_by(
'-nums')
return movies
def get_context_data(self, *, object_list=None, **kwargs):
context = super(PopularMovieView, self).get_context_data(*kwargs)
paginator = context.get('paginator')
page_obj = context.get('page_obj')
pagination_data = self.get_pagination_data(paginator, page_obj)
context.update(pagination_data)
return context
def get_pagination_data(self, paginator, page_obj, around_count=2):
current_page = page_obj.number
if current_page <= around_count + 2:
left_pages = range(1, current_page)
left_has_more = False
else:
left_pages = range(current_page - around_count, current_page)
left_has_more = True
if current_page >= paginator.num_pages - around_count - 1:
right_pages = range(current_page + 1, paginator.num_pages + 1)
right_has_more = False
else:
right_pages = range(current_page + 1, current_page + 1 + around_count)
right_has_more = True
return {
'left_pages': left_pages,
'right_pages': right_pages,
'current_page': current_page,
'left_has_more': left_has_more,
'right_has_more': right_has_more
}
# 电影分类视图
class TagView(ListView):
model = Movie
template_name = 'movie/tag.html'
paginate_by = 15
context_object_name = 'movies'
page_kwarg = 'p'
# 获取数据
def get_queryset(self):
# 未选择分类
if 'genre' not in self.request.GET.dict().keys() or self.request.GET.dict()['genre'] == "":
movies = Movie.objects.all()
return movies[100:200]
# 有分类选择
else:
movies = Movie.objects.filter(genre__name=self.request.GET.dict()['genre'])
print(movies)
return movies[:100]
def get_context_data(self, *, object_list=None, **kwargs):
context = super(TagView, self).get_context_data(*kwargs)
if 'genre' in self.request.GET.dict().keys():
genre = self.request.GET.dict()['genre']
context.update({'genre': genre})
paginator = context.get('paginator')
page_obj = context.get('page_obj')
pagination_data = self.get_pagination_data(paginator, page_obj)
context.update(pagination_data)
return context
def get_pagination_data(self, paginator, page_obj, around_count=2):
current_page = page_obj.number
if current_page <= around_count + 2:
left_pages = range(1, current_page)
left_has_more = False
else:
left_pages = range(current_page - around_count, current_page)
left_has_more = True
if current_page >= paginator.num_pages - around_count - 1:
right_pages = range(current_page + 1, paginator.num_pages + 1)
right_has_more = False
else:
right_pages = range(current_page + 1, current_page + 1 + around_count)
right_has_more = True
return {
'left_pages': left_pages,
'right_pages': right_pages,
'current_page': current_page,
'left_has_more': left_has_more,
'right_has_more': right_has_more
}
# 搜索电影视图
class SearchView(ListView):
model = Movie
template_name = 'movie/search.html'
paginate_by = 15
context_object_name = 'movies'
page_kwarg = 'p'
def get_queryset(self):
movies = Movie.objects.filter(name__icontains=self.request.GET.dict()['keyword'])
return movies
def get_context_data(self, *, object_list=None, **kwargs):
context = super(SearchView, self).get_context_data(*kwargs)
paginator = context.get('paginator')
page_obj = context.get('page_obj')
pagination_data = self.get_pagination_data(paginator, page_obj)
context.update(pagination_data)
context.update({'keyword': self.request.GET.dict()['keyword']})
return context
def get_pagination_data(self, paginator, page_obj, around_count=2):
current_page = page_obj.number
if current_page <= around_count + 2:
left_pages = range(1, current_page)
left_has_more = False
else:
left_pages = range(current_page - around_count, current_page)
left_has_more = True
if current_page >= paginator.num_pages - around_count - 1:
right_pages = range(current_page + 1, paginator.num_pages + 1)
right_has_more = False
else:
right_pages = range(current_page + 1, current_page + 1 + around_count)
right_has_more = True
return {
'left_pages': left_pages,
'right_pages': right_pages,
'current_page': current_page,
'left_has_more': left_has_more,
'right_has_more': right_has_more
}
# 注册视图
class RegisterView(View):
def get(self, request):
return render(request, 'movie/register.html')
def post(self, request):
form = RegisterForm(request.POST)
if form.is_valid():
form.save()
return redirect(reverse('movie:index'))
else:
# 表单验证失败,重定向到注册页面
errors = form.get_errors()
for error in errors:
messages.info(request, error)
print(form.errors.get_json_data())
return redirect(reverse('movie:register'))
# 登录视图
class LoginView(View):
def get(self, request):
return render(request, 'movie/login.html')
def post(self, request):
form = LoginForm(request.POST)
if form.is_valid():
name = form.cleaned_data.get('name')
pwd = form.cleaned_data.get('password')
remember = form.cleaned_data.get('remember')
user = User.objects.filter(name=name, password=pwd).first()
if user:
if remember:
# 设置为None则表示使用全局的过期时间
request.session.set_expiry(None)
else:
# 立即过期
request.session.set_expiry(0)
# 登录成功在session 存储当前用户的id作为标识
request.session['user_id'] = user.id
return redirect(reverse('movie:index'))
else:
messages.info(request, '用户名或者密码错误!')
return redirect(reverse('movie:login'))
else:
errors = form.get_errors()
for error in errors:
messages.info(request, error)
return redirect(reverse('movie:login'))
# 登出立即销毁session停止会话
def UserLogout(request):
request.session.set_expiry(-1)
return redirect(reverse('movie:index'))
# 电影详情视图
class MovieDetailView(DetailView):
model = Movie
template_name = 'movie/detail.html'
# 上下文对象的名称
context_object_name = 'movie'
# 重写获取上下文方法,增加评分参数
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# 判断是否登录用
login = True
try:
user_id = self.request.session['user_id']
except KeyError as e:
login = False # 未登录
# 获得电影的pk这里pk就是id
pk = self.kwargs['pk']
movie = Movie.objects.get(pk=pk)
if login:
# 已经登录,获取当前用户的历史评分数据
user = User.objects.get(pk=user_id)
rating = Movie_rating.objects.filter(user=user, movie=movie).first()
# 默认值
score = 0
comment = ''
if rating:
score = rating.score
comment = rating.comment
context.update({'score': score, 'comment': comment})
similarity_movies = movie.get_similarity()
# 获取与当前电影最相似的电影
context.update({'similarity_movies': similarity_movies})
# 判断是否登录,没有登录则不显示评分页面
context.update({'login': login})
return context
# 接受评分表单,pk是当前电影的数据库主键id
def post(self, request, pk):
form = CommentForm(request.POST)
if form.is_valid():
# 获取分数和评论
score = form.cleaned_data.get('score')
comment = form.cleaned_data.get('comment')
# 获取用户和电影
user_id = request.session['user_id']
user = User.objects.get(pk=user_id)
movie = Movie.objects.get(pk=pk)
# 更新一条记录
rating = Movie_rating.objects.filter(user=user, movie=movie).first()
if rating:
# 如果存在则更新
# print(rating)
rating.score = score
rating.comment = comment
rating.save()
else:
# 如果不存在则添加
rating = Movie_rating(user=user, movie=movie, score=score, comment=comment)
rating.save()
messages.info(request, "评论成功!")
else:
# 表单没有验证通过
messages.info(request, "评分不能为空!")
return redirect(reverse('movie:detail', args=(pk,)))
# 历史评分视图
class RatingHistoryView(DetailView):
model = User
template_name = 'movie/history.html'
# 上下文对象的名称
context_object_name = 'user'
def get_context_data(self, **kwargs):
# 这里要增加的对象:当前用户打过分的电影历史
context = super().get_context_data(**kwargs)
user_id = self.request.session['user_id']
user = User.objects.get(pk=user_id)
# 获取ratings即可
ratings = Movie_rating.objects.filter(user=user).order_by('-score')
context.update({'ratings': ratings})
return context
# 删除打分评论数据
def delete_recode(request, pk):
movie = Movie.objects.get(pk=pk)
user_id = request.session['user_id']
user = User.objects.get(pk=user_id)
rating = Movie_rating.objects.get(user=user, movie=movie)
rating.delete()
messages.info(request, f"删除 {movie.name} 评分记录成功!")
# 跳转回评分历史
return redirect(reverse('movie:history', args=(user_id,)))
# 推荐电影视图
class RecommendMovieView(ListView):
model = Movie
template_name = 'movie/recommend.html'
paginate_by = 15
context_object_name = 'movies'
ordering = 'movie_rating__score'
page_kwarg = 'p'
def __init__(self):
super().__init__()
# 最相似的20个用户
self.K = 20
# 推荐出10本书
self.N = 10
# 存放当前用户评分过的电影querySet
self.cur_user_movie_qs = None
# 获取用户相似度
def get_user_sim(self):
# 用户相似度字典,格式为{ user_id1:val , user_id2:val , ... }
user_sim_dct = dict()
'''获取用户之间的相似度,存放在user_sim_dct中'''
# 获取当前用户
cur_user_id = self.request.session['user_id']
cur_user = User.objects.get(pk=cur_user_id)
# 获取其它用户
other_users = User.objects.exclude(pk=cur_user_id) # 除了当前用户外的所有用户
# 当前用户评分过的电影
self.cur_user_movie_qs = Movie.objects.filter(user=cur_user)
# 计算当前用户与其他用户共同评分过的电影交集数
for user in other_users:
# 记录感兴趣的数量
user_sim_dct[user.id] = len(Movie.objects.filter(user=user) & self.cur_user_movie_qs)
# 按照key排序value返回K个最相近的用户共同评分过的电影交集数更多
print("user similarity calculated!")
# 格式 [ (user, value), (user, value), ... ]
return sorted(user_sim_dct.items(), key=lambda x: -x[1])[:self.K]
# 获取推荐电影(按照相似用户总得分排序)
def get_recommend_movie(self, user_lst):
# 电影兴趣值字典,{ movie:value, movie:value , ...}
movie_val_dct = dict()
# 用户,相似度
for user, _ in user_lst:
# 获取相似用户评分过的电影并且不在前用户的评分列表中的再加上score字段方便计算兴趣值
movie_set = Movie.objects.filter(user=user).exclude(id__in=self.cur_user_movie_qs).annotate(
score=Max('movie_rating__score'))
for movie in movie_set:
movie_val_dct.setdefault(movie, 0)
# 累计用户的评分
movie_val_dct[movie] += movie.score
return sorted(movie_val_dct.items(), key=lambda x: -x[1])[:self.N]
# 获取数据
def get_queryset(self):
s = time.time()
# 获得最相似的K个用户列表
user_lst = self.get_user_sim()
# 获得推荐电影的id
movie_lst = self.get_recommend_movie(user_lst)
# print(movie_lst)
result_lst = []
for movie, _ in movie_lst:
result_lst.append(movie)
e = time.time()
print(f"算法推荐用时:{e - s}秒!")
return result_lst
def get_context_data(self, *, object_list=None, **kwargs):
context = super(RecommendMovieView, self).get_context_data(*kwargs)
print(context)
paginator = context.get('paginator')
page_obj = context.get('page_obj')
pagination_data = self.get_pagination_data(paginator, page_obj)
context.update(pagination_data)
return context
def get_pagination_data(self, paginator, page_obj, around_count=2):
current_page = page_obj.number
if current_page <= around_count + 2:
left_pages = range(1, current_page)
left_has_more = False
else:
left_pages = range(current_page - around_count, current_page)
left_has_more = True
if current_page >= paginator.num_pages - around_count - 1:
right_pages = range(current_page + 1, paginator.num_pages + 1)
right_has_more = False
else:
right_pages = range(current_page + 1, current_page + 1 + around_count)
right_has_more = True
return {
'left_pages': left_pages,
'right_pages': right_pages,
'current_page': current_page,
'left_has_more': left_has_more,
'right_has_more': right_has_more
}