一篇文章带你了解django-rest-framework

     阅读:77

django-rest-framework

restful

api部署在专有域名下

路径不能有动词

版本在地址后拼接,或在header中

不同请求不同方式

通过参数过滤

状态码, 200,201,204,401,403,404,500

使用json返回数据

安装

pip install djangorestframework

使用

https://q1mi.github.io/Django-REST-framework-documentation/

settings

INSTALLED_APPS = [
  'rest_framework',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (  # 身份验证
        'rest_framework.authentication.BasicAuthentication',  # 需要登录,用于测试,弹出框用于登录
        'rest_framework.authentication.SessionAuthentication',  # session登录
    ),
    'DEFAULT_PERMISSION_CLASSES': (  # 权限验证(全局)
        'rest_framework.permissions.IsAuthenticated',  # 普通用户
        'rest_framework.permissions.AllowAny',  # 所有用户
        'rest_framework.permissions.IsAdminUser', # 管理员
    ),
    'DEFAULT_THROTTLE_CLASSES': (  # 限流用户
        'rest_framework.throttling.AnonRateThrottle',  # 限制未授权用户
        'rest_framework.throttling.UserRateThrottle'  # 限制认证用户
    ),
    'DEFAULT_THROTTLE_RATES': {  # 限流量
        'anon': '100/day',  # 未授权用户访问限制次数, 1/minute
        'user': '1000/day',  # 认证用户访问限制次数
        'my_throttle': '10/minute',  # 自定义限制, 通用视图中用throttle_scope添加限制
    },
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',  # 默认分页器
    'PAGE_SIZE': 100,  # 默认每页数量,使用全局的时候无法用过参数修改每页数量
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
}

url

from rest_framework.routers import DefaultRouter,SimpleRouter
urlpatterns = [
  path('api/', include('rest_framework.urls'))
]
job_router = SimpleRouter()  # 创建router
job_router.register(prefix='manager/job', viewset=ModelViewSetApi, basename='job')  # 注册view视图, prefix基本路径, viewset注册的视图, basename url的name的基本部分' basename+urlname
# 访问时根据  manager/job/-> list post , manager/job/pk  put delete get,  默认没有自定义方法
# 还可通过加  .json 返回json格式的数据

urlpatterns += job_router.urls  # 添加路由
print(urlpatterns)

views

django通用视图

from django.http import HttpResponse, JsonResponse
from django.shortcuts import render
from .models import Jobs
from rest_framework import viewsets
from django.views.generic import ListView
from .serializer import JobSerializer, LabelSerializer  # 从序列化器文件引入序列化器


class Index(ListView):  # 通用视图,具体看django笔记
    template_name = 'job/index.html'
    paginate_by = 10
    job_serializer = JobSerializer(instance=Jobs.objects.all(), many=True)  # 序列化

    model = Jobs

    # def get_queryset(self):
    #     return Jobs.objects.all()

    # def get(self, request, *args, **kwargs):  # 重写get参数
    #
    #     print(self.job_serializer)
    #     return JsonResponse(self.job_serializer.data, safe=False)

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data()
        context['another'] = '其他的参数'
        context['page_range'] = pages_divider(context['page_obj'].number, context['paginator'].page_range)
        # print(context)
        """
        {'paginator': None, 'page_obj': None, 'is_paginated': False, 'object_list': <QuerySet [<Jobs: Jobs object (00018e71bca90ceacb1bacde563bd236)>]>, 'jobs_list': <QuerySet [<Jobs: Jobs object (00018e71bca90ceacb1bacde563bd236)>]>, 'view': <job.views.Index object at 0x0000022DCD9883D0>, 'another': '其他的参数'}
        """
        return context

    def post(self, request, *args, **kwargs):
        data = request.POST.get('data')
        print(data)
        d = JobSerializer(data=data)  # 反序列化, 创建数据的序列化器
        if d.is_valid(raise_exception=True):  # 数据格式检验
          print(d)
          d.save()  # 保存数据
        return HttpResponse(data)


