Django项目实战——13—(自定义Django文件存储类、商品列表页、列表页面包屑导航、列表页分页和排序)

1、自定义Django文件存储类

上一篇博客中介绍了首页轮播图的显示,由于因为域名的问题,图片无法访问虚拟机中的storage容器中存储的data数据。

结论:

  • 通过FastDFS上传文件后返回的Remote file_id字段是文件索引。
  • 文件索引会被我们存储到MySQL数据库。所以将来读取出来的也是文件索引,导致界面无法下载到图片。

解决:

  • 重写Django文件存储类的url()方法。
  • 在重写时拼接完整的图片下载地址(协议、IP、端口、文件索引)

Django文件存储类url()方法介绍

在这里插入图片描述
结论:

  • 文件存储类url()方法的作用:返回name所代表的文件内容的URL。
  • 文件存储类url()方法的触发:content.image.url
    • 虽然表面上调用的是ImageFieldurl方法。但是内部会去调用文件存储类的url()方法。
  • 文件存储类url()方法的使用:
    • 我们可以通过自定义Django文件存储类达到重写url()方法的目的。
    • 自定义Django文件存储类必须提供url()方法。
    • 返回name所指的文件对应的绝对URL

自定义Django文件存储类

自定义文件存储类的官方文档https://docs.djangoproject.com/en/2.2/howto/custom-file-storage/

重写文件存储继承的类utils/fastdfs/fdfs_storage.py

# -*- encoding: utf-8 -*-
"""
@File    : fdfs_storage.py
@Time    : 2020/9/7 15:55
@Author  : chen

重写文件存储继承的类:utils/fastdfs/fdfs_storage.py
"""
from django.core.files.storage import Storage
from django.conf import settings


class FastDFSStorage(Storage):
    """自定义文件存储系统,修改存储的方案"""
    def __init__(self, FDFS_BASE_URL=None):
        # if not FDFS_BASE_URL:
        #     self.FDFS_BASE_URL = settings.FDFS_BASE_URL         # 如果参数中没有链接地址,使用dev.py文件中配置的
        # else:
        #     self.FDFS_BASE_URL = FDFS_BASE_URL                      # 传参有链接地址,使用其本身
        
        self.FDFS_BASE_URL = FDFS_BASE_URL or settings.FDFS_BASE_URL    # 功能与上面相同,代码简化
    
    #  _open 和_save方法在Storage类源码中已经存在,所以也不用重写,但是是重写的必添加项
    def _open(self, name, mode='rb'):
        """
        用于打开文件
        :param name: 要打开的文件的名字
        :param mode: 打开文件方式
        :return: None
        """
        # 打开文件时使用的,此时不需要,而文档告诉说明必须实现,所以pass
        pass
    
    def _save(self, name, content):
        """
        用于保存文件
        :param name: 要保存的文件名字
        :param content: 要保存的文件的内容
        :return: None
        """
        # 保存文件时使用的,此时不需要,而文档告诉说明必须实现,所以pass
        pass
    
    # 重写的重要方法是url()
    def url(self, name):
        """
        需要返回的路由: http://192.168.232.141:8888/group1/M00/00/01/CtM3BVrLmnaADtSKAAGlxZuk7uk4998927
        :param name:文件名称,也是路由,相当于是storage中的:group1/M00/00/01/CtM3BVrLmnaADtSKAAGlxZuk7uk4998927
        :return: 返回路由进行拼接:“http://192.168.232.141:8888/” + name
        """
        # 拼接路由
        return self.FDFS_BASE_URL + name            # FDFS_BASE_URL是dev.py文件中的配置参数

开发环境配置文件dev.py

# 指定自定义的Django文件存储类
DEFAULT_FILE_STORAGE = 'utils.fastdfs.fdfs_storage.FastDFSStorage'
# FastDFS相关参数
# FDFS_BASE_URL = "http://192.168.232.141:8888/"
FDFS_BASE_URL = 'http://image.meiduo.site:8888/'      # 可以在/etc/hosts中添加访问Storage的域名进行绑定

可以添加访问图片的域名
• 在/etc/hosts中添加访问Storage的域名

$ Storage的IP        域名
$ 192.168.232.141    image.meiduo.site

在这里插入图片描述

文件存储类url()方法的使用

• 以图片轮播图为例:content.image.url

首页界面templates/index.html

