so i have this kind of queryset:
prods = Product.objects.prefetch_related(
Prefetch(
'packs',
queryset=Pack.objects.all(),
to_attr='my_packs'),
Prefetch(
'packs__orders',
queryset=Order.objects.all(),
to_attr='my_orders')
)
pack has a foreign key to product, and pack with order has a m2m relation.
so this prods[0].my_packs works.
but there is no attribute called my_orders in my qs -> prods[0].my_orders
why does this happen? and how can i make this work?
Related
Context
I really want to, but I don't understand how I can limit an already existing Prefetch object
Models
class MyUser(AbstractUser):
pass
class Absence(Model):
employee = ForeignKey(MyUser, related_name='absences', on_delete=PROTECT)
start_date = DateField()
end_date = DateField()
View
class UserAbsencesListAPIView(ListAPIView):
queryset = MyUser.objects.order_by('first_name')
serializer_class = serializers.UserWithAbsencesSerializer
filterset_class = filters.UserAbsencesFilterSet
Filter
class UserAbsencesFilterSet(FilterSet):
first_name = CharFilter(lookup_expr='icontains', field_name='first_name')
from_ = DateFilter(method='filter_from', distinct=True)
to = DateFilter(method='filter_to', distinct=True)
What do I need
With the Request there are two arguments from_ and to. I should return Users with their Absences, which (Absences) are bounded by from_ and/or to intervals. It's very simple for a single argument, i can limit the set using Prefetch object:
def filter_from(self, queryset, name, value):
return queryset.prefetch_related(
Prefetch(
'absences',
Absence.objects.filter(Q(start_date__gte=value) | Q(start_date__lte=value, end_date__gte=value)),
)
)
Similarly for to.
But what if I want to get a limit by two arguments at once?
When the from_ attribute is requested - 'filter_from' method is executed; for the to argument, another method filter_to is executed.
I can't use prefetch_related twice, I get an exception ValueError: 'absences' lookup was already seen with a different queryset. You may need to adjust the ordering of your lookups..
I've tried using to_attr, but it looks like I can't access it in an un-evaluated queryset.
I know that I can find the first defined Prefetch in the _prefetch_related_lookups attribute of queryset, but is there any way to apply an additional filter to it or replace it with another Prefetch object so that I can end up with a query similar to:
queryset.prefetch_related(
Prefetch(
'absences',
Absence.objects.filter(
Q(Q(start_date__gte=from_) | Q(start_date__lte=from_, end_date__gte=from_))
& Q(Q(end_date__lte=to) | Q(start_date__lte=to, end_date__gte=to))
),
)
)
django-filter seems to have its own built-in filter for range queries:
More info here and here
So probably just easier to use that instead:
def filter_date_range(self, queryset, name, value):
if self.lookup_expr = "range":
#return queryset with specific prefetch
if self.lookup_expr = "lte":
#return queryset with specific prefetch
if self.lookup_expr = "gte":
#return queryset with specific prefetch
I haven't tested this and you may need to play around with the unpacking of value but it should get you most of the way there.
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
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]
I have a model class with a status field, which might have several alternatives, say:
class MyModel(models.Model):
status_choices = (
('status1', 'status1'),
('status2', 'status2'),
('status3', 'status3'),
('status4', 'status4'),
)
status = models.CharField(choices=status_choices)
Then I want to annotate the instances with an active field which might be either True or False. The instance is active when status is IN [status1, status2].
Django version is 1.11.
We can do this with a Case expression [Django-doc]:
from django.db.models import BooleanField, Case, Value, When
MyModel.objects.annotate(
active=Case(
When(status__in=['status1', 'status2'], then=Value(True))
default=Value(False),
output_field=BooleanField()
)
)
Note that the list of the status__in condition should contain the keys of the choices (so the left item of the tuples, not the right one).
I would like to augment one of my model admins with an interesting value. Given a model like this:
class Participant(models.Model):
pass
class Registration(models.Model):
participant = models.ForeignKey(Participant)
is_going = models.BooleanField(verbose_name='Is going')
Now, I would like to show the number of other Registrations for this Participant where is_going is False. So, something akin to this SQL query:
SELECT reg.*, COUNT(past.id) AS not_going_num
FROM registrations AS reg, registrations AS past
WHERE past.participant_id = reg.participant_id AND
past.is_going = False
I think I can extend the Admin's queryset() method according to Django Admin, Show Aggregate Values From Related Model, by annotating it with the extra Count, but I still cannot figure out how to work the self-join and filter into this.
I looked at Self join with django ORM and Django self join , How to convert this query to ORM query, but the former is doing SELECT * AND the latter seems to have data model problems.
Any suggestions on how to solve this?
See edit history for previous version of the answer.
The admin implementation below will display "Not Going Count" for each Registration model. The "Not Going Count" is the count of is_going=False for the registration's participant.
#admin.register(Registration)
class RegistrationAdmin(admin.ModelAdmin):
list_display = ['id', 'participant', 'is_going', 'ng_count']
def ng_count(self, obj):
return obj.not_going_count
ng_count.short_description = 'Not Going Count'
def get_queryset(self, request):
qs = super(RegistrationAdmin, self).get_queryset(request)
qs = qs.filter(participant__registration__isnull=False)
qs = qs.annotate(not_going_count=Sum(
Case(
When(participant__registration__is_going=False, then=1),
default=0,
output_field=models.IntegerField())
))
return qs
Below is a more thorough explanation of the QuerySet:
qs = qs.filter(participant__registration__isnull=False)
The filter causes Django to perform two joins - an INNER JOIN to participant table, and a LEFT OUTER JOIN to registration table.
qs = qs.annotate(not_going_count=Sum(
Case(
When(participant__registration__is_going=False, then=1),
default=0,
output_field=models.IntegerField())
)
))
This is a standard aggregate, which will be used to SUM up the count of is_going=False. This translates into the SQL
SUM(CASE WHEN past."is_going" = False THEN 1 ELSE 0 END)
The sum is generated for each registration model, and the sum belongs to the registration's participant.
I might misunderstood, but you can do for single participant:
participant = Participant.objects.get(id=1)
not_going_count = Registration.objects.filter(participant=participant,
is_going=False).count()
For all participants,
from django.db.models import Count
Registration.objects.filter(is_going=False).values('participant') \
.annotate(not_going_num=Count('participant'))
Django doc about aggregating for each item in a queryset.