Django ORM query with exclude not working properly - django

I have below Django ORM query which excluding product having 0(zero) sale_price.
selected_attr_values = ProductAttribValue.objects.filter(
product__status_id=1,
product_id__in=product_attributes_values.values_list('product_id', flat=True).distinct()
).exclude(
product__sale_price = 0,
ifield_value = '',
field_value__isnull=False
).distinct(
"field_value",
'field_id'
).values(
'field_value',
'product_id',
'field__caption',
'field_id',
'id'
)
Above query does not excluding products having 0 sale_price.
But after updating query like below.
selected_attr_values = ProductAttribValue.objects.filter(
product__status_id=1,
product_id__in=product_attributes_values.values_list('product_id', flat=True).distinct()
).exclude(
field_value='',
field_value__isnull=False
).distinct(
"field_value",
'field_id'
).exclude(
product__sale_price=0
).values(
'field_value',
'product_id',
'field__caption',
'field_id',
'id'
)
it working fine.
So my question is why do I need to call exclude 2 times to get desired output.
Thanks.

Django by default join multiple conditions with AND operator. Your query will only exclude rows with product__sale_price=0 AND field_value='' AND field_value__isnull=False. If you want OR operator between your conditions, you have to use Q.
from django.db.models import Q
...exclude(Q(product__sale_price=0) | Q(field_value='') | Q(field_value__isnull=False))

Related

Django annotate by foreign key's date field but exclude past dates

I want to have my queryset of Card model annotated with new field called available_on, which should be calculated as closest date in future of relative Booking model's field removal_date. It should consider only dates in future, how can I filter out removal_date dates that are in the past? What I have now is this.
def with_available_on(self):
qs = self.annotate(available_on=Case(
When(
bookings_count__gt=0,
slots_available__lt=1,
then=Min('bookings__removal_date')),
default=None
)
)
return qs
Also I want it to be calculated on database side if possible for performance purposes. Thanks
You can use the filter=… parameter [Django-doc] to filter the objects over which you span the aggregate in the Min aggregate [Django-doc]:
from django.db.models import Q
from django.db.models.functions import Now
def with_available_on(self):
qs = self.annotate(available_on=Case(
When(
bookings_count__gt=0, slots_available__lt=1,
then=Min(
'bookings__removal_date',
filter=Q(bookings__remval_date__gte=Now())
)
),
default=None)
)
return qs

How can I have statistics in django admin panel on User with date filter on related field?

