diff --git a/README.md b/README.md
index 3c78fbf..a5c1979 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,6 @@ pip install django-editorjs-fields --upgrade
python manage.py collectstatic # upgrade js and css files
```
-
## Usage
Add code in your model
@@ -41,13 +40,14 @@ Add code in your model
```python
# models.py
from django.db import models
-from django_editorjs_fields import EditorJsJSONField, EditorJsTextField
+from django_editorjs_fields import EditorJsJSONFiel # Django >= 3.1
+from django_editorjs_fields import EditorJsTextField
class Post(models.Model):
body_default = models.TextField()
body_editorjs = EditorJsJSONField() # Django >= 3.1
- body_editorjs_text = EditorJsTextField() # Django <= 3.0
+ body_editorjs_text = EditorJsTextField()
```
@@ -65,7 +65,7 @@ class Post(models.Model):
Document
- {% load editorjs %}
+ {% load editorjs %}
{{ post.body_default }}
{{ post.body_editorjs | editorjs}}
{{ post.body_editorjs_text | editorjs}}
@@ -165,7 +165,7 @@ EDITORJS_DEFAULT_CONFIG_TOOLS = {
'Image': {
'class': 'ImageTool',
'inlineToolbar': True,
- "config": {"endpoints": {"byFile": "/editorjs/image_upload/"}},
+ "config": {"endpoints": {"byFile": reverse_lazy('editorjs_image_upload')}},
},
'Header': {
'class': 'Header',
@@ -185,7 +185,12 @@ EDITORJS_DEFAULT_CONFIG_TOOLS = {
'Embed': {'class': 'Embed'},
'Delimiter': {'class': 'Delimiter'},
'Warning': {'class': 'Warning', 'inlineToolbar': True},
- 'LinkTool': {'class': 'LinkTool'},
+ 'LinkTool': {
+ 'class': 'LinkTool',
+ 'config': {
+ 'endpoint': reverse_lazy('editorjs_linktool'),
+ }
+ },
'Marker': {'class': 'Marker', 'inlineToolbar': True},
'Table': {'class': 'Table', 'inlineToolbar': True},
}
@@ -283,15 +288,15 @@ 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` |
+| 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` |
+| `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 f434eee..f444e24 100644
--- a/django_editorjs_fields/__init__.py
+++ b/django_editorjs_fields/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "0.2.3"
+__version__ = "0.2.4"
from .fields import EditorJsJSONField, EditorJsTextField
from .widgets import EditorJsWidget
diff --git a/django_editorjs_fields/config.py b/django_editorjs_fields/config.py
index 37fce02..15d3c8e 100644
--- a/django_editorjs_fields/config.py
+++ b/django_editorjs_fields/config.py
@@ -1,6 +1,7 @@
from secrets import token_urlsafe
from django.conf import settings
+from django.urls import reverse_lazy
DEBUG = getattr(settings, "DEBUG", False)
@@ -44,7 +45,7 @@
'Image': {
'class': 'ImageTool',
'inlineToolbar': True,
- "config": {"endpoints": {"byFile": "/editorjs/image_upload/"}},
+ "config": {"endpoints": {"byFile": reverse_lazy('editorjs_image_upload')}},
},
'Header': {
'class': 'Header',
@@ -64,7 +65,13 @@
'Embed': {'class': 'Embed'},
'Delimiter': {'class': 'Delimiter'},
'Warning': {'class': 'Warning', 'inlineToolbar': True},
- 'LinkTool': {'class': 'LinkTool'},
+ 'LinkTool': {
+ 'class': 'LinkTool',
+ 'config': {
+ # Backend endpoint for url data fetching
+ 'endpoint': reverse_lazy('editorjs_linktool'),
+ }
+ },
'Marker': {'class': 'Marker', 'inlineToolbar': True},
'Table': {'class': 'Table', 'inlineToolbar': True},
}
diff --git a/django_editorjs_fields/templatetags/editorjs.py b/django_editorjs_fields/templatetags/editorjs.py
index 4f485e4..89cb3e3 100644
--- a/django_editorjs_fields/templatetags/editorjs.py
+++ b/django_editorjs_fields/templatetags/editorjs.py
@@ -99,6 +99,34 @@ def generate_embed(data):
return f'{iframe}{caption}
'
+def generate_link(data):
+
+ link, meta = data.get('link'), data.get('meta')
+
+ if not link or not meta:
+ return ''
+
+ title = meta.get('title')
+ description = meta.get('description')
+ image = meta.get('image')
+
+ wrapper = f''
+ return wrapper
+
+
@register.filter(is_safe=True)
def editorjs(value):
if not value or value == 'null':
@@ -139,5 +167,7 @@ def editorjs(value):
html_list.append(generate_embed(data))
elif type == 'Quote':
html_list.append(generate_quote(data))
+ elif type == 'LinkTool':
+ html_list.append(generate_link(data))
return mark_safe(''.join(html_list))
diff --git a/django_editorjs_fields/urls.py b/django_editorjs_fields/urls.py
index f57874a..1a3b29c 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
+from .views import ImageUploadView, LinkToolView
urlpatterns = [
path(
@@ -9,4 +9,9 @@
staff_member_required(ImageUploadView.as_view()),
name='editorjs_image_upload',
),
+ path(
+ 'linktool/',
+ staff_member_required(LinkToolView.as_view()),
+ name='editorjs_linktool',
+ ),
]
diff --git a/django_editorjs_fields/views.py b/django_editorjs_fields/views.py
index d87b02d..26c2fa1 100644
--- a/django_editorjs_fields/views.py
+++ b/django_editorjs_fields/views.py
@@ -1,6 +1,14 @@
+import json
+import logging
import os
from datetime import datetime
+from urllib.error import HTTPError, URLError
+from urllib.parse import urlencode
+from urllib.request import Request, urlopen
+# from django.conf import settings
+from django.core.exceptions import ValidationError
+from django.core.validators import URLValidator
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
@@ -10,9 +18,12 @@
IMAGE_UPLOAD_PATH_DATE)
from .utils import storage
+LOGGER = logging.getLogger('django_editorjs_fields')
+
class ImageUploadView(View):
http_method_names = ["post"]
+ # http_method_names = ["post", "delete"]
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
@@ -35,9 +46,6 @@ def post(self, request):
{'success': 0, 'message': 'You can only upload images.'}
)
- # filesize = len(the_file['content'])
- # filetype = the_file['content-type']
-
filename, extension = os.path.splitext(the_file.name)
if IMAGE_NAME_ORIGINAL is False:
@@ -57,3 +65,82 @@ def post(self, request):
return JsonResponse({'success': 1, 'file': {"url": link}})
return JsonResponse({'success': 0})
+
+ # def delete(self, request):
+ # path_file = request.GET.get('pathFile')
+
+ # if not path_file:
+ # return JsonResponse({'success': 0, 'message': 'Parameter "pathFile" Not Found'})
+
+ # base_dir = getattr(settings, "BASE_DIR", '')
+ # path_file = f'{base_dir}{path_file}'
+
+ # if not os.path.isfile(path_file):
+ # return JsonResponse({'success': 0, 'message': 'File Not Found'})
+
+ # os.remove(path_file)
+
+ # return JsonResponse({'success': 1})
+
+
+class LinkToolView(View):
+ http_method_names = ["get"]
+
+ @method_decorator(csrf_exempt)
+ def dispatch(self, request, *args, **kwargs):
+ return super().dispatch(request, *args, **kwargs)
+
+ def get(self, request):
+
+ url = request.GET.get('url', '')
+
+ LOGGER.debug('Starting to get meta for: %s', url)
+
+ if not any([url.startswith(s) for s in ('http://', 'https://')]):
+ LOGGER.debug('Adding the http protocol to the link: %s', url)
+ url = 'http://' + url
+
+ validate = URLValidator(schemes=['http', 'https'])
+
+ try:
+ validate(url)
+ except ValidationError as e:
+ LOGGER.error(e)
+ else:
+ try:
+ LOGGER.debug('Let\'s try to get meta from: %s', url)
+
+ full_url = 'https://api.microlink.io/?' + \
+ urlencode({'url': url})
+
+ req = Request(full_url, headers={
+ 'User-Agent': request.META.get('HTTP_USER_AGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)')
+ })
+ res = urlopen(req)
+ except HTTPError as e:
+ LOGGER.error('The server couldn\'t fulfill the request.')
+ LOGGER.error('Error code: %s %s', e.code, e.msg)
+ except URLError as e:
+ LOGGER.error('We failed to reach a server. url: %s', url)
+ LOGGER.error('Reason: %s', e.reason)
+ else:
+ res_body = res.read()
+ res_json = json.loads(res_body.decode("utf-8"))
+
+ if 'success' in res_json.get('status'):
+ data = res_json.get('data')
+
+ if data:
+ LOGGER.debug('Response meta: %s', data)
+ meta = {}
+ meta['title'] = data.get('title')
+ meta['description'] = data.get('description')
+ meta['image'] = data.get('image')
+
+ return JsonResponse({
+ 'success': 1,
+ 'link': data.get('url', url),
+ 'meta': meta
+ })
+
+ return JsonResponse({'success': 0})
diff --git a/django_editorjs_fields/widgets.py b/django_editorjs_fields/widgets.py
index 61e5c3c..50f8e6f 100644
--- a/django_editorjs_fields/widgets.py
+++ b/django_editorjs_fields/widgets.py
@@ -3,7 +3,6 @@
from django.core.serializers.json import DjangoJSONEncoder
from django.forms import Media, widgets
from django.forms.renderers import get_default_renderer
-from django.forms.utils import flatatt
from django.utils.encoding import force_str
from django.utils.functional import Promise, cached_property
from django.utils.html import conditional_escape
diff --git a/example/.gitignore b/example/.gitignore
index c59768b..d5e1e18 100644
--- a/example/.gitignore
+++ b/example/.gitignore
@@ -1 +1,2 @@
-media/
\ No newline at end of file
+media/
+*.log
\ No newline at end of file
diff --git a/example/blog/admin.py b/example/blog/admin.py
index a81d503..6913375 100644
--- a/example/blog/admin.py
+++ b/example/blog/admin.py
@@ -4,7 +4,7 @@
class CommentInline(admin.TabularInline):
model = Comment
- extra = 2
+ extra = 0
fields = ('content',)
diff --git a/example/example/settings.py b/example/example/settings.py
index b6c5098..1695a7a 100644
--- a/example/example/settings.py
+++ b/example/example/settings.py
@@ -94,6 +94,35 @@
}
}
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'formatters': {
+ 'standard': {
+ 'format': '%(asctime)s %(filename)s:%(lineno)d %(levelname)s - %(message)s'
+ },
+ },
+ 'handlers': {
+ 'console': {
+ 'class': 'logging.StreamHandler',
+ },
+ 'django_editorjs_fields': {
+ 'level': 'DEBUG',
+ 'class': 'logging.handlers.RotatingFileHandler',
+ 'filename': 'django_editorjs_fields.log',
+ 'maxBytes': 1024*1024*5, # 5 MB
+ 'backupCount': 5,
+ 'formatter': 'standard',
+ },
+ },
+ 'loggers': {
+ 'django_editorjs_fields': {
+ 'handlers': ['django_editorjs_fields', 'console'],
+ 'level': 'DEBUG',
+ },
+ },
+}
+
# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
diff --git a/pyproject.toml b/pyproject.toml
index 384bedb..552b8c4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "django-editorjs-fields"
-version = "0.2.3"
+version = "0.2.4"
description = "Django plugin for using Editor.js"
authors = ["Ilya Kotlyakov "]
license = "MIT"
@@ -24,6 +24,7 @@ classifiers = [
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
"Topic :: Software Development :: Libraries :: Application Frameworks",
"Topic :: Software Development :: Libraries :: Python Modules",
]