第3章 用Django設計大型電商的類别表
在本章中,我們将要做一個符合資料量龐大的電商平台的類别表。看似使用SQL語句就可以完成的功能需求,其實并沒有想象中那麼簡單。對于如天貓和京東這樣資料量龐大的電商類平台來說,類别表内的資料記錄是存在層層嵌套的,如果采用傳統的建構資料表的方式來完成功能需求,是非常難以實作的。通過本章的學習,我們就可以了解到,如何使用Django來設計一張資料表,以實作資料量龐大的商品分類需求。
3.1 電商類别表的項目功能需求
本節将分析電商“大鳄”的類别表中實作了哪些功能。讓我們從宏觀角度來觀察一下大型的企業電商對于類别表的設計思路,進而提升和夯實我們對于資料庫設計的水準。相信從這一節中,讀者會對資料類别表有一個重新的認識,進而對以後實戰項目中的資料庫設計,能夠做到胸有成竹。
3.1.1 類别表需求分析
如圖3-1所示,天貓、京東,以及目前國内的一線電商平台,大多都是以這種結構作為首頁上方的分類導航界面,将樹狀分類導航與輪播圖鑲嵌在一起,這樣從網頁設計角度上來講,可以用最少的空間呈現最多的資料資訊。當然,使用Django主要是分析後端的邏輯,前端的網頁設計并不是本書的重點。
大家可以将圖3-1與天貓或者京東的首頁進行對比檢視。如圖3-1所示,在輪播圖上方是一級類目,包括商品分類及一些業務分類。當我們将光标懸浮在任何一個二級類目上時,就會出現在此二級類目下的三級類目、二級類目所包含領域的一些推薦品牌的Logo,以及在三級類目中比較熱門的一些四級類目的搜尋内容。
這樣聽起來似乎有一點繞,我們可以拿天貓來舉一個例子。比如圖3-1中的一級類目,在天貓中,這些一級類目是“商品分類”“天貓超市”“天貓國際”“喵生鮮”等,二級類目是“女裝/内衣”“男裝/戶外運動”“女鞋/男鞋/箱包”等,當我們将光标移動懸浮到某一個二級類目上方時,比如将光标懸浮到“女鞋/男鞋/箱包”這個類目上方,在原本輪播圖的上面,就會出現一個新的導航頁将其覆寫,而在導航頁中分為左、中、右三個結構,左邊這一列就是三級類目,比如“推薦女鞋”“潮流男鞋”“女單鞋”“特色鞋”等,中間這一列就是四級類目,比如“潮流過膝靴”“氣質百搭踝靴”“永遠的帆布鞋”,右邊這一列則是一些品牌Logo,比如“李甯”“耐克”“喬丹”等。總結一下,類别表的設計需求如下:

