├── 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 |
2 |
3 | 8 | 11 |
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 | [![GitHub version](https://badge.fury.io/gh/VLZH%2Fdjango-editorjs.svg)](https://badge.fury.io/gh/VLZH%2Fdjango-editorjs) 4 | [![PyPi downloads](https://img.shields.io/pypi/dm/django-editorjs)](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` - [![npm version](https://badge.fury.io/js/%40editorjs%2Fparagraph.svg)](https://badge.fury.io/js/%40editorjs%2Fparagraph) 11 | - `@editorjs/image` - [![npm version](https://badge.fury.io/js/%40editorjs%2Fimage.svg)](https://badge.fury.io/js/%40editorjs%2Fimage) 12 | - `@editorjs/header` - [![npm version](https://badge.fury.io/js/%40editorjs%2Fheader.svg)](https://badge.fury.io/js/%40editorjs%2Fheader) 13 | - `@editorjs/checklist` - [![npm version](https://badge.fury.io/js/%40editorjs%2Fchecklist.svg)](https://badge.fury.io/js/%40editorjs%2Fchecklist) 14 | - `@editorjs/list` - [![npm version](https://badge.fury.io/js/%40editorjs%2Flist.svg)](https://badge.fury.io/js/%40editorjs%2Flist) 15 | - `@editorjs/quote` - [![npm version](https://badge.fury.io/js/%40editorjs%2Fquote.svg)](https://badge.fury.io/js/%40editorjs%2Fquote) 16 | - `@editorjs/raw` - [![npm version](https://badge.fury.io/js/%40editorjs%2Fraw.svg)](https://badge.fury.io/js/%40editorjs%2Fraw) 17 | - `@editorjs/embed` - [![npm version](https://badge.fury.io/js/%40editorjs%2Fembed.svg)](https://badge.fury.io/js/%40editorjs%2Fembed) 18 | - `@editorjs/delimiter` - [![npm version](https://badge.fury.io/js/%40editorjs%2Fdelimiter.svg)](https://badge.fury.io/js/%40editorjs%2Fdelimiter) 19 | - `@editorjs/warning` - [![npm version](https://badge.fury.io/js/%40editorjs%2Fwarning.svg)](https://badge.fury.io/js/%40editorjs%2Fwarning) 20 | - `@editorjs/link` - [![npm version](https://badge.fury.io/js/%40editorjs%2Flink.svg)](https://badge.fury.io/js/%40editorjs%2Flink) 21 | - `@editorjs/marker` - [![npm version](https://badge.fury.io/js/%40editorjs%2Fmarker.svg)](https://badge.fury.io/js/%40editorjs%2Fmarker) 22 | - `@editorjs/attaches` - [![npm version](https://badge.fury.io/js/%40editorjs%2Fattaches.svg)](https://badge.fury.io/js/%40editorjs%2Fattaches) 23 | - `@editorjs/table` - [![npm version](https://badge.fury.io/js/%40editorjs%2Ftable.svg)](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 | --------------------------------------------------------------------------------