Queryset, get only one of reverse relationships - django

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

check user have liked msg or not from list of messages

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))

Django: cannot annotate using prefetch calculated attribute

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))))

How to query on OneToOne relation of a related_name object in django

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')

Prefetch related with a filter that contains a field of the original model

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

How can I filter a Django queryset by the latest of a related model?

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()