Search code examples
djangodjango-viewsdjango-forms

__init__() got an unexpected keyword argument 'id'


It's my first time asking question here :) I'm a beginner with Django and I've been stuck on my edit_booking method for days, I don't even know what I'm doing wrong anymore... I'm making a booking system and I'm getting a "TypeError at /edit-booking/6 __init__() got an unexpected keyword argument 'id'". I've been trying a lot of things to make editing the exact booking possible, but they only cause more errors...

Views:

class EditBooking(DetailView):

    def __init__(self):    
        template_name = 'edit-profile.html'        
        initial = {}

    @login_required
    def edit_booking(request, id):
        booking = get_object_or_404(Booking, id=id)
        if request.method == 'POST':
            form = BookingForm(request.POST, instance=booking)
            customer_data = UserProfile.objects.all()
            if form.is_valid():
                instance = form.save(commit=False)
                instance.customer_data = customer_data
                instance.save()
                messages.success(request, 'Thank you! Your booking has been updated!')
            else:
                return messages.error(request, form.errors)
        else:
            form = BookingForm(initial={
                'booking_date': booking.booking_date,
                'booking_time': booking.booking_time,
                'table_size': booking.table_size,
                'additional_info': booking.additional_info,
            })
        return render(request, template_name, {'form': form})

Templates:

profile.html button triggering edit page:

<a href="{% url 'editbooking' id=booking.id %}" aria-label="edit booking">
    <button type="button" class="btn btn-secondary btn-brown px-5">Edit</button>
</a>

edit_booking.html:

<form method="POST" action="{% url 'editbooking' id=booking.id %}" 
class="col-6">                                  
    {% csrf_token %}
    {% crispy form %}
</form>

Urls:

path('edit-booking/<int:id>', views.EditBooking, name='editbooking'),

Models:

class Booking(models.Model):

    booking_customer = models.ForeignKey(UserProfile, on_delete=models.CASCADE, null=True)
    booking_date = models.DateField(default=datetime.date.today)
    booking_time = models.CharField(choices=TIME_SLOTS, default='8:00 - 8:30', max_length=50)
    table_size = models.CharField(choices=TABLE_SIZE, default='1', max_length=50)
    additional_info = models.TextField(max_length=400, null=True, blank=True)
    booked_on = models.DateTimeField(auto_now_add=True)
    is_confirmed = models.CharField(choices=CONFIRMATION, default='Awaiting confirmation', max_length=50)
    slug = AutoSlugField(max_length=70, unique=True, null=True)


    class Meta:
        ordering = ["booked_on"]
        

    def __str__(self):

        return f'Booking for {self.booking_date} at {self.booking_time} was booked on {self.booked_on} and currently has a status: {self.is_confirmed}'

Forms:

class BookingForm(ModelForm):
    """ 
    Provides necessary fields to the booking form for customers
    """

    def __init__(self, *args, **kwargs):

        super().__init__(*args, **kwargs)

        # Crispy form helpers
        self.helper = FormHelper()
        self.helper.form_method = 'post'        
        self.helper.add_input(Submit('submit', 'Submit', css_class='btn btn-secondary btn-brown mb-4 mx-auto px-5'))        

    # Provides a date widget to the form 
    booking_date = forms.DateField(widget=forms.DateInput(attrs={'class':'form-control', 'type':'date', 'value': datetime.date.today}), required=False)
    booking_time = forms.ChoiceField(choices=TIME_SLOTS, required=False)
    table_size = forms.ChoiceField(choices=TABLE_SIZE, required=False)
    additional_info = forms.CharField(max_length=400, widget=SummernoteWidget(), required=False)
    booked_on = forms.DateTimeField(initial=datetime.datetime.now, widget=forms.HiddenInput(), required = False)
    slug = AutoSlugField(max_length=70, unique=True, populate_from=lambda instance: instance.title,
                         unique_with=['booked_on', 'booking_date'],
                         slugify=lambda value: value.replace(' ','-')) 


    # Provides a model to pull the fields from
    class Meta:
        model = Booking
        fields = ['booking_date', 'booking_time', 'table_size', 'additional_info']
        read_only = ['booked_on', 'slug']


    # Prevents booking dates in the past
    def save_booking(self, *args, **kwargs):

        data = self.cleaned_data
        if data.get('booking_date') < datetime.date.today():        
            raise ValidationError("The date cannot be in the past!")
        else:
            super(Booking, self).save(*args, **kwargs)

I was trying to use slug, pk and id to get the exact booking, I changed the urls and templates to display the id, used Booking.objects.get() and swapped it in the end with get_object_or_404, getting rid of init etc... I've been searching through similar questions here, but none of the solutions that i've tried worked for me really :(


Solution

  • There are several problems here. First of all, you register a class-based view with .as_view() which essentially wraps the class into a function that handles requests, so:

    path('edit-booking/<int:id>', views.EditBooking.as_view(), name='editbooking'),

    Likely you now get an error about the template_name and the model not being specified, which makes sense: you didn't define it, and tried to define it in the constructor. Your view also looks a lot like an UpdateView [Django-doc], not that much a DetailView. You can see an UpdateView as a DetailView, but with a form to allow editing. This will also populate the form already with the instance in the GET request, which probably makes it more convenient.

    We can use the LoginRequiredMixin [Django-doc] and SuccessMessageMixin [Django-doc] to check if the user has logged in and also already encode a message when the item was successful:

    What makes not much sense is the .customer_data = customer_data: you don't store that in your model object. You can set the .booking_customer with the UserProfile of the logged in user, but that depends on the UserProfile model, so you probably have to revise the form_valid() method overload.

    Finally a successful POST request normally results in a redirect to some path, you set that by the success_url:

    from django.contrib.auth.mixins import LoginRequiredMixin
    from django.contrib.messages.views import SuccessMessageMixin
    from django.views.generic import UpdateView
    
    
    class EditBooking(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
        model = Booking
        form_class = BookingForm
        template_name = 'edit-profile.html'
        success_message = 'Thank you! Your booking has been updated!'
        success_url = '/path/to/some/view'
    
        # no __init__
    
        def form_valid(self, form):
            form.instance.booking_customer = UserProfile.objects.get(
                user=self.request.user
            )
            return super().form_valid(form)