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)
让我们将当前的视图重构为视图集;
首先,让我们将UserList和UserDetail视图重构为一个UserViewSet。我们可以删除这两个视图,并用一个类替换它们:
from rest_framework import viewsets
class UserViewSet(viewsets.ReadOnlyModelViewSet):
“”"
这个视图集自动提供’list’和’detail’操作
“”"
queryset = User.objects.all()
serializer_class = UserSerializer
这里我们使用ReadOnlyModelViewSet类自动提供默认的“只读”操作。我们仍然像使用常规视图时一样设置queryset和serializer_class属性,但是我们不再需要向两个单独的类提供相同的信息。
ReadOnlyModelViewSet 类源码:
class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet):
“”"
A viewset that provides default list()
and retrieve()
actions.
提供默认的’list’和’retrieve’操作的ViewSet。
“”"
pass
接下来,我们将替换SnippetList、SnippetDetail和SnippetHighlight视图类;我们可以删除这三个视图,并再次用一个类替换它们。
from rest_framework.decorators import action
from rest_framework.response import Response
class SnippetViewSet(viewsets.ModelViewSet):
“”"
这个视图集自动提供’list’、‘create’、‘retrieve’、'update’和’destroy’操作;
此外,还提供了一个额外的’highlight’操作;
“”"
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
@action(detail=True, renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
这一次,我们使用ModelViewSet类来获得完整的默认读和写操作集;
ModelViewSet 类源码:
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
“”"
提供默认的’create()'、‘retrieve()’、‘update()’、‘partial_update()’、'destroy()'和’list()'操作;
“”"
pass
注意,我们还使用**@action装饰器创建了一个名为highlight的自定义操作;此装饰器可用于添加任何不符合标准create/update/delete**样式的自定义端点。
默认情况下,使用**@action装饰器的自定义操作将响应GET请求;如果想要响应POST请求的操作,可以使用methods**参数。例如:
@action(detail=True, methods=[‘GET’,‘POST’], renderer_classes=[renderers.StaticHTMLRenderer])
- 如果没有methods=[‘GET’, ‘POST’]参数,向API发送POST请求:
- 如果添加了methods=[‘GET’, ‘POST’]参数,向API发送POST请求:
默认情况下,自定义操作的URL依赖于方法名称本身;如果希望更改URL的构造方式,可以将url_path包含为装饰器关键字参数。
明确地将视图集(ViewSets)绑定到URLs
处理程序方法只在定义URL Conf时绑定到操作;为了了解底层发生了什么,我们首先要从ViewSets中明确地创建一组视图。
在snippets/urls.py文件中,我们将ViewSet类绑定到一组具体视图中。
from snippets.views import SnippetViewSet, UserViewSet, api_root
from rest_framework import renderers
snippet_list = SnippetViewSet.as_view({
‘get’: ‘list’,
‘post’: ‘create’
})
snippet_detail = SnippetViewSet.as_view({
‘get’: ‘retrieve’,
‘put’: ‘update’,
‘patch’: ‘partial_update’,
‘delete’: ‘destroy’
})
snippet_highlight = SnippetViewSet.as_view({
‘get’: ‘highlight’
}, renderer_classes=[renderers.StaticHTMLRenderer])
user_list = UserViewSet.as_view({
‘get’: ‘list’
})
user_detail = UserViewSet.as_view({
‘get’: ‘retrieve’
})
请注意,我们如何通过将http方法绑定到每个视图所需的操作来从每个类创建多个视图;
现在我们已将资源绑定到具体视图中,我们可以向往常一样使用URL conf注册视图;
urlpatterns = format_suffix_patterns([
path(‘’, api_root),
path(‘snippets/’, snippet_list, name=‘snippet-list’),
path(‘snippets/int:pk/’, snippet_detail, name=‘snippet-detail’),
path(‘snippets/int:pk/highlight/’, snippet_highlight, name=‘snippet-highlight’),
path(‘users/’, user_list, name=‘user-list’),
path(‘users/int:pk/’, user_detail, name=‘user-detail’)
])
此时,我们可以重新运行我们的开发服务器(python manage.py runserver)并且前往浏览器进行我们的API测试了。
使用Routers(路由器)
因为我们使用的是ViewSet类而不是View类,所以我们实际上不需要自己设计URL conf;可以使用Router类自动处理将资源连接到视图和URL的约定;我们所需要做的就是向**Router(路由器)**注册适当的视图集,然后让它来完成剩下的工作。
这是我们重新连接的snippets/urls.py文件:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from snippets import views
创建一个路由器并注册我们的视图集
router = DefaultRouter()
router.register(r’snippets’, views.SnippetViewSet)
router.register(r’users’, views.UserViewSet)
API的URLs现在由路由器自动确定
urlpatterns = [
path(‘’, include(router.urls)),
]
向路由器注册视图集类似于提供urlpattern;我们包括两个参数—视图的URL前缀和视图集本身。
我们使用的DefaultRouter类还自动为我们创建API根视图(api_root),所以现在我们可以从views(视图)模块中删除api_root方法。
视图(views)与视图集(viewsets)之间的权衡
使用视图集可能是一个非常有用的抽象。它有助于确保URL约定在API中保持一致,最大限度地减少需要编写的代码量,使我们可以专注于API提供的交互和表示,而不是URL conf的细节。
这并不意味着它始终是正确的方法。当使用基于类的视图而不是基于函数的视图时,需要考虑类似的一组权衡。使用视图集不如单独构建视图那么明确。
教程7-模式(Schemas) & 客户端库(client libraries)
模式(schema)是机器可读的文档,描述可用的API端点、它们的URLS,以及它们支持哪些操作;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
r()
router.register(r’snippets’, views.SnippetViewSet)
router.register(r’users’, views.UserViewSet)
API的URLs现在由路由器自动确定
urlpatterns = [
path(‘’, include(router.urls)),
]
向路由器注册视图集类似于提供urlpattern;我们包括两个参数—视图的URL前缀和视图集本身。
我们使用的DefaultRouter类还自动为我们创建API根视图(api_root),所以现在我们可以从views(视图)模块中删除api_root方法。
视图(views)与视图集(viewsets)之间的权衡
使用视图集可能是一个非常有用的抽象。它有助于确保URL约定在API中保持一致,最大限度地减少需要编写的代码量,使我们可以专注于API提供的交互和表示,而不是URL conf的细节。
这并不意味着它始终是正确的方法。当使用基于类的视图而不是基于函数的视图时,需要考虑类似的一组权衡。使用视图集不如单独构建视图那么明确。
教程7-模式(Schemas) & 客户端库(client libraries)
模式(schema)是机器可读的文档,描述可用的API端点、它们的URLS,以及它们支持哪些操作;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Go语言工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Go语言全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-B3Wbgw6J-1712960924607)]
[外链图片转存中…(img-UXqA1AKX-1712960924608)]
[外链图片转存中…(img-U011DTtB-1712960924609)]
[外链图片转存中…(img-UEB9yEtI-1712960924610)]
[外链图片转存中…(img-KbBLb2ot-1712960924610)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Golang知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-X3hNb0it-1712960924611)]
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!