图书推荐系统(六)之配置应用index

1、url配置

(1)在Book/Book/urls.py下添加路径

    url(r'index/', include('index.urls'), name='index'),

(2)在index目录添加urls.py 里面填充内容

from django.conf.urls import url
from index.views import home

urlpatterns = [
    url(r'^home/$',home),
]

2、index应用目录结构

├── index
    │   ├── __init__.py  # 配置应用
    │   ├── admin.py   # 提供django.contrib.admin 模块管理我们创建的数据模型
    │   ├── apps.py
    │   ├── migrations  # 是一个文件夹,用于数据模型与数据库结构的同步
    │   ├── models.py  # 用于定义数据模型
    │   ├── tests.py      # 供单元测试使用
    │   └── views.py     # 是写主要的业务逻辑的地方
		│   └── urls.py     

2.1 init.py

from django.apps import AppConfig

default_app_config = 'index.IndexConfig'

class IndexConfig(AppConfig):
    name="index" #app名称
    verbose_name = u"图书" #后台显示的名称

2.2 admin.py

# -*- coding: utf-8 -*-

from django.contrib import admin
from index.models import Book,BookScore

class AdminBook(admin.ModelAdmin):
    # 配置要显示的字段的
    list_display = ('bookid', 'bookname', 'bookimg', 'bookscore', 'booktag')
    # 搜索功能
    search_fields = ('bookid', 'bookname', 'bookscore')
    # 筛选功能
    list_filter = ('bookscore',)
    # 排序
    ordering = ('-bookscore',)
admin.site.register(Book, AdminBook)

class AdminBookScore(admin.ModelAdmin):
    list_display = ('userid', 'bookid', 'bookscore','actiontime',)
    search_fields = ('bookscore',)
    list_filter = ('bookscore',)
    ordering = ('-actiontime',)
admin.site.register(BookScore, AdminBookScore)

2.3 views.py

(1)导入包

# -*- condign:utf-8 -*-
# 加载模板、填充 context 、将经解析的模板结果返回为 HttpResponse 
from django.shortcuts import render_to_response
from index.models import BookScore,Book
import json,os
# 获取数据
from Book.settings import user_rec_result_path, item_sim_path, online_weight, offline_weight

在Book/settings.py下添加

# 相关配置
# 用户离线计算的推荐结果数据路径
user_rec_result_path = './../BookRec/z_data/data/user_rec_result.txt'
# 离线计算的图书相似度文件
item_sim_path = "./../BookRec/z_data/data/item_sim.json"
# 标签及其出现的次数
tags_path = "./../BookRec/z_data/data/tags.json"
# 实时计算和离线计算的推荐结果权重比例
online_weight = 1.0
offline_weight = 1.0

(2)建立一个函数home
1)判断用户是否登录,若未登录则返回登录界面
2)(_is_have_score(username)函数)判断当前用户是否已经产生评分行为
3)(_load_rec_result(username)函数)如果用户已经登录,则判断用户是新注册用户还是已经存在于系统中的用户
4)是否评分 若登录用户已经产生过评分,得到推荐列表( _cal_rec_result函数),遍历bookid,从数据库Book表得到bookid的相关信息(图片、书名等),将用户民和图书信息返回index.html

若登录用户未产生评分行为,首先需要区分是新注册用户还是已经存在于系统中的用户。不能从离线数据rec_result获取当前用户推荐集的是新注册用户,筛选评分大于9.5的图书,返回评分最高的20本书;如果能获取推荐结果,则直接返回离线计算好的推荐结果

另:_cal_rec_result函数思路: 目的是综合离线计算的推荐结果和用户评分行为产生推荐结果集 1)查询当前用户评分的图书和对应的分数 2)加载离线计算好的图书相似度矩阵 3)rec_result代表推荐集, 若为空表示新注册用户,遍历用户评过分的图书id,判断该本图书是不是用户评价过的图书,若是,则忽略,继续执行,遍历相似度矩阵,从相似度矩阵中找到相似图书,加权计算,推荐度=产生过行为的图书评分乘以产生过行为的图书评分与bid的相似度,排序,推荐前20本图书。

