Asynchronous file upload for Django
Project description
paper-uploads
Асинхронная загрузка файлов для административного интерфейса Django.
Requirements
- Python 3.6+
- Django 2.1+
- paper-admin
- variations
Features
- Каждый файл представлен своей моделью, что позволяет
хранить вместе с изображением дополнительные данные.
Например,
alt
иtitle
. - Загрузка файлов происходит асинхронно и начинается сразу, при выборе файла в интерфейсе администратора.
- Поля модели, ссылающиеся на файлы, являются производными
от
OneToOneField
и не используют<input type="file">
. Благодаря этому, при ошибках валидации прикрепленные файлы не сбрасываются. - Загруженные картинки можно нарезать на множество вариаций. Каждая вариация гибко настраивается. Можно указать размеры, качество сжатия, формат, добавить дополнительные pilkit-процессоры, распознавание лиц и другое. См. variations.
- Совместим с django-storages.
- Опциональная интеграция с django-rq для отложенной нарезки картинок на вариации.
- Внутренний подмодуль
paper_uploads.cloudinary
предоставляет поля и классы, реализующие хранение файлов в облаке Cloudinary. - Возможность создавать коллекции файлов. В частности, галерей изображений с возможностью сортировки элементов.
Installation
Install paper-uploads
:
pip install paper-uploads[full]
Add paper_uploads
to INSTALLED_APPS
in settings.py
:
INSTALLED_APPS = [
# ...
'paper_uploads',
# ...
]
Configure paper-uploads
in django's settings.py
:
PAPER_UPLOADS = {
'VARIATION_DEFAULTS': {
'jpeg': dict(
quality=80,
progressive=True,
),
'webp': dict(
quality=75,
)
}
}
FileField
Поле для загрузки файла.
На загружаемые файлы можно наложить ограничения с помощью валидаторов.
from django.db import models
from django.utils.translation import ugettext_lazy as _
from paper_uploads.models import *
from paper_uploads.validators import *
class Page(models.Model):
report = FileField(_('file'), blank=True, validators=[
SizeValidator(10*1024*1024) # up to 10Mb
])
При загрузке файла создается экземпляр модели UploadedFile
.
UploadedFile
Модель, представляющая загруженный файл.
Поля и свойства модели:
file
- ссылка на файл, хранящийся в Django-хранилище.basename
- имя файла без пути, суффикса и расширения. Пример:my_document
.extension
- расширение файла в нижнем регистре. Пример:doc
.name
- полное имя файла. Пример:files/my_document_19sc2Kj.pdf
.display_name
- удобочитаемое название файла для вывода на сайте. Пример:Отчёт за 2019 год
.size
- размер файла в байтах.checksum
- контрольная сумма файла. Используется для отслеживания изменения файла.created_at
- дата создания экземпляра модели.modified_at
- дата изменения модели.uploaded_at
- дата загрузки файла.
Для упрощения работы с файлами, некоторые методы и свойства
стандартного класса FieldFile
проксированы на уровень модели:
UploadedFile
:
open
close
closed
read
seek
tell
readable
writable
seekable
url
path
chunks
Таким образом, вместо Page.report.file.url
можно использовать
Page.report.url
.
ImageField
Поле для загрузки изображений.
Во многом аналогично FileField. Может хранить ссылку
как на единственное изображение (подобно стандартному полю
ImageField
), так и на семейство вариаций одного изображения,
созданных из исходного с помощью библиотеки variations.
from django.db import models
from django.utils.translation import ugettext_lazy as _
from paper_uploads.models import *
class Page(models.Model):
image = ImageField(_('single image'), blank=True)
image_set = ImageField(_('image with variations'),
blank=True,
variations=dict(
desktop=dict(
size=(1600, 0),
clip=False,
jpeg=dict(
quality=80,
progressive=True
),
),
tablet=dict(
size=(1024, 0),
clip=False,
jpeg=dict(
quality=75,
),
),
mobile=dict(
size=(640, 0),
clip=False,
)
)
)
При загрузке изображения создается экземпляр модели UploadedImage
.
UploadedImage
Модель, представляющая загруженное изображение.
Поля и свойства модели:
file
- ссылка на файл, хранящийся в Django-хранилище.basename
- имя файла без пути, суффикса и расширения. Пример:my_image
.extension
- расширение файла в нижнем регистре. Пример:jpg
.name
- полное имя файла. Пример:images/my_image_19sc2Kj.jpg
.size
- размер файла в байтах.title
- текст атрибутаtitle
для тэга<img>
.description
- текст атрибутаalt
для тэга<img>
.width
- ширина исходного изображения в пикселях.height
- высота исходного изображения в пикселях.checksum
- контрольная сумма файла. Используется для отслеживания изменения файла.created_at
- дата создания экземпляра модели.modified_at
- дата изменения модели.uploaded_at
- дата загрузки файла.
По аналогии с FileField
, модель UploadedImage
проксирует
методы и свойства стандартного класса FieldFile
.
К вариациям можно обращаться прямо из экземпляра UploadedImage
:
page.image_set.desktop.url
По умолчанию, нарезка изображения на вариации происходит
при его загрузке. Можно перенести процесс нарезки в отложенную
задачу django-rq
, установив значение RQ_ENABLED
в True
в настройках модуля.
# settings.py
PAPER_UPLOADS = {
# ...
'RQ_ENABLED': True,
'RQ_QUEUE_NAME': 'default'
}
Collections
Коллекция — это модель, группирующая экземпляры других моделей (элементов коллекции). В частности, с помощью коллекции можно создать фото-галерею или список файлов.
Для создания коллекции необходимо создать класс, унаследованный
от Collection
и объявить модели элементов, которые могут входить
в коллекцию. Синтаксис объявления элементов подобен добавлению полей:
from paper_uploads.models import *
class PageFiles(Collection):
svg = CollectionItem(SVGItem)
image = CollectionItem(ImageItem)
file = CollectionItem(FileItem)
Псевдо-поле CollectionItem
подключает к коллекции модель
элемента под заданным именем. Это имя записывается в БД при добавлении
элемента к коллекции и позволяет фильтровать элементы по их типу.
При изменении имен псевдо-полей или при удалении класса элемента
из существующих коллекций, разработчик должен самостоятельно
обеспечить согласованное состояние БД.
В примере выше, коллекция PageFiles
может содержать элементы трех
классов: FileItem
, ImageItem
и SVGItem
. У элементов коллекции
с типом FileItem
в поле item_type
будет записано значение file
,
у элементов ImageItem
- image
и т.п.
Порядок подключения классов элементов к коллекции имеет значение:
первый класс, чей метод file_supported()
вернет True
,
определит модель загружаемого файла. Поэтому FileItem
должен
указываться последним, т.к. он принимает любые файлы.
Вместе с моделью элемента, в поле CollectionItem
можно указать
валидаторы и дополнительные параметры
(в словаре options
), которые могут быть использованы для
более детальной настройки элемента коллекции.
Полученную коллекцию можно подключать к моделям с помощью
CollectionField
:
from django.db import models
from paper_uploads.models import *
class PageFiles(Collection):
svg = CollectionItem(SVGItem)
image = CollectionItem(ImageItem)
file = CollectionItem(FileItem)
class Page(models.Model):
files = CollectionField(PageFiles)
В состав библиотеки входят следующие классы элементов:
FileItem
. Может хранить любой файл. Из-за этого при подключении к коллекции этот тип должен быть подключен последним.SVGItem
. Функционально идениченFileItem
, но в админке вместо абстрактной иконки показывается само SVG-изображение.ImageItem
. Для хранения изображения с возможностью нарезки на вариации.
Вариации для изображений коллекции можно указать двумя способами:
-
в члене класса коллекции
VARIATIONS
:from paper_uploads.models import * class PageGallery(Collection): VARIATIONS = dict( mobile=dict( size=(640, 0), clip=False ) ) image = CollectionItem(ImageItem)
-
в дополнительных параметрах поля
CollectionItem
по ключуvariations
:from paper_uploads.models import * class PageGallery(Collection): image = CollectionItem(ImageItem, options={ 'variations': dict( mobile=dict( size=(640, 0), clip=False ) ) })
Вариации, указанные первым способом (через VARIATIONS
коллекции),
используются всеми классами элементов-изображений по умолчанию.
Но, если конкретный элемент коллекции объявляет свои собственные
вариации (вторым методом), то использовать он будет именно их.
ImageCollection
Для коллекций, предназначенных исключительно для изображений,
"из коробки" доступна модель для наследования ImageCollection
.
К ней уже подключен класс элементов-изображений.
from paper_uploads.models import *
class PageGallery(ImageCollection):
VARIATIONS = dict(
wide=dict(
size=(1600, 0),
clip=False,
),
desktop=dict(
size=(1280, 0),
clip=False,
),
tablet=dict(
size=(960, 0),
clip=False,
),
mobile=dict(
size=(640, 0),
)
)
Наследование от Collection
на самом деле создает
proxy-модель.
Это позволяет не создавать для каждой коллекции отдельную
таблицу в БД, но делает невозможным добавление к модели коллекции
дополнительных полей.
Чтобы экземпляры коллекций не смешивались при выполнении SQL-запросов,
менеджер objects
в классе Collection
был переопределен для того,
чтобы принимать во внимание ContentType
коллекции и выполнять операции
только над теми коллекциями, класс которых соответствует текущему.
# Вернет только экземпляры класса MyCollection
MyCollection.objects.all()
# Вернет абсолютно все экземпляры коллекций, всех классов
MyCollection._base_manager.all()
Programmatically upload files
from paper_uploads.models import *
# file / image
with open('file.doc', 'rb') as fp:
file = UploadedFile()
file.attach_file(fp)
file.save()
# gallery
gallery = PageGallery.objects.create()
with open('image.jpg', 'rb') as fp:
item = ImageItem()
item.attach_to(gallery)
item.attach_file(fp)
item.save()
Management Commands
check_uploads
Запускает комплексную проверку загруженных файлов и выводит результат.
Список производимых тестов:
- загруженный файл существует в файловой системе
- для изображений существуют все файлы вариаций
- модель-владелец (указанная в
owner_app_label
иowner_model_name
) существует - в модели-владельце существует поле
owner_fieldname
- существует единственный экземпляр модели-владельца со ссылкой на файл
- у элементов коллекций указан существующий и допустимый
item_type
- модель элементов коллекций идентична указанной
для
item_type
При указании ключа --fix-missing
все отсутствующие
вариации изображений будут автоматически перенарезаны
из исходников.
python3 manage.py check_uploads --fix-missing
clean_uploads
Находит мусорные записи в БД (например те, у которых нет владельца) и предлагает их удалить.
Ссылка на загруженный файл создается в момент отправки формы
в админке. Из-за этого, в течение некоторого времени файл
будет являться "сиротой". Для того, чтобы такие файлы не удалялись,
по-умолчанию установлен фильтр, отсеивающий все файлы, загруженные
за последние 30 минут. Указать свой интервал фильтрации
(в минутах) можно через ключ --min-age
.
python3 manage.py clean_uploads --min-age=10
recreate_variations
Перенарезает вариации для указанных моделей.
Рекомендуется запускать в интерактивном режиме:
python3 manage.py recreate_variations --interactive
Возможен вызов и в неинтерактивном режиме. Для этого
необходимо указать модель в виде строки вида
AppLabel.ModelName
и имя поля, ссылающегося на изображение.
python3 manage.py recreate_variations 'app.Page' 'image'
Если нужно перенарезать не все вариации, а только некоторые,
то их можно перечислить в параметре --variations
.
python3 manage.py recreate_variations 'app.Page' 'image' --variations big small
Также, изображения можно перенарезать через код, для конкретных
экземпляров UploadedImage
или ImageItem
:
# перенарезка `big` и `medium` вариаций поля ImageField
page.image.recut(['big', 'medium'])
# перенарезка всех вариаций для всех картинок галереи
for image in page.gallery.get_items('image'):
image.recut()
Validators
Для добавления ограничений на загружаемые файлы применяются специальные валидаторы:
SizeValidator
- задает максимально допустимый размер файла в байтах.ExtensionValidator
- задает допустимые расширения файлов.MimeTypeValidator
- задает допустимые MIME типы файлов.ImageMinSizeValidator
- устанавливает минимальный размер загружаемых изображений.ImageMaxSizeValidator
- устанавливает максимальный размер загружаемых изображений.
from django.db import models
from django.utils.translation import ugettext_lazy as _
from paper_uploads.models import *
from paper_uploads.validators import *
class Page(models.Model):
image = ImageField(_('image'), blank=True, validators=[
SizeValidator(10 * 1024 * 1024), # max 10Mb
ImageMaxSizeValidator(800, 800) # max dimensions 800x800
])
class PageGallery(Collection):
file = CollectionItem(FileItem, validators=[
SizeValidator(10 * 1024 * 1024),
])
Variation versions
Допустим, у нас есть изображение, которое нужно отобразить в трех
вариантах: desktop
, tablet
и mobile
. Если мы хотим поддерживать
дисплеи Retina, нам нужно добавить ещё три вариации
для размера 2x
. Если мы также хотим использовать формат WebP
(сохранив исходные изображения для обратной совместимости),
то общее количество вариаций достигает 12.
Поскольку Retina-вариации отличаются от обычных только увеличенным
на постоянный коэффициент размером, а WebP
-вариации — принудительной
конвертацией в формат WebP
, мы можем создавать эти вариации
автоматически.
Для этого нужно объявить перечень версий, которые нужно
сгенерировать, в параметре вариации versions
. Поддерживаются
следующие значения: webp
, 2x
, 3x
, 4x
.
class Page(models.Model):
image = ImageField(_('image'), blank=True,
variations=dict(
desktop=dict(
# ...
versions={'webp', '2x', '3x'}
)
)
)
Приведенный выше код создаст следующие вариации:
desktop
- оригинальная вариацияdesktop_webp
-WebP
-версия оригинальной вариацииdesktop_2x
- Retina 2xdesktop_webp_2x
-WebP
-версия Retina 2xdesktop_3x
- Retina 3xdesktop_webp_3x
-WebP
-версия Retina 3x
NOTE: Суффикс для Retina всегда следует после суффикса WebP
.
Если необходимо переопределить какие-то параметры дополнительной вариации, то придётся объявлять вариацию явно — она переопределит одноименную сгенерированную вариацию.
class Page(models.Model):
image = ImageField(_('image'), blank=True,
variations=dict(
desktop=dict(
size=(800, 600),
versions={'webp', '2x', '3x'}
),
desktop_2x=dict(
size=(1600, 1200),
jpeg=dict(
quality=72
)
)
)
)
Cloudinary
Во встроенном модуле paper_uploads.cloudinary
описаны поля и классы,
позволяющие загружать файлы и картинки в облачный сервис Cloudinary.
В этом случае нарезка вариаций не имеет смысла и поэтому недоступна.
Installation
pip install cloudinary
- Добавить
paper_uploads.cloudinary
иcloudinary
вINSTALLED_APPS
.INSTALLED_APPS = [ # ... 'paper_uploads', 'paper_uploads.cloudinary', 'cloudinary', # ... ]
- Задать данные учетной записи Cloudinary
CLOUDINARY = { 'cloud_name': 'mycloud', 'api_key': '012345678901234', 'api_secret': 'g1rtyOCvm4tDIfCPFFuh4u1W0PC', 'sign_url': True, 'secure': True }
Model fields
from paper_uploads.cloudinary.models import *
class Page(models.Model):
file = CloudinaryFileField(_('file'), blank=True)
media = CloudinaryMediaField(_('media'), blank=True)
image = CloudinaryImageField(_('image'), blank=True)
Collections
В модуле объявлено три класса элементов коллекции:
CloudinaryFileItem
, CloudinaryImageItem
и
CloudinaryMediaItem
(для аудио и видео).
NOTE: В отличие от библиотеки PIL
, Cloudinary поддерживает
загрузку SVG-файлов как изображений. Поэтому для SVG-файлов отдельный
класс не нужен.
Классы элементов коллекций Cloudinary используются также как обычные:
from paper_uploads.models import *
from paper_uploads.cloudinary.models import *
class PageFiles(Collection):
image = CollectionItem(CloudinaryImageItem)
file = CollectionItem(CloudinaryFileItem)
class Page(models.Model):
files = CollectionField(PageFiles)
Также, как и для обычных коллекций, для Cloudinary объявлено
два класса готовых коллекций: CloudinaryCollection
и CloudinaryImageCollection
. CloudinaryCollection
может хранить
любые файлы, а CloudinaryImageCollection
— только изображения.
from paper_uploads.models import *
from paper_uploads.cloudinary.models import *
class PageFiles(CloudinaryCollection):
pass
class PageGallery(CloudinaryImageCollection):
pass
class Page(models.Model):
files = CollectionField(PageFiles)
gallery = CollectionField(PageGallery)
Usage
Для вывода ссылки на файл, загруженный в Cloudinary, библиотека содержит
шаблонный тэг paper_cloudinary_url
:
{% load paper_cloudinary %}
<img src={% paper_cloudinary_url page.image width=1024 crop=fill %}>
Jinja2
<img src={% paper_cloudinary_url page.image, width=1024, crop=fill %}>
Также доступна одноименная глобальная функция:
<img src={{ paper_cloudinary_url(page.image, width=1024, crop='fill') }}>
Settings
Все настройки указываются в словаре PAPER_UPLOADS
.
PAPER_UPLOADS = {
'STORAGE': 'django.core.files.storage.FileSystemStorage',
'STORAGE_OPTIONS': {},
'RQ_ENABLED': True,
'VARIATION_DEFAULTS': {
'jpeg': dict(
quality=80,
progressive=True,
),
'webp': dict(
quality=75,
)
}
}
STORAGE
Путь к классу хранилища Django.
Значение по умолчанию: django.core.files.storage.FileSystemStorage
STORAGE_OPTIONS
Параметры инициализации хранилища.
Значение по умолчанию: {}
FILES_UPLOAD_TO
Путь к папке, в которую загружаются файлы из FileField. Может содержать параметры для даты и времени (см. upload_to).
Значение по умолчанию: files/%Y-%m-%d
IMAGES_UPLOAD_TO
Путь к папке, в которую загружаются файлы из ImageField.
Значение по умолчанию: images/%Y-%m-%d
COLLECTION_FILES_UPLOAD_TO
Путь к папке, в которую загружаются файлы коллекций.
Значение по умолчанию: collections/files/%Y-%m-%d
COLLECTION_IMAGES_UPLOAD_TO
Путь к папке, в которую загружаются изображения коллекций.
Значение по умолчанию: collections/images/%Y-%m-%d
COLLECTION_ITEM_PREVIEW_WIDTH
, COLLECTION_ITEM_PREVIEW_HEIGTH
Размеры превью элементов коллекций в админке.
Значение по умолчанию: 180
x 135
COLLECTION_IMAGE_ITEM_PREVIEW_VARIATIONS
Вариации, добавляемые к каждому классу изображений коллекций
для отображения превью в админке. Размеры файлов должны
совпадать с COLLECTION_ITEM_PREVIEW_WIDTH
и
COLLECTION_ITEM_PREVIEW_HEIGTH
.
RQ_ENABLED
Включает нарезку картинок на вариации через отложенные задачи. Требует наличие установленного пакета django-rq.
Значение по умолчанию: False
RQ_QUEUE_NAME
Название очереди, в которую помещаются задачи по нарезке картинок.
Значение по умолчанию: default
VARIATION_DEFAULTS
Параметры вариаций по умолчанию.
Параметры, указанные в этом словаре, будут применены к каждой вариации, если только вариация их явно не переопределяет.
Значение по умолчанию: None
CLOUDINARY_TYPE
Тип загрузки файлов. Возможные значения: private
, upload
.
Значение по умолчанию: private
CLOUDINARY_TEMP_DIR
Папка в разделе /tmp/
, в которую скачиваются файлы из Cloudinary
при чтении их содержимого. Доступ к содержимому большого количества
файлов из Cloudinary может привести к скачиванию больших объемов данных
и захламлению временной папки.
CLOUDINARY_UPLOADER_OPTIONS
Словарь, задающий глобальные параметры загрузки для Cloudinary.
Значение по умолчанию:
{
'use_filename': True,
'unique_filename': True,
'overwrite': True,
'invalidate': True
}
Development and Testing
After cloning the Git repository, you should install this in a virtualenv and set up for development:
virtualenv .venv
source .venv/bin/activate
pip install -r ./requirements_dev.txt
pre-commit install
Install npm
dependencies and build static files:
npm i
npx webpack
Create .env
file:
CLOUDINARY_URL=cloudinary://XXXXXXXXXXXXXXX:YYYYYYYYYYYYYYYYYYYYYYYYYYY@ZZZZZZ?sign_url=1&secure=1
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for paper_uploads-0.4.4-py2.py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | b46b9d36ede5ae665c7bdc120433db1f613a840e9a0d0857666b9c781b5c74ec |
|
MD5 | 4842eb5ca4729b17b94822616ba9428a |
|
BLAKE2b-256 | a337ddf3dbfeeb5abe5f2147b1465a2fe4bc5a4cfd01ffeb89fe0b00757a1b2f |