Django REST framework 简介与中文教程_restframework教程中文版(1)


序列化器类的第一部分定义得到\*\*serialized/deserialized(序列化/反序列化)\*\*的字段;\*\*create()**和**update()**方法定义了在调用**serializer.save()\*\*时如何创建或修改完全成熟的实例。


\*\*Serializer(序列化器)**类与Django的**Form(表单)\*\*类非常相似,并且在各个字段上包含类似的验证标志,比如:**required、max\_length和default**。


字段表示还可以控制**Serializer(序列化器)**在某些情况下应该如何显示,比如呈现到HTML时;**{‘base\_template’: ‘textarea.html’}**上面的标志相当于在Django的Form类上使用**widget=widgets.Textarea**。这对于控制可浏览API的显示方式特别有用,我们将在本教程后面看到。


我们实际上也可以通过使用**ModelSerializer**类来节省一些时间,我们稍后会看到,但是现在我们将保持明确的序列化器定义。


### 使用Serializers(序列化器)


在继续之前,我们将熟悉如何使用新的\*\*Serializer(序列化器)\*\*类。让我们进入Django shell:



python manage.py shell


OK,当我们完成了一些导入之后,让我们创建一些代码片段来使用。



from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser

snippet = Snippet(code=‘foo = “bar”\n’)
snippet.save()

snippet = Snippet(code=‘print(“hello, world”)\n’)
snippet.save()


现在我们有了一些代码片段实例。让我们来看看序列化其中一个实例:



serializer = SnippetSerializer(snippet)
serializer.data

{‘id’: 2, ‘title’: ‘’, ‘code’: ‘print(“hello, world”)\n’, ‘linenos’: False, ‘language’: ‘python’, ‘style’: ‘friendly’}


此时,我们已经将模型实例转换为Python原生数据类型。为了完成序列化过程,我们将数据呈现为**json**:



content = JSONRenderer().render(serializer.data)
content

b’{“id”: 2, “title”: “”, “code”: “print(\“hello, world\”)\n”, “linenos”: false, “language”: “python”, “style”: “friendly”}’


**反序列**是相似的,首先,我们将\*\*stream(流)\*\*解析为Python原生数据类型…



import io

stream = io.BytesIO(content)
data = JSONParser().parse(stream)


…然后,我们将这些原生数据类型恢复到一个完全填充的对象实例中:



serializer = SnippetSerializer(data=data)
serializer.is_valid()

True

serializer.validated_data

OrderedDict([(‘title’, ‘’), (‘code’, ‘print(“hello, world”)\n’), (‘linenos’, False), (‘language’, ‘python’), (‘style’, ‘friendly’)])

serializer.save()

<Snippet: Snippet object>


注意这个**API**与**处理表单**有多么相似;当我们开始编写使用序列化器的\*\*views(视图)\*\*时,这种相似性会变得更加明显。


我们还可以序列化**querysets**而不是模型实例;为此,我们只需向**Serializer(序列化器)**参数添加一个**many=True**标志:



serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data

[OrderedDict([(‘id’, 1), (‘title’, ‘’), (‘code’, ‘foo = “bar”\n’), (‘linenos’, False), (‘language’, ‘python’), (‘style’, ‘friendly’)]), OrderedDict([(‘id’, 2), (‘title’, ‘’), (‘code’, ‘print(“hello, world”)\n’), (‘linenos’, False), (‘language’, ‘python’), (‘style’, ‘friendly’)]), OrderedDict([(‘id’, 3), (‘title’, ‘’), (‘code’, ‘print(“hello, world”)’), (‘linenos’, False), (‘language’, ‘python’), (‘style’, ‘friendly’)])]


### 使用ModelSerializers(模型序列化器)


我们的**SnippetSerializer**类正在复制**Snippet**模型中包含的大量信息;如果我们能让代码更简洁一些就好了。


与Django同事提供**Form**类和**ModelForm**类一样,REST框架也包含**Serializer**类和**ModelSerializer**类。


让我们看看如何使用**ModelSerializer**类重构我们的序列化器;在此打开文件**snippets/serializers.py**,并使用一下代码替换**SnippetSerializer**类:



class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = (‘id’, ‘title’, ‘code’, ‘linenos’, ‘language’, ‘style’)


序列化器有一个很好的属性,您可以通过打印它的表示形式来检查序列化器实例中的所有字段;使用**python [manage.py]( ) shell**打开Django shell,然后尝试以下操作:



from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))

SnippetSerializer():

id = IntegerField(label=‘ID’, read_only=True)

title = CharField(allow_blank=True, max_length=100, required=False)

code = CharField(style={‘base_template’: ‘textarea.html’})

linenos = BooleanField(required=False)

