Import python-django-jsonfield_1.0.1.orig.tar.gz

[dgit import orig python-django-jsonfield_1.0.1.orig.tar.gz]
This commit is contained in:
Raphaël Hertzog 2016-07-21 10:45:17 +02:00
commit ae9fcb7da0
26 changed files with 1374 additions and 0 deletions

23
LICENSE Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2012, Matthew Schinckel.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The names of its contributors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MATTHEW SCHINCKEL BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

6
MANIFEST.in Normal file
View File

@ -0,0 +1,6 @@
recursive-include jsonfield *.py
include jsonfield/VERSION
include README.rst
include tests.py
include LICENSE
recursive-exclude jsonfield *.pyc

227
PKG-INFO Normal file
View File

@ -0,0 +1,227 @@
Metadata-Version: 1.1
Name: django-jsonfield
Version: 1.0.1
Summary: JSONField for django models
Home-page: http://bitbucket.org/schinckel/django-jsonfield/
Author: Matthew Schinckel
Author-email: matt@schinckel.net
License: UNKNOWN
Description: django-jsonfield
===================
.. image:: https://codeship.com/projects/2e1a3d30-7db7-0132-629f-4abd151a3721/status?branch=default
I had a serious need for a JSON field for django. There were a couple out
there, but none packaged up nicely on bitbucket/github that were usable
with ``pip install -e``.
So I took the code from `David Cramer's blog`_, and packaged it up.
Usage
-----
To use, just install the package, and then use the field::
from django.db import models
import jsonfield
class MyModel(models.Model):
the_json = jsonfield.JSONField()
You can assign any JSON-encodable object to this field. It will be
JSON-encoded before being stored in the database as a text value and it
will be turned back into a python list/dict/string upon retrieval from the
database.
There is also a ``TypedJSONField``, that allows you to define data types that must be included within each object in the array. More documentation to follow.
Notes
~~~~~
If no ``default`` is provided, and ``null=True`` is not passed in to the
field constructor, then a default of ``{}`` will be used.
Supported django versions
-------------------------
All versions of Django from 1.8 onwards are tested, however, if you are using Postgres, I highly recommend that you consider using the ``django.contrib.postgres`` module's ``JSONField`` instead.
Extras
------
jsonify templatetag
~~~~~~~~~~~~~~~~~~~
This allows you to convert a python data structure into JSON within a template::
{% load jsonify %}
<script>
var foo = {{ bar|jsonify|safe }};
</script>
Note that you must only use the "safe" filter when you use the jsonify
filter within a <script> tag (which is parsed like a CDATA section).
If you use it in some other places like in an HTML attribute, then
you must not use the safe filter so that its output is properly escaped::
<div data-foo="{{ bar|jsonify }}">
The above rules are important to avoid XSS attacks with unsafe strings
stored in the converted data structure.
History
----------
1.0.1
~~~~~~
Fix issue with Postgres JSONB fields.
Limit XSS attacks with jsonify template tag.
1.0.0
~~~~~
Add support for Django 1.8 and 1.9 (without warnings). Remove support for Django < 1.8
as none of those releases are supported upstream anyway.
With this version, ``JSONField`` no longer decodes assigned string values as JSON. Instead it assumes that any value that you assign is the decoded value which will be JSON-encoded before storage in the database. This explains the bump to version 1.0 as it's a backwards incompatible change.
0.9.19
~~~~~~
Allow passing `decoder_kwargs` as an argument to a field. This dict will be passed as kwargs to
the `json.loads()` calls when loading data that is a string.
You may also set this as a global value in settings.JSONFIELD_DECODER_KWARGS.
A new dict is created for each field: so if this value is altered after field definition, it shouldn't
affect already attached fields.
0.9.16
~~~~~~
Allow passing an argument of `encoder_class` to a field, which will result in that object (or
the object located at that path, for instance `core.utils.JSONEncoder`) being used as the `cls`
argument when serializing objects.
You may also set this as a global value in settings.JSONFIELD_ENCODER_CLASS
0.9.15
~~~~~~
Bump version number to get around uploading issues.
0.9.14
~~~~~~
No longer hit the db to work out db_type.
0.9.12
~~~~~~
Cache the result of db_type.
Handle incoming data from multiple select widget better.
0.9.9
~~~~~
Finally strip out non-required files.
0.9.8
~~~~~
Remove freezegun workarounds.
Fix broken build.
0.9.4
~~~~~
Fixes for mutable defaults: we serialize and then deserialize in this
case, so you can still use ``default={}``.
0.9.3
~~~~~
Remove support for storing data using Postgres' 9.2's JSON data type, as
you cannot currently query against this!
Remove support for django < 1.3.
0.9.0
~~~~~
Add LICENSE file.
Added TypedJSONField.
0.8.10
~~~~~~
Allow ``{{ variable|jsonify }}`` to work with querysets.
0.8.8
~~~~~
Prevent circular import problem with django 1.3.1 and gargoyle.
0.8.7
~~~~~
Better handle null=True and blank=True: it should make sense what they do now.
0.8.5
~~~~~
Allow for '{}' and '[]', and make them not appear to be None.
0.8.4
~~~~~
Ensure the version number file is installed with the package.
0.8.3
~~~~~
Store the version number in one place only, now.
0.8.2
~~~~~
Oops. Packaging error prevented install from pypi. Added README.rst to manifest.
0.8.1
~~~~~
Converting to string does nothing, as serializing a model instance with a JSONField would have a string version of that field, instead of it embedded inline. (Back to pre 0.8 behaviour).
Added better querying support: (``field__contains={'key':'value','key2':'value2'}`` works.)
Removed JSONTableWidget from package.
0.8
~~~
(Many thanks to `IanLewis`_ for these features)
Supports django 1.2
Supports callable and json serializable objects as default
Implemented get_db_prep_value()
Add tests and test runner.
Removed JSONTableWidget from README.
0.7.1
~~~~~
Don't fail when trying to install before django is installed.
0.7
~~~
First time I tagged releases.
Todo
----------
Allow for passing in a function to use for processing unknown data types.
Convert date/time objects nicely to/from ISO strings (YYYY-mm-dd HH:MM:SS
TZNAME). This is actually a bit tricky, as we don't know if we are expecting
a date/time object. We may parse objects as we go, but there could be
some performance issues with this. I'm tempted to say "only do this on TypedJSONField()"
.. _David Cramer's blog: http://justcramer.com/2009/04/14/cleaning-up-with-json-and-sql/
.. _IanLewis: https://bitbucket.org/IanLewis
Platform: UNKNOWN
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Framework :: Django

