diff --git a/CHANGELOG.md b/CHANGELOG.md index 00c36df..1b41b32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [0.18.6](https://github.com/dldevinc/paper-streamfield/tree/v0.18.6) - 2023-12-21 + +### Features + +- In `ImageCollection` subclasses, you can set the file validators + with the new `VALIDATORS` attribute. + ## [0.18.5](https://github.com/dldevinc/paper-streamfield/tree/v0.18.5) - 2023-12-14 ### Bug Fixes diff --git a/README.md b/README.md index c3a7c3d..f00f83a 100644 --- a/README.md +++ b/README.md @@ -810,6 +810,39 @@ page = Page.objects.create( {% 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 diff --git a/paper_uploads/models/collection.py b/paper_uploads/models/collection.py index 0bb8201..0beab0f 100644 --- a/paper_uploads/models/collection.py +++ b/paper_uploads/models/collection.py @@ -16,6 +16,7 @@ from django.db.models.fields.files import FieldFile from django.db.models.functions import Coalesce from django.template import loader +from django.utils.functional import cached_property from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ from polymorphic.base import PolymorphicModelBase @@ -42,6 +43,7 @@ ) from .fields import CollectionItem from .fields.base import DynamicStorageFileField +from .fields.helpers import validators_to_options from .image import VariationalFileField from .mixins import BacklinkModelMixin, EditableResourceMixin from .query import PolymorphicResourceManager, ProxyPolymorphicManager @@ -746,15 +748,20 @@ def get_file_folder(self) -> str: folder = getattr(collection_cls, "UPLOAD_TO", None) return folder or super().get_file_folder() + @cached_property + def validators(self): + collection_cls = self.get_collection_class() + validators = getattr(collection_cls, "VALIDATORS", ()) + return [*self.default_validators, *validators, *self._validators] + class ImageCollection(Collection): """ Коллекция, позволяющая хранить только изображения. - Вариации могут быть заданы через атрибут класса VARIATIONS, - а путь для хранения файлов - через атрибут UPLOAD_TO. + Вариации, путь для хранения файлов и валидаторы + указываются в атрибутах класса. Пример: - class MyCollection(ImageCollection): UPLOAD_TO = "images/%Y-%m-%d" VARIATIONS = dict( @@ -762,14 +769,19 @@ class MyCollection(ImageCollection): size=(1600, 900), ) ) + VALIDATORS = [ + ImageMinSizeValidator(640, 480), + ImageMaxSizeValidator(4000, 3000) + ] """ - UPLOAD_TO: ClassVar[str] + UPLOAD_TO: ClassVar[str] = None + VALIDATORS: ClassVar[Any] = () image = CollectionItem(ConfigurableImageItem) @classmethod def get_configuration(cls) -> Dict[str, Any]: - return { + default_configuration = { "strictImageValidation": True, "acceptFiles": [ "image/bmp", @@ -781,3 +793,6 @@ def get_configuration(cls) -> Dict[str, Any]: "image/webp", ], } + + extra_configuration = validators_to_options(cls.VALIDATORS) + return dict(default_configuration, **extra_configuration) diff --git a/paper_uploads/models/fields/base.py b/paper_uploads/models/fields/base.py index 6f99a57..ff6c1a4 100644 --- a/paper_uploads/models/fields/base.py +++ b/paper_uploads/models/fields/base.py @@ -1,5 +1,3 @@ -from typing import Any, Dict - import django from django.core import checks from django.core.files import File @@ -7,7 +5,7 @@ from django.db.models.fields.files import FieldFile, FileDescriptor from django.db.models.signals import post_delete -from ... import validators +from .helpers import validators_to_options class ResourceFieldBase(models.OneToOneField): @@ -144,29 +142,12 @@ def deconstruct(self): return name, path, args, kwargs def formfield(self, **kwargs): - return super().formfield(**{"configuration": self.get_configuration(), **kwargs}) - - def get_configuration(self) -> Dict[str, Any]: - """ - Превращает Django-валидаторы в словарь конфигурации, - который может использоваться для вывода или проверки - на стороне клиента. - """ - config = {} - for v in self.validators: - if isinstance(v, validators.MimeTypeValidator): - config["acceptFiles"] = v.allowed - elif isinstance(v, validators.ExtensionValidator): - config["allowedExtensions"] = v.allowed - elif isinstance(v, validators.MaxSizeValidator): - config["sizeLimit"] = v.limit_value - elif isinstance(v, validators.ImageMinSizeValidator): - config["minImageWidth"] = v.width_limit - config["minImageHeight"] = v.height_limit - elif isinstance(v, validators.ImageMaxSizeValidator): - config["maxImageWidth"] = v.width_limit - config["maxImageHeight"] = v.height_limit - return config + return super().formfield( + **{ + "configuration": validators_to_options(self.validators), + **kwargs + } + ) class DynamicStorageFieldFile(FieldFile): @@ -291,7 +272,7 @@ class DynamicStorageFileField(models.FileField): attr_class = DynamicStorageFieldFile descriptor_class = DynamicStorageFileDescriptor - def __init__(self, verbose_name=None, name=None, upload_to='', storage=None, **kwargs): + def __init__(self, verbose_name=None, name=None, upload_to="", storage=None, **kwargs): self._primary_key_set_explicitly = "primary_key" in kwargs kwargs.setdefault("max_length", 255) super(models.FileField, self).__init__(verbose_name, name, **kwargs) diff --git a/paper_uploads/models/fields/helpers.py b/paper_uploads/models/fields/helpers.py new file mode 100644 index 0000000..738994b --- /dev/null +++ b/paper_uploads/models/fields/helpers.py @@ -0,0 +1,27 @@ +from typing import Any, Dict + +from ... import validators + + +def validators_to_options(field_validators) -> Dict[str, Any]: + """ + Превращает Django-валидаторы в словарь конфигурации, + который может использоваться для вывода или проверки + на стороне клиента. + """ + config = {} + for v in field_validators: + if isinstance(v, validators.MimeTypeValidator): + config["acceptFiles"] = v.allowed + elif isinstance(v, validators.ExtensionValidator): + config["allowedExtensions"] = v.allowed + elif isinstance(v, validators.MaxSizeValidator): + config["sizeLimit"] = v.limit_value + elif isinstance(v, validators.ImageMinSizeValidator): + config["minImageWidth"] = v.width_limit + config["minImageHeight"] = v.height_limit + elif isinstance(v, validators.ImageMaxSizeValidator): + config["maxImageWidth"] = v.width_limit + config["maxImageHeight"] = v.height_limit + + return config diff --git a/tests/examples/collections/validators/migrations/0003_gallery_alter_page_collection_page_gallery.py b/tests/examples/collections/validators/migrations/0003_gallery_alter_page_collection_page_gallery.py new file mode 100644 index 0000000..dd3e545 --- /dev/null +++ b/tests/examples/collections/validators/migrations/0003_gallery_alter_page_collection_page_gallery.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.8 on 2023-12-21 13:19 + +from django.db import migrations +import django.db.models.deletion +import django.db.models.manager +import paper_uploads.models.fields.collection + + +class Migration(migrations.Migration): + + dependencies = [ + ('paper_uploads', '0013_configurableimageitem'), + ('validators_collections', '0002_alter_page_id'), + ] + + operations = [ + migrations.CreateModel( + name='Gallery', + fields=[ + ], + options={ + 'proxy': True, + 'default_permissions': (), + 'indexes': [], + 'constraints': [], + }, + bases=('paper_uploads.imagecollection',), + managers=[ + ('default_mgr', django.db.models.manager.Manager()), + ], + ), + migrations.AlterField( + model_name='page', + name='collection', + field=paper_uploads.models.fields.collection.CollectionField(on_delete=django.db.models.deletion.SET_NULL, storage=None, to='validators_collections.mixedcollection', upload_to='', verbose_name='mixed collection'), + ), + migrations.AddField( + model_name='page', + name='gallery', + field=paper_uploads.models.fields.collection.CollectionField(on_delete=django.db.models.deletion.SET_NULL, storage=None, to='validators_collections.gallery', upload_to='', verbose_name='gallery'), + ), + ] diff --git a/tests/examples/collections/validators/models.py b/tests/examples/collections/validators/models.py index 2f9f42f..df023a0 100644 --- a/tests/examples/collections/validators/models.py +++ b/tests/examples/collections/validators/models.py @@ -10,6 +10,13 @@ ) +class Gallery(ImageCollection): + VALIDATORS = [ + ImageMinSizeValidator(640, 480), + ImageMaxSizeValidator(4000, 3000) + ] + + class MixedCollection(Collection): svg = CollectionItem(SVGItem, validators=[ MimeTypeValidator(["image/svg+xml"]) @@ -24,9 +31,13 @@ class MixedCollection(Collection): class Page(models.Model): + gallery = CollectionField( + Gallery, + verbose_name=_("gallery") + ) collection = CollectionField( MixedCollection, - verbose_name=_("collection") + verbose_name=_("mixed collection") ) class Meta: