关键:全文检索框架、搜索引擎、分词包、Ajax请求的使用...
仅作为个人笔记!
目录
1.搜索
1.1. haystack框架和whoosh引擎安装
1.2.索引文件的生成
1.3.全文检索的使用
1.4.更改分词方式
2.购物车
2.3.购物车前端Ajax请求
在首页、详情页和列表页中,都有搜索框,如何实现搜索呢?
关键:全文检索框架、搜索引擎、分词包的使用、
全文检索不同于特定字段的模糊查询,使用全文检索的效率更高,并且能够对于中文进行分词处理。
搜索引擎:可以对表中的某些字段进行关键词分析,建立关键词对应的索引数据;
全文检索框架:帮助用户使用搜索引擎;
本处使用 haystack框架 和 whoosh引擎 。
- haystack:全文检索的框架,支持whoosh、solr、Xapian、Elasticsearc四种全文检索引擎,点击查看官方网站。
- whoosh:纯Python编写的全文搜索引擎,虽然性能比不上sphinx、xapian、Elasticsearc等,但是无二进制包,程序不会莫名其妙的崩溃,对于小型的站点,whoosh已经足够使用,点击查看whoosh文档。
- jieba:一款免费的中文分词包,如果觉得不好用可以使用一些收费产品。
(1)在虚拟环境中依次安装需要的包
pip install django-haystack
pip install whoosh
pip install jieba
(2)在settings.py文件中注册应用haystack,并做配置
# 注册
INSTALLED_APPS = (...'haystack',
)...
# 全文检索框架配置
HAYSTACK_CONNECTIONS = {'default': {# 使用whoosh引擎(配置路径)'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',# 'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine', # 配置好jieba中文分词包后用这个# 设置索引文件生成的路径'PATH': os.path.join(BASE_DIR, 'whoosh_index'),}
}# 当添加、修改、删除数据时,自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
# 指定搜索结果每页显示的条数
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 2
要搜索商品表中的数据,就需要搜索引擎根据表的某些字段来建立关键词对应的索引数据。然后才能让搜索引擎来搜索对应的数据。
根据模型类(如:GoodsSKU)的表的数据生成索引的数据。
from haystack import indexes
# 导入你的模型类
from goods.models import GoodsSKU# 指定对于某个类的某些数据建立索引
# 建议的索引类名格式:模型类名+Index
class GoodsSKUIndex(indexes.SearchIndex, indexes.Indexable):# 索引字段 use_template=True:指定根据表中的哪些字段建立索引文件,这个'指定说明'放在一个文件中text = indexes.CharField(document=True, use_template=True)def get_model(self):# 返回你的模型类return GoodsSKU# 建立索引的数据def index_queryset(self, using=None):return self.get_model().objects.all()
# 指定根据表中的哪些字段建立索引数据
{{ object.name }} # 根据商品的名称建立索引
{{ object.desc }} # 根据商品的简介建立索引
{{ object.goods.detail }} # 根据商品的详情建立索引
python manage.py rebuild_index
就会按照配置,生成目录whoosh_index,并在目录下生成索引数据。
(1)搜索框的前端,点击进行提交时,会通过 haystack 搜索数据。
(2)action提交后的处理应该让搜索引擎来完成,会通过 haystack 搜索数据,故在项目的url中配置。注意提交的地址应该和配置的url一致。
urlpatterns = [... path('search', include('haystack.urls')), # 全文检索框架
]
(3)全文检索结果。
搜索出结果后,haystack会把搜索出的结果传递给templates/search目录下的search.html(没有这个文件可以先自己建),传递的上下文包括:
通过HAYSTACK_SEARCH_RESULTS_PER_PAGE 可以控制每页显示数量。
以下可以快速的查看搜索结果:
......
搜索的关键字:{{ query }}
当前页的Page对象:{{ page }}{% for item in page %}
分页paginator对象:{{ paginator }}
...浏览器显示结果如下:
搜索的关键字:草莓
当前页的Page对象:
草莓 500g
分页paginator对象:
项目中templates/search目录下的search.html关键代码:
{% for item in page %}
{{ item.object.name }}
默认的引擎在对中文进行关键词分析的时候,可能支持的不是很好。可以使用jieba这个分词包,对中文的处理更好。
(1)安装jieba分词模块:
pip install jieba
(2)更改whoosh引擎的默认词语分析类
import jieba
from whoosh.analysis import Tokenizer, Tokenclass ChineseTokenizer(Tokenizer):def __call__(self, value, positions=False, chars=False,keeporiginal=False, removestops=True,start_pos=0, start_char=0, mode='', **kwargs):t = Token(positions, chars, removestops=removestops, mode=mode, **kwargs)seglist = jieba.cut(value, cut_all=True) # 关键是这句for w in seglist:t.original = t.text = wt.boost = 1.0if positions:t.pos = start_pos + value.find(w)if chars:t.startchar = start_char + value.find(w)t.endchar = start_char + value.find(w) + len(w)yield tdef ChineseAnalyzer():return ChineseTokenizer()
...
from .ChineseAnalyzer import ChineseAnalyzer
...# 查找词语分析类:analyzer=StemmingAnalyzer()
# 并改为: analyzer=ChineseAnalyzer()
# 如下:
...schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=ChineseAnalyzer(), field_boost=field_class.b oost, sortable=True)
...
# 全文检索框架配置
HAYSTACK_CONNECTIONS = {'default': {# 使用whoosh引擎(配置路径)# 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine','ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine', # 配置好jieba中文分词包后用这个# 设置索引文件生成的路径'PATH': os.path.join(BASE_DIR, 'whoosh_index'),}
}# 当添加、修改、删除数据时,自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
python manage.py rebuild_index
这样,使用新的分词类后,即使是商品详情中包含的词语,也能搜索到结果。
要完成购物车功能,要确定(尤其是前后端分开开发时):
在商品的详情页,包含购买商品的数量、加入购物车等功能。
在js中,绑定相关按钮等的点击事件进行处理。比如增减商品数、计算总价格等
// 更新总价 update_goods_amount()// 计算商品的总价function update_goods_amount() {// 获取商品的单价和数量price &#61; $(&#39;.show_pirze&#39;).children(&#39;em&#39;).text()count &#61; $(&#39;.num_show&#39;).val()// 计算商品的总价price &#61; parseFloat(price)count &#61; parseInt(count)amount &#61; price*count// 设置商品的总价,设置两位小数$(&#39;.total&#39;).children(&#39;em&#39;).text(amount.toFixed(2)&#43;&#39;元&#39;)}//增商品的数量(减同理$(&#39;.add&#39;).click(function () {// 获取商品数目并&#43;1count &#61; $(&#39;.num_show&#39;).val()count &#61; parseInt(count)&#43;parseInt(&#39;1&#39;)// 重新设置商品的数目$(&#39;.num_show&#39;).val(count)update_goods_amount()})...// 手动输入商品的数量$(&#39;.num_show&#39;).blur(function () {count &#61; $(this).val()// 校验count是否合法(能否转为数字、去除空格...)if (isNaN(count) || count.trim().length&#61;&#61;0 || parseInt(count) <&#61;0){count &#61; 1}$(this).val(parseInt(count))update_goods_amount()})
注&#xff1a;点击事件中&#xff0c;若加1的操作结果并不对&#xff0c;检查代码也没问题。使用alert输出后发现被多次执行了。原来是出现了累加绑定的问题。解决办法可以参考这个博文试试&#xff1a;https://blog.csdn.net/GSCurry/article/details/71857127。
- 使用前先解除绑定&#xff1a;$("#id").unbind("click")
- 使用jQuery的one()方法。该方法为元素绑定一个一次性的事件处理函数&#xff0c;这个事件处理函数只会被执行一次。
- 配合off()方法解除绑定。该方法为元素绑定一个的事件处理函数&#xff0c;再次给改元素添加相同事件时不会累加绑定。
购物车设计
是否传递数据、什么格式什么数据&#xff1f;
访问方式get? post?
返回给前端什么格式什么数据&#xff1f;
redis存储购物车记录&#xff1a;
- 用户点击加入购物车时需要添加购物车记录&#xff08;添加&#xff09;&#xff1b;
- 使用购物车中数据和访问购物车页面时需要获取购物车记录&#xff08;获取&#xff09;&#xff1b;
- 存储购物车记录的格式&#xff1a;一个用户的购物车记录用一条数据保存&#xff0c;用hash类型&#xff08;属性&#xff1a;值&#xff0c;&#39;cart_用户id&#39;:{&#39;sku_id1&#39;:商品数目, &#39;sku_id2&#39;:商品数目, ...}&#xff09;记录skuid和数量&#xff1b;
在前端点击加入购物车之后&#xff0c;通常页面的整体是不进行刷新操作的。所以采用的方式如下
附&#xff1a;传参的几种方式
- get传参&#xff1a;cart/add?sku_id&#61;1?count&#61;3 &#xff1b;值涉及获取&#xff0c;可采用get&#xff1b;
- post传参&#xff1a;参数会放在一个字典 {&#39;sku_id&#39;:1, &#39;count&#39;:3 } &#xff1b;若涉及到数据的修改&#xff08;增删改&#xff09;,采用post &#xff1b;
- url传参&#xff1a;url配置时捕获参数&#xff1b;
cart/view.py中相关视图
...
# /cart/add
class CartAddView(View):"""购物车记录添加"""def post(self, request):user &#61; request.userif not user.is_authenticated:return JsonResponse({&#39;res&#39;: 0, &#39;errmsg&#39;: &#39;请先登录&#39;})# 接收数据sku_id &#61; request.POST.get(&#39;sku_id&#39;)count &#61; request.POST.get(&#39;count&#39;)if not all([sku_id, count]):return JsonResponse({&#39;res&#39;: 1, &#39;errmsg&#39;: &#39;数据不完整&#39;})# 校验添加的商品数量try:count &#61; int(count)except Exception as e:return JsonResponse({&#39;res&#39;: 2, &#39;errmsg&#39;: &#39;商品数目出错&#39;})try:sku &#61; GoodsSKU.objects.get(id&#61;sku_id)except GoodsSKU.DoesNotExist:return JsonResponse({&#39;res&#39;: 3, &#39;errmsg&#39;: &#39;商品不存在&#39;})# 业务处理:添加购物车记录(已经有的累加、没有的添加)conn &#61; get_redis_connection(&#39;default&#39;)cart_key &#61; &#39;cart_%d&#39; % user.id# 先尝试获取sku_id的值 : hget cart_key 属性# 如果sku_id在hash中不存在&#xff0c;hget会返回Nonecart_count &#61; conn.hget(cart_key, sku_id)if cart_count:# 累加购物车中商品的数目count &#43;&#61; int(cart_count)# 校验商品的库存if count > sku.stock:return JsonResponse({&#39;res&#39;: 4, &#39;errmsg&#39;: &#39;商品库存不足&#39;})# 设置hash中sku_id对应的值# hset:如果sku_id已经存在&#xff0c;更新数据&#xff0c; 如果sku_id不存在&#xff0c;添加数据conn.hset(cart_key, sku_id, count)# 计算用户购物车商品的条目数total_count &#61; conn.hlen(cart_key)# 返回应答return JsonResponse({&#39;res&#39;:5, &#39;total_count&#39;:total_count, &#39;message&#39;:&#39;添加成功&#39;})
购物车记录的添加在Ajax中提交&#xff0c;在刚刚的视图中进行处理后返回。
注意&#xff0c;如果是表单post提交&#xff0c;csrf验证比较好处理&#xff0c;加上&#xff5b;% csrf_token %&#xff5d;即可。Ajax提交怎么处理csrf验证呢&#xff1f;
&#xff08;1&#xff09;在前端同加上&#xff5b;% csrf_token %&#xff5d;&#xff1b;
&#xff08;2&#xff09;访问页面&#xff0c;然后查看源代码&#xff0c;发现对应的隐藏域&#xff0c;类似这样 &#xff1b;
&#xff08;3&#xff09;在Ajax的js中&#xff0c;获取它的值&#xff0c;并加入到要传递的参数中
csrf &#61; $(&#39;input[name&#61;"csrfmiddlewaretoken"]&#39;).val()
params &#61; {&#39;sku_id&#39;:sku_id, &#39;count&#39;:count, &#39;csrfmiddlewaretoken&#39;:csrf}这样&#xff0c;就可以通过验证。
// 获取add_cart div元素左上角的坐标var $add_x &#61; $(&#39;#add_cart&#39;).offset().top;var $add_y &#61; $(&#39;#add_cart&#39;).offset().left;// 获取show_count div元素左上角的坐标var $to_x &#61; $(&#39;#show_count&#39;).offset().top;var $to_y &#61; $(&#39;#show_count&#39;).offset().left;$(&#39;#add_cart&#39;).click(function(){// 获取商品的id和数量sku_id &#61; $(this).attr(&#39;sku_id&#39;) // 获取自定义属性用attrcount &#61; $(&#39;.num_show&#39;).val()// 获取csrf隐藏域csrf &#61; $(&#39;input[name&#61;"csrfmiddlewaretoken"]&#39;).val()params &#61; {&#39;sku_id&#39;:sku_id, &#39;count&#39;:count, &#39;csrfmiddlewaretoken&#39;:csrf}// 发起ajax post请求&#xff0c;访问/cart/add, 传递参数$.post(&#39;/cart/add&#39;, params, function (data) {if (data.res &#61;&#61; 5){// 添加成功,并显示动画$(".add_jump").css({&#39;left&#39;:$add_y&#43;80,&#39;top&#39;:$add_x&#43;10,&#39;display&#39;:&#39;block&#39;})$(".add_jump").stop().animate({&#39;left&#39;: $to_y&#43;7,&#39;top&#39;: $to_x&#43;7},"fast", function() {$(".add_jump").fadeOut(&#39;fast&#39;,function(){// 设置用户购物车中商品的条目数$(&#39;#show_count&#39;).html(data.total_count);});});}else {// 添加失败alert(data.errmsg)}})})
用一个独立的页面来显示购物车。
相关基本视图如下&#xff1a;
# /cart/
class CartInfoView(LoginRequiredMixin, View):"""购物车页面显示"""def get(self, request):"""显示"""# 获取登录的用户user &#61; request.user# 获取用户购物车中商品的信息(保存在redis)conn &#61; get_redis_connection(&#39;default&#39;)cart_key &#61; &#39;cart_%d&#39; % user.id# 记录的格式&#xff1a;{&#39;商品id&#39;:商品数量, ...}cart_dict &#61; conn.hgetall(cart_key)skus &#61; []# 保存用户购物车中商品的总数目和总价格total_count &#61; 0total_price &#61; 0# 遍历获取商品的信息for sku_id, count in cart_dict.items():# 根据商品的id获取商品的信息sku &#61; GoodsSKU.objects.get(id&#61;sku_id)# 计算商品的小计amount &#61; sku.price * int(count)# 动态给sku对象增加一个属性amount, 保存商品的小计sku.amount &#61; amount# 动态给sku对象增加一个属性count, 保存购物车中对应商品的数量sku.count &#61; int(count) # 注意转为int# 添加skus.append(sku)# 累加计算商品的总数目和总价格total_count &#43;&#61; int(count)total_price &#43;&#61; amount# 组织上下文context &#61; {&#39;total_count&#39;: total_count,&#39;total_price&#39;: total_price,&#39;skus&#39;: skus}# 使用模板return render(request, &#39;cart.html&#39;, context)
使用js实现购物车页面显示的全选、选部分商品时相应的处理。在cart.html中添加js&#xff0c;改变相应的checkbox的事件。
JQuery选择器参考&#xff1a;https://www.w3school.com.cn/jquery/jquery_ref_selectors.asp、:checkbox、:checked、
购物车页面中&#xff0c;可以对数量进行增减等操作。采用Ajax post请求提交给后台&#xff0c;传递的参数:商品的id(sku_id)。在后台的视图中进行处理。
相关视图跟基本视图差不多&#xff08;校验登录、接收数据、检验数据、业务处理、返回json...&#xff09;&#xff0c;注意Ajax返回值。前端购物车商品数量的增加的Ajax如下&#xff0c;减少的类似&#xff1a;
&#xff08;注意&#xff1a;默认发起的ajax请求都是异步的&#xff0c;不会等回调函数执行&#xff0c;需要时要先设置ajax请求为同步&#xff0c;再设置回异步&#xff09;
// 计算商品的小计function update_goods_amount(sku_ul) {count &#61; sku_ul.find(&#39;.num_show&#39;).val()price &#61; sku_ul.children(&#39;.col05&#39;).text()amount &#61; parseInt(count)*parseFloat(price)// 设置商品的小计sku_ul.children(&#39;.col07&#39;).text(amount.toFixed(2)&#43;&#39;元&#39;)}
...// 更新购物车中商品的数量error_update &#61; falsetotal &#61; 0function update_remote_cart_info(sku_id, count){csrf &#61; $(&#39;input[name&#61;"csrfmiddlewaretoken"]&#39;).val()//组织参数params &#61; {&#39;sku_id&#39;: sku_id, &#39;count&#39;: count, &#39;csrfmiddlewaretoken&#39;: csrf}// 设置ajax请求为同步$.ajaxSettings.async &#61; false// 发起Ajax post请求&#xff0c;访问/cart/update, 传递参数:sku_id count// 注意&#xff1a;默认发起的ajax请求都是异步的&#xff0c;不会等回调函数执行&#xff0c;需要时要先设置ajax请求为同步&#xff0c;再设置回异步$.post(&#39;/cart/update&#39;, params, function (data) {if(data.res &#61;&#61; 5){// 更新成功error_update &#61; falsetotal &#61; data.total_count}else{error_update &#61; truealert(data.errmsg)}})// 回调函数执行结束&#xff0c;设置ajax请求为异步$.ajaxSettings.async &#61; true}// 购物车商品数量的增加$(&#39;.add&#39;).click(function () {// 获取商品的id和商品的数量sku_id &#61; $(this).next().attr(&#39;sku_id&#39;)count &#61; $(this).next().val()count &#61; parseInt(count)&#43;1// 更新购物车记录update_remote_cart_info(sku_id, count)// 判断更新是否成功if(error_update &#61;&#61; false){// 重新设置商品的数目$(this).next().val(count)// 计算商品的小计update_goods_amount($(this).parents(&#39;ul&#39;))// 获取商品对应的checkbox的选中状态&#xff0c;如果被选中&#xff0c;更新页面信息is_checked &#61; $(this).parents(&#39;ul&#39;).find(&#39;:checkbox&#39;).prop(&#39;checked&#39;)if (is_checked){// 更新页面信息update_page_info()}// 更新页面上购物车商品的总件数$(&#39;.total_count&#39;).children(&#39;em&#39;).text(total)}})// 购物车商品数量的减少......// 记录用户输入之前商品的数量pre_count &#61; 0$(&#39;.num_show&#39;).focus(function () {pre_count &#61; $(this).val()})// 手动输入购物车中的商品数量$(&#39;.num_show&#39;).blur(function () {sku_id &#61; $(this).attr(&#39;sku_id&#39;)count &#61; $(this).val()if (isNaN(count) || count.trim().length&#61;&#61;0 || parseInt(count)<&#61;0){// 设置商品的数目为用户输入之前的数目$(this).val(pre_count)return}update_remote_cart_info(sku_id, count)if(error_update &#61;&#61; false){$(this).val(count)update_goods_amount($(this).parents(&#39;ul&#39;))// 获取商品对应的checkbox的选中状态&#xff0c;如果被选中&#xff0c;更新页面信息is_checked &#61; $(this).parents(&#39;ul&#39;).find(&#39;:checkbox&#39;).prop(&#39;checked&#39;)if (is_checked){update_page_info()}$(&#39;.total_count&#39;).children(&#39;em&#39;).text(total)}else {// 设置商品的数目为用户输入之前的数目$(this).val(pre_count)}})
点击删除将购物车商品删除。
同样采用ajax post请求、 前端需要传递的参数:商品的id(sku_id)。
view.py视图关键code&#xff1a;
...# 业务处理:删除购物车记录conn &#61; get_redis_connection(&#39;default&#39;)cart_key &#61; &#39;cart_%d&#39; % user.id# 删除 hdelconn.hdel(cart_key, sku_id)。。。
cart.html提交的相关Ajax请求&#xff0c;关键是回调成功后&#xff0c;执行.remove()&#xff1a;
// 删除购物车中的记录$(&#39;.cart_list_td&#39;).children(&#39;.col08&#39;).children(&#39;a&#39;).click(function () {// 获取对应商品的idsku_id &#61; $(this).parents(&#39;ul&#39;).find(&#39;.num_show&#39;).attr(&#39;sku_id&#39;)csrf &#61; $(&#39;input[name&#61;"csrfmiddlewaretoken"]&#39;).val()params &#61; {&#39;sku_id&#39;:sku_id, &#39;csrfmiddlewaretoken&#39;:csrf}// 获取商品所在的ul元素sku_ul &#61; $(this).parents(&#39;ul&#39;)// 发起ajax post请求&#xff0c; 访问/cart/delete, 传递参数:sku_id$.post(&#39;/cart/delete&#39;, params, function (data) {if (data.res &#61;&#61; 3){// 删除成功&#xff0c;移除页面上商品所在的ul元素sku_ul.remove()// 获取sku_ul中商品的选中状态is_checked &#61; sku_ul.find(&#39;:checkbox&#39;).prop(&#39;checked&#39;)if (is_checked){update_page_info()}// 重新设置页面上购物车中商品的总件数$(&#39;.total_count&#39;).children(&#39;em&#39;).text(data.total_count)}else{alert(data.errmsg)}})})
01 框架、数据表设计、项目框架笔记
02 注册、登录、用户中心 &#xff08;itsdangerous模块加密、celery异步、 Django 的验证系统、redis作为缓存等&#xff09;
03 FastDFS文件存储-首页-详情页-列表页
04 搜索&#xff08;搜索引擎、分词包的使用&#xff09;、购物车
05 订单&#xff08;Mysql事务、并发处理、支付宝支付、评论&#xff09;
06 项目部署&#xff08;uwsgi服务器、Nginx服务器&#xff09;
仅作为个人笔记 &#xff01;
-----end-----