具有复杂外键遍历的棘手 Django QuerySet

Posted

技术标签:

【中文标题】具有复杂外键遍历的棘手 Django QuerySet【英文标题】:Tricky Django QuerySet with Complicated ForeignKey Traversals 【发布时间】:2015-01-16 22:09:00 【问题描述】:

我正在改编 Jim McGaw 的电子商务网站,以供我的客户使用。我的客户销售由一组定制部件组成的计算机版本。进入系统的部分将发生变化。例如,两年后售出的超级武士系统的零件将与我们明天售出的零件不同。因此,当我们销售每个 Super Samurai 系统时,我们需要了解销售时进入 Super Samurai 的部件的快照。

我遇到了 QuerySet 的问题,它从将部件映射到构建的表中复制了所有部件(即进入超级武士的部件)......

class Build(models.Model):
    build = models.ForeignKey(PartModel, related_name='+')
    part = models.ForeignKey(PartModel, related_name='+')
    quantity = models.PositiveSmallIntegerField(default=1)

    class Meta:
        abstract = True
        unique_together = ('build', 'part')

    def __unicode__(self):
        return self.build.name + ' with ' + str(self.quantity) + ' * ' + \
               self.part.family.make.name + ' ' + self.part.name

class BuildPart(Build):
    pass

    class Meta:
        verbose_name = "Build Part"

我需要将 BuildPart 表中的构建部分复制到 OrderBuildPart 表中...

class OrderBuildPart(Build):
    orderItem = models.ForeignKey(OrderItem, unique=False)

    class Meta:
        verbose_name = "Ordered Build Part"

...这样将来我们就可以知道哪些部分进入了某某的构建中。

McGaw 的电子商务网站不允许商品与其他商品捆绑在一起。因此,与其为构建及其部件创建两个不同表(和两个系列 SKU)的噩梦般的场景,我希望构建与任何其他部分一样......

class PartModel(models.Model):
    family = models.ForeignKey(PartFamily)
    name = models.CharField("Model Name", max_length=50, unique=True)
    slug = models.SlugField(help_text="http://www.Knowele.com/<b>*slug*</b>",
                            unique=True)
    *** = models.CharField("***", help_text="Vendor's Part Number",
                               max_length=30, blank=True, null=True)
    url = models.URLField("URL", blank=True, null=True)
    costurl = models.URLField("Cost URL", blank=True, null=True)
    cost = models.DecimalField(help_text="How much knowele.com pays", max_digits=9, decimal_places=2, blank=True, null=True)
    price = models.DecimalField(help_text="How much a customer pays", max_digits=9, decimal_places=2, blank=True, null=True)
    isActive = models.BooleanField(default=True)
    isBestseller = models.BooleanField(default=False)
    isFeatured = models.BooleanField(default=False)
    isBuild = models.BooleanField(default=False)
    description = models.TextField(blank=True, null=True)
    updated = models.DateTimeField(auto_now=True)
    created = models.DateTimeField(auto_now_add=True)
    buildpart = models.ManyToManyField('self', through='BuildPart',
                                symmetrical=False, related_name='+')

    class Meta:
        ordering = ['name']
        verbose_name = "Product Model"

    def __unicode__(self):
        return self.name

    def get_absolute_url(self):
        from django.core.urlresolvers import reverse
        return reverse('productdetail', args=[self.slug])

buildpart 字段引用 ManyToMany BuildPart 表,该表允许构建具有多个部分,并且允许一个部分与多个构建相关联。

通过调整 McGaw 的代码,我几乎得到了我需要的东西,直到我最终完成 PayPal 付款并尝试记录在销售的确切时刻哪些部件进入了已售出的版本......

def payment(request):
    token = request.POST['token']
    payer = request.POST['payer']

    result = paypal.do_express_checkout_payment(request, token, payer)

    if result['ACK'][0] in ['Success', 'SuccessWithWarning']:
        cart = Cart.objects.get(cart_id=get_cart_id(request))
        finalOrder = Order()
        finalOrder.cart_id = get_cart_id(request)
        finalOrder.token = token
        finalOrder.corID = result['CORRELATIONID'][0]
        finalOrder.payerID = payer
        finalOrder.ipAddress = request.META['REMOTE_ADDR']
        finalOrder.first = cart.first
        finalOrder.last = cart.last
        finalOrder.address = cart.address
        finalOrder.email = cart.email
        finalOrder.transactionID = result['PAYMENTINFO_0_TRANSACTIONID'][0]
        finalOrder.status = 'f'
        finalOrder.save()

        for item in get_cart_items(request):
            oi = OrderItem()
            oi.cart_id = item.cart_id
            oi.quantity = item.quantity
            oi.product = item.product
            oi.price = item.price()
            oi.save()

            if item.product.isBuild:
                for part in get_build_parts(request, item):
                    bp = OrderBuildPart()
                    bp.build = part.build
                    bp.part = part.part
                    bp.quantity = part.quantity
                    bp.orderItem = oi
                    bp.save()

        empty_cart(request)

        return render(request, 'payment.html', locals())

在我们点击 get_build_parts 函数之前,一切似乎都很好......

def get_build_parts(request, part):
    return BuildPart.objects.filter(build__id=part__product__pk)

...Django 的验尸报告“NameError at /payment/ global name 'part__product__pk' is not defined”

我如何遍历这些复杂的关系,以便我的老板可以查看每个客户的构建中包含哪些部分?

【问题讨论】:

【参考方案1】:

查找的价值方面并不像您想象的那样工作。双下划线仅适用于左侧:实际上,这是一种绕过 Python 语法要求的技巧。在右侧,您传递了一个普通表达式,它可以使用标准点语法遵循对象关系:

return BuildPart.objects.filter(build__id=part.product.pk)

【讨论】:

【参考方案2】:

试试BuildPart.objects.filter(build=part__product),而不是你所拥有的。

此外,您的“事后分析”似乎来自实际使用的 web 应用程序。您应该通过单元测试失败而不是 HTTP 调用来了解此类问题。

【讨论】:

以上是关于具有复杂外键遍历的棘手 Django QuerySet的主要内容,如果未能解决你的问题,请参考以下文章

Django通过多对多ORM对象中的多个标签进行过滤

如何遍历 Django 字典中的外键?

Django过滤器遍历多个外键和通用外键

父模型具有多个外键时的Django外键反向访问[重复]

django 查询具有外键的模型,检查该外键的属性

如何在Django中查询具有特定数量的外键关系并且在这些外键值中具有特定值的对象?