language = ChoiceField(choices=[(‘Clipper’, ‘FoxPro’), (‘Cucumber’, ‘Gherkin’), (‘RobotFramework’, ‘RobotFramework’), (‘abap’, ‘ABAP’), (‘ada’, ‘Ada’)…

style = ChoiceField(choices=[(‘autumn’, ‘autumn’), (‘borland’, ‘borland’), (‘bw’, ‘bw’), (‘colorful’, ‘colorful’)…


重要的是要记住,**ModelSerializer**类并没有什么特别神奇的功能,它们只是创建序列化器类的快捷方式:


* 一组自动确定的字段;
* `create()`和`update()`方法的简单默认实现。


### 使用我们的序列化器编写常规的Django视图


让我们看看如何使用新的序列化器类来编写一些API视图;目前,我们不会使用REST框架的任何其他特性,我们只将视图编写为普通的Django视图。


编辑**snippets/views.py**文件,并添加一下内容:



from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


我们的API的根将是一个视图,它支持列出所有现有的代码片段,或者创建一个新的代码片段:



@csrf_exempt
def snippet_list(request):
“”"
列出所有的代码片段,或者创建一个新的代码片段
“”"
if request.method == ‘GET’:
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return JsonResponse(serializer.data, safe=False)

elif request.method == 'POST':
    data = JSONParser().parse(request)
    serializer = SnippetSerializer(data=data)
    if serializer.is_valid():
        serializer.save()
        return JsonResponse(serializer.data, status=201)
    return JsonResponse(serializer.errors, status=400)

注意,由于我们希望能够从没有**CSRF**token的客户端向这个视图发送**POST**请求,所以需要将该视图标记为**csrf\_exempt**;这不是您通常想要做的事情,REST框架视图实际上使用了比这更合理的行为,但是对于我们的目的,现在这样做。


我们还需要一个对应于单个代码片段的视图,该视图可用于`检索(retrieve)`、`更新(update)`或`删除(delete)`代码片段:



@csrf_exempt
def snippet_detail(request, pk):
“”"
检索、更新或删除一个代码片段
“”"
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return HttpResponse(status=404)

if request.method == 'GET':
    serializer = SnippetSerializer(snippet)
    return JsonResponse(serializer.data)

elif request.method == 'PUT':
    data = JSONParser().parse(request)
    serializer = SnippetSerializer(snippet, data=data)
    if serializer.is_valid():
        serializer.save()
        return JsonResponse(serializer.data)
    return JsonResponse(serializer.errors, status=400)

elif request.method == 'DELETE':
    snippet.delete()
    return HttpResponse(status=204)

最后,我们需要将这些视图连接起来;创建**snippets/urls.py**文件:



from django.urls import path
from snippets import views

urlpatterns = [
path(‘snippets/’, views.snippet_list),
path(‘snippets/int:pk/’, views.snippet_detail),
]


我们还需要在**tutorial/urls.py**文件中连接**根urlconf**,以包含**snippet应用程序**的URLs:



urlpatterns = [
path(‘’, include(‘snippets.urls’)),
]


值得注意的是,有几个边缘情况我们目前没有正确处理;如果我们发送格式不正确的**json**,或者使用视图无法处理的方法发出请求,那么我们将得到一个`500"server error"`响应;不过,现在这样就可以了。


### 测试我们对Web API的第一次尝试


现在,我们可以启动一个示例服务器,它为我们的snippets(代码片段)提供服务。


退出shell…



quit()


…并启动Django的开发服务器:



python manage.py runserver

Validating models…

0 errors found
Django version 1.11, using settings ‘tutorial.settings’
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.


在另一个终端窗口中,我们可以测试服务器。


我们可以使用**curl**或**httpie**来测试我们的API。**Httpie**是一个用Python编写的用户友好的http客户机。让我们安装它。


你可以使用pip安装**httpie**:



pip install httpie


最后,我们可以得到所有代码片段的列表:



http http://127.0.0.1:8000/snippets/

HTTP/1.1 200 OK

[
{
“id”: 1,
“title”: “”,
“code”: “foo = “bar”\n”,
“linenos”: false,
“language”: “python”,
“style”: “friendly”
},
{
“id”: 2,
“title”: “”,
“code”: “print(“hello, world”)\n”,
“linenos”: false,
“language”: “python”,
“style”: “friendly”
}
]


或者我们可以通过引用其id来获取特定代码片段:



http http://127.0.0.1:8000/snippets/2/

HTTP/1.1 200 OK

{
“id”: 2,
“title”: “”,
“code”: “print(“hello, world”)\n”,
“linenos”: false,
“language”: “python”,
“style”: “friendly”
}


同样,您可以通过在Web浏览器中访问这些URL来显示相同的json。


### 我们现在在哪里


到目前为止,我们做得还不错,我们有一个序列化API,感觉非常类似于Django的表单API,以及一些常规的Django视图。


目前,我们的API视图除了提供json响应之外,没有做任何特别的事情,还有一些我们仍想清理的错误处理边缘情况,但它是一个正常运行的Web API。


我们将在本教程的第2部分中看到如何开始改进。


## 教程2-Requests(请求)和Response(响应)


从这一点开始,我们将真正开始涵盖REST框架的核心;让我们介绍几个基本构建块。


### 请求对象


REST框架引入了一个扩展常规**HttpRequest**的**Request(请求)**对象,并提供了更灵活的请求解析。请求对象的核心功能是**request.data**属性,类似于**request.POST**,但对于处理Web API更有用。



request.POST # 只处理表单数据,只适用于’POST’方法;

request.data # 处理任意数据。适用于’POST’、‘PUT’、'PATCH’方法。


### 响应对象


REST框架还引入了一个响应对象,这是一种**TemplateResponse**类型,它接受未呈现的内容,并使用内容协商来确定要返回给客户端的正确内容类型。



return Response(data) # 根据客户端请求呈现内容类型


### 状态代码(Status codes)


在视图中使用数值HTTP状态码并不总是很明显,而且如果出现错误代码,很容易就不会注意到。REST框架为每个状态代码提供了更显式的标识符,例如状态模块中的**HTTP\_400\_BAD\_REQUEST**。始终使用这些标识符而不是使用数字标识符是一个好主意。


### 装饰API views(装饰API视图)


REST framework提供了两个用于装饰API视图的装饰器


1. **@api\_view**装饰器基于函数视图的装饰器;
2. **APIView**类用于类视图;


这些装饰器提供了一些功能,比如确保在视图中接收请求实例,并向\*\*Response(响应)\*\*对象添加上下文,以便执行内容协商。


装饰器还提供了一些行为,比如在适当的时候返回`405 Method Not Allowed`,以及处理使用格式错误的输入进行**ParseError**访问时发生的任何异常**request.data**。


### 把它们放在一起


OK,让我们继续并开始使用这些新组件来编写一些视图。


我们不再需要**[views.py]( )**中的**JSONResponse**类,所以删除它。完成之后,我们可以开始稍微重构视图。



from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer

@api_view([‘GET’, ‘POST’])
def snippet_list(request):
“”"
列出所有的代码片段,或者创建一个新的代码片段
“”"
if request.method == ‘GET’:
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)

elif request.method == 'POST':
    serializer = SnippetSerializer(data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_201_CREATED)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

我们的实例视图是对前一个示例的改进。它更加简洁,代码现在感觉非常类似于我们使用**表单(Forms)**API时的感觉。我们还使用了**命名状态代码**,这使得响应的含义更加明显。


下面是**[views.py]( )**模块中单个代码片段的视图。



@api_view([‘GET’, ‘PUT’, ‘DELETE’])
def snippet_detail(request, pk):
“”"
检索,更新或删除一个代码片段
“”"
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

if request.method == 'GET':
    serializer = SnippetSerializer(snippet)
    return Response(serializer.data)

elif request.method == 'PUT':
    serializer = SnippetSerializer(snippet, data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

elif request.method == 'DELETE':
    snippet.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)

这应该都非常熟悉 - 与使用常规Django视图没有太大区别。


请注意,我们不再明确地将我们的请求或响应绑定到给定的内容类型。 `request.data`可以处理传入的`json`请求,但它也可以处理其他格式。类似地,我们返回带有数据的响应对象,但允许REST框架将响应呈现给我们正确的内容类型。


### 在我们的URL中添加可选的格式后缀


为了使我们的响应不再硬连接到单个内容类型这一事实,我们将API格式后缀添加到API端点。使用格式后缀为我们提供了明确引用给定格式的URL,这意味着我们的API将能够处理诸如[http://example.com/api/items/4.json之类的]( ) URL 。


首先向这两个视图添加一个`format`关键字参数,就像这样:



def snippet_list(request, format=None):

def snippet_detail(request, pk, format=None):


现在稍微更新`snippets/urls.py`文件,在现有URLs之外附加一组`format_suffix_patterns`。



from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
path(‘snippets/’, views.snippet_list),
path(‘snippets/int:pk’, views.snippet_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)


我们不一定需要添加这些额外的url模式,但它为我们提供了一种简单,干净的方式来引用特定的格式。


### 去测试


继续从命令行测试API,正如我们在教程第1部分中所做的那样。所有工作都非常类似,尽管如果发送无效请求,我们有一些更好的错误处理。


我们可以像以前一样得到所有代码片段的列表。



http http://127.0.0.1:8000/snippets/

HTTP/1.1 200 OK

[
{
“id”: 1,
“title”: “”,
“code”: “foo = “bar”\n”,
“linenos”: false,
“language”: “python”,
“style”: “friendly”
},
{
“id”: 2,
“title”: “”,
“code”: “print(“hello, world”)\n”,
“linenos”: false,
“language”: “python”,
“style”: “friendly”
}
]


我们可以控制返回的响应的格式,或者使用`Accept`头部(header):



http http://127.0.0.1:8000/snippets/ Accept:application/json # 请求 JSON
http http://127.0.0.1:8000/snippets/ Accept:text/html # 请求 HTML


或附加格式后缀:



http http://127.0.0.1:8000/snippets.json # JSON 后缀
http http://127.0.0.1:8000/snippets.api # 可浏览 API 后缀


类似地,我们可以使用`Content-Type`头控制发送的请求格式:



POST 使用表单数据

http --form POST http://127.0.0.1:8000/snippets/ code=“print(123)”

{
“id”: 3,
“title”: “”,
“code”: “print(123)”,
“linenos”: false,
“language”: “python”,
“style”: “friendly”
}

POST 使用JSON

http --json POST http://127.0.0.1:8000/snippets/ code=“print(456)”

{
“id”: 4,
“title”: “”,
“code”: “print(456)”,
“linenos”: false,
“language”: “python”,
“style”: “friendly”
}


如果向上面的http请求添加一个`--debug`开关,您将能够在请求头中看到请求类型。


现在,通过访问http://127.0.0.1:8000/snippets/,在web浏览器中打开API。


### 随机索得率


由于API根据客户端请求选择响应的内容类型,所以在web浏览器请求资源时,它将默认返回资源的html格式表示。这允许API返回一个完全可web浏览的HTML表示。


拥有一个web可浏览的API是一个巨大的可用性胜利,它使开发和使用您的API变得更加容易。它还极大地降低了其他希望检查和使用您的API的开发人员的进入壁垒。


有关可浏览api特性以及如何自定义的更多信息,请参阅[browsable api]( )。


### 接下来是什么?


在教程第3部分中,我们将开始使用基于类的视图,并查看通用视图如何减少我们需要编写的代码量。


## 教程3-基于类的视图


我们还可以使用基于类的视图而不是基于函数的视图来编写API视图。我们将看到这是一个强大的模式,允许我们重用常用功能,并帮助我们保持代码[DRY]( )(Don’t Repeat Yourself)。


### 使用基于类的视图重写API


我们首先将根视图重写为一个基于类的视图。所有这些都涉及到一点对`views.py`的重构。



from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

class SnippetList(APIView):
“”"
列出所有的代码片段,或者创建一个新的代码片段
“”"
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)

def post(self, request, format=None):
    serializer = SnippetSerializer(data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data, status=status.HTTP_201_CREATED)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

到目前为止,一切顺利。它看起来与前面的例子非常相似,但是我们在不同的HTTP方法之间有更好的分离。我们还需要更新`views.py`中的实例视图。



class SnippetDetail(APIView):
“”"
检索,更新或删除一个代码片段实例
“”"
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404

def get(self, request, pk, format=None):
    snippet = self.get_object(pk)
    serializer = SnippetSerializer(snippet)
    return Response(serializer.data)

def put(self, request, pk, format=None):
    snippet = self.get_object(pk)
    serializer = SnippetSerializer(snippet, data=request.data)
    if serializer.is_valid():
        serializer.save()
        return Response(serializer.data)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

def delete(self, request, pk, format=None):
    snippet = self.get_object(pk)
    snippet.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)

OK,它还是很像基于函数的视图。


既然使用了基于类的视图,我们还需要稍微重构`snippets/urls.py`:



from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
path(‘snippets/’, views.SnippetList.as_view()),
path(‘snippets/int:pk/’, views.SnippetDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)


OK,如果您运行开发服务器,那么一切都应该像以前一样工作。


### 使用mixins


使用基于类的视图的一大好处是,它允许我们轻松地组合可重用的行为。


到目前为止,我们使用的`create/retrieve/update/delete`操作对于我们创建的任何模型支持的API视图都非常类似。这些公共行为是在REST框架的`mixin`类中实现的。


让我们看看如何使用`mixin`类来组合视图。这是`views.py`模块:



from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics

class SnippetList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer

def get(self, request, \*args, \*\*kwargs):
    return self.list(request, \*args, \*\*kwargs)

def post(self, request, \*args, \*\*kwargs):
    return self.create(request, \*args, \*\*kwargs)

我们将花一点时间研究到底发生了什么。我们使用`GenericAPIView`构建视图,并添加`ListModelMixin`和`CreateModelMixin`。




---


这是`mixins.ListModelMixin`的源码:



class ListModelMixin(object):
“”"
List a queryset.
“”"
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())

    page = self.paginate_queryset(queryset)
    if page is not None:
        serializer = self.get_serializer(page, many=True)
        return self.get_paginated_response(serializer.data)

    serializer = self.get_serializer(queryset, many=True)
    return Response(serializer.data)

这是`mixins.CreateModelMixin`源码:



class CreateModelMixin(object):
“”"
Create a model instance.
“”"
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

def perform\_create(self, serializer):
    serializer.save()

def get\_success\_headers(self, data):
    try:
        return {'Location': str(data[api_settings.URL_FIELD_NAME])}
    except (TypeError, KeyError):
        return {}

`generics.GenericAPIView`类继承了`views.APIView`类;




---


基类提供核心功能,`mixin`类提供`.list()`和`.create()`操作;然后明确地将`get()`和`post()`方法绑定到适当的操作;到目前为止已经足够简单了。



class SnippetDetail(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
generics.GenericAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer

def get(self, request, \*args, \*\*kwargs):
    return self.retrieve(request, \*args, \*\*kwargs)

def put(self, request, \*args, \*\*kwargs):
    return self.update(request, \*args, \*\*kwargs)

def delete(self, request, \*args, \*\*kwargs):
    return self.destroy(request, \*args, \*\*kwargs)

相似地,我们再次使用`GenericAPIView`类来提供核心功能,并添加`mixins`来提供`.retrieve()、.update()和.destroy()`操作。




---


这是`RetrieveModelMixin`源码:



class RetrieveModelMixin(object):
“”"
Retrieve a model instance.
“”"
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)


这是`UpdateModelMixin`源码:



class UpdateModelMixin(object):
“”"
Update a model instance.
“”"
def update(self, request, *args, **kwargs):
partial = kwargs.pop(‘partial’, False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)

    if getattr(instance, '\_prefetched\_objects\_cache', None):
        # If 'prefetch\_related' has been applied to a queryset, we need to
        # forcibly invalidate the prefetch cache on the instance.
        instance._prefetched_objects_cache = {}

    return Response(serializer.data)

def perform\_update(self, serializer):
    serializer.save()

def partial\_update(self, request, \*args, \*\*kwargs):
    kwargs['partial'] = True
    return self.update(request, \*args, \*\*kwargs)

这是`DestroyModelMixin`源码:



class DestroyModelMixin(object):
“”"
Destroy a model instance.
“”"
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)

