应用API

在下一章开始之前,我们先来搭建一下API平台,不仅仅可以提供一些额外的功能,还可以为我们的APP提供API。

博客列表

Django REST Framework

在这里,我们需要用到一个名为Django REST Framework的RESTful API库。通过这个库,我们可以快速创建我们所需要的API。

Django REST Framework 这个名字很直白,就是基于 Django 的 REST 框架。因此,首先我们仍是要安装这个库:

  1. pip install djangorestframework

然后把它添加到INSTALLED_APPS中:

  1. INSTALLED_APPS = (
  2. ...
  3. 'rest_framework',
  4. )

如下所示:

  1. INSTALLED_APPS = (
  2. 'django.contrib.admin',
  3. 'django.contrib.auth',
  4. 'django.contrib.contenttypes',
  5. 'django.contrib.sessions',
  6. 'django.contrib.messages',
  7. 'django.contrib.staticfiles',
  8. 'rest_framework',
  9. 'blogpost'
  10. )

接着我们可以在我们的API中创建一个URL,用于匹配它的授权机制。

  1. urlpatterns = [
  2. ...
  3. url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
  4. ]

不过这个API,目前并没有多大的用途。只有当我们在制作一些需要权限验证的接口时,它才会突显它的重要性。

创建博客列表API

为了方便我们继续展开后面的内容,我们先来创建一个博客列表API。参考Django REST Framework的官方文档,我们可以很快地创建出下面的Demo:

  1. from django.contrib.auth.models import User
  2. from rest_framework import serializers, viewsets
  3. from blogpost.models import Blogpost
  4. class BlogpsotSerializer(serializers.HyperlinkedModelSerializer):
  5. class Meta:
  6. model = Blogpost
  7. fields = ('title', 'author', 'body', 'slug')
  8. class BlogpostSet(viewsets.ModelViewSet):
  9. queryset = Blogpost.objects.all()
  10. serializer_class = BlogpsotSerializer

在上面这个例子中,API由两个部分组成:

  • ViewSets,用于定义视图的展现形式——如返回哪些内容,需要做哪些权限处理
  • Serializers,用于定义API的表现形式——如返回哪些字段,返回怎样的格式

我们在我们的URL中,会定义相应的规则到ViewSet,而ViewSet则通过serializer_class找到对应的Serializers。我们将Blogpost的所有对象赋予queryset,并返回这些值。在BlogpsotSerializer中,我们定义了我们要返回的几个字段:titleauthorbodyslug

