修复 Django Rest Framework 模型序列化程序相关主键查询集中的错误

Posted

技术标签:

【中文标题】修复 Django Rest Framework 模型序列化程序相关主键查询集中的错误【英文标题】:Fixing Error in Django Rest Framework Model Serializer Related Primary Key Queryset 【发布时间】:2020-05-20 21:55:35 【问题描述】:

我觉得我在这里追赶我的尾巴,所以我来找你们的好人来帮助我了解我在哪里搞砸了,为什么我对此的想法一定存在某种缺陷。

我正在 DRF 中编写一个 API,虽然它在 DB 中没有很多表,但 DB 中有很多对很多的关系,这让人感觉很复杂,或者至少很难做到查看 db 表并直观地了解相关内容。

首先是我的模型。尝试将新对象发布到 jobs 模型时,我得到了这个。我需要验证请求的工作人员是否有权使用相关的targetworkergroup 创建job

型号:

class Workers(models.Model):
    class Meta:
        ordering = ['id']
    workerid = models.CharField(max_length=16, verbose_name="Worker ID", unique=True, default="Empty")
    customer = models.ForeignKey(Customers, on_delete=models.DO_NOTHING, default=1)
    workername = models.CharField(max_length=64, verbose_name="Worker Friendly Name", default="")
    datecreated = models.DateTimeField(auto_now_add=True)
    awsarn = models.CharField(max_length=60, verbose_name="ARN Name of Worker", blank=True, null=True)
    customerrights = models.ManyToManyField(Customers, related_name="access_rights", default="")


class Targets(models.Model):
    class Meta:
        ordering = ['id']
    customer = models.ForeignKey(Customers, on_delete=models.SET_NULL, default=1, null=True)
    friendly_name = models.CharField(max_length=70, verbose_name="Target Friendly Name", unique=False)
    hostname = models.CharField(max_length=120, verbose_name="Target Hostname", default="")
    ipaddr = models.GenericIPAddressField(protocol='both', unpack_ipv4=True, default="", null=True)


class WorkerGroups(models.Model):
    class Meta:
        ordering = ['id']
    name = models.CharField(max_length=60, default="Default Group")
    workers = models.ManyToManyField(Workers)


class Jobs(models.Model):
    target = models.ForeignKey(Targets, on_delete=models.DO_NOTHING)
    datecreated = models.DateTimeField(auto_now_add=True)
    startdate = models.DateTimeField()
    enddate = models.DateTimeField(null=True)
    frequency = models.TimeField(default='00:05')
    workergroup = models.ForeignKey(WorkerGroups, on_delete=models.DO_NOTHING)
    jobdefinition = models.ForeignKey(JobDefinitions, on_delete=models.DO_NOTHING)

在我的序列化程序中,我有一个JobSerializer,它引用了我认为应该具有限制验证这些相关模型的查询集的 PrimaryKeyRelatedField 类。顺便说一句,customerrightsids 正在视图中构建,这似乎适用于所有其他模型。

序列化器:

    def get_queryset(self):
        print("In Custom TargetPK get_queryset")
        queryset = Targets.objects.filter(customer_id__in=self.context['auth'].customerrightsids)
        if isinstance(queryset, (QuerySet, Manager)):
            queryset = queryset.all()
        return queryset


class WorkerGroupPKSerializer(serializers.PrimaryKeyRelatedField):

    def get_queryset(self):
        print("In Custom WorkerGroupPK get_queryset")
        queryset = WorkerGroups.objects.filter(workers__customer_id__in=self.context['auth'].customerrightsids)
        if isinstance(queryset, (QuerySet, Manager)):
            queryset = queryset.all()
        return queryset

class JobSerializer(serializers.ModelSerializer):
    workergroup = WorkerGroupPKSerializer(many=False) # Commenting this out removes error
    target = TargetPKSerializer(many=False) # This seems to work fine even though it's similar to the line above

    class Meta:
        model = Jobs
        fields = '__all__'

    def create(self, validated_data):
        print(self.context['auth'])
        return super().create(validated_data)

viewset 对象的 create 方法没有什么特别之处。它接受请求并将一对传递给 ViewSet 并更新其上下文。如果需要,我可以分享,但这似乎不是问题所在。

最后,对于错误。当我执行 POST/jobs/ 时,我得到以下信息:

错误:

MultipleObjectsReturned at /jobs/
get() returned more than one WorkerGroups -- it returned 2!

错误清楚地表明我在 get() 中返回了多个 WorkerGroup,但我不知道在这种情况下在哪里或如何解决它。

这显然是 WorkerGroupPKSerializer 的问题。如果我从 JobSerializer 注释掉对它的引用,错误就会消失。但这会停止对该字段的验证,因此这不是一个可行的解决方案!

【问题讨论】:

对不起这个垃圾标题,我不知道还能叫什么! 【参考方案1】:

我不是 100% 确定我在正确的轨道上,但似乎您可能遇到重复/多个结果的问题。您应该尝试在WorkerGroupPKSerializer 中的Queryset 上使用.distinct()(另请参阅Django 文档)。您在Worker 模型的customer 属性上使用了ForeignKey,这样就可以有多个Worker 属于同一个WorkerGroup 匹配过滤器查询,从而返回相同的@987654331 @两次。因此,当 WorkerGroupidPOSTed 时,get() 将匹配两个结果,从而引发该错误。

注释掉似乎可以清除错误,这可能是因为您还注释掉了many=False,因此不再调用.get()。但正如问题中提到的,这将禁用用于过滤的自定义查询集。

【讨论】:

谢谢你,我的好人 Sasja!我想我在跟着你,但我必须在早上好好休息时整理一下。这看起来是我一整天以来最好的领先! 好的!那完全奏效了。我再次阅读您的评论。我从概念上理解唯一性如何消除重复。但我不确定我是否理解这里发生的具体重复。工人“拥有”购买特定客户,但许多工人可以是许多工人组的成员。我理解由于工作组造成的重复,但不一定是由于客户。为了后代,我将在下面的答案中发布我更新的序列化程序。 @JarredMasterson 根本原因来自像 mysql 这样的 RDBMS 的工作方式(在 SO 上找到 this 问题)基本上,您在 Workers 中过滤了客户的 ID(django orm 执行多个 sql JOIN s 是“到达”该表的基础),因此每次遇到客户的 id 时都会显示“filter = true”,因此返回 WorkerGroup。所以如果遇到两次客户id,每次都会返回一个WorkerGroup(可能都一样)。【参考方案2】:

感谢 Sasja 的回答。这是更新后的 WorkerGroupPKSerializer,现在可以使用了。

class WorkerGroupPKSerializer(serializers.PrimaryKeyRelatedField):

    def get_queryset(self):
        print("In Custom WorkerGroupPK get_queryset")
        queryset = WorkerGroups.objects.filter(workers__customer_id__in=self.context['auth'].customerrightsids).distinct()
        if isinstance(queryset, (QuerySet, Manager)):
            queryset = queryset.all()
        return queryset

【讨论】:

以上是关于修复 Django Rest Framework 模型序列化程序相关主键查询集中的错误的主要内容,如果未能解决你的问题,请参考以下文章

Django-rest-framework 和 django-rest-framework-jwt APIViews and validation Authorization headers

Django Rest Framework 和 django Rest Framework simplejwt 两因素身份验证

怎么安装django rest framework

django rest framework中文介绍

17-Django-Django REST framework-REST framework及RESTful简介

为啥 django-rest-framework 不显示 OneToOneField 数据 - django