def perform\_destroy(self, instance):
    instance.delete()

`generics.GenericAPIView`类继承了`views.APIView`类;




---


### 使用通用的基于类的视图


使用`mixin`类,我们重写了视图,比以前使用的代码稍微少一些,但是我们可以更进一步。REST框架提供了一组已经混合在一起的通用视图,我们可以使用这些视图来进一步简化`views.py`模块。



from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics

class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer

class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer




---


这是`ListCreateAPIView`源码:**发现它其实是继承了那两个mixins类和一个基类**



class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
“”"
Concrete view for listing a queryset or creating a model instance.
“”"
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)

def post(self, request, \*args, \*\*kwargs):
    return self.create(request, \*args, \*\*kwargs)

这是`RetrieveUpdateDestroyAPIView`源码:**同样的,也是继承了三个mixins类和一个基类**



class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
“”"
Concrete view for retrieving, updating or deleting a model instance.
“”"
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)

def put(self, request, \*args, \*\*kwargs):
    return self.update(request, \*args, \*\*kwargs)

def patch(self, request, \*args, \*\*kwargs):
    return self.partial_update(request, \*args, \*\*kwargs)

def delete(self, request, \*args, \*\*kwargs):
    return self.destroy(request, \*args, \*\*kwargs)



---


哇,太简洁了。我们已经免费获得了大量的资源,我们的代码看起来很好,干净,符合Django的习惯。