212
README.rst Normal file
View File

@ -0,0 +1,212 @@
django-jsonfield
===================
.. image:: https://codeship.com/projects/2e1a3d30-7db7-0132-629f-4abd151a3721/status?branch=default
I had a serious need for a JSON field for django. There were a couple out
there, but none packaged up nicely on bitbucket/github that were usable
with ``pip install -e``.
So I took the code from `David Cramer's blog`_, and packaged it up.
Usage
-----
To use, just install the package, and then use the field::
from django.db import models
import jsonfield
class MyModel(models.Model):
the_json = jsonfield.JSONField()
You can assign any JSON-encodable object to this field. It will be
JSON-encoded before being stored in the database as a text value and it
will be turned back into a python list/dict/string upon retrieval from the
database.
There is also a ``TypedJSONField``, that allows you to define data types that must be included within each object in the array. More documentation to follow.
Notes
~~~~~
If no ``default`` is provided, and ``null=True`` is not passed in to the
field constructor, then a default of ``{}`` will be used.
Supported django versions
-------------------------
All versions of Django from 1.8 onwards are tested, however, if you are using Postgres, I highly recommend that you consider using the ``django.contrib.postgres`` module's ``JSONField`` instead.
Extras
------
jsonify templatetag
~~~~~~~~~~~~~~~~~~~
This allows you to convert a python data structure into JSON within a template::
{% load jsonify %}
<script>
var foo = {{ bar|jsonify|safe }};
</script>
Note that you must only use the "safe" filter when you use the jsonify
filter within a <script> tag (which is parsed like a CDATA section).
If you use it in some other places like in an HTML attribute, then
you must not use the safe filter so that its output is properly escaped::
<div data-foo="{{ bar|jsonify }}">
The above rules are important to avoid XSS attacks with unsafe strings
stored in the converted data structure.
History
----------
1.0.1
~~~~~~
Fix issue with Postgres JSONB fields.
Limit XSS attacks with jsonify template tag.
1.0.0
~~~~~
Add support for Django 1.8 and 1.9 (without warnings). Remove support for Django < 1.8
as none of those releases are supported upstream anyway.
With this version, ``JSONField`` no longer decodes assigned string values as JSON. Instead it assumes that any value that you assign is the decoded value which will be JSON-encoded before storage in the database. This explains the bump to version 1.0 as it's a backwards incompatible change.
0.9.19
~~~~~~
Allow passing `decoder_kwargs` as an argument to a field. This dict will be passed as kwargs to
the `json.loads()` calls when loading data that is a string.
You may also set this as a global value in settings.JSONFIELD_DECODER_KWARGS.
A new dict is created for each field: so if this value is altered after field definition, it shouldn't
affect already attached fields.
0.9.16
~~~~~~
Allow passing an argument of `encoder_class` to a field, which will result in that object (or
the object located at that path, for instance `core.utils.JSONEncoder`) being used as the `cls`
argument when serializing objects.
You may also set this as a global value in settings.JSONFIELD_ENCODER_CLASS
0.9.15
~~~~~~
Bump version number to get around uploading issues.
0.9.14
~~~~~~
No longer hit the db to work out db_type.
0.9.12
~~~~~~
Cache the result of db_type.
Handle incoming data from multiple select widget better.
0.9.9
~~~~~
Finally strip out non-required files.
0.9.8
~~~~~
Remove freezegun workarounds.
Fix broken build.
0.9.4
~~~~~
Fixes for mutable defaults: we serialize and then deserialize in this
case, so you can still use ``default={}``.
0.9.3
~~~~~
Remove support for storing data using Postgres' 9.2's JSON data type, as
you cannot currently query against this!
Remove support for django < 1.3.
0.9.0
~~~~~
Add LICENSE file.
Added TypedJSONField.
0.8.10
~~~~~~
Allow ``{{ variable|jsonify }}`` to work with querysets.
0.8.8
~~~~~
Prevent circular import problem with django 1.3.1 and gargoyle.
0.8.7
~~~~~
Better handle null=True and blank=True: it should make sense what they do now.
0.8.5
~~~~~
Allow for '{}' and '[]', and make them not appear to be None.
0.8.4
~~~~~
Ensure the version number file is installed with the package.
0.8.3
~~~~~
Store the version number in one place only, now.
0.8.2
~~~~~
Oops. Packaging error prevented install from pypi. Added README.rst to manifest.
0.8.1
~~~~~
Converting to string does nothing, as serializing a model instance with a JSONField would have a string version of that field, instead of it embedded inline. (Back to pre 0.8 behaviour).
Added better querying support: (``field__contains={'key':'value','key2':'value2'}`` works.)
Removed JSONTableWidget from package.
0.8
~~~
(Many thanks to `IanLewis`_ for these features)
Supports django 1.2
Supports callable and json serializable objects as default
Implemented get_db_prep_value()
Add tests and test runner.
Removed JSONTableWidget from README.
0.7.1
~~~~~
Don't fail when trying to install before django is installed.
0.7
~~~
First time I tagged releases.
Todo
----------
Allow for passing in a function to use for processing unknown data types.
Convert date/time objects nicely to/from ISO strings (YYYY-mm-dd HH:MM:SS
TZNAME). This is actually a bit tricky, as we don't know if we are expecting
a date/time object. We may parse objects as we go, but there could be
some performance issues with this. I'm tempted to say "only do this on TypedJSONField()"
.. _David Cramer's blog: http://justcramer.com/2009/04/14/cleaning-up-with-json-and-sql/
.. _IanLewis: https://bitbucket.org/IanLewis

