Validation of modelformsets from modelforms - django

One view I have is using a modelform with custom field cleaning. One type of cleaning is checking if the user is trying to submit a change to a field that is already set to the value, and it works exactly how I want it to work by throwing a ValidationError. The problem of course is that I can only submit one form at a time, so I'd like to use a modelformset to submit multiple forms.
I know it is possible to override the modelformset's clean method, but I'm asking if it's possible to use the modelform's field cleaning methods on the modelformset?. Currently when I submit the modelformset with empty fields the is_valid() passes which seems strange to me...
I also would like to know "typically" where the custom modelformset validation code would go? I was thinking with the forms.py.
*Edit -- with answer. My httpResponseRedirect was allowing the form to be submitted without validation.
def mass_check_in(request):
# queryset
qs = Part.objects.none()
errlst=[]
c = {}
c.update(csrf(request))
# Creating a model_formset out of PartForm
PartFormSetFactory = modelformset_factory(model=Part,
form=PartForm,
formset=BasePartFormSet,
extra=2)
if request.method == 'POST':
PartFormSet = PartFormSetFactory(request.POST)
if PartFormSet.is_valid():
PartFormSet.save()
return http.HttpResponseRedirect('/current_count/')
else:
PartFormSet = PartFormSetFactory(queryset=qs, initial=[
{'serial_number':'placeholder',
},
{'serial_number':'placeholder'
}])
return render(request,'mass_check_in.html',{
'title':'Add Item',
'formset': PartFormSet,
'formset_errors': PartFormSet.non_form_errors(),
})

If you don't enter any data at all in one of the modelforms in your model formset, it will skip validation for that form; from the docs:
The formset is smart enough to ignore extra forms that were not changed.
You can actually disable this functionality though by forcing empty_permitted=False on the forms; see the accepted answer to this question for more: Django formsets: make first required?
Formsets do have their own clean method, but it's used to validate information between two or more forms in the formset, not for validating the individual forms themselves (which should be taken care of in the forms clean method - as you are doing now.
A formset has a clean method similar to the one on a Form class. This is where you define your own validation that works at the formset level:
Here's another similar question:
Django formset doesn't validate

Related

Prevent Django forms highlighting correct fields

After submitting a form that contains errors, the incorrect fields get marked as such. Correctly submitted fields, however, also get marked.
Is there a way to prevent this from happening? I'd prefer it if Django were to only render the incorrect fields differently, and render the correctly submitted fields as normal.
I checked the API offered by the Form object, but there does not seem to be a property that lists these correctly submitted fields.
Django by default only marks the invalid fields, not the valid ones.
Be sure you are passing the POST data to the form in the view when POST.
(incomplete example below)
if request.method == 'POST':
form = YourForm(request.POST)
if form.is_valid():
# your code then redirect
else: #GET
form = YourForm()
You can take a look to this Django example in the docs for a full example.

Django custom forms.clean() method not working

I've been looking for an answer for a while, but i really don't know how this happens. I'm trying to make a form that receives 2 files, and I'm overriding the clean() method to check if the names and extensions are correct. This is what I have:
def clean(self):
cleaned_data = super(UploadForm, self).clean()
obs_filename = cleaned_data.get('obs').name.split('.')
nav_filename = cleaned_data.get('nav').name.split('.')
if obs_filename[0] != nav_filename[0] or [obs_filename[1], nav_filename[1]] != ['obs', 'nav']:
raise forms.ValidationError('Filenames do not match.')
if os.path.isfile(PROJECT_ROOT + '/data/unprocessed/' + '.'.join(obs_filename)) and os.path.isfile(PROJECT_ROOT + '/data/unprocessed/' + '.'.join(nav_filename)):
raise forms.ValidationError('Files already exist.')
return cleaned_data
The problem is, when I use this, the check for input doesn't work (the form submits without selecting any files). When I remove this code, it works fine.
The call to super(UploadForm, self).clean() is the same as in the django documentation for custom clean() functions. I'm using django 1.4. Any thoughts?
Thanks!
File fields are a bit more complicated; you have to make sure your files are bound to your form when you instantiate it. If you use class-based generic views, this should happen automatically, so if you're not seeing them then I'm guessing you're not. In which case, have a look at https://docs.djangoproject.com/en/1.4/ref/forms/api/#binding-uploaded-files. In short, when you instantiate your form, you have to get the files from request.FILES and pass these in as a separate argument to your form e.g. f = MyForm(request.POST, request.FILES).
Also make sure you have enctype="multipart/form-data" in your FORM tag in the HTML.

Django: Form in base template

