Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django forms.FileField(required=True) is passing validation if empty

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:

  1. 'No file or empty file given', raised by BucketUploadForm.clean()
  2. '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.

like image 684
Scott Duckworth Avatar asked Jul 11 '13 14:07

Scott Duckworth


1 Answers

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
like image 73
OEP Avatar answered Nov 14 '22 23:11

OEP