Django根据ForeignKey和许多子查询的结果进行过滤



我曾考虑过使用额外和/或注释进行查询,但未能得到我想要的结果。

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

我定义的模型是:

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中可用许可证数量的可用产品列表。我宁愿尽量不使用生的。

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

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和list的过度使用。无论如何,它是有效的,我可以在以后用额外的信息来扩展它,而不会有巨大的风险或自定义SQL。它还使用了我在模型类中定义的一些模型参数。

类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

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

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

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

Product.objects.filter(id__in=productIDList)

最新更新