Full Code of 2ik/django-editorjs-fields for AI

dev 5af2eb1f38bd cached
37 files
61.4 KB
14.7k tokens
65 symbols
1 requests
Download .txt
Repository: 2ik/django-editorjs-fields
Branch: dev
Commit: 5af2eb1f38bd
Files: 37
Total size: 61.4 KB

Directory structure:
gitextract_wf8k7gqy/

├── .gitignore
├── .pylintrc
├── LICENSE
├── README.md
├── django_editorjs_fields/
│   ├── __init__.py
│   ├── config.py
│   ├── fields.py
│   ├── static/
│   │   └── django-editorjs-fields/
│   │       ├── css/
│   │       │   └── django-editorjs-fields.css
│   │       └── js/
│   │           └── django-editorjs-fields.js
│   ├── templates/
│   │   └── django-editorjs-fields/
│   │       └── widget.html
│   ├── templatetags/
│   │   ├── __init__.py
│   │   └── editorjs.py
│   ├── urls.py
│   ├── utils.py
│   ├── views.py
│   └── widgets.py
├── example/
│   ├── .gitignore
│   ├── blog/
│   │   ├── __init__.py
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── forms.py
│   │   ├── migrations/
│   │   │   ├── 0001_initial.py
│   │   │   ├── 0002_comment.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── templates/
│   │   │   └── blog/
│   │   │       ├── post_update.html
│   │   │       └── post_view.html
│   │   ├── tests.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── example/
│   │   ├── __init__.py
│   │   ├── asgi.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── manage.py
└── pyproject.toml

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.DS_Store
.vscode
.idea

venv/
.venv/
dist/
__pycache__

#egg's specific
*.egg-info

db.sqlite3


================================================
FILE: .pylintrc
================================================
[MASTER]
disable=
    C0114, # missing-module-docstring
    C0115,
    C0116,


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 Ilya Kotlyakov

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Editor.js for Django

