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
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示例如下
补充:使用'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(),会验证不通过,可以去如下源码打印错误信息
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)