View File

@ -0,0 +1,227 @@
Metadata-Version: 1.1
Name: django-jsonfield
Version: 1.0.1
Summary: JSONField for django models
Home-page: http://bitbucket.org/schinckel/django-jsonfield/
Author: Matthew Schinckel
Author-email: matt@schinckel.net
License: UNKNOWN
Description: django-jsonfield
===================
.. image:: https://codeship.com/projects/2e1a3d30-7db7-0132-629f-4abd151a3721/status?branch=default
I had a serious need for a JSON field for django. There were a couple out
there, but none packaged up nicely on bitbucket/github that were usable
with ``pip install -e``.
So I took the code from `David Cramer's blog`_, and packaged it up.
Usage
-----
To use, just install the package, and then use the field::
from django.db import models
import jsonfield
class MyModel(models.Model):
the_json = jsonfield.JSONField()
You can assign any JSON-encodable object to this field. It will be
JSON-encoded before being stored in the database as a text value and it
will be turned back into a python list/dict/string upon retrieval from the
database.
There is also a ``TypedJSONField``, that allows you to define data types that must be included within each object in the array. More documentation to follow.
Notes
~~~~~
If no ``default`` is provided, and ``null=True`` is not passed in to the
field constructor, then a default of ``{}`` will be used.
Supported django versions
-------------------------
All versions of Django from 1.8 onwards are tested, however, if you are using Postgres, I highly recommend that you consider using the ``django.contrib.postgres`` module's ``JSONField`` instead.
Extras
------
jsonify templatetag
~~~~~~~~~~~~~~~~~~~
This allows you to convert a python data structure into JSON within a template::
{% load jsonify %}
<script>
var foo = {{ bar|jsonify|safe }};
</script>
Note that you must only use the "safe" filter when you use the jsonify
filter within a <script> tag (which is parsed like a CDATA section).
If you use it in some other places like in an HTML attribute, then
you must not use the safe filter so that its output is properly escaped::
<div data-foo="{{ bar|jsonify }}">
The above rules are important to avoid XSS attacks with unsafe strings
stored in the converted data structure.
History
----------
1.0.1
~~~~~~
Fix issue with Postgres JSONB fields.
Limit XSS attacks with jsonify template tag.
1.0.0
~~~~~
Add support for Django 1.8 and 1.9 (without warnings). Remove support for Django < 1.8
as none of those releases are supported upstream anyway.
With this version, ``JSONField`` no longer decodes assigned string values as JSON. Instead it assumes that any value that you assign is the decoded value which will be JSON-encoded before storage in the database. This explains the bump to version 1.0 as it's a backwards incompatible change.
0.9.19
~~~~~~
Allow passing `decoder_kwargs` as an argument to a field. This dict will be passed as kwargs to
the `json.loads()` calls when loading data that is a string.
You may also set this as a global value in settings.JSONFIELD_DECODER_KWARGS.
A new dict is created for each field: so if this value is altered after field definition, it shouldn't
affect already attached fields.
0.9.16
~~~~~~
Allow passing an argument of `encoder_class` to a field, which will result in that object (or
the object located at that path, for instance `core.utils.JSONEncoder`) being used as the `cls`
argument when serializing objects.
You may also set this as a global value in settings.JSONFIELD_ENCODER_CLASS
0.9.15
~~~~~~
Bump version number to get around uploading issues.
0.9.14
~~~~~~
No longer hit the db to work out db_type.
0.9.12
~~~~~~
Cache the result of db_type.
Handle incoming data from multiple select widget better.
0.9.9
~~~~~
Finally strip out non-required files.
0.9.8
~~~~~
Remove freezegun workarounds.
Fix broken build.
0.9.4
~~~~~
Fixes for mutable defaults: we serialize and then deserialize in this
case, so you can still use ``default={}``.
0.9.3
~~~~~
Remove support for storing data using Postgres' 9.2's JSON data type, as
you cannot currently query against this!
Remove support for django < 1.3.
0.9.0
~~~~~
Add LICENSE file.
Added TypedJSONField.
0.8.10
~~~~~~
Allow ``{{ variable|jsonify }}`` to work with querysets.
0.8.8
~~~~~
Prevent circular import problem with django 1.3.1 and gargoyle.
0.8.7
~~~~~
Better handle null=True and blank=True: it should make sense what they do now.
0.8.5
~~~~~
Allow for '{}' and '[]', and make them not appear to be None.
0.8.4
~~~~~
Ensure the version number file is installed with the package.
0.8.3
~~~~~
Store the version number in one place only, now.
0.8.2
~~~~~
Oops. Packaging error prevented install from pypi. Added README.rst to manifest.
0.8.1
~~~~~
Converting to string does nothing, as serializing a model instance with a JSONField would have a string version of that field, instead of it embedded inline. (Back to pre 0.8 behaviour).
Added better querying support: (``field__contains={'key':'value','key2':'value2'}`` works.)
Removed JSONTableWidget from package.
0.8
~~~
(Many thanks to `IanLewis`_ for these features)
Supports django 1.2
Supports callable and json serializable objects as default
Implemented get_db_prep_value()
Add tests and test runner.
Removed JSONTableWidget from README.
0.7.1
~~~~~
Don't fail when trying to install before django is installed.
0.7
~~~
First time I tagged releases.
Todo
----------
Allow for passing in a function to use for processing unknown data types.
Convert date/time objects nicely to/from ISO strings (YYYY-mm-dd HH:MM:SS
TZNAME). This is actually a bit tricky, as we don't know if we are expecting
a date/time object. We may parse objects as we go, but there could be
some performance issues with this. I'm tempted to say "only do this on TypedJSONField()"
.. _David Cramer's blog: http://justcramer.com/2009/04/14/cleaning-up-with-json-and-sql/
.. _IanLewis: https://bitbucket.org/IanLewis
Platform: UNKNOWN
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Framework :: Django

