关于django-rest-framework:drfyasg打开文档速度很慢的解决方案

背景在应用drf-yasg的时候,有时候一不小心写了一些代码,比方写serializers的时候写了一些default参数,该参数是会查询数据库的,关上swagger文档的时候就会触发数据库查问,从而缓缓的减少了关上swagger文档的工夫 找到耗时的接口须要装置依赖pip install prettytable不便打印耗时接口 在我的项目根目录下新增一个drf-yasg的generator generators.py import timeimport prettytablefrom drf_yasg.generators import OpenAPISchemaGeneratorclass TimeCostSchemaGenerator(OpenAPISchemaGenerator): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.path_method_cost_map = {} self.table = prettytable.PrettyTable(field_names=['cost', 'method', 'url']) def get_operation(self, view, path, prefix, method, components, request): """计算获取每个接口的schema的工夫 如果接口操作的耗时小于0.01秒,则不记录该接口 """ now = time.perf_counter() operation = super().get_operation(view, path, prefix, method, components, request) cost = round(time.perf_counter() - now, 2) if cost: self.path_method_cost_map[(path, method)] = cost return operation def get_schema(self, request=None, public=False): """在获取schema的时候打印记录下来的接口和耗时表格""" schema = super().get_schema(request, public) path_method_cost_items = sorted(self.path_method_cost_map.items(), key=lambda i: i[1], reverse=True) for ((path, method), cost) in path_method_cost_items: self.table.add_row([cost, method, path]) print(self.table) return schema执行命令验证$ python manage.py generate_swagger test.json -g generators.TimeCostSchemaGenerator -o上述命令会打印出如下格局的输入,表格三列示意接口耗时,申请办法,申请url,依照耗时倒序排列 ...

October 14, 2022 · 1 min · jiezi

Django-Signals信号量

