Django submit optional forms - django

I need to create registration form for an event. Each person that register can bring guests. I want to register everything in one page. To do that I use a view with 1 RegistrationForm and X GuestForms. In my models, I have a Registration and Guest class I used to create the two forms with ModelForm.
The problem is that a GuestForm is not required to be filled (you don't have to bring guests).
def register_form(request):
error = False
if request.method == 'POST':
register = RegistrationForm(request.POST, instance=Registration() )
guests = [GuestForm(request.POST, prefix=str(x), instance=Guest()) for x in range(MAXGUESTS)]
if register.is_valid():
print("register is valid")
for guest in guests:
if guest.is_valid():
print("guest is valid")
else:
print("guest is not valid") # always when empty form
error = True
else:
print("register is not valid")
error = True
if not error:
... # save the form in the database
register = RegistrationForm(instance=Registration())
guests = [GuestForm(prefix=str(x), instance=Guest()) for x in range(MAXGUESTS)]
return render_to_response('register.html',{
'form': register,
'max_guests': MAXGUESTS,
'guests': guests,
'error': error,
})
So I need to set a form as optional and be able to differentiate when the whole form is empty and when there is an error. Any idea how ?
Thank you
Solution
def register_form(request):
GuestFormSet = modelformset_factory(Guest, exclude=('register',))
error = False
if request.method == 'POST':
register = RegistrationForm(request.POST, instance=Registration() )
guests = GuestFormSet(request.POST)
if register.is_valid():
print("register is valid")
for guest in guests:
if guest.is_valid():
print("guest is valid") # even if some forms are empty
else:
print("guest is not valid")
error = True
else:
print("register is not valid")
error = True
if not error:
...
# save the form in the database
return something
else:
register = RegistrationForm(instance=Registration())
guests = GuestFormSet(queryset=Guest.objects.none())
return render_to_response('register.html',{
'form': register,
'max_guests': MAXGUESTS,
'guests': guests,
'error': error,
})

You can use a model formset for your guest forms. It can distinguish between empty and invalid forms.
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#model-formsets

I guess you will need some way to determine if a submitted guest form was actually filled in. When an empty form was submitted, ignore it:
forms_to_save =[]
for form in guest_forms:
if guest_form.is_valid()
forms_to_save.append( form )
else:
if form.contains_data(): # you'll have to implement this
error = True
if not error:
for form in forms_to_save():
form.save()

I have a similar problem in a wedding RSVP application that I'm building. In my case, each Guest has an 'attending' checkbox, and if it is not checked (i.e. the guest isn't attending) then I don't want any errors to be reported to the user.
My Guest model (slimmed down for brevity):
class Guest(models.Model):
email = models.EmailField(max_length=50, unique=True)
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
attending = models.BooleanField(default=False)
I have a ModelForm class called RsvpForm that creates a form based on the Guest model. It looks like this:
class RsvpForm(ModelForm):
class Meta:
model = Guest
fields = ('attending', 'email', 'first_name', 'last_name')
def clean(self):
cleaned_data = self.cleaned_data
attending = cleaned_data.get('attending')
if not attending:
#clear the error collection
self._errors['email'] = {}
return cleaned_data
The solution to this problem lies in the clean() method that I've overriden in my RsvpForm class. In it, I check whether or not the guest is attending. If they aren't, then I clear the error messages for each of the other fields.
Originally, I had cleared the errors for the entire form like this:
if not attending:
#clear the error collection
self._errors['email'] = ''
self._errors['first_name'] = ''
self._errors['last_name'] = ''
But for some reason, modifying the values of any of these keys caused the form to fail validation, which in turn prevented Guest data from being saved if they had indicated that they were not attending. A small bug that most would never find, but annoying nonetheless.
At some point along the line, I had also tried to clear the errors collection by calling .clear() on the self._errors dictionary:
if not attending:
#clear the error collection
self._errors.clear()
But for some reason, the errors were still shown, even though I could verify that the dictionary was empty.
The end result is that form validation errors are only shown to my user if the 'attending' checkbox is selected, which is ideal because Guests might want to update their own contact information prior to deciding on who they will bring as a plus one.

There is a simpler solution than Alasdair's: using the empty_permitted keyword argument.
>>> f = MyForm(data={}, empty_permitted=True)
>>> f.is_valid()
True

Related

Django initial value for MultiChoice Field ignored for ModelForm

