Django Rest通过id更新了许多对

时间:2015-10-29 14:39:31

标签: python django django-rest-framework

我是python和django休息的新手。但我很困惑。在django rest框架中更新多对多关系的最佳方法是什么。 我读了文档 http://www.django-rest-framework.org/api-guide/relations/#manytomanyfields-with-a-through-model 默认情况下,将指定了贯穿模型的ManyToManyField的关系字段设置为只读。

如果使用直通模型明确指定指向ManyToManyField的关系字段,请务必将read_only设置为True。

所以如果我有代码

class Master(models.Model):
    # other fields  
    skills = models.ManyToManyField(Skill)

class MasterSerializer(serializers.ModelSerializer):
    skills = SkillSerializer(many=True, read_only=False)

这将返回作为对象列表的技能。我没有办法更新它们。据我所知,当涉及到M2M时,Django更喜欢使用对象和对象id。如果我使用yii或rails,我将使用"通过"楷模。我想获得skill_ids字段。我可以读写。我可以为写操作执行此操作

class MasterSerializer(serializers.ModelSerializer):
    skill_ids = serializers.ListField(write_only=True)

    def update(self, instance, validated_data):

    # ...
    validated_data['skill_ids'] = filter(None, validated_data['skill_ids'])
    for skill_id in validated_data['skill_ids']:
        skill = Skill.objects.get(pk=skill_id)
        instance.skills.add(skill)

    return instance

但是我无法让它返回现场的技能。并为读写操作工作。

4 个答案:

答案 0 :(得分:19)

有几点需要注意。

首先,您不要在示例中使用明确的直通表。因此,您可以跳过该部分。

其次,您正在尝试使用嵌套的序列化程序,这些序列化程序远比您尝试实现的要复杂得多。

您可以使用PrimaryKeyRelatedField:

简单地读/写相关的id
class MasterSerializer(serializers.ModelSerializer):
    skills_ids = serializers.PrimaryKeyRelatedField(many=True, read_only=False, queryset=Skill.objects.all(), source='skills')

哪个应该能够读/写:

{id: 123, first_name: "John", "skill_ids": [1, 2, 3]}

请注意JSON" skill_ids"的映射。模拟"技能"通过使用可选参数源

完成

答案 1 :(得分:1)

我将尝试在设计方面带来一些亮点:如果为ManyToManyRelation指定模型,则在Django中,模型上的关系字段变为只读。如果您需要更改关联,则可以直接在直通模型上删除或注册新记录。

这意味着您可能需要为直通模型使用完全不同的序列化程序,或者编写自定义更新/创建方法。

通过模型有一些自定义设置,你确定你对ManyToManyFields的默认实现不够好吗?

答案 2 :(得分:1)

tl; dr:

对于M2M来说,一种更简单,单线的解决方案,我使用了以下形式的解决方案:

serializer = ServiceSerializer(instance=inst, data={'name':'updated', 'countries': [1,3]}, partial=True)
if serializer.is_valid():
    serializer.save()

对于更完整的示例,我包括以下内容:

models.py

from django.db import models

class Country(models.Model):
    name = models.CharField(max_length=50, null=False, blank=False)

class Service(models.Model):
    name = models.CharField(max_length=20, null=True)
    countries = models.ManyToManyField('Country')

serializers.py

from rest_framework import serializers
from .models import *

class CountrySerializer(serializers.ModelSerializer):
    class Meta:
        model = Country
        fields = ('name',)

class ServiceSerializer(serializers.ModelSerializer):
    class Meta:
        model = Service
        fields = ('name', 'countries',)

确保已创建一些虚拟服务和国家/地区实例以进行测试。然后,您可以使用如下功能更新实例:

更新示例

# get an object instance by key:
inst = ServiceOffering.objects.get(pk=1)

# Pass the object instance to the serializer and a dictionary
# Stating the fields and values to update. The key here is
# Passing an instance object and the 'partial' argument:
serializer = ServiceSerializer(instance=inst, data={'name':'updated', 'countries': [1,3]}, partial=True)


# validate the serializer and save
if serializer.is_valid():
    serializer.save()   
    return 'Saved successfully!'
else:
    print("serializer not valid")
    print(serializer.errors)
    print(serializer.data)
    return "Save failed"

如果您检查相关表,更新将一直进行到M2M桥接表。

为扩展此示例,我们可以以非常相似的方式创建对象实例:

### Create a new instance example:
# get the potential drop down options:
countries = ['Germany', 'France']

# get the primary keys of the objects:
countries = list(Country.objects.filter(name__in=countries).values_list('pk', flat=True))

# put in to a dictionary and serialize:
data = {'countries': countries, 'name': 'hello-world'}
serializer = ServiceOfferingSerializer(data=data)

答案 3 :(得分:0)

我已经处理了这个问题很长一段时间了,我发现解决更新任意多个字段的一般问题的最佳方法是解决它。

在我的例子中,有一个名为Listing的模型,用户可以将一个Subscription(另一个模型)作为Listing模型的一个实例。订阅使用通用外键,列表通过Many2Many导入用户的订阅。

我不是通过API向列表模型发出PUT请求,而是在订阅API视图的POST方法中将订阅实例添加到正确的模型中。这是我调整后的代码:

    #Model
    class Listing(models.Model):

      #Basics
      user = models.ForeignKey(settings.AUTH_USER_MODEL)
      slug = models.SlugField(unique=True, blank=True)
      timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)

      #Listing
      title = models.CharField(max_length=200)
      price = models.CharField(max_length=50, null=True, blank=True)
      subscriptions = models.ManyToManyField(Subscription, blank=True)

    class Subscription(models.Model):
      user = models.ForeignKey(settings.AUTH_USER_MODEL)
      content_type = models.ForeignKey(ContentType)
      object_id = models.PositiveIntegerField()
      content_object = GenericForeignKey('content_type', 'object_id')
      timestamp = models.DateTimeField(auto_now_add=True)

    #Views
    class APISubscriptionCreateView(APIView): #Retrieve Detail

      def post(self, request, format=None):
        serializer = SubscriptionCreateSerializer(data=request.data)
        if serializer.is_valid():
            sub = serializer.save(user=self.request.user)
            object_id = request.data['object_id']
            lis = Listing.objects.get(pk=object_id)
            lis.subscriptions.add(sub)

            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors,    status=status.HTTP_400_BAD_REQUEST)

我希望这会有所帮助,我花了一段时间才弄明白