2016-05-26 20:48:36 +08:00
|
|
|
import datetime
|
2016-06-28 19:48:19 +08:00
|
|
|
import decimal
|
2015-09-26 17:02:09 +08:00
|
|
|
import json
|
|
|
|
import re
|
|
|
|
|
|
|
|
from django.core import serializers
|
|
|
|
from django.core.serializers.base import DeserializationError
|
2015-09-27 02:15:26 +08:00
|
|
|
from django.core.serializers.json import DjangoJSONEncoder
|
2016-06-28 19:48:19 +08:00
|
|
|
from django.db import models
|
2015-09-27 02:15:26 +08:00
|
|
|
from django.test import SimpleTestCase, TestCase, TransactionTestCase
|
2016-06-28 19:48:19 +08:00
|
|
|
from django.test.utils import isolate_apps
|
2017-01-27 03:58:33 +08:00
|
|
|
from django.utils.translation import gettext_lazy, override
|
2015-09-26 17:02:09 +08:00
|
|
|
|
|
|
|
from .models import Score
|
|
|
|
from .tests import SerializersTestBase, SerializersTransactionTestBase
|
|
|
|
|
|
|
|
|
|
|
|
class JsonSerializerTestCase(SerializersTestBase, TestCase):
|
|
|
|
serializer_name = "json"
|
|
|
|
pkless_str = """[
|
|
|
|
{
|
|
|
|
"pk": null,
|
|
|
|
"model": "serializers.category",
|
|
|
|
"fields": {"name": "Reference"}
|
|
|
|
}, {
|
|
|
|
"model": "serializers.category",
|
|
|
|
"fields": {"name": "Non-fiction"}
|
|
|
|
}]"""
|
|
|
|
mapping_ordering_str = """[
|
|
|
|
{
|
|
|
|
"model": "serializers.article",
|
|
|
|
"pk": %(article_pk)s,
|
|
|
|
"fields": {
|
|
|
|
"author": %(author_pk)s,
|
|
|
|
"headline": "Poker has no place on ESPN",
|
|
|
|
"pub_date": "2006-06-16T11:00:00",
|
|
|
|
"categories": [
|
|
|
|
%(first_category_pk)s,
|
|
|
|
%(second_category_pk)s
|
|
|
|
],
|
|
|
|
"meta_data": []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
"""
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _validate_output(serial_str):
|
|
|
|
try:
|
|
|
|
json.loads(serial_str)
|
|
|
|
except Exception:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _get_pk_values(serial_str):
|
|
|
|
serial_list = json.loads(serial_str)
|
2018-01-04 02:24:02 +08:00
|
|
|
return [obj_dict["pk"] for obj_dict in serial_list]
|
2015-09-26 17:02:09 +08:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _get_field_values(serial_str, field_name):
|
|
|
|
serial_list = json.loads(serial_str)
|
2018-01-04 02:24:02 +08:00
|
|
|
return [
|
|
|
|
obj_dict["fields"][field_name]
|
|
|
|
for obj_dict in serial_list
|
|
|
|
if field_name in obj_dict["fields"]
|
2022-02-04 03:24:19 +08:00
|
|
|
]
|
2015-09-26 17:02:09 +08:00
|
|
|
|
|
|
|
def test_indentation_whitespace(self):
|
|
|
|
s = serializers.json.Serializer()
|
2016-06-29 21:56:27 +08:00
|
|
|
json_data = s.serialize([Score(score=5.0), Score(score=6.0)], indent=2)
|
2015-09-26 17:02:09 +08:00
|
|
|
for line in json_data.splitlines():
|
|
|
|
if re.search(r".+,\s*$", line):
|
|
|
|
self.assertEqual(line, line.rstrip())
|
|
|
|
|
2016-06-28 19:48:19 +08:00
|
|
|
@isolate_apps("serializers")
|
|
|
|
def test_custom_encoder(self):
|
|
|
|
class ScoreDecimal(models.Model):
|
|
|
|
score = models.DecimalField()
|
|
|
|
|
|
|
|
class CustomJSONEncoder(json.JSONEncoder):
|
|
|
|
def default(self, o):
|
|
|
|
if isinstance(o, decimal.Decimal):
|
|
|
|
return str(o)
|
2017-01-21 21:13:44 +08:00
|
|
|
return super().default(o)
|
2016-06-28 19:48:19 +08:00
|
|
|
|
|
|
|
s = serializers.json.Serializer()
|
|
|
|
json_data = s.serialize(
|
|
|
|
[ScoreDecimal(score=decimal.Decimal(1.0))], cls=CustomJSONEncoder
|
|
|
|
)
|
|
|
|
self.assertIn('"fields": {"score": "1"}', json_data)
|
|
|
|
|
2015-09-26 17:02:09 +08:00
|
|
|
def test_json_deserializer_exception(self):
|
|
|
|
with self.assertRaises(DeserializationError):
|
|
|
|
for obj in serializers.deserialize("json", """[{"pk":1}"""):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def test_helpful_error_message_invalid_pk(self):
|
|
|
|
"""
|
|
|
|
If there is an invalid primary key, the error message should contain
|
|
|
|
the model associated with it.
|
|
|
|
"""
|
|
|
|
test_string = """[{
|
|
|
|
"pk": "badpk",
|
|
|
|
"model": "serializers.player",
|
|
|
|
"fields": {
|
|
|
|
"name": "Bob",
|
|
|
|
"rank": 1,
|
|
|
|
"team": "Team"
|
|
|
|
}
|
|
|
|
}]"""
|
|
|
|
with self.assertRaisesMessage(
|
|
|
|
DeserializationError, "(serializers.player:pk=badpk)"
|
|
|
|
):
|
|
|
|
list(serializers.deserialize("json", test_string))
|
|
|
|
|
|
|
|
def test_helpful_error_message_invalid_field(self):
|
|
|
|
"""
|
|
|
|
If there is an invalid field value, the error message should contain
|
|
|
|
the model associated with it.
|
|
|
|
"""
|
|
|
|
test_string = """[{
|
|
|
|
"pk": "1",
|
|
|
|
"model": "serializers.player",
|
|
|
|
"fields": {
|
|
|
|
"name": "Bob",
|
|
|
|
"rank": "invalidint",
|
|
|
|
"team": "Team"
|
|
|
|
}
|
|
|
|
}]"""
|
|
|
|
expected = "(serializers.player:pk=1) field_value was 'invalidint'"
|
|
|
|
with self.assertRaisesMessage(DeserializationError, expected):
|
|
|
|
list(serializers.deserialize("json", test_string))
|
|
|
|
|
|
|
|
def test_helpful_error_message_for_foreign_keys(self):
|
|
|
|
"""
|
|
|
|
Invalid foreign keys with a natural key should throw a helpful error
|
|
|
|
message, such as what the failing key is.
|
|
|
|
"""
|
|
|
|
test_string = """[{
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.category",
|
|
|
|
"fields": {
|
|
|
|
"name": "Unknown foreign key",
|
|
|
|
"meta_data": [
|
|
|
|
"doesnotexist",
|
|
|
|
"metadata"
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}]"""
|
|
|
|
key = ["doesnotexist", "metadata"]
|
|
|
|
expected = "(serializers.category:pk=1) field_value was '%r'" % key
|
|
|
|
with self.assertRaisesMessage(DeserializationError, expected):
|
|
|
|
list(serializers.deserialize("json", test_string))
|
|
|
|
|
|
|
|
def test_helpful_error_message_for_many2many_non_natural(self):
|
|
|
|
"""
|
|
|
|
Invalid many-to-many keys should throw a helpful error message.
|
|
|
|
"""
|
|
|
|
test_string = """[{
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.article",
|
|
|
|
"fields": {
|
|
|
|
"author": 1,
|
|
|
|
"headline": "Unknown many to many",
|
|
|
|
"pub_date": "2014-09-15T10:35:00",
|
|
|
|
"categories": [1, "doesnotexist"]
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.author",
|
|
|
|
"fields": {
|
|
|
|
"name": "Agnes"
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.category",
|
|
|
|
"fields": {
|
|
|
|
"name": "Reference"
|
|
|
|
}
|
|
|
|
}]"""
|
|
|
|
expected = "(serializers.article:pk=1) field_value was 'doesnotexist'"
|
|
|
|
with self.assertRaisesMessage(DeserializationError, expected):
|
|
|
|
list(serializers.deserialize("json", test_string))
|
|
|
|
|
|
|
|
def test_helpful_error_message_for_many2many_natural1(self):
|
|
|
|
"""
|
|
|
|
Invalid many-to-many keys should throw a helpful error message.
|
|
|
|
This tests the code path where one of a list of natural keys is invalid.
|
|
|
|
"""
|
|
|
|
test_string = """[{
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.categorymetadata",
|
|
|
|
"fields": {
|
|
|
|
"kind": "author",
|
|
|
|
"name": "meta1",
|
|
|
|
"value": "Agnes"
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.article",
|
|
|
|
"fields": {
|
|
|
|
"author": 1,
|
|
|
|
"headline": "Unknown many to many",
|
|
|
|
"pub_date": "2014-09-15T10:35:00",
|
|
|
|
"meta_data": [
|
|
|
|
["author", "meta1"],
|
|
|
|
["doesnotexist", "meta1"],
|
|
|
|
["author", "meta1"]
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.author",
|
|
|
|
"fields": {
|
|
|
|
"name": "Agnes"
|
|
|
|
}
|
|
|
|
}]"""
|
|
|
|
key = ["doesnotexist", "meta1"]
|
|
|
|
expected = "(serializers.article:pk=1) field_value was '%r'" % key
|
|
|
|
with self.assertRaisesMessage(DeserializationError, expected):
|
|
|
|
for obj in serializers.deserialize("json", test_string):
|
|
|
|
obj.save()
|
|
|
|
|
|
|
|
def test_helpful_error_message_for_many2many_natural2(self):
|
|
|
|
"""
|
|
|
|
Invalid many-to-many keys should throw a helpful error message. This
|
|
|
|
tests the code path where a natural many-to-many key has only a single
|
|
|
|
value.
|
|
|
|
"""
|
|
|
|
test_string = """[{
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.article",
|
|
|
|
"fields": {
|
|
|
|
"author": 1,
|
|
|
|
"headline": "Unknown many to many",
|
|
|
|
"pub_date": "2014-09-15T10:35:00",
|
|
|
|
"meta_data": [1, "doesnotexist"]
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.categorymetadata",
|
|
|
|
"fields": {
|
|
|
|
"kind": "author",
|
|
|
|
"name": "meta1",
|
|
|
|
"value": "Agnes"
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.author",
|
|
|
|
"fields": {
|
|
|
|
"name": "Agnes"
|
|
|
|
}
|
|
|
|
}]"""
|
|
|
|
expected = "(serializers.article:pk=1) field_value was 'doesnotexist'"
|
|
|
|
with self.assertRaisesMessage(DeserializationError, expected):
|
|
|
|
for obj in serializers.deserialize("json", test_string, ignore=False):
|
|
|
|
obj.save()
|
|
|
|
|
2019-12-12 02:36:57 +08:00
|
|
|
def test_helpful_error_message_for_many2many_not_iterable(self):
|
|
|
|
"""
|
|
|
|
Not iterable many-to-many field value throws a helpful error message.
|
|
|
|
"""
|
|
|
|
test_string = """[{
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.m2mdata",
|
|
|
|
"fields": {"data": null}
|
|
|
|
}]"""
|
|
|
|
|
|
|
|
expected = "(serializers.m2mdata:pk=1) field_value was 'None'"
|
|
|
|
with self.assertRaisesMessage(DeserializationError, expected):
|
|
|
|
next(serializers.deserialize("json", test_string, ignore=False))
|
|
|
|
|
2015-09-26 17:02:09 +08:00
|
|
|
|
|
|
|
class JsonSerializerTransactionTestCase(
|
|
|
|
SerializersTransactionTestBase, TransactionTestCase
|
|
|
|
):
|
|
|
|
serializer_name = "json"
|
|
|
|
fwd_ref_str = """[
|
|
|
|
{
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.article",
|
|
|
|
"fields": {
|
|
|
|
"headline": "Forward references pose no problem",
|
|
|
|
"pub_date": "2006-06-16T15:00:00",
|
|
|
|
"categories": [1],
|
|
|
|
"author": 1
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.category",
|
|
|
|
"fields": {
|
|
|
|
"name": "Reference"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"pk": 1,
|
|
|
|
"model": "serializers.author",
|
|
|
|
"fields": {
|
|
|
|
"name": "Agnes"
|
|
|
|
}
|
|
|
|
}]"""
|
2015-09-27 02:15:26 +08:00
|
|
|
|
|
|
|
|
|
|
|
class DjangoJSONEncoderTests(SimpleTestCase):
|
|
|
|
def test_lazy_string_encoding(self):
|
|
|
|
self.assertEqual(
|
2017-01-27 03:58:33 +08:00
|
|
|
json.dumps({"lang": gettext_lazy("French")}, cls=DjangoJSONEncoder),
|
2015-09-27 02:15:26 +08:00
|
|
|
'{"lang": "French"}',
|
|
|
|
)
|
|
|
|
with override("fr"):
|
|
|
|
self.assertEqual(
|
2017-01-27 03:58:33 +08:00
|
|
|
json.dumps({"lang": gettext_lazy("French")}, cls=DjangoJSONEncoder),
|
2015-09-27 02:15:26 +08:00
|
|
|
'{"lang": "Fran\\u00e7ais"}',
|
|
|
|
)
|
2016-05-26 20:48:36 +08:00
|
|
|
|
|
|
|
def test_timedelta(self):
|
|
|
|
duration = datetime.timedelta(days=1, hours=2, seconds=3)
|
|
|
|
self.assertEqual(
|
|
|
|
json.dumps({"duration": duration}, cls=DjangoJSONEncoder),
|
|
|
|
'{"duration": "P1DT02H00M03S"}',
|
|
|
|
)
|
|
|
|
duration = datetime.timedelta(0)
|
|
|
|
self.assertEqual(
|
|
|
|
json.dumps({"duration": duration}, cls=DjangoJSONEncoder),
|
|
|
|
'{"duration": "P0DT00H00M00S"}',
|
|
|
|
)
|