(1)類别表必須包含多級類目,至少分為四級類目。
(2)類别表每一級類目資料都要有各自類目級别的标注,以便前端進行網頁設計。
(3)類别表内的類别資料,必須可以靈活地進行增、減,并且不會是以而改變其上下層級的資料關系。
3.1.2 使用Vue.js在前端開發一個電商導航欄項目demo1
在3.1.1節中,我們了解到了一個大型的電商網站的導航欄類别表的項目需求。本節我們将使用Vue.js開發一個電商導航欄的前端demo。
什麼是Vue.js?在回答這個問題之前,我們需要先了解一個概念:前端架構。
在第1章中,我們詳細地講述了什麼是前後端分離,以及在當今技術領域,使用前後端分離面相切面程式設計的技術模式,來處理多端應用開發的需求。既然是前後端分離,有後端架構Django,當然也要有前端架構。前端架構與後端架構不同,後端架構Django是為Python處理網站後端邏輯而創造出來的,Django之于Python,就如同ThinkPHP之于PHP,.NET之于C#,幾乎每一種後端架構,都對應着一種程式設計語言。但是對于前端架構而言,所有的前端架構,都隻對應HTML+CSS+JavaScript語言,絕大多數的前端架構,都是為了将HTML+CSS+JavaScript工程化而産生的。
Vue.js就是一個前端架構。Vue.js的單頁面應用模式對于前端技術的影響非常深遠,包括微信小程式在内的很多前端原生語言,都是借鑒了此模式。
Vue.js是一個建構資料驅動的Web界面漸進式架構,與Angular和React并稱為前端三大架構,而Vue.js架構是由華人尤雨溪所創造,開發文檔更适合中國人閱讀,而且尤雨溪也已經加盟阿裡巴巴,是以Vue.js在國内也得到了阿裡巴巴的推廣,已經成為了國内最熱門的前端架構之一。Vue.js的知識非常簡單,如果大家已經掌握了HTML+CSS+JavaScript語言,通過Vue.js的官方文檔,隻需要幾個小時的學習,就可以輕松上手Vue.js架構了。Vue.js是實作多端并行中非常重要,也是非常基礎的一個技術。
下面我們來搭建Vue.js的開發環境:
(1)下載下傳Node.js。
Node.js官網位址為
https://nodejs.org/en/,如圖3-2所示,選擇适合大多數使用者使用的穩定版本10.15.0LTS安裝包。
注意:如圖3-2所示,這裡下載下傳的是适合Windows 64位作業系統的Node.js。由于Node.js的下載下傳和安裝非常簡單,是以這裡隻介紹Windows 64位作業系統下Node.js的下載下傳與安裝方法。Mac OS系統和Linux系統下的下載下傳和安裝方法,大家可以自行在網上檢視相關教程。
(2)安裝Node.js。
将Node.js安裝包msi檔案下載下傳到計算機中,輕按兩下安裝包,打開Node.js安裝對話框,如圖3-3所示,然後單擊Next按鈕。
在進入的對話框中,勾選同意協定,然後單擊Next按鈕,進入下一步,如圖3-4所示。
在進入的對話框中,單擊Change…按鈕可以自定義将Node.js安裝到哪個路徑,也可以不做修改,安裝在預設路徑下。然後單擊Next按鈕,進入下一步,如圖3-5所示。
在進入的對話框中,單擊Next按鈕,進入下一步,如圖3-6所示。
在進入的對話框中,單擊Install按鈕進行安裝,如圖3-7所示。
經過幾分鐘的等待,出現如圖3-8所示的對話框後,單擊Finish按鈕,完成安裝。
(3)檢視Node.js是否安裝成功。
通過單擊桌面左下角的“開始”按鈕,然後輸入cmd,打開cmd.exe程式,如圖3-9所示。
在cmd操作界面輸入以下指令,運作結果如圖3-10所示。
然後按Enter鍵,如果像圖3-10中一樣顯示Node.js的目前版本,則證明安裝成功。
(4)安裝淘寶鏡像cnpm,可以讓資源包下載下傳得更快。在cmd操作界面輸入以下指令,運作結果如圖3-11所示。
然後按Enter鍵進行安裝。
注意:由于國内網絡原因,使用淘寶鏡像cnpm進行資源包下載下傳雖然比直接使用npm進行資源包下載下傳速度快得多,但是使用npm進行資源包下載下傳很多時候會因為網速過于緩慢導緻資源包下載下傳失敗,是以多數情況下都會選擇使用cnpm。但是有一點需要注意,cnpm在鏡像的過程中,存在一個時間差,有可能因為這個時間差造成資源包的版本差異,進而導緻項目報錯無法運作的情況,當大家發現有因為資源包而報錯的情況時,可以嘗試用npm重新下載下傳一次資源包。
(5)安裝Vue.js的腳手架工具。輸入以下指令,運作結果如圖3-12所示。
(6)建立項目。建立Vue.js 項目,命名為demo1,如圖3-13所示。
然後連續按5次Enter鍵預設選項。
注意:5次Enter鍵,分别預設5個選項,其意義是:第1項Project name,代表項目名稱,預設是demo1;第2項Project description,代表對此項目進行一個簡短的說明,預設是A Vue.js project;第3項Author,代表輸入作者姓名,預設是暫不設定作者姓名;第4項License,代表軟體授權許可協定類型,預設是MIT(代表作者隻想保留版權,而無其他限制);第5項Use sass,代表是否使用sass,預設是No。
項目建立完成後,切換到項目目錄下:
cd demo1
安裝依賴:
cnpm install
(7)運作初始項目。在項目目錄demo1下,執行以下指令,結果如圖3-14所示。
在運作了項目以後,浏覽器會自動打開如圖3-15所示的頁面,并通路以下網址:
當大家見到如圖3-15所示的頁面,代表我們建立的Vue.js項目成功了。
(8)項目目錄結構。如圖3-16所示,使用VS Code編輯器将項目demo1打開,可以看到項目的目錄結構,由于我們僅僅需要示範導航欄這一個功能,是以可以将代碼都寫在App.vue中。
注意:VS Code是一款專門做前端代碼編輯的編輯器,是完全免費的,而且不需要安裝,下載下傳以後就可以直接打開。在這裡可以使用VS Code打開項目,大家也可以選擇自己喜歡使用的前端代碼編輯器,并沒有特殊限制。
(9)編輯電商導航欄所需代碼。将App.vue中的代碼替換如下:
HTML部分:
<template>
<div id="app">
<div class="all">
<div class="one">
<div class="oneType" v-for="(item,index) in one" :key="index">
<b>{{one[index]}}</b>
</div>
</div>
<div class="twothreefour">
<div class="two">
<div class="twoType"
v-for="(item,index) in two" :key="index"
@mouseenter="open(index)">
<b>{{two[index]}}</b>
</div>
</div>
<div class="threefour" v-if="flag"
@mouseleave="close()">
<div class="threefourType" v-for="(item,index) in three" :key=
"index">
<span class="three">{{three[index]}}</span>
<span class="four" v-for="(item4,index4) in four" :key="index4">
{{four[index4]}}</span>
</div>
</div>
</div>
</div>
</div>
</template>
JavaScript部分:
<script>
export default {
name: 'app',
data () {
return {
one:['一級類目','一級類目','一級類目','一級類目','一級類目'],
two:['二級類目1','二級類目2','二級類目3','二級類目4','二級類目5'],
three:[],
four:['四級類目','四級類目','四級類目','四級類目','四級類目'],
flag:false
}
},
methods: {
open(index){
var index=index+1;
var i=index+"";
this.three=['三級目錄'+i,'三級目錄'+i,'三級目錄'+i,'三級目錄'+i,'三級目錄
'+i]
this.flag=true
},
close(){
this.flag=false
}
},
}
</script>
CSS樣式部分如下:
<style>
*{
/* 樣式初始化 */
box-sizing: border-box;
margin: 0;
padding: 0;
}
.all{
/* 将整個導航欄元件做整體設定 */
/*寬度占浏覽器80%,高度400px;背景為灰色;上外邊距50px; 左右居中*/
/* 設定為flex彈性盒子,并且定義為高度不夠自動折行模式,用于橫向排列子元素 */
width: 80%;
height: 400px;
background:#eee;
margin: 50px auto;
display: -webkit-flex; /* Safari */
display: flex;
flex-wrap: wrap;
}
.one{
/* 設定一級類目所占地區的樣式,寬度占滿all盒子的100% */
width: 100%;
height: 50px;
background: #FF8888;
display: flex;
display: -webkit-flex; /* Safari */
flex-wrap: wrap;
/* 彈性盒子内部的子元素都均勻排列成一橫排,并且左右兩邊都留有一定空隙 */
justify-content: space-around;
}
.oneType{
width: 20%;
height: 50px;
line-height: 50px;
text-align: center;
}
.oneType:hover{
background-color:chocolate;
color: #eee;
}
.twothreefour{
/* 盛放二、三、四級目錄的盒子 */
width: 100%;
height: 350px;
background: #66FF66;
display: -webkit-flex; /* Safari */
display: flex;
flex-wrap: wrap;
/* 彈性盒子内部的子元素都均勻排列成一橫排,并且左右兩邊都不留白隙 */
justify-content: space-between;
}
.two{
/* 設定盛放二級類目的彈性盒子 */
width: 15%;
height: 100%;
background: #77FFCC;
display: -webkit-flex; /* Safari */
display: flex;
/* 彈性盒子内部的子元素從上到下排成一列 */
flex-direction: column;
}
.twoType{
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
background: #EEFFBB;
}
.twoType:hover{
background-color:black;
color: #eee;
}
.threefour{
width: 40%;
margin-right: 45%;
height: 100%;
background: #33FFDD;
display: -webkit-flex; /* Safari */
display: flex;
flex-direction: column;
}
.threefourType{
margin: 10px auto;
}
.three{
font-family: 微軟雅黑, 黑體;
font-size: 16px;
font-weight: 800;
}
.four{
font-family: 宋體;
font-size: 12px;
font-weight: 400;
}
</style>
重新運作項目demo1,然後在浏覽器中通路
http://localhost:8080/,我們使用Vue.js開發的最簡易的電商導航欄效果圖,如圖3-17所示。
3.2 為什麼不用傳統建表方式建類别表
在3.1.2節中,我們建立了一個前端電商平台導航欄項目demo1。通過demo1項目中App.vue的代碼不難看出,在完整的前後端分離項目中,前端的Vue.js項目,是将網絡請求通過API發送給後端項目,進而擷取資料并且指派給前端項目的data,替換原本的data内容。這也是實作前後端分離的基本原理。
在本節中,我們将建立一個Django後端項目demo2,與前端的Vue.js項目demo1組成一個前後端分離項目,通過實際項目中前後端資料之間的調試來解述為什麼傳統的建表方式無法滿足大型電商網站的類别表需求。
注意:傳統的建表方式雖然可解決大部分功能需求,但是當遇到諸如大型電商網站類别表這種級别的開發需求時,傳統建表方式就顯得捉襟見肘了。是以不建議大家跳過此節内容直接看下一節。
3.2.1 使用PyCharm建立後端示範項目
PyCharm是一種Python的IDE,是廣大Python程式員非常喜歡的一款IDE。本書的讀者定位是具有一定Python基礎的人,相信許多讀者的計算機上即使沒有Office,也有一款PyCharm。此處不再介紹關于PyCharm的下載下傳與安裝步驟,請讀者自行檢視相關介紹。
(1)如圖3-18所示,建立Django項目并命名為demo2,同時建立App,命名為app01。
(2)如圖3-19所示,在PyCharm中打開項目終端,安裝相關依賴包:
注意:這裡也可以使用cmd視窗,通過指令行切換到項目目錄下來執行依賴包的下載下傳和安裝指令,隻不過這樣顯然要麻煩很多。
(3)在demo2/demo2/settings.py中注冊rest_framework:
INSTALLED_APPS = [
'Django.contrib.admin',
'Django.contrib.auth',
'Django.contrib.contentTypes',
'Django.contrib.sessions',
'Django.contrib.messages',
'Django.contrib.staticfiles',
'app01.apps.App01Config',
'rest_framework'
]
(4)在demo2/app01/models.py中建立類别表:
from Django.db import models
from datetime import datetime
# Create your models here.
class Type1(models.Model):
"""
一級類目
"""
name=models.CharField(max_length=10,default="",verbose_name="類目名")
add_time = models.DateTimeField(default=datetime.now, verbose_name='
添加時間')
class Meta:
verbose_name = '商品類别'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Type2(models.Model):
"""
二級類目
"""
parent=models.ForeignKey(Type1,verbose_name="父級類别",
null=True,blank=True,on_delete=models.CASCADE)
name=models.CharField(max_length=10,default="",verbose_name="類目名")
add_time = models.DateTimeField(default=datetime.now, verbose_name='
添加時間')
class Meta:
verbose_name = '商品類别2'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Type3(models.Model):
"""
三級類目
"""
parent=models.ForeignKey(Type2,verbose_name="父級類别",
null=True,blank=True,on_delete=models.CASCADE)
name=models.CharField(max_length=10,default="",verbose_name="類目名")
add_time = models.DateTimeField(default=datetime.now, verbose_name='
添加時間')
class Meta:
verbose_name = '商品類别3'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Type4(models.Model):
"""
四級類目
"""
parent=models.ForeignKey(Type3,verbose_name="父級類别",
null=True,blank=True,on_delete=models.CASCADE)
name=models.CharField(max_length=10,default="",verbose_name="類目名")
add_time = models.DateTimeField(default=datetime.now, verbose_name='
添加時間')
class Meta:
verbose_name = '商品類别4'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
然後同第(2)步一樣,打開項目終端,執行資料更新指令:
Python manage.py makemigrations
Python manage.py migrate
如圖3-20所示,當資料表建構成功後,在項目目錄中會生成一個db.sqlite3資料庫檔案。
(5)手動添加一些資料。
我們不需要其他的資料庫操作軟體,隻要将圖3-20中所示的db.sqlite3檔案直接拖曳到PyCharm的Database視窗内,如圖3-21所示,就可以直接對項目demo2的所有資料表進行增、删、改操作了。
注意:因為本章所介紹的是一個資料量很小的項目,是以我們并沒有使用MySQL資料庫,而是直接使用了Django自帶的sqlite3資料庫,的确友善了許多。但是sqlite3的局限性是隻适合資料量級比較小的資料庫服務,一旦涉及資料量比較龐大的項目,就要選擇使用MySQL資料庫或者其他資料庫。
如圖3-22所示,我們通過PyCharm的Database界面,手動向一級類目表中增加了5條資料記錄。
如圖3-23所示,我們給二級類目表内手動添加了5條資料記錄。
如圖3-24所示,同樣給三級類目表内手動添加了5條資料記錄。
如圖3-25所示,在四級類目表内,我們手動添加了11條資料記錄。
3.2.2 完善demo2的背景邏輯代碼
本節中會将demo2的業務邏輯補充完整,為下一節前後端項目聯合調試做好準備。
(1)在app01目錄下建立序列化子產品serializers.py,建立4個類别表的序列化類:
from rest_framework import serializers #引入序列化子產品
from .models import Type1,Type2,Type3,Type4 #引入所有資料表類
class Type1ModelSerializer(serializers.ModelSerializer):
class Meta:
model=Type1
fields="__all__"
class Type2ModelSerializer(serializers.ModelSerializer):
class Meta:
model=Type2
fields="__all__"
class Type3ModelSerializer(serializers.ModelSerializer):
class Meta:
model=Type3
fields="__all__"
class Type4ModelSerializer(serializers.ModelSerializer):
class Meta:
model=Type4
fields="__all__"
(2)在app01/views.py中,編寫通路4個類别表的視圖邏輯代碼:
#引入序列化類
from .serializers import Type1ModelSerializer,Type2ModelSerializer
from .serializers import Type3ModelSerializer,Type4ModelSerializer
#引入資料表
from .models import Type1,Type2,Type3,Type4
#引入rest_framework相關子產品
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer, BrowsableAPIRenderer
# Create your views here.
class Type1View(APIView):
"""
all Type1
"""
renderer_classes = [JSONRenderer]
def get(self, request, format=None):
Types=Type1.objects.all()
Types_serializer = Type1ModelSerializer(Types, many=True)
return Response(Types_serializer.data)
class Type2View(APIView):
"""
all Type2
"""
renderer_classes = [JSONRenderer]
def get(self, request, format=None):
Types=Type2.objects.all()
Types_serializer = Type2ModelSerializer(Types, many=True)
return Response(Types_serializer.data)
class Type3View(APIView):
"""
all Type3
"""
renderer_classes = [JSONRenderer]
def get(self, request, format=None):
Types=Type3.objects.all()
Types_serializer = Type3ModelSerializer(Types, many=True)
return Response(Types_serializer.data)
class Type4View(APIView):
"""
all Type4
"""
renderer_classes = [JSONRenderer]
def get(self, request, format=None):
Types=Type4.objects.all()
Types_serializer = Type4ModelSerializer(Types, many=True)
return Response(Types_serializer.data)
注意:這裡用到了Django REST framework的選擇器,大家可以根據自己的喜好,選擇使用JSONRenderer模式還是BrowsableAPIRenderer。
(3)在demo2/urls.py中添加路由代碼:
from Django.contrib import admin
from Django.urls import path
#引入視圖類
from app01.views import Type1View,Type2View,Type3View,Type4View
urlpatterns = [
path('admin/', admin.site.urls),
path('api/Type1/',Type1View.as_view()),
path('api/Type2/',Type2View.as_view()),
path('api/Type3/',Type3View.as_view()),
path('api/Type4/',Type4View.as_view())
]
3.2.3 前後端項目聯合調試
現在,前端項目demo1和後端項目demo2都完成了,可以正式開始前後端聯合調試的工作了。
(1)運作demo2項目。如圖3-26所示,使用PyCharm可以通過右上方的運作項目按鈕,一鍵便捷啟動運作demo2項目。
(2)給demo1項目安裝網絡請求子產品axios。在cmd視窗執行安裝axios子產品指令,結果如圖3-27所示。
注意:axios是前端項目中非常主流的一款提供網絡請求功能的第三方子產品,我們在使用cnpm進行安裝的時候,不要忘記在指令的後面加上--save,将子產品的注冊資訊寫入前端項目的配置資訊中。
(3)改寫前端項目,在demo1/src/App.vue中,style樣式标簽内的代碼不做改變,其他代碼修改如下:
<template>
<div id="app">
<div class="all">
<div class="one">
<div class="oneType" v-for="(item,index) in one" :key="index">
<b>{{one[index].name}}</b>
</div>
</div>
<div class="twothreefour">
<div class="two">
<div class="twoType"
v-for="(item,index) in two" :key="index"
@mouseenter="open(index)">
<b>{{two[index].name}}</b>
</div>
</div>
<div class="threefour" v-if="flag"
@mouseleave="close()">
<div class="threefourType" v-for="(item,index) in three1" : key=
"index">
<span class="three">{{three1[index]}}</span>
<span class="four" v-for="(item4,index4) in four1" :key="index4">
{{four1[index4]}} </span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Axios from 'axios';
export default {
name: 'app',
data () {
return {
one:[],
two:[],
three:[],
four:[],
flag:false,
three1:[],
four1:[]
}
},
methods: {
getData(){
const api='http://127.0.0.1:8000/';
var api1=api+'api/Type1/';
var api2=api+'api/Type2/';
var api3=api+'api/Type3/';
var api4=api+'api/Type4/';
var Type1=[];
var Type2=[];
var Type3=[];
var Type4=[];
Axios.get(api1)
.then(function (response) {
// console.log(response);
for(var i=0;i<response.data.length;i++){
// console.log(response.data[i])
Type1.push(response.data[i])
}
// console.log(Type1)
})
.catch(function (error) {
console.log(error);
});
this.one=Type1;
Axios.get(api2)
.then(function (response) {
// console.log(response);
for(var i=0;i<response.data.length;i++){
// console.log(response.data[i])
Type2.push(response.data[i])
}
// console.log(Type2)
})
.catch(function (error) {
console.log(error);
});
this.two=Type2;
Axios.get(api3)
.then(function (response) {
// console.log(response);
for(var i=0;i<response.data.length;i++){
// console.log(response.data[i])
Type3.push(response.data[i])
}
// console.log(Type3)
})
.catch(function (error) {
console.log(error);
});
this.three=Type3;
Axios.get(api4)
.then(function (response) {
// console.log(response);
for(var i=0;i<response.data.length;i++){
// console.log(response.data[i])
Type4.push(response.data[i])
}
// console.log(Type4)
})
.catch(function (error) {
console.log(error);
});
this.four=Type4;
// console.log(this.one)
// console.log(this.two)
// console.log(this.three)
// console.log(this.four)
},
open(index){
// console.log(this.two[index].id)
var temp=[]
for(var i=0;i<this.three.length;i++){
if(this.three[i].parent===index){
temp.push(this.three[i].name)
}
}
console.log(temp)
this.three1=temp;
var temp4=[]
for(var j=0;j<this.four.length;j++){
temp4.push(this.four[j].name)
}
this.four1=temp4
this.flag=true
},
close(){
this.flag=false
}
},
mounted() {
this.getData()
},
}
</script>
注意:考慮到篇幅問題,一級、二級、三級類目遵循了從屬關系,而第四級類目為了展現效果并沒有通過篩選指派,篩選的邏輯原理跟三級類目相同,大家如果有興趣,可以做進一步的優化和完善。
(4)解決跨域問題。在後端Django項目demo2中安裝相關子產品:
pip install Django-cors-headers
然後在settings.py中的注冊裡配置如下:
INSTALLED_APPS = [
'Django.contrib.admin',
'Django.contrib.auth',
'Django.contrib.contentTypes',
'Django.contrib.sessions',
'Django.contrib.messages',
'Django.contrib.staticfiles',
'app01.apps.App01Config',
'rest_framework',
'corsheaders'
]
在settings.py中的MIDDLEWARE裡設定如下:
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', #放到中間件頂部
'Django.middleware.security.SecurityMiddleware',
'Django.contrib.sessions.middleware.SessionMiddleware',
'Django.middleware.common.CommonMiddleware',
'Django.middleware.csrf.CsrfViewMiddleware',
'Django.contrib.auth.middleware.AuthenticationMiddleware',
'Django.contrib.messages.middleware.MessageMiddleware',
'Django.middleware.clickjacking.XFrameOptionsMiddleware',
]
在settings.py中新增配置項,即可解決本項目中的跨域問題。
CORS_ORIGIN_ALLOW_ALL = True
注意:在Python全棧開發的知識體系裡,跨域問題和深淺拷貝,幾乎是逢面試必考的兩個筆試題。不同的是,深淺拷貝在實際項目中很少用到,而跨域問題卻幾乎在每個項目中都有涉及,隻是并非都能被察覺罷了。跨域問題是非常重要的一個知識點,關系到網絡安全,甚至說跨域問題是Web安全中最重要的一環也不為過。我們在這裡隻是先預熱一下,在第9章中将針對跨域問題進行詳細分析。
(5)重新開機前後端,即可看到效果,如圖3-28所示。
總結一下,從本節不難看出,為了擷取類别表資料,前端通過不同的4個API發送了4次網絡請求,這是因為我們假設一個電商平台隻有四級類目。但是現實中這顯然是不可能的,往往一個使用者量越龐大的電商平台,商品的種類越齊全、越細分,就意味着發送網絡請求的倍數也就越多,需要的帶寬也就更多,這個成本是非常龐大的。
抛去帶寬成本不談,假如恰巧你的老闆不在乎錢,不懂技術,又很好騙,但是前端工程師也會在開發擷取類别資料時,因不得不開發冗長的代碼而投訴後端工程師。是以,不要抱有僥幸心理,傳統的建表方式建構大型電商平台的類别表是行不通的。
3.3 使用Django的model實作類别表建立
利用Django架構,Python已經可以通過建立一個類直接建構一個高品質的資料表了。在本節中,我們将建構一個能滿足大型電商網站業務需求的類别表。
3.3.1 四表合一
類别表本來就應該是一張表,我們來建構一張類别表,把上一節用傳統建表方式建立的四張類别表合為一張。
(1)在demo2/app01/models.py中建立類Type。
class Type(models.Model):
"""
商品類别
"""
CATEGORY_TYPE=(
(1,'一級類目'),
(2,'二級類目'),
(3,'三級類目'),
(4,'四級類目')
)
name=models.CharField(default='',max_length=30,verbose_name='類别名',
help_text='類别名')
code=models.CharField(default='',max_length=30,verbose_name='類别code',
help_text='類别code')
desc=models.CharField(default='',max_length=30,verbose_name='類别描述',
help_text='類别描述')
category_Type=models.IntegerField(choices=CATEGORY_TYPE,verbose_name='類别描述',help_text='類别描述')
parent_category=models.ForeignKey('self',null=True,blank=True,verbose_name='父類目錄',
help_text='父類别',related_name='sub_cat',on_delete=models.CASCADE)
is_tab=models.BooleanField(default=False,verbose_name='是否導航',help_text=
'是否導航')
class Meta:
verbose_name='商品類别'
verbose_name_plural=verbose_name
def __str__(self):
return self.name
(2)建表,執行資料更新指令如下:
3.3.2 資料導入
因為在上一節中建立的4個類别表已經手動加入了一些資料,我們沒有必要再用手動加入的方式将這些記錄加入建立的Type表裡,而是選擇使用一種更加友善的方式,即通過Postman将資料以post的方式,加入Type表内。
(1)在demo2/app01/ serializers.py内增加Type表的序列化類:
from .models import Type
class TypeModelSerializer(serializers.ModelSerializer):
class Meta:
model=Type
fields="__all__"
(2)在demo2/app01/views.py内建立TypeView視圖類:
from .models import Type
#導入序列化類
from .serializers import TypeModelSerializer
class TypeView(APIView):
"""
操作類别表
"""
renderer_classes = [JSONRenderer]
def get(self,request,format=None):
types=Type.objects.all()
types_serializer = TypeModelSerializer(types, many=True)
return Response(types_serializer.data)
def post(self,request):
name=request.data.get('name')
category_type=request.data.get('lei')
parent_category_id=request.data.get('parent')
type=Type()
type.name=name
type.category_type=category_type
if parent_category_id:
parent_category=Type.objects.filter(id=parent_category_id).first()
type.parent_category=parent_category
type.save()
type_serializer=TypeModelSerializer(type)
return Response(type_serializer.data)
(3)在demo2/demo2/urls.py内增加路由代碼如下:
from app01.views import TypeView
urlpatterns = [
#......
path('api/type/',TypeView.as_view())
]
(4)運作demo2項目,使用Postman通過post加入資料記錄,如圖3-29所示。
注意:如圖3-29所示,我們之前建立的4個表,非一級類目的父級id(parent)跟四合一以後的表中非一級類目的父級id是不一樣的。
如圖3-30和圖3-31所示,大家在加入資料記錄以後,可以對照一下檢視是否正确。
3.3.3 前後端項目聯合調試
嶄新的資料類别表建構完成了,下面将前端項目改造一下,對接調試我們的類别表,看看可以節省多少前端的工作量。
(1)在demo1/src/App.vue原來的基礎上,隻修改
<script>
import Axios from 'axios';
export default {
name: 'app',
data () {
return {
type:[],
one:[],
two:[],
flag:false,
three1:[],
four1:[]
}
},
methods: {
getData(){
const api='http://127.0.0.1:8000/api/type/';
var _this=this
Axios.get(api)
.then(function (response) {
_this.type=response.data;
for(var i=0;i<_this.type.length;i++){
if(_this.type[i].category_type===1){
_this.one.push(_this.type[i])
}
}
for(var j=0;j<_this.type.length;j++){
if(_this.type[j].category_type===2){
_this.two.push(_this.type[j])
}
}
})
.catch(function (error) {
console.log(error);
});
},
open(index){
this.three1=[]
this.four1=[]
var parent=this.two[index].id
for(var i=0;i<this.type.length;i++){
if(this.type[i].parent_category===parent){
this.three1.push(this.type[i].name)
}
if(this.type[i].category_type===4){
this.four1.push(this.type[i].name)
}
}
this.flag=true
},
close(){
this.flag=false
}
},
mounted() {
this.getData()
}
}
</script>
注意:關于同步網絡請求和異步網絡請求的問題,因為考慮到本書主要面對的讀者群體是Django開發者,可能對于同步網絡請求和異步網絡請求的“挖坑”經驗不足,如果大家将上面methods中的getData方法與給this.one和this.two指派分開,就有可能掉到異步網絡請求的“坑”裡去,導緻出現加載出來的數值為空的情況。
為什麼JavaScript異步網絡請求的“坑”這麼多?因為早些年網速比較慢,浏覽器端通過網址向後端伺服器請求擷取資源(代碼、文字、圖檔、音頻、視訊等),如果等所有的資源都加載完成,使用者才可以與網頁做互動,那麼網站将會毫無體驗可言,于是有了AJAX異步網絡請求。異步網絡請求可以了解為,先把最重要的資源(大多時候是代碼,但某些時候是圖檔或視訊)接收到網頁端,使用者就可以操作網頁了,與此同時,其他的資源也在慢慢地加載着。往往網頁中所有的資源還沒加載完,使用者已經通過網頁加載的部分資源,将想要做的事給辦完啦,然後就可以關閉網頁了。
這無疑是解決網頁加載資源過慢問題的一個絕佳方案。但異步網絡請求所帶來的一個令人煩惱的問題就是,當想要通過網絡請求擷取一些資料,然後用這些資料對網頁的某些變量做初始化時,網絡請求還沒來得及響應,這些變量就已經完成了初始化,當然,所有變量都初始化為null。是以為了避免異步網絡請求所帶來的問題,除非開發者對JavaScript運用得比較熟練,不然最好不要将this.one和this.two的初始化與getData方法分離。
(2)運作前端demo1和後端demo2,檢視效果圖,如圖3-32所示。我們用四分之一的代碼量,完成了一個更加強大的類别表。細心的讀者可以發現,我們的類别表還有一些字段沒有用上,不然功能還會更加強大,這些就交給讀者去發現和體驗吧。