View File

@ -0,0 +1,24 @@
LICENSE
MANIFEST.in
README.rst
setup.py
tests.py
django_jsonfield.egg-info/PKG-INFO
django_jsonfield.egg-info/SOURCES.txt
django_jsonfield.egg-info/dependency_links.txt
django_jsonfield.egg-info/top_level.txt
jsonfield/VERSION
jsonfield/__init__.py
jsonfield/fields.py
jsonfield/forms.py
jsonfield/models.py
jsonfield/utils.py
jsonfield/widgets.py
jsonfield/templatetags/__init__.py
jsonfield/templatetags/jsonify.py
jsonfield/tests/__init__.py
jsonfield/tests/test_fields.py
jsonfield/tests/test_forms.py
jsonfield/tests/jsonfield_test_app/__init__.py
jsonfield/tests/jsonfield_test_app/forms.py
jsonfield/tests/jsonfield_test_app/models.py

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@
jsonfield

1
jsonfield/VERSION Normal file
View File

@ -0,0 +1 @@
1.0.1

8
jsonfield/__init__.py Normal file
View File

@ -0,0 +1,8 @@
import os
from .fields import JSONField
__all__ = ('JSONField',)
with open(os.path.join(os.path.dirname(__file__), 'VERSION')) as fp:
__version__ = fp.read().strip()

169
jsonfield/fields.py Normal file
View File

