├── demo
├── demo
│ ├── __init__.py
│ ├── asgi.py
│ ├── wsgi.py
│ ├── urls.py
│ └── settings.py
├── posts
│ ├── __init__.py
│ ├── migrations
│ │ ├── __init__.py
│ │ └── 0001_initial.py
│ ├── views.py
│ ├── apps.py
│ ├── admin.py
│ └── models.py
└── manage.py
├── poetry.toml
├── .gitignore
├── django_editorjs
├── __init__.py
├── templates
│ └── editorjs.html
├── fields.py
├── widgets.py
└── static
│ ├── django-editorjs.css
│ └── django-editorjs.js
├── pyproject.toml
├── LICENSE
├── README.md
└── poetry.lock
/demo/demo/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/posts/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/posts/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/poetry.toml:
--------------------------------------------------------------------------------
1 | [virtualenvs]
2 | create = false
3 | in-project = true
4 | path = ".pyenv"
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.DS_Store
2 | /.vscode
3 |
4 | /.pyenv
5 | /dist
6 | __pycache__
7 | db.sqlite3
8 |
--------------------------------------------------------------------------------
/demo/posts/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 |
3 | # Create your views here.
4 |
--------------------------------------------------------------------------------
/demo/posts/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class PostsConfig(AppConfig):
5 | name = 'posts'
6 |
--------------------------------------------------------------------------------
/demo/posts/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Post
3 |
4 |
5 | @admin.register(Post)
6 | class PostModelAdmin(admin.ModelAdmin):
7 | pass
8 |
--------------------------------------------------------------------------------
/django_editorjs/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "0.1.0"
2 | from .widgets import EditorJsWidget
3 | from .fields import EditorJsField
4 |
5 | __all__ = ("EditorJsWidget", "EditorJsField", "__version__")
6 |
--------------------------------------------------------------------------------
/django_editorjs/templates/editorjs.html:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/demo/demo/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for demo project.
3 |
4 | It exposes the ASGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.asgi import get_asgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/demo/demo/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for demo project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/demo/posts/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django_editorjs import EditorJsField
3 |
4 |
5 | class Post(models.Model):
6 | title = models.TextField()
7 | body = EditorJsField(
8 | editorjs_config={
9 | "tools": {
10 | "Table": {
11 | "disabled": True,
12 | "inlineToolbar": True,
13 | "config": {"rows": 2, "cols": 3,},
14 | }
15 | }
16 | }
17 | )
18 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "django-editorjs"
3 | version = "0.2.1"
4 | description = "Django plugin for using Editor.js in admin"
5 | authors = ["vlzh "]
6 | repository = "https://github.com/VLZH/django-editorjs"
7 | documentation = "https://github.com/VLZH/django-editorjs"
8 | readme = "README.md"
9 |
10 | [tool.poetry.dependencies]
11 | python = "^3.4"
12 |
13 | [tool.poetry.dev-dependencies]
14 | pytest = "^5.2"
15 | Django = "^3.0.0"
16 | black = "^19.10b0"
17 | flake8 = "^3.8.3"
18 |
19 | [build-system]
20 | requires = ["poetry>=0.12"]
21 | build-backend = "poetry.masonry.api"
22 |
--------------------------------------------------------------------------------
/demo/posts/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 3.0.8 on 2020-07-18 22:56
2 |
3 | from django.db import migrations, models
4 | import django_editorjs.fields
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | initial = True
10 |
11 | dependencies = [
12 | ]
13 |
14 | operations = [
15 | migrations.CreateModel(
16 | name='Post',
17 | fields=[
18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19 | ('title', models.TextField()),
20 | ('body', django_editorjs.fields.EditorJsField()),
21 | ],
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/demo/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo.settings')
9 | try:
10 | from django.core.management import execute_from_command_line
11 | except ImportError as exc:
12 | raise ImportError(
13 | "Couldn't import Django. Are you sure it's installed and "
14 | "available on your PYTHONPATH environment variable? Did you "
15 | "forget to activate a virtual environment?"
16 | ) from exc
17 | execute_from_command_line(sys.argv)
18 |
19 |
20 | if __name__ == '__main__':
21 | main()
22 |
--------------------------------------------------------------------------------
/django_editorjs/fields.py:
--------------------------------------------------------------------------------
1 | from django.db.models import Field
2 | from urllib.parse import unquote
3 |
4 | from django_editorjs.widgets import EditorJsWidget
5 |
6 |
7 | class EditorJsField(Field):
8 | def __init__(self, editorjs_config=None, *args, **kwargs):
9 | super().__init__(*args, **kwargs)
10 | self._editorjs_config = editorjs_config
11 |
12 | def get_internal_type(self):
13 | return "TextField"
14 |
15 | def clean(self, value, model_instance):
16 | if value is not None:
17 | return unquote(super().clean(value, model_instance))
18 | else:
19 | return None
20 |
21 | def formfield(self, *args, **kwargs):
22 | kwargs["widget"] = EditorJsWidget(editorjs_config=self._editorjs_config)
23 | return super().formfield(*args, **kwargs)
24 |
--------------------------------------------------------------------------------
/demo/demo/urls.py:
--------------------------------------------------------------------------------
1 | """demo URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/3.0/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.urls import include, path
14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 | """
16 | from django.contrib import admin
17 | from django.urls import path
18 |
19 | urlpatterns = [
20 | path('admin/', admin.site.urls),
21 | ]
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 VLZH
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included
14 | in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/django_editorjs/widgets.py:
--------------------------------------------------------------------------------
1 | import json
2 | from django.forms import widgets, Media
3 | from django.template.loader import render_to_string
4 |
5 |
6 | class EditorJsWidget(widgets.Textarea):
7 | def __init__(self, editorjs_config, *args, **kwargs):
8 | super(EditorJsWidget, self).__init__(*args, **kwargs)
9 | self._editorjs_config = editorjs_config
10 |
11 | @property
12 | def media(self):
13 | return Media(
14 | css={"all": ["django-editorjs.css"]},
15 | js=(
16 | "https://cdn.jsdelivr.net/combine/npm/@editorjs/editorjs@2.18.0,npm/@editorjs/paragraph@2.7.0,npm/@editorjs/image@2.4.2,npm/@editorjs/header@2.5.0,npm/@editorjs/list@1.5.0,npm/@editorjs/checklist@1.1.0,npm/@editorjs/quote@2.3.0,npm/@editorjs/raw@2.1.2,npm/@editorjs/embed@2.3.1,npm/@editorjs/delimiter@1.1.0,npm/@editorjs/warning@1.1.1,npm/@editorjs/link@2.2.1,npm/@editorjs/marker@1.2.2,npm/@editorjs/attaches@1.0.1,npm/@editorjs/table@1.2.2",
17 | "django-editorjs.js",
18 | ),
19 | )
20 |
21 | def render(self, name, value, **kwargs):
22 | ctx = {
23 | "name": name,
24 | "id": kwargs["attrs"]["id"],
25 | "value": value,
26 | "editorjs_config": json.dumps(self._editorjs_config),
27 | }
28 | return render_to_string("editorjs.html", ctx)
29 |
--------------------------------------------------------------------------------
/django_editorjs/static/django-editorjs.css:
--------------------------------------------------------------------------------
1 | /*GLOBAL*/
2 | [data-editorjs-wrapper] {
3 | max-width: 700px;
4 | width: 100%;
5 | border-radius: 4px;
6 | background: #fff;
7 | color: #000;
8 | padding: 1.2em;
9 | display: inline-block;
10 | border: 1px solid #ccc;
11 | }
12 | .codex-editor {
13 | }
14 |
15 | /*LIST*/
16 | body .codex-editor .cdx-list {
17 | margin: 0;
18 | padding-left: 40px;
19 | outline: none;
20 | }
21 | body .codex-editor .cdx-list__item {
22 | padding: 5.5px 0 5.5px 3px;
23 | line-height: 1.6em;
24 | list-style: inherit;
25 | }
26 | body .codex-editor .cdx-list--unordered {
27 | list-style: disc;
28 | }
29 | body .codex-editor .cdx-list--ordered {
30 | list-style: decimal;
31 | }
32 | body .codex-editor .cdx-list-settings {
33 | display: flex;
34 | }
35 | body .codex-editor .cdx-list-settings .cdx-settings-button {
36 | width: 50%;
37 | }
38 | /*HEADER*/
39 | body .codex-editor .ce-header {
40 | padding: 1em 0;
41 | margin: 0;
42 | margin-bottom: -0.9em;
43 | line-height: 1.5em;
44 | outline: none;
45 | background: transparent;
46 | color: #000;
47 | font-weight: 800;
48 | text-transform: initial;
49 | }
50 | body .codex-editor h1.ce-header {
51 | font-size: 2em;
52 | }
53 | body .codex-editor h2.ce-header {
54 | font-size: 1.5em;
55 | }
56 | body .codex-editor h3.ce-header {
57 | font-size: 1.17em;
58 | }
59 | body .codex-editor h4.ce-header {
60 | font-size: 1.17em;
61 | }
62 | body .codex-editor h5.ce-header {
63 | font-size: 1em;
64 | }
65 | body .codex-editor h6.ce-header {
66 | font-size: 0.67em;
67 | }
68 |
69 | body .codex-editor blockquote {
70 | border: initial;
71 | margin: initial;
72 | color: initial;
73 | font-size: inherit;
74 | }
75 |
76 | body .codex-editor .link-tool__progress {
77 | float: initial;
78 | width: 100%;
79 | line-height: initial;
80 | padding: initial;
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/demo/demo/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for demo project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.0.5.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.0/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/3.0/ref/settings/
11 | """
12 |
13 | import os
14 |
15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = "r(_&hy(545r3w(i*h)jpf0jf-u4tcn==17kfn0$vga=*^_wpc7"
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = []
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | "django.contrib.admin",
35 | "django.contrib.auth",
36 | "django.contrib.contenttypes",
37 | "django.contrib.sessions",
38 | "django.contrib.messages",
39 | "django.contrib.staticfiles",
40 | "django_editorjs",
41 | "posts",
42 | ]
43 |
44 | MIDDLEWARE = [
45 | "django.middleware.security.SecurityMiddleware",
46 | "django.contrib.sessions.middleware.SessionMiddleware",
47 | "django.middleware.common.CommonMiddleware",
48 | "django.middleware.csrf.CsrfViewMiddleware",
49 | "django.contrib.auth.middleware.AuthenticationMiddleware",
50 | "django.contrib.messages.middleware.MessageMiddleware",
51 | "django.middleware.clickjacking.XFrameOptionsMiddleware",
52 | ]
53 |
54 | ROOT_URLCONF = "demo.urls"
55 |
56 | TEMPLATES = [
57 | {
58 | "BACKEND": "django.template.backends.django.DjangoTemplates",
59 | "DIRS": [],
60 | "APP_DIRS": True,
61 | "OPTIONS": {
62 | "context_processors": [
63 | "django.template.context_processors.debug",
64 | "django.template.context_processors.request",
65 | "django.contrib.auth.context_processors.auth",
66 | "django.contrib.messages.context_processors.messages",
67 | ],
68 | },
69 | },
70 | ]
71 |
72 | WSGI_APPLICATION = "demo.wsgi.application"
73 |
74 |
75 | # Database
76 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases
77 |
78 | DATABASES = {
79 | "default": {
80 | "ENGINE": "django.db.backends.sqlite3",
81 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
82 | }
83 | }
84 |
85 |
86 | # Password validation
87 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
88 |
89 | AUTH_PASSWORD_VALIDATORS = [
90 | {
91 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
92 | },
93 | {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",},
94 | {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",},
95 | {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",},
96 | ]
97 |
98 |
99 | # Internationalization
100 | # https://docs.djangoproject.com/en/3.0/topics/i18n/
101 |
102 | LANGUAGE_CODE = "en-us"
103 |
104 | TIME_ZONE = "UTC"
105 |
106 | USE_I18N = True
107 |
108 | USE_L10N = True
109 |
110 | USE_TZ = True
111 |
112 |
113 | # Static files (CSS, JavaScript, Images)
114 | # https://docs.djangoproject.com/en/3.0/howto/static-files/
115 |
116 | STATIC_URL = "/static/"
117 |
--------------------------------------------------------------------------------
/django_editorjs/static/django-editorjs.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | /**
3 | * @param {Object} config
4 | * @param {String} tool
5 | * @param {Object} default_config
6 | */
7 | function extractToolConfig(config, tool, default_config) {
8 | var result = Object.assign({}, default_config);
9 | if (config && config.tools && config.tools[tool]) {
10 | if (config.tools[tool].disabled) {
11 | return undefined;
12 | }
13 | Object.assign(result, config.tools[tool]);
14 | }
15 | return result;
16 | }
17 |
18 | /**
19 | * @param {Object} config
20 | * @param {String} tool
21 | */
22 | function isDisabled(config, tool) {
23 | return !!(
24 | config &&
25 | config.tools &&
26 | config.tools[tool] &&
27 | config.tools[tool].disabled
28 | );
29 | }
30 |
31 | /**
32 | * @param {HTMLDivElement} field_wrapper
33 | */
34 | function initEditorJsField(field_wrapper) {
35 | var holder_el = field_wrapper.querySelector("[data-editorjs-holder]");
36 | var input_el = field_wrapper.querySelector("[data-editorjs-input]");
37 | var config_el = field_wrapper.querySelector("[data-editorjs-config]");
38 | var config = JSON.parse(config_el.innerHTML.trim());
39 | var tools = {};
40 | if (!isDisabled(config, "Image")) {
41 | tools.Image = extractToolConfig(config, "Image", {
42 | class: ImageTool,
43 | inlineToolbar: true,
44 | });
45 | }
46 | if (!isDisabled(config, "Header")) {
47 | tools.Header = extractToolConfig(config, "Header", {
48 | class: Header,
49 | });
50 | }
51 | if (!isDisabled(config, "Checklist")) {
52 | tools.Checklist = extractToolConfig(config, "Checklist", {
53 | class: Checklist,
54 | inlineToolbar: true,
55 | });
56 | }
57 | if (!isDisabled(config, "List")) {
58 | tools.List = extractToolConfig(config, "List", {
59 | class: List,
60 | inlineToolbar: true,
61 | });
62 | }
63 | if (!isDisabled(config, "Quote")) {
64 | tools.Quote = extractToolConfig(config, "Quote", {
65 | class: Quote,
66 | inlineToolbar: true,
67 | });
68 | }
69 | if (!isDisabled(config, "Raw")) {
70 | tools.Raw = extractToolConfig(config, "Raw", {
71 | class: RawTool,
72 | });
73 | }
74 | if (!isDisabled(config, "Embed")) {
75 | tools.Embed = extractToolConfig(config, "Embed", {
76 | class: Embed,
77 | inlineToolbar: true,
78 | });
79 | }
80 | if (!isDisabled(config, "Delimiter")) {
81 | tools.Delimiter = extractToolConfig(config, "Delimiter", {
82 | class: Delimiter,
83 | });
84 | }
85 | if (!isDisabled(config, "Warning")) {
86 | tools.Warning = extractToolConfig(config, "Warning", {
87 | class: Warning,
88 | inlineToolbar: true,
89 | });
90 | }
91 | if (!isDisabled(config, "Link")) {
92 | tools.Link = extractToolConfig(config, "Link", {
93 | class: LinkTool,
94 | });
95 | }
96 | if (!isDisabled(config, "Marker")) {
97 | tools.Marker = extractToolConfig(config, "Marker", {
98 | class: Marker,
99 | });
100 | }
101 | if (!isDisabled(config, "Attaches")) {
102 | tools.Attaches = extractToolConfig(config, "Attaches", {
103 | class: AttachesTool,
104 | });
105 | }
106 | if (!isDisabled(config, "Table")) {
107 | tools.Table = extractToolConfig(config, "Table", {
108 | class: Table,
109 | inlineToolbar: true,
110 | });
111 | }
112 |
113 | const editor = new EditorJS({
114 | holder: holder_el,
115 | tools: tools,
116 | data:
117 | (input_el.value &&
118 | input_el.value.trim() &&
119 | JSON.parse(input_el.value.trim())) ||
120 | undefined,
121 | onChange: function () {
122 | editor
123 | .save()
124 | .then(function (outputData) {
125 | console.log(JSON.stringify(outputData));
126 | input_el.value = JSON.stringify(outputData);
127 | })
128 | .catch(function (error) {
129 | console.log("Saving failed: ", error);
130 | });
131 | },
132 | });
133 | }
134 |
135 | window.addEventListener("load", function () {
136 | var editor_wrappers = document.querySelectorAll("[data-editorjs-wrapper]");
137 | editor_wrappers.forEach(initEditorJsField);
138 | });
139 | })();
140 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # django-editorjs
2 |
3 | [](https://badge.fury.io/gh/VLZH%2Fdjango-editorjs)
4 | [](https://pypi.org/project/django-editorjs/)
5 |
6 | Plugin for using [Editor.js](https://editorjs.io/) in django admin.
7 |
8 | # Supported plugins/tools
9 |
10 | - `@editorjs/paragraph` - [](https://badge.fury.io/js/%40editorjs%2Fparagraph)
11 | - `@editorjs/image` - [](https://badge.fury.io/js/%40editorjs%2Fimage)
12 | - `@editorjs/header` - [](https://badge.fury.io/js/%40editorjs%2Fheader)
13 | - `@editorjs/checklist` - [](https://badge.fury.io/js/%40editorjs%2Fchecklist)
14 | - `@editorjs/list` - [](https://badge.fury.io/js/%40editorjs%2Flist)
15 | - `@editorjs/quote` - [](https://badge.fury.io/js/%40editorjs%2Fquote)
16 | - `@editorjs/raw` - [](https://badge.fury.io/js/%40editorjs%2Fraw)
17 | - `@editorjs/embed` - [](https://badge.fury.io/js/%40editorjs%2Fembed)
18 | - `@editorjs/delimiter` - [](https://badge.fury.io/js/%40editorjs%2Fdelimiter)
19 | - `@editorjs/warning` - [](https://badge.fury.io/js/%40editorjs%2Fwarning)
20 | - `@editorjs/link` - [](https://badge.fury.io/js/%40editorjs%2Flink)
21 | - `@editorjs/marker` - [](https://badge.fury.io/js/%40editorjs%2Fmarker)
22 | - `@editorjs/attaches` - [](https://badge.fury.io/js/%40editorjs%2Fattaches)
23 | - `@editorjs/table` - [](https://badge.fury.io/js/%40editorjs%2Ftable)
24 |
25 | > #### ⚠️ Note (for plugin configuration)
26 | >
27 | > Usually in examples for Editor.js you will see tool names starts with lowercase, but for bypass potential conflicts i use uppercase.
28 |
29 | # Installation
30 |
31 | ```bash
32 | pip install django-editorjs
33 | ```
34 |
35 | # Simple example
36 |
37 | ```python
38 | # models.py
39 | from django.db import models
40 | from django_editorjs import EditorJsField
41 |
42 | class Post(models.Model):
43 | title = models.CharField(max_length=255)
44 | body = EditorJsField()
45 |
46 | def __str__(self):
47 | return self.title
48 | ```
49 |
50 | # How to configure
51 |
52 | You can provide field specific configuration options to `EditorJsField` by argument `editorjs_config`.
53 |
54 | #### Example
55 |
56 | ```python
57 | class Post(models.Model):
58 | title = models.TextField()
59 | body = EditorJsField(
60 | editorjs_config={
61 | "tools": {
62 | "Table": {
63 | "disabled": False,
64 | "inlineToolbar": True,
65 | "config": {"rows": 2, "cols": 3,},
66 | }
67 | }
68 | }
69 | )
70 |
71 | ```
72 |
73 | ## Config schema
74 |
75 | - `tools`
76 | - `Image` - (`dict`) configuration for tool `ImageTool`. (_For more info see official documentation for tool_).
77 | - `Header` - (`dict`) configuration for tool `Header`. (_For more info see official documentation for tool_).
78 | - `Checklist` - (`dict`) configuration for tool `Checklist`. (_For more info see official documentation for tool_).
79 | - `List` - (`dict`) configuration for tool `List`. (_For more info see official documentation for tool_).
80 | - `Quote` - (`dict`) configuration for tool `Quote`. (_For more info see official documentation for tool_).
81 | - `Raw` - (`dict`) configuration for tool `RawTool`. (_For more info see official documentation for tool_).
82 | - `Embed` - (`dict`) configuration for tool `Embed`. (_For more info see official documentation for tool_).
83 | - `Delimiter` - (`dict`) configuration for tool `Delimiter`. (_For more info see official documentation for tool_).
84 | - `Warning` - (`dict`) configuration for tool `Warning`. (_For more info see official documentation for tool_).
85 | - `Link` - (`dict`) configuration for tool `LinkTool`. (_For more info see official documentation for tool_).
86 | - `Marker` - (`dict`) configuration for tool `Marker`. (_For more info see official documentation for tool_).
87 | - `Attaches` - (`dict`) configuration for tool `AttachesTool`. (_For more info see official documentation for tool_).
88 | - `Table` - (`dict`) configuration for tool `Table`. (_For more info see official documentation for tool_).
89 |
90 | # API
91 |
92 | - `EditorJsField`
93 |
94 | Extends `TextField` and use `EditorJsWidget` as widget + have additional argument in constructor: `editorjs_config`.
95 |
96 | - `EditorJsWidget`
97 |
98 | Widget that you can to use for using Editor.js in Django.
99 |
100 | # TODO
101 |
102 | - load tool on demand
103 | - more examples in README.md
104 | - view-function for file uploading
105 | - view-function for image uploading
106 | - view-function for link info crawler
107 |
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | category = "dev"
3 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
4 | name = "appdirs"
5 | optional = false
6 | python-versions = "*"
7 | version = "1.4.4"
8 |
9 | [[package]]
10 | category = "dev"
11 | description = "ASGI specs, helper code, and adapters"
12 | name = "asgiref"
13 | optional = false
14 | python-versions = ">=3.5"
15 | version = "3.2.10"
16 |
17 | [package.extras]
18 | tests = ["pytest", "pytest-asyncio"]
19 |
20 | [[package]]
21 | category = "dev"
22 | description = "Atomic file writes."
23 | marker = "sys_platform == \"win32\""
24 | name = "atomicwrites"
25 | optional = false
26 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
27 | version = "1.4.0"
28 |
29 | [[package]]
30 | category = "dev"
31 | description = "Classes Without Boilerplate"
32 | name = "attrs"
33 | optional = false
34 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
35 | version = "19.3.0"
36 |
37 | [package.extras]
38 | azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
39 | dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
40 | docs = ["sphinx", "zope.interface"]
41 | tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
42 |
43 | [[package]]
44 | category = "dev"
45 | description = "The uncompromising code formatter."
46 | name = "black"
47 | optional = false
48 | python-versions = ">=3.6"
49 | version = "19.10b0"
50 |
51 | [package.dependencies]
52 | appdirs = "*"
53 | attrs = ">=18.1.0"
54 | click = ">=6.5"
55 | pathspec = ">=0.6,<1"
56 | regex = "*"
57 | toml = ">=0.9.4"
58 | typed-ast = ">=1.4.0"
59 |
60 | [package.extras]
61 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"]
62 |
63 | [[package]]
64 | category = "dev"
65 | description = "Composable command line interface toolkit"
66 | name = "click"
67 | optional = false
68 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
69 | version = "7.1.2"
70 |
71 | [[package]]
72 | category = "dev"
73 | description = "Cross-platform colored terminal text."
74 | marker = "sys_platform == \"win32\""
75 | name = "colorama"
76 | optional = false
77 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
78 | version = "0.4.3"
79 |
80 | [[package]]
81 | category = "dev"
82 | description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
83 | name = "django"
84 | optional = false
85 | python-versions = ">=3.6"
86 | version = "3.0.8"
87 |
88 | [package.dependencies]
89 | asgiref = ">=3.2,<4.0"
90 | pytz = "*"
91 | sqlparse = ">=0.2.2"
92 |
93 | [package.extras]
94 | argon2 = ["argon2-cffi (>=16.1.0)"]
95 | bcrypt = ["bcrypt"]
96 |
97 | [[package]]
98 | category = "dev"
99 | description = "the modular source code checker: pep8 pyflakes and co"
100 | name = "flake8"
101 | optional = false
102 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
103 | version = "3.8.3"
104 |
105 | [package.dependencies]
106 | mccabe = ">=0.6.0,<0.7.0"
107 | pycodestyle = ">=2.6.0a1,<2.7.0"
108 | pyflakes = ">=2.2.0,<2.3.0"
109 |
110 | [package.dependencies.importlib-metadata]
111 | python = "<3.8"
112 | version = "*"
113 |
114 | [[package]]
115 | category = "dev"
116 | description = "Read metadata from Python packages"
117 | marker = "python_version < \"3.8\""
118 | name = "importlib-metadata"
119 | optional = false
120 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
121 | version = "1.7.0"
122 |
123 | [package.dependencies]
124 | zipp = ">=0.5"
125 |
126 | [package.extras]
127 | docs = ["sphinx", "rst.linker"]
128 | testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
129 |
130 | [[package]]
131 | category = "dev"
132 | description = "McCabe checker, plugin for flake8"
133 | name = "mccabe"
134 | optional = false
135 | python-versions = "*"
136 | version = "0.6.1"
137 |
138 | [[package]]
139 | category = "dev"
140 | description = "More routines for operating on iterables, beyond itertools"
141 | name = "more-itertools"
142 | optional = false
143 | python-versions = ">=3.5"
144 | version = "8.4.0"
145 |
146 | [[package]]
147 | category = "dev"
148 | description = "Core utilities for Python packages"
149 | name = "packaging"
150 | optional = false
151 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
152 | version = "20.4"
153 |
154 | [package.dependencies]
155 | pyparsing = ">=2.0.2"
156 | six = "*"
157 |
158 | [[package]]
159 | category = "dev"
160 | description = "Utility library for gitignore style pattern matching of file paths."
161 | name = "pathspec"
162 | optional = false
163 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
164 | version = "0.8.0"
165 |
166 | [[package]]
167 | category = "dev"
168 | description = "plugin and hook calling mechanisms for python"
169 | name = "pluggy"
170 | optional = false
171 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
172 | version = "0.13.1"
173 |
174 | [package.dependencies]
175 | [package.dependencies.importlib-metadata]
176 | python = "<3.8"
177 | version = ">=0.12"
178 |
179 | [package.extras]
180 | dev = ["pre-commit", "tox"]
181 |
182 | [[package]]
183 | category = "dev"
184 | description = "library with cross-python path, ini-parsing, io, code, log facilities"
185 | name = "py"
186 | optional = false
187 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
188 | version = "1.9.0"
189 |
190 | [[package]]
191 | category = "dev"
192 | description = "Python style guide checker"
193 | name = "pycodestyle"
194 | optional = false
195 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
196 | version = "2.6.0"
197 |
198 | [[package]]
199 | category = "dev"
200 | description = "passive checker of Python programs"
201 | name = "pyflakes"
202 | optional = false
203 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
204 | version = "2.2.0"
205 |
206 | [[package]]
207 | category = "dev"
208 | description = "Python parsing module"
209 | name = "pyparsing"
210 | optional = false
211 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
212 | version = "2.4.7"
213 |
214 | [[package]]
215 | category = "dev"
216 | description = "pytest: simple powerful testing with Python"
217 | name = "pytest"
218 | optional = false
219 | python-versions = ">=3.5"
220 | version = "5.4.3"
221 |
222 | [package.dependencies]
223 | atomicwrites = ">=1.0"
224 | attrs = ">=17.4.0"
225 | colorama = "*"
226 | more-itertools = ">=4.0.0"
227 | packaging = "*"
228 | pluggy = ">=0.12,<1.0"
229 | py = ">=1.5.0"
230 | wcwidth = "*"
231 |
232 | [package.dependencies.importlib-metadata]
233 | python = "<3.8"
234 | version = ">=0.12"
235 |
236 | [package.extras]
237 | checkqa-mypy = ["mypy (v0.761)"]
238 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
239 |
240 | [[package]]
241 | category = "dev"
242 | description = "World timezone definitions, modern and historical"
243 | name = "pytz"
244 | optional = false
245 | python-versions = "*"
246 | version = "2020.1"
247 |
248 | [[package]]
249 | category = "dev"
250 | description = "Alternative regular expression module, to replace re."
251 | name = "regex"
252 | optional = false
253 | python-versions = "*"
254 | version = "2020.7.14"
255 |
256 | [[package]]
257 | category = "dev"
258 | description = "Python 2 and 3 compatibility utilities"
259 | name = "six"
260 | optional = false
261 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
262 | version = "1.15.0"
263 |
264 | [[package]]
265 | category = "dev"
266 | description = "Non-validating SQL parser"
267 | name = "sqlparse"
268 | optional = false
269 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
270 | version = "0.3.1"
271 |
272 | [[package]]
273 | category = "dev"
274 | description = "Python Library for Tom's Obvious, Minimal Language"
275 | name = "toml"
276 | optional = false
277 | python-versions = "*"
278 | version = "0.10.1"
279 |
280 | [[package]]
281 | category = "dev"
282 | description = "a fork of Python 2 and 3 ast modules with type comment support"
283 | name = "typed-ast"
284 | optional = false
285 | python-versions = "*"
286 | version = "1.4.1"
287 |
288 | [[package]]
289 | category = "dev"
290 | description = "Measures the displayed width of unicode strings in a terminal"
291 | name = "wcwidth"
292 | optional = false
293 | python-versions = "*"
294 | version = "0.2.5"
295 |
296 | [[package]]
297 | category = "dev"
298 | description = "Backport of pathlib-compatible object wrapper for zip files"
299 | marker = "python_version < \"3.8\""
300 | name = "zipp"
301 | optional = false
302 | python-versions = ">=3.6"
303 | version = "3.1.0"
304 |
305 | [package.extras]
306 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
307 | testing = ["jaraco.itertools", "func-timeout"]
308 |
309 | [metadata]
310 | content-hash = "9a42b53905f9498060a3247fb888b6b75873c85728804b5b42c703ee6fdf770d"
311 | python-versions = "^3.7"
312 |
313 | [metadata.files]
314 | appdirs = [
315 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
316 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
317 | ]
318 | asgiref = [
319 | {file = "asgiref-3.2.10-py3-none-any.whl", hash = "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"},
320 | {file = "asgiref-3.2.10.tar.gz", hash = "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a"},
321 | ]
322 | atomicwrites = [
323 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
324 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
325 | ]
326 | attrs = [
327 | {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
328 | {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
329 | ]
330 | black = [
331 | {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"},
332 | {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"},
333 | ]
334 | click = [
335 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
336 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
337 | ]
338 | colorama = [
339 | {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
340 | {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
341 | ]
342 | django = [
343 | {file = "Django-3.0.8-py3-none-any.whl", hash = "sha256:5457fc953ec560c5521b41fad9e6734a4668b7ba205832191bbdff40ec61073c"},
344 | {file = "Django-3.0.8.tar.gz", hash = "sha256:31a5fbbea5fc71c99e288ec0b2f00302a0a92c44b13ede80b73a6a4d6d205582"},
345 | ]
346 | flake8 = [
347 | {file = "flake8-3.8.3-py2.py3-none-any.whl", hash = "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c"},
348 | {file = "flake8-3.8.3.tar.gz", hash = "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"},
349 | ]
350 | importlib-metadata = [
351 | {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"},
352 | {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
353 | ]
354 | mccabe = [
355 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
356 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
357 | ]
358 | more-itertools = [
359 | {file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"},
360 | {file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"},
361 | ]
362 | packaging = [
363 | {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"},
364 | {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
365 | ]
366 | pathspec = [
367 | {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"},
368 | {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"},
369 | ]
370 | pluggy = [
371 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
372 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
373 | ]
374 | py = [
375 | {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"},
376 | {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"},
377 | ]
378 | pycodestyle = [
379 | {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"},
380 | {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"},
381 | ]
382 | pyflakes = [
383 | {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"},
384 | {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"},
385 | ]
386 | pyparsing = [
387 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
388 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
389 | ]
390 | pytest = [
391 | {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
392 | {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
393 | ]
394 | pytz = [
395 | {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"},
396 | {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"},
397 | ]
398 | regex = [
399 | {file = "regex-2020.7.14-cp27-cp27m-win32.whl", hash = "sha256:e46d13f38cfcbb79bfdb2964b0fe12561fe633caf964a77a5f8d4e45fe5d2ef7"},
400 | {file = "regex-2020.7.14-cp27-cp27m-win_amd64.whl", hash = "sha256:6961548bba529cac7c07af2fd4d527c5b91bb8fe18995fed6044ac22b3d14644"},
401 | {file = "regex-2020.7.14-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c50a724d136ec10d920661f1442e4a8b010a4fe5aebd65e0c2241ea41dbe93dc"},
402 | {file = "regex-2020.7.14-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8a51f2c6d1f884e98846a0a9021ff6861bdb98457879f412fdc2b42d14494067"},
403 | {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9c568495e35599625f7b999774e29e8d6b01a6fb684d77dee1f56d41b11b40cd"},
404 | {file = "regex-2020.7.14-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:51178c738d559a2d1071ce0b0f56e57eb315bcf8f7d4cf127674b533e3101f88"},
405 | {file = "regex-2020.7.14-cp36-cp36m-win32.whl", hash = "sha256:9eddaafb3c48e0900690c1727fba226c4804b8e6127ea409689c3bb492d06de4"},
406 | {file = "regex-2020.7.14-cp36-cp36m-win_amd64.whl", hash = "sha256:14a53646369157baa0499513f96091eb70382eb50b2c82393d17d7ec81b7b85f"},
407 | {file = "regex-2020.7.14-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1269fef3167bb52631ad4fa7dd27bf635d5a0790b8e6222065d42e91bede4162"},
408 | {file = "regex-2020.7.14-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d0a5095d52b90ff38592bbdc2644f17c6d495762edf47d876049cfd2968fbccf"},
409 | {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:4c037fd14c5f4e308b8370b447b469ca10e69427966527edcab07f52d88388f7"},
410 | {file = "regex-2020.7.14-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bc3d98f621898b4a9bc7fecc00513eec8f40b5b83913d74ccb445f037d58cd89"},
411 | {file = "regex-2020.7.14-cp37-cp37m-win32.whl", hash = "sha256:46bac5ca10fb748d6c55843a931855e2727a7a22584f302dd9bb1506e69f83f6"},
412 | {file = "regex-2020.7.14-cp37-cp37m-win_amd64.whl", hash = "sha256:0dc64ee3f33cd7899f79a8d788abfbec168410be356ed9bd30bbd3f0a23a7204"},
413 | {file = "regex-2020.7.14-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5ea81ea3dbd6767873c611687141ec7b06ed8bab43f68fad5b7be184a920dc99"},
414 | {file = "regex-2020.7.14-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bbb332d45b32df41200380fff14712cb6093b61bd142272a10b16778c418e98e"},
415 | {file = "regex-2020.7.14-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c11d6033115dc4887c456565303f540c44197f4fc1a2bfb192224a301534888e"},
416 | {file = "regex-2020.7.14-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:75aaa27aa521a182824d89e5ab0a1d16ca207318a6b65042b046053cfc8ed07a"},
417 | {file = "regex-2020.7.14-cp38-cp38-win32.whl", hash = "sha256:d6cff2276e502b86a25fd10c2a96973fdb45c7a977dca2138d661417f3728341"},
418 | {file = "regex-2020.7.14-cp38-cp38-win_amd64.whl", hash = "sha256:7a2dd66d2d4df34fa82c9dc85657c5e019b87932019947faece7983f2089a840"},
419 | {file = "regex-2020.7.14.tar.gz", hash = "sha256:3a3af27a8d23143c49a3420efe5b3f8cf1a48c6fc8bc6856b03f638abc1833bb"},
420 | ]
421 | six = [
422 | {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
423 | {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
424 | ]
425 | sqlparse = [
426 | {file = "sqlparse-0.3.1-py2.py3-none-any.whl", hash = "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e"},
427 | {file = "sqlparse-0.3.1.tar.gz", hash = "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"},
428 | ]
429 | toml = [
430 | {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
431 | {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
432 | ]
433 | typed-ast = [
434 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
435 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
436 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
437 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
438 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
439 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
440 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
441 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
442 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
443 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
444 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
445 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
446 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
447 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
448 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
449 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
450 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
451 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
452 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
453 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
454 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
455 | ]
456 | wcwidth = [
457 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
458 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
459 | ]
460 | zipp = [
461 | {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"},
462 | {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"},
463 | ]
464 |
--------------------------------------------------------------------------------