一. APIView版本#
1. models.py#
Copy
from django.db import models
from django.contrib.auth.models import AbstractUser
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)
'''
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)
'''
TypeError: __init__() got an unexpected keyword argument 'db_contraint'
'''
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
def authors_list(self):
return [{'name': author_obj.name, 'sex': author_obj.get_gender_display()} for author_obj in self.authors.all()]
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文件
Copy
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)]
class BookModelSerializer(serializers.ModelSerializer):
publish_name = serializers.CharField(source='publish.name', read_only=True)
'''
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
Copy
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):
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')
affected_rows = models.Book.objects.filter(pk__in=pks).update(
is_delete=True)
return CommonResponse(results={'affected_rows': affected_rows})
4. urls.py
Copy
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 自定义异常处理#
Copy
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 自定义错误类型
Copy
class NonentityError(Exception):
def __init__(self, value):
self.value = value
super().__init__()
def __str__(self):
return '< %s >' % self.value
3) response.py 自定义封装response对象
Copy
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 自定义频率校验
Copy
from rest_framework.throttling import SimpleRateThrottle
class CustomSimpleRateThrottle(SimpleRateThrottle):
scope = 'custom'
def get_cache_key(self, request, view):
return request.META.g
6. 总结
Copy
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) 专门用与反序列化的字段设计: 默认的外键字段指定只写.
先配置2条路由, 2条路由都指向同一个视图类. 再通过url传递过来kwargs中是时候有pk值.
有: 单条数据 没有: 多条数据
单条数据格式: {}
多条数据格式: [{}, {}]
数据都是从body中获取, 反序列化时指定的参数是data, 还需要注意的就是多条数据需要指定many=True.
提示: 新增多条数据, ListSerializer中定义了create方法
本质就是通过for循环, 再调用ModelSerializer中的create方法.
def create(self, validate_data):
return [self.child.create(attrs) for attrs in validate_data]
判断: 通过路由的又名分组, 到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 = 新建类名
单条数据: 判断kwargs中是否有pk值
多条数据: {'pks': [1, 2, 3]}
提示: 不是真正的删除, 而是修改对应数据中的is_delete字段等于True
删除单个可
二. GenericAPIView版本
Copy
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()
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):
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})
总结
Copy
主要要明白GenericAPIView中提供的三种主要的方法的返回值
self.get_object() 返回数据对象
self.get_queryset() 返回queryset对象. 因此这里就可以继续有filter(), update()等连点操作
self.get_serializer() many=