Reputation: 1297
I am creating a car reservation application in Django. Users can create a Reservation for a given Car. This happens at /reservation/<car_id>/add
. This form does not contain the Car field because the id is given in the URL. Now I want to add a validation to the Reservation model so no reservations can overlap. I have the following code (minimal version):
# models.py
class Car(models.Model):
name = models.CharField(max_length=200)
class Reservation(models.Model):
car = models.ForeignKey(Car, on_delete=models.CASCADE)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
start_time = models.DateTimeField()
end_time = models.DateTimeField()
def clean(self):
# Check if overlaps with other reservations in self.car.reservation_set
# views.py
class ReservationAdd(LoginRequiredMixin, CreateView):
template_name = 'reservation/reservation_add.html'
model = Reservation
form_class = ReservationAddForm
def form_valid(self, form):
form.instance.car = Car.objects.get(pk=self.kwargs['car_id'])
form.instance.owner = self.request.user
return super().form_valid(form)
# forms.py
class ReservationAddForm(forms.ModelForm):
class Meta:
fields = ('start_time', 'end_time')
model = Reservation
Now I get the following error at the Reservation.clean
method:
reservation.models.Reservation.car.RelatedObjectDoesNotExist: Reservation has no car.
It seems to me that the clean
method gets called before the form_valid
method. So my design is not entirely correct I think. What is the correct way to do this?
Upvotes: 2
Views: 2433
Reputation: 1623
I think the better approach is
class ReservationAdd(LoginRequiredMixin, CreateView):
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["car"] = Car.objects.get(pk=self.kwargs['car_id'])
return kwargs
and the model form override the __init__
method
class ReservationAddForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.car = kwargs.pop("car")
super().__init__(*args, **kwargs)
def clean(self):
# cleaned data
cleaned_data = super().cleaned_data()
# access the car instance using self.car
# to do validation
return cleaned_data
Upvotes: 1
Reputation: 599630
Yes. form_valid
means "code to run once the form is confirmed as valid".
You need to make a couple of changes. Firstly, move your validation to the ReservationAddForm itself; and secondly, pass the Car instance into that form. So:
class ReservationAddForm(forms.ModelForm):
class Meta:
fields = ('start_time', 'end_time')
model = Reservation
def __init__(self, *args, **kwargs):
self.car = kwargs.pop('car')
super().__init__(*args, **kwargs)
def clean(self):
... do something with self.car
class ReservationAdd(LoginRequiredMixin, CreateView):
template_name = 'reservation/reservation_add.html'
model = Reservation
form_class = ReservationAddForm
def get_form_kwargs(self):
self.car = Car.objects.get(pk=self.kwargs['car_id'])
kwargs = super().get_form_kwargs()
kwargs['car'] = self.car
return kwargs
def form_valid(self, form):
form.instance.car = self.car
form.instance.owner = self.request.user
return super().form_valid(form)
Upvotes: 6