Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Image uploader support #35

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 88 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,21 @@ python app/manage.py runserver
Documentation for **django-quill-editor** is located at [https://django-quill-editor.readthedocs.io/](https://django-quill-editor.readthedocs.io/)


## Change toolbar menus`

## Change toolbar configs

Add `QUILL_CONFIGS` to the **settings.py**

```
If you want to use inline style attributes (`style="text-align: center;"`) instead of class (`class="ql-align-center"`)
, set `useInlineStyleAttributes` to `True`.
It changes the settings only for `align` now. You can check the related
[Quill Docs](https://quilljs.com/guides/how-to-customize-quill/#class-vs-inline).

```python
QUILL_CONFIGS = {
'default':{
'theme': 'snow',
'useInlineStyleAttributes': True,
'modules': {
'syntax': True,
'toolbar': [
Expand All @@ -77,12 +84,58 @@ QUILL_CONFIGS = {
],
['code-block', 'link'],
['clean'],
]
],
'imageUploader': {
'uploadURL': '/admin/quill/upload/', # You can also use an absolute URL (https://example.com/3rd-party/uploader/)
'addCSRFTokenHeader': True,
}
}
}

}
```

## Image uploads

If you want to upload images instead of storing encoded images in your database. You need to add `imageUploader` module
to your configuration. If you set a `uploadURL` for this modules, it registers
[quill-image-uploader](https://www.npmjs.com/package/quill-image-uploader) to Quill.
You can add a view to upload images to your storage service. Response of the view must contain `image_url` field.

```python
# urls.py
from django.urls import path
from .views import EditorImageUploadAPIView

urlpatterns = [
...
path('admin/quill/upload/', EditorImageUploadAPIView.as_view(), name='quill-editor-upload'),
...
]
```

```python
# You don't have to use Django Rest Framework. This is just an example.
from rest_framework import status
from rest_framework.generics import CreateAPIView
from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response

from .serializers import EditorImageSerializer


class EditorImageUploadAPIView(CreateAPIView):
serializer_class = EditorImageSerializer
permission_classes = (IsAdminUser,)

def post(self, request, *args, **kwargs):
# image_url = handle image upload
return Response({'image_url': "https://xxx.s3.amazonaws.com/xxx/x.png"}, status=status.HTTP_200_OK)
```

```json
{
"image_url": "https://xxx.s3.amazonaws.com/xxx/x.png"
}
```

## Usage
Expand Down Expand Up @@ -213,3 +266,34 @@ def model_form(request):
As an open source project, we welcome contributions.
The code lives on [GitHub](https://github.com/LeeHanYeong/django-quill-editor)



## Distribution (for owners)

### PyPI Release

```shell
poetry install # Install PyPI distribution packages
python deploy.py
```



### Sphinx docs

```shell
brew install sphinx-doc # macOS
```

#### Local

```
cd docs
make html
# ...
# The HTML pages are in _build/html.

