0.学习链接

HyperLinkedModelSerializer

HyperLinkedModelSerializer 是一个值得推荐的 Serializer,它能够自动为 HTMLRenderer 提供相关外键资源的超链接,便于 web 调试。

参考链接

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

https://www.cnblogs.com/liwenzhou/p/9403382.html

 

https://www.jianshu.com/p/fad41a00c2de

https://juejin.im/post/5a98ee2a51882555677df987

官网:https://www.django-rest-framework.org/api-guide/views/

https://blog.windrunner.me/python/web/django-rest-framework.html

http://blog.leanote.com/post/1982916058@qq.com/django-REST-framework%E7%9A%84%E5%90%84%E7%A7%8DView%E8%A7%86%E5%9B%BE%E7%B1%BB

https://cloud.tencent.com/developer/article/1466174

https://segmentfault.com/a/1190000011488275

https://www.qingtingip.com/h_190913.html

https://www.520mwx.com/view/30160

https://blog.csdn.net/aaronthon/article/details/82923029

django rest framework serializers小结

django rest framework mixins小结

django rest framework apiview、viewset总结分析

http://code4fs.xyz/category/3/

Django REST framework的各种技巧——1.基础讲解
Django REST framework的各种技巧——2.serializer
Django REST framework的各种技巧——3.权限
Django REST framework的各种技巧——4.Generic View
Django REST framework的各种技巧——5.搜索
Django REST framework的各种技巧——6.异常处理
Django REST framework的各种技巧——7.导入导出

1.django多认证类

REST_FRAMEWORK = {
    "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
    'ALLOWED_VERSIONS': ['v1', 'v2'],
    "DEFAULT_VERSION": 'v1',
    "DEFAULT_AUTHENTICATION_CLASSES": [
        'user.utils.auth.token_auth.LoginTokenAuthtication',
        'user.utils.auth.token_auth.ApiTokenAuthtication',
    ]
}

此处DEFAULT_AUTHENTICATION_CLASSES是支持多个认证类,当第一个认证类返回None的时候,自动进行下一个认证类,如果返回了元祖或者异常,则不再尝试后面的认证类,参考:https://www.django-rest-framework.org/api-guide/authentication/

如果是继承BaseAuthentication的话,必须重写authenticate方法,返回值则参考上面说的,如果是继承其他的认证类,比如_TokenAuthentication,这个类authenticate默认下面红色字体返回的是None,即默认本身认证不通过,需要尝试下一个认证流程,但是在实际项目中,如果这个类是最后一个认证的类,这块就得触发异常,否则会导致的现象是认证不通过,却可以访问资源

class ApiTokenAuthtication(_TokenAuthentication):
    model = ApiToken

    def authenticate(self, request):
        auth = get_authorization_header(request).split()
        print(auth)
        if not auth or auth[0].lower() != self.keyword.lower().encode():
            raise exceptions.AuthenticationFailed('认证信息有误')
        if len(auth) == 1:
            msg = _('Invalid token header. No credentials provided.')
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = _('Invalid token header. Token string should not contain spaces.')
            raise exceptions.AuthenticationFailed(msg)

        try:
            token = auth[1].decode()
        except UnicodeError:
            msg = _('Invalid token header. Token string should not contain invalid characters.')
            raise exceptions.AuthenticationFailed(msg)

        return self.authenticate_credentials(token)

另外

def get_authorization_header(request):
    """
    Return request's 'Authorization:' header, as a bytestring.

    Hide some test client ickyness where the header can be unicode.
    """
    auth = request.META.get('HTTP_AUTHORIZATION', b'')
    if isinstance(auth, text_type):
        # Work around django test client oddness
        auth = auth.encode(HTTP_HEADER_ENCODING)
    return auth

由于这块的定义,所以header中传入参数的时候key必须是authorization,可以是大写或者小写,http会自动全部转化为大写,postman示例如下

22-自己学习的DRF笔记

 补充:使用'user.utils.auth.token_auth.ApiTokenAuthtication', 这种认证方式需要手工创建token,创建方式如下,执行python manage.py shell

>>> from user.models import LoginToken, ApiToken, UserInfo
>>> UserInfo.objects.get(id=4)
<UserInfo: wuxiaoyu>
>>> user=UserInfo.objects.get(id=4)
>>> ApiToken.objects.get_or_create(user=user)
(<ApiToken: 4cde9916b91ae132efdd5c105b418958879ea140>, True)
>>> 

