Reputation: 497
I have been trying to optimize my queries in Django to reduce my postgres database hits, so here are the, simplified for this question, concerned models
class SentEmail(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
sent_date = models.DateTimeField(default=timezone.now)
sender = models.EmailField()
class AppUser(models.Model):
user= models.OneToOneField(User, primary_key=True)
is_active= models.BooleanField(default= False)
expiration_date = models.DateTimeField(default=make_default_expiration_date) #note : 90 days after the creation
And User
is the standard Django auth user with the username, password and other stuff. What I want to do, is retrieving the AppUser
's various data for active users and the last SentEmail
that concerns them. For now, I have this.
active_users = Language_new.objects.filter(expiration_date__gt=timezone.now(),
is_active=True)
And if I want to include the last sent email, I'd do something like this
active_users = Language_new.objects.filter(expiration_date__gt=timezone.now(),
is_active=True).prefetch_related(
Prefetch(
"sentemail",
queryset=SentEmail.objects.filter().latest('sent_date'),
to_attr="last_sentemail"
))
Is Django smart enough to make the relation here : queryset=SentEmail.objects.filter().latest('sent_date')
? If not, how do I reference each user in the filter()
?
Also, as there might not be a "latest" sentemail
for every AppUser
, how do I handle the DoesNotExist in this kind of query?
Executing this code gets me an AttributeError
AttributeError: 'SentEmail' object has no attribute '_iterable_class'
Pointing on this line : to_attr="last_sentemail"
. I don't know why.
Note that I have a working solution that look like this :
active_users = Language_new.objects.filter(expiration_date__gt=timezone.now(),
is_active=True)
active_users = list(active_users)
for user in active_users:
last_sent_mail = SentEmail.objects.filter(user=active_user.user)
if last_sent_mail.exists():
setattr(user, 'last_sent_mail_date', last_sent_mail.latest('sent_date').sent_date)
else:
setattr(user, 'last_sent_mail_date', 'None')
It's working, but I think we all agree that it's not the best one around.
Note : Here I retreive the date of the last sentemail
directly, but having the full sentemail
or its date both work for me.
Thanks.
Upvotes: 1
Views: 1338
Reputation: 2900
try:
class SentEmail(models.Model):
user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='sent_mails'
)
sent_date = models.DateTimeField(default=timezone.now)
sender = models.EmailField()
and
class AppUser(models.Model):
user= models.OneToOneField(User, primary_key=True)
is_active= models.BooleanField(default= False)
expiration_date = models.DateTimeField(
default=make_default_expiration_date
) #note : 90 days after the creation
@property
def last_sent_mail(self): # or last_sent_mail_date
sent_mails = self.user.sent_mails.order_by('sent_date')
return sent_mails[0] if sent_mails.exists() else None
to retrieve the last email sent:
last_mail = user.last_sent_mail
if last_mail:
print last_mail.sent_date
Upvotes: 1