Pull to refresh

Реализация «dropbox» сервиса при помощи Blobstore API (Часть 1)

Reading time4 min
Views1.6K
Original author: Nick Johnson
Blobstore api — недавнее пополнение платформы App Engine, позволяет загружать и раздавать большие файлы, в настоящий момент до 2Gb (в оригинале — 50Mb, прим. переводчика). Это также один из наиболее сложных в использовании API. Небольшая серия заметок покажет как реализовать «dropbox» сервис на базе App Engine используя Blobstore API. Для начала мы рассмотрим основы, необходимые для загрузки файлов, сохранении информации о них в datastore и дальнейшей отдачи их пользователям для скачивания.

Начнем с формы загрузки. Этот шаг довольно очевидный: мы создаем стандартную HTML-форму, единственное, что мы должны сделать это сгенерировать URL для action'а формы, вызвав метод blobstore.create_upload_url и передав ему URL с желаемым обработчиком. Ниже наш код:
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('/'),
    })

Все стандартно, но все же стоит заметить — для удобства мы используем декоратор login_required из пакета google.appengine.ext.webapp.util который требует от пользователя быть залогиненым (и перенаправляет на форму входа в противном случае). А это шаблон:
<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>

Обратите внимание на атрибуты тэга формы: method должен быть «POST», а enctype должен быть «multipart/form-data», иначе загрузка не будет работать!

Далее мы хотим написать обработчик загрузки. Начнем с описания модели для хранения информации о загруженых файлах. Вот она:
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)

Довольно стандартная модель, за исключением поля 'blob' типа blobstore.BlobReferenceProperty. BlobReferenceProperty похоже на ReferenceProperty, с одним лишь отличием, что оно ссылается не на другую модель, а на Blob. При запросе возвращается объект BlobInfo.

Код отвечающий непосредственно за процесс загрузки:
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(),))

Пройдемся по шагам. Во-первых, обработчик наследуется от класса BlobstoreUploadHandler вместо обычного webapp.RequestHandler. BlobstoreUploadHandler определяет вспомогательный метод self.get_uploads(), который возвращает список объектов BlobInfo, по одному на каждый загруженный файл.

В методе post() мы вызываем self.get_uploads() и извлекаем первый элемент. Так как Blobstore API сейчас не поддерживает множественную загрузку файлов в одной форме, то возвращается всегда только один элемент, мы просто извлекаем его (уже поддерживается, прим. переводчика). Затем, мы защищаемся от незалогиненных пользователей, так или иначе отправиших форму. Мы не можем использовать login_required декоратор здесь, потому что мы должны удалить файл если попытка загрузки не была авторизована. Наконец, мы создаем запись в datastore для загруженного файла и перенаправляем пользователя на стараницу с информаций о только что добавленном файле.

Следующий шаг — реализовать обработчик, который получает и отображает информацию о загруженном файле. Здесь ничего сложного:
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('/'),
    })

И шаблон для отображения:
<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>

Наконец, обработчик, непосредственно отдающий файл пользователю:
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)

Здесь мы запрашиваем запись FileInfo по ключу, и, если она существует и содержит blob, мы вызываем вспомогательный метод send_blob, предоставленный родительским классом BlobstoreDownloadHandler. Мы передаем дополнительный аргумент 'save_as' установленный в True, гарантирующий что файл будет отослан для скачивания, а не для отображения в браузере. Мы могли бы также указать имя файла вместо значения True, для указания специфичного имени файла: по умолчанию это имя, с которым файл был загружен.

Эта статья только поверхностно излагает то, что мы можем сделать с Blobstore, и для более полной картины вы можете многое подчерпнуть из документации. Это необходимые основы, однако, в следующей части мы сделаем что-нибудь новое: добавим поддержку виджета загрузки на JavaScript или Flash, для лучшего взаимодействия с пользователем!
Для тех, кто хочет проверить все это в действии, исходные коды демо-приложения доступны здесь.
Tags:
Hubs:
+21
Comments25

Articles

Change theme settings