I'm trying to create a form that save 3 users.
my forms:
class UserForm(forms.ModelForm):
class Meta:
model = MyUsers
exclude = ('address',)
my views:
def adduser(request):
if request.method == "POST":
rform = UserForm(request.POST, instance=MyUsers())
if rform.is_valid():
new_users = rform.save()
return HttpResponseRedirect...
else:
rform = UserForm(instance=MyUsers())
return render_to_response...
my template structure:
<form method="post">
{% for field in rform %}
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% endfor %}
{% for field in rform %}
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% endfor %}
{% for field in rform %}
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% endfor %}
<input type="submit" value="Save" />
</form>
the problem
The form works properly but add only the last inserted user.
Alasdair's suggestion (formset) is a way to generate multiple instances of the same form at once.
The forms can be rendered in the template by looping over the formset instance:
{% for form in formset %}
{{ form.id }} #PK field is MANDATORY, see reference[2] below
{{ form.field1 }}
{{ form.field2 }} #or just as something {{ form.as_p }}
{% endfor %}
In your views, formset validation is made once for all if formset.is_valid(): and rformset = formset_factory(MyUsers, extra=1, max_num=3) to create the formset.
Don't forget the imports.
References, both from Django Docs:
Formsets {1}
Creating forms from models {2}
Related
Some troubles with validation.
I'm using this construction in my template:
<form method="POST">
{% csrf_token %}
{{ formset.media.js }}
{% for form in formset %}
<p>{{ form }}</p>
{% endfor %}
<button type="submit" class="btn btn-primary">Send</button>
And this validation in views:
def flow_type(request):
patternFormset = modelformset_factory(CashFlowPattern, fields='__all__')
if request.method == 'POST':
formset = patternFormset(request.POST)
if formset.is_valid():
formset.save()
formset = patternFormset()
template = 'cash_table/index.html'
context = {
# 'form': form,
'formset': formset
}
return render(request, template, context)
I get form at page but nothing happens after submit.
But if I use another template construction it works:
<form method="POST">
{% csrf_token %}
{{ formset.media.js }}
{{ formset }}
<button type="submit" class="btn btn-primary">Send</button>
</form>
But then I get all fields of new form at the same line.
I think you need to have {{ formset.management_form }} in the template where you iterate through the forms of the formset so that Django knows that it is a formset, and knows how many forms are in the formset, etc...
<form method="POST">
{% csrf_token %}
{{ formset.management_form }}
{{ formset.media.js }}
{% for form in formset %}
<p>{{ form }}</p>
{% endfor %}
<button type="submit" class="btn btn-primary">Send</button>
When you use {{ formset }} the management is done automatically (it's a shortcut).
Source: https://docs.djangoproject.com/en/4.1/topics/forms/formsets/#custom-formset-validation
I had this view that rendered a form and a formset in the same template:
class LearnerUpdateView(LearnerProfileMixin, UpdateView):
model = User
form_class = UserForm
formset_class = LearnerFormSet
template_name = "formset_edit_learner.html"
success_url = reverse_lazy('pages:home')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
learner = User.objects.get(learner=self.request.user.learner)
formset = LearnerFormSet(instance=learner)
context["learner_formset"] = formset
return context
def get_object(self, queryset=None):
user = self.request.user
return user
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
user = User.objects.get(learner=self.get_object().learner)
formsets = LearnerFormSet(self.request.POST, request.FILES, instance=user)
if form.is_valid():
for fs in formsets:
if fs.is_valid():
# Messages test start
messages.success(request, "Profile updated successfully!")
# Messages test end
fs.save()
else:
messages.error(request, "It didn't save!")
return self.form_valid(form)
return self.form_invalid(form)
Then i wanted to make it prettier and i added the select2 multicheckbox widget and the django-bootstrap-datepicker-plus
Nothing has changed elsewhere, yet when i submit the post it only saves the data relative to User and not to Learner (which relies on the formset)
According to the messages, the formset data is not validated, I don't understand why since i didn't touch the substance at all but just the appearance.
Being a beginner im probably missing something big, I thank in advance whoever can help me find out the problem.
Here below the forms and the template:
(users.forms)
class LearnerForm(forms.ModelForm):
class Meta:
model = Learner
fields = ['locations', 'organization', 'supervisor', 'intro']
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = ['username', 'first_name', 'last_name', 'birthday', 'email', 'profile_pic']
widgets = {
'birthday': DatePickerInput(format='%Y-%m-%d'), }
LearnerFormSet = inlineformset_factory(User, Learner, form=LearnerForm)
template
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
{{ learner_formset.management_form}}
{% for form in learner_formset %}
{% if forloop.first %}
{% for field in form.visible_fields %}
{% if field.name != 'DELETE' %}
<label for="id_{{ field.name }}">{{ field.label|capfirst }}</label>
<div id='id_{{ field.name }}' class="form-group">
{{ field.errors.as_ul }}
{{ field }}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
<input class="btn btn-success" type="submit" value="Update"/>
</form>
{% endblock content %}
I figured it out by combing the previous commits. The problem was in the template, the formset wasn't getting validated because i had ignored the hidden fields, which i hear are a common problem when dealing with formsets.
So the correct code is as such:
{% extends '_base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
{{ learner_formset.management_form}}
{% for form in learner_formset %}
{% if forloop.first %}
{% comment %} This makes it so that it doesnt show the annoying DELETE checkbox {% endcomment %}
{% for field in form.visible_fields %}
{% if field.name != 'DELETE' %}
<label for="{{ field.name }}">{{ field.label|capfirst }}</label>
<div id="{{ field.name }}" class="form-group">
{{ field }}
{{ field.errors.as_ul }}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% for field in form.visible_fields %}
{% if field.name == 'DELETE' %}
{{ field.as_hidden }}
{% else %}
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
<input class="btn btn-success" type="submit" value="Update"/>
</form>
{% endblock content %}
So I have my view:
def home_page(request):
form = UsersForm()
if request.method == "POST":
form = UsersForm(request.POST)
if form.is_valid():
form.save()
c = {}
c.update(csrf(request))
c.update({'form':form})
return render_to_response('home_page.html', c)
my forms.py:
class UsersForm(forms.ModelForm):
class Meta:
model = Users
widgets = {'password':forms.PasswordInput()}
def __init__(self, *args, **kwargs):
super( UsersForm, self ).__init__(*args, **kwargs)
self.fields[ 'first_name' ].widget.attrs[ 'placeholder' ]="First Name"
self.fields[ 'last_name' ].widget.attrs[ 'placeholder' ]="Last Name"
self.fields[ 'password' ].widget.attrs[ 'placeholder' ]="Password"
and my template:
<html>
<body>
<form method="post" action="">{% csrf_token %}
{{ form.first_name }} {{form.last_name }} <br>
{{ form.password }} <br>
<input type="submit" value="Submit Form"/>
</form>
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
<p> {{ errors }} </p>
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %}
<p> {{ error }} </p>
{% endfor %}
{% endif %}
</body>
</html>
Now, keep in mind that before I split the form field, my form just looked like this:
<form method="post" action="">{% csrf_token %}
{{ form }}
<input type="submit" value="Submit Form"/>
</form>
and if the form had a problem (like if one of the fields was missing) it would automatically give an error message. After I split up the form fields in the template (made {{ form.first_name }}, {{ form.last_name }} and {{ form.password }} their own section) it stopped automatically giving error messages. Is this normal?
But the main problem is, how come my {{ if form.errors }} statement is not working / displaying error messages if there are error messages? For example, if I purposely not fill out a field in the form and I click submit, the database does not get updates (which is a good thing) but it does not give any error messages. Any idea why?
I also tried remove the {{ forms.non_field_errors }} and tried to return just field errors like so:
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
<p> {{ errors }} </p>
{% endfor %}
{% endfor %}
{% endif %}
but it still doesn't work.
I found the mistake (typo).
The snippet should be:
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
<p> {{ error }} </p>
{% endfor %}
{% endfor %}
{% endif %}
I had errors instead of error.
you have to send your form context again if is_valid() isn't true,
if form.is_valid():
return redirect('/user/contact/')
else:
return render(request, 'users/contact.html', context={'form': form})
because your errors will display in your form
else: it will return ValueError
Add this snippets in views.py like this
if request.method == "POST":
fm = UserRegsitrationForm(request.POST)
if fm.is_valid():
fm.save()
else:
fm = UserRegsitrationForm()
context = {'fm':fm}
return render(request,'registration.html',context)
The error capture could very well have been done with:
{% for field in form %}
{{ field.errors }}
{% endfor %}
my template looks like:
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset.forms %}
{{ form.contractor }} {{ form.date }} {{ form.value }} {{ form.comment }} {{ form.operation_type }} {{ form.category }} {{ form.account }}
{% endfor %}
</form>
but the result allows to change all of fields - but i want only one.
I thought that (please notice ".value" after all but category field) solves the problem, but not.
<form method="post" action="">
{{ formset.management_form }}
{% for form in formset.forms %}
{{ form.contractor.value }} {{ form.date.value }} {{ form.value.value }} {{ form.comment.value }} {{ form.operation_type.value }} {{ form.category }} {{ form.account.value }}
{% endfor %}
</form>
UPD:
relevant view code
def detail(request, category_id):
from django.forms.models import modelformset_factory
OperationFormSet = modelformset_factory(Operation)
if request.method == "POST":
formset = OperationFormSet(request.POST, request.FILES,
queryset=Operation.objects.filter(category=category_id))
if formset.is_valid():
formset.save()
# Do something.
else:
formset = OperationFormSet(queryset=Operation.objects.filter(category=category_id))
return render_to_response("reports/operation_list.html", {
"formset": formset,
})
Your problem is that the readonly values are NOT passed back to the server, and thus your formset should fail as it's only receiving one field.
You'd want to show the value AND set a hidden field to store the data the formset is expecting. But I'd recommend a different approach...
Ultimately, a formset and form is used to display html forms and not built for anything else. You'd have to hack it to show hidden widgets, make sure users can't POST arbitrary data, etc. So instead, I would use the forms framework to only display your one editable field.
Create a ModelForm that only has one editable field
class MyModelForm(forms.ModelForm):
class Meta:
model = Operation
fields = ('category',)
MyModelFormSet = modelformset_factory(Operation, form=MyModelForm)
As for displaying values in the template, ModelForms should have their instance directly accessible via form.instance so you should be able to do something like this:
{% for form in formset %}
{{ form.instance.some_attribute }}
{{ form.category }}
{% endfor %}
I am still fighting with formsets and I cant really understand why I am getting this error:
u'ManagementForm data is missing or has been tampered with
Thats my code:
Please point out my mistakes and help me with resolving this issue.
#csrf_protect
#transaction.commit_on_success
def signup(request):
form = NewUserCreationForm()
doc_form = NewDocRegisterForm()
SpecialityLicensesFormSet = modelformset_factory(SpecialityLicenses, extra=1, exclude = ('user'))
formset = SpecialityLicensesFormSet(queryset=SpecialityLicenses.objects.none())
if request.method == "POST":
form = NewUserCreationForm(request.POST or None)
doc_form = NewDocRegisterForm(request.POST or None)
formset = SpecialityLicensesFormSet(request.POST or None)
if form.is_valid() and doc_form.is_valid() and formset.is_valid():
user = form.save()
doc = doc_form.save(commit=False)
doc.user = user
doc.save()
print formset
fset = formset.save(commit=False)
for n in fset:
n.user = user
n.save()
return HttpResponse("Uzytkownik utowrzony")
return render_to_response("userena/signup_new.html", {'form': form,
'doc_form': doc_form,
'spec_form': formset,},
context_instance=RequestContex
t(request))
Template code:
<form action="/en/accounts/doc_register/" method="post">{% csrf_token %}
{% for field in form %}
<div>
{% if field.errors %}
{{ field.errors|striptags }} |
{% endif %}
{{field.label}} | {{ field}}
</div>
{% endfor %}
<hr>
{% for f in doc_form %}
<div>
{% if f.errors %}
{{f.errors|striptags}} |
{% endif %}
{{f.label}} : {{ f }}
</div>
{% endfor %}
<hr>
{{ spec_form.management_form }}
{{ spec_form }}
<hr>
<input type="submit" value="Submit"/>
</form>
{% endblock %}
you don't need to do {{ formset.management_form }} if you do {{ formset }}, just if you do
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
try removing the {{ spec_form.management_form }} bit from your template. Look at the third example
add prefix for formset if prefix is missing for formset it will give error