I have trouble figuring out how to create nested forms. I have two models: Poll and Question (foreign key for Poll is inside Question model). I want it to be possible to add several questions for the poll dynamically inside one form. I've already tried making it work with inlineform_factory, but found troubles saving the form. Right now I have overcomplicated code that throws MultiValueDictError after saving the poll for the second time. I thought maybe there is some other way. Here's my code
models.py
class Question(models.Model):
type_choices = (
('text', 'Текстовый'),
('integer', 'Числовой'),
)
type = models.CharField(verbose_name=u'Тип вопроса', choices=type_choices, max_length=70)
text = models.CharField(verbose_name=u'Текст вопроса', max_length=255)
poll = models.ForeignKey('Poll', on_delete=models.CASCADE, related_name='questions')
class Poll(BaseFields):
objects = models.Manager()
published = PublishedManager()
title = models.CharField(verbose_name=u'Заголовок', max_length=70)
date_from = models.DateTimeField(u'Дата начала')
date_to = models.DateTimeField(u'Дата окончания')
status = models.IntegerField(choices=Status.choices, default=0)
def status_name(self):
return dict(Status.choices).get(self.status)
def update_url(self):
return reverse('poll_update', args=(self.id, ))
forms.py
QuestionInlineFormSet = inlineformset_factory(Poll, Question, extra=1, can_delete=False,
fields=('type', 'text', 'poll'),
widgets={
'type': w.Select(attrs={'class': 'form-control'}),
'text': w.TextInput(attrs={'class': 'form-control'}),
})
class PollForm(CommonForm):
class Meta(CommonForm.Meta):
model = Poll
views.py
class Create(BaseCreateView):
template_name = 'back/poll/poll.html'
page_title = 'Создание опроса'
model = Poll
form_class = PollForm
def form_valid(self, form):
result = super(Create, self).form_valid(form)
questions_formset = QuestionInlineFormSet(data=form.data, instance=self.object, prefix='questions_formset')
if questions_formset.is_valid():
questions_formset.save()
return result
def get_context_data(self, **kwargs):
context = super(Create, self).get_context_data(**kwargs)
context['questions_formset'] = QuestionInlineFormSet(prefix='questions_formset')
return context
class Update(BaseUpdateView):
template_name = 'back/poll/poll.html'
page_title = 'Редактирование опроса'
model = Poll
form_class = PollForm
def form_valid(self, form):
result = super(Update, self).form_valid(form)
questions_formset = QuestionInlineFormSet(data=form.data, instance=self.object, prefix='questions_formset')
if questions_formset.is_valid():
questions_formset.save()
return result
def get_context_data(self, **kwargs):
context = super(Update, self).get_context_data(**kwargs)
context['questions_formset'] = QuestionInlineFormSet(instance=self.get_object(), prefix='questions_formset')
return context
poll.html
{% extends 'back/base/form/base.html' %}
{% block form %}
{% include "back/includes/status_buttons.html" %}
<div class="row">
<div class="col-md-7">
<div class="row" style="margin-bottom: 15px;">
<div class="col-md-12">
{% include 'back/includes/status_label.html' %}
</div>
</div>
{% include "back/base/form/field.html" with field=form.title %}
{% include "back/base/form/datetime_field.html" with field=form.date_from %}
{% include "back/base/form/datetime_field.html" with field=form.date_to %}
{{ questions_formset.management_form }}
{% for question_form in questions_formset %}
{% include "back/poll/question.html" with form=question_form %}
{% endfor %}
<div style="float: right; margin-right: 45px">
<button class="btn btn-success add-form-row">Добавить вопрос</button>
</div>
</div>
</div>
{% endblock %}
question.html
<div class="questions">
<div class="row form-row">
<div class="form-group col-md-5{% if form.type.errors %} has-error{% endif %}">
<label>{{ form.type.label }}</label>
{{ form.type }}
<p class="help-block">{{ form.type.errors }}</p>
</div>
<div class="form-group col-md-5{% if form.text.errors %} has-error{% endif %}">
<label>{{ form.text.label }}</label>
{{ form.text }}
<p class="help-block">{{ form.text.errors }}</p>
</div>
<div class="col-md-2" style="margin-top: 25px">
<button type="button" class="btn btn-danger remove-form-row">✕</button>
</div>
</div>
</div>
<script>
function updateElementIndex(el, prefix, ndx) {
let id_regex = new RegExp('(' + prefix + '-\\d+)');
let 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(prefix, btn) {
let total = parseInt($('#id_' + prefix + '_formset' + '-TOTAL_FORMS').val());
if (total > 1) {
btn.closest('.form-row').remove();
let forms = $('.form-row');
$('#id_' + prefix + '_formset' + '-TOTAL_FORMS').val(forms.length);
for (let i = 0, formCount = forms.length; i < formCount; i++) {
$(forms.get(i)).find(':input').each(function () {
updateElementIndex(this, prefix, i);
});
}
}
}
function cloneMore(selector, prefix) {
let newElement = $(selector).clone(true);
let total = $('#id_' + prefix + '_formset' + '-TOTAL_FORMS').val();
newElement.find(':input:not([type=button]):not([type=submit]):not([type=reset])').each(function () {
let name = $(this).attr('name').replace('-' + (total - 1) + '-', '-' + total + '-');
let id = 'id_' + name;
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
});
newElement.find('label').each(function () {
let forValue = $(this).attr('for');
if (forValue) {
forValue = forValue.replace('-' + (total - 1) + '-', '-' + total + '-');
$(this).attr({'for': forValue});
}
});
total++;
$('#id_' + prefix + '_formset' + '-TOTAL_FORMS').val(total);
$(selector).after(newElement);
}
$(document).on('click', '.add-form-row', function (event) {
event.preventDefault();
cloneMore('.form-row:last', 'questions');
})
$(document).on('click', '.remove-form-row', function (event) {
event.preventDefault();
deleteForm('questions', $(this));
});
</script>
Here's a photo of what it should look like
Again, I want it to be possible to add questions, remove them, save the form. And after I click on the poll in the list of all polls and go to it's edit page I want already added questions to be there.
UPDATE
Found solution to MultiValueDictKeyError in this post
How to debug a Django MultiValueDictKeyError on Formset POST
Now the only problem I have is removing Questions from Poll. My delete button works, it removes the form from page, but after saving the document it reappears.
Related
I have a view to update 6 of my formset only that the click of the send button gives me the error that the id of each form is missing ... how do you fix it?
When I have to create formset there are never problems with the id...
Can anyone tell me where I'm wrong? I leave my code below
view
#login_required
def PianoSingleUpdateView(request, id):
piano = models.Piano.objects.get(single_piano__id = id)
piano_sett = models.PianoSingleDay.objects.get(id = id)
dato = models.PianoDay.objects.filter( piano_day = piano_sett)
DatiFormSet = modelformset_factory(models.PianoDay, extra = 0, fields=('id', 'kcal', 'carboidrati', 'proteine', 'grassi'))
if request.method == 'POST':
dati_formset = DatiFormSet(request.POST, request.FILES, queryset = dato)
if dati_formset.is_valid():
for dato in dati_formset:
dato.save()
return redirect('gestione-piano', id = piano.id)
else:
dati_formset = DatiFormSet(queryset = dato)
context = {'piano': piano, 'piano_sett': piano_sett, 'dati_formset': dati_formset}
return render(request, 'crud/update/update_piano_giornaliero.html', context)
models
class Piano(models.Model):
nome_piano = models.CharField(max_length=100)
data_inizio = models.DateField()
data_fine = models.DateField()
utente_piano = models.ForeignKey(
User,
on_delete = models.CASCADE,
related_name = 'utente_piano'
)
def __str__(self):
return self.nome_piano
class PianoSingleDay(models.Model):
giorni_settimana_scelta = [
("1","Lunedì"),
("2","Martedì"),
("3","Mercoledì"),
("4","Giovedì"),
("5","Venerdì"),
("6","Sabato"),
("7","Domenica")
]
giorni_settimana = models.CharField(
choices = giorni_settimana_scelta,
max_length = 300
)
single_piano = models.ForeignKey(
Piano,
on_delete = models.CASCADE,
related_name = 'single_piano'
)
def __str__(self):
return self.giorni_settimana
class PianoDay(models.Model):
scelta_pasto = [
("1","Colazione"),
("2","Spuntino mattina"),
("3","Pranzo"),
("4","Merenda"),
("5","Cena"),
("6","Spuntino sera")
]
pasto = models.CharField(
choices = scelta_pasto,
max_length = 300,
default = '-'
)
kcal = models.IntegerField(default = 0)
grassi = models.IntegerField(default = 0)
carboidrati = models.IntegerField(default = 0)
proteine = models.IntegerField(default = 0)
piano_day = models.ForeignKey(
PianoSingleDay,
on_delete = models.CASCADE,
related_name = 'piano_day'
)
file html
<form method="post" novalidate autocomplete="off" class="form-not-box">
{% csrf_token %}
<div class="box-schede">
<div class="alert alert-pop-up mt-3" role="alert">
Le colonne lasciate a 0 non verranno considerate.
</div>
{{ dati_formset.management_form }}
<div class="row mt-3">
{% for dati in dati_formset %}
<div class="col-lg-2">
<div class="info-piano">
<div class="ico">
{% if forloop.counter == 1 %}
<object type="image/svg+xml" data="{% static 'img/icone/colazione.svg' %}">
Icona colazione
</object>
{% elif forloop.counter == 3 %}
<object type="image/svg+xml" data="{% static 'img/icone/pranzo.svg' %}">
Icona pranzo
</object>
{% elif forloop.counter == 5 %}
<object type="image/svg+xml" data="{% static 'img/icone/cena.svg' %}">
Icona cena
</object>
{% elif forloop.counter == 2 or forloop.counter == 4 or forloop.counter == 6 %}
<object type="image/svg+xml" data="{% static 'img/icone/merenda.svg' %}">
Icona cena
</object>
{% endif %}
</div>
<div class="form-floating mb-3 mt-3">
{{ dati.kcal|add_class:'form-control'|append_attr:"placeholder: dati" }}
{{ dati.kcal.label_tag }}
</div>
<div class="form-floating mb-3">
{{ dati.carboidrati|add_class:'form-control'|append_attr:"placeholder: dati" }}
{{ dati.carboidrati.label_tag }}
</div>
<div class="form-floating mb-3">
{{ dati.proteine|add_class:'form-control'|append_attr:"placeholder: dati" }}
{{ dati.proteine.label_tag }}
</div>
<div class="form-floating">
{{ dati.grassi|add_class:'form-control'|append_attr:"placeholder: dati" }}
{{ dati.grassi.label_tag }}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<div class="buttons mb-3">
Indietro
<input type="submit" class="btn btn-warning" value="Aggiorna piano">
</div>
</form>
My observation tells me that your problem is the DatiFormSet fields which shouldn't contain id value and also your function view had in queryset = dato which is not needed, but in some cases you should add an instance of Piano.
#login_required
def PianoSingleUpdateView(request, id):
piano = models.Piano.objects.get(single_piano__id = id)
piano_sett = models.PianoSingleDay.objects.get(id = id)
dato = models.PianoDay.objects.filter( piano_day = piano_sett)
DatiFormSet = modelformset_factory(models.PianoDay, extra = 0, fields=('kcal', 'carboidrati', 'proteine', 'grassi'))
if request.method == 'POST':
dati_formset = DatiFormSet(request.POST, request.FILES , instance=piano)
if dati_formset.is_valid():
for dato in dati_formset:
dato.save()
return redirect('gestione-piano', id = piano.id)
else:
dati_formset = DatiFormSet(request.POST)
context = {'piano': piano, 'piano_sett': piano_sett, 'dati_formset': dati_formset}
return render(request, 'crud/update/update_piano_giornaliero.html', context)
Say I have 3 models: Groups, Answers, and Questions (Group as ForeignKey and models are broken for normalization). I have created corresponding formsets for Answers and Questions for these models.
On selection of a group, my goal is to display related Answers and Questions in a inline table on form. Something like this:
I am able to get formsets in a template and display them in separate sections, but not able to display them as one table (I don't want to do this using HTML-JS). Is there any way I can pass the answer and question formsets as one context object so that they can be displayed as one table in the template?
Note: there may be 5 questions but only 3 answers for a group, or vice versa.
Also, it would be nice to add pagination to this inline table (or the formsets).
urls.py:
from django.conf.urls import url
from .views import lists, load_questions
from django.urls import path
urlpatterns = [
url(r'lists', lists, name="lists"),
path('ajax/load_questions/', load_questions, name='load_questions'),
]
I have as of now:
models.py:
from django.db import models
from django.utils.timezone import now
class Groups(models.Model):
class Meta:
verbose_name_plural = "Groups"
group = models.CharField(max_length=30)
group_description = models.CharField(max_length=4000,default=None,null=True)
group_date = models.DateTimeField(blank=True, null=True)
def __str__(self):
return self.group
def save(self,*args,**kwargs):
if(self.group and self.group_date is None):
self.group_date=now()
super(Groups,self).save(*args,**kwargs)
class Questions(models.Model):
class Meta:
verbose_name_plural = "Questions"
question = models.CharField(max_length=30,verbose_name="Input Question")
question_description = models.CharField(max_length=4000 ,default=None)
question_date=models.DateTimeField(blank=True,null=True)
question_group = models.ForeignKey(Groups, on_delete=models.CASCADE)
def __str__(self):
return self.question
def save(self,*args,**kwargs):
if(self.question and self.question_date is None):
self.question_date=now()
super(Questions,self).save(*args,**kwargs)
class Answers(models.Model):
class Meta:
verbose_name_plural = "Answers"
answer = models.CharField(max_length=100,verbose_name="Your Answer")
answers_description = models.CharField(max_length=4000,default=None,help_text="",blank=True,null=True)
answer_date = models.DateTimeField(blank=True, null=True)
answer_group = models.ForeignKey(Groups, on_delete=models.CASCADE,blank=True)
#valid_question = models.ManyToManyField("Questions",related_name="answers")
print("Model Called")
def __str__(self):
return self.answer
def save(self,*args,**kwargs):
if(self.answer and self.answer_date is None):
self.answer_date=now()
super(Answers,self).save(*args,**kwargs)
forms.py:
from django.forms import inlineformset_factory,Textarea,BaseFormSet
from django.forms.models import modelformset_factory
from .models import Groups,Questions,Answers
from django import forms
class groupForm(forms.Form):
group = forms.CharField(label="Group",max_length=100)
def choices(self):
a=[('','Select a Group')]
return a+[ (o.id, str(o)) for o in Groups.objects.all()]
def __init__(self, *args, **kwargs):
super(groupForm, self).__init__(*args, **kwargs)
print([(o.id, str(o)) for o in Groups.objects.all()])
print(type([(o.id, str(o)) for o in Groups.objects.all()]))
self.fields['group'] = forms.ChoiceField(
choices=self.choices(),
required=True,
)
#self.fields['group'].label = "Select Group"
from crispy_forms.helper import FormHelper, Layout
class questionForm(forms.ModelForm):
class Meta:
model = Questions
#fields = ['question', 'question_description',]
exclude = ()
questionFormSet = modelformset_factory(Questions, form=questionForm, extra=1)
class answerForm(forms.ModelForm):
class Meta:
model = Groups
exclude = ()
def add_fields(self, form, index):
"""A hook for adding extra fields on to each form instance."""
super(answerForm, self).add_fields(form, index)
# here call a custom method or perform any other required action
form.set_index(index)
#answerFormSet = modelformset_factory(Answers, form=answerForm, extra=1)
questionFormSet = inlineformset_factory(Groups, Questions,form=answerForm)
answerFormSet = inlineformset_factory(Groups, Answers,form=answerForm)
#myFormset=questionFormSet|answerFormSet
views.py:
from django.shortcuts import render, redirect
from .models import Answers, Questions, Groups
# Create your views here.
from .forms import groupForm,questionFormSet,answerFormSet
from django.http import JsonResponse
def lists(request):
if request.method == 'POST':
questionsform = questionFormSet(request.POST)
answersform = answerFormSet(request.POST)
groupsform = groupForm(request.POST)
print(answersform.is_valid())
print(answersform.errors.as_data())
if answersform.is_valid() and groupsform.is_valid() and questionsform.is_valid():
print("in views")
print(answersform.cleaned_data)
print(type(groupsform.cleaned_data))
a=Answers(answer=answersform.cleaned_data['answer'],
answers_description="",
answer_group=Groups(groupsform.cleaned_data['group']))
a.save()
q = Questions(question=questionsform.cleaned_data['question'],
question_description="",
question_group=Groups(groupsform.cleaned_data['group']))
q.save()
return redirect('lists')
else:
return redirect('chatbot_welcome')
else:
context = {
'groups': groupForm,
'questions': questionFormSet,
'answers': answerFormSet,
}
return render(request, 'lists.html', context)
def load_questions(request):
group_id = request.GET.get ('groupId')
data = {
"Table": {
"TR": [
]
}
}
if group_id is not None:
questions = (Questions.objects.filter(question_group=group_id).values("question", "question_group"))
answers = (Answers.objects.filter(answer_group=group_id).values("answer", "answer_group"))
l_len = len(questions) if len(questions) >= len(answers) else len(answers)#lambda can be used here
for i in range(l_len):
try:
if list(questions)[i]["question"]:
q=list(questions)[i]["question"]
except:
q=''
try:
if list(answers)[i]["answer"]:
a=list(answers)[i]["answer"]
except:
a=''
data["Table"]["TR"].append({
"row": i+1,
"group": group_id,
"question": q,
"Answer": a
})
print(data)
return JsonResponse(data)
else:
return JsonResponse(data)
Template:
{% extends "base.html" %}
{% load staticfiles %}
{% block content %}
{% load crispy_forms_tags %}
<form method="post" id="groupform" data-group-url="{% url 'load_questions' %}">
{% csrf_token %}
<div class="panel-group" id="accordion18" style="display: block;">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion18" href="#accBody20" id="accHeading20" class="">Groups</a>
</h4>
</div>
<div class="panel-collapse collapse in" id="accBody20" style="height: auto;">
<div class="panel-body">
<div class="row"><div class="col-md-12"><div class="form-group">
<div class="form-group">
{{ groups }}
Create a New Group
</div></div></div></div>
</div>
</div>
</div>
</div>
<div class="panel-group" id="accordion21" style="display: block;">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion21" href="#accBody23" id="accHeading23" class="">Questionnaires</a>
</h4>
</div>
<div class="panel-collapse collapse in" id="accBody23" style="height: auto;">
<div class="panel-body">
<div class="row"><div class="col-md-12"></div></div><div class="row"><div class="col-md-12"><div class="form-group">
<div class="panel-body">
<span>{{groups.group.name|title }}</span>:<span id="group">None</span>
{{questions.forms}}
{% for form in questions.forms %}
{% for field in form %}
field={{field}}
{% endfor %}
{% endfor %}
<table>
<thead>
{% for form in questions.forms %}
{% if forloop.first %}
{% for field in form %}
<th>{{ field.label_tag }}</th>
{% endfor %}
{% endif %}
</thead>
<tbody>
<tr>
{% for field in form %}
<td>{{ field }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<table>
<thead>
{% for form in answers.forms %}
{% if forloop.first %}
{% for field in form %}
<th>{{ field.label_tag }}</th>
{% endfor %}
{% endif %}
</thead>
<tbody>
<tr>
{% for field in form %}
<td>{{ field }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<!--
<form action="" method="post">
{% csrf_token %}
{% crispy questions %}{% crispy answers %}
</form>
-->
<input type="button" class="add-row" value="Add Row">
<button type="button" class="delete-row">Delete Row</button>
</div>
<div class="panel-body">
<div class="tbl_user_data"></div>
</div>
<button id="button40" type="submit" value="Submit" class="btn btn-primary">Submit</button></div></div></div></div>
</div>
</div>
</div>
</form>
<script>
var groupId=0;
$("#id_group").change(function () {
var url = $("#groupform").attr("data-group-url"); // get the url of the `load_cities` view
groupId = $(this).val(); // get the selected country ID from the HTML input
$('#group').text(groupId);
$.ajax({ // initialize an AJAX request
url: url,
//data: $("#groupform").serialize(),
dataType: 'json',// set the url of the request (= localhost:8000/hr/ajax/load-cities/)
data: {
'groupId': groupId // add the country id to the GET parameters
},
success: function (data) { // `data` is the return of the `load_cities` view function
var data1={
"Table": {
"TR": [
{
"row": 1,
"group": 1,
"question": "heyy",
"Answer": "hey"
},
{
"row": 2,
"group": 1,
"question": "y",
"Answer": "heyy"
},
{
"group": 1,
"row": 3,
"question": "How are you",
"Answer": "Fine"
}
]
}
}
//questions_list=JSON.parse(data.questions)[0].question;
//alert(questions_list)
//answers_list=JSON.parse(data.answers)[0].answer;
//alert(answers_list)
writeintoTable(data)
//alert('done')
}
});
});
function runOnLoad(){
//alert(1)
}
var random_id = function ()
{
var id_num = Math.random().toString(9).substr(2,3);
var id_str = Math.random().toString(36).substr(2);
return id_num + id_str;
}
function writeintoTable(data){
/*
$.each(data.Table.TR, function( index, json ) {
//alert(index +" -- "+ data.Table.TR[index].question)
$("#setupbody").append('<tr><td>'
+data.Table.TR[index].row+
'</td><td>'
+data.Table.TR[index].group+
'</td><td>'
+data.Table.TR[index].question+
'</td><td>'
+data.Table.TR[index].Answer+
'</td></tr>');
});*/
/*
for (i=0;i<=data.Table.TR;i++)
{
$("#setupbody").append('<tr><td></td><td>1</td><td>Hi</td><td>Hello</td></tr>');
}*/
var tbl = '';
tbl +='<table class="table table-hover">'
//--->create table header > start
tbl +='<thead>';
tbl +='<tr>';
tbl +='<th>Select</th>';
tbl +='<th>Row</th>';
tbl +='<th>Group</th>';
tbl +='<th>Question</th>';
tbl +='<th>Answer</th>';
tbl +='<th>Options</th>';
tbl +='</tr>';
tbl +='</thead>';
//--->create table header > end
//--->create table body > start
tbl +='<tbody>';
//--->create table body rows > start
$.each(data.Table.TR, function(index, val)
{
//you can replace with your database row id
var row_id = random_id();
//loop through ajax row data
tbl +='<tr row_id="'+row_id+'">';
tbl +='<td><input type="checkbox" value="" name="record" id="cb'+val['row']+'"></td>'
tbl +='<td ><div class="row_data" edit_type="click" col_name="rnum">'+val['row']+'</div></td>';
tbl +='<td ><div class="row_data" edit_type="click" col_name="fname">'+val['group']+'</div></td>';
tbl +='<td ><div class="row_data" edit_type="click" col_name="lname">'+val['question']+'</div></td>';
tbl +='<td ><div class="row_data" edit_type="click" col_name="email">'+val['Answer']+'</div></td>';
//--->edit options > start
tbl +='<td>';
tbl +='<span class="btn_edit" > <a href="#" class="btn btn-link " row_id="'+row_id+'" > Edit</a> </span>';
//only show this button if edit button is clicked
tbl +='<span class="btn_save"> Save | </span>';
tbl +='<span class="btn_cancel"> Cancel | </span>';
tbl +='</td>';
//--->edit options > end
tbl +='</tr>';
});
//--->create table body rows > end
tbl +='</tbody>';
//--->create table body > end
tbl +='</table>'
//--->create data table > end
//$("#dataTable").append(tbl);
$(document).find('.tbl_user_data').html(tbl);
$(document).find('.btn_save').hide();
$(document).find('.btn_cancel').hide();
}
//--->make div editable > start
$(document).on('click', '.row_data', function(event)
{
event.preventDefault();
if($(this).attr('edit_type') == 'button')
{
return false;
}
//make div editable
$(this).closest('div').attr('contenteditable', 'true');
//add bg css
$(this).addClass('bg-warning').css('padding','5px');
$(this).focus();
})
//--->make div editable > end
//--->save single field data > start
$(document).on('focusout', '.row_data', function(event)
{
event.preventDefault();
if($(this).attr('edit_type') == 'button')
{
return false;
}
var row_id = $(this).closest('tr').attr('row_id');
var row_div = $(this)
.removeClass('bg-warning') //add bg css
.css('padding','')
var col_name = row_div.attr('col_name');
var col_val = row_div.html();
var arr = {};
arr[col_name] = col_val;
//use the "arr" object for your ajax call
$.extend(arr, {row_id:row_id});
//out put to show
$('.post_msg').html( '<pre class="bg-success">'+JSON.stringify(arr, null, 2) +'</pre>');
})
//--->save single field data > end
//--->button > edit > start
$(document).on('click', '.btn_edit', function(event)
{
event.preventDefault();
var tbl_row = $(this).closest('tr');
var row_id = tbl_row.attr('row_id');
tbl_row.find('.btn_save').show();
tbl_row.find('.btn_cancel').show();
//hide edit button
tbl_row.find('.btn_edit').hide();
//make the whole row editable
tbl_row.find('.row_data')
.attr('contenteditable', 'true')
.attr('edit_type', 'button')
.addClass('bg-warning')
.css('padding','3px')
//--->add the original entry > start
tbl_row.find('.row_data').each(function(index, val)
{
//this will help in case user decided to click on cancel button
$(this).attr('original_entry', $(this).html());
});
//--->add the original entry > end
});
//--->button > edit > end
//--->button > cancel > start
$(document).on('click', '.btn_cancel', function(event)
{
event.preventDefault();
var tbl_row = $(this).closest('tr');
var row_id = tbl_row.attr('row_id');
//hide save and cacel buttons
tbl_row.find('.btn_save').hide();
tbl_row.find('.btn_cancel').hide();
//show edit button
tbl_row.find('.btn_edit').show();
//make the whole row editable
tbl_row.find('.row_data')
.attr('edit_type', 'click')
.removeClass('bg-warning')
.css('padding','')
tbl_row.find('.row_data').each(function(index, val)
{
$(this).html( $(this).attr('original_entry') );
});
});
//--->button > cancel > end
//--->save whole row entery > start
$(document).on('click', '.btn_save', function(event)
{
event.preventDefault();
var tbl_row = $(this).closest('tr');
var row_id = tbl_row.attr('row_id');
//hide save and cacel buttons
tbl_row.find('.btn_save').hide();
tbl_row.find('.btn_cancel').hide();
//show edit button
tbl_row.find('.btn_edit').show();
//make the whole row editable
tbl_row.find('.row_data')
.attr('edit_type', 'click')
.removeClass('bg-warning')
.css('padding','')
//--->get row data > start
var arr = {};
tbl_row.find('.row_data').each(function(index, val)
{
var col_name = $(this).attr('col_name');
var col_val = $(this).html();
arr[col_name] = col_val;
});
//--->get row data > end
//use the "arr" object for your ajax call
$.extend(arr, {row_id:row_id});
//out put to show
$('.post_msg').html( '<pre class="bg-success">'+JSON.stringify(arr, null, 2) +'</pre>')
});
//--->save whole row entery > end
$(".add-row").click(function(){
var question = $("#id_question").val();
//var group = $("#group").val();
var answer = $("#id_answer").val();
if (question=='' || question=='undefined'||answer=='' || answer=='undefined' )
{
alert('row not complete to add')
return false;
}
//var markup = "<tr><td><input type='checkbox' name='record'></td><td>" + name + "</td><td>" + email + "</td></tr>";
var tbl = '';
//you can replace with your database row id
var row_id = random_id();
//loop through ajax row data
tbl +='<tr row_id="'+row_id+'">';
tbl +='<td><input type="checkbox" value="" name="record" id="cb'+100+'"></td>'
tbl +='<td ><div class="row_data" edit_type="click" col_name="rnum">'+100+'</div></td>';
tbl +='<td ><div class="row_data" edit_type="click" col_name="fname">'+groupId+'</div></td>';
tbl +='<td ><div class="row_data" edit_type="click" col_name="lname">'+question+'</div></td>';
tbl +='<td ><div class="row_data" edit_type="click" col_name="email">'+answer+'</div></td>';
//--->edit options > start
tbl +='<td>';
tbl +='<span class="btn_edit" > <a href="#" class="btn btn-link " row_id="'+row_id+'" > Edit</a> </span>';
//only show this button if edit button is clicked
tbl +='<span class="btn_save"> Save | </span>';
tbl +='<span class="btn_cancel"> Cancel | </span>';
tbl +='</td>';
//--->edit options > end
tbl +='</tr>';
$("table tbody").append(tbl);
$(document).find('.btn_save').hide();
$(document).find('.btn_cancel').hide();
});
// Find and remove selected table rows
$(".delete-row").click(function(){
var r = confirm("Selected Row will be deleted.");
if (r == true) {
$("table tbody").find('input[name="record"]').each(function(){
if($(this).is(":checked")){
$(this).parents("tr").remove();
}
});
} else {
$("table tbody").find('input[name="record"]').each(function(){
if($(this).is(":checked")){
$(this).prop('checked', false);
}
});
alert("Row(s) not Deleted!");
}
});
</script>
{% endblock %}
i am trying to create a form to edit skills of user where they have saved multiple no. of skills. after rendering the template input fields are empty but the no. of form created is correct as per queryset.
forms.py
class skillform(forms.ModelForm):
name = forms.CharField(label='Skill',widget=forms.TextInput(attrs={'class': 'form-control',}))
level = forms.ChoiceField(choices=(('Novice','Novice'),('Beginner','Beginner'),('Skillful','Skillful'),('Experienced','Experienced'),('Expert','Expert')),label="level",initial='Skillful',widget=forms.Select(),required=False)
class Meta:
model = userskills_model
fields = ('name','level')
skillformset = modelformset_factory(userskills_model, form = skillform, extra=0, can_delete=False)
models.py
class userskills_model(models.Model):
userid = models.ForeignKey(user_model, on_delete=models.PROTECT)
skills =models.CharField(max_length=264,unique=False,blank=False,null=False)
skills_level = models.CharField(max_length=264,unique=False,blank=False,null=False)
def __str__(self):
return str(self.userid)
views.py
def skillview(request):
qset=userskills_model.objects.filter( userid=user_model.objects.get(userid=userid))
skillformset(queryset = qset)
if request.method == 'GET':
formset = skillformset(request.GET or None)
elif request.method == 'POST':
formset = skillformset(request.POST)
#validating and saving
return render(request, template_name, {
'formset': formset,
})
template/skills.html
{% extends 'app/base.html' %}
{% load staticfiles%}
{% block head %}
<link href="{% static "/css/skills.css" %}" rel="stylesheet"
type="text/css"/>
{% endblock %}
{% block content %}
<div class="heading_text">SKILLS</div>
<form class="form-horizontal" method="POST" action="">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<div class="row form-row spacer">
<div class="col-5">
<label>{{form.name.label}}</label>
<div class="input-group">
{{form.name}}
</div>
</div>
<div class="col-5">
<label>{{form.level.label}}</label>
<div class="input-group">
{{form.level}}
<!-- <div class="input-group-append">
<button class="btn btn-success add-form-row">+</button>
</div> -->
</div>
</div>
<div class="input-group-append">
<button class="btn btn-success add-form-row">+</button>
</div>
</div>
{% endfor %}
<div class="row spacer">
<div class="col-3 button1">
<button type="submit" class="btn3">Save and Continue</button>
</div>
</div>
</form>
{% endblock %}
{% block custom_js %}
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script type="text/javascript">
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 cloneMore(selector, prefix) {
var newElement = $(selector).clone(true);
var total = $('#id_' + prefix + '-TOTAL_FORMS').val();
newElement.find(':input').each(function() {
var name = $(this).attr('name')
if(name) {
name = name.replace('-' + (total-1) + '-', '-' + total + '-');
var id = 'id_' + name;
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');}});
total++;
$('#id_' + prefix + '-TOTAL_FORMS').val(total);
$(selector).after(newElement);
var conditionRow = $('.form-row:not(:last)');
conditionRow.find('.btn.add-form-row')
.removeClass('btn-success').addClass('btn-danger')
.removeClass('add-form-row').addClass('remove-form-row')
.html('-');
return false;}
function deleteForm(prefix, btn) {
var total = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
if (total > 1){
btn.closest('.form-row').remove();
var forms = $('.form-row');
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
for (var i=0, formCount=forms.length; i<formCount; i++) {
$(forms.get(i)).find(':input').each(function() {
updateElementIndex(this, prefix, i);
});
}
}
return false;}
$(document).on('click', '.add-form-row', function(e){
e.preventDefault();
cloneMore('.form-row:last', 'form');
return false;});
$(document).on('click', '.remove-form-row', function(e){
e.preventDefault();
deleteForm('form', $(this));
return false;});
</script>
{% endblock %}
edit:added the template/skills.html . i m stuck with this please help me out here
The fields you have declared on your skillform do not exist on userskills_model so they won't be populated with anything. Try renaming the fields on your form to skills and skills_model so they match your model:
class skillform(forms.ModelForm):
skills = forms.CharField(label='Skill',widget=forms.TextInput(attrs={'class': 'form-control',}))
skills_level = forms.ChoiceField(choices=(('Novice','Novice'),('Beginner','Beginner'),('Skillful','Skillful'),('Experienced','Experienced'),('Expert','Expert')),label="level",initial='Skillful',widget=forms.Select(),required=False)
class Meta:
model = userskills_model
fields = ('skills','skills_level')
and update your template to reference form.skills and form.skills_level:
...
{% for form in formset %}
<div class="row form-row spacer">
<div class="col-5">
<label>{{form.skills.label}}</label>
<div class="input-group">
{{form.skills}}
</div>
</div>
<div class="col-5">
<label>{{form.skills_level.label}}</label>
<div class="input-group">
{{form.skills_level}}
...
I guess your qset is empty, try this -
qset=userskills_model.objects.filter( userid=self.request.user) # This for logged in user
For update case -
user = get_object_or_404(user_model, userid=userid)
qset=userskills_model.objects.filter( userid=user)
I have a form with a selection box related to a foreign key (for example, category). And on the same page, I have another link that opens a new page to add a new instance of the foreign key. Once the new instance is added, how can I update the current form to add the new option, and preserve the text in the text field (just like how the admin page behaves)?
Here are some of my code snippets:
update_post.html:
...
<form method="post" novalidate action='.'>
{% csrf_token %}
{% include 'base_form.html' with form=form %}
# button to add a category
{% trans "Add category" %}
<button type="submit" class="btn btn-primary" name="publish" value={% trans 'Publish' %}>{% trans 'Publish' %}</button>
</form>
...
create_category.html:
...
<form action="./{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}" method="post" novalidate>
{% csrf_token %}
{% include 'base_form.html' with form=form %}
<button type="submit" class="btn btn-primary" value={% trans 'Create' %}>{% trans 'Create' %}</button>
</form>
...
views.py:
...
class PostUpdate(UpdateView):
template_name = 'update_post.html'
success_url = '/'
model = Post
fields = ['title', 'body', 'category']
class CategoryCreate(CreateView):
template_name = 'create_category.html'
model = Category
fields = ['name']
def get_success_url(self):
if 'next' in self.request.GET:
return self.request.GET.get('next')
else:
return reverse('index')
...
What I want to do is that when a new category is added, it becomes available in the update_post page right away, and any changes to the body field are preserved.
I have done this yesterday inspired by django admin ForeignKey popup window add.my condition is a goods has goodcategory and i can add/edit/delet goodscategory in goods's add/update view and the result add/edit/delet goodcategory will sync to goods's add/update view.Here is a demo,which popup is support by layui.
as you can see i can add\change\delete ForeignKey without refresh the parent page.
first custom a new Field to ForeignKey which will receieve add_url\update_url\delete_url:
class ForeignKeyWidget(Select):
template_name = 'widgets/foreign_key_select.html'
def __init__(self, url_template, *args, **kw):
super(ForeignKeyWidget, self).__init__(*args, **kw)
# Be careful that here "reverse" is not allowed
self.url_template = url_template
def get_context(self, name, value, attrs):
context = super(ForeignKeyWidget, self).get_context(name, value, attrs)
context['add_url'] = self.url_template
context['update_url'] = self.url_template
context['delete_url'] = self.url_template + 'lang_delete/'
return context
second is custom a widget for your custom field which can popup add/update category windows and use ajax to delete category:
foreign_key_select.html:
{% include "django/forms/widgets/select.html" %}
<style>
#{{ widget.attrs.id }}_add, #{{ widget.attrs.id }}_change, #{{ widget.attrs.id }}_delete {
margin-top: 10px;
padding: 0 10px;
height: 25px;
line-height: 25px;
}
</style>
<a class="layui-btn layui-btn-mini" id="{{ widget.attrs.id }}_add">
add
</a>
<a class="layui-btn layui-btn-mini layui-btn-disabled" id="{{ widget.attrs.id }}_change">
change
</a><a class="layui-btn layui-btn-mini layui-btn-disabled" id="{{ widget.attrs.id }}_delete">
delete
</a>
<script>
$('#{{ widget.attrs.id }}_add').click(function () {
var index = layui.layer.open({
title: "add_category",
type: 2,
area: ['700px', '500px'],
content: "{{ add_url }}" + '?popup=1&to_field={{ widget.attrs.id }}',
success: function (layer, index) {
}
});
});
$("#{{ widget.attrs.id }}_change").click(function () {
var id = $('#{{ widget.attrs.id }}').val();
if (id) {
var index = layui.layer.open({
title: "change_category",
type: 2,
area: ['700px', '500px'],
content: '{{ update_url }}' + id + '?popup=1&to_field={{ widget.attrs.id }}',
success: function (layer, index) {
}
});
}
});
$("#{{ widget.attrs.id }}_delete").click(function () {
var id = $('#{{ widget.attrs.id }}').val();
var value = $('#{{ widget.attrs.id }} option[value=' + id + ']').text();
var indexGood = value.lastIndexOf('>');
var valueN = indexGood > 0 ? value.substring(indexGood + 1, value.length) : value;
if (id) {
layer.confirm('corform delete' + valueN + ' ?', {icon: 3, title: 'delete'}, function (index) {
$.ajax({
type: "POST",
data: {},
url: '{{ delete_url }}' + id + '/',
beforeSend: function (xhr) {
xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
},
success: function (data, textStatus) {
layer.close(index);
$('#{{ widget.attrs.id }} option[value=' + data.id + ']').remove();
$("#{{ widget.attrs.id }}_change,#{{ widget.attrs.id }}_delete").addClass('layui-btn-disabled');
return false;
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
layer.alert('delete failed' + XMLHttpRequest.responseText)
}
});
});
}
});
function {{ widget.attrs.id }}_isDisabled() {
if ($('#{{ widget.attrs.id }}').val()) {
$("#{{ widget.attrs.id }}_change,#{{ widget.attrs.id }}_delete").removeClass('layui-btn-disabled');
} else {
$("#{{ widget.attrs.id }}_change,#{{ widget.attrs.id }}_delete").addClass('layui-btn-disabled');
}
}
$('#{{ widget.attrs.id }}').change(function () {
{{ widget.attrs.id }}_isDisabled();
});
{{ widget.attrs.id }}_isDisabled();
</script>
third is use your custom field for category in your forms.py:
class GoodsForm(ModelForm):
def __init__(self, *args, **kwargs):
super(GoodsForm, self).__init__(*args, **kwargs)
self.fields['category'].widget.attrs.update({'class': 'form-control'})
self.fields['title'].widget.attrs.update({'class': 'form-control'})
self.fields['content'].widget.attrs.update({'class': 'form-control'})
class Meta:
model = Goods
fields = ['category', 'title', 'content']
widgets = {
'category': ForeignKeyWidget(url_template=reverse_lazy('goods_category_ajax_create')),
}
and new a goodcategory form is forms.py
class GoodsCategoryForm(TranslatableModelForm):
def __init__(self, *args, **kwargs):
super(GoodsCategoryForm, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({'class': 'form-control'})
self.fields['cover'].widget.attrs.update({'class': 'form-control'})
self.fields['parent'].widget.attrs.update({'class': 'form-control'})
class Meta:
model = GoodsCategory
fields = ['name', 'cover', 'parent']
four is handle request in your views.py:
class GoodsCategoryAjaxCreateView(BaseContextMixin, IsStaffUserMixin, CreateView):
form_class = GoodsCategoryForm
template_name = 'goods_category_ajax/create.html'
def get_context_data(self, **kwargs):
if 'to_field' in self.request.GET:
kwargs['to_field'] = self.request.GET['to_field']
return super(GoodsCategoryAjaxCreateView, self).get_context_data(**kwargs)
def form_valid(self, form):
self.object = form.save()
context = {'op': 'create', 'id': self.object.id, 'value': self.object.__str__()}
if 'to_field' in self.request.GET:
context['to_field'] = self.request.GET['to_field']
return TemplateResponse(self.request, 'goods_category_ajax/success.html', context=context)
class GoodsCategoryAjaxUpdateView(BaseContextMixin, IsStaffUserMixin, UpdateView):
model = GoodsCategory
form_class = GoodsCategoryForm
slug_field = 'id'
context_object_name = 'goods_category'
template_name = 'goods_category_ajax/update.html'
def get_context_data(self, **kwargs):
if 'to_field' in self.request.GET:
kwargs['to_field'] = self.request.GET['to_field']
return super(GoodsCategoryAjaxUpdateView, self).get_context_data(**kwargs)
def form_valid(self, form):
self.object = form.save()
context = {'op': 'create', 'id': self.object.id, 'value': self.object.__str__()}
if 'to_field' in self.request.GET:
context['update'] = self.request.GET['to_field']
return TemplateResponse(self.request, 'goods_category_ajax/success.html', context=context)
class GoodsCategoryAjaxLangDeleteView(BaseContextMixin, IsStaffUserMixin, FakeDeleteView):
model = GoodsCategory
slug_field = 'id'
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
data = {'op': 'delete', 'id': self.object.id, 'value': self.object.__str__()}
self.object.delete()
return JsonResponse(data=data)
urls.py:
url(r'^ajax/$', GoodsCategoryAjaxCreateView.as_view(), name='goods_category_ajax_create'),
url(r'^ajax/(?P<slug>\d+)/$', GoodsCategoryAjaxUpdateView.as_view(), name='goods_category_ajax_update'),
url(r'^ajax/lang_delete/(?P<slug>\d+)/$', GoodsCategoryAjaxLangDeleteView.as_view(),
name='goods_category_ajax_lang_delete'),
five is your add popup windows will open url handle by GoodsCategoryAjaxCreateView and the return template is:
{% extends "manage/base.html" %}
{% block main %}
<form id='goods_category_ajax_create' class="form-horizontal" enctype="multipart/form-data"
action="{% url 'goods_category_ajax_create' %}{% if to_field %}?to_field={{ to_field }}{% endif %}"
method="post">
{% include 'manage/widgets/form.html' %}
<div class="form-group">
<div class="col-sm-offset-1 col-sm-10">
<input class="layui-btn layui-btn-normal" type="submit" value="add_category"/>
</div>
</div>
</form>
{% endblock %}
you submit a new category with modelform and createview,when the form is_vaild a success.html will be return by TemplateResponse(as you can see in GoodsCategoryAjaxCreateView form_valid),and the point is success.html is nothing but a script that can close popup window and insert new option to the to_field element in parent window.here is success.html:
{% extends "manage/base.html" %}
{% block main %}
<script>
var to_field = '#{{ to_field }}', op = '{{ op }}', id = '{{ id }}', value = '{{ value }}';
if (to_field) {
switch (op) {
case 'create':
if (id) {
var index = parent.layer.getFrameIndex(window.name); //get current iFrame index
parent.layer.close(index); //close
$option = '<option value=' + id + ' selected>' + value + '</option>';
$(to_field, window.parent.document).append($option);
$(to_field + '_change,' + to_field + '_delete', window.parent.document).removeClass('layui-btn-disabled');
}
break;
case 'update':
if (id) {
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
$(to_field + ' option[value=' + id + ']', window.parent.document).html(value);
}
break;
}
}
</script>
{% endblock %}
I have three forms, depending on number of rooms i want to add or delete new fields "adult" and "children". For example: if value of field "room" will be 2, it should generate two couples another fields for each room 'adult' and 'children', but if change value from 2 on 1, it should delete one couple of fields. And when change the value of 'room' field from 2 to 3, it should add one couple of fields.
My forms.py:
class HotelForm(forms.Form):
rooms = forms.IntegerField(label=(u'Rooms'), min_value=1)
class TouristsForm(forms.Form):
adult = forms.IntegerField(label=(u'Adults'), min_value=1, initial=1)
children = forms.IntegerField(label=(u'Children'), min_value=0, initial=0, required=False)
class ChildrenAgeForm(forms.Form):
children_age = forms.IntegerField(label=(u'Children age'), min_value=2, max_value=10, initial=2, required=False
That's how i realize formset and validation in views.py:
class BookingForm(View):
template_name = 'booking/booking.html'
def get(self, request, *args, **kwargs):
TouristsFormSet = formset_factory(TouristsForm, extra = 1, max_num = 15)
ChildrenAgeFormSet = formset_factory(ChildrenAgeForm, extra = 1, max_num = 20)
booking_form = HotelForm(prefix='booking_form')
tourists_formset = TouristsFormSet(prefix='tourists')
childrenage_formset = ChildrenAgeFormSet(prefix='childrenage')
return render(request, self.template_name, { 'booking_form' : booking_form, 'tourists_formset' : tourists_formset, 'childrenage_formset' : childrenage_formset })
def post(self, request, *args, **kwargs):
TouristsFormSet = formset_factory(TouristsForm, extra = 1, max_num = 15)
ChildrenAgeFormSet = formset_factory(ChildrenAgeForm, extra = 1, max_num = 20)
booking_form = HotelForm(request.POST, prefix='booking_form')
tourists_formset = TouristsFormSet(request.POST, prefix='tourists')
childrenage_formset = ChildrenAgeFormSet(request.POST, prefix='childrenage')
if booking_form.is_valid() and tourists_formset.is_valid() and childrenage_formset.is_valid():
I add new formset fields in the form using java-script:
$(function(){
$('#id_booking_form-rooms').on('change', function(e){
var n = $('#id_booking_form-rooms').val() || 0;
var html = "";
for (var i = 0; i < n; i++) {
html += "<div>People in the room " + (i + 1) + "</div>"
+ "<br/><label for='id_tourists-" + i + "-adult'>Adults:</label>"
+ "<input id='id_tourists-" + i + "-adult' type='number' name='tourists-" + i + "-adult'/>"
+ "<label for='id_tourists-" + i + "-children'>Children:</label>"
+ "<input id='id_tourists-" + i + "-children' type='number' name='tourists-" + i + "-children' class='children_age'/>"
+ "<div class='extrafieldWrapperChAge'></div>";
}
$(".extrafieldWrapper").html(html);
});
$(".extrafieldWrapper").on('change', '.children_age', function(e){
var n = $(this).val() || 0;
var html = "";
for (var i = 0; i < n; i++) {
html += "<br/><label for='id_childrenage-" + i + "-children_age'>Children age "+(i+1)+"</label>"
+ "<input id='id_childrenage-" + i + "-children_age' type='number' name='childrenage-" + i + "children_age' />";
}
$(this).next('.extrafieldWrapperChAge').html(html);
});
});
There is how look like my template:
<div class="row">
<div class="fieldWrapper">
{% if booking_form.rooms.errors %}
<ol style="list-style-type:square" >
{% for error in booking_form.rooms.errors %}
<li><strong>This field required</strong></li>
{% endfor %}
</ol>
{% endif %}
{{ booking_form.rooms.label_tag }}
{{ booking_form.rooms }}
</div>
<div class="extrafieldWrapper">
{{ tourists_formset.management_form }}
{{ childrenage_formset.management_form }}
</div>
</div>
There is how look like my form in browser:
<div class="fieldWrapper">
<label for="id_booking_form-rooms">Rooms:</label>
<input id="id_booking_form-rooms" type="number" name="booking_form-rooms" min="1">
</div>
<div class="extrafieldWrapper">
<div>People in the room 1</div>
<br>
<label for="id_tourists-0-adult">Adults:</label>
<input id="id_tourists-0-adult" type="number" name="tourists-0-adult">
<label for="id_tourists-0-children">Children:</label>
<input id="id_tourists-0-children" class="children_age" type="number" name="tourists-0-children">
<div class="extrafieldWrapperChAge">
<br>
<label for="id_childrenage-0-children_age">Children age 1</label>
<input id="id_childrenage-0-children_age" type="number" name="childrenage-0children_age">
</div>
</div>
But when i press submit button i always got [u'ManagementForm data is missing or has been tampered with']. What did i wrong? Thanks for your help
When you change the number of your forms in formset on the client side, you should change the ManagementForm hidden fields: form-TOTAL_FORMS, form-INITIAL_FORMS and form-MAX_NUM_FORMS.