你好 Django REST Framework

在第二章,我们学习了 REST 开发的基本知识,并且在没有借助任何框架的情况下
完成了我们的 RESTful APP 的开发,虽然我们已经考虑到了许多的情况,但是我们的 APP
依然有许多的漏洞。在本章,我们将会进入 VueDjango REST framework
的学习。本章将会分为三个部分,分别是:

  • 你好 Django REST Framework
  • 你好 Vue
  • 重构 APP

这就是我们的三个部分了。第一个部分学习 DRF ,第二个部分学习 Vue ,最后一个部分为实战部分。在上部分,我们会学习以下知识点:

  1. 了解 DRF 的基本使用。
  2. 了解并能灵活使用序列化器。

这个部分的知识点看起来很少,其实等大家真正进入他们的学习中时,会发现其中的知识点也不少。当然,这是一个教程,不是 DRF 官方文档复读机,所以一旦在看教程的过程中有什么不懂的地方,去查询 DRF 文档是个好习惯。同时,本章也会涉及 python 的编程知识,由此可见,对于 web 后端的开发来说,语言的基础是多么重要。同样的,如果遇到在自己查资料之后还不懂的地方,评论留言或者提 ISSUE。

准备工作

首先,我们需要安装 DRF ,在终端中运行:

pip install djangorestframework

创建一个新的项目:

python django-admin.py startproject api_learn

把路径切换到项目路径内,创建一个新的 APP :

python manage.py startapp rest_learn
settings.py
INSTALLED_APPS = [
    ...
    'rest_framework',
    'rest_learn'
]
rest_learnmodels.py
from django.db import models
class TestModel(models.Model):
    name = models.CharField(max_length=20)
    code = models.TextField()
    created_time = models.DateTimeField(auto_now_add=True)
    changed_time = models.DateTimeField(auto_now=True)

    def __str__(self): 
        return self.name
DateTimeField
auto_nowchanged_timeauto_now_addcreated_time
rest_learnadmin.py
from django.contrib import admin
from .models import TestModel

@admin.register(TestModel)
class TestModelAdmin(admin.ModelAdmin):
    pass
admin.registerModelAdmin
api_learnurls.py
from django.conf.urls import url, include
from django.contrib import admin
import rest_framework

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^drf-auth/',include('rest_framework.urls'))
]
url

在项目路径下运行:

python manage.py makemigrations
python migrate

生成我们需要的数据库文件。

创建管理员。在终端中运行:

(root) λ python manage.py createsuperuser
Username (leave blank to use 'administrator'): admin
Email address:
Password:
Password (again):
Superuser created successfully.
data.pyrest_test.py
data.py
data.py
from django import setup
import os
os.environ.setdefault('DJAGNO_SETTINGS_MODULE','api_learn.settings') # 在环境变量中设置配置文件
setup() # 加载配置文件

from rest_learn.models import TestModel

data_set = {
    'ls':"""import os\r\nprint(os.listdir())""",
    'pwd':"""import os\r\nprint(os.getcwd())""",
    'hello world':"""print('Hello world')"""
}
for name, code in data_set.items():
    TestModel.objects.create(name=name,code=code)

print('Done')
data.py单文件 Django
使用前先配置
单文件 Django

为了确保万无一失,大家可以选择登录进后台看看我们的数据是否写入了数据库。

rest_test.py
rest_test.py
from django import setup
import os

# 加载配置
os.environ.setdefault('DJAGNO_SETTINGS_MODULE','api_learn.settings')
setup()


准备工作已经完成了,让我们正式的开始学习。

你好 Django REST Framework

xml

序列化器

ifname
def validate_name(post_data):
    name = post_data.get('name')
    if isinstance(name, str):
        if len(name) <= 20:
            return name
    raise Exception('Invalid name data.')
ifnamevalidate_namenamecode_namevalidate_nameSerializer
created_time
client ----> views <-----> serializer <-----> model
namecodecreated_time
rest_test.py
from rest_framework import serializers