@ -0,0 +1,169 @@
from __future__ import unicode_literals
import json
from django.core.exceptions import ValidationError
from django.conf import settings
from django.db import models
from django.db.backends.signals import connection_created
from django.utils.translation import ugettext_lazy as _
from django.utils import six
from .utils import _resolve_object_path
from .widgets import JSONWidget
from .forms import JSONFormField
class JSONField(models.Field):
"""
A field that will ensure the data entered into it is valid JSON.
"""
default_error_messages = {
'invalid': _("'%s' is not a valid JSON string.")
}
description = "JSON object"
def __init__(self, *args, **kwargs):
if not kwargs.get('null', False):
kwargs['default'] = kwargs.get('default', dict)
self.encoder_kwargs = {
'indent': kwargs.pop('indent', getattr(settings, 'JSONFIELD_INDENT', None)),
}
# This can be an object (probably a class), or a path which can be imported, resulting
# in an object.
encoder_class = kwargs.pop('encoder_class', getattr(settings, 'JSONFIELD_ENCODER_CLASS', None))
if encoder_class:
self.encoder_kwargs['cls'] = _resolve_object_path(encoder_class)
self.decoder_kwargs = dict(kwargs.pop('decoder_kwargs', getattr(settings, 'JSONFIELD_DECODER_KWARGS', {})))
super(JSONField, self).__init__(*args, **kwargs)
self.validate(self.get_default(), None)
def formfield(self, **kwargs):
defaults = {
'form_class': JSONFormField,
'widget': JSONWidget
}
defaults.update(**kwargs)
return super(JSONField, self).formfield(**defaults)
def validate(self, value, model_instance):
if not self.null and value is None:
raise ValidationError(self.error_messages['null'])
try:
self.get_prep_value(value)
except ValueError:
raise ValidationError(self.error_messages['invalid'] % value)
def get_default(self):
if self.has_default():
default = self.default
if callable(default):
default = default()
if isinstance(default, six.string_types):
return json.loads(default, **self.decoder_kwargs)
return json.loads(json.dumps(default, **self.encoder_kwargs), **self.decoder_kwargs)
return super(JSONField, self).get_default()
def get_internal_type(self):
return 'TextField'
def db_type(self, connection):
if connection.vendor == 'postgresql':
# Only do jsonb if in pg 9.4+
if connection.pg_version >= 90400:
return 'jsonb'
return 'text'
if connection.vendor == 'mysql':
return 'longtext'
if connection.vendor == 'oracle':
return 'long'
return 'text'
def from_db_value(self, value, expression, connection, context):
if value is None:
return None
return json.loads(value, **self.decoder_kwargs)
def get_db_prep_value(self, value, connection=None, prepared=None):
return self.get_prep_value(value)
def get_prep_value(self, value):
if value is None:
if not self.null and self.blank:
return ""
return None
return json.dumps(value, **self.encoder_kwargs)
def get_prep_lookup(self, lookup_type, value):
if lookup_type in ["exact", "iexact", "in", "isnull"]:
return value
if lookup_type in ["contains", "icontains"]:
if isinstance(value, (list, tuple)):
raise TypeError("Lookup type %r not supported with argument of %s" % (
lookup_type, type(value).__name__
))
# Need a way co combine the values with '%', but don't escape that.
return self.get_prep_value(value)[1:-1].replace(', ', r'%')
if isinstance(value, dict):
return self.get_prep_value(value)[1:-1]
return self.get_prep_value(value)
raise TypeError('Lookup type %r not supported' % lookup_type)
def value_to_string(self, obj):
return self._get_val_from_obj(obj)
class TypedJSONField(JSONField):
"""
"""
def __init__(self, *args, **kwargs):
self.json_required_fields = kwargs.pop('required_fields', {})
self.json_validators = kwargs.pop('validators', [])
super(TypedJSONField, self).__init__(*args, **kwargs)
def cast_required_fields(self, obj):
if not obj:
return
for field_name, field_type in self.json_required_fields.items():
obj[field_name] = field_type.to_python(obj[field_name])
def to_python(self, value):
value = super(TypedJSONField, self).to_python(value)
if isinstance(value, list):
for item in value:
self.cast_required_fields(item)
else:
self.cast_required_fields(value)
return value
def validate(self, value, model_instance):
super(TypedJSONField, self).validate(value, model_instance)
for v in self.json_validators:
if isinstance(value, list):
for item in value:
v(item)
else:
v(value)
def configure_database_connection(connection, **kwargs):
if connection.vendor != 'postgresql':
return
# Ensure that psycopg does not do JSON decoding under the hood
# We want to be able to do our own decoding with our own options
import psycopg2.extras
if hasattr(psycopg2.extras, 'register_default_jsonb'):
psycopg2.extras.register_default_jsonb(
connection.connection,
globally=False,
loads=lambda x: x)
connection_created.connect(configure_database_connection)

31
jsonfield/forms.py Normal file
View File

