paper-uploads


Namepaper-uploads JSON
Version 0.18.7 PyPI version JSON
download
home_pagehttps://github.com/dldevinc/paper-uploads
SummaryAsynchronous file upload for Django
upload_time2024-03-14 10:29:15
maintainerMihail Mishakin
docs_urlNone
authorMihail Mishakin
requires_python>=3.9
licenseBSD license
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # paper-uploads

Асинхронная загрузка файлов для административного интерфейса Django.

[![PyPI](https://img.shields.io/pypi/v/paper-uploads.svg)](https://pypi.org/project/paper-uploads/)
[![Build Status](https://github.com/dldevinc/paper-uploads/actions/workflows/tests.yml/badge.svg)](https://github.com/dldevinc/paper-uploads)
[![Software license](https://img.shields.io/pypi/l/paper-uploads.svg)](https://pypi.org/project/paper-uploads/)

## Requirements

-   Python >= 3.9
-   Django >= 3.2
-   [paper-admin][paper-admin] >= 7.0
-   [variations][variations]

## Features

-   Каждый файл представлен своей моделью. Это позволяет хранить
    вместе с файлом дополнительные данные. Например, `alt` для изображений.
-   Загрузка файлов происходит асинхронно и начинается сразу
    при выборе файла в интерфейсе администратора.
-   Поля модели, предоставляемые библиотекой `paper-uploads`,
    являются производными от `OneToOneField` и не используют
    `<input type="file">`. Благодаря этому, при ошибках валидации формы
    прикрепленные файлы не сбрасываются.
-   Загруженные картинки можно нарезать на множество вариаций.
    Каждая вариация гибко настраивается. Можно указать размеры,
    качество сжатия, формат, добавить дополнительные
    [pilkit][pilkit]-процессоры, распознавание лиц и прочее.
    <br>См. [variations][variations].
-   Совместим с [django-storages][django-storages].
-   Опциональная интеграция с [django-rq][django-rq]
    для отложенной нарезки картинок на вариации.
-   Возможность создавать коллекции файлов. В частности, галерей
    изображений с возможностью сортировки элементов.

## Table of Contents

-   [Installation](#Installation)
-   [Описание](#Описание)
-   [FileField и ImageField](#FileField-и-ImageField)
    -   [Поля моделей загруженных файлов](#Поля-моделей-загруженных-файлов)
    -   [Storage](#Storage)
    -   [Каталог загрузки файла](#Каталог-загрузки-файла)
    -   [Валидаторы](#Валидаторы)
    -   [Программная загрузка файлов](#Программная-загрузка-файлов)
    -   [Вариации](#Вариации)
        -   [Версии вариаций](#Версии-вариаций)
        -   [Redis Queue](#Redis-Queue)
-   [SVGFileField](#SVGFileField)
-   [Коллекции](#Коллекции)
    -   [Элементы коллекции](#Элементы-коллекции)
        -   [Storage и каталог загрузки файлов](#Storage-и-каталог-загрузки-файлов)
        -   [Валидаторы](#Валидаторы-2)
        -   [Программное создание элемента коллекции](#Программное-создание-элемента-коллекции)
        -   [Вариации](#Вариации-2)
    -   [HTML Template Example](#HTML-Template-Example)
-   [Management команды](#Management-команды)
-   [Settings](#Settings)

## Installation

Install `paper-uploads`:

```shell
pip install paper-uploads[full]
```

Add `paper_uploads` to `INSTALLED_APPS` in `settings.py`:

```python
INSTALLED_APPS = [
    # ...
    "paper_uploads",
    # ...
]
```

Configure `paper-uploads` in django's `settings.py`:

```python
PAPER_UPLOADS = {
    "VARIATION_DEFAULTS": {
        "jpeg": dict(
            quality=80,
            progressive=True,
        ),
        "webp": dict(
            quality=75,
        )
    }
}

# Add JS translations
PAPER_LOCALE_PACKAGES = [
   "paper_admin",
   "paper_uploads",
   "django.contrib.admin",
]
```

## Описание

В состав библиотеки входит два поля &mdash; `FileField` и `ImageField` &mdash;
и модель `Collection`, предназначенная для группировки загруженных файлов
с целью создания, к примеру, фотогалерей.

С примерами использования библиотеки вы можете ознакомиться
[здесь](https://github.com/dldevinc/paper-uploads/tree/master/tests/examples).

## FileField и ImageField

```python
from django.db import models
from django.utils.translation import gettext_lazy as _
from paper_uploads.models import FileField, ImageField


class Page(models.Model):
    file = FileField(
        _("file"),
        blank=True
    )
    image = ImageField(
        _("image"),
        blank=True
    )
```

![image](https://user-images.githubusercontent.com/6928240/154901303-be8a6a26-c0c1-4bb1-a9cc-ece14f5b04d2.png)

Эти поля используются для тех же целей, что и одноимённые стандартные поля Django
&mdash; для загрузки файлов и изображений &mdash; но имеют ряд существенных отличий.

Главное отличие заключается в том, что поля `FileField` и `ImageField` являются
производными от стандартного `OneToOneField`. Соответственно, загруженные файлы
представлены экземплярами полноценных моделей.

### Поля моделей загруженных файлов

Файлы, загруженные с помощью полей `FileField` и `ImageField`, хранятся в
экземплярах моделей `UploadedFile` и `UploadedImage` соответственно.

В следующих таблицах перечислены общие поля и свойства обеих моделей:

| Поле          | Описание                                                                               |
|---------------|----------------------------------------------------------------------------------------|
| resource_name | Имя файла без пути, суффикса и расширения.<br>Пример: `report2020`.                    |
| extension     | Расширение файла в нижнем регистре без точки.<br>Пример: `pdf`.                        |
| size          | Размер файла в байтах.                                                                 |
| checksum      | Контрольная сумма содержимого файла.<br>Используется для отслеживания изменений файла. |
| uploaded_at   | Дата и время загрузки файла.                                                           |
| created_at    | Дата и время создания экземпляра модели.                                               |
| modified_at   | Дата и время изменения экземпляра модели.                                              |

| Свойство | Описание                                                                                    |
|----------|---------------------------------------------------------------------------------------------|
| name     | Полное имя файла.<br>Пример: `files/report2020_19sc2Kj.pdf`.                                |
| url      | URL-адрес файла.<br>Пример: `/media/files/report2020_19sc2Kj.pdf`.                          |
| path     | Абсолютный путь к файлу.<br>Пример: `/home/www/django/media/files/report2020_19sc2Kj.pdf`.  |

Ниже перечислены поля и свойства, специфичные для каждой модели.

Специфичные поля `UploadedFile`:

| Поле         | Описание                                                                                                                                  |
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------|
| display_name | Удобочитаемое название файла для вывода на сайте.<br>Заполняется в диалоговом окне редактирования файла.<br>Пример: `Annual report 2020`. |

Специфичные поля `UploadedImage`:

| Поле        | Описание                                                                     |
|-------------|------------------------------------------------------------------------------|
| title       | Название изображения, которое можно вставить в атрибут `title` тэга `<img>`. |
| description | Описание изображения, которое можно вставить в атрибут `alt` тэга `<img>`.   |
| width       | Ширина загруженного изображения.                                             |
| height      | Высота загруженного изображения.                                             |
| ratio       | Отношение ширины изображения к высоте в формате `Decimal`.                   |
| hw_ratio    | Отношение высоте изображения к ширине в формате `Decimal`.                   |
| srcset      | Строка формата `[URL] [WIDTH]w`.                                             |

Большинство полей заполняются автоматически при загрузке файла и предназначены
только для чтения. Но такие поля, как `display_name` или `title`, заполняются
пользователем в диалоговом окне редактирования файла:

![image](https://user-images.githubusercontent.com/6928240/154904780-5d365952-ce75-4491-952e-6b2992e35309.png)
![image](https://user-images.githubusercontent.com/6928240/154910567-991bc27c-e7c6-40e7-883e-f3120897c197.png)

### Storage

По умолчанию все поля `paper-uploads` используют единый экземпляр хранилища,
определяемый настройками `STORAGE` и `STORAGE_OPTIONS`:

```python
# settings.py

PAPER_UPLOADS = {
    "STORAGE": "django.core.files.storage.FileSystemStorage",
    "STORAGE_OPTIONS": {},
    # ...
}
```

Вы можете указать экземпляр хранилища для конкретного поля:

```python
from django.db import models
from django.core.files.storage import FileSystemStorage
from django.utils.translation import gettext_lazy as _
from paper_uploads.models import FileField


class Page(models.Model):
    report = FileField(
        _("report"),
        blank=True,
        storage=FileSystemStorage(location="uploads/"),
        upload_to="reports/%Y/%m"
    )
```

### Каталог загрузки файла

Все поля используют единые значения, указанные в настройках
`FILES_UPLOAD_TO` и `IMAGES_UPLOAD_TO`:

```python
# settings.py

PAPER_UPLOADS = {
    # ...
    "FILES_UPLOAD_TO": "files/%Y/%m/%d",
    "IMAGES_UPLOAD_TO": "images/%Y/%m/%d",
    # ...
}
```

Для конкретного поля каталог сохранения можно указать в параметре поля `upload_to`.
Параметр поддерживает форматирование `strftime()`, которое будет заменено на
дату/время загруженного файла (и загружаемые файлы не заполнят один каталог).

```python
from django.db import models
from django.utils.translation import gettext_lazy as _
from paper_uploads.models import FileField


class Page(models.Model):
    report = FileField(
        _("report"),
        blank=True,
        upload_to="pdf/reports/%Y"
    )
```

Обратите внимание, что в параметр `upload_to` _нельзя передать вызываемый объект_.

Если вам требуется динамическое определение каталога или имени загруженного файла,
создайте proxy-модель и переопрелите метод `generate_filename()`:

```python
import os
import datetime
from django.db import models
from django.utils.translation import gettext_lazy as _
from paper_uploads.models import FileField, UploadedFile


class UploadedFileProxy(UploadedFile):
    class Meta:
        proxy = True

    def generate_filename(self, filename: str) -> str:
        _, ext = os.path.splitext(filename)
        filename = "proxy-files/file-%Y-%m-%d_%H%M%S{}".format(ext)
        filename = datetime.datetime.now().strftime(filename)

        storage = self.get_file_storage()
        return storage.generate_filename(filename)


class Page(models.Model):
    file = FileField(
        _("file"),
        to=UploadedFileProxy,
        blank=True,
    )
```

### Валидаторы

На загружаемые файлы можно наложить ограничения с помощью валидаторов.

Модуль `paper-uploads.validators` предоставляет следующие классы для валидации файлов:

-   `MaxSizeValidator` - задает максимально допустимый размер файла.
    <br>Максимальный размер можно указать как в виде числа (в байтах),
    так и в виде строки.
    <br>Например: `4 * 10 ** 6`, `4mb`, `4MB`, `4M`.
-   `ExtensionValidator` - задает допустимые расширения файлов.
-   `MimeTypeValidator` - задает допустимые MIME-типы файлов.
-   `ImageMinSizeValidator` - устанавливает минимальный размер загружаемых изображений.
-   `ImageMaxSizeValidator` - устанавливает максимальный размер загружаемых изображений.

Пример:

```python
from django.db import models
from django.utils.translation import gettext_lazy as _
from paper_uploads.models import FileField
from paper_uploads.validators import ExtensionValidator, MaxSizeValidator


class Page(models.Model):
    report = FileField(
        _("file"),
        blank=True,
        validators=[
            ExtensionValidator([".pdf", ".doc", ".docx"]),
            MaxSizeValidator("10MB")
        ]
    )
```

Ограничения, наложенные этими валидаторами, отображаются в виджете:
![image](https://user-images.githubusercontent.com/6928240/152322863-33108ef9-061c-4af5-8d0c-aeb5b467e04b.png)

### Программная загрузка файлов

```python
from paper_uploads.models import *

report = UploadedFile()
report.set_owner_field(Page, "report")
report.attach("/tmp/file.doc")
report.save()

page = Page.objects.create(
    report=report
)
```

В метод `set_owner_field()` передаётся модель и имя поля модели, в которое будет
сохранен экземпляр модели файла. Эти данные необходимы для выявления файлов, которые
нигде не используются.

Метод `attach()` производит непосредственное сохранение файла и заполняет экземпляр
дополнительными данными. В метод можно передать как путь к локальному файлу, так
и файловый объект.

### Вариации

`ImageField` позволяет создавать вариации для загруженного изображения.
Вариация - это изображение, полученное из исходного по _заранее_ объявленным правилам.

Для создания вариаций используется библиотека [variations][variations].

```python
from django.db import models
from django.utils.translation import gettext_lazy as _
from paper_uploads.models import *


class Page(models.Model):
    image = ImageField(
        _("image"),
        blank=True,
        variations=dict(
            desktop=dict(
                size=(800, 0),
                clip=False,
            ),
            mobile=dict(
                size=(600, 0),
                clip=False,
            ),
        )
    )
```

К файлам вариаций можно обратиться через модель `UploadedImage` используя их имена:

```python
print(page.image.desktop.url)
# /media/images/2022/02/21/sample.desktop.jpg
```

Создание файлов вариаций происходит в момент загрузки изображения на сервер.
Поэтому изменение настроек вариаций не окажет никакого эффекта на уже загруженные
изображения.

Для того, чтобы создать файлы для новых вариаций (либо перезаписать существующие файлы
вариаций) можно поступить одним из ниже описанных способов.

1. Вызвать метод `recut()`:

    ```python
    page.image.recut()
    ```

    При вызове этого метода все файлы вариаций для текущего экземпляра
    создаются заново.
    <br>
    <br>
    Можно явно указать имена вариаций, которые необходимо перезаписать:

    ```python
    page.image_group.recut(["desktop", "mobile"])
    ```

2. Выполнить management-команду `recreate_variations`:

    ```shell
    python3 manage.py recreate_variations app.page \
            --field image
            --variations desktop mobile
    ```

    Эта команда сгенерирует вариации для всех экземпляров указанной модели.

#### Версии вариаций

Допустим, у нас есть изображение, которое нужно отобразить в трех
вариантах: `desktop`, `tablet` и `mobile`. Если мы хотим поддерживать
дисплеи Retina, нам нужно добавить ещё три вариации для размера `2x`.
Если мы также хотим использовать формат `WebP` (сохранив исходный формат
для обратной совместимости), то общее количество вариаций достигает **12**.

Поскольку Retina-вариации отличаются от обычных только увеличенным
на постоянный коэффициент размером, а `WebP`-вариации — добавлением
параметра `format = "webp"`, мы можем создавать эти вариации
автоматически. Это и есть версии вариации.

Перечень версий, которые нужно сгенерировать, указываются в параметре
вариации `versions`. Поддерживаются следующие значения:
`webp`, `2x`, `3x`, `4x`.

```python
from django.db import models
from django.utils.translation import gettext_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 2x
-   `desktop_webp_2x` - `WebP`-версия Retina 2x
-   `desktop_3x` - Retina 3x
-   `desktop_webp_3x` - `WebP`-версия Retina 3x

**NOTE**: Retina-суффикс всегда следует после суффикса `webp`, если
он есть.

Если необходимо переопределить какие-то параметры дополнительной
вариации, то придётся объявлять вариацию явно:

```python
from django.db import models
from django.utils.translation import gettext_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
                )
            )
        )
    )
```

#### Redis Queue

При загрузке большого количества изображений процесс создания вариаций может занимать
значительное время. Эту работу можно вынести в отдельный процесс с помощью
[django-rq][django-rq]:

```shell
pip install django-rq
```

```python
# settings.py
PAPER_UPLOADS = {
    "RQ_ENABLED": True,
    "RQ_QUEUE_NAME": "default",
    # ...
}
```

Теперь при загрузке изображений, в очередь под именем `default` будет добавляться
задача, которая создаст все необходимые вариации.

## SVGFileField

Поле `SVGFileField` предназначено для загрузки SVG-файлов. Оно идентично `FileField`,
но связанная с ним модель `UploadedSVGFile` включает несколько дополнительных полей:

| Поле        | Описание                                                                     |
|-------------|------------------------------------------------------------------------------|
| width       | Ширина изображения в формате `Decimal`. Может быть вещественным числом.      |
| height      | Высота изображения в формате `Decimal`. Может быть вещественным числом.      |
| title       | Название изображения, которое можно вставить в атрибут `title` тэга `<img>`. |
| description | Описание изображения, которое можно вставить в атрибут `alt` тэга `<img>`.   |

```python
from django.db import models
from django.utils.translation import gettext_lazy as _
from paper_uploads.models import SVGFileField


class Page(models.Model):
    svg = SVGFileField(
        _("svg"),
        blank=True
    )
```

## Коллекции

Коллекция — это модель, группирующая экземпляры других моделей (элементов коллекции).
В частности, с помощью коллекции можно создать фотогалерею или список файлов.

Для создания коллекции необходимо объявить класс, унаследованный от `Collection`
и указать модели элементов, которые могут входить в коллекцию с помощью псевдо-поля
`CollectionItem`:

```python
from django.db import models
from paper_uploads.models import *


# Collection model
class PageFiles(Collection):
    svg = CollectionItem(SVGItem)
    image = CollectionItem(ImageItem)
    file = CollectionItem(FileItem)


class Page(models.Model):
    files = CollectionField(PageFiles)
```

Класс `Collection` обладает особенным свойством: _любой дочерний класс, унаследованный
от `Collection`, является proxy-моделью для `Collection`_.

В большинстве случаев коллекции отличаются друг от друга только набором элементов,
которые могут входить в коллекцию. Использование proxy-моделей предотвращает создание
множества одинаковых таблиц в БД.

Если же для коллекции необходима отдельная таблица (например, если вы решили добавить
в неё новое поле), то необходимо явно установить свойство `Meta.proxy` в значение `False`:

```python
from django.db import models
from paper_uploads.models import *


class CustomCollection(Collection):
    name = models.CharField("name", max_length=128, blank=True)

    file = CollectionItem(FileItem)

    class Meta:
        proxy = False
```

### Элементы коллекции

Псевдо-поле `CollectionItem` регистрирует модель элемента коллекции под заданным именем.

```python
from paper_uploads.models import *


class PageFiles(Collection):
    svg = CollectionItem(SVGItem)
    image = CollectionItem(ImageItem)
    file = CollectionItem(FileItem)
```

В приведённом примере, коллекция `PageFiles` может включать элементы трех моделей:
`SVGItem`, `ImageItem` и `FileItem`.

Порядок объявления элементов коллекции важен: первый класс модели, чей метод `accept()`
вернет `True`, определит модель загруженного файла. По этой причине _`FileItem` должен
указываться последним_, т.к. он принимает любые файлы.

Получить элементы определённого типа можно с помощью метода `get_items()`:

```python
for item in page.files.get_items("image"):
    # ...
```

---

В состав библиотеки входят следующие модели элементов:

-   `ImageItem`<br>
    Для хранения изображения с возможностью нарезки на [вариации](#Вариации).
    Допускются только те файлы, которые можно открыть с помощью [Pillow](https://pillow.readthedocs.io/en/stable/).

-   `SVGItem`<br>
    Для хранения SVG иконок.

-   `FileItem`<br>
    Может хранить любой файл.

#### Storage и каталог загрузки файлов

По умолчанию элементы коллекции используют тот же экземпляр хранилища,
что используется полями `FileField` и `ImageField`.

Каталоги загрузки файлов для элементов коллекций указываются в настройках
`COLLECTION_FILES_UPLOAD_TO` и `COLLECTION_IMAGES_UPLOAD_TO`:

```python
# settings.py

PAPER_UPLOADS = {
    # ...
    "COLLECTION_FILES_UPLOAD_TO": "collections/files/%Y/%m/%d",
    "COLLECTION_IMAGES_UPLOAD_TO": "collections/images/%Y/%m/%d",
    # ...
}
```

Для отдельно взятого элемента коллекции экземпляр хранилища и каталог загрузки  
можно указать в параметре `options` псевдо-поля `CollectionItem`:

```python
from django.core.files.storage import FileSystemStorage
from paper_uploads.models import *


class PageFiles(Collection):
    image = CollectionItem(ImageItem, options={
        "storage": FileSystemStorage(location="uploads/"),
        "upload_to": "gallery",
    })
```

Как в случае с `FileField` и `ImageField`, значением `upload_to` не может выступать
вызываемый объект.

Если вам требуется динамическое определение каталога или имени загруженного файла,
создайте proxy-модель и переопрелите метод `generate_filename()`:

```python
import os
import datetime
from django.db import models
from django.utils.translation import gettext_lazy as _
from paper_uploads.models import *


class ProxyImageItem(ImageItem):
    class Meta:
        proxy = True

    def generate_filename(self, filename: str) -> str:
        _, ext = os.path.splitext(filename)
        filename = "gallery/image-%Y-%m-%d_%H%M%S{}".format(ext)
        filename = datetime.datetime.now().strftime(filename)

        storage = self.get_file_storage()
        return storage.generate_filename(filename)


class PageGallery(Collection):
    image = CollectionItem(ProxyImageItem)


class Page(models.Model):
    gallery = CollectionField(
        PageGallery,
        verbose_name=_("gallery")
    )
```

#### <a id="Валидаторы-2" />Валидаторы

На загружаемые в коллекции файлы можно наложить ограничения с помощью валидаторов:

```python
from django.db import models
from django.utils.translation import gettext_lazy as _
from paper_uploads.models import *
from paper_uploads.validators import ImageMaxSizeValidator, ImageMinSizeValidator


class PageGallery(Collection):
    image = CollectionItem(ImageItem, validators=[
        ImageMinSizeValidator(640, 480),
        ImageMaxSizeValidator(4000, 3000)
    ])


class Page(models.Model):
    gallery = CollectionField(
        PageGallery,
        verbose_name=_("gallery")
    )
```

#### Программное создание элемента коллекции

Элементы коллекций создаются почти также, как `UploadedFile` и `UploadedImage`.
Разница лишь в том, что вместо вызова метода `set_owner_field()` необходимо
вызвать метод `attach_to()` для присоединения элемента к коллекции:

```python
from paper_uploads.models import *

collection = PageGallery.objects.create()

item = ImageItem()
item.attach_to(collection)
item.attach("/tmp/image.jpg")
item.save()

page = Page.objects.create(
    gallery=collection
)
```

#### <a id="Вариации-2" />Вариации

Вариации для изображений коллекции можно указать одним из двух способов:

1. Параметр `options` псевдо-поля `CollectionItem`:

    ```python
    from paper_uploads.models import *

    class PageGallery(Collection):
        image = CollectionItem(ImageItem, options={
            "variations": dict(
                mobile=dict(
                    size=(640, 0),
                    clip=False
                )
            )
        })
    ```

2. Атрибут класса коллекции `VARIATIONS`:

    ```python
    from paper_uploads.models import *

    class PageGallery(Collection):
        VARIATIONS = dict(
            mobile=dict(
                size=(640, 0),
                clip=False
            )
        )

        image = CollectionItem(ImageItem)
    ```

### HTML Template Example

```html
{% if page.gallery %}
<div class="gallery">
    {% for item in page.gallery %} {% if item.type == "image" %}
    <div class="item item--{{ item.type }}">
        <img
            src="{{ item.url }}"
            width="{{ item.width }}"
            height="{{ item.height }}"
            title="{{ item.title }}"
            alt="{{ item.description }}"
        />
    </div>
    {% elif item.type == "file" %}}
    <div class="item item--{{ item.type }}">
        <a href="{{ item.url }}" download> Download file "{{ item.display_name }}" ({{ item.size|filesizeformat }}) </a>
    </div>
    {% endif %}} {% endfor %}
</div>
{% endif %}
```

### `ImageCollection`

Для коллекций изображений существует специальный базовый класс `ImageCollection`,
который не требует для каждого отдельного случая создавать отдельный класс элемента 
коллекции. Все необходимые параметры можно указать сразу, через атрибуты класса:  

```python
from django.db import models
from django.utils.translation import gettext_lazy as _
from paper_uploads.models import *
from paper_uploads.validators import ImageMaxSizeValidator, ImageMinSizeValidator


class PageGallery(ImageCollection):
    UPLOAD_TO = "page/gallery",
    VARIATIONS = dict(
        gallery=dict(
            size=(1600, 900),
        )
    )
    VALIDATORS = [
        ImageMinSizeValidator(640, 480),
        ImageMaxSizeValidator(4000, 3000)
    ]


class Page(models.Model):
    gallery = CollectionField(
        PageGallery,
        verbose_name=_("gallery")
    )
```

## Management команды

### check_uploads

Запускает комплексную проверку загруженных файлов
и выводит результат.

Список производимых тестов:

-   загруженный файл существует
-   класс модели владельца (указанный в `owner_app_label` и `owner_model_name`) существует
-   в классе модели владельца существует поле, указанное в `owner_fieldname`
-   у элементов коллекций указано корректное значение в поле `type`
-   модель элемента коллекции соответствует модели, указанной в классе коллекции

```shell
python3 manage.py check_uploads
```

### clean_uploads

Находит мусорные записи в БД (например те, у которых нет владельца)
и предлагает их удалить.

Владелец загруженного файла устанавливается в момент сохранения страницы
в интерфейсе администратора. Это происходит позже фактической загрузки файла
на сервер. В промежутке времени между этими событиями файл будет являться "сиротой".
Для того, чтобы такие файлы не удалялись, команда `clean_uploads` игнорирует
файлы, загруженные в течение последнего часа.

```shell
python3 manage.py clean_uploads
```

### remove_empty_collections

Удаление экземпляров коллекций, в которых нет ни одного элемента.

```shell
python3 manage.py clean_uploads
```

### create_missing_variations

Создаёт отсутствующие файлы вариаций.

```shell
python3 manage.py create_missing_variations
```

### recreate_variations

Создание/перезапись вариаций для всех экземпляров указанной модели.

```shell
# for collections
python3 manage.py recreate_variations app.Photos image

# for regular models
python3 manage.py recreate_variations app.Page image
```

В результате вызова этих команд, пользователю будет предложено выбрать 
вариации, которые следует обновить.

Можно сразу указать нужные вариации:

```shell
python3 manage.py recreate_variations app.Page image -- desktop mobile
```

### remove_variations

Удаление файлов вариаций.
Удаляются только файлы объявленных вариаций.
Параметры аналогичны параметрам `recreate_variations`.

```shell
# for collections
python3 manage.py remove_variations app.Photos image

# for regular models
python3 manage.py remove_variations app.Page image
```

## Settings

Все настройки указываются в словаре `PAPER_UPLOADS`.

```python
PAPER_UPLOADS = {
    "STORAGE": "django.core.files.storage.FileSystemStorage",
    "STORAGE_OPTIONS": {},
    "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",

    "RQ_ENABLED": True,
    "RQ_QUEUE_NAME": "default",

    "VARIATION_DEFAULTS": {
        "jpeg": dict(
            quality=80,
            progressive=True,
        ),
        "webp": dict(
            quality=75,
        )
    }
}
```

### `STORAGE`

Путь к классу [хранилища Django](https://docs.djangoproject.com/en/2.2/ref/files/storage/).

Значение по умолчанию: `django.core.files.storage.FileSystemStorage`

### `STORAGE_OPTIONS`

Параметры инициализации хранилища.

Значение по умолчанию: `{}`

### `FILES_UPLOAD_TO`

Путь к папке, в которую загружаются файлы из FileField.
Может содержать параметры для даты и времени (см. [upload_to](https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.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_HEIGHT`

Размеры превью элементов коллекций в админке.

Значение по умолчанию: `180` x `135`

### `COLLECTION_IMAGE_ITEM_PREVIEW_VARIATIONS`

Вариации, добавляемые к каждому классу изображений коллекций
для отображения превью в админке. Размеры файлов должны
совпадать с `COLLECTION_ITEM_PREVIEW_WIDTH` и
`COLLECTION_ITEM_PREVIEW_HEIGHT`.

### `RQ_ENABLED`

Включает нарезку картинок на вариации через отложенные задачи.
Требует наличие установленного пакета [django-rq][django-rq].

Значение по умолчанию: `False`

### `RQ_QUEUE_NAME`

Название очереди, в которую помещаются задачи по нарезке картинок.

Значение по умолчанию: `default`

### `VARIATION_DEFAULTS`

Параметры вариаций по умолчанию.

Параметры, указанные в этом словаре, будут применены к каждой
вариации &mdash; если только вариация их явно не переопределяет.

Значение по умолчанию: `None`

## Development and Testing

After cloning the Git repository, you should install this
in a virtualenv and set up for development:

```shell script
virtualenv .venv
source .venv/bin/activate
pip install -r ./requirements.txt
pre-commit install
```

Install `npm` dependencies and build static files:

```shell script
npm ci
npx webpack
```

[paper-admin]: https://github.com/dldevinc/paper-admin
[variations]: https://github.com/dldevinc/variations
[pilkit]: https://github.com/matthewwithanm/pilkit
[django-storages]: https://github.com/jschneier/django-storages
[django-rq]: https://github.com/rq/django-rq

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/dldevinc/paper-uploads",
    "name": "paper-uploads",
    "maintainer": "Mihail Mishakin",
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": "x896321475@gmail.com",
    "keywords": "",
    "author": "Mihail Mishakin",
    "author_email": "x896321475@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/90/b7/e4bea4857ab9900c432a44b6f24b7f446feb9aef8f819ddec4f92aa14f1e/paper-uploads-0.18.7.tar.gz",
    "platform": "OS Independent",
    "description": "# paper-uploads\n\n\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u0434\u043b\u044f \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 Django.\n\n[![PyPI](https://img.shields.io/pypi/v/paper-uploads.svg)](https://pypi.org/project/paper-uploads/)\n[![Build Status](https://github.com/dldevinc/paper-uploads/actions/workflows/tests.yml/badge.svg)](https://github.com/dldevinc/paper-uploads)\n[![Software license](https://img.shields.io/pypi/l/paper-uploads.svg)](https://pypi.org/project/paper-uploads/)\n\n## Requirements\n\n-   Python >= 3.9\n-   Django >= 3.2\n-   [paper-admin][paper-admin] >= 7.0\n-   [variations][variations]\n\n## Features\n\n-   \u041a\u0430\u0436\u0434\u044b\u0439 \u0444\u0430\u0439\u043b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u0441\u0432\u043e\u0435\u0439 \u043c\u043e\u0434\u0435\u043b\u044c\u044e. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c\n    \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u0444\u0430\u0439\u043b\u043e\u043c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, `alt` \u0434\u043b\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439.\n-   \u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e \u0438 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441\u0440\u0430\u0437\u0443\n    \u043f\u0440\u0438 \u0432\u044b\u0431\u043e\u0440\u0435 \u0444\u0430\u0439\u043b\u0430 \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430.\n-   \u041f\u043e\u043b\u044f \u043c\u043e\u0434\u0435\u043b\u0438, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u043e\u0439 `paper-uploads`,\n    \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u044b\u043c\u0438 \u043e\u0442 `OneToOneField` \u0438 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\n    `<input type=\"file\">`. \u0411\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u044f \u044d\u0442\u043e\u043c\u0443, \u043f\u0440\u0438 \u043e\u0448\u0438\u0431\u043a\u0430\u0445 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0444\u043e\u0440\u043c\u044b\n    \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u0435\u043d\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u043d\u0435 \u0441\u0431\u0440\u0430\u0441\u044b\u0432\u0430\u044e\u0442\u0441\u044f.\n-   \u0417\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0440\u0435\u0437\u0430\u0442\u044c \u043d\u0430 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439.\n    \u041a\u0430\u0436\u0434\u0430\u044f \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u044f \u0433\u0438\u0431\u043a\u043e \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u0442\u0441\u044f. \u041c\u043e\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0440\u044b,\n    \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u043e \u0441\u0436\u0430\u0442\u0438\u044f, \u0444\u043e\u0440\u043c\u0430\u0442, \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435\n    [pilkit][pilkit]-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u044b, \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u0435 \u043b\u0438\u0446 \u0438 \u043f\u0440\u043e\u0447\u0435\u0435.\n    <br>\u0421\u043c. [variations][variations].\n-   \u0421\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c \u0441 [django-storages][django-storages].\n-   \u041e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0441 [django-rq][django-rq]\n    \u0434\u043b\u044f \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0439 \u043d\u0430\u0440\u0435\u0437\u043a\u0438 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u043d\u0430 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438.\n-   \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u0444\u0430\u0439\u043b\u043e\u0432. \u0412 \u0447\u0430\u0441\u0442\u043d\u043e\u0441\u0442\u0438, \u0433\u0430\u043b\u0435\u0440\u0435\u0439\n    \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432.\n\n## Table of Contents\n\n-   [Installation](#Installation)\n-   [\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435](#\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435)\n-   [FileField \u0438 ImageField](#FileField-\u0438-ImageField)\n    -   [\u041f\u043e\u043b\u044f \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432](#\u041f\u043e\u043b\u044f-\u043c\u043e\u0434\u0435\u043b\u0435\u0439-\u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0445-\u0444\u0430\u0439\u043b\u043e\u0432)\n    -   [Storage](#Storage)\n    -   [\u041a\u0430\u0442\u0430\u043b\u043e\u0433 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430](#\u041a\u0430\u0442\u0430\u043b\u043e\u0433-\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438-\u0444\u0430\u0439\u043b\u0430)\n    -   [\u0412\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u044b](#\u0412\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u044b)\n    -   [\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u0430\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432](#\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u0430\u044f-\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430-\u0444\u0430\u0439\u043b\u043e\u0432)\n    -   [\u0412\u0430\u0440\u0438\u0430\u0446\u0438\u0438](#\u0412\u0430\u0440\u0438\u0430\u0446\u0438\u0438)\n        -   [\u0412\u0435\u0440\u0441\u0438\u0438 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439](#\u0412\u0435\u0440\u0441\u0438\u0438-\u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439)\n        -   [Redis Queue](#Redis-Queue)\n-   [SVGFileField](#SVGFileField)\n-   [\u041a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438](#\u041a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438)\n    -   [\u042d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438](#\u042d\u043b\u0435\u043c\u0435\u043d\u0442\u044b-\u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438)\n        -   [Storage \u0438 \u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432](#Storage-\u0438-\u043a\u0430\u0442\u0430\u043b\u043e\u0433-\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438-\u0444\u0430\u0439\u043b\u043e\u0432)\n        -   [\u0412\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u044b](#\u0412\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u044b-2)\n        -   [\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438](#\u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0435-\u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435-\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430-\u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438)\n        -   [\u0412\u0430\u0440\u0438\u0430\u0446\u0438\u0438](#\u0412\u0430\u0440\u0438\u0430\u0446\u0438\u0438-2)\n    -   [HTML Template Example](#HTML-Template-Example)\n-   [Management \u043a\u043e\u043c\u0430\u043d\u0434\u044b](#Management-\u043a\u043e\u043c\u0430\u043d\u0434\u044b)\n-   [Settings](#Settings)\n\n## Installation\n\nInstall `paper-uploads`:\n\n```shell\npip install paper-uploads[full]\n```\n\nAdd `paper_uploads` to `INSTALLED_APPS` in `settings.py`:\n\n```python\nINSTALLED_APPS = [\n    # ...\n    \"paper_uploads\",\n    # ...\n]\n```\n\nConfigure `paper-uploads` in django's `settings.py`:\n\n```python\nPAPER_UPLOADS = {\n    \"VARIATION_DEFAULTS\": {\n        \"jpeg\": dict(\n            quality=80,\n            progressive=True,\n        ),\n        \"webp\": dict(\n            quality=75,\n        )\n    }\n}\n\n# Add JS translations\nPAPER_LOCALE_PACKAGES = [\n   \"paper_admin\",\n   \"paper_uploads\",\n   \"django.contrib.admin\",\n]\n```\n\n## \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435\n\n\u0412 \u0441\u043e\u0441\u0442\u0430\u0432 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0432\u0445\u043e\u0434\u0438\u0442 \u0434\u0432\u0430 \u043f\u043e\u043b\u044f &mdash; `FileField` \u0438 `ImageField` &mdash;\n\u0438 \u043c\u043e\u0434\u0435\u043b\u044c `Collection`, \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u0430\u044f \u0434\u043b\u044f \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432\n\u0441 \u0446\u0435\u043b\u044c\u044e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f, \u043a \u043f\u0440\u0438\u043c\u0435\u0440\u0443, \u0444\u043e\u0442\u043e\u0433\u0430\u043b\u0435\u0440\u0435\u0439.\n\n\u0421 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u043c\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f\n[\u0437\u0434\u0435\u0441\u044c](https://github.com/dldevinc/paper-uploads/tree/master/tests/examples).\n\n## FileField \u0438 ImageField\n\n```python\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\nfrom paper_uploads.models import FileField, ImageField\n\n\nclass Page(models.Model):\n    file = FileField(\n        _(\"file\"),\n        blank=True\n    )\n    image = ImageField(\n        _(\"image\"),\n        blank=True\n    )\n```\n\n![image](https://user-images.githubusercontent.com/6928240/154901303-be8a6a26-c0c1-4bb1-a9cc-ece14f5b04d2.png)\n\n\u042d\u0442\u0438 \u043f\u043e\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0434\u043b\u044f \u0442\u0435\u0445 \u0436\u0435 \u0446\u0435\u043b\u0435\u0439, \u0447\u0442\u043e \u0438 \u043e\u0434\u043d\u043e\u0438\u043c\u0451\u043d\u043d\u044b\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u043f\u043e\u043b\u044f Django\n&mdash; \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 &mdash; \u043d\u043e \u0438\u043c\u0435\u044e\u0442 \u0440\u044f\u0434 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0445 \u043e\u0442\u043b\u0438\u0447\u0438\u0439.\n\n\u0413\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u0437\u0430\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u043f\u043e\u043b\u044f `FileField` \u0438 `ImageField` \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f\n\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u044b\u043c\u0438 \u043e\u0442 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u043e\u0433\u043e `OneToOneField`. \u0421\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b\n\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u044b \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430\u043c\u0438 \u043f\u043e\u043b\u043d\u043e\u0446\u0435\u043d\u043d\u044b\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439.\n\n### \u041f\u043e\u043b\u044f \u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432\n\n\u0424\u0430\u0439\u043b\u044b, \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u043b\u0435\u0439 `FileField` \u0438 `ImageField`, \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432\n\u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 `UploadedFile` \u0438 `UploadedImage` \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e.\n\n\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0442\u0430\u0431\u043b\u0438\u0446\u0430\u0445 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u044b \u043e\u0431\u0449\u0438\u0435 \u043f\u043e\u043b\u044f \u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430 \u043e\u0431\u0435\u0438\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439:\n\n| \u041f\u043e\u043b\u0435          | \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435                                                                               |\n|---------------|----------------------------------------------------------------------------------------|\n| resource_name | \u0418\u043c\u044f \u0444\u0430\u0439\u043b\u0430 \u0431\u0435\u0437 \u043f\u0443\u0442\u0438, \u0441\u0443\u0444\u0444\u0438\u043a\u0441\u0430 \u0438 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f.<br>\u041f\u0440\u0438\u043c\u0435\u0440: `report2020`.                    |\n| extension     | \u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 \u0432 \u043d\u0438\u0436\u043d\u0435\u043c \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0435 \u0431\u0435\u0437 \u0442\u043e\u0447\u043a\u0438.<br>\u041f\u0440\u0438\u043c\u0435\u0440: `pdf`.                        |\n| size          | \u0420\u0430\u0437\u043c\u0435\u0440 \u0444\u0430\u0439\u043b\u0430 \u0432 \u0431\u0430\u0439\u0442\u0430\u0445.                                                                 |\n| checksum      | \u041a\u043e\u043d\u0442\u0440\u043e\u043b\u044c\u043d\u0430\u044f \u0441\u0443\u043c\u043c\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430.<br>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0434\u043b\u044f \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0444\u0430\u0439\u043b\u0430. |\n| uploaded_at   | \u0414\u0430\u0442\u0430 \u0438 \u0432\u0440\u0435\u043c\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430.                                                           |\n| created_at    | \u0414\u0430\u0442\u0430 \u0438 \u0432\u0440\u0435\u043c\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 \u043c\u043e\u0434\u0435\u043b\u0438.                                               |\n| modified_at   | \u0414\u0430\u0442\u0430 \u0438 \u0432\u0440\u0435\u043c\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 \u043c\u043e\u0434\u0435\u043b\u0438.                                              |\n\n| \u0421\u0432\u043e\u0439\u0441\u0442\u0432\u043e | \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435                                                                                    |\n|----------|---------------------------------------------------------------------------------------------|\n| name     | \u041f\u043e\u043b\u043d\u043e\u0435 \u0438\u043c\u044f \u0444\u0430\u0439\u043b\u0430.<br>\u041f\u0440\u0438\u043c\u0435\u0440: `files/report2020_19sc2Kj.pdf`.                                |\n| url      | URL-\u0430\u0434\u0440\u0435\u0441 \u0444\u0430\u0439\u043b\u0430.<br>\u041f\u0440\u0438\u043c\u0435\u0440: `/media/files/report2020_19sc2Kj.pdf`.                          |\n| path     | \u0410\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u043a \u0444\u0430\u0439\u043b\u0443.<br>\u041f\u0440\u0438\u043c\u0435\u0440: `/home/www/django/media/files/report2020_19sc2Kj.pdf`.  |\n\n\u041d\u0438\u0436\u0435 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u044b \u043f\u043e\u043b\u044f \u0438 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430, \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u044b\u0435 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438.\n\n\u0421\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u044b\u0435 \u043f\u043e\u043b\u044f `UploadedFile`:\n\n| \u041f\u043e\u043b\u0435         | \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435                                                                                                                                  |\n|--------------|-------------------------------------------------------------------------------------------------------------------------------------------|\n| display_name | \u0423\u0434\u043e\u0431\u043e\u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 \u0434\u043b\u044f \u0432\u044b\u0432\u043e\u0434\u0430 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435.<br>\u0417\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u0432 \u0434\u0438\u0430\u043b\u043e\u0433\u043e\u0432\u043e\u043c \u043e\u043a\u043d\u0435 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430.<br>\u041f\u0440\u0438\u043c\u0435\u0440: `Annual report 2020`. |\n\n\u0421\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u043d\u044b\u0435 \u043f\u043e\u043b\u044f `UploadedImage`:\n\n| \u041f\u043e\u043b\u0435        | \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435                                                                     |\n|-------------|------------------------------------------------------------------------------|\n| title       | \u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u043e\u0436\u043d\u043e \u0432\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432 \u0430\u0442\u0440\u0438\u0431\u0443\u0442 `title` \u0442\u044d\u0433\u0430 `<img>`. |\n| description | \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u043e\u0436\u043d\u043e \u0432\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432 \u0430\u0442\u0440\u0438\u0431\u0443\u0442 `alt` \u0442\u044d\u0433\u0430 `<img>`.   |\n| width       | \u0428\u0438\u0440\u0438\u043d\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f.                                             |\n| height      | \u0412\u044b\u0441\u043e\u0442\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f.                                             |\n| ratio       | \u041e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0435 \u0448\u0438\u0440\u0438\u043d\u044b \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043a \u0432\u044b\u0441\u043e\u0442\u0435 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 `Decimal`.                   |\n| hw_ratio    | \u041e\u0442\u043d\u043e\u0448\u0435\u043d\u0438\u0435 \u0432\u044b\u0441\u043e\u0442\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043a \u0448\u0438\u0440\u0438\u043d\u0435 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 `Decimal`.                   |\n| srcset      | \u0421\u0442\u0440\u043e\u043a\u0430 \u0444\u043e\u0440\u043c\u0430\u0442\u0430 `[URL] [WIDTH]w`.                                             |\n\n\u0411\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043f\u043e\u043b\u0435\u0439 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0444\u0430\u0439\u043b\u0430 \u0438 \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u044b\n\u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f. \u041d\u043e \u0442\u0430\u043a\u0438\u0435 \u043f\u043e\u043b\u044f, \u043a\u0430\u043a `display_name` \u0438\u043b\u0438 `title`, \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f\n\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c \u0432 \u0434\u0438\u0430\u043b\u043e\u0433\u043e\u0432\u043e\u043c \u043e\u043a\u043d\u0435 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430:\n\n![image](https://user-images.githubusercontent.com/6928240/154904780-5d365952-ce75-4491-952e-6b2992e35309.png)\n![image](https://user-images.githubusercontent.com/6928240/154910567-991bc27c-e7c6-40e7-883e-f3120897c197.png)\n\n### Storage\n\n\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0432\u0441\u0435 \u043f\u043e\u043b\u044f `paper-uploads` \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0435\u0434\u0438\u043d\u044b\u0439 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430,\n\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c\u044b\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u043c\u0438 `STORAGE` \u0438 `STORAGE_OPTIONS`:\n\n```python\n# settings.py\n\nPAPER_UPLOADS = {\n    \"STORAGE\": \"django.core.files.storage.FileSystemStorage\",\n    \"STORAGE_OPTIONS\": {},\n    # ...\n}\n```\n\n\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u0434\u043b\u044f \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044f:\n\n```python\nfrom django.db import models\nfrom django.core.files.storage import FileSystemStorage\nfrom django.utils.translation import gettext_lazy as _\nfrom paper_uploads.models import FileField\n\n\nclass Page(models.Model):\n    report = FileField(\n        _(\"report\"),\n        blank=True,\n        storage=FileSystemStorage(location=\"uploads/\"),\n        upload_to=\"reports/%Y/%m\"\n    )\n```\n\n### \u041a\u0430\u0442\u0430\u043b\u043e\u0433 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430\n\n\u0412\u0441\u0435 \u043f\u043e\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0435\u0434\u0438\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445\n`FILES_UPLOAD_TO` \u0438 `IMAGES_UPLOAD_TO`:\n\n```python\n# settings.py\n\nPAPER_UPLOADS = {\n    # ...\n    \"FILES_UPLOAD_TO\": \"files/%Y/%m/%d\",\n    \"IMAGES_UPLOAD_TO\": \"images/%Y/%m/%d\",\n    # ...\n}\n```\n\n\u0414\u043b\u044f \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u043f\u043e\u043b\u044f \u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0432 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0435 \u043f\u043e\u043b\u044f `upload_to`.\n\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 `strftime()`, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043c\u0435\u043d\u0435\u043d\u043e \u043d\u0430\n\u0434\u0430\u0442\u0443/\u0432\u0440\u0435\u043c\u044f \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 (\u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u043d\u0435 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0442 \u043e\u0434\u0438\u043d \u043a\u0430\u0442\u0430\u043b\u043e\u0433).\n\n```python\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\nfrom paper_uploads.models import FileField\n\n\nclass Page(models.Model):\n    report = FileField(\n        _(\"report\"),\n        blank=True,\n        upload_to=\"pdf/reports/%Y\"\n    )\n```\n\n\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0432 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 `upload_to` _\u043d\u0435\u043b\u044c\u0437\u044f \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442_.\n\n\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430 \u0438\u043b\u0438 \u0438\u043c\u0435\u043d\u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430,\n\u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 proxy-\u043c\u043e\u0434\u0435\u043b\u044c \u0438 \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u043b\u0438\u0442\u0435 \u043c\u0435\u0442\u043e\u0434 `generate_filename()`:\n\n```python\nimport os\nimport datetime\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\nfrom paper_uploads.models import FileField, UploadedFile\n\n\nclass UploadedFileProxy(UploadedFile):\n    class Meta:\n        proxy = True\n\n    def generate_filename(self, filename: str) -> str:\n        _, ext = os.path.splitext(filename)\n        filename = \"proxy-files/file-%Y-%m-%d_%H%M%S{}\".format(ext)\n        filename = datetime.datetime.now().strftime(filename)\n\n        storage = self.get_file_storage()\n        return storage.generate_filename(filename)\n\n\nclass Page(models.Model):\n    file = FileField(\n        _(\"file\"),\n        to=UploadedFileProxy,\n        blank=True,\n    )\n```\n\n### \u0412\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u044b\n\n\u041d\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u043b\u043e\u0436\u0438\u0442\u044c \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u043e\u0432.\n\n\u041c\u043e\u0434\u0443\u043b\u044c `paper-uploads.validators` \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043a\u043b\u0430\u0441\u0441\u044b \u0434\u043b\u044f \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0444\u0430\u0439\u043b\u043e\u0432:\n\n-   `MaxSizeValidator` - \u0437\u0430\u0434\u0430\u0435\u0442 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 \u0444\u0430\u0439\u043b\u0430.\n    <br>\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 \u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043a\u0430\u043a \u0432 \u0432\u0438\u0434\u0435 \u0447\u0438\u0441\u043b\u0430 (\u0432 \u0431\u0430\u0439\u0442\u0430\u0445),\n    \u0442\u0430\u043a \u0438 \u0432 \u0432\u0438\u0434\u0435 \u0441\u0442\u0440\u043e\u043a\u0438.\n    <br>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: `4 * 10 ** 6`, `4mb`, `4MB`, `4M`.\n-   `ExtensionValidator` - \u0437\u0430\u0434\u0430\u0435\u0442 \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u043e\u0432.\n-   `MimeTypeValidator` - \u0437\u0430\u0434\u0430\u0435\u0442 \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0435 MIME-\u0442\u0438\u043f\u044b \u0444\u0430\u0439\u043b\u043e\u0432.\n-   `ImageMinSizeValidator` - \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u043c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0445 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439.\n-   `ImageMaxSizeValidator` - \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442 \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0445 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439.\n\n\u041f\u0440\u0438\u043c\u0435\u0440:\n\n```python\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\nfrom paper_uploads.models import FileField\nfrom paper_uploads.validators import ExtensionValidator, MaxSizeValidator\n\n\nclass Page(models.Model):\n    report = FileField(\n        _(\"file\"),\n        blank=True,\n        validators=[\n            ExtensionValidator([\".pdf\", \".doc\", \".docx\"]),\n            MaxSizeValidator(\"10MB\")\n        ]\n    )\n```\n\n\u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f, \u043d\u0430\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u044d\u0442\u0438\u043c\u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u0430\u043c\u0438, \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u044e\u0442\u0441\u044f \u0432 \u0432\u0438\u0434\u0436\u0435\u0442\u0435:\n![image](https://user-images.githubusercontent.com/6928240/152322863-33108ef9-061c-4af5-8d0c-aeb5b467e04b.png)\n\n### \u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u0430\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432\n\n```python\nfrom paper_uploads.models import *\n\nreport = UploadedFile()\nreport.set_owner_field(Page, \"report\")\nreport.attach(\"/tmp/file.doc\")\nreport.save()\n\npage = Page.objects.create(\n    report=report\n)\n```\n\n\u0412 \u043c\u0435\u0442\u043e\u0434 `set_owner_field()` \u043f\u0435\u0440\u0435\u0434\u0430\u0451\u0442\u0441\u044f \u043c\u043e\u0434\u0435\u043b\u044c \u0438 \u0438\u043c\u044f \u043f\u043e\u043b\u044f \u043c\u043e\u0434\u0435\u043b\u0438, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442\n\u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043c\u043e\u0434\u0435\u043b\u0438 \u0444\u0430\u0439\u043b\u0430. \u042d\u0442\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b \u0434\u043b\u044f \u0432\u044b\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435\n\u043d\u0438\u0433\u0434\u0435 \u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f.\n\n\u041c\u0435\u0442\u043e\u0434 `attach()` \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\n\u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438. \u0412 \u043c\u0435\u0442\u043e\u0434 \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u0442\u044c \u043a\u0430\u043a \u043f\u0443\u0442\u044c \u043a \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u0444\u0430\u0439\u043b\u0443, \u0442\u0430\u043a\n\u0438 \u0444\u0430\u0439\u043b\u043e\u0432\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442.\n\n### \u0412\u0430\u0440\u0438\u0430\u0446\u0438\u0438\n\n`ImageField` \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f.\n\u0412\u0430\u0440\u0438\u0430\u0446\u0438\u044f - \u044d\u0442\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435, \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u0435 \u0438\u0437 \u0438\u0441\u0445\u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043e _\u0437\u0430\u0440\u0430\u043d\u0435\u0435_ \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u0430\u0432\u0438\u043b\u0430\u043c.\n\n\u0414\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 [variations][variations].\n\n```python\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\nfrom paper_uploads.models import *\n\n\nclass Page(models.Model):\n    image = ImageField(\n        _(\"image\"),\n        blank=True,\n        variations=dict(\n            desktop=dict(\n                size=(800, 0),\n                clip=False,\n            ),\n            mobile=dict(\n                size=(600, 0),\n                clip=False,\n            ),\n        )\n    )\n```\n\n\u041a \u0444\u0430\u0439\u043b\u0430\u043c \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439 \u043c\u043e\u0436\u043d\u043e \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u043c\u043e\u0434\u0435\u043b\u044c `UploadedImage` \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0438\u0445 \u0438\u043c\u0435\u043d\u0430:\n\n```python\nprint(page.image.desktop.url)\n# /media/images/2022/02/21/sample.desktop.jpg\n```\n\n\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440.\n\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439 \u043d\u0435 \u043e\u043a\u0430\u0436\u0435\u0442 \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u044d\u0444\u0444\u0435\u043a\u0442\u0430 \u043d\u0430 \u0443\u0436\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0435\n\u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f.\n\n\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b \u0434\u043b\u044f \u043d\u043e\u0432\u044b\u0445 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439 (\u043b\u0438\u0431\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0444\u0430\u0439\u043b\u044b\n\u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439) \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u0442\u044c \u043e\u0434\u043d\u0438\u043c \u0438\u0437 \u043d\u0438\u0436\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0445 \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432.\n\n1. \u0412\u044b\u0437\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 `recut()`:\n\n    ```python\n    page.image.recut()\n    ```\n\n    \u041f\u0440\u0438 \u0432\u044b\u0437\u043e\u0432\u0435 \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0442\u043e\u0434\u0430 \u0432\u0441\u0435 \u0444\u0430\u0439\u043b\u044b \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439 \u0434\u043b\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430\n    \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u0437\u0430\u043d\u043e\u0432\u043e.\n    <br>\n    <br>\n    \u041c\u043e\u0436\u043d\u043e \u044f\u0432\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0438\u043c\u0435\u043d\u0430 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c:\n\n    ```python\n    page.image_group.recut([\"desktop\", \"mobile\"])\n    ```\n\n2. \u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c management-\u043a\u043e\u043c\u0430\u043d\u0434\u0443 `recreate_variations`:\n\n    ```shell\n    python3 manage.py recreate_variations app.page \\\n            --field image\n            --variations desktop mobile\n    ```\n\n    \u042d\u0442\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u043e\u0432 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438.\n\n#### \u0412\u0435\u0440\u0441\u0438\u0438 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439\n\n\u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c, \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043d\u0443\u0436\u043d\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0437\u0438\u0442\u044c \u0432 \u0442\u0440\u0435\u0445\n\u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430\u0445: `desktop`, `tablet` \u0438 `mobile`. \u0415\u0441\u043b\u0438 \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c\n\u0434\u0438\u0441\u043f\u043b\u0435\u0438 Retina, \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0435\u0449\u0451 \u0442\u0440\u0438 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u0440\u0430\u0437\u043c\u0435\u0440\u0430 `2x`.\n\u0415\u0441\u043b\u0438 \u043c\u044b \u0442\u0430\u043a\u0436\u0435 \u0445\u043e\u0442\u0438\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0444\u043e\u0440\u043c\u0430\u0442 `WebP` (\u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0432 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442\n\u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0442\u043d\u043e\u0439 \u0441\u043e\u0432\u043c\u0435\u0441\u0442\u0438\u043c\u043e\u0441\u0442\u0438), \u0442\u043e \u043e\u0431\u0449\u0435\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439 \u0434\u043e\u0441\u0442\u0438\u0433\u0430\u0435\u0442 **12**.\n\n\u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 Retina-\u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438 \u043e\u0442\u043b\u0438\u0447\u0430\u044e\u0442\u0441\u044f \u043e\u0442 \u043e\u0431\u044b\u0447\u043d\u044b\u0445 \u0442\u043e\u043b\u044c\u043a\u043e \u0443\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u043d\u044b\u043c\n\u043d\u0430 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u044b\u0439 \u043a\u043e\u044d\u0444\u0444\u0438\u0446\u0438\u0435\u043d\u0442 \u0440\u0430\u0437\u043c\u0435\u0440\u043e\u043c, \u0430 `WebP`-\u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438 \u2014 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u043c\n\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 `format = \"webp\"`, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u044d\u0442\u0438 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438\n\u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438. \u042d\u0442\u043e \u0438 \u0435\u0441\u0442\u044c \u0432\u0435\u0440\u0441\u0438\u0438 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438.\n\n\u041f\u0435\u0440\u0435\u0447\u0435\u043d\u044c \u0432\u0435\u0440\u0441\u0438\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0443\u0436\u043d\u043e \u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c, \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0435\n\u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438 `versions`. \u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f:\n`webp`, `2x`, `3x`, `4x`.\n\n```python\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\nfrom paper_uploads.models import *\n\n\nclass Page(models.Model):\n    image = ImageField(\n        _(\"image\"),\n        blank=True,\n        variations=dict(\n            desktop=dict(\n                # ...\n                versions={\"webp\", \"2x\", \"3x\"}\n            )\n        )\n    )\n```\n\n\u041f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u0432\u044b\u0448\u0435 \u043a\u043e\u0434 \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438:\n\n-   `desktop` - \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u0430\u044f \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u044f\n-   `desktop_webp` - `WebP`-\u0432\u0435\u0440\u0441\u0438\u044f \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u043e\u0439 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438\n-   `desktop_2x` - Retina 2x\n-   `desktop_webp_2x` - `WebP`-\u0432\u0435\u0440\u0441\u0438\u044f Retina 2x\n-   `desktop_3x` - Retina 3x\n-   `desktop_webp_3x` - `WebP`-\u0432\u0435\u0440\u0441\u0438\u044f Retina 3x\n\n**NOTE**: Retina-\u0441\u0443\u0444\u0444\u0438\u043a\u0441 \u0432\u0441\u0435\u0433\u0434\u0430 \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043f\u043e\u0441\u043b\u0435 \u0441\u0443\u0444\u0444\u0438\u043a\u0441\u0430 `webp`, \u0435\u0441\u043b\u0438\n\u043e\u043d \u0435\u0441\u0442\u044c.\n\n\u0415\u0441\u043b\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043a\u0430\u043a\u0438\u0435-\u0442\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439\n\u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438, \u0442\u043e \u043f\u0440\u0438\u0434\u0451\u0442\u0441\u044f \u043e\u0431\u044a\u044f\u0432\u043b\u044f\u0442\u044c \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u044e \u044f\u0432\u043d\u043e:\n\n```python\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\nfrom paper_uploads.models import *\n\n\nclass Page(models.Model):\n    image = ImageField(\n        _('image'),\n        blank=True,\n        variations=dict(\n            desktop=dict(\n                size=(800, 600),\n                versions={'webp', '2x', '3x'}\n            ),\n            desktop_2x=dict(\n                size=(1600, 1200),\n                jpeg=dict(\n                    quality=72\n                )\n            )\n        )\n    )\n```\n\n#### Redis Queue\n\n\u041f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439 \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u0438\u043c\u0430\u0442\u044c\n\u0437\u043d\u0430\u0447\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f. \u042d\u0442\u0443 \u0440\u0430\u0431\u043e\u0442\u0443 \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e\n[django-rq][django-rq]:\n\n```shell\npip install django-rq\n```\n\n```python\n# settings.py\nPAPER_UPLOADS = {\n    \"RQ_ENABLED\": True,\n    \"RQ_QUEUE_NAME\": \"default\",\n    # ...\n}\n```\n\n\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439, \u0432 \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u043f\u043e\u0434 \u0438\u043c\u0435\u043d\u0435\u043c `default` \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c\u0441\u044f\n\u0437\u0430\u0434\u0430\u0447\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438.\n\n## SVGFileField\n\n\u041f\u043e\u043b\u0435 `SVGFileField` \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043e \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 SVG-\u0444\u0430\u0439\u043b\u043e\u0432. \u041e\u043d\u043e \u0438\u0434\u0435\u043d\u0442\u0438\u0447\u043d\u043e `FileField`,\n\u043d\u043e \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u0430\u044f \u0441 \u043d\u0438\u043c \u043c\u043e\u0434\u0435\u043b\u044c `UploadedSVGFile` \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439:\n\n| \u041f\u043e\u043b\u0435        | \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435                                                                     |\n|-------------|------------------------------------------------------------------------------|\n| width       | \u0428\u0438\u0440\u0438\u043d\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 `Decimal`. \u041c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432\u0435\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c \u0447\u0438\u0441\u043b\u043e\u043c.      |\n| height      | \u0412\u044b\u0441\u043e\u0442\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 `Decimal`. \u041c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0432\u0435\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c \u0447\u0438\u0441\u043b\u043e\u043c.      |\n| title       | \u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u043e\u0436\u043d\u043e \u0432\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432 \u0430\u0442\u0440\u0438\u0431\u0443\u0442 `title` \u0442\u044d\u0433\u0430 `<img>`. |\n| description | \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043c\u043e\u0436\u043d\u043e \u0432\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432 \u0430\u0442\u0440\u0438\u0431\u0443\u0442 `alt` \u0442\u044d\u0433\u0430 `<img>`.   |\n\n```python\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\nfrom paper_uploads.models import SVGFileField\n\n\nclass Page(models.Model):\n    svg = SVGFileField(\n        _(\"svg\"),\n        blank=True\n    )\n```\n\n## \u041a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438\n\n\u041a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044f \u2014 \u044d\u0442\u043e \u043c\u043e\u0434\u0435\u043b\u044c, \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u0443\u044e\u0449\u0430\u044f \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u044b \u0434\u0440\u0443\u0433\u0438\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439 (\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438).\n\u0412 \u0447\u0430\u0441\u0442\u043d\u043e\u0441\u0442\u0438, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0444\u043e\u0442\u043e\u0433\u0430\u043b\u0435\u0440\u0435\u044e \u0438\u043b\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u0444\u0430\u0439\u043b\u043e\u0432.\n\n\u0414\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043e\u0431\u044a\u044f\u0432\u0438\u0442\u044c \u043a\u043b\u0430\u0441\u0441, \u0443\u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043e\u0442 `Collection`\n\u0438 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043c\u043e\u0434\u0435\u043b\u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0432\u0445\u043e\u0434\u0438\u0442\u044c \u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u0441\u0435\u0432\u0434\u043e-\u043f\u043e\u043b\u044f\n`CollectionItem`:\n\n```python\nfrom django.db import models\nfrom paper_uploads.models import *\n\n\n# Collection model\nclass PageFiles(Collection):\n    svg = CollectionItem(SVGItem)\n    image = CollectionItem(ImageItem)\n    file = CollectionItem(FileItem)\n\n\nclass Page(models.Model):\n    files = CollectionField(PageFiles)\n```\n\n\u041a\u043b\u0430\u0441\u0441 `Collection` \u043e\u0431\u043b\u0430\u0434\u0430\u0435\u0442 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u044b\u043c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e\u043c: _\u043b\u044e\u0431\u043e\u0439 \u0434\u043e\u0447\u0435\u0440\u043d\u0438\u0439 \u043a\u043b\u0430\u0441\u0441, \u0443\u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u043d\u044b\u0439\n\u043e\u0442 `Collection`, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f proxy-\u043c\u043e\u0434\u0435\u043b\u044c\u044e \u0434\u043b\u044f `Collection`_.\n\n\u0412 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0435 \u0441\u043b\u0443\u0447\u0430\u0435\u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u043e\u0442\u043b\u0438\u0447\u0430\u044e\u0442\u0441\u044f \u0434\u0440\u0443\u0433 \u043e\u0442 \u0434\u0440\u0443\u0433\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u0431\u043e\u0440\u043e\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432,\n\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0433\u0443\u0442 \u0432\u0445\u043e\u0434\u0438\u0442\u044c \u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044e. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 proxy-\u043c\u043e\u0434\u0435\u043b\u0435\u0439 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435\n\u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u0430 \u043e\u0434\u0438\u043d\u0430\u043a\u043e\u0432\u044b\u0445 \u0442\u0430\u0431\u043b\u0438\u0446 \u0432 \u0411\u0414.\n\n\u0415\u0441\u043b\u0438 \u0436\u0435 \u0434\u043b\u044f \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u0430\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u0430 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0435\u0441\u043b\u0438 \u0432\u044b \u0440\u0435\u0448\u0438\u043b\u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c\n\u0432 \u043d\u0435\u0451 \u043d\u043e\u0432\u043e\u0435 \u043f\u043e\u043b\u0435), \u0442\u043e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u044f\u0432\u043d\u043e \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e `Meta.proxy` \u0432 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 `False`:\n\n```python\nfrom django.db import models\nfrom paper_uploads.models import *\n\n\nclass CustomCollection(Collection):\n    name = models.CharField(\"name\", max_length=128, blank=True)\n\n    file = CollectionItem(FileItem)\n\n    class Meta:\n        proxy = False\n```\n\n### \u042d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438\n\n\u041f\u0441\u0435\u0432\u0434\u043e-\u043f\u043e\u043b\u0435 `CollectionItem` \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u0443\u0435\u0442 \u043c\u043e\u0434\u0435\u043b\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u043f\u043e\u0434 \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u043c \u0438\u043c\u0435\u043d\u0435\u043c.\n\n```python\nfrom paper_uploads.models import *\n\n\nclass PageFiles(Collection):\n    svg = CollectionItem(SVGItem)\n    image = CollectionItem(ImageItem)\n    file = CollectionItem(FileItem)\n```\n\n\u0412 \u043f\u0440\u0438\u0432\u0435\u0434\u0451\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435, \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044f `PageFiles` \u043c\u043e\u0436\u0435\u0442 \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0442\u0440\u0435\u0445 \u043c\u043e\u0434\u0435\u043b\u0435\u0439:\n`SVGItem`, `ImageItem` \u0438 `FileItem`.\n\n\u041f\u043e\u0440\u044f\u0434\u043e\u043a \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u0432\u0430\u0436\u0435\u043d: \u043f\u0435\u0440\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u043c\u043e\u0434\u0435\u043b\u0438, \u0447\u0435\u0439 \u043c\u0435\u0442\u043e\u0434 `accept()`\n\u0432\u0435\u0440\u043d\u0435\u0442 `True`, \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442 \u043c\u043e\u0434\u0435\u043b\u044c \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430. \u041f\u043e \u044d\u0442\u043e\u0439 \u043f\u0440\u0438\u0447\u0438\u043d\u0435 _`FileItem` \u0434\u043e\u043b\u0436\u0435\u043d\n\u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u043c_, \u0442.\u043a. \u043e\u043d \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043b\u044e\u0431\u044b\u0435 \u0444\u0430\u0439\u043b\u044b.\n\n\u041f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0451\u043d\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0430 \u043c\u043e\u0436\u043d\u043e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0435\u0442\u043e\u0434\u0430 `get_items()`:\n\n```python\nfor item in page.files.get_items(\"image\"):\n    # ...\n```\n\n---\n\n\u0412 \u0441\u043e\u0441\u0442\u0430\u0432 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0432\u0445\u043e\u0434\u044f\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432:\n\n-   `ImageItem`<br>\n    \u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0441 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u043d\u0430\u0440\u0435\u0437\u043a\u0438 \u043d\u0430 [\u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438](#\u0412\u0430\u0440\u0438\u0430\u0446\u0438\u0438).\n    \u0414\u043e\u043f\u0443\u0441\u043a\u044e\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435 \u0444\u0430\u0439\u043b\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e [Pillow](https://pillow.readthedocs.io/en/stable/).\n\n-   `SVGItem`<br>\n    \u0414\u043b\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f SVG \u0438\u043a\u043e\u043d\u043e\u043a.\n\n-   `FileItem`<br>\n    \u041c\u043e\u0436\u0435\u0442 \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043b\u044e\u0431\u043e\u0439 \u0444\u0430\u0439\u043b.\n\n#### Storage \u0438 \u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432\n\n\u041f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0442\u043e\u0442 \u0436\u0435 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430,\n\u0447\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u044f\u043c\u0438 `FileField` \u0438 `ImageField`.\n\n\u041a\u0430\u0442\u0430\u043b\u043e\u0433\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 \u0434\u043b\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0439 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445\n`COLLECTION_FILES_UPLOAD_TO` \u0438 `COLLECTION_IMAGES_UPLOAD_TO`:\n\n```python\n# settings.py\n\nPAPER_UPLOADS = {\n    # ...\n    \"COLLECTION_FILES_UPLOAD_TO\": \"collections/files/%Y/%m/%d\",\n    \"COLLECTION_IMAGES_UPLOAD_TO\": \"collections/images/%Y/%m/%d\",\n    # ...\n}\n```\n\n\u0414\u043b\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0432\u0437\u044f\u0442\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 \u0438 \u043a\u0430\u0442\u0430\u043b\u043e\u0433 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438  \n\u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0432 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0435 `options` \u043f\u0441\u0435\u0432\u0434\u043e-\u043f\u043e\u043b\u044f `CollectionItem`:\n\n```python\nfrom django.core.files.storage import FileSystemStorage\nfrom paper_uploads.models import *\n\n\nclass PageFiles(Collection):\n    image = CollectionItem(ImageItem, options={\n        \"storage\": FileSystemStorage(location=\"uploads/\"),\n        \"upload_to\": \"gallery\",\n    })\n```\n\n\u041a\u0430\u043a \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u0441 `FileField` \u0438 `ImageField`, \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435\u043c `upload_to` \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0441\u0442\u0443\u043f\u0430\u0442\u044c\n\u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442.\n\n\u0415\u0441\u043b\u0438 \u0432\u0430\u043c \u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0430 \u0438\u043b\u0438 \u0438\u043c\u0435\u043d\u0438 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430,\n\u0441\u043e\u0437\u0434\u0430\u0439\u0442\u0435 proxy-\u043c\u043e\u0434\u0435\u043b\u044c \u0438 \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u043b\u0438\u0442\u0435 \u043c\u0435\u0442\u043e\u0434 `generate_filename()`:\n\n```python\nimport os\nimport datetime\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\nfrom paper_uploads.models import *\n\n\nclass ProxyImageItem(ImageItem):\n    class Meta:\n        proxy = True\n\n    def generate_filename(self, filename: str) -> str:\n        _, ext = os.path.splitext(filename)\n        filename = \"gallery/image-%Y-%m-%d_%H%M%S{}\".format(ext)\n        filename = datetime.datetime.now().strftime(filename)\n\n        storage = self.get_file_storage()\n        return storage.generate_filename(filename)\n\n\nclass PageGallery(Collection):\n    image = CollectionItem(ProxyImageItem)\n\n\nclass Page(models.Model):\n    gallery = CollectionField(\n        PageGallery,\n        verbose_name=_(\"gallery\")\n    )\n```\n\n#### <a id=\"\u0412\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u044b-2\" />\u0412\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u044b\n\n\u041d\u0430 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c\u044b\u0435 \u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u0444\u0430\u0439\u043b\u044b \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u043b\u043e\u0436\u0438\u0442\u044c \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0432\u0430\u043b\u0438\u0434\u0430\u0442\u043e\u0440\u043e\u0432:\n\n```python\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\nfrom paper_uploads.models import *\nfrom paper_uploads.validators import ImageMaxSizeValidator, ImageMinSizeValidator\n\n\nclass PageGallery(Collection):\n    image = CollectionItem(ImageItem, validators=[\n        ImageMinSizeValidator(640, 480),\n        ImageMaxSizeValidator(4000, 3000)\n    ])\n\n\nclass Page(models.Model):\n    gallery = CollectionField(\n        PageGallery,\n        verbose_name=_(\"gallery\")\n    )\n```\n\n#### \u041f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438\n\n\u042d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0439 \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u043f\u043e\u0447\u0442\u0438 \u0442\u0430\u043a\u0436\u0435, \u043a\u0430\u043a `UploadedFile` \u0438 `UploadedImage`.\n\u0420\u0430\u0437\u043d\u0438\u0446\u0430 \u043b\u0438\u0448\u044c \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e \u0432\u043c\u0435\u0441\u0442\u043e \u0432\u044b\u0437\u043e\u0432\u0430 \u043c\u0435\u0442\u043e\u0434\u0430 `set_owner_field()` \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\n\u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 `attach_to()` \u0434\u043b\u044f \u043f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u043a \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438:\n\n```python\nfrom paper_uploads.models import *\n\ncollection = PageGallery.objects.create()\n\nitem = ImageItem()\nitem.attach_to(collection)\nitem.attach(\"/tmp/image.jpg\")\nitem.save()\n\npage = Page.objects.create(\n    gallery=collection\n)\n```\n\n#### <a id=\"\u0412\u0430\u0440\u0438\u0430\u0446\u0438\u0438-2\" />\u0412\u0430\u0440\u0438\u0430\u0446\u0438\u0438\n\n\u0412\u0430\u0440\u0438\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043e\u0434\u043d\u0438\u043c \u0438\u0437 \u0434\u0432\u0443\u0445 \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432:\n\n1. \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 `options` \u043f\u0441\u0435\u0432\u0434\u043e-\u043f\u043e\u043b\u044f `CollectionItem`:\n\n    ```python\n    from paper_uploads.models import *\n\n    class PageGallery(Collection):\n        image = CollectionItem(ImageItem, options={\n            \"variations\": dict(\n                mobile=dict(\n                    size=(640, 0),\n                    clip=False\n                )\n            )\n        })\n    ```\n\n2. \u0410\u0442\u0440\u0438\u0431\u0443\u0442 \u043a\u043b\u0430\u0441\u0441\u0430 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 `VARIATIONS`:\n\n    ```python\n    from paper_uploads.models import *\n\n    class PageGallery(Collection):\n        VARIATIONS = dict(\n            mobile=dict(\n                size=(640, 0),\n                clip=False\n            )\n        )\n\n        image = CollectionItem(ImageItem)\n    ```\n\n### HTML Template Example\n\n```html\n{% if page.gallery %}\n<div class=\"gallery\">\n    {% for item in page.gallery %} {% if item.type == \"image\" %}\n    <div class=\"item item--{{ item.type }}\">\n        <img\n            src=\"{{ item.url }}\"\n            width=\"{{ item.width }}\"\n            height=\"{{ item.height }}\"\n            title=\"{{ item.title }}\"\n            alt=\"{{ item.description }}\"\n        />\n    </div>\n    {% elif item.type == \"file\" %}}\n    <div class=\"item item--{{ item.type }}\">\n        <a href=\"{{ item.url }}\" download> Download file \"{{ item.display_name }}\" ({{ item.size|filesizeformat }}) </a>\n    </div>\n    {% endif %}} {% endfor %}\n</div>\n{% endif %}\n```\n\n### `ImageCollection`\n\n\u0414\u043b\u044f \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0439 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 `ImageCollection`,\n\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043b\u0443\u0447\u0430\u044f \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \n\u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438. \u0412\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0441\u0440\u0430\u0437\u0443, \u0447\u0435\u0440\u0435\u0437 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u043a\u043b\u0430\u0441\u0441\u0430:  \n\n```python\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\nfrom paper_uploads.models import *\nfrom paper_uploads.validators import ImageMaxSizeValidator, ImageMinSizeValidator\n\n\nclass PageGallery(ImageCollection):\n    UPLOAD_TO = \"page/gallery\",\n    VARIATIONS = dict(\n        gallery=dict(\n            size=(1600, 900),\n        )\n    )\n    VALIDATORS = [\n        ImageMinSizeValidator(640, 480),\n        ImageMaxSizeValidator(4000, 3000)\n    ]\n\n\nclass Page(models.Model):\n    gallery = CollectionField(\n        PageGallery,\n        verbose_name=_(\"gallery\")\n    )\n```\n\n## Management \u043a\u043e\u043c\u0430\u043d\u0434\u044b\n\n### check_uploads\n\n\u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0441\u043d\u0443\u044e \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0445 \u0444\u0430\u0439\u043b\u043e\u0432\n\u0438 \u0432\u044b\u0432\u043e\u0434\u0438\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442.\n\n\u0421\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u043c\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432:\n\n-   \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442\n-   \u043a\u043b\u0430\u0441\u0441 \u043c\u043e\u0434\u0435\u043b\u0438 \u0432\u043b\u0430\u0434\u0435\u043b\u044c\u0446\u0430 (\u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u0432 `owner_app_label` \u0438 `owner_model_name`) \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442\n-   \u0432 \u043a\u043b\u0430\u0441\u0441\u0435 \u043c\u043e\u0434\u0435\u043b\u0438 \u0432\u043b\u0430\u0434\u0435\u043b\u044c\u0446\u0430 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u043f\u043e\u043b\u0435, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0435 \u0432 `owner_fieldname`\n-   \u0443 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0439 \u0443\u043a\u0430\u0437\u0430\u043d\u043e \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0432 \u043f\u043e\u043b\u0435 `type`\n-   \u043c\u043e\u0434\u0435\u043b\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u043c\u043e\u0434\u0435\u043b\u0438, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0439 \u0432 \u043a\u043b\u0430\u0441\u0441\u0435 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438\n\n```shell\npython3 manage.py check_uploads\n```\n\n### clean_uploads\n\n\u041d\u0430\u0445\u043e\u0434\u0438\u0442 \u043c\u0443\u0441\u043e\u0440\u043d\u044b\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u0432 \u0411\u0414 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0442\u0435, \u0443 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0435\u0442 \u0432\u043b\u0430\u0434\u0435\u043b\u044c\u0446\u0430)\n\u0438 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 \u0438\u0445 \u0443\u0434\u0430\u043b\u0438\u0442\u044c.\n\n\u0412\u043b\u0430\u0434\u0435\u043b\u0435\u0446 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u043c\u043e\u043c\u0435\u043d\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b\n\u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430. \u042d\u0442\u043e \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043f\u043e\u0437\u0436\u0435 \u0444\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u0430\n\u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440. \u0412 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043a\u0435 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043c\u0435\u0436\u0434\u0443 \u044d\u0442\u0438\u043c\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u044f\u043c\u0438 \u0444\u0430\u0439\u043b \u0431\u0443\u0434\u0435\u0442 \u044f\u0432\u043b\u044f\u0442\u044c\u0441\u044f \"\u0441\u0438\u0440\u043e\u0442\u043e\u0439\".\n\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0442\u0430\u043a\u0438\u0435 \u0444\u0430\u0439\u043b\u044b \u043d\u0435 \u0443\u0434\u0430\u043b\u044f\u043b\u0438\u0441\u044c, \u043a\u043e\u043c\u0430\u043d\u0434\u0430 `clean_uploads` \u0438\u0433\u043d\u043e\u0440\u0438\u0440\u0443\u0435\u0442\n\u0444\u0430\u0439\u043b\u044b, \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0435 \u0432 \u0442\u0435\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0433\u043e \u0447\u0430\u0441\u0430.\n\n```shell\npython3 manage.py clean_uploads\n```\n\n### remove_empty_collections\n\n\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u043e\u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0439, \u0432 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0435\u0442 \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430.\n\n```shell\npython3 manage.py clean_uploads\n```\n\n### create_missing_variations\n\n\u0421\u043e\u0437\u0434\u0430\u0451\u0442 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435 \u0444\u0430\u0439\u043b\u044b \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439.\n\n```shell\npython3 manage.py create_missing_variations\n```\n\n### recreate_variations\n\n\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435/\u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u044c \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u043e\u0432 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438.\n\n```shell\n# for collections\npython3 manage.py recreate_variations app.Photos image\n\n# for regular models\npython3 manage.py recreate_variations app.Page image\n```\n\n\u0412 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u0432\u044b\u0437\u043e\u0432\u0430 \u044d\u0442\u0438\u0445 \u043a\u043e\u043c\u0430\u043d\u0434, \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0435\u0434\u043b\u043e\u0436\u0435\u043d\u043e \u0432\u044b\u0431\u0440\u0430\u0442\u044c \n\u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c.\n\n\u041c\u043e\u0436\u043d\u043e \u0441\u0440\u0430\u0437\u0443 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043d\u0443\u0436\u043d\u044b\u0435 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438:\n\n```shell\npython3 manage.py recreate_variations app.Page image -- desktop mobile\n```\n\n### remove_variations\n\n\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439.\n\u0423\u0434\u0430\u043b\u044f\u044e\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0444\u0430\u0439\u043b\u044b \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439.\n\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c `recreate_variations`.\n\n```shell\n# for collections\npython3 manage.py remove_variations app.Photos image\n\n# for regular models\npython3 manage.py remove_variations app.Page image\n```\n\n## Settings\n\n\u0412\u0441\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u0441\u043b\u043e\u0432\u0430\u0440\u0435 `PAPER_UPLOADS`.\n\n```python\nPAPER_UPLOADS = {\n    \"STORAGE\": \"django.core.files.storage.FileSystemStorage\",\n    \"STORAGE_OPTIONS\": {},\n    \"FILES_UPLOAD_TO\": \"files/%Y/%m/%d\",\n    \"IMAGES_UPLOAD_TO\": \"images/%Y/%m/%d\",\n    \"COLLECTION_FILES_UPLOAD_TO\": \"collections/files/%Y/%m/%d\",\n    \"COLLECTION_IMAGES_UPLOAD_TO\": \"collections/images/%Y/%m/%d\",\n\n    \"RQ_ENABLED\": True,\n    \"RQ_QUEUE_NAME\": \"default\",\n\n    \"VARIATION_DEFAULTS\": {\n        \"jpeg\": dict(\n            quality=80,\n            progressive=True,\n        ),\n        \"webp\": dict(\n            quality=75,\n        )\n    }\n}\n```\n\n### `STORAGE`\n\n\u041f\u0443\u0442\u044c \u043a \u043a\u043b\u0430\u0441\u0441\u0443 [\u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430 Django](https://docs.djangoproject.com/en/2.2/ref/files/storage/).\n\n\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: `django.core.files.storage.FileSystemStorage`\n\n### `STORAGE_OPTIONS`\n\n\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430.\n\n\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: `{}`\n\n### `FILES_UPLOAD_TO`\n\n\u041f\u0443\u0442\u044c \u043a \u043f\u0430\u043f\u043a\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044e\u0442\u0441\u044f \u0444\u0430\u0439\u043b\u044b \u0438\u0437 FileField.\n\u041c\u043e\u0436\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0434\u043b\u044f \u0434\u0430\u0442\u044b \u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 (\u0441\u043c. [upload_to](https://docs.djangoproject.com/en/2.2/ref/models/fields/#django.db.models.FileField.upload_to)).\n\n\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: `files/%Y/%m/%d`\n\n### `IMAGES_UPLOAD_TO`\n\n\u041f\u0443\u0442\u044c \u043a \u043f\u0430\u043f\u043a\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044e\u0442\u0441\u044f \u0444\u0430\u0439\u043b\u044b \u0438\u0437 ImageField.\n\n\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: `images/%Y/%m/%d`\n\n### `COLLECTION_FILES_UPLOAD_TO`\n\n\u041f\u0443\u0442\u044c \u043a \u043f\u0430\u043f\u043a\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044e\u0442\u0441\u044f \u0444\u0430\u0439\u043b\u044b \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0439.\n\n\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: `collections/files/%Y/%m/%d`\n\n### `COLLECTION_IMAGES_UPLOAD_TO`\n\n\u041f\u0443\u0442\u044c \u043a \u043f\u0430\u043f\u043a\u0435, \u0432 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u044e\u0442\u0441\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0439.\n\n\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: `collections/images/%Y/%m/%d`\n\n### `COLLECTION_ITEM_PREVIEW_WIDTH`, `COLLECTION_ITEM_PREVIEW_HEIGHT`\n\n\u0420\u0430\u0437\u043c\u0435\u0440\u044b \u043f\u0440\u0435\u0432\u044c\u044e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0439 \u0432 \u0430\u0434\u043c\u0438\u043d\u043a\u0435.\n\n\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: `180` x `135`\n\n### `COLLECTION_IMAGE_ITEM_PREVIEW_VARIATIONS`\n\n\u0412\u0430\u0440\u0438\u0430\u0446\u0438\u0438, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0435 \u043a \u043a\u0430\u0436\u0434\u043e\u043c\u0443 \u043a\u043b\u0430\u0441\u0441\u0443 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0439\n\u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043f\u0440\u0435\u0432\u044c\u044e \u0432 \u0430\u0434\u043c\u0438\u043d\u043a\u0435. \u0420\u0430\u0437\u043c\u0435\u0440\u044b \u0444\u0430\u0439\u043b\u043e\u0432 \u0434\u043e\u043b\u0436\u043d\u044b\n\u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c \u0441 `COLLECTION_ITEM_PREVIEW_WIDTH` \u0438\n`COLLECTION_ITEM_PREVIEW_HEIGHT`.\n\n### `RQ_ENABLED`\n\n\u0412\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043d\u0430\u0440\u0435\u0437\u043a\u0443 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a \u043d\u0430 \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438 \u0447\u0435\u0440\u0435\u0437 \u043e\u0442\u043b\u043e\u0436\u0435\u043d\u043d\u044b\u0435 \u0437\u0430\u0434\u0430\u0447\u0438.\n\u0422\u0440\u0435\u0431\u0443\u0435\u0442 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u043f\u0430\u043a\u0435\u0442\u0430 [django-rq][django-rq].\n\n\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: `False`\n\n### `RQ_QUEUE_NAME`\n\n\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u043e\u0447\u0435\u0440\u0435\u0434\u0438, \u0432 \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043f\u043e\u043c\u0435\u0449\u0430\u044e\u0442\u0441\u044f \u0437\u0430\u0434\u0430\u0447\u0438 \u043f\u043e \u043d\u0430\u0440\u0435\u0437\u043a\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043e\u043a.\n\n\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: `default`\n\n### `VARIATION_DEFAULTS`\n\n\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0439 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e.\n\n\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 \u0432 \u044d\u0442\u043e\u043c \u0441\u043b\u043e\u0432\u0430\u0440\u0435, \u0431\u0443\u0434\u0443\u0442 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u044b \u043a \u043a\u0430\u0436\u0434\u043e\u0439\n\u0432\u0430\u0440\u0438\u0430\u0446\u0438\u0438 &mdash; \u0435\u0441\u043b\u0438 \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0430\u0440\u0438\u0430\u0446\u0438\u044f \u0438\u0445 \u044f\u0432\u043d\u043e \u043d\u0435 \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442.\n\n\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e: `None`\n\n## Development and Testing\n\nAfter cloning the Git repository, you should install this\nin a virtualenv and set up for development:\n\n```shell script\nvirtualenv .venv\nsource .venv/bin/activate\npip install -r ./requirements.txt\npre-commit install\n```\n\nInstall `npm` dependencies and build static files:\n\n```shell script\nnpm ci\nnpx webpack\n```\n\n[paper-admin]: https://github.com/dldevinc/paper-admin\n[variations]: https://github.com/dldevinc/variations\n[pilkit]: https://github.com/matthewwithanm/pilkit\n[django-storages]: https://github.com/jschneier/django-storages\n[django-rq]: https://github.com/rq/django-rq\n",
    "bugtrack_url": null,
    "license": "BSD license",
    "summary": "Asynchronous file upload for Django",
    "version": "0.18.7",
    "project_urls": {
        "Homepage": "https://github.com/dldevinc/paper-uploads"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "a1e2fd73f89f6a4a1cfa0bbbd6d020377969f8353c09287307e151c9fd635630",
                "md5": "f7f59c84b111565b8f1ef00c1e68ade1",
                "sha256": "b72ba02734f097124b8728229dfe9d6858eea57c5e3c841829884b6fd33efd02"
            },
            "downloads": -1,
            "filename": "paper_uploads-0.18.7-py2.py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "f7f59c84b111565b8f1ef00c1e68ade1",
            "packagetype": "bdist_wheel",
            "python_version": "py2.py3",
            "requires_python": ">=3.9",
            "size": 333906,
            "upload_time": "2024-03-14T10:29:10",
            "upload_time_iso_8601": "2024-03-14T10:29:10.646686Z",
            "url": "https://files.pythonhosted.org/packages/a1/e2/fd73f89f6a4a1cfa0bbbd6d020377969f8353c09287307e151c9fd635630/paper_uploads-0.18.7-py2.py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "90b7e4bea4857ab9900c432a44b6f24b7f446feb9aef8f819ddec4f92aa14f1e",
                "md5": "a22889f8e6e09733584388099e0e08f6",
                "sha256": "d8064f52924c03d280bfbbfba9a59504fa26d2ae10dd6e9711a63a6bcbc8a048"
            },
            "downloads": -1,
            "filename": "paper-uploads-0.18.7.tar.gz",
            "has_sig": false,
            "md5_digest": "a22889f8e6e09733584388099e0e08f6",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 289088,
            "upload_time": "2024-03-14T10:29:15",
            "upload_time_iso_8601": "2024-03-14T10:29:15.231587Z",
            "url": "https://files.pythonhosted.org/packages/90/b7/e4bea4857ab9900c432a44b6f24b7f446feb9aef8f819ddec4f92aa14f1e/paper-uploads-0.18.7.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-14 10:29:15",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "dldevinc",
    "github_project": "paper-uploads",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "tox": true,
    "lcname": "paper-uploads"
}
        
Elapsed time: 0.19788s