Hi Stackoverflow people,
In my Django project I created a form to register users. This forms can be called through a specific url -> view method. Once the user has filled in the form, presses submit, the same view method will be called and if form.is_valid() is true, then ... simply a standard form, nothing special.
Now, I would like to integrate this little form on every page, and therefore I would like to add it to the base template. I have read that I could populate the form variable through a context_processor, but could I define the process after the submission of the form?
I have created the context_processor.py (as below), added the context_processor to the TEMPLATE_CONTEXT_PROCESSOR dir in the settings (as described here):
from app.forms import Form
def registration_form(request):
return {
registration_form : Form()
}
First of all, the form variable won't be displayed.
And secondly, how do I manipulate the form submission?
I think I misunderstanding the context_processor of Django and would be more than happy about comments on the overall process.
Thank you!
how are you trying to access to form in your template? you probably don't want to use a function name as your dictionary key, maybe you want
return {
'registration_form': Form(),
}
not sure what you mean by manipulate the form submission, but i guess you'd need all the form processing logic in your context processor
if request.POST:
form = Form(request.POST)
# validate etc
instead of creating context processor, create template tag for the purpose and place the tag in base.html
for form submission and displaying errors use ajax, and front-end validations.

is_valid() vs clean() django forms

In the process of finding a way to validate my django forms, I came across two methods is_valid() and clean() in the django docs. Can anyone enlighten me the how they are different/same? What are the pros and cons of either?
Thanks.
is_valid() calls clean() on the form automatically. You use is_valid() in your views, and clean() in your form classes.
Your clean() function will return self.cleaned_data which if you will notice in the following view is not handled by you as the programmer.
form = myforms.SettingsForm(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
#do stuff
You didn't have to do clean_data = form.is_valid() because is_valid() will call clean and overwrite data in the form object to be cleaned. So everything in your if form.is_valid() block will be clean and valid. The name field in your block will be the sanitized version which is not necessarily what was in request.POST.
Update
You can also display error messages with this. In clean() if the form data isn't valid you can set an error message on a field like this:
self._errors['email'] = [u'Email is already in use']
Now is_valid() will return False, so in the else block you can redisplay the page with your overwritten form object and it will display the error message if your template uses the error string.
Just wanted to add that the best way now to add an error to a form you're manually validating in is_valid() is to use Form.add_error(field, error) to conform with Django's ErrorDict object.
Doing
self._errors['field'] = ['error message']
will come out funky when rendering {{form.errors}}, like:
fielderror messsage
instead of the expected
field
-error message
so instead do:
self.add_error('email', 'Email is already in use')
See https://docs.djangoproject.com/en/1.10/ref/forms/api/#django.forms.Form.add_error

Prepopulate Django (non-Model) Form

I'm trying to prepopulate the data in my django form based on some information, but NOT using ModelForm, so I can't just set the instance.
This seems like it should be really easy, but for some reason I can't find any documentation telling me how to do this. This is my form:
class MyForm(forms.Form):
charfield1 = forms.CharField(max_length=3)
charfield2 = forms.CharField(max_length=3)
choicefield = forms.ModelChoiceField(MyModel.objects.all())
I tried just doing:
form = MyForm()
form.charfield1 = "foo"
form.charfield2 = "bar"
# a model choice field
form.choicefield = MyModel.objects.get(id=3)
which does not work.
Try:
form = MyForm({'charfield1': 'foo', 'charfield2': 'bar'})
The constructor of Form objects can take a dictionary of field values. This creates a bound form, which can be used to validate the data and render the form as HTML with the data displayed. See the forms API documentation for more details.
Edit:
For the sake of completeness, if you do not want to bind the form, and you just want to declare initial values for some fields, you can use the following instead:
form = MyForm(initial={'charfield1': 'foo', 'charfield2': 'bar'})
See the documentation of initial values for details.
There are two ways of populating a Django form.
The first is to pass a dictionary as the first argument when you instantiate it (or pass it as the data kwarg, which is the same thing). This is what you do when you want to use POST data to populate and validate the form.
data_dict = {'charfield1': 'data1', 'charfield2': 'data2', 'choicefield': 3}
form = MyForm(data_dict)
However, this will trigger validation on the form, so only works if you are actually passing in valid and complete data to begin with - otherwise you will start off with errors.
The other way to populate a form is to use the initial parameter (documented here). This gives initial values for the form fields, but does not trigger validation. It's therefore suitable if you're not filling in all values, for example.
form = MyForm(initial=data_dict)
To populate a choicefield via initial, use the pk value.
You can use model_to_dict() to convert an instance to a dictionary, and then populate a form with that. Something like this should work:
from django.forms.models import model_to_dict
...
my_obj = MyModel.objects.get(abc=123)
form = MyForm(initial=model_to_dict(my_obj))
Note: I'm using django version 1.3
For what it's worth, the FormView class-based view way to do this would be to override the FormView's get_initial function. get_initial returns the initial keyword arguments used by get_form_kwargs to instantiate the form.
Docs:
for get_initial, here,
for get_form_kwargs, here.
Sample code:
from django.views.generic.edit import FormView
class MyFormView(FormView):
def get_initial(self):
initial = super(MyFormView, self).get_initial()
# update initial field defaults with custom set default values:
initial.update({'charfield1': 'foo', 'charfield2': 'bar'})
return initial