接下来,我们将进入本教程的第4部分,在这里我们将了解如何处理API的身份验证和权限。


## 教程4-Authentication(身份验证) 和 Permissions(权限)


目前,我们的API谁都可以编辑或删除代码段没有任何限制。我们希望有一些更高级的行为,以确保:


* 代码段始终与创建者相关联。
* 只有经过身份验证的用户才能创建摘要。
* 只有代码段的创建者可以更新或删除它。
* 未经身份验证的请求应具有完全只读访问权限。


### 向模型添加信息


我们将对`Snippet`模型类做一些更改。首先,让我们添加几个字段。其中一个字段将用于表示创建代码片段的用户。另一个字段将用于存储代码的高亮显示的HTML表示。


将以下两个字段添加到`models.py`中的`Snippet`模型中。



owner = models.ForeignKey(‘auth.User’, related_name=‘snippets’, on_delete=models.CASCADE)
highlighted = models.TextField()


我们还需要确保在保存模型时,使用`pygments`代码高亮显示库填充高亮显示的字段。


我们需要一些额外的导入:



from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight


现在我们可以在模型类中添加一个`.save()`方法:



def save(self, *args, **kwargs):
“”"
使用“pygments”库创建高亮显示的HTML代码片段的表示。
“”"
lexer = get_lexer_by_name(self.language)
linenos = ‘table’ if self.linenos else False
options = {‘title’: self.title} if self.title else {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)


