天天看点

Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页

1、列表页热销排行

根据路径参数

category_id

查询出该类型商品销量前二的商品。

使用

Ajax

实现局部刷新的效果。

查询列表页热销排行数据

  1. 请求方式
    Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页
  2. 请求参数:路径参数
    Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页
  3. 响应结果:JSON
    Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页
{
    "code":"0",
    "errmsg":"OK",
    "hot_skus":[
        {
            "id":6,
            "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRbI2ARekNAAFZsBqChgk3141998",
            "name":"Apple iPhone 8 Plus (A1864) 256GB 深空灰色 移动联通电信4G手机",
            "price":"7988.00"
        },
        {
            "id":14,
            "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRdMSAaDUtAAVslh9vkK04466364",
            "name":"华为 HUAWEI P10 Plus 6GB+128GB 玫瑰金 移动联通电信4G手机 双卡双待",
            "price":"3788.00"
        }
    ]
}
           
  1. 接口定义和实现
class HotGoodsView(View):
    """商品热销排行"""
    def get(self, request, category_id):
        """提供商品热销排行JSON数据"""
        # 根据销量倒序
        skus = models.SKU.objects.filter(category_id=category_id, is_launched=True).order_by('-sales')[:2]
        # 序列化
        hot_skus = []
        for sku in skus:
            hot_skus.append({
                'id':sku.id,
                'default_image_url':sku.default_image.url,
                'name':sku.name,
                'price':sku.price
            })
        return http.JsonResponse({'code':RETCODE.OK, 'errmsg':'OK', 'hot_skus':hot_skus})
           

渲染列表页热销排行数据

  1. 模板数据category_id传递到Vue.js
<script type="text/javascript">
    let category_id = "{{ category.id }}";
</script>
           
data: {
    category_id: category_id,
},
           
  1. Ajax请求商品热销排行JSON数据
get_hot_skus(){
    if (this.category_id) {
        let url = '/hot/'+ this.category_id +'/';
        axios.get(url, {
            responseType: 'json'
        })
            .then(response => {
                this.hot_skus = response.data.hot_skus;
                for(let i=0; i<this.hot_skus.length; i++){
                    this.hot_skus[i].url = '/detail/' + this.hot_skus[i].id + '/';
                }
            })
            .catch(error => {
                console.log(error.response);
            })
    }
},
           
  1. 渲染商品热销排行界面
<div class="new_goods" v-cloak>
    <h3>热销排行</h3>
    <ul>
        <li v-for="sku in hot_skus">
            <a :href="sku.url"><img :src="sku.default_image_url"></a>
            <h4><a :href="sku.url">[[ sku.name ]]</a></h4>
            <div class="price">¥[[ sku.price ]]</div>
        </li>
    </ul>
</div>
           

项目实例代码

商品视图文件:

apps/goods/views.py

"""
商品视图文件:apps/goods/views.py
"""
from django.shortcuts import render
from django.views import View
from contents.utils import get_categories         # 导入封装方法
from .utils import get_breadcrumb                 # 导入面包屑封装方法
from .models import GoodsCategory, SKU            # 导入模型
from django.views.generic import ListView          # 分页
from django.core.paginator import Paginator        # 分页
from django import http
from utils.response_code import RETCODE


# 热销商品视图
class HotGoodsView(View):
    """热销排行"""
    def get(self, request, category_id):
        """返回两条热销商品"""
        skus = SKU.objects.filter(category_id=category_id, is_launched=True).order_by('-sales')[:2]  # 根据sales的逆序排序,并取前两个数据
        
        # 拼接数据,序列化
        hot_skus = []
        for sku in skus:
            sku_dict = {
                "id": sku.id,
                "default_image_url": sku.default_image_url.url,
                "name": sku.name,
                "price": sku.price,
            }
            hot_skus.append(sku_dict)          # 数据添加
        # print(hot_skus)
        """ 拼接数据之后的形式
        {
            "code":"0",
            "errmsg":"OK",
            "hot_skus":[
                {
                    "id":6,
                    "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRbI2ARekNAAFZsBqChgk3141998",
                    "name":"Apple iPhone 8 Plus (A1864) 256GB 深空灰色 移动联通电信4G手机",
                    "price":"7988.00"
                },
                {
                    "id":14,
                    "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRdMSAaDUtAAVslh9vkK04466364",
                    "name":"华为 HUAWEI P10 Plus 6GB+128GB 玫瑰金 移动联通电信4G手机 双卡双待",
                    "price":"3788.00"
                }
            ]
        }
        """
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'ok', 'hot_skus': hot_skus})


class GoodsListView(View):
    """商品列表页面"""
    def get(self, request, category_id, page_num):
        """查询并渲染商品"""
        # category_id 是商品分类的id
        
        try:
            # 查询面包屑
            category = GoodsCategory.objects.get(id=category_id)
        except Exception as e:
            return render(request, '404.html')
        
        # 获取排序规则  接收sort参数:如果用户不传,就是默认的排序规则
        sort = request.GET.get('sort', 'default')     # 根据字段sort查询
        if sort == 'price':                # 按照排序规则查询该分类商品SKU信息
            sort_field = 'price'           # 根据字段price的正序排列,价格由低到高
        elif sort == 'hot':
            sort_field = '-sales'          # 根据字段sales的倒序排列,销量由高到低
        else:                              # 当传输的排序方式不是上面的两种,需要让排序方式为默认的
            sort = 'default'
            sort_field = 'create_time'     # 根据create_time字段排序
            
        # 排序      根据category.id筛选,以sort_field排序
        skus = SKU.objects.filter(is_launched=True, category_id=category.id).order_by(sort_field)
        # print(skus)
        
        # 分页  Paginator('分页面的记录', '每页记录的条数')  创建分页器
        paginator = Paginator(skus, 5)
        # 获取page_num的页码的数据
        page_skus = paginator.page(page_num)
        
        # 总的页面数
        tatal_page = paginator.num_pages
        
        # 查询商品的类别
        categories = get_categories()
        
        breadcrumb = get_breadcrumb(category)                     # 调用封装的方法
        # print(breadcrumb)
        
        context = {
            "categories": categories,       # 频道分类
            "breadcrumb": breadcrumb,       # 面包屑导航
            "page_skus": page_skus,         # 分页后数据
            "tatal_page": tatal_page,       # 总页数
            "category_id": category_id,     # 商品类别id
            "sort": sort,                   # 排序字段,以何种方式排序
            "page_num": page_num,           # 当前页码
            
        }
        
        return render(request, 'list.html', context=context)        # 传递数据到前端界面list.html

           

商品模块的路由:

apps/goods/urls.py

