一. APIView版本
1. models.py
from django.db import models # Create your models here. class CommonField(models.Model): is_delete = models.BooleanField(default=0, 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: """ 参数拓展: db_table: 指定字符串. 用来修改表名 abstract: 指定布尔值. 用来建立抽象表, 该表不会在数据库中创建 unique_together: 指定容器. 用来建立多字段唯一 index_together: 指定容器. 用来建立多字段之间的联合索引 单个字段,有索引,有唯一 多个字段,有联合索引,联合唯一 verbose_name: 指定字符串. 用来admin中显示表名, 默认加后缀s. verbose_name_plural: 指定字符串. 用来admin中显示表名, 默认不加后缀s. """ abstract = True # 指定布尔值. 用来建立抽象表, 该表不会在数据库中创建 class Book(CommonField): """ 参数拓展: 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 """ title = models.CharField(max_length=32, verbose_name='书名') price = models.DecimalField(max_digits=4, decimal_places=2, verbose_name='价格') publish = models.ForeignKey(to='Publish', to_field='id', db_constraint=False, on_delete=models.DO_NOTHING) # 提示: 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) # 有第三张表,没有on_delete参数 # 什么时候需要半自动,主要看第三张变需要扩字段,比如要看第三张表的两个字段什么时候建立关系的,扩展一张时间表 # 半自动表的创建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: verbose_name = '图书表' verbose_name_plural = verbose_name def __str__(self): return self.title @property # 提示: property装饰器可以不指定 def publish_name(self): return self.publish.name # @property # 提示: property装饰器可以不指定 def author_list(self): # lis = [] # for author_obj in self.authors.all(): # 子查询正向,字段名,多个加all() # lis.append({'name': author_obj.name, 'sex': author_obj.get_gender_display()}) # return lis # 列表推导式 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(CommonField): 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(CommonField): name = models.CharField(max_length=32, verbose_name='作者名') gender_choice = ( (0, '男'), (1, '女'), (2, '秘密') ) gender = models.IntegerField(choices=gender_choice, default=2, verbose_name='作者性别') author_detail = models.OneToOneField(to='AuthorDetail', db_constraint=False, on_delete=models.CASCADE) class Meta: verbose_name = '作者表' verbose_name_plural = verbose_name def __str__(self): return self.name class AuthorDetail(CommonField): phone = models.CharField(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 app01 import models # many的设置后,重写update方法 class BookListSerializer(serializers.ListSerializer): # def create(self, validated_data): # ListSerializer的create已帮写好了,不需要重写 # pass def update(self, instance, validated_data): return [self.child.update(instance[k], attrs) for k, attrs in enumerate(validated_data)] # 提示: 序列化操作的是数据库的表,推荐使用ModelSerializer class BookModelSerializer(serializers.ModelSerializer): # 第一种方式: 通过指定参数read_only=True, 在反序列化的时候不需要传该字段指定的值 # publish_name = serializers.CharField(source='publish.name', read_only=True) # 一对多关系,字段名 # read_only解决反序列化的问题 # 第二种方式: 在模型类中写方法, 通过方法关联到这里的字段. 如authors_list字段 class Meta: """ depth: 指定整数. 表示跨表查询的深度. 如果指定2, 查询的时候就会将本实例中Book表关联的表, 以及关联表的关联的表所有的数据获取出来. """ # 视图类中有many参数的,利用了元类的控制不同的类来生成对象,重写ListSerializer的方法,需要list_serializer_class = BookListSerializer list_serializer_class = BookListSerializer model = models.Book # fields = '__all__' # depth = 0 fields = ('id', 'title', 'price', 'publish', 'publish_name', 'authors', 'author_list') extra_kwargs = { 'publish': {'write_only': True}, 'publish_name': {'read_only': True}, 'authors': {'write_only': True}, 'author_list': {'read_only': True}, 'pk': {'read_only': True} }
3. views.py
from rest_framework.views import APIView from rest_framework.response import Response from app01 import models from app01 import ser from utils.exception import NonentityError from utils.response import CommonResponse # Create your views here. class BookAPIView(APIView): def get(self, request, *args, **kwargs): ''' # 如何区分请求过来是获取单条还是多条数据? 先配置2条路由, 2条路由都指向同一个视图类. 再通过url传递过来kwargs中是时候有pk值. 有: 单条数据 没有: 多条数据 # 前提: 所有的获取都需要在过滤is_delete=True的字段, 获取的只是没有标记被删除的is_delete=False的数据 # 获取单条数据: 直接获取到数据对象, 再使用自定义的序列化类序列话数据. 拿到序列化之后的结果 # 获取多条数据: 直接获取到queryset对象, 序列化时需要指定many=True, 即可 ''' # 获取一条 pk = kwargs.get('pk') if pk: book_obj = models.Book.objects.filter(pk=pk, is_delete=False).first() book_ser = ser.BookModelSerializer(book_obj) # 获取多条 else: book_query = models.Book.objects.all().filter(is_delete=False) # 筛选出为删除的 book_ser = ser.BookModelSerializer(book_query, many=True) return CommonResponse(results=book_ser.data) # return Response({'code': 1000, 'msg': '成功', 'data': book_ser.data}) def post(self, request, *args, **kwargs): ''' # 如何区分请求过来是新增单条还是多条数据? 在postman中的数据格式 单条数据格式: {} 多条数据格式: [{}, {}] # 新增单条数据 和 新增多条数据 数据都是从body中获取, 反序列化时指定的参数是data, 还需要注意的就是多条数据需要指定many=True. 提示: 新增多条数据, ListSerializer中定义了create方法 本质就是通过for循环, 再调用ModelSerializer中的create方法. def create(self, validate_data): # self.child就是当前视图中所指序列化类实例化得到的对象(BookModelSerializer对象) return [self.child.create(attrs) for attrs in validate_data] ''' # 新增一条 if isinstance(request.data, dict): book_ser = ser.BookModelSerializer(data=request.data) # 关键字参数传参 # 新增多条 elif isinstance(request.data, list): book_ser = ser.BookModelSerializer(data=request.data, many=True) # 关键字参数传参,增多条 # book_ser是ListSerializer对象,for循环后,就是单个新增 else: raise NonentityError('Add data through lists or dictionaries!') book_ser.is_valid(raise_exception=True) book_ser.save() return CommonResponse(results=book_ser.data) def put(self, request, **kwargs): ''' # 如何区分请求过来是修改单条还是多条数据? 判断: 通过路由的又名分组, 到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 = 新建类名 ''' # 修改一条 pk = kwargs.get('pk') if pk: book_obj = models.Book.objects.filter(pk=pk).first() print(request.data) book_ser = ser.BookModelSerializer(instance=book_obj, data=request.data, partial=True) # partial=True,可以对部分字段进行上传修改,没有的话,就要整个表的字段进行上传 book_ser.is_valid(raise_exception=True) book_ser.save() return CommonResponse(results=book_ser.data) # 修改多条 elif isinstance(request.data, list): # 第一种方案,request.data是列表套字典[{},{}],按id,生成[书对象1,书对象2],对应修改数据列表 # for循环一个一个的修改 book_obj_list = [] modify_data = [] for book_dic in request.data: pk = book_dic.get('id') book_obj_list.append(models.Book.objects.filter(pk=pk).first()) modify_data.append(book_dic) # book_ser_list =[] # for k, v in enumerate(modify_data): # book_ser = ser.BookModelSerializer(instance=book_obj_list[k], data=v) # book_ser.is_valid(raise_exception=True) # book_ser.save() # book_ser_list.append(book_ser.data) # return CommonResponse(results=book_ser_list) # 第二种方案,重写ListSerializer的update方法 book_ser = ser.BookModelSerializer(instance=book_obj_list, data=modify_data, many=True) book_ser.is_valid(raise_exception=True) book_ser.save() return CommonResponse(results=book_ser.data) else: raise NonentityError('Specifies that the keyword can be partially modified or that the dictionary \ format of the list can be modified multiple times!') def delete(self, request, *args, **kwargs): ''' # 如何区分请求过来是修改单条还是多条数据? 单条数据: 判断kwargs中是否有pk值 多条数据: {'pks': [1, 2, 3]} # 删除单个 和 删除多个数据 提示: 不是真正的删除, 而是修改对应数据中的is_delete字段等于True 删除单个可 ''' pk = kwargs.get('pk') pks = [] if pk: # 单条删除 pks.append(pk) # 不管单条删除还是多条删除,都是按照多条删除的方式来 # 多条删除 # 前端传过来的格式是{'pks':[1,2,3]} else: pks = request.data.get('pks') # 不是真的删除,只是把is_delete设置为True res = models.Book.objects.filter(pk__in=pks, is_delete=False).update(is_delete=True) # is_delete=False,把为删除的数据筛选出来 print(res) if res: return CommonResponse(msg='删除成功') else: return CommonResponse(msg='没有要删除的数据')
4. urls.py
from django.contrib import admin from django.urls import path from app01 import views urlpatterns = [ path('admin/', admin.site.urls), path('books/', views.BookAPIView.as_view()), path('books/<int:pk>/', views.BookAPIView.as_view()), # 分页器 path('books1/', views.BookListAPIView.as_view()), path('books2/', views.BookAPIView1.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, msg='成功', results=None, error=None, status=None, template_name=None, headers=None, exception=False, content_type=None, **kwargs): dic = {'code': code, 'msg': msg} if results: dic['results'] = results if error: dic['error'] = error dic.update(kwargs) super().__init__(data=dic, 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.get('REMOTE_ADDR')
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=
三、user表练习
作业讲解
1 自定义user表,新增phone唯一约束字段,新增icon图片字段
2 在自定义user表基础上,用GenericViewSet + CreateModelMixin + serializer,完成user表新增接口(就是注册接口)(重要提示:序列化类要重写create方法,不然密码就是明文)
3 在自定义user表基础上,用GenericViewSet + RetrieveModelMixin + serializer 完成user表单查(就是用户中心)
4 在自定义user表基础上,用GenericViewSet + UpdateModelMixin + serializer 完成头像的修改
models.py
from django.db import models from django.contrib.auth.models import AbstractUser # Create your models here. class UserInfo(AbstractUser): phone = models.CharField(max_length=11, unique=True) # 唯一 icon = models.ImageField(upload_to='icon', default='icon/default.png') # ImageField依赖于pillow模块 class Book(models.Model): name = models.CharField(max_length=64) def __str__(self): return self.name class Car(models.Model): name = models.CharField(max_length=64) def __str__(self): return self.name
view.py
# 作业讲解 from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin from app01 import models from app01 import ser class RegisterView(GenericViewSet, CreateModelMixin, RetrieveModelMixin, UpdateModelMixin): queryset = models.UserInfo.objects.all() serializer_class = ser.UserModelSerializer ''' 假设get请求和post请求,用的序列化类不一样,如何处理? 重写get_serializer_class,返回啥,用的序列化类就是啥 注册,用的序列化类是UserModelSerializer,查询一个用的序列化类是UserReadOnlySerializer ''' def get_serializer_class(self): if self.action == 'create': return ser.UserModelSerializer elif self.action == 'retrieve': return ser.UserReadOnlySerializer elif self.action == 'update': return ser.UserImageViewSerializer
ser.py
from rest_framework import serializers from rest_framework.exceptions import ValidationError from app01 import models class UserModelSerializer(serializers.ModelSerializer): re_password = serializers.CharField(max_length=11, required=True, write_only=True) # 因为re_password在表中没有,需要在这里定义 class Meta: model = models.UserInfo fields = ['username', 'password', 'phone', 're_password', 'icon'] extra_kwargs = { 'username': {'max_length': 16}, 'password': {'write_only': True} } # 局部钩子 def validate_phone(self, data): if not len(data) == 11: raise ValidationError('手机号是11位') return data # 全局钩子 def validate(self, attrs): if not attrs.get('password') == attrs.get('re_password'): raise ValidationError('两次密码不一致') attrs.pop('re_password') return attrs # 重写新增方法 def create(self, validated_data): # models.UserInfo.objects.create(**validated_data) # 密码是明文,所以要重写create方法 user_obj = models.UserInfo.objects.create_user(**validated_data) return user_obj class UserReadOnlySerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = ['username', 'icon'] class UserImageViewSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = ['icon', ]
标签:name,models,True,self,练习,user,pk,序列化,data From: https://www.cnblogs.com/coderxueshan/p/17754120.html