Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Ajax Submission with validation and multiple forms handling

In the Django admin interface there is the nice ability to dynamically add new items to foreign key fields and i want to make a similar one using bootstrap modal for popup window and Ajax for form submission and validation.

This is my use case :

This is the Main form for adding Item. Item have a ref and a category.

enter image description here

And this is the second form for adding a new category.

enter image description here

I have no problem with showing the modal and submission the form to add new category. Instead the problem is with the form validation (in case the user submit an empty form), and with refreshing the select content to add the new added category.

This is my code:

forms.py

class ItemForm(forms.ModelForm):
    ref = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}),max_length=255)
    category =  forms.ModelChoiceField(queryset=ItemCategory.objects.all(), empty_label="(choose from the list)")


class ItemCategoryForm(forms.ModelForm):
    category = forms.CharField(
        max_length=255,
        required=True,
        help_text='Add a new category')

views.py

def add(request):
    if request.method == 'POST':
        form = ItemForm(request.POST)
        if form.is_valid():
            item= Item()
            item.ref = form.cleaned_data.get('ref')
            item.save()
            return redirect('/item_list/')
    else:
        form = ItemForm()
        form1 = ItemCategoryForm()
    return render(request, 'item/add.html', {'form': form, 'form1':form1}) 

def add_category(request):
    if request.method == 'POST':
        form1 = ItemCategoryForm(request.POST)
        if form1.is_valid():
            vulnCategory = ItemCategory()
            ItemCategory.category = form1.cleaned_data.get('category')
            ItemCategory.save()          
     if request.is_ajax():
                #TODO: What Should I redirect
            else:
                #TODO: What Should I redirect
    else:
       #TODO: What Sould I do to return errors without reloding the page and to refresh the list of categories

urls.py

url(r'^add/$', 'add', name='add'),
url(r'^add_category/$', 'add_category', name='add_category'),

And I have also added this jQuery function to load the result

$(".add").click(function () {
$.ajax({
  url: '/items/add_category/',
  data: $("form").serialize(),
  cache: false,
  type: 'post',
  beforeSend: function () {
    $("#add_category .modal-body").html("<div style='text-align: center; padding-top: 1em'><img src='/static/img/loading.gif'></div>");
  },
  success: function (data) {
    $("#add_category .modal-body").html(data);
  }
});
});

PS: I know that it may be duplicated, but non of the answers get me to the point.

like image 296
imanis_tn Avatar asked Jul 24 '14 11:07

imanis_tn


3 Answers

Below the solution that i have make for inline adding a related Category for an item.

Using the same forms, urls as the question and adding

The view for adding category

@login_required 
def add_category(request):
      data = {}
     if request.method == 'POST' :
         form = ItemCategoryForm(request.POST)
         if form.is_valid():
             itemCategory= ItemCategory()
             itemCategory.name = form.cleaned_data.get('name')
             itemCategory.save()
             data['new_itemcategory_value'] =  itemCategory.name;
             data['new_itemcategory_key'] =  itemCategory.pk;
             data['stat'] = "ok";
             return HttpResponse(json.dumps(data), mimetype="application/json")
         else:
             data['stat'] = "error";
             return render(request, 'item/add_category_modal.html', {'form': form})
     else:
         form = ItemCategoryForm()
         return render(request, 'item/add_category_modal.html', {'form': form})

Javascript Code

The tricky part of my solution was separating the modal and the man form in two different html files, and handling the adding and selecting of the new item using jQuery.

This Js code has to be included in the two html file:

// This function is for showing the modal 
 $(function () {

     $(".add_category_show_button").click(function () {
         $.ajax({
             type: 'GET',
             url: '/item/add_category/',
             data: $("form").serialize(),
             cache: false,
             success: function (data, status) {
                 $('#add_category_modal_id').html(data);
                 $('#add_category_modal_id').modal()
             }
         });
     }); });

// This second function is for submitting the form inside the modal and handling validation

 $(function () {

     $(".add_category_submit_button").click(function () {
         $.ajax({
             type: 'POST',
             url: '/item/add_category/',
             data: $("form").serialize(),
             cache: false,
             success: function (data, status) {
                 if (data['stat'] == "ok") {
                     $('#add_category_modal_id').modal('hide');
                     $('#add_category_modal_id').children().remove();
                     $('#id_category')
                         .append($("<option></option>")
                             .attr("value", data['new_itemcategory_key'])
                             .text(data['new_itemcategory_value']))
                         .val(data['new_itemcategory_key']);
                 }
                 else {
                     $('#add_category_modal_id').html(data);
                     $('#add_category_modal_id').modal('show');
                 }
             }
         });
     }); });
like image 55
imanis_tn Avatar answered Nov 09 '22 07:11

imanis_tn


This is how I've done it in the past. Note this is a very abridged version and assumes it's all ajax requests to give you an idea of what you could do. You should be able to expand fron here.

if form.is_valid():
  do_form_work()
  # Compile a list of lists (or tuples) of the categories
  # e.g. [[x.pk, x.name] for x in categories]
  categories = get_categories()
  json = json.dumps(categories)
  return HttpRepsonse(json, {'content_type' : 'application/json'})
else:
  # 'template' here is a partial template of just the form
  render(self.request, template, context, status=500)

If the form isn't valid you can use the 'error' method on the ajax post to catch the 500 code, and re-display the form with all the form errors. That way you can keep the form and modal open so the user can correct things

If the form is valid, you can take the returning json and re-build your options in the select list.

like image 3
Esteban Avatar answered Nov 09 '22 06:11

Esteban


The ModelChoiceField validates the choice in the form against an object and if it is empty, the form will not validate. You can debug this by using {{ form.non_field_errors }} and {{ field.errors }} to point out exactly why the form isn't getting validated.

As a suggestion, when I had a similar use case I used Dajax and Dajaxice and they worked wonderfully for me. I used ChoiceFields instead but they work even with ModelChoiceFields.

Here's an example with ChoiceField and Form Submission

like image 1
Abhinav Nair Avatar answered Nov 09 '22 06:11

Abhinav Nair