从如今开始,咱们将真正开始接触REST framework的核心。下面咱们介绍几个基本的模块。html
REST framework引入了一个扩展常规HTTPRequest
和Request
对象,并提供了更灵活的请求解析。request
对象的核心功能是request.data
属性,它与request.POST
相似,但对于使用Web API更加有用。python
request.POST #只处理表单数据。 只适用于'POST'方法web
request.data #处理任意数据。使用与'POST', 'PUT'和'PATCH'方法sql
REST framework还引入了一个Response对象,该对象是一种获取未渲染内容的TemplateResponse
类型,并使用内容协商来肯定正确内容类型返回给客户端。数据库
reture Response(data) # 渲染成客户端请求的内容类型
在你的视图中使用数字HTTP状态码,并非总利于阅读,若是写错代码,很容易被忽略。REST framework为每一个状态码提供更明确的标识符,例如Status模块中HTTP_400_BAD_REQUEST
。django
REST framework提供了两种编写API视图的封装。json
这些视图封装提供了一些功能,例如确保你的视图可以接收Request
实例,并将上下文添加到Response
对象,使得内容协商能够正常的运行。api
视图封装还内置了一些行为。例如在适当的时候返回405 Method Not Allowed
响应,并处理访问错误的输入request.data
时候出发任何ParaseError
异常。浏览器
咱们不须要再view.py
中JSONResponse类因此删掉,而后咱们能够重构咱们的视图。app
# quickstart/view.py from django.http import HttpResponse,JsonResponse from django.views.decorators.csrf import csrf_exempt from rest_framework.parsers import JSONParser from quickstart.models import Snippet from quickstart.serializers import SnippetSerializer from rest_framework.decorators import api_view #新增导入 from rest_framework.response import Response #新增导入 from rest_framework.status import HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND,HTTP_204_NO_CONTENT #新增导入 # @csrf_exempt # 删除 @api_view(['GET', 'POST']) # 新增 def snippet_list(request): # 列出全部代码 snippet, 或者建立一个新的snippet if request.method == 'GET': snippets = Snippet.objects.all() serializer = SnippetSerializer(snippets, many=True) # return JsonResponse(serializer.data, safe=False) # 删除 return Response(serializer.data) # 新增 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 Response(serializer.data, status=HTTP_201_CREATED) # 新增 # return JsonResponse(serializer.errors, status=400) # 删除 return Response(serializer.errors, status=HTTP_400_BAD_REQUEST) # 新增
咱们的实例视图比前面的示例有所改进。它稍微简洁一点,如今的代码与咱们使用 Forms API 时很是类似。咱们还使用了指定的状态码,这使得响应更加明显。
# @csrf_exempt # 删除 @api_view(['GET', 'PUT', 'DELETE']) #新增 def snippet_detail(request, pk): # 获取、更新或者删除一个代码 snippet try: snippet = Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: # return HttpResponse(status=404) # 删除 return Response(status=HTTP_404_NOT_FOUND) #新增 if request.method == 'GET': serializer = SnippetSerializer(snippet) # return JsonResponse(serializer.data) # 删除 return Response(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 Response(serializer.data) #新增 # return JsonResponse(serializer.errors, status=400) # 删除 return Response(serializer.errors, status=HTTP_400_BAD_REQUEST) #新增 elif request.method == 'DELETE': snippet.delete() # return HttpResponse(status=204) # 删除 return Response(status=HTTP_204_NO_CONTENT) #新增
这对咱们来讲应该都是很是熟悉的 - 它和正常 Django 视图并无什么不一样。
注意,咱们再也不显式地将请求或响应绑定到给定的内容类型。request.data 能够处理传入的 json 请求,但它也能够处理其余格式。一样,咱们返回带有数据的响应对象,但容许 REST framework 将响应渲染成正确的内容类型。
为了利用咱们的响应再也不被硬连接到单个内容类型的事实。让咱们将格式后缀的之处添加咱们的API端点。使用格式后缀,欧文提供了明确的只想给定的格式URL,这意味着咱们的API能够处理一些URLs,相似这样的格式:http://example.con/api/items/4.json
.
像下面这样在这两个视图中添加一个format
关键字参数。
def snippet_list(request, format=None): def snippet_detail(request,pk, format=None):
如今更新quickstart/urls.py 文件,在如今的urls基础上追加一组format_suffix_patterns
# quickstart/urls.py from django.conf.urls import url from .views import snippet_list, snippet_detail from rest_framework.urlpatterns import format_suffix_patterns #新增 urlpatterns = [ url('^quickstart/$', snippet_list), url('^quickstart/(?P<pk>[0-9]+)/$', snippet_detail), ] urlpatterns = format_suffix_patterns(urlpatterns) #新增
咱们不必定须要添加额外的url模式。但他给咱们一个简单、清晰的方式来引用特定的格式。
启动服务 python manage.py runserver
在浏览器中输入 http://127.0.0.1:8000/quickstart/
,结果以下
在浏览器输入http://127.0.0.1:8000/quickstart.api
,结果以下
在浏览器输入http://127.0.0.1:8000/quickstart.json
,结果以下
因为API是基于客户端发起请求的选择来响应内容的格式,所以当接收到来之浏览器的请求是,会默认以HTML的格式来描述数据,这容许API返回网页彻底可浏览的HTML。
有关可浏览的API功能以及如何对其进行定制更多的信息,可参考可浏览的API主题。
咱们也可使用基于类的视图来编写API视图,而不是基于函数的视图。正如咱们看到的,这个模式足够强大,可让咱们重用通用功能,并帮助咱们保持代码DRY(Don't repeat yourself)
咱们首先将基于类的视图重写根视图。全部者写都涉及对view.py的修改。
# quickstart/view.py class SnippetList(APIView): # 列出全部的代码snippet,或者建立一个新的snippet 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=HTTP_201_CREATED) return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
这个看起来和之前的很是类似,可是咱们在不一样的HTTP方法之间有更好的分离,还须要更新
view.py
中的示例视图。
# quickstart/view.py class SnippetDetail(APIView): # 获取、更新或者删除一个代码 snippet def get_object(self, pk): try: return Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: raise HTTP_404_NOT_FOUND 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=HTTP_400_BAD_REQUEST) def delete(self, request, pk, format=None): snippet = self.get_object(pk) snippet.delete() return Response(status=HTTP_204_NO_CONTENT)
看起来,它仍然很是相似基于函数的视图
使用基于列的视图,咱们如今还须要重构quickstart/urls.py文件内容:
from django.conf.urls import url from rest_framework.urlpatterns import format_suffix_patterns from .views import SnippetList, SnippetDetail urlpatterns = [ url('^quickstart/$', SnippetList.as_view()), url('^quickstart/(?P<pk>[0-9]+)/$', SnippetDetail.as_view()), ] urlpatterns = format_suffix_patterns(urlpatterns)
重启服务,若是你没有搬错砖,一切应该和之前同样的。
使用基于类的视图的优点之一,就是咱们能够很容易撰写可重复的行为。
到目前为止,咱们使用建立、获取、更新、删除操做和咱们建立的任何基于模型的API视图很是类似。这些常见的行为是在REST framework的mixin类中实现的。
下面咱们具体实践如何经过mixin类编写视图。在quickstart/view.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
.
基类提供核心功能,而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()
操做。重新运行服务,你会发现没有任何变化的。若是有,那就再运行一次。
咱们使用mixin类,使用了比之前更少的代码重写了视图,可是咱们能够更进一步。REST framework已经提供了一套已经混合的通用的视图。咱们能够更加简化view.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
刷新浏览器,你会发现没任何变化,若是有,老规矩:你搬错砖了
目前,咱们的api对谁都是开放的,谁均可以添加或者删除代码,没有任何隐私和限制,咱们须要更高行为来确保:
咱们对quickstart中的模块要再修改一下,首先要添加几个字段。其中一个字段将用于表示建立snippet代码关联的用户。
另一个字段将用于存储高亮显示的,HTML内容表示的代码。
添加字段到quickstart/model.py文件中的Snippet模型中:
from django.db import models from pygments.lexers import get_all_lexers from pygments.styles import get_all_styles # 提取出'pyment' 支持的全部语言的语法分析程序 LEXERS = [item for item in get_all_lexers() if item[1]] # 提取除了'pyments' 支持的全部语言列表 LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS]) # 提取出'pyment' 支持的全部格式化风格列表 STYLE_CHOICES = sorted((item, item) for item in get_all_styles()) class Snippet(models.Model): created = models.DateTimeField(auto_now_add=True) title = models.CharField(max_length=100, blank=True, default='') code = models.TextField() linenos = models.BooleanField(default=False) # 是否显示行号 language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=120) style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=120) owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE) # 新增 highlighted = models.TextField() #新增 class Meta: ordering = ('-created',)
咱们还须要确保在保存模型时候,使用pygments代码的高亮库来填充highlighted字段
咱们须要导入额外模块,
from pygments.formatters.html import HtmlFormatter from pygments import highlight from pygments.lexers import get_lexer_by_name
而后再类中添加一个方法.save()
def save(self, *args, **kwargs): # 使用 pygment 库建立一个高亮显示html表示的Snippet代码 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)
作完这些工做,同创作法是会场街一个数据库迁移来实现这一点,可是如今让咱们删掉数据库,从新开始。
(env) AdministratordeiMac:tutorial administrator$ ls db.sqlite3 env manage.py quickstart tutorial (env) AdministratordeiMac:tutorial administrator$ rm -f db.sqlite3 (env) AdministratordeiMac:tutorial administrator$ rm -r quickstart/migrations (env) AdministratordeiMac:tutorial administrator$ python manage.py makemigrations quickstart (env) AdministratordeiMac:tutorial administrator$ python manage.py migrate quickstart
你能够建立几个不一样的用户,用于测试不一样的API,最快捷的方式就是createsuperuser
命令。
pyhton manage.py createsuperuser #而后按照提示填写用户名和密码就能够了
咱们如今有一些用户可使用,咱们最好将这些用户添加到咱们的API中。建立一个新的序列化器,在serializer.py
我呢间中添加:
# quickstart/serializer.py from rest_framework import serializers from .models import Snippet from django.contrib.auth.models import User class SnippetSerializer(serializers.ModelSerializer): class Meta: model = Snippet fields = ('id', 'title', 'code', 'linenos', 'language', 'style') #新增 class UserSerializer(serializers.ModelSerializer): snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all()) class Meta: model = User fields = ('id', 'username', 'snippets')
因为
snippets
在用户模型上是反向关系,当使用ModelSerializer
类时候,它将不会被默认包含,咱们须要为他添加一个显式字段。
咱们还会在view.py
中添加几个视图。咱们想要使用用户,表示只读视图,因此咱们将使用ListAPIView
和RetrieveAPIView
通用的基于类的视图。
#quictstart/view.py from rest_framework import generics from .models import Snippet from .serializers import SnippetSerializer, UserSerializer #新增导入 from django.contrib.auth.models import User class SnippetList(generics.ListCreateAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer # 新增 class UserList(generics.ListAPIView): queryset = User.objects.all() serializer_class = UserSerializer class UserDetail(generics.RetrieveAPIView): queryset = User.objects.all() serializer_class = UserSerializer
最后咱们须要经过URL conf中引用这些视图来将这些视图添加到API中,修改以下:
from django.conf.urls import url from rest_framework.urlpatterns import format_suffix_patterns from .views import SnippetList, SnippetDetail, UserList, UserDetail #新增导入 urlpatterns = [ url('^quickstart/$', SnippetList.as_view()), url('^quickstart/(?P<pk>[0-9]+)/$', SnippetDetail.as_view()), url('^users/$',UserList.as_view()) # 新增 url('users/(?P<pk>[0-9]+)/$', UserDetail.as_view()), # 新增 ] urlpatterns = format_suffix_patterns(urlpatterns)
如今,若是咱们建立了一个Snippet代码,那么没法将建立Snippet的用户与Snippet实例关联起来。用户不是做为序列化表示的一部分发送的,而是做为请求的属性传入。
咱们处理方式是,在咱们的Snippet视图上覆盖,.perform_create()
方法,这中管理方式,容许咱们修改实例保存,并处理传入请求,或者请求URL中隐含的任何信息。
在SnippetList
视图类中添加一下方法:
class SnippetList(generics.ListCreateAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer # 新增 def perform_create(self, serializer): serializer.save(owner=self.request.user)
如今咱们的序列化器
create()
方法将被传递一个owner
字段以及来自请求的验证数据。
如今Snippet与建立他们的用户相关联了,让咱们更新咱们的SnippetSerializer
以体现这一点。将下面内容添加到serializers.py
文件中:
# quickstart/serializers.py class SnippetSerializer(serializers.ModelSerializer): owner = serializers.ReadOnlyField(source='owner.username') # 新增 class Meta: model = Snippet fields = ('id', 'title', 'code', 'linenos', 'language', 'style', 'owner')
注意:确保将
owner
添加到了内部的Meta
类的字段列表中。这个字段中,
source
参数控制哪一个属性用于填充字段,而且能够指向序列化实例的任何属性。它也能够采用如上所示的点符号(.),在这种状况下,他将与Django模板语言类似的方式遍历给定的属性。咱们添加的字段是无类型的
ReadOnlyField
类,与其余类型的字段相反,如CharField
,BooleanField
等无类型的,但ReadOnlyField
始终是只读的,只能用于序列化表示,但不能用于在反序列化实时更新模型实例。咱们这里能够用CharField(read_only=True)
.
如今,Snippet和用户相关联,咱们但愿确保只要通过身份认证的用户才能建立、更新、和删除snippets。
REST framework包含许多权限类,咱们能够用来限制访问权限。在这种状况下,咱们须要的是IsAuthenticatedOrReadOnly
类,它将确保身份验证的请求得到读写访问权限。未经身份验证的请求得到只读访问权限。
在quickstart/view.py文件中,添加以下
# quickstart/view.py from rest_framework import permissions #新增 class SnippetList(generics.ListCreateAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) #新增 def perform_create(self, serializer): serializer.save(owner=self.request.user) class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) #新增
若是你如今打开浏览器,并导航到浏览器的到API,那么你将发现没法再建立新的snippet。由于须要增长权限。
咱们能够经过编辑url.py
文件中的URLconf来添加可浏览API的登陆视图。
在文件顶部,咱们导入:
from django.conf.urls import include
并在文件末尾添加一个模式以包括可浏览的API的登陆或者注销视图。
urlpatterns = [ url('^quickstart/$', SnippetList.as_view()), url('^quickstart/(?P<pk>[0-9]+)/$', SnippetDetail.as_view()), url('^users/$',UserList.as_view()), url('users/(?P<pk>[0-9]+)/$', UserDetail.as_view()), url('^api-auth/', include('rest_framework.urls')), # 新增 ]
模式
api-auth/
部分实际上能够是你想使用的任何URL。
如今再次打开浏览器刷新页面,则会在右上角看到一个登陆
连接。若是你要用前面的建立用户登陆,就能够再次建立snippets。以下图:
一旦建立了一些snippets后,导航到/users/
端点,并注意到每一个用户的snippets
字段中包含与每一个用户相关联的snippet id列表。
实际上咱们但愿全部人均可以看到snippets,可是只要建立snippet的用户才权限CRUD。
为此,咱们须要建立一个自定义的权限。
在quickstart应用中,建立一个新的文件permission.py
.
# quickstrat/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 # 写入权限只容许给snippet的全部者 return obj.owner == request.user
如今咱们能够经过编辑SnippetDetail
视图中的permission_classes
属性将自定义权限添加到咱们snippet实例中:
# quickstart/view.py from .permissions import IsOwnerOrReadOnly # 新增 class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly) # 新增
如今再次打开浏览器,你会发现"DELETE"和'PUT'操做只会出如今以建立者身份登陆的snippet实例端点上。
由于如今咱们在API上有一组权限,若是咱们想要编辑任何snippet,咱们须要先验证咱们的请求。咱们还没设置任何认证类,所以当前应用默认的SessionAuthentication
和BasicAuthentication
.
但咱们经过web浏览器与API进行交互的时候,咱们能够登陆,而后浏览器会话将为请求提供所需的身份验证。
若是咱们以百年城的方式与API进行交互,那么咱们须要在每一个请求上明确提供身份验证凭据。
若是咱们尝试创建一个没身份验证的snippet,咱们会获得一个错误:
咱们能够经过添加以前的用户名和密码来发送成功请求:
咱们已经在咱们的webAPI上得到了一套精细的权限组合,下一节咱们将介绍如何经过为高亮显示 snippet 建立 HTML 端点来将全部内容联结在一块儿,并经过对系统内的关系使用超连接来提升 API 的凝聚力。