Asynchronous file upload for Django


License
BSD-3-Clause
Install
pip install paper-uploads==0.18.7

Documentation

paper-uploads

Асинхронная Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Ρ„Π°ΠΉΠ»ΠΎΠ² для административного интСрфСйса Django.

PyPI Build Status Software license

Requirements

Features

  • ΠšΠ°ΠΆΠ΄Ρ‹ΠΉ Ρ„Π°ΠΉΠ» прСдставлСн своСй модСлью. Π­Ρ‚ΠΎ позволяСт Ρ…Ρ€Π°Π½ΠΈΡ‚ΡŒ вмСстС с Ρ„Π°ΠΉΠ»ΠΎΠΌ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ Π΄Π°Π½Π½Ρ‹Π΅. НапримСр, alt для ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ.
  • Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Ρ„Π°ΠΉΠ»ΠΎΠ² происходит асинхронно ΠΈ начинаСтся сразу ΠΏΡ€ΠΈ Π²Ρ‹Π±ΠΎΡ€Π΅ Ρ„Π°ΠΉΠ»Π° Π² интСрфСйсС администратора.
  • Поля ΠΌΠΎΠ΄Π΅Π»ΠΈ, прСдоставляСмыС Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΎΠΉ paper-uploads, ΡΠ²Π»ΡΡŽΡ‚ΡΡ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄Π½Ρ‹ΠΌΠΈ ΠΎΡ‚ OneToOneField ΠΈ Π½Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ <input type="file">. Благодаря этому, ΠΏΡ€ΠΈ ΠΎΡˆΠΈΠ±ΠΊΠ°Ρ… Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΠΈ Ρ„ΠΎΡ€ΠΌΡ‹ ΠΏΡ€ΠΈΠΊΡ€Π΅ΠΏΠ»Π΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ Π½Π΅ ΡΠ±Ρ€Π°ΡΡ‹Π²Π°ΡŽΡ‚ΡΡ.
  • Π—Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½Ρ‹Π΅ ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΊΠΈ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°Ρ€Π΅Π·Π°Ρ‚ΡŒ Π½Π° мноТСство Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΉ. КаТдая вариация Π³ΠΈΠ±ΠΊΠΎ настраиваСтся. МоТно ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Ρ€Π°Π·ΠΌΠ΅Ρ€Ρ‹, качСство сТатия, Ρ„ΠΎΡ€ΠΌΠ°Ρ‚, Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ pilkit-процСссоры, распознаваниС Π»ΠΈΡ† ΠΈ ΠΏΡ€ΠΎΡ‡Π΅Π΅.
    Π‘ΠΌ. variations.
  • БовмСстим с django-storages.
  • ΠžΠΏΡ†ΠΈΠΎΠ½Π°Π»ΡŒΠ½Π°Ρ интСграция с django-rq для ΠΎΡ‚Π»ΠΎΠΆΠ΅Π½Π½ΠΎΠΉ Π½Π°Ρ€Π΅Π·ΠΊΠΈ ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΎΠΊ Π½Π° Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΈ.
  • Π’ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ ΡΠΎΠ·Π΄Π°Π²Π°Ρ‚ΡŒ ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΈ Ρ„Π°ΠΉΠ»ΠΎΠ². Π’ частности, Π³Π°Π»Π΅Ρ€Π΅ΠΉ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ с Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒΡŽ сортировки элСмСнтов.

Table of Contents

Installation

Install paper-uploads:

pip install paper-uploads[full]

Add paper_uploads to INSTALLED_APPS in settings.py:

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

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

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

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

ОписаниС

Π’ состав Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ Π²Ρ…ΠΎΠ΄ΠΈΡ‚ Π΄Π²Π° поля β€” FileField ΠΈ ImageField β€” ΠΈ модСль Collection, прСдназначСнная для Π³Ρ€ΡƒΠΏΠΏΠΈΡ€ΠΎΠ²ΠΊΠΈ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ² с Ρ†Π΅Π»ΡŒΡŽ создания, ΠΊ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Ρƒ, Ρ„ΠΎΡ‚ΠΎΠ³Π°Π»Π΅Ρ€Π΅ΠΉ.

Π‘ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π°ΠΌΠΈ использования Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ Π²Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΠΎΠ·Π½Π°ΠΊΠΎΠΌΠΈΡ‚ΡŒΡΡ здСсь.