def pages_divider(page, page_range, perpage=5):  # 获取页码范围
    page = int(page)
    if len(page_range) < perpage:
        return page_range
    if perpage / 2 - int(perpage / 2) == 0.5:
        start = page - int(perpage / 2)
        end = page + int(perpage / 2)
        if start < 1:
            start = 1
            end = perpage
        if end > page_range[-1]:
            end = page_range[-1]
            start = end - perpage
        return range(start, end + 1)
    else:
        start = page - int(perpage / 2) + 1
        end = page + int(perpage / 2)
        if start < 1:
            start = 1
            end = perpage
        if end > page_range[-1]:
            end = page_range[-1]
            start = end - perpage + 1
        return range(start, end + 1)

APIView(一级视图)

from rest_framework.views import APIView # 基本视图
from rest_framework.response import Response  # DRF响应
from rest_framework import status  # DRF状态码


class IndexApi(APIView):  # 使用DRF的类视图
    def get(self, request):  # get方法接口
        d = request.query_params  # 获取get参数
        page = request.query_params['page']

        return Response(data={'d': 1}, status=status.HTTP_200_OK)  # 使用DRF的响应,及状态码

    def post(self, request):  # post方法接口
        d = request.data  # 获取表单信息
        return Response({'d': 1})


class DetailApi(APIView):
    def get(self, request):
        id_job = request.query_params['id']
        job = Jobs.objects.get(id=id_job)  # 获取要展示model
        s_job = JobModelSerializer(instance=job, many=False)
        return Response(data=s_job.data, status=status.HTTP_200_OK)

    def post(self, request):
        data = request.data
        s = JobModelSerializer(data=data)  # 用表单数据创建序列化实例
        if s.is_valid(raise_exception=True):  # 验证数据格式
            s.save()  # 验证通过保存数据
            return Response(data)
        raise serializers.ValidationError('error')  # 不通过报错

    def put(self, request):  # put方法接口
        data = request.data
        job = Jobs.objects.get(id=data['id'])  # 获取要更新的model
        s = JobModelSerializer(instance=job, data=data)  # 更新数据的序列化器
        if s.is_valid(raise_exception=True):
            s.save()
            return Response(data)
        raise serializers.ValidationError('error')
    def delete(self, request):  # delete方法接口
        job = Jobs.objects.get(id=request.query_params['id']).delete()
        return Response(1, status=200)

Generic二级视图

三个属性三个方法 queryset,serializer_class,lookup_field及pagination_class(分页器)

from rest_framework.generics import GenericAPIView  # Generic视图
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin
from rest_framework.response import Response
from rest_framework import status


class GenericListApi(GenericAPIView):  # 二级视图-列表
    template_name = 'job/index.html'
    queryset = Jobs.objects.all()  # 通用数据集,必须用queryset
    serializer_class = JobModelSerializer  # 通用序列化器,必须用serializer_class
    class Pager(PageNumberPagination):  # 创建自定义DRF分页器, 也可以在settings直接写全局分页配置, 继承GenericAPIView的类才能使用
        max_page_size = 10  # 每页最大显示数
        page_size = 5  # 默认每页数量
        page_query_param = 'page'  # 页码参数名
        page_size_query_param = 'page_size'

    pagination_class = Pager

    def get(self, request, page):
        queryset= self.get_queryset()  # 获取数据用self.get_queryset()
        paginate_queryset = self.paginate_queryset(self.filter_queryset(queryset))  # 获取分页后的数据
        serializer = self.get_serializer(instance=job, many=True)  # 获取序列化器用, self.get_serializer(instance=job,
        # many=True)
        return Response(data=serializer.data, status=status.HTTP_200_OK)
        return render(request, self.template_name, {'data': paginate_queryset})  3 也可以使用django的响应
    def post(self,request):
        serializer = self.get_serizlizer(data=request.data, many=True)
        if serizlizer.is_valued():
            serizlizer.save()
            return Response(1)
        raise serializers.ValidationError('error') 

