Asynchronous file upload for Django
Project description
paper-uploads
Асинхронная загрузка файлов для административного интерфейса Django.
Requirements
- Python >= 3.6
- Django >= 2.2
- paper-admin >= 3.0
- variations
Features
- Каждый файл представлен своей моделью, что позволяет
хранить вместе с изображением дополнительные данные.
Например,
alt
иtitle
. - Загрузка файлов происходит асинхронно и начинается сразу, при выборе файла в интерфейсе администратора.
- Поля модели, ссылающиеся на файлы, являются производными
от
OneToOneField
и не используют<input type="file">
. Благодаря этому, при ошибках валидации формы, прикрепленные файлы не сбрасываются. - Загруженные картинки можно нарезать на множество вариаций. Каждая вариация гибко настраивается. Можно указать размеры, качество сжатия, формат, добавить дополнительные pilkit-процессоры, распознавание лиц и другое. См. variations.
- Совместим с django-storages.
- Опциональная интеграция с django-rq для отложенной нарезки картинок на вариации.
- Внутренний подмодуль
paper_uploads.cloudinary
предоставляет поля и классы, реализующие хранение файлов в облаке Cloudinary. - Возможность создавать коллекции файлов. В частности, галерей изображений с возможностью сортировки элементов.
Table of Contents
- Installation
- FileField
- ImageField
- Collections
- Programmatically upload files
- Management Commands
- Validators
- Cloudinary
- Settings
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,
)
}
}
# Add JS translations
PAPER_LOCALE_PACKAGES = [
"django.contrib.admin",
"paper_admin",
"paper_uploads",
]
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 | FileField |
Ссылка на файл, хранящийся в Django-хранилище. |
display_name | CharField |
Удобочитаемое название файла для вывода на сайте. Пример: Отчёт за 2019 год . |
basename | CharField |
Имя файла без пути, суффикса и расширения. Пример: my_document . |
extension | CharField |
Расширение файла в нижнем регистре, без точки в начале. Пример: doc . |
size | PositiveIntegerField |
Размер файла в байтах. |
checksum | CharField |
Контрольная сумма файла. Используется для отслеживания изменений файла. |
created_at | DateTimeField |
Дата создания экземпляра модели. |
modified_at | DateTimeField |
Дата изменения модели. |
uploaded_at | DateTimeField |
Дата загрузки файла. |
Свойства модели:
Поле | Тип | Описание |
---|---|---|
name | str |
Полное имя файла, передающееся в Django storage. Пример: files/my_document_19sc2Kj.pdf . |
Для упрощения работы с загруженными файлами, некоторые методы и свойства
стандартного класса FieldFile
проксированы на уровень модели:
open
close
closed
read
seek
tell
readable
writable
seekable
url
path
chunks
Таким образом, вместо page.report.file.url
можно использовать
page.report.url
.
Поддерживается протокол контекстного менеджера:
page = Page.objects.first()
with page.report.open() as fp:
print(fp.read(10))
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)
При загрузке изображения создается экземпляр модели UploadedImage
.
UploadedImage
Модель, представляющая загруженное изображение.
Поля модели:
Поле | Тип | Описание |
---|---|---|
file | FileField |
Ссылка на файл, хранящийся в Django-хранилище. |
title | CharField |
Название изображения, которое можно вставить в атрибут title тэга <img> . |
description | CharField |
Описание изображения, которое можно вставить в атрибут alt тэга <img> . |
width | PositiveSmallIntegerField |
Ширина загруженного изображения. |
height | PositiveSmallIntegerField |
Высота загруженного изображения. |
basename | CharField |
Имя файла без пути, суффикса и расширения. Пример: my_image . |
extension | CharField |
Расширение файла в нижнем регистре, без точки в начале. Пример: jpg . |
size | PositiveIntegerField |
Размер файла в байтах. |
checksum | CharField |
Контрольная сумма файла. Используется для отслеживания изменений файла. |
created_at | DateTimeField |
Дата создания экземпляра модели. |
modified_at | DateTimeField |
Дата изменения модели. |
uploaded_at | DateTimeField |
Дата загрузки файла. |
Свойства модели:
Поле | Тип | Описание |
---|---|---|
name | str |
Полное имя файла, передающееся в Django storage. Пример: images/my_image_19sc2Kj.jpg . |
По аналогии с FileField
, модель UploadedImage
проксирует
методы и свойства стандартного класса FieldFile
.
Variations
Вариация - это дополнительное изображение, которое получается из оригинального путём заранее заданных трансформаций.
Вариации описываются словарем variations
поля ImageField
:
from django.db import models
from django.utils.translation import ugettext_lazy as _
from paper_uploads.models import *
class Page(models.Model):
image = 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,
)
)
)
Со списком допустимых опций для вариаций можно ознакомиться в библитеке variations.
К вариациям можно обращаться прямо из экземпляра UploadedImage
:
print(page.image.desktop.url)
Manually creating variation files
Нарезка изображения на вариации происходит при его загрузке. Объявление новых вариаций (равно как изменение существующих) для поля, в которое уже загружен файл, не приведёт к созданию новых файлов.
Для того, чтобы создать файлы для новых вариаций (либо перезаписать существующие вариации, в которые были внесены изменения) можно поступить одним из ниже описанных способов.
-
Вызвать метод
recut()
(либоrecut_async()
) из экземпляраUploadedImage
:page.image.recut()
При вызове этого метода все файлы вариаций для указанного экземпляра создаются заново.
Можно явно указать имена вариаций, которые необходимо перезаписать:page.image.recut(["desktop", "mobile"])
-
Выполнить management-команду
recreate_variations
:python3 manage.py recreate_variations app.page --field=report
Эта команда сгенерирует вариации для всех экземпляров указанной модели.
Using Redis Queue for variation update
Если загрузка изображений происходит достаточно часто и количество вариаций для каждого изображения велико, то процесс создания вариаций может занимать значительное время. Это может оказать негативное влияние на производительность веб-сервера и даже послужить зоной для DoS-атаки.
Для того, чтобы стабилизировать процесс загрузки изображений, рекомендуется создавать вариации асинхронно, в отдельном процессе, с помощью django-rq.
pip install django-rq
# settings.py
PAPER_UPLOADS = {
# ...
"RQ_ENABLED": True,
"RQ_QUEUE_NAME": "default"
}
Теперь при загрузке изображения вместо метода recut()
будет вызываться
метод recut_async()
. При его использовании, превью для админки генерируется
синхронно, а все остальные вариации - через отложенную задачу, которая
помещается в очередь, указанную в опции RQ_QUEUE_NAME
.
Variation versions
Допустим, у нас есть изображение, которое нужно отобразить в трех
вариантах: desktop
, tablet
и mobile
. Если мы хотим поддерживать
дисплеи Retina, нам нужно добавить ещё три вариации для размера 2x
.
Если мы также хотим использовать формат WebP
(сохранив исходный формат
для обратной совместимости), то общее количество вариаций достигает 12.
Поскольку Retina-вариации отличаются от обычных только увеличенным
на постоянный коэффициент размером, а WebP
-вариации — добавлением
параметра format = "webp"
, мы можем создавать эти вариации
автоматически. Это и есть версии вариации.
Перечень версий, которые нужно сгенерировать, указываются в параметре
вариации versions
. Поддерживаются следующие значения:
webp
, 2x
, 3x
, 4x
.
from django.db import models
from django.utils.translation import ugettext_lazy as _
from paper_uploads.models import *
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
, если
он есть.
Если необходимо переопределить какие-то параметры дополнительной вариации, то придётся объявлять вариацию явно:
from django.db import models
from django.utils.translation import ugettext_lazy as _
from paper_uploads.models import *
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
)
)
)
)
Collections
Коллекция — это модель, группирующая экземпляры других моделей (элементов коллекции). В частности, с помощью коллекции можно создать фото-галерею или список файлов.
Для создания коллекции необходимо объявить класс, унаследованный
от Collection
и указать модели элементов, которые могут входить
в коллекцию. Созданную коллекцию можно подключить к модели с
помощью CollectionField
:
from django.db import models
from paper_uploads.models import *
# Collection model
class PageFiles(Collection):
svg = CollectionItem(SVGItem)
image = CollectionItem(ImageItem)
file = CollectionItem(FileItem)
# Target model
class Page(models.Model):
files = CollectionField(PageFiles)
Класс Collection
обладает особенным свойством: любой дочерний
класс, унаследованный от Collection
, является proxy-классом для
Collection
.
В большинстве случаев коллекции отличаются друг от друга только набором элементов, которые могут входит в коллекцию. Использование proxy-моделей предотвращает создание для каждой такой коллекции отдельной таблицы в БД.
Как следствие, вы не можете добавлять собственные поля в классы коллеций. Это ограничение можно снять, явно указав, что дочерний класс не должен быть proxy-моделью. В этом случае для коллекции будет создана отдельная таблица в БД.
from django.db import models
from paper_uploads.models import *
class CustomCollection(Collection):
file = CollectionItem(FileItem)
name = models.CharField("name", max_length=128, blank=True)
class Meta:
proxy = False
Collection items
Псевдо-поле CollectionItem
подключает к коллекции модель элемента
под заданным именем. Это имя сохраняется в БД при загрузке файла.
По этой причине не рекомендуется менять имена элементов коллекций,
если в неё уже загружены файлы.
from paper_uploads.models import *
class PageFiles(Collection):
svg = CollectionItem(SVGItem)
image = CollectionItem(ImageItem)
file = CollectionItem(FileItem)
В приведённом примере, коллекция PageFiles
может содержать элементы
трех классов: SVGItem
, ImageItem
и FileItem
. Порядок подключения
элементов коллекции имеет значение: первый класс, чей метод file_supported()
вернет True
, определит модель загруженного файла. По этой причине
FileItem
должен указываться последним, т.к. он принимает любые файлы.
Вместе с моделью элемента, в поле CollectionItem
можно указать
валидаторы:
from paper_uploads.models import *
from paper_uploads.validators import SizeValidator
class FileCollection(Collection):
file = CollectionItem(FileItem, validators=[
SizeValidator(2 * 1000 * 1000)
])
В состав библиотеки входят следующие классы элементов:
ImageItem
. Для хранения изображения с возможностью нарезки на вариации.SVGItem
. Для хранения SVG иконок.FileItem
. Может хранить любой файл.
Вариации для изображений коллекции можно указать двумя способами:
-
в атрибуте класса коллекции
VARIATIONS
:from paper_uploads.models import * class PageGallery(Collection): image = CollectionItem(ImageItem) VARIATIONS = dict( mobile=dict( size=(640, 0), clip=False ) )
-
в дополнительных параметрах поля
CollectionItem
по ключуvariations
:from paper_uploads.models import * class PageGallery(Collection): image = CollectionItem(ImageItem, options={ "variations": dict( mobile=dict( size=(640, 0), clip=False ) ) })
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),
)
)
Custom collection item classes
При создании пользовательских классов элементов коллекций не рекомендуется
использовать прямое наследование от существующих моделей FileItem
, ImageItem
и т.п.
Это содзаёт сложные One2One-связи между коллекциями и элементами коллекций и может
привести к RecursionError
при удалении коллекций или их элементов.
Для того, чтобы избежать потенциальных проблем, в качестве базовых классов следует
использовать абстрактные классы. Такие как FileItemBase
, ImageItemBase
или
более общие CollectionItemBase
и CollectionFileItemBase
.
from django.db import models
from paper_uploads.models import *
class CustomImageItem(ImageItemBase):
caption = models.TextField(_("caption"), blank=True)
class CustomCollection(Collection):
image = CollectionItem(CustomImageItem)
Programmatically upload files
Для FileField
и ImageField
:
from django.db import models
from paper_uploads.models import *
class Page(models.Model):
report = FileField(_("report"))
# Поля `owner_*` формируют ссылку на поле модели,
# с которым будет связан файл. Эти поля позволяют
# находить и удалять неиспользуемые файлы.
file = UploadedFile(
owner_app_label=Page._meta.app_label,
owner_model_name=Page._meta.model_name,
owner_fieldname="report"
)
with open("file.doc", "rb") as fp:
file.attach_file(fp)
file.save()
page = Page.objects.create(
report=file
)
Для коллекций:
from paper_uploads.models import *
class PageImages(ImageCollection):
pass
gallery = PageImages.objects.create()
item = ImageItem()
item.attach_to(gallery)
with open("image.jpg", "rb") as fp:
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
Находит мусорные записи в БД (например те, у которых нет владельца) и предлагает их удалить.
Владелец загруженного файла устанавливается в момент сохранения страницы
в админке. А это происходит позже фактической загрузки файла на сервер.
Как следствие, в течение некоторого времени файл будет являться "сиротой".
Для того, чтобы такие файлы не удалялись, команда clean_uploads
игнорирует
файлы, загруженные за последние 30 минут. Изменить интервал фильтрации
(в минутах) можно через ключ --min-age
.
python3 manage.py clean_uploads --min-age=60
recreate_variations
Перенарезает вариации для указанных моделей.
Модель указывается в формате app_label.model_name
.
Если модель является коллекцией, необходимо указать параметр --item-type
:
python3 manage.py recreate_variations 'app.Photos' --item-type='image'
Для обычных моделей необходимо указать параметр --field
:
python3 manage.py recreate_variations 'app.Page' --field='image'
По умолчанию перенарезаются все возможные вариации для каждого экземпляра указанной модели. Можно указать конкретные вариации, которые нужно перенарезать:
python3 manage.py recreate_variations 'app.Page' --field='image' --variations big small
remove_variations
Удаление файлов вариаций.
Параметры аналогичны параметрам recreate_variations
.
python3 manage.py remove_variations 'app.Page' --field='image'
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 * 1000 * 1000), # max 10Mb
ImageMaxSizeValidator(800, 800) # max dimensions 800x800
])
class PageGallery(Collection):
file = CollectionItem(FileItem, validators=[
SizeValidator(10 * 1000 * 1000),
])
Cloudinary
Во встроенном модуле paper_uploads.cloudinary
находятся поля и классы,
позволяющие загружать файлы и картинки в облачный сервис Cloudinary.
Помимо очевидной выгоды от хранения данных в облаке, использование Cloudinary в качестве хранилища файлов даёт возможность пользоваться API для трансформации изображений и медиа.
Installation
pip install cloudinary
- Добавить
paper_uploads.cloudinary
иcloudinary
вINSTALLED_APPS
.INSTALLED_APPS = [ # ... 'paper_uploads', 'paper_uploads.cloudinary', 'cloudinary', # ... ]
- Задать данные учетной записи Cloudinary
$ export CLOUDINARY_URL=cloudinary://API-Key:API-Secret@Cloud-name?sign_url=1&secure=1
Чтобы предотвратить генерацию трасформаций конечными пользователями, рекомендуется включить в вашем аккаунте Cloudinary Strict transformations.
Model fields
Вместо FileField
и ImageField
используются поля CloudinaryFileField
,
CloudinaryImageField
и CloudinaryMediaField
.
from django.db import models
from paper_uploads.cloudinary.models import *
class Page(models.Model):
file = CloudinaryFileField(_('file'), blank=True)
image = CloudinaryImageField(_('image'), blank=True)
media = CloudinaryMediaField(_('media'), blank=True)
Дополнительные параметры загрузки Cloudinary
можно задать с помощью параметра cloudinary
:
from django.db import models
from paper_uploads.cloudinary.models import *
class Page(models.Model):
file = CloudinaryFileField(_('file'), blank=True, cloudinary={
"use_filename": False
})
Collections
Для коллекций используется тот же класс Collection
, что используется
при локальном хранении файлов. Отличаются только классы элементов коллекций.
В состав библиотеки входит три класса элементов коллекции:
CloudinaryFileItem
, CloudinaryImageItem
и CloudinaryMediaItem
(для аудио и видео).
from django.db import models
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 объявлена готовая
коллекция для изображений — CloudinaryImageCollection
:
from django.db import models
from paper_uploads.models import *
from paper_uploads.cloudinary.models import *
class PageGallery(CloudinaryImageCollection):
pass
class Page(models.Model):
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 %}>
Также, для jinja2
доступна одноименная глобальная функция:
<img src={{ paper_cloudinary_url(page.image, width=1024, crop='fill') }}>
Customize upload folder
По умолчанию, папки для загружаемых файлов задаются глобально, в настройках:
PAPER_UPLOADS = {
# ...
"FILES_UPLOAD_TO": "files/%Y-%m-%d",
"IMAGES_UPLOAD_TO": "images/%Y-%m-%d",
"COLLECTION_FILES_UPLOAD_TO": "collections/files/%Y-%m-%d",
"COLLECTION_IMAGES_UPLOAD_TO": "collections/images/%Y-%m-%d",
}
Для того, чтобы загружаемые файлы помещались в отдельную папку для конкретного
поля, необходимо переопределить метод get_file_folder
:
from django.db import models
from paper_uploads.models import *
class CustomUploadedFile(UploadedFile):
class Meta:
proxy = True
def get_file_folder(self) -> str:
return "custom-files/%Y-%m-%d"
class Page(models.Model):
file = FileField(_("file"), to=CustomUploadedFile)
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.
Значение по умолчанию:
PAPER_UPLOADS = {
"CLOUDINARY_UPLOADER_OPTIONS": {
"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.txt
pre-commit install
Install npm
dependencies and build static files:
npm ci
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
File details
Details for the file paper-uploads-0.6.3.tar.gz
.
File metadata
- Download URL: paper-uploads-0.6.3.tar.gz
- Upload date:
- Size: 395.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.5.0 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.10.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 |
9f8443cda80adc12cb89be30ccc77a6902114d4342b840a8876a96948b1f89be
|
|
MD5 |
885ea690429ef7a8162921a6437bdae4
|
|
BLAKE2b-256 |
5e6696e6d921c384894d587c13505caefcaa2e71da85dc917a554a8c61706cd9
|
File details
Details for the file paper_uploads-0.6.3-py2.py3-none-any.whl
.
File metadata
- Download URL: paper_uploads-0.6.3-py2.py3-none-any.whl
- Upload date:
- Size: 489.6 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.5.0 importlib_metadata/4.8.1 pkginfo/1.7.1 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.10.0
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 |
8b915ad47373481cc533a820a42de07214de92dca11d31baf4f31611fab3dbbe
|
|
MD5 |
3bdfaf2a57b47190d44dba8fed08fc1c
|
|
BLAKE2b-256 |
78610bcb5108b6f7447b0f68ed5bce7d45a2849541bd92289dc3215be229de30
|