Source code for ferris.components.upload

from google.appengine.ext import blobstore
import wtforms
import urllib2
import cgi
import logging


[docs]class Upload(object): """ Automatically handles file upload fields that need to use the blobstore. This works by: * Detecting if you're on an add or edit action (you can add additional actions with ``upload_actions``, or set ``process_uploads`` to True) * Adding the ``upload_url`` template variable that points to the blobstore * Updating the ``form_action`` and ``form_encoding`` scaffolding variables to use the new blobstore action * Processing uploads when they come back * Adding each upload's key to the form data so that it can be saved to the model Does not require that the controller subclass ``BlobstoreUploadHandler``, however to serve blobs you must either use the built-in Download controller or create a custom controller that subclasses ``BlobstoreDownloadHandler``. """ def __init__(self, controller): self.controller = controller self.__uploads = None self.process_uploads = False self.upload_actions = ('add', 'edit') self.cloud_storage_bucket = controller.Meta.cloud_storage_bucket if hasattr(controller.Meta, 'cloud_storage_bucket') else None controller.events.before_startup += self.on_before_startup controller.events.scaffold_before_apply += self.on_scaffold_before_apply controller.events.after_dispatch += self.on_after_dispatch def on_before_startup(self, controller): if controller.route.action in self.upload_actions: self.process_uploads = True def on_scaffold_before_apply(self, controller, container, item): if self.process_uploads and isinstance(container, wtforms.Form): self.process(container) def on_after_dispatch(self, controller, response): """ This will additionally check if ?start is the query string. If so, it will return just the upload url. This is great for rest apis. """ if self.process_uploads: if not 'upload_url' in controller.context: controller.context.set(upload_url=self.generate_upload_url(self.controller.route.action)) if hasattr(controller, 'scaffold'): controller.scaffold.form_action = controller.context['upload_url'] controller.scaffold.form_encoding = 'multipart/form-data' if 'start' in controller.request.params: if not response: controller.context['data'] = controller.context['upload_url'] if 'json' in controller.components: controller.components.json.render() def process(self, form, item=None): """ Process all of the incoming file upload and populates the current model form with them. Additionally, if using cloudstorage and there is a field present with the name "x_cloud_storage" that corresponds to the file, then the cloud storage object name will be saved to that field. """ uploads = self.get_uploads() for field in [x for x in form if isinstance(x, wtforms.fields.FileField)]: files = uploads.get(field.name) if files and files[0]: getattr(form, field.name).data = files[0].key() if hasattr(form, "%s_cloud_storage" % field.name): getattr(form, "%s_cloud_storage" % field.name).data = files[0].cloud_storage.gs_object_name else: delattr(form, field.name)
[docs] def generate_upload_url(self, action=None): if not action: action = self.controller.route.action url = urllib2.unquote(self.controller.uri(action=action, _pass_all=True, _full=True)) return blobstore.create_upload_url( success_path=url, gs_bucket_name=self.cloud_storage_bucket)
def serve(self, item, property): if not item: return 404 self.controller.send_blob(getattr(item, property)) return self.controller.response
[docs] def get_uploads(self): """Get all uploads sent to this controller. Returns: A dictionary mapping field names to a list of blobinfo objects. This blobinfos will have an additional cloud_storage property if they have been uploaded to cloud storage but be aware that this will not be persisted. """ if self.__uploads is None: self.__uploads = {} for key, value in self.controller.request.params.items(): if isinstance(value, cgi.FieldStorage): if 'blob-key' in value.type_options: blob_info = blobstore.parse_blob_info(value) cloud_info = blobstore.parse_file_info(value) # work around mangled names blob_info = blobstore.BlobInfo.get(blob_info.key()) # Add cloud storage data setattr(blob_info, 'cloud_storage', cloud_info) self.__uploads.setdefault(key, []).append(blob_info) return self.__uploads