class GenericDetailApi(GenericAPIView):  # 二级视图-详情
    queryset = Jobs.objects.all()  # 一般都是固定的 模型的all, 想要展示的数据的所有
    serializer_class = JobModelSerializer  # 数据对应的序列化器
    lookup_field = 'pk'  # 主键参数名(默认pk), 使用get_object方法使用的get参数

    # lookup_url_kwarg =

    def get(self, request, pk):
        job = self.get_object()  # 通过lookup_field, 从queryset中get想要的数据
        serializer = self.get_serializer(instance=job)  # 获取序列化器
        return Response(data=serializer.data, status=status.HTTP_200_OK)

    def put(self, request, pk):
        job = self.get_object()  # 会自动使用lookup_field及传入的值get对应model
        serializer = self.get_serializer(instance=job, data=request.data)
        return Response(data=serializer.data, status=status.HTTP_200_OK)

    def delete(self, pk):
        job = self.get_object()
        job.delete()
        return Response(data='success', status=status.HTTP_203_NON_AUTHORITATIVE_INFORMATION)

Generic+Minin

from rest_framework.generics import GenericAPIView  # Generic视图
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin
from rest_framework.response import Response
from rest_framework import status


class MixinListApi(GenericAPIView, ListModelMixin, CreateModelMixin):  # Mixin, 通用的增删改查
    queryset = Jobs.objects.all()[:200]
    serializer_class = JobModelSerializer
    paginate_by = 20  # 分页数量
    page_kwarg = 'page'  # 页码参数

    def get(self, request):  # 获取
        return self.list(request)  # 这里的方法就是继承的ListModelMixin 创建的方法, 获取数据

    def post(self, request):  # 创建
        return self.create(request)  # CreateModelMixin的方法, 创建model


class MixinDetailApi(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):  #
    queryset = Jobs.objects.all()
    serializer_class = JobModelSerializer
    lookup_field = 'pk'  # 主键参数名(默认pk)

    lookup_url_kwarg = 'pk'

    def get(self, request, pk):
        return self.retrieve(request)

    def put(self, request, pk):  # 修改
        return self.update(request)  # 修改pk对应model

    def delete(self, request, pk):   # 删除
        return self.delete(request)  # DestroyModelMixin的方法, 删除pk对应model

视图集

     views.ModelViewSetApi.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
from rest_framework.viewsets import ViewSet, ModelViewSet, ReadOnlyModelViewSet
from django.shortcuts import get_object_or_404

# 视图集>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.
"""
ViewSet 路由映射  , path('api', viewset.as_view({'get':'list','post':'create'}),)

urlpatterns = [
    path('ModelViewSetApi/', views.ModelViewSetApi.as_view({'get': 'list', 'post': 'create'})),
    path('ModelViewSetApi/<str:pk>/', views.ModelViewSetApi.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),

]


GenericViewSet  路由映射 三个属性三个方法 queryset,serializer_class,lookup_field, get_queryset, get_serizlizer, get_object
ModelViewSet 增删改查,三个属性三个方法
ReadOnlyModelViewSet  # 获取单个,获取列表,三个属性三个方法
"""