@ -0,0 +1,31 @@
import json
from django import forms
from django.utils import six
from .widgets import JSONWidget
class JSONFormField(forms.CharField):
empty_values = [None, '']
def __init__(self, *args, **kwargs):
if 'widget' not in kwargs:
kwargs['widget'] = JSONWidget
super(JSONFormField, self).__init__(*args, **kwargs)
def to_python(self, value):
if isinstance(value, six.string_types) and value:
try:
return json.loads(value)
except ValueError as exc:
raise forms.ValidationError(
'JSON decode error: %s' % (six.u(exc.args[0]),)
)
else:
return value
def validate(self, value):
# This is required in older django versions.
if value in self.empty_values and self.required:
raise forms.ValidationError(self.error_messages['required'], code='required')

0
jsonfield/models.py Normal file
View File

View File

View File

@ -0,0 +1,28 @@
import json
from django import template
from django.utils.safestring import mark_safe
from jsonfield.utils import TZAwareJSONEncoder
register = template.Library()
@register.filter
def jsonify(value):
# If we have a queryset, then convert it into a list.
if getattr(value, 'all', False):
value = list(value)
json_str = json.dumps(value, cls=TZAwareJSONEncoder)
unsafe_chars = {
'&': '\\u0026',
'<': '\\u003c',
'>': '\\u003e',
'\u2028': '\\u2028',
'\u2029': '\\u2029',
}
for (unsafe, safe) in unsafe_chars.items():
json_str = json_str.replace(unsafe, safe)
return json_str

View File

@ -0,0 +1,2 @@
from .test_fields import * # NOQA
from .test_forms import * # NOQA

View File

@ -0,0 +1,16 @@
from django import forms
from jsonfield.forms import JSONFormField
from .models import JSONFieldTestModel
class JSONTestForm(forms.Form):
json_data = JSONFormField()
optional_json_data = JSONFormField(required=False)
class JSONTestModelForm(forms.ModelForm):
class Meta:
model = JSONFieldTestModel
exclude = []

View File

@ -0,0 +1,31 @@
from django.db import models
from jsonfield.fields import JSONField
class JSONFieldTestModel(models.Model):
json = JSONField("test", null=True, blank=True)
class Meta:
app_label = 'jsonfield'
class JSONFieldWithDefaultTestModel(models.Model):
json = JSONField(default={"sukasuka": "YAAAAAZ"})
class Meta:
app_label = 'jsonfield'
class BlankJSONFieldTestModel(models.Model):
null_json = JSONField(null=True)
blank_json = JSONField(blank=True)
class Meta:
app_label = 'jsonfield'
class CallableDefaultModel(models.Model):
json = JSONField(default=lambda: {'x': 2})
class Meta:
app_label = 'jsonfield'

View File

