开源web框架django知识总结(十八)
商品详情页
商品详情页分析和准备
1. 商品详情页组成结构分析
1.商品频道分类
- 封装在
goods.utils.py
文件中,直接调用方法即可。
from .models import GoodsCategory,GoodsChannel,SKUSpecification,SKU,SPUSpecification,SpecificationOption
from copy import deepcopy
def get_categories():
# 模版参数categories是首页分类频道
categories = {}
# 获取首页所有的分类频道数据
channels = GoodsChannel.objects.order_by(
'group_id',
'sequence'
)
# 遍历所有是分类频道,构建以组号作为key的键值对
for channel in channels:
# channel: GoodsChannel对象
if channel.group_id not in categories:
categories[channel.group_id] = {
'channels': [], # 当前分组中的分类频道(一级分类)
'sub_cats': [] # 二级分类
}
# (1)、填充当前组中的一级分类
cat1 = channel.category
categories[channel.group_id]['channels'].append({
'id': cat1.id,
'name': cat1.name,
'url': channel.url
})
# (2)、填充当前组中的二级分类
cat2s = GoodsCategory.objects.filter(parent=cat1)
for cat2 in cat2s:
# cat2:二级分类对象
cat3_list = [] # 每一次遍历到一个二级分类对象的时候,初始化一个空列表,用来构建三级分类
cat3s = GoodsCategory.objects.filter(parent=cat2)
# (3)、填充当前组中的三级分类
for cat3 in cat3s:
# cat3;三级分类对象
cat3_list.append({
'id': cat3.id,
'name': cat3.name
})
categories[channel.group_id]['sub_cats'].append({
'id': cat2.id,
'name': cat2.name,
'sub_cats': cat3_list # 三级分类
})
return categories
2.面包屑导航
- 已经提前封装在
goods.utils.py
文件中,直接调用方法即可。
def get_breadcrumb(category_id):
# 根据category_id获取导航信息 这里category_id只是一个参数,并不是数据库字段名字
ret_dict = {}
category = GoodsCategory.objects.get(pk=category_id)
# 1级
if not category.parent:
ret_dict['cat1'] = category.name
# 2级
elif not category.parent.parent:
ret_dict['cat2'] = category.name
ret_dict['cat1'] = category.parent.name
# 3级
elif not category.parent.parent.parent:
ret_dict['cat3'] = category.name
ret_dict['cat2'] = category.parent.name
ret_dict['cat1'] = category.parent.parent.name
return ret_dict
3.热销排行
- 该接口已经在商品列表页中实现完毕,前端直接调用接口即可。
4.商品SKU信息(详情信息)
- 通过
sku_id
可以找到SKU信息,然后渲染模板即可。 - 使用Ajax实现局部刷新效果。
5.SKU规格信息
- 通过
SKU
可以找到SPU规格和SKU规格信息。
6.商品详情介绍、规格与包装、售后服务
-
通过
SKU
可以找到SPU
信息,SPU
中可以查询出商品详情介绍、规格与包装、售后服务。 -
提前封装在
goods.utils.py
文件中,直接调用方法即可。
def get_goods_and_spec(sku_id):
# 当前SKU商品
sku = SKU.objects.get(pk=sku_id)
# 记录当前sku的选项组合
cur_sku_spec_options = SKUSpecification.objects.filter(sku=sku).order_by('spec_id')
cur_sku_options = [] # [1,4,7]
for temp in cur_sku_spec_options:
# temp是SKUSpecification中间表对象
cur_sku_options.append(temp.option_id)
# spu对象(SPU商品)
goods = sku.spu
# 罗列出和当前sku同类的所有商品的选项和商品id的映射关系
# {(1,4,7):1, (1,3,7):2}
sku_options_mapping = {}
skus = SKU.objects.filter(spu=goods)
for temp_sku in skus:
# temp_sku:每一个sku商品对象
sku_spec_options = SKUSpecification.objects.filter(sku=temp_sku).order_by('spec_id')
sku_options = []
for temp in sku_spec_options:
sku_options.append(temp.option_id) # [1,4,7]
sku_options_mapping[tuple(sku_options)] = temp_sku.id # {(1,4,7):1}
# specs当前页面需要渲染的所有规格
specs = SPUSpecification.objects.filter(spu=goods).order_by('id')
for index, spec in enumerate(specs):
# spec每一个规格对象
options = SpecificationOption.objects.filter(spec=spec)
# 每一次选项规格的时候,准备一个当前sku的选项组合列表,便于后续使用
temp_list = deepcopy(cur_sku_options) # [1,4,7]
for option in options:
# 每一个选项,动态添加一个sku_id值,来确定这个选项是否属于当前sku商品
temp_list[index] = option.id # [1,3,7] --> sku_id?
option.sku_id = sku_options_mapping.get(tuple(temp_list)) # 找到对应选项组合的sku_id
# 在每一个规格对象中动态添加一个属性spec_options来记录当前规格有哪些选项
spec.spec_options = options
return goods, sku, specs
7.商品评价
-
商品评价需要在生成了订单,对订单商品进行评价后再实现,商品评价信息是动态数据。
-
使用Ajax实现局部刷新效果。
统计分类商品访问量
提示:
- 统计分类商品访问量 是统计一天内该类别的商品被访问的次数。
- 需要统计的数据,包括商品分类,访问次数,访问时间。
- 一天内,一种类别,统计一条记录。
1. 统计分类商品访问量模型类
模型类定义在
goods.models.py
中,然后完成迁移建表。
class GoodsVisitCount(BaseModel):
"""统计分类商品访问量模型类"""
category = models.ForeignKey(GoodsCategory, on_delete=models.CASCADE, verbose_name='商品分类')
count = models.IntegerField(verbose_name='访问量', default=0)
date = models.DateField(auto_now_add=True, verbose_name='统计日期')
class Meta:
db_table = 'tb_goods_visit'
verbose_name = '统计分类商品访问量'
verbose_name_plural = verbose_name
用户浏览记录
设计浏览记录存储方案
- 当登录用户在浏览商品的详情页时,我们就可以把详情页这件商品信息存储起来,作为该登录用户的浏览记录。
- 用户未登录,我们不记录其商品浏览记录。
1. 存储数据说明
- 虽然浏览记录界面上要展示商品的一些SKU信息,但是我们在存储时没有必要存很多SKU信息。
- 我们选择存储SKU信息的唯一编号(sku_id)来表示该件商品的浏览记录。
- 存储数据:sku_id
2. 存储位置说明
- 用户浏览记录是临时数据,且经常变化,数据量不大,所以我们选择内存型数据库进行存储。
- 存储位置:Redis数据库 4号库
"history": { # 用户浏览历史
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/4",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
3. 存储类型说明
- 由于用户浏览记录跟用户浏览商品详情的顺序有关,所以我们选择使用Redis中的list类型存储 sku_id
- 每个用户维护一条浏览记录,且浏览记录都是独立存储的,不能共用。所以我们需要对用户的浏览记录进行唯一标识。
- 我们可以使用登录用户的ID来唯一标识该用户的浏览记录。
- 存储类型:‘history_user_id’ : [sku_id_1, sku_id_2, …]
4. 存储逻辑说明
- SKU信息不能重复。
- 最近一次浏览的商品SKU信息排在最前面,以此类推。
- 每个用户的浏览记录最多存储五个商品SKU信息。
- 存储逻辑:先去重,再存储,最后截取。
============================
保存和查询浏览记录
1. 保存用户浏览记录
1.请求方式
选项 | 方案 |
---|---|
请求方法 | POST |
请求地址 | /browse_histories/ |
2.请求参数:JSON
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
sku_id | string | 是 | 商品SKU编号 |
3.响应结果:JSON
字段 | 说明 |
---|---|
code | 状态码 |
errmsg | 错误信息 |
4.后端接口定义和实现users.views.py
from goods.models import SKU
class UserBrowseHistory(View):
@method_decorator(login_required)
def post(self, request):
# 记录用户历史
# 1、获取请求参数
data = json.loads(request.body.decode())
sku_id = data.get('sku_id')
user = request.user
# 2、加入用户历史记录redis列表中
conn = get_redis_connection('history')
p = conn.pipeline()
# 2.1 去重
p.lrem('history_%s'%user.id, 0, sku_id)
# 2.2 左侧插入
p.lpush('history_%s'%user.id, sku_id)
# 2.3 截断列表(保证最多5条)
p.ltrim('history_%s'%user.id, 0, 4)
p.execute()
# 3、构建响应
return JsonResponse({'code': 0, 'errmsg': 'ok'})
@method_decorator(login_required)
def get(self, request):
# 展示用户浏览历史
user = request.user
conn = get_redis_connection('history')
# 1、读取redis浏览历史
# sku_ids = [b'4', b'5', b'11'] 其中 0 表示列表的第一个元素以 -1 表示列表的最后一个元素
sku_ids = conn.lrange('history_%s'%user.id, 0, -1)
# 2、获取sku商品信息
# sku_ids = [int(x) for x in sku_ids]
skus = SKU.objects.filter(id__in=sku_ids)
# 3、构建响应
sku_list = []
for sku in skus:
sku_list.append({
'id': sku.id,
'name': sku.name,
'price': sku.price,
'default_image_url': sku.default_image_url.url
})
return JsonResponse({
'code': 0,
'errmsg': 'ok',
'skus': sku_list
})
users.urls.py
# 记录用户历史
re_path(r'^browse_histories/$', UserBrowseHistory.as_view()),