Как вернуть список доступных временных интервалов через форму ValidationError

models.py

from django.db import models
from django.utils import timezone
from django.urls import reverse
from django.contrib.auth.models import User

class Customer(models.Model):
    username = models.ForeignKey(User,on_delete=models.CASCADE)
    name = models.CharField(max_length=20,null=True)

    def __str__(self):
        return self.name

# Create your models here.
class Booking(models.Model):
    customer_name = models.ForeignKey(Customer,on_delete=models.CASCADE,null=True)
    username = models.ForeignKey(User,on_delete=models.CASCADE)
    qty_plts = models.PositiveSmallIntegerField(default=1)
    cbm = models.PositiveSmallIntegerField(default=1)
    created_date = models.DateTimeField(default=timezone.now())
    delivery_date = models.DateField(null=True)
    delivery_time = models.TimeField(null=True)
    booking_number = models.CharField(max_length=50,unique=True)

    def __str__(self):
        return self.booking_number

    def save(self, **kwargs):
        if not self.booking_number:
            self.booking_number = f"{self.delivery_date:%Y%m%d}{self.delivery_time:%H%M}"
        super().save(**kwargs)

    def get_absolute_url(self):
        return reverse('bookmyslot:detail',kwargs={'pk':self.pk})

forms.py

from django import forms
from bookmyslot.models import Booking,Customer
from bootstrap_datepicker_plus import DatePickerInput
import datetime as dt
from django.utils import timezone


HOUR_CHOICES = [(dt.time(hour=x), '{:02d}:00'.format(x)) for x in range(7, 13)]

class BookingForm(forms.ModelForm):

    def __init__(self,*args,**kwargs):
        user = kwargs.pop('username',None)
        super(BookingForm,self).__init__(*args,**kwargs)
        self.fields['qty_plts'].label = "Quantity Of Pallets"
        self.fields['cbm'].label = "Shipment CBM"
        self.fields['delivery_date'].label = "Delivery Date"
        self.fields['delivery_time'].label = "Delivery Time"
        self.fields['customer_name'].label = "Customer Name"
        self.fields['customer_name'].queryset = Customer.objects.filter(username=user)

    def clean(self):
        cleaned_data = super(BookingForm,self).clean()
        booking_number = f"{cleaned_data.get('delivery_date'):%Y%m%d}{cleaned_data.get('delivery_time'):%H%M}"
        if Booking.objects.filter(booking_number=booking_number).exists():
            raise forms.ValidationError("Requested slot is already booked, please choose another time")

    class Meta:
        model = Booking
        fields = ('customer_name','qty_plts','cbm','delivery_date','delivery_time')
        widgets = {'delivery_date':DatePickerInput(options={"daysOfWeekDisabled":[0,6],"minDate":timezone.now().date().strftime('%Y-%m-%d')}),
                    'delivery_time':forms.Select(choices=HOUR_CHOICES)}

views.py

from django.shortcuts import render

# Create your views here.
from .models import Booking,Customer
from .forms import BookingForm
from django.urls import reverse,reverse_lazy
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import (ListView,DetailView,CreateView,UpdateView,DeleteView,TemplateView)

class BookingCreate(LoginRequiredMixin,CreateView):
    login_url = '/login'
    redirect_field_name = 'bookmyslot/booking_detail.html'
    model = Booking
    form_class = BookingForm

    def get_form_kwargs(self, **kwargs):
        form_kwargs = super(BookingCreate,self).get_form_kwargs(**kwargs)
        form_kwargs['username'] = self.request.user
        return form_kwargs

    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.username = self.request.user
        self.object.save()
        return super().form_valid(form)

Я пытаюсь понять, как вернуть список доступных временных интервалов, используя ValidationError. Есть 6 временных слотов [время_доставки] на выбор на любую дату -> [7,8,9,10,11,12]. Поле booking_number - это уникальный идентификатор, представляющий собой конкатенацию delivery_date и delivery_time, который генерируется каждый раз, когда пользователь успешно создает бронирование.

Допустим, есть 3 существующих бронирования на 2021-10-21 на 7:00, 08:00 и 10:00, которые сохранены в модели Booking со следующими номерами бронирования: 202110210700 202110210800 202110211000

Предположим, пользователь пытается забронировать уже существующий слот, например, 202110210700, ошибка валидации должна вернуть "Запрашиваемый слот уже забронирован, пожалуйста, выберите другой из этих доступных слотов": 09:00 11:00 12:00

Как я могу этого достичь?

Для возврата оставшегося временного интервала при валидации формы можно использовать утилитарную функцию вроде этой :

app_name/utilities.py

def list_diff(l1: list, l2: list):
    """ Return a list of elements that are present in l1
        or in l2 but not in both l1 & l2.
        IE: list_diff([1, 2, 3, 4], [2,4]) => [1, 3] 
    """
    return [i for i in l1 + l2 if i not in l1 or i not in l2]


def check_free_time(time_slot: list, exist_list: list):
    """ Return the list of available time slot if exist,
        according to a given exist slot list.
        Return the remained time slot, or empty list if all are used
        IE: ([7, 12], [7, 8, 9, 10, 11, 12]) => [8, 9, 10, 11]
    """

    remain_slot = list_diff(time_slot, exist_list)
    return remain_slot

Теперь импортируйте check_free_time в ваш forms.py файл и используйте его, если booking_number существует.

from datetime import datetime
from .utilities import check_free_time

if Booking.objects.filter(booking_number=booking_number).exists():
    today = datetime.today()
    d = today.day
    m = today.month
    y = today.year
    
    # Retrieve today's bookings
    today_bookings = Booking.objects.filter(delivery_date__year=y,delivery_date__month=m delivery_date__day=d)

    # A list of today's bookings time slot (take only hours)
    # Return something like <QuerySet [{'delivery_date__hour': 11}, ...]>
    today_time_slot = today_bookings.values('delivery_date__hour')
    # Convert it to list of hours values since the utility function accept list.
    today_time_slot_list = [h['delivery_date__hour'] for h in list(today_time_slot)]
    # The line above return something like [9, 11, ...]
    
    all_time_slot = [7, 8, 9, 10, 11, 12]

    # Now we can call the utility function `check_free_time`
    available_slot = check_free_time(all_time_slot, today_time_slot_list)
    if available_slot:  # The are some available slot (list not empty)
        # I use python3.6 f-string to format the message
        # Note that the list is in a raw format ([8,11,12]), you can do better like ['8h:00', '11h:00', '12h:00']
        message = f"Requested slot is already booked, please choose another time in {available slot}."
        raise forms.ValidationError(message)
    else:  # The list is empty, all slot are taken
        message = "The are not available slot for this booking today."
        raise forms.ValidationError(message)

NB : Я тестировал только в оболочке python и Django, если есть ошибки, попробуйте добавить в комментарии.

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