我是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
但是我无法让它返回现场的技能。并为读写操作工作。
答案 0 :(得分:19)
有几点需要注意。
首先,您不要在示例中使用明确的直通表。因此,您可以跳过该部分。
其次,您正在尝试使用嵌套的序列化程序,这些序列化程序远比您尝试实现的要复杂得多。
您可以使用PrimaryKeyRelatedField:
简单地读/写相关的idclass 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)
我希望这会有所帮助,我花了一段时间才弄明白