你好 Django REST Framework
在第二章,我们学习了 REST 开发的基本知识,并且在没有借助任何框架的情况下
完成了我们的 RESTful APP 的开发,虽然我们已经考虑到了许多的情况,但是我们的 APP
依然有许多的漏洞。在本章,我们将会进入 Vue 和 Django REST framework
的学习。本章将会分为三个部分,分别是:
- 你好 Django REST Framework
- 你好 Vue
- 重构 APP
这就是我们的三个部分了。第一个部分学习 DRF ,第二个部分学习 Vue ,最后一个部分为实战部分。在上部分,我们会学习以下知识点:
- 了解 DRF 的基本使用。
- 了解并能灵活使用序列化器。
这个部分的知识点看起来很少,其实等大家真正进入他们的学习中时,会发现其中的知识点也不少。当然,这是一个教程,不是 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