FileField ΠΈ ImageField

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

Π­Ρ‚ΠΈ поля ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ΡΡ для Ρ‚Π΅Ρ… ΠΆΠ΅ Ρ†Π΅Π»Π΅ΠΉ, Ρ‡Ρ‚ΠΎ ΠΈ ΠΎΠ΄Π½ΠΎΠΈΠΌΡ‘Π½Π½Ρ‹Π΅ стандартныС поля Django β€” для Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Ρ„Π°ΠΉΠ»ΠΎΠ² ΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ β€” Π½ΠΎ ΠΈΠΌΠ΅ΡŽΡ‚ ряд сущСствСнных ΠΎΡ‚Π»ΠΈΡ‡ΠΈΠΉ.

Π“Π»Π°Π²Π½ΠΎΠ΅ ΠΎΡ‚Π»ΠΈΡ‡ΠΈΠ΅ Π·Π°ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ΡΡ Π² Ρ‚ΠΎΠΌ, Ρ‡Ρ‚ΠΎ поля FileField ΠΈ ImageField ΡΠ²Π»ΡΡŽΡ‚ΡΡ ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ΄Π½Ρ‹ΠΌΠΈ ΠΎΡ‚ стандартного OneToOneField. БоотвСтствСнно, Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½Ρ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ прСдставлСны экзСмплярами ΠΏΠΎΠ»Π½ΠΎΡ†Π΅Π½Π½Ρ‹Ρ… ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ.

Поля ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ²

Π€Π°ΠΉΠ»Ρ‹, Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½Ρ‹Π΅ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ ΠΏΠΎΠ»Π΅ΠΉ FileField ΠΈ ImageField, хранятся Π² экзСмплярах ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ UploadedFile ΠΈ UploadedImage соотвСтствСнно.

Π’ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΡ… Ρ‚Π°Π±Π»ΠΈΡ†Π°Ρ… пСрСчислСны ΠΎΠ±Ρ‰ΠΈΠ΅ поля ΠΈ свойства ΠΎΠ±Π΅ΠΈΡ… ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ:

ПолС ОписаниС
resource_name Имя Ρ„Π°ΠΉΠ»Π° Π±Π΅Π· ΠΏΡƒΡ‚ΠΈ, суффикса ΠΈ Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ.
ΠŸΡ€ΠΈΠΌΠ΅Ρ€: report2020.
extension Π Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΠ΅ Ρ„Π°ΠΉΠ»Π° Π² Π½ΠΈΠΆΠ½Π΅ΠΌ рСгистрС Π±Π΅Π· Ρ‚ΠΎΡ‡ΠΊΠΈ.
ΠŸΡ€ΠΈΠΌΠ΅Ρ€: pdf.
size Π Π°Π·ΠΌΠ΅Ρ€ Ρ„Π°ΠΉΠ»Π° Π² Π±Π°ΠΉΡ‚Π°Ρ….
checksum ΠšΠΎΠ½Ρ‚Ρ€ΠΎΠ»ΡŒΠ½Π°Ρ сумма содСрТимого Ρ„Π°ΠΉΠ»Π°.
Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ для отслСТивания ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ Ρ„Π°ΠΉΠ»Π°.
uploaded_at Π”Π°Ρ‚Π° ΠΈ врСмя Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Ρ„Π°ΠΉΠ»Π°.
created_at Π”Π°Ρ‚Π° ΠΈ врСмя создания экзСмпляра ΠΌΠΎΠ΄Π΅Π»ΠΈ.
modified_at Π”Π°Ρ‚Π° ΠΈ врСмя измСнСния экзСмпляра ΠΌΠΎΠ΄Π΅Π»ΠΈ.
Бвойство ОписаниС
name ПолноС имя Ρ„Π°ΠΉΠ»Π°.
ΠŸΡ€ΠΈΠΌΠ΅Ρ€: files/report2020_19sc2Kj.pdf.
url URL-адрСс Ρ„Π°ΠΉΠ»Π°.
ΠŸΡ€ΠΈΠΌΠ΅Ρ€: /media/files/report2020_19sc2Kj.pdf.
path ΠΠ±ΡΠΎΠ»ΡŽΡ‚Π½Ρ‹ΠΉ ΠΏΡƒΡ‚ΡŒ ΠΊ Ρ„Π°ΠΉΠ»Ρƒ.
ΠŸΡ€ΠΈΠΌΠ΅Ρ€: /home/www/django/media/files/report2020_19sc2Kj.pdf.

