Skip to content

Commit

Permalink
Merge pull request #62 from beshrkayali/feature/forms-plugin
Browse files Browse the repository at this point in the history
Add forms plugin and use it as base for Image plugin
  • Loading branch information
lundberg authored Jan 21, 2020
2 parents 09f59d2 + da8b653 commit 133a24a
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 52 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ htmlcov/
node_modules/
.next/
example/db.sqlite3

# Emacs Temp/Autosaves
*~
\#*\#
.#*.*
*_flymake*
11 changes: 9 additions & 2 deletions djedi/admin/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from collections import defaultdict
from djedi.plugins.base import DjediPlugin

from django.core.exceptions import PermissionDenied
from django.http import HttpResponse, Http404, HttpResponseBadRequest
from django.utils.http import urlunquote
Expand Down Expand Up @@ -193,11 +195,16 @@ def get(self, request, uri):
try:
uri = self.decode_uri(uri)
uri = URI(uri)
plugins.resolve(uri)
plugin = plugins.resolve(uri)
plugin_context = self.get_context_data(uri=uri)

if isinstance(plugin, DjediPlugin):
plugin_context = plugin.get_editor_context(**plugin_context)

except UnknownPlugin:
raise Http404
else:
return self.render_plugin(request, self.get_context_data(uri=uri))
return self.render_plugin(request, plugin_context)

@never_cache
def post(self, request, uri):
Expand Down
9 changes: 9 additions & 0 deletions djedi/plugins/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from cio.plugins.base import BasePlugin


class DjediPlugin(BasePlugin):
def get_editor_context(self, **kwargs):
"""
Returns custom context
"""
return kwargs
67 changes: 67 additions & 0 deletions djedi/plugins/form.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import json
from djedi.plugins.base import DjediPlugin
from django import forms


def deprefix(s):
# Remove prefix (anything including and before __)
return s.rpartition('__')[-1]


def get_custom_render_widget(cls):
class CustomRenderWidget(cls):
def render(self, *args, **kwargs):
name = kwargs.pop("name", None)

if not name:
name = args[0]
args = args[1:]

name = deprefix(name)

return super(CustomRenderWidget, self).render(
"data[%s]" % name,
*args,
**kwargs
)

return CustomRenderWidget


class BaseEditorForm(forms.Form):
def __init__(self, *args, **kwargs):
super(BaseEditorForm, self).__init__(*args, **kwargs)

for field in list(self.fields.keys()):
self.fields[field].widget.__class__ = get_custom_render_widget(
self.fields[field].widget.__class__
)


class FormsBasePlugin(DjediPlugin):
ext = None

@property
def forms(self):
return {}

def get_editor_context(self, **context):
context.update(
{"forms": {
tab: form()
for tab, form in self.forms.items()
}}
)

return context

def save(self, data, dumps=True):
data = self.collect_forms_data(data)
return json.dumps(data) if dumps else data

def collect_forms_data(self, data):
return {
deprefix(field): data.get(deprefix(field))
for tab, form in self.forms.items()
for field in form.base_fields.keys()
}
64 changes: 47 additions & 17 deletions djedi/plugins/img.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,44 @@
import json
import six
from django.utils.html import escape
from cio.plugins.base import BasePlugin
from django.core.files.uploadedfile import InMemoryUploadedFile
from django import forms
from hashlib import sha1
from os import path

from .form import FormsBasePlugin, BaseEditorForm

class ImagePluginBase(BasePlugin):

class DataForm(BaseEditorForm):
data__id = forms.CharField(
label="ID",
max_length=255,
required=False,
widget=forms.TextInput(attrs={"class": "form-control"}),
)

data__alt = forms.CharField(
label="Alt text",
max_length=255,
required=False,
widget=forms.TextInput(attrs={"class": "form-control"}),
)

data__class = forms.CharField(
label="Class",
max_length=255,
required=False,
widget=forms.TextInput(attrs={"class": "form-control"}),
)


class ImagePluginBase(FormsBasePlugin):
ext = 'img'

@property
def forms(self):
return {'HTML': DataForm}

def _open(self, filename):
raise NotImplementedError

Expand Down Expand Up @@ -101,14 +129,12 @@ def save(self, data):
if file:
file.close()

content = {
content = super(ImagePluginBase, self).save(data, dumps=False)
content.update({
'filename': filename,
'width': width,
'height': height,
'id': data.get('id') or None,
'class': data.get('class') or None,
'alt': data.get('alt') or None
}
})

return json.dumps(content)

Expand All @@ -126,23 +152,27 @@ def render(self, data):
'width': 160,
'height': 90
}

if data:
url = data.get('url')
width = data.get('width') or 0
height = data.get('height') or 0
alt = data.get('alt') or ''
tag_id = data.get('id')
tag_class = data.get('class')
if url:
attrs['src'] = url
attrs['alt'] = alt

width = data.get('width') or 0
height = data.get('height') or 0
if width and height:
attrs['width'] = width
attrs['height'] = height
if tag_id:
attrs['id'] = tag_id
if tag_class:
attrs['class'] = tag_class

attrs['alt'] = data.get('alt') or ''

attr_id = data.get('id')
if attr_id:
attrs['id'] = attr_id

attr_class = data.get('class')
if attr_class:
attrs['class'] = attr_class

html_attrs = (u'{0}="{1}"'.format(attr, escape(attrs[attr])) for attr in sorted(attrs.keys()))
return u'<img {0} />'.format(u' '.join(html_attrs))
Expand Down
14 changes: 10 additions & 4 deletions djedi/static/djedi/plugins/img/js/img.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -375,13 +375,19 @@ class window.ImageEditor extends window.Editor
@firstRender = false

