3

How can the following code be made more efficient (e.g., how to replace the loop with a query)?

def get_question(datetime_now, questions_queryset, user):
    best_schedule = None
    best_question = None
    # HOW TO ELIMINATE THE FOLLOWING LOOP AND REPLACE WITH A QUERY?
    for question in questions_queryset:
        try:
            schedule = (Schedule.objects
                        .filter(question=question, user=user)
                        .latest(field_name='datetime_added')
        except ObjectDoesNotExist:
            schedule = None
        if (schedule and (schedule.date_show_next >= datetime_now) and
                ((not best_schedule) or
                 (schedule.datetime_added >= best_schedule.datetime_added))):
            best_schedule = schedule
            best_question = question

    return best_question



models.py

from django.contrib.auth.models import User
from django.db.models import DateTimeField, ForeignKey, Model, TextField

class Question(Model):
    question = TextField()

class Schedule(Model):
    datetime_added = DateTimeField(auto_now_add=True)
    datetime_show_next = DateTimeField(null=True, default=None)
    question = ForeignKey(Question)
    user = ForeignKey(User, null=True)
Rob Bednark
  • 25,981
  • 23
  • 80
  • 125

2 Answers2

6

You could use Subquery like in this answer https://stackoverflow.com/a/43883397/3627387 or with use of Prefetch https://stackoverflow.com/a/31237026/3627387

Here is one way to achieve this with Prefetch:

schedules_prefetch = Prefetch(
        'schedule_set',
        queryset=Schedule.objects.filter(user=user))
for question in questions_queryset.prefetch_related(schedules_prefetch):
    try:
        # using max here so it wouldn't do another DB hit
        schedule = max(question.schedule_set.all(),
                       key=lambda x: x.datetime_added)
    except ValueError:
        schedule = None

Here is an example using Subquery(it might not actually work, but will give you the general idea):

from django.db.models import OuterRef, Subquery
schedules = (Schedule.objects
             .filter(user=user, question=OuterRef('pk'))
             .order_by('datetime_added'))
questions_queryset = (questions_queryset
                    .annotate(latest_schedule=Subquery(schedules[:1])))
for question in questions_queryset:
    schedule = question.latest_schedule
Rob Bednark
  • 25,981
  • 23
  • 80
  • 125
Sardorbek Imomaliev
  • 14,861
  • 2
  • 51
  • 63
2
    # Get the question ids    
    question_ids = questions_queryset.values_list('id', flat=True)

    # get the beloved shedule
    schedule = Schedule.objects.filter(question__in=question_ids, user=user).latest(field_name='datetime_added')

    # You may opt for Schedule.objects.get() so as not to run into
    # the problem of multiple objects returned if all you need is strictly one schedule
Erick M
  • 465
  • 4
  • 9