class TestSeriOne(serializers.Serializer):
    name = serializers.CharField(max_length=20)

这样我们就创建好了一个序列化器。对 Django Form 很熟悉的同学或许已经发现了,这不就很像是 Django 表单的写法吗?是的,事实上,序列化器用的就是表单的逻辑,所以如果你熟悉 Django Form 的 API ,那你上手序列化器也会很快。同时,序列化器和表单一样,拥有很多的字段,在之后的章节中我们会慢慢学习到它们,现在我们对字段的了解就先知道一点是一点。我们来使用一下我们的序列化器。
接着在下面写:

frontend_data = {
        'name':'ucag',
        'age':18
    }

test = TestSerilOne(data=frontend_data)
if test.is_valid():
    print(test.validated_data)
frontend_data.is_valid().validated_datarest_test.py
OrderedDict([('name', 'ucag')])
age
class TestSerilOne(serializers.Serializer):
    name = serializers.CharField(max_length=20)
    age = serializers.IntegerField()
frontend_data
frontend_data = {
        'name':'ucag',
        'age':'18'
    }
rest_test.py
OrderedDict([('name', 'ucag'), ('age', 18)])
agefrontend_data
frontend_data = {
        'name':'ucag',
        'age':'ucag'
    }

把之前的测试改成这样:

test = TestSerilOne(data=frontend_data)
if not test.is_valid():
    print(test.errors)

输出应该是这样的:

{'age': ['A valid integer is required.']}
.errors
name

注释掉刚才做实验的代码,接着在下面再创建一个序列化器:

# test = TestSerilOne(data=frontend_data)
# if not test.is_valid():
#     print(test.errors)

class TestSerilTwo(serializers.Serializer):
    name = serializers.CharField(max_length=20)

现在我们来使用它来验证后端的数据,在下面接着写:

from rest_learn.models import TestModel
code = TestModel.objects.get(name='ls')
test = TestSerilTwo(instance=code)
print(test.data)
rest_test.py
{'name': 'ls'}
instance.datanamecodecreated_timechanged_time
from rest_learn.models import TestModel
codes = TestModel.objects.all()
test = TestSerilTwo(instance=codes,many=True)
print(test.data)

你会看到输出是这个样子的:

[OrderedDict([('name', 'hello world')]), OrderedDict([('name', 'pwd')]), OrderedDict([('name', 'ls')])]
.datainstancemany=True

到目前为止,我们的序列化器都是一个个字段手写出来的,通常,我们序列化的字段和模型的字段是统一的,那能不能通过模型来生成我们的序列化器呢,就像模型表单那样?当然是可以的。
注释掉之前的验证代码,接着在后面写:

# from rest_learn.models import TestModel
# codes = TestModel.objects.all()
# test = TestSerilTwo(instance=codes,many=True)
# print(test.data)

from rest_learn.models import TestModel
class TestSerilThree(serializers.ModelSerializer):
    class Meta:
        model = TestModel
        fields = ['name','code','created_time','changed_time','id']
        read_only_fields = ['created_time','changed_time']
Metafieldsread_only_fields
read_only_fields

好像还不是很懂?别着急,我们先用它试试看,接着在下面写:

code = TestModel.objects.get(name='ls')
codes = TestModel.objects.all()

# 前端写入测试
frontend_data = {
    'name':'ModelSeril',
    'code':"""print('frontend test')""",
    'created_time':'2107-12-16'
}
test1 = TestSerilThree(data=frontend_data)
if test1.is_valid():
    print('Frontend test:',test1.validated_data)
# 后端传出测试:
test2 = TestSerilThree(instance=code)
print('Backend single instance test:',test2.data)
test3 = TestSerilThree(instance=codes,many=True)
print('Backend multiple instances test',test3.data)

输出应该是这样的:

Frontend test: OrderedDict([('name', 'ModelSeril'), ('code', "print('frontend test')")])

Backend single instance test: {'created_time': '2017-12-16T05:16:12.846759Z', 'name': 'ls', 'code': 'import os\r\nprint(os.listdir())', 'id': 3, 'changed_time': '2017-12-16T05:16:12.846759Z'}

Backend multiple instances test [OrderedDict([('name', 'hello world'), ('code', "print('Hello world')"), ('created_time', '2017-12-16T05:16:12.815559Z'), ('changed_time', '2017-12-16T05:16:12.815559Z'), ('id', 1)]), OrderedDict([('name', 'pwd'), ('code', 'import os\r\nprint(os.getcwd())'), ('created_time', '2017-12-16T05:16:12.831159Z'), ('changed_time', '2017-12-16T05:16:12.831159Z'), ('id', 2)]), OrderedDict([('name', 'ls'), ('code', 'import os\r\nprint(os.listdir())'), ('created_time', '2017-12-16T05:16:12.846759Z'), ('changed_time', '2017-12-16T05:16:12.846759Z'), ('id', 3)])]
DateTimeField
frontend_datacreated_time.validated_data
created_timechanged_timeread_only_fields

这样就方便许多了!接下来我们进入序列化器的进阶学习。

刚刚的序列化器结构都很简单,使用起来也很简单,要是有关系字段该怎么处理呢?我并不打算直接用模型序列化器来讲解,因为模型序列化器都帮我们把工作都完成了,我们最后什么都看不到。所以然我们来手写一个能处理关系字段的序列化器。在开始之前,注释掉之前的实验代码:

# code = TestModel.objects.get(name='ls')
# codes = TestModel.objects.all()

# 前端写入测试
# frontend_data = {
#     'name':'ModelSeril',
#     'code':"""print('frontend test')""",
#     'created_time':'2107-12-16'
# }
# test1 = TestSerilThree(data=frontend_data)
# if test1.is_valid():
#     print('Frontend test:',test1.validated_data)
# 后端传出测试:
# test2 = TestSerilThree(instance=code)
# print('Backend single instance test:',test2.data)
# test3 = TestSerilThree(instance=codes,many=True)
# print('Backend multiple instances test',test3.data)
PrimaryKeyRelatedFieldPrimaryKeyRelatedFieldTestModelUserPrimaryKeyRelatedField
{
    user:1,
    code:'some code',
    name:'script name' 
}
TestModelUserUserTestModel
{
    user:{
        'id':1,
        'email':'email@example.com',
        'name':'username'
    },
    code:'some code',
    name:'script name'
}
UserPrimaryKeyRelatedFieldPrimaryKeyRelatedFieldUserTestModelUserTestModelUserUserTestModelUser
UserProfilerest_test.py
class ProfileSerializer(serializers.Serializer):
    tel = serializers.CharField(max_length=15)
    height = serializers.IntegerField()

class UserSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=20)
    qq = serializers.CharField(max_length=15)
    profile = ProfileSerializer()
UserSerializerprofileProfileSerializer
frontend_data = {
    'name':'ucag',
    'qq':'88888888',
    'profile':{
        'tel':'66666666666',
        'height':'185'
    }
}

test = UserSerializer(data=frontend_data)
if test.is_valid():
    print(test.validated_data)

我们可以看到输出是这样的:

OrderedDict([('name', 'ucag'), ('qq', '88888888'), ('profile', OrderedDict([('tel', '66666666666'), ('height', 185)]))])
UserProfile

现在可以问,这是怎么回事呢?这是因为序列化器其实就是一个特殊的“序列化器字段”。怎么理解呢?再说的容易懂一点,因为序列化器和序列化字段都是 python 的同一种数据结构——描述符。那描述符又是什么东西呢?官方文档是这么说的:

__get__()__set__()__delete__()__get__()__set__()__delete__()

说的太绕了,我们来简化一下。

__get__()__set__()__delete__()

所以,描述符是属性,描述符也是对象。

a.bba.bbclass__get__()__set__()__delete__()

满足以上两个条件,就可以说这个对象是描述符。

__get__()__set__()__delete__()
descr.__get__(self, obj, type=None) --> value