备注,djaogo默认的表中的token值是用key这个字段存储的,但是在使用select查询的时候,由于key是mysql的关键词会冲突报语法错误,所以解决方式是把key改成其他字符串比如token

原先

from rest_framework.authtoken.models import Token as _Token
class ApiToken(_Token):
    """
    API用户的token信息和指定资源类型分配的操作权限
    """
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        db_constraint=False, on_delete=models.CASCADE,
        related_name='api_token', verbose_name='User',
    )
    grant_resource = models.CharField(max_length=64, verbose_name='授权的资源类型')
    grant_operation = models.CharField(max_length=64, verbose_name='授权的操作')

继承的_Token内使用的是key这个单词作为字段名

优化,需要重写部分父类的方法

把父类from rest_framework.authtoken.models import Token 中的所有字段和方法copy过来,去掉key字段,加上token,如果继承的话会有两个主键的冲突!!!
import binascii
import os

class ApiToken(models.Model):
"""
API用户的token信息和指定资源类型分配的操作权限
"""
token = models.CharField(max_length=40, primary_key=True)
user = models.OneToOneField(
settings.AUTH_USER_MODEL, related_name='auth_token',
on_delete=models.CASCADE, verbose_name="User"
)
grant_resource = models.CharField(max_length=64, verbose_name='授权的资源类型')
grant_operation = models.CharField(max_length=64, verbose_name='授权的操作')
created = models.DateTimeField(auto_now_add=True)

class Meta:
# abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS # 这个要注释掉,否则为True的话是抽象类,则不在数据库中创建对应的数据表了
verbose_name = "Token"
verbose_name_plural = "Tokens"

def save(self, *args, **kwargs):
if not self.token:
self.token = self.generate_key()
return super(ApiToken, self).save(*args, **kwargs)

def generate_key(self):
return binascii.hexlify(os.urandom(20)).decode()

def __str__(self):
return self.token

 同时要重写authenticate_credentials方法,将get函数的key替换为token,如下

class ApiTokenAuthtication(_TokenAuthentication):
    # 分配的token不失效,除非删除
    model = ApiToken

    def authenticate_credentials(self, key):
        model = self.get_model()
        try:
            token = model.objects.select_related('user').get(token=key)
        except model.DoesNotExist:
            raise exceptions.AuthenticationFailed('Invalid token.')

        if not token.user.is_active:
            raise exceptions.AuthenticationFailed('User inactive or deleted.')

        return (token.user, token)

    def authenticate(self, request):
        # 重写父类的方法
        auth = get_authorization_header(request).split()
        if not auth or auth[0].lower() != self.keyword.lower().encode():
            raise exceptions.AuthenticationFailed('认证信息有误')
        if len(auth) == 1:
            msg = 'Invalid token header. No credentials provided.'
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = 'Invalid token header. Token string should not contain spaces.'
            raise exceptions.AuthenticationFailed(msg)

        try:
            token = auth[1].decode()
        except UnicodeError:
            msg = 'Invalid token header. Token string should not contain invalid characters.'
            raise exceptions.AuthenticationFailed(msg)

        return self.authenticate_credentials(token)

 

2.django序列化之PrimaryKeyRelatedField

models.py中
class Snippet(models.Model):
    owner = models.ForeignKey('auth.User', related_name="snippets", on_delete=models.CASCADE)

serializers.py当中
from django.contrib.auth.models import User

from snippets.models import Snippet
class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

    class Meta:
        model = User
        fields = ("id", "username", "snippets")
        
"""
因为'snippets' 在用户模型中是一个反向关联关系。
在使用 ModelSerializer 类时它默认不会被包含,
所以我们需要为它添加一个显式字段。
"""

更多可参考:https://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield 

3.序列化一对多的创建

https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers 这里介绍的是一对多的关系,但创建的时候是创建【一】这一边的对象,以及如何关联处理【多】 这边的对象,但是如果是反过来,我们是要创建【多】这边的对象,并和【一】这边的对象关联,这个该怎么做呢,

https://www.django-rest-framework.org/api-guide/relations/#slugrelatedfield 这个也是创建【一】这一边的对象,以及如何关联处理【多】 这边的对象,但是

When using SlugRelatedField as a read-write field, you will normally want to ensure that the slug field corresponds to a model field with unique=True.

一对多创建方法如下