完成这些之后,我们需要更新数据库表。通常我们会创建一个数据库迁移来实现这一点,但是出于本教程的目的,让我们删除数据库并重新开始。



rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate


您可能还想创建几个不同的用户,用于测试API。最快的方法是使用`createsuperuser`命令。



python manage.py createsuperuser


### 为我们的User模型添加端点


现在我们已经有了一些用户,我们最好将这些用户的表示添加到我们的API中。创建一个新的序列化器很容易。在`serializers.py`添加:



from django.contrib.auth.models import User

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

class Meta:
    model = User
    fields = ('id', 'username', 'snippets')

因为`“snippets”`是User模型上的*反向*关系,所以在使用`ModelSerializer`类时,默认情况下不会包含它,所以我们需要为它添加一个显式字段。


我们还将向`views.py`添加几个视图。我们希望仅为用户表示使用只读视图,因此我们将使用`ListAPIView`和`RetrieveAPIView`通用的基于类的视图。



from django.contrib.auth.models import User

class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer

class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer


还要确保导入`UserSerializer`类:



from snippets.serializers import UserSerializer


最后,我们需要通过从`URL conf`引用这些视图,将这些视图添加到API中。



path(‘users/’, views.UserList.as_view()),
path(‘users/int:pk/’, views.UserDetail.as_view()),


