Django Rest framework入门笔记及跳坑记录



更新记录

2016-01-26 初稿

序列化时嵌套显示外键关联字段

  • 自动
    使用depth参数指定外键深度

  • 手动指定
    使用外键对应model的小写为属性,外键对应的model序列化程序为值
    以下例子在HospitalPic序列化结果里嵌套显示Hospital
    models.py

    1
    2
    3
    4
    5
    6
    7
    from django.db import models

    class Hospital(models.Model):
    name = models.CharField()

    class HospitalPic(models.Model):
    hospital = models.ForeignKey(Hospital)

serializers.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from rest_framework import serializers

class HospitalSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Hospital
fields = '__all__'


class HospitalPicSerializer(serializers.HyperlinkedModelSerializer):
hospital = HospitalSerializer()

class Meta:
model = HospitalPic
fields = '__all__'

反向关系嵌套
Hospital序列化结果里嵌套显示HospitalPic
serializers.py

1
2
3
4
5
6
7
8
9
10
11
12
13
from rest_framework import serializers

class HospitalPicSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = HospitalPic
fields = '__all__'


class HospitalSerializer(serializers.HyperlinkedModelSerializer):
hospitalpic_set = HospitalPicSerializer(many=Ture)
class Meta:
model = Hospital
fields = '__all__'

在序列化对象里添加关联表的字段内容

定义一个serializer Field,并添加参数source指向外键对对应的字段(source值其实是从当前序列化的实例的属性)

1
my_address= serializers.ReadOnlyField(source='address.full_address')

在序列化对象里添加自定义内容

1
2
3
4
5
6
7
8
9
10
11
12
from django.contrib.auth.models import User
from django.utils.timezone import now
from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):
days_since_joined = serializers.SerializerMethodField()

class Meta:
model = User

def get_days_since_joined(self, obj):
return (now() - obj.date_joined).days

使用ViewSet,并不有设置queryset,而是重写了get_queryset时,需要在router里增加base_name参数(base_namerouterViewSet注册url时自动添加的name前缀,如果未设置则从ViewSetqueryset里取,使用ViewSet自动生成的url name为<base_name>-list <base_name>-detail 等)

views.py

1
2
3
4
5
6
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
permission_classes = (permissions.IsAuthenticated,)

def get_queryset(self):
return self.request.user.contact_set.all()

urls.py

1
router.register(r'contact', ContactViewSet, base_name='contact')

未设置base_name会报下面错误

1
'base_name' argument not specified, and could not automatically determine the name from the viewset, as it does not have a '.queryset' attribute.

给api接口的url添加了命名空间namespace

urls.py

1
url(r'^api/', include(router.urls, namespace='api')),

需要对HyperlinkedRelatedField字段的参数进行修改
serializers.py

1
2
3
4
5
6
7
8
class HospitalPicSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = HospitalPic
fields = '__all__'
extra_kwargs = {
'url': {'view_name': 'api:hospitalpic-detail'},
'hospital': {'view_name': 'api:hospital-detail'}
}

不然会出现以下错误

1
Could not resolve URL for hyperlinked relationship using view name "user-detail". You may have failed to include the related model in your API, or incorrectly configured the `lookup_field` attribute on this field.

不过话说我们全api的url加namespace一般是为了版本控制,所以有一种简单的方法,只要在settings.py添加基于namespace的版本控制,这样就不需要修改HyperlinkedRelatedField字段的view_name
urls.py

1
2
url(r'^api/v1/', include(router.urls, namespace='v1')),
url(r'^api/v2/', include(router.urls, namespace='v2')),

settings.py

1
2
3
4
5
REST_FRAMEWORK = {
……
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning',
……
}

要drf的错误提示为中文,需要设置

1
LANGUAGE_CODE = 'zh-CN'

如果设置为

1
LANGUAGE_CODE = 'zh-Hans'

虽然django默认表单错误会输出中文,但drf还是输出英文

django的validators可以直接在drf中使用,不需要做任何修改

当字段里的属性editable=False时,ModelSerializer里该字段会抛弃model里显式和隐式(unique)的所有validators

Serializerwrite_only写在field里和写在extra_kwargs里是有区别的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class UserRegisterSerializer(serializers.ModelSerializer):
"""用户注册Serializer"""

code = serializers.CharField(min_length=4, max_length=6, label=_('验证码'),
help_text=_('验证码'), write_only=True)
re_password = serializers.CharField(label=_('重复密码'), help_text=_('重复密码'),
validators=validators.password_validators(),
write_only=True)

class Meta:
model = User
fields = ('mobile_phone', 'code', 'password', 're_password')
extra_kwargs = {'password':
{'write_only': True}
}

def validate(self, attrs):
"""
Check that the start is before the stop.
"""
if attrs['password'] != attrs['re_password']:
raise serializers.ValidationError(_('密码不一致'))

# 校验验证码
verify_result = Sms(attrs['mobile_phone']).verify_sms_code(
attrs.pop('code'))
if not verify_result:
error = verify_result.get('error')
raise ParseError(error)
return attrs

def create(self, validated_data):
user = User(
username=validated_data['mobile_phone'],
mobile_phone=validated_data['mobile_phone'],
)
user.set_password(validated_data['password'])
user.save()
return user

因为create()这个方法return了一个user实例,User里没有的字段codere_password需要将write_only写在field参数里,不然会报以下错误

1
2
3
AttributeError: Got AttributeError when attempting to get a value for field `code` on serializer `UserRegisterSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'code'.

如果使用django-rest-swagger报以下错误

1
Can't read from server. It may not have the appropriate access-control-origin settings.

注释掉设置里的

1
# 'base_path': '127.0.0.1:8000/docs',

serializer.dataserializer.validated_data

serializer只使用data参数实例化的时:

  • serializer.data是原始数据(字符串),serializer.validated_data是进行数据验证并转换成对应数据类型的数据。
  • 两者者必须在serializer调用is_valid方法后才能调用
    serializer只使用instance参数实例化时:
  • 只有serializer.data没有serializer.validated_data,并且serializer.data里的数据也是字符串;
  • 没有方法is_valid
  • is_validvalidated_data只在有data参数实例化时才可调用;

serializer里获取原始请求信息

默认的,上下文信息会被传递到serializer里,所以在serializer可以直接使用self.context['request']来获取请求信息。(在要继承自viewsets.GenericViewSet的类里使用的serializer才能取到,如果是继承APIView的,自己传入即可serializer = self.serializer_class(data=request.data, context={'request': request})

自定义serializer字段

自定义字段继承serializers.Fieldto_representation方法处理出来的数据用来序列化显示,to_internal_value处理接收到的数据,get_attribute方法指定这个字段访问的实例属性,get_value方法指定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class QiNiuField(serializers.Field):
def get_attribute(self, instance):
# (序列化时)从模型实例中取一个值给这个字段处理,也可以使用`source`参数指定
return instance.key

def get_value(self, dictionary):
# (反序列化时)从传入数据中提取一个值给这个字段处理
return super(QiNiuField, self).get_value(dictionary)

def to_representation(self, value):
# (序列化时)处理出来的数据用来序列化显示
return value.url

def to_internal_value(self, data):
# (反序列化时)处理接收到的数据
return data['key']

嵌套序列化,传参问题

官方文档中有这么一个例子Dealing with nested objects
如果是以Content-Type:application/json形式传数据格式传数据,直接嵌套传就可以了{'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'},但如果是以,
但是如果以Content-Type:form-dataContent-Type:x-www-form-urlencoded上传,则上传user信息进不是嵌套,而是就.连接了,"user.email":"foobar".

0%