Django过滤ForeignKey并通过manytomany子查询的结果

时间:2014-02-10 10:38:51

标签: python django

我看过使用额外和/或注释进行查询但是无法获得我想要的结果。

我想获得一个产品列表,其中包含有效许可证以及可用许可证总数。活动许可证定义为在日期不过时,许可证数量减去分配的许可证数量(由manytomany字段上的计数定义)。

我定义的模型是:

class Vendor(models.Model):
    name = models.CharField(max_length=200)
    url = models.URLField(blank=True)


class Product(models.Model):

    name = models.CharField(max_length=200)
    vendor = models.ForeignKey(Vendor)
    product_url = models.URLField(blank=True)
    is_obsolete = models.BooleanField(default=False, help_text="Is this product obsolete?")


class License(models.Model):

    product = models.ForeignKey(Product)
    num_licenses = models.IntegerField(default=1, help_text="The number of assignable licenses.")
    licensee_name = models.CharField(max_length=200, blank=True)
    license_key = models.TextField(blank=True)
    license_startdate = models.DateField(default=date.today())
    license_enddate = models.DateField(null=True, blank=True)
    is_obsolete = models.BooleanField(default=False, help_text="Is this licenses obsolete?")
    licensees = models.ManyToManyField(User, blank=True)

我尝试过许可模型过滤。哪个有效,但我不知道如何整理/ GROUP BY /将返回的数据聚合到返回的单个查询集中。

当尝试通过procuct过滤时,我可以完全弄清楚我需要做的查询。我可以得到点点滴滴,并尝试使用.extra() select=查询来返回可用许可证的数量(这是我此时真正需要的),其中将有多个许可证与一个产品。

因此,我追求的最终答案是,如何检索Django中可用许可证数量的可用产品列表。我宁愿不尽可能地使用raw。

获取我想要的所有许可证详细信息的示例查询集,我无法获得该产品:

License.objects.annotate(
    used_licenses=Count('licensees')
).extra(
    select={
        'avail_licenses': 'licenses_license.num_licenses - (SELECT count(*) FROM licenses_license_licensees WHERE licenses_license_licensees.license_id = licenses_license.id)'
    }
).filter(
    is_obsolete=False,
    num_licenses__gt=F('used_licenses')
).exclude(
    license_enddate__lte=date.today()
)

提前谢谢你。

编辑(2014-02-11): 我想我可能是以一种丑陋的方式解决了它。如果可以的话,我不想做太多的DB调用,所以我使用License查询获取所有信息,然后在Python中过滤它并从管理器类中返回所有信息。也许过度使用Dict和列表。无论如何,它可以工作,我可以在以后使用其他信息扩展它,而不会产生大量风险或自定义SQL。它还使用了我在模型类中定义的一些模型参数。

class LicenseManager(models.Manager):

def get_available_products(self):
    licenses = self.get_queryset().annotate(
        used_licenses=Count('licensees')
    ).extra(
        select={
            'avail_licenses': 'licenses_license.num_licenses - (SELECT count(*) FROM licenses_license_licensees WHERE licenses_license_licensees.license_id = licenses_license.id)'
        }
    ).filter(
        is_obsolete=False,
        num_licenses__gt=F('used_licenses')
    ).exclude(
        license_enddate__lte=date.today()
    ).prefetch_related('product')

    products = {}
    for lic in licenses:
        if lic.product not in products:
            products[lic.product] = lic.product
            products[lic.product].avail_licenses = lic.avail_licenses
        else:
            products[lic.product].avail_licenses += lic.avail_licenses

    avail_products = []
    for prod in products.values():
        if prod.avail_licenses > 0:
            avail_products.append(prod)

    return avail_products

编辑(2014-02-12): 好的,这是我决定采用的最终解决方案。使用Python过滤结果。减少缓存调用,并具有恒定数量的SQL查询。

这里的教训是,对于具有多级过滤的内容,最好尽可能多地获取,并在返回时使用Python进行过滤。

class ProductManager(models.Manager):

    def get_all_available(self, curruser):
        """
        Gets all available Products that are available to the current user
        """
        q = self.get_queryset().select_related().prefetch_related('license', 'license__licensees').filter(
            is_obsolete=False,
            license__is_obsolete=False
        ).exclude(
            license__enddate__lte=date.today()

        ).distinct()

        # return a curated list. Need further information first
        products = []
        for x in q:
            x.avail_licenses = 0
            x.user_assigned = False

            # checks licenses. Does this on the model level as it's cached so as to save SQL queries
            for y in x.license.all():
                if not y.is_active:
                    break

                x.avail_licenses += y.available_licenses

                if curruser in y.licensees.all():
                    x.user_assigned = True
            products.append(x)

        return q

1 个答案:

答案 0 :(得分:1)

一种策略是从许可证查询集中获取所有产品ID:

    productIDList = list(License.objects.filter(...).values_list(
        'product_id', flat=True))

然后使用该ID列表查询产品:

    Product.objects.filter(id__in=productIDList)