Related model
class AbstractTask(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
issued_at = models.DateTimeField(auto_now_add=True)
Problem
I need to show some User statistics per days in the admin panel. Lets say I just need the number of issued tasks. And I need to be able to filter it by issue date (how many were issued yesterday, the day before yesterday, etc).
How I am trying to do it
I use User proxy models to register ModelAdmin for different statistics pages.
I use slightly modified (changed date ranges) DateFieldListFilter on task__issued_at field:
list_filter = [
('task__issued_at', DateFieldListFilter),
'username',
]
Filters on date field don't work
Filters don't work because they end up generating query similar to this:
queryset = (User.objects
.annotate(
# Different statistics.
num_tasks=Count('task'),
)
.filter(
# DateFieldListFilter.
task__issued_at__gte='2020-01-01',
task__issued_at__lt='2020-01-02',
)
.values('id', 'num_tasks')
)
SQL:
SELECT "auth_user"."id",
COUNT("task"."id") AS "num_tasks"
FROM "auth_user"
LEFT OUTER JOIN "task" ON ("auth_user"."id" = "task"."user_id")
INNER JOIN "task" T3 ON ("auth_user"."id" = T3."user_id")
WHERE (T3."issued_at" >= 2020-01-01 00:00:00+03:00
AND T3."issued_at" < 2020-01-02 00:00:00+03:00)
GROUP BY "auth_user"."id"
The problem is that filter adds second join on table "task" when I need just one.
Forcing first inner join by adding .filter(task__isnull=False) doesn't help. It just keeps performing two identical inner joins.
It is the same behavior in django 2 and 3.
Can It be done in Django?
Preferably as simple as possible: without raw sql, without much magic and with continuing using DateFieldListFilter.
But any solution would help.
The alternative QuerySet below gives the same result without any additional joins:
(queryset = User.objects
.annotate(
# Different statistics.
num_tasks=Count(
'task',
filter=models.Q(
Q(task__issued_at__gte='2020-01-01') &
Q(task__issued_at__lt='2020-01-02')
)
),
)
.values('id', 'num_tasks')
)
SQL:
SELECT "auth_user"."id", COUNT("task"."id")
FILTER (WHERE ("task"."issued_at" >= 2020-01-01 00:00:00+03:00 AND "task"."issed_at" < 2020-01-02 00:00:00+03:00)) AS "num_tasks"
FROM "auth_user"
LEFT OUTER JOIN "task" ON ("auth_user"."id" = "task"."user_id")
GROUP BY "auth_user"."id"
but not sure about the performance compared with yours.
Anyway, to make it work with the DateFieldListFilter you just need to override the queryset method:
class CustomDateFieldListFilter(DateFieldListFilter):
def queryset(self, request, queryset):
# Compare the requested value to decide how to filter the queryset.
q_objects = models.Q()
for key, value in self.used_parameters.items():
q_objects &= models.Q(**{key: value})
return queryset.annotate(num_tasks=Count('task', filter=models.Q(q_objects))).values('id', 'num_tasks')
and specify the new class:
list_filter = [
('task__issued_at', CustomDateFieldListFilter),
...
]
That's it.

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

Django query set object query taking too much time?

I am using a job model fetching the related jobs data which is also relationship with others model too for one-to-one or foreign key relations i have used select_related() and pass relative model inside and another one manny-to-manny relations i have used prefetch_related() and pass relative model inside. I have used this queryset on page 2 times based on different conditions filter everythings working fine but 1job queryset taking time and 2nd working well I can't understand that what's thing i missed up and how to resolved it. Please help if any one understand.
Model queryset
jobs = Job.objects.filter(
(Q(job_status__job_status_description='Booked') |
Q(job_status__job_status_description='Allocated') |
Q(job_status__job_is_done=True)) &
Q(completed_date__gte=start.strftime("%Y-%m-%d"), completed_date__lte=week_end_date.strftime("%Y-%m-%d")) |
Q(allocated_date__gte=start.strftime("%Y-%m-%d"), allocated_date__lte=week_end_date.strftime("%Y-%m-%d")),
functools.reduce(operator.and_, jobs_q_condition)
).select_related(
'customer',
'job_status',
'customer__book_with',
'customer__frequency'
).prefetch_related(
'customer__booking_road__area__franchise',
'customer__booking_road__area'
).annotate(
job_id=F('id'),
job_window_cleaner=Concat(
'window_cleaner__user__first_name',
Value(' '),
'window_cleaner__user__last_name'
),
job_window_cleaner_booking_road=Concat(
'customer__booking_road__area__default_cleaner__user__first_name',
Value(' '),
'customer__booking_road__area__default_cleaner__user__last_name'),
job_window_cleaner_id1=F('window_cleaner__user__id'),
job_window_cleaner_id2=F('customer__booking_road__area__default_cleaner__user__id'),
address=F('customer__address_line_1'),
wc_wants=F('customer__booking_road__area__default_cleaner__requested_turnover'),
action_on_check_in_str=F('action_on_check_in'),
booking_info_str=F('customer__booking_info'),
book_with_str=F('customer__book_with__book_with'),
is_job_completed = F('job_status__job_is_done'),
job_status_str = F('job_status__job_status_description'),
area_str=F('customer__booking_road__area__area'),
booking_road_str=F('customer__booking_road__booking_road'),
price_str=Coalesce('price_on_day', 'set_price'),
frequency_text_str=F('customer__frequency__frequency_text'),
due_date_str=F('due_date'),
allocated_date_str=F('allocated_date'),
completed_date_str=F('completed_date')
).order_by(
'job_window_cleaner','area_str','booking_road_str','due_date'
)[:20]
due_jobs = Job.objects.filter(
functools.reduce(operator.and_, due_jobs_q_condition)
).select_related(
'customer',
'job_status',
'customer__book_with',
'customer__frequency'
).prefetch_related(
'customer__booking_road__area__franchise',
'customer__booking_road__area'
).annotate(
job_id=F('id'),
job_window_cleaner=Concat(
'customer__booking_road__area__default_cleaner__user__first_name',
Value(' '),
'customer__booking_road__area__default_cleaner__user__last_name'),
job_window_cleaner_id=F('customer__booking_road__area__default_cleaner__user__id'),
address=F('customer__address_line_1'),
wc_wants=F('customer__booking_road__area__default_cleaner__requested_turnover'),
action_on_check_in_str=F('action_on_check_in'),
booking_info_str=F('customer__booking_info'),
book_with_str=F('customer__book_with__book_with'),
is_job_completed = F('job_status__job_is_done'),
job_status_str = F('job_status__job_status_description'),
area_str=F('customer__booking_road__area__area'),
booking_road_str=F('customer__booking_road__booking_road'),
price_str=Coalesce('price_on_day', 'set_price'),
frequency_text_str=F('customer__frequency__frequency_text'),
due_date_str=F('due_date'),
).order_by(
'job_window_cleaner','area_str','booking_road_str','due_date'
)[:20]

Django filter by annotated field is too slow

I use DRF and I have model Motocycle, which has > 2000 objects in DB. Model has one brand. I want to search by full_name:
queryset = Motocycle.objects.prefetch_related(
"brand"
).annotate(
full_name=Concat(
'brand__title',
Value(' - '),
'title',
)
)
)
I want to filter by full_name, but query is running very slowly:
(1.156) SELECT "mp_api_motocycle"."id"...
Without filtering with pagination:
(3.980) SELECT "mp_api_motocycle"."id"...
There is some possibilty to make this query faster?
Keep your full_name annotation as a column in the database and add an index to it.
Otherwise, you are doing full table scan while calculating full_name and then sorting by it.