所有步骤中的账号密码仅供参考,千万不要在自己的生产环境中使用,否则产生的安全问题由您自己承担。
1. 创建数据模型
显而易见,在这个项目中我们需要两个数据模型,一个是Django内置User数据模型拓展KaopuShopUser,还有一个是产品数据模型KaopuProduct,各数据模型的字段如下:
字段(类型):描述
KaopuShopUser
user_id(唯一字符串): 用户编号(主键),使用uuid1算法
user_name(50位字符串,中英文数字混合): 用户名称
telephone(11位数字):电话号码
email(邮件地址): 邮箱
mod_date(时间): 最后一次更新时间,初始为创建时间
因为我们的用户数据模型是对Django内置User数据模型的拓展,因此我们只要继承内置的User数据模型即可。
KaopuShopProduct
product_id(唯一字符串): 产品编号(主键),使用uuid1算法
product_name(100位字符串): 产品名称
product_type (枚举类型):产品类型
cover_image(图片):封面图片
detail_images(图片): 详情图片组,使用关系字段ManyToManyField创建
price(Decimal):价格
mod_date(时间): 最后一次更新时间,初始为创建时间
Tips: Django 指定在app文件夹下的models.py中创建数据模型,但是如果数据模型都写在一个文件中,后期拓展数据模型时会导致这个文件中的代码行数非常多,维护不方便,因此推荐在同级目录下新建一个kaopu_shop_models 包,每个数据模型单独使用一个py文件保存。
KaopuShopProduct.py
import uuid
from django.db import models
from django.db.models import UUIDField, CharField, DateTimeField, ImageField, OneToOneField, DecimalField
from django.conf import settings
class KaopuShopProduct(models.Model):
"""
产品数据模型
"""
product_id = UUIDField("编号", primary_key=True, default=uuid.uuid1(), unique=True)
product_name = CharField("用户名", default='no_user_name', max_length=50)
Product_KINDS = (
('A', "类型A"), ('B', '类型B'), ('C', '类型C'), ('D', '类型D'),)
product_type = CharField("类型", default='', max_length=20, choices=Product_KINDS)
cover_img = ImageField("封面图片", upload_to=settings.PRODUCT_COVER_IMAGE_UPLOAD_TO)
detail_images = OneToOneField(MultiImageModel, on_delete=models.CASCADE,
related_name='KaopuShopProductDetailImages')
price = DecimalField("价格", max_digits=9, decimal_places=2)
mod_date = DateTimeField("最后更新", auto_now=True)
KaopuShopUser.py
import uuid
from django.contrib.auth.models import User
from django.db import models
from django.db.models import UUIDField, CharField, OneToOneField, DateTimeField
class KaopuShopUser(User):
"""
kaopu_shop用户数据表拓展
"""
user_id = UUIDField("编号", primary_key=True, default=uuid.uuid1(), unique=True)
tel = CharField("电话", null=True, default='', max_length=20)
mod_date = DateTimeField("最后更新", auto_now=True)
Kaopu_shop_multi_image_model.py
import uuid
from django.conf import settings
from django.db import models
from django.db.models import ImageField
from django.db.models.fields import DateTimeField
class MultiImageModel(models.Model):
"""
多图片数据模型
"""
detail_image = ImageField("详情图片", upload_to=settings.PRODUCT_COVER_IMAGE_UPLOAD_TO, default=None)
image_id = models.UUIDField('编号', primary_key=True, default=uuid.uuid1, unique=True)
mod_date = DateTimeField("最后更新", auto_now=True)
数据模型目录结构
可以看到,如果需要引入settings.py中的常量,一定要通过django.conf.settings对象引入,不要直接从settings.py中导入,防止发生意想不到的错误。其次,因为我们的模型处于别的包下,所以必须在models.py中引入才能让django 发现它们,在models.py中引入即可:
# Create your models here.
from .kaopu_shop_models.kaopu_shop_multi_image_model import MultiImageModel
from .kaopu_shop_models.kaopu_shop_product import KaopuShopProduct
from .kaopu_shop_models.kaopu_shop_user import KaopuShopUser
创建完数据模型必须运行以下命令完成数据库迁移:
提示
现在我们完成了数据模型的定义,这是一个重要的更新,因此不要忘记使用git跟踪这个更新。
提交信息:
2.序列化和反序列化
首先了解一下什么是序列化和反序列化:
序列化
将python对象转换为二进制流通过网络传输。对于这里的API来说就是把Django数据模型解析成json返回给客户端。
反序列化
将二进制流转换为python对象。对于这里的API来说就是将请求携带的数据,例如新产品信息解析成Django数据模型实例。
DjangoRestFramework序列化器允许把像查询集和模型实例这样的复杂数据转换为JSON,XML或其他内容类型。序列化器还提供反序列化,在验证传入的数据之后允许解析数据转换回复杂类型(数据模型类的实例或者自定义类)。因为序列化器和数据模型紧密相关,因此将其和数据模型放在同一个py文件下,方便对照。为了减少重复代码,我们使用ModelSerializers来序列化数据模型。
class KaopuShopUserSerializer(serializers.ModelSerializer):
"""
KaopuShopProduct 数据模型的序列化类
"""
class Meta:
model = KaopuShopUser
fields = ('user_id', 'date_joined', 'username', 'tel', 'mod_date', 'email', 'groups', 'is_staff', 'is_active',
'last_login')
class KaopuShopProductSerializer(serializers.ModelSerializer):
"""
KaopuShopProduct 数据模型的序列化类
"""
class Meta:
model = KaopuShopProduct
fields = ('product_id', 'product_name', 'product_type', 'cover_img', 'detail_images', 'price', 'mod_date')
提示
现在我们完成了序列化器的开发,别忘了使用git 跟踪这个重大特性。
3. 使用Viewset和Mixin简化视图
API用来给客户端访问受保护的资源使用。Django 的API主要由两部分构成,即URL和View(视图)。URL定义API路径,视图定义API调用的方法。Django有两种定义视图的方法,一种是基于函数,一种是基于类。由于基于函数的视图常常存在大量的if-else逻辑,维护不便,因此这里我们选择使用DjangoRestFramework提供的Viewset类来简化我们的视图,它是DjangoRestFramework拓展过的基于类的视图。首先来了解Mixin和Viewset概念:
Mixin是一种编程理念,用来将代码注入到一个类里,使被注入的类具备Mixin所实现的功能。当需要在多个类之间复用代码时,将这部分代码抽出来定义一个新的类,就构成一个Mixin。Mixin通常作为父类,使继承类具备了它实现的功能。这样看来,Mixin类似于Java里面的接口(Interface)或者抽象类。
- 引入Mixin概念的目的,是实现类之间的功能复用。
- 一个Mixin就是一个语法上的类,但是这个类本身不一定需要有明确的语义(具体是干什么的可以不明确),它仅仅是可复用代码(类可复用方法)的堆积。Mixin存在的目的是给真正具有语义的类添加这些可复用的功能。
- 其他类通过继承Mixin类,来获得Minxi实现的功能。
Viewset
ViewSet 是一种基于类的视图,它不提供任何http方法处理程序(如 .get()或.post()),而是提供诸如 .list() 和 .create() 之类面向数据模型的操作。ViewSet 的方法处理程序使用 .as_view() 方法绑定到对应的URL。通常不需要在 urlconf 中(对应于项目的urls.py)中显式注册视图,而是使用路由类Router注册Viewset,该类会自动确定 urlconf(自动生成URL并确定对应的Http 方法)。
权衡
与使用Django View 类或者视图函数相比,使用 ViewSet 类有两个主要优点:
- 视图中重复的逻辑 ViewSet 类的属性整合到一起,特别是需要为某个数据模型对应的一组视图函数设置相同权限或者返回信息过滤器时。
- 通过使用 routers类自动生成API URL, 我们不再需要自己处理URL conf。
- ViewSet 继承自Mixin(DjangoRestFramework提供的APIView类),因此ViewSet 已经包含很多restful api特性,可以极大简化API配置。
这两者都有一个权衡。使用常规的 views 和 URL confs 更明确也能够提供更多的控制,但是同时可能会产生很多URL配置,不便于管理。ViewSets有助于快速启动和运行,或者当API个数很多时,保证 URL 配置一致且规范,但会限制对URL的多粒度配置。
4. 标准化和复用响应消息
响应参考:https://www.django-rest-framework.org/api-guide/responses/
错误消息:https://www.django-rest-framework.org/api-guide/exceptions/
Django 的请求响应大致分为两种,一种返回模板渲染对象,用于在前端显示网页,通常用在前后端不分离的项目中;另一种通常返回json格式的数据,显示请求成功或错误消息。Restful API 要求服务器返回json格式的数据,因此我们这里不考虑渲染模板的响应。通常响应消息可以这样传递:
Response({"msg": "成功"},status=200)
但是不推荐直接在视图函数中这样写,原因是响应消息没法复用也不方便管理,更改起来很麻烦。为了解决这个问题,这里使用DjangoRestFramework 提供的一系列错误消息类来生成我们的响应消息。首先新建api_exception_handle.py,无论成功还是失败消息,都是继承DjangoRestFramework 提供的错误消息类得来的。这个类还提供文本翻译以及辅助消息生成(例如指出哪个请求参数出错)。
在api_exception_handle.py中创建常见的错误消息类:
from rest_framework.exceptions import APIException, ParseError, NotFound
from rest_framework.status import HTTP_404_NOT_FOUND, HTTP_400_BAD_REQUEST, HTTP_501_NOT_IMPLEMENTED, \
HTTP_503_SERVICE_UNAVAILABLE
"""
API 错误消息
"""
class KaopuServiceUnavailable(APIException):
status_code = HTTP_503_SERVICE_UNAVAILABLE
default_detail = 'Service temporarily unavailable, try again later.'
default_code = 'service_unavailable'
class KaopuParseError(ParseError):
status_code = HTTP_400_BAD_REQUEST
detail = "invalid params."
default_code = status_code
class KaopuNotFoundError(NotFound):
status_code = HTTP_404_NOT_FOUND
detail = " Not found."
default_code = status_code
class KaopuUnKnownError(APIException):
status_code = HTTP_501_NOT_IMPLEMENTED
detail = "Unknown error,please check your request."
default_code = status_code
class KaopuSuccessResponse(APIException):
status_code = HTTP_200_OK
default_detail = 'success.'
default_code = status_code
然后在视图中可以这样使用:
try:
.....
success_response = KaopuSuccessResponse()
return Response(success_response.get_full_details(), status=success_response.status_code)
except ValidationError as e:
error = KaopuParseError(detail=e.messages)
return Response(error.get_full_details(), status=error.status_code)
返回形式:
因为这里只是简单的想返回json格式的消息,因此错误消息和成功消息都使用同样的基类;如果需要进行模板渲染,建议使用DjangoRestFramework 的Response 类。
提示
现在我们定义了响应消息类,别忘了使用git 跟踪这个新特性。
5. API 缓存使用
缓存使用可以参考:https://django-redis-chs.readthedocs.io/zh_CN/latest/
API缓存主要通过django-redis连接件和redis数据库实现。例如缓存客户端传来的client_id:
from django.core.cache import cache
cache.set("client_id", client_id, timeout=2500)
获取time-to-live(ttl):
cache.ttl("client_id")
返回值为
- 0: key 不存在 (或已过期).
- None: key 存在但没有设置过期.
- ttl: 任何有超时设置的 key 的超时值.
使用 persist 或者 expire 方法让一个值永久存在或者指定一个新的过期时间:
>>>cache.set("foo", "bar", timeout=22)
>>> cache.ttl("foo")
22
>>> cache.persist("foo")
>>> cache.ttl("foo")
None
>>> cache.set("foo", "bar", timeout=22)
>>> cache.expire("foo", timeout=5)
>>> cache.ttl("foo")
5