1
0
mirror of https://github.com/django/django.git synced 2025-11-07 07:15:35 +00:00

Fixed #12990, Refs #27694 -- Added JSONField model field.

Thanks to Adam Johnson, Carlton Gibson, Mariusz Felisiak, and Raphael
Michel for mentoring this Google Summer of Code 2019 project and
everyone else who helped with the patch.

Special thanks to Mads Jensen, Nick Pope, and Simon Charette for
extensive reviews.

Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
This commit is contained in:
sage
2019-06-09 07:56:37 +07:00
committed by Mariusz Felisiak
parent f97f71f592
commit 6789ded0a6
54 changed files with 2240 additions and 981 deletions

View File

@@ -4,6 +4,7 @@ Field classes.
import copy
import datetime
import json
import math
import operator
import os
@@ -21,8 +22,8 @@ from django.forms.widgets import (
FILE_INPUT_CONTRADICTION, CheckboxInput, ClearableFileInput, DateInput,
DateTimeInput, EmailInput, FileInput, HiddenInput, MultipleHiddenInput,
NullBooleanSelect, NumberInput, Select, SelectMultiple,
SplitDateTimeWidget, SplitHiddenDateTimeWidget, TextInput, TimeInput,
URLInput,
SplitDateTimeWidget, SplitHiddenDateTimeWidget, Textarea, TextInput,
TimeInput, URLInput,
)
from django.utils import formats
from django.utils.dateparse import parse_datetime, parse_duration
@@ -38,7 +39,8 @@ __all__ = (
'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField', 'GenericIPAddressField', 'FilePathField',
'SlugField', 'TypedChoiceField', 'TypedMultipleChoiceField', 'UUIDField',
'JSONField', 'SlugField', 'TypedChoiceField', 'TypedMultipleChoiceField',
'UUIDField',
)
@@ -1211,3 +1213,66 @@ class UUIDField(CharField):
except ValueError:
raise ValidationError(self.error_messages['invalid'], code='invalid')
return value
class InvalidJSONInput(str):
pass
class JSONString(str):
pass
class JSONField(CharField):
default_error_messages = {
'invalid': _('Enter a valid JSON.'),
}
widget = Textarea
def __init__(self, encoder=None, decoder=None, **kwargs):
self.encoder = encoder
self.decoder = decoder
super().__init__(**kwargs)
def to_python(self, value):
if self.disabled:
return value
if value in self.empty_values:
return None
elif isinstance(value, (list, dict, int, float, JSONString)):
return value
try:
converted = json.loads(value, cls=self.decoder)
except json.JSONDecodeError:
raise ValidationError(
self.error_messages['invalid'],
code='invalid',
params={'value': value},
)
if isinstance(converted, str):
return JSONString(converted)
else:
return converted
def bound_data(self, data, initial):
if self.disabled:
return initial
try:
return json.loads(data, cls=self.decoder)
except json.JSONDecodeError:
return InvalidJSONInput(data)
def prepare_value(self, value):
if isinstance(value, InvalidJSONInput):
return value
return json.dumps(value, cls=self.encoder)
def has_changed(self, initial, data):
if super().has_changed(initial, data):
return True
# For purposes of seeing whether something has changed, True isn't the
# same as 1 and the order of keys doesn't matter.
return (
json.dumps(initial, sort_keys=True, cls=self.encoder) !=
json.dumps(self.to_python(data), sort_keys=True, cls=self.encoder)
)