Django plugin for using [Editor.js](https://editorjs.io/)

> This plugin works fine with JSONField in Django >= 3.1

[![Django Editor.js](https://i.ibb.co/r6xt4HJ/image.png)](https://github.com/2ik/django-editorjs-fields)

[![Python versions](https://img.shields.io/pypi/pyversions/django-editorjs-fields)](https://pypi.org/project/django-editorjs-fields/)
[![Python versions](https://img.shields.io/pypi/djversions/django-editorjs-fields)](https://pypi.org/project/django-editorjs-fields/)
[![Downloads](https://static.pepy.tech/personalized-badge/django-editorjs-fields?period=total&units=international_system&left_color=grey&right_color=brightgreen&left_text=Downloads)](https://pepy.tech/project/django-editorjs-fields)

## Installation

```bash
pip install django-editorjs-fields
```

Add `django_editorjs_fields` to `INSTALLED_APPS` in `settings.py` for your project:

```python
# settings.py
INSTALLED_APPS = [
    ...
    'django_editorjs_fields',
]
```

## Upgrade

```bash
pip install django-editorjs-fields --upgrade
python manage.py collectstatic  # upgrade js and css files
```

## Usage

Add code in your model

```python
# models.py
from django.db import models
from django_editorjs_fields import EditorJsJSONField  # Django >= 3.1
from django_editorjs_fields import EditorJsTextField


class Post(models.Model):
    body_default = models.TextField()
    body_editorjs = EditorJsJSONField()  # Django >= 3.1
    body_editorjs_text = EditorJsTextField()

```

#### New in version 0.2.1. Django Templates support

```html
<!-- template.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    {% load editorjs %}
    {{ post.body_default }}
    {{ post.body_editorjs | editorjs}}
    {{ post.body_editorjs_text | editorjs}}
  </body>
</html>
```

## Additionally

You can add custom Editor.js plugins and configs ([List plugins](https://github.com/editor-js/awesome-editorjs))

Example custom field in models.py

```python
# models.py
from django.db import models
from django_editorjs_fields import EditorJsJSONField


class Post(models.Model):
    body_editorjs_custom = EditorJsJSONField(
        plugins=[
            "@editorjs/image",
            "@editorjs/header",
            "editorjs-github-gist-plugin",
            "@editorjs/code@2.6.0",  # version allowed :)
            "@editorjs/list@latest",
            "@editorjs/inline-code",
            "@editorjs/table",
        ],
        tools={
            "Gist": {
                "class": "Gist"  # Include the plugin class. See docs Editor.js plugins
            },
            "Image": {
                "config": {
                    "endpoints": {
                        "byFile": "/editorjs/image_upload/"  # Your custom backend file uploader endpoint
                    }
                }
            }
        },
        i18n={
            'messages': {
                'blockTunes': {
                    "delete": {
                        "Delete": "Удалить"
                    },
                    "moveUp": {
                        "Move up": "Переместить вверх"
                    },
                    "moveDown": {
                        "Move down": "Переместить вниз"
                    }
                }
            },
        }
        null=True,
        blank=True
    )

```

**django-editorjs-fields** support this list of Editor.js plugins by default:
<a name="plugins"></a>

<details>
    <summary>EDITORJS_DEFAULT_PLUGINS</summary>

```python
EDITORJS_DEFAULT_PLUGINS = (
    '@editorjs/paragraph',
    '@editorjs/image',
    '@editorjs/header',
    '@editorjs/list',
    '@editorjs/checklist',
    '@editorjs/quote',
    '@editorjs/raw',
    '@editorjs/code',
    '@editorjs/inline-code',
    '@editorjs/embed',
    '@editorjs/delimiter',
    '@editorjs/warning',
    '@editorjs/link',
    '@editorjs/marker',
    '@editorjs/table',
)
```

</details>

<details>
    <summary>EDITORJS_DEFAULT_CONFIG_TOOLS</summary>

```python
EDITORJS_DEFAULT_CONFIG_TOOLS = {
    'Image': {
        'class': 'ImageTool',
        'inlineToolbar': True,
        "config": {
            "endpoints": {
                "byFile": reverse_lazy('editorjs_image_upload'),
                "byUrl": reverse_lazy('editorjs_image_by_url')
            }
        },
    },
    'Header': {
        'class': 'Header',
        'inlineToolbar': True,
        'config': {
            'placeholder': 'Enter a header',
            'levels': [2, 3, 4],
            'defaultLevel': 2,
        }
    },
    'Checklist': {'class': 'Checklist', 'inlineToolbar': True},
    'List': {'class': 'List', 'inlineToolbar': True},
    'Quote': {'class': 'Quote', 'inlineToolbar': True},
    'Raw': {'class': 'RawTool'},
    'Code': {'class': 'CodeTool'},
    'InlineCode': {'class': 'InlineCode'},
    'Embed': {'class': 'Embed'},
    'Delimiter': {'class': 'Delimiter'},
    'Warning': {'class': 'Warning', 'inlineToolbar': True},
    'LinkTool': {
        'class': 'LinkTool',
        'config': {
            'endpoint': reverse_lazy('editorjs_linktool'),
        }
    },
    'Marker': {'class': 'Marker', 'inlineToolbar': True},
    'Table': {'class': 'Table', 'inlineToolbar': True},
}
```

</details>

`EditorJsJSONField` accepts all the arguments of `JSONField` class.

`EditorJsTextField` accepts all the arguments of `TextField` class.

Additionally, it includes arguments such as:

| Args            | Description                                                                                                                                  | Default                         |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| `plugins`       | List plugins Editor.js                                                                                                                       | `EDITORJS_DEFAULT_PLUGINS`      |
| `tools`         | Map of Tools to use. Set config `tools` for Editor.js [See docs](https://editorjs.io/configuration#passing-saved-data)                       | `EDITORJS_DEFAULT_CONFIG_TOOLS` |
| `use_editor_js` | Enables or disables the Editor.js plugin for the field                                                                                       | `True`                          |
| `autofocus`     | If true, set caret at the first Block after Editor is ready                                                                                  | `False`                         |
| `hideToolbar`   | If true, toolbar won't be shown                                                                                                              | `False`                         |
| `inlineToolbar` | Defines default toolbar for all tools.                                                                                                       | `True`                          |
| `readOnly`      | Enable read-only mode                                                                                                                        | `False`                         |
| `minHeight`     | Height of Editor's bottom area that allows to set focus on the last Block                                                                    | `300`                           |
| `logLevel`      | Editors log level (how many logs you want to see)                                                                                            | `ERROR`                         |
| `placeholder`   | First Block placeholder                                                                                                                      | `Type text...`                  |
| `defaultBlock`  | This Tool will be used as default. Name should be equal to one of Tool`s keys of passed tools. If not specified, Paragraph Tool will be used | `paragraph`                     |
| `i18n`          | Internalization config                                                                                                                       | `{}`                            |
| `sanitizer`     | Define default sanitizer configuration                                                                                                       | `{ p: true, b: true, a: true }` |

## Image uploads

If you want to upload images to the editor then add `django_editorjs_fields.urls` to `urls.py` for your project with `DEBUG=True`:

```python
# urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    ...
    path('editorjs/', include('django_editorjs_fields.urls')),
    ...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
```

In production `DEBUG=False` (use nginx to display images):

```python
# urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    ...
    path('editorjs/', include('django_editorjs_fields.urls')),
    ...
]
```

See an example of how you can work with the plugin [here](https://github.com/2ik/django-editorjs-fields/blob/main/example)

## Forms

```python
from django import forms
from django_editorjs_fields import EditorJsWidget


class TestForm(forms.ModelForm):
    class Meta:
        model = Post
        exclude = []
        widgets = {
            'body_editorjs': EditorJsWidget(config={'minHeight': 100}),
            'body_editorjs_text': EditorJsWidget(plugins=["@editorjs/image", "@editorjs/header"])
        }
```

## Theme

### Default Theme

![image](https://user-images.githubusercontent.com/6692517/124242133-7a7dad00-db2d-11eb-812f-84a5c44e88c9.png)

### Dark Theme

plugin use css property [prefers-color-scheme](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) to define a dark theme in browser

![image](https://user-images.githubusercontent.com/6692517/124240864-3dfd8180-db2c-11eb-85c1-21f0faf41775.png)

## Configure

The application can be configured by editing the project's `settings.py`
file.

| Key                               | Description                                                            | Default                                                                                                                                                                                                                                                     | Type                                                                                                                     |
| --------------------------------- | ---------------------------------------------------------------------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------------------------------------------------------------------------------------------------------------------ |
| `EDITORJS_DEFAULT_PLUGINS`        | List of plugins names Editor.js from npm                               | [See above](#plugins)                                                                                                                                                                                                                                       | `list[str]`, `tuple[str]`                                                                                                |
| `EDITORJS_DEFAULT_CONFIG_TOOLS`   | Map of Tools to use                                                    | [See above](#plugins)                                                                                                                                                                                                                                       | `dict[str, dict]`                                                                                                        |
| `EDITORJS_IMAGE_UPLOAD_PATH`      | Path uploads images                                                    | `uploads/images/`                                                                                                                                                                                                                                           | `str`                                                                                                                    |
| `EDITORJS_IMAGE_UPLOAD_PATH_DATE` | Subdirectories                                                         | `%Y/%m/`                                                                                                                                                                                                                                                    | `str`                                                                                                                    |
| `EDITORJS_IMAGE_NAME_ORIGINAL`    | To use the original name of the image file?                            | `False`                                                                                                                                                                                                                                                     | `bool`                                                                                                                   |
| `EDITORJS_IMAGE_NAME`             | Image file name. Ignored when `EDITORJS_IMAGE_NAME_ORIGINAL` is `True` | `token_urlsafe(8)`                                                                                                                                                                                                                                          | `callable(filename: str, file: InMemoryUploadedFile)` ([docs](https://docs.djangoproject.com/en/3.0/ref/files/uploads/)) |
| `EDITORJS_EMBED_HOSTNAME_ALLOWED` | List of allowed hostname for embed                                     | `('player.vimeo.com','www.youtube.com','coub.com','vine.co','imgur.com','gfycat.com','player.twitch.tv','player.twitch.tv','music.yandex.ru','codepen.io','www.instagram.com','twitframe.com','assets.pinterest.com','www.facebook.com','www.aparat.com'),` | `list[str]`, `tuple[str]`                                                                                                |
| `EDITORJS_VERSION`                | Version Editor.js                                                      | `2.25.0`                                                                                                                                                                                                                                                    | `str`                                                                                                                    |

For `EDITORJS_IMAGE_NAME` was used `from secrets import token_urlsafe`

## Support and updates

Use github issues https://github.com/2ik/django-editorjs-fields/issues


================================================
FILE: django_editorjs_fields/__init__.py
================================================
__version__ = "0.2.7"

from .fields import EditorJsJSONField, EditorJsTextField
from .widgets import EditorJsWidget

__all__ = ("EditorJsTextField", "EditorJsJSONField", "EditorJsWidget", "__version__")


================================================
FILE: django_editorjs_fields/config.py
================================================
from secrets import token_urlsafe

from django.conf import settings
from django.urls import reverse_lazy

DEBUG = getattr(settings, "DEBUG", False)

VERSION = getattr(settings, "EDITORJS_VERSION", '2.25.0')

# ATTACHMENT_REQUIRE_AUTHENTICATION = str(
#     getattr(settings, "EDITORJS_ATTACHMENT_REQUIRE_AUTHENTICATION", True)
# )

EMBED_HOSTNAME_ALLOWED = str(
    getattr(settings, "EDITORJS_EMBED_HOSTNAME_ALLOWED", (
        'player.vimeo.com',
        'www.youtube.com',
        'coub.com',
        'vine.co',
        'imgur.com',
        'gfycat.com',
        'player.twitch.tv',
        'player.twitch.tv',
        'music.yandex.ru',
        'codepen.io',
        'www.instagram.com',
        'twitframe.com',
        'assets.pinterest.com',
        'www.facebook.com',
        'www.aparat.com',
    ))
)

IMAGE_UPLOAD_PATH = str(
    getattr(settings, "EDITORJS_IMAGE_UPLOAD_PATH", 'uploads/images/')
)

IMAGE_UPLOAD_PATH_DATE = getattr(
    settings, "EDITORJS_IMAGE_UPLOAD_PATH_DATE", '%Y/%m/')

IMAGE_NAME_ORIGINAL = getattr(
    settings, "EDITORJS_IMAGE_NAME_ORIGINAL", False)

IMAGE_NAME = getattr(
    settings, "EDITORJS_IMAGE_NAME", lambda **_: token_urlsafe(8))

PLUGINS = getattr(
    settings, "EDITORJS_DEFAULT_PLUGINS", (
        '@editorjs/paragraph',
        '@editorjs/image',
        '@editorjs/header',
        '@editorjs/list',
        '@editorjs/checklist',
        '@editorjs/quote',
        '@editorjs/raw',
        '@editorjs/code',
        '@editorjs/inline-code',
        '@editorjs/embed',
        '@editorjs/delimiter',
        '@editorjs/warning',
        '@editorjs/link',
        '@editorjs/marker',
        '@editorjs/table',
    )
)

CONFIG_TOOLS = getattr(
    settings, "EDITORJS_DEFAULT_CONFIG_TOOLS", {
        'Image': {
            'class': 'ImageTool',
            'inlineToolbar': True,
            "config": {
                "endpoints": {
                    "byFile": reverse_lazy('editorjs_image_upload'),
                    "byUrl": reverse_lazy('editorjs_image_by_url')
                }
            },
        },
        'Header': {
            'class': 'Header',
            'inlineToolbar': True,
            'config': {
                'placeholder': 'Enter a header',
                'levels': [2, 3, 4],
                'defaultLevel': 2,
            }
        },
        'Checklist': {'class': 'Checklist', 'inlineToolbar': True},
        'List': {'class': 'List', 'inlineToolbar': True},
        'Quote': {'class': 'Quote', 'inlineToolbar': True},
        'Raw': {'class': 'RawTool'},
        'Code': {'class': 'CodeTool'},
        'InlineCode': {'class': 'InlineCode'},
        'Embed': {'class': 'Embed'},
        'Delimiter': {'class': 'Delimiter'},
        'Warning': {'class': 'Warning', 'inlineToolbar': True},
        'LinkTool': {
            'class': 'LinkTool',
            'config': {
                # Backend endpoint for url data fetching
                'endpoint': reverse_lazy('editorjs_linktool'),
            }
        },
        'Marker': {'class': 'Marker', 'inlineToolbar': True},
        'Table': {'class': 'Table', 'inlineToolbar': True},
    }
)

PLUGINS_KEYS = {
    '@editorjs/image': 'Image',
    '@editorjs/header': 'Header',
    '@editorjs/checklist': 'Checklist',
    '@editorjs/list': 'List',
    '@editorjs/quote': 'Quote',
    '@editorjs/raw': 'Raw',
    '@editorjs/code': 'Code',
    '@editorjs/inline-code': 'InlineCode',
    '@editorjs/embed': 'Embed',
    '@editorjs/delimiter': 'Delimiter',
    '@editorjs/warning': 'Warning',
    '@editorjs/link': 'LinkTool',
    '@editorjs/marker': 'Marker',
    '@editorjs/table': 'Table',
}


================================================
FILE: django_editorjs_fields/fields.py
================================================
import json

from django.core import checks
from django.core.exceptions import ValidationError
from django.db.models import Field
from django.forms import Textarea

from .config import DEBUG, EMBED_HOSTNAME_ALLOWED
from .utils import get_hostname_from_url
from .widgets import EditorJsWidget

try:
    # pylint: disable=ungrouped-imports
    from django.db.models import JSONField  # Django >= 3.1
except ImportError:
    HAS_JSONFIELD = False
else:
    HAS_JSONFIELD = True

__all__ = ['EditorJsTextField', 'EditorJsJSONField']


class FieldMixin(Field):
    def get_internal_type(self):
        return 'TextField'


class EditorJsFieldMixin:
    def __init__(self, plugins, tools, **kwargs):
        self.use_editorjs = kwargs.pop('use_editorjs', True)
        self.plugins = plugins
        self.tools = tools
        self.config = {}

        if 'autofocus' in kwargs:
            self.config['autofocus'] = kwargs.pop('autofocus')
        if 'hideToolbar' in kwargs:
            self.config['hideToolbar'] = kwargs.pop('hideToolbar')
        if 'inlineToolbar' in kwargs:
            self.config['inlineToolbar'] = kwargs.pop('inlineToolbar')
        if 'readOnly' in kwargs:
            self.config['readOnly'] = kwargs.pop('readOnly')
        if 'minHeight' in kwargs:
            self.config['minHeight'] = kwargs.pop('minHeight')
        if 'logLevel' in kwargs:
            self.config['logLevel'] = kwargs.pop('logLevel')
        if 'placeholder' in kwargs:
            self.config['placeholder'] = kwargs.pop('placeholder')
        if 'defaultBlock' in kwargs:
            self.config['defaultBlock'] = kwargs.pop('defaultBlock')
        if 'sanitizer' in kwargs:
            self.config['sanitizer'] = kwargs.pop('sanitizer')
        if 'i18n' in kwargs:
            self.config['i18n'] = kwargs.pop('i18n')

        super().__init__(**kwargs)

    def validate_embed(self, value):
        for item in value.get('blocks', []):
            type = item.get('type', '').lower()
            if type == 'embed':
                embed = item['data']['embed']
                hostname = get_hostname_from_url(embed)

                if hostname not in EMBED_HOSTNAME_ALLOWED:
                    raise ValidationError(
                        hostname + ' is not allowed in EDITORJS_EMBED_HOSTNAME_ALLOWED')

    def clean(self, value, model_instance):
        if value and value != 'null':
            if not isinstance(value, dict):
                try:
                    value = json.loads(value)
                except ValueError:
                    pass
                except TypeError:
                    pass
                else:
                    self.validate_embed(value)
                    value = json.dumps(value)
            else:
                self.validate_embed(value)

        return super().clean(value, model_instance)

    def formfield(self, **kwargs):
        if self.use_editorjs:
            kwargs['widget'] = EditorJsWidget(
                self.plugins, self.tools, self.config, **kwargs)
        else:
            kwargs['widget'] = Textarea(**kwargs)

        # pylint: disable=no-member
        return super().formfield(**kwargs)


class EditorJsTextField(EditorJsFieldMixin, FieldMixin):
    # pylint: disable=useless-super-delegation
    def __init__(self, plugins=None, tools=None, **kwargs):
        super().__init__(plugins, tools, **kwargs)

    def clean(self, value, model_instance):
        if value == 'null':
            value = None

        return super().clean(value, model_instance)


class EditorJsJSONField(EditorJsFieldMixin, JSONField if HAS_JSONFIELD else FieldMixin):
    # pylint: disable=useless-super-delegation
    def __init__(self, plugins=None, tools=None, **kwargs):
        super().__init__(plugins, tools, **kwargs)

    def check(self, **kwargs):
        errors = super().check(**kwargs)
        errors.extend(self._check_supported_json())
        return errors

    def _check_supported_json(self):
        if not HAS_JSONFIELD and DEBUG:
            return [
                checks.Warning(
                    'You don\'t support JSONField, please use'
                    'EditorJsTextField instead of EditorJsJSONField',
                    obj=self,
                )
            ]
        return []


================================================
FILE: django_editorjs_fields/static/django-editorjs-fields/css/django-editorjs-fields.css
================================================
div[data-editorjs-holder] {
	display: inline-block;
	width: 100%;
	max-width: 750px;
	padding: 1.5em 1em;
	border: 1px solid #ccc;
	border-radius: 4px;
	background-color: #fcfeff;
}

.codex-editor .ce-rawtool__textarea {
	background-color: #010e15;
	color: #ccced2;
}

.codex-editor .cdx-list {
	margin: 0;
	padding-left: 32px;
	outline: none;
}

.codex-editor .cdx-list__item {
	padding: 8px;
	line-height: 1.4em;
	list-style: inherit;
}

.codex-editor .cdx-checklist__item-text {
	align-self: center;
}

.codex-editor .ce-header {
	padding: 1em 0;
	margin: 0;
	margin-bottom: -1em;
	line-height: 1.4em;
	outline: none;
	background: transparent;
	color: #000;
	font-weight: 800;
	text-transform: initial;
}

.codex-editor h2.ce-header {
	font-size: 1.5em;
}

.codex-editor h3.ce-header {
	font-size: 1.3em;
}

.codex-editor h4.ce-header {
	font-size: 1.1em;
}

.codex-editor blockquote {
	border: initial;
	margin: initial;
	color: initial;
	font-size: inherit;
}

.codex-editor .wrapper .cdx-button {
	display: none;
}

.codex-editor .link-tool__progress {
	float: initial;
	width: 100%;
	line-height: initial;
	padding: initial;
}

@media (max-width: 767px) {
	div[data-editorjs-holder] {
		width: auto;
	}

	.aligned .form-row,
	.aligned .form-row>div {
		flex-direction: column;
	}
}

@media (prefers-color-scheme: dark) {

	.tc-popover,
	.tc-wrap {
		--color-border: #4c6b7a !important;
		--color-background: #264b5d !important;
		--color-background-hover: #162a34 !important;
		--color-text-secondary: #fbfbfb !important;
	}

	.change-form #container #content-main div[data-editorjs-holder] {
		border: 1px solid var(--border-color);
		background-color: var(--body-bg);
	}

	.change-form #container #content-main .link-tool__input {
		color: var(--primary);
	}

	.change-form #container #content-main .codex-editor .ce-header,
	.change-form #container #content-main .codex-editor blockquote {
		color: var(--body-fg);
	}

	.change-form #container #content-main .codex-editor .ce-rawtool__textarea {
		background-color: #264b5d;
		color: #fbfbfb;
	}

	.change-form #container #content-main .cdx-marker {
		background: #fff03b;
	}

	.change-form #container #content-main .ce-inline-toolbar {
		color: #000;
	}

	.change-form #container #content-main ::-moz-selection,
	.change-form #container #content-main ::selection {
		color: #fff;
		background: #616161;
	}

	.change-form #container #content-main .ce-block--selected .ce-block__content {
		background: #426b8a;
	}

	.change-form #container #content-main .codex-editor svg {
		fill: #fff
	}

	.change-form #container #content-main .ce-toolbar__plus:hover,
	.change-form #container #content-main .ce-toolbar__settings-btn:hover {
		background-color: #264b5d;
	}

	.change-form #container #content-main .ce-popover__item-icon,
	.change-form #container #content-main .ce-conversion-tool__icon {
		background: #2fa9a9;
	}

	.change-form #container #content-main .ce-popover,
	.change-form #container #content-main .ce-settings,
	.change-form #container #content-main .ce-inline-toolbar,
	.change-form #container #content-main .ce-conversion-toolbar {
		background-color: #264b5d;
		border-color: #4c6b7a;
		color: #fbfbfb
	}

	.change-form #container #content-main .ce-popover__item:hover,
	.change-form #container #content-main .ce-settings__button:hover,
	.change-form #container #content-main .cdx-settings-button:hover,
	.change-form #container #content-main .ce-inline-toolbar__dropdown:hover,
	.change-form #container #content-main .ce-inline-tool:hover,
	.change-form #container #content-main .ce-conversion-tool:hover {
		background-color: #162a34;
		color: #fff
	}
}

================================================
FILE: django_editorjs_fields/static/django-editorjs-fields/js/django-editorjs-fields.js
================================================
;(function () {
  var pluginName = "django_editorjs_fields"
  var pluginHelp =
    "Write about the issue here: https://github.com/2ik/django-editorjs-fields/issues"

  function initEditorJsPlugin() {
    var fields = document.querySelectorAll("[data-editorjs-textarea]")

    for (let i = 0; i < fields.length; i++) {
      initEditorJsField(fields[i])
    }
  }

  function initEditorJsField(textarea) {
    if (!textarea) {
      logError("bad textarea")
      return false
    }

    var id = textarea.getAttribute("id")

    if (!id) {
      logError("empty field 'id'")
      holder.remove()
      return false
    }

    var holder = document.getElementById(id + "_editorjs_holder")

    if (!holder) {
      logError("holder not found")
      holder.remove()
      return false
    }

    if (id.indexOf("__prefix__") !== -1) return

    try {
      var config = JSON.parse(textarea.getAttribute("data-config"))
    } catch (error) {
      console.error(error)
      logError(
        "invalid 'data-config' on field: " + id + " . Clear the field manually"
      )
      holder.remove()
      return false
    }

    var text = textarea.value.trim()

    if (text) {
      try {
        text = JSON.parse(text)
      } catch (error) {
        console.error(error)
        logError(
          "invalid json data from the database. Clear the field manually"
        )
        holder.remove()
        return false
      }
    }

    textarea.style.display = "none" // remove old textarea

    var editorConfig = {
      id: id,
      holder: holder,
      data: text,
    }

    if ("tools" in config) {
      // set config
      var tools = config.tools

      for (var plugin in tools) {
        var cls = tools[plugin].class

        if (cls && window[cls] != undefined) {
          tools[plugin].class = eval(cls)
          continue
        }

        delete tools[plugin]
        logError("[" + plugin + "] Class " + cls + " Not Found")
      }

      editorConfig.tools = tools
    }

    if ("autofocus" in config) {
      editorConfig.autofocus = !!config.autofocus
    }

    if ("hideToolbar" in config) {
      editorConfig.hideToolbar = !!config.hideToolbar
    }

    if ("inlineToolbar" in config) {
      editorConfig.inlineToolbar = config.inlineToolbar
    }

    if ("readOnly" in config) {
      editorConfig.readOnly = config.readOnly
    }

    if ("minHeight" in config) {
      editorConfig.minHeight = config.minHeight || 300
    }

    if ("logLevel" in config) {
      editorConfig.logLevel = config.logLevel || "VERBOSE"
    } else {
      editorConfig.logLevel = "ERROR"
    }

    if ("placeholder" in config) {
      editorConfig.placeholder = config.placeholder || "Type text..."
    } else {
      editorConfig.placeholder = "Type text..."
    }

    if ("defaultBlock" in config) {
      editorConfig.defaultBlock = config.defaultBlock || "paragraph"
    }

    if ("sanitizer" in config) {
      editorConfig.sanitizer = config.sanitizer || {
        p: true,
        b: true,
        a: true,
      }
    }

    if ("i18n" in config) {
      editorConfig.i18n = config.i18n || {}
    }

    editorConfig.onChange = function () {
      editor
        .save()
        .then(function (data) {
          if (data.blocks.length) {
            textarea.value = JSON.stringify(data)
          } else {
            textarea.value = 'null'
          }
        })
        .catch(function (error) {
          console.log("save error: ", error)
        })
    }
    var editor = new EditorJS(editorConfig)
    holder.setAttribute("data-processed", 1)
  }

  function logError(msg) {
    console.error(pluginName + " - " + msg + ". " + pluginHelp)
  }

  addEventListener("DOMContentLoaded", initEditorJsPlugin)

  // Event
  if (typeof django === "object" && django.jQuery) {
    django.jQuery(document).on("formset:added", function (event, $row) {
      var areas = $row.find("[data-editorjs-textarea]").get()

      if (areas) {
        for (let i = 0; i < areas.length; i++) {
          initEditorJsField(areas[i])
        }
      }
    })
  }
})()


================================================
FILE: django_editorjs_fields/templates/django-editorjs-fields/widget.html
================================================
<textarea data-editorjs-textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %} data-config='{{ widget.config }}'>{% if widget.value %}{{ widget.value }}{% endif %}</textarea>
<div data-editorjs-holder id="{{ widget.attrs.id }}_editorjs_holder" class="editorjs-holder"></div>

================================================
FILE: django_editorjs_fields/templatetags/__init__.py
================================================


================================================
FILE: django_editorjs_fields/templatetags/editorjs.py
================================================
import json

from django import template
from django.utils.safestring import mark_safe

register = template.Library()


def generate_paragraph(data):
    text = data.get('text').replace('&nbsp;', ' ')
    return f'<p>{text}</p>'


def generate_list(data):
    list_li = ''.join([f'<li>{item}</li>' for item in data.get('items')])
    tag = 'ol' if data.get('style') == 'ordered' else 'ul'
    return f'<{tag}>{list_li}</{tag}>'


def generate_header(data):
    text = data.get('text').replace('&nbsp;', ' ')
    level = data.get('level')
    return f'<h{level}>{text}</h{level}>'


def generate_image(data):
    url = data.get('file', {}).get('url')
    caption = data.get('caption')
    classes = []

    if data.get('stretched'):
        classes.append('stretched')
    if data.get('withBorder'):
        classes.append('withBorder')
    if data.get('withBackground'):
        classes.append('withBackground')

    classes = ' '.join(classes)

    return f'<img src="{url}" alt="{caption}" class="{classes}" />'


def generate_delimiter():
    return '<div class="delimiter"></div>'


def generate_table(data):
    rows = data.get('content', [])
    table = ''

    for row in rows:
        table += '<tr>'
        for cell in row:
            table += f'<td>{cell}</td>'
        table += '</tr>'

    return f'<table>{table}</table>'


def generate_warning(data):
    title, message = data.get('title'), data.get('message')

    if title:
        title = f'<div class="alert__title">{title}</div>'
    if message:
        message = f'<div class="alert__message">{message}</div>'

    return f'<div class="alert">{title}{message}</div>'


def generate_quote(data):
    alignment = data.get('alignment')
    caption = data.get('caption')
    text = data.get('text')

    if caption:
        caption = f'<cite>{caption}</cite>'

    classes = f'align-{alignment}' if alignment else None

    return f'<blockquote class="{classes}">{text}{caption}</blockquote>'


def generate_code(data):
    code = data.get('code')
    return f'<code class="code">{code}</code>'


def generate_raw(data):
    return data.get('html')


def generate_embed(data):
    service = data.get('service')
    caption = data.get('caption')
    embed = data.get('embed')
    iframe = f'<iframe src="{embed}" allow="autoplay" allowfullscreen="allowfullscreen"></iframe>'

    return f'<div class="embed {service}">{iframe}{caption}</div>'


def generate_link(data):

    link, meta = data.get('link'), data.get('meta')

    if not link or not meta:
        return ''

    title = meta.get('title')
    description = meta.get('description')
    image = meta.get('image')

    wrapper = f'<div class="link-block"><a href="{ link }" target="_blank" rel="nofollow noopener noreferrer">'

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

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

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

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


@register.filter(is_safe=True)
def editorjs(value):
    if not value or value == 'null':
        return ""

    if not isinstance(value, dict):
        try:
            value = json.loads(value)
        except ValueError:
            return value
        except TypeError:
            return value

    html_list = []
    for item in value['blocks']:

        type, data = item.get('type'), item.get('data')
        type = type.lower()

        if type == 'paragraph':
            html_list.append(generate_paragraph(data))
        elif type == 'header':
            html_list.append(generate_header(data))
        elif type == 'list':
            html_list.append(generate_list(data))
        elif type == 'image':
            html_list.append(generate_image(data))
        elif type == 'delimiter':
            html_list.append(generate_delimiter())
        elif type == 'warning':
            html_list.append(generate_warning(data))
        elif type == 'table':
            html_list.append(generate_table(data))
        elif type == 'code':
            html_list.append(generate_code(data))
        elif type == 'raw':
            html_list.append(generate_raw(data))
        elif type == 'embed':
            html_list.append(generate_embed(data))
        elif type == 'quote':
            html_list.append(generate_quote(data))
        elif type == 'linktool':
            html_list.append(generate_link(data))

    return mark_safe(''.join(html_list))


================================================
FILE: django_editorjs_fields/urls.py
================================================
from django.contrib.admin.views.decorators import staff_member_required
from django.urls import path

from .views import ImageByUrl, ImageUploadView, LinkToolView

urlpatterns = [
    path(
        'image_upload/',
        staff_member_required(ImageUploadView.as_view()),
        name='editorjs_image_upload',
    ),
    path(
        'linktool/',
        staff_member_required(LinkToolView.as_view()),
        name='editorjs_linktool',
    ),
    path(
        'image_by_url/',
        ImageByUrl.as_view(),
        name='editorjs_image_by_url',
    ),
]


================================================
FILE: django_editorjs_fields/utils.py
================================================
import urllib.parse

from django.conf import settings
from django.utils.module_loading import import_string


def get_storage_class():
    return import_string(
        getattr(
            settings,
            'EDITORJS_STORAGE_BACKEND',
            'django.core.files.storage.DefaultStorage',
        )
    )()


def get_hostname_from_url(url):
    obj_url = urllib.parse.urlsplit(url)
    return obj_url.hostname


storage = get_storage_class()


================================================
FILE: django_editorjs_fields/views.py
================================================
import json
import logging
import os
from datetime import datetime
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen

# from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.http import JsonResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt

from .config import (IMAGE_NAME, IMAGE_NAME_ORIGINAL, IMAGE_UPLOAD_PATH,
                     IMAGE_UPLOAD_PATH_DATE)
from .utils import storage

LOGGER = logging.getLogger('django_editorjs_fields')


class ImageUploadView(View):
    http_method_names = ["post"]
    # http_method_names = ["post", "delete"]

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

    def post(self, request):
        if 'image' in request.FILES:
            the_file = request.FILES['image']
            allowed_types = [
                'image/jpeg',
                'image/jpg',
                'image/pjpeg',
                'image/x-png',
                'image/png',
                'image/webp',
                'image/gif',
            ]
            if the_file.content_type not in allowed_types:
                return JsonResponse(
                    {'success': 0, 'message': 'You can only upload images.'}
                )

            filename, extension = os.path.splitext(the_file.name)

            if IMAGE_NAME_ORIGINAL is False:
                filename = IMAGE_NAME(filename=filename, file=the_file)

            filename += extension

            upload_path = IMAGE_UPLOAD_PATH

            if IMAGE_UPLOAD_PATH_DATE:
                upload_path += datetime.now().strftime(IMAGE_UPLOAD_PATH_DATE)

            path = storage.save(
                os.path.join(upload_path, filename), the_file
            )
            link = storage.url(path)

            return JsonResponse({'success': 1, 'file': {"url": link}})
        return JsonResponse({'success': 0})

    # def delete(self, request):
    #     path_file = request.GET.get('pathFile')

    #     if not path_file:
    #         return JsonResponse({'success': 0, 'message': 'Parameter "pathFile" Not Found'})

    #     base_dir = getattr(settings, "BASE_DIR", '')
    #     path_file = f'{base_dir}{path_file}'

    #     if not os.path.isfile(path_file):
    #         return JsonResponse({'success': 0, 'message': 'File Not Found'})

    #     os.remove(path_file)

    #     return JsonResponse({'success': 1})


class LinkToolView(View):
    http_method_names = ["get"]

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

    def get(self, request):

        url = request.GET.get('url', '')

        LOGGER.debug('Starting to get meta for: %s', url)

        if not any([url.startswith(s) for s in ('http://', 'https://')]):
            LOGGER.debug('Adding the http protocol to the link: %s', url)
            url = 'http://' + url

        validate = URLValidator(schemes=['http', 'https'])

        try:
            validate(url)
        except ValidationError as e:
            LOGGER.error(e)
        else:
            try:
                LOGGER.debug('Let\'s try to get meta from: %s', url)

                full_url = 'https://api.microlink.io/?' + \
                    urlencode({'url': url})

                req = Request(full_url, headers={
                    'User-Agent': request.META.get('HTTP_USER_AGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)')
                })
                res = urlopen(req)
            except HTTPError as e:
                LOGGER.error('The server couldn\'t fulfill the request.')
                LOGGER.error('Error code: %s %s', e.code, e.msg)
            except URLError as e:
                LOGGER.error('We failed to reach a server. url: %s', url)
                LOGGER.error('Reason: %s', e.reason)
            else:
                res_body = res.read()
                res_json = json.loads(res_body.decode("utf-8"))

                if 'success' in res_json.get('status'):
                    data = res_json.get('data')

                    if data:
                        LOGGER.debug('Response meta: %s', data)
                        meta = {}
                        meta['title'] = data.get('title')
                        meta['description'] = data.get('description')
                        meta['image'] = data.get('image')

                        return JsonResponse({
                            'success': 1,
                            'link': data.get('url', url),
                            'meta': meta
                        })

        return JsonResponse({'success': 0})


class ImageByUrl(View):
    http_method_names = ["post"]

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

    def post(self, request):
        body = json.loads(request.body.decode())
        if 'url' in body:
            return JsonResponse({'success': 1, 'file': {"url": body['url']}})
        return JsonResponse({'success': 0})


================================================
FILE: django_editorjs_fields/widgets.py
================================================
import json

from django.core.serializers.json import DjangoJSONEncoder
from django.forms import Media, widgets
from django.forms.renderers import get_default_renderer
from django.utils.encoding import force_str
from django.utils.functional import Promise, cached_property
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe

from .config import CONFIG_TOOLS, PLUGINS, PLUGINS_KEYS, VERSION


class LazyEncoder(DjangoJSONEncoder):
    def default(self, obj):
        if isinstance(obj, Promise):
            return force_str(obj)
        return super().default(obj)


json_encode = LazyEncoder().encode


class EditorJsWidget(widgets.Textarea):
    def __init__(self, plugins=None, tools=None, config=None, **kwargs):
        self.plugins = plugins
        self.tools = tools
        self.config = config

        # Fix "__init__() got an unexpected keyword argument 'widget'"
        widget = kwargs.pop('widget', None)
        if widget:
            self.plugins = widget.plugins
            self.tools = widget.tools
            self.config = widget.config

        super().__init__(**kwargs)

    def configuration(self):
        tools = {}
        config = self.config or {}

        if self.plugins or self.tools:
            custom_tools = self.tools or {}
            # get name packages without version
            plugins = ['@'.join(p.split('@')[:2])
                       for p in self.plugins or PLUGINS]

            for plugin in plugins:
                plugin_key = PLUGINS_KEYS.get(plugin)

                if not plugin_key:
                    continue

                plugin_tools = custom_tools.get(
                    plugin_key) or CONFIG_TOOLS.get(plugin_key) or {}
                plugin_class = plugin_tools.get('class')

                if plugin_class:

                    tools[plugin_key] = custom_tools.get(
                        plugin_key, CONFIG_TOOLS.get(plugin_key)
                    )

                    tools[plugin_key]['class'] = plugin_class

                    custom_tools.pop(plugin_key, None)

            if custom_tools:
                tools.update(custom_tools)
        else:  # default
            tools.update(CONFIG_TOOLS)

        config.update(tools=tools)
        return config

    @cached_property
    def media(self):
        js_list = [
            '//cdn.jsdelivr.net/npm/@editorjs/editorjs@' + VERSION  # lib
        ]

        plugins = self.plugins or PLUGINS

        if plugins:
            js_list += ['//cdn.jsdelivr.net/npm/' + p for p in plugins]

        js_list.append('django-editorjs-fields/js/django-editorjs-fields.js')

        return Media(
            js=js_list,
            css={
                'all': [
                    'django-editorjs-fields/css/django-editorjs-fields.css'
                ]
            },
        )

    def render(self, name, value, attrs=None, renderer=None):
        if value is None:
            value = ''

        if renderer is None:
            renderer = get_default_renderer()

        return mark_safe(renderer.render("django-editorjs-fields/widget.html", {
            'widget': {
                'name': name,
                'value': conditional_escape(force_str(value)),
                'attrs': self.build_attrs(self.attrs, attrs),
                'config': json_encode(self.configuration()),
            }
        }))


================================================
FILE: example/.gitignore
================================================
media/
*.log

================================================
FILE: example/blog/__init__.py
================================================


================================================
FILE: example/blog/admin.py
================================================
from django.contrib import admin
from blog.models import Post, Comment


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

    fields = ('content',)


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    inlines = [
        CommentInline,
    ]


================================================
FILE: example/blog/apps.py
================================================
from django.apps import AppConfig


class BlogConfig(AppConfig):
    name = 'blog'


================================================
FILE: example/blog/forms.py
================================================
from django import forms
from django_editorjs_fields import EditorJsWidget

from .models import Post


class TestForm(forms.ModelForm):
    # body_editorjs = EditorJsWidget(config={"minHeight": 100, 'autofocus': False})

    # inputs = forms.JSONField(widget=EditorJsWidget())
    # inputs.widget.config = {"minHeight": 100}

    class Meta:
        model = Post
        exclude = []
        widgets = {
            'body_editorjs': EditorJsWidget(config={'minHeight': 100}),
            'body_textfield': EditorJsWidget(plugins=[
                "@editorjs/image",
                "@editorjs/header"
            ], config={'minHeight': 100})
        }


================================================
FILE: example/blog/migrations/0001_initial.py
================================================
# Generated by Django 3.1.2 on 2020-10-15 14:14

from django.db import migrations, models
import django_editorjs_fields.fields


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='Post',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('body_default', models.TextField()),
                ('body_editorjs', django_editorjs_fields.fields.EditorJsJSONField()),
                ('body_custom', django_editorjs_fields.fields.EditorJsJSONField(blank=True, null=True)),
                ('body_textfield', django_editorjs_fields.fields.EditorJsTextField(blank=True, null=True)),
            ],
        ),
    ]


================================================
FILE: example/blog/migrations/0002_comment.py
================================================
# Generated by Django 3.2.7 on 2021-09-22 14:33

from django.db import migrations, models
import django.db.models.deletion
import django_editorjs_fields.fields


class Migration(migrations.Migration):

    dependencies = [
        ('blog', '0001_initial'),
    ]

    operations = [
        migrations.CreateModel(
            name='Comment',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('content', django_editorjs_fields.fields.EditorJsJSONField(blank=True, null=True)),
                ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='blog.post')),
            ],
        ),
    ]


================================================
FILE: example/blog/migrations/__init__.py
================================================


================================================
FILE: example/blog/models.py
================================================
from django.db import models
from django.urls import reverse
from django_editorjs_fields import EditorJsJSONField, EditorJsTextField


class Post(models.Model):
    body_default = models.TextField()
    body_editorjs = EditorJsJSONField(readOnly=False, autofocus=True)
    body_custom = EditorJsJSONField(
        plugins=[
            "@editorjs/image",
            "@editorjs/header",
            "@editorjs/list",
            "editorjs-github-gist-plugin",
            "editorjs-hyperlink",
            "@editorjs/code",
            "@editorjs/inline-code",
            "@editorjs/table@1.3.0",
        ],
        tools={
            "Gist": {
                "class": "Gist"
            },
            "Hyperlink": {
                "class": "Hyperlink",
                "config": {
                    "shortcut": 'CMD+L',
                    "target": '_blank',
                    "rel": 'nofollow',
                    "availableTargets": ['_blank', '_self'],
                    "availableRels": ['author', 'noreferrer'],
                    "validate": False,
                }
            },
            "Image": {
                'class': 'ImageTool',
                "config": {
                    "endpoints": {
                        # Your custom backend file uploader endpoint
                        "byFile": "/editorjs/image_upload/"
                    }
                }
            }
        },
        null=True,
        blank=True,
    )
    body_textfield = EditorJsTextField(  # only images and paragraph (default)
        plugins=["@editorjs/image", "@editorjs/embed"], null=True, blank=True,
        i18n={
            'messages': {
                'blockTunes': {
                    "delete": {
                        "Delete": "Удалить"
                    },
                    "moveUp": {
                        "Move up": "Переместить вверх"
                    },
                    "moveDown": {
                        "Move down": "Переместить вниз"
                    }
                }
            },
        }
    )

    def get_absolute_url(self):
        return reverse('post_detail', kwargs={'pk': self.id})

    def __str__(self):
        return '{}'.format(self.id)


class Comment(models.Model):
    content = EditorJsJSONField(null=True, blank=True)
    post = models.ForeignKey(
        'Post', related_name='comments', on_delete=models.CASCADE)


================================================
FILE: example/blog/templates/blog/post_update.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
{% if form %}
    <!-- Form Errors -->
    {% if form.errors %}
        <ul class="errors">
            {% for error in form.errors %}
                <li>{{ error }}</li>
            {% endfor %}
        </ul>
    {% endif %}

    <!-- Display Form -->
    <form method="post">
    {% csrf_token %}
    {% for field in form %}
        <div class="mydiv">
            <label class="mylabel">{{ field.label }}</label>
            {{ field }}
        </div>
    {% endfor %}
        <button type="submit">Submit</button>
    </form>
{% endif %}

{{ form.media }}
</body>
</html>


================================================
FILE: example/blog/templates/blog/post_view.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
{% load editorjs %}
{{ post.body_editorjs | editorjs}}
{{ post.body_custom | editorjs}}
{{ post.body_textfield | editorjs}}

</body>
</html>


================================================
FILE: example/blog/tests.py
================================================
from django.test import TestCase

# Create your tests here.


================================================
FILE: example/blog/urls.py
================================================
from django.urls import path

from .views import PostUpdate, PostView

urlpatterns = [
    path('posts/<int:pk>/edit', PostUpdate.as_view(), name='post_edit'),
    path('posts/<int:pk>', PostView.as_view(), name='post_detail'),
]


================================================
FILE: example/blog/views.py
================================================
from django.shortcuts import redirect, render
from django.views import View

from .forms import TestForm
from .models import Post


class PostUpdate(View):
    def get(self, request, pk):
        post = Post.objects.get(id=pk)
        bound_form = TestForm(instance=post)
        return render(request, 'blog/post_update.html', {'form': bound_form, 'post': post})

    def post(self, request, pk):
        post = Post.objects.get(id=pk)
        bound_form = TestForm(request.POST, instance=post)

        if bound_form.is_valid():
            new_post = bound_form.save()
            return redirect(new_post)
        return render(request, 'blog/post_update.html', {'form': bound_form, 'post': post})


class PostView(View):
    def get(self, request, pk):
        post = Post.objects.get(id=pk)
        return render(request, 'blog/post_view.html', {'post': post})


================================================
FILE: example/example/__init__.py
================================================


================================================
FILE: example/example/asgi.py
================================================
"""
ASGI config for example project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
"""

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings')

application = get_asgi_application()


================================================
FILE: example/example/settings.py
================================================
"""
Django settings for example project.

Generated by 'django-admin startproject' using Django 3.1.2.

For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '9yx!8#ocntj+t40#&2+qj&+)n1ajvx_-mo5247&evr*)37=y_x'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

# Application definition

DEFAULT_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

INSTALLED_APPS = DEFAULT_APPS + [
    'blog.apps.BlogConfig',
    'django_editorjs_fields',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'example.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'example.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases

DATABASES = {
    # 'default': {
    #     'ENGINE': 'django.db.backends.sqlite3',
    #     'NAME': BASE_DIR / 'db.sqlite3',
    # }
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'test_django',
        'USER': 'postgres',
        'PASSWORD': 'postgres',
        'HOST': '127.0.0.1',
        'PORT': '5432',
    }
}

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'standard': {
            'format': '%(asctime)s %(filename)s:%(lineno)d %(levelname)s - %(message)s'
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
        'django_editorjs_fields': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': 'django_editorjs_fields.log',
            'maxBytes': 1024*1024*5,  # 5 MB
            'backupCount': 5,
            'formatter': 'standard',
        },
    },
    'loggers': {
        'django_editorjs_fields': {
            'handlers': ['django_editorjs_fields', 'console'],
            'level': 'DEBUG',
        },
    },
}


# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/

STATIC_ROOT = BASE_DIR / "static/"
STATIC_URL = '/static/'

MEDIA_ROOT = BASE_DIR / "media"
MEDIA_URL = "/media/"

# django_editorjs_fields
EDITORJS_VERSION = '2.25.0'
# EDITORJS_IMAGE_NAME_ORIGINAL = True
# EDITORJS_IMAGE_UPLOAD_PATH_DATE = None
# EDITORJS_IMAGE_NAME = lambda filename, **_: f"{filename}_12345"


================================================
FILE: example/example/urls.py
================================================
"""example URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.1/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('editorjs/', include('django_editorjs_fields.urls')),
    path('', include('blog.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)


================================================
FILE: example/example/wsgi.py
================================================
"""
WSGI config for example project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings')

application = get_wsgi_application()


================================================
FILE: example/manage.py
================================================
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    """Run administrative tasks."""
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()


================================================
FILE: pyproject.toml
================================================
[tool.poetry]
name = "django-editorjs-fields"
version = "0.2.7"
description = "Django plugin for using Editor.js"
authors = ["Ilya Kotlyakov <m@2ik.ru>"]
license = "MIT"
repository = "https://github.com/2ik/django-editorjs-fields"
documentation = "https://github.com/2ik/django-editorjs-fields"
readme = "README.md"
keywords = ["editorjs", "django-editor", "django-wysiwyg", "wysiwyg", "django-admin"]

classifiers = [
    "Development Status :: 5 - Production/Stable",
    "Operating System :: OS Independent",
    "Intended Audience :: Developers",
    "Framework :: Django",
    "Framework :: Django :: 2.2",
    "Framework :: Django :: 3.0",
    "Framework :: Django :: 3.1",
    "Framework :: Django :: 3.2",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3 :: Only",
    "Programming Language :: Python :: 3.6",
    "Programming Language :: Python :: 3.7",
    "Programming Language :: Python :: 3.8",
    "Programming Language :: Python :: 3.9",
    "Programming Language :: Python :: 3.10",
    "Topic :: Software Development :: Libraries :: Application Frameworks",
    "Topic :: Software Development :: Libraries :: Python Modules",
]

include = [
    "LICENSE",
]

[tool.poetry.dependencies]
python = "^3.6"

[tool.poetry.dev-dependencies]
Django = "^3.1.0"
pylint = "^2.6.0"
autopep8 = "^1.5.4"
pylint-django = "^2.3.0"

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
Download .txt
gitextract_wf8k7gqy/

├── .gitignore
├── .pylintrc
├── LICENSE
├── README.md
├── django_editorjs_fields/
│   ├── __init__.py
│   ├── config.py
│   ├── fields.py
│   ├── static/
│   │   └── django-editorjs-fields/
│   │       ├── css/
│   │       │   └── django-editorjs-fields.css
│   │       └── js/
│   │           └── django-editorjs-fields.js
│   ├── templates/
│   │   └── django-editorjs-fields/
│   │       └── widget.html
│   ├── templatetags/
│   │   ├── __init__.py
│   │   └── editorjs.py
│   ├── urls.py
│   ├── utils.py
│   ├── views.py
│   └── widgets.py
├── example/
│   ├── .gitignore
│   ├── blog/
│   │   ├── __init__.py
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── forms.py
│   │   ├── migrations/
│   │   │   ├── 0001_initial.py
│   │   │   ├── 0002_comment.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── templates/
│   │   │   └── blog/
│   │   │       ├── post_update.html
│   │   │       └── post_view.html
│   │   ├── tests.py
│   │   ├── urls.py
│   │   └── views.py
│   ├── example/
│   │   ├── __init__.py
│   │   ├── asgi.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── manage.py
└── pyproject.toml
Download .txt
SYMBOL INDEX (65 symbols across 14 files)

FILE: django_editorjs_fields/fields.py
  class FieldMixin (line 23) | class FieldMixin(Field):
    method get_internal_type (line 24) | def get_internal_type(self):
  class EditorJsFieldMixin (line 28) | class EditorJsFieldMixin:
    method __init__ (line 29) | def __init__(self, plugins, tools, **kwargs):
    method validate_embed (line 58) | def validate_embed(self, value):
    method clean (line 69) | def clean(self, value, model_instance):
    method formfield (line 86) | def formfield(self, **kwargs):
  class EditorJsTextField (line 97) | class EditorJsTextField(EditorJsFieldMixin, FieldMixin):
    method __init__ (line 99) | def __init__(self, plugins=None, tools=None, **kwargs):
    method clean (line 102) | def clean(self, value, model_instance):
  class EditorJsJSONField (line 109) | class EditorJsJSONField(EditorJsFieldMixin, JSONField if HAS_JSONFIELD e...
    method __init__ (line 111) | def __init__(self, plugins=None, tools=None, **kwargs):
    method check (line 114) | def check(self, **kwargs):
    method _check_supported_json (line 119) | def _check_supported_json(self):

FILE: django_editorjs_fields/static/django-editorjs-fields/js/django-editorjs-fields.js
  function initEditorJsPlugin (line 6) | function initEditorJsPlugin() {
  function initEditorJsField (line 14) | function initEditorJsField(textarea) {
  function logError (line 157) | function logError(msg) {

FILE: django_editorjs_fields/templatetags/editorjs.py
  function generate_paragraph (line 9) | def generate_paragraph(data):
  function generate_list (line 14) | def generate_list(data):
  function generate_header (line 20) | def generate_header(data):
  function generate_image (line 26) | def generate_image(data):
  function generate_delimiter (line 43) | def generate_delimiter():
  function generate_table (line 47) | def generate_table(data):
  function generate_warning (line 60) | def generate_warning(data):
  function generate_quote (line 71) | def generate_quote(data):
  function generate_code (line 84) | def generate_code(data):
  function generate_raw (line 89) | def generate_raw(data):
  function generate_embed (line 93) | def generate_embed(data):
  function generate_link (line 102) | def generate_link(data):
  function editorjs (line 131) | def editorjs(value):

FILE: django_editorjs_fields/utils.py
  function get_storage_class (line 7) | def get_storage_class():
  function get_hostname_from_url (line 17) | def get_hostname_from_url(url):

FILE: django_editorjs_fields/views.py
  class ImageUploadView (line 24) | class ImageUploadView(View):
    method dispatch (line 29) | def dispatch(self, request, *args, **kwargs):
    method post (line 32) | def post(self, request):
  class LinkToolView (line 86) | class LinkToolView(View):
    method dispatch (line 90) | def dispatch(self, request, *args, **kwargs):
    method get (line 93) | def get(self, request):
  class ImageByUrl (line 149) | class ImageByUrl(View):
    method dispatch (line 153) | def dispatch(self, request, *args, **kwargs):
    method post (line 156) | def post(self, request):

FILE: django_editorjs_fields/widgets.py
  class LazyEncoder (line 14) | class LazyEncoder(DjangoJSONEncoder):
    method default (line 15) | def default(self, obj):
  class EditorJsWidget (line 24) | class EditorJsWidget(widgets.Textarea):
    method __init__ (line 25) | def __init__(self, plugins=None, tools=None, config=None, **kwargs):
    method configuration (line 39) | def configuration(self):
    method media (line 78) | def media(self):
    method render (line 99) | def render(self, name, value, attrs=None, renderer=None):

FILE: example/blog/admin.py
  class CommentInline (line 5) | class CommentInline(admin.TabularInline):
  class PostAdmin (line 13) | class PostAdmin(admin.ModelAdmin):

FILE: example/blog/apps.py
  class BlogConfig (line 4) | class BlogConfig(AppConfig):

FILE: example/blog/forms.py
  class TestForm (line 7) | class TestForm(forms.ModelForm):
    class Meta (line 13) | class Meta:

FILE: example/blog/migrations/0001_initial.py
  class Migration (line 7) | class Migration(migrations.Migration):

FILE: example/blog/migrations/0002_comment.py
  class Migration (line 8) | class Migration(migrations.Migration):

FILE: example/blog/models.py
  class Post (line 6) | class Post(models.Model):
    method get_absolute_url (line 67) | def get_absolute_url(self):
    method __str__ (line 70) | def __str__(self):
  class Comment (line 74) | class Comment(models.Model):

FILE: example/blog/views.py
  class PostUpdate (line 8) | class PostUpdate(View):
    method get (line 9) | def get(self, request, pk):
    method post (line 14) | def post(self, request, pk):
  class PostView (line 24) | class PostView(View):
    method get (line 25) | def get(self, request, pk):

FILE: example/manage.py
  function main (line 7) | def main():
Condensed preview — 37 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (68K chars).
[
  {
    "path": ".gitignore",
    "chars": 96,
    "preview": ".DS_Store\n.vscode\n.idea\n\nvenv/\n.venv/\ndist/\n__pycache__\n\n#egg's specific\n*.egg-info\n\ndb.sqlite3\n"
  },
  {
    "path": ".pylintrc",
    "chars": 78,
    "preview": "[MASTER]\ndisable=\n    C0114, # missing-module-docstring\n    C0115,\n    C0116,\n"
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2020 Ilya Kotlyakov\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "README.md",
    "chars": 15376,
    "preview": "# Editor.js for Django\n\nDjango plugin for using [Editor.js](https://editorjs.io/)\n\n> This plugin works fine with JSONFie"
  },
  {
    "path": "django_editorjs_fields/__init__.py",
    "chars": 203,
    "preview": "__version__ = \"0.2.7\"\n\nfrom .fields import EditorJsJSONField, EditorJsTextField\nfrom .widgets import EditorJsWidget\n\n__a"
  },
  {
    "path": "django_editorjs_fields/config.py",
    "chars": 3633,
    "preview": "from secrets import token_urlsafe\n\nfrom django.conf import settings\nfrom django.urls import reverse_lazy\n\nDEBUG = getatt"
  },
  {
    "path": "django_editorjs_fields/fields.py",
    "chars": 4286,
    "preview": "import json\n\nfrom django.core import checks\nfrom django.core.exceptions import ValidationError\nfrom django.db.models imp"
  },
  {
    "path": "django_editorjs_fields/static/django-editorjs-fields/css/django-editorjs-fields.css",
    "chars": 3630,
    "preview": "div[data-editorjs-holder] {\n\tdisplay: inline-block;\n\twidth: 100%;\n\tmax-width: 750px;\n\tpadding: 1.5em 1em;\n\tborder: 1px s"
  },
  {
    "path": "django_editorjs_fields/static/django-editorjs-fields/js/django-editorjs-fields.js",
    "chars": 4080,
    "preview": ";(function () {\n  var pluginName = \"django_editorjs_fields\"\n  var pluginHelp =\n    \"Write about the issue here: https://"
  },
  {
    "path": "django_editorjs_fields/templates/django-editorjs-fields/widget.html",
    "chars": 300,
    "preview": "<textarea data-editorjs-textarea name=\"{{ widget.name }}\"{% include \"django/forms/widgets/attrs.html\" %} data-config='{{"
  },
  {
    "path": "django_editorjs_fields/templatetags/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "django_editorjs_fields/templatetags/editorjs.py",
    "chars": 4667,
    "preview": "import json\n\nfrom django import template\nfrom django.utils.safestring import mark_safe\n\nregister = template.Library()\n\n\n"
  },
  {
    "path": "django_editorjs_fields/urls.py",
    "chars": 557,
    "preview": "from django.contrib.admin.views.decorators import staff_member_required\nfrom django.urls import path\n\nfrom .views import"
  },
  {
    "path": "django_editorjs_fields/utils.py",
    "chars": 449,
    "preview": "import urllib.parse\n\nfrom django.conf import settings\nfrom django.utils.module_loading import import_string\n\n\ndef get_st"
  },
  {
    "path": "django_editorjs_fields/views.py",
    "chars": 5340,
    "preview": "import json\nimport logging\nimport os\nfrom datetime import datetime\nfrom urllib.error import HTTPError, URLError\nfrom url"
  },
  {
    "path": "django_editorjs_fields/widgets.py",
    "chars": 3395,
    "preview": "import json\n\nfrom django.core.serializers.json import DjangoJSONEncoder\nfrom django.forms import Media, widgets\nfrom dja"
  },
  {
    "path": "example/.gitignore",
    "chars": 12,
    "preview": "media/\n*.log"
  },
  {
    "path": "example/blog/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "example/blog/admin.py",
    "chars": 280,
    "preview": "from django.contrib import admin\nfrom blog.models import Post, Comment\n\n\nclass CommentInline(admin.TabularInline):\n    m"
  },
  {
    "path": "example/blog/apps.py",
    "chars": 83,
    "preview": "from django.apps import AppConfig\n\n\nclass BlogConfig(AppConfig):\n    name = 'blog'\n"
  },
  {
    "path": "example/blog/forms.py",
    "chars": 653,
    "preview": "from django import forms\nfrom django_editorjs_fields import EditorJsWidget\n\nfrom .models import Post\n\n\nclass TestForm(fo"
  },
  {
    "path": "example/blog/migrations/0001_initial.py",
    "chars": 814,
    "preview": "# Generated by Django 3.1.2 on 2020-10-15 14:14\n\nfrom django.db import migrations, models\nimport django_editorjs_fields."
  },
  {
    "path": "example/blog/migrations/0002_comment.py",
    "chars": 743,
    "preview": "# Generated by Django 3.2.7 on 2021-09-22 14:33\n\nfrom django.db import migrations, models\nimport django.db.models.deleti"
  },
  {
    "path": "example/blog/migrations/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "example/blog/models.py",
    "chars": 2405,
    "preview": "from django.db import models\nfrom django.urls import reverse\nfrom django_editorjs_fields import EditorJsJSONField, Edito"
  },
  {
    "path": "example/blog/templates/blog/post_update.html",
    "chars": 820,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=ed"
  },
  {
    "path": "example/blog/templates/blog/post_view.html",
    "chars": 384,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=ed"
  },
  {
    "path": "example/blog/tests.py",
    "chars": 60,
    "preview": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "example/blog/urls.py",
    "chars": 230,
    "preview": "from django.urls import path\n\nfrom .views import PostUpdate, PostView\n\nurlpatterns = [\n    path('posts/<int:pk>/edit', P"
  },
  {
    "path": "example/blog/views.py",
    "chars": 867,
    "preview": "from django.shortcuts import redirect, render\nfrom django.views import View\n\nfrom .forms import TestForm\nfrom .models im"
  },
  {
    "path": "example/example/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "example/example/asgi.py",
    "chars": 391,
    "preview": "\"\"\"\nASGI config for example project.\n\nIt exposes the ASGI callable as a module-level variable named ``application``.\n\nFo"
  },
  {
    "path": "example/example/settings.py",
    "chars": 4514,
    "preview": "\"\"\"\nDjango settings for example project.\n\nGenerated by 'django-admin startproject' using Django 3.1.2.\n\nFor more informa"
  },
  {
    "path": "example/example/urls.py",
    "chars": 997,
    "preview": "\"\"\"example URL Configuration\n\nThe `urlpatterns` list routes URLs to views. For more information please see:\n    https://"
  },
  {
    "path": "example/example/wsgi.py",
    "chars": 391,
    "preview": "\"\"\"\nWSGI config for example project.\n\nIt exposes the WSGI callable as a module-level variable named ``application``.\n\nFo"
  },
  {
    "path": "example/manage.py",
    "chars": 663,
    "preview": "#!/usr/bin/env python\n\"\"\"Django's command-line utility for administrative tasks.\"\"\"\nimport os\nimport sys\n\n\ndef main():\n "
  },
  {
    "path": "pyproject.toml",
    "chars": 1448,
    "preview": "[tool.poetry]\nname = \"django-editorjs-fields\"\nversion = \"0.2.7\"\ndescription = \"Django plugin for using Editor.js\"\nauthor"
  }
]

About this extraction

This page contains the full source code of the 2ik/django-editorjs-fields GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 37 files (61.4 KB), approximately 14.7k tokens, and a symbol index with 65 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!