0

Sorry, a beginner's question: I have a very simple function in my Django application with which I can upload a file from a web browser to my server (works perfectly!). Now, instead of the web browser, I would like to use an iPhone.

I got a bit stuck as I don't really know how to provide Django with a valid form, i.e. we need a file name and enctype="multipart/form-data" as far as I understand.

Here is my upload function in Django:

class UploadFileForm(forms.Form):
    file  = forms.FileField()

def handle_uploaded_file(f):
    destination = open('uploads/example.txt', 'wb+')
    for chunk in f.chunks():
        destination.write(chunk)
    destination.close()

def upload_file(request):
    if request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            handle_uploaded_file(request.FILES['file'])
            print form
            print request.FILES
            return HttpResponse('Upload Successful')
    else:
        form = UploadFileForm()
    return render_to_response('upload.html', {'form': form})

My template looks like this (upload.html):

<form action="" method="post" enctype="multipart/form-data">
    {{ form.file }}
    {{ form.non_field_errors }}
    <input type="submit" value="Upload" />
</form>

Now, let's suppose that I want to send a simple txt file from my iPhone app to the sever. I don't really know how to:

  1. provide the file name
  2. specify the enctype and
  3. make sure that it's in a format Django can read

This is how far I got:

NSString *fileContents = [self loadTXTFromDisk:@"example.txt"];

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] 
                                initWithURL:[NSURL 
                                             URLWithString:@"http://127.0.0.1:8000/uploadfile/"]];


[request setHTTPMethod:@"POST"];
[request setValue:@"text/xml"   forHTTPHeaderField:@"Content-type"];
[request setValue:[NSString stringWithFormat:@"%d", [fileContents length]] 
                                forHTTPHeaderField:@"Content-length"];
[request setHTTPBody:[fileContents dataUsingEncoding:NSUTF8StringEncoding]];

NSURLConnection *theConnection = [[NSURLConnection alloc] 
                                  initWithRequest:request 
                                  delegate:self];

However, Django will not except this as the form it expects is not valid. Cf. above:

form = UploadFileForm(request.POST, request.FILES)
if form.is_valid(): #this will not be true ...
n.evermind
  • 11,944
  • 19
  • 78
  • 122

1 Answers1

2

The enctype from the HTML form should be the Content-Type of the HTTP request. You are currently setting the content type to 'text/xml' instead.

You will also have to build up the body as a multipart mime object. The answers to this question seem to have some code for that: File Upload to HTTP server in iphone programming

Your other option, since you have complete control of the client, is to do an HTTP PUT request. That actually looks closer to what you were doing with your original code. Change the method to 'PUT', and don't use a Django form on the other end; just access request.raw_post_data to get the complete file data.

Community
  • 1
  • 1
Ian Clelland
  • 43,011
  • 8
  • 86
  • 87
  • Thanks, but it still doesn't work. Now I get the exception: "Invalid boundary in multipart: None" What is a boundary and how do I need to set it? Thanks for your help! – n.evermind Jan 21 '12 at 16:09
  • Something like NSString *boundary = [NSString stringWithString:@"---------------------------Boundary Line---------------------------"]; NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary]; [request addValue:contentType forHTTPHeaderField: @"Content-Type"]; – n.evermind Jan 21 '12 at 16:13
  • If so, I have now idea what the boundary line is. Is it random? – n.evermind Jan 21 '12 at 16:13
  • If I change my code to one including boundary and enctype (like in the snippet I posted), my form will still not be valid. – n.evermind Jan 21 '12 at 16:15
  • 1
    The boundary is essentialy a random string, but you need to declare what it is in the content-type line, and then use that to separate parts inside the body. The very last separator is the same boundary string with two '-' characters appended to it. – Ian Clelland Jan 21 '12 at 16:34
  • Thanks Ian. I now tried the PUT method. However, there seem to be a couple of disadvantages: I can't use chunks when saving the file (e.g. for chunk in request.raw_post_data.chunks():) nor can I validate the input. But perhaps validating is not too important anyway. Is the PUT method something which is normally done? I've never heard about it (only GET and POST). Why would people use it over POST? Just because it's simpler? Also, do I still need to use the hole enctype/boundaries thing when using PUT? Thanks so much for your help! – n.evermind Jan 21 '12 at 17:32
  • 1
    PUT is not used as often as GET and POST, simply because web browsers can't use it for forms. You don't need to specify the enctype, or use any multipart MIME types -- PUT says to the server, essentially, "The body of this request is the entire file". – Ian Clelland Jan 21 '12 at 17:48
  • I see, thanks so much for this explanation. However, I still don't see how I can specify the name of the raw_data I'm sending. Can I put the name in the header and then read it in Django? My problem is that the iPhone determines the name of the file and that Django needs to use the same name. – n.evermind Jan 21 '12 at 18:24