Django中内置的有一些Json数据的处理和返回方案
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")
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
fromdjango.forms.modelsimportmodel_to_dict
formyModelinmyModels:
json_dict=model_to_dict(myModel)
json_list.append(json_dict)
# 2. 直接序列化并通过JsonResponse和HttpResponse返回
fromdjango.coreimportserializers
importjson
json_data=serializers.serialize('json',myModels)
json_data=json.loads(json_data)
fromdjango.httpimportHttpResponse,JsonResponse
# return HttpResponse(json.dumps(json_data), content_type="application/json")
returnJsonResponse(json_data,safe=False)
# 注:在工作中出现调用系统外的SQL Server来获取数据,通过以下方式可返回Json
cur=cursor.fetchall()
fromdjango.core.serializers.jsonimportDjangoJSONEncoder
json_data=json.dumps(cur,cls=DjangoJSONEncoder)
returnHttpResponse(json_data,content_type="application/json")
虽然看似以上方案也可以打造我们的API,但更为灵活的Rest API方案还是应该要采用Django REST framework
安装
除djangorestframework, markdown, django-filter外,建议同时安装coreapi,django-guardian, 环境搭建参照Django环境搭建及开发
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')),
1
2
3
4
5
6
mkvirtualenv-p/usr/local/bin/python3myproject
pipinstalldjangodjangorestframeworkmarkdowndjango-filtermysqlclientpillow
# urls.py
fromrest_framework.documentationimportinclude_docs_urls
url(r'docs/',include_docs_urls(title="")),
url(r'^api-auth/',include('rest_framework.urls',namespace='rest_framework')),
Serializer
使用步骤一、在对应App下新建serializers.py
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__"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fromrest_frameworkimportserializers
# 方法一、指定字段
classModelNameSerializer(serializers.Serializer):
name=serializers.CharField(max_length=100,required=True)
num=serializers.IntegerField(default=0)
# 方法二、ModelSerializer(更推荐),其中__all__表示输出所有字段,也可通过传递数组/列表来指定字段
from.modelsimportModelName
classModelNameSerializer(serializers.ModelSerializer):
classMeta:
model=ModelName
fields="__all__"
# 扩展,如果model拥有外键,那么实际只会输出相应的id,要输出其中的内容,可以新建另一个Serializer来实现内嵌输出,如上述更改为:
from.modelsimportModelName,Model2Name
classModel2NameSerializer(serializers.ModelSerializer):
classMeta:
model=Model2Name
fields="__all__"
classModelNameSerializer(serializers.ModelSerializer):
fieldName=Model2NameSerializer()
classMeta:
model=ModelName
fields="__all__"
使用步骤二、Views.py
# 方法一
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
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.modelsimportModelName
from.serializersimportModelNameSerializer
fromrest_framework.viewsimportAPIView
fromrest_framework.responseimportResponse
classModelNameList(APIView):
"""
Your description
"""
defget(self,request,format=None):
model_name=ModelName.objects.all()
serializer=ModelNameSerializer(modelName,many=True)
returnResponse(serializer.data)
# 方法二
from.modelsimportModelName
from.serializersimportModelNameSerializer
fromrest_frameworkimportmixins,generics
classModelNameList(mixins.ListModelMixin,generics.GenericAPIView):
"""
Your description
"""
queryset=ModelName.objects.all()
serializer_class=ModelNameSerializer
defget(self,request,*args,**kwargs):
returnself.list(request,*args,**kwargs)
# 方法三
from.modelsimportModelName
from.serializersimportModelNameSerializer
fromrest_frameworkimportgenerics
classModelNameList(generics.ListCreateAPIView):
"""
Your description
"""
queryset=ModelName.objects.all()
serializer_class=ModelNameSerializer
分页
# 配置方法一(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
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)
fromrest_framework.paginationimportPageNumberPagination
classModelNamePagination(PageNumberPagination):
page_size=10
page_size_query_param='page_size'
max_page_size=100
page_query_param='p'
classModelNameList(generics.ListCreateAPIView):
...
pagination_class=ModelNamePagination
ViewSet
用法一
# 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
12
13
14
15
16
17
18
19
20
# Views.py
fromrest_frameworkimportviewsets,mixins
# 习惯性地将上述ModelNameList修改为ModelNameListViewSet
classModelNameListViewSet(mixins.ListModelMixin,viewsets.GenericViewSet):
# urls.py用法一
modelname_list=ModelNameViewSet.as_view({
'get':'list',
})
urlpatterns=[
url(r'modelname/$',modelname_list,name="goods_list"),
]
# urls.py用法二(更推荐)
fromrest_framework.routersimportDefaultRouter
router=DefaultRouter()
router.register(r'modelname',ModelNameListViewSet)
urlpatterns=[
url(r'^',include(router.urls)),
]
GenericViewSet
GenericAPIView
APIView
View
mixin
CreateModelMixin
ListModelMixin
RetrieveModelMixin
UpdateModelMixin
DestroyModelMixin
1
2
3
4
5
6
7
8
9
10
11
GenericViewSet
GenericAPIView
APIView
View
mixin
CreateModelMixin
ListModelMixin
RetrieveModelMixin
UpdateModelMixin
DestroyModelMixin
过滤、搜索、排序
# 方法一、在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
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中定义如下方法
defget_queryset(self):
...
# 方法二、
# 注意:这里过滤使用的是django-filter,而搜索、排序则使用的是DRF自身的filters
fromdjango_filters.rest_frameworkimportDjangoFilterBackend
fromrest_frameworkimportfilters
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
importdjango_filters
from.modelsimportProduct
classProductFilter(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')
classMeta:
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.安装django-cors-headers
pip install django-cors-headers
# 2.settings.py
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
#请放在CsrfViewMiddleware之前
...
]
CORS_ORIGIN_ALLOW_ALL = True
# 或通过CORS_ORIGIN_WHITELIST设置白名单
1
2
3
4
5
6
7
8
9
10
11
# 1.安装django-cors-headers
pipinstalldjango-cors-headers
# 2.settings.py
MIDDLEWARE=[
'corsheaders.middleware.CorsMiddleware',
#请放在CsrfViewMiddleware之前
...
]
CORS_ORIGIN_ALLOW_ALL=True
# 或通过CORS_ORIGIN_WHITELIST设置白名单
权限认证
Token配置
# 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,)
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
fromrest_framework.authtokenimportviews
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的认证方式,安装配置也很简单
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),
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pipinstalldjangorestframework-jwt
# settings.py
REST_FRAMEWORK={
'DEFAULT_AUTHENTICATION_CLASSES':(
...
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
)
}
importdatetime
JWT_AUTH={
'JWT_REFRESH_EXPIRATION_DELTA':datetime.timedelta(days=7),
'JWT_AUTH_HEADER_PREFIX':'JWT',
}
# urls.py
fromrest_framework_jwt.viewsimportobtain_jwt_token
urlpatterns=[
...
url(r'^api-token-auth/',obtain_jwt_token),
]
请求时的header为Authorization: JWT
权限控制
AllowAny
IsAuthenticated
IsAdminUser
IsAuthenticatedOrReadOnly
DjangoModelPermissions
DjangoModelPermissionsOrAnonReadOnly
DjangoObjectPermissions API暴露太多权限(POST, PUT)时需谨慎
#官方文档中给出的示例是这样的
class XxxViewSet(viewsets.ModelViewSet):
#只需将ModelViewSet修改成ReadOnlyModelViewSet即可变为只读
class XxxViewSet(viewsets.ReadOnlyModelViewSet):
1
2
3
4
#官方文档中给出的示例是这样的
classXxxViewSet(viewsets.ModelViewSet):
#只需将ModelViewSet修改成ReadOnlyModelViewSet即可变为只读
classXxxViewSet(viewsets.ReadOnlyModelViewSet):
常见问题
1.Cannot apply DjangoModelPermissions on a view that does not set .queryset or have a .get_queryset() method. 很明显这是Django Rest Framework的权限导致某些接口页面无法访问,所以自然而然地大多数人会告诉你进行如下修改
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny'
],
1
2
3
'DEFAULT_PERMISSION_CLASSES':[
'rest_framework.permissions.AllowAny'
],
但这样做会对全局产生影响,建议通过装饰器仅对相应的类或方法进行这一控制
from rest_framework import permissions
from rest_framework.decorators import permission_classes
@permission_classes((permissions.AllowAny,))
1
2
3
fromrest_frameworkimportpermissions
fromrest_framework.decoratorsimportpermission_classes
@permission_classes((permissions.AllowAny,))
或
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
class ExampleView(APIView):
permission_classes = (AllowAny,)
[php]
<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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
fromrest_framework.permissionsimportIsAuthenticated
fromrest_framework.viewsimportAPIView
classExampleView(APIView):
permission_classes=(AllowAny,)
[php]
<strong><spanstyle="color: #0000ff;">2.</span></strong>Pymssql
通过pymssql获取SQLServer中的数据会出现Decimal,datetime.datetime等无法序列化的情况
[php]
TypeError(repr(o)+" is not JSON serializable")
TypeError:datetime.datetime(2017,4,2,17,9,40)isnotJSONserializable
添加DjangoJSONEncoder来解决这一问题
from django.core.serializers.json import DjangoJSONEncoder
json.dumps(yourdata, cls=DjangoJSONEncoder)
1
2
fromdjango.core.serializers.jsonimportDjangoJSONEncoder
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这样的限制,但如果不只想在调用时进行限制呢?一种方法是添加字段用于过滤,还有就是下面的方法可供参考
# 原语句
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')
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