如何在实例中有自定义 .update() 以更新 DRF 可写嵌套序列化程序中的多对多关系时更新值

Posted

技术标签:

【中文标题】如何在实例中有自定义 .update() 以更新 DRF 可写嵌套序列化程序中的多对多关系时更新值【英文标题】:How to update values in instance when have a custom .update() to update many-to-many relations in DRF writable nested serializer 【发布时间】:2016-07-23 16:22:09 【问题描述】:

我有三个模型 Player、Team 和 Membership,其中 Player 和 Team 使用 Membership 作为中介模型具有多对多关系。

class Player(models.Model):
    name = models.CharField(max_length=254)
    rating = models.FloatField(null=True)
    install_ts = models.DateTimeField(auto_now_add=True, blank=True)
    update_ts = models.DateTimeField(auto_now_add=True, blank=True)


class Team(models.Model):
    name = models.CharField(max_length=254)
    rating = models.FloatField(null=True)
    players = models.ManyToManyField(
            Player,
            through='Membership',
            through_fields=('team', 'player'))
    is_active = models.BooleanField(default=True)
    install_ts = models.DateTimeField(auto_now_add=True, blank=True)
    update_ts = models.DateTimeField(auto_now_add=True, blank=True)


class Membership(models.Model):
    team = models.ForeignKey('Team')
    player = models.ForeignKey('Player')
    #date_of_joining = models.DateTimeField()
    install_ts = models.DateTimeField(auto_now_add=True, blank=True)
    update_ts = models.DateTimeField(auto_now_add=True, blank=True)

现在我需要使用 django rest 框架更新此成员资格。我尝试通过编写团队序列化程序的自定义.update() 来更新那些使用Writable nested serializers 的人。

@transaction.atomic
def update(self, instance, validated_data):
    '''
    Cutomize the update function for the serializer to update the
    related_field values.
    '''

    if 'memberships' in validated_data:
        instance = self._update_membership(instance, validated_data)

        # remove memberships key from validated_data to use update method of
        # base serializer class to update model fields
        validated_data.pop('memberships', None)

    return super(TeamSerializer, self).update(instance, validated_data)


def _update_membership(self, instance, validated_data):
    '''
    Update membership data for a team.
    '''
    memberships = self.initial_data.get('memberships')
    if isinstance(membership, list) and len(memberships) >= 1:
        # make a set of incoming membership
        incoming_player_ids = set()

        try:
            for member in memberships:
                incoming_player_ids.add(member['id'])
        except:
            raise serializers.ValidationError(
                'id is required field in memberships objects.'
            )

        Membership.objects.filter(
            team_id=instance.id
        ).delete()

        # add merchant member mappings
        Membership.objects.bulk_create(
            [
                Membership(
                    team_id=instance.id,
                    player_id=player
                )
                for player in incoming_player_ids
            ]
        )
        return instance
    else:
        raise serializers.ValidationError(
                'memberships is not a list of objects'
            )

现在这适用于更新数据库中的成员资格表值。我面临的唯一问题是我无法更新内存中的预取实例,该实例在对此 API 的 PATCH 请求更新数据库中的值但 API 响应显示过时的数据。

对同一资源的下一个 GET 请求会提供更新的数据。任何在 django 中处理过多对多关系并为可写嵌套序列化程序编写自定义更新/创建方法的人都可以帮助我理解解决此问题的可能方法。

【问题讨论】:

【参考方案1】:

在这种情况下,我能够找到issue。我在查询集上使用.prefetch_related(),这导致实例将预取数据用于多对多关系。我有两个解决方案,这可能会导致更多的数据库命中来获取更新的数据。

1。不使用.prefetch_related()

一个明显的方法是不使用.prefetch_related(),这是不推荐,因为它会导致大量的数据库命中。

2。在序列化器的更新方法中更新多对多关系后获取更新的模型实例

instance = self.context['view'].get_queryset().get(
    id=instance.id
)

修改了.update()TeamSerializer

@transaction.atomic
def update(self, instance, validated_data):
    '''
    Cutomize the update function for the serializer to update the
    related_field values.
    '''

    if 'memberships' in validated_data:
        instance = self._update_membership(instance, validated_data)

        # remove memberships key from validated_data to use update method of
        # base serializer class to update model fields
        validated_data.pop('memberships', None)

        # fetch updated model instance after updating many-to-many relations
        instance = self.context['view'].get_queryset().get(
            id=instance.id
        )
    return super(TeamSerializer, self).update(instance, validated_data)

如果有人有更好的方法,如果他们能添加这个问题的答案,我将不胜感激。

【讨论】:

从 Django 1.8 开始,refresh an object 有一个内置函数。你可以使用instance.refresh_from_db() 我尝试过使用 prefetch_related 不起作用的方法。 好吧,考虑到第一点已经到位,我的评论仅针对您的第二点。还有一件事:你为什么要从initial_data而不是validated_data更新membershipsupdate_membership函数中不用memberships = validated_data.get('memberships')吗? 我在给定的两个解决方案之间添加了 OR 以区分两者是独立的。当我们在查询集中使用prefetch_related() 时会遇到这个问题。我正在使用initial_data,因为我无法从validated_data 获取嵌套对象的id,这是我在json 中发送的。我已经从initial_data 明确地在membership 键上添加了自定义验证。如果您对从 validated_data 检索 id 有任何意见,我将不胜感激。 此外,不幸的是,refresh_from_db 不会重新加载多对多并反转多对一关系【参考方案2】:

我认为您必须使用instance.players.all()“重新加载”玩家字段,因为它已经在序列化程序的is_valid 方法中评估了查询集。

@transaction.atomic
def update(self, instance, validated_data):
    '''
    Cutomize the update function for the serializer to update the
    related_field values.
    '''

    if 'memberships' in validated_data:
        instance = self._update_membership(instance, validated_data)

        # remove memberships key from validated_data to use update method of
        # base serializer class to update model fields
        validated_data.pop('memberships', None)
        instance.players.all()

    return super(TeamSerializer, self).update(instance, validated_data)

【讨论】:

instance.players.all() 不起作用,因为我在查询集中执行 prefetch_related()。为了避免使用预取数据,我添加了一个像 instance.players.filter(is_active=True) 这样的过滤器,它在控制台中打印更新的成员资格,但实例仍然没有像以前那样在响应中显示更新的数据。 @ilse2005 也许这个新方法有帮助:docs.djangoproject.com/en/1.9/ref/models/instances/… 我在调试问题时确实尝试过这个,但例如没有工作。我已经解决了这个案例。我会尽快添加这个问题的答案。 这个问题是因为预取数据。 github.com/tomchristie/django-rest-framework/issues/… @ilse2005,你一定是在开玩笑!第一个实例的查询集将永远在序列化程序的is_valid 上的update 操作中进行评估,因为它打算验证您的request.data。其次,如果你想直接清除预取的相关缓存对象并更新你的QuerySet结果,你应该调用instance.players.get_queryset().all()

以上是关于如何在实例中有自定义 .update() 以更新 DRF 可写嵌套序列化程序中的多对多关系时更新值的主要内容,如果未能解决你的问题,请参考以下文章

如何在媒体基础中有自定义视频媒体/流接收器请求 RGB32 帧?

如果我在 python 管道中有自定义的集成模型,如何进行交叉验证和网格搜索

有自定义导航navarin

安装 Windows Update KB4340558 后,如何在经典 ASP 中正确实例化 32 位 COM 对象?

SupportMapFragment 顶部有自定义视图

Hibernate hbm2ddl.auto=update 不更新 MySQL 中的列定义