在作其余事以前,咱们会用virtualenv建立一个新的虚拟环境。这将确保咱们的包配置与咱们正在工做的其余项目彻底隔离。html
virtualenv env # 建立虚拟环境,命名: env source env/bin/activate # 进入虚拟环境env
既然咱们已经在虚拟环境中,那么咱们就能够安装咱们依赖的包了。python
pip install django pip install djangorestframework pip install pygments # 代码高亮插件
首先,咱们来建立一个新项目。web
cd ~ django-admin.py startproject tutorial cd tutorial
输完以上命令,咱们就能够建立一个应用,咱们将会用他来建立简单的Web API。sql
python manage.py startapp snippets
咱们会添加一个新的snippets
应用和rest_framework
应用到INSTALLED_APPS
。让咱们编辑tutorial/settings.py
文件:shell
INSTALLED_APPS = ( ... 'rest_framework', 'snippets.apps.SnippetsConfig', )
Ok, 咱们准备下一步。数据库
为了实现本教程的目的,咱们将建立一个简单的Snippet
模型,这个模型用来保存snippets
代码。开始编辑snippets/models.py
文件。django
from django.db import models from pygments.lexers import get_all_lexers from pygments.styles import get_all_styles LEXERS = [item for item in get_all_lexers() if item[1]] LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS]) 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=100) style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100) class Meta: ordering = ('created',)
咱们也须要为咱们的snippet模型建立一个初始迁移(initial migration),而后第一次同步数据库。json
python manage.py makemigrations snippets
python manage.py migrate
着手咱们的Web API,首先要作的是,提供一种将咱们的snippet
实例序列化/反序列化成例如json
这样的表述形式。咱们能够经过声明序列来完成,这些序列与Django
的表单(forms)
工做类似。在snippets
目录建立一个新文件serializers.py
,添加下列代码。后端
from rest_framework import serializers from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES class SnippetSerializer(serializers.Serializer): id = serializers.IntegerField(read_only=True) title = serializers.CharField(required=False, allow_blank=True, max_length=100) code = serializers.CharField(style={'base_template': 'textarea.html'}) linenos = serializers.BooleanField(required=False) language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python') style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly') def create(self, validated_data): """ Create and return a new `Snippet` instance, given the validated data. """ return Snippet.objects.create(**validated_data) def update(self, instance, validated_data): """ Update and return an existing `Snippet` instance, given the validated data. """ instance.title = validated_data.get('title', instance.title) instance.code = validated_data.get('code', instance.code) instance.linenos = validated_data.get('linenos', instance.linenos) instance.language = validated_data.get('language', instance.language) instance.style = validated_data.get('style', instance.style) instance.save() return instance
序列化类(serializer class)的第一部分定义了一些须要被序列化/反序列化字段。create()
和update()
方法定义了在调用serializer.save()
时成熟的实例是如何被建立和修改的。 序列化类(serializer class)与Django的表单类(Form class)很是类似,包括对各类字段有类似的确认标志(flag),例如required
,max_length
和default
。 在某些状况下,这些字段标志也能控制序列应该怎么表现,例如在将序列渲染成HTML时。{'base_template': 'textarea.html}'
标志至关于对Django表单(Form)类使用widget=widgets.Textarea
。这对控制API的显示尤为有用,之后的教程将会看到。 事实上,之后咱们能够经过使用ModelSerializer
类来节约咱们的时间,可是如今为了让咱们序列化定义更清晰,咱们用Serializer类。api
在咱们深刻以前,咱们须要熟练使用新的序列化列(Serializer class)。然咱们开始使用Django命令行吧。
python manage.py shell
Okay,让咱们写一些snippets代码来使序列化工做。
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()
如今咱们已经有了一些snippet实例。让咱们看看如何将其中一个实例序列化。
注: Model -> Serializer
serializer = SnippetSerializer(snippet) serializer.data # {'id': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
如今,咱们已经将模型实例(model instance)转化成Python原生数据类型。为了完成实例化过程,咱们要将数据渲染成json。
注: Serializer -> JSON
content = JSONRenderer().render(serializer.data) content # '{"id": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'
反序列化也同样。首先,咱们须要将流(stream)解析成Python原生数据类型...
注: stream -> json
from django.utils.six import BytesIO stream = BytesIO(content) data = JSONParser().parse(stream)
...而后咱们要将Python原生数据类型恢复成正常的对象实例。
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和表单(forms)是多么类似啊。当咱们用咱们的序列写视图的时候,类似性会至关明显。 除了将模型实例(model instance)
序列化外,咱们也能序列化查询集(querysets)
,只须要添加一个序列化参数many=True
。
serializer = SnippetSerializer(Snippet.objects.all(), many=True) serializer.data # [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
咱们的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')
序列一个很是棒的属性就是,你可以经过打印序列实例的结构(representation)
查看它的全部字段。输入python manage.py 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()
方法的实现。让咱们看看,使用咱们新的序列化类,咱们怎么写一些API视图。此刻,咱们不会使用REST框架的其余特性,仅仅像写常规Django视图同样。 经过建立HttpResponse
的一个子类来开始,其中,咱们能够用这个子类来渲染任何咱们返回的json
数据。 编辑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将是一个支持列出全部存在的snippets
的视图,或者建立一个新的snippet
对象。
@csrf_exempt def snippet_list(request): """ List all code snippets, or create a new snippet. """ 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框架视图更实用的作法不是这样的,可是目前来讲,这足以到达咱们的目的。 咱们也须要一个与单个snippet
对象相应的视图,而且咱们使用这个视图来读取、更新或者删除这个snippet
对象。
@csrf_exempt def snippet_detail(request, pk): """ Retrieve, update or delete a code snippet. """ 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.conf.urls import url from snippets import views urlpatterns = [ url(r'^snippets/$', views.snippet_list), url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail), ]
咱们也须要在根url配置文件tutorial/urls.py中添加咱们的snippet应用URL。
from django.conf.urls import url, include urlpatterns = [ url(r'^', include('snippets.urls')), ]
有一些当时咱们没有正确处理的边缘事件是没有价值的。若是咱们发送不正确的json数据,或者若是咱们制造了一个视图没有写处理的方法(method),那么咱们会获得500“服务器错误”的响应。固然,如今也会出现这个问题。
如今咱们开始建立一个测试服务器来服务咱们的snippets应用。 退出命令行......
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是一个面向用户的很是友好的http客户端,它是用Python写的。让咱们来安装它。 你能够经过pip来安装httpie:
pip install httpie
最后,咱们来获取一个包含全部snippets的列表:
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来获取指定的snippet:
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" }
类似地,你能够经过在浏览器中访问这些连接来得到相同的json数据。
到目前为止,咱们作的都很好,咱们已经得到一个序列化API,这和Django的表单API很是类似,而且咱们写好了一些经常使用的Django视图。 如今,咱们的API视图除了服务于json外,不会作任何其余特别的东西,而且有一些错误咱们仍然须要清理,可是它是一个可用的Web API。 咱们将会在本教程的第二部分改善这里东西。
从这开始,咱们将接触REST框架的核心。让咱们来介绍一系列必要的搭建模块。
REST框架介绍了一个请求(Request)
对象,它扩展了常规的HttpResquest
,而且,提供更灵活的请求解析。请求(Request)
对象的核心功能是request.data
属性,这个属性与request.POST
类似,可是它对Web APIs
更加有用。
request.POST # 只处理表单数据。只对'POST'方法起做用。 request.data # 能够处理任意数据。对'POST','PUT'和'PATCH'方法起做用。
REST 框架也介绍了Response
对象,它是一类用未渲染内容和内容协商来决定正确的内容类型并把它返回给客户端的模板响应(TemplateResponse)
。
return Response(data) # 根据客户端的请求来渲染成指定的内容类型。
老是在你的视图中用数字的HTTP状态码会更加容易理解,而且若是你用其余错误代码表示错误,就不太容易注意到了。REST框架为每一个状态码`(status code)`提供更明确的标识符,例如在状态`(status)`模型中的`HTTP_400_BAD_REQUEST`。用这些标识符代替纯数字的HTTP状态码是很好的注意。
REST框架提供两个装饰器,你能够用它们来写API视图。
@api_view
装饰器用在基于视图的方法上。APIView
类用在基于视图的类上。 这些装饰器提供一些功能,例如确保在你的视图中接收Request对象,例如在你的Response对象中添加上下文,这样咱们就能实现内容通讯。 这里装饰器也提供了一些行为,例如在合适的时候返回405 Method Not Allowed
响应,例如处理任何在访问错误输入的request.data
时出现的解析错误(ParseError)
异常。好了,让咱们开始用这些新的组件写一些视图。 咱们再也不须要在咱们的视图(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): """ List all code snippets, or create a new snippet. """ 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
模块中,有一个独立的snippet视图。
@api_view(['GET', 'PUT', 'DELETE']) def snippet_detail(request, pk): """ Retrieve, update or delete a code snippet. """ 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框架渲染响应成正确的内容类型。
为了利用咱们的响应内容再也不是单一格式的事实,咱们应该为咱们的API尾部添加格式后缀。用格式后缀给咱们明确参考指定格式的URL,这意味着咱们的API可以处理像http://example.com/api/items/4/.json
同样的连接。 在视图函数中添加一个format
参数,像这样:
def snippet_list(request, format=None):
和
def snippet_detail(request, pk, format=None):
如今能够很快更新urls.py文件,在已经存在的URL中添加一个格式后缀模式(format_suffix_patterns)
。
from django.conf.urls import url from rest_framework.urlpatterns import format_suffix_patterns from snippets import views urlpatterns = [ url(r'^snippets/$', views.snippet_list), url(r'^snippets/(?P<pk>[0-9]+)$', views.snippet_detail), ] urlpatterns = format_suffix_patterns(urlpatterns)
咱们没必要添加额外的URL模式,可是它给咱们简单、清楚的方式渲染除特定的格式。
和教程第一部分同样,咱们要开始从命令行测试API。虽然咱们能在发送无效的请求时更稳当处理错误,可是如今一切都作的够好了。 咱们能想以前同样获取全部的snippets列表。
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
响应头。
http http://127.0.0.1:8000/snippets/ Accept:application/json # Request JSON http http://127.0.0.1:8000/snippets/ Accept:text/html # Request HTML
或者在URL后添加格式后缀:
http http://127.0.0.1:8000/snippets.json # JSON 后缀 http http://127.0.0.1:8000/snippets.api # 浏览用的 API 后缀
一样地,咱们能够控制咱们发送的请求格式,用Content-Type
请求头。
# POST using form data 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 using 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" }
你也能够从浏览器打开API,经过访问http://127.0.0.1:8000/snippets/。
由于API是基于客户端请求来选择响应内容的类型,因此默认状况下,在Web浏览器访问资源时,API返回HTML格式的资源。这语序API返回彻底能够网页浏览的HTML。 有能够网页浏览API是很好的,这使开发和使用你的API更简单,这也为其余想要查看和使用你的API的开发者大大下降了门槛。 关于可浏览API的特性和如何自定义可浏览API,请见可浏览API话题。
在教程的第三部分,咱们基于视图用类,而且看看普通的视图咱们如何减小代码。
除了能够用基于视图的函数(function based view)写咱们的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): """ List all snippets, or create a new 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=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
到目前为止,一切都很好。这和以前的状况很类似,可是咱们已经很好地经过不一样的HTTP方法区分。如今咱们也须要在views.py
中更新实例视图。
class SnippetDetail(APIView): """ Retrieve, update or delete a snippet instance. """ 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)
那看起来不错。再次强调,这和基于视图的函数很是类似。 咱们也须要用基于视图的类重构咱们的urls.py
。
from django.conf.urls import url from rest_framework.urlpatterns import format_suffix_patterns from snippets import views urlpatterns = [ url(r'^snippets/$', views.SnippetList.as_view()), url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()), ] urlpatterns = format_suffix_patterns(urlpatterns)
好了,咱们作完了。若是你启用开发服务器,那么一切都和以前同样。
使用基于视图的类最大的一个好处是,它容许咱们快速建立可复用的行为。咱们一直使用的create/retrieve/update/delete
操做将和咱们建立的任何后端模型API视图很是类似。这些广泛的行为是经过REST框架的混合类(mixin classes)
实现的。 让咱们看看如何经过混合类(mixin classes)
组建视图。下面是咱们的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
和CreatteModelMixin
建立咱们的视图。 基类提供核心功能,混合类提供.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
类提供核心功能,添加混合(mixin)
,来提供.retrieve()
,.update()
和.destroy()
动做。
尽管咱们已经使用混合类(mixin classes)以比以前更少的代码重写了视图,可是咱们能够进一步深刻。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
哇,如此简洁。咱们的代码看起来是如此简洁、地道的Django。 接下来咱们要学习本教程的第四部分,在第四部分咱们会为咱们的API处理受权(authentication)和权限(permissions)。
当前,咱们的API没有限制谁能编辑或删除snippets代码。咱们想要一些更高级的行为以确保:
咱们打算对咱们的Snippet
模型类作些改变。首先,让咱们添加几个字段。其中一个字段将显示出哪一个用户建立里snippet
数据。另外一个字段将用于HTML代码高亮。 将下面两个字段添加到Snippet
模型中,在snippets/models.py
中。
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): """ Use the `pygments` library to create a highlighted HTML representation of the code snippet. """ lexer = get_lexer_by_name(self.language) linenos = self.linenos and 'table' or False options = self.title and {'title': self.title} or {} formatter = HtmlFormatter(style=self.style, linenos=linenos, full=True, **options) self.highlighted = highlight(self.code, lexer, formatter) super(Snippet, self).save(*args, **kwargs)
而后,咱们须要更细咱们的数据库表。为此,正常状况下,咱们会建立数据库迁移(database migration),可是就本教程来讲,咱们只须要删除原来的数据库,而后从新建立便可。
rm -f db.sqlite3 rm -r snippets/migrations python manage.py makemigrations snippets python manage.py migrate
你可能也想要建立不一样的用户来测试API。最快的方式就是用createsuperuser
命令。
python manage.py createsuperuser
既然咱们已经建立了多个用户,那么咱们最好将用户添加到咱们的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'
在用户模型中是一个相反的关系,默认状况下在使用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配置,将这些视图添加进API。添加如下urls.py
中。
url(r'^users/$', views.UserList.as_view()), url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
如今,若是咱们建立snippet
数据,咱们没办法将用户和snippet
实例联系起来。虽然用户不是序列表示的部分,可是它是请求的一个属性。 咱们经过重写snippet
视图的.perform_create()
方法来作到,这个方法容许咱们修改如何保存实例,修改任何请求对象或者请求链接里的信息。 在SnippetList
视图类中添加如下方法;
def perform_create(self, serializer): serializer.save(owner=self.request.user)
如今,咱们序列的create()
方法将会另外传入一个来自有效的请求数据的'owner'
字段。
既然已经将snippets
和建立它们的用户联系在一块儿了,那么咱们须要更新对应的SnippetSerializer
。在serializers.py
的序列定义(serializer definition)
中添加如下字段:
owner = serializers.ReadOnlyField(source='owner.username')
注意;确保你将'owner'字段添加到内部类Meta的字段列表里。 这个字段颇有趣。source参数控制哪一个属性被用于构成一个字段,而且可以指出序列实例的任何属性。它也能像上面同样使用点标记(.),这种状况下他会横贯给定的属性,就是咱们使用Django模板语言同样。 咱们添加的字段是隐式ReadOnly类,与其余类相反,如CharField,BooleanField,隐式ReadOnlyField老是只读的,用于序列化表示,但在数据非序列化时不能用于更新实例。这里咱们也能够用CharField(read_only=True)。
snippets
数据已经和用户联系在一块儿,咱们想确保只有受权的用户能够建立、更新和删除snippet
数据。 REST框架包括许多权限类(permission classes)
,咱们可使用这些权限类来如今视图的访问权限。这种状况下,其中咱们须要IsAuthenticatedOrReadOnly
,这个类确保受权请求有读写权限,而没有受权的用户只有只读权限。 首先,在视图模块中引入如下代码:
from rest_framework import permissions
接下来,将如下属性添加到SnippetList
和SnippetDetail
的视图类中。
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
若是你打开浏览器并浏览API,你将会发现你没法再建立新的snippets code,若是想要有建立的权限,须要登陆。 咱们能够添加一个登陆视图,经过编辑咱们的根路由urls.py文件。 将下列包导入到文件上方:
from django.conf.urls import include
在文件的末尾,将login和logout的路由配好。
urlpatterns += [ url(r'^api-auth/', include('rest_framework.urls')), ]
url
样式的r'^api-auth/'
部分实际上能够是任何你想要的URL
。惟一的限制就是include
的连接必须使用'rest_framework'
名字空间。如今若是你刷新浏览器页面,你会看到右上角的'Login'连接。若是你用以前建立的用户登陆,你就能够再次写snippets数据了。 一旦你建立snippets数据,浏览'/users/',而后你会发如今每一个用户的'snippets'字段,显示的内容包括与每一个用户相关的snippets主键。
虽然咱们真的想任何人都和一看见snippets
数据,但也要确保只有建立snippet
的用户能够修改或删除他的snippet
。 为此,咱们须要建立自定义权限。 在snippets app
中,建立一个新文件permissions.py
。
from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): """ Custom permission to only allow owners of an object to edit it. """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request, # so we'll always allow GET, HEAD or OPTIONS requests. if request.method in permissions.SAFE_METHODS: return True # Write permissions are only allowed to the owner of the snippet. return obj.owner == request.user
而后编辑SnippetDetail
视图类中的permission_classes
属性,添加自定义权限。
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
确保引入了IsOwnerOrReadOnly
类。
from snippets.permissions import IsOwnerOrReadOnly
如今,若是你再次打开浏览器,你会发现只有你登入,你才能删除(DELETE)
或更新(PUT)
属于你的snippet
数据。
由于咱们的API有一系列权限,因此若是咱们想编辑任何snippets
,咱们须要受权咱们的请求。咱们如今尚未任何受权类(authenticaions classes)
,因此默认状况下只有SessionAuthentication
和BasicAuthentication
。 当咱们经过Web浏览器与API交互时,咱们能够登陆,而后浏览器会话(session)
将会提供必须的请求受权。 若是咱们经过程序与API交互,咱们须要为每一个请求提供明确的受权证实。 若是咱们在没有受权的状况下建立一个snippet
,那么咱们会获得下面的错误:
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上,为咱们的系统用户和snippet的建立者,添加了不少权限和端点。 在第五部分,咱们将会看怎么咱们能够经过为咱们的高亮snippets建立HTML端点来将全部东西联系在一块儿,而后在系统内用超连接将咱们的API联系起来。
如今,用主键表明咱们API之间的关系。在这部分教程,咱们会用超连接改善API之间的关系。
如今,咱们已经为'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格式是方便的名字标识符,咱们会在以后会在snippets/urls.py
中声明。
另外一件明显的事就是,咱们的API缺少代码高亮端点。 和咱们全部的API端点不同,咱们不想用JSON,而只是想用HTML显示。REST框架提供两种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)
一般,咱们须要添加新的视图到咱们的URL配置。而后,在snippest/urls.py
中添加一个连接:
url(r'^$', views.api_root),
而后,为高亮snippet添加一个url样式:
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),
处理好实体之间的关系是Web API设计中极具挑战性的方面之一。表明一种关系能够有不少种方式:
(serializers)
,以拓展HyperlinkedModelSerializer
,不是使用已经存在的ModelSerializer
。 如下是HyperlinkedModelSerializer
不一样于ModelSerializer
的地方:HyperlinkedModelSerializer
默认不包括pk字段。HyperlinkedIndentityField
。HyperlinkedRelatedField
,而不是PrimaryKeyRelatedField
。 咱们能使用超连接快速重写现存的序列。在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-highlighturl
模式,而不是snippet-detailurl
模式。 由于咱们已经包含了格式后缀的URL,如.json
,因此咱们也须要在highlight字段指明,任何格式后缀超连接应该用.html
后缀。
若是咱们想要超连接的API,那么咱们要保证咱们给URL起了名字。让咱们看看咱们须要命名哪一个连接。
user-list
和snippet-list
。snippet
序列包括一个指向snippet-highlight
的字段。snippet-detail
的字段。'{model_name}-detail'
,这种状况下,它是snippet-detail
和user-detail
。 在将那些名字加入咱们的URL配置(URLconf)
后,咱们的snippets/urls.py
应该是下面的样子:from django.conf.urls import url, include from rest_framework.urlpatterns import format_suffix_patterns from snippets import views # API endpoints urlpatterns = format_suffix_patterns([ url(r'^$', views.api_root), url(r'^snippets/$', views.SnippetList.as_view(), name='snippet-list'), url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view(), name='snippet-detail'), url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view(), name='snippet-highlight'), url(r'^users/$', views.UserList.as_view(), name='user-list'), url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view(), name='user-detail') ])
用户和snippet的列表视图会返回不少实例,因此咱们想要给这些结果分页,分页后容许API客户端访问每一个单页。 咱们能够用分页改变默认的列表风格,只要稍微修改tutorial/settings.py
文件。添加下面设置:
REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10 }
注意:REST框架的分页设置(settings)是一个单独的字典,叫'REST_FRAMEWORK',它能够帮咱们区分项目中的其余配置。 若是咱们须要,咱们能够自定义分页样式,可是如今咱们只是用默认的。
若是咱们打开浏览器访问API,那么你会发现你能够经过下面的连接使用API。 你也能够看见snippet实例的高亮(highlight)连接,这些连接会返回高亮HTML代码。 在本教程的第六部分,咱们会用ViewSets
和Routers
来减小咱们API的代码量。
REST框架包括对ViewSets
的简短描述,这可让开发者把精力集中在构建状态和交互的API模型,并且它能够基于通常规范自动构建URL。 ViewSet
类几乎和View
类同样,除了他们提供像read
或update
的操做,而不是像get
和put
的方法。 目前,一个ViewSet
类只绑定一个方法的集合,当它初始化一个视图的集合时,通常使用为你处理复杂的URL定义的Router
类。
让咱们来用视图集重写当前视图。 首先,咱们要把咱们的UserList
和UserDetail
视图重写成单个UserViewSet
。咱们能够UserViewSet
代替UserList
和UserDetail
。
from rest_framework import viewsets class UserViewSet(viewsets.ReadOnlyModelViewSet): """ This viewset automatically provides `list` and `detail` actions. """ queryset = User.objects.all() serializer_class = UserSerializer
这里咱们使用ReadOnlyModelViewSet
类自动提供默认的'只读'操做。党咱们使用常规视图的时候,咱们仍然须要设置准确设置queryset
和serializer_class
属性,可是咱们不在须要为两个分开的类提供一样的信息。 接下来,咱们将用SnippetHighlight
视图类来代替SnippetList
和SnippetDetail
。咱们能够用一个类代替以前的三个类。
from rest_framework.decorators import detail_route from rest_framework.response import Response class SnippetViewSet(viewsets.ModelViewSet): """ This viewset automatically provides `list`, `create`, `retrieve`, `update` and `destroy` actions. Additionally we also provide an extra `highlight` action. """ queryset = Snippet.objects.all() serializer_class = SnippetSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,) @detail_route(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
类是为了得到完整的默认读写操做的集合。 注意:咱们也用了@detail_route
装饰器来建立自定义动做,命名为highlight
。这个装饰器用于添加任何自定义的端点,这些端点不符合标准的create/update/delete
方式。 使用@detail_route
装饰器的自定义动做会响应GET请求。若是咱们让动做响应POST请求,咱们可使用methods
参数。 自定义动做的URL在默认状况下是依赖于方法自己。若是你想改变url原本建立的方式,你能够将url_path
包含在装饰器关键参数中。
咱们定义URLConf
的时候,处理方法只绑定了动做。为了看看发生了什么,咱们必须从咱们的视图集(ViewSets)
建立一个视图集合。 在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
方法到每一个视图须要的动做来从ViewSet
类建立多视图。 既然咱们已经绑定了咱们的资源和具体视图,咱们就能够和之前同样将咱们的视图注册到URL配置中。
urlpatterns = format_suffix_patterns([ url(r'^$', api_root), url(r'^snippets/$', snippet_list, name='snippet-list'), url(r'^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'), url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'), url(r'^users/$', user_list, name='user-list'), url(r'^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail') ])
由于咱们使用ViewSet
类而不是View类,因此实际上咱们不须要本身设计URL配置。按惯例,使用Router
类就能够自动将资源与视图(views)
、连接(urls)
联系起来。咱们须要作的只是用一个路由注册合适的视图集合。如今,咱们把剩下的作完。 咱们重写了urls.py
文件。
from django.conf.urls import url, include from rest_framework.routers import DefaultRouter from snippets import views # Create a router and register our viewsets with it. router = DefaultRouter() router.register(r'snippets', views.SnippetViewSet) router.register(r'users', views.UserViewSet) # The API URLs are now determined automatically by the router. urlpatterns = [ url(r'^', include(router.urls)) ]
用路由注册视图和提供一个urlpattern
是类似的,包括两个参数--视图的URL前缀和视图自己。 咱们使用的默认路由(DefaultRouter)
类会自动为咱们建立API根视图,因此咱们就能够从咱们的views
模块删除api_root
方法。
使用视图集(viewsets)真的颇有用。它保证URL规范存在你的API中,让你写最少的代码,容许你把注意力集中在你的API提供的交互和表现上而不须要特定的URL配置。 这并不意味着这样作老是正确的。在使用基于类的视图代替基于函数的视图时,咱们总会发现views
与viewsets
有类似的地方。使用视图集(viewsets)
没有比你本身的视图更清晰。
难以置信,用这么少的代码,咱们已经完成了一个Web API,它是彻底可浏览的,拥有完整的受权(authentication)
、每一个对象权限(per-object permissions)
和多重渲染格式(multiple renderer formats)
。 咱们已经经历了设计过程的每一步,看到了若是咱们只是使用常规的Django视图自定义任何东西。