修复 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
模型时,我得到了这个。我需要验证请求的工作人员是否有权使用相关的target
和workergroup
创建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 @两次。因此,当 WorkerGroup
的 id
是 POST
ed 时,get()
将匹配两个结果,从而引发该错误。
注释掉似乎可以清除错误,这可能是因为您还注释掉了many=False
,因此不再调用.get()
。但正如问题中提到的,这将禁用用于过滤的自定义查询集。
【讨论】:
谢谢你,我的好人 Sasja!我想我在跟着你,但我必须在早上好好休息时整理一下。这看起来是我一整天以来最好的领先! 好的!那完全奏效了。我再次阅读您的评论。我从概念上理解唯一性如何消除重复。但我不确定我是否理解这里发生的具体重复。工人“拥有”购买特定客户,但许多工人可以是许多工人组的成员。我理解由于工作组造成的重复,但不一定是由于客户。为了后代,我将在下面的答案中发布我更新的序列化程序。 @JarredMasterson 根本原因来自像 mysql 这样的 RDBMS 的工作方式(在 SO 上找到 this 问题)基本上,您在Worker
s 中过滤了客户的 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 两因素身份验证