若不为空表示存在于系统中的用户,遍历用户评过分的图书id,判断该本书是否在离线计算好的相似矩阵中,若在,继续执行,遍历相似度矩阵,从相似度矩阵中找到相似图书,加权计算,推荐度=产生过行为的图书评分乘以产生过行为的图书与bid的相似度得到推荐度,若推荐的图书已经在离线被推荐的图书列表里,最终评分加上离线的评分数据,进行综合排序。

index/views.py

# -*- condign:utf-8 -*-
from django.shortcuts import render_to_response
from index.models import BookScore,Book
import json,os
from Book.settings import user_rec_result_path, item_sim_path, online_weight, offline_weight

def home(request):
    # 访问者在第一次访问服务器时,服务器在其cookie中设置一个唯一的ID号——会话ID(session)。
    # 登陆是将用户写入session(服务端),此时若获取不到,则返回错误
    # 获取当前登录用户
    username = request.COOKIES.get('name')
    # 如果用户未登录,则返回登录界面
    if not username:
        return render_to_response('login.html',{
            'error':"用户未登录,请先登录",
            'user_name':"",
            'user_pwd':''
        })

    # 判断当前用户是否已经产生评分行为
    is_action = _is_have_score(username)

    # 如果用户已经登录,则判断用户是新注册用户还是已经存在于系统中的用户
    rec_result = _load_rec_result(username)

    # is_action 为True表示已经产生过评分行为,借鉴cf-based-item的思想进行推荐
    # 这里不需要区分是新注册用户还是已经存在于系统中的用户,在_cal_rec_result 函数中进行判断
    if is_action:
        # 得到的推荐列表
        book_scores = _cal_rec_result(username, rec_result)
        bookids = [one[0] for one in book_scores]
        # 字段查询:bookid__in=bookids相当于select * from Book where bookid in bookids
        books = Book.objects.filter(bookid__in=bookids).order_by("-bookscore")
        new_books = list()
        for book in books:
            _dict = dict()
            _dict["bookimg"] = book.bookimg
            _dict["bookid"] = book.bookid
            _dict["bookscore"] = book.bookscore
            _dict["booktag"] = book.booktag.split(",")
            _dict["bookname"] = book.bookname
            _dict["bookrescore"] = book.bookscore
            new_books.append(_dict)
        return render_to_response("index.html",{
            "user_name":username,
            "books":new_books,
        })

    # is_action 为False表示没有产生评分行为,这里区分是新注册用户还是已经存在于系统中的用户
    else:
        # 如果从离线结果中不能获取当前用户的推荐结果集,则为新注册用户,返回评分最高的20本图书
        if not rec_result:
            # 筛选评分大于9.5的图书
            books = Book.objects.filter(bookscore__gt=9.5).order_by("-bookscore")[:20]
            # 拼接推荐指数
            new_books = list()
            for book in books:
                _dict = dict()
                _dict["bookimg"] = books.bookimg
                _dict["bookid"] = book.bookid
                _dict["bookscore"] = book.bookscore
                _dict["booktag"] = book.booktag.split(",")
                _dict["bookname"] = book.bookname
                _dict["bookrecscore"]=book.bookscore
                new_books.append(_dict)
        # 如果能获取推荐结果,则直接返回推荐结果
        else:
            bookids = rec_result.keys()
            books = Book.objects.filter(bookid_in=bookids).order_by("-bookscore")[:20]
            new_books = list()
            for book in books:
                _dict = dict()
                _dict["bookimg"] = book.bookimg
                _dict["bookid"] = book.bookid
                _dict["bookscore"] = book.bookscore
                _dict["booktag"] = book.booktag.split(",")
                _dict["bookname"] = book.bookname
                _dict["bookrecscore"] = rec_result[book.bookid]
                new_books.append(_dict)
        new_books.sort(key=lambda k:k["bookrecdcore"],reverse=True)
        return render_to_response("index.html",{
            "user_name":username,
            "books":new_books,
        })



