如果我有:
class Info(Model):
...
class Ad(Model):
listed_date = DatetimeField()
info = ForeignKey('Info', related_name='ads', null=True)
....
我想根据Info
中的字段查询Ad
,但只查看最新广告。我知道我能做到:
Ad.objects.latest('listed_date')
但是,由于我将通过将多个过滤器/排除链接在一起来构建查询,我想要类似的东西:
query = query.filter(
Q(**{
'ads__latest__'+attr_name: query_value
})
)
或者甚至可能有一个字段' latest_ad'总是基于某个领域指向最新的。目标是能够在构建的过滤/排除方法中仅查询相关字段中的最新内容。
我该怎么做?
修改:
一点背景...... 我有2个模型(LegalAd,TrusteeInfo)存储有关同一拍卖项目的数据,某些字段需要进行大量处理以提取必要的值(因此我决定将信息存储在单独的模型中)存储数据不同的加工阶段。然后,我尝试将两个模型合并为一个(AuctionItem),并广泛使用属性来优先处理来自LegalAd的TrusteeInfo中的数据,以获得它们共享的类似字段。问题是我想查询那些使用属性禁止的字段。所以我创建了一个管理器并覆盖了过滤器并排除了保存优先级逻辑的方法。以下是代码:
class LegalAd(models.Model):
listed_date = models.DateField(null=True) # field I would like to use for latest query
auction = models.ForeignKey('auction_table.Auction', related_name='legal_ads', null=True)
...
class TrusteeInfo(models.Model):
auction = models.OneToOneField('auction_table.Auction', null=True)
...
class AuctionManager(models.Manager):
def do_query_action(self, action, kwargs):
trusteeinfo = apps.get_model('scrapers', 'TrusteeInfo')
trustee_fields = [field.name for field in trusteeinfo._meta.get_fields()]
legalad = apps.get_model('scrapers', 'LegalAd')
legalad_fields = [field.name for field in legalad._meta.get_fields()]
related_fields = trustee_fields + legalad_fields
auction_native_fields = [
'legal_ads',
'trusteeinfo',
'properties',
'id',
'pk',
'created_date',
'updated_date'
]
query = super(AuctionManager, self)
for attr, value in kwargs.items():
attr_base = attr.split('__')[0] # get the base attr name
if attr_base in auction_native_fields:
query = getattr(query, action)(**{attr: value})
elif attr_base in related_fields:
qs = []
if attr_base in trustee_fields:
trustee_attr_name = 'trusteeinfo__' + attr
qs.append(Q(**{trustee_attr_name: value}))
if attr_base in legalad_fields:
legalad_attr_name = 'legalads__' + attr
qs.append(Q(**{legalad_attr_name: value}))
query = getattr(query, action)(reduce(or_, qs))
else:
raise AttributeError("type object `Auction` has no attribute '{attr}'".format(attr=attr))
return query.distinct()
def filter(self, **kwargs):
return self.do_query_action('filter', kwargs)
def exclude(self, **kwargs):
return self.do_query_action('exclude', kwargs)
class Auction(models.Model):
objects = AuctionManager()
created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True)
@property
def latest_ad(self):
return self.legal_ads.exists() and self.legal_ads.latest('listed_date')
@property
def sale_datetime(self):
if self.trusteeinfo and self.trusteeinfo.sale_datetime:
return self.trusteeinfo.sale_datetime
else:
return self.latest_ad and self.latest_ad.sale_datetime
@property
def county(self):
if self.trusteeinfo and self.trusteeinfo.county:
return self.trusteeinfo.county
else:
return self.latest_ad and self.latest_ad.county
@property
def sale_location(self):
return self.latest_ad and self.latest_ad.sale_address
@property
def property_addresses(self):
if self.trusteeinfo and self.trusteeinfo.parsed_addresses.exists():
return self.trusteeinfo.parsed_addresses
else:
return self.latest_ad and self.latest_ad.parsed_addresses
@property
def raw_addresses(self):
if self.trusteeinfo and self.trusteeinfo.addresses:
return self.trusteeinfo.addresses
else:
return self.latest_ad and self.latest_ad.addresses.get('addresses', None)
@property
def parcel_numbers(self):
return self.latest_ad and self.latest_ad.parcel_numbers
@property
def trustee(self):
if self.trusteeinfo:
return self.trusteeinfo.trustee
else:
return self.latest_ad and self.latest_ad.trustee.get('trustee', None)
@property
def opening_bid(self):
if self.trusteeinfo and self.trusteeinfo.opening_bid:
return self.trusteeinfo.opening_bid
else:
return self.latest_ad and self.latest_ad.dollar_amounts.get('bid_owed', [[None]])[0][0]
@property
def deposit_amount(self):
if self.trusteeinfo and self.trusteeinfo.deposit_amount:
return self.trusteeinfo.deposit_amount
else:
return self.latest_ad and self.latest_ad.dollar_amounts.get('deposit', [[None]])[0][0]
@property
def sale_status(self):
return self.trusteeinfo and self.trusteeinfo.sale_status
@property
def trustors(self):
if self.trusteeinfo and self.trusteeinfo.parsed_names.exists():
return self.trusteeinfo.parsed_names
else:
return self.latest_ad and self.latest_ad.parsed_names
事实上,广告通常一次列出2个,因此很有可能会有2个广告出现在最新日期,这意味着我必须运行类似{{1}的广告。也就是它的方法。我可以查找某些kwargs并为此运行一个特殊的查询但是如何将其合并到链式查询中的其余kwargs中?理想情况下,如果我可以保持一个到多个first()
,但也可以执行以下操作:
legal_ads
或:
query.filter(latest_ad__<queryfield>=value)
那太棒了。
答案 0 :(得分:1)
你所拥有的是所谓的greatest-n-per-group问题,它很难与ORM打交道甚至不可能。
可以找到解决问题的一种方法here。
在你的情况下,它可能是这样的:
Info.objects.filter(
ad__listed_date__in=Info.objects.annotate(
last_date=Max('ad__listed_date')
).values_list('last_date', flat=True)
#now you can add more
#ad__<somefiled> statements
#but you need to make it in a single `.filter` call
#otherwise the ORM will do separate joins per `.filter` call
)
我个人不喜欢这样。对我来说,它看起来像是一个黑客,它效率不高,如果某个组中的倒数第二个ad
与最后一个listed_date
ad
相同,则很容易返回错误结果在另一组。
如果你给我们一些关于为什么你需要过滤每个信息的latest_ad的背景知识,也许我们可以找到另一种方法来获得相同/相似的结果。
但是,我更喜欢的一种解决方法是过滤一些date_range
。例如,请勿在{{1}}或latest_ad
或.filter
中的latest_ads
上搜索last_day
,two
,根据您的需要。它非常简单有效的(易于优化)查询。
week
你还提到了一个很好的解决方法,如果你能够轻松掌握最新的Info.objects.filter(
ad__listed_date__gte=(today-timedelta(days=1))
#now again you can keep adding more `ad__<somefiled>` statements
#but make sure to enclose them in a single `.filter` call.
)
字段,那么我想你会很高兴。
如果您采用这种方法,请务必设置Info.latest_ad
,因为默认行为(级联删除)可能会给您带来问题。
on_delete=models.SET_NULL
答案 1 :(得分:0)
您可以.latest()
.filter()
Ad.objects.filter(your_filter=your_value).latest('listed_date')
或使用oder_by
Ad.objects.filter(your_filter=your_value).order_by('-listed_date')[0]