果生鮮項目介紹
- 1.商業模式說明
- 2.開發流程介紹
- 3.項目需求分析
- 4.項目架構分析
- 5.數據庫設計
- 6.模型類說明
- 7.創建dailyfresh項目
- 8.展示注冊頁面
- 9.視圖函數的get和post請求處理
- 10.類視圖
商業模式介紹
目的:知道天天生鮮項目屬於那種商業模式
1.B2B--企業對企業
- B2B (Business to Business)是指進行電子商務交易的供需雙方都是商家(或企業、公司),她(他)們使用了互聯網的技術或各種商務網絡平台,完成商務交易的過程。電子商務是現代 B2B marketing 的一種具體主要的表現形式。
- 案例:阿里巴巴、慧聰網
2.C2C--個人對個人
- C2C 即 Customer to Customer,意思就是消費者個人間的電子商務行為。比如一個消費者有一台電腦,通過網絡進行交易,把它出售給另外一個消費者,此種交易類型就稱為 C2C 電子商務。```
- 案例:淘寶、易趣、瓜子二手車
3.B2C--企業對個人
- B2C 是 Business to Customer 的縮寫,而其中文簡稱為“商對客”。“商對客”是電子商務 的一種模式,也就是通常說的直接面向消費者銷售產品和服務商業零售模式。這種形式的電子商務一般以網絡零售業為主,主要借助於互聯網開展在線銷售活動。B2C 即企業通過互 聯網為消費者提供一個新型的購物環境——網上商店,消費者通過網絡在網上購物、網上支付等消費行為。
- 案例:唯品會、樂蜂網
4.C2B--個人對企業
- C2B(Consumer to Business,即消費者到企業),是互聯網經濟時代新的商業模式。這一模式改變了原有生產者(企業和機構)和消費者的關系,是一種消費者貢獻價值(Create Value),企業和機構消費價值(Consume Value)。C2B 模式和我們熟知的供需模式(DSM, Demand Supply Model)恰恰相反,真正的 C2B 應該先有消費者需求產生而后有企業生產,即先有消費者提出需求,后有生產企業按 需求組織生產。通常情況為消費者根據自身需求定制產品和價格,或主動參與產品設計、生產和定價,產品、價格等彰顯消費者的個性化需求,生產企業進行定制化生產。
- 案例:海爾商城、 尚品宅配
5.O2O--線上到線下
- O2O 即 Online To Offline(在線離線/線上到線下),是指將線下的商務機會與互聯網結合,讓互聯網成為線下交易的平台,這個概念最早來源於美國。O2O 的概念非常廣泛,既可涉及到線上,又可涉及到線下,可以通稱為 O2O。主流商業管理課程均對 O2O 這種新型的商業模式有所介紹及關注。```
- 案例:美團、餓了嗎
6.F2C--工廠到個人
- F2C 指的是 Factory to customer,即從廠商到消費者的電子商務模式
- 案例:戴爾
7.B2B2C--企業--企業--個人
-
B2B2C 是一種電子商務類型的網絡購物商業模式,B 是 BUSINESS 的簡稱,C 是 CUSTOMER 的簡稱,第一個 B 指的是商品或服務的供應商,第二個 B 指的是從事電子商務的企業,C 則是表示消費者。第一個 BUSINESS,並不僅僅局限於品牌供應商、影視制作公司和圖書出版商,任何的商品供應商或服務供應商都能可以成為第一個 BUSINESS;第二 B 是 B2B2C 模式的電子商務企業,通過統一的經營管理對商品和服務、消費者終端同時進行整合,是廣大供應商和消費 者之間的橋梁,為供應商和消費者提供優質的服務,是互聯網電子商務服務供應商。C 表示 消費者,在第二個 B 構建的統一電子商務平台購物的消費者。B2B2C 的來源於目前的 B2B、B2C 模式的演變和完善,把 B2C 和 C2C 完美地結合起來,通過 B2B2C 模式的電子商務企業構建自己的物流供應鏈系統,提供統一的服務。
-
案例:京東商城、天貓商城
開發流程介紹
提示:
- 1.架構設計
- 分析可能用到的技術點
- 前后端是否分離
- 前端使用哪些框架
- 后端使用哪些框架,Django、Flask、......
- 選擇什么數據庫
- 如何實現緩存
- 如何搭建分布式服務
- 如何管理源代碼
- ......
- 2.數據庫設計
- 數據庫表的設計至關重要
- 根據項目需求,設計合適的數據庫表
- 數據庫表在前期如果設計不合理,后期隨需求增加將很難維護
- ......
- 3.集成測試
- 在測試階段要留意測試系統發送的BUG郵件
- 4.前后端是否分離
- Django框架,一般是前后端不分離,后端需要處理前端一些業務邏輯
- 后端使用模板動態渲染好html頁面,響應給客戶端
- 后端維護html頁面時,壓力大,但是是必須承受的
- 也可以實現前后端分離
- 不使用模板,直接響應JSON等一些數據給客戶端
- Flask框架,一般是前后端分離
- Django框架,一般是前后端不分離,后端需要處理前端一些業務邏輯
項目需求分析
靜態文件預覽
頁面需求分析
- register.html
- 注冊頁面,已加入了初步的表單驗證效果,此效果在課程中已講述如何制作
- login.html
- 登錄頁面
- user_center_info.html
- 用戶中心-用戶信息頁 用戶中心功能一,查看用戶的基本信息
- user_center_order.html
- 用戶中心-用戶訂單頁 用戶中心功能二,查看用戶的全部訂單
- user_center_site.html
- 用戶中心-用戶收貨地址頁 用戶中心功能三,查看和設置用戶的收貨地址
- index.html
- 網站首頁,頂部“注冊|登錄”和用戶信息是切換顯示的,商品分類菜單點擊直接鏈接滾動到本頁面商品模塊。首頁已加入幻燈片效果。此效果在課程中已講述如何制作
- list.html
- 商品列表頁,商品分類菜單鼠標懸停時切換顯示和隱藏,點擊菜單后鏈接到對應商品的列表頁
- detail.html
- 商品詳情頁,某一件商品的詳細信息
- cart.html
- 我的購物車頁,列出已放入購物車上的商品
- place_order.html
- 提交訂單頁
項目架構分析
項目架構
項目功能模塊
功能模塊說明
1.用戶模塊
register.html
login.html
user_center_info.html
user_center_order.html
user_center_site.html
- 注冊頁
- 顯示注冊頁面
- 實現用戶的注冊邏輯
- 登錄頁
- 顯示登錄頁面
- 實現用戶的登錄邏輯
- 用戶中心
- 用戶中心信息頁:
- 顯示用戶的信息,包括用戶名、電話和地址,同時頁面下方顯示出用戶最近瀏覽的商品信息
- 用戶中心地址頁:
- 顯示用戶的當前收件地址,頁面下方的表單可以新增用戶的收貨地址
- 用戶中心訂單頁:
- 顯示出當前用戶的訂單信息
- 用戶中心信息頁:
2.商品模塊
index.html
list.html
detail.html
- 首頁
- 點擊頁面上相應的商品種類鏈接,跳轉到相應的商品列表頁
- 每個種類的商品顯示4個,按照默認排序方式進行顯示
- 每個種類商品后面顯示出3個最新的商品標題
- 點擊某一個商品時跳轉到商品的詳情頁面
- 如果用戶已經登錄,頁面頂部顯示登錄用戶的信息
- 商品詳情頁
- 顯示出某一個商品的詳細信息
- 頁面的左下方顯示出該種類商品的2個最新商品信息
- 商品列表頁
- 顯示出某一個種類商品的列表數據
- 頁面的左下方顯示出該種類商品的2個最新商品信息
- 其他
- 通過頁面搜索框搜索商品信息
3.購物車模塊
cart.html
- 列表頁和詳情頁將商品添加到購物車
- 用戶登錄后,除注冊頁和登錄頁之外,其他頁面上顯示登錄用戶購物車中商品的數目
- 購物車頁面:對用戶購物車中商品的操作。如選擇某件商品,增加或減少購物車中商品的數目
4.訂單模塊
place_order.html
- 提交訂單頁面:顯示用戶准備購買的商品信息
- 用戶中心信息頁顯示用戶的訂單信息
項目整體架構
數據庫設計
提示
- 項目開發需要數據來驅動,所以我們需要先思考數據庫表該如何設計
- 只有先設計好了數據庫表,后台運營人員才能通過后台管理平台向數據庫中發布數據
- Django內嵌ORM框架,我們是通過模型類以面向對象思想操作數據庫
- 一個模型類映射數據庫中一張表
需要設計的數據庫表
- 1.用戶模塊
- 用戶表
- 用戶地址表
- 2.商品模塊
- 商品類別表
- 商品SPU表
- 商品SKU表
- 商品圖片表
- 主頁輪播商品展示表
- 主頁分類商品展示表
- 主頁促銷活動展示表
- 3.訂單模塊
- 訂單信息表
- 訂單商品表
- 4.購物車模塊數據存儲在redis中
數據庫表詳情
基類:BaseModel
- 模型類補充字段,作為基類使用
字段名 | 字段類型 | 字段選項 | 字段說明 |
---|---|---|---|
create_time | DateTimeField | auto_now_add=True | 創建時間 |
update_time | DateTimeField | auto_now=True | 更新時間 |
用戶表
- 模型類名:
User
;表名:df_users
- 使用Django自帶的用戶認證系統管理
- 繼承自:
AbstractUser
,導包from django.contrib.auth.models import AbstractUser
-
遷移前,需要在
settings.py
文件中設置:AUTH_USER_MODEL = '應用.用戶模型類'
用戶地址表
- 模型類名:
Address
;表名:df_address
- 使用
User
模型類作為外鍵約束地址信息屬於哪個用戶
字段名 | 字段類型 | 字段選項 | 字段說明 |
---|---|---|---|
id | IntegerField | primary_key=True | 主鍵字段 |
user | ForeignKey | 外鍵為User模型 | 約束地址屬於哪個用戶 |
receiver_name | CharField | max_length=20 | 收件人 |
receiver_mobile | CharField | max_length=11 | 聯系電話 |
detail_addr | CharField | max_length=256 | 詳細地址 |
zip_code | CharField | max_length=6 | 郵政編碼 |
商品類別表
- 模型類名:
GoodsCategory
;表名:df_goods_category
字段名 | 字段類型 | 字段選項 | 字段說明 |
---|---|---|---|
id | IntegerField | primary_key=True | 主鍵字段 |
name | CharField | max_length=20 | 商品類別名稱 |
logo | CharField | max_length=100 | 商品類別標識 |
image | ImageField | upload_to="category" | 商品類別圖片 |
商品SPU表
- 模型類名:
Goods
;表名:df_goods
字段名 | 字段類型 | 字段選項 | 字段說明 |
---|---|---|---|
id | IntegerField | primary_key=True | 主鍵字段 |
name | CharField | max_length=100 | 商品名稱 |
desc | HTMLField | blank=True | 商品詳細介紹 |
商品SKU表
- 模型類名:
GoodsSKU
;表名:df_goods_sku
字段名 | 字段類型 | 字段選項 | 字段說明 |
---|---|---|---|
id | IntegerField | primary_key=True | 主鍵字段 |
category | ForeignKey | 外鍵為GoodsCategory模型 | 約束該商品的類別 |
goods | ForeignKey | 外鍵為Goods模型 | 約束該商品的SPU |
name | CharField | max_length=100 | 商品名稱 |
title | CharField | max_length=200 | 商品簡介 |
unit | CharField | max_length=10 | 銷售單位 |
price | DecimalField | max_digits=10, decimal_places=2 | 商品價格 |
stock | IntegerField | default=0 | 商品庫存 |
sales | IntegerField | default=0 | 商品銷量 |
default_image | ImageField | upload_to="goods" | 商品默認圖片 |
status | BooleanField | default=True | 是否上線 |
商品圖片表
- 模型類名:
GoodsImage
;表名:df_goods_image
字段名 | 字段類型 | 字段選項 | 字段說明 |
---|---|---|---|
id | IntegerField | primary_key=True | 主鍵字段 |
sku | ForeignKey | 外鍵為GoodsSKU模型 | 約束圖片屬於哪個商品的 |
image | ImageField | upload_to="goods" | 商品圖片 |
主頁輪播商品展示表
- 模型類名:
IndexGoodsBanner
;表名:df_index_goods
字段名 | 字段類型 | 字段選項 | 字段說明 |
---|---|---|---|
id | IntegerField | primary_key=True | 主鍵字段 |
sku | ForeignKey | 外鍵為GoodsSKU模型 | 約束該商品的sku |
image | ImageField | upload_to="banner" | 商品圖片 |
index | SmallIntegerField | default=0 | 輪播順序 |
主頁分類商品展示表
- 模型類名:
IndexCategoryGoodsBanner
;表名:df_index_category_goods
字段名 | 字段類型 | 字段選項 | 字段說明 |
---|---|---|---|
id | IntegerField | primary_key=True | 主鍵字段 |
category | ForeignKey | 外鍵為GoodsCategory模型 | 約束該商品類型 |
sku | ForeignKey | 外鍵為GoodsSKU模型 | 約束該商品的sku |
display_type | SmallIntegerField | choices=DISPLAY_TYPE_CHOICES | 展示類型:圖片或標題 |
index | SmallIntegerField | default=0 | 展示順序 |
主頁促銷活動展示表
- 模型類名:
IndexCategoryGoodsBanner
;表名:df_index_category_goods
字段名 | 字段類型 | 字段選項 | 字段說明 |
---|---|---|---|
id | IntegerField | primary_key=True | 主鍵字段 |
name | CharField | max_length=50 | 活動名稱 |
url | URLField | 活動鏈接 | |
image | ImageField | upload_to="banner" | 活動商品圖片 |
index | SmallIntegerField | default=0 | 活動商品順序 |
訂單信息表
- 模型類名:
OrderInfo
;表名:df_order_info
字段名 | 字段類型 | 字段選項 | 字段說明 |
---|---|---|---|
order_id | IntegerField | primary_key=True | 主鍵字段 |
user | ForeignKey | 外鍵為User模型 | 下單用戶 |
address | ForeignKey | 外鍵為Address模型 | 下單地址 |
total_count | IntegerField | default=1 | 商品總數 |
total_amount | DecimalField | max_digits=10, decimal_places=2 | 商品總金額 |
trans_cost | DecimalField | max_digits=10, decimal_places=2 | 運費 |
pay_method | SmallIntegerField | choices=PAY_METHOD_CHOICES | 支付方式,定義支付選項 |
status | SmallIntegerField | choices=ORDER_STATUS_CHOICES | 訂單狀態,自定義狀態 |
trade_id | CharField | max_length=100, unique=True | 訂單編號 |
訂單商品表
- 模型類名:
OrderGoods
;表名:df_order_goods
字段名 | 字段類型 | 字段選項 | 字段說明 |
---|---|---|---|
id | IntegerField | primary_key=True | 主鍵字段 |
order | ForeignKey | 外鍵為OrderInfo模型 | 約束是哪個商品訂單 |
sku | ForeignKey | 外鍵為GoodsSKU模型 | 約束訂單商品的sku |
count | IntegerField | default=1 | 訂單商品數量 |
price | DecimalField | max_digits=10, decimal_places=2 | 商品單價 |
comment | TextField | default="" | 評價信息 |
補充:SKU與SPU概念
- SPU = Standard Product Unit (標准產品單位)
- SPU 是商品信息聚合的最小單位,是一組可復用、易檢索的標准化信息的集合,該集合描述了一個產品的特性。
- 通俗點講,屬性值、特性相同的商品就可以稱為一個SPU。
- 例如:iphone7 就是一個SPU,與商家,與顏色、款式、套餐都無關。
- SKU=stock keeping unit(庫存量單位)
- SKU 即庫存進出計量的單位,可以是以件、盒、托盤等為單位。 SKU 是物理上不可分割的最小存貨單元。
- 在使用時要根據不同業態,不同管理模式來處理。在服裝、鞋類商品中使用最多最普遍。
- 例如:紡織品中一個SKU,通常表示:規格、顏色、款式。
模型類說明
用戶模塊
- 模型類名:
User
;表名:df_users
- 使用Django自帶的用戶認證系統管理
- 繼承自:
AbstractUser
,導包from django.contrib.auth.models import AbstractUser
- 遷移前,需要在
settings.py
文件中設置:AUTH_USER_MODEL = '應用.用戶模型類'
from django.db import models from django.contrib.auth.models import AbstractUser from utils.models import BaseModel from django.conf import settings from goods.models import GoodsSKU from itsdangerous import TimedJSONWebSignatureSerializer as Serializer class User(AbstractUser, BaseModel): """用戶""" class Meta: db_table = "df_users" def generate_active_token(self): """生成激活令牌""" serializer = Serializer(settings.SECRET_KEY, 3600) token = serializer.dumps({"confirm": self.id}) # 返回bytes類型 return token.decode() class Address(BaseModel): """地址""" user = models.ForeignKey(User, verbose_name="所屬用戶") receiver_name = models.CharField(max_length=20, verbose_name="收件人") receiver_mobile = models.CharField(max_length=11, verbose_name="聯系電話") detail_addr = models.CharField(max_length=256, verbose_name="詳細地址") zip_code = models.CharField(max_length=6, verbose_name="郵政編碼") class Meta: db_table = "df_address"
商品模塊
from django.db import models from utils.models import BaseModel from tinymce.models import HTMLField class GoodsCategory(BaseModel): """商品類別表""" name = models.CharField(max_length=20, verbose_name="名稱") logo = models.CharField(max_length=100, verbose_name="標識") image = models.ImageField(upload_to="category", verbose_name="圖片") class Meta: db_table = "df_goods_category" verbose_name = "商品類別" # admin站點使用 verbose_name_plural = verbose_name def __str__(self): return self.name class Goods(BaseModel): """商品SPU表""" name = models.CharField(max_length=100, verbose_name="名稱") desc = HTMLField(verbose_name="詳細介紹", default="", blank=True) class Meta: db_table = "df_goods" verbose_name = "商品" verbose_name_plural = verbose_name def __str__(self): return self.name class GoodsSKU(BaseModel): """商品SKU表""" category = models.ForeignKey(GoodsCategory, verbose_name="類別") goods = models.ForeignKey(Goods, verbose_name="商品") name = models.CharField(max_length=100, verbose_name="名稱") title = models.CharField(max_length=200, verbose_name="簡介") unit = models.CharField(max_length=10, verbose_name="銷售單位") price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="價格") stock = models.IntegerField(default=0, verbose_name="庫存") sales = models.IntegerField(default=0, verbose_name="銷量") default_image = models.ImageField(upload_to="goods", verbose_name="圖片") status = models.BooleanField(default=True, verbose_name="是否上線") class Meta: db_table = "df_goods_sku" verbose_name = "商品SKU" verbose_name_plural = verbose_name def __str__(self): return self.name class GoodsImage(BaseModel): """商品圖片""" sku = models.ForeignKey(GoodsSKU, verbose_name="商品SKU") image = models.ImageField(upload_to="goods", verbose_name="圖片") class Meta: db_table = "df_goods_image" verbose_name = "商品圖片" verbose_name_plural = verbose_name def __str__(self): return str(self.sku) class IndexGoodsBanner(BaseModel): """主頁輪播商品展示""" sku = models.ForeignKey(GoodsSKU, verbose_name="商品SKU") image = models.ImageField(upload_to="banner", verbose_name="圖片") index = models.SmallIntegerField(default=0, verbose_name="順序") class Meta: db_table = "df_index_goods" verbose_name = "主頁輪播商品" verbose_name_plural = verbose_name def __str__(self): return str(self.sku) class IndexCategoryGoodsBanner(BaseModel): """主頁分類商品展示""" DISPLAY_TYPE_CHOICES = ( (0, "標題"), (1, "圖片") ) category = models.ForeignKey(GoodsCategory, verbose_name="商品類別") sku = models.ForeignKey(GoodsSKU, verbose_name="商品SKU") display_type = models.SmallIntegerField(choices=DISPLAY_TYPE_CHOICES, verbose_name="展示類型") index = models.SmallIntegerField(default=0, verbose_name="順序") class Meta: db_table = "df_index_category_goods" verbose_name = "主頁分類展示商品" verbose_name_plural = verbose_name def __str__(self): return str(self.sku) class IndexPromotionBanner(BaseModel): """主頁促銷活動展示""" name = models.CharField(max_length=50, verbose_name="活動名稱") url = models.URLField(verbose_name="活動連接") image = models.ImageField(upload_to="banner", verbose_name="圖片") index = models.SmallIntegerField(default=0, verbose_name="順序") class Meta: db_table = "df_index_promotion" verbose_name = "主頁促銷活動" verbose_name_plural = verbose_name def __str__(self): return self.name
訂單模塊
from django.db import models from utils.models import BaseModel from users.models import User, Address from goods.models import GoodsSKU class OrderInfo(BaseModel): """訂單信息""" PAY_METHODS = { 1: "貨到付款", 2: "支付寶", } PAY_METHODS_ENUM = { "CASH": 1, "ALIPAY": 2 } PAY_METHOD_CHOICES = ( (1, "貨到付款"), (2, "支付寶"), ) ORDER_STATUS = { 1: "待支付", 2: "待發貨", 3: "待收貨", 4: "待評價", 5: "已完成", } ORDER_STATUS_ENUM = { "UNPAID": 1, "UNSEND": 2, "UNRECEIVED": 3, "UNCOMMENT": 4, "FINISHED": 5 } ORDER_STATUS_CHOICES = ( (1, "待支付"), (2, "待發貨"), (3, "待收貨"), (4, "待評價"), (5, "已完成"), ) order_id = models.CharField(max_length=64, primary_key=True, verbose_name="訂單號") user = models.ForeignKey(User, verbose_name="下單用戶") address = models.ForeignKey(Address, verbose_name="收獲地址") total_count = models.IntegerField(default=1, verbose_name="商品總數") total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="商品總金額") trans_cost = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="運費") pay_method = models.SmallIntegerField(choices=PAY_METHOD_CHOICES, default=1, verbose_name="支付方式") status = models.SmallIntegerField(choices=ORDER_STATUS_CHOICES, default=1, verbose_name="訂單狀態") trade_id = models.CharField(max_length=100, unique=True, null=True, blank=True, verbose_name="支付編號") class Meta: db_table = "df_order_info" class OrderGoods(BaseModel): """訂單商品""" order = models.ForeignKey(OrderInfo, verbose_name="訂單") sku = models.ForeignKey(GoodsSKU, verbose_name="訂單商品") count = models.IntegerField(default=1, verbose_name="數量") price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="單價") comment = models.TextField(default="", verbose_name="評價信息") class Meta: db_table = "df_order_goods"
創建yiguofresh項目
基礎項目目錄
基礎項目目錄說明
apps
:應用目錄,包含用戶、商品、訂單、購物車四個應用dailyfresh
:項目同名目錄static
:靜態文件目錄,包含images、css、js、html ...templates
:模板文件目錄utils
:實用用工具類,包含模型基類 ...
創建應用
-
注意:該項目的應用在apps文件目錄下,不是在項目根目錄下
# 以用戶模塊為例 cd Desktop/dailyfresh/apps/ python ../manage.py startapp users
MySQL數據庫
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dailyfresh', 'HOST': '192.168.24.136', # MySQL數據庫地址 'PORT': '3306', 'USER': 'root', 'PASSWORD': 'mysql', } }
定義模型
- 分別在
users
、goods
、orders
應用中定義好對應的模型類cart
應用中暫時不定義模型類,其中的數據是使用redis數據庫維護的
User模型提示
-
users
應用中的模型類User
是使用Django自帶的用戶認證系統維護的
遷移前,需要在settings.py
文件中設置:AUTH_USER_MODEL = '應用.用戶模型類'
增加導包路徑
- 原因:在
settings.py
中設置AUTH_USER_MODEL
時,編碼規則為'應用.用戶模型類'
- 但是,應用在
apps/
文件目錄下,為了保證正確的編碼,我們需要增加導包路徑 -
同時,為了配合
AUTH_USER_MODEL
的配置,應用的安裝直接使用users
,不要使用apps.users
import sys sys.path.insert(1, os.path.join(BASE_DIR, 'apps'))
完成模型遷移
展示注冊頁面
准備靜態文件
- 配置靜態文件加載路徑
STATIC_URL = '/static/' STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
- 測試靜態頁面展示效果
展示注冊頁面
- 提示:在用戶模塊
users
中
1.准備注冊頁面視圖
def register(request): """返回注冊頁面""" return render(request, 'register.html')
2.准備模板
3.匹配URL
-
項目中的urls.py
urlpatterns = [ url(r'^admin/', include(admin.site.urls)), # 訪問用戶模塊的路由配置 url(r'^users/', include('apps.users.urls',namespace='users')), ]
users應用中的urls.py
urlpatterns = [from django.conf.urls import url from apps.users import views urlpatterns = [ # 注冊 url(r'^register$', views.register, name='register'), ]
測試注冊頁面展示效果
提示
- URL正則匹配中,增加了命名空間,方便后續的反解析
- URL正則匹配中,
register
后面是否加/
,根據公司需求而定
視圖函數的get和post請求處理
思考:一個register視圖,是否可以處理兩種邏輯?比如get和post請求邏輯。
如何在一個視圖中處理get和post請求
注冊視圖處理get和post請求
def register(request): """處理注冊""" # 獲取請求方法,判斷是GET/POST請求 if request.method == 'GET': # 處理GET請求,返回注冊頁面 return render(request, 'register.html') else: # 處理POST請求,實現注冊邏輯 return HttpResponse('這里實現注冊邏輯')
類視圖
類視圖介紹
- 將視圖以類的形式定義
- 需要繼承自:通用類視圖基類
View
- 需要導包:
from django.views.generic import View
或from django.views.generic.base import View
應用/urls.py
中配置路由時,使用類視圖的as_view()
方法 ,將類視圖轉成視圖函數- 由
dispatch()
方法將具體的request分發至對應請求方式的處理方法中,比如get、post ... - 類視圖和視圖函數根據具體的需求而選擇使用,但是目前更加傾向於類視圖,如果類視圖不好實現的就可以選擇視圖函數,以保證實現邏輯為主
- 類視圖文檔
類視圖使用
1.定義類視圖處理注冊邏輯
class RegisterView(View): """類視圖:處理注冊""" def get(self, request): """處理GET請求,返回注冊頁面""" return render(request, 'register.html') def post(self, request): """處理POST請求,實現注冊邏輯""" return HttpResponse('這里實現注冊邏輯')
2.匹配URL
- users應用中的urls.py
urlpatterns = [ # 視圖函數:注冊 # url(r'^register$', views.register, name='register'), # 類視圖:注冊 url(r'^register$', views.RegisterView.as_view(), name='register'), ]
提示
- 類視圖相對於函數視圖有更高的復用性
- 如果其他地方需要用到某個類視圖的邏輯,直接繼承該類視圖即可
模板加載靜態文件
如果發現模板中,靜態文件是以前端的方式處理的,Django程序猿在使用時,需要修改成Django的處理方式
前端的方式處理靜態文件
Django的方式處理靜態文件
提示
- Django擅長處理動態的業務邏輯,靜態的業務邏輯交給nginx來處理
- 關於static標簽了解用法即可
- 項目后期我們會使用nginx來處理靜態文件
注冊登陸
- 1.需要實現用戶注冊邏輯
- 2.需要實現用戶激活邏輯
- 3.需要實現用戶登陸邏輯
注冊邏輯介紹
-
提示:用戶注冊邏輯中,包含了用戶郵件激活邏輯
注冊邏輯實現
准備處理注冊邏輯類視圖
class RegisterView(View): """類視圖:處理注冊""" def get(self, request): """處理GET請求,返回注冊頁面""" return render(request, 'register.html') def post(self, request): """處理POST請求,實現注冊邏輯""" return HttpResponse('這里實現注冊邏輯')
獲取注冊請求參數
class RegisterView(View): """類視圖:處理注冊""" def get(self, request): """處理GET請求,返回注冊頁面""" return render(request, 'register.html') def post(self, request): """處理POST請求,實現注冊邏輯""" # 獲取注冊請求參數 user_name = request.POST.get('user_name') password = request.POST.get('pwd') email = request.POST.get('email') allow = request.POST.get('allow') return HttpResponse('這里實現注冊邏輯')
校驗注冊請求參數
- 前后端的校驗需要分離:前端檢驗完,數據到服務器后繼續校驗,避免黑客繞過客戶端發請求
- 提示:出現異常的處理方式,根據公司具體需求來實現
class RegisterView(View): """類視圖:處理注冊""" def get(self, request): """處理GET請求,返回注冊頁面""" return render(request, 'register.html') def post(self, request): """處理POST請求,實現注冊邏輯""" # 獲取注冊請求參數 user_name = request.POST.get('user_name') password = request.POST.get('pwd') email = request.POST.get('email') allow = request.POST.get('allow') # 參數校驗:缺少任意一個參數,就不要在繼續執行 if not all([user_name, password, email]): return redirect(reverse('users:register')) # 判斷郵箱 if not re.match(r"^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$", email): return render(request, 'register.html', {'errmsg':'郵箱格式不正確'}) # 判斷是否勾選協 if allow != 'on': return render(request, 'register.html', {'errmsg': '沒有勾選用戶協議'}) return HttpResponse('這里實現注冊邏輯')
保存用戶注冊信息
- 提示:隱私信息需要加密,可以直接使用django提供的用戶認證系統完成
- 用戶認證系統文檔
- 調用create_user(user_name, email, password)實現用戶保存和加密隱私信息
- 參數順序不能錯
- IntegrityError異常用於判斷用戶是否重名、已注冊,這樣可以減少訪問數據庫頻率
- 保存完用戶注冊信息后,需要重置用戶激活狀態,因為Django用戶認證系統默認激活狀態為True
class RegisterView(View): """類視圖:處理注冊""" def get(self, request): """處理GET請求,返回注冊頁面""" return render(request, 'register.html') def post(self, request): """處理POST請求,實現注冊邏輯""" # 獲取注冊請求參數 user_name = request.POST.get('user_name') password = request.POST.get('pwd') email = request.POST.get('email') allow = request.POST.get('allow') # 參數校驗:缺少任意一個參數,就不要在繼續執行 if not all([user_name, password, email]): return redirect(reverse('users:register')) # 判斷郵箱 if not re.match(r"^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$", email): return render(request, 'register.html', {'errmsg':'郵箱格式不正確'}) # 判斷是否勾選協 if allow != 'on': return render(request, 'register.html', {'errmsg': '沒有勾選用戶協議'}) # 保存數據到數據庫 try: # 隱私信息需要加密,可以直接使用django提供的用戶認證系統完成 user = User.objects.create_user(user_name, email, password) except db.IntegrityError: return render(request, 'register.html', {'errmsg': '用戶已注冊'}) # 手動的將用戶認證系統默認的激活狀態is_active設置成False,默認是True user.is_active = False # 保存數據到數據庫 user.save() return HttpResponse('這里實現注冊邏輯')
查看保存用戶注冊信息結果
# 查詢出數據,並以列表形式展示 select * from df_users \G
郵件激活用戶
思考:
- 服務器激活郵件如何發送?
- 服務器如何才能知道是誰要激活?
提示
-
1.服務器激活郵件如何發送?
- 激活郵件的發送不能阻塞注冊結果的響應
-
激活郵件需要異步發送,集成Celery模塊可以實現異步任務
2.服務器如何才能知道是誰要激活?
- 激活郵件鏈接:
http://127.0.0.1:8000/users/active/user_id
- 注意:user_id需要以密文的形式發送到用戶郵箱,避免被黑客獲取破解
- user_id加密后的結果稱之為token、口令、令牌
-
token用於客戶端向服務器發送激活請求時,服務器對用戶身份的識別
准備工作
class ActiveView(View): """郵件激活""" def get(self, request, token): """處理激活請求""" pass
# 郵件激活 url(r'^active/(?P<token>.+)$', views.ActiveView.as_view(), name='active'),
實現步驟
- 第一步:生成激活token
- 第二步:Celery異步發送激活郵件
-
1.安裝 itsdangerous 模塊
pip install itsdangerous
2.生成用戶激活token的方法封裝在User模型類中
- Serializer()生成序列化器,傳入混淆字符串和過期時間
- dumps()生成user_id加密后的token,傳入封裝user_id的字典
- 返回token字符串
-
loads()解出token字符串,得到用戶id明文
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from django.conf import settings class User(AbstractUser, BaseModel): """用戶""" class Meta: db_table = "df_users" def generate_active_token(self): """生成激活令牌""" serializer = Serializer(settings.SECRET_KEY, 3600) token = serializer.dumps({"confirm": self.id}) # 返回bytes類型 return token.decode()
3.生成激活token方法的調用
token = user.generate_active_token()
提示: SignatureExpired 異常
簽名過期的異常
Django發送郵件
- Django中內置了郵件發送功能
- 被定義在django.core.mail模塊中,調用方法 send_mail()
- 發送郵件需要使用SMTP服務器,常用的免費服務器有:163、126、QQ
-
提示:Django需要知道是誰在幫它發郵件,所以需要提前配置郵箱服務器
Django發送郵件步驟
- 1.確定郵件服務器和發件人sender
- 2.
settings.py
中配置郵件服務器參數 - 3.調用send_email()
1. 配置郵件服務器和發件人sender
- 示例:此處演示網易郵箱服務器作為發件方的郵件服務器
- 發件人sender:
dailyfreshzxc@yeah.net
-
發件人郵箱授權
2. settings.py
中配置郵件服務器參數
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' # 導入郵件模塊 EMAIL_HOST = 'smtp.yeah.net' # 發郵件主機 EMAIL_PORT = 25 # 發郵件端口 EMAIL_HOST_USER = 'dailyfreshzxc@yeah.net' # 授權的郵箱 EMAIL_HOST_PASSWORD = 'dailyfresh123' # 郵箱授權時獲得的密碼,非注冊登錄密碼 EMAIL_FROM = '天天生鮮<dailyfreshzxc@yeah.net>' # 發件人抬頭
3. 調用send_email()
from django.core.mail import send_mail from django.conf import settings 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)
4.使用
Celery異步發送激活郵件
Celery介紹
- 1.Celery介紹
- 點擊查看Celery參考文檔
- Celery是一個功能完備即插即用的任務隊列
- Celery適用異步處理問題,比如發送郵件、文件上傳,圖像處理等等比較耗時的操作,我們可將其異步執行,這樣用戶不需要等待很久,提高用戶體驗
- 2.Celery特點:
- 簡單,易於使用和維護,有豐富的文檔
- 高效,單個Celery進程每分鍾可以處理數百萬個任務
- 靈活,Celery中幾乎每個部分都可以自定義擴展
- Celery非常易於集成到一些web開發框架中
-
3.安裝Celery
# 進入虛擬環境 pip install celery
-
4.Celery組成結構
- 任務隊列是一種跨線程、跨機器工作的一種機制
- 任務隊列中包含任務的工作單元。有專門的工作進程持續不斷的監視任務隊列,並從中獲得新的任務並處理
- Celery通過消息進行通信,通常使用一個叫broker(中間人)來協client(任務的發出者)和worker(任務的處理者)
- client發出消息到隊列中,broker將隊列中的信息派發給worker來處理
- 一個Celery系統可以包含很多的worker和broker,可增強橫向擴展性和高可用性能。
-
Celery組成結構是生產者消費者模型的一種體現
Celery使用
1.創建Celery異步任務文件
2.創建應用對象/客戶端/client
- 應用對象內部封裝要異步執行的任務
- Celery():
- 參數1是異步任務路徑
- 參數2是指定的broker
- redis://密碼@redis的ip:端口/數據庫
- redis://192.168.243.191:6379/4
- 返回客戶端應用對象app
- send_active_email():內部封裝激活郵件內容,並用裝飾器@app.task注冊
-
調用python的send_mail()將激活郵件發送出去
from celery import Celery from django.core.mail import send_mail from django.conf import settings # 創建celery應用對象 app = Celery('celery_tasks.tasks', broker='redis://192.168.243.191:6379/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)
3.中間人broker
- 示例:此處演示Redis數據庫作為中間人broker
- Celery需要一種解決消息的發送和接受的方式,我們把這種用來存儲消息的的中間裝置叫做message broker, 也可叫做消息中間人。
-
作為中間人,我們有幾種方案可選擇:
-
1.RabbitMQ
- RabbitMQ是一個功能完備,穩定的並且易於安裝的broker. 它是生產環境中最優的選擇。
-
使用RabbitMQ的細節參照以下鏈接: http://docs.celeryproject.org/en/latest/getting-started/brokers/rabbitmq.html#broker-rabbitmq
-
如果使用的是Ubuntu或者Debian發行版的Linux,可以直接通過命令安裝RabbitMQ:
sudo apt-get install rabbitmq-server
- 安裝完畢之后,RabbitMQ-server服務器就已經在后台運行。
- 如果用的並不是Ubuntu或Debian, 可以在以下網址: http://www.rabbitmq.com/download.html去查找自己所需要的版本軟件。
-
2.Redis
- Redis也是一款功能完備的broker可選項,但是其更可能因意外中斷或者電源故障導致數據丟失的情況。
- 關於是由那個Redis作為Broker,可訪下面網址: http://docs.celeryproject.org/en/latest/getting-started/brokers/redis.html#broker-redis
-
4.創建worker
- 示例:此處演示把worker創建到ubuntu虛擬機中,ubuntu作為Celery服務器
-
Celery服務器創建worker步驟
-
1.把項目代碼拷貝一份到ubuntu虛擬機中
- 並在celery_tasks/tasks.py文件頂部添加以下代碼
-
作用:讓Celery的worker能夠加載Django配置環境
-
import os os.environ["DJANGO_SETTINGS_MODULE"] = "dailyfresh.settings" # 放到Celery服務器上時添加的代碼 import django django.setup()
2.終端創建worker
celery -A celery_tasks.tasks worker -l info
3.開啟redis-server,查看broker
4.測試發郵件
5.查看worker收到的異步任務消息
完整注冊邏輯實現代碼
class RegisterView(View): """類視圖:處理注冊""" def get(self, request): """處理GET請求,返回注冊頁面""" return render(request, 'register.html') def post(self, request): """處理POST請求,實現注冊邏輯""" # 獲取注冊請求參數 user_name = request.POST.get('user_name') password = request.POST.get('pwd') email = request.POST.get('email') allow = request.POST.get('allow') # 參數校驗:缺少任意一個參數,就不要在繼續執行 if not all([user_name, password, email]): return redirect(reverse('users:register')) # 判斷郵箱 if not re.match(r"^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$", email): return render(request, 'register.html', {'errmsg':'郵箱格式不正確'}) # 判斷是否勾選協 if allow != 'on': return render(request, 'register.html', {'errmsg': '沒有勾選用戶協議'}) # 保存數據到數據庫 try: # 隱私信息需要加密,可以直接使用django提供的用戶認證系統完成 user = User.objects.create_user(user_name, email, password) except db.IntegrityError: return render(request, 'register.html', {'errmsg': '用戶已注冊'}) # 手動的將用戶認證系統默認的激活狀態is_active設置成False,默認是True user.is_active = False # 保存數據到數據庫 user.save() # 生成激活token token = user.generate_active_token() # celery發送激活郵件:異步完成,發送郵件不會阻塞結果的返回 send_active_email.delay(email, user_name, token) # 返回結果:比如重定向到首頁 return redirect(reverse('goods:index'))
注冊一個用戶測試激活郵件是否正確發送
激活邏輯實現
- 當點擊激活郵件中的激活鏈接時,訪問以下視圖
- 實現說明:
- Serializer():創建序列化器
- loads(token):獲取token明文信息、字典
- SignatureExpired異常:判斷token是否過期
- DoesNotExist異常:判斷激活用戶是否存在,防止激活黑客賬戶
- 查詢出要激活的用戶后,設置is_active=True,完成用戶激活
class ActiveView(View): """用戶激活""" def get(self, request, token): # 創建序列化器 serializer = Serializer(settings.SECRET_KEY, 3600) try: # 使用序列化器,獲取token明文信息,需要判斷簽名是否過期 result = serializer.loads(token) except SignatureExpired: # 提示激活鏈接已過期 return HttpResponse('激活鏈接已過期') # 獲取用戶id user_id = result.get('confirm') try: # 查詢需要激活的用戶,需要判斷查詢的用戶是否存在 user = User.objects.get(id=user_id) except User.DoesNotExist: # 提示用戶不存在 return HttpResponse('用戶不存在') # 設置激活用戶的is_active為Ture user.is_active = True # 保存數據到數據庫 user.save() # 響應信息給客戶端 return redirect(reverse('users:login'))
- 郵件激活前
- 郵件激活后
提示:DoesNotExist 異常
登陸邏輯介紹
登陸邏輯實現
- 訪問登陸頁面和登陸請求都是以下類視圖完成:
- Django自帶的用戶認證系統完成登陸驗證
- authenticate():
- 參數1:用戶名
- 參數2:密碼
- 返回用戶對象,如果用戶對象不存在,表示登陸驗證失敗
- authenticate():
- 登陸驗證成功后,需要繼續判斷是否是激活用戶
- 如果用戶驗證成功+是激活用戶:下一步就是登入用戶
- Django用戶認證系統提供登入方法:
login(request, user)
- 登入時,服務器需要記錄登陸狀態,即需要記錄session數據
- 默認是存儲在MySQL的
django_session
數據庫表中
- 默認是存儲在MySQL的
- Django用戶認證系統提供登入方法:
- 如果用戶session數據需要存儲到Redis數據庫中
- 需要安裝
django-redis-sessions
來輔助完成 - 或者安裝
django-redis
來輔助完成(功能更加豐富,推薦使用)
- 需要安裝
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') # 參數校驗 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 is False: # 如果不是激活用戶 return render(request, 'login.html', {'errmsg':'用戶未激活'}) # 使用django的用戶認證系統,在session中保存用戶的登陸狀態 login(request, user) # 登陸成功,重定向到主頁 return redirect(reverse('goods:index'))
狀態保持
- 用戶登陸成功后,需要將用戶的登陸狀態記錄下來,即保存由服務器生成的session數據
- 瀏覽器和服務器中都要保存用戶登陸狀態
- 服務器存儲session數據
- 瀏覽器存儲sessionid
以下配置是配合
Django用戶認證系統
的login()
方法,完成session信息
存儲
Redis數據庫緩存session信息
-
1.安裝
django-redis
-
pip install django-redis
2.
settings.py
文件配置django-redis
-
# 緩存 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://192.168.243.193:6379/5", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } } # Session # http://django-redis-chs.readthedocs.io/zh_CN/latest/#session-backend SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_CACHE_ALIAS = "default"
查看服務器存儲session效果
- session信息存儲在服務器
- session信息被寫入到客戶端Cookie
登陸記住用戶
需求分析
為什么要記住用戶?
-
登陸-->沒有記住用戶名-->退出瀏覽器-->再打開網頁-->狀態沒有保持-->需要再登陸
-
登陸-->記住用戶名-->退出瀏覽器-->再打開網頁-->狀態依然保持-->不用再登陸直接訪問網站
記住用戶技術點分析
- 已知:
- 服務器的redis數據庫中,存儲了用戶session信息
- 瀏覽器的cookie中,存儲了用戶sessionid信息
- 每次請求瀏覽器會帶上cookie信息
-
結論:記住用戶就是設置session有效期
request.session.set_expiry(value)
- 如果value是一個整數,那么會話將在value秒沒有活動后過期
- 如果value為0,那么會話的Cookie將在用戶的瀏覽會話結束時過期
- 如果value為None,那么會話則兩個星期后過期
記住用戶實現
# 獲取是否勾選'記住用戶名' remembered = request.POST.get('remembered') # 登入用戶 login(request, user) # 判斷是否是否勾選'記住用戶名' if remembered != 'on': # 沒有勾選,不需要記住cookie信息,瀏覽會話結束后過期 request.session.set_expiry(0) else: # 已勾選,需要記住cookie信息,兩周后過期 request.session.set_expiry(None) # 響應結果: 重定向到主頁 return redirect(reverse('goods:index'))
退出登錄
需求分析
退出登錄邏輯分析
- 退出登錄對應的是get請求方法,不需要向服務器傳參數
- 需要清理服務器的session數據
- 需要清理瀏覽器的cookie數據
- 以上兩步操作,都由Django用戶認證系統來完成
退出登錄實現
- Django用戶認證系統提供logout()函數
- 參數:request
- 說明:如果用戶已登錄,Django用戶認證系統會把user對象加入到request當中
-
結論:從request中可以獲取到user信息,
request.user
class LogoutView(View): """退出登錄""" def get(self, request): """處理退出登錄邏輯""" # 由Django用戶認證系統完成:需要清理cookie和session,request參數中有user對象 logout(request) # 退出后跳轉:由產品經理設計 return redirect(reverse('goods:index'))
配置URL正則
url(r'^logout$', views.LogoutView.as_view(), name='logout'),