关于 Django drf 的反序列化一对多字段问题

2021-04-05 15:14:55 +08:00
 endpain

大家好,我在使用 django drf 的时候遇到了一个问题,找了一堆资料,暂时还没找到好的解决方法,希望有懂的兄弟可以给指点一下。非常感谢。

models.py


class ProjectStatusModels(models.Model):
    name = models.CharField(max_length=150,verbose_name='状态名称',unique=True,db_index=True)

    def __str__(self):
        return self.name
    class Meta:
        verbose_name = '项目状态管理表'
        ordering = ('-id',)


class ProjectModels(models.Model):
    project_id = models.CharField(verbose_name='项目编号',max_length=120,unique=True,db_index=True)
    name = models.CharField(verbose_name='项目名称',max_length=150)
    customer_name = models.CharField(verbose_name='客户名称',max_length=150,null=True,blank=True)
    evaluation = models.IntegerField(verbose_name='项目估价',null=True,blank=True)

    start_date = models.DateField(verbose_name='项目开始日期',null=True,blank=True)
    expiration_date = models.DateField(verbose_name='项目截止日期',null=True,blank=True)

    sales_name = models.ForeignKey(EmployeeModels,on_delete=models.DO_NOTHING,verbose_name='销售经理',null=True,blank=True)
    status = models.ForeignKey(ProjectStatusModels,on_delete=models.DO_NOTHING,verbose_name='状态',null=True,blank=True,db_index=True)

    create_time = models.DateTimeField(verbose_name='发布时间', auto_now_add=True)
    update_time = models.DateTimeField(verbose_name='修改时间', auto_now=True)

    def __str__(self):
        return self.name
    class Meta:
        verbose_name = '项目管理表'
        ordering = ('-id',)

view.py

class ProjectJsonView(APIView):
    def get(self,request,*args,**kwargs):
        project_id = request.query_params.get('project_id')
        if not project_id:
            project_obj = project_models.ProjectModels.objects.all()
            ser = admin_project_serializers.ProjectModelsSerializers(instance=project_obj,many=True)
        else:
            project_obj = project_models.ProjectModels.objects.filter(id=project_id).first()
            ser = admin_project_serializers.ProjectModelsSerializers(instance=project_obj,many=False)

        return Response({
            'status_code':20000,
            'code':0,
            'data':ser.data
        })

    def post(self,request,*args,**kwargs):
        # 新增项目
        req = {
            'status_code':20000,
            'msg':'新增成功'
        }
        ser = admin_project_serializers.ProjectModelsSerializers(data = request.data)
        if ser.is_valid():
            ser.save()
        else:
            req['status_code'] = 50000
            req['msg'] = str(ser.errors)

        return Response(req)

    def put(self,request,*args,**kwargs):
        # 编辑现有项目
        req = {
            'status_code':20000,
            'msg':'修改成功'
        }

        sid = request.data.get('id')
        project_obj = project_models.ProjectModels.objects.filter(id=sid).first()

        ser = admin_project_serializers.ProjectModelsSerializers(data=request.data,instance=project_obj)

        if ser.is_valid():
            print('ser.validated_data:',ser.validated_data)
            ser.save()
        else:
            req['status_code'] = 50000
            req['msg'] = str(ser.errors)

        return Response(req)

    def delete(self,request,*args,**kwargs):
        project_id = request.data.get('project_id')
        project_obj = project_models.ProjectModels.objects.filter(id=project_id).first()
        project_obj.delete()
        return Response({
            'status_code':20000,
            'msg':'删除成功'
        })


serializer.py


class ProjectStatusModelsSerializers(serializers.ModelSerializer):
    # 项目状态序列化器
    class Meta:
        model = project_models.ProjectStatusModels
        fields = '__all__'


class ProjectModelsSerializers(serializers.ModelSerializer):
    # 项目序列化器
    class Meta:
        model = project_models.ProjectModels
        fields = '__all__'
        depth = 1

在项目 查询的时候可以返回如下的字段