# 判断当前用户是否是首次进入为你推荐模块
def _is_have_score(user):
    actions = BookScore.objects.filter(userid=user)

# 加载当前登陆用户的离线推荐结果
def _load_rec_result(user):
    result = dict()
    for line in open(user_rec_result_path,"r",encoding="utf-8").readlines():
        # 一列以user开头
        if line.startswith(user):
            # '_'表示忽略
            _, bid, score = line.strip().split(",")
            # 保留两位小数
            result[bid] = round(float(score),2)
    return result

# 综合离线计算的推荐结果和用户评分行为产生推荐结果集
def _cal_rec_result(user,rec_result):
    # 查询当前用户评分的图书和对应的分数
    actions = { one.bookid:one.bookscore for one in BookScore.objects.filter(userid=user)}

    # 加载离线计算好的图书相似度矩阵
    if os.path.exists(item_sim_path):
        itemSim = json.load(open(item_sim_path, "r"))
    else:
        print("图书相似度文件不存在,请检查!")

    # rec_result为空表示新注册用户,直接计算其评分图书的相似度
    if not rec_result:
        last_result = dict()
        # 遍历用户有行为的每一本书
        for bookid in actions.keys():
            # 判断该本书是否在离线计算的相似key中
            if bookid not in itemSim.keys():
                continue
            # sim_books={booid:相似度}
            sim_books = itemSim[bookid]
            # bid是要对用户推荐的图书
            for bid in sim_books.keys():
                # 判断该本图书是不是用户评价过的图书,若是,则忽略,继续执行
                if bid in actions.keys():
                    continue
                last_result.setdefault(bid, 0.0)
                # 推荐度=产生过行为的图书评分*产生过行为的图书评分与bid的相似度
                last_result[bid] += sim_books[bid] * actions[bookid]
        # 排序,推荐前20本图书
        # new_last_result = {bid:score}
        new_last_result = sorted(last_result.items(), key=lambda k:k[1], reverse=True)[:20]
        return new_last_result
    # 已经存在于系统的用户
    else:
        last_result = dict()
        # 遍历用户有行为的每一本书
        for bookid in actions.keys():
            # 判断该本书是否在离线计算的相似key中
            if bookid not in itemSim.keys():
                continue
            sim_books = itemSim[bookid]
            # bid是要推荐的书
            for bid in sim_books.keys():
                if bid in actions.keys():
                    continue
                last_result.setdefault(bid,0.0)
                last_result[bid] += sim_books[bid] * actions[bookid] * online_weight
        # 若推荐的图书已经在离线被推荐的图书列表里
        for bookid in rec_result.keys():
            # 若图书已经被评分
            if bookid in actions.keys():
                continue
            last_result.setdefault(bookid, 0.0)
            # 加上之前的评分
            last_result[bid] += rec_result[bookid] * offline_weight
        new_last_result = sorted(last_result.items(), key=lambda k:k[1],reverse=True)[20]
        return new_last_result

3、templates

(1)在Book/Book/setting.py里添加

        'DIRS': [os.path.join(BASE_DIR, 'templates')],

(2)静态文件路径的配置

STATIC_URL = '/static/'
# os.path.dirname(__file__) 得到文件所在目录,再来一个os.path.dirname()就是目录的上一级
# 配置静态文件的路径
STATIC_ROOT = os.path.join(os.path.dirname(__file__), '../static')
STATICFILES_DIRS = (
    ('css', os.path.join(STATIC_ROOT, 'css').replace('\\', '/')),
    ('js', os.path.join(STATIC_ROOT, 'js').replace('\\', '/')),
    ('img', os.path.join(STATIC_ROOT, 'img').replace('\\', '/')),
    ('bookimg', os.path.join(STATIC_ROOT, 'bookimg').replace('\\', '/')),
    ('uploads', os.path.join(STATIC_ROOT, 'uploads').replace('\\', '/')),
)

