01.drf文档及外键字段反序列化

一 安装drf

  • 1.1 安装库
pip install djangorestframework
pip install markdown       # Markdown support for the browsable API.
pip install django-filter  # Filtering support
  • 1.2 settings 添加配置
    'rest_framework',

二 接口文档

  • 2.1 安装库
pip3 install coreapi
  • 2.2 settings 添加配置
REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
  • 2.3 添加url

url(r'^docs/', include_docs_urls("运维平台接口文档"))

from rest_framework.documentation import include_docs_urls

urlpatterns = [
    path('docs/', include_docs_urls(title="运维平台API接口文档",description="django drf 接口文档")),
]

三 单个表CRUD

  • 3.1 models
class Idc(models.Model):
    name    = models.CharField("机房名称",max_length=32)
    address = models.CharField("机房地址",max_length=256)
    phone   = models.CharField("联系人",max_length=15)
    email   = models.EmailField("邮件地址",default="null")
    letter  = models.CharField("IDC简称",max_length=5)

    def __str__(self):
        return self.name

    class Meta:
        db_table = 'resources_idc'
  • 3.2 serializers

serializers.Serializer不需要指定模型,需要序列化那些字段都需要显式指定,同时需要重写createupdate方法

class IdcSerializer(serializers.Serializer):
    """
    Idc 序列化类
    """

    id = serializers.IntegerField(read_only=True)
    name = serializers.CharField(required=True, max_length=32, label="机房名称", help_text="机房名称",  error_messages={"blank": "机房名称不能为空", "required": "这个字段为必要字段"})
    address = serializers.CharField(required=True, max_length=256, label="机房地址", help_text="IDC详细地址", error_messages={"blank": "这个字段不能为空", "required": "这个字段为必要字段"} )
    phone = serializers.CharField(required=True, max_length=15, label="联系电话", help_text="联系电话")
    email = serializers.EmailField(required=True, label="email", help_text="email地址")
    letter = serializers.CharField(required=True, max_length=5, label="字母简称", help_text="字母简称")

    def create(self, validated_data):
        return Idc.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.name = validated_data.get("name", instance.name)
        instance.address = validated_data.get("address", instance.address)
        instance.phone = validated_data.get("phone", instance.phone)
        instance.email = validated_data.get("email", instance.email)
        instance.save()
        return instance

  • 3.3 views
class IdcViewset(viewsets.ModelViewSet):
    queryset = Idc.objects.all()
    serializer_class = IdcSerializer
  • 3.4 路由
from django.urls import path,include
from rest_framework.routers import DefaultRouter
from idcs.views import IdcViewset

router = DefaultRouter()
router.register('idcs',IdcViewset,basename='idcs')
urlpatterns = [
    path('', include(router.urls))
    ]
  • 3.5 此时可以完成单个模型的CRUD操作
[
    {
        "id": 1,
        "name": "亚太机房",
        "address": "神舟路999号",
        "phone": "13812345678",
        "email": "xxx@com.cn",
        "letter": "ytjf"
    },
    {
        "id": 2,
        "name": "亚太机房1",
        "address": "神舟路999号1",
        "phone": "13812345678",
        "email": "xxx@com.cn",
        "letter": "ytjf1"
    },
    {
        "id": 3,
        "name": "亚太机房2",
        "address": "神舟路999号2",
        "phone": "13812345678",
        "email": "xxx@com.cn",
        "letter": "ytjf2"
    }
]

四 增加一对多中的多,在多的模型中指定外键字段

  • 4.1 models

class Cabinet(models.Model):
    idc = models.ForeignKey(Idc, verbose_name="所在机房",on_delete=models.CASCADE)
    name = models.CharField(max_length=255)

    def __str__(self):
        return self.name

    class Meta:
        db_table = "resources_cabinet"
        ordering = ["id"]
  • 4.2 serializers
class CabinetSerializer(serializers.Serializer):
    idc = serializers.PrimaryKeyRelatedField(many=False, queryset=Idc.objects.all())
    name = serializers.CharField(required=True)
  • 4.3 views
class CabinetViewset(viewsets.ModelViewSet):
    queryset = Cabinet.objects.all()
    serializer_class = CabinetSerializer
  • 4.4 如果不重写create方法,提交时报错,但可以正常显示