@ -0,0 +1,163 @@
from django.test import TestCase as DjangoTestCase
from django.utils.encoding import force_text
from django import forms
from jsonfield.tests.jsonfield_test_app.models import * # NOQA
from jsonfield.fields import JSONField
class JSONFieldTest(DjangoTestCase):
def test_json_field(self):
obj = JSONFieldTestModel(json={'spam': 'eggs'})
self.assertEqual(obj.json, {'spam': 'eggs'})
def test_json_field_empty(self):
obj = JSONFieldTestModel(json='')
self.assertEqual(obj.json, '')
def test_json_field_null(self):
obj = JSONFieldTestModel(json=None)
self.assertEqual(obj.json, None)
def test_json_field_save(self):
JSONFieldTestModel.objects.create(
id=10,
json={'spam': 'eggs'},
)
obj2 = JSONFieldTestModel.objects.get(id=10)
self.assertEqual(obj2.json, {'spam': 'eggs'})
def test_json_field_save_empty(self):
JSONFieldTestModel.objects.create(id=10, json='')
obj2 = JSONFieldTestModel.objects.get(id=10)
self.assertEqual(obj2.json, '')
def test_json_field_save_null(self):
JSONFieldTestModel.objects.create(id=10, json=None)
obj2 = JSONFieldTestModel.objects.get(id=10)
self.assertEqual(obj2.json, None)
def test_db_prep_save(self):
field = JSONField("test")
field.set_attributes_from_name("json")
self.assertEqual(None, field.get_db_prep_save(None, connection=None))
self.assertEqual('{"spam": "eggs"}', field.get_db_prep_save({"spam": "eggs"}, connection=None))
def test_formfield(self):
from jsonfield.forms import JSONFormField
from jsonfield.widgets import JSONWidget
field = JSONField("test")
field.set_attributes_from_name("json")
formfield = field.formfield()
self.assertEqual(type(formfield), JSONFormField)
self.assertEqual(type(formfield.widget), JSONWidget)
def test_formfield_clean_blank(self):
field = JSONField("test")
formfield = field.formfield()
self.assertRaisesMessage(
forms.ValidationError,
force_text(formfield.error_messages['required']),
formfield.clean,
value='')
def test_formfield_clean_none(self):
field = JSONField("test")
formfield = field.formfield()
self.assertRaisesMessage(
forms.ValidationError,
force_text(formfield.error_messages['required']),
formfield.clean,
value=None)
def test_formfield_null_and_blank_clean_blank(self):
field = JSONField("test", null=True, blank=True)
formfield = field.formfield()
self.assertEqual(formfield.clean(value=''), '')
def test_formfield_null_and_blank_clean_none(self):
field = JSONField("test", null=True, blank=True)
formfield = field.formfield()
self.assertEqual(formfield.clean(value=None), None)
def test_formfield_blank_clean_blank(self):
field = JSONField("test", null=False, blank=True)
formfield = field.formfield()
self.assertEqual(formfield.clean(value=''), '')
def test_formfield_blank_clean_none(self):
# Hmm, I'm not sure how to do this. What happens if we pass a
# None to a field that has null=False?
field = JSONField("test", null=False, blank=True)
formfield = field.formfield()
self.assertEqual(formfield.clean(value=None), None)
def test_default_value(self):
obj = JSONFieldWithDefaultTestModel.objects.create()
obj = JSONFieldWithDefaultTestModel.objects.get(id=obj.id)
self.assertEqual(obj.json, {'sukasuka': 'YAAAAAZ'})
def test_query_object(self):
JSONFieldTestModel.objects.create(json={})
JSONFieldTestModel.objects.create(json={'foo': 'bar'})
self.assertEqual(2, JSONFieldTestModel.objects.all().count())
self.assertEqual(1, JSONFieldTestModel.objects.exclude(json={}).count())
self.assertEqual(1, JSONFieldTestModel.objects.filter(json={}).count())
self.assertEqual(1, JSONFieldTestModel.objects.filter(json={'foo': 'bar'}).count())
self.assertEqual(1, JSONFieldTestModel.objects.filter(json__contains={'foo': 'bar'}).count())
JSONFieldTestModel.objects.create(json={'foo': 'bar', 'baz': 'bing'})
self.assertEqual(2, JSONFieldTestModel.objects.filter(json__contains={'foo': 'bar'}).count())
# This next one is a bit hard to do without proper lookups, which I'm unlikely to implement.
# self.assertEqual(1, JSONFieldTestModel.objects.filter(json__contains={'baz':'bing', 'foo':'bar'}).count())
self.assertEqual(2, JSONFieldTestModel.objects.filter(json__contains='foo').count())
# This code needs to be implemented!
self.assertRaises(TypeError, lambda: JSONFieldTestModel.objects.filter(json__contains=['baz', 'foo']))
def test_query_isnull(self):
JSONFieldTestModel.objects.create(json=None)
JSONFieldTestModel.objects.create(json={})
JSONFieldTestModel.objects.create(json={'foo': 'bar'})
self.assertEqual(1, JSONFieldTestModel.objects.filter(json=None).count())
self.assertEqual(None, JSONFieldTestModel.objects.get(json=None).json)
def test_jsonfield_blank(self):
BlankJSONFieldTestModel.objects.create(blank_json='', null_json=None)
obj = BlankJSONFieldTestModel.objects.get()
self.assertEqual(None, obj.null_json)
self.assertEqual("", obj.blank_json)
obj.save()
obj = BlankJSONFieldTestModel.objects.get()
self.assertEqual(None, obj.null_json)
self.assertEqual("", obj.blank_json)
def test_callable_default(self):
CallableDefaultModel.objects.create()
obj = CallableDefaultModel.objects.get()
self.assertEqual({'x': 2}, obj.json)
def test_callable_default_overridden(self):
CallableDefaultModel.objects.create(json={'x': 3})
obj = CallableDefaultModel.objects.get()
self.assertEqual({'x': 3}, obj.json)
def test_mutable_default_checking(self):
obj1 = JSONFieldWithDefaultTestModel()
obj2 = JSONFieldWithDefaultTestModel()
obj1.json['foo'] = 'bar'
self.assertNotIn('foo', obj2.json)
def test_indent(self):
JSONField('test', indent=2)
def test_string_is_not_json_decoded(self):
JSONFieldTestModel.objects.create(json='"foo"')
self.assertEqual('"foo"', JSONFieldTestModel.objects.get().json)
class SavingModelsTest(DjangoTestCase):
def test_saving_null(self):
obj = BlankJSONFieldTestModel.objects.create(blank_json='', null_json=None)
self.assertEqual('', obj.blank_json)
self.assertEqual(None, obj.null_json)

View File

@ -0,0 +1,62 @@
from django.test import TestCase as DjangoTestCase
from django.forms import ValidationError
from jsonfield.forms import JSONFormField
from jsonfield.tests.jsonfield_test_app.forms import JSONTestForm
class JSONFormFieldTest(DjangoTestCase):
def test_form_field_clean_empty_object(self):
field = JSONFormField(required=False)
self.assertEqual({}, field.clean('{}'))
def test_form_field_clean_object(self):
field = JSONFormField(required=False)
self.assertEqual(
{'foo': 'bar', 'baz': 2},
field.clean('{"foo":"bar","baz":2}')
)
def test_form_field_widget(self):
field = JSONFormField(required=False)
self.assertIn(
'{\n &quot;a&quot;: true\n}',
field.widget.render('json', {"a": True})
)
def test_form_field_clean_empty_array(self):
field = JSONFormField(required=False)
self.assertEqual([], field.clean('[]'))
def test_required_form_field_array(self):
field = JSONFormField(required=True)
self.assertEqual([], field.clean('[]'))
def test_required_form_field_object(self):
field = JSONFormField(required=True)
self.assertEqual({}, field.clean('{}'))
def test_required_form_field_empty(self):
field = JSONFormField(required=True)
with self.assertRaises(ValidationError):
field.clean('')
def test_invalid_json(self):
field = JSONFormField(required=True)
with self.assertRaises(ValidationError):
field.clean('{"foo"}')
class JSONFormTest(DjangoTestCase):
def test_form_clean(self):
form = JSONTestForm({})
self.assertFalse(form.is_valid())
class JSONFormMultipleSelectFieldTest(DjangoTestCase):
def test_multiple_select_data(self):
form = JSONTestForm({'json_data': ['SA', 'WA']})
assert form.is_valid()
self.assertEqual(['SA', 'WA'], form.cleaned_data['json_data'])

44
jsonfield/utils.py Normal file
View File

@ -0,0 +1,44 @@
import datetime
from decimal import Decimal
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import six
class TZAwareJSONEncoder(DjangoJSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S%z")
return super(TZAwareJSONEncoder, self).default(obj)
def default(o):
if hasattr(o, 'to_json'):
return o.to_json()
if isinstance(o, Decimal):
return str(o)
if isinstance(o, datetime.datetime):
if o.tzinfo:
return o.strftime('%Y-%m-%dT%H:%M:%S%z')
return o.strftime("%Y-%m-%dT%H:%M:%S")
if isinstance(o, datetime.date):
return o.strftime("%Y-%m-%d")
if isinstance(o, datetime.time):
if o.tzinfo:
return o.strftime('%H:%M:%S%z')
return o.strftime("%H:%M:%S")
if isinstance(o, set):
return list(o)
raise TypeError(repr(o) + " is not JSON serializable")
def _resolve_object_path(dotted_name):
if isinstance(dotted_name, six.string_types):
path = dotted_name.split('.')
module = __import__(dotted_name.rsplit('.', 1)[0])
for item in path[1:-1]:
module = getattr(module, item)
return getattr(module, path[-1])
return dotted_name

20
jsonfield/widgets.py Normal file
View File

@ -0,0 +1,20 @@
import json
from django import forms
from django.utils import six
from .utils import default
class JSONWidget(forms.Textarea):
def render(self, name, value, attrs=None):
if value is None:
value = ""
if not isinstance(value, six.string_types):
value = json.dumps(value, ensure_ascii=False, indent=2,
default=default)
return super(JSONWidget, self).render(name, value, attrs)
class JSONSelectWidget(forms.SelectMultiple):
pass

5
setup.cfg Normal file
View File

@ -0,0 +1,5 @@
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0

24
setup.py Normal file
View File

@ -0,0 +1,24 @@
import os
from setuptools import setup
setup(
name = "django-jsonfield",
version = open(os.path.join(os.path.dirname(__file__), 'jsonfield', 'VERSION')).read().strip(),
description = "JSONField for django models",
long_description = open("README.rst").read(),
url = "http://bitbucket.org/schinckel/django-jsonfield/",
author = "Matthew Schinckel",
author_email = "matt@schinckel.net",
packages = [
"jsonfield",
],
classifiers = [
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Framework :: Django',
],
test_suite='tests.main',
include_package_data=True,
)

49
tests.py Normal file
View File

@ -0,0 +1,49 @@
import os
import sys
import django
BASE_PATH = os.path.dirname(__file__)
def main():
"""
Standalone django model test with a 'memory-only-django-installation'.
You can play with a django model without a complete django app installation.
http://www.djangosnippets.org/snippets/1044/
"""
os.environ["DJANGO_SETTINGS_MODULE"] = "django.conf.global_settings"
from django.conf import global_settings
global_settings.INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'jsonfield',
)
global_settings.DATABASES = {
'default': {
'ENGINE': 'django.db.backends.{DB_ENGINE}'.format(**os.environ),
'NAME': 'jsonfield-{DB_NAME}'.format(**os.environ),
'USER': os.environ.get('DB_USER', ''),
'PASSWORD': os.environ.get('DB_PASSWORD', ''),
}
}
global_settings.STATIC_URL = "/static/"
global_settings.MEDIA_ROOT = os.path.join(BASE_PATH, 'static')
global_settings.STATIC_ROOT = global_settings.MEDIA_ROOT
global_settings.SECRET_KEY = '334ebe58-a77d-4321-9d01-a7d2cb8d3eea'
from django.test.utils import get_runner
test_runner = get_runner(global_settings)
test_runner = test_runner()
if getattr(django, 'setup', None):
django.setup()
failures = test_runner.run_tests(['jsonfield'])
sys.exit(failures)
if __name__ == '__main__':
main()