摘抄Django项目之用户、商品(二)

 

用户中心

用户地址

 

个人信息

 

 

提示:
  • 只有登录成功的用户才能进入到用户中心界面
  • 如果没有登陆用户,试图进入用户中心,我们需要引导到登陆界面
  • 用户中心页面,需要限制页面访问,只允许登录的用户访问

准备工作

用户地址界面:先从用户地址信息开始实现

  • 定义用户地址视图

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连接好的链接对象,该对象就可以直接操作Redis
    • redis_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
  • FastDFS的安装和配置文档

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
    • 登录后,重定向到主页:
    • 如何区分:使用地址区分,动态页面增加/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 }}&nbsp;&nbsp;&nbsp;时间:{{ 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

商品列表视图

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草莓

搜索引擎和框架

  • 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.SearchIndexindexes.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文件,并定义自己的搜索结果页面 

 

  • 传递的上下文包括:

    • 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">|&nbsp;&nbsp;&nbsp;&nbsp;搜索结果</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 }}&amp;page={{ page.previous_page_number }}">上一页</a>{% endif %}
            |
            {% if page.has_next %}<a href="/search/?q={{ query }}&amp;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.pywhoosh_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

测试结果


 

  

 

转载于:https://www.cnblogs.com/kaiping23/p/9741529.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值