一、搭建项目
python版本: 3.7.8.
mysql版本:5.7.28.
1、创建虚拟环境
在d盘下创建一个文件夹my_work,然后在里面创建两个文件夹:pro和venv。
win+R输入cmd进入文件夹venv,然后执行以下命令创建虚拟环境:
python -m venv movie_manager激活虚拟环境:
cd movie_manager cd Scripts activate升级pip:
pip install --upgrade pip导入django:
pip install Django==3.0.7 -i https://pypi.mirrors.ustc.edu.cn/simple/ pip install PyMySQL==0.9.2 -i https://pypi.mirrors.ustc.edu.cn/simple/ pip install xadmin-django==3.0.2 -i https://pypi.mirrors.ustc.edu.cn/simple/ pip install mysqlclient==2.0.1 -i https://pypi.mirrors.ustc.edu.cn/simple/ pip install beautifulsoup4==4.9.3 -i https://pypi.mirrors.ustc.edu.cn/simple/然后进入pro目录
cd .. cd .. cd .. cd pro2、创建项目
执行命令:
django-admin startproject movie_manager3、创建子应用
切换到项目根目录:
cd movie_manager创建子应用
python manage.py startapp movie自此项目创建完成。
二、settings.py配置
1、创建数据库
使用MySQL可视化工具(比如Navicat)创建一个数据库movie_manager,
字符集选择utf8mb4:
2、PyCharm打开项目
使用PyCharm打开项目:file->open.
在项目根目录下创建以下文件夹:
imgs、log、media、static、template。
其中media中再创建一个文件夹movie_cover存放电影封面。
设置template,选中template->右键->Make Directory as- >Template Folder.
3、配置项目虚拟环境
4、允许所有网站访问
在movie_manager\settings.py中做修改:
ALLOWED_HOSTS = ['*']5、添加子应用
在movie_manager\settings.py中的INSTALLED_APPS加入子应用movie,如下:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'xadmin', 'crispy_forms', 'movie' ]6、添加template目录
在movie_manager\settings.py中的TEMPLATES中DIRS改为:
'DIRS': [os.path.join(BASE_DIR, 'template')],7、使用mysql数据库
把在movie_manager\settings.py中的DATABASES注释掉,改为:
ip = '127.0.0.1' DATABASE_NAME = 'movie_manager' # mysql数据库名称 DATABASE_USER = 'root' # mysql数据库用户名 DATABASE_PASS = 'ldc-root' # mysql数据库密码 DATABASE_HOST = ip # mysql数据库IP DATABASE_PORT = 3306 # mysql数据库端口 # 配置数据库 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # 修改数据库为MySQL,并进行配置 'NAME': DATABASE_NAME, # 'USER': DATABASE_USER, # 用户名 'PASSWORD': DATABASE_PASS, # 密码 'HOST': DATABASE_HOST, 'PORT': DATABASE_PORT, 'OPTIONS': {'charset': 'utf8mb4', } } }8、使用中文
把movie_manager\settings.py的LANGUAGE_CODE、TIME_ZONE和USE_TZ改为:
LANGUAGE_CODE = 'zh-hans' # 使用中国时区 TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False9、配置静态文件路由
把movie_manager\settings.py的STATIC_URL改为:
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ] # STATIC_ROOT = os.path.join(BASE_DIR, 'static') # 收集静态文件时打开,然后关闭STATICFILES_DIRS MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_USER_ICON = os.path.join(BASE_DIR, 'media/user_icon')三、models.py数据表
1、电影数据获取
从豆瓣电影top250获取数据
2、创建表
在movie\models.py中创建用户、类型表、电影表、评分表、收藏表、点赞表、评论表:
from django.db import models # 用户表 class User(models.Model): username = models.CharField(max_length=32, unique=True, verbose_name='账号') password = models.CharField(max_length=32, verbose_name='密码') phone = models.CharField(max_length=32, verbose_name='手机号码') name = models.CharField(max_length=32, verbose_name='名字', unique=True) address = models.CharField(max_length=32, verbose_name='地址') email = models.EmailField(verbose_name='邮箱') class Meta: db_table = 'user' verbose_name_plural = '用户' verbose_name = '用户' def __str__(self): return self.name # 类型表 class Types(models.Model): name = models.CharField(max_length=32, verbose_name='类型') intro = models.TextField(blank=True, null=True, verbose_name='简介') class Meta: db_table = 'types' verbose_name = '类型' verbose_name_plural = '类型' def __str__(self): return self.name # 电影表 class Movies(models.Model): title = models.CharField(verbose_name='电影名', max_length=128) director = models.CharField(verbose_name='导演', max_length=128, null=True, blank=True) writers = models.CharField(verbose_name='编剧', max_length=128, null=True, blank=True) actors = models.TextField(verbose_name='主演', null=True, blank=True) movie_type = models.ManyToManyField(Types, verbose_name='类型', blank=True) region = models.CharField(verbose_name='制片国家/地区', max_length=128, null=True, blank=True) language = models.CharField(verbose_name='语言', max_length=128, null=True, blank=True) detail_url = models.TextField(verbose_name='详情链接', null=True, blank=True) watch_url = models.TextField(verbose_name='观看链接', null=True, blank=True) screen_time = models.TextField(verbose_name='上映日期', null=True, blank=True) running_time = models.CharField(verbose_name='片长', max_length=255, null=True, blank=True) other_name = models.CharField(verbose_name='又名', max_length=255, null=True, blank=True) pic = models.FileField(verbose_name='封面图片', max_length=64, upload_to='movie_cover') score = models.FloatField(verbose_name='豆瓣评分', default=0) intro = models.TextField(verbose_name='简介', null=True, blank=True) look_num = models.IntegerField(default=0, verbose_name='浏览人数') like_num = models.IntegerField(default=0, verbose_name='点赞人数') collect_num = models.IntegerField(default=0, verbose_name='收藏人数') rate_num = models.IntegerField(default=0, verbose_name='评分人数') class Meta: db_table = 'movie' verbose_name = '电影' verbose_name_plural = '电影' def __str__(self): return self.title # 用户评分表 class RateMovie(models.Model): movie = models.ForeignKey(Movies, related_name='rate_movie', on_delete=models.CASCADE, blank=True, null=True, verbose_name='电影id') user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True, verbose_name='用户id') score = models.FloatField(verbose_name='评分') create_time = models.DateTimeField(verbose_name='添加时间', auto_now_add=True) class Meta: db_table = 'rate_movie' verbose_name = '评分表' verbose_name_plural = '评分表' # 用户收藏表 class CollectMovie(models.Model): movie = models.ForeignKey(Movies, on_delete=models.CASCADE, related_name='collect_movie', blank=True, null=True, verbose_name='电影id' ) user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True, verbose_name='用户id') create_time = models.DateTimeField(verbose_name='收藏时间', auto_now_add=True) class Meta: db_table = 'collect_movie' verbose_name = '电影收藏表' verbose_name_plural = '电影收藏表' # 用户点赞表 class LikeMovie(models.Model): movie = models.ForeignKey( Movies, on_delete=models.CASCADE, related_name='like_movie', blank=True, null=True, verbose_name='电影id') user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True, verbose_name='用户id') create_time = models.DateTimeField(verbose_name='点赞时间', auto_now_add=True) class Meta: db_table = 'like_movie' verbose_name = '电影点赞表' verbose_name_plural = '电影点赞表' # 用户评论表 class CommentMovie(models.Model): movie = models.ForeignKey(Movies, on_delete=models.CASCADE, blank=True, null=True, verbose_name='电影id') user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True, verbose_name='用户id') content = models.TextField(verbose_name='评论内容') create_time = models.DateTimeField(verbose_name='评论时间', auto_now_add=True) like_num = models.IntegerField(verbose_name='点赞数', default=0) like_users = models.TextField(null=True, blank=True, default=None, verbose_name='点赞用户id列表') is_show = models.BooleanField(default=True, verbose_name='是否显示') class Meta: db_table = 'comment_movie' verbose_name = '电影评论表' verbose_name_plural = '电影评论表'四、urls.py路由配置
1、修改movie_manager\urls.py
在movie文件夹下创建一个urls.py,并在movie_manager\urls.py分配路由。
其中movie_manager\urls.py改为:
import xadmin from django.urls import path, re_path, include from django.views.generic import RedirectView from django.views.static import serve from django.conf import settings urlpatterns = [ path('xadmin/', xadmin.site.urls), path("", include('movie.urls')), # favicon.cio re_path(r'^favicon\.ico$', RedirectView.as_view(url=r'media/favicon.ico')), re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), # re_path(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATICFILES_DIRS}), # 收集静态文件时关闭 path(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_ROOT}), # 收集静态文件时打开,然后关闭STATICFILES_DIRS ]2、修改movie\urls.py
先改为:
from django.urls import path, re_path from movie import views urlpatterns = [ ]后期会添加各种路由。
3、数据迁移
在pycharm左下角的Terminal里执行数据迁移命令
python manage.py makemigrations python manage.py migrate4、创建缓存表
python manage.py createcachetable5、收集静态文件
先把movie_manager\settings.py中的静态文件路由改为:
STATIC_URL = '/static/' # STATICFILES_DIRS = [ # os.path.join(BASE_DIR, 'static'), # ] STATIC_ROOT = os.path.join(BASE_DIR, 'static') # 收集静态文件时打开,然后关闭STATICFILES_DIRS然后执行:
python manage.py collectstatic执行成功后,把movie_manager\settings.py中的静态文件路由改为:
STATIC_URL = '/static/' STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ] # STATIC_ROOT = os.path.join(BASE_DIR, 'static') # 收集静态文件时打开,然后关闭STATICFILES_DIRS把movie_manager\urls.py改为:
"""movie_manager URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/3.0/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ import xadmin from django.urls import path, re_path, include from django.views.generic import RedirectView from django.views.static import serve from django.conf import settings urlpatterns = [ path('xadmin/', xadmin.site.urls), path("", include('movie.urls')), # favicon.cio re_path(r'^favicon\.ico$', RedirectView.as_view(url=r'media/favicon.ico')), re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), re_path(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATICFILES_DIRS}), # 收集静态文件时关闭 # path(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_ROOT}), # 收集静态文件时打开,然后关闭STATICFILES_DIRS ]6、创建后台管理员
python manage.py createsuperuser 设置账号为 root 邮箱为 1@qq.com 密码为 movie-root五、导入基础数据
把根目录下的movies.sql在mysql可视化工具中执行即可。
六、核心代码
1、static创建文件夹
在static目录下创建三个文件夹image、css、js和fonts用来存放前端需要使用到的文件。
2、base.html前端框架
在目录templates下创建前端页面框架base.html,代码如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! --> <meta name="description" content=""> <meta name="author" content=""> <link rel="icon" href="/media/books.png"> <title>电影推荐系统</title> {% block style %} {% endblock %} <!-- Bootstrap core CSS --> <link href="/static/css/bootstrap.min.css" rel="stylesheet"> <!-- Custom styles for this template --> <link href="/static/css/dashboard.css" rel="stylesheet"> <link href="/static/css/custom.css" rel="stylesheet"> {% block extrastyle %} {% endblock %} <script src="/static/js/ie-emulation-modes-warning.js"></script> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> <script src="/static/js/html5shiv.min.js"></script> <script src="/static/js/respond.min.js"></script> <![endif]--> </head> <body> <nav class="navbar navbar-inverse navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a rel="nofollow" class="navbar-brand" href="/">电影推荐系统</a> </div> <div id="navbar" class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> {% if request.session.login_in == True %} <li><a rel="nofollow" href="{% url 'personal' %}">{{ request.session.name }}</a></li> <li><a rel="nofollow" href="{% url 'logout' %}">退出</a></li> {% else %} <li><a rel="nofollow" href="{% url 'login' %}">登录</a></li> <li><a rel="nofollow" href="{% url 'register' %}">注册</a></li> {% endif %} </ul> <form class="navbar-form navbar-right" action="{% url 'search' %}" method='post'> {% csrf_token %} <label for="search"></label> <input id="search" type="text" class="form-control" name="search" placeholder="电影|导演|主演"/> <button class="btn btn-default" type="submit">提交</button> </form> </div> </div> </nav> {% block content-nav %}{% endblock %} <div class="container-fluid"> <div class="row" > <div class="col-sm-3 col-md-2 sidebar"> <ul class="nav nav-sidebar"> <li class="active"><a rel="nofollow" href="{% url 'all_movie' %}">全部电影<span class="sr-only">(current)</span></a></li> <li><a rel="nofollow" href="{% url 'new_movie' %}">最新电影</a></li> <li><a rel="nofollow" href="{% url 'hot_movie' %}">热门电影</a></li> <li><a rel="nofollow" href="{% url 'sort_movie' %}">电影分类</a></li> <li><a rel="nofollow" href="{% url 'recommend_movie' %}">猜你喜欢</a></li> <li><a rel="nofollow" href="{% url 'personal' %}">个人中心</a></li> </ul> </div> <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main" style="margin-right:0;padding-right:0;"> {% block right-panel-content %} {% endblock %} </div> </div> </div> <script src="/static/js/jquery-2.1.1.min.js"></script> <script src="/static/js/jquery.min.js"></script> <script src="/static/js/bootstrap.min.js"></script> <script src="/static/js/ie10-viewport-bug-workaround.js"></script> <script src="/static/js/custom.js"></script> <script src="/static/js/plugins/highstock/js/highstock.js"></script> <script src="/static/js/plugins/highstock/js/modules/exporting.js"></script> <script type="text/javascript"> window.__user_media_prefix__ = "/media/"; window.__user_path_prefix__ = ""; window.__user_language_code__ = ""; $(function ($) { {# 导航栏按钮渲染#} $(".sidebar").find("li").each(function () { var a = $(this).find("a:first")[0]; if ($(a).attr("href") === location.pathname) { $(this).addClass("active"); } else { $(this).removeClass("active"); } }); }); </script> {% block bottom-js %} {% endblock %} </body> </html>3、all_movie.html全部电影
在template下创建all_movie.html页面:
{% extends 'base.html' %} {% block right-panel-content %} <h3 class="text-center">{{ title }}</h3> {% if not movies %} <h3 class="text-center">对不起没有电影</h3> {% endif %} {% for movie in movies %} <div class="container-fluid"> <div class="row clearfix"> <div class="col-md-2 column"> <a rel="nofollow" href="{% url 'movie' movie.id %}"> <img class="img-thumbnail book-image" alt="140x140" src="/media/{{ movie.pic }}" width=140px height=140px/> </a> </div> <div class="col-md-7 column"> <h3> <a rel="nofollow" href="{% url 'movie' movie.id %}"> {{ movie.title }}</a> </h3> <p> <strong> <span style="margin-right: 2px">导演: </span> </strong> {{ movie.director }} </p> <p> <strong> <span style="margin-right: 2px">主演: </span> </strong> {{ movie.actors }} </p> <p> <strong> <span style="margin-right: 2px">上映日期: </span> </strong> {{ movie.screen_time }} </p> <p> <strong> <span style="margin-right: 2px">简介: </span> </strong> {{ movie.intro | slice:":100" }}...... </p> <p> <strong> <span style="margin-right: 2px">豆瓣评分: </span> </strong> {{ movie.score }} </p> <p> <strong> <span style="margin-right: 2px">浏览量: </span> {{ movie.look_num }} </strong> <strong> <span style="margin-right: 2px">点赞量: </span> {{ movie.like_num }} </strong><strong> <span style="margin-right: 2px">收藏量: </span> {{ movie.collect_num }} </strong> </p> </div> </div> </div> {% endfor %} <div class="container-fluid"> <ul class="pagination" id="pager"> {#上一页按钮开始#} {# 如果当前页有上一页#} {% if movies.has_previous %} {# 当前页的上一页按钮正常使用#} <li class="previous"><a rel="nofollow" href="{{ path }}?page={{ movies.previous_page_number }}">上一页</a></li> {% else %} {# 当前页的不存在上一页时,上一页的按钮不可用#} <li class="previous disabled"><a rel="nofollow" href="#">上一页</a></li> {% endif %} {#上一页按钮结束#} {# 页码开始#} <li class="item active"><a rel="nofollow" href="{{ path }}?page={{ movies.number }}">{{ movies.number }}</a></li> {#页码结束#} {# 下一页按钮开始#} {% if movies.has_next %} <li class="next"><a rel="nofollow" href="{{ path }}?page={{ movies.next_page_number }}">下一页</a></li> {% else %} <li class="next disabled"><a rel="nofollow" href="#">下一页</a></li> {% endif %} <li class="item"><a rel="nofollow" href="#"> {{ movies.number }}/{{ movies.paginator.num_pages }}</a> </li> </ul> </div> {% endblock %}3、movie/urls.py创建基础路由
movie/urls.py创建搜索、全部电影、最新电影、热门电影、电影分类、猜你喜欢个人中心等路由,代码如下:
# !/usr/bin/python # -*- coding: utf-8 -*- from django.urls import path from movie import views urlpatterns = [ path("import_movie/", views.import_movie, name="import_movie"), # 导入电影 path("", views.index, name="index"), # 首页 path("login/", views.login, name="login"), # 登录 path("register/", views.register, name="register"), # 注册 path("logout/", views.logout, name="logout"), # 退出 path("modify_pwd/", views.modify_pwd, name="modify_pwd"), # 修改密码 path("search/", views.search, name="search"), # 搜索 path("all_movie/", views.all_movie, name="all_movie"), # 所有电影 path("movie/<int:movie_id>/", views.movie, name="movie"), # 具体的电影 path("score/<int:movie_id>/", views.score, name="score"), # 评分 path("comment/<int:movie_id>/", views.comment, name="comment"), # 评论 path("comment_like/<int:comment_id>/", views.comment_like, name="comment_like"), # 给评论点赞 path("collect/<int:movie_id>/", views.collect, name="collect"), # 收藏 path("like/<int:movie_id>/", views.like, name="like"), # 点赞 path("new_movie/", views.new_movie, name="new_movie"), # 最新电影 path("hot_movie/", views.hot_movie, name="hot_movie"), # 热门电影 path("sort_movie/", views.sort_movie, name="sort_movie"), # 电影分类 path("recommend_movie/", views.recommend_movie, name="recommend_movie"), # 猜你喜欢 path("personal/", views.personal, name="personal"), # 个人中心 path("my_like/", views.my_like, name="my_like"), # 获取我的点赞 path("my_collect/", views.my_collect, name="my_collect"), # 获取我的收藏 path("my_rate/", views.my_rate, name="my_rate"), # 我打分过的电影 path("delete_rate/<int:rate_id>", views.delete_rate, name="delete_rate"), # 取消评分 path("my_comments/", views.my_comments, name="my_comments"), # 我的评论 path("delete_comment/<int:comment_id>", views.delete_comment, name="delete_comment"), # 取消评论 ]在movie\views.py中为每个路由创建响应。
4、登录
def login(request): if request.method == "POST": form = Login(request.POST) if form.is_valid(): username = form.cleaned_data["username"] password = form.cleaned_data["password"] result = User.objects.filter(username=username) if result: user = User.objects.get(username=username) if user.password == password: request.session["login_in"] = True request.session["user_id"] = user.id request.session["name"] = user.name return redirect(reverse("all_movie")) else: return render( request, "login.html", {"form": form, "error": "账号或密码错误"} ) else: return render( request, "login.html", {"form": form, "error": "账号不存在"} ) else: form = Login() return render(request, "login.html", {"form": form})5、注册
def register(request): if request.method == "POST": form = RegisterForm(request.POST) error = None if form.is_valid(): username = form.cleaned_data["username"] password = form.cleaned_data["password2"] email = form.cleaned_data["email"] name = form.cleaned_data["name"] phone = form.cleaned_data["phone"] address = form.cleaned_data["address"] User.objects.create( username=username, password=password, email=email, name=name, phone=phone, address=address, ) # 根据表单数据创建一个新的用户 return redirect(reverse("login")) # 跳转到登录界面 else: return render( request, "register.html", {"form": form, "error": error} ) # 表单验证失败返回一个空表单到注册页面 form = RegisterForm() return render(request, "register.html", {"form": form})6、登出
def logout(request): if not request.session.get("login_in", None): # 不在登录状态跳转回首页 return redirect(reverse("index")) request.session.flush() # 清除session信息 return redirect(reverse("index"))7、修改密码
@login_in def modify_pwd(request): # 获取我的信息 user = User.objects.get(id=request.session.get("user_id")) if request.method != "POST": return render(request, '404.html') form = Edit(instance=user, data=request.POST) if form.is_valid(): form.save() # return redirect(reverse("personal")) return render(request, "personal.html", {"inform_message": "修改成功", "inform_type": "success", "form": form}) else: return render(request, "personal.html", {"inform_message": "修改失败", "inform_type": "danger", "form": form})8、搜索
def search(request): # 搜索 if request.method == "POST": # 搜索提交 key = request.POST["search"] request.session["search"] = key # 记录搜索关键词解决跳页问题 else: key = request.session.get("search") # 得到关键词 # 进行内容的模糊搜索 movies = Movies.objects.filter(Q(title__icontains=key) | Q(director__icontains=key) | Q(actors__icontains=key)) page_num = request.GET.get("page", 1) movies = movies_paginator(movies, page_num) return render(request, "all_movie.html", {"movies": movies})9、所有电影
def all_movie(request): # 按评分进行排序 movies = Movies.objects.all().order_by('-score') paginator = Paginator(movies, 10) current_page = request.GET.get("page", 1) movies = paginator.page(current_page) return render(request, "all_movie.html", {"movies": movies, "title": "全部电影"})10、具体的电影
def movie(request, movie_id): # 获取具体的电影 user_id = request.session.get("user_id") movie = Movies.objects.get(pk=movie_id) movie.look_num += 1 movie.save() comments = movie.commentmovie_set.filter(is_show=True).order_by("-create_time") rate = RateMovie.objects.filter(movie=movie).aggregate(Avg("score")).get("score__avg", 0) rate = rate if rate else 0 movie_rate = round(rate, 2) if user_id: user = User.objects.get(pk=user_id) is_collect = True if movie.collect_movie.filter(user_id=user_id) else False is_like = True if movie.like_movie.filter(user_id=user_id) else False is_rate = RateMovie.objects.filter(movie=movie, user=user).first() recoommend_movies = recommend_by_user_id(user_id, movie_id) else: recoommend_movies = Movies.objects.all().exclude(pk=movie_id).order_by("-like_num")[:3] rate_num = movie.rate_num collect_num = movie.collect_num return render(request, "movie.html", locals())11、评分
@login_in def score(request, movie_id): user = User.objects.get(id=request.session.get("user_id")) movie = Movies.objects.get(id=movie_id) score = float(request.POST.get("score", 0)) is_rate = RateMovie.objects.filter(movie=movie, user=user) if not is_rate: movie.rate_num += 1 movie.save() RateMovie.objects.get_or_create(user=user, movie=movie, defaults={"score": score}) is_rate = {'score': score} else: is_rate = is_rate.first() comments = movie.commentmovie_set.filter(is_show=True).order_by("-create_time") user_id = request.session.get("user_id") rate = RateMovie.objects.filter(movie=movie).aggregate(Avg("score")).get("score__avg", 0) rate = rate if rate else 0 movie_rate = round(rate, 2) is_collect = True if movie.collect_movie.filter(user_id=user_id) else False is_like = True if movie.like_movie.filter(user_id=user_id) else False rate_num = movie.rate_num collect_num = movie.collect_num recoommend_movies = recommend_by_user_id(user_id, movie_id) return render(request, "movie.html", locals())12、评论
@login_in def comment(request, movie_id): # 评论 user = User.objects.get(id=request.session.get("user_id")) movie = Movies.objects.get(id=movie_id) comment = request.POST.get("comment", "") CommentMovie.objects.create(user=user, movie=movie, content=comment) comments = movie.commentmovie_set.filter(is_show=True).order_by("-create_time") user_id = request.session.get("user_id") rate = RateMovie.objects.filter(movie=movie).aggregate(Avg("score")).get("score__avg", 0) rate = rate if rate else 0 movie_rate = round(rate, 2) is_collect = True if movie.collect_movie.filter(user_id=user_id) else False is_like = True if movie.like_movie.filter(user_id=user_id) else False is_rate = RateMovie.objects.filter(movie=movie, user=user).first() rate_num = movie.rate_num collect_num = movie.collect_num recoommend_movies = recommend_by_user_id(user_id, movie_id) return render(request, "movie.html", locals())13、给评论点赞
@login_in def comment_like(request, comment_id): user_id = request.session.get("user_id") user = User.objects.get(id=user_id) comment = CommentMovie.objects.get(id=comment_id) if not comment.like_users: comment.like_users = '{},'.format(user_id) comment.like_num += 1 elif str(user_id) not in comment.like_users.split(','): comment.like_users += '{},'.format(user_id) comment.like_num += 1 else: pass comment.save() movie = comment.movie comments = movie.commentmovie_set.filter(is_show=True).order_by("-create_time") user_id = request.session.get("user_id") rate = RateMovie.objects.filter(movie=movie).aggregate(Avg("score")).get("score__avg", 0) rate = rate if rate else 0 movie_rate = round(rate, 2) is_collect = True if movie.collect_movie.filter(user_id=user_id) else False is_like = True if movie.like_movie.filter(user_id=user_id) else False is_rate = RateMovie.objects.filter(movie=movie, user=user).first() rate_num = movie.rate_num collect_num = movie.collect_num recoommend_movies = ItemCf(user_id, movie.id).recommendation() return render(request, "movie.html", locals())14、收藏
@login_in def collect(request, movie_id): user_id = request.session.get("user_id") user = User.objects.get(id=user_id) movie = Movies.objects.get(id=movie_id) collects = movie.collect_movie.filter(user_id=user_id) if collects: # 已经存在收藏,用户取消收藏 collect_num_ = 0 for collect in collects: collect.delete() collect_num_ -= 1 is_collect = False else: # 未存在收藏,创建收藏记录 CollectMovie.objects.create(movie=movie, user=user) is_collect = True collect_num_ = 1 movie.collect_num += collect_num_ # 收藏人数加1 movie.save() comments = movie.commentmovie_set.filter(is_show=True).order_by("-create_time") rate = RateMovie.objects.filter(movie=movie).aggregate(Avg("score")).get("score__avg", 0) rate = rate if rate else 0 movie_rate = round(rate, 2) is_like = True if movie.like_movie.filter(user_id=user_id) else False is_rate = RateMovie.objects.filter(movie=movie, user=user).first() rate_num = movie.rate_num collect_num = movie.collect_num recoommend_movies = recommend_by_user_id(user_id, movie_id) return render(request, "movie.html", locals())15、点赞
@login_in def like(request, movie_id): user_id = request.session.get("user_id") user = User.objects.get(id=user_id) movie = Movies.objects.get(id=movie_id) likes = movie.like_movie.filter(user_id=user_id) if likes: # 已经存在点赞,用户取消点赞 like_num_ = 0 for like in likes: like.delete() like_num_ -= 1 is_like = False else: # 未存在点赞,创建点赞记录 LikeMovie.objects.create(movie=movie, user=user) is_like = True like_num_ = 1 movie.like_num += like_num_ # 收藏人数加1 movie.save() comments = movie.commentmovie_set.filter(is_show=True).order_by("-create_time") rate = RateMovie.objects.filter(movie=movie).aggregate(Avg("score")).get("score__avg", 0) rate = rate if rate else 0 movie_rate = round(rate, 2) is_collect = True if movie.collect_movie.filter(user_id=user_id) else False is_rate = RateMovie.objects.filter(movie=movie, user=user).first() rate_num = movie.rate_num collect_num = movie.collect_num like_num = movie.like_num recoommend_movies = recommend_by_user_id(user_id, movie_id) return render(request, "movie.html", locals())16、猜你喜欢
基于物品协同过滤推荐算法
@login_in def recommend_movie(request): page = request.GET.get("page", 1) user_id = request.session.get("user_id") recoommend_movies = ItemCf(user_id).recommendation() movies = movies_paginator(recoommend_movies, page) path = request.path title = "猜你喜欢" return render(request, "all_movie.html", {"movies": movies, "path": path, "title": title})17、推荐算法
在项目根目录下的recommend_movies.py文件中:
# -*-coding:utf-8-*- import os import django import operator from movie.models import * from math import sqrt, pow os.environ["DJANGO_SETTINGS_MODULE"] = "movie.settings" django.setup() class UserCf: # 基于用户协同算法来获取推荐列表 """ 利用用户的群体行为来计算用户的相关性。 计算用户相关性的时候我们就是通过对比他们对相同物品打分的相关度来计算的 举例: --------+--------+--------+--------+--------+ | X | Y | Z | R | --------+--------+--------+--------+--------+ a | 5 | 4 | 1 | 5 | --------+--------+--------+--------+--------+ b | 4 | 3 | 1 | ? | --------+--------+--------+--------+--------+ c | 2 | 2 | 5 | 1 | --------+--------+--------+--------+--------+ a用户给X物品打了5分,给Y打了4分,给Z打了1分 b用户给X物品打了4分,给Y打了3分,给Z打了1分 c用户给X物品打了2分,给Y打了2分,给Z打了5分 那么很容易看到a用户和b用户非常相似,但是b用户没有看过R物品, 那么我们就可以把和b用户很相似的a用户打分很高的R物品推荐给b用户, 这就是基于用户的协同过滤。 """ # 获得初始化数据 def __init__(self, data): self.data = data # 通过用户名获得电影列表,仅调试使用 def getItems(self, username1, username2): return self.data[username1], self.data[username2] # 计算两个用户的皮尔逊相关系数 def pearson(self, user1, user2): # 数据格式为:电影id,浏览次数 print("user message", user1) sumXY = 0.0 n = 0 sumX = 0.0 sumY = 0.0 sumX2 = 0.0 sumY2 = 0.0 for movie1, score1 in user1.items(): if movie1 in user2.keys(): # 计算公共的电影浏览次数 n += 1 sumXY += score1 * user2[movie1] sumX += score1 sumY += user2[movie1] sumX2 += pow(score1, 2) sumY2 += pow(user2[movie1], 2) if n == 0: print("p氏距离为0") return 0 molecule = sumXY - (sumX * sumY) / n denominator = sqrt((sumX2 - pow(sumX, 2) / n) * (sumY2 - pow(sumY, 2) / n)) if denominator == 0: print("共同特征为0") return 0 r = molecule / denominator print("p氏距离:", r) return r # 计算与当前用户的距离,获得最临近的用户 def nearest_user(self, username, n=1): distances = {} # 用户,相似度 # 遍历整个数据集 for user, rate_set in self.data.items(): # 非当前的用户 if user != username: distance = self.pearson(self.data[username], self.data[user]) # 计算两个用户的相似度 distances[user] = distance closest_distance = sorted( distances.items(), key=operator.itemgetter(1), reverse=True ) # 最相似的N个用户 print("closest user:", closest_distance[:n]) return closest_distance[:n] # 给用户推荐电影 def recommend(self, username, n=1): recommend = {} nearest_user = self.nearest_user(username, n) for user, score in dict(nearest_user).items(): # 最相近的n个用户 for movie_id, scores in self.data[user].items(): # 推荐的用户的电影列表 rate = RateMovie.objects.filter(movie_id=movie_id, user__username=user) # 如果用户评分低于3分,则表明用户不喜欢此电影,则不推荐给别的用户 if rate and rate.first().score < 3: continue if movie_id not in recommend.keys(): # 添加到推荐列表中 recommend[movie_id] = scores # 对推荐的结果按照电影浏览次数排序 return sorted(recommend.items(), key=operator.itemgetter(1), reverse=True) def recommend_by_user_id(user_id, movie_id=None): # 通过用户协同算法来进行推荐 current_user = User.objects.get(id=user_id) # 如果当前用户没有打分 则按照热度顺序返回 if current_user.ratemovie_set.count() == 0: if movie_id: movie_list = Movies.objects.exclude(pk=movie_id).order_by("-like_num")[:3] else: movie_list = Movies.objects.all().order_by("-like_num")[:3] return movie_list users = User.objects.all() all_user = {} for user in users: rates = user.ratemovie_set.all() rate = {} # 用户有给电影打分 if rates: for i in rates: rate.setdefault(str(i.movie.id), i.score) all_user.setdefault(user.username, rate) else: # 用户没有为电影打过分,设为0 all_user.setdefault(user.username, {}) print("this is all user:", all_user) user_cf = UserCf(data=all_user) recommend_list = user_cf.recommend(current_user.username, 3) good_list = [each[0] for each in recommend_list] print('this is the good list', good_list) if not good_list: # 如果没有找到相似用户喜欢的电影则按照热度顺序返回 if movie_id: movie_list = Movies.objects.exclude(pk=movie_id).order_by("-score")[:3] else: movie_list = Movies.objects.all().order_by("-score")[:3] return movie_list if movie_id and str(movie_id) in good_list: good_list.pop(good_list.index(str(movie_id))) # 不推荐电影movie_id if not good_list: # 如果没有找到相似用户喜欢的电影则按照热度顺序返回 if movie_id: movie_list = Movies.objects.exclude(pk=movie_id).order_by("-score")[:3] else: movie_list = Movies.objects.all().order_by("-score")[:3] return movie_list movie_list = Movies.objects.filter(id__in=good_list).order_by("-score")[:3] return movie_list class ItemCf: # 基于物品协同算法来获取推荐列表 ''' 1.构建⽤户–>物品的对应表 2.构建物品与物品的关系矩阵(同现矩阵) 3.通过求余弦向量夹角计算物品之间的相似度,即计算相似矩阵 4.根据⽤户的历史记录,给⽤户推荐物品 ''' def __init__(self, user_id, movie_id=None): self.movie_id = movie_id # 电影id self.user_id = user_id # 用户id def get_data(self): # 获取用户评分过的电影 rate_movies = RateMovie.objects.filter() if not rate_movies: return False datas = {} for rate_movie in rate_movies: user_id = rate_movie.user_id if user_id not in datas: datas.setdefault(user_id,{}) datas[user_id][rate_movie.movie.id] = rate_movie.score else: datas[user_id][rate_movie.movie.id] = rate_movie.score return datas def similarity(self, data): # 1 构造物品:物品的共现矩阵 N = {} # 喜欢物品i的总⼈数 C = {} # 喜欢物品i也喜欢物品j的⼈数 for user, item in data.items(): for i, score in item.items(): N.setdefault(i, 0) N[i] += 1 C.setdefault(i, {}) for j, scores in item.items(): if j != i: C[i].setdefault(j, 0) C[i][j] += 1 print("---1.构造的共现矩阵---") print('N:', N) print('C', C) # 2 计算物品与物品的相似矩阵 W = {} for i, item in C.items(): W.setdefault(i, {}) for j, item2 in item.items(): W[i].setdefault(j, 0) W[i][j] = C[i][j] / sqrt(N[i] * N[j]) print("---2.构造的相似矩阵---") print(W) return W def recommand_list(self, data, W, user, k=15, N=10): ''' # 3.根据⽤户的历史记录,给⽤户推荐物品 :param data: 用户数据 :param W: 相似矩阵 :param user: 推荐的用户 :param k: 相似的k个物品 :param N: 推荐物品数量 :return: ''' rank = {} for i, score in data[user].items(): # 获得⽤户user历史记录,如A⽤户的历史记录为{'唐伯虎点秋香': 5, '逃学威龙1': 1, '追龙': 2} for j, w in sorted(W[i].items(), key=operator.itemgetter(1), reverse=True)[0:k]: # 获得与物品i相似的k个物品 if j not in data[user].keys(): # 该相似的物品不在⽤户user的记录⾥ rank.setdefault(j, 0) rank[j] += float(score) * w # 预测兴趣度=评分*相似度 print("---3.推荐----") print(sorted(rank.items(), key=operator.itemgetter(1), reverse=True)[0:N]) return sorted(rank.items(), key=operator.itemgetter(1), reverse=True)[0:N] def recommendation(self): """ 给用户推荐相似电影 """ data = self.get_data() if not data or self.user_id not in data: # 用户没有评分过任何电影,就返回前3本热门电影,按点赞量降序返回 movie_list = Movies.objects.all().exclude(pk=self.movie_id).order_by("-like_num")[:15] return movie_list W = self.similarity(data) # 计算物品相似矩阵 sort_rank = self.recommand_list(data, W, self.user_id, 15, 10) # 推荐 if not sort_rank: # 用户没有评分过任何电影,就返回前15本热门电影,按点赞量降序返回 movie_list = Movies.objects.all().exclude(pk=self.movie_id).order_by("-like_num")[:15] return movie_list movie_list = Movies.objects.filter(id__in=[s[0] for s in sort_rank]).exclude(pk=self.movie_id).order_by("-like_num")[:15] return movie_list18、个人中心
@login_in def personal(request): user = User.objects.get(id=request.session.get("user_id")) form = Edit(instance=user) return render(request, "personal.html", {"form": form})19、我的点赞
# 我的点赞 @login_in def my_like(request): like_movies = LikeMovie.objects.filter(user_id=request.session.get("user_id")) return render(request, "my_like.html", {"like_movies": like_movies})七、后台管理
1、创建adminx.py文件
在子应用movie下创建一个adminx.py文件:
里面代码为:
# !/usr/bin/python # -*- coding: utf-8 -*- import xadmin from django.utils.safestring import mark_safe from xadmin import views from django.conf import settings from .models import * # https://fontawesome.dashgame.com/ 图标字体网站 # 基础设置 class BaseSetting(object): enable_themes = True # 使用主题 use_bootswatch = True # 全局设置 class GlobalSettings(object): site_title = '电影管理系统' # 标题 site_footer = mark_safe(settings.SITE_FOOTER) # 页尾 site_url = '/' menu_style = 'accordion' # 设置左侧菜单 折叠样式 # 用户管理 class UserAdmin(object): search_fields = ['username', 'phone', 'name'] # 检索字段 list_display = ['id', 'username', 'phone', 'name'] # 要显示的字段 list_per_page = 30 # 默认每页显示多少条记录,默认是100条 model_icon = 'fa fa-users' # 左侧小图标 list_editable = ['name', 'address'] # 可编辑字段 # 类型管理 class TypesAdmin(object): search_fields = ['name'] # 检索字段 list_display = ['id', 'name', 'intro'] list_filter = ['name'] ordering = ('id',) model_icon = 'fa fa-tags' # 左侧小图标 # 电影管理 class MovieAdmin(object): search_fields = ['title', 'director', 'actors'] # 检索字段 list_display = ['id', 'show_pic', 'title', 'director', 'screen_time', 'show_intro', 'movie_type', 'score','look_num', 'like_num', 'collect_num', 'rate_num'] # 分组过滤的字段 ordering = ('id',) # 设置默认排序字段,负号表示降序排序 list_per_page = 30 # 默认每页显示多少条记录,默认是100条 model_icon = 'fa fa-book' # 左侧小图标 list_editable = ['title', 'director', 'intro', 'running_time'] # 可编辑字段 style_fields = {'movie_type': 'm2m_transfer'} # 控制字段的显示样式 filter_horizontal = ('movie_type', ) # 水平选择编辑多对多字段 def show_pic(self, obj): # 显示书籍封面 if obj.pic.name: text = """ <style type="text/css"> #div1 img{ cursor: pointer; transition: all 0.6s; } #div1 img:hover{ transform: scale(2); } </style> <div id="div1"> <img src="%s" style="width:50px;"/> </div> """ % (self.request.build_absolute_uri('/') + 'media/' + obj.pic.name) return mark_safe(text) return '' def show_intro(self, obj): # 显示简介 if not obj.intro: return mark_safe('') if len(obj.intro) < 20: return mark_safe(obj.intro) short_id = f'{obj._meta.db_table}_short_text_{obj.id}' short_text = obj.intro[:len(obj.intro) // 4] + '......' detail_id = f'{obj._meta.db_table}_detail_text_{obj.id}' detail_text = obj.intro text = """<style type="text/css"> #%s,%s {padding:10px;border:1px solid green;} </style> <script type="text/javascript"> function openShutManager(oSourceObj,oTargetObj,shutAble,oOpenTip,oShutTip,oShortObj){ var sourceObj = typeof oSourceObj == "string" ? document.getElementById(oSourceObj) : oSourceObj; var targetObj = typeof oTargetObj == "string" ? document.getElementById(oTargetObj) : oTargetObj; var shortObj = typeof oShortObj == "string" ? document.getElementById(oShortObj) : oShortObj; var openTip = oOpenTip || ""; var shutTip = oShutTip || ""; if(targetObj.style.display!="none"){ if(shutAble) return; targetObj.style.display="none"; shortObj.style.display="block"; if(openTip && shutTip){ sourceObj.innerHTML = shutTip; } } else { targetObj.style.display="block"; shortObj.style.display="none"; if(openTip && shutTip){ sourceObj.innerHTML = openTip; } } } </script> <p id="%s" title="%s">%s</p> <p><a rel="nofollow" href="###" onclick="openShutManager(this,'%s',false,'点击关闭','点击展开','%s')">点击展开</a></p> <p id="%s" style="display:none"> %s </p> """ % ( short_id, detail_id, short_id, detail_text, short_text, detail_id, short_id, detail_id, detail_text) return mark_safe(text) def save_models(self): flag = self.org_obj is None and 'create' or 'change' if flag == 'create': if self.new_obj.pic.name: self.new_obj.pic.name = f"{self.new_obj.title}.{self.new_obj.pic.name.split('.')[1]}" if flag == 'change' and 'pic' in self.change_message(): if self.org_obj.pic.name: self.org_obj.pic.name = f"{self.org_obj.title}.{self.org_obj.pic.name.split('.')[1]}" super().save_models() show_pic.short_description = '封面' show_intro.short_description = '描述' # 电影评分管理 class RateAdmin(object): search_fields = ['movie__title', 'user__name', 'score'] # 检索字段 list_display = ['movie', 'user', 'score', 'create_time'] # 要显示的字段 list_filter = ['score', 'create_time'] # 分组过滤的字段 ordering = ('id',) # 设置默认排序字段,负号表示降序排序 list_per_page = 30 # 默认每页显示多少条记录,默认是100条 list_editable = [] # 可编辑字段 fk_fields = ('movie', 'user') # 设置显示外键字段 # 电影点赞管理 class LikeAdmin(object): search_fields = ['movie__title', 'user__name'] # 检索字段 list_display = ['movie', 'user', 'create_time'] # 要显示的字段 list_filter = ['create_time'] # 分组过滤的字段 ordering = ('id',) # 设置默认排序字段,负号表示降序排序 list_per_page = 30 # 默认每页显示多少条记录,默认是100条 list_editable = [] # 可编辑字段 fk_fields = ('movie', 'user') # 设置显示外键字段 # 电影收藏管理 class CollectAdmin(object): search_fields = ['movie__title', 'user__name'] # 检索字段 list_display = ['movie', 'user', 'create_time'] # 要显示的字段 list_filter = ['create_time'] # 分组过滤的字段 ordering = ('id',) # 设置默认排序字段,负号表示降序排序 list_per_page = 30 # 默认每页显示多少条记录,默认是100条 list_editable = [] # 可编辑字段 fk_fields = ('movie', 'user') # 设置显示外键字段 # 电影评论管理 class CommentAdmin(object): search_fields = ['movie__title', 'user__name'] # 检索字段 list_display = ['user', 'movie', 'show_content', 'like_num', 'is_show', 'create_time'] # 要显示的字段 list_filter = ['movie', 'is_show', 'create_time'] # 分组过滤的字段 ordering = ('id',) # 设置默认排序字段,负号表示降序排序 list_per_page = 30 # 默认每页显示多少条记录,默认是100条 list_editable = [] # 可编辑字段 fk_fields = ('movie', 'user') # 设置显示外键字段 def show_content(self, obj): # 显示评论内容 if not obj.content: return mark_safe('') if len(obj.content) < 20: return mark_safe(obj.content) short_id = f'{obj._meta.db_table}_short_text_{obj.id}' short_text = obj.content[:len(obj.content) // 4] + '......' detail_id = f'{obj._meta.db_table}_detail_text_{obj.id}' detail_text = obj.content text = """<style type="text/css"> #%s,%s {padding:10px;border:1px solid green;} </style> <script type="text/javascript"> function openShutManager(oSourceObj,oTargetObj,shutAble,oOpenTip,oShutTip,oShortObj){ var sourceObj = typeof oSourceObj == "string" ? document.getElementById(oSourceObj) : oSourceObj; var targetObj = typeof oTargetObj == "string" ? document.getElementById(oTargetObj) : oTargetObj; var shortObj = typeof oShortObj == "string" ? document.getElementById(oShortObj) : oShortObj; var openTip = oOpenTip || ""; var shutTip = oShutTip || ""; if(targetObj.style.display!="none"){ if(shutAble) return; targetObj.style.display="none"; shortObj.style.display="block"; if(openTip && shutTip){ sourceObj.innerHTML = shutTip; } } else { targetObj.style.display="block"; shortObj.style.display="none"; if(openTip && shutTip){ sourceObj.innerHTML = openTip; } } } </script> <p id="%s">%s</p> <p><a rel="nofollow" href="###" onclick="openShutManager(this,'%s',false,'点击关闭','点击展开','%s')">点击展开</a></p> <p id="%s" style="display:none"> %s </p> """ % (short_id, detail_id, short_id, short_text, detail_id, short_id, detail_id, detail_text) return mark_safe(text) show_content.short_description = '评论内容' xadmin.site.register(views.CommAdminView, GlobalSettings) xadmin.site.register(views.BaseAdminView, BaseSetting) xadmin.site.register(User, UserAdmin) xadmin.site.register(Types, TypesAdmin) xadmin.site.register(Movies, MovieAdmin) xadmin.site.register(RateMovie, RateAdmin) xadmin.site.register(LikeMovie, LikeAdmin) xadmin.site.register(CollectMovie, CollectAdmin) xadmin.site.register(CommentMovie, CommentAdmin)2、修改movie\apps.py
代码改为如下:
from django.apps import AppConfig class MovieConfig(AppConfig): name = 'movie' verbose_name = "电影推荐系统"3、修改movie\__init__.py
代码改为:
default_app_config = 'movie.apps.MovieConfig'4、浏览器登录
浏览器访问http://127.0.0.1:8000/xadmin/
输入账号:root
输入密码:movie-root