Django: Fix order issue with list of objects

Question:

I have a function that gets queryset results from the database and does some manipulations.

When I get the results of that list, somehow the order is changed.

And exactly this is the function that makes the order change: schedules = list(set(schedule_list) - set(excluded_schedules))

So I will explain exactly:

I want to display the availability of a professional for booking an appointment. This professional has a list of available slots.

When the visitor loads the professional profile page, Django makes a query to get all time slots of the professional, and then gets all the existing appointments, then proceeds to remove the booked schedules from the total schedules, to display the rest (the available schedules). So far so good?

So, the code is the following (edited for sensitive information):

def get_home_schedules(date, professional):
    day = get_day_by_date(date)
    try:
        schedules = Schedule.objects.filter(professional=professional, day=day, stype="Home").order_by('timefrom')
        excluded_schedules = []
        schedule_list = []
        for s in schedules:
            new_appointments = s.appointments.filter(schedule=s, date=date, status='New')
            confirmed_appointments = s.appointments.filter(schedule=s, date=date, status='Confirmed')
            appointments = chain(new_appointments,confirmed_appointments)
            schedule_list.append(s)
            if appointments:
                for a in appointments:
                    excluded_schedules.append(a.schedule)
        schedules = list(set(schedule_list) - set(excluded_schedules))
        return schedules
    except:
        return None

The schedule model is:

class Schedule(models.Model):
    professional = models.ForeignKey(d, on_delete=models.CASCADE)
    timefrom = models.CharField(max_length=5, choices=HOURTIME, default="00:00")
    timeto = models.CharField(max_length=5, choices=HOURTIME, default="00:00")
    day = models.CharField(max_length=8, choices=DAYS, default="Monday")
    stype = models.CharField(max_length=10, choices=APPOINTMENT_TYPE, default='Office')
    posted = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = "Availability"
        verbose_name_plural = "Availabilities"
        constraints = [
            models.UniqueConstraint(fields=['professional', 'timefrom', 'day'], name='unique schedule')
        ]

    def __str__(self):
        return '%s (%s - %s - %s)' % (self.professional, self.day, self.timefrom, self.timeto)

The appointment types are: Office, Online, Home

The issue I have is that I have 3 different functions, that get the available schedules, one for each appointment type, and the function works without any problems in the Office and Online types, but has the issue of order in the Home type.

The order might be because of the hour, please tell me if it’s true or not (even thought I do not think it’s the case because the order doesn’t get messed up in the other cases).

Asked By: Kaiss B.

||

Answers:

A python set is an unordered collection of objects, meaning that the order might change when you create the list of schedules like below:

schedules = list(set(schedule_list) - set(excluded_schedules))

You should delete the .order_by('timefrom') when retrieving the initial queryset (as the order will not be guaranteed anyways), and add it in the return statement. For example:

def get_home_schedules(date, professional):
    day = get_day_by_date(date)
    try:
        schedules = Schedule.objects.filter(professional=professional, day=day, stype="Home")
        excluded_schedules = []
        schedule_list = []
        for s in schedules:
            new_appointments = s.appointments.filter(schedule=s, date=date, status='New')
            confirmed_appointments = s.appointments.filter(schedule=s, date=date, status='Confirmed')
            appointments = chain(new_appointments,confirmed_appointments)
            schedule_list.append(s.id)
            if appointments:
                for a in appointments:
                    excluded_schedules.append(a.schedule.id)
        schedule_ids = list(set(schedule_list) - set(excluded_schedules))
        return Schedule.objects.filter(id__in=schedule_ids).order_by('timefrom')
    except:
        return None
Answered By: WoodyG

When you do

schedules = list(set(schedule_list) - set(excluded_schedules))

the order is lost (since sets are unordered).

You can just re-sort afterwards (and with that, get rid of the .order_by() since it doesn’t matter):

schedules = sorted(
   set(schedule_list) - set(excluded_schedules),
   key=lambda s: s.timefrom,
)

You can also optimize this to exactly 2 queries with

day = get_day_by_date(date)

schedules = Schedule.objects.filter(professional=professional, day=day, stype="Home").order_by('timefrom')

conflicting_appointment_schedule_ids = set(Appointment.objects.filter(
    schedule__in=schedules,
    status__in=["New", "Confirmed"],
    date=date,
).values_list("schedule_id", flat=True))

return [sched for sched in schedules if sched.id not in conflicting_appointment_schedule_ids]

and I’m sure a single query would be possible too.

Answered By: AKX
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.