接着,我们可以在我们的urls.py配置URL。

  1. ...
  2. from rest_framework import routers
  3. from blogpost.api import BlogpostSet
  4. apiRouter = routers.DefaultRouter()
  5. apiRouter.register(r'blogpost', BlogpostSet)
  6. urlpatterns = [,
  7. ...
  8. url(r'^api/', include(apiRouter.urls)),
  9. ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

我们使用默认的Router来配置我们的URL,即DefaultRouter,它提供了一个非常简单的机制来自动检测URL规则。因此,我们只需要注册好我们的url——blogpost以及它值BlogpostSet即可。随后,我们再为其定义一个根URL即可:

  1. url(r'^api/', include(apiRouter.urls))

测试 API

现在,我们可以访问http://127.0.0.1:8000/api/来访问我们现在的API。由于Django REST Framework提供了一个UI机制,所以我们可以在网页上直接看到我们所有的API:

Django REST Framework列表

然后,点击页面中的http://127.0.0.1:8000/api/blogpost/,我们就可以访问博客相关的API了,如下图所示:

博客API

在页面上显示了所有的博客内容,在页面的下面有一个表单可以先让我们来创建数据:

创建博客的表单

直接在表单中添加数据,我们就可以完成数据创建了。

当然,我们也可以直接用命令行工具来测试,执行:

  1. curl -i http://127.0.0.1:8000/api/blogpost/

即可返回相应的结果:

CuRL API

自动完成

AutoComplete是一个很有意思的功能,特别是当我们的文章很多的时候,我们可以让读者有机会能搜索到相应的功能。以Google为例,Google在我们输入一些关键字的时候,会向我们推荐一些比较流行的词条可以让我们选择。

Google AutoComplete

同样的,我们也可以实现一个同样的效果用于我们的博客搜索:

自动完成

当我们输入某一些关键字的时候,就会出现文章的标题,随后我们只需要点击相应的标题即可跳转到文章。

搜索API

为了实现这个功能我们需要对之前的博客API做一些简单的改造——可以支持搜索博客标题。这里我们需要稍微扩展一下我们的博客API即可:

  1. class BlogpostSet(viewsets.ModelViewSet):
  2. permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
  3. serializer_class = BlogpsotSerializer
  4. search_fields = 'title'
  5. def list(self, request):
  6. queryset = Blogpost.objects.all()
  7. search_param = self.request.query_params.get('title', None)
  8. if search_param is not None:
  9. queryset = Blogpost.objects.filter(title__contains=search_param)
  10. serializer = BlogpsotSerializer(queryset, many=True)
  11. return Response(serializer.data)

我们添加了一个名为search_fields的变量,顾名思义就是定义搜索字段。接着我们覆写了ModelViewSet的list方法,它是用于列出(list)所有的结果。我们会尝试在我们的请求中获取搜索参量,如果没有的话我们就返回所有的结果。如果搜索的参数中含有标题,则从所有博客中过滤出标题中含有搜索标题中的内容,再返回这些结果。如下是一个搜索的URL:http://127.0.0.1:8000/api/blogpost/?format=json&title=test,我们搜索标题中含有test的内容。

同时,我们还需要为我们的apiRouter设置一个basename,即下面代码中最后的Blogpost

  1. apiRouter.register(r'blogpost', BlogpostSet, 'Blogpost')

页面实现

接着,我们就可以在页面上实现这个功能。在这里我们使用一个名为Bootstrap-3-Typeahead的插件来实现,下载这个插件以及它对应的CSS:https://github.com/bassjobsen/typeahead.js-bootstrap-css,并添加到base.html中,然后创建一个main.js文件负责相关的逻辑处理。

  1. <script src="{% static 'js/jquery.min.js' %}"></script>
  2. <script src="{% static 'js/bootstrap.min.js' %}"></script>
  3. <script src="{% static 'js/bootstrap3-typeahead.min.js' %}"></script>
  4. <script src="{% static 'js/main.js' %}"></script>

接着我们需要在页面上创建对应的UI,我们可以直接在登录后面添加这个搜索按钮:

  1. <nav class="collapse navbar-collapse bs-navbar-collapse" role="navigation">
  2. <ul class="nav navbar-nav">
  3. <li>
  4. <a href="/pages/about/">关于我</a>
  5. </li>
  6. <li>
  7. <a href="/pages/resume/">简历</a>
  8. </li>
  9. </ul>
  10. <ul class="nav navbar-nav navbar-right">
  11. <li><a href="/admin" id="loginLink">登录</a></li>
  12. </ul>
  13. <div class="col-sm-3 col-md-3 pull-right">
  14. <form class="navbar-form" role="search">
  15. <div class="input-group">
  16. <input type="text" id="typeahead-input" class="form-control" placeholder="Search" name="search" data-provide="typeahead">
  17. <div class="input-group-btn">
  18. <button class="btn btn-default search-button" type="submit"><i class="glyphicon glyphicon-search"></i></button>
  19. </div>
  20. </div>
  21. </form>
  22. </div>
  23. </nav>

我们主要是使用input标签,标签上对应有一个id

  1. <input type="text" id="typeahead-input" class="form-control" placeholder="Search"

对应于这个ID,我们就可以开始编写我们的功能了:

  1. $(document).ready(function () {
  2. $('#typeahead-input').typeahead({
  3. source: function (query, process) {
  4. return $.get('/api/blogpost/?format=json&title=' + query, function (data) {
  5. return process(data);
  6. });
  7. },
  8. updater: function (item) {
  9. return item;
  10. },
  11. displayText: function (item) {
  12. return item.title;
  13. },
  14. afterSelect: function (item) {
  15. location.href = 'http://localhost:8000/blog/' + item.slug + ".html";
  16. },
  17. delay: 500
  18. });
  19. });

$(document).ready()方法可以是在DOM完成加载后,运行其中的函数。接着我们开始监听#typeahead-input,对应的便是id为typeahead-input的元素。可以看到在这其中有五个对象:

  • source,即搜索的来源,我们返回的是我们搜索的URL。
  • updater,即每次更新要做的事
  • displayText,显示在页面上的内容,如在这里我们返回的是博客的标题
  • afterSelect,每用户选中某一项后做的事,这里我们直接中转到对应的博客。
  • delay,延时500ms。

虽然我们使用的是插件来完成我们的功能,但是总体的处理逻辑是:

  1. 监听我们的输入文本
  2. 获取API的返回结果
  3. 对返回结果进行处理——如高亮输入文本、显示到页面上
  4. 处理用户点击事件

跨域支持

当我们想为其他的网页提供我们的API时,可能会报错——原因是不支持跨域请求。为了方便我们下一章更好的展开,内容我们在这里对跨域进行支持。

添加跨域支持

有一个名为django-cors-headers的插件用于实现对跨域请求的支持,我们只需要安装它,并进行一些简单的配置即可。

  1. pip install django-cors-headers

安装过程如下:

  1. Collecting django-cors-headers
  2. Downloading django-cors-headers-1.1.0.tar.gz
  3. Building wheels for collected packages: django-cors-headers
  4. Running setup.py bdist_wheel for django-cors-headers ... done
  5. Stored in directory: /Users/fdhuang/Library/Caches/pip/wheels/b0/75/89/7b17f134fc01b74e10523f3128e45b917da0c5f8638213e073
  6. Successfully built django-cors-headers
  7. Installing collected packages: django-cors-headers
  8. Successfully installed django-cors-headers-1.1.0

我们还需要添加到django-cors-headers=1.1.0requirements.txt文件中,以及添加到settings.py中:

  1. INSTALLED_APPS = (
  2. ...
  3. 'corsheaders',
  4. ...
  5. )

以及对应的中间件:

  1. MIDDLEWARE_CLASSES = (
  2. ...
  3. 'corsheaders.middleware.CorsMiddleware',
  4. 'django.middleware.common.CommonMiddleware',
  5. ...
  6. )

同时还有对应的配置:

  1. CORS_ALLOW_CREDENTIALS = True

现在,让我们进行下一步,开始APP吧!