Код: Выделить всё
appointment.date_timeКод: Выделить всё
appointment.service.durationЯ хотел получить данные медика «свободное время», то есть доступные места, «где» я могу добавить больше встреч.
Например:
У меня есть временной интервал на понедельник: 09:00-12:00. У меня назначена встреча с 10:30 до 11:00.
В идеале это представление должно отображать свободное время:
Код: Выделить всё
09:00-09:30, 09:30-10:00, 10:00-10:30, 11:00-11:30, 11:30-12:00(Я знаю, что это похоже на интервальное планирование, но я не уверен, что оно точно соответствует тому, что я хочу).
Мне не обязательно нужно наиболее оптимальное размещение свободного времени. Существует множество крайних случаев, которые можно или нельзя урегулировать или усугубить с помощью проектных решений, например, встречи на два дня, недействительные встречи, встречи различной продолжительности.
вопрос: Есть ли способ оптимизировать этот код, уделив особое внимание снижению нагрузки на базу данных?
Поскольку я буду запускать это на prod, я могу просто заставить пользователей выбирать время начала встречи (что позволяет они решают, что делать в определенных крайних случаях), я хотел бы сделать приоритетом эффективность.
Или, возможно, это все вопрос преждевременной оптимизации, я не конечно...
Меня больше всего беспокоит то, что я слишком часто вызываю filter().
Обратите внимание, что встречи настроены для передачи по возрастанию Порядок date_time по умолчанию.
Следующий код работает (по крайней мере, на данный момент):
Код: Выделить всё
# TODO: Refactor this function and make it more efficient, by avoiding use of filter.
# (specially inside of for loop).
# Idea: the same way I use the time slots to define limits for searching, I can get all appointments
# and define their starting and ending times as limits.
@require_GET
def get_next_free_times(request):
username = request.GET.get('medic_id')
start_str = request.GET.get('start_date')
start_time_str = request.GET.get('start_time')
service_duration = time_string_to_seconds(request.GET.get('duration'))
try:
start = datetime.strptime(start_str, "%Y-%m-%d")
end = start + timedelta(days = 1)
# This variable is used to fix the very rare edge case where an appointment occurs during more than one day
# If this happens, the value of this variable is updated, and is then compared (using max) with the query_min value.
mult_day_app_start_time = start
available_slots = []
time_slots = TimeSlots.objects.get(medic__user__username=username).slots
all_appointments = Appointment.objects.filter(
medic__user__username=username,
)
# Keep querying until we find valid time slots from 4 different days
days_counter = 0
while days_counter < 4:
# Get slots for day of the week
day_slots = time_slots.get(day_mapping[start.weekday()])
# If there are no available slots today, check next day
if day_slots == None:
start = end
end = start + timedelta(days=1)
continue
# Now, we can limit the range of appointments queried by using the time slots.
# We want to calculate 'service_duration' length time slots, between start and end.
added_appointments_today = False
for slot in day_slots:
slot_start = datetime.strptime(slot['start_time'], '%H:%M').time()
slot_end = datetime.strptime(slot['end_time'], '%H:%M').time()
query_min = datetime.combine(start.date(), slot_start)
query_max = datetime.combine(start.date(), slot_end) + timedelta(minutes=1)
# Query appointments in current time slot.
appointments = all_appointments.filter(
date_time__range=(query_min, query_max)
)
current_start = max(round_time_to_duration(query_min, service_duration), mult_day_app_start_time)
current_end = current_start + timedelta(seconds=service_duration)
# While our current (possible) appointment ends before the time slot's end...
while current_end day_slot_end):
continue
add_slot(available_slots, current_start, current_end)
added_appointments_today = True
current_start += timedelta(seconds=service_duration)
current_end = current_start + timedelta(seconds=service_duration)
if added_appointments_today:
days_counter += 1
added_appointments_today = False
# Move range to the next day
start = end
end = start + timedelta(days=1)
# Finally, get rid of appointments which are before the initial starting datetime
# (the way this code works makes it possible to select those timeslots as valid)
if start_time_str != 'none':
start = datetime.strptime(start_str, "%Y-%m-%d")
start_time = datetime.strptime(start_time_str, "%H:%M").time()
start_datetime = datetime.combine(start, start_time)
for i in range(len(available_slots)):
slot = available_slots[i]
slot_start = datetime.strptime(slot['start_time'], '%Y-%m-%d %H:%M:%S')
if slot_start >= start_datetime:
break
available_slots = available_slots[i:]
return JsonResponse({'available_slots': json.dumps(available_slots)})
except (TimeSlots.DoesNotExist, Appointment.DoesNotExist):
return JsonResponse({'error': 'Medic or related data not found.'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
Код: Выделить всё
class MedicalService(models.Model):
medic = models.ForeignKey(Professional, on_delete=models.CASCADE)
name = models.CharField(max_length=255, verbose_name='Práctica')
duration = models.DurationField(default=datetime.timedelta(minutes=30), verbose_name='Duración')
def __str__(self):
return self.name
class TimeSlots(models.Model):
DAYS_OF_WEEK = (
('sunday', 'Sunday'),
('monday', 'Monday'),
('tuesday', 'Tuesday'),
('wednesday', 'Wednesday'),
('thursday', 'Thursday'),
('friday', 'Friday'),
('saturday', 'Saturday'),
)
medic = models.ForeignKey(Professional, on_delete=models.CASCADE)
slots = models.JSONField(default=dict)
def clean(self):
"""
Validate time slots to ensure they are ordered and do not collide.
Raises ValidationError if any slots are invalid.
"""
for day, slots in self.slots.items():
slots.sort(key=lambda slot: slot['start_time'])
for i in range(1, len(slots)):
if slots[i - 1]['end_time'] > slots[i]['start_time']:
raise ValidationError(f"Time slots for {day.capitalize()} are not ordered or collide.")
def add_time_slot(self, day_of_week, start_time, end_time):
day_of_week = day_of_week.lower()
if day_of_week not in self.slots:
self.slots[day_of_week] = []
self.slots[day_of_week].append({
'start_time': start_time.strftime('%H:%M'),
'end_time': end_time.strftime('%H:%M')
})
self.slots[day_of_week].sort(key=lambda slot: slot['start_time'])
self.save()
def update_time_slot(self, day_of_week, index, start_time, end_time):
day_of_week = day_of_week.lower()
if day_of_week in self.slots and 0
Подробнее здесь: [url]https://stackoverflow.com/questions/78332210/how-to-optimze-datetime-query-with-intersections[/url]