descr.__set__(self, obj, value) --> None

descr.__delete__(self, obj) --> None

一般地,描述符是作为对象属性来使用的。

a.bba.bb.__get__(a)b.__get__(a)type(a).__dict__['b'].__get__(a, type(a))
a.baAbB
type(a)aAA.__dict__['b'].__get__(a, type(a))A.__dict__['b']AbA.__dict__['b']AbAb.__get__(a, type(a))
A.__dict__['b']AbAb
AbbBA


__get__()Ab.__get__(a, A)b.__get__(a, A)__get__aAselfself
bAAaba.bb.__get__(a, A)bb__get__()

我们稍微再推理一下,就可以知道,如果一个对象的属性是描述符对象,而且这个对象本身也是描述符的话,那么,这个对象的各种子类就可以相互作为彼此的属性。说的很复杂,举个简单的例子。

我们来简单的运用下刚才学到的知识,在解释器里输入以下代码:

In [1]: class Person: # 定义 Person 描述符
   ...:     def __init__(self, name=None):
   ...:         self.name = name
   ...:     def __set__(self, obj, value):
   ...:         if isinstance(value, str):
   ...:             self.name = value
   ...:         else:
   ...:             print('str is required!')
   ...:     def __get__(self, obj, objtype):
   ...:         return 'Person(name={})'.format(s
   ...: elf.name)
   ...: class Dad(Person):
   ...:     kid = Person('Son')
   ...: class Grandpa(Person):
   ...:     kid = Dad('Dad')
   ...:
   ...: dad = Dad('Dad')
   ...: gp = Grandpa('Granpa')
   ...:
   ...: print("Dad's kid:",dad.kid)
   ...: print("Grandpa's kid:",gp.kid)
   ...:
Dad's kid: Person(name=Son)
Grandpa's kid: Person(name=Dad)

In [2]: dad.kid = 18
str is required!

In [3]: dad.kid
Out[3]: 'Person(name=Son)'
Dadkidkid__set__
__get____set____delete__serializers.Field.to_representation(obj).to_internal_value(data)
.to_representation(obj)to_internal_value__get__datetimedatetimeNone.to_internal_value(data)__set__serializers.ValidationError
rest_test.py
# frontend_data = {
#     'name':'ucag',
#     'qq':'88888888',
#     'profile':{
#         'tel':'66666666666',
#         'height':'185'
#     }
# }

# test = UserSerializer(data=frontend_data)
# if test.is_valid():
#     print(test.validated_data)
class TEL(object):
    """电话号码对象"""
    def __init__(self, num=None):
        self.num = num
    def text(self, message):
        """发短信功能"""
        return self._send_message(message)
    def _send_message(self,message):
        """发短信"""
        print('Send {} to {}'.format(message[:10], self.num))
class TELField(serializers.Field):
    def to_representation(self, tel_obj):
        return tel_obj.num
    def to_internal_value(self, data):
        data = data.lstrip().rstrip().strip()
        if 8 <= len(data) <=11:
            return TEL(num=data)
        raise serializers.ValidationError('Invalid telephone number.')

这样就完成了我们的“骚操作”字段。我们就可以这样使用它,接着在下面写:

class ContactSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=20)
    tel = TELField()

frontend_data = {
    'name':'ucag',
    'tel':'88888888'
}
test = ContactSerializer(data=frontend_data)
if test.is_valid():
    tel = test.validated_data['tel']
    print('TEL',tel.num)
    tel.text('这是一个骚字段')
rest_test.py
TEL 88888888
Send 这是一个骚字段 to 88888888

我们自定义的字段就完成了。

以上就是我们对序列化器的学习。目前我们就学习到这个程度,序列化器剩下知识的都是一些 API 相关的信息,需要用到的时候直接去查就是了。我们已经明白了序列化器的原理。以后遇到什么样的数据类型处理都不怕了,要是遇到太奇葩的的需求,大不了我们自己写一个字段。相关的细节我们在以后的学习中慢慢学习。

API View 与 URL 配置