НиТС пСрСчислСны поля ΠΈ свойства, спСцифичныС для ΠΊΠ°ΠΆΠ΄ΠΎΠΉ ΠΌΠΎΠ΄Π΅Π»ΠΈ.

Π‘ΠΏΠ΅Ρ†ΠΈΡ„ΠΈΡ‡Π½Ρ‹Π΅ поля UploadedFile:

ПолС ОписаниС
display_name Π£Π΄ΠΎΠ±ΠΎΡ‡ΠΈΡ‚Π°Π΅ΠΌΠΎΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ Ρ„Π°ΠΉΠ»Π° для Π²Ρ‹Π²ΠΎΠ΄Π° Π½Π° сайтС.
ЗаполняСтся Π² Π΄ΠΈΠ°Π»ΠΎΠ³ΠΎΠ²ΠΎΠΌ ΠΎΠΊΠ½Π΅ рСдактирования Ρ„Π°ΠΉΠ»Π°.
ΠŸΡ€ΠΈΠΌΠ΅Ρ€: 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 image

Storage

По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ всС поля paper-uploads ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ Π΅Π΄ΠΈΠ½Ρ‹ΠΉ экзСмпляр Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π°, опрСдСляСмый настройками STORAGE ΠΈ STORAGE_OPTIONS:

# settings.py

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

Π’Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ экзСмпляр Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π° для ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠ³ΠΎ поля:

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:

# settings.py

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

Для ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠ³ΠΎ поля ΠΊΠ°Ρ‚Π°Π»ΠΎΠ³ сохранСния ΠΌΠΎΠΆΠ½ΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Π² ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π΅ поля upload_to. ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ ΠΏΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΈΠ²Π°Π΅Ρ‚ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ strftime(), ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ Π±ΡƒΠ΄Π΅Ρ‚ Π·Π°ΠΌΠ΅Π½Π΅Π½ΠΎ Π½Π° Π΄Π°Ρ‚Ρƒ/врСмя Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π° (ΠΈ Π·Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌΡ‹Π΅ Ρ„Π°ΠΉΠ»Ρ‹ Π½Π΅ заполнят ΠΎΠ΄ΠΈΠ½ ΠΊΠ°Ρ‚Π°Π»ΠΎΠ³).

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():

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 - Π·Π°Π΄Π°Π΅Ρ‚ максимально допустимый Ρ€Π°Π·ΠΌΠ΅Ρ€ Ρ„Π°ΠΉΠ»Π°.
    ΠœΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ Ρ€Π°Π·ΠΌΠ΅Ρ€ ΠΌΠΎΠΆΠ½ΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΊΠ°ΠΊ Π² Π²ΠΈΠ΄Π΅ числа (Π² Π±Π°ΠΉΡ‚Π°Ρ…), Ρ‚Π°ΠΊ ΠΈ Π² Π²ΠΈΠ΄Π΅ строки.
    НапримСр: 4 * 10 ** 6, 4mb, 4MB, 4M.
  • ExtensionValidator - Π·Π°Π΄Π°Π΅Ρ‚ допустимыС Ρ€Π°ΡΡˆΠΈΡ€Π΅Π½ΠΈΡ Ρ„Π°ΠΉΠ»ΠΎΠ².
  • MimeTypeValidator - Π·Π°Π΄Π°Π΅Ρ‚ допустимыС MIME-Ρ‚ΠΈΠΏΡ‹ Ρ„Π°ΠΉΠ»ΠΎΠ².
  • ImageMinSizeValidator - устанавливаСт ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ Ρ€Π°Π·ΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌΡ‹Ρ… ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ.
  • ImageMaxSizeValidator - устанавливаСт ΠΌΠ°ΠΊΡΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ Ρ€Π°Π·ΠΌΠ΅Ρ€ Π·Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌΡ‹Ρ… ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ.

ΠŸΡ€ΠΈΠΌΠ΅Ρ€:

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

ΠŸΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠ½Π°Ρ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠ° Ρ„Π°ΠΉΠ»ΠΎΠ²

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.

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 ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ ΠΈΡ… ΠΈΠΌΠ΅Π½Π°:

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

Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Ρ„Π°ΠΉΠ»ΠΎΠ² Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΉ происходит Π² ΠΌΠΎΠΌΠ΅Π½Ρ‚ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ изобраТСния Π½Π° сСрвСр. ΠŸΠΎΡΡ‚ΠΎΠΌΡƒ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠ΅ настроСк Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΉ Π½Π΅ ΠΎΠΊΠ°ΠΆΠ΅Ρ‚ Π½ΠΈΠΊΠ°ΠΊΠΎΠ³ΠΎ эффСкта Π½Π° ΡƒΠΆΠ΅ Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½Ρ‹Π΅ изобраТСния.

Для Ρ‚ΠΎΠ³ΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ Ρ„Π°ΠΉΠ»Ρ‹ для Π½ΠΎΠ²Ρ‹Ρ… Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΉ (Π»ΠΈΠ±ΠΎ ΠΏΠ΅Ρ€Π΅Π·Π°ΠΏΠΈΡΠ°Ρ‚ΡŒ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠ΅ Ρ„Π°ΠΉΠ»Ρ‹ Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΉ) ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΡΡ‚ΡƒΠΏΠΈΡ‚ΡŒ ΠΎΠ΄Π½ΠΈΠΌ ΠΈΠ· Π½ΠΈΠΆΠ΅ описанных способов.

  1. Π’Ρ‹Π·Π²Π°Ρ‚ΡŒ ΠΌΠ΅Ρ‚ΠΎΠ΄ recut():

    page.image.recut()

    ΠŸΡ€ΠΈ Π²Ρ‹Π·ΠΎΠ²Π΅ этого ΠΌΠ΅Ρ‚ΠΎΠ΄Π° всС Ρ„Π°ΠΉΠ»Ρ‹ Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΉ для Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π³ΠΎ экзСмпляра ΡΠΎΠ·Π΄Π°ΡŽΡ‚ΡΡ Π·Π°Π½ΠΎΠ²ΠΎ.

    МоТно явно ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΈΠΌΠ΅Π½Π° Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΉ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΏΠ΅Ρ€Π΅Π·Π°ΠΏΠΈΡΠ°Ρ‚ΡŒ:

    page.image_group.recut(["desktop", "mobile"])
  2. Π’Ρ‹ΠΏΠΎΠ»Π½ΠΈΡ‚ΡŒ management-ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ recreate_variations:

    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.

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, Ссли ΠΎΠ½ Π΅ΡΡ‚ΡŒ.

Если Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΠΎ ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ ΠΊΠ°ΠΊΠΈΠ΅-Ρ‚ΠΎ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠΉ Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΈ, Ρ‚ΠΎ придётся ΠΎΠ±ΡŠΡΠ²Π»ΡΡ‚ΡŒ Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΡŽ явно:

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:

pip install django-rq
# 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>.
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:

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:

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 рСгистрируСт модСль элСмСнта ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΈ ΠΏΠΎΠ΄ Π·Π°Π΄Π°Π½Π½Ρ‹ΠΌ ΠΈΠΌΠ΅Π½Π΅ΠΌ.

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():

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

Π’ состав Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ входят ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ ΠΌΠΎΠ΄Π΅Π»ΠΈ элСмСнтов:

  • ImageItem
    Для хранСния изобраТСния с Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒΡŽ Π½Π°Ρ€Π΅Π·ΠΊΠΈ Π½Π° Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΈ. Π”ΠΎΠΏΡƒΡΠΊΡŽΡ‚ΡΡ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ρ‚Π΅ Ρ„Π°ΠΉΠ»Ρ‹, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΌΠΎΠΆΠ½ΠΎ ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ Pillow.

  • SVGItem
    Для хранСния SVG ΠΈΠΊΠΎΠ½ΠΎΠΊ.

  • FileItem
    ΠœΠΎΠΆΠ΅Ρ‚ Ρ…Ρ€Π°Π½ΠΈΡ‚ΡŒ любой Ρ„Π°ΠΉΠ».

Storage ΠΈ ΠΊΠ°Ρ‚Π°Π»ΠΎΠ³ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Ρ„Π°ΠΉΠ»ΠΎΠ²

По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ элСмСнты ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ Ρ‚ΠΎΡ‚ ΠΆΠ΅ экзСмпляр Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π°, Ρ‡Ρ‚ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ полями FileField ΠΈ ImageField.

ΠšΠ°Ρ‚Π°Π»ΠΎΠ³ΠΈ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Ρ„Π°ΠΉΠ»ΠΎΠ² для элСмСнтов ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΉ ΡƒΠΊΠ°Π·Ρ‹Π²Π°ΡŽΡ‚ΡΡ Π² настройках COLLECTION_FILES_UPLOAD_TO ΠΈ COLLECTION_IMAGES_UPLOAD_TO:

# settings.py

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

Для ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎ взятого элСмСнта ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΈ экзСмпляр Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π° ΠΈ ΠΊΠ°Ρ‚Π°Π»ΠΎΠ³ Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ
ΠΌΠΎΠΆΠ½ΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Π² ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π΅ options псСвдо-поля CollectionItem:

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():

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")
    )

На Π·Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌΡ‹Π΅ Π² ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΈ Ρ„Π°ΠΉΠ»Ρ‹ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°Π»ΠΎΠΆΠΈΡ‚ΡŒ ограничСния с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ Π²Π°Π»ΠΈΠ΄Π°Ρ‚ΠΎΡ€ΠΎΠ²:

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() для присоСдинСния элСмСнта ΠΊ ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΈ:

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
)

Π’Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΈ для ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΈ ΠΌΠΎΠΆΠ½ΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ ΠΎΠ΄Π½ΠΈΠΌ ΠΈΠ· Π΄Π²ΡƒΡ… способов:

  1. ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ options псСвдо-поля CollectionItem:

    from paper_uploads.models import *
    
    class PageGallery(Collection):
        image = CollectionItem(ImageItem, options={
            "variations": dict(
                mobile=dict(
                    size=(640, 0),
                    clip=False
                )
            )
        })
  2. Атрибут класса ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΈ VARIATIONS:

    from paper_uploads.models import *
    
    class PageGallery(Collection):
        VARIATIONS = dict(
            mobile=dict(
                size=(640, 0),
                clip=False
            )
        )
    
        image = CollectionItem(ImageItem)

HTML Template Example

