class Foo(models.Model):
name = CharField
createdat = DateTimeField
class Bar(models.Model):
rel = ForeignKey(Foo, related_name='bars')
createdat = DateTimeField
Foo.prefetch_related('bars').all() gives me all Bars. Are there any way I can only get the latest Bar for each Foo, using only one query?
You want to use a Prefetch object, which is here in the docs.
Prefetch() lets you filter the query, like so in your example:
queryset = Bar.objects.latest('createdat')
latest_bars = Foo.objects.prefetch_related(
Prefetch(
'bars',
queryset,
to_attr="latest_bar"
)
)
the_latest_bar_for_first_foo = latest_bars[0].latest_bar
Try this:
A.prefetch_related('bars').latest()
Related
suppose
class Msg(models.Model):
...
likes = models.ManyToManyField(User,...)
channelname = models.CharField(...)
Now my queryset is
queryset = Msg.objects.filter(channelname='home')
What should i do after this to get somelike
[{id:xyz,liked=true},{id:tuv,liked=true},{id:abc,liked:false}]
You can annotate an Exists() subquery using the through model of your many to many field:
from django.db.models import Exists, OuterRef
liked_subquery = Msg.likes.through.objects.filter(
msg=OuterRef('pk'), # Filter for outer queries Msg instance
user=some_user_instance # Filter for user whose like we are checking for
)
queryset = Msg.objects.filter(
channelname='home'
).annotate(
liked=Exists(liked_subquery)
).values('id', 'liked')
print(list(queryset))
Target is to sum and annotate workingtimes for each employee on a given time range.
models:
class Employee(models.Model):
first_name = models.CharField(max_length=64)
class WorkTime(models.Model):
employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name="work_times")
work_start = models.DateTimeField()
work_end = models.DateTimeField()
work_delta = models.IntegerField(default=0)
def save(self, *args, **kwargs):
self.work_delta = (self.work_end - self.work_start).seconds
super().save(*args, **kwargs)
getting work times for each employee at a given date range:
queryset = Employee.objects.prefetch_related(
Prefetch(
'work_times',
queryset=WorkTime.objects.filter(work_start__date__range=("2021-03-01", "2021-03-15"]))
.order_by("work_start"),
to_attr="filtered_work_times"
)).all()
trying to annotate sum of work_delta to each employee:
queryset.annotate(work_sum=Sum("filtered_work_times__work_delta"))
This causes a FieldError:
Cannot resolve keyword 'filtered_work_times' into field. Choices are: first_name, id, work_times
How would one proceed from here? Using Django 3.1 btw.
You should use filtering on annotations.
I haven't tried, but I think the following code might help you:
from django.db.models import Sum, Q
Employee.objects.annotate(
work_sum=Sum(
'work_times__work_delta',
filter=Q(work_times__work_start__date__range=["2021-03-01", "2021-03-15"])
)
)
You cannot use the prefetch_related values in the query because simply the prefetching is done separately, Django would first fetch the current objects and then make queries to fetch the related objects so the field you try to refer is not even part of the query you want to add it to.
Instead of doing this simply add a filter [Django docs] keyword argument to your aggregation function:
from django.db.models import Q
start_date = datetime.date(2021, 3, 1)
end_date = datetime.date(2021, 3, 15)
result = queryset.annotate(work_sum=Sum("work_times__work_delta", filter=Q(work_times__work_start__date__range=(start_date, end_date))))
I have this model:
class ProgramRequirement(Model):
program = OneToOneField(Program, related_name='program_requirement')
prereq_program = ForeignKey(Program, related_name='prereq_program_requirement')
is_english_required = BooleanField()
and this model
class Program(Model):
field_1 = ...
field_3 = ...
I need to write a query that would return the primary key of the programs of which is_english_required of the prereq_program is True.
I tried this but it seems to be a wrong query:
ProgramRequirement.objects.filter(prereq_program__prereq_program_requirement__is_english_required =True).values_list('program__pk', flat=True)
However, it is not returning the correct result.
I am not sure if it is what I want but I am also thinking of this:
Program.objects.filter(prereq_program_requirement__is_english_required =True).values_lis('pk', flat=True)
Any idea of how to retrieve do the abovementioned result?
You might try:
ProgramRequirement.objects.filter(prereq_program__programrequirement__is_english_required = True).values('pk')
If I have Model A with a ForeignKey to Model B, how can I prefetch Model A from Model B using some criteria in Model B?
This is something what I would want (OuterRef obviously only works in a SubQuery, but this is basically the functionality I need):
class ModelA(models.Model):
somecriteria = models.CharField()
class ModelB(models.Model):
somerelation = models.ForeignKey(ModelA)
someattribute = models.CharField()
qs = ModelA.objects.all()
qs = qs.prefetch_related(
Prefetch(
'modelb_set',
queryset=ModelB.objects.filter(someattribute=OuterRef('somecriteria')),
to_attr='some_attr'
),
)
The query is supposed to do the following (in less queries):
for obj in qs:
# Change this to new_qs = obj.some_attr if prefetch is working.
newqs = obj.modelb_set.filter(someattribute=obj.somecriteria)
if newqs.exists():
# Do something
modelb_qs = models.Subquery(
ModelB.objects.filter(
someattribute=models.OuterRef("somerelation__somecriteria")
).values("id")
)
qs = ModelA.objects.prefetch_related(
models.Prefetch("modelb_set", queryset=ModelB.objects.filter(id__in=modelb_qs))
)
for obj in qs:
if len(obj.modelb_set.all()):
# Do something
This will ONLY make 2 queries, if you stick a filter() inside the loop, you're going to make N(O^2) queries... not good.
I wrote a test to prove this only make 2 queries, you can find it here:
https://gist.github.com/kingbuzzman/fd2b635f2cf011f1330a6be088ce3664#file-modelabquery-py-L125
Imagine I have the following 2 models in a contrived example:
class User(models.Model):
name = models.CharField()
class Login(models.Model):
user = models.ForeignKey(User, related_name='logins')
success = models.BooleanField()
datetime = models.DateTimeField()
class Meta:
get_latest_by = 'datetime'
How can I get a queryset of Users, which only contains users whose last login was not successful.
I know the following does not work, but it illustrates what I want to get:
User.objects.filter(login__latest__success=False)
I'm guessing I can do it with Q objects, and/or Case When, and/or some other form of annotation and filtering, but I can't suss it out.
We can use a Subquery here:
from django.db.models import OuterRef, Subquery
latest_login = Subquery(Login.objects.filter(
user=OuterRef('pk')
).order_by('-datetime').values('success')[:1])
User.objects.annotate(
latest_login=latest_login
).filter(latest_login=False)
This will generate a query that looks like:
SELECT auth_user.*, (
SELECT U0.success
FROM login U0
WHERE U0.user_id = auth_user.id
ORDER BY U0.datetime DESC
LIMIT 1
) AS latest_login
FROM auth_user
WHERE (
SELECT U0.success
FROM login U0
WHERE U0.user_id = auth_user.id
ORDER BY U0.datetime
DESC LIMIT 1
) = False
So the outcome of the Subquery is the success of the latest Login object, and if that is False, we add the related User to the QuerySet.
You can first annotate the max dates, and then filter based on success and the max date using F expressions:
User.objects.annotate(max_date=Max('logins__datetime'))\
.filter(logins__datetime=F('max_date'), logins__success=False)
for check bool use success=False and for get latest use latest()
your filter has been look this:
User.objects.filter(success=False).latest()