(3)添加index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>图书推荐系统</title>
    <link rel="shortcut icon" href="/static/img/favicon.ico"/>
    <link rel="bookmark" href="/static/img/favicon.ico"/>
    <link href="/static/css/bootstrap/css/bootstrap.min.css" type="text/css" rel="stylesheet"/>
    <link href="/static/css/index.css" type="text/css" rel="stylesheet"/>
    <script src="/static/js/jquery-1.10.2.min.js"></script>
    <script src="/static/css/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>
    <div class="head">
        <div class="top">
            <img src="/static/img/icon.jpg">
            <span><a href="/user/action/">{{ user_name }}</a>  |  <a href="/user/logout/">退出</a> </span>
        </div>
        <ul class="menavs">
            <li><a class="active" href="/index/home/">为你推荐</a></li>
            <li class="dropdown">
                <!--data-toggle="dropdown"是下拉菜单-->
                <a href="/rank/score/" class="dropdown-toggle" data-toggle="dropdown">排行榜<span class="caret"></span></a>
                <ul class="dropdown-menu">
                    <li><a href="/rank/score/">高分榜</a></li>
                    <li><a href="/rank/tag/">标签云</a></li>
                    <li><a href="/rank/all/">所有图书</a></li>
                </ul>
            </li>
            <li><a href="/user/action/">我的足迹</a></li>

        </ul>
    </div>
    <div class="main">
        <ul class="recomcan">
            <li class="recomtitle">
                <span>封面</span>
                <div>
                    <span>书名</span>
                    <span>推荐指数</span>
                    <span>图书评分</span>
                    <span>查看详情</span>
                    <span>相关标签</span>
                </div>
            </li>
            <!--数据循环部分-->
            {% for book in books %}
            <li class="recomitem">
                <img src="/static/bookimg/{{ book.bookid }}.jpg">
                <div>
                    <span>{{ book.bookname }}</span>
                    <span>{{ book.bookrecscore }} °C</span>
                    <span>{{ book.bookscore }}分</span>
                    <span><a href="https://book.douban.com/subject/{{ book.bookid }}" target="_blank">查看详情</a></span>
                    <span class="sign">
                        {% for tag in book.booktag %}
                            <a class="badge badge-default" >{{ tag }}</a>
                        {% endfor %}
                    </span>
                </div>
            </li>
            {% endfor %}
            <!--数据循环部分end-->
        </ul>
    </div>
    <div class="foot">
        <span>@2019 Publish by Smile</span>
    </div>
</body>
</html>

(4)添加login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <!--图标-->
    <link rel="shortcut icon" href="/static/img/favicon.ico"/>
    <link rel="bookmark" href="/static/img/favicon.ico"/>
    <link rel="stylesheet" href="/static/css/bootstrap/css/bootstrap.min.css">
    <link rel="stylesheet" href="/static/css/check.css">
</head>
<body class="loginbody">
    <!--从views.py里获取error变量,alert表示弹出窗口"用户未登录"-->
    <!--弹出窗口,内容是{{ login_error }}里的内容-->
    <!--返回当前显示的文档的完整url-->

<script language="JavaScript">
    { % if error % }
        alert("{{ error }}");
    {% endif %}
    {% if login_error %}
        var r = confirm("{{ login_error }}");
        if(r==true){
            window.location.href = '/user/login/'
        }else{

            window.location.href = '{{ form }}'
        }
    {% endif %}

</script>

<div class="login-main">
    <h3><center>欢迎登录图书推荐系统</center></h3>
    <form role="form" method="post" action="/user/login/">
            <input class="incon" name="username" type="text" placeholder="用户名" value="{{ user_name }}">
            <input class="incon" name="password" type="password" placeholder="密码" value="{{ uer_pwd }}">
            <input class="btnincon" type="submit" value="登录">
            <hr style="height:1px;border:none;border-top:1px solid darkgray;" />
            <a href="/user/register" class="fl acon"> 新用户?去注册</a>
            <!--target="_blank":在新窗口中打开被链接文档-->
            <a class="fr acon" href="/admin/login/" target="_blank">管理员登录</a>
    </form>

</div>

</body>
</html>