【问题标题】:Django-Rest-Framework. Updating nested objectDjango 休息框架。更新嵌套对象
【发布时间】:2016-09-11 10:59:35
【问题描述】:

我在更新嵌套对象时遇到问题。

所以我有一个结构类似于这个的模型:

class Invoice(models.Model):
    nr = models.CharField(max_length=100)
    title = models.CharField(max_length=100)

class InvoiceItem(models.Model):
    name = models.CharField(max_length=100)
    price = models.FloatField()
    invoice = models.ForeignKey(Invoice, related_name='items')

我需要从父对象创建子对象,我的意思是在创建Invoice 对象时直接创建InvoiceItems。 为此,我编写了以下序列化程序:

class InvoiceItemSerializer(serializers.ModelSerializer):
    invoice = serializers.PrimaryKeyRelatedField(queryset=Invoice.objects.all(), required=False)
    class Meta:
        model = InvoiceItem


class InvoiceSerializer(serializers.ModelSerializer):
    items = InvoiceItemSerializer(many=True)

    class Meta:
        model = Invoice

    def create(self, validated_data):
        items = validated_data.pop('items', None)
        invoice = Invoice(**validated_data)
        invoice.save()
        for item in items:
            InvoiceItem.objects.create(invoice=invoice, **item)
        return invoice

到目前为止,除了update 之外,创建/读取/删除方法都可以正常工作。 我认为下面的逻辑应该是正确的,但它遗漏了一些东西。

def update(self, instance, validated_data):
    instance.nr = validated_data.get('nr', instance.nr)
    instance.title = validated_data.get('title', instance.title)
    instance.save()

    # up till here everything is updating, however the problem appears here.
    # I don't know how to get the right InvoiceItem object, because in the validated
    # data I get the items queryset, but without an id.

    items = validated_data.get('items')
    for item in items:
        inv_item = InvoiceItem.objects.get(id=?????, invoice=instance)
        inv_item.name = item.get('name', inv_item.name)
        inv_item.price = item.get('price', inv_item.price)
        inv_item.save()

    return instance

任何帮助将不胜感激。

【问题讨论】:

  • 您可能必须将 pk 作为有效负载的一部分传递。
  • 我路过,但validated_data中不存在
  • 你能展示你的payload吗?
  • 这是我提出请求的方式pastie.org/private/3mnrcxp64ra4j65kvcmoyw

标签: python django rest django-rest-framework


【解决方案1】:

drf-writable-nested 包提供可写的嵌套模型序列化程序,允许使用嵌套的相关数据创建/更新模型。

https://github.com/beda-software/drf-writable-nested

