Search code examples
djangopython-3.xajaxdjango-formsajaxform

Django Python Forms - submitting complex view with multiple forms and formsets


Imagine this concept, I have a Taxi that can be ordered by a group for a full day multiple visits, and I should assign a group leader for each booking. now I have a Booking (PNR) that holds Clients traveling Routes, and a Group Leader (Operator) assigned for that booking.

my view holds these:

  1. form for selecting the operator
  2. formset to add clients

in this view I'm trying to make it easier for the user by giving the ability to save each form separately by ajax or save all data of forms by a button at the bottom of the view.

I've been searching for a few days and I got the nearest approach on these two linkes 1 & 2 but still can't make my code run correctly and do what it's supposed to do. :( ANY SUPPORT WILL BE HIGHLY APPRECIATED!

My models.py:

class Operator (models.Model):
    name = models.CharField(max_length=50)
    # Other Fields

    def __str__(self):
        return self.code


class PNR (models.Model):
    date_created = models.DateTimeField(auto_now_add=True)
    # Other Fields

    def __str__(self):
        return self.pk


class Client (models.Model):
    related_pnr = models.ForeignKey(PNR, on_delete=models.CASCADE)
    name = models.CharField(max_length=200)
    # Other Fields

    def __str__(self):
        return self.related_pnr+" "+self.name

My forms.py:

class ChooseOperatorCode(forms.Form):
    operator = forms.ModelChoiceField(queryset=Operator.objects.all())

    def clean(self, *args, **kwargs):
        operator = self.cleaned_data.get('operator')

        return super(ChooseOperatorCode, self).clean(*args, **kwargs)


class NewClientForm(forms.Form):
    name = forms.CharField(widget=forms.TextInput(attrs={}), label=False, max_length=200)
    # Other Fields

    def clean(self, *args, **kwargs):
        name = self.cleaned_data.get('name')
        # Other Fields

        return super(NewClientForm, self).clean(*args, **kwargs)

My views.py:

@login_required
def create_pnr(request):

    pnr = PNR.objects.create(created_by_user=request.user)

    choose_operator_form = ChooseOperatorCode(request.POST or None)
    if choose_operator_form.is_valid():
        pnr.created_by_operator = choose_operator_form.cleaned_data.get('operator')
        pnr.save()
        choose_operator_form.save()

    clients_form = NewClientForm(request.POST or None)
    if clients_form.is_valid():
        client = Client()
        client.related_pnr = pnr.pk
        client.name = clients_form.cleaned_data.get('name')
        client.save()
        clients_form.save()
    context = {
        'pnr': pnr.pk,
        'choose_operator_form': choose_operator_form,
        'clients_form': clients_form,
    }
    return render(request, 'reservation_new.html', context)


@login_required
def edit_pnr(request, pnr_id):
    pnr = PNR.objects.get(id=pnr_id) 

    choose_operator_form = ChooseOperatorCode(request.POST or None)
    if choose_operator_form.is_valid():
        pnr.created_by_operator = choose_operator_form.cleaned_data.get('operator')
        pnr.save()

    clients_form = NewClientForm(request.POST or None)
    if clients_form.is_valid():
        client = Client()
        client.related_pnr = pnr.pk
        client.name = clients_form.cleaned_data.get('name')
        client.save()
        clients_form.save()
    context = {
        'pnr': pnr.pk,
        'choose_operator_form': choose_operator_form,
        'clients_form': clients_form,
    }
    return render(request, 'reservation_edit.html', context)

My url.py:

    path('operation/reservation/new/', views.create_pnr, name='new_pnr'),
    path('operation/reservation/existing/<int:pnr_id>/', views.edit_pnr, 
       name='existing_pnr'),

And Finally my template.html: (for both new and edit)

<form  method="POST" action="{% url 'existing_pnr' pnr_id=pnr %}" id="choose_operator_form">
     {% csrf_token %}
     {{choose_operator_form}}
</form>
<form  method="POST" action="{% url 'existing_pnr' pnr_id=pnr %}" id="clients_form">
     {% csrf_token %}
     {{clients_form}}
</form>
<script type="javascript">
    $(document).('submit', '#choose_operator_form', function(e){
        e.preventDefault();

        $.ajax({
            type:'POST',
            url:"{% url 'existing_pnr' %}",
            data: $('#choose_operator_form').serialize(),
            success: function (result) {
                // show success msg
            },
            error: function (data) {
                // show error msg
            }
        });
    });

    //same code for clients form
</script>

Solution

  • The pnr doesn't get added to the request object, so request.pnr makes no sense. You need to pass the id of the PNR through the URL, that way it can be accessed in the view:

    • In urls.py, make sure that the url for your edit_pnr view gets the pnr_id: something like path('pnr/<int:pnr_id>/edit/', ..., name="existing_pnr").
    • In your template, construct the url for editing a pnr like this: {% url 'existing_pnr' pnr_id=pnr %}
    • In your view, receive the pnr_id: def edit_pnr(request, pnr_id): ...

    Now you can fetch the PNR that's being edit like this: pnr = get_object_or_404(PNR, pk=pnr_id) which will correctly return a 404 Not Found if someone tries to access a non-existing PNR.