{% 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, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π½Π΅ Ρ‚Ρ€Π΅Π±ΡƒΠ΅Ρ‚ для ΠΊΠ°ΠΆΠ΄ΠΎΠ³ΠΎ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠ³ΠΎ случая ΡΠΎΠ·Π΄Π°Π²Π°Ρ‚ΡŒ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΉ класс элСмСнта ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΈ. ВсС Π½Π΅ΠΎΠ±Ρ…ΠΎΠ΄ΠΈΠΌΡ‹Π΅ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΌΠΎΠΆΠ½ΠΎ ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ сразу, Ρ‡Π΅Ρ€Π΅Π· Π°Ρ‚Ρ€ΠΈΠ±ΡƒΡ‚Ρ‹ класса:

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
  • модСль элСмСнта ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΈ соотвСтствуСт ΠΌΠΎΠ΄Π΅Π»ΠΈ, ΡƒΠΊΠ°Π·Π°Π½Π½ΠΎΠΉ Π² классС ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΈ
python3 manage.py check_uploads

clean_uploads

Находит мусорныС записи Π² Π‘Π” (Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Ρ‚Π΅, Ρƒ ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Π½Π΅Ρ‚ Π²Π»Π°Π΄Π΅Π»ΡŒΡ†Π°) ΠΈ ΠΏΡ€Π΅Π΄Π»Π°Π³Π°Π΅Ρ‚ ΠΈΡ… ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ.

Π’Π»Π°Π΄Π΅Π»Π΅Ρ† Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π° устанавливаСтся Π² ΠΌΠΎΠΌΠ΅Π½Ρ‚ сохранСния страницы Π² интСрфСйсС администратора. Π­Ρ‚ΠΎ происходит ΠΏΠΎΠ·ΠΆΠ΅ фактичСской Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΈ Ρ„Π°ΠΉΠ»Π° Π½Π° сСрвСр. Π’ ΠΏΡ€ΠΎΠΌΠ΅ΠΆΡƒΡ‚ΠΊΠ΅ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ ΠΌΠ΅ΠΆΠ΄Ρƒ этими событиями Ρ„Π°ΠΉΠ» Π±ΡƒΠ΄Π΅Ρ‚ ΡΠ²Π»ΡΡ‚ΡŒΡΡ "сиротой". Для Ρ‚ΠΎΠ³ΠΎ, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Ρ‚Π°ΠΊΠΈΠ΅ Ρ„Π°ΠΉΠ»Ρ‹ Π½Π΅ ΡƒΠ΄Π°Π»ΡΠ»ΠΈΡΡŒ, ΠΊΠΎΠΌΠ°Π½Π΄Π° clean_uploads ΠΈΠ³Π½ΠΎΡ€ΠΈΡ€ΡƒΠ΅Ρ‚ Ρ„Π°ΠΉΠ»Ρ‹, Π·Π°Π³Ρ€ΡƒΠΆΠ΅Π½Π½Ρ‹Π΅ Π² Ρ‚Π΅Ρ‡Π΅Π½ΠΈΠ΅ послСднСго часа.

python3 manage.py clean_uploads

remove_empty_collections

Π£Π΄Π°Π»Π΅Π½ΠΈΠ΅ экзСмпляров ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΉ, Π² ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… Π½Π΅Ρ‚ Π½ΠΈ ΠΎΠ΄Π½ΠΎΠ³ΠΎ элСмСнта.

python3 manage.py clean_uploads

create_missing_variations

Π‘ΠΎΠ·Π΄Π°Ρ‘Ρ‚ ΠΎΡ‚ΡΡƒΡ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠ΅ Ρ„Π°ΠΉΠ»Ρ‹ Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΉ.

python3 manage.py create_missing_variations

recreate_variations

Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅/ΠΏΠ΅Ρ€Π΅Π·Π°ΠΏΠΈΡΡŒ Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΉ для всСх экзСмпляров ΡƒΠΊΠ°Π·Π°Π½Π½ΠΎΠΉ ΠΌΠΎΠ΄Π΅Π»ΠΈ.

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

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

Π’ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π΅ Π²Ρ‹Π·ΠΎΠ²Π° этих ΠΊΠΎΠΌΠ°Π½Π΄, ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŽ Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€Π΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΎ Π²Ρ‹Π±Ρ€Π°Ρ‚ΡŒ Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΈ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ слСдуСт ΠΎΠ±Π½ΠΎΠ²ΠΈΡ‚ΡŒ.

МоТно сразу ΡƒΠΊΠ°Π·Π°Ρ‚ΡŒ Π½ΡƒΠΆΠ½Ρ‹Π΅ Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΈ:

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

remove_variations

Π£Π΄Π°Π»Π΅Π½ΠΈΠ΅ Ρ„Π°ΠΉΠ»ΠΎΠ² Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΉ. Π£Π΄Π°Π»ΡΡŽΡ‚ΡΡ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ρ„Π°ΠΉΠ»Ρ‹ ΠΎΠ±ΡŠΡΠ²Π»Π΅Π½Π½Ρ‹Ρ… Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΉ. ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ Π°Π½Π°Π»ΠΎΠ³ΠΈΡ‡Π½Ρ‹ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Π°ΠΌ recreate_variations.

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

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

Settings

ВсС настройки ΡƒΠΊΠ°Π·Ρ‹Π²Π°ΡŽΡ‚ΡΡ Π² словарС PAPER_UPLOADS.

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.

Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ: django.core.files.storage.FileSystemStorage

STORAGE_OPTIONS

ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π°.

Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ: {}

FILES_UPLOAD_TO

ΠŸΡƒΡ‚ΡŒ ΠΊ ΠΏΠ°ΠΏΠΊΠ΅, Π² ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ Π·Π°Π³Ρ€ΡƒΠΆΠ°ΡŽΡ‚ΡΡ Ρ„Π°ΠΉΠ»Ρ‹ ΠΈΠ· FileField. ΠœΠΎΠΆΠ΅Ρ‚ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ для Π΄Π°Ρ‚Ρ‹ ΠΈ Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ (см. upload_to).

Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ: files/%Y/%m/%d

IMAGES_UPLOAD_TO

ΠŸΡƒΡ‚ΡŒ ΠΊ ΠΏΠ°ΠΏΠΊΠ΅, Π² ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ Π·Π°Π³Ρ€ΡƒΠΆΠ°ΡŽΡ‚ΡΡ Ρ„Π°ΠΉΠ»Ρ‹ ΠΈΠ· ImageField.

Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ: images/%Y/%m/%d

COLLECTION_FILES_UPLOAD_TO

ΠŸΡƒΡ‚ΡŒ ΠΊ ΠΏΠ°ΠΏΠΊΠ΅, Π² ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ Π·Π°Π³Ρ€ΡƒΠΆΠ°ΡŽΡ‚ΡΡ Ρ„Π°ΠΉΠ»Ρ‹ ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΉ.

Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ: collections/files/%Y/%m/%d

COLLECTION_IMAGES_UPLOAD_TO

ΠŸΡƒΡ‚ΡŒ ΠΊ ΠΏΠ°ΠΏΠΊΠ΅, Π² ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ Π·Π°Π³Ρ€ΡƒΠΆΠ°ΡŽΡ‚ΡΡ изобраТСния ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΉ.

Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ: collections/images/%Y/%m/%d

COLLECTION_ITEM_PREVIEW_WIDTH, COLLECTION_ITEM_PREVIEW_HEIGHT

Π Π°Π·ΠΌΠ΅Ρ€Ρ‹ ΠΏΡ€Π΅Π²ΡŒΡŽ элСмСнтов ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΉ Π² Π°Π΄ΠΌΠΈΠ½ΠΊΠ΅.

Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ: 180 x 135

COLLECTION_IMAGE_ITEM_PREVIEW_VARIATIONS

Π’Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΈ, добавляСмыС ΠΊ ΠΊΠ°ΠΆΠ΄ΠΎΠΌΡƒ классу ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ ΠΊΠΎΠ»Π»Π΅ΠΊΡ†ΠΈΠΉ для отобраТСния ΠΏΡ€Π΅Π²ΡŒΡŽ Π² Π°Π΄ΠΌΠΈΠ½ΠΊΠ΅. Π Π°Π·ΠΌΠ΅Ρ€Ρ‹ Ρ„Π°ΠΉΠ»ΠΎΠ² Π΄ΠΎΠ»ΠΆΠ½Ρ‹ ΡΠΎΠ²ΠΏΠ°Π΄Π°Ρ‚ΡŒ с COLLECTION_ITEM_PREVIEW_WIDTH ΠΈ COLLECTION_ITEM_PREVIEW_HEIGHT.

RQ_ENABLED

Π’ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ Π½Π°Ρ€Π΅Π·ΠΊΡƒ ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΎΠΊ Π½Π° Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΈ Ρ‡Π΅Ρ€Π΅Π· ΠΎΡ‚Π»ΠΎΠΆΠ΅Π½Π½Ρ‹Π΅ Π·Π°Π΄Π°Ρ‡ΠΈ. Π’Ρ€Π΅Π±ΡƒΠ΅Ρ‚ Π½Π°Π»ΠΈΡ‡ΠΈΠ΅ установлСнного ΠΏΠ°ΠΊΠ΅Ρ‚Π° django-rq.

Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ: False

RQ_QUEUE_NAME

НазваниС ΠΎΡ‡Π΅Ρ€Π΅Π΄ΠΈ, Π² ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ ΠΏΠΎΠΌΠ΅Ρ‰Π°ΡŽΡ‚ΡΡ Π·Π°Π΄Π°Ρ‡ΠΈ ΠΏΠΎ Π½Π°Ρ€Π΅Π·ΠΊΠ΅ ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΎΠΊ.

Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ: default

VARIATION_DEFAULTS

ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΉ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ.

ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹, ΡƒΠΊΠ°Π·Π°Π½Π½Ρ‹Π΅ Π² этом словарС, Π±ΡƒΠ΄ΡƒΡ‚ ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½Ρ‹ ΠΊ ΠΊΠ°ΠΆΠ΄ΠΎΠΉ Π²Π°Ρ€ΠΈΠ°Ρ†ΠΈΠΈ β€” Ссли Ρ‚ΠΎΠ»ΡŒΠΊΠΎ вариация ΠΈΡ… явно Π½Π΅ пСрСопрСдСляСт.

Π—Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ: None

Development and Testing

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

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

Install npm dependencies and build static files:

npm ci
npx webpack