{
  "status_code": 20000,
  "code": 0,
  "data": [
    {
      "id": 5,
      "status": null,
      "project_id": "asdasdasd",
      "name": "123123123",
      "customer_name": "Abcd",
      "evaluation": null,
      "start_date": null,
      "expiration_date": null,
      "create_time": "2021-04-04T23:17:58.433641",
      "update_time": "2021-04-04T23:17:58.433666",
      "sales_name": null
    },
    {
      "id": 4,
      "status": null,
      "project_id": "sss03",
      "name": "qweqqweasd",
      "customer_name": "",
      "evaluation": 10000,
      "start_date": null,
      "expiration_date": null,
      "create_time": "2021-04-04T23:17:03.571699",
      "update_time": "2021-04-04T23:17:03.571714",
      "sales_name": null
    },
    {
      "id": 3,
      "status": null,
      "project_id": "sss01",
      "name": "asasas",
      "customer_name": "",
      "evaluation": null,
      "start_date": null,
      "expiration_date": null,
      "create_time": "2021-04-04T23:15:31.134011",
      "update_time": "2021-04-04T23:15:31.134029",
      "sales_name": null
    },
    {
      "id": 2,
      "status": {
        "id": 2,
        "name": "End"
      },
      "project_id": "0002",
      "name": "s0002",
      "customer_name": "微创医疗 111",
      "evaluation": 500,
      "start_date": "2021-04-04",
      "expiration_date": "2021-05-11",
      "create_time": "2021-04-04T22:43:18.374238",
      "update_time": "2021-04-05T11:39:18.852015",
      "sales_name": {
        "id": 5,
        "code": "0003",
        "name": "endpein",
        "phone": "",
        "birthday": "2021-04-02",
        "age": 18,
        "on_the_job": true,
        "identity_card": "",
        "work_id": "",
        "work_id_date": null,
        "work_id_expiry_date": null,
        "dormitory_address": "",
        "passport_id": "",
        "passport_expiry_date": null,
        "multiskill_expiry": null,
        "csoc_expiry": null,
        "multiskill_category": "[]",
        "bank_details": "",
        "email": "280435089@qq.com",
        "remarks": "哈哈哈",
        "nationality": null
      }
    },
    {
      "id": 1,
      "status": {
        "id": 6,
        "name": "asdasd"
      },
      "project_id": "0001",
      "name": "s01 项目",
      "customer_name": "微创医疗",
      "evaluation": 1000,
      "start_date": "2021-04-29",
      "expiration_date": "2021-04-30",
      "create_time": "2021-04-04T22:42:51.462897",
      "update_time": "2021-04-05T14:34:03.122631",
      "sales_name": null
    }
  ]
}

但是前端传来的数据如下 ,这个数据是 request.data 输出的

<QueryDict: {'csrfmiddlewaretoken': ['ntdydSUNRztDKOKmDSST36oFnSokUFVJG0t9YLXXvQtm6hyYOPT3WHMRPahy6kz8'], 'id': ['1'], 'name': ['s01 项目'], 'project_id': ['0001'], 'customer_name': ['微创医疗'], 'evaluation': ['1000'], 'start_date': ['2021-04-29'], 'expiration_date': ['2021-04-30'], 'status': ['2'], 'sales_name': ['']}>

但是当 ser.is_valid() 在输入 validated_data 的时候 表中 statussales_name这 2 个一对多的 ForeignKey 字段就没有了。输出变成了如下样式:

ser.validated_data: OrderedDict([('project_id', '0001'), ('name', 's01 项目'), ('customer_name', '微创医疗'), ('evaluation', 1000), ('start_date', datetime.date(2021, 4, 29)), ('expiration_date', datetime.date(2021, 4, 30))])

请问一下,各位高手应该如何修改和调整呢?

1750 次点击
所在节点    Python
3 条回复
Zhuzhuchenyan
2021-04-05 16:49:52 +08:00
根据官方文档对于 nested serialization 的描述,详见 https://www.django-rest-framework.org/api-guide/serializers/#writing-create-methods-for-nested-representations

“If you're supporting writable nested representations you'll need to write .create() or .update() methods that handle saving multiple objects.”

所以光有 depth=1 是不够的,需要重写对应的`create`方法

正好有空,给你个最低限度能用的代码

```python

# models.py
class Author(models.Model):
name = models.CharField(max_length=128)

def __str__(self):
return self.name


class Book(models.Model):
name = models.CharField(max_length=128)
author = models.ForeignKey(to=Author, related_name='books', on_delete=models.CASCADE)

def __str__(self):
return self.name

# serializers.py

class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = '__all__'


class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer()

class Meta:
model = Book
fields = '__all__'

def create(self, validated_data):
author_data = validated_data.pop('author')
author = Author.objects.get(name=author_data['name'])
return Book.objects.create(author=author, **validated_data)

# viewset.py

class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
pagination_class = None
http_method_names = ['get', 'post']

```

```
# 示例代码 curl
curl --location --request POST '{root_url}/books/' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Book 2",
"author": {
"name": "YYH"
}
}'
```

需要注意的是即使 author 里面传递了 ID,这个 ID 也不会出现在 validated_data 中,这个算是 drf 的一个局限性。

环境:
djangorestframework==3.12.2
Django==3.1.3
Zhuzhuchenyan
2021-04-05 16:52:07 +08:00
不好意思,不知道为啥没有出现 readme 排版,难道是我使用姿势错了,
代码缩进都不见了,凑合着看看吧,这个问题只要重新 create 方法一般都能解决
endpain
2021-04-05 17:32:44 +08:00
@Zhuzhuchenyan 非常感谢,我在仔细看看。跪谢!

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/768107

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX