用戶中心
用戶地址
個人信息
提示:
- 只有登錄成功的用戶才能進入到用戶中心界面
- 如果沒有登陸用戶,試圖進入用戶中心,我們需要引導到登陸界面
- 用戶中心頁面,需要限制頁面訪問,只允許登錄的用戶訪問
准備工作
用戶地址界面:先從用戶地址信息開始實現
-
定義用戶地址視圖
class AddressView(View): """用戶地址""" def get(self, request): """提供用戶地址頁面""" return render(request, 'user_center_site.html') def post(self, request): """修改地址信息""" pass
-
URL正則匹配
url(r'^address$', views.AddressView.as_view(), name='address'),
-
問題:
- 以上操作,在進入用戶中心時,沒有進行任何的登陸驗證
- 需求:
- 只有登錄成功的用戶才能進入用戶中心
- 用戶中心頁面,需要限制頁面訪問,只允許登錄的用戶訪問
限制頁面訪問的next參數
next參數作用
- 在沒有登陸時,如果訪問了用戶地址頁面,裝飾器
@login_required
會限制頁面訪問 - 在限制頁面訪問時,該操作被引導到用戶登陸界面
- next參數用於標記,從哪兒來,回哪兒去。從用戶地址頁來就回到用戶地址頁去,以此類推
next參數使用
class LoginView(View): """登陸""" def get(self, request): """響應登陸頁面""" return render(request, 'login.html') def post(self, request): """處理登陸邏輯""" # 獲取用戶名和密碼 user_name = request.POST.get('username') password = request.POST.get('pwd') # 獲取是否勾選'記住用戶名' remembered = request.POST.get('remembered') # 參數校驗 if not all([user_name, password]): return redirect(reverse('users:login')) # django用戶認證系統判斷是否登陸成功 user = authenticate(username=user_name, password=password) # 驗證登陸失敗 if user is None: # 響應登錄頁面,提示用戶名或密碼錯誤 return render(request, 'login.html', {'errmsg':'用戶名或密碼錯誤'}) # 驗證登陸成功,並判斷是否是激活用戶 if user.is_active == False: # 如果不是激活用戶 return render(request, 'login.html', {'errmsg':'用戶未激活'}) # 使用django的用戶認證系統,在session中保存用戶的登陸狀態 login(request, user) # 服務器記錄session后,設置客戶端cookie的過期日期 if remembered != 'on': # 不需要記住cookie信息 request.session.set_expiry(0) else: # 需要記住cookie信息 request.session.set_expiry(None) # 登陸成功,根據next參數決定跳轉方向 next = request.GET.get('next') if next is None: # 如果是直接登陸成功,就重定向到首頁 return redirect(reverse('goods:index')) else: # 如果是用戶中心重定向到登陸頁面,就回到用戶中心 return redirect(next)
提示
- 如果需要提取的參數在URL中,使用
request.GET
- 如果需要提取的參數在POST請求體中,使用
request.POST
瀏覽效果
用戶地址頁視圖編寫
用戶地址頁界面分析
- 收貨地址
- 客戶端發送GET請求時,展示用戶地址頁面,並查詢用戶的地址信息
- 編輯地址
- 客戶端編輯地址,並發送POST請求將編輯的地址的表單信息發送到服務器
用戶地址頁視圖編寫之展示用戶地址頁面
- 展示用戶地址頁面,並查詢用戶的地址信息
- Django用戶認證系統中間件中,會在請求中驗證用戶
- 所以如果用戶登陸了,request中能夠拿到user對象,即
request.user
- 用戶和地址是一對多的關系,如果知道是哪個用戶在訪問地址頁,就可以通過關聯查詢得到用戶所有地址
- 查詢出來的地址可以進行排序,比如按照創建時間排序
- 如果沒有查詢出來地址就返回空的地址模型對象
- 將查詢出來的地址和用戶信息,構造上下文,並傳入到用戶地址模板中即可
latest('時間')
函數:按照時間排序,最近的時間在最前,並取出第0個數據,也可以按照其他條件排序-
render()
函數:參數1傳request,所以模板中可以拿到user對象,不需要在上下文中構造
class AddressView(LoginRequiredMixin, View): """用戶地址""" def get(self, request): """提供用戶地址頁面:如果驗證失敗重定向到登陸頁面""" # 從request中獲取user對象,中間件從驗證請求中的用戶,所以request中帶有user user = request.user try: # 查詢用戶地址:根據創建時間排序,最近的時間在最前,取第1個地址 # address = Address.objects.filter(user=user).order_by('-create_time')[0] # address = user.address_set.order_by('-create_time')[0] address = user.address_set.latest('create_time') except Address.DoesNotExist: # 如果地址信息不存在 address = None # 構造上下文 context = { # request中自帶user,調用模板時,request會傳給模板 # 'user':user, 'address':address } # return HttpResponse('這是用戶中心地址頁面') return render(request, 'user_center_site.html', context) def post(self, request): """修改地址信息""" pass
用戶地址頁視圖編寫之處理地址表單數據
class AddressView(LoginRequiredMixin, View)
類的post方法中- 接收用戶遞交到服務器的地址表單數據
- 驗證地址表單數據是否完整
- 將地址表單數據保存到數據庫地址表中
- ORM提供了create方法,幫我們快速的保存數據到數據庫表
def post(self, request): """修改地址信息""" # 接收地址表單數據 user = request.user recv_name = request.POST.get("recv_name") addr = request.POST.get("addr") zip_code = request.POST.get("zip_code") recv_mobile = request.POST.get("recv_mobile") # 參數校驗 if all([recv_name, addr, zip_code, recv_mobile]): # address = Address( # user=user, # receiver_name=recv_name, # detail_addr=addr, # zip_code=zip_code, # receiver_mobile=recv_mobile # ) # address.save() # 保存地址信息到數據庫 Address.objects.create( user=user, receiver_name=recv_name, detail_addr=addr, zip_code=zip_code, receiver_mobile=recv_mobile ) return redirect(reverse("users:address"))
抽離父模板
- 網站中很多頁面都很相似,重復的樣式不需要重復編寫代碼
- 所以需要抽離出父模板,保證模板的復用性
- 子模板繼承了父模板后,只需要專注於子模板的差異性內容即可
抽離父模板原則
- 以界面最豐富的頁面作為父模板的參考,父模板是通過現有html頁面抽離出來的
- 公共不變的內容定義在父模板中,變化的內容使用block標簽預留出來
- 子模板繼承父模板就可以把公共不變的部分繼承下來
- 子模板重寫父模板中預留的block標簽就可以實現子模板自己樣式的定制
需求
- 將主頁模板作為抽取父模板的參照,抽取基類模板:
base.html
- 抽取出用戶中心的父模板:
user_center_base.html
- 使用模板繼承,展示用戶地址頁面
個人中心頁面展示
個人中心界面展示之分析
發送get請求,獲取個人中心界面
- 基本信息
- 查詢數據庫,得到用戶基本信息
- 瀏覽記錄
- 使用Redis數據庫存儲瀏覽記錄
- 因為只要用戶訪問過某個商品,就要記錄下來,該操作非常頻繁
- 所以不要使用MySQL數據庫,頻繁操作磁盤性能消耗大
- 如果存儲在session中,用戶退出登陸,瀏覽記錄就沒有了
- 結論:需要選擇內存型數據庫,比如Redis數據庫,訪問速度快,而且是專機配置
個人中心頁面展示之視圖處理
class UserInfoView(LoginRequiredMixin, View): """用戶中心""" def get(self, request): """查詢用戶信息和地址信息""" # 從request中獲取user對象,中間件從驗證請求中的用戶,所以request中帶有user user = request.user try: # 查詢用戶地址:根據創建時間排序,取第1個地址 address = user.address_set.latest('create_time') except Address.DoesNotExist: # 如果地址信息不存在 address = None # 構造上下文 context = { 'address': address } # 渲染模板 return render(request, 'user_center_info.html', context)
個人中心頁面展示之模板處理
- 學習如何根據現有的模板,使用繼承完成模板渲染
user_center_info.html
繼承自user_center_base.html
{% extends 'user_center_base.html' %} {% load staticfiles %} {% block body %} <div class="main_con clearfix"> <div class="left_menu_con clearfix"> <h3>用戶中心</h3> <ul> <li><a href="{% url 'users:info' %}" class="active">· 個人信息</a></li> <li><a href="user_center_order.html">· 全部訂單</a></li> <li><a href="{% url 'users:address' %}">· 收貨地址</a></li> </ul> </div> <div class="right_content clearfix"> <div class="info_con clearfix"> <h3 class="common_title2">基本信息</h3> <ul class="user_info_list"> <li><span>用戶名:</span>{{ user.username }}</li> <li><span>聯系方式:</span>{{ address.receiver_mobile }}</li> <li><span>聯系地址:</span>{{ address.detail_addr }}</li> </ul> </div> <h3 class="common_title2">最近瀏覽</h3> <div class="has_view_list"> <ul class="goods_type_list clearfix"> {# 用於填充瀏覽記錄 #} </ul> </div> </div> </div> {% endblock body %}
商品瀏覽記錄設計和查詢
Redis保存商品瀏覽記錄相關文檔
瀏覽記錄產生
- 用戶訪問商品詳情頁時記錄:后續在實現商品詳情頁時補充
- 瀏覽記錄會頻繁生成,所以需要放到內存型數據庫中,訪問速度快
- 綜上所訴,瀏覽記錄使用Redis數據庫保存
- 提示:
- 只需要保存瀏覽的商品的sku_id即可,這樣既節省了內存,將來也能直接使用sku_id查詢到商品的詳情
- 不建議保存到session數據中,因為用戶退出登錄后會清空session數據
- 將用戶瀏覽的商品的sku_id保存到列表中,方便維護
history_userid = [sku_id2, sku_id8, sku_id5 , ...]
- 每個用戶一條記錄單獨維護,鍵跟用戶產生關系
瀏覽記錄查詢
這里查詢瀏覽記錄的前提是:已經設計好瀏覽記錄的存儲規則
- 用戶訪問個人中心頁時查詢Redis數據庫
- 使用安裝的
django-redis
模塊來操作redis數據庫get_redis_connection
返回一個已經跟Redis連接好的鏈接對象,該對象就可以直接操作Redisredis_connection = get_redis_connection('default')
-
提示:
settings.py
文件已經配置CACHES
選項的default
# 緩存 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://192.168.243.193:6379/5", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } }
瀏覽記錄查詢
-
提示:目前沒有瀏覽記錄,所以查詢的是空的瀏覽記錄
class UserInfoView(LoginRequiredMixin, View): """用戶中心""" def get(self, request): """查詢用戶信息和地址信息""" # 從request中獲取user對象,中間件從驗證請求中的用戶,所以request中帶有user user = request.user try: # 查詢用戶地址:根據創建時間排序,取第1個地址 address = user.address_set.latest('create_time') except Address.DoesNotExist: # 如果地址信息不存在 address = None # 創建redis連接對象 redis_connection = get_redis_connection('default') # 從Redis中獲取用戶瀏覽商品的sku_id,在redis中需要維護商品瀏覽順序[8,2,5] sku_ids = redis_connection.lrange('history_%s'%user.id, 0, 4) # 從數據庫中查詢商品sku信息,范圍在sku_ids中 # skuList = GoodsSKU.objects.filter(id__in=sku_ids) # 問題:經過數據庫查詢后得到的skuList,就不再是redis中維護的順序了,而是[2,5,8] # 需求:保證經過數據庫查詢后,依然是[8,2,5] skuList = [] for sku_id in sku_ids: sku = GoodsSKU.objects.get(id=sku_id) skuList.append(sku) # 構造上下文 context = { 'address':address, 'skuList':skuList, } # 調出並渲染模板 return render(request, 'user_center_info.html', context)
展示瀏覽記錄模板處理
- 在
user_center_info.html
模板中補充用戶商品瀏覽記錄數據
{% extends 'user_center_base.html' %} {% load staticfiles %} {% block body %} <div class="main_con clearfix"> <div class="left_menu_con clearfix"> <h3>用戶中心</h3> <ul> <li><a href="{% url 'users:info' %}" class="active">· 個人信息</a></li> <li><a href="user_center_order.html">· 全部訂單</a></li> <li><a href="{% url 'users:address' %}">· 收貨地址</a></li> </ul> </div> <div class="right_content clearfix"> <div class="info_con clearfix"> <h3 class="common_title2">基本信息</h3> <ul class="user_info_list"> <li><span>用戶名:</span>{{ user.username }}</li> <li><span>聯系方式:</span>{{ address.receiver_mobile }}</li> <li><span>聯系地址:</span>{{ address.detail_addr }}</li> </ul> </div> <h3 class="common_title2">最近瀏覽</h3> <div class="has_view_list"> <ul class="goods_type_list clearfix"> {% for sku in skuList %} <li> {# fastDFS:sku.default_image.url表示存放圖片的主機地址 #} <a href="detail.html"><img src="{{ sku.default_image.url }}"></a> <h4><a href="detail.html">{{ sku.name }}</a></h4> <div class="operate"> <span class="prize">¥{{ sku.price }}</span> <span class="unit">{{ sku.price }}/{{ sku.unit }}</span> <a href="#" class="add_goods" title="加入購物車"></a> </div> </li> {% endfor %} </ul> </div> </div> </div> {% endblock body %}
商品信息
FastDFS服務器
- 作用:以分布式的方式處理靜態文件,保證負載均衡,並且已經解決了文件去重的問題
- FastDFS百度百科
FastDFS服務器介紹
-
FastDFS分布式介紹
FastDFS上傳和下載工作流程介紹
文件檢索的索引介紹
FastDFS服務器安裝
FastDFS服務器安裝
- FastDFS的安裝和配置,不需要掌握,實際開發中一般不需要我們配置
- 參考課件:
FastDFS分布式存儲服務器安裝.docx
nginx服務器安裝
- Django中處理靜態文件的服務器,可以輔助FastDFS服務器完成負載均衡
- nginx服務器的安裝和配置,需要掌握
- 參考課件:
FastDFS分布式存儲服務器安裝.docx
FastDFS_client安裝
- FastDFS_client表示Django對接FastDFS服務器的客戶端
- FastDFS_client中提供了Django程序和FastDFS服務器交互的接口/API
- 參考課件:
FastDFS分布式存儲服務器安裝.docx
總結
-
1.程序猿需要配置的文件:
client.conf
tracker.conf
storage.conf
nginx.conf
sudo vim /etc/fdfs/tracker.conf sudo vim /etc/fdfs/storage.conf sudo vim /etc/fdfs/client.conf sudo vim /usr/local/nginx/conf/nginx.conf
2.需要啟動的:tracker,storage,nginx
sudo service fdfs_trackerd start 或者 sudo /etc/init.d/fdfs_trackerd start sudo service fdfs_storaged start 或者 sudo /etc/init.d/fdfs_storaged start sudo /usr/local/nginx/sbin/nginx
3.參考課件:FastDFS分布式存儲服務器安裝.docx
Django對接FastDFS流程
說明
- Django編碼的部分
- 在Django和fdfs客戶端之間:上圖中的黃色區域
- 開發中,需要在Django中調用fdfs客戶端提供的API操作FastFDS服務器
- 瀏覽器部分
- 當后台運維,以管理員身份進入后台站點發布內容時,是做的文件上傳並保存的操作
- 用戶通過瀏覽器,訪問我們提供的頁面時,加載圖片信息時,是做的文件下載的操作
- nginx服務器
- 提供文件的下載,不參與文件的上傳
- Django擅長處理動態的業務邏輯,靜態的業務邏輯交給FastFDS和nginx處理
安裝 fdfs_client
fdfs_client
是Django對接FastDFS的工具包- fdfs_client托管網站github
-
提示:
fdfs_client-py-master.zip
已經下載成功,建議使用課件提供的壓縮包
進入到 fdfs_client-py-master.zip 目錄
pip install fdfs_client-py-master.zip
思考
- 如何讓Django把后台站點發布的內容引導到FastFDS服務器進行存儲
- 提示:自定義文件存儲系統,繼承自
Storage
類
自定義文件存儲系統Storage
目的
- 能夠讓Django把后台站點發布的內容引導到FastFDS服務器進行存儲
相關文檔
自定義文件存儲系統實現
-
1.自定義文件存儲系統的目錄結構
2.Django項目中,使用client.conf
文件注意點
3.settings.py
中指定文件存儲系統類,指定為自定義的文件存儲系統類
from django.core.files.storage import Storage class FastDFSStorage(Storage): """自定義Django存儲系統的類""" pass
# 配置Django自定義的存儲系統 DEFAULT_FILE_STORAGE = 'utils.fastdfs.storage.FastDFSStorage'
4.自定義文件存儲系統類代碼實現
from django.core.files.storage import Storage from fdfs_client.client import Fdfs_client from django.conf import settings class FastDFSStorage(Storage): """自定義Django存儲系統的類""" def __init__(self, client_conf=None,server_ip=None): """初始化,設置參數""" if client_conf is None: client_conf = settings.CLIENT_CONF self.client_conf = client_conf if server_ip is None: server_ip = settings.SERVER_IP self.server_ip = server_ip def _open(self, name, mode='rb'): """讀取文件時使用""" pass def _save(self, name, content): """存儲文件時使用:參數2是上傳來的文件名,參數3是上傳來的File對象""" # 創建fdfs客戶端client client = Fdfs_client(self.client_conf) # client獲取文件內容 file_data = content.read() # Django借助client向FastDFS服務器上傳文件 try: result = client.upload_by_buffer(file_data) except Exception as e: print(e) # 自己調試臨時打印 raise # 根據返回數據,判斷是否上傳成功 if result.get('Status') == 'Upload successed.': # 讀取file_id file_id = result.get('Remote file_id') # 返回給Django存儲起來即可 return file_id else: # 開發工具類時,出現異常不要擅自處理,交給使用者處理 raise Exception('上傳文件到FastDFS失敗') def exists(self, name): """Django用來判斷文件是否存在的""" # 由於Djnago不存儲圖片,所以永遠返回Fasle,直接保存到FastFDS return False def url(self, name): """用於返回圖片在服務器上完整的地址:server_ip+path""" return self.server_ip + name
5.自定義文件存儲系統代碼優化部分
- 定義初始化方法,接收外界傳入的參數,交給私有方法使用(模仿系統的存儲實現)
- 開發工具類或者框架時,遇到異常直接拋出即可,出現的問題交給使用者、調用者解決
- 定義實現exists()方法,返回False
- 由於Djnago不存儲圖片,所以永遠返回Fasle,直接引導到FastFDS
- 定義實現url()方法,返回文件完整路徑,方便模板中調用並得到文件完整路徑
- 將server_ip和client.conf的默認數據,定義到settings.py文件中,保證自定義存儲系統的封裝度
# FastFDS使用的配置信息 CLIENT_CONF = os.path.join(BASE_DIR, 'utils/fastdfs/client.conf') SERVER_IP = 'http://192.168.243.193:8888/'
自定義文件存儲系統測試
-
需求:使用后台站點,向
goods
應用中的GoodsCategory
模型中發布內容 -
步驟:
- 1.本地化
- 2.注冊模型類到后台站點
- 3.創建超級管理員並登陸進入到后台站點
- 4.發布
GoodsCategory
模型中的內容
-
1.本地化
LANGUAGE_CODE = 'zh-Hans' TIME_ZONE = 'Asia/Shanghai'
2.注冊模型類到后台站點
from django.contrib import admin from goods.models import GoodsCategory,Goods,GoodsSKU # Register your models here. admin.site.register(GoodsCategory) admin.site.register(Goods) admin.site.register(GoodsSKU)
3.創建超級管理員並登陸進入到后台站點
python manage.py createsuperuser
4.發布GoodsCategory
模型中的內容
可能出現的錯誤
-
沒有mutagen和requests模塊
鏈接FastFDS服務器失敗
重新啟動tracker和storage和nginx即可
sudo service fdfs_trackerd start 或者 sudo /etc/init.d/fdfs_trackerd start sudo service fdfs_storaged start 或者 sudo /etc/init.d/fdfs_storaged start sudo /usr/local/nginx/sbin/nginx
富文本編輯器
-
富文本編輯器在后台站點的展示效果
富文本字段:HTMLField
# 富文本編輯器字段 from tinymce.models import HTMLField
pip install django-tinymce==2.6.0
安裝富文本編輯器應用
INSTALLED_APPS = ( ... 'tinymce', )
settings.py
中添加編輯器配置
TINYMCE_DEFAULT_CONFIG = { 'theme': 'advanced', # 豐富樣式 'width': 600, 'height': 400, }
項目/urls.py中配置編輯器url
import tinymce.urls urlpatterns = [ ... url(r'^tinymce/', include('tinymce.urls')), ]
主頁商品信息展示
主頁商品數據分析
主頁商品數據查詢
class IndexView(View): """首頁""" def get(self, request): """查詢首頁頁面需要的數據,構造上下文,渲染首頁頁面""" # 查詢用戶個人信息(request.user) # 查詢商品分類信息 categorys = GoodsCategory.objects.all() # 查詢圖片輪播信息:按照index進行排序 banners = IndexGoodsBanner.objects.all().order_by('index') # 查詢活動信息 promotion_banners = IndexPromotionBanner.objects.all().order_by('index') # 查詢分類商品信息 for category in categorys: title_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=0).order_by('index') category.title_banners = title_banners image_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=1).order_by('index') category.image_banners = image_banners # 查詢購物車信息 cart_num = 0 # 構造上下文:先處理購物車以外的上下文,並緩存 context = { 'categorys':categorys, 'banners':banners, 'promotion_banners':promotion_banners, 'cart_num':cart_num } return render(request, 'index.html',context)
主頁商品數據展示
{% extends 'base.html' %} {% block title %}天天生鮮-首頁{% endblock %} {# 刪除<head>,搜索框,底部. 保留body部分進行重寫 #} {% block body %} <div class="navbar_con"> <div class="navbar"> <h1 class="fl">全部商品分類</h1> <ul class="navlist fl"> <li><a href="">首頁</a></li> <li class="interval">|</li> <li><a href="">手機生鮮</a></li> <li class="interval">|</li> <li><a href="">抽獎</a></li> </ul> </div> </div> <div class="center_con clearfix"> <ul class="subnav fl"> {% for category in categorys %} <li><a href="#model0{{ forloop.counter }}" class="{{ category.logo }}">{{ category.name }}</a></li> {% endfor %} </ul> <div class="slide fl"> <ul class="slide_pics"> {% for banner in banners %} {# banner.image.url 獲取輪播模型類圖片屬性,url方法是配置FastDFS服務器提供圖片完整地址的方法 #} <li><a href="#"><img src="{{ banner.image.url }}" alt="幻燈片"></a></li> {% endfor %} </ul> <div class="prev"></div> <div class="next"></div> <ul class="points"></ul> </div> <div class="adv fl"> {% for promotion_banner in promotion_banners %} <a href="{{ promotion_banner.url }}"><img src="{{ promotion_banner.image.url }}"></a> {% endfor %} </div> </div> {% for category in categorys %} <div class="list_model"> <div class="list_title clearfix"> <h3 class="fl" id="model0{{ forloop.counter }}">{{ category.name }}</h3> <div class="subtitle fl"> <span>|</span> {% for title_banner in category.title_banners %} <a href="#">{{ title_banner.sku.name }}</a> {% endfor %} </div> <a href="#" class="goods_more fr" id="fruit_more">查看更多 ></a> </div> <div class="goods_con clearfix"> <div class="goods_banner fl"><img src="{{ category.image.url }}"></div> <ul class="goods_list fl"> {% for image_banner in category.image_banners %} <li> <h4><a href="#">{{ image_banner.sku.name }}</a></h4> <a href="#"><img src="{{ image_banner.sku.default_image.url }}"></a> <div class="prize">¥ {{ image_banner.sku.price }}</div> </li> {% endfor %} </ul> </div> </div> {% endfor %} {% endblock %} {% block bottom_files %} <script type="text/javascript" src="js/slideshow.js"></script> <script type="text/javascript"> BCSlideshow('focuspic'); var oFruit = document.getElementById('fruit_more'); var oShownum = document.getElementById('show_count'); var hasorder = localStorage.getItem('order_finish'); if(hasorder) { oShownum.innerHTML = '2'; } oFruit.onclick = function(){ window.location.href = 'list.html'; } </script> {% endblock %}
頁面靜態化
- 對於主頁,信息豐富,需要多次查詢數據庫才能得到全部數據。
- 如果用戶每次訪問主頁,都做多次數據庫查詢,性能差。
- 優化:將主頁存儲成靜態頁面,用戶訪問時,響應靜態頁面
- 實現:把render()返回的html數據存儲起來
- 以上是wed服務器優化方案之一,把頁面靜態化,減少服務器處理動態數據的壓力
實現思路
- 后台站點在發布主頁內容時,Django使用異步任務生成主頁靜態頁面
- 需要使用celery服務器執行異步任務
- 需要使用nginx服務器提供靜態頁面訪問服務
- 需要注冊模型類到站點,並創建模型類管理類,在模型類管理類中調用celery異步任務
celery生成靜態html頁面
- 生成的靜態html文件,不需要返回render(),只需要一個html即可
- 生成的靜態html文件,不需要處理用戶驗證的邏輯,不需要請求對象request
- 生成的靜態html文件,存放在celery服務器中,由nginx提供數據訪問
- 定義靜態html模板的父模板:static_base.html
- 去掉用戶驗證的邏輯
- 定義靜態主頁的html模板:static_index.html
定義異步任務
import os os.environ["DJANGO_SETTINGS_MODULE"] = "dailyfresh.settings" # 放到celery服務器上時將注釋打開 #import django #django.setup() from celery import Celery from django.core.mail import send_mail from django.conf import settings from goods.models import GoodsCategory,Goods,IndexGoodsBanner,IndexPromotionBanner,IndexCategoryGoodsBanner from django.template import loader # 創建celery應用對象 app = Celery('celery_tasks.tasks', broker='redis://192.168.243.193/4') @app.task def send_active_email(to_email, user_name, token): """發送激活郵件""" subject = "天天生鮮用戶激活" # 標題 body = "" # 文本郵件體 sender = settings.EMAIL_FROM # 發件人 receiver = [to_email] # 接收人 html_body = '<h1>尊敬的用戶 %s, 感謝您注冊天天生鮮!</h1>' \ '<br/><p>請點擊此鏈接激活您的帳號<a href="http://127.0.0.1:8000/users/active/%s">' \ 'http://127.0.0.1:8000/users/active/%s</a></p>' %(user_name, token, token) send_mail(subject, body, sender, receiver, html_message=html_body) @app.task def generate_static_index_html(): """生成靜態的html頁面""" # 查詢商品分類信息 categorys = GoodsCategory.objects.all() # 查詢圖片輪播信息:按照index進行排序 banners = IndexGoodsBanner.objects.all().order_by('index') # 查詢活動信息 promotion_banners = IndexPromotionBanner.objects.all().order_by('index') # 查詢分類商品信息 for category in categorys: title_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=0).order_by('index') category.title_banners = title_banners image_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=1).order_by('index') category.image_banners = image_banners # 查詢購物車信息 cart_num = 0 # 構造上下文 context = { 'categorys': categorys, 'banners': banners, 'promotion_banners': promotion_banners, 'cart_num': cart_num } # 加載模板 template = loader.get_template('static_index.html') html_data = template.render(context) # 保存成html文件:放到靜態文件中 file_path = os.path.join(settings.STATICFILES_DIRS[0], 'index.html') with open(file_path, 'w') as file: file.write(html_data)
測試celery生成靜態文件
- 將項目拷貝到celery服務器中
- 開啟celery:celery -A celery_tasks.tasks worker -l info
- 終端 python manage.py shell 測試異步任務調度
配置nginx訪問靜態頁面
- 為了讓celery服務器中的靜態文件能夠被高效訪問,需要給celery配置nginx服務器
- nginx服務器提供靜態數據效率高
配置參數
- 進入到:/usr/local/nginx/conf
- 在nginx.conf文件中,配置新的server選項,將主頁的訪問引導到nginx服務器
- 在nginx.conf文件中,具體配置html頁面中靜態文件image、css、js訪問路徑
-
重啟nginx:sudo /usr/local/nginx/sbin/nginx -s reload
測試nginx服務
- 瀏覽器中輸入nginx服務器地址,默認端口號80,查看能否加載到主頁靜態頁面
模型管理類調用celery異步方法
- 這里是生成靜態頁面的發起點
- 管理員通過站點發布內容時,在這里會調用celery異步方法,生成靜態html頁面
- 封裝了類:BaseAdmin,把保存和刪除封裝進去
class BaseAdmin(admin.ModelAdmin): """商品活動信息的管理類,運營人員在后台發布內容時,異步生成靜態頁面""" def save_model(self, request, obj, form, change): """后台保存對象數據時使用""" # obj表示要保存的對象,調用save(),將對象保存到數據庫中 obj.save() # 調用celery異步生成靜態文件方法 generate_static_index_html.delay() def delete_model(self, request, obj): """后台保存對象數據時使用""" obj.delete() generate_static_index_html.delay() class IndexPromotionBannerAdmin(BaseAdmin): """商品活動站點管理,如果有自己的新的邏輯也是寫在這里""" # list_display = [] pass class GoodsCategoryAdmin(BaseAdmin): pass class GoodsAdmin(BaseAdmin): pass class GoodsSKUAdmin(BaseAdmin): pass class IndexCategoryGoodsBannerAdmin(BaseAdmin): pass # Register your models here. admin.site.register(GoodsCategory,GoodsCategoryAdmin) admin.site.register(Goods,GoodsAdmin) admin.site.register(GoodsSKU,GoodsSKUAdmin) admin.site.register(IndexPromotionBanner,IndexPromotionBannerAdmin) admin.site.register(IndexCategoryGoodsBanner,IndexCategoryGoodsBannerAdmin)
用戶靜態動態頁面區分
-
未登錄用戶訪問主頁
- 未登錄用戶訪問主頁:
- 此時是nginx提供的靜態頁面
- nginx服務器ip:80
- 192.168.243.193:80
- 登錄后,重定向到主頁:
- 此時是Django提供的動態頁面
- 請求地址:http://127.0.0.1:8000/index
- 如何區分:使用地址區分,動態頁面增加
/index
- 未登錄用戶訪問主頁:
-
登陸用戶訪問主頁
- 動態主頁的請求地址:http://127.0.0.1:8000/index
- 動態主頁的正則匹配:
url(r'^index$', views.IndexView.as_view(), name='index')
訪問示例
-
訪問靜態頁面
訪問動態頁面
緩存
緩存介紹
- 靜態html頁面由nginx處理,但是,登陸用戶訪問的是動態邏輯,也會涉及到大量的數據庫查詢
- 對於動態查詢的數據的結果,我們也是要存儲,叫做緩存
- 提示:購物車數據不能被緩存,因為購物車數據是可能實時變化的
- 緩存中文文檔
from django.core.cache import cache
- 設置緩存:
cache.set('key', 內容, 有效期)
- 讀取緩存:
cache.get('key')
- 存儲進去的是什么,取出來的也是什么
-
邏輯:
- 先檢查是否有緩存數據,如果有緩存數據就讀取緩存數據
-
如果沒有緩存數據,就查詢數據庫
緩存主頁數據
class IndexView(View): """首頁""" def get(self, request): """查詢首頁頁面需要的數據,構造上下文,渲染首頁頁面""" # 查詢用戶個人信息(request.user) # 先從緩存中讀取數據,如果有就獲取緩存數據,反之,就執行查詢 context = cache.get('index_page_data') if context is None: print('沒有緩存數據,查詢了數據庫') # 查詢商品分類信息 categorys = GoodsCategory.objects.all() # 查詢圖片輪播信息:按照index進行排序 banners = IndexGoodsBanner.objects.all().order_by('index') # 查詢活動信息 promotion_banners = IndexPromotionBanner.objects.all().order_by('index') # 查詢分類商品信息 for category in categorys: title_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=0).order_by('index') category.title_banners = title_banners image_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=1).order_by('index') category.image_banners = image_banners # 構造上下文:先處理購物車以外的上下文,並緩存 context = { 'categorys':categorys, 'banners':banners, 'promotion_banners':promotion_banners, } # 設置緩存數據:名字,內容,有效期 cache.set('index_page_data',context,3600) # 查詢購物車信息:不能被緩存,因為會經常變化 cart_num = 0 # 補充購物車數據 context.update(cart_num=cart_num) return render(request, 'index.html',context)
緩存有效期和刪除緩存
- 緩存需要設置有效期,不然數據永遠無法得到更新,具體的有效期時間根據公司需求而定
-
緩存需要在修改內容時刪除,不然內容修改了,但還是緩存的舊數據
class BaseAdmin(admin.ModelAdmin): """商品活動信息的管理類,運營人員在后台發布內容時,異步生成靜態頁面""" def save_model(self, request, obj, form, change): """后台保存對象數據時使用""" # obj表示要保存的對象,調用save(),將對象保存到數據庫中 obj.save() # 調用celery異步生成靜態文件方法,操作完表單后刪除靜態文件 generate_static_index_html.delay() # 修改了數據庫數據就需要刪除緩存 cache.delete('index_page_data') def delete_model(self, request, obj): """后台保存對象數據時使用""" obj.delete() generate_static_index_html.delay() cache.delete('index_page_data')
主頁購物車
- 保存在redis中,每個人維護一條購物車數據, 選擇哈希類型
哈希類型存儲:cart_userid sku_1 10 sku_2 20 字典結構:cart_userid:{sku_1:10,sku_2:20}
class IndexView(View): """首頁""" def get(self, request): """查詢首頁頁面需要的數據,構造上下文,渲染首頁頁面""" # 查詢用戶個人信息(request.user) # 先從緩存中讀取數據,如果有就獲取緩存數據,反之,就執行查詢 context = cache.get('index_page_data') if context is None: print('沒有緩存數據,查詢了數據庫') # 查詢商品分類信息 categorys = GoodsCategory.objects.all() # 查詢圖片輪播信息:按照index進行排序 banners = IndexGoodsBanner.objects.all().order_by('index') # 查詢活動信息 promotion_banners = IndexPromotionBanner.objects.all().order_by('index') # 查詢分類商品信息 for category in categorys: title_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=0).order_by('index') category.title_banners = title_banners image_banners = IndexCategoryGoodsBanner.objects.filter(category=category, display_type=1).order_by('index') category.image_banners = image_banners # 構造上下文:先處理購物車以外的上下文,並緩存 context = { 'categorys':categorys, 'banners':banners, 'promotion_banners':promotion_banners, } # 設置緩存數據:名字,內容,有效期 cache.set('index_page_data',context,3600) # 查詢購物車信息:不能被緩存,因為會經常變化 cart_num = 0 # 如果用戶登錄,就獲取購物車數據 if request.user.is_authenticated(): # 創建redis_conn對象 redis_conn = get_redis_connection('default') # 獲取用戶id user_id = request.user.id # 從redis中獲取購物車數據,返回字典 cart_dict = redis_conn.hgetall('cart_%s'%user_id) # 遍歷購物車字典的值,累加購物車的值 for value in cart_dict.values(): cart_num += int(value) # 補充購物車數據 context.update(cart_num=cart_num) return render(request, 'index.html',context)
商品詳情頁
商品詳情視圖編寫
- 參數: 需求客戶端傳入商品的sku_id
- 查詢
- 查詢商品SKU信息
- 查詢所有商品分類信息
- 查詢商品訂單評論信息
- 查詢最新商品推薦
- 查詢其他規格商品
- 如果已登錄,查詢購物車信息
- 提示:
- 檢查是否有緩存,如果緩存不存在就查詢數據;反之,直接讀取緩存數據
- 在商品詳情頁需要實現存儲瀏覽記錄的邏輯
- 瀏覽記錄存儲在redis中,之前已經在用戶中心界面實現了瀏覽記錄的讀取
- URL的設計:
/detail/1
url(r'^detail/(?P<sku_id>\d+)$', views.DetailView.as_view(), name='detail'),
class DetailView(View): """商品詳細信息頁面""" def get(self, request, sku_id): # 嘗試獲取緩存數據 context = cache.get("detail_%s" % sku_id) # 如果緩存不存在 if context is None: try: # 獲取商品信息 sku = GoodsSKU.objects.get(id=sku_id) except GoodsSKU.DoesNotExist: # from django.http import Http404 # raise Http404("商品不存在!") return redirect(reverse("goods:index")) # 獲取類別 categorys = GoodsCategory.objects.all() # 從訂單中獲取評論信息 sku_orders = sku.ordergoods_set.all().order_by('-create_time')[:30] if sku_orders: for sku_order in sku_orders: sku_order.ctime = sku_order.create_time.strftime('%Y-%m-%d %H:%M:%S') sku_order.username = sku_order.order.user.username else: sku_orders = [] # 獲取最新推薦 new_skus = GoodsSKU.objects.filter(category=sku.category).order_by("-create_time")[:2] # 獲取其他規格的商品 other_skus = sku.goods.goodssku_set.exclude(id=sku_id) context = { "categorys": categorys, "sku": sku, "orders": sku_orders, "new_skus": new_skus, "other_skus": other_skus } # 設置緩存 cache.set("detail_%s"%sku_id, context, 3600) # 購物車數量 cart_num = 0 # 如果是登錄的用戶 if request.user.is_authenticated(): # 獲取用戶id user_id = request.user.id # 從redis中獲取購物車信息 redis_conn = get_redis_connection("default") # 如果redis中不存在,會返回None cart_dict = redis_conn.hgetall("cart_%s"%user_id) for val in cart_dict.values(): cart_num += int(val) # 瀏覽記錄: lpush history_userid sku_1, sku_2 # 移除已經存在的本商品瀏覽記錄 redis_conn.lrem("history_%s"%user_id, 0, sku_id) # 添加新的瀏覽記錄 redis_conn.lpush("history_%s"%user_id, sku_id) # 只保存最多5條記錄 redis_conn.ltrim("history_%s"%user_id, 0, 4) context.update({"cart_num": cart_num}) return render(request, 'detail.html', context)
商品詳情模板編寫
- 在處理詳情頁模板時,遇到詳情頁的跳轉需要處理
- 主頁中跳轉到詳情頁的鏈接也需要處理
{% extends 'base.html' %} {% load staticfiles %} {% block title %}天天生鮮-商品詳情{% endblock %} {% block body %} <div class="navbar_con"> <div class="navbar clearfix"> <div class="subnav_con fl"> <h1>全部商品分類</h1> <span></span> <ul class="subnav"> {% for category in categorys %} <li><a href="#" class="{{ category.logo }}">{{ category.name }}</a></li> {% endfor %} </ul> </div> </div> </div> <div class="breadcrumb"> <a href="/">全部分類</a> <span>></span> <a href="#">{{ sku.category.name }}</a> <span>></span> <a href="#">商品詳情</a> </div> <div class="goods_detail_con clearfix"> <div class="goods_detail_pic fl"><img src="{{ sku.default_image.url }}"></div> <div class="goods_detail_list fr"> <h3>{{ sku.name}}</h3> <p>{{ sku.title }}</p> <div class="prize_bar"> <span class="show_pirze">¥<em>{{ sku.price }}</em></span> <span class="show_unit">單 位:{{ sku.unit }}</span> </div> {% if other_skus %} <div> <p>其他規格:</p> <ul> {% for sku in other_skus %} <li><a href="{% url 'goods:detail' sku.id %}">{{ sku.price }}/{{ sku.unit }}</a></li> {% endfor %} </ul> </div> {% endif %} <div class="goods_num clearfix"> <div class="num_name fl">數 量:</div> <div class="num_add fl"> <input type="text" class="num_show fl" id="num_show" value="1"> <a href="javascript:;" class="add fr" id="add">+</a> <a href="javascript:;" class="minus fr" id="minus">-</a> </div> </div> <div class="total">總價:<em>{{ sku.price }}</em>元</div> <div class="operate_btn"> <a href="javascript:;" class="buy_btn" id="buy_btn">立即購買</a> <a href="javascript:;" class="add_cart" sku_id="{{ sku.id }}" id="add_cart">加入購物車</a> </div> </div> </div> <div class="main_wrap clearfix"> <div class="l_wrap fl clearfix"> <div class="new_goods"> <h3>新品推薦</h3> <ul> {% for sku in new_skus %} <li> <a href="{% url 'goods:detail' sku.id %}"><img src="{{ sku.default_image.url }}"></a> <h4><a href="{% url 'goods:detail' sku.id %}">{{ sku.name }}</a></h4> <div class="prize">¥{{ sku.price }}</div> </li> {% endfor %} </ul> </div> </div> <div class="r_wrap fr clearfix"> <ul class="detail_tab clearfix"> <li id="tag_detail" class="active">商品介紹</li> <li id="tag_comment">評論</li> </ul> <div class="tab_content" id="tab_detail"> <dl> <dt>商品詳情:</dt> <dd>{{ sku.goods.desc|safe }}</dd> </dl> </div> <div class="tab_content" id="tab_comment" style="display: none;"> {% for order in orders %} <dl> <dd>客戶:{{ order.username }} 時間:{{ order.ctime }}</dd> <dt>{{ order.comment }}</dt> </dl> <hr/> {% endfor %} </div> </div> </div> {% endblock %} {% block footer %} <div class="add_jump"></div> {% endblock %} {% block bottom_files %} <script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script> <script type="text/javascript"> $("#tag_detail").click(function(){ $("#tag_comment").removeClass("active"); $(this).addClass("active"); $("#tab_comment").hide(); $("#tab_detail").show(); }); $("#tag_comment").click(function(){ $("#tag_detail").removeClass("active"); $(this).addClass("active"); $("#tab_detail").hide(); $("#tab_comment").show(); }); $("#buy_btn").click(function(){ var count = $("#num_show").val(); window.location.href = '/order/commit?g={{goods.id}}@' + count; }); var $add_x = $('#add_cart').offset().top; var $add_y = $('#add_cart').offset().left; var $to_x = $('#show_count').offset().top; var $to_y = $('#show_count').offset().left; // 點擊加入購物車 $('#add_cart').click(function(){ // 將商品的id 和 數量發送給后端視圖,保存到購物車數據中 var req_data = { sku_id: $('#add_cart').attr("sku_id"), count: $("#num_show").val(), csrfmiddlewaretoken: "{{ csrf_token }}" }; {# // 使用ajax向后端發送數據#} {# $.post("/cart/add", req_data, function (response_data) {#} {# // 根據response_data中的code決定處理效果#} {# if (1 == response_data.code) {#} {# // 用戶未登錄#} {# window.location.href = "/users/login"; // 讓頁面跳轉到登錄頁面#} {# } else if (0 == response_data.code) {#} {# // 添加到購物車成功動畫#} {# $(".add_jump").css({'left':$add_y+80,'top':$add_x+10,'display':'block'});#} {##} {# $(".add_jump").stop().animate({#} {# 'left': $to_y+7,#} {# 'top': $to_x+7},#} {# "fast", function() {#} {# $(".add_jump").fadeOut('fast',function(){#} {# $('#show_count').html(response_data.cart_num);#} {# });#} {# });#} {##} {# } else {#} {# // 其他錯誤信息,alert展示#} {# alert(response_data.message);#} {# }#} {# }, "json");#} }); $("#add").click(function(){ var num_show = $("#num_show").val(); num_show = parseInt(num_show); num_show += 1; $("#num_show").val(num_show); var price = $(".show_pirze>em").html(); price = parseFloat(price); var total = price * num_show; $(".total>em").html(total.toFixed(2)); }); $("#minus").click(function(){ var num_show = $("#num_show").val(); num_show = parseInt(num_show); num_show -= 1; if (num_show < 1){ num_show = 1; } $("#num_show").val(num_show); var price = $(".show_pirze>em").html(); price = parseFloat(price); var total = price * num_show; $(".total>em").html(total.toFixed(2)); }); </script> {% endblock %}
商品列表頁
商品列表頁分析
- 需要知道是展示哪一類商品的列表
- 需要知道展示的是第幾頁
- 需要知道排序的規則,默認,價格,人氣
- 請求方法是get,只為了獲取數據
- 外界需要傳遞相關參數到商品列表視圖中,就需要在視圖中進行參數校驗
- 需要查詢的數據
- 商品分類信息
- 新品推薦信息,在GoodsSKU表中,查詢特定類別信息,按照時間倒序
- 商品列表信息
- 商品分頁信息
- 購物車信息
參數傳遞方式分析
- 展示某商品第幾頁的數據,然后再排序
- 默認排序:
/list/category_id/page_num/?sort='default'
- 價格排序:
/list/category_id/page_num/?sort='price'
- 人氣排序:
/list/category_id/page_num/?sort='hot'
提示
- 1.獲取請求參數信息,商品id,第幾頁數據,排序規則
- 2.校驗參數
- 2.1.判斷類別是否存在,查詢數據庫,如果不存在,異常為:
GoodsCategory.DoesNotExist
- 2.2.分頁的異常,在創建分頁對象時校驗
- 因為只有創建了分頁數據,才能知道頁數page是否正確
- 如果頁數錯誤,異常為:
EmptyPage
- 2.1.判斷類別是否存在,查詢數據庫,如果不存在,異常為:
商品列表視圖
class ListView(View): """商品列表""" def get(self, request, category_id, page_num): # 獲取sort參數:如果用戶不傳,就是默認的排序規則 sort = request.GET.get('sort', 'default') # 校驗參數 # 判斷category_id是否正確,通過異常來判斷 try: category = GoodsCategory.objects.get(id=category_id) except GoodsCategory.DoesNotExist: return redirect(reverse('goods:index')) # 查詢商品所有類別 categorys = GoodsCategory.objects.all() # 查詢該類別商品新品推薦 new_skus = GoodsSKU.objects.filter(category=category).order_by('-create_time')[:2] # 查詢該類別所有商品SKU信息:按照排序規則來查詢 if sort == 'price': # 按照價格由低到高 skus = GoodsSKU.objects.filter(category=category).order_by('price') elif sort == 'hot': # 按照銷量由高到低 skus = GoodsSKU.objects.filter(category=category).order_by('-sales') else: skus = GoodsSKU.objects.filter(category=category) # 無論用戶是否傳入或者傳入其他的排序規則,我在這里都重置成'default' sort = 'default' # 分頁:需要知道從第幾頁展示 page_num = int(page_num) # 創建分頁器:每頁兩條記錄 paginator = Paginator(skus,2) # 校驗page_num:只有知道分頁對對象,才能知道page_num是否正確 try: page_skus = paginator.page(page_num) except EmptyPage: # 如果page_num不正確,默認給用戶第一頁數據 page_skus = paginator.page(1) # 獲取頁數列表 page_list = paginator.page_range # 購物車 cart_num = 0 # 如果是登錄的用戶 if request.user.is_authenticated(): # 獲取用戶id user_id = request.user.id # 從redis中獲取購物車信息 redis_conn = get_redis_connection("default") # 如果redis中不存在,會返回None cart_dict = redis_conn.hgetall("cart_%s" % user_id) for val in cart_dict.values(): cart_num += int(val) # 構造上下文 context = { 'sort':sort, 'category':category, 'cart_num':cart_num, 'categorys':categorys, 'new_skus':new_skus, 'page_skus':page_skus, 'page_list':page_list } # 渲染模板 return render(request, 'list.html', context)
商品列表模板
{% extends 'base.html' %} {% load staticfiles %} {% block title %} 天天生鮮-商品列表 {% endblock %} {% block body %} <div class="navbar_con"> <div class="navbar clearfix"> <div class="subnav_con fl"> <h1>全部商品分類</h1> <span></span> <ul class="subnav"> {% for category in categorys %} {# 默認跳轉到某個分類商品列表的第一頁 #} <li><a href="{% url 'goods:list' category.id 1 %}" class="{{ category.logo }}">{{ category.name }}</a></li> {% endfor %} </ul> </div> <ul class="navlist fl"> <li><a href="">首頁</a></li> <li class="interval">|</li> <li><a href="">手機生鮮</a></li> <li class="interval">|</li> <li><a href="">抽獎</a></li> </ul> </div> </div> <div class="breadcrumb"> <a href="{% url 'goods:index' %}">全部分類</a> <span>></span> <a href="{% url 'goods:list' category.id 1 %}">{{ category.name }}</a> </div> <div class="main_wrap clearfix"> <div class="l_wrap fl clearfix"> <div class="new_goods"> <h3>新品推薦</h3> <ul> {% for new_sku in new_skus %} <li> <a href="{% url 'goods:detail' new_sku.id %}"><img src="{{ new_sku.default_image.url }}"></a> <h4><a href="{% url 'goods:detail' new_sku.id %}">{{ new_sku.name }}</a></h4> <div class="prize">¥{{ new_sku.price }}</div> </li> {% endfor %} </ul> </div> </div> <div class="r_wrap fr clearfix"> <div class="sort_bar"> <a href="{% url 'goods:list' category.id 1 %}?srot=default" {% if sort == 'default' %}class="active"{% endif %}>默認</a> <a href="{% url 'goods:list' category.id 1 %}?srot=price" {% if sort == 'price' %}class="active"{% endif %}>價格</a> <a href="{% url 'goods:list' category.id 1 %}?srot=hot" {% if sort == 'hot' %}class="active"{% endif %}>人氣</a> </div> <ul class="goods_type_list clearfix"> {% for sku in page_skus %} <li> <a href="{% url 'goods:detail' sku.id %}"><img src="{{ sku.default_image.url }}"></a> <h4><a href="{% url 'goods:detail' sku.id %}">{{ sku.name }}</a></h4> <div class="operate"> <span class="prize">¥{{ sku.price }}</span> <span class="unit">{{ sku.price }}/{{ sku.unit }}</span> <a href="#" class="add_goods" title="加入購物車"></a> </div> </li> {% endfor %} </ul> <div class="pagenation"> {% if page_skus.has_previous %} <a href="{% url 'goods:list' category.id page_skus.previous_page_number %}?sort={{ sort }}">上一頁</a> {% endif %} {% for index in page_list %} <a href="{% url 'goods:list' category.id index %}?sort={{ sort }}" {% if index == page_skus.number %}class="active"{% endif %}>{{ index }}</a> {% endfor %} {% if page_skus.has_next %} <a href="{% url 'goods:list' category.id page_skus.next_page_number %}?sort={{ sort }}">下一頁</a> {% endif %} </div> </div> </div> {% endblock %}
商品搜索
全文檢索
select * from table where name like '%草莓%' or title like '%草莓%';
- 全文檢索不同於特定字段的模糊查詢,使用全文檢索的效率更高,並且能夠對於中文進行分詞處理。
- 以上sql語句,可以查詢到
草莓
,盒裝草莓
,500g草莓
- 但是把
like '%草莓%'
改成like '%北京草莓%'
就無法再查詢出草莓
,盒裝草莓
,500g草莓
- 以上sql語句,可以查詢到
搜索引擎和框架
- whoosh:
- 純Python編寫的全文搜索引擎,雖然性能比不上sphinx、xapian、Elasticsearc等,但是無二進制包,程序不會莫名其妙的崩潰,對於小型的站點,whoosh已經足夠使用。
- 點擊whoosh查看官方網站
- haystack:
- 全文檢索的框架,支持whoosh、solr、Xapian、Elasticsearc四種全文檢索引擎。
- 作用:搭建了用戶和搜索引擎之間的溝通橋梁
- 點擊haystack查看官方網站
- jieba:
- 一款免費的中文分詞包,如果覺得不好用可以使用一些收費產品。
安裝全文檢索包
# 全文檢索框架 pip install django-haystack # 全文檢索引擎 pip install whoosh # 中文分詞框架 pip install jieba
haystack的使用
配置全文檢索
-
1.安裝haystack應用
INSTALLED_APPS = ( ... 'haystack', )
2.在settings.py文件中配置搜索引擎
# 配置搜索引擎后端 HAYSTACK_CONNECTIONS = { 'default': { # 使用whoosh引擎:提示,如果不需要使用jieba框架實現分詞,就使用whoosh_backend 'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine', # 索引文件路徑 'PATH': os.path.join(BASE_DIR, 'whoosh_index'), } } # 當添加、修改、刪除數據時,自動生成索引 HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
定義商品索引類
-
在要建立索引的表對應的應用下,創建
search_indexes.py
文件
定義商品索引類GoodsSKUIndex()
,繼承自indexes.SearchIndex
和indexes.Indexable
from haystack import indexes from goods.models import GoodsSKU class GoodsSKUIndex(indexes.SearchIndex, indexes.Indexable): """建立索引時被使用的類""" text = indexes.CharField(document=True, use_template=True) def get_model(self): """從哪個表中查詢""" return GoodsSKU def index_queryset(self, using=None): """返回要建立索引的數據""" return self.get_model().objects.all()
指定要建立索引的字段
-
在
templates
下面新建目錄search/indexes/應用名
- 比如
goods
應用中的GoodsSKU
模型類中的字段要建立索引:search/indexes/goods
-
在新建目錄下,創建
goodssku_text.txt
,並編輯要建立索引的字段,如下圖
- 比如
生成索引文件
python manage.py rebuild_index
搜索表單處理
- 搜索地址:
/search/
- 搜索方法:
get
-
接收關鍵字:
q
配置搜索地址正則
import haystack.urls url(r'^search/', include(haystack.urls)),
測試搜索效果,接收結果
-
全文檢索結果:
- 搜索出結果后,haystack會把搜索出的結果傳遞給
templates/search
目錄下的search.html
- 對於
search.html
,我們需要自己建立該html文件,並定義自己的搜索結果頁面
- 搜索出結果后,haystack會把搜索出的結果傳遞給
-
傳遞的上下文包括:
- query:搜索關鍵字
- page:當前頁的page對象
- paginator:分頁paginator對象
- 提示:
settings.py
文件中設置HAYSTACK_SEARCH_RESULTS_PER_PAGE
- 通過
HAYSTACK_SEARCH_RESULTS_PER_PAGE
可以控制每頁顯示數量 - 每頁顯示一條數據:
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 1
-
search.html
編寫,類似商品列表頁面
{% extends 'base.html' %} {% load staticfiles %} {% block title %}天天生鮮-搜索結果{% endblock %} {% block search_bar %} <div class="search_bar clearfix"> <a href="{% url 'goods:index' %}" class="logo fl"><img src="{% static 'images/logo.png' %}"></a> <div class="sub_page_name fl">| 搜索結果</div> <div class="search_con fr"> <form action="/search/" method="get"> <input type="text" class="input_text fl" name="q" placeholder="搜索商品"> <input type="submit" class="input_btn fr" value="搜索"> </form> </div> </div> {% endblock %} {% block body %} <div class="main_wrap clearfix"> <ul class="goods_type_list clearfix"> {% for result in page %} <li> {# object取得才是sku對象 #} <a href="{% url 'goods:detail' result.object.id %}"><img src="{{ result.object.default_image.url }}"></a> <h4><a href="{% url 'goods:detail' result.object.id %}">{{result.object.name}}</a></h4> <div class="operate"> <span class="prize">¥{{ result.object.price }}</span> <span class="unit">{{ result.object.price }}/{{ result.object.unit }}</span> </div> </li> {% empty %} <p>沒有找到您要查詢的商品。</p> {% endfor %} </ul> {% if page.has_previous or page.has_next %} <div class="pagenation"> {% if page.has_previous %}<a href="/search/?q={{ query }}&page={{ page.previous_page_number }}">上一頁</a>{% endif %} | {% if page.has_next %}<a href="/search/?q={{ query }}&page={{ page.next_page_number }}">下一頁</a>{% endif %} </div> {% endif %} </div> {% endblock %}
中文分詞工具jieba
提示:
- 當出現
草莓
、盒裝草莓
、大草莓
、北京草莓
- 使用
草莓
作為關鍵字時,檢索不出來盒裝草莓
、大草莓
、北京草莓
- 如果要滿足以上全部檢索的需求,需要使用中文分詞
- 全文檢索的中文分詞工具:
jieba
中文分詞工具jieba的使用
- 1.進入到安裝了全文檢索工具包的虛擬環境中
/home/python/.virtualenvs/py3_django/lib/python3.5/site-packages/
- 進入到
haystack/backends/
中
-
2.創建
ChineseAnalyzer.py
文件
import jieba from whoosh.analysis import Tokenizer, Token class ChineseTokenizer(Tokenizer): def __call__(self, value, positions=False, chars=False, keeporiginal=False, removestops=True, start_pos=0, start_char=0, mode='', **kwargs): t = Token(positions, chars, removestops=removestops, mode=mode, **kwargs) seglist = jieba.cut(value, cut_all=True) for w in seglist: t.original = t.text = w t.boost = 1.0 if positions: t.pos = start_pos + value.find(w) if chars: t.startchar = start_char + value.find(w) t.endchar = start_char + value.find(w) + len(w) yield t def ChineseAnalyzer(): return ChineseTokenizer()
3.拷貝whoosh_backend.py
為whoosh_cn_backend.py
cp whoosh_backend.py whoosh_cn_backend.py
4.更改分詞的類為ChineseAnalyzer
- 打開並編輯
whoosh_cn_backend.py
- 引入
from .ChineseAnalyzer import ChineseAnalyzer
- 查找
analyzer=StemmingAnalyzer() 改為 analyzer=ChineseAnalyzer()
5.更改分詞引擎
6.重新創建索引數據
python manage.py rebuild_index
測試結果