creating a django form in formset dynamically like

2019-07-27 09:02发布

I have a two models Publisher and Book like below

models.py

class Publisher(models.Model):
    name = models.CharField(max_length=255)

class Book(models.model):
    name = models.CharField(max_length=255)
    price = models.DecimalField()
    generic = generic.GenericForeignKey()
    publisher_id = models.PositiveIntegerField()

forms.py

class PublisherForm(ModelForm):
    model = Publisher

class BookForm(ModelForm):
    model = Book
    exclude = ('generic', 'publisher_id',)

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

        super(BookForm, self).__init__(*args, **kwargs)
        self.fields['name'].widget.attrs = {'id':'inputId', 'class':'input-block-level, 'placeholder':'Name'}
        self.fields['name'].error_messages = {'required': 'Please enter name'}

        self.fields['age'].widget.attrs = {'id':'inputId', 'class':'input-block-level, 'placeholder':'Age'}
        self.fields['age'].error_messages = {'required': 'Please enter age'}  

views.py

Here in this view i will send the publisher id, because Book model does not had a foreign Key to Publisher model

from .forms import BookForm

@login_required
def create_multiple_books(request, publisher_id):
    class RequiredFormSet(BaseFormSet):
        def __init__(self, *args, **kwargs):
            super(RequiredFormSet, self).__init__(*args, **kwargs)
            for form in self.forms:
                form.empty_permitted = False

    BookFormset = formset_factory(BookForm, max_num=10, formset=RequiredFormSet)
    if request.method == 'POST':
        book_formset = BookFormset(request.POST, request.FILES)
        if book_formset.is_valid():
            for form in book_formset.forms:
                obj = form.save(commit=False)
                obj.publisher_id = publisher_id
                obj.save()
            return redirect(reverse('created'))
    else:
        book_formset = BookFormset()            
    c = {'book_formset': book_formset,
         'publisher_id':publisher_id,
        }
    c.update(csrf(request))
    return render_to_response('add_books.html',c,context_instance = RequestContext(request))  

template.html

So in the below template rendered the forms as form.as_p its working fine and multiple records are creating to that publisher id successfully

<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js"></script>

<script type="text/javascript">
$(document).ready(function() {
  // Code adapted from http://djangosnippets.org/snippets/1389/

  function updateElementIndex(el, prefix, ndx) {
    var id_regex = new RegExp('(' + prefix + '-\\d+-)');
    var replacement = prefix + '-' + ndx + '-';
    if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex,
 replacement));
    if (el.id) el.id = el.id.replace(id_regex, replacement);
    if (el.name) el.name = el.name.replace(id_regex, replacement);
  }

  function deleteForm(btn, prefix) {
    var formCount = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());

    if (formCount > 1) {
      // Delete the item/form
      $(btn).parents('.item').remove();

      var forms = $('.item'); // Get all the forms

      // Update the total number of forms (1 less than before)
      $('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);

      var i = 0;
      // Go through the forms and set their indices, names and IDs
      for (formCount = forms.length; i < formCount; i++) {
        $(forms.get(i)).children().children().each(function() {
          updateElementIndex(this, prefix, i);
        });
      }

    } // End if
    else {
        alert("You have to enter at least one todo item!");
    }
    return false;
  }


  function addForm(btn, prefix) {
    var formCount = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());

    // You can only submit a maximum of 10 todo items 
    if (formCount < 10) {
      // Clone a form (without event handlers) from the first form
      var row = $(".item:first").clone(false).get(0);
      // Insert it after the last form
      $(row).removeAttr('id').hide().insertAfter(".item:last").slideDown(300);

      // Remove the bits we don't want in the new row/form
      // e.g. error messages
      $(".errorlist", row).remove();
      $(row).children().removeClass('error');

      // Relabel/rename all the relevant bits
      $(row).children().children().each(function() {
        updateElementIndex(this, prefix, formCount);
        if ( $(this).attr('type') == 'text' )
          $(this).val('');
      });

      // Add an event handler for the delete item/form link 
      $(row).find('.delete').click(function() {
        return deleteForm(this, prefix);
      });

      // Update the total form count
      $('#id_' + prefix + '-TOTAL_FORMS').val(formCount + 1); 

    } // End if
    else {
      alert("Sorry, you can only enter a maximum of ten items.");
    }
    return false;
  }

  // Register the click event handlers
  $("#add").click(function() {
    return addForm(this, 'form');
  });

  $(".delete").click(function() {
    return deleteForm(this, 'form');
  });


});
</script>
</head>
<body>
<form action="" method="POST">{% csrf_token %}
    <div class="section">
        {{ todo_list_form.as_p }}
    </div>

    <h2>Todo Items</h2>
    {{ book_formset.management_form }}
    {% for form in book_formset.forms %}
    <div class="item">
      {{ form.as_p }}
      <p style=""><a class="delete" href="#">Delete</a></p>
    </div>
    {% endfor %}

    <p><a id="add" href="#">Add another item</a></p>

    <input type="submit" value=" Submit " />