# -*- encoding: utf-8 -*-
"""
@File    : urls.py
@Time    : 2020/9/10 10:03
@Author  : chen

商品模块的路由:apps/goods/urls.py
"""
from django.urls import path, include, re_path
from . import views

app_name = 'goods'

urlpatterns = [
    re_path(r'^list/(?P<category_id>\d+)/(?P<page_num>\d+)/$', views.GoodsListView.as_view(), name='list'),
    re_path(r'^hot/(?P<category_id>\d+)/$', views.HotGoodsView.as_view()),
]

           

商品列表界面:

templates/list.html

<h3>热销排行</h3>
				<ul>
            {#       热销产品的循环播放       vue的语法        #}
                    <li v-for="sku in hot_skus">
                        <a href="detail.html"><img :src="sku.default_image_url"></a>
						<h4><a href="detail.html">[[  sku.name ]]</a></h4>
						<div class="price">[[ sku.price ]]</div>
					</li>
				</ul>
           

2、商品搜索

全文检索方案Elasticsearch

全文检索和搜索引擎原理

商品搜索 需求

  • 当用户在搜索框输入商品关键字后,我们要为用户提供相关的商品搜索结果。

商品搜索 实现

  • 可以选择使用模糊查询

    like

    关键字实现。
  • 但是

    like

    关键字的效率极低。
  • 查询需要在多个字段中进行,使用

    like

    关键字也不方便。

全文检索方案

  • 我们引入全文检索的方案来实现商品搜索。
  • 全文检索即在指定的任意字段中进行检索查询。
  • 全文检索方案需要配合搜索引擎来实现。

搜索引擎原理

  • 搜索引擎进行全文检索时,会对数据库中的数据进行一遍预处理,单独建立起一份索引结构数据。
  • 索引结构数据类似新华字典的索引检索页,里面包含了关键词与词条的对应关系,并记录词条的位置。
  • 搜索引擎进行全文检索时,将关键字在索引数据中进行快速对比查找,进而找到数据的真实存储位置。

结论:

搜索引擎建立索引结构数据,类似新华字典的索引检索页,全文检索时,关键字在索引数据中进行快速对比查找,进而找到数据的真实存储位置。

Elasticsearch

介绍

实现全文检索的搜索引擎,首选的是

Elasticsearch

  • Elasticsearch

    是用

    Java

    实现的,开源的搜索引擎。
  • 它可以快速地储存、搜索和分析海量数据。维基百科、

    Stack Overflow

    Github

    等都采用它。
  • Elasticsearch

    的底层是开源库

    Lucene

    。但是,没法直接使用

    Lucene

    ,必须自己写代码去调用它的接口。

分词说明

  • 搜索引擎在对数据构建索引时,需要进行分词处理。
  • 分词是指将一句话拆解成多个单字 或 词,这些字或词便是这句话的关键词。
  • 比如:我是中国人; 分词后:我、是、中、国、人、中国等等都可以是这句话的关键字。
  • Elasticsearch

    不支持对中文进行分词建立索引,需要配合扩展

    elasticsearch-analysis-ik

    来实现中文分词处理。

使用Docker安装Elasticsearch

  1. 获取Elasticsearch-ik镜像
# 从仓库拉取镜像
sudo docker image pull delron/elasticsearch-ik:2.4.6-1.0

# 解压资料中本地镜像
sudo docker load -i elasticsearch-ik-2.4.6_docker.tar
           
Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页
  1. 配置

    Elasticsearch-ik

  • 将资料中的

    elasticsearc-2.4.6

    目录拷贝到

    home

    目录下。
  • 修改

    /home/xxx/elasticsearc-2.4.6/config/elasticsearch.yml

    第54行。
  • 更改

    ip

    地址为本机真实

    ip

    地址。
Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页

3. 使用Docker运行Elasticsearch-ik

sudo docker run -dti --name=elasticsearch --network=host -v /home/xx/elasticsearch-2.4.6/config:/usr/share/elasticsearch/config delron/elasticsearch-ik:2.4.6-1.0
           

注意上面的文件路径需要根据自己的虚拟机路径来修改

Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页

Haystack扩展建立索引

Elasticsearch

的底层是开源库

Lucene

。但是没法直接使用

Lucene

,必须自己写代码去调用它的接口。

思考:

  • 我们如何对接

    Elasticsearch

    服务端?

解决方案:

  • Haystack

Haystack介绍和安装配置

  1. Haystack

    介绍

    Haystack

    是在

    Django

    中对接搜索引擎的框架,搭建了用户和搜索引擎之间的沟通桥梁。

    • 我们在

    Django

    中可以通过使用

    Haystack

    来调用

    Elasticsearch

    搜索引擎。

    Haystack

    可以在不修改代码的情况下使用不同的搜索后端(比如

    Elasticsearch

    Whoosh

    Solr

    等等)。
  2. Haystack

    安装

安装版本要确定,与上面的Elasticsearch-ik匹配

pip install django-haystack
pip install elasticsearch==2.4.1
           
Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页
  1. Haystack

    注册应用和路由
INSTALLED_APPS = [
    'haystack', # 全文检索
]
           
  1. 总路由
  1. Haystack

    配置

    • 在配置文件中配置

    Haystack

    为搜索引擎后端
# Haystack
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
        'URL': 'http://192.168.232.142:9200/', # Elasticsearch服务器ip地址,端口号固定为9200
        'INDEX_NAME': 'lgshop', # Elasticsearch建立的索引库的名称
    },
}
# 当添加、修改、删除数据时,自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
           

HAYSTACK_SIGNAL_PROCESSOR

配置项保证了在

Django

运行起来后,有新的数据产生时,

Haystack

仍然可以让

Elasticsearch

实时生成新数据的索引。

项目实例代码

开发环境配置文件:

dev.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # 'apps.users',                     # 在apps文件夹下的users模块进行app注册
    'users',                            # 用户模块
    'contents',                         # 网页首页模块,
    'verifications',                    # 验证码模块app
    'oauth',                            # 第三方登陆QQ的模块
    'areas',                            # 用户地址模块
    'goods',                            # 商品模块
    'haystack',                         # 全文检索
]


# Haystack
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
        'URL': 'http://192.168.232.142:9200/',          # Elasticsearch服务器ip地址,端口号固定为9200
        'INDEX_NAME': 'lgshop',                         # Elasticsearch建立的索引库的名称
    },
}
# 当添加、修改、删除数据时,自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
           

项目总路由文件:

shop/urls.py

'''
项目总路由文件:shop/urls.py
'''

