#前言:
在最近十天我会用Python做一个购物类项目,会用到Django+Mysql+Redis+Vue等。
今天是第六天,主要负责撰写编写商品详细页以及商品搜索、商品访问量统计。若是有不懂大家可以先阅读我的前五篇博客以能够顺承。
若是大家基础有不懂的,小编前面已经关于Django博客都更新完了,大家可以自行查看。若是有需要更改的地方欢迎大家指正,同时也欢迎大家关注点赞收藏等待后续,小编会尽力更新优质博客。
在此我会直接继承上一篇博客继续往下写。
十一、商品详细页:
所谓商品详细页就是打开具体的商品时能弹出关于这个商品的全部信息。
例如这个页面一样。
所以我们的思路就是先从和数据库获取到关于这个商品关于他的具体信息,再将其响应到前端页面,在进行响应,与前面很多功能的思路都是一致的,但是响应这个具体信息会有一些抽象,下面我会逐层解析。
1、响应页面与视图:
#商品详细页
re_path(r"^detail/(?P<sku_id>\d+)$",views.DetailGoodsView.as_view(),name='detail'),
class DetailGoodsView(View):
'''
商品详情页
'''
def get(self , requesr , sku_id):
return render(requesr , 'detail.html' , context=context)
2、获取数据:
因为在这个页面我们仍需要获取到三个级别商品信息名称,所以我们首先要做的就是获取三个级别商品信息名称,具体方法在我上篇博客中详细介绍了,我在这就不多说了。
如何按照get请求获取的id来到SKU数据库来获取到具体的商品,然后在SKUSpecification(它的三个属性全是外键,主要负责关联别的表)数据库通过 sku.id 商品编号获取商品对象的对应规格信息 ,并且按照规格名称排序。然后按照SKU表来获取到SKU的信息,最后响应到前端。
class DetailGoodsView(View):
'''
商品详情页
'''
def get(self , requesr , sku_id):
# 获取商品分类功能
categories = get_categories()
# 获取具体的商品
sku = SKU.objects.get(id=sku_id)
# 调用列表导航栏(面包屑)category是具体的商品从属类别
breadcrumd = get_breadcrumd(sku.category)
# 通过 sku.id 商品编号获取商品对象的对应规格信息 ,并且按照规格名称排序
sku_specs = SKUSpecification.objects.filter(sku_id=sku_id).order_by('spec_id')
'''
spec_id是在这个表中关于每件具体商品的某一属性的关联表的外键值,也就是同一个spec_id代表的是同一个商品的一个属性
sku_id也是外键,所以按照sku_id=sku_id获取到的不只是一个值而是与这个商品关联的很多个值
'''
# 创建一个空列表 , 用来存储当前 sku 商品对应的规格选项
sku_key = []
# 遍历当前 sku 规格选项
for spec in sku_specs:
# 将每个规格选项的 ID 添加到 sku_key 列表中
sku_key.append(spec.option.id)
# [8, 11] 颜色: 金色 , 内存:64GB
# [1, 4, 7] 屏幕尺寸:13.3英寸 , 颜色:银色 , 版本:core i5/8G内存/512G存储
# 获取当前商品的所有 规格选择值
# 保证在选择不同规格的情况下 , 商品不变
spu_id = sku.spu_id
skus = SKU.objects.filter(spu_id=spu_id)
# 构建商品不同规格的参数 , sku的选项值字段
spec_sku_map = {}
# 遍历所有的 SKU , 构建规格选项 ID 与 SKU ID 的映射关系
for i in skus:
# 获取 sku 规格的参数 , 按照 spec_id 排序
s_pecs = i.specs.order_by('spec_id')
# 创建一个空列表 , 用于存储 sku 规格参数
key = []
# 遍历当前 sku 规格参数对象列表
for spec in s_pecs:
# 将规格选项 ID 添加到列表中
key.append(spec.option.id)
# 将规格选项 ID 组合作为键 , sku ID 作为值存入到字典中
spec_sku_map[tuple(key)] = i.id
# 获取当前商品的规格名称
# 根据商品的 ID 获取当前商品的所有规格名称
goods_specs = SPUSpecification.objects.filter(spu_id=spu_id).order_by('id')
# 前端渲染
# 便于前端根据用户的选择展示对应的 SKU 的数据 , 为了给用户展示所有的规格参数
# 根据规格选项生成对应的 sku.id , 更新规格对象获取规格选项信息
# 遍历商品规格 , 为每个规格选择动态绑定对应的 sku id
for index,spec in enumerate(goods_specs):
# 复制 sku_key 列表中的数据 , 避免直接操作 sku_key 中的内容
key = sku_key[:]
# 获取当前商品的规格名称
spec_options = spec.options.all()
# 遍历当前商品的规格名称
for spec_option in spec_options:
# 将当前规格选项对象 spec_option 的 id 赋值给 key 列表中的 index的位置
key[index] = spec_option.id
# 根据列表中的值,在 spec_sku_map 字典中查询对应的 sku 数据
spec_option.sku_id = spec_sku_map.get(tuple(key))
# 更新每个规格对象的选项内容
spec.spec_options = spec_options
context = {
'categories':categories ,
'breadcrumd' : breadcrumd,
'sku' : sku,
'goods_specs' : goods_specs
}
return render(requesr , 'detail.html' , context=context)
注意里面有很多需要注意的细节,这些我在代码的注释中都有撰写,大家看的时候可以细心查看。
3、响应到前端页面:
首先响应商品的三级名称:
<div class="navbar_con">
<div class="navbar">
<div class="sub_menu_con fl">
<h1 class="fl">商品分类</h1>
<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_cat %}
<a href="/list/{{ cat3.id }}/1/">{{ cat3.name }}</a>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</li>
{% endfor %}
</ul>
</div>
<div class="breadcrumb">
<a href="http://shouji.jd.com/">{{ breadcrumd.cat1.name }}</a>
<span>></span>
<a href="javascript:;">{{ breadcrumd.cat2.name }}</a>
<span>></span>
<a href="javascript:;">{{ breadcrumd.cat3.name }}</a>
</div>
其次响应商品具体数据:
<div class="goods_detail_con clearfix">
<div class="goods_detail_pic fl"><img src="/static/images/goods/{{ sku.default_image.url }}.jpg"></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 'detail' option.sku_id %}">{{ option.value }}</a>
{% else %}
<a href="javascript:;">{{ option.value }}</a>
{% endif %}
{% endfor %}
</div>
{% endfor %}
<div class="total">总价:<em>[[ sku_amount ]]元</em></div>
<div class="operate_btn">
<a @click="add_carts" class="add_cart" id="add_cart">加入购物车</a>
</div>
</div>
</div>
<script type="text/javascript">
let category_id = {{ sku.category_id }};
let sku_price = {{ sku.price }};
let sku_id = {{ sku.id }};
</script>
十二、统计分类商品的访问量:
统计商品的访问量很简单,就是首先判断商品存不存在,若是存在就将访问记录存储,注意这个访问记录要看前面是否存在,如果以及存在就不存储,防止同一人刷访问量。
# 统计商品的访问量
re_path(r'^detail/visit/(?P<category_id>\d+)/$' , views.DetailVisitView.as_view()),
class DetailVisitView(View):
'''
统计商品的访问量
'''
def post(self , request , category_id):
# 校验数据
try:
category = GoodsCategory.objects.get(id=category_id)
except Exception:
return HttpResponseForbidden('商品不存在')
t = timezone.localtime()
# yyyy-mm-dd , 数据存储日期的格式
today = '%d-%02d-%02d'%(t.year , t.month , t.day)
# 如果商品类别的访问记录存在 , 那么就不添加,增加访问量数据,修改访问时间即可;反之则添加记录数据
try:
count_data = GoodsVisitCount.objects.get(category=category_id , date=today)
except GoodsVisitCount.DoesNotExist:
# DoesNotExist:模型类数据不存在
# 创建一个空的数据对象
count_data = GoodsVisitCount()
count_data.category = category
count_data.date = today
count_data.count += 1
count_data.save()
return JsonResponse({'code':RETCODE.OK , 'errmsg':'OK'})
十三、商品搜索:
1、安装所需库:
商品搜索所用的库有两个:
django_haystack:这是 django 抽象化出来的搜索层。可以让开发者不关心底层搜索引擎逻辑的情况下 , 进行全文的数据模型搜索。
whoosh:这个是 python 编写的全文搜索引擎库,轻量级,小型的项目或者本地测试。
需要下载:
pip install django_haystack
pip install whoosh
对表中的某些字段进行关键字的分词 , 建立关键词对应的搜索引擎
2、配置文件:
在配置文件中 INSTALLED_APPS 的列表中添加: haystack;
在配置文件添加配置信息:
# 配置 haystack
HAYSTACK_CONNECTIONS = {
'default': {
# 设置搜索引擎
'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
'PATH':os.path.join(BASE_DIR,'whoosh_index'),
},
}
# 当数据库改变时,自动更新新引擎
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
3、创建搜索文件:
在 goods 应用中创建 search_indexes.py
from haystack import indexes
from goods.models import SKU
# 类名必须为模型类名+Index
class SKUIndex(indexes.SearchIndex , indexes.Indexable):
# document 代表搜索引擎将使用此字段内容作为引擎进行搜索
# use_template 代表使用索引模板建立索引文件
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.all()
4、创建搜索引擎文件:
在 templates 目录下创建搜索引擎文件。
templates
|---- search
|---- indexes
|----goods(这个目录的名称是指定搜索模型类所在的应用名称)
|---- sku_text.txt(这个名称根据小写的模型类名称_text.txt)
按照这个顺序来进行创建。
在sku_text.txt中配置搜索索引字段,就是需要搜索的内容是什么属性我们就配置什么属性。
# 指定根据表中的字段建立索引
{{ object.name }}
{{ object.caption }}
5、分页实现:
实现分页 ,在 goods 应用的视图下。
from haystack.query import SearchQuerySet
def search_view(request):
query = request.GET.get('q', '')
page = request.GET.get('page', 1)
# 使用 Haystack 的 SearchQuerySet 进行搜索,过滤出包含搜索关键词的结果集
search_results = SearchQuerySet().filter(content=query)
paginator = Paginator(search_results, 5) # 每页显示5条搜索结果
try:
results = paginator.page(page)
except PageNotAnInteger: # 处理用户在 URL 中输入的页数不是整数的情况,将当前页设为第一页
results = paginator.page(1)
except EmptyPage: # 处理用户请求的页面超出搜索结果范围的情况,将当前页设为最后一页。
results = paginator.page(paginator.num_pages)
return render(request, 'search.html', {'results': results, 'query': query})
6、响应到前端页面:
<div class=" clearfix">
<ul class="goods_type_list clearfix">
{% for result in results %}
<li>
{# object取得才是sku对象 #}
<a href="/detail/{{ result.object.id }}/"><img src="/static/images/goods/{{ result.object.default_image.url }}.jpg"></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">
{% if results.has_previous %}
<a href="?q={{ query }}&page=1">« 首页</a>
<a href="?q={{ query }}&page={{ results.previous_page_number }}">上一页</a>
{% endif %}
当前页: {{ results.number }} of 总页数: {{ results.paginator.num_pages }}
{% if results.has_next %}
<a href="?q={{ query }}&page={{ results.next_page_number }}">下一页</a>
<a href="?q={{ query }}&page={{ results.paginator.num_pages }}">尾页 »</a>
{% endif %}
</div>
</div>
7、路由配置:
# 商品搜索
path('search/' , views.search_view , name='search'),
8、在项目的终端执行创建全文搜索索引文件:
python manage.py rebuild_index
#总结:
本篇博客小编主要撰写商品详细页以及商品搜索、商品访问量统计,其中最为难以理解的就是
前面博客中所创建的有关商品数据模型类之间相互调用使用的问题,在上面内容我已经详细介绍了,其次最为主要的还是关于商品搜索的内容。若是各位大佬发现需要加强的地方欢迎各位前来指正,同时也欢迎您的关注和点赞,小编后续还会持续更新优质好文。您的支持是小编变强的最大动力!