【讨论】:

    【解决方案2】:

    所有这些解决方案对我来说似乎都太复杂或太具体了,我最终使用了来自 the tutorial here 的代码,它非常简单且可重复使用

    from rest_framework import serializers
    from django.contrib.auth import get_user_model
    from myapp.models import UserProfile
    
    
    # You should already have this somewhere
    class UserProfileSerializer(serializers.ModelSerializer):
        class Meta:
            model = UserProfile
            fields = ['nested', 'fields', 'you', 'can', 'edit']
    
    
    class UserSerializer(serializers.ModelSerializer):
        # CHANGE "userprofile" here to match your one-to-one field name
        userprofile = UserProfileSerializer()
    
        def update(self, instance, validated_data):
            # CHANGE "userprofile" here to match your one-to-one field name
            if 'userprofile' in validated_data:
                nested_serializer = self.fields['userprofile']
                nested_instance = instance.userprofile
                nested_data = validated_data.pop('userprofile')
    
                # Runs the update on whatever serializer the nested data belongs to
                nested_serializer.update(nested_instance, nested_data)
    
            # Runs the original parent update(), since the nested fields were
            # "popped" out of the data
            return super(UserSerializer, self).update(instance, validated_data)
    

    编辑:错误修正,我在尝试更新嵌套字段之前添加了一个检查嵌套字段的存在。

    【讨论】:

    • 投反对票的人:请为您投反对票提供反馈或理由,以便我们继续改进 StackOverflow 上的内容。谢谢
    • 很好的解决方案,但是如果用户配置文件被删除并且您正在检查它是否不在经过验证的数据中,那么什么都不做?
    • 如果用户配置文件不存在,此代码应该仍然有效。这只是我的意见,但我不确定允许用户删除他们链接的用户个人资料是否是个好主意。
    【解决方案3】:

    我认为 Vitor Hugo Morales 的回答非常棒,并且希望通过循环键将对象中的每个字段分配给经过验证的数据中的字段,而不是像他所做的那样对其进行硬编码,从而贡献我的一分钱。例如,

    def update_product_items(self, instance, validated_data):
        # get the nested objects list
        product_items = validated_data.pop('products')
        # get all nested objects related with this instance and make a dict(id, object)
        product_items_dict = dict((i.id, i) for i in instance.products.all())
    
        for item_data in product_items:
            if 'id' in item_data:
                # if exists id remove from the dict and update
                product_item = product_items_dict.pop(item_data['id'])
                # remove id from validated data as we don't require it.
                item_data.pop('id')
                # loop through the rest of keys in validated data to assign it to its respective field
                for key in item_data.keys():
                    setattr(product_item,key,item_data[key])
    
                product_item.save()
            else:
                # else create a new object
                ProductItem.objects.create(product=instance, **item_data)
    
        # delete remaining elements because they're not present in my update call
        if len(product_items_dict) > 0:
            for item in product_items_dict.values():
                item.delete()
    

    【讨论】:

      【解决方案4】:

      试试这个。

      from rest_framework.utils import model_meta
      
      class InvoiceSerializer(serializers.ModelSerializer):
          invoice_item=InvoiceItemSerializer(many=True,required=False)
      
          field_map={"invoice_item" : { "model":  models.InvoiceItem
                                         "pk_field" : "id"}}    
      
      
      
          class Meta:
              model = models.Invoice
              fields = '__all__'
      
          def create(self, validated_data):
              extra_data={}
              for key in self.field_map.keys():
                  extra_data[key]=validated_data.pop(key,[])
      
              # create invoice
              invoice = models.Invoice.objects.create(**validated_data)
      
              for key in extra_data.keys():
                  for data in extra_data[key]:
                      self.field_map[key]["model"].objects.create(invoice=invoice,**data)
      
              return invoice
      
          def _update(self,instance,validated_data):
              #drf default implementation
              info = model_meta.get_field_info(instance)
      
              for attr, value in validated_data.items():
                  if attr in info.relations and info.relations[attr].to_many:
                      field = getattr(instance, attr)
                      field.set(value)
                  else:
                      setattr(instance, attr, value)
              instance.save()
              return instance
      
          def update(self,instance,validated_data):
      
              extra_data={}
              for key in self.field_map.keys():
                  extra_data[key]=validated_data.pop(key,[])
      
              instance=self._update(instance,validated_data)
      
              for key in extra_data.keys():
                  for data in extra_data[key]:
      
                      id=data.get(self.field_map[key]["pk_field"],None)
                      if id:
                          try:
                              related_instance=self.field_map[key]["model"].objects.get(id=id)
                          except:
                              raise
                          self._update(related_instance,data)
                      else:
                          self.field_map[key]["model"].objects.create(**data)
      
              return instance    
      

      【讨论】:

        【解决方案5】:

        就我而言,我希望更新所有嵌套对象列表,即使它们已被删除。

        我不想在每个嵌套对象中删除,调用嵌套模型的DELETE方法;只需更新整个对象和您的嵌套对象列表。

        对于这个实现:1-Product 有 N-ProductItems

        def update_product_items(self, instance, validated_data):
            # get the nested objects list
            product_items = validated_data.pop('products')
            # get all nested objects related with this instance and make a dict(id, object)
            product_items_dict = dict((i.id, i) for i in instance.products.all())
        
            for item_data in product_items:
                if 'id' in item_data:
                    # if exists id remove from the dict and update
                    product_item = product_items_dict.pop(item_data['id'])
        
                    product_item.quantity = item_data['quantity']
                    product_item.size_pmg = item_data['size_pmg']
                    product_item.size_number = item_data['size_number']
                    product_item.color = item_data['color']
                    product_item.save()
                else:
                    # else create a new object
                    ProductItem.objects.create(product=instance, **item_data)
        
            # delete remaining elements because they're not present in my update call
            if len(product_items_dict) > 0:
                for item in product_items_dict.values():
                    item.delete()
        

        【讨论】:

          【解决方案6】:

          这是我完成任务的方式:

          我在InvoiceItemSerializer 中添加了一个id 字段

          class InvoiceItemSerializer(serializers.ModelSerializer):
              ...
              id = serializers.IntegerField(required=False)
              ...
          

          以及InvoiceSerializer的更新方法

          def update(self, instance, validated_data):
              instance.nr = validated_data.get('nr', instance.nr)
              instance.title = validated_data.get('title', instance.title)
              instance.save()
          
              items = validated_data.get('items')
          
              for item in items:
                  item_id = item.get('id', None)
                  if item_id:
                      inv_item = InvoiceItem.objects.get(id=item_id, invoice=instance)
                      inv_item.name = item.get('name', inv_item.name)
                      inv_item.price = item.get('price', inv_item.price)
                      inv_item.save()
                  else:
                      InvoiceItem.objects.create(account=instance, **item)
          
              return instance
          

          同样在 create 方法中,如果 id 通过,我将弹出它。

          【讨论】:

          • 感谢您的样品。但请用户不要忘记捕捉inv_item = InvoiceItem.objects.get(id=item_id, invoice=instance) 可能引发的DoesNotExist 异常。
          • 我没有在更新函数的已验证数据对象中获取 id 字段。我做错了什么?
          • 值得注意的是,如果您正在创建一个真正的“id”字段,您可能需要遵循 django 的约定:id = models.BigAutoField(primary_key=True)(参见docs.djangoproject.com/en/3.2/topics/db/models/…)另外,而不是检查 InvoiceItem 是否存在并拆分您的代码,您可以只使用助手 get_or_create:InvoiceItem.objects.get_or_create()docs.djangoproject.com/en/3.2/ref/models/querysets/…
          【解决方案7】:

          试试

          def update(self, instance, validated_data):
              instance.nr = validated_data.get('nr', instance.nr)
              instance.title = validated_data.get('title', instance.title)
              instance.save()
          
          
              items = validated_data.get('items')
              for item in items:
                  inv_item = InvoiceItem.objects.get(invoice=instance, pk=item.pk)
                  inv_item.name = item.get('name', inv_item.name)
                  inv_item.price = item.get('price', inv_item.price)
                  inv_item.invoice = instance
                  inv_item.save()
          
              instance.save()
              return instance
          

          【讨论】:

            【解决方案8】:

            我最近遇到了同样的问题。我解决它的方法是强制 id 成为必填字段:

            class MySerializer(serializers.ModelSerializer):
            
                class Meta:
                    model = MyModel
                    fields = ('id', 'name', 'url', )
                    extra_kwargs = {'id': {'read_only': False, 'required': True}}
            

            这样我就能够检索到正确的实例并对其进行更新

            【讨论】:

            • 如果create 方法在id 是多余的情况下会发生什么?
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-05-21
            • 1970-01-01
            • 2019-08-06
            • 2015-03-31
            • 1970-01-01
            • 1970-01-01
            • 2020-11-07
            相关资源
            最近更新 更多