Tuesday, February 7, 2012

Upload Files to a Django form

Decided to add a little feature to the Remixerator the other day - the ability to supply your own base kickstart file. Django has a Filefield widget for its forms that make this work like magic. The form looks a bit like this.


class KickstartForm(forms.Form):
    select_language = forms.ChoiceField(choices=languages(), initial='en_US')
    select_timezone = forms.ChoiceField(choices=timezones())
    name_of_the_spin = forms.CharField()
   
    ks_choices = ((None, 'Use your own!'),) + ls_ks()
    based_on = forms.ChoiceField(choices=ks_choices)
   
    uploaded_kickstart = forms.FileField()
    # ... Set the widget css...



I then tossed in a little jquery to hide the form until the "Use your own!" option is selected...


// Show the "upload file" portion if the user decides to use their own kickstart file
$("#id_based_on").change(
  function() {
    if( $('#id_based_on option:selected').text() == 'Use your own!' ) {
      $("label[for='id_uploaded_kickstart']").show('fast');
      $('#id_uploaded_kickstart').show('fast');
    } else {
      $("label[for='id_uploaded_kickstart']").hide('fast');
      $('#id_uploaded_kickstart').hide('fast');
    }
  });


Next comes the handler for actually dealing with the file, which gets sent along with a POST


def handle_uploaded_ks(uploaded_ks):
    # MEDIA_ROOT defined with django settings, mine is set to a temp cache
    ks_path = os.path.join(settings.MEDIA_ROOT, uploaded_ks._name)
    print ks_path
    destination = open(ks_path, 'wb+')
    for chunk in uploaded_ks.chunks():
        destination.write(chunk)
    destination.close()


Then I needed to go to the view that handles the POST and make it execute the handler.

# The package view (page after the form)

def packages(request):
    """
    Select packages and groups
    """
    # if the user is uploading their own kickstart file, toss it in the cache directory
    if request.FILES:
        handle_uploaded_ks(request.FILES['uploaded_kickstart'])


And finally, we had to allow the form to handle all of this. This is simply done through the form tag's enctype in the html...
<form method="POST" action="/packages/" enctype="multipart/form-data">
    {{ form.as_p }}
    <input type="submit" value="Next" id="nextButton"/>
</form>