</form>
</body>
</html>

But when i display the html fields from the form and render like below

 {% for form in book_formset.forms %}
        <div class="item"> 
             <div class="with_name_design">{{ form.name }}</div>
             {% if form.name.errors %}
                  {{form.name.errors}}
             {% endif %}  
             <div class="with_age_design">{{ form.age }}</div> 
             {% if form.age.errors %}
                  {{form.age.errors}}
             {% endif %}    
         </div>
{% endfor %}     

The form is displaying succesfully and when i clicked on the link Add another item a new form is generating with above jquery, and when i tried to submit by entering all the details and clicked on submit,the next form which was added by the jquery is displaying validation errors like name, age is required ?(This is happening only in this case that is displaying the fields seperately instead of form.as_p(), and if we render as form.as_p() its working fine and records are creating in to database)

So i really could n't able to figure out why it is succeded when i rendered the form as form.as_p() and why not when i rendered individual fields with their errors

Am i missing anything/need to and anything in the above javascript code that generates additional form?

because when we render the fields individually, the form generated by clicking on the Add another form button is displaying validation errors ?

I really wasted a lot of time in figuring out the above javascript, as i got it some where by googling around,

So finally the above functionlality is working when we render the formset forms as form.as_p(), but why the above functionality is not working when we render the form fields individually ?

Can anyone please let me know how to solve the above issue(Also may be above code will be useful form many users to create the forms dynamically like we have inline forms in django admin)

Edit

K thanks schillingt,

So according to ur answer below the have i modified the javascript, and html like below

 {% for form in book_formset.forms %}
        <div class="item"> 
             <div class="with_name_design">{{ form.name }}</div>
             {% if form.name.errors %}
                 <span>{{form.name.errors.0}}</span>
             {% endif %}  
             <div class="with_age_design">{{ form.age }}</div> 
             {% if form.age.errors %}
                  <span>{{form.age.errors.0}}</span>
             {% endif %}    
         </div>
{% endfor %} 

and form has been rendered with errors after form validation

But i am facing the different issue as below

  1. When we click on Add another item button, a new form has been creating successfully.
  2. And when we submitted the forms with empty data, the validation error messages are displayed correctly below the respective fields

Issue one

  1. And now when we try to add another form, all the previous forms including error messages are redisplaying again

like if we have two forms and when we click on submit without data, the validation error messages are generating for both the forms, and now immediately when we click on Add another item, totally four forms have been created, i mean the previously created two forms are repeated including the validation messages

Issue two

  1. So now immediately when we try to delete a form, i mean when we click on delete form button, all the forms(like 4 forms) in this case are deleted ?

So how can you please let me know how to solve this ?

1条回答
来,给爷笑一个
2楼-- · 2019-07-27 09:49

The problem is that the code to fetch all of the elements to change the prefix counter aren't actually selecting any elements.

Change in deleteForm:

forms.get(i)).children().children().each

to:

forms.get(i)).find('input,select,textarea').each

and change in addForm:

$(row).children().children().each

To:

$(row).find('input,select,textarea').each

This will select all of the elements that would be included in the form on the POST to the server.

查看更多
登录 后发表回答