### 将Snippets与Users关联


现在,如果我们创建了一个代码片段,就没有办法将创建代码片段的用户与代码片段实例关联起来。用户不是作为序列化表示的一部分发送的,而是作为传入请求的属性发送的。


我们处理这个问题的方法是在代码片段视图上重写`.perform_create()`方法,该方法允许我们修改实例保存的管理方式,并处理传入请求或请求URL中隐含的任何信息。


在`SnippetList`视图类中,添加以下方法:



def perform_create(self, serializer):
serializer.save(owner=self.request.user)


我们的序列化器的`create()`方法现在将传递一个附加的`“owner”`字段,以及来自请求的经过验证的数据。


### 更新我们的序列化器


现在代码片段与创建它们的用户相关联,让我们更新`SnippetSerializer`来反映这一点。将以下字段添加到`serializer.py`中的序列化器定义中:



owner = serializers.ReadOnlyField(source=‘owner.username’)


注意:确保您还将`“owner”`添加到内部元类中的字段列表中:



class Meta:
    model = Snippet
    fields = ('id', 'title', 'code', 'linenos', 'language', 'style', 'owner')

这个字段正在做一些非常有趣的事情。`source`参数控制用于填充字段的属性,并可以指向序列化实例上的任何属性。它还可以使用上面所示的`.(点)`符号,在这种情况下,它将遍历给定的属性,方法与Django模板语言中使用的方法类似。


我们添加的字段是无类型`ReadOnlyField`类,而其他类型的字段,如CharField、BooleanField等……无类型的ReadOnlyField始终是只读的,将用于序列化表示,但在反序列化模式实例时不用于更新它们;我们也可以在这里使用CharField(read\_only=True)。


### 向视图添加所需的权限


现在代码片段与用户相关联,我们希望确保只有经过身份验证的用户才能创建、更新和删除代码片段。


REST框架包含许多权限类,我们可以使用它们来限制谁可以访问给定的视图。在本例中,我们正在寻找的是`IsAuthenticatedOrReadOnly`,它将确保经过身份验证的请求获得`读写`访问,而未经身份验证的请求获得`只读`访问。


首先在views模块中添加以下导入:



from rest_framework import permissions


然后,将以下属性添加到`SnippetList`和`SnippetDetail`视图类中:



permission_classes = (permissions.IsAuthenticatedOrReadOnly,)


### 向可浏览API添加登录


如果您现在打开浏览器并导航到browsable API,您将发现您不再能够创建新的代码片段。为了做到这一点,我们需要能够以用户身份登录。


通过在项目级的`urls.py`文件中编辑URLconf,我们可以添加一个login视图,以便与可浏览的API一起使用。


在文件顶部添加以下导入:



from django.conf.urls import include


在文件的末尾,添加一个模式来包含可浏览API的`login`和`logout`视图。



urlpatterns += [
path(‘api-auth/’, include(‘rest_framework.urls’)),
]


模式的`'api-auth/'`部分实际上可以是您想使用的任何URL。


现在,如果您再次打开浏览器并刷新页面,您将在页面右上角看到一个“Login”链接。如果您作为前面创建的用户之一登录,您将能够再次创建代码片段。


创建了几个代码片段后,导航到`“/users/”`端点,注意,在每个用户的`“snippets”`字段中,表示包含与每个用户关联的代码片段(snippet) id列表。


### 对象级权限


实际上,我们希望所有代码片段对任何人都可见,但也要确保只有创建代码片段的用户才能更新或删除它。


为此,我们需要创建一个自定义权限。


在`snippets`应用程序中,创建一个新文件`permissions.py`:



from rest_framework import permissions

class IsOwnerOrReadOnly(permissions.BasePermission):
“”"
自定义权限,只允许对象的所有者编辑它。
“”"

def has\_object\_permission(self, request, view, obj):
    # 任何请求都允许有读权限,
    # 所以我们总是允许GET, HEAD或OPTIONS请求。
    if request.method in permissions.SAFE_METHODS:
        return True

    # 写权限只允许给代码片段的所有者。
    return obj.owner == request.user

