Django Rest Framework - обновление поля типа ImageField
Убил сегодня полдня на решение этой проблемы. Чтобы не забыть, сразу же публикую всё здесь.
Исходные данные
Дано:
- Модель, имеющая поле типа
ImageField
- Django REST Framework
- ngFileUpload на фронте
Задача: сделать возможным загрузку изображений в указанное поле на основе Class-Based View в DRF.
Решение
Фронт-энд:
Вёрстка
<img ng-src="{$ item.logo200x200 $}" ng-model="logo" ngf-select ngf-change="uploadLogo(files)" accept="image/*" />
Да, всего одна строка. Вы можете поместить указанное изображение в любой подходящий контейнер, например, панель из Twitter Bootstrap.
Что делает этот код:
Параметр | Описание |
---|---|
ng-src="{$ item.logo200x200 $}" |
Связываем свойство модели и источник для нашего изображения. Делается через директиву Angular ng-src, как того советует официальная документация. На скобки в виде '{$' и '$}' не обращайте внимания. Т.к. на сервере используется стандартный шаблонизатор Django, приходится для Angular использовать другие скобки. |
ng-model="logo" |
Для выбора файлов будет использоваться отдельная модель - logo |
ngf-select |
Указываем, что данное изображение (можно использовать вообще-то что угодно) является полем ввода для плагина ngFileUpload |
ngf-change="uploadLogo(files)" |
При изменении значения поля выполняем указанную функцию. Загрузка без нажатия кнопки "Загрузить", в общем, достаточно лишь выбрать файл. |
accept="image/*" |
Разрешаем выбирать любые изображения. Фильтр для окна выбора файла. |
После того, как будет произведён клик по указанному изображению, откроется обычное окно открытия файла. Когда же файл будет выбран, запустится функция загрузки изображения:
LogoController.js
$scope.uploadLogo = function() {
if ($scope.logo.length < 1) {
return;
}
Upload.upload({
url: logoUrl, // /api/item/3/logo/
file: $scope.logo,
method: 'PATCH'
}).success(function(data) {
$scope.item.logo = data.logo;
});
};
Я описал лишь одну функцию контроллера. Надеюсь, догадаться, что нужно инжектировать $scope
и Upload
, не сложно.
file
- потом именно его будем обрабатывать на сервере.Бэк-энд
Нам понадобятся модель, отдельный сериализатор для логотипов и отдельное представление. Так же размеры всех логотипов следует нормализовать - не более 200px по большей стороне. Для этого можно написать отдельную функцию - resize_logo()
, принимающую как аргумент экземпляр нашей модели.
core.helpers.py
from PIL import Image
MAX_THUMBNAIL_SIZE = 200
def resize_logo(instance):
"""
Resize model logo to needed sizes.
"""
width = instance.logo.width
height = instance.logo.height
filename = instance.logo.path
max_size = max(width, height)
if max_size > MAX_THUMBNAIL_SIZE: # Да, надо изменять размер
image = Image.open(filename)
image = image.resize(
(round(width / max_size * MAX_THUMBNAIL_SIZE),
round(height / max_size * MAX_THUMBNAIL_SIZE)),
Image.ANTIALIAS
)
image.save(filename)
Пришло время описать саму модель, переопределив её метод save()
таким образом, чтобы при сохранении размеры изображения для логотипа нормализовались, как нам нужно:
core.items.models.py
from os import path
from django.db import models
from core.helpers import resize_logo
class ItemModel(models.Model):
name = models.CharField(
"Название",
max_length=255,
help_text='Максимум 255 знаков',
null=False,
blank=False
)
logo = models.ImageField(
"Логотип",
upload_to=path.join('item', 'logo'), # Отдельный каталог для аватаров
null=True,
blank=True,
)
def save(self, *args, **kwargs):
# Сначала модель нужно сохранить, иначе изменять/обновлять будет нечего
super(ItemModel, self).save(*args, **kwargs)
# Приводит размеры лого к одному виду - 200px по наибольшей стороне
if self.logo:
resize_logo(self)
class Meta:
app_label = 'core'
db_table = 'item'
verbose_name = 'элемент'
verbose_name_plural = 'элементы'
Теперь можно описать части, относящиеся к API - сериализатор, представление и часть конфигурации URL.
api.items.serializers.py
from rest_framework import serializers
from core.items.models import ItemModel
# Тут должны быть описаны остальные сериализаторы, сейчас же опускаю для краткости
class ItemLogoSerializer(serializers.ModelSerializer):
class Meta:
model = ItemModel
Как видно, сериализатор крайне прост. Опишем наше представление.
api.items.api.py
from rest_framework import permissions
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from core.items.models import ItemModel
from .serializers import ItemLogoSerializer
class ItemLogoAPIView(APIView):
permission_classes = [
permissions.IsAdminUser,
]
serializer_class = ItemLogoSerializer
# Обновление модели - методом PATCH, как я уже писал выше
def patch(self, *args, **kwargs):
# Находим нужную модель (по-хорошему надо обернуть в try ... except, но
# сейчас я этого делать не буду, чтобы не загромождать код)
instance = ItemModel.objects.get(pk=kwargs.get('pk'))
# Получаем из запроса наш файл (как указали выше, в JS)
instance.logo = self.request.FILES['file']
# Сохраняем запись (тут должна быть проверка значений встроенными в DRF
# методами, но сейчас я этого делать не буду)
instance.save()
# Возвращаем ответ - нашу сериализованную модель и статус 200
return Response(
ItemLogoSerializer(instance).data,
status=status.HTTP_200_OK
)
permission_classes
.Теперь - самое простое - конфигурация URL:
api.items.urls.py
from django.conf.urls import url
# Тут должен быть импорт остальных сериализаторов
from .api import ItemLogoAPIView
urlpatterns = [
# А здесь должны быть остальные URL (создание/получение/обнавление)
url(r'^(?P\d+)/logo/$', ServiceLogoAPIView.as_view()),
]
Ну что ж, всё выглядит не таким уж сложным. Пришло время закрыть вопросы на Toster'е и StackOverflow.
Комментариев нет :
Отправить комментарий