# 视图集原理
class ViewSetApi(ViewSet):  # 提供了通过as_view映射请求方式到本身的方法(可以映射自定义方法),viewset.as_view({'get':'list','post':'create'}
    paginate_by = 10
    page_kwarg = 'page'  # 页码参数

    def list(self, request):
        queryset = Jobs.objects.all()
        p = Paginator(queryset, 10)
        serializer = JobModelSerializer(instance=p.page(request.query_params['page']), many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

    def retrieve(self, request, pk):
        queryset = Jobs.objects.all()
        job = get_object_or_404(queryset, pk=pk)
        serializer = JobModelSerializer(instance=job)
        return Response(serializer.data, status=status.HTTP_200_OK)


from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination

# 分页器
class Pager(PageNumberPagination):  # 创建自定义DRF分页器, 也可以在settings直接写全局分页配置, 继承GenericAPIView的类才能使用
    max_page_size = 10  # 每页最大显示数
    page_query_param = 'page'  # 页码参数名
    page_size_query_param = 'page_size'  # 页码数据量参数名

# 只读视图集
class ReadOnlyModelViewSetApi(ReadOnlyModelViewSet):  # 读取单个和列表,及请求映射
    queryset = Jobs.objects.all()
    serializer_class = JobModelSerializer
    lookup_field = 'pk'
    pagination_class = Pager  # 使用自定义分页器,请求时必须写page(page_query_param)和page_size(page_size_query_param)这两个参数, 继承GenericAPIView的类才能使用


from rest_framework.decorators import action
# 通用视图集
class ModelViewSetApi(ModelViewSet):  # 通用增删改查及请求方式映射
    queryset = Jobs.objects.all()
    serializer_class = JobModelSerializer
    lookup_field = 'pk'
    pagination_class = Pager  # 使用自定义分页器,请求时必须写page(page_query_param)和page_size(page_size_query_param)这两个参数, 继承GenericAPIView的类才能使用
    
    permission_class = [rest_framework.permissions.AllowAny]  # 局部权限验证
    authentication_classes = [IsAuthenticated]  # 局部身份验证
    throttle_classes = [AnonRateThrottle, UserRateThrottle]  # 局部限流
    throttle_scope = "my_throttle"  # 可选限流
    
    
    # 使DRF自动创建路由, methods允许访问的方式, url_path路径, basename(router中)+url_name = name(path中), detail是否需要传入lookup_field
    @action(methods=['put'], detail=True, url_path='change', url_name='change')  
    def update_job_requires(self,request, pk):  # 局部更新数据  prefix/<str:pk>/url_path
        job = self.get_object()
        serializer = self.get_serializer(instance=job, data=request.data, partial=True)  # partial修改部分数据
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(request.data)

自动生成路由

from rest_framework.routers import DefaultRouter,SimpleRouter
urlpatterns = []

job_router = DefaultRouter()  # 创建router
job_router.register(prefix='job', viewset=ModelViewSetApi, basename='job')  # 注册view视图, prefix基本路径, viewset注册的视图, basename url的name的基本部分' basename+urlname
# 访问时根据  manager/job/-> list post , manager/job/pk  put delete get,  默认不创建自定义方法的路由, 需要加action装饰器
# 还可通过加  .json 返回json格式的数据

urlpatterns += job_router.urls  # 添加路由

视图集自动生成的url (DefaultRouter)

<URLPattern '^job/$' [name='jobs-list']>, 
<URLPattern '^job\.(?P<format>[a-z0-9]+)/?$' [name='jobs-list']>, 
<URLPattern '^job/(?P<pk>[^/.]+)/$' [name='jobs-detail']>, 
<URLPattern '^job/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$' [name='jobs-detail']>, 
<URLPattern '^job/(?P<pk>[^/.]+)/change/$' [name='jobs-c']>, 
<URLPattern '^job/(?P<pk>[^/.]+)/change\.(?P<format>[a-z0-9]+)/?$' [name='jobs-c']>, 
<URLPattern '^$' [name='api-root']>, 
<URLPattern '^\.(?P<format>[a-z0-9]+)/?$' [name='api-root']>]

视图集自动生成的url (SimpleRouter)

<URLPattern '^job/$' [name='jobs-list']>, 
<URLPattern '^job/(?P<pk>[^/.]+)/$' [name='jobs-detail']>, 
<URLPattern '^job/(?P<pk>[^/.]+)/change/$' [name='jobs-change']>]  # 自定义方法的路由

serializer

标准序列化器

from rest_framework import serializers
import datetime
from .models import Jobs