现在,通过编辑`SnippetDetail`视图类上的`permission_classes`属性,我们可以将自定义权限添加到代码片段实例端点:



permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)


还要确保导入`IsOwnerOrReadOnly`类。



from snippets.permissions import IsOwnerOrReadOnly


现在,如果您再次打开浏览器,如果您是以创建代码片段的相同用户登录的,您会发现“DELETE”和“PUT”操作只出现在代码片段实例端点上。


### 使用API进行身份验证


因为我们现在对API有一组权限,如果我们想编辑任何代码片段,就需要对请求进行身份验证。我们没有设置任何身份验证类,因此当前应用的是默认值,即`SessionAuthentication`和`BasicAuthentication`。


当我们通过web浏览器与API交互时,我们可以登录,然后浏览器会话将为请求提供所需的身份验证。


如果以编程方式与API交互,则需要明确地为每个请求提供身份验证凭据。


如果我们试图创建一个没有认证的代码片段,我们会得到一个错误:



http POST http://127.0.0.1:8000/snippets/ code=“print(123)”

{
“detail”: “Authentication credentials were not provided.”
}


通过包含前面创建的用户的用户名和密码,我们可以成功地发出请求。



http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code=“print(789)”

{
“id”: 1,
“owner”: “admin”,
“title”: “foo”,
“code”: “print(789)”,
“linenos”: false,
“language”: “python”,
“style”: “friendly”
}


### 总结


现在,我们已经在Web API上获得了一组相当细粒度的权限,以及系统用户和他们创建的代码片段的端点。


在本教程的第5部分中,我们将研究如何通过为高亮显示的代码段创建HTML端点来将所有内容连接在一起,并通过使用超链接处理系统中的关系来提高API的内聚性。


## 教程5-Relationships(关系) 和 Hyperlinked APIs(超链接API)


目前,我们的API中的关系用主键表示。在本教程的这一部分中,我们将通过使用关系超链接来改进API的内聚性和可发现性。


### 为API的根(root)创建端点


现在我们有了“snippets”和“users”的端点,但是我们没有API的单一入口点。要创建一个视图,我们将使用一个常规的基于函数的视图和前面介绍的`@api_view`装饰器。在你的`snippets/views.py`中添加:



from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse

@api_view([‘GET’])
def api_root(request, format=None):
return Response({
‘users’: reverse(‘user-list’, request=request, format=format),
‘snippets’: reverse(‘snippet-list’, request=request, format=format)
})


这里需要注意两件事;首先,我们使用REST框架的`reverse`函数来返回完全限定的URL;其次,URL模式由方便的名称标识,稍后我们将在`snippet/url.py`中声明这些名称。


### 为高亮显示的代码片段创建端点


我们的pastebin API中还缺少的另一个明显的东西是高亮显示代码的端点。


与所有其他API端点不同,我们不希望使用JSON,而是只显示HTML表示。REST framework提供了两种样式的HTML呈现器,一种用于处理使用模板呈现的HTML,另一种用于处理预呈现的HTML。第二个渲染器是我们想要为这个端点使用的。


在创建代码高亮显示视图时,我们需要考虑的另一件事是,没有现有的具体通用视图可供我们使用。我们返回的不是对象实例,而是对象实例的属性。


我们将使用基类来表示实例,并创建我们自己的`.get()`方法,而不是使用具体的通用视图。在你的`snippets/views.py`中添加:



from rest_framework import renderers
from rest_framework.response import Response

class SnippetHighlight(generics.GenericAPIView):
queryset = Snippet.objects.all()
renderer_classes = (renderers.StaticHTMLRenderer,)

def get(self, request, \*args, \*\*kwargs):
    snippet = self.get_object()
    return Response(snippet.highlighted)

与往常一样,我们需要将创建的新视图添加到URLconf中。我们将在`snippets/urls.py`中为我们的新API根(root)添加一个url模式:



path(‘’, views.api_root),


然后为代码片段的高亮部分添加url模式:



path(‘snippets/int:pk/highlight/’, views.SnippetHighlight.as_view()),


### 超链接我们的API


处理实体之间的关系是Web API设计中更具挑战性的方面之一。我们可以用很多不同的方式来表达一段关系:


* 使用主键。
* 使用实体之间的超链接。
* 在相关实体上使用唯一的标识字段。
* 使用相关实体的默认字符串表示。
* 将相关实体嵌套在父表示形式中。
* 其他一些自定义表示。


REST框架支持所有这些样式,并且可以跨正向或反向关系应用它们,或者跨自定义管理器(如通用外键)应用它们。


在本例中,我们希望在实体之间使用超链接样式。为此,我们将修改我们的序列化器,以扩展`HyperlinkedModelSerializer`,而不是现有的`ModelSerializer`。


