import release 0.9.19
This commit is contained in:
commit
4c6730a146
|
@ -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.
|
|
@ -0,0 +1,6 @@
|
|||
recursive-include jsonfield *.py
|
||||
include jsonfield/VERSION
|
||||
include README.rst
|
||||
include tests.py
|
||||
include LICENSE
|
||||
recursive-exclude jsonfield *.pyc
|
|
@ -0,0 +1,197 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: django-jsonfield
|
||||
Version: 0.9.19
|
||||
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()
|
||||
|
||||
Now, it will validate the JSON on entry, and store it as a string in the
|
||||
database. When you instantiate/fetch the object, it will be turned back
|
||||
into a python list/dict/string.
|
||||
|
||||
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.
|
||||
|
||||
There are also a couple of other bits and bobs:
|
||||
|
||||
Extras
|
||||
------
|
||||
|
||||
jsonify templatetag
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
This allows you to convert a python data structure into JSON within a template::
|
||||
|
||||
{% load jsonify %}
|
||||
|
||||
<script>
|
||||
var foo = {{ bar|jsonify }};
|
||||
</script>
|
||||
|
||||
History
|
||||
----------
|
||||
|
||||
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
|
|
@ -0,0 +1,182 @@
|
|||
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()
|
||||
|
||||
Now, it will validate the JSON on entry, and store it as a string in the
|
||||
database. When you instantiate/fetch the object, it will be turned back
|
||||
into a python list/dict/string.
|
||||
|
||||
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.
|
||||
|
||||
There are also a couple of other bits and bobs:
|
||||
|
||||
Extras
|
||||
------
|
||||
|
||||
jsonify templatetag
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
This allows you to convert a python data structure into JSON within a template::
|
||||
|
||||
{% load jsonify %}
|
||||
|
||||
<script>
|
||||
var foo = {{ bar|jsonify }};
|
||||
</script>
|
||||
|
||||
History
|
||||
----------
|
||||
|
||||
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
|
|
@ -0,0 +1,7 @@
|
|||
import os
|
||||
__version__ = open(os.path.join(os.path.dirname(__file__), 'VERSION')).read().strip()
|
||||
|
||||
try:
|
||||
from .fields import JSONField # NOQA
|
||||
except ImportError:
|
||||
pass
|
|
@ -0,0 +1,181 @@
|
|||
from __future__ import unicode_literals
|
||||
import json
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.conf import settings
|
||||
from django.db import models, DatabaseError, transaction
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import six
|
||||
from django.core.cache import cache
|
||||
|
||||
from decimal import Decimal
|
||||
import datetime
|
||||
|
||||
from .utils import default, _resolve_object_path
|
||||
from .widgets import JSONWidget
|
||||
from .forms import JSONFormField
|
||||
from jsonfield import __version__
|
||||
|
||||
DB_TYPE_CACHE_KEY = (
|
||||
'django-jsonfield:db-type:%s' % __version__ +
|
||||
'%(ENGINE)s:%(HOST)s:%(PORT)s:%(NAME)s'
|
||||
)
|
||||
|
||||
|
||||
class JSONField(six.with_metaclass(models.SubfieldBase, 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 to_python(self, value):
|
||||
if isinstance(value, six.string_types):
|
||||
if value == "":
|
||||
if self.null:
|
||||
return None
|
||||
if self.blank:
|
||||
return ""
|
||||
try:
|
||||
value = json.loads(value, **self.decoder_kwargs)
|
||||
except ValueError:
|
||||
msg = self.error_messages['invalid'] % value
|
||||
raise ValidationError(msg)
|
||||
# TODO: Look for date/time/datetime objects within the structure?
|
||||
return value
|
||||
|
||||
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"]:
|
||||
return self.to_python(self.get_prep_value(value))
|
||||
if lookup_type == "in":
|
||||
return [self.to_python(self.get_prep_value(v)) for v in value]
|
||||
if lookup_type == "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.to_python(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)
|
||||
|
||||
try:
|
||||
from south.modelsinspector import add_introspection_rules
|
||||
add_introspection_rules([], ['^jsonfield\.fields\.JSONField'])
|
||||
add_introspection_rules([], ['^jsonfield\.fields\.TypedJSONField'])
|
||||
except ImportError:
|
||||
pass
|
|
@ -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,0 +1,15 @@
|
|||
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)
|
||||
return mark_safe(json.dumps(value, cls=TZAwareJSONEncoder))
|
|
@ -0,0 +1,2 @@
|
|||
from .test_fields import * # NOQA
|
||||
from .test_forms import * # NOQA
|
|
@ -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 = []
|
|
@ -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'
|
|
@ -0,0 +1,165 @@
|
|||
#:coding=utf-8:
|
||||
import unittest
|
||||
|
||||
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, 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, 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_invalid_json(self):
|
||||
obj = JSONFieldTestModel()
|
||||
obj.json = '{"foo": 2}'
|
||||
self.assertIn('foo', obj.json)
|
||||
with self.assertRaises(forms.ValidationError):
|
||||
obj.json = '{"foo"}'
|
||||
|
||||
def test_invalid_json_default(self):
|
||||
with self.assertRaises(ValueError):
|
||||
JSONField('test', default='{"foo"}')
|
||||
|
||||
def test_indent(self):
|
||||
JSONField('test', indent=2)
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_string_is_valid_json(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)
|
|
@ -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 "a": 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'])
|
|
@ -0,0 +1,44 @@
|
|||
import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
|
||||
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
|
|
@ -0,0 +1,19 @@
|
|||
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, indent=2, default=default)
|
||||
return super(JSONWidget, self).render(name, value, attrs)
|
||||
|
||||
|
||||
class JSONSelectWidget(forms.SelectMultiple):
|
||||
pass
|
|
@ -0,0 +1,5 @@
|
|||
[egg_info]
|
||||
tag_build =
|
||||
tag_date = 0
|
||||
tag_svn_revision = 0
|
||||
|
|
@ -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,
|
||||
)
|
|
@ -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()
|
Loading…
Reference in New Issue