Django - Показать таймслоты по доступности
Я новичок в django & python. Я потратил ~2-3 недели на это и перепробовал много, много решений. У меня есть служба планирования автомобилей. Клиенты могут войти в систему, посмотреть свои автомобили и запланировать обслуживание. Они могут выбрать сервисный центр ("филиал"), дату, время, свой автомобиль и услугу, которую они хотят получить. Я хочу иметь возможность показывать свободные места по дате и филиалу. Вот что у меня есть на данный момент.
Models.py
class NewAppt(models.Model):
created = models.DateTimeField(auto_now_add=True)
start_time = models.DateTimeField(blank=False)
end_time = models.DateTimeField(blank=False)
branch = models.ForeignKey(Branch, on_delete=models.SET_NULL, null=True)
unit = models.ForeignKey(Vehicle, on_delete=models.SET_NULL, null=True)
vin = models.CharField(max_length=17)
sold_to = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
servicelevel = models.ForeignKey(ServiceLevel, on_delete=models.SET_NULL, null=True)
servicetype = models.ForeignKey(ServiceType, on_delete=models.SET_NULL, null=True)
def __str__(self):
return f'{self.branch} | StartTime = {self.start_time} | EndTime = {self.end_time} | SoldTo = {self.sold_to} | Service = {self.servicetype} | Level = {self.servicelevel}'
class Meta:
ordering = ['branch', 'start_time']
unique_together = ('branch', 'start_time')
@property
def duration(self):
return self.end_time - self.start_time
class BranchSchedule(models.Model):
"""Creates one schedule for each branch"""
branch = models.OneToOneField(Branch, on_delete=models.CASCADE, primary_key=True)
holidays = models.CharField(max_length=1000, validators=[int_list_validator], blank=True, null=True) # list of comma separated days [2021-01-01, 2022-12-31]
monday_first_appt = models.TimeField(blank=False, null=True)
monday_last_appt = models.TimeField(blank=False, null=True)
tuesday_first_appt = models.TimeField(blank=False, null=True)
tuesday_last_appt = models.TimeField(blank=False, null=True)
wednesday_first_appt = models.TimeField(blank=False, null=True)
wednesday_last_appt = models.TimeField(blank=False, null=True)
thursday_first_appt = models.TimeField(blank=False, null=True)
thursday_last_appt = models.TimeField(blank=False, null=True)
friday_first_appt = models.TimeField(blank=False, null=True)
friday_last_appt = models.TimeField(blank=False, null=True)
saturday_first_appt = models.TimeField(blank=False, null=True)
saturday_last_appt = models.TimeField(blank=False, null=True)
Forms.py
class AppointmentForm(forms.ModelForm):
class Meta:
model = NewAppt
exclude = ('status', 'sold_to', 'vin', 'servicelevel', 'end_time')
Views.py
def new_appointment(request):
if request.method == 'POST':
form = AppointmentForm(user=request.user, data=request.POST)
if form.is_valid():
appt = form.save(commit=False)
appt.sold_to = request.user
appt.vin = appt.unit.vin
appt.servicelevel = appt.unit.engine.servicelevel
appt.status = 'Requested'
appt.end_time = appt.start_time + datetime.timedelta(hours=2)
appt.save()
return redirect('/')
else:
form = AppointmentForm(user=request.user)
return render(request, 'catalog/appointment_form.html', {'form':form})
Serializers.py
class AppointmentSerializer(serializers.ModelSerializer):
def validate(self, appointment):
appointment = appt_fits_branch_schedule.validate(appointment)
appointment = prevent_double_book.validate(appointment)
return appointment
class Meta:
model = NewAppt
fields = ('branch', 'start_time', 'end_time', 'unit', 'servicetype', 'vin', 'sold_to', 'servicelevel')
appt_fits_branch_schedule.py
from datetime import datetime
from django.core.exceptions import ValidationError
import pytz
from django.conf import settings
from catalog.models import BranchSchedule
SERVER_TIMEZONE = pytz.timezone(settings.TIME_ZONE)
def _localize_date_string(date_string):
if date_string:
return SERVER_TIMEZONE.localize(datetime.strptime(str(date_string), "%Y-%m-%d")).date()
else:
return None
def validate(appointment):
timezone_to_use = SERVER_TIMEZONE
appointment_start_datetime = appointment['start_time'].astimezone(timezone_to_use)
appointment_start_time = appointment_start_datetime.time()
appointment_weekday = appointment_start_datetime.weekday()
appointment_date = appointment_start_datetime.date()
error = False
branch_schedule = BranchSchedule.objects.get(pk=appointment['branch'])
if branch_schedule:
#convert holiday from list of CSV string to list of dates
holidays = branch_schedule.holidays.split(',')
holidays = [_localize_date_string(holiday) for holiday in holidays]
schedule_weekdays = {
0: (branch_schedule.monday_first_appt, branch_schedule.monday_last_appt),
1: (branch_schedule.tuesday_first_appt, branch_schedule.tuesday_last_appt),
2: (branch_schedule.wednesday_first_appt, branch_schedule.wednesday_last_appt),
3: (branch_schedule.thursday_first_appt, branch_schedule.thursday_last_appt),
4: (branch_schedule.friday_first_appt, branch_schedule.friday_last_appt),
5: (branch_schedule.saturday_first_appt, branch_schedule.saturday_last_appt),
}
min_start_time_local_tz, max_start_time_local_tz = schedule_weekdays[appointment_weekday]
if min_start_time_local_tz and max_start_time_local_tz:
if appointment_start_time < min_start_time_local_tz or appointment_start_time > max_start_time_local_tz:
error = True
if appointment_date in holidays:
error = True
else: # branch has a null value for this date/time
error = True
else: # no schedule has been created for this branch
error = True
if error:
raise ValidationError('Appointment has been created outside of branch schedule time.')
return appointment
prevent_double_book.py
from django.core.exceptions import ValidationError
import pytz
from django.conf import settings
from catalog.models import NewAppt
def validate(appointment):
server_timezone = pytz.timezone(settings.TIME_ZONE)
appointment_start_datetime_tz = appointment['start_time'].astimezone(server_timezone)
appointment_end_datetime_tz = appointment['end_time'].astimezone(server_timezone)
appointment_start_time_tz = appointment_start_datetime_tz.time()
appointment_end_time_tz = appointment_end_datetime_tz.time()
appointment_date_utc = appointment['start_time'].astimezone(pytz.utc).date() # convert to UTC to compare with server
appointments_for_date = NewAppt.objects.filter(start_time__date=appointment_date_utc)
for existing_appointment in appointments_for_date:
existing_appointment.start_time = existing_appointment.start_time.astimezone(server_timezone).time()
existing_appointment.end_time = existing_appointment.end_time.astimezone(server_timezone).time()
branch_is_same = appointment['branch'] == existing_appointment.branch
error = False
if branch_is_same and appointment_start_time_tz < existing_appointment.start_time and appointment_end_time_tz > existing_appointment.end_time:
error = True
elif branch_is_same and appointment_start_time_tz < existing_appointment.end_time and appointment_end_time_tz > existing_appointment.end_time:
error = True
elif branch_is_same and appointment_start_time_tz >= existing_appointment.start_time and appointment_end_time_tz <= existing_appointment.end_time:
error = True
if error:
appointment_string = f'[ {appointment_start_time_tz} - {appointment_end_time_tz} ]'
existing_appointment_string = f'[ {existing_appointment.start_time} - {existing_appointment.end_time} ]'
raise ValidationError( 'Appointment %(appointment)s overlaps with the existing appoint %(exisiting_appointment)s',
params={'appointment': appointment_string, 'existing_appointment': existing_appointment_string})
if appointment_start_datetime_tz > appointment_end_datetime_tz:
error = True
raise ValidationError('Appointment start date is after appointment end date')
if appointment_start_time_tz > appointment_end_time_tz and appointment_start_datetime_tz > appointment_end_datetime_tz:
error = True
raise ValidationError('Appointment start time is after appointment end time')
return appointment
Как узнать доступное время и представить его конечному пользователю по филиалу и дате?