摘抄Django项目(一)

果生鲜项目介绍

  • 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框架,一般是前后端分离

 


 

项目需求分析

静态文件预览

 

页面需求分析

  • 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.htmluser_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_timeDateTimeFieldauto_now_add=True创建时间
update_timeDateTimeFieldauto_now=True更新时间

用户表

  • 模型类名:User;表名:df_users
  • 使用Django自带的用户认证系统管理
  • 继承自:AbstractUser,导包 from django.contrib.auth.models import AbstractUser
  • 迁移前,需要在settings.py文件中设置:AUTH_USER_MODEL = '应用.用户模型类'

 

用户地址表

  • 模型类名:Address;表名:df_address
  • 使用User模型类作为外键约束地址信息属于哪个用户
字段名字段类型字段选项字段说明
idIntegerFieldprimary_key=True主键字段
userForeignKey外键为User模型约束地址属于哪个用户
receiver_nameCharFieldmax_length=20收件人
receiver_mobileCharFieldmax_length=11联系电话
detail_addrCharFieldmax_length=256详细地址
zip_codeCharFieldmax_length=6邮政编码

商品类别表

  • 模型类名:GoodsCategory;表名:df_goods_category
字段名字段类型字段选项字段说明
idIntegerFieldprimary_key=True主键字段
nameCharFieldmax_length=20商品类别名称
logoCharFieldmax_length=100商品类别标识
imageImageFieldupload_to="category"商品类别图片

商品SPU表

  • 模型类名:Goods;表名:df_goods
字段名字段类型字段选项字段说明
idIntegerFieldprimary_key=True主键字段
nameCharFieldmax_length=100商品名称
descHTMLFieldblank=True商品详细介绍

商品SKU表

  • 模型类名:GoodsSKU;表名:df_goods_sku
字段名字段类型字段选项字段说明
idIntegerFieldprimary_key=True主键字段
categoryForeignKey外键为GoodsCategory模型约束该商品的类别
goodsForeignKey外键为Goods模型约束该商品的SPU
nameCharFieldmax_length=100商品名称
titleCharFieldmax_length=200商品简介
unitCharFieldmax_length=10销售单位
priceDecimalFieldmax_digits=10, decimal_places=2商品价格
stockIntegerFielddefault=0商品库存
salesIntegerFielddefault=0商品销量
default_imageImageFieldupload_to="goods"商品默认图片
statusBooleanFielddefault=True是否上线

商品图片表

  • 模型类名:GoodsImage;表名:df_goods_image
字段名字段类型字段选项字段说明
idIntegerFieldprimary_key=True主键字段
skuForeignKey外键为GoodsSKU模型约束图片属于哪个商品的
imageImageFieldupload_to="goods"商品图片

主页轮播商品展示表

  • 模型类名:IndexGoodsBanner;表名:df_index_goods
字段名字段类型字段选项字段说明
idIntegerFieldprimary_key=True主键字段
skuForeignKey外键为GoodsSKU模型约束该商品的sku
imageImageFieldupload_to="banner"商品图片
indexSmallIntegerFielddefault=0轮播顺序

主页分类商品展示表

  • 模型类名:IndexCategoryGoodsBanner;表名:df_index_category_goods
字段名字段类型字段选项字段说明
idIntegerFieldprimary_key=True主键字段
categoryForeignKey外键为GoodsCategory模型约束该商品类型
skuForeignKey外键为GoodsSKU模型约束该商品的sku
display_typeSmallIntegerFieldchoices=DISPLAY_TYPE_CHOICES展示类型:图片或标题
indexSmallIntegerFielddefault=0展示顺序

主页促销活动展示表

  • 模型类名:IndexCategoryGoodsBanner;表名:df_index_category_goods
字段名字段类型字段选项字段说明
idIntegerFieldprimary_key=True主键字段
nameCharFieldmax_length=50活动名称
urlURLField 活动链接
imageImageFieldupload_to="banner"活动商品图片
indexSmallIntegerFielddefault=0活动商品顺序

订单信息表

  • 模型类名:OrderInfo;表名:df_order_info
字段名字段类型字段选项字段说明
order_idIntegerFieldprimary_key=True主键字段
userForeignKey外键为User模型下单用户
addressForeignKey外键为Address模型下单地址
total_countIntegerFielddefault=1商品总数
total_amountDecimalFieldmax_digits=10, decimal_places=2商品总金额
trans_costDecimalFieldmax_digits=10, decimal_places=2运费
pay_methodSmallIntegerFieldchoices=PAY_METHOD_CHOICES支付方式,定义支付选项
statusSmallIntegerFieldchoices=ORDER_STATUS_CHOICES订单状态,自定义状态
trade_idCharFieldmax_length=100, unique=True订单编号

订单商品表

  • 模型类名:OrderGoods;表名:df_order_goods
字段名字段类型字段选项字段说明
idIntegerFieldprimary_key=True主键字段
orderForeignKey外键为OrderInfo模型约束是哪个商品订单
skuForeignKey外键为GoodsSKU模型约束订单商品的sku
countIntegerFielddefault=1订单商品数量
priceDecimalFieldmax_digits=10, decimal_places=2商品单价
commentTextFielddefault=""评价信息

补充: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',
    }
}

 

 

定义模型

  • 分别在usersgoodsorders应用中定义好对应的模型类
    • 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: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, 也可叫做消息中间人。
  • 作为中间人,我们有几种方案可选择:

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:密码
      • 返回用户对象,如果用户对象不存在,表示登陆验证失败
  • 登陆验证成功后,需要继续判断是否是激活用户
  • 如果用户验证成功+是激活用户:下一步就是登入用户
    • Django用户认证系统提供登入方法:login(request, user)
    • 登入时,服务器需要记录登陆状态,即需要记录session数据
      • 默认是存储在MySQL的django_session数据库表中
  • 如果用户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信息

django-redis 中文文档

  • 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信息存储在服务器

 

  • 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

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值