果生鲜项目介绍
- 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异步发送激活邮件
itsdangerous中文文档
- 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:
[email protected]
- 发件人邮箱授权
2. settings.py
中配置邮件服务器参数
EMAIL_BACKEND = ‘django.core.mail.backends.smtp.EmailBackend‘ # 导入邮件模块 EMAIL_HOST = ‘smtp.yeah.net‘ # 发邮件主机 EMAIL_PORT = 25 # 发邮件端口 EMAIL_HOST_USER = ‘[email protected]‘ # 授权的邮箱 EMAIL_HOST_PASSWORD = ‘dailyfresh123‘ # 邮箱授权时获得的密码,非注册登录密码 EMAIL_FROM = ‘天天生鲜<[email protected]>‘ # 发件人抬头
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
- 1.RabbitMQ
4.创建worker
- 示例:此处演示把worker创建到ubuntu虚拟机中,ubuntu作为Celery服务器
- Celery服务器创建worker步骤
- 1.把项目代码拷贝一份到ubuntu虚拟机中
- 并在celery_tasks/tasks.py文件顶部添加以下代码
- 作用:让Celery的worker能够加载Django配置环境
- 1.把项目代码拷贝一份到ubuntu虚拟机中
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‘),
原文地址:https://www.cnblogs.com/kaiping23/p/9736841.html
时间: 2024-10-02 22:43:41