给定嵌套对象时仅更新ForeignKey引用

时间:2016-05-17 20:05:12

标签: angularjs django nested django-rest-framework serializer

我被认为应该是一个简单的模式一段时间。所以我将在这里分享我如何为遇到同样事情的其他人解决它。也许有些人会建议更好的解决方案。

为API使用Django Rest Framework,为前端使用Angular 1.5。

挑战:

包括有关个人家庭的详细(嵌套)信息以及个人记录,以避免额外的网络往返,并且能够更改此人所属的家庭。

默认情况下,DRF很乐意提供相关模型的嵌套信息。但是,如果你改变其中任何一个,它就不会很高兴收回它。

Angular很乐意显示资源的嵌套信息,但其默认操作是在更新时以完全相同的嵌套格式发回所有资源数据。

这里的DRF设置并不起作用:

# Models
class Household(models.Model):
    full_address = models.CharField(max_length=200, blank=True, default='')

class Person(models.Model):
    household=models.ForeignKey(
        'household.Household',
        null=True,
        related_name="persons",)
    full_name=models.CharField(max_length=100)


# Views
class HouseholdViewSet(ModelViewSet):
    model = Household
    queryset = Household.objects.all()
    serializer_class = HouseholdSerializer

class PersonViewSet(ModelViewSet):
    model = Person
    queryset = Person.objects.all()
    serializer_class = PersonSerializer


# Serializers
class HouseholdSerializer(serializers.ModelSerializer):
    class Meta:
        model = Household


class PersonSerializer(serializers.ModelSerializer):
    household = HouseholdSerializer(many=False, read_only=True)
    class Meta:
        model = Person
        fields = ('id', 'household', 'full_name', )

当角度获取人员资源时,家庭ID和完整地址被嵌套,以便"家庭"现在是一个对象而不是主键。

{
    "id": 42,
    "full_name": "Lisa Adams",
    "household": {
        "id": 17,
        "full_address": "123 Main st, Springfield, ST, 55555"
    }
}

当Lisa离开家并与其他家庭中的新室友一起搬入时,用户将家庭更改为不同的现有地址,而角度发送PUT请求。服务器200响应指示成功,但新家庭不持久保存到数据库。因此,如果用户再次查看人员记录,它仍然有旧家庭。

这是在人员序列化程序中将home字段设置为read_only的结果。 DRF以静默方式忽略嵌套对象。

但如果将其更改为read_only = False,DRF会坚持必须包含明确的.update()。

AssertionError: The `.update()` method does not support writable
nestedfields by default. Write an explicit `.update()` method for serializer

我在写一个自定义更新功能已经工作了一段时间,但无法让它工作。我不需要更新嵌套信息中的任何内容。我只需要改变对不同家庭记录的引用。所有示例似乎都比需要的更复杂,我从来没有使用自定义update()来处理这个简单的用例。

当然,更新对外国记录的引用是很常见的,即使存在该记录的详细信息。但我没有找到任何正式记录的例子,我只是在试图修改SO和博客上的例子时坚持错过了一个或几个细节。

2 个答案:

答案 0 :(得分:0)

解决方案:

由于DRF很乐意改变对家庭"如果它没有任何嵌套的细节,我通过离开"家庭"来解决更新它的问题。字段完整只是一个主键。然后我添加了一个新字段" household_details"使用PersonSerializer在新名称空间中提供详细信息。

class PersonSerializer(serializers.ModelSerializer):
    household_details = HouseholdSerializer(many=False, read_only=True, source='household')
    class Meta:
        model = Person
        fields = ('id', 'household', 'household_details', 'full_name',)

这会在同一个"家庭"创建一个新的字段点。模型,但由于它是read_only,DRF会在以角度提交回来时忽略它。

然后我只需要将我的角度控制器稍微复杂一点,以便从" household_details"现场并写信给"家庭"来自" household_details"的id字段的字段更新时。

<household-small-card household="person.household_detail"></household-small-card>
function update(new_household_detail) {
  vm.person.household = new_household_detail.id;
  vm.person.$update(function () {
    toastr.success('Updated ' + vm.person.full_name + ' Successfully');
  }, function (errorResponse) {
    toastr.error('Failed:  ' + errorResponse.data.message);
  });
}

注意:此代码全部简化为我的工作代码。我没有确认它是否有效。但这里的概念应该更加清晰。以这种格式编写它有助于我在脑海中巩固解决方案。我希望它可以帮助其他人在这里。

答案 1 :(得分:0)

反向替代到@shanemgrey:

不是将_detail附加到主对象(这是令人讨厌的IMO),而是可以翻转字段。使用household_id作为您的ID字段,并将household保留为您的对象字段:

class PersonSerializer(serializers.ModelSerializer):
    household = HouseholdSerializer(read_only=True)
    household_id = serializers.IntegerField()

    class Meta:
        model = Person
        fields = ('id', 'household', 'household_id', 'full_name',)

DRF会自动将您的JSON中的household_id链接到该对象,因此您无需执行任何其他操作。