前言
我们的下一个项目是使用 Django REST Framework 功能的博客 API。 它将具有用户,权限,并允许完整的 CRUD(创建-读取-更新-删除)功能。 我们还将探索视图集,路由器和文档。
在本文中,我们将构建博客系统基本的 API 部分。
初始化
我们的设置与以前相同。 导航到我们的代码目录,并在其中为该项目创建一个名为blogapi的目录。 然后在新的虚拟环境中安装 Django,创建新的 Django项目(blog_project)和用于博客条目(posts)的应用。
$ cd ~/Desktop && cd code$ mkdir blogapi && cd blogapi
$ pipenv install django==2.2.6
$ pipenv shell
(blogapi) $ django-admin startproject blog_project . (blogapi) $ python manage.py startapp posts
由于我们添加了新应用,因此我们需要将其告知 Django。 因此,请确保在 settings.py 文件中将帖子添加到我们的 INSTALLED_APPS 列表中。
# blog_project/settings.pyINSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Local
'posts.apps.PostsConfig', # new
]
现在,第一次运行 migrate,将我们的数据库与 Django 的默认设置和新应用同步。
(blogapi) $ python manage.py migrate模型
我们的数据库模型将包含五个字段:author,title,body,created_at 和 updated_at。 如果我们在顶部的第二行中导入了 Django 的内置用户模型,则可以使用该模型。
# posts/models.pyfrom django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
请注意,我们还定义了模型的__str__表示形式,这是Django的最佳做法。 这样,我们稍后将在Django管理员中看到标题。现在,通过首先创建一个新的迁移文件,然后运行迁移将数据库与我们的模型更改同步来更新数据库。
(blogapi) $ python manage.py makemigrations posts (blogapi) $ python manage.py migrate好! 我们想在Django出色的内置管理应用程序中查看数据,因此,如下所示将其添加到posts/admin.py中。
# posts/admin.pyfrom django.contrib import admin
from .models import Post
admin.site.register(Post)
然后创建一个超级用户帐户,以便我们可以访问管理员。 在下面键入命令,然后输入所有提示。
(blogapi) $ python manage.py createsuperuser现在我们可以启动本地Web服务器。
导航到 http://127.0.0.1:8000/admin/ 并使用您的超级用户凭据登录。
单击帖子旁边的“ +Add”按钮,然后创建一个新博客帖子。“作者”旁边将是一个具有您的超级用户帐户的下拉菜单(我的帐户称为wsv)。 确保选择了作者。 添加标题和正文内容,然后单击“保存”按钮。
您将被重定向到显示所有现有博客帖子的“帖子”页面。
测试
让我们为Post模型编写一个基本测试。 我们希望确保已登录的用户可以创建带有标题和正文的博客文章。
# posts/tests.pyfrom django.test import TestCase
from django.contrib.auth.models import User
from .models import Post
class BlogTests(TestCase):
@classmethod
def setUpTestData(cls):
# Create a user
testuser1 = User.objects.create_user(username='testuser1', password='abc123')
testuser1.save()
# Create a blog post
test_post = Post.objects.create(
author=testuser1,
title='Blog title',
body='Body content...'
)
test_post.save()
def test_blog_content(self):
post = Post.objects.get(id=1)
author = f'{post.author}'
title = f'{post.title}'
body = f'{post.body}'
self.assertEqual(author, 'testuser1')
self.assertEqual(title, 'Blog title')
self.assertEqual(body, 'Body content...')
为了确认我们的测试正常,请退出本地服务器Control + c。 然后运行我们的测试。
(blogapi) $ python manage.py test您应该看到类似以下的输出,该输出确认一切正常。
(blogapi) $ python manage.py testCreating test database for alias 'default'...
System check identified no issues (0 silenced).
.
---------------------------------------------------------------------- Ran 1 test in 0.119s
OK
Destroying test database for alias 'default'...
现在,我们已经完成了API的常规Django部分。 我们真正需要的只是模型和数据库中的一些数据。 现在是时候添加Django REST框架,以将我们的模型数据转换为API了。
Django REST Framework
如前所述,Django REST Framework负责将数据库模型转换为RESTful API的繁重工作。 此过程包括三个主要步骤:
- URL路由的urls.py文件
- 将数据转换为JSON的serializers.py
- 将应用逻辑用于每个API端点的views.py文件
在命令行中,使用Control + c停止本地服务器,然后使用pipenv安装Django REST Framework。
(blogapi) $ pipenv install djangorestframework==3.10.3然后将其添加到settings.py文件的INSTALLED_APPS部分。 明确设置我们的权限也是个好主意,默认情况下,Django REST Framework中的权限已配置为 AllowAny 。 我们将在下一章中进行更新。
# blog_project/settings.pyINSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 3rd-party apps
'rest_framework', # new
# Local
'posts.apps.PostsConfig',
]
# new
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
]
}
现在,我们需要创建URL,视图和序列化程序。
URLs
让我们从端点的实际位置的URL路由开始。 使用第二行的include import和我们的posts app的新api/v1/路由更新项目级别的urls.py文件。
# blog_project/urls.pyfrom django.contrib import admin
from django.urls import include, path # new
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('posts.urls')), # new
]
最好始终对API(v1 /,v2 /等)进行版本控制,因为进行较大的更改时,可能需要一段时间才能使API的各个使用者进行更新。 这样,您可以在一段时间内支持API的v1,同时还可以启动新的更新的v2,并避免破坏依赖于API后端的其他应用程序。
请注意,由于目前我们唯一的应用是posts,因此我们可以直接在此处添加博客。 如果我们在一个项目中有多个应用程序,那么最好创建一个专用的api应用程序,然后将所有其他API url路由包含到其中。 但是对于像这样的基础项目,我宁愿避免使用仅用于路由的api应用。 如有需要,我们随时可以添加一个。
接下来,创建我们的帖子应用urls.py文件。
(blogapi) $ touch posts/urls.py然后包含以下代码。
# posts/urls.pyfrom django.urls import path
from .views import PostList, PostDetail
urlpatterns = [
path('<int:pk>/', PostDetail.as_view()),
path('', PostList.as_view()),
]
所有博客路由都将位于 api/v1/ 上,因此我们的PostList视图(我们将很快写出)的 '' 将位于 api/v1/ 上,而 PostDetail 视图(也将被写入)位于 api/v1/# 其中 # 表示条目的主键。 例如,第一篇博文的主要ID为1,因此它将位于路由 api/v1/1处,第二篇博文的API为 api/v1/2,依此类推。
Serializers
现在为我们的序列化函数。 在我们的posts应用中创建一个新的serializers.py文件。
(blogapi) $ touch posts/serializers.py序列化器不仅可以将数据转换为JSON,还可以指定要包括或排除的字段。 在我们的例子中,我们将包括Django自动添加到数据库模型的id字段,但由于不将update_at字段包含在我们的字段中,因此我们将排除它。
在我们的API中轻松包含/排除字段的功能是一项了不起的功能。 通常,基础数据库模型具有的字段远远多于需要公开的字段。 Django REST Framework强大的序列化程序类使控制它非常简单。
# posts/serializers.pyfrom rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'author', 'title', 'body', 'created_at',)
model = Post
在文件的顶部,我们导入了Django REST Framework的serializers类和我们自己的模型。 然后,我们创建了一个PostSerializer,并添加了一个Meta类,在其中我们指定要包括的字段并显式设置要使用的模型。 自定义序列化器有很多方法,但是对于常见的用例(例如,基本的博客),这就是我们所需要的。
Views
最后一步是创建我们的视图。 Django REST Framework具有几个有用的通用视图。 我们已经在Library API和Todos API中都使用ListAPIView来创建一个只读端点集合,实质上是所有模型实例的列表。 在Todos API中,我们还将RetrieveAPIView用作只读的单个端点,这类似于传统Django中的详细信息视图。
对于我们的Blog API,我们希望将所有可用的博客文章列出为读写端点,这意味着使用ListCreateAPIView,它类似于我们之前使用的ListAPIView,但允许写操作。 我们还希望使各个博客帖子可供阅读,更新或删除。 可以肯定的是,有一个内置的通用Django REST Framework视图正用于此目的:RetrieveUpdateDestroyAPIView。 这就是我们在这里使用的。
更新view.py文件,如下:
# posts/views.pyfrom rest_framework import generics
from .models import Post
from .serializers import PostSerializer
class PostList(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
在文件的顶部,我们从Django REST Framework以及模型和序列化器文件导入泛型。 然后,我们创建两个视图。 PostList使用通用的ListCreateAPIView,而PostDetail使用RetrieveUpdateDestroyAPIView。
我们要做的就是更新通用视图以从根本上改变给定API端点的行为,这非常令人惊讶。 这是使用诸如Django REST Framework之类的功能齐全的框架的优势:所有这些功能都是可用的,经过测试的并且可以正常使用。 作为开发人员,我们不必在这里重新发明轮子。
至此,我们的API现在已经完成,我们真的不必自己编写太多代码。 在接下来的章节中,我们将对API进行其他改进,但是值得一提的是,它已经执行了我们想要的基本列表和CRUD功能。 是时候使用Django Rest Framework的可浏览API进行测试了。
Browsable API
启动本地服务器以与我们的API进行交互。
(blogapi) $ python manage.py runserver然后转到 http://127.0.0.1:8000/api/v1/ 查看博客列表终端。
该页面以JSON格式显示了我们的博客文章列表(目前只有一个)。 注意,GET和POST方法都被允许。
现在,让我们确认是否存在我们的模型实例终结点,该终结点与单个帖子而不是所有帖子的列表有关。
转到 http://127.0.0.1:8000/api/v1/1/.
您可以在标头中看到支持GET,PUT,PATCH和DELETE,但不支持POST。 实际上,您可以使用下面的HTML表单进行更改,甚至可以使用红色的“ DELETE”按钮删除实例。
让我们尝试一下。 最后用其他文字(已编辑)更新标题。 然后点击“ PUT”按钮。
通过单击页面顶部的链接返回到“帖子列表”视图,或直接导航到 http://127.0.0.1:8000/api/v1/,您也可以在那里查看更新的文本。
总结
此时,我们的 Blog API 完全可用。 但是,有一个大问题:任何人都可以更新或删除现有博客文章! 换句话说,我们没有任何权限。 在下一篇文章中,我们将学习如何应用权限来保护我们的 API。