Django中内置的有一些Json数据的处理和返回方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
myModels = MyModel.objects.all() # 1.model直接转换为dict from django.forms.models import model_to_dict for myModel in myModels: json_dict = model_to_dict(myModel) json_list.append(json_dict) # 2. 直接序列化并通过JsonResponse和HttpResponse返回 from django.core import serializers import json json_data = serializers.serialize('json', myModels) json_data = json.loads(json_data) from django.http import HttpResponse, JsonResponse # return HttpResponse(json.dumps(json_data), content_type="application/json") return JsonResponse(json_data, safe=False) # 注:在工作中出现调用系统外的SQL Server来获取数据,通过以下方式可返回Json cur = cursor.fetchall() from django.core.serializers.json import DjangoJSONEncoder json_data = json.dumps(cur, cls=DjangoJSONEncoder) return HttpResponse(json_data, content_type="application/json") |
虽然看似以上方案也可以打造我们的API,但更为灵活的Rest API方案还是应该要采用Django REST framework
安装
除djangorestframework, markdown, django-filter外,建议同时安装coreapi,django-guardian, 环境搭建参照Django环境搭建及开发
1 2 3 4 5 6 |
mkvirtualenv -p /usr/local/bin/python3 myproject pip install django djangorestframework markdown django-filter mysqlclient pillow # urls.py from rest_framework.documentation import include_docs_urls url(r'docs/', include_docs_urls(title="")), url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), |
Serializer
使用步骤一、在对应App下新建serializers.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from rest_framework import serializers # 方法一、指定字段 class ModelNameSerializer(serializers.Serializer): name = serializers.CharField(max_length=100, required=True) num = serializers.IntegerField(default=0) # 方法二、ModelSerializer(更推荐),其中__all__表示输出所有字段,也可通过传递数组/列表来指定字段 from .models import ModelName class ModelNameSerializer(serializers.ModelSerializer): class Meta: model = ModelName fields = "__all__" # 扩展,如果model拥有外键,那么实际只会输出相应的id,要输出其中的内容,可以新建另一个Serializer来实现内嵌输出,如上述更改为: from .models import ModelName, Model2Name class Model2NameSerializer(serializers.ModelSerializer): class Meta: model = Model2Name fields = "__all__" class ModelNameSerializer(serializers.ModelSerializer): fieldName = Model2NameSerializer() class Meta: model = ModelName fields = "__all__" |
使用步骤二、Views.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# 方法一 from .models import ModelName from .serializers import ModelNameSerializer from rest_framework.views import APIView from rest_framework.response import Response class ModelNameList(APIView): """ Your description """ def get(self, request, format=None): model_name = ModelName.objects.all() serializer = ModelNameSerializer(modelName, many=True) return Response(serializer.data) # 方法二 from .models import ModelName from .serializers import ModelNameSerializer from rest_framework import mixins, generics class ModelNameList(mixins.ListModelMixin, generics.GenericAPIView): """ Your description """ queryset = ModelName.objects.all() serializer_class = ModelNameSerializer def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) # 方法三 from .models import ModelName from .serializers import ModelNameSerializer from rest_framework import generics class ModelNameList(generics.ListCreateAPIView): """ Your description """ queryset = ModelName.objects.all() serializer_class = ModelNameSerializer |
分页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 配置方法一(settings.py) REST_FRAMEWORK = { 'PAGE_SIZE': 10 } # 配置方法二(Views.py) from rest_framework.pagination import PageNumberPagination class ModelNamePagination(PageNumberPagination): page_size = 10 page_size_query_param = 'page_size' max_page_size = 100 page_query_param = 'p' class ModelNameList(generics.ListCreateAPIView): ... pagination_class = ModelNamePagination |
ViewSet
用法一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# Views.py from rest_framework import viewsets, mixins # 习惯性地将上述ModelNameList修改为ModelNameListViewSet class ModelNameListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): # urls.py用法一 modelname_list = ModelNameViewSet.as_view({ 'get': 'list', }) urlpatterns = [ url(r'modelname/$', modelname_list, name="goods_list"), ] # urls.py用法二(更推荐) from rest_framework.routers import DefaultRouter router = DefaultRouter() router.register(r'modelname', ModelNameListViewSet) urlpatterns = [ url(r'^', include(router.urls)), ] |
1 2 3 4 5 6 7 8 9 10 11 |
GenericViewSet GenericAPIView APIView View mixin CreateModelMixin ListModelMixin RetrieveModelMixin UpdateModelMixin DestroyModelMixin |
过滤、搜索、排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# 方法一、在ViewSet中定义如下方法 def get_queryset(self): ... # 方法二、 # 注意:这里过滤使用的是django-filter,而搜索、排序则使用的是DRF自身的filters from django_filters.rest_framework import DjangoFilterBackend from rest_framework import filters filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) filter_fields = ('field_name1', 'field_name2') search_fields = ('field_name1', 'field_name2') ordering_fields = ('field_name1',) # 注:在Django的配置中使用tuple如果只有一个元素建议在最后加一个逗号,否则会导致配置失效,因为不加逗号会将其视为字符串类型 # 升级方案,创建filters.py import django_filters from .models import Product class ProductFilter(django_filters.rest_framework.FilterSet): # django-filter2.0之后,以下 name 需修改为 field_name min_price = django_filters.NumberFilter(name="price", lookup_expr='gte') max_price = django_filters.NumberFilter(name="price", lookup_expr='lte') class Meta: model = Product fields = ['min_price', 'max_price'] # 在ViewSet中添加 filter_class = ProductFilter |
跨域访问
API通常都是由不同域名来进行调用,此将会出现如下报错 Failed to load resource: the server responded with a status of 504 (Gateway Timeout) Django的解决方案如下
1 2 3 4 5 6 7 8 9 10 11 |
# 1.安装django-cors-headers pip install django-cors-headers # 2.settings.py MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', #请放在CsrfViewMiddleware之前 ... ] CORS_ORIGIN_ALLOW_ALL = True # 或通过CORS_ORIGIN_WHITELIST设置白名单 |
权限认证
Token配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# settings.py INSTALLED_APPS = ( ... 'rest_framework.authtoken' ) REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.TokenAuthentication', ) } # urls.py from rest_framework.authtoken import views urlpatterns += [ url(r'^api-token-auth/', views.obtain_auth_token) ] # 执行makemigrations 和 migrate生成Token相关数据表 # View内Token认证,注释DEFAULT_AUTHENTICATION_CLASSES中的Token部分,添加 authentication_classes = (TokenAuthentication,) |
请求时的header为Authorization: Token 虽然Django REST Framework(DRF)的Token非常强大,但存在着无有效期、一旦泄露则面临风险,因此有人根据JWT规范开发一个REST framework JWT的认证方式,安装配置也很简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
pip install djangorestframework-jwt # settings.py REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( ... 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', ) } import datetime JWT_AUTH = { 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7), 'JWT_AUTH_HEADER_PREFIX': 'JWT', } # urls.py from rest_framework_jwt.views import obtain_jwt_token urlpatterns = [ ... url(r'^api-token-auth/', obtain_jwt_token), ] |
请求时的header为Authorization: JWT
权限控制
AllowAny
IsAuthenticated
IsAdminUser
IsAuthenticatedOrReadOnly
DjangoModelPermissions
DjangoModelPermissionsOrAnonReadOnly
DjangoObjectPermissions API暴露太多权限(POST, PUT)时需谨慎
1 2 3 4 |
#官方文档中给出的示例是这样的 class XxxViewSet(viewsets.ModelViewSet): #只需将ModelViewSet修改成ReadOnlyModelViewSet即可变为只读 class XxxViewSet(viewsets.ReadOnlyModelViewSet): |
常见问题
1.Cannot apply DjangoModelPermissions on a view that does not set .queryset
or have a .get_queryset()
method. 很明显这是Django Rest Framework的权限导致某些接口页面无法访问,所以自然而然地大多数人会告诉你进行如下修改
1 2 3 |
'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.AllowAny' ], |
但这样做会对全局产生影响,建议通过装饰器仅对相应的类或方法进行这一控制
1 2 3 |
from rest_framework import permissions from rest_framework.decorators import permission_classes @permission_classes((permissions.AllowAny,)) |
或
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from rest_framework.permissions import IsAuthenticated from rest_framework.views import APIView class ExampleView(APIView): permission_classes = (AllowAny,) [php] &nbsp; <strong><span style="color: #0000ff;">2.</span></strong>Pymssql 通过pymssql获取SQL Server中的数据会出现Decimal, datetime.datetime等无法序列化的情况 [php] TypeError(repr(o) + " is not JSON serializable") TypeError: datetime.datetime(2017, 4, 2, 17, 9, 40) is not JSON serializable |
添加DjangoJSONEncoder来解决这一问题
1 2 |
from django.core.serializers.json import DjangoJSONEncoder json.dumps(yourdata, cls=DjangoJSONEncoder) |
3.IntegrityError at /api/***/ (1048, “Column ‘***’ cannot be null”) 这个似乎出现在操作包含外链的Model时,将POST的数据插入到数据库中,一种解决方法是: 将Serializer中的HyperlinkedModelSerializer修改为ModelSerializer 4.Cannot filter a query once a slice has been taken. 在写api时只想暴露几条记录,获取单条记录详细信息时就出现了上面的错误,只针对调用的话在url后加上?limit=3这样的限制,但如果不只想在调用时进行限制呢?一种方法是添加字段用于过滤,还有就是下面的方法可供参考
1 2 3 4 5 |
# 原语句 queryset = Articles.objects.all().order_by('-pub_date')[:3] # 改正后的语句 count = Articles.objects.all().count() queryset = Articles.objects.filter(id__range=(count-2, count)).order_by('-pub_date') |
5. TemplateDoesNotExist at /xxxx/ django_filters/rest_framework/crispy_form.html 出现这一报错是由于没有在settings.py的INSTALLED_APPS中添加django_filters