this is my first post here and I am very new to Django but I just can't seem to find a solution for this problem... I've searched stackoverflow and google but nothing seems to work for me...
I have a wine-app and want to be able to add and remove wines from the user's stock. In the list of wines the user can choose a wine to add and the ID of this wine is passed in the POST data. Since the data is getting lost after the first time the view is rendered I saved the ID in a cookie, which is working, but the problem is when I work with ModelForm de user has to select the foreign key for the user and for the wine, which is bad, so I tried to make it hidden and set the Fk_user and Fk_wine after the user choose the number of bottles to be added but before validation. Here's the problem after google everyone suggested I should use the "initial" and pass that to the form, but this is clearly not working because if I make the fields visible in the form I can see that it is not preselected...
viewy.py:
def addStockView(request):
wineId = request.POST.get('addStock')
if 'addStock' in request.POST:
wine = get_object_or_404(Wine, idwine=int(wineId))
userId = request.user.id
user = get_object_or_404(AuthUser, id=userId)
if request.method == 'POST':
#wineIdNew = request.COOKIES.get('wineIdToAdd')
#wineNew = get_object_or_404(Wine, idwine=wineIdNew)
form = StockForm(request.POST, initial={'fk_wine': wineNew.idwine, 'fk_auth_user': user.id})
if form.is_valid():
form.save()
return redirect('home')
else:
form = StockForm(initial={'fk_wine': wine.id,
'fk_auth_user': user.id})
response = render(request, 'addToStock.html', {'form': form})
response.set_cookie('wineIdToAdd', wineId)
return response
forms.py:
class StockForm(forms.ModelForm):
#fk_wine = ModelChoiceField(queryset=Wine.objects.all(),
# widget=HiddenInput())
#fk_auth_user = ModelChoiceField(queryset=AuthUser.objects.all(),
# widget=HiddenInput())
class Meta:
model = UserWineStock
fields = ['fk_auth_user', 'fk_wine', 'number']
can anyone help me with this..?
Yes, initial data is ignored when a form is bound to submitted data.
Instead of using initial here, you should exclude those two fields from the form and set them on the created object:
form = StockForm(request.POST)
if form.is_valid():
item = form.save(commit=False)
item.fk_wine = wine
item.fk_auth_user = request.user
item.save()
return redirect('home')
(Also, please don't call your fields things like fk_auth_user. Just call it user.)

django form errors before submit

My django form has errors in the initial page load, before the form even has a chance to be submitted.
My view:
def example_function(request):
if request.method == 'POST':
# the request is GET
else:
form = MyForm(user=request.user)
import pdb;pdb.set_trace()
return render_to_response('templates/example.html', locals(), context_instance=RequestContext(request),)
Where I have my pdb imported, in the console I can see that my form already has errors. The output of form.errors in my console is all the fields in the model which are set to not null.
(Pdb) form.errors
{'example_field_1': [u'This field is required.'], 'example_field_2': [u'This field is required.']}
The form has not submit yet, but I am still getting errors. Can someone explain?
I'm using django 1.4.
My form:
class MyForm(forms.ModelForm):
captcha = ReCaptchaField()
_readonly_template = form.TextInput(attrs={'readonly':'readonly'})
first_name = forms.CharField(widget = _readonly_tempalte)
def __init__(self, data=None, *args, **kwargs):
data = data or {}
if 'user' in kwargs:
user = kwargs['user']
del kwargs['user']
data.update({
'first_name' : user.first_name,
})
super(MyForm, self).__init__(data, *args, **kwargs)
class Meta:
model = MyModel
My model:
class MyModel(models.Model):
first_name = models.CharField(max_length=255)
example_field_1 = models.CharField(max_length=255)
example_field_2 = models.CharField(max_length=255)
https://docs.djangoproject.com/en/1.8/ref/forms/validation/
accessing the form.errors attribute will trigger the various form validation methods. Those errors shouldn't show up when you render the form.
I'm not sure how the user field is structured, but keep in mind that if you want the user name, you may want to change that from request.user to request.user.username.
I hope you resolved your issue, but in case you haven't, I had a similar issue which I was able to resolve by using "or None" when setting the form after checking if it is a POST (or GET) request.
In your case it looks like this may be a slightly different issue, but I wondered if this snippet might fix things up:
if request.method == "POST":
form = MyForm(request.POST or None)
# .. do stuff....
else: #.....this is a GET
data = {'user': request.user.username} #note this is changed to username
form = MyForm(data)
Not sure if still useful, but adding it here, as I just ran into this for my ChoiceField items within my form.
I was getting the same error messages, but eventually found out I had forgotten to ad 'or None' when initiating the form inside my view.
The initial code inside my view function that was displaying the error messages from the start:
form=FormName(request.POST)
I just added the 'or None' to it:
form=FormName(request.POST or None)
And all good after that.
Don't you need to do something like this
form = NameForm(request.POST)
Rather then attempting to use the user object to populate the form? Will the user object have an example_field_1 in it?
https://docs.djangoproject.com/en/1.8/topics/forms/
This is the normal behavior.
Some properties of fields are checked on client side. The error messages belong to the form, are part of the html but are not displayed until needed. It saves a client-server request.

Force a form field to be populated in Django

I'm using Django 1.4 with Python 2.7 and Ubunutu 12.04.
I have a form that will update a user's profile. The last item in the form is the password. I pre-populate the form with the existing user's data. The password field does not get pre-populated - and that's fine.
The problem is that when I "save" the data it overwrites the password to be a null or empty field (I can't tell which). Bad.
What can I do to prevent this?
I've tried to make it a required field (forms.py):
password = forms.CharField(widget = forms.PasswordInput(), required = True)
Didn't work.
I've tried to check that the password is not None before updating it (views.py):
if (request.POST.get('password') is not None):
user.set_password(request.POST.get('password'))
Didn't work.
Does an empty form value come back as None? If not, what does it come back as and how can I check if it's empty?
EDIT 1:
I updated my one of my views to check for validation - maybe I did this wrong?
#login_required
def profile(request):
"""
.. function:: profile()
Provide the profile page, where it can be updated
:param request: Django Request object
"""
if request.user.is_authenticated():
user = User.objects.get(username = request.user.username)
user_dict = createUserProfileDict(user)
form = ProfileForm(initial = user_dict);
data = { 'user' : request.user }
data.update({ 'form' : form })
data.update(csrf(request))
if form.is_valid():
return render_to_response("profile.html", data)
Now I receive the following error:
The view rsb.views.profile didn't return an HttpResponse object.
So, it appears my form is not valid? How can I find out why?
Here is the update_profile view:
#login_required
def update_profile(request):
"""
.. function:: profile()
provide the profile page
:param request: Django Request object
"""
if request.user.is_authenticated():
user = User.objects.get(username = request.user)
user.first_name = request.POST.get('first_name')
user.last_name = request.POST.get('last_name')
user.email = request.POST.get('email')
if (request.POST.get('password') is not None):
user.set_password(request.POST.get('password'))
user.save()
# Update the additional user information tied to the user
user_info = UserProfile.objects.get(user_id = user.id)
user_info.company_name = request.POST.get('company_name')
user_info.client_type = request.POST.get('client_type')
user_info.address1 = request.POST.get('address1')
user_info.address2 = request.POST.get('address2')
user_info.city = request.POST.get('city')
user_info.state = request.POST.get('state')
user_info.country = request.POST.get('country')
user_info.zip_code = request.POST.get('zip_code')
user_info.phone_number = request.POST.get('phone_number')
user_info.save()
return profile(request)
First of all, remember to control if your form "is_valid()"
To theck if your form has been submitted with empty values or not, use
MyForm.has_changed()
too bad this is not a documented functionality :(
If you want a default password, i suggest you check if the field is valid then use something like
''.join([choice(string.letters + string.digits) for i in range(7)])
to generate a new password for the user (range(7) is the length you want). Then use an opt-in method (see: send a user an email with his temporary password)
edit based on new context:
from the django docs:
If a Field has required=False and you pass clean() an empty value,
then clean() will return a normalized empty value
rather than raising ValidationError.
For CharField, this will be a Unicode empty string.
For other Field classes, it might be None. (This varies from field to field.)
That's it, your password field should have required=False, so you can treat that as an empty string
Then in your view you could do:
if input_password != '' and input_password != saved_password:
saved_password = input_password
It's just pseudocode, but it should give you a clear idea

Duplication check before saving the form data in Django

I got a form as following:
class CourseAddForm(forms.ModelForm):
"""Add a new course"""
name = forms.CharField(label=_("Course Name"), max_length=100)
description = forms.Textarea()
course_no = forms.CharField(label=_("course Number"), max_length=15)
#Attach a form helper to this class
helper = FormHelper()
helper.form_id = "addcourse"
helper.form_class = "course"
#Add in a submit and reset button
submit = Submit("Add", "Add New Record")
helper.add_input(submit)
reset = Reset("Reset", "Reset")
helper.add_input(reset)
def clean(self):
"""
Override the default clean method to check whether this course has been already inputted.
"""
cleaned_data = self.cleaned_data
name = cleaned_data.get('name')
hic = cleaned_data.get('course_no')
try:
course=Course.objects.get(name=name)
except Course.DoesNotExist:
course=None
if course:
msg = u"Course name: %s has already exist." % name
self._errors['name'] = self.error_class([msg])
del cleaned_data['name']
return cleaned_data
else:
return self.cleaned_data
class Meta:
model = Course
As you can see I overwrote the clean method to check whether this course has already existed in the database when the user is trying to add it. This works fine for me.
However, when I want to add the same check for the form for editing, the problem happened. Because it is editing, so the record with same course name has already exist in the DB. Thus, the same check would throw error the course name has already exist. But I need to check the duplication in order to avoid the user updating the course name to another already existed course name.
I am thinking of checking the value of the course name to see if it is changed. If it has been changed, than I can do the same check as above. If it has not been changed, I don't need to do the check. But I don't know how can I obtain the origin data for editing.
Does anyone know how to do this in Django?
My view looks as following:
#login_required
#csrf_protect
#never_cache
#custom_permission_required('records.change_course', 'course')
def edit_course(request,course_id):
# See if the family exists:
try:
course = Course.objects.get(id=course_id)
except Course.DoesNotExist:
course = None
if course:
if request.method == 'GET':
form = CourseEditForm(instance=course)
return render_to_response('records/add.html',
{'form': form},
context_instance=RequestContext(request)
)
elif request.method == 'POST':
form = CourseEditForm(request.POST, instance=course)
if form.is_valid():
form.save()
return HttpResponseRedirect('/records/')
# form is not valid:
else:
error_message = "Please correct all values marked in red."
return render_to_response('records/edit.html',
{'form': form, 'error_message': error_message},
context_instance=RequestContext(request)
)
else:
error = "Course %s does not exist. Press the 'BACK' button on your browser." % (course)
return HttpResponseRedirect(reverse('DigitalRecords.views.error', args=(error,)))
Thank you.
I think you should just set unique=True on the Course.name field and let the framework handle that validation for you.
Update:
Since unique=True is not the right answer for your case, you can check this way:
def clean(self):
"""
Override the default clean method to check whether this course has
been already inputted.
"""
cleaned_data = self.cleaned_data
name = cleaned_data.get('name')
matching_courses = Course.objects.filter(name=name)
if self.instance:
matching_courses = matching_courses.exclude(pk=self.instance.pk)
if matching_courses.exists():
msg = u"Course name: %s has already exist." % name
raise ValidationError(msg)
else:
return self.cleaned_data
class Meta:
model = Course
As a side note, I've also changed your custom error handling to use a more standard ValidationError.
I believe excluding the current instance id from the results would solve the problem:
from django.db.models import Q
try:
qs = Course.objects.filter(name=self.cleaned_data.get('name'))
if self.instance.pk is not None:
qs = qs.filter(~Q(pk=self.instance.pk))
course = qs.get()
except Course.DoesNotExist:
course = None
However as dokkaebi pointed out, unique is really the better way to go with this, as this solution is vulnerable to race conditions. I'm not sure what your datamodel looks like but I suspect defining
class Meta:
unique_together = ('department', 'name')
should accomplish what you want.

django inline editing - inline form only required if at least one field is filled out

I created a view which returns a form including a contact form and two phone_number forms, following this example:
multiple forms
The phone number forms should only be validated if the user inserts at least a value for one field in a phone number form. For example: a phone number has a type and a number. If the user is selecting the type, the number is required.
Now I'm wondering how i can check in the view whether the user inserted a value / selected a type or inserted a number. It should work like in the admin for inline editing a model.
my view looks like this:
def contact_add(request):
user = request.user
if request.method == 'POST':
cform = ContactForm(request.POST)
pforms = [PhoneNumberForm(request.POST, prefix=str(x)) for x in range(0,3)]
if cform.is_valid() and all([pf.is_valid() for pf in pforms]):
new_contact = cform.save(commit=False)
new_contact.created_by = user
new_contact.save()
for pf in pforms:
new_phone_number = pf.save(commit=False)
new_phone_number.contact = new_contact
new_phone_number.save()
request.user.message_set.create(message='Contact %s has been added.' % new_contact.__str__())
return HttpResponseRedirect("/crm/contacts/?oby=1")
else:
cform = ContactForm()
pforms = [PhoneNumberForm(prefix=str(x)) for x in range(0,3)]
return render_to_response(
'crm/contact_add.html',
{'cform': cform, 'pforms': pforms,},
context_instance = RequestContext(request),
)
Edit after first response below:
I tried to accomplish this task with custom validation but did not come to a satisfying end. To ease my task I changed the use-case a bit. I create a form which includes one Contact Form and one Address Form. The Address Form should only be validated if at least one field of the Address Form is filled in, since it should be possible to create a contact without creating a corresponding Address.
First I tried to use custome validation, which looked like this:
class AddressForm(forms.ModelForm):
class Meta:
model = Address
exclude = ('contact',)
def clean(self):
cleaned_data = self.cleaned_data
street = cleaned_data.get("street")
postal_code = cleaned_data.get("postal_code")
city = cleaned_data.get("city")
country = cleaned_data.get("country")
if not street and not postal_code and not city and not country:
#searching a better idea here
return 0
else:
return cleaned_data
But this does not really help, since this way I do not get rid of the validation errors.
This lead me to the idea that the clean method is the wrong place to do this validation, I think I have to check already in the POST.request whether all values for the Address Form are missing. And if they are missing, I do not call is_valid() for the Address Form and just ignore it. If at least one value is available, I just do the normal validation of the Address Form, without overriding the clean() method..
Good or bad idea?
If it is a good idea, how can I easily check the POST request for the values of my Address Form.
Probably I`m thinking way to complicated :-)
Edit: The solution using FormSets:
#login_required
def contact_add(request):
user = request.user
if request.method == 'POST':
cform = ContactForm(request.POST)
phonenumberformset = PhoneNumberFormSet(request.POST)
if cform.is_valid() and classificationformset.is_valid() and addressformset.is_valid() and phonenumberformset.is_valid():
new_contact = cform.save(commit=False)
new_contact.created_by = user
new_contact.save()
new_phonenumber_instances = phonenumberformset.save(commit=False)
for new_phonenumber in new_phonenumber_instances:
new_phonenumber.contact = new_contact
new_phonenumber.save()
request.user.message_set.create(message='Contact %s has been added.' % new_contact.__str__())
return HttpResponseRedirect("/crm/contacts/?oby=1")
else:
cform = ContactForm()
#By default, when you create a formset from a model, the formset will use
#a queryset that includes all objects in the model (e.g., Author.objects.all()).
#Here we want to present an empty formset in order to add a new object
phonenumberformset = PhoneNumberFormSet(queryset=PhoneNumber.objects.none())
return render_to_response(
'crm/contact_add.html',
{'cform': cform, 'phonenumberformset': phonenumberformset,},
context_instance = RequestContext(request),
)
Please note that this can also be accomplished using an inlineformset_factory, see my other post for more details: link
Note that if you are using FormSets you have to include a management_form for each form_set in your template. docs
Otherwise you get this error:
[u'ManagementForm data is missing or has been tampered with']
Using a formset inside a view is as easy as using a regular Form class. The only thing you will want to be aware of is making sure to use the management form inside the template.
{{ context.phonenumberformset.management_form }}
You should be using formsets rather than messing around with dynamic prefixes for your PhoneNumber subform - it will make everything much easier, and this is indeed how the admin manages inline forms (see also the model formsets documentation).
Formsets are intelligent enough that if no information is entered in one form of the formset, it does not enforce the required elements - but if one element is filled, it will enforce all the validation requirements. This sounds like it should solve your problem.
What you want to do is define custom validation on the form.
class PhoneNumberForm(forms.Form):
# Everything as before.
...
def clean(self):
cleaned_data = self.cleaned_data
phone1 = cleaned_data.get("phone1")
if phone1:
# validate manually, and if it doesn't pass:
self._errors["phone1"] = ErrorList(["Hey, this field is wrong."])
del cleaned_data["phone1"]
# Always return the full collection of cleaned data.
return cleaned_data
Then in the view, you want to rely on Django's built-in error form validation error handling:
{{ pforms.phone1 }}
{{ pforms.phone1.errors }}