REST,即Representational State Transfer的缩写 ,我们一般叫他'表现层状态转化'
REST的路径设计思路是简洁:资源(比如HTML,或者图片,文档之类的)他应是名词的,我们之前在获取商品的时候,我们可能会这样写:/get_products/ 但是这样是不对的.错的.我们在路径中不应该出现动词也就是get,我们使用rest设计路径就会是这样:GET/products/ 这里的GET是请求方式,表示我们以get的方式来请求数据,当然我们在地址栏里面是不需要输入GET的.这样我们的路径就可以很简洁了.
HTTP动词
HTTP的动词有四个:
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
DELETE(DELETE):从服务器删除资源。
括号里面是相对应的SQL命令.
这前面的都是概念.就写这么多吧.
视图中一般做三件事:
将请求的数据(如JSON格式)转换为模型类对象
操作数据库
将模型类对象转换为响应的数据(如JSON格式)
我们在第一步和第三步的时候我们都会涉及到将json数据转化成模型类对象,以及将模型类对象转化成json的数据返回回去.
这里就会涉及到一个来回重复转化的问题,所以我们使用序列化,以及反序列化.
序列化和反序列化的定义:
将程序中的一个数据结构类型转换为其他格式(字典、JSON、XML等),例如将Django中的模型类对象装换为JSON字符串,这个转换过程我们称为序列化。反之,将其他格式(字典、JSON、XML等)转换为程序中的数据,例如将JSON字符串转换为Django中的模型类对象,这个过程我们称为反序列化。
1.定义方法
对于我们之前所使用的BookInfo来建立一个序列化器.
class BookInfoSerializer(serializers.Serializer): """图书数据序列化器""" id = serializers.IntegerField(label='ID', read_only=True) btitle = serializers.CharField(label='名称', max_length=20) bpub_date = serializers.DateField(label='发布日期', required=False) bread = serializers.IntegerField(label='阅读量', required=False) bcomment = serializers.IntegerField(label='评论量', required=False) image = serializers.ImageField(label='图片', required=False)
这个序列化器继承serializers.Serializer 我们定义的每一个字段,名称都和数据库中的字段保持一致,label表示标签,是在HTML页面显示api时,显示的字段名称.read_only表示该字段只用于序列化输出.也就是只可以读他,在返回的时候可以返回他,接收的时候不需要接收他,因为是自增的.required 表示是否是必填项,false表示是可以填,可以不填的.
字段构造方式
字段 | 字段构造方式 |
---|---|
BooleanField | BooleanField() |
NullBooleanField | NullBooleanField() |
CharField | CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True) |
EmailField | EmailField(max_length=None, min_length=None, allow_blank=False) |
RegexField | RegexField(regex, max_length=None, min_length=None, allow_blank=False) |
SlugField | SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+ |
URLField | URLField(max_length=200, min_length=None, allow_blank=False) |
UUIDField | UUIDField(format='hex_verbose') format: 1) 'hex_verbose' 如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex' 如 "5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a" |
IPAddressField | IPAddressField(protocol='both', unpack_ipv4=False, **options) |
IntegerField | IntegerField(max_value=None, min_value=None) |
FloatField | FloatField(max_value=None, min_value=None) |
DecimalField | DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置 |
DateTimeField | DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None) |
DateField | DateField(format=api_settings.DATE_FORMAT, input_formats=None) |
TimeField | TimeField(format=api_settings.TIME_FORMAT, input_formats=None) |
DurationField | DurationField() |
ChoiceField | ChoiceField(choices) choices与Django的用法相同 |
MultipleChoiceField | MultipleChoiceField(choices) |
FileField | FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ImageField | ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ListField | ListField(child=, min_length=None, max_length=None) |
DictField | DictField(child=) |
这以上是每个类型的定义的语法,其中required没有写,这个required是每个里面都可以写的.
每个选项里面的选项参数的定义:
参数名称 | 作用 |
---|---|
max_length | 最大长度 |
min_lenght | 最小长度 |
allow_blank | 是否允许为空 |
trim_whitespace | 是否截断空白字符 |
max_value | 最小值 |
min_value | 最大值 |
通用参数的意义如下表:
参数名称 | 说明 |
---|---|
read_only | 表明该字段仅用于序列化输出,默认False |
write_only | 表明该字段仅用于反序列化输入,默认False |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
allow_null | 表明该字段是否允许传入None,默认False |
validators | 该字段使用的验证器 |
error_messages | 包含错误编号与错误信息的字典 |
label | 用于HTML展示API页面时,显示的字段名称 |
help_text | 用于HTML展示API页面时,显示的字段帮助提示信息 |
这三张表以后在定义序列化器的时候可以用到.
在这里补充一下allow_blank 和allow_null的区别:
null: If True, Django will store empty values as NULL in the database. Default # 原文解释 is False. 如果为True,空值将会被存储为NULL,默认为False。 blank: If True, the field is allowed to be blank. Default is False. # 原文解释 如果为True,字段允许为空,默认不允许。 其实感觉没什么区别.
创建serializer对象
Serializer的构造结构:
Serializer(instance=None, data=empty, **kwarg)
1)用于序列化时,将模型类对象传入instance参数
2)用于反序列化时,将要被反序列化的数据传入data参数
3)除了instance和data参数外,在构造Serializer对象时,还可通过context参数额外添加数据
下面是一个事例:
serializer = AccountSerializer(account, context={'request': request})
通过context添加的数据,我们可以使用Serializer对象的context属性获取.data数据我们也可以使用该对象的data属性获取,获取到的是序列化完成的数据.
2.序列化
基本的使用:
先创建一个模型类对象:book = BookInfo.objects.get(id=1)
然后创建一个序列化的对象:ser = BookInfoSerializer(book) # 此处的book就是instance
我们可以通过:ser.data取出序列化后的数据:
我们查询的不是一个数据,是多个数据,我们就要加上many = True 这个选项可以序列化模型类里面含有很多数据的情况.
关联对象的序列化
我们在定义一对多的多的一方是,定义外键有很多种办法:
PrimaryKeyRelatedField
这个是将被序列化为关键对象的主键.也就是bookinfo的主键
hbook = serializers.PrimaryKeyRelatedField(label='图书', read_only=True) 或 hbook = serializers.PrimaryKeyRelatedField(label='图书', queryset=BookInfo.objects.all())
read_only = True是指该字段将不能作为反序列化使用
query_set 表示将用作反序列化时参数校验使用.
StringRelatedField
这个将被序列化为关联对象的字符串表示方式(即str方法的返回值)
我举个栗子:
hbook = serializers.StringRelatedField(label='图书')
在这里我们得到的data数据是:我们在str方法里面定义的返回的数据.
使用关联对象的序列化器
这个也就是在hbook = BookInfoSerializer()
就是将整个的序列器全部加进去.这样我们会得到一本书的所有信息.
HyperlinkedRelatedField
这里是我们将其序列化为获取关联对象数据的链接接口.
for example:
hbook = serializers.HyperlinkedRelatedField(label='图书', read_only=True, view_name='books-detail')
view_name = XXX 这里的view_name是必须要指明的,这样DRF框架才可以找到指定的路由,然后在拼接成一个完整的URL.
SlugRelatedField
这将被序列化为关联对象的指定字段数据
hbook = serializers.SlugRelatedField(label='图书', read_only=True, slug_field='bpub_date') # 也就是显示关联对象你想他显示的数据
重写to_representation()方法
序列化器的每个字段其实都是都是有该字段类型的to_representation来决定的,我们重写他.
class BookRelateField(serializers.RelatedField): """自定义用于处理图书的字段""" def to_representation(self, value): return 'Book: %d %s' % (value.id, value.btitle)
我们定义了一个新的关联字段类型,
hbook = BookRelateField(read_only = True)
3.反序列化
同样的,我们使用序列化器进行反序列化的时候,我们需要对数据进行验证,验证通过之后,我们才可以获取保存成功的数据,或者我们保存数据模型类的对象.
我们通过is_valid()来验证数据的正确性.
验证失败:我们可以用序列化对象.errors来获取错误
验证成功:我们可以使用序列化对象.validated_data 来获取数据
is_valid()方法还可以在验证失败时抛出异常serializers.ValidationError,可以通过传递raise_exception=True参数开启,REST framework接收到此异常,会向前端返回HTTP 400 Bad Request响应。 如:
serializer.is_valid(raise_exception=True)
我们还可以自定义验证方法:
validate_
对我们后面填入的field_name字段进行验证,在我们的BookInfoSerializer里面添加:
def validate_btitle(self, value): if 'django' not in value.lower(): raise serializers.ValidationError("图书不是关于Django的") return value
btitle就是field_name 这样设置之后,这个方法就可以验证btitle的正确性了.btitle的值,就会传给value.
我们验证的时候,依然是用is_valid,来进行验证.
validate
一般需要将模型中的多个字段进行验证时,我们可以使用validate进行验证
我们在BookInfoSerializer中定义一个validate方法:
def validate(self, attrs): bread = attrs['bread'] bcomment = attrs['bcomment'] if bread < bcomment: raise serializers.ValidationError('阅读量小于评论量') return attrs
3.validators
我们在序列化器中添加选项参数,也可以补充验证行为.
btitle = serializers.CharField(label='名称', max_length=20, validators=[about_django])
我们在类的上面定义一个about_Leijingjing方法:
def about_django(value): if 'Leijingjing' not in value.lower(): raise serializers.ValidationError("图书不是关于Leijingjing的")
这就OK了.
4.保存
验证成功后,我们就可以保存了.我们在BookInfoSerializer里面定义两个方法:create(), update()
def create(self, validated_data): """新建""" # **用于将字典解包,解成 a = 1 的这种形式 return BookInfo.objects.create(**validated_data) def update(self, instance, validated_data): """更新,instance为要更新的对象实例""" instance.btitle = validated_data.get('btitle', instance.btitle) instance.bpub_date = validated_data.get('bpub_date', instance.bpub_date) instance.bread = validated_data.get('bread', instance.bread) instance.bcomment = validated_data.get('bcomment', instance.bcomment) instance.save() return instance
定义了这个之后,我们就可以在反序列化字段的时候,就可以使用save()来返回一个数据对象实例了.
如果创建序列化器对象的时候,没有传递instance实例,则调用save()方法的时候,create()被调用,相反,如果传递了instance实例,则调用save()方法的时候,update()被调用。
这很神奇,理解不了,只有记住了.
还有两点:
1) 在对序列化器进行save()保存时,可以额外传递数据,这些数据可以在create()和update()中的validated_data参数获取到
serializer.save(owner=request.user)
2)默认序列化器必须传递所有required的字段,否则会抛出验证异常。但是我们可以使用partial参数来允许部分字段更新
# Update `comment` with partial data serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
...
5.模型类序列化器
在生成模型类序列化器的时候,有一些注意点.需要记一下.
我们生成一个模型类序列化器:BookInfoSerializer
class BookInfoSerializer(serializers.ModelSerializer): """图书数据序列化器""" class Meta: model = BookInfo fields = '__all__'
model 指向的是哪个模型类
fields指向的是我们序列化哪些字段.
指定字段
我们可以在操作fields 来实现字段控制:fields = ('id', 'btitle', 'bpub_date')
这个表示只显示这三个字段.
我们可以使用exclude来排除哪些字段:exclude = ('image',) 这是一个元祖.
我们可以使用depth来实现嵌套表示,depth是整数,它表示嵌套的层级数.
如:
class HeroInfoSerializer2(serializers.ModelSerializer): class Meta: model = HeroInfo fields = '__all__' depth = 1
形成的序列化器如下: 在hbook后面又套了一层.
HeroInfoSerializer(): id = IntegerField(label='ID', read_only=True) hname = CharField(label='名称', max_length=20) hgender = ChoiceField(choices=((0, 'male'), (1, 'female')), label='性别', required=False, validators=[, ]) hcomment = CharField(allow_null=True, label='描述信息', max_length=200, required=False) hbook = NestedSerializer(read_only=True): id = IntegerField(label='ID', read_only=True) btitle = CharField(label='名称', max_length=20) bpub_date = DateField(allow_null=True, label='发布日期', required=False) bread = IntegerField(label='阅读量', max_value=2147483647, min_value=-2147483648, required=False) bcomment = IntegerField(label='评论量', max_value=2147483647, min_value=-2147483648, required=False) image = ImageField(allow_null=True, label='图片', max_length=100, required=False)
我们还可以显示只读字段:
class BookInfoSerializer(serializers.ModelSerializer): """图书数据序列化器""" class Meta: model = BookInfo fields = ('id', 'btitle', 'bpub_date', 'bread', 'bcomment') read_only_fields = ('id', 'bread', 'bcomment') # 这些表示是只读字段,也就是只用于序列化输出的字段.
添加额外参数
我们可以使用extra_kwargs参数为ModelSerializer添加或修改原有的选项参数
class BookInfoSerializer(serializers.ModelSerializer): """图书数据序列化器""" class Meta: model = BookInfo fields = ('id', 'btitle', 'bpub_date', 'bread', 'bcomment') extra_kwargs = { 'bread': {'min_value': 0, 'required': True}, 'bcomment': {'min_value': 0, 'required': True}, }
序列化与反序列化到此为止!!!
安装drf命令: pip install djangorestframework 然后在install_apps里面注册rest_framework
书写视图函数:在views.py中
class BookInfoViewSet(ModelViewSet): queryset = BookInfo.objects.all() serializer_class = BookInfoSerializer
定义路由:是在urls.py中定义的
urlpatterns = [ ... ] router = DefaultRouter() # 可以处理视图的路由器 router.register(r'books', views.BookInfoViewSet) # 向路由器中注册视图集 urlpatterns += router.urls # 将路由器中的所以路由信息追到到django的路由列表
1.request 和 response
DRF框架封装了一个request对象在里面,我们就不在使用原来Django中的httprequest对象了.他一般有两个属性一个是.data ,一个是.query_params
request.data是对应于原先Django中的.json 和.files.这个也是使用最多的,有如下特点:
包含了解析之后的文件和非文件数据
包含了对POST、PUT、PATCH请求方式解析后的数据
利用了REST framework的parsers解析器,不仅支持表单类型数据,也支持JSON数据
request.query_params 和原先Django中的GET方法相同,是获取查询字符串中的内容的.
同样的,框架内部也还有一个response对象,我们最开始需要在配置文件里面配置一下,如下
REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ( # 默认响应渲染类 'rest_framework.renderers.JSONRenderer', # json渲染器 'rest_framework.renderers.BrowsableAPIRenderer', # 浏览API渲染器 ) }
构造方式:Response(data, status=None, template_name=None, headers=None, content_type=None)
一般使用的最多的就是data 和status ,data指的是序列化之后的字典数据,但尚未render的数据. status表示状态码,还有一个.content表示render过后的数据.
2.视图
视图集,这里是一个链接,是各视图之间的关系.
可能看不清,不要慌,接着往下看
1)两个基类
APIView是DRF框架提供的所有视图的基类,他继承自Django的View类.他和Django中的View不同的地方在于
传入到视图方法中的是REST framework的Request
对象,而不是Django的HttpRequeset
对象;
视图方法可以返回REST framework的Response
对象,视图会为响应数据设置(render)符合前端要求的格式;
任何APIException
异常都会被捕获到,并且处理成合适的响应信息;
在进行dispatch()分发前,会对请求进行身份认证、权限检查、流量控制。
他其中定义的属性有:权限控制,流量限制,身份认证
authentication_classes 列表或元祖,身份认证类
permissoin_classes 列表或元祖,权限检查类
throttle_classes 列表或元祖,流量控制类
也就是说,如果我们定义的类视图继承了APIView我们就可以使用这些属性.
GenericAPIView,他继承自APIView类,他在APIView的基础上增加了列表视图(也就是获取全部数据)和详情视图(获取单个数据)的通用支持方法.使用的时候我们需要搭配多个mixin扩展类使用.他的属性有:
列表视图和详情视图通用的:
queryset 列表视图的查询集
serializer_class 视图使用的序列化器
列表视图使用的:
pagination_class 分页控制类 我们获取所有数据,我们就需要使用分页操作,或者过滤操作
filter_backends 过滤控制后端
详情视图使用的:
lookup_field 查询单一数据库对象时使用的条件字段,默认为'pk
' 这个使用的比较多.pk表示主键
lookup_url_kwarg 查询单一数据时URL中的参数关键字名称,默认与look_field相同
提供的方法,我们如果继承了他,我们在我们定义的类视图中,就可以直接使用self.方法名调用他.很方便
列表视图与详情视图通用的:
get_queryset()返回列表视图与详情视图的查询集,是两个视图获取数据的基础.
get_serializer()返回序列化器对象,我们在类视图的最开始的地方,会指定一个查询集,一个序列化集,我们使用这个方法,我们就不用再写序列化集,直接使用这个来返回序列化对象了.比如
本来: ser = BookInfoSerializer(book) 现在 ser = get_Serializer(book) 就好了
get_serializer_class 会返回一个序列化器类,这个有什么用,我也不知道
详情视图专用的:
get_object() 这个会返回我们需要的详情类的模型类对象.不过我们需要在方法中传入一个pk值,
2)五个扩展类
五个分别是:ListModelMixin(返回所有数据),CreateModelMixin(创造一个数据),RetrieveModelMixin(获取单个的数据对象),UpdateModelMixin (更新某条数据),DestoryModelMixin(删除数据)
我们如果继承了:这个其中的方法,我们就可以不用写视图方法中的处理逻辑了.
我们就可以这样写!
继承ListModelMixin ,我们就可以这样写:
def get(self, request): return self.list(request) # 这是因为我们调用了扩展类里面的list方法.我们传入request就好了啊
CreateModelMixin里面是create方法,RetrieveModelMixin里面是retrieve方法,UpdateModelMixin 里面是update方法,DestoryModelMixin里面是destroy方法.
3)子视图
还有七个可以的子类视图.就是他已经继承好了,我们继承一个,其实就已经继承了好几个的意思.
CreateAPIView 继承自: GenericAPIView、CreateModelMixin
ListAPIView 继承自:GenericAPIView、ListModelMixin
RetrieveAPIView 继承自:GenericAPIView、RetrieveModelMixin
DestroyAPIView 继承自:GenericAPIView、DestoryModelMixin
UpdateAPIView 继承自:GenericAPIView、UpdateModelMixin
RetrieveUpdateAPIView 继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin
RetrieveUpdateDestroyAPIView 继承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestoryModelMixin
真多!
3.视图集
视图集就是Viewset(),就是将一系列逻辑相关的动作放在一个类中,
list() 提供一组数据
retrieve() 提供单个数据
create() 创建数据
update() 更新数据
destory() 删除数据
我们在视图集中不在实现get 和post方法了,我们就使用上面列的几种方法.
# 比如我们定义了以下两个方法 class BookInfoViewSet(viewsets.ViewSet): def list(self, request): ... def retrieve(self, request, pk=None): ... # 我们定义路由的时候,就这样定义 urlpatterns = [ url(r'^books/$', BookInfoViewSet.as_view({'get':'list'}), url(r'^books/(?P\d+)/$', BookInfoViewSet.as_view({'get': 'retrieve'}) ] # 在as_view后面加上什么类型,以及对应的方法.
上面的五个已经定义好的,我们不需要给他指定请求方法.
但是我们要是定义一个自定义的方法,我们就需要给他指定一个请求方式了.
这时,我们就需要给我们写的方法,添加一个装饰器,action,
action需要接收两个参数:
methods: 该action支持的请求方式,使用列表包裹
detail: 表示是action中要处理的是否是视图资源的对象(即是否通过url路径获取主键),如果是True,我们就是要给他传入一个主键pk,False就不用传.
比如:
class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet): queryset = BookInfo.objects.all() serializer_class = BookInfoSerializer # detail为False 表示不需要处理具体的BookInfo对象 @action(methods=['get'], detail=False) def latest(self, request): """ 返回最新的图书信息 """ book = BookInfo.objects.latest('id') serializer = self.get_serializer(book) return Response(serializer.data) # detail为True,表示要处理具体与pk主键对应的BookInfo对象 @action(methods=['put'], detail=True) def read(self, request, pk): """ 修改图书的阅读量数据 """ book = self.get_object() book.bread = request.data.get('read') book.save() serializer = self.get_serializer(book) return Response(serializer.data) # 我们定义路由的时候,我们这样定义 url(r'^books/latest/$', views.BookInfoViewSet.as_view({'get': 'latest'})), url(r'^books/(?P\d+)/$', views.BookInfoViewSet.as_view({'get': 'retrieve'})),
和之前无差.
视图集父类
这其中也有很多已经封装好的子类用于继承的,如下:
1) ViewSet
继承自APIView
,作用也与APIView基本类似,提供了身份认证、权限校验、流量管理等。
在ViewSet中,没有提供任何动作action方法,需要我们自己实现action方法。
2)GenericViewSet
继承自GenericAPIView
,作用也与GenericAPIVIew类似,提供了get_object、get_queryset等方法便于列表视图与详情信息视图的开发。
3)ModelViewSet
继承自GenericAPIVIew
,同时包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin。
4)ReadOnlyModelViewSet
继承自GenericAPIVIew
,同时包括了ListModelMixin、RetrieveModelMixin。
路由一共分为两种: DefaultRouter ,和 SimpleRouter,
创建路由对象,并注册视图集
router = routers.SimpleRouter() router.register(r'books', BookInfoViewSet, base_name='book')
注册的语法如下:register(prefix, viewset, base_name)
books 就是prefix , viewset就是BookInfoViewSet, book就是 base_name
prefix是路由前缀,在请求的地址中.如127.0.0.1:8000/books
base_name就是区别视图方法的,一般是这样的格式 : book-list (list方法)
这后面的不重要了,看看就行了.反正我是这样想的!