updateForm: (data) ->
# Hardcoded fields
$("input[name='data[filename]']").val data.filename
$("input[name='data[crop]']").val ''
$("input[name='data[width]']").val data.width
$("input[name='data[height]']").val data.height
$("input[name='data[crop]']").val ''
$("input[name='data[id]']").val data.id
$("input[name='data[class]']").val data.class
$("input[name='data[alt]']").val data.alt
delete data.filename
delete data.width
delete data.height

# Form fields
for k, v of data
$("input[name='data[#{k}]']").val v

@ratioButton.removeClass 'active'

renderThumbnail: (url) ->
Expand Down
13 changes: 9 additions & 4 deletions djedi/static/djedi/plugins/img/js/img.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 18 additions & 23 deletions djedi/templates/djedi/plugins/img/editor.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
{% block tabs %}
<ul class="nav navbar-default nav-tabs">
<li class="active"><a href="#image-pane" data-toggle="tab"><i class="icon-camera"></i> Image</a></li>
<li><a href="#html-pane" data-toggle="tab"><i class="icon-code"></i> Html</a></li>
{% for tab, form in forms.items %}
<li><a href="#{{tab}}-pane" data-toggle="tab"><i class="icon-code"></i> {{tab}}</a></li>
{% endfor %}
</ul>
{% endblock tabs %}

Expand All @@ -29,28 +31,21 @@
<input type="hidden" name="data[crop]" id="field-crop">
</div>
</div>
<div id="html-pane" class="tab-pane">
<div class="form-horizontal">
<div class="form-group">
<label class="col-xs-2 control-label">ID:</label>
<div class="col-xs-10">
<input type="text" class="form-control" name="data[id]">
</div>
</div>
<div class="form-group">
<label class="col-xs-2 control-label">Class:</label>
<div class="col-xs-10">
<input type="text" class="form-control" name="data[class]">
</div>
</div>
<div class="form-group">
<label class="col-xs-2 control-label">Alt:</label>
<div class="col-xs-10">
<input type="text" class="form-control" name="data[alt]">
</div>
</div>
</div>
</div>

{% for tab, form in forms.items %}
<div id="{{tab}}-pane" class="tab-pane">
<div class="form-horizontal">
{% for field in form %}
<div class="form-group">
<label class="col-xs-2 control-label">{{field.label}}:</label>
<div class="col-xs-10">
{{ field }}
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
{% endblock editor %}

Expand Down
28 changes: 27 additions & 1 deletion djedi/tests/test_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from cio.backends.exceptions import NodeDoesNotExist, PersistenceError
from cio.plugins import plugins
from cio.utils.uri import URI
from djedi.plugins.form import BaseEditorForm
from djedi.tests.base import ClientTest, DjediTest, UserMixin
from djedi.utils.encoding import smart_unicode

Expand Down Expand Up @@ -201,6 +202,7 @@ def test_render(self):
'width': '64',
'height': '64'
})})

self.assertEqual(response.status_code, 200)
self.assertEqual(smart_unicode(response.content), u'<img alt="" height="64" src="/foo/bar.png" width="64" />')

Expand All @@ -214,14 +216,36 @@ def test_editor(self):
for ext in plugins:
response = self.get('cms.editor', 'sv-se@page/title.' + ext)
self.assertEqual(response.status_code, 200)
assert set(response.context_data.keys()) == set(('THEME', 'VERSION', 'uri',))
if ext == 'img':
assert set(response.context_data.keys()) == set(('THEME', 'VERSION', 'uri', 'forms'))
assert 'HTML' in response.context_data['forms']
assert isinstance(response.context_data['forms']['HTML'], BaseEditorForm)

self.assertListEqual(
["data__id", "data__alt", "data__class"],
list(response.context_data['forms']['HTML'].fields.keys())
)

else:
assert set(response.context_data.keys()) == set(('THEME', 'VERSION', 'uri',))

self.assertNotIn(b'document.domain', response.content)

with cio.conf.settings(XSS_DOMAIN='foobar.se'):
response = self.post('cms.editor', 'sv-se@page/title', {'data': u'Djedi'})
self.assertEqual(response.status_code, 200)
self.assertIn(b'document.domain = "foobar.se"', response.content)

def test_image_dataform(self):
from djedi.plugins.img import DataForm

data_form = DataForm()
html = data_form.as_table()

self.assertTrue('name="data[alt]"' in html)
self.assertTrue('name="data[class]"' in html)
self.assertTrue('name="data[id]"' in html)

def test_upload(self):
tests_dir = os.path.dirname(os.path.abspath(__file__))
image_path = os.path.join(tests_dir, 'assets', 'image.png')
Expand All @@ -235,7 +259,9 @@ def test_upload(self):
'data[alt]': u'Zwitter',
'meta[comment]': u'VW'
}

response = self.post('api', 'i18n://sv-se@header/logo.img', form)

self.assertEqual(response.status_code, 200)

with open(image_path, "rb") as image:
Expand Down
3 changes: 2 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
envlist = py27-django{ 15, 16, 17, 18, 19, 110, 111 },
py35-django{ 18, 19, 110, 111, 20, 21, 22 },
py36-django{ 111, 20, 21, 22 },
py37-django{ 111, 20, 21, 22 }
py37-django{ 111, 20, 21, 22 },
py38-django{ 111, 20, 21, 22 }

[testenv]
passenv = COVERAGE_FILE
Expand Down

0 comments on commit 133a24a

Please sign in to comment.