📜 ⬆️ ⬇️

Implementing the “dropbox” service using the Blobstore API (Part 1)

Blobstore api - a recent addition to the App Engine platform, allows you to download and distribute large files, currently up to 2Gb ( in the original - 50Mb, approx. Translator ). It is also one of the most difficult to use APIs. A small series of notes will show how to implement the “dropbox” service based on App Engine using the Blobstore API. To begin with, we will look at the basics needed to upload files, save information about them in the datastore and further return them to users for download.

Let's start with the download form. This step is pretty obvious: we create a standard HTML form, the only thing we have to do is generate the URL for the form action by calling the blobstore.create_upload_url method and passing it the URL with the desired handler. Below is our code:
class FileUploadFormHandler(BaseHandler): @util.login_required def get(self): self.render_template("upload.html", { 'form_url': blobstore.create_upload_url('/upload'), 'logout_url': users.create_logout_url('/'), }) 

Everything is standard, but still worth noting - for convenience, we use the login_required decorator from the google.appengine.ext.webapp.util package, which requires the user to be logged in (and redirects to the login form otherwise). And this is a template:
 <html> <head> <title> </title> </head> <body> <p style="float: right"><a href="{{logout_url}}">Log Out</a></p> <h1>Upload a file to App Engine File Hangar</h1> <form method="POST" action="{{form_url}}" enctype="multipart/form-data"> <input type="file" name="file" /><br /> <input type="submit" value="Upload" /> </form> </body> </html> 

Pay attention to the attributes of the form tag: method must be “POST”, and enctype must be “multipart / form-data”, otherwise the download will not work!

Next we want to write a boot handler. Let's start with a description of the model for storing information about the uploaded files. Here she is:
 class FileInfo(db.Model): blob = blobstore.BlobReferenceProperty(required=True) uploaded_by = db.UserProperty(required=True) uploaded_at = db.DateTimeProperty(required=True, auto_now_add=True) 

A fairly standard model, with the exception of the blob field of type blobstore. BlobReferenceProperty. BlobReferenceProperty is similar to ReferenceProperty, with the only difference that it refers not to a different model, but to Blob. When requested, a BlobInfo object is returned .

The code responsible for the boot process:
 class FileUploadHandler(blobstore_handlers.BlobstoreUploadHandler): def post(self): blob_info = self.get_uploads()[0] if not users.get_current_user(): blob_info.delete() self.redirect(users.create_login_url("/")) return file_info = FileInfo(blob=blob_info.key(), uploaded_by=users.get_current_user()) db.put(file_info) self.redirect("/file/%d" % (file_info.key().id(),)) 

Go through the steps. First, the handler is inherited from the BlobstoreUploadHandler class instead of the usual webapp.RequestHandler. BlobstoreUploadHandler defines a self.get_uploads () helper method that returns a list of BlobInfo objects, one for each uploaded file.
')
In the post () method, we call self.get_uploads () and retrieve the first element. Since the Blobstore API currently does not support multiple file uploads in one form, only one item is always returned, we simply extract it ( already supported, note by translator ). Then, we protect ourselves from unregistered users who sent the form anyway. We cannot use the login_required decorator here, because we have to delete the file if the download attempt has not been authorized. Finally, we create an entry in the datastore for the uploaded file and redirect the user to the woman with information about the file just added.

The next step is to implement a handler that receives and displays information about the downloaded file. There is nothing complicated here:
 class FileInfoHandler(BaseHandler): def get(self, file_id): file_info = FileInfo.get_by_id(long(file_id)) if not file_info: self.error(404) return self.render_template("info.html", { 'file_info': file_info, 'logout_url': users.create_logout_url('/'), }) 

And the template to display:
 <html> <head> <title>  </title> </head> <body> <p style="float: right"><a href="{{logout_url}}">Log Out</a></p> <h1>  </h1> <table> <tr><th> </th><td>{{file_info.blob.filename}}</td></tr> <tr><th></th><td>{{file_info.blob.size}} bytes</td></tr> <tr><th> </th><td>{{file_info.uploaded_at}}</td></tr> <tr><th> </th><td>{{file_info.uploaded_by}}</td></tr> <tr><th>Content Type</th><td>{{file_info.blob.content_type}}</td></tr> </table> <p><a href="/file/{{file_info.key.id}}/download">  </a></p> </body> </html> 

Finally, a handler that directly gives the file to the user:
 class FileDownloadHandler(blobstore_handlers.BlobstoreDownloadHandler): def get(self, file_id): file_info = FileInfo.get_by_id(long(file_id)) if not file_info or not file_info.blob: self.error(404) return self.send_blob(file_info.blob, save_as=True) 

Here we request the FileInfo entry by key, and if it exists and contains a blob, we call the send_blob helper method provided by the parent class BlobstoreDownloadHandler . We pass an additional argument 'save_as' set to True, ensuring that the file will be sent for download, and not for display in the browser. We could also specify a file name instead of True, to specify a specific file name: by default, this is the name with which the file was loaded.

This article only superficially outlines what we can do with the Blobstore, and for a more complete picture you can learn a lot from the documentation. These are necessary fundamentals, however, in the next part we will do something new: we will add support for the download widget in JavaScript or Flash, for better user interaction!
For those who want to check all this in action, the source code of the demo application is available here .

Source: https://habr.com/ru/post/111574/


All Articles