Codejoy Codejoy - 6 months ago 31
Python Question

forms in django, overriding validation on file upload to make sure just one value is there

Edit: Mostly got this working with a clean update

I added docfile2 and docfile3 beyond the example I used to code this. I also changed the view to handle them:

@login_required(login_url='/ngasite/login/')
def list(request):
# Handle file upload
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
for fieldname in ('docfile', 'docfile2', 'docfile3'):
file = form.cleaned_data[fieldname]
if file:
Document(docfile=file).save()


# Redirect to the document list after POST
return HttpResponseRedirect(reverse('ngasite:list', args=""))
else:
form = DocumentForm() # A empty, unbound form

# Load documents for the list page
documents = Document.objects.all()
paginator = Paginator(documents, 25)
page = request.GET.get('page')
try:
docs = paginator.page(page)
except PageNotAnInteger:
docs = paginator.page(1)
except EmptyPage:
docs = paginator.page(paginator.num_pages)


# Render list page with the documents and the form
return render_to_response(
'ngasite/list.html',
{'documents': documents, 'form': form, 'docs':docs},
context_instance=RequestContext(request)
)
This worked pretty well, I can


handle three files on my form now instead of one. But relisting after a post gives me an
attribute has no file associated with it
error. I am guessing because my checks for saving in the view is not really working correctly and saving regardless if there was a file or not. Maybe my python syntax is bad?
no errors int he console though.

This is what is failing in my list.html template:

{% if docs %}
<ul>
{% for doc in docs %}
<li><a href="{{ doc.docfile.url }}">{{ doc.docfile.name }} </a></li>
{% endfor %}

</ul>
{% else %}
<p>No documents.</p>
{% endif %}


So my real forms.py with the overriding of the is_valid

from django import forms

class DocumentForm(forms.Form):
docfile = forms.FileField(
label='Select a file',
help_text='max. 7 Gigabytes',
required=False
)
docfile2 = forms.FileField(
label='Select a file',
help_text='max. 7 Gigabytes',
required=False
)
docfile3 = forms.FileField(
label='Select a file',
help_text='max. 7 Gigabytes',
required=False
)
def clean(self):
data = super(DocumentForm, self).clean()

if not (data['docfile'] or data['docfile2'] or data['docfile3']):
return ValidationError('You must upload at least one file.')

return data


What am i missing? my view fails on lines like

docfile=request.FILES['docfile'] cause it knows its missing, I thought my if newdoc2: etc would handle that but it doesnt work like I think it does/should

Ultimately as I learn more python and Django i want to turn this into a nice file uploader with responses to the browser to show the upload progress etc.

Answer

You should set your fields to required=False, which will avoid a validation error if any field is empty:

docfile3 = forms.FileField(
    label='Select a file',
    help_text='max. 7 Gigabytes',
    required=False
)

Then in your form clean method check to see if at least one file is there:

from django.core.exceptions import ValidationError

def clean(self):
    data = super(DocumentForm, self).clean()

    # if there are already form errors, don't proceed with additional
    # validation because it may depend on fields which didn't validate.
    if self.errors:
        return data

    if not (data['docfile1'] or data['docfile2'] or data['docfile3']):
        return ValidationError('You must upload at least one file.')

    return data

I have not actually run this code so there may be some little issue but you get the idea...

In your view form_valid() method, there are problems with your saving code. Do something more like this:

for fieldname in ('docfile', 'docfile2', 'docfile3'):
    file = request.FILES[fieldname]
    if file:
        Document(docfile = file).save()

If you get an exception on file = request.FILES[fieldname] then do it as file = request.FILES.get(fieldname, None)...

Comments