cd _build/html
python -m http.server 3001
```

10 changes: 8 additions & 2 deletions django_quill/fields.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

from django.db import models

from .forms import QuillFormField
Expand All @@ -14,7 +16,7 @@ class FieldQuill:
def __init__(self, instance, field, json_string):
self.instance = instance
self.field = field
self.json_string = json_string
self.json_string = json_string or '{"delta":"","html":""}'
self._committed = True

def __eq__(self, other):
Expand All @@ -31,7 +33,7 @@ def _require_quill(self):

def _get_quill(self):
self._require_quill()
self._quill = Quill(self.json_string)
self._quill = Quill(json.loads(self.json_string))
return self._quill

def _set_quill(self, quill):
Expand Down Expand Up @@ -142,3 +144,7 @@ def get_prep_value(self, value):
if isinstance(value, Quill):
return value.json_string
return value

def value_to_string(self, obj):
value = self.value_from_object(obj)
return self.get_prep_value(value)
5 changes: 4 additions & 1 deletion django_quill/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
)


class QuillFormField(forms.fields.CharField):
class QuillFormField(forms.fields.JSONField):
def __init__(self, *args, **kwargs):
kwargs.update({
'widget': QuillWidget(),
})
super().__init__(*args, **kwargs)

def prepare_value(self, value):
return value.json_string
7 changes: 3 additions & 4 deletions django_quill/quill.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import json
from json import JSONDecodeError

__all__ = (
'QuillParseError',
Expand All @@ -18,9 +17,9 @@ def __str__(self):
class Quill:
def __init__(self, json_string):
try:
self.json_string = json_string
json_data = json.loads(json_string)
self.json_string = json.dumps(json_string)
json_data = json_string
self.delta = json_data['delta']
self.html = json_data['html']
except (JSONDecodeError, KeyError, TypeError):
except (json.JSONDecodeError, KeyError, TypeError):
raise QuillParseError(json_string)
6 changes: 5 additions & 1 deletion django_quill/static/django_quill/django_quill.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
div.form-row.field-content div.django-quill-widget-container {
div.form-row.field-content, div.django-quill-widget-container {
display: inline-block;
}

.ql-editor{
min-height:350px;
}
80 changes: 66 additions & 14 deletions django_quill/static/django_quill/django_quill.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,69 @@
class QuillWrapper {
constructor(targetDivId, targetInputId, quillOptions) {
this.targetDiv = document.getElementById(targetDivId);
if (!this.targetDiv) throw 'Target div(' + targetDivId + ') id was invalid';

this.targetInput = document.getElementById(targetInputId);
if (!this.targetInput) throw 'Target Input id was invalid';

this.quill = new Quill('#' + targetDivId, quillOptions);
this.quill.on('text-change', () => {
var delta = JSON.stringify(this.quill.getContents());
var html = this.targetDiv.getElementsByClassName('ql-editor')[0].innerHTML;
var data = {delta: delta, html: html};
this.targetInput.value = JSON.stringify(data);
});
constructor(targetDivId, targetInputId, quillOptions) {
this.targetDiv = document.getElementById(targetDivId);
if (!this.targetDiv) throw 'Target div(' + targetDivId + ') id was invalid';

this.targetInput = document.getElementById(targetInputId);
if (!this.targetInput) throw 'Target Input id was invalid';

if (quillOptions.useInlineStyleAttributes) {
// https://quilljs.com/guides/how-to-customize-quill/#class-vs-inline
Quill.register(Quill.import('attributors/style/align'), true);
}

if (quillOptions.modules && quillOptions.modules.imageUploader && quillOptions.modules.imageUploader.uploadURL) {
// https://www.npmjs.com/package/quill-image-uploader
Quill.register("modules/imageUploader", ImageUploader);

var headers = {};
if (quillOptions.modules.imageUploader.addCSRFTokenHeader) {
headers['X-CSRFToken'] = document.querySelector('[name=csrfmiddlewaretoken]').value
}

var imageUploaderModule = {
upload: file => {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append("image", file);

fetch(
quillOptions.modules.imageUploader.uploadURL, {
method: "POST",
body: formData,
headers: headers,
}
)
.then(response => response.json())
.then(result => {
console.log(result);
resolve(result.image_url);
})
.catch(error => {
reject("Upload failed");
alert("Uploading failed");
console.error("Error:", error);
});
});
}
}

}

this.quill = new Quill('#' + targetDivId, {
...quillOptions,
modules: {
...quillOptions.modules,
imageUploader: imageUploaderModule
}
});
this.quill.on('text-change', () => {
var delta = JSON.stringify(this.quill.getContents());
var html = this.targetDiv.getElementsByClassName('ql-editor')[0].innerHTML;
var data = {
delta: delta,
html: html
};
this.targetInput.value = JSON.stringify(data);
});
}
}
7 changes: 5 additions & 2 deletions django_quill/templates/django_quill/media.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js"></script>

<!-- Quill.js -->
<link href="//cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<script src="//cdn.quilljs.com/1.3.6/quill.min.js"></script>
<link href="//cdn.quilljs.com/1.3.7/quill.snow.css" rel="stylesheet">
<script src="//cdn.quilljs.com/1.3.7/quill.min.js"></script>

<!-- Custom -->
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/quill.imageUploader.min.css"/>
<script src="https://unpkg.com/[email protected]/dist/quill.imageUploader.min.js"></script>

<link rel="stylesheet" href="{% static 'django_quill/django_quill.css' %}">
<script src="{% static 'django_quill/django_quill.js' %}"></script>
11 changes: 10 additions & 1 deletion django_quill/templates/django_quill/widget.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,19 @@
(function () {
var wrapper = new QuillWrapper('quill-{{ id }}', 'quill-input-{{ id }}', JSON.parse('{{ config|safe }}'));
{% if quill and quill.delta %}
// try django_quill/quill.py/Quill instance
var contents = JSON.parse('{{ quill.delta|safe|escapejs }}');
wrapper.quill.setContents(contents);
{% elif value %}
wrapper.quill.clipboard.dangerouslyPasteHTML(0, '{{ value|safe }}')
// try Parsing value as JSON
try {
var value = JSON.parse('{{ value|safe|escapejs }}');
wrapper.quill.setContents(JSON.parse(value['delta']));
}
// When a parsing error occurs, the contents are regarded as HTML and the contents of the editor are filled.
catch (e) {
wrapper.quill.clipboard.dangerouslyPasteHTML(0, '{{ value|safe }}')
}
{% endif %}
})();
</script>
Expand Down
10 changes: 6 additions & 4 deletions django_quill/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ def default(self, obj):
class QuillWidget(forms.Textarea):
class Media:
js = (
'django_quill/highlight.pack.js',
'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js',
'https://cdn.quilljs.com/1.3.7/quill.min.js',
'https://unpkg.com/[email protected]/dist/quill.imageUploader.min.js',
'django_quill/django_quill.js',
'django_quill/quill.js',
)
css = {
'all': (
'django_quill/highlight.darcula.min.css',
'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/darcula.min.css',
'django_quill/django_quill.css',
'django_quill/quill.snow.css',
'https://unpkg.com/[email protected]/dist/quill.imageUploader.min.css',
'https://cdn.quilljs.com/1.3.7/quill.snow.css',
)
}

Expand Down
2 changes: 2 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ django-quill-editor makes `Quill.js` easy to use on Django Forms and admin sites
pages/using-in-admin
pages/using-as-form
pages/using-as-modelform
pages/image-uploads
pages/change-toolbar-configs

Installation
************
Expand Down
41 changes: 41 additions & 0 deletions docs/pages/change-toolbar-configs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Change toolbar config

> More settings can be found on the official site
> https://quilljs.com/docs/modules/toolbar/

Add `QUILL_CONFIGS` to the **settings.py**

If you want to use inline style attributes (`style="text-align: center;"`) instead of class (`class="ql-align-center"`)
, set `useInlineStyleAttributes` to `True`.
It changes the settings only for `align` now. You can check the related
[Quill Docs](https://quilljs.com/guides/how-to-customize-quill/#class-vs-inline).


```python
QUILL_CONFIGS = {
'default':{
'theme': 'snow',
'useInlineStyleAttributes': True,
'modules': {
'syntax': True,
'toolbar': [
[
{'font': []},
{'header': []},
{'align': []},
'bold', 'italic', 'underline', 'strike', 'blockquote',
{'color': []},
{'background': []},
],
['code-block', 'link'],
['clean'],
],
'imageUploader': {
'uploadURL': '/admin/quill/upload/', # You can also use an absolute URL (https://example.com/3rd-party/uploader/)
'addCSRFTokenHeader': True,
}
}
}
}
```

Loading