用户中心
用户地址
个人信息
提示:
- 只有登录成功的用户才能进入到用户中心界面
- 如果没有登陆用户,试图进入用户中心,我们需要引导到登陆界面
- 用户中心页面,需要限制页面访问,只允许登录的用户访问
准备工作
用户地址界面:先从用户地址信息开始实现
-
定义用户地址视图
class AddressView(View): """用户地址""" def get(self, request): """提供用户地址页面""" return render(request, 'user_center_site.html') def post(self, request): """修改地址信息""" pass
-
URL正则匹配
url(r'^address$', views.AddressView.as_view(), name='address'),
-
问题:
- 以上操作,在进入用户中心时,没有进行任何的登陆验证
- 需求:
- 只有登录成功的用户才能进入用户中心
- 用户中心页面,需要限制页面访问,只允许登录的用户访问
限制页面访问的next参数
next参数作用
- 在没有登陆时,如果访问了用户地址页面,装饰器
@login_required
会限制页面访问 - 在限制页面访问时,该操作被引导到用户登陆界面
- next参数用于标记,从哪儿来,回哪儿去。从用户地址页来就回到用户地址页去,以此类推
next参数使用
class LoginView(View): """登陆""" def get(self, request): """响应登陆页面""" return render(request, 'login.html') def post(self, request): """处理登陆逻辑""" # 获取用户名和密码 user_name = request.POST.get('username') password = request.POST.get('pwd') # 获取是否勾选'记住用户名' remembered = request.POST.get('remembered') # 参数校验 if not all([user_name, password]): return redirect(reverse('users:login')) # django用户认证系统判断是否登陆成功 user = authenticate(username=user_name, password=password) # 验证登陆失败 if user is None: # 响应登录页面,提示用户名或密码错误 return render(request, 'login.html', {'errmsg':'用户名或密码错误'}) # 验证登陆成功,并判断是否是激活用户 if user.is_active == False: # 如果不是激活用户 return render(request, 'login.html', {'errmsg':'用户未激活'}) # 使用django的用户认证系统,在session中保存用户的登陆状态 login(request, user) # 服务器记录session后,设置客户端cookie的过期日期 if remembered != 'on': # 不需要记住cookie信息 request.session.set_expiry(0) else: # 需要记住cookie信息 request.session.set_expiry(None) # 登陆成功,根据next参数决定跳转方向 next = request.GET.get('next') if next is None: # 如果是直接登陆成功,就重定向到首页 return redirect(reverse('goods:index')) else: # 如果是用户中心重定向到登陆页面,就回到用户中心 return redirect(next)
提示
- 如果需要提取的参数在URL中,使用
request.GET
- 如果需要提取的参数在POST请求体中,使用
request.POST
浏览效果
用户地址页视图编写
用户地址页界面分析
- 收货地址
- 客户端发送GET请求时,展示用户地址页面,并查询用户的地址信息
- 编辑地址
- 客户端编辑地址,并发送POST请求将编辑的地址的表单信息发送到服务器
用户地址页视图编写之展示用户地址页面
- 展示用户地址页面,并查询用户的地址信息
- Django用户认证系统中间件中,会在请求中验证用户
- 所以如果用户登陆了,request中能够拿到user对象,即
request.user
- 用户和地址是一对多的关系,如果知道是哪个用户在访问地址页,就可以通过关联查询得到用户所有地址
- 查询出来的地址可以进行排序,比如按照创建时间排序
- 如果没有查询出来地址就返回空的地址模型对象
- 将查询出来的地址和用户信息,构造上下文,并传入到用户地址模板中即可
latest('时间')
函数:按照时间排序,最近的时间在最前,并取出第0个数据,也可以按照其他条件排序-
render()
函数:参数1传request,所以模板中可以拿到user对象,不需要在上下文中构造
class AddressView(LoginRequiredMixin, View): """用户地址""" def get(self, request): """提供用户地址页面:如果验证失败重定向到登陆页面""" # 从request中获取user对象,中间件从验证请求中的用户,所以request中带有user user = request.user try: # 查询用户地址:根据创建时间排序,最近的时间在最前,取第1个地址 # address = Address.objects.filter(user=user).order_by('-create_time')[0] # address = user.address_set.order_by('-create_time')[0] address = user.address_set.latest('create_time') except Address.DoesNotExist: # 如果地址信息不存在 address = None # 构造上下文 context = { # request中自带user,调用模板时,request会传给模板 # 'user':user, 'address':address } # return HttpResponse('这是用户中心地址页面') return render(request, 'user_center_site.html', context) def post(self, request): """修改地址信息""" pass
用户地址页视图编写之处理地址表单数据
class AddressView(LoginRequiredMixin, View)
类的post方法中- 接收用户递交到服务器的地址表单数据
- 验证地址表单数据是否完整
- 将地址表单数据保存到数据库地址表中
- ORM提供了create方法,帮我们快速的保存数据到数据库表
def post(self, request): """修改地址信息""" # 接收地址表单数据 user = request.user recv_name = request.POST.get("recv_name") addr = request.POST.get("addr") zip_code = request.POST.get("zip_code") recv_mobile = request.POST.get("recv_mobile") # 参数校验 if all([recv_name, addr, zip_code, recv_mobile]): # address = Address( # user=user, # receiver_name=recv_name, # detail_addr=addr, # zip_code=zip_code, # receiver_mobile=recv_mobile # ) # address.save() # 保存地址信息到数据库 Address.objects.create( user=user, receiver_name=recv_name, detail_addr=addr, zip_code=zip_code, receiver_mobile=recv_mobile ) return redirect(reverse("users:address"))
抽离父模板
- 网站中很多页面都很相似,重复的样式不需要重复编写代码
- 所以需要抽离出父模板,保证模板的复用性
- 子模板继承了父模板后,只需要专注于子模板的差异性内容即可
抽离父模板原则
- 以界面最丰富的页面作为父模板的参考,父模板是通过现有html页面抽离出来的
- 公共不变的内容定义在父模板中,变化的内容使用block标签预留出来
- 子模板继承父模板就可以把公共不变的部分继承下来
- 子模板重写父模板中预留的block标签就可以实现子模板自己样式的定制
需求
- 将主页模板作为抽取父模板的参照,抽取基类模板:
base.html
- 抽取出用户中心的父模板:
user_center_base.html
- 使用模板继承,展示用户地址页面
个人中心页面展示
个人中心界面展示之分析
发送get请求,获取个人中心界面
- 基本信息
- 查询数据库,得到用户基本信息
- 浏览记录
- 使用Redis数据库存储浏览记录
- 因为只要用户访问过某个商品,就要记录下来,该操作非常频繁
- 所以不要使用MySQL数据库,频繁操作磁盘性能消耗大
- 如果存储在session中,用户退出登陆,浏览记录就没有了
- 结论:需要选择内存型数据库,比如Redis数据库,访问速度快,而且是专机配置
个人中心页面展示之视图处理
class UserInfoView(LoginRequiredMixin, View): """用户中心""" def get(self, request): """查询用户信息和地址信息""" # 从request中获取user对象,中间件从验证请求中的用户,所以request中带有user user = request.user try: # 查询用户地址:根据创建时间排序,取第1个地址 address = user.address_set.latest('create_time') except Address.DoesNotExist: # 如果地址信息不存在 address = None # 构造上下文 context = { 'address': address } # 渲染模板 return render(request, 'user_center_info.html', context)
个人中心页面展示之模板处理
- 学习如何根据现有的模板,使用继承完成模板渲染
user_center_info.html
继承自user_center_base.html
{% extends 'user_center_base.html' %} {% load staticfiles %} {% block body %} <div class="main_con clearfix"> <div class="left_menu_con clearfix"> <h3>用户中心</h3> <ul> <li><a href="{% url 'users:info' %}" class="active">· 个人信息</a></li> <li><a href="user_center_order.html">· 全部订单</a></li> <li><a href="{% url 'users:address' %}">· 收货地址</a></li> </ul> </div> <div class="right_content clearfix"> <div class="info_con clearfix"> <h3 class="common_title2">基本信息</h3> <ul class="user_info_list"> <li><span>用户名:</span>{{ user.username }}</li> <li><span>联系方式:</span>{{ address.receiver_mobile }}</li> <li><span>联系地址:</span>{{ address.detail_addr }}</li> </ul> </div> <h3 class="common_title2">最近浏览</h3> <div class="has_view_list"> <ul class="goods_type_list clearfix"> {# 用于填充浏览记录 #} </ul> </div> </div> </div> {% endblock body %}
商品浏览记录设计和查询
Redis保存商品浏览记录相关文档
浏览记录产生
- 用户访问商品详情页时记录:后续在实现商品详情页时补充
- 浏览记录会频繁生成,所以需要放到内存型数据库中,访问速度快
- 综上所诉,浏览记录使用Redis数据库保存
- 提示:
- 只需要保存浏览的商品的sku_id即可,这样既节省了内存,将来也能直接使用sku_id查询到商品的详情
- 不建议保存到session数据中,因为用户退出登录后会清空session数据
- 将用户浏览的商品的sku_id保存到列表中,方便维护
history_userid = [sku_id2, sku_id8, sku_id5 , ...]
- 每个用户一条记录单独维护,键跟用户产生关系
浏览记录查询
这里查询浏览记录的前提是:已经设计好浏览记录的存储规则
- 用户访问个人中心页时查询Redis数据库
- 使用安装的
django-redis
模块来操作redis数据库get_redis_connection
返回一个已经跟Redis连接好的链接对象,该对象就可以直接操作Redisredis_connection = get_redis_connection('default')
-
提示:
settings.py
文件已经配置CACHES
选项的default
# 缓存 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://192.168.243.193:6379/5", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } }
浏览记录查询
-
提示:目前没有浏览记录,所以查询的是空的浏览记录
class UserInfoView(LoginRequiredMixin, View): """用户中心""" def get(self, request): """查询用户信息和地址信息""" # 从request中获取user对象,中间件从验证请求中的用户,所以request中带有user user = request.user try: # 查询用户地址:根据创建时间排序,取第1个地址 address = user.address_set.latest('create_time') except Address.DoesNotExist: # 如果地址信息不存在 address = None # 创建redis连接对象 redis_connection = get_redis_connection('default') # 从Redis中获取用户浏览商品的sku_id,在redis中需要维护商品浏览顺序[8,2,5] sku_ids = redis_connection.lrange('history_%s'%user.id, 0, 4) # 从数据库中查询商品sku信息,范围在sku_ids中 # skuList = GoodsSKU.objects.filter(id__in=sku_ids) # 问题:经过数据库查询后得到的skuList,就不再是redis中维护的顺序了,而是[2,5,8] # 需求:保证经过数据库查询后,依然是[8,2,5] skuList = [] for sku_id in sku_ids: sku = GoodsSKU.objects.get(id=sku_id) skuList.append(sku) # 构造上下文 context = { 'address':address, 'skuList':skuList, } # 调出并渲染模板 return render(request, 'user_center_info.html', context)
展示浏览记录模板处理
- 在
user_center_info.html
模板中补充用户商品浏览记录数据
{% extends 'user_center_base.html' %} {% load staticfiles %} {% block body %} <div class="main_con clearfix"> <div class="left_menu_con clearfix"> <h3>用户中心</h3> <ul> <li><a href="{% url 'users:info' %}" class="active">· 个人信息</a></li> <li><a href="user_center_order.html">· 全部订单</a></li> <li><a href="{% url 'users:address' %}">· 收货地址</a></li> </ul> </div> <div class="right_content clearfix"> <div class="info_con clearfix"> <h3 class="common_title2">基本信息</h3> <ul class="user_info_list"> <li><span>用户名:</span>{{ user.username }}</li> <li><span>联系方式:</span>{{ address.receiver_mobile }}</li> <li><span>联系地址:</span>{{ address.detail_addr }}</li> </ul> </div> <h3 class="common_title2">最近浏览</h3> <div class="has_view_list"> <ul class="goods_type_list clearfix"> {% for sku in skuList %} <li> {# fastDFS:sku.default_image.url表示存放图片的主机地址 #} <a href="detail.html"><img src="{{ sku.default_image.url }}"></a> <h4><a href="detail.html">{{ sku.name }}</a></h4> <div class="operate"> <span class="prize">¥{{ sku.price }}</span> <span class="unit">{{ sku.price }}/{{ sku.unit }}</span> <a href="#" class="add_goods" title="加入购物车"></a> </div> </li> {% endfor %} </ul> </div> </div> </div> {% endblock body %}
商品信息
FastDFS服务器
- 作用:以分布式的方式处理静态文件,保证负载均衡,并且已经解决了文件去重的问题
- FastDFS百度百科
FastDFS服务器介绍
-
FastDFS分布式介绍
FastDFS上传和下载工作流程介绍
文件检索的索引介绍
FastDFS服务器安装
FastDFS服务器安装
- FastDFS的安装和配置,不需要掌握,实际开发中一般不需要我们配置
- 参考课件:
FastDFS分布式存储服务器安装.docx
nginx服务器安装
- Django中处理静态文件的服务器,可以辅助FastDFS服务器完成负载均衡
- nginx服务器的安装和配置,需要掌握
- 参考课件:
FastDFS分布式存储服务器安装.docx
FastDFS_client安装
- FastDFS_client表示Django对接FastDFS服务器的客户端
- FastDFS_client中提供了Django程序和FastDFS服务器交互的接口/API
- 参考课件:
FastDFS分布式存储服务器安装.docx
总结
-
1.程序猿需要配置的文件:
client.conf
tracker.conf
storage.conf
nginx.conf
sudo vim /etc/fdfs/tracker.conf sudo vim /etc/fdfs/storage.conf sudo vim /etc/fdfs/client.conf sudo vim /usr/local/nginx/conf/nginx.conf
2.需要启动的:tracker,storage,nginx
sudo service fdfs_trackerd start 或者 sudo /etc/init.d/fdfs_trackerd start sudo service fdfs_storaged start 或者 sudo /etc/init.d/fdfs_storaged start sudo /usr/local/nginx/sbin/nginx
3.参考课件:FastDFS分布式存储服务器安装.docx
Django对接FastDFS流程
说明
- Django编码的部分
- 在Django和fdfs客户端之间:上图中的黄色区域
- 开发中,需要在Django中调用fdfs客户端提供的API操作FastFDS服务器
- 浏览器部分
- 当后台运维,以管理员身份进入后台站点发布内容时,是做的文件上传并保存的操作
- 用户通过浏览器,访问我们提供的页面时,加载图片信息时,是做的文件下载的操作
- nginx服务器
- 提供文件的下载,不参与文件的上传
- Django擅长处理动态的业务逻辑,静态的业务逻辑交给FastFDS和nginx处理
安装 fdfs_client
fdfs_client
是Django对接FastDFS的工具包- fdfs_client托管网站github
-
提示:
fdfs_client-py-master.zip
已经下载成功,建议使用课件提供的压缩包
进入到 fdfs_client-py-master.zip 目录
pip install fdfs_client-py-master.zip
思考
- 如何让Django把后台站点发布的内容引导到FastFDS服务器进行存储
- 提示:自定义文件存储系统,继承自
Storage
类
自定义文件存储系统Storage
目的
- 能够让Django把后台站点发布的内容引导到FastFDS服务器进行存储
相关文档
自定义文件存储系统实现
-
1.自定义文件存储系统的目录结构
2.Django项目中,使用client.conf
文件注意点
3.settings.py
中指定文件存储系统类,指定为自定义的文件存储系统类
from django.core.files.storage import Storage class FastDFSStorage(Storage): """自定义Django存储系统的类""" pass
# 配置Django自定义的存储系统 DEFAULT_FILE_STORAGE = 'utils.fastdfs.storage.FastDFSStorage'
4.自定义文件存储系统类代码实现
from django.core.files.storage import Storage from fdfs_client.client import Fdfs_client from django.conf import settings class FastDFSStorage(Storage): """自定义Django存储系统的类""" def __init__(self, client_conf=None,server_ip=None): """初始化,设置参数""" if client_conf is None: client_conf = settings.CLIENT_CONF self.client_conf = client_conf if server_ip is None: server_ip = settings.SERVER_IP self.server_ip = server_ip def _open(self, name, mode='rb'): """读取文件时使用""" pass def _save(self, name, content): """存储文件时使用:参数2是上传来的文件名,参数3是上传来的File对象""" # 创建fdfs客户端client client = Fdfs_client(self.client_conf) # client获取文件内容 file_data = content.read() # Django借助client向FastDFS服务器上传文件 try: result = client.upload_by_buffer(file_data) except Exception as e: print(e) # 自己调试临时打印 raise # 根据返回数据,判断是否上传成功 if result.get('Status') == 'Upload successed.': # 读取file_id file_id = result.get('Remote file_id') # 返回给Django存储起来即可 return file_id else: # 开发工具类时,出现异常不要擅自处理,交给使用者处理 raise Exception('上传文件到FastDFS失败') def exists(self, name): """Django用来判断文件是否存在的""" # 由于Djnago不存储图片,所以永远返回Fasle,直接保存到FastFDS return False def url(self, name): """用于返回图片在服务器上完整的地址:server_ip+path""" return self.server_ip + name
5.自定义文件存储系统代码优化部分
- 定义初始化方法,接收外界传入的参数,交给私有方法使用(模仿系统的存储实现)
- 开发工具类或者框架时,遇到异常直接抛出即可,出现的问题交给使用者、调用者解决
- 定义实现exists()方法,返回False
- 由于Djnago不存储图片,所以永远返回Fasle,直接引导到FastFDS
- 定义实现url()方法,返回文件完整路径,方便模板中调用并得到文件完整路径
- 将server_ip和client.conf的默认数据,定义到settings.py文件中,保证自定义存储系统的封装度
# FastFDS使用的配置信息 CLIENT_CONF = os.path.join(BASE_DIR, 'utils/fastdfs/client.conf') SERVER_IP = 'http://192.168.243.193:8888/'
自定义文件存储系统测试
-
需求:使用后台站点,向
goods
应用中的GoodsCategory
模型中发布内容 -
步骤:
- 1.本地化
- 2.注册模型类到后台站点
- 3.创建超级管理员并登陆进入到后台站点
- 4.发布
GoodsCategory
模型中的内容
-
1.本地化
LANGUAGE_CODE = 'zh-Hans' TIME_ZONE = 'Asia/Shanghai'
2.注册模型类到后台站点
from django.contrib import admin from goods.models import GoodsCategory,Goods,GoodsSKU # Register your models here. admin.site.register(GoodsCategory) admin.site.register(Goods) admin.site.register(GoodsSKU)
3.创建超级管理员并登陆进入到后台站点
python manage.py createsuperuser
4.发布GoodsCategory
模型中的内容
可能出现的错误
-
没有mutagen和requests模块
链接FastFDS服务器失败
重新启动tracker和storage和nginx即可
sudo service fdfs_trackerd start 或者 sudo /etc/init.d/fdfs_trackerd start sudo service fdfs_storaged start 或者 sudo /etc/init.d/fdfs_storaged start sudo /usr/local/nginx/sbin/nginx
富文本编辑器
-
富文本编辑器在后台站点的展示效果
富文本字段:HTMLField
# 富文本编辑器字段 from tinymce.models import HTMLField
pip install django-tinymce==2.6.0
安装富文本编辑器应用
INSTALLED_APPS = ( ... 'tinymce', )
settings.py
中添加编辑器配置
TINYMCE_DEFAULT_CONFIG = { 'theme': 'advanced', # 丰富样式 'width': 600, 'height': 400, }
项目/urls.py中配置编辑器url
import tinymce.urls urlpatterns = [ ... url(r'^tinymce/', include('tinymce.urls')), ]
主页商品信息展示
主页商品数据分析
主页商品数据查询
class IndexView(View): """首页""" def get(self, request): """查询首页页面需要的数据,构造上下文,渲染首页页面""" # 查询用户个人信息(request.user) # 查询商品分类信息 categorys = GoodsCategory.objects.all() # 查询图片轮播信息:按照index进行排序 banners = IndexGoodsBanner.objects.all().order_by('index') # 查询活动信息 promotion_banners = IndexPromotionBanner.objects.all().order_by('index') # 查询分类商品信息 for category in categorys: title_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=0).order_by('index') category.title_banners = title_banners image_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=1).order_by('index') category.image_banners = image_banners # 查询购物车信息 cart_num = 0 # 构造上下文:先处理购物车以外的上下文,并缓存 context = { 'categorys':categorys, 'banners':banners, 'promotion_banners':promotion_banners, 'cart_num':cart_num } return render(request, 'index.html',context)
主页商品数据展示
{% extends 'base.html' %} {% block title %}天天生鲜-首页{% endblock %} {# 删除<head>,搜索框,底部. 保留body部分进行重写 #} {% block body %} <div class="navbar_con"> <div class="navbar"> <h1 class="fl">全部商品分类</h1> <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="center_con clearfix"> <ul class="subnav fl"> {% for category in categorys %} <li><a href="#model0{{ forloop.counter }}" class="{{ category.logo }}">{{ category.name }}</a></li> {% endfor %} </ul> <div class="slide fl"> <ul class="slide_pics"> {% for banner in banners %} {# banner.image.url 获取轮播模型类图片属性,url方法是配置FastDFS服务器提供图片完整地址的方法 #} <li><a href="#"><img src="{{ banner.image.url }}" alt="幻灯片"></a></li> {% endfor %} </ul> <div class="prev"></div> <div class="next"></div> <ul class="points"></ul> </div> <div class="adv fl"> {% for promotion_banner in promotion_banners %} <a href="{{ promotion_banner.url }}"><img src="{{ promotion_banner.image.url }}"></a> {% endfor %} </div> </div> {% for category in categorys %} <div class="list_model"> <div class="list_title clearfix"> <h3 class="fl" id="model0{{ forloop.counter }}">{{ category.name }}</h3> <div class="subtitle fl"> <span>|</span> {% for title_banner in category.title_banners %} <a href="#">{{ title_banner.sku.name }}</a> {% endfor %} </div> <a href="#" class="goods_more fr" id="fruit_more">查看更多 ></a> </div> <div class="goods_con clearfix"> <div class="goods_banner fl"><img src="{{ category.image.url }}"></div> <ul class="goods_list fl"> {% for image_banner in category.image_banners %} <li> <h4><a href="#">{{ image_banner.sku.name }}</a></h4> <a href="#"><img src="{{ image_banner.sku.default_image.url }}"></a> <div class="prize">¥ {{ image_banner.sku.price }}</div> </li> {% endfor %} </ul> </div> </div> {% endfor %} {% endblock %} {% block bottom_files %} <script type="text/javascript" src="js/slideshow.js"></script> <script type="text/javascript"> BCSlideshow('focuspic'); var oFruit = document.getElementById('fruit_more'); var oShownum = document.getElementById('show_count'); var hasorder = localStorage.getItem('order_finish'); if(hasorder) { oShownum.innerHTML = '2'; } oFruit.onclick = function(){ window.location.href = 'list.html'; } </script> {% endblock %}
页面静态化
- 对于主页,信息丰富,需要多次查询数据库才能得到全部数据。
- 如果用户每次访问主页,都做多次数据库查询,性能差。
- 优化:将主页存储成静态页面,用户访问时,响应静态页面
- 实现:把render()返回的html数据存储起来
- 以上是wed服务器优化方案之一,把页面静态化,减少服务器处理动态数据的压力
实现思路
- 后台站点在发布主页内容时,Django使用异步任务生成主页静态页面
- 需要使用celery服务器执行异步任务
- 需要使用nginx服务器提供静态页面访问服务
- 需要注册模型类到站点,并创建模型类管理类,在模型类管理类中调用celery异步任务
celery生成静态html页面
- 生成的静态html文件,不需要返回render(),只需要一个html即可
- 生成的静态html文件,不需要处理用户验证的逻辑,不需要请求对象request
- 生成的静态html文件,存放在celery服务器中,由nginx提供数据访问
- 定义静态html模板的父模板:static_base.html
- 去掉用户验证的逻辑
- 定义静态主页的html模板:static_index.html
定义异步任务
import os os.environ["DJANGO_SETTINGS_MODULE"] = "dailyfresh.settings" # 放到celery服务器上时将注释打开 #import django #django.setup() from celery import Celery from django.core.mail import send_mail from django.conf import settings from goods.models import GoodsCategory,Goods,IndexGoodsBanner,IndexPromotionBanner,IndexCategoryGoodsBanner from django.template import loader # 创建celery应用对象 app = Celery('celery_tasks.tasks', broker='redis://192.168.243.193/4') @app.task def send_active_email(to_email, user_name, token): """发送激活邮件""" subject = "天天生鲜用户激活" # 标题 body = "" # 文本邮件体 sender = settings.EMAIL_FROM # 发件人 receiver = [to_email] # 接收人 html_body = '<h1>尊敬的用户 %s, 感谢您注册天天生鲜!</h1>' \ '<br/><p>请点击此链接激活您的帐号<a href="http://127.0.0.1:8000/users/active/%s">' \ 'http://127.0.0.1:8000/users/active/%s</a></p>' %(user_name, token, token) send_mail(subject, body, sender, receiver, html_message=html_body) @app.task def generate_static_index_html(): """生成静态的html页面""" # 查询商品分类信息 categorys = GoodsCategory.objects.all() # 查询图片轮播信息:按照index进行排序 banners = IndexGoodsBanner.objects.all().order_by('index') # 查询活动信息 promotion_banners = IndexPromotionBanner.objects.all().order_by('index') # 查询分类商品信息 for category in categorys: title_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=0).order_by('index') category.title_banners = title_banners image_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=1).order_by('index') category.image_banners = image_banners # 查询购物车信息 cart_num = 0 # 构造上下文 context = { 'categorys': categorys, 'banners': banners, 'promotion_banners': promotion_banners, 'cart_num': cart_num } # 加载模板 template = loader.get_template('static_index.html') html_data = template.render(context) # 保存成html文件:放到静态文件中 file_path = os.path.join(settings.STATICFILES_DIRS[0], 'index.html') with open(file_path, 'w') as file: file.write(html_data)
测试celery生成静态文件
- 将项目拷贝到celery服务器中
- 开启celery:celery -A celery_tasks.tasks worker -l info
- 终端 python manage.py shell 测试异步任务调度
配置nginx访问静态页面
- 为了让celery服务器中的静态文件能够被高效访问,需要给celery配置nginx服务器
- nginx服务器提供静态数据效率高
配置参数
- 进入到:/usr/local/nginx/conf
- 在nginx.conf文件中,配置新的server选项,将主页的访问引导到nginx服务器
- 在nginx.conf文件中,具体配置html页面中静态文件image、css、js访问路径
-
重启nginx:sudo /usr/local/nginx/sbin/nginx -s reload
测试nginx服务
- 浏览器中输入nginx服务器地址,默认端口号80,查看能否加载到主页静态页面
模型管理类调用celery异步方法
- 这里是生成静态页面的发起点
- 管理员通过站点发布内容时,在这里会调用celery异步方法,生成静态html页面
- 封装了类:BaseAdmin,把保存和删除封装进去
class BaseAdmin(admin.ModelAdmin): """商品活动信息的管理类,运营人员在后台发布内容时,异步生成静态页面""" def save_model(self, request, obj, form, change): """后台保存对象数据时使用""" # obj表示要保存的对象,调用save(),将对象保存到数据库中 obj.save() # 调用celery异步生成静态文件方法 generate_static_index_html.delay() def delete_model(self, request, obj): """后台保存对象数据时使用""" obj.delete() generate_static_index_html.delay() class IndexPromotionBannerAdmin(BaseAdmin): """商品活动站点管理,如果有自己的新的逻辑也是写在这里""" # list_display = [] pass class GoodsCategoryAdmin(BaseAdmin): pass class GoodsAdmin(BaseAdmin): pass class GoodsSKUAdmin(BaseAdmin): pass class IndexCategoryGoodsBannerAdmin(BaseAdmin): pass # Register your models here. admin.site.register(GoodsCategory,GoodsCategoryAdmin) admin.site.register(Goods,GoodsAdmin) admin.site.register(GoodsSKU,GoodsSKUAdmin) admin.site.register(IndexPromotionBanner,IndexPromotionBannerAdmin) admin.site.register(IndexCategoryGoodsBanner,IndexCategoryGoodsBannerAdmin)
用户静态动态页面区分
-
未登录用户访问主页
- 未登录用户访问主页:
- 此时是nginx提供的静态页面
- nginx服务器ip:80
- 192.168.243.193:80
- 登录后,重定向到主页:
- 此时是Django提供的动态页面
- 请求地址:http://127.0.0.1:8000/index
- 如何区分:使用地址区分,动态页面增加
/index
- 未登录用户访问主页:
-
登陆用户访问主页
- 动态主页的请求地址:http://127.0.0.1:8000/index
- 动态主页的正则匹配:
url(r'^index$', views.IndexView.as_view(), name='index')
访问示例
-
访问静态页面
访问动态页面
缓存
缓存介绍
- 静态html页面由nginx处理,但是,登陆用户访问的是动态逻辑,也会涉及到大量的数据库查询
- 对于动态查询的数据的结果,我们也是要存储,叫做缓存
- 提示:购物车数据不能被缓存,因为购物车数据是可能实时变化的
- 缓存中文文档
from django.core.cache import cache
- 设置缓存:
cache.set('key', 内容, 有效期)
- 读取缓存:
cache.get('key')
- 存储进去的是什么,取出来的也是什么
-
逻辑:
- 先检查是否有缓存数据,如果有缓存数据就读取缓存数据
-
如果没有缓存数据,就查询数据库
缓存主页数据
class IndexView(View): """首页""" def get(self, request): """查询首页页面需要的数据,构造上下文,渲染首页页面""" # 查询用户个人信息(request.user) # 先从缓存中读取数据,如果有就获取缓存数据,反之,就执行查询 context = cache.get('index_page_data') if context is None: print('没有缓存数据,查询了数据库') # 查询商品分类信息 categorys = GoodsCategory.objects.all() # 查询图片轮播信息:按照index进行排序 banners = IndexGoodsBanner.objects.all().order_by('index') # 查询活动信息 promotion_banners = IndexPromotionBanner.objects.all().order_by('index') # 查询分类商品信息 for category in categorys: title_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=0).order_by('index') category.title_banners = title_banners image_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=1).order_by('index') category.image_banners = image_banners # 构造上下文:先处理购物车以外的上下文,并缓存 context = { 'categorys':categorys, 'banners':banners, 'promotion_banners':promotion_banners, } # 设置缓存数据:名字,内容,有效期 cache.set('index_page_data',context,3600) # 查询购物车信息:不能被缓存,因为会经常变化 cart_num = 0 # 补充购物车数据 context.update(cart_num=cart_num) return render(request, 'index.html',context)
缓存有效期和删除缓存
- 缓存需要设置有效期,不然数据永远无法得到更新,具体的有效期时间根据公司需求而定
-
缓存需要在修改内容时删除,不然内容修改了,但还是缓存的旧数据
class BaseAdmin(admin.ModelAdmin): """商品活动信息的管理类,运营人员在后台发布内容时,异步生成静态页面""" def save_model(self, request, obj, form, change): """后台保存对象数据时使用""" # obj表示要保存的对象,调用save(),将对象保存到数据库中 obj.save() # 调用celery异步生成静态文件方法,操作完表单后删除静态文件 generate_static_index_html.delay() # 修改了数据库数据就需要删除缓存 cache.delete('index_page_data') def delete_model(self, request, obj): """后台保存对象数据时使用""" obj.delete() generate_static_index_html.delay() cache.delete('index_page_data')
主页购物车
- 保存在redis中,每个人维护一条购物车数据, 选择哈希类型
哈希类型存储:cart_userid sku_1 10 sku_2 20 字典结构:cart_userid:{sku_1:10,sku_2:20}
class IndexView(View): """首页""" def get(self, request): """查询首页页面需要的数据,构造上下文,渲染首页页面""" # 查询用户个人信息(request.user) # 先从缓存中读取数据,如果有就获取缓存数据,反之,就执行查询 context = cache.get('index_page_data') if context is None: print('没有缓存数据,查询了数据库') # 查询商品分类信息 categorys = GoodsCategory.objects.all() # 查询图片轮播信息:按照index进行排序 banners = IndexGoodsBanner.objects.all().order_by('index') # 查询活动信息 promotion_banners = IndexPromotionBanner.objects.all().order_by('index') # 查询分类商品信息 for category in categorys: title_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=0).order_by('index') category.title_banners = title_banners image_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=1).order_by('index') category.image_banners = image_banners # 构造上下文:先处理购物车以外的上下文,并缓存 context = { 'categorys':categorys, 'banners':banners, 'promotion_banners':promotion_banners, } # 设置缓存数据:名字,内容,有效期 cache.set('index_page_data',context,3600) # 查询购物车信息:不能被缓存,因为会经常变化 cart_num = 0 # 如果用户登录,就获取购物车数据 if request.user.is_authenticated(): # 创建redis_conn对象 redis_conn = get_redis_connection('default') # 获取用户id user_id = request.user.id # 从redis中获取购物车数据,返回字典 cart_dict = redis_conn.hgetall('cart_%s'%user_id) # 遍历购物车字典的值,累加购物车的值 for value in cart_dict.values(): cart_num += int(value) # 补充购物车数据 context.update(cart_num=cart_num) return render(request, 'index.html',context)
商品详情页
商品详情视图编写
- 参数: 需求客户端传入商品的sku_id
- 查询
- 查询商品SKU信息
- 查询所有商品分类信息
- 查询商品订单评论信息
- 查询最新商品推荐
- 查询其他规格商品
- 如果已登录,查询购物车信息
- 提示:
- 检查是否有缓存,如果缓存不存在就查询数据;反之,直接读取缓存数据
- 在商品详情页需要实现存储浏览记录的逻辑
- 浏览记录存储在redis中,之前已经在用户中心界面实现了浏览记录的读取
- URL的设计:
/detail/1
url(r'^detail/(?P<sku_id>\d+)$', views.DetailView.as_view(), name='detail'),
class DetailView(View): """商品详细信息页面""" def get(self, request, sku_id): # 尝试获取缓存数据 context = cache.get("detail_%s" % sku_id) # 如果缓存不存在 if context is None: try: # 获取商品信息 sku = GoodsSKU.objects.get(id=sku_id) except GoodsSKU.DoesNotExist: # from django.http import Http404 # raise Http404("商品不存在!") return redirect(reverse("goods:index")) # 获取类别 categorys = GoodsCategory.objects.all() # 从订单中获取评论信息 sku_orders = sku.ordergoods_set.all().order_by('-create_time')[:30] if sku_orders: for sku_order in sku_orders: sku_order.ctime = sku_order.create_time.strftime('%Y-%m-%d %H:%M:%S') sku_order.username = sku_order.order.user.username else: sku_orders = [] # 获取最新推荐 new_skus = GoodsSKU.objects.filter(category=sku.category).order_by("-create_time")[:2] # 获取其他规格的商品 other_skus = sku.goods.goodssku_set.exclude(id=sku_id) context = { "categorys": categorys, "sku": sku, "orders": sku_orders, "new_skus": new_skus, "other_skus": other_skus } # 设置缓存 cache.set("detail_%s"%sku_id, context, 3600) # 购物车数量 cart_num = 0 # 如果是登录的用户 if request.user.is_authenticated(): # 获取用户id user_id = request.user.id # 从redis中获取购物车信息 redis_conn = get_redis_connection("default") # 如果redis中不存在,会返回None cart_dict = redis_conn.hgetall("cart_%s"%user_id) for val in cart_dict.values(): cart_num += int(val) # 浏览记录: lpush history_userid sku_1, sku_2 # 移除已经存在的本商品浏览记录 redis_conn.lrem("history_%s"%user_id, 0, sku_id) # 添加新的浏览记录 redis_conn.lpush("history_%s"%user_id, sku_id) # 只保存最多5条记录 redis_conn.ltrim("history_%s"%user_id, 0, 4) context.update({"cart_num": cart_num}) return render(request, 'detail.html', context)
商品详情模板编写
- 在处理详情页模板时,遇到详情页的跳转需要处理
- 主页中跳转到详情页的链接也需要处理
{% extends 'base.html' %} {% load staticfiles %} {% block title %}天天生鲜-商品详情{% endblock %} {% block body %} <div class="navbar_con"> <div class="navbar clearfix"> <div class="subnav_con fl"> <h1>全部商品分类</h1> <span></span> <ul class="subnav"> {% for category in categorys %} <li><a href="#" class="{{ category.logo }}">{{ category.name }}</a></li> {% endfor %} </ul> </div> </div> </div> <div class="breadcrumb"> <a href="/">全部分类</a> <span>></span> <a href="#">{{ sku.category.name }}</a> <span>></span> <a href="#">商品详情</a> </div> <div class="goods_detail_con clearfix"> <div class="goods_detail_pic fl"><img src="{{ sku.default_image.url }}"></div> <div class="goods_detail_list fr"> <h3>{{ sku.name}}</h3> <p>{{ sku.title }}</p> <div class="prize_bar"> <span class="show_pirze">¥<em>{{ sku.price }}</em></span> <span class="show_unit">单 位:{{ sku.unit }}</span> </div> {% if other_skus %} <div> <p>其他规格:</p> <ul> {% for sku in other_skus %} <li><a href="{% url 'goods:detail' sku.id %}">{{ sku.price }}/{{ sku.unit }}</a></li> {% endfor %} </ul> </div> {% endif %} <div class="goods_num clearfix"> <div class="num_name fl">数 量:</div> <div class="num_add fl"> <input type="text" class="num_show fl" id="num_show" value="1"> <a href="javascript:;" class="add fr" id="add">+</a> <a href="javascript:;" class="minus fr" id="minus">-</a> </div> </div> <div class="total">总价:<em>{{ sku.price }}</em>元</div> <div class="operate_btn"> <a href="javascript:;" class="buy_btn" id="buy_btn">立即购买</a> <a href="javascript:;" class="add_cart" sku_id="{{ sku.id }}" id="add_cart">加入购物车</a> </div> </div> </div> <div class="main_wrap clearfix"> <div class="l_wrap fl clearfix"> <div class="new_goods"> <h3>新品推荐</h3> <ul> {% for sku in new_skus %} <li> <a href="{% url 'goods:detail' sku.id %}"><img src="{{ sku.default_image.url }}"></a> <h4><a href="{% url 'goods:detail' sku.id %}">{{ sku.name }}</a></h4> <div class="prize">¥{{ sku.price }}</div> </li> {% endfor %} </ul> </div> </div> <div class="r_wrap fr clearfix"> <ul class="detail_tab clearfix"> <li id="tag_detail" class="active">商品介绍</li> <li id="tag_comment">评论</li> </ul> <div class="tab_content" id="tab_detail"> <dl> <dt>商品详情:</dt> <dd>{{ sku.goods.desc|safe }}</dd> </dl> </div> <div class="tab_content" id="tab_comment" style="display: none;"> {% for order in orders %} <dl> <dd>客户:{{ order.username }} 时间:{{ order.ctime }}</dd> <dt>{{ order.comment }}</dt> </dl> <hr/> {% endfor %} </div> </div> </div> {% endblock %} {% block footer %} <div class="add_jump"></div> {% endblock %} {% block bottom_files %} <script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script> <script type="text/javascript"> $("#tag_detail").click(function(){ $("#tag_comment").removeClass("active"); $(this).addClass("active"); $("#tab_comment").hide(); $("#tab_detail").show(); }); $("#tag_comment").click(function(){ $("#tag_detail").removeClass("active"); $(this).addClass("active"); $("#tab_detail").hide(); $("#tab_comment").show(); }); $("#buy_btn").click(function(){ var count = $("#num_show").val(); window.location.href = '/order/commit?g={{goods.id}}@' + count; }); var $add_x = $('#add_cart').offset().top; var $add_y = $('#add_cart').offset().left; var $to_x = $('#show_count').offset().top; var $to_y = $('#show_count').offset().left; // 点击加入购物车 $('#add_cart').click(function(){ // 将商品的id 和 数量发送给后端视图,保存到购物车数据中 var req_data = { sku_id: $('#add_cart').attr("sku_id"), count: $("#num_show").val(), csrfmiddlewaretoken: "{{ csrf_token }}" }; {# // 使用ajax向后端发送数据#} {# $.post("/cart/add", req_data, function (response_data) {#} {# // 根据response_data中的code决定处理效果#} {# if (1 == response_data.code) {#} {# // 用户未登录#} {# window.location.href = "/users/login"; // 让页面跳转到登录页面#} {# } else if (0 == response_data.code) {#} {# // 添加到购物车成功动画#} {# $(".add_jump").css({'left':$add_y+80,'top':$add_x+10,'display':'block'});#} {##} {# $(".add_jump").stop().animate({#} {# 'left': $to_y+7,#} {# 'top': $to_x+7},#} {# "fast", function() {#} {# $(".add_jump").fadeOut('fast',function(){#} {# $('#show_count').html(response_data.cart_num);#} {# });#} {# });#} {##} {# } else {#} {# // 其他错误信息,alert展示#} {# alert(response_data.message);#} {# }#} {# }, "json");#} }); $("#add").click(function(){ var num_show = $("#num_show").val(); num_show = parseInt(num_show); num_show += 1; $("#num_show").val(num_show); var price = $(".show_pirze>em").html(); price = parseFloat(price); var total = price * num_show; $(".total>em").html(total.toFixed(2)); }); $("#minus").click(function(){ var num_show = $("#num_show").val(); num_show = parseInt(num_show); num_show -= 1; if (num_show < 1){ num_show = 1; } $("#num_show").val(num_show); var price = $(".show_pirze>em").html(); price = parseFloat(price); var total = price * num_show; $(".total>em").html(total.toFixed(2)); }); </script> {% endblock %}
商品列表页
商品列表页分析
- 需要知道是展示哪一类商品的列表
- 需要知道展示的是第几页
- 需要知道排序的规则,默认,价格,人气
- 请求方法是get,只为了获取数据
- 外界需要传递相关参数到商品列表视图中,就需要在视图中进行参数校验
- 需要查询的数据
- 商品分类信息
- 新品推荐信息,在GoodsSKU表中,查询特定类别信息,按照时间倒序
- 商品列表信息
- 商品分页信息
- 购物车信息
参数传递方式分析
- 展示某商品第几页的数据,然后再排序
- 默认排序:
/list/category_id/page_num/?sort='default'
- 价格排序:
/list/category_id/page_num/?sort='price'
- 人气排序:
/list/category_id/page_num/?sort='hot'
提示
- 1.获取请求参数信息,商品id,第几页数据,排序规则
- 2.校验参数
- 2.1.判断类别是否存在,查询数据库,如果不存在,异常为:
GoodsCategory.DoesNotExist
- 2.2.分页的异常,在创建分页对象时校验
- 因为只有创建了分页数据,才能知道页数page是否正确
- 如果页数错误,异常为:
EmptyPage
- 2.1.判断类别是否存在,查询数据库,如果不存在,异常为:
商品列表视图
class ListView(View): """商品列表""" def get(self, request, category_id, page_num): # 获取sort参数:如果用户不传,就是默认的排序规则 sort = request.GET.get('sort', 'default') # 校验参数 # 判断category_id是否正确,通过异常来判断 try: category = GoodsCategory.objects.get(id=category_id) except GoodsCategory.DoesNotExist: return redirect(reverse('goods:index')) # 查询商品所有类别 categorys = GoodsCategory.objects.all() # 查询该类别商品新品推荐 new_skus = GoodsSKU.objects.filter(category=category).order_by('-create_time')[:2] # 查询该类别所有商品SKU信息:按照排序规则来查询 if sort == 'price': # 按照价格由低到高 skus = GoodsSKU.objects.filter(category=category).order_by('price') elif sort == 'hot': # 按照销量由高到低 skus = GoodsSKU.objects.filter(category=category).order_by('-sales') else: skus = GoodsSKU.objects.filter(category=category) # 无论用户是否传入或者传入其他的排序规则,我在这里都重置成'default' sort = 'default' # 分页:需要知道从第几页展示 page_num = int(page_num) # 创建分页器:每页两条记录 paginator = Paginator(skus,2) # 校验page_num:只有知道分页对对象,才能知道page_num是否正确 try: page_skus = paginator.page(page_num) except EmptyPage: # 如果page_num不正确,默认给用户第一页数据 page_skus = paginator.page(1) # 获取页数列表 page_list = paginator.page_range # 购物车 cart_num = 0 # 如果是登录的用户 if request.user.is_authenticated(): # 获取用户id user_id = request.user.id # 从redis中获取购物车信息 redis_conn = get_redis_connection("default") # 如果redis中不存在,会返回None cart_dict = redis_conn.hgetall("cart_%s" % user_id) for val in cart_dict.values(): cart_num += int(val) # 构造上下文 context = { 'sort':sort, 'category':category, 'cart_num':cart_num, 'categorys':categorys, 'new_skus':new_skus, 'page_skus':page_skus, 'page_list':page_list } # 渲染模板 return render(request, 'list.html', context)
商品列表模板
{% extends 'base.html' %} {% load staticfiles %} {% block title %} 天天生鲜-商品列表 {% endblock %} {% block body %} <div class="navbar_con"> <div class="navbar clearfix"> <div class="subnav_con fl"> <h1>全部商品分类</h1> <span></span> <ul class="subnav"> {% for category in categorys %} {# 默认跳转到某个分类商品列表的第一页 #} <li><a href="{% url 'goods:list' category.id 1 %}" class="{{ category.logo }}">{{ category.name }}</a></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="{% url 'goods:index' %}">全部分类</a> <span>></span> <a href="{% url 'goods:list' category.id 1 %}">{{ category.name }}</a> </div> <div class="main_wrap clearfix"> <div class="l_wrap fl clearfix"> <div class="new_goods"> <h3>新品推荐</h3> <ul> {% for new_sku in new_skus %} <li> <a href="{% url 'goods:detail' new_sku.id %}"><img src="{{ new_sku.default_image.url }}"></a> <h4><a href="{% url 'goods:detail' new_sku.id %}">{{ new_sku.name }}</a></h4> <div class="prize">¥{{ new_sku.price }}</div> </li> {% endfor %} </ul> </div> </div> <div class="r_wrap fr clearfix"> <div class="sort_bar"> <a href="{% url 'goods:list' category.id 1 %}?srot=default" {% if sort == 'default' %}class="active"{% endif %}>默认</a> <a href="{% url 'goods:list' category.id 1 %}?srot=price" {% if sort == 'price' %}class="active"{% endif %}>价格</a> <a href="{% url 'goods:list' category.id 1 %}?srot=hot" {% if sort == 'hot' %}class="active"{% endif %}>人气</a> </div> <ul class="goods_type_list clearfix"> {% for sku in page_skus %} <li> <a href="{% url 'goods:detail' sku.id %}"><img src="{{ sku.default_image.url }}"></a> <h4><a href="{% url 'goods:detail' sku.id %}">{{ sku.name }}</a></h4> <div class="operate"> <span class="prize">¥{{ sku.price }}</span> <span class="unit">{{ sku.price }}/{{ sku.unit }}</span> <a href="#" class="add_goods" title="加入购物车"></a> </div> </li> {% endfor %} </ul> <div class="pagenation"> {% if page_skus.has_previous %} <a href="{% url 'goods:list' category.id page_skus.previous_page_number %}?sort={{ sort }}">上一页</a> {% endif %} {% for index in page_list %} <a href="{% url 'goods:list' category.id index %}?sort={{ sort }}" {% if index == page_skus.number %}class="active"{% endif %}>{{ index }}</a> {% endfor %} {% if page_skus.has_next %} <a href="{% url 'goods:list' category.id page_skus.next_page_number %}?sort={{ sort }}">下一页</a> {% endif %} </div> </div> </div> {% endblock %}
商品搜索
全文检索
select * from table where name like '%草莓%' or title like '%草莓%';
- 全文检索不同于特定字段的模糊查询,使用全文检索的效率更高,并且能够对于中文进行分词处理。
- 以上sql语句,可以查询到
草莓
,盒装草莓
,500g草莓
- 但是把
like '%草莓%'
改成like '%北京草莓%'
就无法再查询出草莓
,盒装草莓
,500g草莓
- 以上sql语句,可以查询到
搜索引擎和框架
- whoosh:
- 纯Python编写的全文搜索引擎,虽然性能比不上sphinx、xapian、Elasticsearc等,但是无二进制包,程序不会莫名其妙的崩溃,对于小型的站点,whoosh已经足够使用。
- 点击whoosh查看官方网站
- haystack:
- 全文检索的框架,支持whoosh、solr、Xapian、Elasticsearc四种全文检索引擎。
- 作用:搭建了用户和搜索引擎之间的沟通桥梁
- 点击haystack查看官方网站
- jieba:
- 一款免费的中文分词包,如果觉得不好用可以使用一些收费产品。
安装全文检索包
# 全文检索框架 pip install django-haystack # 全文检索引擎 pip install whoosh # 中文分词框架 pip install jieba
haystack的使用
配置全文检索
-
1.安装haystack应用
INSTALLED_APPS = ( ... 'haystack', )
2.在settings.py文件中配置搜索引擎
# 配置搜索引擎后端 HAYSTACK_CONNECTIONS = { 'default': { # 使用whoosh引擎:提示,如果不需要使用jieba框架实现分词,就使用whoosh_backend 'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine', # 索引文件路径 'PATH': os.path.join(BASE_DIR, 'whoosh_index'), } } # 当添加、修改、删除数据时,自动生成索引 HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
定义商品索引类
-
在要建立索引的表对应的应用下,创建
search_indexes.py
文件
定义商品索引类GoodsSKUIndex()
,继承自indexes.SearchIndex
和indexes.Indexable
from haystack import indexes from goods.models import GoodsSKU class GoodsSKUIndex(indexes.SearchIndex, indexes.Indexable): """建立索引时被使用的类""" 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()
指定要建立索引的字段
-
在
templates
下面新建目录search/indexes/应用名
- 比如
goods
应用中的GoodsSKU
模型类中的字段要建立索引:search/indexes/goods
-
在新建目录下,创建
goodssku_text.txt
,并编辑要建立索引的字段,如下图
- 比如
生成索引文件
python manage.py rebuild_index
搜索表单处理
- 搜索地址:
/search/
- 搜索方法:
get
-
接收关键字:
q
配置搜索地址正则
import haystack.urls url(r'^search/', include(haystack.urls)),
测试搜索效果,接收结果
-
全文检索结果:
- 搜索出结果后,haystack会把搜索出的结果传递给
templates/search
目录下的search.html
- 对于
search.html
,我们需要自己建立该html文件,并定义自己的搜索结果页面
- 搜索出结果后,haystack会把搜索出的结果传递给
-
传递的上下文包括:
- query:搜索关键字
- page:当前页的page对象
- paginator:分页paginator对象
- 提示:
settings.py
文件中设置HAYSTACK_SEARCH_RESULTS_PER_PAGE
- 通过
HAYSTACK_SEARCH_RESULTS_PER_PAGE
可以控制每页显示数量 - 每页显示一条数据:
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 1
-
search.html
编写,类似商品列表页面
{% extends 'base.html' %} {% load staticfiles %} {% block title %}天天生鲜-搜索结果{% endblock %} {% block search_bar %} <div class="search_bar clearfix"> <a href="{% url 'goods:index' %}" class="logo fl"><img src="{% static 'images/logo.png' %}"></a> <div class="sub_page_name fl">| 搜索结果</div> <div class="search_con fr"> <form action="/search/" method="get"> <input type="text" class="input_text fl" name="q" placeholder="搜索商品"> <input type="submit" class="input_btn fr" value="搜索"> </form> </div> </div> {% endblock %} {% block body %} <div class="main_wrap clearfix"> <ul class="goods_type_list clearfix"> {% for result in page %} <li> {# object取得才是sku对象 #} <a href="{% url 'goods:detail' result.object.id %}"><img src="{{ result.object.default_image.url }}"></a> <h4><a href="{% url 'goods:detail' result.object.id %}">{{result.object.name}}</a></h4> <div class="operate"> <span class="prize">¥{{ result.object.price }}</span> <span class="unit">{{ result.object.price }}/{{ result.object.unit }}</span> </div> </li> {% empty %} <p>没有找到您要查询的商品。</p> {% endfor %} </ul> {% if page.has_previous or page.has_next %} <div class="pagenation"> {% if page.has_previous %}<a href="/search/?q={{ query }}&page={{ page.previous_page_number }}">上一页</a>{% endif %} | {% if page.has_next %}<a href="/search/?q={{ query }}&page={{ page.next_page_number }}">下一页</a>{% endif %} </div> {% endif %} </div> {% endblock %}
中文分词工具jieba
提示:
- 当出现
草莓
、盒装草莓
、大草莓
、北京草莓
- 使用
草莓
作为关键字时,检索不出来盒装草莓
、大草莓
、北京草莓
- 如果要满足以上全部检索的需求,需要使用中文分词
- 全文检索的中文分词工具:
jieba
中文分词工具jieba的使用
- 1.进入到安装了全文检索工具包的虚拟环境中
/home/python/.virtualenvs/py3_django/lib/python3.5/site-packages/
- 进入到
haystack/backends/
中
-
2.创建
ChineseAnalyzer.py
文件
import jieba from whoosh.analysis import Tokenizer, Token class 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 = w t.boost = 1.0 if 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 t def ChineseAnalyzer(): return ChineseTokenizer()
3.拷贝whoosh_backend.py
为whoosh_cn_backend.py
cp whoosh_backend.py whoosh_cn_backend.py
4.更改分词的类为ChineseAnalyzer
- 打开并编辑
whoosh_cn_backend.py
- 引入
from .ChineseAnalyzer import ChineseAnalyzer
- 查找
analyzer=StemmingAnalyzer() 改为 analyzer=ChineseAnalyzer()
5.更改分词引擎
6.重新创建索引数据
python manage.py rebuild_index
测试结果