from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path('admin/', admin.site.urls),
    
    path('users/', include('users.urls')),    # 如果当时注册users模块时候没有使用sys.path.insert导入路径,这里就需要改为 'apps.users.urls'
    path('', include('contents.urls')),       # 首页路由
    path('', include('verifications.urls')),  # 验证码路由
    path('', include('oauth.urls')),          # QQ登陆路由
    path('', include('areas.urls')),          # 用户地址模块路由
    path('', include('goods.urls')),           # 商品模块总路由
    path(r'search/', include('haystack.urls')),     # 全文检索的总路由
]

           

商品搜索结果界面:

templates/search/search.html

{#    商品搜索结果界面:templates/search/search.html   #}
{% load static %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>LG商城-商品搜索</title>
    <link rel="stylesheet" type="text/css" href="{% static 'css/jquery.pagination.css' %}">
    <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
	<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
    <script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
</head>
<body>
    <div id="app">
	<div class="header_con">
		<div class="header" v-cloak>
            <div class="welcome fl">欢迎来到LG商城!</div>
            <div class="fr">
                <div v-if="username" class="login_btn fl">
                    欢迎您:<em>[[ username ]]</em>
                    <span>|</span>
                    <a href="{{ url('users:logout' %}">退出</a>
                </div>
                <div v-else class="login_btn fl">
                    <a href="{{ url('users:login' %}">登录</a>
                    <span>|</span>
                    <a href="{{ url('users:register' %}">注册</a>
                </div>
                <div class="user_link fl">
                    <span>|</span>
                    <a href="{{ url('users:info' %}">用户中心</a>
                    <span>|</span>
                    <a href="cart.html">我的购物车</a>
                    <span>|</span>
                    <a href="user_center_order.html">我的订单</a>
                </div>
            </div>
        </div>
	</div>
	<div class="search_bar clearfix">
		<a href="{{ url('contents:index' %}" class="logo fl"><img src="{% static 'images/1.png' %}"></a>
		<div class="search_wrap fl">
			<form method="get" action="/search/" class="search_con">
                <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
                <input type="submit" class="input_btn fr" name="" value="搜索">
            </form>
			<ul class="search_suggest fl">
				<li><a href="#">索尼微单</a></li>
				<li><a href="#">优惠15元</a></li>
				<li><a href="#">美妆个护</a></li>
				<li><a href="#">买2免1</a></li>
			</ul>
		</div>
	</div>
    <div class="main_wrap clearfix">
        <div class=" clearfix">
            <ul class="goods_type_list clearfix">
            {#      搜索的商品结果循环显示             #}
                {% for result in page %}
                <li>
                    {# object取得才是sku对象 #}
                    <a href="/detail/{{ result.object.id }}/"><img src="{{ result.object.default_image.url }}"></a>
                    <h4><a href="/detail/{{ result.object.id }}/">{{ result.object.name }}</a></h4>
                    <div class="operate">
                        <span class="price">¥{{ result.object.price }}</span>
                        <span>{{ result.object.comments }}评价</span>
                    </div>
                </li>
                {% empty %}
                    <p>没有找到您要查询的商品。</p>
                {% endfor %}

            </ul>
            <div class="pagenation">
                <div id="pagination" class="page"></div>
            </div>
        </div>
    </div>
	<div class="footer">
		<div class="foot_link">
			<a href="#">关于我们</a>
			<span>|</span>
			<a href="#">联系我们</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情链接</a>
		</div>
		<p>CopyRight © 2016 北京LG商业股份有限公司 All Rights Reserved</p>
		<p>电话:010-****888    京ICP备*******8号</p>
	</div>
    </div>
    <script type="text/javascript" src="{% static 'js/common.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/search.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/jquery.pagination.min.js' %}"></script>
    <script type="text/javascript">
        $(function () {
            $('#pagination').pagination({
                currentPage: 1,
                totalPage: 3,
                callback:function (current) {
                    {#location.href = '/search/?q=iphone&page=' + current;#}
                }
            })
        });
    </script>
</body>
</html>
           

3、Haystack建立数据索引

  1. 创建索引类

    • 通过创建索引类,来指明让搜索引擎对哪些字段建立索引,也就是可以通过哪些字段的关键字来检索数据。

    • 本项目中对

    SKU

    信息进行全文检索,所以在

    goods

    应用中新建

    search_indexes.py

    文件,用于存放索引类。
from haystack import indexes
from .models import SKU
class SKUIndex(indexes.SearchIndex, indexes.Indexable):
    """SKU索引数据模型类"""
    text = indexes.CharField(document=True, use_template=True)
    
    def get_model(self):
        """返回建立索引的模型类"""
        return SKU
    
    def index_queryset(self, using=None):
        """返回要建立索引的数据查询集"""
        return self.get_model().objects.filter(is_launched=True)
           
  • 索引类

    SKUIndex

    说明:

    • 在

    SKUIndex

    建立的字段,都可以借助

    Haystack

    Elasticsearch

    搜索引擎查询。

    • 其中

    text

    字段我们声明为

    document=True

    ,表名该字段是主要进行关键字查询的字段。

    text

    字段的索引值可以由多个数据库模型类字段组成,具体由哪些模型类字段组成,我们用

    use_template=True

    表示后续通过模板来指明。
  1. 创建

    text

    字段索引值模板文件

    • 在

    templates

    目录中创建

    text

    字段使用的模板文件

    • 具体在

    templates/search/indexes/goods/sku_text.txt

    文件中定义
{{ object.id }}
{{ object.name }}
{{ object.caption }}
           
Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页
  • 模板文件说明:当将关键词通过

    text

    参数名传递时

    • 此模板指明

    SKU

    id

    name

    caption

    作为

    text

    字段的索引值来进行关键字索引查询。
  1. 手动生成初始索引
python manage.py rebuild_index
           
Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页

全文检索测试

  1. 准备测试表单

    • 请求方法:GET

    • 请求地址:/search/

    • 请求参数:q

    Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页
    结论:错误提示告诉我们在templates/search/⽬录中缺少⼀个search.html⽂件 search.html⽂件作⽤就是接收和渲染全⽂检索的结果。

检索测试期间遇到的BUG问题及解决方案

  1. Ubuntu虚拟机固定IP方法

    https://my.oschina.net/u/2556916/blog/1509644

  2. 开启虚拟机的

    storage

    tracker

    服务
    Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页
  3. django 报错:urllib3.exceptions.ConnectTimeoutError 问题解决方法

    解答:之前运行docker容器还没有退出,导致出现容器重名情况:

    https://blog.csdn.net/u012129607/article/details/79616547

    https://blog.csdn.net/qq_36662478/article/details/91044571

# 查看所有存在的进程
sudo docker container ls -all
# 删掉已经存在的elasticsearch进程号
sudo docker rm xxxxxx
# 开启storage
sudo docker start storage
# 开启tracker
sudo docker start tracker
# 重新创建elasticsearch进程
sudo docker run -dti --name=elasticsearch --network=host -v /home/ch/elasticsearch-2.4.6/config:/usr/share/elasticsearch/config delron/elasticsearch-ik:2.4.6-1.0

# 查看最新的所有进程
sudo docker container ls
           

4、渲染商品搜索结果

准备商品搜索结果页面

Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页

渲染商品搜索结果

Haystack返回的数据包括:

  • query:搜索关键字
  • paginator:分页paginator 对象
  • page:当前页的page对象(遍历page中的对象,可以得到result对象)
  • result.objects: 当前遍历出来的SKU对象

Haystack搜索结果分页

  1. 设置每页返回数据条数
  • 通过

    HAYSTACK_SEARCH_RESULTS_PER_PAGE

    可以控制每页显示数量
  • 每页显示五条数据:

    HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5

开发环境配置文件:

dev.py

# 开发环境配置文件:dev.py

# es搜索设置的分页每页数据条数
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
           
  1. 准备搜索页分页器

搜索结果页面:

templates/search/search.html

{#  搜索结果页面:templates/search/search.html  #}
{% load static %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>LG商城-商品搜索</title>
    <link rel="stylesheet" type="text/css" href="{% static 'css/jquery.pagination.css' %}">
    <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
	<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
    <script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
</head>
<body>
    <div id="app">
	<div class="header_con">
		<div class="header" v-cloak>
            <div class="welcome fl">欢迎来到LG商城!</div>
            <div class="fr">
                <div v-if="username" class="login_btn fl">
                    欢迎您:<em>[[ username ]]</em>
                    <span>|</span>
                    <a href="{{ url('users:logout' %}">退出</a>
                </div>
                <div v-else class="login_btn fl">
                    <a href="{{ url('users:login' %}">登录</a>
                    <span>|</span>
                    <a href="{{ url('users:register' %}">注册</a>
                </div>
                <div class="user_link fl">
                    <span>|</span>
                    <a href="{{ url('users:info' %}">用户中心</a>
                    <span>|</span>
                    <a href="cart.html">我的购物车</a>
                    <span>|</span>
                    <a href="user_center_order.html">我的订单</a>
                </div>
            </div>
        </div>
	</div>
	<div class="search_bar clearfix">
		<a href="{{ url('contents:index' %}" class="logo fl"><img src="{% static 'images/1.png' %}"></a>
		<div class="search_wrap fl">
			<form method="get" action="/search/" class="search_con">
                <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
                <input type="submit" class="input_btn fr" name="" value="搜索">
            </form>
			<ul class="search_suggest fl">
				<li><a href="#">索尼微单</a></li>
				<li><a href="#">优惠15元</a></li>
				<li><a href="#">美妆个护</a></li>
				<li><a href="#">买2免1</a></li>
			</ul>
		</div>
	</div>
    <div class="main_wrap clearfix">
        <div class=" clearfix">
            <ul class="goods_type_list clearfix">
                {% for result in page %}

                    <li>
                        {# object取得才是sku对象 #}
                        <a href="/detail/{{ result.object.id }}/"><img src="{{ result.object.default_image_url.url }}"></a>
                        <h4><a href="/detail/{{ result.object.id }}/">{{ result.object.name }}</a></h4>
                        <div class="operate">
                            <span class="price">¥{{ result.object.price }}</span>
                            <span>{{ result.object.comments }}评价</span>
                        </div>
                    </li>

                {% empty %}
                    <p>没有找到您要查询的商品。</p>
                {% endfor %}
            </ul>
            <div class="pagenation">
                <div id="pagination" class="page"></div>
            </div>
        </div>
    </div>
	<div class="footer">
		<div class="foot_link">
			<a href="#">关于我们</a>
			<span>|</span>
			<a href="#">联系我们</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情链接</a>
		</div>
		<p>CopyRight © 2016 北京LG商业股份有限公司 All Rights Reserved</p>
		<p>电话:010-****888    京ICP备*******8号</p>
	</div>
    </div>
    <script type="text/javascript" src="{% static 'js/common.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/search.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/jquery.pagination.min.js' %}"></script>
    <script type="text/javascript">
        $(function () {
            $('#pagination').pagination({
                currentPage: {{ page.number }},
                totalPage: {{ paginator.num_pages }},
                callback:function (current) {
                    location.href = '/search/?q={{ query }}&page=' + current;
                }
            })
        });
    </script>
</body>
</html>
           

搜索页面样式文件:

static/js/search.js

文件

// 搜索页面样式文件: static/js/search.js文件
let vm = new Vue({
    el: '#app',
    delimiters: ['[[', ']]'],
    data: {
        username: getCookie('username'),
    },
    mounted(){
    },
    methods: {
    }
});
           

项目总路由文件:

shop/urls.py

'''
项目总路由文件:shop/urls.py
'''

from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path('admin/', admin.site.urls),
    
    path('users/', include('users.urls')),    # 如果当时注册users模块时候没有使用sys.path.insert导入路径,这里就需要改为 'apps.users.urls'
    path('', include('contents.urls')),       # 首页路由
    path('', include('verifications.urls')),  # 验证码路由
    path('', include('oauth.urls')),          # QQ登陆路由
    path('', include('areas.urls')),          # 用户地址模块路由
    path('', include('goods.urls')),           # 商品模块总路由
    path(r'search/', include('haystack.urls')),     # 全文检索的总路由,es固定的写法
]

           

5、商品详情页

商品详情页分析和准备

商品详情页组成结构分析

  1. 商品频道分类

    已经提前封装在contents.utils.py⽂件中,直接调用方法即可。

  2. 面包屑导航

    已经提前封装在goods.utils.py⽂件中,直接调用方法即可。

  3. 热销排行

    该接口已经在商品列表页中实现完毕,前端直接调用接口即可。

  4. 商品

    SKU

    信息(详情信息)

    通过

    sku_id

    可以找到

    SKU

    信息,然后渲染模板即可。

    使用

    Ajax

    实现局部刷新效果。
  5. SKU

    规格信息

    通过

    SKU

    可以找到

    SPU

    规格和

    SKU

    规格信息。
  6. 商品详情介绍、规格与包装、售后服务

    通过

    SKU

    可以找到

    SPU

    信息,

    SPU

    中可以查询出商品详情介绍、规格与包 装、售后服务。
  7. 商品评价

    商品评价需要在生成了订单,对订单商品进行评价后再实现,商品评价信息是动态数据。

    使用

    Ajax

    实现局部刷新效果。

商品详情页接口设计和定义

  1. 请求方式
    Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页
  2. 请求参数:路径参数
    Django项目实战——14—(列表页热销排行、商品搜索、Haystack建立数据索引、渲染商品搜索结果、商品详情页)1、列表页热销排行2、商品搜索3、Haystack建立数据索引4、渲染商品搜索结果5、商品详情页
  3. 响应结果:HTML

    detail.html

  4. 接⼝定义

渲染商品频道分类、面包屑导航、商品热销排行

  • 将原先在商品列表页实现的代码拷贝到商品详情页即可。
  • 添加

    detail.js

# 商品详情类
class DetailGoodsView(View):
    """商品详情"""
    def get(self, request, sku_id):
        try:
            sku = SKU.objects.get(id=sku_id)          # 异常处理,sku_id获取的值异常时候
        except Exception as e:
            return render(request, '404.html')
        
        # 查询商品类别
        categories = get_categories()
        
        # 查询面包屑
        breadcrumb = get_breadcrumb(sku.category)
        
        # 传递数据
        context = {
            'categories': categories,
            'breadcrumb': breadcrumb,
            'sku': sku,
            'category_id': sku.category.id,
        }
        return render(request, 'detail.html', context=context)
    
           

为了让前端在获取商品热销排行数据时,能够拿到商品分类

ID

,我们将商品分类

ID

从模板传⼊到

Vue.js

商品详情页面文件:

templates/detail.html

{#   商品详情页面文件:templates/detail.html        #}
{% load static %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>LG商城-商品详情</title>
    <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
	<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
	<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
</head>
<body>
	<div id="app">
	<div class="header_con">
		<div class="header" v-cloak>
			<div class="welcome fl">欢迎来到LG商城!</div>
			<div class="fr">
                <div v-if="username" class="login_btn fl">
                    欢迎您:<em>[[ username ]]</em>
                    <span>|</span>
                    <a href="{{ url('users:logout' %}">退出</a>
                </div>
                <div v-else class="login_btn fl">
                    <a href="{{ url('users:login' %}">登录</a>
                    <span>|</span>
                    <a href="{{ url('users:register' %}">注册</a>
                </div>
				<div class="user_link fl">
					<span>|</span>
					<a href="{{ url('users:info' %}">用户中心</a>
					<span>|</span>
					<a href="cart.html">我的购物车</a>
					<span>|</span>
					<a href="user_center_order.html">我的订单</a>
				</div>
			</div>
		</div>		
	</div>
	<div class="search_bar clearfix">
		<a href="{{ url('contents:index' %}" class="logo fl"><img src="{% static 'images/logo.png' %}"></a>
		<div class="search_wrap fl">
			<form method="get" action="/search/" class="search_con">
                <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
                <input type="submit" class="input_btn fr" name="" value="搜索">
            </form>
			<ul class="search_suggest fl">
				<li><a href="#">索尼微单</a></li>
				<li><a href="#">优惠15元</a></li>
				<li><a href="#">美妆个护</a></li>
				<li><a href="#">买2免1</a></li>
			</ul>
		</div>
		<div class="guest_cart fr">
			<a href="cart.html" class="cart_name fl">我的购物车</a>
			<div class="goods_count fl" id="show_count">2</div>
			<ul class="cart_goods_show">
				<li>
					<img src="{% static 'images/goods/goods001.jpg' %}" alt="商品图片">
					<h4>华为 HUAWEI P10 Plus 6GB+64GB 钻雕金 移动联通电信4G手机 双卡双待</h4>
					<div>1</div>
				</li>
				<li>
					<img src="{% static 'images/goods/goods002.jpg' %}" alt="商品图片">
					<h4>Apple iPhoneX 64GB 深空灰色 移动联通电信4G手机</h4>
					<div>1</div>
				</li>
			</ul>			
		</div>
	</div>
	<div class="navbar_con">
		<div class="navbar">
			<div class="sub_menu_con fl">
				<h1 class="fl">商品分类</h1>

				{#  apps/goods/views.py文件中传递的categories数据进行渲染	#}
                <ul class="sub_menu">
                    {% for group in categories.values %}
                        <li>
                        {#      一级查询      #}
                        <div class="level1">
                            {% for channel in group.channels %}
                                <a href="{{ channel.url }}">{{ channel.name }}</a>
                            {% endfor %}
                        </div>
                        <div class="level2">
                            {#      二级查询      #}
                            {% for cat2 in group.sub_cats %}
                                <div class="list_group">
                                    <div class="group_name fl">{{ cat2.name }} &gt;</div>
                                    <div class="group_detail fl">
                                        {#      三级查询      #}
                                        {% for cat3 in cat2.sub_cats %}
                                            <a href="/list/{{ cat3.id }}/1/">{{ cat3.name }}</a>
                                        {% endfor %}
                                    </div>
                                </div>
                            {% endfor %}
                        </div>
                    </li>
                    {% endfor %}
                </ul>

			</div>
			<ul class="navlist fl">
				<li><a href="">首页</a></li>
				<li class="interval">|</li>
				<li><a href="">真划算</a></li>
				<li class="interval">|</li>
				<li><a href="">抽奖</a></li>
			</ul>
		</div>
	</div>

	 {#  面包屑标题展示:传输数据breadcrumb是由商品视图文件:apps/goods/views.py文件传输过来  #}
    <div class="breadcrumb">
        {#   breadcrumb.cat1.name  一级标题     #}
		<a href="http://shouji.jd.com/">{{ breadcrumb.cat1.name }}</a>
        {% if breadcrumb.cat2.name %}
            <span>></span>
            <a href="javascript:;">{{ breadcrumb.cat2.name }}</a>
        {% endif %}
        {#   三级标题     #}
        {% if breadcrumb.cat3.name %}
            <span>></span>
            <a href="javascript:;">{{ breadcrumb.cat3.name }}</a>
        {% endif %}
	</div>

        <div class="goods_detail_con clearfix">
		<div class="goods_detail_pic fl"><img src="{{ sku.default_image_url.url }}"></div>
		<div class="goods_detail_list fr">
			<h3>{{ sku.name }}</h3>
			<p>{{ sku.caption }}</p>
			<div class="price_bar">
				<span class="show_pirce">¥<em>{{ sku.price }}</em></span>
				<a href="javascript:;" class="goods_judge">18人评价</a>
			</div>
			<div class="goods_num clearfix">
				<div class="num_name fl">数 量:</div>
				<div class="num_add fl">
					<input v-model="sku_count" @blur="check_sku_count" type="text" class="num_show fl">
                    <a @click="on_addition" class="add fr">+</a>
                    <a @click="on_minus" class="minus fr">-</a>
				</div> 
			</div>
			{% for spec in specs %}
				<div class="type_select">
					<label>{{ spec.name }}:</label>
					{% for option in spec.spec_options %}
						{% if option.sku_id == sku.id %}
						<a href="javascript:;" class="select">{{ option.value }}</a>
						{% elif option.sku_id %}
						<a href="{% url 'goods:detail' option.sku_id %}">{{ option.value }}</a>
						{% else %}
						<a href="javascript:;">{{ option.value }}</a>
						{% endif %}
					{% endfor %}
				</div>
        	{% endfor %}
			<div class="total" v-cloak>总价:<em>[[ sku_amount ]]元</em></div>
			<div class="operate_btn">
				<a href="javascript:;" class="add_cart" id="add_cart">加入购物车</a>				
			</div>
		</div>
	</div>
	<div class="main_wrap clearfix">
		<div class="l_wrap fl clearfix">
			<div class="new_goods">
				<h3>热销排行</h3>
				<ul>
            {#       热销产品的循环播放       vue的语法        #}
                    <li v-for="sku in hot_skus">
                        <a href="detail.html"><img :src="sku.default_image_url"></a>
						<h4><a href="detail.html">[[  sku.name ]]</a></h4>
						<div class="price">[[ sku.price ]]</div>
					</li>

				</ul>
			</div>
		</div>
		<div class="r_wrap fr clearfix">
			<ul class="detail_tab clearfix">
				<li @click="on_tab_content('detail')" :class="tab_content.detail?'active':''">商品详情</li>
				<li @click="on_tab_content('pack')" :class="tab_content.pack?'active':''">规格与包装</li>
				<li @click="on_tab_content('service')" :class="tab_content.service?'active':''">售后服务</li>
                <li @click="on_tab_content('comment')" :class="tab_content.comment?'active':''">商品评价(18)</li>
			</ul>
			<div @click="on_tab_content('detail')" class="tab_content" :class="tab_content.detail?'current':''">
				<dl>
					<dt>商品详情:</dt>
					<dd>{{ sku.spu.desc_detail|safe }}</dd>
				</dl>
			</div>
			<div @click="on_tab_content('pack')" class="tab_content" :class="tab_content.pack?'current':''">
				<dl>
					<dt>规格与包装:</dt>
					<dd>{{ sku.spu.desc_pack|safe }}</dd>
				</dl>
			</div>
			<div @click="on_tab_content('service')" class="tab_content" :class="tab_content.service?'current':''">
				<dl>
					<dt>售后服务:</dt>
					<dd>{{ sku.spu.desc_service|safe }}</dd>
				</dl>
			</div>
            <div @click="on_tab_content('comment')" class="tab_content" :class="tab_content.comment?'current':''">
				<ul class="judge_list_con">
					<li class="judge_list fl">
						<div class="user_info fl">
							<img src="{% static 'images/cat.jpg' %}">
							<b>张***厨</b>
						</div>
						<div class="judge_info fl">
							<div class="stars_five"></div>
							<div class="judge_detail">派送非常快,第二天上午就收到。2天使用初步总结,前一部手机也是华为P9plus.MATE10pro包装原封未拆精致大气。拆开后第一眼就看到宝石蓝的手机,非常惊艳;然后就是配件一应俱全。开机各方面设置,把通讯录、短信等同步好,同品牌手机同步很快。和P9plus一样的后置指纹识别很方便。录制指纹容易,解锁非常快,秒开!屏幕完好,默认分辨率显示效果很好。</div>
						</div>
					</li>			
				</ul>
			</div>
		</div>
	</div>
	<div class="footer">
		<div class="foot_link">
			<a href="#">关于我们</a>
			<span>|</span>
			<a href="#">联系我们</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情链接</a>		
		</div>
		<p>CopyRight © 2016 北京LG商业股份有限公司 All Rights Reserved</p>
		<p>电话:010-****888    京ICP备*******8号</p>
	</div>
	</div>
    <script type="text/javascript">
		let category_id = "{{ category_id }}";
		let sku_price = "{{ sku.price }}";
        let sku_id = "";
    </script>
	<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/detail.js' %}"></script>
</body>
</html>

           

项目实例代码

商品视图文件:

apps/goods/views.py

"""
商品视图文件:apps/goods/views.py
"""
from django.shortcuts import render
from django.views import View
from contents.utils import get_categories         # 导入封装方法
from .utils import get_breadcrumb                 # 导入面包屑封装方法
from .models import GoodsCategory, SKU            # 导入模型
from django.views.generic import ListView          # 分页
from django.core.paginator import Paginator        # 分页
from django import http
from utils.response_code import RETCODE


# 商品详情类
class DetailGoodsView(View):
    """商品详情"""
    def get(self, request, sku_id):
        try:
            sku = SKU.objects.get(id=sku_id)          # 异常处理,sku_id获取的值异常时候
        except Exception as e:
            return render(request, '404.html')
        
        # 查询商品类别
        categories = get_categories()
        
        # 查询面包屑
        breadcrumb = get_breadcrumb(sku.category)

        # 构建当前商品的规格键
        sku_specs = sku.specs.order_by('spec_id')      # specs外键,根据spec_id排序,从sku_specification表中查询
        # print(sku_specs)
        sku_key = []
        for spec in sku_specs:
            sku_key.append(spec.option.id)             # 添加默认选项
            
        # 获取当前商品的所有SKU
        skus = sku.spu.sku_set.all()                   # 查询tb_sku表
        # print(skus)
        
        # 构建不同规格参数(选项)的sku字典
        spec_sku_map = {}
        for s in skus:
            # 获取sku的规格参数
            s_specs = s.specs.order_by('spec_id')      # 查询的是tb_sku_specification表中数据
            print(s_specs)
            
            # ⽤于形成规格参数-sku字典的键
            key = []
            for spec in s_specs:
                key.append(spec.option.id)              # 添加option.id
            
            # 向规格参数-sku字典添加记录
            spec_sku_map[tuple(key)] = s.id             # key为字典,value值为s.id     {(1,4,7):1, (1,3,7):2}
            
        # 获取当前商品的规格信息
        goods_specs = sku.spu.specs.order_by('id') # spu为外键,specs是商品SPU规格模型中的related_name字段,查询tb_spu_specification表中数据
        
        # 若当前sku的规格信息不完整,则不再继续
        if len(sku_key) < len(goods_specs):
            return
        
        for index, spec in enumerate(goods_specs):       # enumerate()枚举    index是索引值,从0开始,spec是商品SPU规格内容
            # 复制当前sku的规格键
            key = sku_key[:]
            # 该规格的选项
            spec_options = spec.options.all()            # 查询的是tb_specification_option表中的数据
            
            for option in spec_options:
                # 在规格参数sku字典中查找符合当前规格的sku
                key[index] = option.id
                option.sku_id = spec_sku_map.get(tuple(key))        # spec_sku_map是上面拼接的字典,字典根据元祖key取值,将值添加到属性sku_id
            spec.spec_options = spec_options            # 添加属性,spec是对象
        
        # 传递数据
        context = {
            'categories': categories,
            'breadcrumb': breadcrumb,
            'sku': sku,
            'category_id': sku.category.id,
            'goods_specs': goods_specs,             # 商品规格
        }
        return render(request, 'detail.html', context=context)
    

# 热销商品视图
class HotGoodsView(View):
    """热销排行"""
    def get(self, request, category_id):
        """返回两条热销商品"""
        skus = SKU.objects.filter(category_id=category_id, is_launched=True).order_by('-sales')[:2]  # 根据sales的逆序排序,并取前两个数据
        
        # 拼接数据,序列化
        hot_skus = []
        for sku in skus:
            sku_dict = {
                "id": sku.id,
                "default_image_url": sku.default_image_url.url,
                "name": sku.name,
                "price": sku.price,
            }
            hot_skus.append(sku_dict)          # 数据添加
        # print(hot_skus)
        """ 拼接数据之后的形式
        {
            "code":"0",
            "errmsg":"OK",
            "hot_skus":[
                {
                    "id":6,
                    "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRbI2ARekNAAFZsBqChgk3141998",
                    "name":"Apple iPhone 8 Plus (A1864) 256GB 深空灰色 移动联通电信4G手机",
                    "price":"7988.00"
                },
                {
                    "id":14,
                    "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRdMSAaDUtAAVslh9vkK04466364",
                    "name":"华为 HUAWEI P10 Plus 6GB+128GB 玫瑰金 移动联通电信4G手机 双卡双待",
                    "price":"3788.00"
                }
            ]
        }
        """
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'ok', 'hot_skus': hot_skus})


class GoodsListView(View):
    """商品列表页面"""
    def get(self, request, category_id, page_num):
        """查询并渲染商品"""
        # category_id 是商品分类的id
        
        try:
            # 查询面包屑
            category = GoodsCategory.objects.get(id=category_id)
        except Exception as e:
            return render(request, '404.html')
        
        # 获取排序规则  接收sort参数:如果用户不传,就是默认的排序规则
        sort = request.GET.get('sort', 'default')     # 根据字段sort查询
        if sort == 'price':                # 按照排序规则查询该分类商品SKU信息
            sort_field = 'price'           # 根据字段price的正序排列,价格由低到高
        elif sort == 'hot':
            sort_field = '-sales'          # 根据字段sales的倒序排列,销量由高到低
        else:                              # 当传输的排序方式不是上面的两种,需要让排序方式为默认的
            sort = 'default'
            sort_field = 'create_time'     # 根据create_time字段排序
            
        # 排序      根据category.id筛选,以sort_field排序
        skus = SKU.objects.filter(is_launched=True, category_id=category.id).order_by(sort_field)
        # print(skus)
        
        # 分页  Paginator('分页面的记录', '每页记录的条数')  创建分页器
        paginator = Paginator(skus, 5)
        # 获取page_num的页码的数据
        page_skus = paginator.page(page_num)
        
        # 总的页面数
        tatal_page = paginator.num_pages
        
        # 查询商品的类别
        categories = get_categories()
        
        breadcrumb = get_breadcrumb(category)                     # 调用封装的方法
        # print(breadcrumb)
        
        context = {
            "categories": categories,       # 频道分类
            "breadcrumb": breadcrumb,       # 面包屑导航
            "page_skus": page_skus,         # 分页后数据
            "tatal_page": tatal_page,       # 总页数
            "category_id": category_id,     # 商品类别id
            "sort": sort,                   # 排序字段,以何种方式排序
            "page_num": page_num,           # 当前页码
            
        }
        
        return render(request, 'list.html', context=context)        # 传递数据到前端界面list.html

           

商品详情页面文件:

templates/detail.html

{#   商品详情页面文件:templates/detail.html        #}
{% load static %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>LG商城-商品详情</title>
    <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
	<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
	<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
</head>
<body>
	<div id="app">
	<div class="header_con">
		<div class="header" v-cloak>
			<div class="welcome fl">欢迎来到LG商城!</div>
			<div class="fr">
                <div v-if="username" class="login_btn fl">
                    欢迎您:<em>[[ username ]]</em>
                    <span>|</span>
                    <a href="{{ url('users:logout' %}">退出</a>
                </div>
                <div v-else class="login_btn fl">
                    <a href="{{ url('users:login' %}">登录</a>
                    <span>|</span>
                    <a href="{{ url('users:register' %}">注册</a>
                </div>
				<div class="user_link fl">
					<span>|</span>
					<a href="{{ url('users:info' %}">用户中心</a>
					<span>|</span>
					<a href="cart.html">我的购物车</a>
					<span>|</span>
					<a href="user_center_order.html">我的订单</a>
				</div>
			</div>
		</div>		
	</div>
	<div class="search_bar clearfix">
		<a href="{{ url('contents:index' %}" class="logo fl"><img src="{% static 'images/logo.png' %}"></a>
		<div class="search_wrap fl">
			<form method="get" action="/search/" class="search_con">
                <input type="text" class="input_text fl" name="q" placeholder="搜索商品">
                <input type="submit" class="input_btn fr" name="" value="搜索">
            </form>
			<ul class="search_suggest fl">
				<li><a href="#">索尼微单</a></li>
				<li><a href="#">优惠15元</a></li>
				<li><a href="#">美妆个护</a></li>
				<li><a href="#">买2免1</a></li>
			</ul>
		</div>
		<div class="guest_cart fr">
			<a href="cart.html" class="cart_name fl">我的购物车</a>
			<div class="goods_count fl" id="show_count">2</div>
			<ul class="cart_goods_show">
				<li>
					<img src="{% static 'images/goods/goods001.jpg' %}" alt="商品图片">
					<h4>华为 HUAWEI P10 Plus 6GB+64GB 钻雕金 移动联通电信4G手机 双卡双待</h4>
					<div>1</div>
				</li>
				<li>
					<img src="{% static 'images/goods/goods002.jpg' %}" alt="商品图片">
					<h4>Apple iPhoneX 64GB 深空灰色 移动联通电信4G手机</h4>
					<div>1</div>
				</li>
			</ul>			
		</div>
	</div>
	<div class="navbar_con">
		<div class="navbar">
			<div class="sub_menu_con fl">
				<h1 class="fl">商品分类</h1>
                    {#  apps/goods/views.py文件中传递的categories数据进行渲染	#}
                    <ul class="sub_menu">
                        {% for group in categories.values %}
                        <li>
                            {#      一级查询      #}
                            <div class="level1">
                                {% for channel in group.channels %}
                                    <a href="{{ channel.url }}">{{ channel.name }}</a>
                                {% endfor %}
                            </div>
                            <div class="level2">
                                {#      二级查询      #}
                                {% for cat2 in group.sub_cats %}
                                    <div class="list_group">
                                        <div class="group_name fl">{{ cat2.name }} &gt;</div>
                                        <div class="group_detail fl">
                                            {#      三级查询      #}
                                            {% for cat3 in cat2.sub_cats %}
                                                <a href="/list/{{ cat3.id }}/1/">{{ cat3.name }}</a>
                                            {% endfor %}
                                        </div>
                                    </div>
                                {% endfor %}
                            </div>
                        </li>
                    {% endfor %}
                    </ul>

			</div>
			<ul class="navlist fl">
				<li><a href="">首页</a></li>
				<li class="interval">|</li>
				<li><a href="">真划算</a></li>
				<li class="interval">|</li>
				<li><a href="">抽奖</a></li>
			</ul>
		</div>
	</div>

	 {#  面包屑标题展示:传输数据breadcrumb是由商品视图文件:apps/goods/views.py文件传输过来  #}
    <div class="breadcrumb">
        {#   breadcrumb.cat1.name  一级标题     #}
		<a href="http://shouji.jd.com/">{{ breadcrumb.cat1.name }}</a>
        {% if breadcrumb.cat2.name %}
            <span>></span>
            <a href="javascript:;">{{ breadcrumb.cat2.name }}</a>
        {% endif %}
        {#   三级标题     #}
        {% if breadcrumb.cat3.name %}
            <span>></span>
            <a href="javascript:;">{{ breadcrumb.cat3.name }}</a>
        {% endif %}
	</div>

        <div class="goods_detail_con clearfix">
		<div class="goods_detail_pic fl"><img src="{{ sku.default_image_url.url }}"></div>
		<div class="goods_detail_list fr">
			<h3>{{ sku.name }}</h3>
			<p>{{ sku.caption }}</p>

            <div class="price_bar">
				<span class="show_pirce">¥<em>{{ sku.price }}</em></span>
				<a href="javascript:;" class="goods_judge">18人评价</a>
			</div>

            <div class="goods_num clearfix">
				<div class="num_name fl">数 量:</div>
				<div class="num_add fl">
					<input v-model="sku_count" @blur="check_sku_count" type="text" class="num_show fl">
                    <a @click="on_addition" class="add fr">+</a>
                    <a @click="on_minus" class="minus fr">-</a>
				</div> 
			</div>

            {% for spec in goods_specs %}
				<div class="type_select">
					<label>{{ spec.name }}:</label>
					{% for option in spec.spec_options %}
						{% if option.sku_id == sku.id %}
						<a href="javascript:;" class="select">{{ option.value }}</a>
						{% elif option.sku_id %}
						<a href="{% url 'goods:detail' option.sku_id %}">{{ option.value }}</a>
						{% else %}
						<a href="javascript:;">{{ option.value }}</a>
						{% endif %}
					{% endfor %}
				</div>
        	{% endfor %}

            {#  sku_amount是detail.js文件中计算的价格,传输过来的 ;v-cloak是为了解决页面加载显示顺序的bug    #}
			<div class="total" v-cloak>总价:<em>[[ sku_amount ]]元</em></div>
			<div class="operate_btn">
				<a href="javascript:;" class="add_cart" id="add_cart">加入购物车</a>				
			</div>
		</div>
	</div>
	<div class="main_wrap clearfix">
		<div class="l_wrap fl clearfix">
			<div class="new_goods">
				<h3>热销排行</h3>
				<ul>
            {#       热销产品的循环播放       vue的语法        #}
                    <li v-for="sku in hot_skus">
                        <a href="detail.html"><img :src="sku.default_image_url"></a>
						<h4><a href="detail.html">[[  sku.name ]]</a></h4>
						<div class="price">[[ sku.price ]]</div>
					</li>

				</ul>
			</div>
		</div>
		<div class="r_wrap fr clearfix">
			<ul class="detail_tab clearfix">
				<li @click="on_tab_content('detail')" :class="tab_content.detail?'active':''">商品详情</li>
				<li @click="on_tab_content('pack')" :class="tab_content.pack?'active':''">规格与包装</li>
				<li @click="on_tab_content('service')" :class="tab_content.service?'active':''">售后服务</li>
                <li @click="on_tab_content('comment')" :class="tab_content.comment?'active':''">商品评价(18)</li>
			</ul>
			<div @click="on_tab_content('detail')" class="tab_content" :class="tab_content.detail?'current':''">
				<dl>
					<dt>商品详情:</dt>
                    {#     desc_detail 、desc_pack、desc_service是apps/goods/models.py 文件中的SPU模型字段内容    #}
					<dd>{{ sku.spu.desc_detail|safe }}</dd>
				</dl>
			</div>
			<div @click="on_tab_content('pack')" class="tab_content" :class="tab_content.pack?'current':''">
				<dl>
					<dt>规格与包装:</dt>
					<dd>{{ sku.spu.desc_pack|safe }}</dd>
				</dl>
			</div>
			<div @click="on_tab_content('service')" class="tab_content" :class="tab_content.service?'current':''">
				<dl>
					<dt>售后服务:</dt>
					<dd>{{ sku.spu.desc_service|safe }}</dd>
				</dl>
			</div>
            <div @click="on_tab_content('comment')" class="tab_content" :class="tab_content.comment?'current':''">
				<ul class="judge_list_con">
					<li class="judge_list fl">
						<div class="user_info fl">
							<img src="{% static 'images/cat.jpg' %}">
							<b>张***厨</b>
						</div>
						<div class="judge_info fl">
							<div class="stars_five"></div>
							<div class="judge_detail">派送非常快,第二天上午就收到。2天使用初步总结,前一部手机也是华为P9plus.MATE10pro包装原封未拆精致大气。拆开后第一眼就看到宝石蓝的手机,非常惊艳;然后就是配件一应俱全。开机各方面设置,把通讯录、短信等同步好,同品牌手机同步很快。和P9plus一样的后置指纹识别很方便。录制指纹容易,解锁非常快,秒开!屏幕完好,默认分辨率显示效果很好。</div>
						</div>
					</li>			
				</ul>
			</div>
		</div>
	</div>
	<div class="footer">
		<div class="foot_link">
			<a href="#">关于我们</a>
			<span>|</span>
			<a href="#">联系我们</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情链接</a>		
		</div>
		<p>CopyRight © 2016 北京LG商业股份有限公司 All Rights Reserved</p>
		<p>电话:010-****888    京ICP备*******8号</p>
	</div>
	</div>
    <script type="text/javascript">
		let category_id = "{{ category_id }}";
		let sku_price = "{{ sku.price }}";
        let sku_id = "";
    </script>
	<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/detail.js' %}"></script>
</body>
</html>