Django forms.FileField(required=True) is passing validation if empty
django, django-forms
Solution
I think I've seen this before. From anecdotal experience it seems like Django does not stop validation if a field is missing (e.g. it will call clean() even if missing required fields). It says a required field is missing by not including the field name in cleaned_data.
Scott pointed out it is documented here:
For any field, if the Field.clean() method raises a ValidationError, any field-specific cleaning method is not called. However, the cleaning methods for all remaining fields are still executed.
Therefore, I think the solution is just to program your clean() method defensively by checking if the 'file' key is in cleaned_data and if it isn't, just return cleaned_data as is. Django already knows the required field is missing, so is_valid() will fail and no harm will come.
def clean(self):
upload_to = '/some/path'
if not 'file' in self.cleaned_data:
return self.cleaned_data
upload_to += self.cleaned_data['file'].name
Problem
I have a Django Form (not a ModelForm) with a required FileField. According to the FileField documentation, validation of the FileField should validate that non-empty file data has been bound to the form, but I am not seeing this behavior. Instead I am able to submit the form without a file and the form passes validation. The expected behavior would be to fail validation. Things work as expected when a file is specified in the form. My form looks something like this: ``` class BucketUploadForm(forms.Form): file = forms.FileField(required=True) # required=True is the default, but I'm being explicit def clean(self): upload_to = '/some/path' upload_to += self.cleaned_data['file'].name # this is raising a KeyError ``` My view looks something like this: ``` def bucket_upload(request): if request.method == 'POST': form = BucketUploadForm(request.POST, request.FILES) if form.is_valid(): # this is raising the aforementioned KeyError when no file is submitted do_stuff() return HttpResponseRedirect(some_url) else: form = BucketUploadForm(initial=request.GET) return render_to_response('handin/bucket_upload.html', {'form': form}, context_instance=RequestContext(request)) ``` My template looks something like this: ``` <form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} <table> {{ form }} </table> <input type="submit" value="Upload" /> </form> ``` The traceback looks like this: ``` Django Version: 1.3.1 Python Version: 2.7.3 Installed Applications: ['django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.admin', 'django.contrib.admindocs', 'hgrepo', 'sshkey', 'handin', 'accounts'] Installed Middleware: ('django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware') Traceback: File "/usr/lib/python2.7/dist-packages/django/core/handlers/base.py" in get_response 111. response = callback(request, *callback_args, **callback_kwargs) File "/usr/lib/python2.7/dist-packages/django/contrib/auth/decorators.py" in _wrapped_view 23. return view_func(request, *args, **kwargs) File "/usr/lib/python2.7/dist-packages/django/views/decorators/http.py" in inner 45. return func(request, *args, **kwargs) File "/home/sduckwo/projects/webhandin/webhandin/handin/views.py" in bucket_upload 461. if form.is_valid(): File "/usr/lib/python2.7/dist-packages/django/forms/forms.py" in is_valid 121. return self.is_bound and not bool(self.errors) File "/usr/lib/python2.7/dist-packages/django/forms/forms.py" in _get_errors 112. self.full_clean() File "/usr/lib/python2.7/dist-packages/django/forms/forms.py" in full_clean 268. self._clean_form() File "/usr/lib/python2.7/dist-packages/django/forms/forms.py" in _clean_form 296. self.cleaned_data = self.clean() File "/home/sduckwo/projects/webhandin/webhandin/handin/forms.py" in clean 116. upload_to += self.cleaned_data['file'].name Exception Type: KeyError at /courses/a/b/assignments/c/sduckwo/upload Exception Value: 'file' ``` UPDATE Removing the clean method from BucketUploadForm causes the FileField's validation to fail as expected. However, I need the clean method for other checks, so removing it permanently is not an option. I've also found that by modifying the clean method to look like this: ``` class BucketUploadForm(forms.Form): file = forms.FileField(required=True) # required=True is the default, but I'm being explicit def clean(self): if 'file' not in self.cleaned_data: raise ValidationError('No file or empty file given') upload_to = '/some/path' upload_to += self.cleaned_data['file'].name # this is raising a KeyError ``` then validation fails as expected, but I get two error messages: - 'No file or empty file given', raised by BucketUploadForm.clean() - 'This field is required', raised by FileField.clean(), which is what I was after in the first place. This tells me that FileField.clean() is raising a ValidationError, but that exception is somehow being ignored unless BucketUploadForm.clean() either does not exist or also raises a ValidationError. So now I'm maybe a little closer to my end goal but still very confused.