Django框架電商網站開發流程(Python)
一、配置
1 建立專案和應用
- 配置專案setting.py(應用,templates路徑,資料庫,STATICFILES_DIRS)
- 配置專案urls.py,namespace起名最好和應用名一樣,不容易混淆
2 配置應用
- 在每個應用中建立urls.py
- 同時對應該應用的views.py,同步配置檢視函式,name和檢視函式以及url的名字最好保持一致
3 建立相應的資料夾,將前臺檔案放入
static
- images
- js
- css
templates
templates下建立每個應用單獨的資料夾,將html檔案放入
二、拆分模板
- 建立父模板
在templates下建立資料夾common;
該資料夾儲存父模板base.html和status.html模板(其他相同程式碼片模板)
父模板留空給子模板寫不同程式碼片
{% block 模板名 %}{% endblock 模板名 %}
繼承父模板
{% extends 父模板名 %}
包含相同程式碼片模板
{% include 'common/status.html' %}
- 在html頁面中修改,繼承父模板
- 記得更改html頁面中static元素
載入靜態檔案
{% load staticfiles %}
靜態檔案寫入方式:
{% static ‘路徑/檔名‘ %}
該路徑以static檔案為相對路徑
三、建立模型
1、建立抽象模型基類
- 在專案下新建資料夾db,儲存AbstractModel.py 在該檔案中建立抽象基類
- 在models.py中
from db.AbstractModel import *
建立模型(繼承抽象基類)和模型管理器 - 表遷移
2、模型操作的原則
如果定義的方法是針對表資料操作,需要定義在自定義管理類中
例如:插入資料、查詢表中的資料如果定義的方法針對是行資料進行操作,需要定義在模型類中
例如:修改某一行資料的某個欄位資料在模型管理類中寫方法可以直接用self;
在模型類中寫方法,需要self.objects;
兩者都可以用 模型類.objects。表級操作用的是模型管理器物件;
行級操作用的是那一行資料物件(例如:self.get(user_id = XX)獲取到的物件)。
模型管理類
- self.all()
模型類
- self.objects.all()
- user = User()
user.objects.all()
出錯點:
模型類要重新寫objects = 管理類(),objects記得寫s,
提示錯誤資訊,User沒有objects
三、註冊功能
頁面 user/register
1 更改register.html中表單提交方式和表單提交的檢視
method=“post” action=“{% url ‘/user/register_handle‘ %}”
2 建立檢視register_handle,處理表單提交事件
views.py中匯入 from .functions import *
functions中匯入 from utils.wrapper import *
檢視中只寫大概的邏輯判斷(根據資訊匹配成功與否判斷是否提交),具體的函式另外同級建立一個檔案functions.py處理,使views保持整潔,邏輯易讀。
(1) 同級建立 functions.py
該檔案主要對該檢視內需要裡獲取的引數進行判斷,只對users來使用,並把判斷結果返回給檢視。
(2) 建立utils資料夾
該資料夾下的模組的函式可以對全域性使用
a. 建立 wrapper.py
在functions.py中寫函式進行判斷後,此時需要用到django的原生函式post獲取資料,我們在專案下再建立一個資料夾utils工具,在utils下建立wrapper.py,在該檔案內對post等原生函式進行封裝,方便呼叫。
舉例:
def post(request, param):
<!--去掉取出資料的兩邊空格-->
return request.POST.get(param, "").strip()
b. 修改html頁面
獲取頁面使用者輸入資料,需要表單中的input標籤的name屬性,去html檔案中進行name屬性的命名修改。
(3) functions.py處理表單提交的資料
- 獲取所有使用者輸入資料
- 建立flag,假設所有資料均為錯誤
- 對每個資料進行逐一格式判斷,更改flag
- 判斷使用者名稱是否已存在
需要查詢使用者表,對資料庫進行操作,對資料庫進行的所有操作均在models.py中進行,寫在uesr管理器中。
models中管理類中寫入方法對資料庫表進行操
- 註冊功能用到了查詢和插入資料兩項
- 方法的引數為request或者是已經處理過的資料
- 查詢該使用者是否存在於資料庫中(引數為使用者名稱)
若通過校驗,獲取資料,存入資料庫
若沒有通過校驗:
a. 檢視之間通訊 from django.contrib import messages
messages也是基於cookie和session的封裝
從一個頁面(檢視)跳轉到另一個頁面(檢視)時,如果需要傳遞引數,可以用get,在URL後面加上?xx=xx,
但是這裡傳遞錯誤訊息,內容較長,用這種方式不合理,就需要一個可以在檢視間進行資料傳輸的工具。這個模組是進行檢視之間通訊,資料傳輸所用,相當於一個佇列,可以放入資料,取出資料,一次性使用。
<!--message.INFO INFO 代表資訊級別,
還有ERROR WARNING ,
這裡僅僅是傳遞一個錯誤資訊,INFO足夠了-->
新增資訊
messages.add_message(request, message.INFO, "資料內容")
取出資訊
mess = messages.get_messages(request)
這裡mess並不是一個佇列,但是是 可以迭代的,可通過for迴圈取出
這裡mess並不是一個佇列,但是是可以迭代的,可通過for迴圈取出
值得注意的是,取出的資料是先進先出,取出,並不能確定哪條資料歸屬於哪個內容下的錯誤提示,
所以要在wrapper.py中對這兩個原生介面進行封裝:
- 新增訊息時給每個訊息加個訊息頭,組成類似於字典的字串“key:value”;
- 取出資料時,根據:對訊息進行切割,組成字典的形式。
3 處理錯誤提示訊息
user/register/當點選註冊時在前端頁面顯示
- 將錯誤提示訊息放在register檢視中取出,記為mess;
- 在index.html中顯示錯誤提示訊息的地方新增一樣的標籤,更改class,並寫入{% mess.key %},會自動尋找字典中值而新增。
- 修改樣式,和前端的提示資訊css樣式一致,更改display:block為顯示;
- 修改js中焦點觸發事件,在觸發焦點時,後臺錯誤提示訊息的標籤隱藏。
4 通過ajax請求,非同步判斷使用者名稱是否存在
js傳送ajax請求後臺判斷使用者名稱是否存在,返回結果給js進行非同步判斷,註冊頁面在使用者輸入使用者名稱時可實時顯示使用者名稱是否存在
四、登入功能
不能進行使用者是否存在驗證,不安全
1 建立處理使用者登入時的檢視
在該檢視中寫入主要邏輯:
如果驗證通過,回到xx頁面;(先返回首頁,後面寫中介軟體處理過後,再判斷session中是否有pre_url,如果有,返回pre_url,沒有則返回主頁)
驗證不通過,重回登入頁面,並提示錯誤訊息
2 在function.py中判斷使用者名稱和密碼輸入的格式是否正確
- 如果格式不正確,直接回到登入頁面
- 如果格式正確,進行使用者名稱和密碼驗證
- 驗證通過:先跳轉到首頁
- 驗證不通過:
通過messages模組,將錯誤提示訊息(使用者名稱或密碼錯誤)放入佇列中;
更改views中的登入檢視,取出message中的錯誤提示訊息,更改登入頁面login.html和js,使錯誤提示訊息在表單提交後顯示。
3 設定cookie和session
(1)思路
- 如果使用者登入時,選擇記住使用者名稱,需要設定使用者登入名的cookie,儲存在瀏覽器;
- 當用戶登入後,需要在每個頁面保持使用者的登入狀態,需要設定使用者登入名的session;
因為使用者每次傳送請求,request中都帶有cookie,cookie中帶有sessionid,可以識別使用者; - 當未登入使用者在商品等頁面點選或跳轉至登入頁面之後,如果登入成功,則直接跳轉回上次的商品瀏覽頁面;
- 當用戶未登入時,不能進入使用者中心,地址和訂單頁面,可使用裝飾器對這三個檢視進行包裝,若點選這個三個連結,直接重定向到登入頁面。
(2)實現
先登入
1. 在warpper.py中對cookie和session進行封裝
response.set_cookie[key] = value
request.get_cookie[key]
response.delete_cookie[key] 如果key不存在什麼也不發生
<!--設定session過期時間:
0為關閉瀏覽器時過期,
整數為多少秒後過期,
不設定或value為None時使用預設的,
Django預設為兩週-->
<!--request.session.set_expiry(value)-->
request.session[key] = value
request.session.get(key,"") 獲取session,key不存在預設為空
request.session[key] 獲取session,key不存在會報錯
del request.session[key]
request.session.flush()
<!--del request.session[key]只是刪除value,key還在,所以一般用request.session.flush()全部刪除-->
2. 使用者登入:
- 設定session,記錄登入狀態
- 若勾選記住使用者名稱,需設定cookie
3. 使用者登出,刪除session
4. 瀏覽商品後再登入,需要:中介軟體–記錄上次的瀏覽頁面
- 在檢視處理url前,記錄上次瀏覽的頁面,process_request和process_view均可,這裡顯然process_request更方便(傳入引數只需要request);
- 設定一個列表,儲存,不需要儲存的URL;
(1)例如,登入、註冊的url,處理表單的URL,使用者中心、地址、訂單的URL,網路圖示favicon.ico等。
(2)這裡也包含了使用者中心、地址、訂單頁面,如果使用者在該頁面登出,再登入,也返回首頁。
(3)如果需求為再登入時,回到之前的使用者中心、地址、訂單頁面,中介軟體中這幾個url就不加入列表。 - 獲取上次登入頁面pre_url=request.get_all_path()的包含帶有?的引數等全部url。
如果url在列表中,則直接返回首頁;
如果不在列表中,則返回上次登入頁面,即pre_url。
!!! 中介軟體修改:
- 由於process_request會記錄一些跳轉頁面,所以這裡更改為process_response
- 將攔截條件新增response.status_code==200,最後一定要返回response。
4 使用者瀏覽頁面的許可權
需求:
若已登入,可以進入使用者中心、訂單、地址、頁面;
若未登入,點選這些連結,回到登入頁面。
這裡使用裝飾器
def permission_verfy(view_func):
def wraper(request,*args,**kwargs):
<!--判斷使用者是否登入-->
if get_session(request, 'username'):
return view_func(request,*args,**keargs)
else:
return redirect(首頁url)
return wrapper
五、使用者中心
1 使用者中心左側選單欄拆分
- 由於左側選單一樣,需要拆分模板;
- 左側選單種當前頁面的欄目有特殊樣式;
方法一:
模板包含的時候可以通過with傳遞引數
{% include 'user/left_menu.html' with flag="order" %}
在通用模板left_menu.html中,進行判斷
{% if flag="order" %}class="active"{% endif %}
方法二(根據列表自動生成選單欄):
使用過濾器:
{% with menus=flag|creat_meau %}
(將引數flag傳入過濾器函式creat_meau,函式返回結果賦給menus)
在過濾器中設定列表,列表中字典根據鍵值對儲存主要引數,
對於特殊樣式,action鍵可以通過三步運算判斷
flag = "order" and "active" or ""
在left_menu.html中for迴圈自動生成選單欄,
2 使用者個人中心修改地址
form表單提交,套路:
- 前臺js驗證輸入是否合法;
- 配製form表單提交方式和提交的url;
- views後臺獲取form表單資料,驗證資料是否合法;
- 合法資料存入資料庫,
- 驗證到不合法資料,將驗證失敗的錯誤提示通過messages加入佇列;
- 前臺html顯示。
- 新增html後臺錯誤提示的標籤;
- 修改後臺錯誤提示的樣式,display=block;
- 修改js,焦點獲取時blur(),將後臺的錯誤提示標籤隱藏;
- 後臺使用者中心頁面view取出messages佇列中的錯誤提示,加入html中。
六、商品
1 建立商品分類模型,商品資訊模型
(1)商品分類模型
- 商品類名
(2)商品資訊模型
- 商品名
- 商品價格
- 商品單位
- 商品簡介
- 商品詳情(採用富文字編輯器)
- 商品狀態(上架,下架)
- 商品圖片
- 商品分類(外來鍵關聯商品分類模型)
- 商品瀏覽量
- 商品銷售量
2 建立測試資料
3 商品首頁的資料顯示
(1)獲取最新商品
(2)獲取最熱門商品
3 商品詳細頁面
(1)設計為帶?引數的url或者正則匹配的url
這裡為/goods/detail/?id=xxx
根據商品id動態生成商品詳細頁面
在商品詳情url的檢視中,通過utl中傳入的引數,get(request,”id”)獲取該商品物件,在html中寫入資料.
(2)獲取兩個最新產品
(3)配製這些圖片的連結
帶引數的?url
{% url "goods:detail" %}?id={{ goods.id }}
或 正則提取url
{% url "goods:detail" goods.id %}
(4)js操作 根據商品價格和數量實時變動總價
js就夠了,不需要ajax,讓後臺來做
(5) 商品瀏覽記錄
a.建立商品瀏覽記錄模型
- 瀏覽使用者(外來鍵關聯使用者表)
- 瀏覽商品(外來鍵關聯商品表)
b.這裡需要使用者id
我們把user_id,新增到session或者cookie中,可以直接獲取
1. 當用戶登入成功,設定cookie,user_id
2. 在商品詳細頁面的檢視中,獲取user_id判斷使用者是否登入,如果使用者已登入,獲取goods_id,將它們存入資料庫;
3. 在存入資料庫時,進行判斷:
如果使用者存在:
獲取使用者的瀏覽商品record_goods_list,並按更新時間排序
1 如果該商品存在:
直接save,可以更新時間
2 如果商品不存在:
判斷商品數量:
如果商品數量<5:直接存入資料庫;
否則:獲取更新時間最早的一條資料物件,record_goods_list[0],對其進行更新覆蓋。
- 去使用者中心,在使用者中心檢視獲取瀏覽記錄,在html中顯示。
4 商品列表頁
(1)需求:
- 根據分類顯示商品
- 根據銷量、人氣、預設進行商品排序
(2)設計url
可以正則匹配兩個引數,param1為商品分類id,param2為商品排序方式
^"/goods/detail/(\d)/(\w+)"/$
檢視獲取商品
goods = Goods.objects.filter(分類欄位_id=param1).order_by(param2)
(3)商品列表分頁
分頁器 Paginator
頁碼用?在url中傳入引數
from django.core.paginator import Paginator
<!--建立分頁器-->
paginator = Paginator(所有商品物件,每頁放置商品的個數)
<!--獲取當前頁碼-->
page_now = get(request,"page","1") # 給預設頁碼為1
<!--獲取當前頁碼內商品物件-->
goods_now = paginator.page(page_now)
---常用屬性---
paginator屬性:
paginator.page_range 頁碼範圍,可以在html中for迴圈生成頁碼
goods_now的屬性、方法:
goods_now.number 當前頁碼
has_next() 是否有下一頁
has_previous() 是否有上一頁
next_page_number() 返回下一頁頁碼
previous_page_number() 返回上一頁頁碼
七、購物車
1 建立購物車模型
- 使用者 (關聯使用者表使用者id)
- 商品 (關聯商品表商品id)
- 商品數量
2 在商品詳細頁面做新增購物車點選事件
(1)非同步更新購物車數量
ajax向後臺傳入資料(新增商品數量,商品id),配製新增購物車url和檢視,返回json資料(現有購物車數量),非同步更新購物車數量標籤。
js如何獲取商品id?
在detail頁面中新增一個隱藏域,js可以通過標籤獲取val。
<input type="hidden" value={{ goods.id }} class="xx">
- 進行判斷,並存入資料庫
1 如果使用者未登入,跳轉至登入頁面
2 如果已登入,
判斷該使用者是否在購物車表中
(1)存在:獲取使用者id的所有商品物件
判斷該商品是否存在:
如果存在,更新數量
如果不存在,直接存入
(2)不存在該使用者
直接存入
(2)全域性顯示購物車商品數量
a.需求:
- 由於商品首頁,列表頁,詳情頁面均由購物車的標籤,所以需要全域性實時顯示購物車數量。
b.實現:
- 由於每次進行頁面請求,均會發出request,所以讓購物車商品總數成為request的一個屬性,這樣每次request請求都會攜帶這個資料。
- 不能在每個檢視中都對這個屬性進行設定,這樣程式碼重複度太高,也同樣秉持著原始碼最好不要進行修改的原則,所以寫一個裝飾器,裝飾器內設定這個屬性。
- 但是,不是所有的頁面均需要該屬性,在商品首頁、列表頁、詳情頁的檢視上新增該裝飾器。
3 購物車頁面
(1)從資料庫獲取資料,資料顯示
(2)js操作,商品數量、單品總價,選中商品總價格實時變化
- 這裡需要後臺先更新資料庫的購物車表資料,前臺才能更新資料,所以這裡的ajax請求需要同步,後臺更新資料之後,前臺html才顯示更新;
- ajax預設非同步async:true,這裡需要具體配置;
- post方式不同於get方式可以被django直接得到,因為django為post加入了csrf保護;
- 解決方法一:在HTML頁面中新增一個隱藏域{% csrf_token %},手動獲取name和value,傳送給後臺;
<input type='hidden' name='csrfmiddlewaretoken' value='MErcUv3Df23t84YsP4skrRbIWCAtVwpe' />
- 解決方法二(不可取):在 settings.py 中 MIDDLEWARE_CLASSES 中 註釋掉’django.middleware.csrf.CsrfViewMiddleware’
<!--get請求-->
$.ajax({
url:"請求的url",
data:{json資料},
dataType:"json", // 預設為json資料格式
async:false, // 設定同步
type:"get", // 預設為get請求方式
}).done(function(data){
獲得從後臺返回的json資料
}).fail(function(){
alert("伺服器超時,請重試!");
})
<!--post請求 -->
$.ajax({
url:"請求的url",
data:{"csrfmiddlewaretoken":xxxxxxxx},
dataType:"json", // 預設為json資料格式
async:false, // 設定同步
type:"post", // 預設為get請求方式
}).done(function(data){
獲得從後臺返回的json資料
}).fail(function(){
alert("伺服器超時,請重試!");
})
- 將實時更新頁面單品總價,商品總數量,購物車總價等內容,寫一個通用的函式,方便呼叫,將點選事件的標籤當做引數傳給這個函式即可。這些標籤可以通過$(this)父級標籤往下找。
- 注意:在ajax請求內部,不能直接操作$(this),可以在外部設定一個旗子flag=false,在ajax內部操作flag;在外部通過對flag值的判斷來繼續執行。
(3) 提交訂單
a. 為購物車HTML頁面新增form,提交地址為提交訂單頁面,這時就要換到order應用下操作了。
b. js寫入表單提交事件,判斷是否選中商品,若選中,則提交表單,未選中,提示使用者。
4 提交訂單頁面
- 獲取商品id列表
request.POST.getlist("標籤name屬性")
- 通過商品id列表獲取商品物件列表,HTML渲染