信号量最为Django的一个核心知识点,在项目中很少有使用到,所以很多人都不了解或者没听过过(包括我)。简单来说就是在进行一些操作的前后我们可以发出一个信号来获得特定的操作,这些操作包括(信息来自:https://yiyibooks.cn/xx/Djang…:django.db.models.signals.pre_save&django.db.models.signals.post_save在模型 save()方法调用之前或之后发送。django.db.models.signals.pre_delete&django.db.models.signals.post_delete在模型delete()方法或查询集的delete() 方法调用之前或之后发送。django.db.models.signals.m2m_changed模型上的 ManyToManyField 修改时发送。django.core.signals.request_started&django.core.signals.request_finishedDjango开始或完成HTTP请求时发送。其他细致的知识点,大家可以点链接查看,直接通过一个例子解释:在自定义用户模型类的时候,在后台添加用户数据因为使用了自定义模型类的create所以密码会以明文保存,接下来使用信号量方式在保存后马上修改密码解决。(网上一个项目的例子)users/signals.pyfrom django.db.models.signals import post_savefrom django.dispatch import receiverfrom django.contrib.auth import get_user_modelUser = get_user_model()# post_save:上面七大方法之一:在模型保存之后的操作# sender: 发出信号的model@receiver(post_save, sender=User)def create_user(sender, instance=None, created=False, **kwargs): """ sender:模型类。 instance:保存的实际实例。 created:如果创建了新记录True。 update_fields:Model.save()要更新的字段集,如果没有传递则为None """ if created: password = instance.password # instance相当于user instance.set_password(password) instance.save()users/apps.pyfrom django.apps import AppConfigclass UsersConfig(AppConfig): name = ‘users’ verbose_name = ‘用户管理’ def ready(self): “““使用ready加载,否则不生效””” import users.signals

February 18, 2019 · 1 min · jiezi

drf实现常用数据缓存

在以往的后台数据访问时,我们往往都会进行数据库查询,基本的流程是这样的:图中发生了三次请求,则很正常向数据库查询了三次。但是现在有这样一个场景:我们有1000个人在一个十分钟内向一个我们网站都看了同一个文章,那么我们有没有哪些可以优化我们的后端代码,因为这只是一篇文章在短时间内就被访问了1000次,当然我们网站的文章是海量的,那我们该怎么办?那么我们的数据缓存就派上用场了,基本的流程是这样的:流程在第一次请求的时候查看缓存中(redis)是否有数据,有数据则直接返回响应若redis中没有数据,则查询数据库查询数据库并将数据保存到redis中,返回响应这就是我们为什么只查询了一次数据库,若有1000次,我们在缓存时间内也只需要查询一次数据库,这里向redis中获取数据也需要耗时,但是由于redis数据存储在内存中,数据获取性能较数据库高了不止一点半点。那么在drf项目中如何去实现呢?只需要简单的三步安装pip install drf-extensions配置(可以省略)# DRF扩展REST_FRAMEWORK_EXTENSIONS = { # 缓存时间 ‘DEFAULT_CACHE_RESPONSE_TIMEOUT’: 60 * 60,}使用使用cache_response装饰器from rest_framework.response import Responsefrom rest_framework import viewsfrom rest_framework_extensions.cache.decorators import ( cache_response)from myapp.models import Cityclass CityView(views.APIView): @cache_response() def get(self, request, *args, **kwargs): cities = City.objects.all().values_list(’name’, flat=True) return Response(cities)注意,cache_response装饰器既可以装饰在类视图中的get方法上,也可以装饰在REST framework扩展类提供的list或retrieve方法上。使用cache_response装饰器无需使用method_decorator进行转换。使用扩展类(使用了视图集ViewSet)ListCacheResponseMixin:用于缓存返回列表数据的视图,与ListModelMixin扩展类配合使用,实际是为list方法添加了cache_response装饰器RetrieveCacheResponseMixin:用于缓存返回单一数据的视图,与RetrieveModelMixin扩展类配合使用,实际是为retrieve方法添加了cache_response装饰器CacheResponseMixin:为视图集同时补充List和Retrieve两种缓存,与ListModelMixin和RetrieveModelMixin一起配合使用。from myapps.serializers import UserSerializerfrom rest_framework_extensions.cache.mixins import CacheResponseMixinclass UserViewSet(CacheResponseMixin, viewsets.ModelViewSet):#继承顺序一定在ViewSet前,其实必须在对应的mixin前 serializer_class = UserSerializerdef-extensions官方文档:http://chibisov.github.io/drf…

February 15, 2019 · 1 min · jiezi

JWT验证

JWT(Json Web Token):是目前最流行的跨域身份验证解决方案。此前我们使用的身份验证方式都是基于Session:这种方式并没有什么不妥,但其实这里有三个缺点:Session一般存储在redis中,而redis数据保存在内存中,随着用户的增多,内存消耗太大。扩展性不好,用户每次验证都需要请求session服务器,增大了负载均衡能力,应用扩展受限。因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。所以,我们需要一种既能实现相同要求并且还要比session存储更有效的身份验证方式。JWT通过一种加密的方式,将加密后的数据保存返回给用户本地进行保存,我们称为token数据。其数据由三部分组成:1、header声明类型和加密的算法:{ ’typ’: ‘JWT’, #固定值 ‘alg’: ‘HS256’ #加密算法}2、payload负载这是有效信息的存放地方,其分为三部分:标准中注册的声明、公共声明、私有声明(用户信息)标准中的注册声明(有需要在使用,不强制使用):iss: jwt签发者sub: jwt所面向的用户aud: 接收jwt的一方exp: jwt的过期时间,这个过期时间必须要大于签发时间nbf: 定义在什么时间之前,该jwt都是不可用的.iat: jwt的签发时间jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。公共声明:公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.私有声明:{ “name”: “jim”, “id”: “111111”, “admin”: true}3、signature签名需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。由于base64是对称加密算法,所以可以轻松解密:因此我们在负载部分不要将私密信息放置在里面,只需要把能验证唯一的标识信息添加就可以了。有关base64,请参考:https://www.liaoxuefeng.com/w…由于目前在学习DRF,所以我介绍一下怎样在DRF项目中使用JWT进行身份验证:安装djangorestframework-jwt:pip install djangorestframework-jwt添加jwt认证类:REST_FRAMEWORK = { ‘DEFAULT_PERMISSION_CLASSES’: ( ‘rest_framework.permissions.IsAuthenticated’, ), ‘DEFAULT_AUTHENTICATION_CLASSES’: ( ‘rest_framework_jwt.authentication.JSONWebTokenAuthentication’, ‘rest_framework.authentication.SessionAuthentication’, ‘rest_framework.authentication.BasicAuthentication’, ),}添加jwt路由用于生成token:from rest_framework_jwt.views import obtain_jwt_tokenurlpatterns = [ url(r’^自己的路由/’, obtain_jwt_token),]然后我们就可以通过自己添加的路由并通过post添加username和password来获取到token了,在进行访问页面的时候我们只需要在请求头中添加一个:Authorization: JWT <your_token>,就可以得到验证了。本文参考:https://lion1ou.win/2017/01/18/

January 27, 2019 · 1 min · jiezi

DRF跨域后端解决之django-cors-headers

在使用django-rest-framework开发项目的时候我们总是避免不了跨域的问题,因为现在大多数的项目都是前后端分离,前后端项目部署在不同的web服务器上,因为我们是后端程序员,因此我要通过后端的程序实现跨域。当然如果前端框架是Vue的话,则可以代理服务实现跨域,我也就知道一点点,如果有兴趣,大家可以自行搜索哦。DRF后端实现跨域第三方扩展———djangocorsheaders,在介绍之前,我先介绍两个概念:同源策略、跨域同源策略同源策略/SOP(Same origin policy)是一种约定,是浏览器的一种安全机制。这里同源需要"协议+域名+端口"三者都相同,否则不能进行Ajax访问。跨域不同源之间的网站通信就是跨域。安装pip install django-cors-headers注册INSTALLED_APPS = ( ‘corsheaders’,)添加中间件MIDDLEWARE = [ ‘corsheaders.middleware.CorsMiddleware’, #最好添加至第一行]配置白名单#单个配置CORS_ORIGIN_WHITELIST =( ’ 域名’,)#正则配置:CORS_ORIGIN_REGEX_WHITELIST =(r’^(https?://)?(\w+.)?jim.com $’,)或者直接允许所有主机跨域CORS_ORIGIN_ALLOW_ALL = True 默认为False一般情况下,我们配置这些就足够,当然最为一个出名的扩展,肯定做的很完美,更多的配置,请访问:https://github.com/ottoyiu/dj…

January 25, 2019 · 1 min · jiezi

DRF 处理模型中的选项字段

GET API 返回选项的字符串而不是IDDjango 中常常要用到选项字段为了提高数据库效率和用户可读性,我们实际存储的是整数,显示的时候以字符串显示。这个属性在 Django Admin 得到了很好的处理,但到了 Django Rest Framework 就不会自动转化了。下面的在GET的时候显示字符串,但这个字段就变成只读但了。# models.pyclass User(AbstractUser): GENDER_CHOICES = ( (‘M’, ‘Male’), (‘F’, ‘Female’), ) gender = models.CharField(max_length=1, choices=GENDER_CHOICES)# serializers.py class UserSerializer(serializers.ModelSerializer): # 自定义了gender 字段,该字段变成只读的了。 gender = serializers.CharField(source=‘get_gender_display’) class Meta: model = User# viewsets.pyclass UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer这里涉及到一个有趣的实例方法: get_FOO_display 对于模型中含有 ++choices++ 参数的字段, FOO 是字段的名字, get_FOO_display() 返回选项的可读字符串要实现 model 中的 Choice Field, 在 GET 的时候显示选项名字,在POST的时候既能字符串又能接受ID方法一比如模型中有个status# models.pyclass CommonInfo(models.Model): INACTIVE = 0 PUBLISHED = 1 PENDING = -1 DRAFT = -2 REPORTED = -3 DELETED = -4 STATUS_CHOICES = ( (INACTIVE, ‘INACTIVE’), (PUBLISHED, ‘PUBLISHED’), (PENDING, ‘PENDING’), (DRAFT, ‘DRAFT’), (REPORTED, ‘REPORTED’), (DELETED, ‘DELETED’), ) status = models.SmallIntegerField( choices=STATUS_CHOICES, default=PUBLISHED)# utils.pyfrom rest_framework import serializersfrom collections import OrderedDictclass ChoiceDisplayField(serializers.Field): “““Custom ChoiceField serializer field.””” def init(self, choices, **kwargs): “““init.””” self._choices = OrderedDict(choices) super(ChoiceDisplayField, self).init(**kwargs) # 返回可读性良好的字符串而不是 1,-1 这样的数字 def to_representation(self, obj): “““Used while retrieving value for the field.””” return self._choices[obj] def to_internal_value(self, data): “““Used while storing value for the field.””” for i in self._choices: # 这样无论用户POST上来但是CHOICES的 Key 还是Value 都能被接受 if i == data or self._choices[i] == data: return i raise serializers.ValidationError(“Acceptable values are {0}.".format(list(self._choices.values())))# serializers.pyfrom utils import ChoiceDisplayFieldclass CommonInfoSerializer(serializers.ModelSerializer): INACTIVE = 0 PUBLISHED = 1 PENDING = -1 DRAFT = -2 REPORTED = -3 DELETED = -4 STATUS_CHOICES = ( (INACTIVE, ‘INACTIVE’), (PUBLISHED, ‘PUBLISHED’), (PENDING, ‘PENDING’), (DRAFT, ‘DRAFT’), (REPORTED, ‘REPORTED’), (DELETED, ‘DELETED’), ) status = ChoiceDisplayField(choices=STATUS_CHOICES)方法二参考页面提到了更简单的方法,虽然我没有试过。即:继承 ChoiceField 而不是 Field,就样就不用写 to_internal_value() 了。但 POST 只能接受 ID, 不能接受字符串,如下from rest_framework import serializersfrom collections import OrderedDictclass ChoiceDisplayField(serializers.ChoiceField): “““Custom ChoiceField serializer field.””” def init(self, choices, **kwargs): “““init.””” self._choices = OrderedDict(choices) super(ChoiceDisplayField, self).init(**kwargs) # 返回可读性良好的字符串而不是 1,-1 这样的数字 def to_representation(self, obj): “““Used while retrieving value for the field.””” return self._choices[obj]参考 ...

December 31, 2018 · 2 min · jiezi