问题描述
COMP9900大作业做一个电影搜索平台,将搜索结果按照电影种类筛选查看是必不可少的功能。
任务目标: 以用户的评论过的所有电影包含的电影流派为推荐依据,转化为标签,让用户可以按照标签筛选电影库中匹配的结果
解决流程
1.数据库
login/models.py
from django.db import models
from movie.models import Movie
from datetime import datetime
# Create your models here.
# login/models.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
'''User Info'''
id = models.AutoField(primary_key=True)
email = models.EmailField(unique=True)
username = models.CharField(max_length=128, unique=True, verbose_name='User Name')
info = models.TextField(verbose_name="Personal profile", default='Hi!')
REQUIRED_FIELDS = ['email']
USERNAME_FIELD = 'username'
password = models.CharField(max_length=256)
c_time = models.DateTimeField(auto_now_add=True, verbose_name='Create time')
wishlist = models.ManyToManyField(Movie)
# is_authenticated=True
# is_anonymous=False
is_active=True
# is_staff=True
# has_module_perms=True
def __str__(self):
return self.username
class Meta:
ordering = ['id']
unique_together = (("username", "email"),)
verbose_name = 'Users'
verbose_name_plural = 'Users'
class Comment(models.Model):
star = models.FloatField(verbose_name='star', default=0)
content = models.TextField(verbose_name="review content", default='')
movie = models.ForeignKey(Movie, default='', on_delete=models.CASCADE, verbose_name='movie')
user = models.ForeignKey(User, default='', on_delete=models.CASCADE, verbose_name='user')
add_time = models.DateTimeField(default=datetime.now, verbose_name='add_time')
class Meta:
ordering = ['user']
verbose_name = 'Comment'
verbose_name_plural = verbose_name
def __str__(self):
return self.user.username
movie/models.py
from django.db import models
from urllib import request
import ssl
# Create your models here.
# Movie genres tag
class Tag(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100, verbose_name='名称')
class Meta:
indexes = [
models.Index(fields=['id', 'name']),
models.Index(fields=['id'], name='genres_id_idx'),
]
verbose_name = 'Genre'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Director(models.Model):
id = models.TextField(verbose_name="director_id",primary_key=True)
name = models.CharField(max_length=100, verbose_name='Name')
class Meta:
indexes = [
models.Index(fields=['id', 'name']),
models.Index(fields=['id'], name='id_idx'),
]
verbose_name = 'director'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Movie(models.Model):
"""
movie info
"""
id = models.TextField(verbose_name="IMDBID",primary_key=True)
# id = models.AutoField(primary_key=True)
title = models.CharField(verbose_name='Title', max_length=200,default='')
director = models.TextField(verbose_name="导演", default='',blank=True,null=True)
directors = models.ManyToManyField('Director',related_name='director_movie')
# tag= models.ForeignKey(Tag, default='', on_delete=models.DO_NOTHING, verbose_name='类型标签')
genres = models.ManyToManyField('Tag')
genres_text = models.TextField(verbose_name="genres in text", default='',blank=True,null=True)
info = models.TextField(verbose_name="电影简介", default='',blank=True,null=True)
year = models.CharField('Year', max_length=10, blank=True, null=True)
run_time = models.CharField('Run Time', max_length=10, blank=True, null=True)
logo = models.ImageField(upload_to='banner/%Y/%m', default='image/default.png',blank=True,null=True, max_length=100, verbose_name='封面')
star = models.FloatField(verbose_name='星级',default=0)
comment_nums = models.IntegerField(verbose_name='评论数',default=0)
img_path = models.TextField(verbose_name="image path", default='',blank=True)
# add_time = models.DateTimeField(default=datetime.now, verbose_name='添加时间')
def image_data(self):
head = {}
context = ssl._create_unverified_context()
head['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15'
# 创建Request对象并添加heads
req = request.Request('https://api.themoviedb.org/3/find/'
+self.id+'?api_key=1a0eeda873912b473a595640ef04b25e&language=en-US&external_source=imdb_id', headers=head)
# 传入创建好的Request对象
response = request.urlopen(req,context=context)
# 读取响应信息并解码
html = response.read().decode('utf-8')
img_url = html.split('"poster_path":"')[1].split('"')[0]
# 打印爬到的信息
return 'https://image.tmdb.org/t/p/w500/' + img_url
def image_url(self):
# img_addr = format_html('https://api.themoviedb.org/3/find/{}?api_key=1a0eeda873912b473a595640ef04b25e&language=en-US&external_source=imdb_id',self.imdb)
return 'https://image.tmdb.org/t/p/w500/' + '2pwwawlnti0BoluxFiksnVg5TZ7.jpg'
def __str__(self):
return self.title # 主要__str__最好是个字符串,不然你会遇到很多的坑,还有我们返回的这两个字段填写数据的时候必须写上数据,必然相加会报错,null类型和str类型不能相加等错误信息。
class Meta:
verbose_name = 'Movie'
verbose_name_plural = verbose_name
Tag类存的是电影种类,Movie和Tag是多对多(ManyToMany)的关系,一个电影可以是喜剧片也可以是恐怖片,同样一个种类也对应许多不同的电影。
2.url映射
第二步就要想好怎么去传递用户点击的是哪一个种类这个信息,这里我采用的方法是在url路径后加上"-xx"用数字来对应种类的id,在数据库中,Tag的形式是如下图的
因此“-1”代表 Thriller,“-3”就代表Horror,因为一共有27个种类,我选择50作为全部种类(不筛选)的代表数字。
movie/urls.py
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from django.conf.urls import url,include
from movie import views
urlpatterns = [
path('', views.IndexView, name='index'),
url(r'^search/(?P<keywords>.*)/$', views.SearchView.as_view(), name='search'),
url(r'^searchByGenresOrDirector/(?P<keywords>.*)/$', views.SearchByGenresView.as_view(), name='search_gd'),
path('logout/', views.Logout, name='logout'),
url(r'^detail/(?P<movie_id>.*)-(?P<genre_id>\d+)/$', views.MovieDetailView.as_view(), name="detail"),
url(r'^add_wishlist/(?P<movie_id>.*)/$', views.Add_wishlist.as_view(), name='add_wishlist'),
url(r'^comment/$', views.CommentView.as_view(), name='comment'),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
只需看detail那一列即可。
3. 后端逻辑
一般我习惯先写出后端代码再往前端填,也不知道是不是正确的顺序。
因为作业中要求拉黑用户不计入电影平均分,所以这部分还要加入计算电影评分的代码,有些冗长,几个部分已经注释标出,只需看Recommendation部分即可
用字典q来传递命令是个很出乎我意料的操作,我也是从网上看的,可能因为第一次用Django,觉得很神奇
class MovieDetailView(View):
"""
Movie Details
"""
def get(self, request, movie_id, genre_id):
"""
主体部分
"""
movie = Movie.objects.get(id=movie_id)
username = request.COOKIES['username']
user = User.objects.get(username=username)
if_in_wishlist = movie in user.wishlist.all()
if_commented = Comment.objects.filter(movie=movie_id,user=user)
try:
bannedlist = Bannedlist.objects.get(user=user).banned_user.all()
except:
bannedlist = ''
comments = Comment.objects.filter(Q(movie=movie_id) & ~Q(user__in=bannedlist))
if comments:
star = sum([c.star for c in comments]) / len([c.star for c in comments])
else:
star = 0
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
p = Paginator(comments, 8, request=request)
comments = p.page(page)
"""
Recommendation部分
"""
request_path = request.path
q = {} #筛选条件的字典
genre_list = Comment.objects.filter(Q(user=user)).values('movie').values('movie__genres','movie__genres__name')
if genre_id == '50':
# all the genres
pass
else:
# select one genres
genre = Tag.objects.get(id=int(genre_id))
commented_movies_genres = Comment.objects.filter(Q(user=user)).values('movie').values('movie__genres__id') # 感兴趣的genreid
q['genres__in'] = [genre_id,]
# request_path = request_path.split('-')[0]+str(genre_id)
recommendation_movies = Movie.objects.filter(**q).order_by('star','title')[:20]
try:
page = request.GET.get('page', 1)
except PageNotAnInteger:
page = 1
p_2 = Paginator(recommendation_movies, 3, request=request)
recommendation_movies = p_2.page(page)
"""
movie rate
"""
movies = Movie.objects.filter(**q).order_by('star','title')[:20]
star_dic = dict()
for m in movies:
movie_id = m.id
username = request.COOKIES['username']
user = User.objects.get(username=username)
try:
bannedlist = Bannedlist.objects.get(user=user).banned_user.all()
except:
bannedlist = ''
comment = Comment.objects.filter(Q(movie=movie_id) & ~Q(user__in=bannedlist))
if comment:
star = sum([c.star for c in comment]) / len([c.star for c in comment])
star_dic[movie_id] = star
else:
star_dic[movie_id] = 0
return render(request, 'movie/details.html', {"movie": movie, "comments": comments,
'if_in_wishlist': if_in_wishlist, "if_commented": if_commented,
"star":star, "recommendation_movies": recommendation_movies,
"current_url":request_path,"genre_list": genre_list,"star_dic":star_dic})
4. 前端代码
对我这个初学者来说也是难点之一,要自己定义模板标签simple_tag。
如果不知道这个是什么,参考一下我的上篇文章
movie/templatetags/movie_extras.py
from django.utils.safestring import mark_safe
from django import template
register = template.Library()
@register.simple_tag
def action_all(current_url):
"""
获取当前url,.../detail/tt000000-1/
:param current_url:.../detail/tt000000-1/
:return: all
"""
url_part_list = current_url.split('-')
if url_part_list[1].strip('/') == "50":
temp = "<a href='%s' class='active'>ALL</a>"
else:
temp = "<a href='%s'>ALL</a>"
url_part_list[1] = "50"
href = '-'.join(url_part_list)
temp = temp % (href,) #把链接填进去
return mark_safe(temp)
@register.simple_tag
def action(current_url, item):
# .../detail/tt000000-1/
# item: genres_id genre_name
url_part_list = current_url.split('-')
if str(item['movie__genres']) == url_part_list[1].strip('/'):
temp = "<a href='%s' class='active'>%s</a>"
else:
temp = "<a href='%s'>%s</a>"
url_part_list[1] = str(item['movie__genres'])
ur_str = '-'.join(url_part_list) # 拼接整体url
temp = temp % (ur_str, item['movie__genres__name']) # 生成对应的a标签
return mark_safe(temp) # 返回安全的html
movie/templates/movie/details.py
<ul>
<li><span>{% action_all current_url%}</span></li>
{% for item in genre_list %}
<li><span>{% action current_url item %}</span></li>
{% endfor %}
</ul>
主要代码就是这些,参考学习了这篇大佬的文章真的很感谢,这篇文章里情况更加复杂,是多层筛选的,方法是在url后面多加几个"-",代码也会更加复杂。