一. APIView版本
1. models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
# 自定义字段Char类型
class CustomChar(models.Field):
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super().__init__(max_length=self.max_length, *args, **kwargs)
def db_type(self, connection):
return f'Char({self.max_length})'
# 定义所有的表共有的字段
class CommonModel(models.Model):
is_delete = models.BooleanField(default=False, verbose_name='True标记被删除的数据, False标记正常使用的数据')
create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
update_time = models.DateTimeField(auto_now=True, verbose_name='最后更新时间')
class Meta:
abstract = True
class Book(CommonModel):
"""
参数拓展:
blank: 指定布尔值. 用来表示admin后台管理该字段时候可以为空.
help_text: 指定字符串. 用来表示admin后台管理的提示信息.
外键关联:
to_field: 指定关联的表的外键字段. 默认不写,关联到关联表的主键值.
db_constraint: 逻辑上的关联,实质上没有外键练习,增删不会受外键影响,以及不影响orm查询.
ForeignKey 与 OneToOneField
由源码得知OneToOneField继承ForeignKey, 并且默认制定了unique=True参数
ForeignKey(to='AuthorDetail', unique=True)
OneToOneField(to='AuthorDetail')
on_delete参数:
1、表之间没有外键关联,但是有外键逻辑关联(有充当外键的字段)
2、断关联后不会影响数据库查询效率,但是会极大提高数据库增删改效率(不影响增删改查操作)
3、断关联一定要通过逻辑保证表之间数据的安全,不要出现脏数据,代码控制
4、断关联
5、级联关系
作者没了,详情也没:on_delete=models.CASCADE
出版社没了,书还是那个出版社出版:on_delete=models.DO_NOTHING
部门没了,员工没有部门(空不能):null=True, on_delete=models.SET_NULL
部门没了,员工进入默认部门(默认值):default=0, on_delete=models.SET_DEFAULT
"""
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=32, verbose_name='书名', help_text=''
'这里填写书名')
price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name='书价格')
publish = models.ForeignKey(to='Publish', to_field='id', on_delete=models.DO_NOTHING, db_constraint=False)
# 提示: to_field不能指定pk, 而是需要指定对应关联表的实际字段. 如果指定pk, 将会抛出如下异常:
'''
publish = models.ForeignKey(to='Publish', to_field='pk', on_delete=models.DO_NOTHING, db_constraint=False)
ERRORS:
api.Book.publish: (fields.E312) The to_field 'pk' doesn't exist on the related model 'api.Publish'.
'''
authors = models.ManyToManyField(to='Author', db_constraint=False)
# 半自动表的创建db_constraint参数的存在就会抛出如下异常:
'''
TypeError: __init__() got an unexpected keyword argument 'db_contraint'
'''
# authors = models.ManyToManyField(to='Author', through_fields=('book', 'author'), through='Author2Book', db_constraint=False)
class Meta:
"""
参数拓展:
db_table: 指定字符串. 用来修改表名
abstract: 指定布尔值. 用来建立抽象表, 该表不会在数据库中创建
unique_together: 指定容器. 用来建立多字段唯一
index_together: 指定容器. 用来建立多字段之间的联合索引
verbose_name: 指定字符串. 用来admin中显示表名, 默认加后缀s.
verbose_name_plural: 指定字符串. 用来admin中显示表名, 默认不加后缀s.
"""
verbose_name = '图书表'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
# @property # 提示: property装饰器可以不指定
# def publish_name(self):
# return self.publish.name
# @property # 提示: property装饰器可以不指定
def authors_list(self):
return [{'name': author_obj.name, 'sex': author_obj.get_gender_display()} for author_obj in self.authors.all()]
# class Author2Book(models.Model):
# author = models.ForeignKey(to='Author', on_delete=models.DO_NOTHING, db_constraint=False)
# book = models.ForeignKey(to='Book', on_delete=models.NOT_PROVIDED, db_constraint=False)
class Publish(CommonModel):
name = models.CharField(max_length=32, verbose_name='出版社名')
addr = models.CharField(max_length=64, verbose_name='出版社地址')
class Meta:
verbose_name = '出版社表'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class Author(CommonModel):
name = models.CharField(max_length=32, verbose_name='作者姓名')
gender_choices = (
(0, '女'),
(1, '男'),
(2, '保密'),
)
gender = models.IntegerField(choices=gender_choices, verbose_name='作者性别', default=2)
author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE, db_constraint=False)
class Meta:
verbose_name = '作者表'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class AuthorDetail(CommonModel):
phone = CustomChar(max_length=11, verbose_name='作者手机号码')
class Meta:
verbose_name = '作者详情表'
verbose_name_plural = verbose_name
def __str__(self):
return self.phone
2. ser.py 自定义序列化.py文件
from rest_framework import serializers
from . import models
class BookListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
print('instance:', instance)
print('validated_data:', validated_data)
print('self.child:', self.child)
"""
return [
self.child.create(attrs) for attrs in validated_data
]
"""
return [self.child.update(instance[i], attrs) for i, attrs in enumerate(validated_data)]
# 提示: 序列化操作的是数据库的表,推荐使用ModelSerializer
class BookModelSerializer(serializers.ModelSerializer):
# 第一种方式: 通过指定参数read_only=True, 在反序列化的时候不需要传该字段指定的值
publish_name = serializers.CharField(source='publish.name', read_only=True)
# 第二种方式: 在模型类中写方法, 通过方法关联到这里的字段. 如authors_list字段
'''
def authors_list(self):
return [{'name': author_obj.name, 'sex': author_obj.get_gender_display()} for author_obj in self.authors.all()]
'''
class Meta:
"""
depth: 指定整数. 表示跨表查询的深度.
如果指定2, 查询的时候就会将本实例中Book表关联的表, 以及关联表的关联的表所有的数据获取出来.
"""
model = models.Book
fields = ('id', 'name', 'price', 'publish', 'authors', 'publish_name', 'authors_list')
depth = 0
extra_kwargs = {
'publish': {'write_only': True},
'authors': {'write_only': True},
'publish_name': {'read_only': True},
'authors_list': {'read_only': True},
}
lis
3. views.py
from rest_framework.views import APIView
from . import models
from . import ser
from utils.exception import NonentityError
from utils.response import CommonResponse
class BookAPIView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
instance = models.Book.objects.filter(pk=pk, is_delete=False).first()
serializer = ser.BookModelSerializer(instance=instance)
else:
instance = models.Book.objects.filter(is_delete=False)
serializer = ser.BookModelSerializer(instance=instance, many=True)
return CommonResponse(results=serializer.data)
def post(self, request, *args, **kwargs):
if isinstance(request.data, list):
serializer = ser.BookModelSerializer(data=request.data, many=True)
elif isinstance(request.data, dict):
serializer = ser.BookModelSerializer(data=request.data)
else:
raise NonentityError('Add data through lists or dictionaries!')
serializer.is_valid(raise_exception=True)
serializer.save()
return CommonResponse(results=serializer.data)
def put(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
instance = models.Book.objects.get(pk=pk)
serializer = ser.BookModelSerializer(instance=instance, data=request.data)
elif isinstance(request.data, list):
# request.data = [{'id': 1, 'name': 'xxx', 'price': 'xxx'}]
pks = [dic.get('id') for dic in request.data]
instance = models.Book.objects.filter(pk__in=pks)
serializer = ser.BookModelSerializer(instance=instance, data=request.data, many=True)
else:
raise NonentityError(
'Specifies that the keyword can be partially modified or that the dictionary \
format of the list can be modified multiple times!')
serializer.is_valid(raise_exception=True)
serializer.save()
return CommonResponse(results=serializer.data)
def delete(self, request, *args, **kwargs):
pk = kwargs.get('pk')
pks = []
pks = pks.append(pk) if pk else request.data.get('pks')
# {'pks': [1, 2, 3]}
affected_rows = models.Book.objects.filter(pk__in=pks).update(
is_delete=True)
return CommonResponse(results={'affected_rows': affected_rows})
4. urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'books/$', views.BookAPIView.as_view()),
url(r'books/(?P<pk>[^/.]+)', views.BookAPIView.as_view())
]
5. utils 自定义工具包
1) exception_handler.py 自定义异常处理
from rest_framework.views import exception_handler
from rest_framework import status
from .response import CommonResponse
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if not response:
obj = CommonResponse(code=2000, messages='失败', error=str(exc), results=str(context),
status=status.HTTP_403_FORBIDDEN)
else:
obj = CommonResponse(code=2001, messages='失败', error=str(response.data), results=str(context),
status=status.HTTP_403_FORBIDDEN)
return obj
2) exception.py 自定义错误类型
class NonentityError(Exception):
def __init__(self, value):
self.value = value
super().__init__()
def __str__(self):
return '< %s >' % self.value
3) response.py 自定义封装response对象
from rest_framework.response import Response
class CommonResponse(Response):
def __init__(self, code=1000, messages='成功', results=None, error=None,
status=None,
template_name=None, headers=None,
exception=False, content_type=None, **kwargs):
data = {
'code': code,
'messages': messages,
}
data.update(kwargs)
if results:
data['results'] = results
if error:
data['error'] = error
super().__init__(data=data, status=status,
template_name=template_name, headers=headers,
exception=exception, content_type=content_type)
4) throttle.py 自定义频率校验
from rest_framework.throttling import SimpleRateThrottle
class CustomSimpleRateThrottle(SimpleRateThrottle):
scope = 'custom'
def get_cache_key(self, request, view):
# 'REMOTE_ADDR': '127.0.0.1',
# print(request.META.get('REMOTE_ADDR'))
return request.META.g
6. 总结
# 设计模型表的总结
1. 所有的表应该都有三个基本字段: is_delete, create_time, update_time
因此新增一个类继承Model, 之后的所有的类表继承它即可.
注意: 新增的类表默认会在执行数据库迁移命令以后, 会在数据库中生成,
因此应该新建Meat类, 指定abstract=True抽象表, 这样它就不会生成该表了.
2. 外键关联字段需要注意如下几种问题
1) 一对一关系要建立在查询次数较多的一行
2) 一对多关系要建立在多的一方, 如果建立在一的一行, 那么以后一的一行该外键对应的值将会是一个'[1, 2, 3]'这种结构.
这不是我们想要的. 我们最起码应该满足数据的数据设计存储规范.
3) 级联关系: 建立外键关联的字段要着重考虑on_delete参数.
如果一方没了, 那么另一方应该没有. 那么使用models.CASCADE(提示: 一对一关系表, 一般都是指定这个参数)
如果一方没了, 那么另一方应该存在. 那么使用models.DO_NOTHING
如果一方没了, 那么另一行应该设置为空. 那么使用models.SET_NULL
如果一方没了, 那么另一行应该转移到设置的默认情况上. 那么使用models.SET_DEFAULT
4) 断关联: 建立外键关联的字段最好使用db_constraint=False.
使用它可以提升对数据的操作, 不用依照外键之间删除数据, 新增数据的限制, 就可以直接对数据进行操作,
使用这种名义上的关联能最大化的提升数据库增删改的效率.
缺点: 如果直接操作数据库会出现脏数据, 因此不要直接操作数据库, 代码层面对数据的操作, 可以进行有效的控制即可
3. 疑问: 为什么创建半自动表使用db_constraint=False就会出现问题???
# 序列化器实现总结
1. 序列化操作涉及到数据库的操作, 推荐使用ModelSerializer
2. depth: 指定整数. 表示跨表查询的深度. 提示: 一般都不深度查询
3. 重点: 关于外键关联, 序列化 与 反序列化 是有所不同的
1) 专门用与序列化的字段设计: 新建一个外键字段指定只读
2种方法:
第一种: 序列化器中使用source
第二种: 模型类中写方法, 在fields中声明
注意: 该字段只读, 可在extra_kwargs中声明
2) 专门用与反序列化的字段设计: 默认的外键字段指定只写.
# 视图方法实现总结
# get
# 如何区分请求过来是获取单条还是多条数据?
先配置2条路由, 2条路由都指向同一个视图类. 再通过url传递过来kwargs中是时候有pk值.
有: 单条数据 没有: 多条数据
# 前提: 所有的获取都需要在过滤is_delete=True的字段, 获取的只是没有标记被删除的is_delete=False的数据
# 获取单条数据: 直接获取到数据对象, 再使用自定义的序列化类序列话数据. 拿到序列化之后的结果
# 获取多条数据: 直接获取到queryset对象, 序列化时需要指定many=True, 即可
# post
# 如何区分请求过来是新增单条还是多条数据?
单条数据格式: {}
多条数据格式: [{}, {}]
# 新增单条数据 和 新增多条数据
数据都是从body中获取, 反序列化时指定的参数是data, 还需要注意的就是多条数据需要指定many=True.
提示: 新增多条数据, ListSerializer中定义了create方法
本质就是通过for循环, 再调用ModelSerializer中的create方法.
def create(self, validate_data):
# self.child就是当前视图中指行序列化类实例化得到的对象
return [self.child.create(attrs) for attrs in validate_data]
# put
# 如何区分请求过来是修改单条还是多条数据?
判断: 通过路由的又名分组, 到kwargs中时候能获取pk值来判断
单条数据格式: pk -> {}
多条数据格式: [{'pk': ... }, {'pk': ... }]
# 修改单条数据
先获取传递过来的pk, 过滤出需要修改的数据对象
再往序列化的类中传递需要修改的对象, 以及该对象修改的数据
# 修改多条数据
先将传过来的列表套字典格式的数据中的所有字典中pk获取, 再使用双下划线过滤出对应的所有对象, 返回一个queryset对象.
再往序列换的类很重传递需要修改的queryset对象, 以及传递过来的要修改成什么样子的数据, 注意: 需要指定many=True
提示: 上面指定了many=True, 序列化完毕以后返回的是一个由ListSerializer类. ListSerializer类中定义了create,
但是没办法书写update方法, 因此需要我们重写.
步骤:
1) 新建一个类, 继承ListSerializer
2) 重写create方法
def create(self, instance, validate_data):
return [self.child.update(instance[i], attrs) for i, attrs in enumerate(validate_data)]
3) 在当前视图中执行序化类中在其, Meta中声明处理many=True时的类. list_serializer_class = 新建类名
# delete
# 如何区分请求过来是修改单条还是多条数据?
单条数据: 判断kwargs中是否有pk值
多条数据: {'pks': [1, 2, 3]}
# 删除单个 和 删除多个数据
提示: 不是真正的删除, 而是修改对应数据中的is_delete字段等于True
删除单个可
二. GenericAPIView版本
from rest_framework.generics import GenericAPIView
from . import models
from . import ser
from utils.response import CommonResponse
from utils.exception import NonentityError
class BookAPIView(GenericAPIView):
queryset = models.Book.objects.all()
serializer_class = ser.BookModelSerializer
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
instance = self.get_object()
# is_delete=True表示数据是做了删除标记的
if instance.is_delete:
raise NonentityError('The data as well does not exist anymore!')
else:
serializer = self.get_serializer(instance=instance)
else:
instance = self.get_queryset().filter(is_delete=False)
serializer = self.get_serializer(instance=instance, many=True)
return CommonResponse(results=serializer.data)
def post(self, request, *args, **kwargs):
if isinstance(request.data, list):
serializer = self.get_serializer(data=request.data, many=True)
elif isinstance(request.data, dict):
serializer = self.get_serializer(data=request.data)
else:
raise NonentityError('Add data through lists or dictionaries!')
serializer.is_valid(raise_exception=True)
serializer.save()
return CommonResponse(results=serializer.data)
def put(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
instance = self.get_object()
serializer = self.get_serializer(instance=instance, data=request.data, partial=True)
elif isinstance(request.data, list):
# request.data = [{'id': 1, 'name': 'xxx', 'price': 'xxx'}]
pks = [dic.get('id') for dic in request.data]
instance = self.get_queryset().filter(pk__in=pks)
serializer = self.get_serializer(instance=instance, data=request.data, many=True)
else:
raise NonentityError(
'Specifies that the keyword can be partially modified or that the dictionary \
format of the list can be modified multiple times!')
serializer.is_valid(raise_exception=True)
serializer.save()
return CommonResponse(results=serializer.data)
def delete(self, request, *args, **kwargs):
pk = kwargs.get('pk')
pks = []
pks = pks.append(pk) if pk else request.date.get('pks')
affected_rows = self.get_queryset().filter(pk__in=pks).update(is_delete=True)
return CommonResponse(results={'affected_rows': affected_rows})
总结
主要要明白GenericAPIView中提供的三种主要的方法的返回值
self.get_object() 返回数据对象
self.get_queryset() 返回queryset对象. 因此这里就可以继续有filter(), update()等连点操作
self.get_serializer() many=