1、自定义Django文件存储类
上一篇博客中介绍了首页轮播图的显示,由于因为域名的问题,图片无法访问虚拟机中的storage容器
中存储的data
数据。
结论:
- 通过
FastDFS
上传文件后返回的Remote file_id
字段是文件索引。 - 文件索引会被我们存储到
MySQL
数据库。所以将来读取出来的也是文件索引,导致界面无法下载到图片。
解决:
- 重写
Django
文件存储类的url()
方法。 - 在重写时拼接完整的图片下载地址(协议、IP、端口、文件索引)
Django文件存储类url()方法介绍
结论:
- 文件存储类
url()
方法的作用:返回name
所代表的文件内容的URL。 - 文件存储类
url()
方法的触发:content.image.url
• 虽然表面上调用的是ImageField
的url
方法。但是内部会去调用文件存储类的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 }} ></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、商品列表页
商品列表页组成结构分析
- 商品频道分类
• 已经提前封装在contents.utils.py文件中,直接调用即可。 - 面包屑导航
• 可以使用三级分类ID,查询出该类型商品的三级分类数据。 - 排序和分页
• 无论如何排序和分页,商品的分类不能变。
• 排序时需要知道当前排序方式。
• 分页时需要知道当前分页的页码,且每页五条商品记录。 - 热销排行
• 热销排行中的商品分类要和排序、分页的商品分类一致。
• 热销排行是查询出指定分类商品销量前二的商品。
• 热销排行使用Ajax实现局部刷新的效果。
商品列表页接口设计和定义
- 请求方式
# 按照商品创建时间排序
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
- 请求参数:路径参数 和 查询参数
- 响应结果:HTML
list.html
- 接口定义
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 }} ></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 }} ></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>