Use Django views for handling blobstore uploads

2020-06-06 06:26发布

In stead of using the BlobstoreUploadHandler supplied in AppEngine, I'd prefer to use a Django view, so I can keep all the urls and view functions together. However, I can't find out how to get the blob-key of the uploaded file! (like get_uploads() does for the upload handler). I saw that the BlobstoreUploadHandler uses request.params, but I don't think that is available from Django's Request.

def upload_form(request):
    upload_url = blobstore.create_upload_url(reverse(upload_blob))
    output = '<html><body>'
    output += '<form action="%s" method="POST" enctype="multipart/form-data">' % upload_url)
    output += ('''Upload File: <input type="file" name="file"><br> <input type="submit"
        name="submit" value="Submit"> </form></body></html>''')

def upload_blob(request):
    print request
    # How to get the 'blob-key' from request?!

When I examine the request object, all I get is

<WSGIRequest
GET:<QueryDict: {}>,
POST:<QueryDict: {u'submit': [u'Submit']}>
# And COOKIES, META, etcetera

EDIT: Request.FILES

I discovered that some info can be extracted using Request.FILES, which gives:

<MultiValueDict: {u'file': [<InMemoryUploadedFile: my_file (message/external-body)>]}>

However, I assume that the blobstore still handles the file content (is that why it says "content_type=message/external-body"?), so I still need the key somehow. Calling read() gives:

Content-Type: application/octet-stream
MIME-Version: 1.0
Content-Length: 17
Content-MD5: ZmQ3OTJhNjMzNGE0OTAzNGU4NjE5MDNmMGEwNjliMGE=
content-type: application/octet-stream
content-disposition: form-data; name="file"; filename="a1_blob"
X-AppEngine-Upload-Creation: 2012-02-12 22:11:49.643751

So it looks like AppEngine actually replaced the file content by this descriptor, but still, where does AppEngine put the key?

I'm starting to suspect that the blob-key is just lost when not using the webapp framework, since the UploadedFile object has no key() method.

4条回答
三岁会撩人
2楼-- · 2020-06-06 06:28

The key info isn't directly in the file, it's in file.blobstore_info.key()

  1. post your form containing your image to a url created using blobstore.create_upload_url():

    from google.appengine.ext import blobstore
    
    upload_url = blobstore.create_upload_url('/add_image/')
    

the created url will save the image in the blobstore and redirect the request (with modified file object) to /add_image/

  1. define a url pattern and view for /add_image/ and handle the image:

    def add_action_image(request):
        image = request.data.get('image')
        image_key = image.blobstore_info.key()
        ... addl' logic to save a record with the image_key...
    
查看更多
我欲成王,谁敢阻挡
3楼-- · 2020-06-06 06:42

I had the same problem yesterday. Thanks to your post I realizad that the problem was django and his class views. I finally use a code that I have since 2011 and it still works. It does not use BlobstoreUploadHandler, but it gets the blob_infos from the request after automatically upload it to blobstore.

You can use that function in the next way from your callback django function or class (I finally did not try it in a class view from Django but I think it will work. Currently I'm using it in a function view from Django with its request):

media_blobs = get_uploads(request, populate_post=True)

The function is the next:

import cgi 
from google.appengine.ext import blobstore
def get_uploads(request, field_name=None, populate_post=False):
    """Get uploads sent to this handler.
    Args:
    field_name: Only select uploads that were sent as a specific field.
    populate_post: Add the non blob fields to request.POST
    Returns:
      A list of BlobInfo records corresponding to each upload.
      Empty list if there are no blob-info records for field_name.
    """

    if hasattr(request,'__uploads') == False:
        request.META['wsgi.input'].seek(0)
    ja = request.META['wsgi.input']
    fields = cgi.FieldStorage(request.META['wsgi.input'], environ=request.META)

    request.__uploads = {}
    if populate_post:
        request.POST = {}

    for key in fields.keys():
        field = fields[key]
        if isinstance(field, cgi.FieldStorage) and 'blob-key' in field.type_options:
            request.__uploads.setdefault(key, []).append(blobstore.parse_blob_info(field))
        elif populate_post:
            if isinstance(field, list):
                request.POST[key] = [val.value for val in field]
            else:
                request.POST[key] = field.value
    if field_name:
        try:
            return list(request.__uploads[field_name])
        except KeyError:
            return []
    else:
        results = []
        for uploads in request.__uploads.itervalues():
            results += uploads
        return results

The last function is not mine. I do not remember where I got it three or four years ago. But I think it will help someone.

UPDATE:

You also can use a view handler of webapp.WSGIApplication and at the same time use django. This way will allow you to use BlobstoreUploadHandler and BlobstoreDownloadHandler (for video stream as example). You only need to add the view class in main.py and create its handler:

class ServeVideoHandler(blobstore_handlers.BlobstoreDownloadHandler):
    def get(self, resource):
        ...

downloader_handler = webapp.WSGIApplication([('/pathA/pathB/([A-Za-z0-9\-\=_]+)', ServeVideoHandler),], debug=True)

And in app.yaml add the handler before the script main.application that contains your django app.

- url: /pathA/pathB/(.+)
  script: main.downloader_handler
查看更多
欢心
4楼-- · 2020-06-06 06:44

As you noted, BlobstoreUploadHandler is open source, so you can see the logic they use to parse the key out of the request params. Note that request.params just includes variables from both the query string and the request body (for POST requests). So you might want to start with your djnago request's REQUEST object.

查看更多
何必那么认真
5楼-- · 2020-06-06 06:45

It took me a long time to find, but the content_type: message/external-body requires extra parameters, to find the actual file, in AppEngine's case, this is the blob-key. However, Django doesn't support these extra content_type parameters, so they are indeed lost in the process. There seems to be a patch, but I don't think it's in the AppEngine Django version yet.

https://code.djangoproject.com/ticket/13721

查看更多
登录 后发表回答