class BusinessLineList(generics.ListCreateAPIView):
    def perform_create(self, serializer):
        """
        执行perform_create的时候就是is_valid已经通过了
        和上面一样,这块实现外键的插入逻辑,有两种思路:
        1.插入对象,本例子就是插入队形
        2.插入 字段名_id = id
        """
        dp_entry = Department.objects.get(id=self.request.data.get('department_id'))
        serializer.save(department=dp_entry)
    queryset = BusinessLine.objects.all()
    serializer_class = BusinessLineSerializer


class BusinessLineDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = BusinessLine.objects.all()
    serializer_class = BusinessLineSerializer

多对多创建方法如下

class HostList(generics.ListCreateAPIView):
    def perform_create(self, serializer):
        """
        执行perform_create的时候就是is_valid已经通过了
        和上面一样,这块实现外键的插入逻辑,有两种思路:
        1.插入对象,本例子就是插入队形
        2.插入 字段名_id = id
        """
        host_entry = serializer.save()
        service_list = self.request.data.get('Service')
        for service in service_list:
            sv_entry = Service.objects.get(id=service['id'])
            host_entry.Service.add(sv_entry)

    queryset = Host.objects.all()
    serializer_class = HostSerializer

如果在序列化中实现可参考https://www.jianshu.com/p/62088c9b05d5

4.反序列化 is_valid不通过怎么办

接口序列化的时候默认展示的是models中显示的字段,比如Service的model是

class Service(models.Model):
    service_name = models.CharField(max_length=64, verbose_name='服务名称')
    service_env = models.CharField(max_length=64, default='', verbose_name='环境')
    service_introduce = models.TextField(default='', verbose_name='服务介绍')
    service_owner = models.CharField(max_length=64, verbose_name='服务负责人')
    service_qa = models.CharField(max_length=64, verbose_name='服务负责人')
    service_op = models.CharField(max_length=64, verbose_name='服务负责人')
    business_line = models.ForeignKey(BusinessLine, on_delete=models.DO_NOTHING)
    department = models.ForeignKey(Department, on_delete=models.DO_NOTHING)
    node_key = models.CharField(default='', max_length=128, verbose_name='唯一key')
    git_registry = models.CharField(default='', max_length=128, verbose_name='服务的git仓库地址')
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    modify_time = models.DateTimeField(auto_now=True, verbose_name='修改时间')

展示的信息是

[
    {
        "id": 1,
        "service_name": "ops_temp_service",
        "service_env": "prod",
        "service_introduce": "ops_temp_service",
        "service_owner": "[\"jump\"]",
        "service_qa": "[\"reader\"]",
        "service_op": "[\"reader\"]",
        "node_key": "ops_temp_node.ops_temp_bl.ops_temp_service.prod",
        "git_registry": "git",
        "create_time": "2019-08-27T10:24:47.674545",
        "modify_time": "2019-08-27T10:24:47.674568",
        "business_line": 1,
        "department": 1
    },
    ......
]

如果在serializers.py中没有重写上面的business_line 和 department 字段(下面的例子展示了通过SerializerMethodField的方式重写的department字段),则在反序列化(即创建)的时候必须也传入business_line 和 department 字段,否则serializer.is_valid(),会验证不通过,可以去如下源码打印错误信息

22-自己学习的DRF笔记
def is_valid(self, raise_exception=False):
    assert not hasattr(self, 'restore_object'), (
            'Serializer `%s.%s` has old-style version 2 `.restore_object()` '
            'that is no longer compatible with REST framework 3. '
            'Use the new-style `.create()` and `.update()` methods instead.' %
            (self.__class__.__module__, self.__class__.__name__)
    )

    assert hasattr(self, 'initial_data'), (
        'Cannot call `.is_valid()` as no `data=` keyword argument was '
        'passed when instantiating the serializer instance.'
    )

    if not hasattr(self, '_validated_data'):
        try:
            self._validated_data = self.run_validation(self.initial_data)
        except ValidationError as exc:
            self._validated_data = {}
            self._errors = exc.detail
        else:
            self._errors = {}

    if self._errors and raise_exception:
        raise ValidationError(self.errors)
    print('----')
    print(self._errors)
    print(bool(self._errors))
    return not bool(self._errors)
View Code

相关文章:

  • 2021-09-05
  • 2022-01-21
  • 2021-07-31
  • 2022-12-23
  • 2021-08-02
  • 2022-12-23
  • 2021-10-03
  • 2021-12-01
猜你喜欢
  • 2021-12-12
  • 2021-05-20
  • 2021-06-01
  • 2021-12-07
  • 2022-12-23
  • 2022-12-23
  • 2021-05-24
相关资源
相似解决方案