摘抄Django項目之用戶、商品(二)

 

用戶中心

用戶地址

 

我的信息

 

 

提示:

  • 只有登陸成功的用戶才能進入到用戶中心界面
  • 若是沒有登錄用戶,試圖進入用戶中心,咱們須要引導到登錄界面
  • 用戶中心頁面,須要限制頁面訪問,只容許登陸的用戶訪問

準備工做

用戶地址界面:先從用戶地址信息開始實現javascript

  • 定義用戶地址視圖css

class AddressView(View):
  """用戶地址"""

  def get(self, request):
      """提供用戶地址頁面"""
      return render(request, 'user_center_site.html')

  def post(self, request):
      """修改地址信息"""
      pass

 

  • URL正則匹配html

    url(r'^address$', views.AddressView.as_view(), name='address'),

     

  • 問題:java

    • 以上操做,在進入用戶中心時,沒有進行任何的登錄驗證
  • 需求:
    • 只有登陸成功的用戶才能進入用戶中心
    • 用戶中心頁面,須要限制頁面訪問,只容許登陸的用戶訪問

 


 

限制頁面訪問的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對象,不須要在上下文中構造python

 

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請求,獲取我的中心界面 jquery

  • 基本信息
    • 查詢數據庫,獲得用戶基本信息
  • 瀏覽記錄
    • 使用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 , ...]
    • 每一個用戶一條記錄單獨維護,鍵跟用戶產生關係

瀏覽記錄查詢

這裏查詢瀏覽記錄的前提是:已經設計好瀏覽記錄的存儲規則nginx

  • 用戶訪問我的中心頁時查詢Redis數據庫
  • 使用安裝的django-redis模塊來操做redis數據庫
    • get_redis_connection返回一個已經跟Redis鏈接好的連接對象,該對象就能夠直接操做Redis
    • redis_connection = get_redis_connection('default')
  • 提示:settings.py文件已經配置CACHES選項的defaultgit

# 緩存
CACHES = {
  "default": {
      "BACKEND": "django_redis.cache.RedisCache",
      "LOCATION": "redis://192.168.243.193:6379/5",
      "OPTIONS": {
          "CLIENT_CLASS": "django_redis.client.DefaultClient",
      }
  }
}

瀏覽記錄查詢github

  • 提示:目前沒有瀏覽記錄,因此查詢的是空的瀏覽記錄ajax

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

測試結果

相關文章
相關標籤/搜索