{# 首页界面:templates/index.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/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" v-cloak>    {#	  v-cloak加载显示的问题    #}
		<div class="header">
			<div class="welcome fl">欢迎来到LG商城!</div>
			<div class="fr">

{#          方法一     登录用户的名称显示    #}
{#                {% if user.is_authenticated %}#}
{#                    <div class="login_btn fl">#}
{#                        欢迎您:<em>{{ user.username }}</em>#}
{#                        <span>|</span>#}
{#                        <a href="#">退出</a>#}
{#                    </div>#}
{#                {% else %}        {#   用户未登录,显示为    #}
{#                    <div class="login_btn fl">#}
{#                        <a href="login.html">登录</a>#}
{#                        <span>|</span>#}
{#                        <a href="register.html">注册</a>#}
{#				    </div>#}
{#                {% endif %}#}


{#      方法三: Vue读取cookie渲染用户信息    v-if="username"有值就显示 #}
                    <div v-if="username" class="login_btn fl">
                        欢迎您:<em>[[ username ]]</em>
                        <span>|</span>
{#                       绑定url,users是实例命名空间         #}
                        <a href="{% url 'users:logout' %}">退出</a>
                    </div>
            {#          v-else                       #}
                    <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="index.html" 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 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">
			<h1 class="fl">商品分类</h1>

            {#  apps/contents/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>
			<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>
	<div class="pos_center_con clearfix">
        {#	轮播图的渲染	#}
        <ul class="slide">
            {#    apps/contents/views.py文件传输的contents对象,通过.index_lbt进行访问属性数据    #}
            {% for content in contents.index_lbt %}
                {# http://192.168.232.141:8888/{{ content.image }}修改为content.image.url #}
                <li><a href="{{ content.url }}"><img src="{{ content.image.url }}" alt="{{ content.title }}"></a></li>
            {% endfor %}
        </ul>

......

2、商品列表页

在这里插入图片描述

商品列表页组成结构分析

  1. 商品频道分类
    • 已经提前封装在contents.utils.py文件中,直接调用即可。
  2. 面包屑导航
    • 可以使用三级分类ID,查询出该类型商品的三级分类数据。
  3. 排序和分页
    • 无论如何排序和分页,商品的分类不能变。
    • 排序时需要知道当前排序方式。
    • 分页时需要知道当前分页的页码,且每页五条商品记录。
  4. 热销排行
    • 热销排行中的商品分类要和排序、分页的商品分类一致。
    • 热销排行是查询出指定分类商品销量前二的商品。
    • 热销排行使用Ajax实现局部刷新的效果。

商品列表页接口设计和定义

  1. 请求方式
    在这里插入图片描述
# 按照商品创建时间排序
http://www.meiduo.site:8000/list/115/1/?sort=default
# 按照商品价格由低到高排序
http://www.meiduo.site:8000/list/115/1/?sort=price
# 按照商品销量由高到低排序
http://www.meiduo.site:8000/list/115/1/?sort=hot
  1. 请求参数:路径参数 和 查询参数
    在这里插入图片描述
  2. 响应结果:HTML
    list.html
  3. 接口定义
class GoodsListView(View):
    """商品列表页"""
    def get(self, request, category_id, page_num):
        """提供商品列表页"""
        return render(request, 'list.html')

项目实例代码

封装方法文件,代码复用apps/contents/utils.py

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

封装方法文件,代码复用:apps/contents/utils.py
"""
from goods.models import GoodsChannel, GoodsCategory       # 商品频道,商品类别


# 封装方法  获得所有商品方法
def get_categories():
    # 查看并展示商品的分类
    categories = {}
    
    # 查询所有的商品频道
    # channels = GoodsChannel.objects.all()
    # 37个一级类别
    channels = GoodsChannel.objects.order_by('group_id', 'sequence')  # 根据group_id排序,再根据组内顺序sequence排序
    
    # 遍历所有频道
    for channel in channels:
        group_id = channel.group_id
        
        if group_id not in categories:  # category最顶级商品类别,如果当前频道不在顶级商品类别中
            categories[group_id] = {'channels': [], "sub_cats": []}  # 创建需要传输的数据格式,注意channels的字段信息
        
        # 当前频道对应的一级类别
        cat1 = channel.category  # cat1是对象,category是外键字段
        categories[group_id]['channels'].append({  # channel本身包含字典数据
            "id": cat1.id,
            "name": cat1.name,  # category外键关联的name字段
            "url": channel.url
        })
        
        # 查询二级和三级类别信息
        for cat2 in GoodsCategory.objects.filter(parent__id=cat1.id).all():  # 通过频道对应的一级类别来查询
            cat2.sub_cats = []  # 创建是为了方便cat3进行添加数据
            categories[group_id]['sub_cats'].append({
                "id": cat2.id,
                "name": cat2.name,  # cat2对象本身模型GoodsCategory的name字段
                "sub_cats": cat2.sub_cats
            })
            
            # 查询三级类别信息
            for cat3 in GoodsCategory.objects.filter(parent__id=cat2.id).all():  # 通过二级类别来查询三级
                cat2.sub_cats.append({  # 方便cat3进行添加数据到cat2的list中
                    "id": cat3.id,
                    "name": cat3.name,
                })
                # categories[group_id]['sub_cats'][].append({            # 这种方法添加数据,sub_cats数据无法添加
                #     "id": cat3.id,
                #     "name": cat3.name,
                # })
    
    return categories


首页广告视图文件 apps/contents/views.py

"""
首页广告视图文件 apps/contents/views.py
"""
from django.shortcuts import render
from django.views import View
from django.http import HttpResponse
from collections import OrderedDict         # 有序的字典,不同于普通字典
from goods.models import GoodsChannel, GoodsCategory       # 商品频道,商品类别
from .models import ContentCategory, Content               # 广告内容类别,广告内容
from .utils import get_categories


class IndexView(View):
    """首页广告界面"""
    
    def get(self, request):
        """提供首页界面"""
        # 查看并展示商品的分类
        categories = get_categories()               # 封装后的方法
            
        # 查询所有的首页广告
        context_categories = ContentCategory.objects.all()
        contents = {}
        for context_categorie in context_categories:
            # contents是一个对象,根据category外键进行查询,status=True限制条件,order_by('sequence')按照sequence排序
            contents[context_categorie.key] = Content.objects.filter(category__id=context_categorie.id,
                                                                     status=True).order_by('sequence')

        context = {
            "categories": categories,         # 商品频道整体拼接后的数据
            "contents": contents,             # 首页广告信息拼接结果
        }
        return render(request, "index.html", context=context)

商品模块的路由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'),
]

项目总路由文件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'))           # 商品模块总路由

]

商品列表界面templates/list.html

{# 商品列表界面:templates/list.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 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>
	<div class="breadcrumb">
		<a href="http://shouji.jd.com/">手机</a>
		<span>></span>
		<a href="javascript:;">手机配件</a>
        <span>></span>
		<a href="javascript:;">手机壳</a>
	</div>
	<div class="main_wrap clearfix">
		<div class="l_wrap fl clearfix">
			<div class="new_goods">
				<h3>热销排行</h3>
				<ul>
					<li>
						<a href="detail.html"><img src="{% static 'images/goods/goods001.jpg' %}"></a>
						<h4><a href="detail.html">360手机 N6 Pro 全网通 6GB+128GB 极夜黑</a></h4>
						<div class="price">¥3999.90</div>
					</li>
					<li>
						<a href="detail.html"><img src="{% static 'images/goods/goods002.jpg' %}"></a>
						<h4><a href="detail.html">360手机 N6 Pro 全网通 6GB+128GB 极夜黑</a></h4>
						<div class="price">¥1666.80</div>
					</li>
				</ul>
			</div>
		</div>
		<div class="r_wrap fr clearfix">
			<div class="sort_bar">
				<a href="/list/115/1/?sort=default" class="active">默认</a>
				<a href="/list/115/1/?sort=price">价格</a>
				<a href="/list/115/1/?sort=hot">人气</a>
			</div>
			<ul class="goods_type_list clearfix">
				<li>
					<a href="detail.html"><img src="{% static 'images/goods/goods003.jpg' %}"></a>
					<h4><a href="detail.html">360手机 N6 Pro 全网通 6GB+128GB 极夜黑</a></h4>
					<div class="operate">
						<span class="price">¥1666.80</span>
						<span class="unit"></span>
						<a href="#" class="add_goods" title="加入购物车"></a>
					</div>
				</li>
				<li>
					<a href="detail.html"><img src="{% static 'images/goods/goods005.jpg' %}"></a>
					<h4><a href="detail.html">360手机 N6 Pro 全网通 6GB+128GB 极夜黑</a></h4>
					<div class="operate">
						<span class="price">¥1000.00</span>
						<span class="unit"></span>
						<a href="#" class="add_goods" title="加入购物车"></a>
					</div>
				</li>
				<li>
					<a href="detail.html"><img src="{% static 'images/goods/goods002.jpg' %}"></a>
					<h4><a href="detail.html">360手机 N6 Pro 全网通 6GB+128GB 极夜黑</a></h4>
					<div class="operate">
						<span class="price">¥2888.80</span>
						<span class="unit"></span>
						<a href="#" class="add_goods" title="加入购物车"></a>
					</div>
				</li>
                <li>
					<a href="detail.html"><img src="{% static 'images/goods/goods003.jpg' %}"></a>
					<h4><a href="detail.html">360手机 N6 Pro 全网通 6GB+128GB 极夜黑</a></h4>
					<div class="operate">
						<span class="price">¥1666.80</span>
						<span class="unit"></span>
						<a href="#" class="add_goods" title="加入购物车"></a>
					</div>
				</li>
				<li>
					<a href="detail.html"><img src="{% static 'images/goods/goods005.jpg' %}"></a>
					<h4><a href="detail.html">360手机 N6 Pro 全网通 6GB+128GB 极夜黑</a></h4>
					<div class="operate">
						<span class="price">¥1000.00</span>
						<span class="unit"></span>
						<a href="#" class="add_goods" title="加入购物车"></a>
					</div>
				</li>
			</ul>
		</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 = "";
    </script>
	<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/list.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/jquery.pagination.min.js' %}"></script>
	<script>
        $(function () {
            $('#pagination').pagination({
                currentPage: 1,
                totalPage: 3,
                callback:function (current) {
                    {#location.href = '/list/115/1/?sort=default';#}
                    {#location.href = '/list/{{ category.id }}/' + current + '/?sort={{ sort }}';#}
                }
            })
        });
    </script>
</body>
</html>

商品列表界面的静态文件:static/js/list.js

// 商品列表界面的静态文件:static/js/list.js
let vm = new Vue({
    el: '#app',
    delimiters: ['[[', ']]'],
    data: {
        username: getCookie('username'),
        category_id: category_id,
        hot_skus: [],
        cart_total_count: 0,
        carts: [],
    },
    mounted(){
        // 获取热销商品数据
        this.get_hot_skus();
        // 获取简单购物车数据
        // this.get_carts();
    },
    methods: {
    	// 获取热销商品数据
        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);
                    })
            }
        },
        // 获取简单购物车数据
        get_carts(){
        	let url = '/carts/simple/';
            axios.get(url, {
                responseType: 'json',
            })
                .then(response => {
                    this.carts = response.data.cart_skus;
                    this.cart_total_count = 0;
                    for(let i=0;i<this.carts.length;i++){
                        if (this.carts[i].name.length>25){
                            this.carts[i].name = this.carts[i].name.substring(0, 25) + '...';
                        }
                        this.cart_total_count += this.carts[i].count;
                    }
                })
                .catch(error => {
                    console.log(error.response);
                })
        },
    }
});

3、列表页面包屑导航

重要提示:路径参数category_id是商品第三级分类

查询列表页面包屑导航数据

封装方法文件,代码复用apps/goods/utils.py

# -*- encoding: utf-8 -*-
"""
@File    : utils.py
@Time    : 2020/9/10 11:20
@Author  : chen

封装方法文件,代码复用:apps/goods/utils.py
"""


# 封装获得面包屑导航方法
def get_breadcrumb(category):
    """
    获取面包屑导航
    :param category: 商品类别
    :return: 面包屑导航字典
    """
    breadcrumb = dict(
        cat1='',
        cat2='',
        cat3=''
    )
    if category.parent is None:            # 数据库信息查询,一级类别的parent_id为None
        # 当前类别为一级类别
        breadcrumb['cat1'] = category
    elif category.subs.count() == 0:       #
        # 当前类别为三级
        breadcrumb['cat3'] = category
        cat2 = category.parent
        breadcrumb['cat2'] = cat2
        breadcrumb['cat1'] = cat2.parent
    else:
        # 当前类别为二级
        breadcrumb['cat2'] = category
        breadcrumb['cat1'] = category.parent
    
    return breadcrumb

商品视图文件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                 # 导入模型


class GoodsListView(View):
    """商品列表页面"""
    def get(self, request, category_id, page_num):
        """查询并渲染商品"""
        # category_id 是商品分类的id
        
        # 查询商品的类别
        categories = get_categories()
        
        try:
            # 查询面包屑
            category = GoodsCategory.objects.get(id=category_id)
        except Exception as e:
            return render(request, '404.html')

        breadcrumb = get_breadcrumb(category)                     # 调用封装的方法
        # print(breadcrumb)
        
        context = {
            "categories": categories,
            "breadcrumb": breadcrumb,
        }
        
        return render(request, 'list.html', context=context)        # 传递数据到前端界面list.html

不存在文件界面文件templates/404.html

{#  不存在文件界面文件:templates/404.html  #}
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>404</title>
	<link rel="stylesheet" href="{% static 'css/reset.css' %}">
	<style>
		.logo{
			position:fixed;
			left:50%;
			margin-left:-500px;
			top:20px;			
		}
		.missing_pic{
			display:block;
			margin:70px auto 0px;
		}
		.tip{
			text-align:center;
			font-size:22px;
			font-weight:bold;
			color:#333;
			margin-top:20px;
		}
		.back_to_index{
			display:block;
			width:130px;
			line-height:30px;
			border-radius:15px;
			border:1px solid #ff3f3c;
			background:#ff3f3c;
			color:#fff;
			text-align:center;
			margin:20px auto 0;
		}
		.back_to_index:hover{
			opacity:0.6
		}
	</style>
</head>
<body>
	<a href="{% url 'contents:index' %}" class="logo"><img src="{% static 'images/1.png' %}" alt="logo"></a>
	<img src="{% static 'images/missing.png' %}" alt="404" class="missing_pic">
	<h3 class="tip">Oops!你访问的页面未找到</h3>
	<a href="{% url 'contents:index' %}" class="back_to_index">返回首页</a>
</body>
</html>

渲染列表页面包屑导航数据

商品列表界面templates/list.html

 {#  面包屑标题展示:传输数据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>

4、列表页分页和排序

# 按照商品创建时间排序
http://www.meiduo.site:8000/list/115/1/?sort=default
# 按照商品价格由低到高排序
http://www.meiduo.site:8000/list/115/1/?sort=price
# 按照商品销量由高到低排序
http://www.meiduo.site:8000/list/115/1/?sort=hot

项目实例代码

项目报错
在这里插入图片描述
在这里插入图片描述
商品视图文件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        # 分页


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/list.html

{# 商品列表界面:templates/list.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 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="main_wrap clearfix">
		<div class="l_wrap fl clearfix">
			<div class="new_goods">
				<h3>热销排行</h3>
				<ul>
					<li>
						<a href="detail.html"><img src="{% static 'images/goods/goods001.jpg' %}"></a>
						<h4><a href="detail.html">360手机 N6 Pro 全网通 6GB+128GB 极夜黑</a></h4>
						<div class="price">¥3999.90</div>
					</li>
					<li>
						<a href="detail.html"><img src="{% static 'images/goods/goods002.jpg' %}"></a>
						<h4><a href="detail.html">360手机 N6 Pro 全网通 6GB+128GB 极夜黑</a></h4>
						<div class="price">¥1666.80</div>
					</li>
				</ul>
			</div>
		</div>
		<div class="r_wrap fr clearfix">

			<div class="sort_bar">

                {#     category_id商品类别信息传输给url链接     1代表第一页    下面链接的另一种写法:/list/{{ category_id }}/1/?sort=default    #}
				<a href="{% url 'goods:list' category_id=category_id page_num=1 %}?sort=default" {% if sort == 'default' %} class="active"{% endif %}>默认</a>
				<a href="{% url 'goods:list' category_id=category_id page_num=1 %}?sort=price" {% if sort == 'price' %} class="active"{% endif %}>价格</a>
				<a href="{% url 'goods:list' category_id=category_id page_num=1 %}?sort=hot" {% if sort == 'hot' %} class="active"{% endif %}>人气</a>
			</div>

			<ul class="goods_type_list clearfix">
        {#  循环views.py文件传递的参数page_skus,当前页面的数据 #}
				{% for sku in page_skus %}
                    <li>
{# sku.default_image_url获得的图片链接,default_image_url字段数据类型需要是models.ImageField,.url属性是utils/fastdfs/fdfs_storage.py文件中重写的url()方法 #}
                        <a href="detail.html"><img src="{{ sku.default_image_url.url }}"></a>
                        {#           sku 模型下的name字段            #}
                        <h4><a href="detail.html">{{ sku.name }}</a></h4>
                        <div class="operate">
                            {#           sku 模型下的price字段            #}
                            <span class="price">¥{{ sku.price }}</span>
                            <span class="unit"></span>
                            <a href="#" class="add_goods" title="加入购物车"></a>
                        </div>
				    </li>
                {% 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">
{#     js文件中调用商品类别id ---> category_id  #}
        let category_id = "{{ category_id }}";
    </script>
	<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/list.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/jquery.pagination.min.js' %}"></script>
	<script>
        $(function () {
            $('#pagination').pagination({
                // 当前所在的页码   page_num、tatal_page、category_id参数是views.py文件传输而来
                currentPage: {{ page_num }},
                totalPage: {{ tatal_page }},
                callback:function (current) {
                    {#location.href = '/list/115/1/?sort=default';#}
                    {#     分页链接        #}
                    location.href = '/list/{{ category_id }}/' + current + '/?sort={{ sort }}';
                }
            })
        });
    </script>
</body>
</html>
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值