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

Как узнать доступное время и представить его конечному пользователю по филиалу и дате?

Вернуться на верх