class JobSerializer(serializers.Serializer):  # 创建序列化类
    def create(self, validated_data):  # 保存数据
        Jobs.objects.create(**validated_data)
        return 1

    def update(self, instance, validated_data):  # 更新数据
        for k, v in validated_data.items():
            if hasattr(instance, k):  # 判断是否有属性
                setattr(instance, k, v)  # 设置属性值
        instance.save()
        return 1

    id = serializers.PrimaryKeyRelatedField(read_only=True)  # 选择想要展示的字段,字段名,字段类型和model的字段一样, 主键`外键
    name = serializers.CharField(max_length=128)
    company = serializers.CharField(max_length=200)
    salary = serializers.CharField(max_length=64)
    requires = serializers.CharField()
    issue = serializers.DateTimeField()
    education = serializers.CharField(max_length=64, allow_null=True, allow_blank=True)
    position = serializers.CharField(max_length=128, allow_null=True)
    platform = serializers.CharField(max_length=20)
    get_data = serializers.DateTimeField(default=datetime.datetime.now())
    # label_set = serializers.PrimaryKeyRelatedField(read_only=True, many=True)  # 关联他的的对象, 名字就是related_name的值
    label_set = serializers.StringRelatedField(read_only=True,
                                               many=True)  # 关联他的的对象的__str__(要有__str__方法), 名字就是related_name的值

    @staticmethod  # 用不着self或cls的话可以用静态方法
    def validate_name(value):  # 单字段校验
        if '?' in value:
            raise serializers.ValidationError("不能包含特殊符号")
        return value

    @staticmethod
    def validate(*args):  # 多字段校验, 直接用一个参数就行, 不用加*和索引访问
        # print(args[0])
        # print(args)
        if args[0]['issue'] > args[0]['get_data']:
            raise serializers.ValidationError("发布时间大于爬取时间")
        return args[0]
    # def validate(data):  # 多字段校验, 直接用一个参数就行, 不用加*和索引访问
    #     if data['issue'] > data['get_data']:
    #         raise serializers.ValidationError("发布时间大于爬取时间")
    #     return data


class LabelSerializer(serializers.Serializer):
    def create(self, validated_data):
        pass

    def update(self, instance, validated_data):
        pass

    job = serializers.PrimaryKeyRelatedField(read_only=True)  # 显示关联对象的id
    # job = serializers.StringRelatedField(read_only=True)  # 显示关联对象的__str__的信息
    # job = JobSerializer()  # 显示关联对象的序列化器所有字段的信息
    value = serializers.CharField(max_length=64)
  

模型序列化器

class JobModelSerializer(serializers.ModelSerializer):  # 模型序列化器, 自动生成对应的序列化字段
    """
    内置了create,update方法
    """
    token = serializers.CharField(max_length=100, write_only=True)  # 添加额外的字段, write_only只反序列化不显示(序列化)

    class Meta:
        model = Jobs  # 自动生成所有序列化字段
        fields = "__all__"  # all生成所有字段, ['name','issue','salary'] 列表或元组,选择部分字段
        read_only_fields = ['id']  # 设置只读字段, 可以显示, 修改无效
        extra_kwargs = {
        
            'name': {  # 修改字段参数, 默认的字段参数不满足时可以在这额外添加或修改
                'max_length': 200
            },
            'position': {
                'max_length': 50
            }
        }

字段选项及字段类型


"""
字段选项
LIST_SERIALIZER_KWARGS = (
    'read_only', 'write_only', 'required', 'default', 'initial', 'source',
    'label', 'help_text', 'style', 'error_messages', 'allow_empty',
    'instance', 'data', 'partial', 'context', 'allow_null',
    'max_length', 'min_length'
)
read_only  设置只读字段, 修改无效
write_only  

反序列化时, 设置的default read_only=True required=False 不需要传参

字段类型(序列化器对应的model字段类型)
serializer_field_mapping = {
    models.AutoField: IntegerField,
    models.BigIntegerField: IntegerField,
    models.BooleanField: BooleanField,
    models.CharField: CharField,
    models.CommaSeparatedIntegerField: CharField,
    models.DateField: DateField,
    models.DateTimeField: DateTimeField,
    models.DecimalField: DecimalField,
    models.DurationField: DurationField,
    models.EmailField: EmailField,
    models.Field: ModelField,
    models.FileField: FileField,
    models.FloatField: FloatField,
    models.ImageField: ImageField,
    models.IntegerField: IntegerField,
    models.NullBooleanField: BooleanField,
    models.PositiveIntegerField: IntegerField,
    models.PositiveSmallIntegerField: IntegerField,
    models.SlugField: SlugField,
    models.SmallIntegerField: IntegerField,
    models.TextField: CharField,
    models.TimeField: TimeField,
    models.URLField: URLField,
    models.UUIDField: UUIDField,
    models.GenericIPAddressField: IPAddressField,
    models.FilePathField: FilePathField,
}

"""