V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
endpain
V2EX  ›  Python

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

  •  
  •   endpain · 2021-04-05 15:14:55 +08:00 · 1894 次点击
    这是一个创建于 1330 天前的主题,其中的信息可能已经有所发展或是发生改变。

    大家好,我在使用 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": "[email protected]",
            "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))])
    

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

    3 条回复    2021-04-05 17:32:44 +08:00
    Zhuzhuchenyan
        1
    Zhuzhuchenyan  
       2021-04-05 16:49:52 +08:00   ❤️ 1
    根据官方文档对于 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
        2
    Zhuzhuchenyan  
       2021-04-05 16:52:07 +08:00
    不好意思,不知道为啥没有出现 readme 排版,难道是我使用姿势错了,
    代码缩进都不见了,凑合着看看吧,这个问题只要重新 create 方法一般都能解决
    endpain
        3
    endpain  
    OP
       2021-04-05 17:32:44 +08:00
    @Zhuzhuchenyan 非常感谢,我在仔细看看。跪谢!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1011 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 21:52 · PVG 05:52 · LAX 13:52 · JFK 16:52
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.