Насколько просто сейчас сделать такой сервис, как хостинг изображений? В принципе, его и раньше было несложно сделать. Но прогресс не стоит на месте, и за то же самое время теперь можно учесть больше нюансов. Я уже рассказывал о проекте Uploadcare. Это сервис, позволяющий облегчить работу с файлами: загрузку, хранение, обработку и раздачу конечному пользователю. Его и будем использовать в качестве основного блока.
Пример будут написан на Питоне. Во-первых, потому что Питон я знаю лучше всего, во-вторых библиотека pyuploadcare обновляется в первую очередь. На самом деле, для Uploadcare есть библиотеки под разные языки, и все они в open source. Если в нужном вам модуле отсутствует какая-то функциональность, можно дождаться, когда она появится, или дописать самому.
Начнем с создания нового проекта на Django:
$ pip install django pyuploadcare==0.19 $ django-admin.py startproject upload_test $ cd upload_test/ && chmod u+x ./manage.py $ ./manage.py startapp imageshare $ ./manage.py syncdb --noinput
В settings.py, помимо привычных параметров подключения к базе данных и INSTALLED_APPS, нужно указать публичный и приватный ключ:
UPLOADCARE = { 'pub_key': 'demopublickey', 'secret': 'demoprivatekey', }
Для демонстрации я буду использовать демонстрационный аккаунт, что само по себе уже кажется логичным. Единственное ограничение этого аккаунта в том, что файлы через какое-то время удаляются сами.
Проект будет совсем небольшой: на главной странице будет форма для загрузки. После её отправки идентификатор картинки будет сохраняться в базу. Для этого вполне хватит такой модели:
import string import random from pyuploadcare.dj import ImageField from django.db import models class Image(models.Model): slug = models.SlugField(max_length=10, primary_key=True, blank=True) image = ImageField(manual_crop="") def save(self, *args, **kwargs): if not self.slug: self.slug = ''.join(random.sample(string.ascii_lowercase, 6)) super(Image, self).save(*args, **kwargs)
Как можно заметить, ImageField тут не джанговский, а из пакета pyuploadcare. Я указал только одну настройку: она позволит пользователю самому выбрать область изображения, которую он хочет загрузить. В методе save() генерируется slug для короткой ссылки.
Теперь прекрасное: вьюшка для главной страницы, сохраняющая картинку, и вьюшка, позволяющая её смотреть:
from django.views.generic import DetailView from django.views.generic.edit import CreateView from .models import Image class UploadView(CreateView): model = Image class ImageView(DetailView): model = Image
Класс, по 2 строчки. Просто Django — тоже очень качественный блок для ваших проектов. Чтобы форма отображалась, нужен небольшой шаблон, который будет её выводить. Ничего особенного, но нужно указать публичный ключ для виджета и не забыть поставить в тег документа {{ form.media }}. Многие забывают.
{% extends "base.html" %} {% block head %} <script> UPLOADCARE_PUBLIC_KEY = 'demopublickey'; </script> {{ form.media }} {% endblock %} {% block body %} <div class="center"> <form action="." method="post"> <p>Please, select an image:</p> <p>{{ form.image }}</p> <p><input type="submit" value="send"></p> {{ form.errors }} </form> </div> {% endblock %}
Запускаем.
Виджет с выбором файлов появился на странице. Но вот сохранение не работает, Джанга ругается: «No URL to redirect to». Оно и понятно, нужно где-то указать, как получить полную ссылку на картинку. Добавим еще один метод к модели.
@models.permalink def get_absolute_url(self): return 'detail', (), {'pk': self.pk}
Осталось написать шаблон полного вывода и можно сказать, что цель достигнута.
{% block body %} <img src="{{ image.image }}"> {% endblock %}
Ребята из моего инстаграма передают привет.
Внимательный читатель заметит, что на все про все ушло максимум минут 15. Wtf, чем же нам занять еще четверть часа?
Можно улучшить страницу загрузки. В нынешнем виде пользователю приходится делать два лишних клика: для открытия виджета и для отправки формы. Можно их убрать. Для этого нужно воспользоваться javascript api виджета:
<script> (function() { uploadcare.start(); var widget = uploadcare.Widget('#id_image'); widget.openDialog(); widget.onChange(function(file) { if (file) { var form = document.getElementById('upload-form'); form.submit(); form.style.display = 'none'; } }); })(); </script>
Тут мы инициализируем виджет с помощью метода start(), не дожидаясь загрузки страницы, после чего открываем диалог, не дожидаясь клика пользователя. Ну а если был загружен файл, отправляем форму.
Что еще? Можно сделать немного информативнее страничку просмотра картинки: показать превьюшку вместо полной картинки и вывести немного информации.
{% block body %} <h2>Uploaded Image</h2> <a href="{{ image.image.cdn_url }}"> <img align="left" src="{{ image.image.cdn_url }}-/stretch/off/-/resize/260x/"></a> <div class="float-info"> <p> <b>Filename</b>: {{ image.image.info.filename }}<br> <b>Uploaded</b>: {{ image.image.info.datetime_uploaded|slice:":10" }}<br> <b>Original size</b>: {{ image.image.info.size|filesizeformat }}<br> </p> <p><a href="{{ image.image.cdn_url }}">Full link</a></p> </div> <br clear="left"> <p><a href="{% url 'index' %}">Upload another image</a></p> {% endblock %}
Превьюшка нужного размера получается с помощью указания опций непосредственно в url картинки. Информация получается через метод info. К сожалению, datetime_uploaded передается в виде строки, поэтому пришлось схитрить — вырезать первые 10 символов. По-хорошему нужно было её парсить. Надеюсь, до десятитысячного года кто-нибудь исправит 🙂
Еще одна мелочь, которую можно исправить — правильно обрабатывать ситуацию, когда картинка была удалена. Правильно обрабатывать — значит отдавать ошибку 404 вместо 500. Лучше всего это делать при получении объекта из базы: запрашивать информацию о файле, и, если в ней есть признак того, что файл удален, удалять хранящуюся у нас ссылку. Кроме того, если файл удален достаточно давно, api может вовсе ничего не вернуть. Нужна обработка и такого случая.
class ImageView(DetailView): model = Image def get_object(self): object = super(ImageView, self).get_object() try: if object.image.is_removed: raise ValueError('File was deleted.') except (InvalidRequestError, ValueError): object.delete() raise Http404 return object
Теперь, пожалуй, можно остановиться. Осталось задействовать последний блок: бесплатный до определенной нагрузки хостинг heroku, и посмотреть результат: iamshare.herokuapp.com. Исходный код тоже доступен, если кому-то интересно посмотреть на все вместе.
ссылка на оригинал статьи http://habrahabr.ru/post/177991/
Добавить комментарий