Commit 3c45e758 authored by Serge S. Koval's avatar Serge S. Koval

Merge pull request #1245 from pawl/add_sqla_json_field

add json support to SQLA backend
parents a9454081 711b33e4
...@@ -11,6 +11,9 @@ env: ...@@ -11,6 +11,9 @@ env:
- WTFORMS_VERSION=1 - WTFORMS_VERSION=1
- WTFORMS_VERSION=2 - WTFORMS_VERSION=2
addons:
postgresql: "9.4"
services: services:
- postgresql - postgresql
- mongodb - mongodb
......
import json
import warnings import warnings
import geoalchemy2 import geoalchemy2
from flask import current_app from flask import current_app
from shapely.geometry import shape from shapely.geometry import shape
from sqlalchemy import func from sqlalchemy import func
from wtforms.fields import TextAreaField
from .widgets import LeafletWidget from flask_admin.form import JSONField
class JSONField(TextAreaField):
def _value(self):
if self.raw_data:
return self.raw_data[0]
if self.data:
return self.data
return ""
def process_formdata(self, valuelist):
if valuelist:
value = valuelist[0]
if not value:
self.data = None
return
try:
self.data = self.from_json(value)
except ValueError:
self.data = None
raise ValueError(self.gettext('Invalid JSON'))
def to_json(self, obj): from .widgets import LeafletWidget
return json.dumps(obj)
def from_json(self, data):
return json.loads(data)
class GeoJSONField(JSONField): class GeoJSONField(JSONField):
widget = LeafletWidget() widget = LeafletWidget()
def __init__(self, label=None, validators=None, geometry_type="GEOMETRY", srid='-1', session=None, **kwargs): def __init__(self, label=None, validators=None, geometry_type="GEOMETRY",
srid='-1', session=None, **kwargs):
super(GeoJSONField, self).__init__(label, validators, **kwargs) super(GeoJSONField, self).__init__(label, validators, **kwargs)
self.web_srid = 4326 self.web_srid = 4326
self.srid = srid self.srid = srid
......
...@@ -350,6 +350,10 @@ class AdminModelConverter(ModelConverterBase): ...@@ -350,6 +350,10 @@ class AdminModelConverter(ModelConverterBase):
inner_form = field_args.pop('form', HstoreForm) inner_form = field_args.pop('form', HstoreForm)
return InlineHstoreList(InlineFormField(inner_form), **field_args) return InlineHstoreList(InlineFormField(inner_form), **field_args)
@converts('JSON')
def convert_JSON(self, field_args, **extra):
return form.JSONField(**field_args)
def _resolve_prop(prop): def _resolve_prop(prop):
""" """
......
import time import time
import datetime import datetime
import json
from wtforms import fields, widgets from wtforms import fields
from flask_admin.babel import gettext from flask_admin.babel import gettext
from flask_admin._compat import text_type, as_unicode from flask_admin._compat import text_type, as_unicode
...@@ -11,7 +12,8 @@ from . import widgets as admin_widgets ...@@ -11,7 +12,8 @@ from . import widgets as admin_widgets
An understanding of WTForms's Custom Widgets is helpful for understanding this code: http://wtforms.simplecodes.com/docs/0.6.2/widgets.html#custom-widgets An understanding of WTForms's Custom Widgets is helpful for understanding this code: http://wtforms.simplecodes.com/docs/0.6.2/widgets.html#custom-widgets
""" """
__all__ = ['DateTimeField', 'TimeField', 'Select2Field', 'Select2TagsField'] __all__ = ['DateTimeField', 'TimeField', 'Select2Field', 'Select2TagsField',
'JSONField']
class DateTimeField(fields.DateTimeField): class DateTimeField(fields.DateTimeField):
...@@ -176,3 +178,28 @@ class Select2TagsField(fields.StringField): ...@@ -176,3 +178,28 @@ class Select2TagsField(fields.StringField):
return as_unicode(self.data) return as_unicode(self.data)
else: else:
return u'' return u''
class JSONField(fields.TextAreaField):
def _value(self):
if self.raw_data:
return self.raw_data[0]
elif self.data:
# prevent utf8 characters from being converted to ascii
return as_unicode(json.dumps(self.data, ensure_ascii=False))
else:
return ''
def process_formdata(self, valuelist):
if valuelist:
value = valuelist[0]
# allow saving blank field as None
if not value:
self.data = None
return
try:
self.data = json.loads(valuelist[0])
except ValueError:
raise ValueError(self.gettext('Invalid JSON'))
import json
from jinja2 import Markup from jinja2 import Markup
from flask_admin._compat import text_type from flask_admin._compat import text_type
try: try:
...@@ -58,15 +60,28 @@ def enum_formatter(view, value): ...@@ -58,15 +60,28 @@ def enum_formatter(view, value):
return value.name return value.name
def dict_formatter(view, value):
"""
Removes unicode entities when displaying dict as string. Also unescapes
non-ASCII characters stored in the JSON.
:param value:
Dict to convert to string
"""
return json.dumps(value, ensure_ascii=False)
BASE_FORMATTERS = { BASE_FORMATTERS = {
type(None): empty_formatter, type(None): empty_formatter,
bool: bool_formatter, bool: bool_formatter,
list: list_formatter, list: list_formatter,
dict: dict_formatter,
} }
EXPORT_FORMATTERS = { EXPORT_FORMATTERS = {
type(None): empty_formatter, type(None): empty_formatter,
list: list_formatter, list: list_formatter,
dict: dict_formatter,
} }
if Enum is not None: if Enum is not None:
......
...@@ -3,7 +3,7 @@ from nose.tools import eq_, ok_ ...@@ -3,7 +3,7 @@ from nose.tools import eq_, ok_
from . import setup_postgres from . import setup_postgres
from .test_basic import CustomModelView from .test_basic import CustomModelView
from sqlalchemy.dialects.postgresql import HSTORE from sqlalchemy.dialects.postgresql import HSTORE, JSON
def test_hstore(): def test_hstore():
...@@ -40,3 +40,38 @@ def test_hstore(): ...@@ -40,3 +40,38 @@ def test_hstore():
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('test_val1' in data) ok_('test_val1' in data)
ok_('test_val2' in data) ok_('test_val2' in data)
def test_json():
app, db, admin = setup_postgres()
class JSONModel(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
json_test = db.Column(JSON)
db.create_all()
view = CustomModelView(JSONModel, db.session)
admin.add_view(view)
client = app.test_client()
rv = client.get('/admin/jsonmodel/')
eq_(rv.status_code, 200)
rv = client.post('/admin/jsonmodel/new/', data={
'json_test': '{"test_key1": "test_value1"}',
})
eq_(rv.status_code, 302)
rv = client.get('/admin/jsonmodel/')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('json_test' in data)
ok_('{"test_key1": "test_value1"}' in data)
rv = client.get('/admin/jsonmodel/edit/?id=1')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('json_test' in data)
ok_('>{"test_key1": "test_value1"}<' in data)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment