If I have:
class Info(Model):
...
class Ad(Model):
listed_date = DatetimeField()
info = ForeignKey('Info', related_name='ads', null=True)
....
I want to query Info
based on fields within Ad
, but only the latest ad. I know I can do:
Ad.objects.latest('listed_date')
But since I will be building up the query by chaining several filter/excludes together, I want something like:
query = query.filter(
Q(**{
'ads__latest__'+attr_name: query_value
})
)
Or perhaps even have a field 'latest_ad' which always points to the most recent based on a certain field. The goal is to be able to query just the latest in the related field in a built up filter/exclude method.
How can I do this?
EDIT:
A little background... I have 2 models (LegalAd, TrusteeInfo) that store scraped data about the same auction item, some of the field need a fair deal of processing to extract the necessary values (hence my decision to store the information in separate models) store the data at different stages of processing. I then attempt to combine both models into one (AuctionItem), and use properties extensively to prioritze data from TrusteeInfo over LegalAd for the similar fields they share. The problem is that I would like to query those fields, which the use of properties prohibits. So I created a manager and overrode the filter and exclude methods to hold the prioritization logic. Below is the code:
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
It gets a bit more complicated with the fact that the ads are usually listed 2 at a time so there is a good chance of 2 ads showing up for the latest date, meaning I would have to run something like a first()
method on it too. I could look out for certain kwargs and run a special query for that but how would I incorporate that into the the rest of the kwargs in the chained query? Ideally, if I could keep the one to many legal_ads
, but also be able to do something like:
query.filter(latest_ad__<queryfield>=value)
or:
query.filter(legal_ads__latest__<queryfield>=value)
That would be great.