mirror of
https://github.com/django/django.git
synced 2025-11-07 07:15:35 +00:00
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:
@@ -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)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user