这是 DRF 的又一个很重要的地方,在第二章,我们自己编写了 APIView ,并且只支持一种内容协商,DRF 为我们提供了功能更加完备的 APIView, 不仅支持多种内容协商,还支持对 API 访问频率的控制,对查询结果过滤等等。

DRF 的 API 视图有两种使用方式,一种是利用装饰器,一种是使用类视图。

我们主要讲类视图 API ,装饰器放在后面作为补充。

HttpResponseHttpRequest
PUTPOST

我们来看看 DRF 的请求对象都有哪些功能:

.datadataresquest.dataPUTjson.query_paramsquery_params.useruser.auth

当然,DRF 的请求对象不止有这些功能,还有许多其它的功能,大家可以去文档里探索一下。

DRF 的响应对象:

DRF 响应对象接收以下参数:

datastatusheaderscontent_type

让我们来看看 DRF 的 APIView 具体的应用方法:

from rest_framework.views import APIView
from rest_framework.response import Response
from django.contrib.auth import get_user_model
User = get_user_model()
class ListUsers(APIView):
    def get(self, request, format=None):
        usernames = [user.username for user in User.objects.all()]
        return Response(usernames)
getformatAPIView
GenericViewGenericViewGenericViewMixin
GenericView
querysetAPIViewquerysetserializer_classGenericViewlookup_fieldlookup_argslookup_url_kwarglookup_fieldpagination_classrest_framework.pagination.PageNumberPaginationNonefilter_backendsDEFAULT_FILTER_BACKENDS

提供的方法有:

get_queryset(self)get_querysetget_object(self)filter_queryset(self, queryset)get_serializer_class(self)get_serializer_context(self)requestviewformat
GenericView
ViewSetViewSetViewSet

比如,像这样,这是官方文档的例子:

# views.py
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from myapps.serializers import UserSerializer
from rest_framework import viewsets
from rest_framework.response import Response

class UserViewSet(viewsets.ViewSet):
    def list(self, request):
        queryset = User.objects.all()
        serializer = UserSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        queryset = User.objects.all()
        user = get_object_or_404(queryset, pk=pk)
        serializer = UserSerializer(user)
        return Response(serializer.data)

# urls.py
user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})
ViewSetgetViewSetMethodMapMixinAPIView
# urls.py
from myapp.views import UserViewSet
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'users', UserViewSet, base_name='user')
urlpatterns = router.urls
RouterGenericViewSetModelViewSetGenericViewSetGenericViewModelViewSetModelViewSet
class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
Router
Router
Router
url(r'^users/$', name='user-list'),
url(r'^users/{pk}/$', name='user-detail'),
url(r'^accounts/$', name='account-list'),
url(r'^accounts/{pk}/$', name='account-detail')

自动生成这些 API ,这些 API 都符合 REST 规范。

RouterRoter

注释掉之前的验证代码,接着在后面写:

# frontend_data = {
#     'name':'ucag',
#     'tel':'88888888'
# }
# test = ContactSerializer(data=frontend_data)
# if test.is_valid():
#     tel = test.validated_data['tel']
#     print('TEL',tel.num)
#     tel.text('这是一个骚字段')

from rest_framework.viewsets import ModelViewSet
class TestViewSet(ModelViewSet):
    queryset = TestModel.objects.all()

from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'codes', TestViewSet)
urlpatterns = router.urls
print(urlpatterns)
registerurl(r'^users/$', name='user-list')usersRouter-list-detail
rest_test.py
[<RegexURLPattern testmodel-list ^codes/$>, 
<RegexURLPattern testmodel-list ^codes\.(?P<format>[a-z0-9]+)/?$>, 
<RegexURLPattern testmodel-detail ^codes/(?P<pk>[^/.]+)/$>, 
<RegexURLPattern testmodel-detail ^codes/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$>, 
<RegexURLPattern api-root ^$>, <RegexURLPattern api-root ^\.(?P<format>[a-z0-9]+)/?$>]
Routerapi-root

Vue