diff --git a/README.md b/README.md index b8165326..87e24126 100644 --- a/README.md +++ b/README.md @@ -165,10 +165,12 @@ EDITORJS_DEFAULT_CONFIG_TOOLS = { 'Image': { 'class': 'ImageTool', 'inlineToolbar': True, - "config": {"endpoints": { + "config": { + "endpoints": { "byFile": reverse_lazy('editorjs_image_upload'), "byUrl": reverse_lazy('editorjs_image_by_url') - }}, + } + }, }, 'Header': { 'class': 'Header', @@ -291,15 +293,16 @@ plugin use css property [prefers-color-scheme](https://developer.mozilla.org/en- The application can be configured by editing the project's `settings.py` file. -| Key | Description | Default | Type | -| --------------------------------- | ---------------------------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| `EDITORJS_DEFAULT_PLUGINS` | List of plugins names Editor.js from npm | [See above](#plugins) | `list[str]`, `tuple[str]` | -| `EDITORJS_DEFAULT_CONFIG_TOOLS` | Map of Tools to use | [See above](#plugins) | `dict[str, dict]` | -| `EDITORJS_IMAGE_UPLOAD_PATH` | Path uploads images | `uploads/images/` | `str` | -| `EDITORJS_IMAGE_UPLOAD_PATH_DATE` | Subdirectories | `%Y/%m/` | `str` | -| `EDITORJS_IMAGE_NAME_ORIGINAL` | To use the original name of the image file? | `False` | `bool` | -| `EDITORJS_IMAGE_NAME` | Image file name. Ignored when `EDITORJS_IMAGE_NAME_ORIGINAL` is `True` | `token_urlsafe(8)` | `callable(filename: str, file: InMemoryUploadedFile)` ([docs](https://docs.djangoproject.com/en/3.0/ref/files/uploads/)) | -| `EDITORJS_VERSION` | Version Editor.js | `2.22.3` | `str` | +| Key | Description | Default | Type | +| --------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `EDITORJS_DEFAULT_PLUGINS` | List of plugins names Editor.js from npm | [See above](#plugins) | `list[str]`, `tuple[str]` | +| `EDITORJS_DEFAULT_CONFIG_TOOLS` | Map of Tools to use | [See above](#plugins) | `dict[str, dict]` | +| `EDITORJS_IMAGE_UPLOAD_PATH` | Path uploads images | `uploads/images/` | `str` | +| `EDITORJS_IMAGE_UPLOAD_PATH_DATE` | Subdirectories | `%Y/%m/` | `str` | +| `EDITORJS_IMAGE_NAME_ORIGINAL` | To use the original name of the image file? | `False` | `bool` | +| `EDITORJS_IMAGE_NAME` | Image file name. Ignored when `EDITORJS_IMAGE_NAME_ORIGINAL` is `True` | `token_urlsafe(8)` | `callable(filename: str, file: InMemoryUploadedFile)` ([docs](https://docs.djangoproject.com/en/3.0/ref/files/uploads/)) | +| `EDITORJS_EMBED_HOSTNAME_ALLOWED` | List of allowed hostname for embed | `('player.vimeo.com','www.youtube.com','coub.com','vine.co','imgur.com','gfycat.com','player.twitch.tv','player.twitch.tv','music.yandex.ru','codepen.io','www.instagram.com','twitframe.com','assets.pinterest.com','www.facebook.com','www.aparat.com'),` | `list[str]`, `tuple[str]` | +| `EDITORJS_VERSION` | Version Editor.js | `2.22.3` | `str` | For `EDITORJS_IMAGE_NAME` was used `from secrets import token_urlsafe` diff --git a/django_editorjs_fields/__init__.py b/django_editorjs_fields/__init__.py index 8e57d2e9..93e4c25a 100644 --- a/django_editorjs_fields/__init__.py +++ b/django_editorjs_fields/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.2.5" +__version__ = "0.2.6" from .fields import EditorJsJSONField, EditorJsTextField from .widgets import EditorJsWidget diff --git a/django_editorjs_fields/config.py b/django_editorjs_fields/config.py index 8b7262ba..23a6e91b 100644 --- a/django_editorjs_fields/config.py +++ b/django_editorjs_fields/config.py @@ -7,6 +7,30 @@ VERSION = getattr(settings, "EDITORJS_VERSION", '2.22.2') +# ATTACHMENT_REQUIRE_AUTHENTICATION = str( +# getattr(settings, "EDITORJS_ATTACHMENT_REQUIRE_AUTHENTICATION", True) +# ) + +EMBED_HOSTNAME_ALLOWED = str( + getattr(settings, "EDITORJS_EMBED_HOSTNAME_ALLOWED", ( + 'player.vimeo.com', + 'www.youtube.com', + 'coub.com', + 'vine.co', + 'imgur.com', + 'gfycat.com', + 'player.twitch.tv', + 'player.twitch.tv', + 'music.yandex.ru', + 'codepen.io', + 'www.instagram.com', + 'twitframe.com', + 'assets.pinterest.com', + 'www.facebook.com', + 'www.aparat.com', + )) +) + IMAGE_UPLOAD_PATH = str( getattr(settings, "EDITORJS_IMAGE_UPLOAD_PATH", 'uploads/images/') ) @@ -45,10 +69,12 @@ 'Image': { 'class': 'ImageTool', 'inlineToolbar': True, - "config": {"endpoints": { - "byFile": reverse_lazy('editorjs_image_upload'), - "byUrl": reverse_lazy('editorjs_image_by_url') - }}, + "config": { + "endpoints": { + "byFile": reverse_lazy('editorjs_image_upload'), + "byUrl": reverse_lazy('editorjs_image_by_url') + } + }, }, 'Header': { 'class': 'Header', diff --git a/django_editorjs_fields/fields.py b/django_editorjs_fields/fields.py index 7f04f0eb..448d654e 100644 --- a/django_editorjs_fields/fields.py +++ b/django_editorjs_fields/fields.py @@ -1,8 +1,12 @@ +import json + from django.core import checks +from django.core.exceptions import ValidationError from django.db.models import Field from django.forms import Textarea -from .config import DEBUG +from .config import DEBUG, EMBED_HOSTNAME_ALLOWED +from .utils import get_hostname_from_url from .widgets import EditorJsWidget try: @@ -51,6 +55,34 @@ def __init__(self, plugins, tools, **kwargs): super().__init__(**kwargs) + def validate_embed(self, value): + for item in value.get('blocks', []): + type = item.get('type', '').lower() + if type == 'embed': + embed = item['data']['embed'] + hostname = get_hostname_from_url(embed) + + if hostname not in EMBED_HOSTNAME_ALLOWED: + raise ValidationError( + hostname + ' is not allowed in EDITORJS_EMBED_HOSTNAME_ALLOWED') + + def clean(self, value, model_instance): + if value and value != 'null': + if not isinstance(value, dict): + try: + value = json.loads(value) + except ValueError: + pass + except TypeError: + pass + else: + self.validate_embed(value) + value = json.dumps(value) + else: + self.validate_embed(value) + + return super().clean(value, model_instance) + def formfield(self, **kwargs): if self.use_editorjs: kwargs['widget'] = EditorJsWidget( diff --git a/django_editorjs_fields/urls.py b/django_editorjs_fields/urls.py index a5b2262b..c5e5a68c 100644 --- a/django_editorjs_fields/urls.py +++ b/django_editorjs_fields/urls.py @@ -1,7 +1,7 @@ from django.contrib.admin.views.decorators import staff_member_required from django.urls import path -from .views import ImageUploadView, LinkToolView, ImageByUrl +from .views import ImageByUrl, ImageUploadView, LinkToolView urlpatterns = [ path( diff --git a/django_editorjs_fields/utils.py b/django_editorjs_fields/utils.py index cc7930ad..07c57159 100644 --- a/django_editorjs_fields/utils.py +++ b/django_editorjs_fields/utils.py @@ -1,3 +1,5 @@ +import urllib.parse + from django.conf import settings from django.utils.module_loading import import_string @@ -12,4 +14,9 @@ def get_storage_class(): )() +def get_hostname_from_url(url): + obj_url = urllib.parse.urlsplit(url) + return obj_url.hostname + + storage = get_storage_class() diff --git a/django_editorjs_fields/widgets.py b/django_editorjs_fields/widgets.py index 50f8e6f6..98fb2e66 100644 --- a/django_editorjs_fields/widgets.py +++ b/django_editorjs_fields/widgets.py @@ -23,7 +23,7 @@ def default(self, obj): class EditorJsWidget(widgets.Textarea): def __init__(self, plugins=None, tools=None, config=None, **kwargs): - self.plugins = PLUGINS if plugins is None else plugins + self.plugins = plugins self.tools = tools self.config = config @@ -39,28 +39,37 @@ def __init__(self, plugins=None, tools=None, config=None, **kwargs): def configuration(self): tools = {} config = self.config or {} - custom_tools = self.tools or {} - # get name packages without version - plugins = ['@'.join(p.split('@')[:2]) for p in self.plugins] - for plugin in plugins: - plugin_key = PLUGINS_KEYS.get(plugin) - plugin_tools = custom_tools.get( - plugin_key) or CONFIG_TOOLS.get(plugin_key) or {} - plugin_class = plugin_tools.get('class') + if self.plugins or self.tools: + custom_tools = self.tools or {} + # get name packages without version + plugins = ['@'.join(p.split('@')[:2]) + for p in self.plugins or PLUGINS] - if plugin_class: + for plugin in plugins: + plugin_key = PLUGINS_KEYS.get(plugin) - tools[plugin_key] = custom_tools.get( - plugin_key, CONFIG_TOOLS.get(plugin_key) - ) + if not plugin_key: + continue - tools[plugin_key]['class'] = plugin_class + plugin_tools = custom_tools.get( + plugin_key) or CONFIG_TOOLS.get(plugin_key) or {} + plugin_class = plugin_tools.get('class') - custom_tools.pop(plugin_key, None) + if plugin_class: - if custom_tools: - tools.update(custom_tools) + tools[plugin_key] = custom_tools.get( + plugin_key, CONFIG_TOOLS.get(plugin_key) + ) + + tools[plugin_key]['class'] = plugin_class + + custom_tools.pop(plugin_key, None) + + if custom_tools: + tools.update(custom_tools) + else: # default + tools.update(CONFIG_TOOLS) config.update(tools=tools) return config @@ -70,7 +79,12 @@ def media(self): js_list = [ '//cdn.jsdelivr.net/npm/@editorjs/editorjs@' + VERSION # lib ] - js_list += ['//cdn.jsdelivr.net/npm/' + p for p in self.plugins or []] + + plugins = self.plugins or PLUGINS + + if plugins: + js_list += ['//cdn.jsdelivr.net/npm/' + p for p in plugins] + js_list.append('django-editorjs-fields/js/django-editorjs-fields.js') return Media( diff --git a/example/blog/models.py b/example/blog/models.py index d5ed2dd3..4b92bd68 100644 --- a/example/blog/models.py +++ b/example/blog/models.py @@ -46,7 +46,7 @@ class Post(models.Model): blank=True, ) body_textfield = EditorJsTextField( # only images and paragraph (default) - plugins=["@editorjs/image"], null=True, blank=True, + plugins=["@editorjs/image", "@editorjs/embed"], null=True, blank=True, i18n={ 'messages': { 'blockTunes': { diff --git a/pyproject.toml b/pyproject.toml index 6dc6ca0b..c8ce2f3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "django-editorjs-fields" -version = "0.2.5" +version = "0.2.6" description = "Django plugin for using Editor.js" authors = ["Ilya Kotlyakov "] license = "MIT"