Loading django/contrib/postgres/fields/__init__.py +1 −0 Original line number Diff line number Diff line from .array import * # NOQA from .hstore import * # NOQA from .jsonb import * # NOQA from .ranges import * # NOQA django/contrib/postgres/fields/jsonb.py 0 → 100644 +99 −0 Original line number Diff line number Diff line import json from psycopg2.extras import Json from django.contrib.postgres import forms, lookups from django.core import exceptions from django.db.models import Field, Transform from django.utils.translation import ugettext_lazy as _ __all__ = ['JSONField'] class JSONField(Field): empty_strings_allowed = False description = _('A JSON object') default_error_messages = { 'invalid': _("Value must be valid JSON."), } def db_type(self, connection): return 'jsonb' def get_transform(self, name): transform = super(JSONField, self).get_transform(name) if transform: return transform return KeyTransformFactory(name) def get_prep_value(self, value): if value is not None: return Json(value) return value def get_prep_lookup(self, lookup_type, value): if lookup_type in ('has_key', 'has_keys', 'has_any_keys'): return value if isinstance(value, (dict, list)): return Json(value) return super(JSONField, self).get_prep_lookup(lookup_type, value) def validate(self, value, model_instance): super(JSONField, self).validate(value, model_instance) try: json.dumps(value) except TypeError: raise exceptions.ValidationError( self.error_messages['invalid'], code='invalid', params={'value': value}, ) def value_to_string(self, obj): value = self._get_val_from_obj(obj) return value def formfield(self, **kwargs): defaults = {'form_class': forms.JSONField} defaults.update(kwargs) return super(JSONField, self).formfield(**defaults) JSONField.register_lookup(lookups.DataContains) JSONField.register_lookup(lookups.ContainedBy) JSONField.register_lookup(lookups.HasKey) JSONField.register_lookup(lookups.HasKeys) JSONField.register_lookup(lookups.HasAnyKeys) class KeyTransform(Transform): def __init__(self, key_name, *args, **kwargs): super(KeyTransform, self).__init__(*args, **kwargs) self.key_name = key_name def as_sql(self, compiler, connection): key_transforms = [self.key_name] previous = self.lhs while isinstance(previous, KeyTransform): key_transforms.insert(0, previous.key_name) previous = previous.lhs lhs, params = compiler.compile(previous) if len(key_transforms) > 1: return "{} #> %s".format(lhs), [key_transforms] + params try: int(self.key_name) except ValueError: lookup = "'%s'" % self.key_name else: lookup = "%s" % self.key_name return "%s -> %s" % (lhs, lookup), params class KeyTransformFactory(object): def __init__(self, key_name): self.key_name = key_name def __call__(self, *args, **kwargs): return KeyTransform(self.key_name, *args, **kwargs) django/contrib/postgres/forms/__init__.py +1 −0 Original line number Diff line number Diff line from .array import * # NOQA from .hstore import * # NOQA from .jsonb import * # NOQA from .ranges import * # NOQA django/contrib/postgres/forms/jsonb.py 0 → 100644 +31 −0 Original line number Diff line number Diff line import json from django import forms from django.utils.translation import ugettext_lazy as _ __all__ = ['JSONField'] class JSONField(forms.CharField): default_error_messages = { 'invalid': _("'%(value)s' value must be valid JSON."), } def __init__(self, **kwargs): kwargs.setdefault('widget', forms.Textarea) super(JSONField, self).__init__(**kwargs) def to_python(self, value): if value in self.empty_values: return None try: return json.loads(value) except ValueError: raise forms.ValidationError( self.error_messages['invalid'], code='invalid', params={'value': value}, ) def prepare_value(self, value): return json.dumps(value) docs/ref/contrib/postgres/fields.txt +105 −0 Original line number Diff line number Diff line Loading @@ -450,6 +450,111 @@ using in conjunction with lookups on >>> Dog.objects.filter(data__values__contains=['collie']) [<Dog: Meg>] JSONField --------- .. versionadded:: 1.9 .. class:: JSONField(**options) A field for storing JSON encoded data. In Python the data is represented in its Python native format: dictionaries, lists, strings, numbers, booleans and ``None``. .. note:: PostgreSQL has two native JSON based data types: ``json`` and ``jsonb``. The main difference between them is how they are stored and how they can be queried. PostgreSQL's ``json`` field is stored as the original string representation of the JSON and must be decoded on the fly when queried based on keys. The ``jsonb`` field is stored based on the actual structure of the JSON which allows indexing. The trade-off is a small additional cost on writing to the ``jsonb`` field. ``JSONField`` uses ``jsonb``. **As a result, the usage of this field is only supported on PostgreSQL versions at least 9.4**. Querying JSONField ^^^^^^^^^^^^^^^^^^ We will use the following example model:: from django.contrib.postgres.fields import JSONField from django.db import models class Dog(models.Model): name = models.CharField(max_length=200) data = JSONField() def __str__(self): # __unicode__ on Python 2 return self.name .. fieldlookup:: jsonfield.key Key, index, and path lookups ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To query based on a given dictionary key, simply use that key as the lookup name:: >>> Dog.objects.create(name='Rufus', data={ ... 'breed': 'labrador', ... 'owner': { ... 'name': 'Bob', ... 'other_pets': [{ ... 'name': 'Fishy', ... }], ... }, ... }) >>> Dog.objects.create(name='Meg', data={'breed': 'collie'}) >>> Dog.objects.filter(data__breed='collie') [<Dog: Meg>] Multiple keys can be chained together to form a path lookup:: >>> Dog.objects.filter(data__owner__name='Bob') [<Dog: Rufus>] If the key is an integer, it will be interpreted as an index lookup in an array:: >>> Dog.objects.filter(data__owner__other_pets__0__name='Fishy') [<Dog: Rufus>] If the key you wish to query by clashes with the name of another lookup, use the :lookup:`jsonfield.contains` lookup instead. If only one key or index is used, the SQL operator ``->`` is used. If multiple operators are used then the ``#>`` operator is used. .. warning:: Since any string could be a key in a JSON object, any lookup other than those listed below will be interpreted as a key lookup. No errors are raised. Be extra careful for typing mistakes, and always check your queries work as you intend. Containment and key operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. fieldlookup:: jsonfield.contains .. fieldlookup:: jsonfield.contained_by .. fieldlookup:: jsonfield.has_key .. fieldlookup:: jsonfield.has_any_keys .. fieldlookup:: jsonfield.has_keys :class:`~django.contrib.postgres.fields.JSONField` shares lookups relating to containment and keys with :class:`~django.contrib.postgres.fields.HStoreField`. - :lookup:`contains <hstorefield.contains>` (accepts any JSON rather than just a dictionary of strings) - :lookup:`contained_by <hstorefield.contained_by>` (accepts any JSON rather than just a dictionary of strings) - :lookup:`has_key <hstorefield.has_key>` - :lookup:`has_any_keys <hstorefield.has_any_keys>` - :lookup:`has_keys <hstorefield.has_keys>` .. _range-fields: Range Fields Loading Loading
django/contrib/postgres/fields/__init__.py +1 −0 Original line number Diff line number Diff line from .array import * # NOQA from .hstore import * # NOQA from .jsonb import * # NOQA from .ranges import * # NOQA
django/contrib/postgres/fields/jsonb.py 0 → 100644 +99 −0 Original line number Diff line number Diff line import json from psycopg2.extras import Json from django.contrib.postgres import forms, lookups from django.core import exceptions from django.db.models import Field, Transform from django.utils.translation import ugettext_lazy as _ __all__ = ['JSONField'] class JSONField(Field): empty_strings_allowed = False description = _('A JSON object') default_error_messages = { 'invalid': _("Value must be valid JSON."), } def db_type(self, connection): return 'jsonb' def get_transform(self, name): transform = super(JSONField, self).get_transform(name) if transform: return transform return KeyTransformFactory(name) def get_prep_value(self, value): if value is not None: return Json(value) return value def get_prep_lookup(self, lookup_type, value): if lookup_type in ('has_key', 'has_keys', 'has_any_keys'): return value if isinstance(value, (dict, list)): return Json(value) return super(JSONField, self).get_prep_lookup(lookup_type, value) def validate(self, value, model_instance): super(JSONField, self).validate(value, model_instance) try: json.dumps(value) except TypeError: raise exceptions.ValidationError( self.error_messages['invalid'], code='invalid', params={'value': value}, ) def value_to_string(self, obj): value = self._get_val_from_obj(obj) return value def formfield(self, **kwargs): defaults = {'form_class': forms.JSONField} defaults.update(kwargs) return super(JSONField, self).formfield(**defaults) JSONField.register_lookup(lookups.DataContains) JSONField.register_lookup(lookups.ContainedBy) JSONField.register_lookup(lookups.HasKey) JSONField.register_lookup(lookups.HasKeys) JSONField.register_lookup(lookups.HasAnyKeys) class KeyTransform(Transform): def __init__(self, key_name, *args, **kwargs): super(KeyTransform, self).__init__(*args, **kwargs) self.key_name = key_name def as_sql(self, compiler, connection): key_transforms = [self.key_name] previous = self.lhs while isinstance(previous, KeyTransform): key_transforms.insert(0, previous.key_name) previous = previous.lhs lhs, params = compiler.compile(previous) if len(key_transforms) > 1: return "{} #> %s".format(lhs), [key_transforms] + params try: int(self.key_name) except ValueError: lookup = "'%s'" % self.key_name else: lookup = "%s" % self.key_name return "%s -> %s" % (lhs, lookup), params class KeyTransformFactory(object): def __init__(self, key_name): self.key_name = key_name def __call__(self, *args, **kwargs): return KeyTransform(self.key_name, *args, **kwargs)
django/contrib/postgres/forms/__init__.py +1 −0 Original line number Diff line number Diff line from .array import * # NOQA from .hstore import * # NOQA from .jsonb import * # NOQA from .ranges import * # NOQA
django/contrib/postgres/forms/jsonb.py 0 → 100644 +31 −0 Original line number Diff line number Diff line import json from django import forms from django.utils.translation import ugettext_lazy as _ __all__ = ['JSONField'] class JSONField(forms.CharField): default_error_messages = { 'invalid': _("'%(value)s' value must be valid JSON."), } def __init__(self, **kwargs): kwargs.setdefault('widget', forms.Textarea) super(JSONField, self).__init__(**kwargs) def to_python(self, value): if value in self.empty_values: return None try: return json.loads(value) except ValueError: raise forms.ValidationError( self.error_messages['invalid'], code='invalid', params={'value': value}, ) def prepare_value(self, value): return json.dumps(value)
docs/ref/contrib/postgres/fields.txt +105 −0 Original line number Diff line number Diff line Loading @@ -450,6 +450,111 @@ using in conjunction with lookups on >>> Dog.objects.filter(data__values__contains=['collie']) [<Dog: Meg>] JSONField --------- .. versionadded:: 1.9 .. class:: JSONField(**options) A field for storing JSON encoded data. In Python the data is represented in its Python native format: dictionaries, lists, strings, numbers, booleans and ``None``. .. note:: PostgreSQL has two native JSON based data types: ``json`` and ``jsonb``. The main difference between them is how they are stored and how they can be queried. PostgreSQL's ``json`` field is stored as the original string representation of the JSON and must be decoded on the fly when queried based on keys. The ``jsonb`` field is stored based on the actual structure of the JSON which allows indexing. The trade-off is a small additional cost on writing to the ``jsonb`` field. ``JSONField`` uses ``jsonb``. **As a result, the usage of this field is only supported on PostgreSQL versions at least 9.4**. Querying JSONField ^^^^^^^^^^^^^^^^^^ We will use the following example model:: from django.contrib.postgres.fields import JSONField from django.db import models class Dog(models.Model): name = models.CharField(max_length=200) data = JSONField() def __str__(self): # __unicode__ on Python 2 return self.name .. fieldlookup:: jsonfield.key Key, index, and path lookups ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To query based on a given dictionary key, simply use that key as the lookup name:: >>> Dog.objects.create(name='Rufus', data={ ... 'breed': 'labrador', ... 'owner': { ... 'name': 'Bob', ... 'other_pets': [{ ... 'name': 'Fishy', ... }], ... }, ... }) >>> Dog.objects.create(name='Meg', data={'breed': 'collie'}) >>> Dog.objects.filter(data__breed='collie') [<Dog: Meg>] Multiple keys can be chained together to form a path lookup:: >>> Dog.objects.filter(data__owner__name='Bob') [<Dog: Rufus>] If the key is an integer, it will be interpreted as an index lookup in an array:: >>> Dog.objects.filter(data__owner__other_pets__0__name='Fishy') [<Dog: Rufus>] If the key you wish to query by clashes with the name of another lookup, use the :lookup:`jsonfield.contains` lookup instead. If only one key or index is used, the SQL operator ``->`` is used. If multiple operators are used then the ``#>`` operator is used. .. warning:: Since any string could be a key in a JSON object, any lookup other than those listed below will be interpreted as a key lookup. No errors are raised. Be extra careful for typing mistakes, and always check your queries work as you intend. Containment and key operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. fieldlookup:: jsonfield.contains .. fieldlookup:: jsonfield.contained_by .. fieldlookup:: jsonfield.has_key .. fieldlookup:: jsonfield.has_any_keys .. fieldlookup:: jsonfield.has_keys :class:`~django.contrib.postgres.fields.JSONField` shares lookups relating to containment and keys with :class:`~django.contrib.postgres.fields.HStoreField`. - :lookup:`contains <hstorefield.contains>` (accepts any JSON rather than just a dictionary of strings) - :lookup:`contained_by <hstorefield.contained_by>` (accepts any JSON rather than just a dictionary of strings) - :lookup:`has_key <hstorefield.has_key>` - :lookup:`has_any_keys <hstorefield.has_any_keys>` - :lookup:`has_keys <hstorefield.has_keys>` .. _range-fields: Range Fields Loading