`HyperlinkedModelSerializer`与`ModelSerializer`有以下区别:


* 默认情况下,它不包含`id`字段。
* 使用`HyperlinkedIdentityField`包含一个`url`字段。
* 关系使用`HyperlinkedRelatedField`,而不是`PrimaryKeyRelatedField`。




---


这是`HyperlinkedModelSerializer`源码(部分):**可以看出它继承了ModelSerializer类**



class HyperlinkedModelSerializer(ModelSerializer):
“”"
A type of ModelSerializer that uses hyperlinked relationships instead
of primary key relationships. Specifically:

* A ‘url’ field is included instead of the ‘id’ field.
* Relationships to other instances are hyperlinks, instead of primary keys.
“”"
serializer_related_field = HyperlinkedRelatedField

def get\_default\_field\_names(self, declared_fields, model_info):
	...	



---


我们可以很容易地重写现有的序列化器来使用超链接。在你的`snippets/serializers.py`中添加:



class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source=‘owner.username’)
highlight = serializers.HyperlinkedIdentityField(view_name=‘snippet-highlight’, format=‘html’)

class Meta:
    model = Snippet
    fields = ('url', 'id', 'highlight', 'owner',
              'title', 'code', 'linenos', 'language', 'style')

class UserSerializer(serializers.HyperlinkedModelSerializer):
snippets = serializers.HyperlinkedRelatedField(many=True, view_name=‘snippet-detail’, read_only=True)

class Meta:
    model = User
    fields = ('url', 'id', 'username', 'snippets')

注意,我们还添加了一个新的`“highlight”`字段。这个字段与`url`字段的类型相同,只是它指向`“snippet-highlight”`url模式,而不是`“snippet-detail”`url模式。


因为我们已经包含了格式后缀的url,比如`'.json'`,我们还需要在`highlight`字段中指出,它返回的任何格式后缀的超链接都应该使用`'.html`的后缀。


### 确保URL模式已命名


如果我们要有一个超链接API,我们需要确保为URL模式命名。让我们看看需要命名哪些URL模式。


* 我们的API的根指的是`user-list`和`snippet-list`。
* 我们的代码片段序列化器包含一个引用`snippet-highlight`的字段。
* 我们的用户序列化器包含一个引用`snippet-detail`的字段。
* 我们的代码片段和用户序列化器包括`url`字段,默认情况下这些字段将引用`“{model_name}-detail”`,在本例中是`“snippet-detail”`和`“user-detail”`。


将所有这些名称添加到URLconf之后,最后的`snippet/urls.py`文件应该是这样的:



from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

API endpoints

urlpatterns = format_suffix_patterns([
path(‘’, views.api_root),
path(‘snippets/’,
views.SnippetList.as_view(),
name=‘snippet-list’),
path(‘snippets/int:pk/’,
views.SnippetDetail.as_view(),
name=‘snippet-detail’),
path(‘snippets/int:pk/highlight/’,
views.SnippetHighlight.as_view(),
name=‘snippet-highlight’),
path(‘users/’,
views.UserList.as_view(),
name=‘user-list’),
path(‘users/int:pk/’,
views.UserDetail.as_view(),
name=‘user-detail’)
])


### 添加分页


用户的列表视图和代码片段的列表视图最终可能返回相当多的实例,因此我们希望确保对结果进行分页,并允许API客户端遍历每个单独的页面。


通过稍微修改我们的`tutorial/settings.py`文件,我们可以更改默认的列表样式来使用分页。添加以下设置:



REST_FRAMEWORK = {
‘DEFAULT_PAGINATION_CLASS’: ‘rest_framework.pagination.PageNumberPagination’,
‘PAGE_SIZE’: 10
}


注意,REST框架中的设置都被命名为一个名为`REST_FRAMEWORK`的字典设置,这有助于将它们与其他项目设置很好地分离。


如果需要,我们还可以定制分页样式,但在本例中,我们将坚持使用默认样式。


### 浏览API


如果我们打开浏览器并导航到可浏览的API,您会发现现在只需按照链接即可熟悉API。


您还可以在snippet实例上看到“highlight”链接,这将把您带到高亮显示的代码HTML表示页面。


在本教程的第6部分中,我们将研究如何使用视图集(ViewSets)和路由器(Routers)来减少构建API所需的代码量。


## 教程6-ViewSets(视图集) & Routers(路由器)


REST框架包含一个用于处理视图集的抽象,它允许开发人员集中精力对API的状态和交互进行建模,并根据公共约定自动处理URL构造。


ViewSet类几乎与视图类相同,只是它们提供了诸如read或update之类的操作,而不是诸如get或put之类的方法处理程序。


ViewSet类只在最后时刻绑定到一组方法处理程序,当它被实例化为一组视图时,通常通过使用一个Router类来处理为您定义URL conf的复杂性。


### 重构以使用视图集(ViewSets)


让我们将当前的视图重构为视图集;


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值