Skip to content

Add LinkTool #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 19 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,21 @@ pip install django-editorjs-fields --upgrade
python manage.py collectstatic # upgrade js and css files
```


## Usage

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

```

Expand All @@ -65,7 +65,7 @@ class Post(models.Model):
<title>Document</title>
</head>
<body>
{% load editorjs %}
{% load editorjs %}
{{ post.body_default }}
{{ post.body_editorjs | editorjs}}
{{ post.body_editorjs_text | editorjs}}
Expand Down Expand Up @@ -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',
Expand All @@ -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},
}
Expand Down Expand Up @@ -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`

Expand Down
2 changes: 1 addition & 1 deletion django_editorjs_fields/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.2.3"
__version__ = "0.2.4"

from .fields import EditorJsJSONField, EditorJsTextField
from .widgets import EditorJsWidget
Expand Down
8 changes: 7 additions & 1 deletion django_editorjs_fields/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,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},
}
Expand Down
30 changes: 30 additions & 0 deletions django_editorjs_fields/templatetags/editorjs.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,34 @@ def generate_embed(data):
return f'<div class="embed {service}">{iframe}{caption}</div>'


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'<div class="link-block"><a href="{ link }" target="_blank" rel="nofollow noopener noreferrer">'

if image.get('url'):
image_url = image.get('url')
wrapper += f'<div class="link-block__image" style="background-image: url(\'{image_url}\');"></div>'

if title:
wrapper += f'<p class="link-block__title">{title}</p>'

if description:
wrapper += f'<p class="link-block__description">{description}</p>'

wrapper += f'<p class="link-block__link">{link}</p>'
wrapper += '</a></div>'
return wrapper


@register.filter(is_safe=True)
def editorjs(value):
if not value or value == 'null':
Expand Down Expand Up @@ -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))
7 changes: 6 additions & 1 deletion django_editorjs_fields/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
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(
'image_upload/',
staff_member_required(ImageUploadView.as_view()),
name='editorjs_image_upload',
),
path(
'linktool/',
staff_member_required(LinkToolView.as_view()),
name='editorjs_linktool',
),
]
93 changes: 90 additions & 3 deletions django_editorjs_fields/views.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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):
Expand All @@ -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:
Expand All @@ -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})
1 change: 0 additions & 1 deletion django_editorjs_fields/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion example/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
media/
media/
*.log
2 changes: 1 addition & 1 deletion example/blog/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

class CommentInline(admin.TabularInline):
model = Comment
extra = 2
extra = 0

fields = ('content',)

Expand Down
29 changes: 29 additions & 0 deletions example/example/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
license = "MIT"
Expand All @@ -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",
]
Expand Down