# get
[
    {
        "idc": 1,
        "name": "A座6楼 13排2机柜"
    },
    {
        "idc": 1,
        "name": "A座1楼 13排2机柜"
    },
    {
        "idc": 1,
        "name": "A座2楼 13排2机柜"
    }
]
# post
Exception Value: `create()` must be implemented.

五 显示给前端时,应该显示idc的名称,而不是id

5.1 方法一,使用1对n中的1序列化
from idcs.serializers import IdcSerializer
class CabinetSerializer(serializers.Serializer):
    idc = IdcSerializer(many=False)
    name = serializers.CharField(required=True)

显示如下

[
    {
        "idc": {
            "id": 1,
            "name": "亚太机房",
            "address": "神舟路999号",
            "phone": "13812345678",
            "email": "xxx@com.cn",
            "letter": "ytjf"
        },
        "name": "A座6楼601 13排2机柜"
    }
]
5.2 方法二,使用 SerializerMethodField
from idcs.serializers import IdcSerializer
class CabinetSerializer(serializers.Serializer):
    idc_name = serializers.SerializerMethodField()
    name = serializers.CharField(required=True)

    def get_idc_name(self,obj):
        return obj.idc.name

显示如下

[
    {
        "idc_name": "亚太机房",
        "name": "A座6楼601 13排2机柜"
    }
]
5.3 方法三,使用 PrimaryKeyRelatedField

to_representation中进行额外字段的增加

class CabinetSerializer(serializers.Serializer):
    idc = serializers. PrimaryKeyRelatedField(many=False, queryset=Idc.objects.all())
    name = serializers.CharField(required=True)
    
    def to_representation(self, instance):
        idc_obj = instance.idc
        ret = super(CabinetSerializer, self).to_representation(instance)
        ret["idc"] = {
            "id": idc_obj.id,
            "name": idc_obj.name
        }
        return ret

显示如下:

[
    {
            "idc": {
                "id": 1,
                "name": "亚太机房"
            },
            "name": "A座6楼601 13排2机柜"
        }
]

六 对子表机柜反序列化写入

反序列化的过程

前端提交数据 --> 数据存储在request的get、post、body中 --> drf通过to_internal_value获取原始的数据 
--> 进入单独字段级别的验证 validated_字段 --> 全部字段验证(比如重复数据 validated)--> 进入表级别的验证
6.1 对应上面的方法一序列化的反序列化
from idcs.serializers import IdcSerializer
class CabinetSerializer(serializers.Serializer):
    idc = IdcSerializer(many=False)
    name = serializers.CharField(required=True)
# 重写 create 方法
    def create(self, validated_data):
        return Cabinet.objects.create(**validated_data)

序列化显式如下

[
    {
        "idc": {
            "id": 1,
            "name": "亚太机房",
            "address": "神舟路999号",
            "phone": "13812345678",
            "email": "xxx@com.cn",
            "letter": "ytjf"
        },
        "name": "A座6楼601 13排2机柜"
    },
  • 6.1.1 创建一个机柜,post如下值
{
    "idc": 1,
    "name": "A座3楼 13排2机柜"
}
  • 6.1.2 报错如下,因为序列化的时候 idc 是一个字典
or key, value in self.value.items():
AttributeError: 'int' object has no attribute 'items'
  • 6.1.3 传入字典
{
    "idc": {
                "id": 1,
                "name": "亚太机房",
                "address": "神舟路999号",
                "phone": "13812345678",
                "email": "xxx@com.cn",
                "letter": "ytjf"
            },
    "name": "A座3楼601 13排2机柜"
}
  • 6.1.4 报错如下,因为机柜的idc是一个外键字段,django的orm需要传入对应外键模型的实例
"Cabinet.idc" must be a "Idc" instance.

模型

class Cabinet(models.Model):
    idc = models.ForeignKey(Idc, verbose_name="所在机房",on_delete=models.CASCADE)
    name = models.CharField(max_length=255)
6.2 解决方法
  • 6.2.1 因为是在ORM层报的错,因此可以在 vaildate 中进行机房实例的获取
def validate(self, attrs):
    idc_data = attrs.pop('idc')
    idc_name = idc_data['name']
    try:
        idc_inst = Idc.objects.get(name=idc_name)
        attrs['idc'] = idc_inst
        return attrs
    except Idc.DoesNotExist:
        raise serializers.ValidationError('机房%s不存在'%idc_name)
  • 6.2.2 返回数据如下
{
    "idc": {
        "id": 1,
        "name": "亚太机房",
        "address": "神舟路999号",
        "phone": "13812345678",
        "email": "xxx@com.cn",
        "letter": "ytjf"
    },
    "name": "A座3楼601 13排2机柜"
}
  • 6.2.3 为什么不用id查找?
    打印一下前端传过来的 id 字段
for k,v in idc_data.items():
    print(k,v)
    # 因为idc的模型 id 字段在序列化时是只读,反序列化时会被丢弃
    # OrderedDict([('idc', OrderedDict([('name', '亚太机房'), ('address', '神舟路999号'), ('phone', '13812345678'), ('email', 'xxx@com.cn'), ('letter', 'ytjf')])), ('name', 'A座3楼601 13排2机柜')])
  • 6.2.4 传入的字段必须满足字段验证
{
    "idc": {
        "name": "亚太机房"
    },
    "name": "A座4楼601 13排2机柜"
}

{
    "idc": {
        "address": [
            "这个字段为必要字段"
        ],
        "phone": [
            "This field is required."
        ],
        "email": [
            "This field is required."
        ],
        "letter": [
            "This field is required."
        ]
    }
}
6.3 对应上面的方法二序列化的反序列化
  • 6.3.1 序列化显式如下
[
    {
        "idc_name": "亚太机房",
        "name": "A座6楼601 13排2机柜"
    }
]
  • 6.3.2 接口文档要求字段如下,如果外键字段不允许为 null,则没有办法进行外键字段的反序列化
    image
6.4 对应上面的方法三序列化的反序列化

序列化显式如下,看起来和方法一的显示层级一样

[
    {
        "idc": {
            "id": 1,
            "name": "亚太机房"
        },
        "name": "A座6楼601 13排2机柜"
    }
]
  • 6.4.1 前端传入数据
{
    "idc": 1,
    "name": "A座4楼601 13排2机柜"
}
  • 6.4.2 返回如下
{
    "idc": {
        "id": 1,
        "name": "亚太机房"
    },
    "name": "A座4楼601 13排2机柜"
}

七序列化和反序列化总结

  • 7.1 models
# 父表
class Idc(models.Model):
    name    = models.CharField("机房名称",max_length=32)
    address = models.CharField("机房地址",max_length=256)
    phone   = models.CharField("联系人",max_length=15)
    email   = models.EmailField("邮件地址",default="null")
    letter  = models.CharField("IDC简称",max_length=5)

    def __str__(self):
        return self.name

    class Meta:
        db_table = 'resources_idc'
# 子表
class Cabinet(models.Model):
    idc = models.ForeignKey(Idc, verbose_name="所在机房",on_delete=models.CASCADE)
    name = models.CharField(max_length=255)

    def __str__(self):
        return self.name

    class Meta:
        db_table = "resources_cabinet"
        ordering = ["id"]
  • 7.2 子表的serializers
class CabinetSerializer(serializers.Serializer):
    idc = serializers.PrimaryKeyRelatedField(many=False, queryset=Idc.objects.all())
    name = serializers.CharField(required=True)


    def to_representation(self, instance):
        idc_obj = instance.idc
        ret = super(CabinetSerializer, self).to_representation(instance)
        ret["idc"] = {
            "id": idc_obj.id,
            "name": idc_obj.name
        }
        return ret
    def create(self, validated_data):
        return Cabinet.objects.create(**validated_data)

八 总结

  • 方法1 idc = IdcSerializer(many=False)指定父级序列化时,指定需要显示的字段,效果和方法3一样,反序列化时需要使用在 validate 进行外键字段的实例获取(捕获不存在时可修改为增加)
  • 方法2 def get_字段名(self,obj)显示的字段可以与当前序列化的字段在同一个级别,如果需要显示多个字段,代码量稍微会多一点,外键字段不允许为null,则无法进行反序列化
  • 方法3 PrimaryKeyRelatedField 不管是 1:n n:1 n:n 都可以进行控制,在 to_representation 进行外键字段的序列化显式,反序列化时前端只需要正常传入外键的id值即可,推荐使用该方法

所有的字段在序列化时可以更改显示名字children = serializers.IntegerField(source='parent_group',required=False)

posted @ 2020-05-17 22:15  Jenvid  阅读(3236)  评论(0编辑  收藏  举报