import decimal import json import re from django.core import serializers from django.core.serializers.base import DeserializationError from django.db import models from django.test import TestCase, TransactionTestCase from django.test.utils import isolate_apps from .models import Score from .tests import SerializersTestBase, SerializersTransactionTestBase class JsonlSerializerTestCase(SerializersTestBase, TestCase): serializer_name = "jsonl" pkless_str = [ '{"pk": null,"model": "serializers.category","fields": {"name": "Reference"}}', '{"model": "serializers.category","fields": {"name": "Non-fiction"}}', ] pkless_str = "\n".join([s.replace("\n", "") for s in pkless_str]) 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": []}}\n' ) @staticmethod def _validate_output(serial_str): try: for line in serial_str.split("\n"): if line: json.loads(line) except Exception: return False else: return True @staticmethod def _get_pk_values(serial_str): serial_list = [json.loads(line) for line in serial_str.split("\n") if line] return [obj_dict['pk'] for obj_dict in serial_list] @staticmethod def _get_field_values(serial_str, field_name): serial_list = [json.loads(line) for line in serial_str.split("\n") if line] return [obj_dict['fields'][field_name] for obj_dict in serial_list if field_name in obj_dict['fields']] def test_no_indentation(self): s = serializers.jsonl.Serializer() json_data = s.serialize([Score(score=5.0), Score(score=6.0)], indent=2) for line in json_data.splitlines(): self.assertIsNone(re.search(r'.+,\s*$', line)) @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) return super().default(o) s = serializers.jsonl.Serializer() json_data = s.serialize( [ScoreDecimal(score=decimal.Decimal(1.0))], cls=CustomJSONEncoder, ) self.assertIn('"fields": {"score": "1"}', json_data) def test_json_deserializer_exception(self): with self.assertRaises(DeserializationError): for obj in serializers.deserialize("jsonl", """[{"pk":1}"""): pass def test_helpful_error_message_invalid_pk(self): """ If there is an invalid primary key, the error message contains 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('jsonl', test_string)) def test_helpful_error_message_invalid_field(self): """ If there is an invalid field value, the error message contains 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('jsonl', test_string)) def test_helpful_error_message_for_foreign_keys(self): """ Invalid foreign keys with a natural key throws 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('jsonl', test_string)) def test_helpful_error_message_for_many2many_non_natural(self): """ Invalid many-to-many keys throws a helpful error message. """ test_strings = [ """{ "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"} }""" ] test_string = "\n".join([s.replace("\n", "") for s in test_strings]) expected = "(serializers.article:pk=1) field_value was 'doesnotexist'" with self.assertRaisesMessage(DeserializationError, expected): list(serializers.deserialize('jsonl', test_string)) def test_helpful_error_message_for_many2many_natural1(self): """ Invalid many-to-many keys throws a helpful error message where one of a list of natural keys is invalid. """ test_strings = [ """{ "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"} }""" ] test_string = "\n".join([s.replace("\n", "") for s in test_strings]) key = ["doesnotexist", "meta1"] expected = "(serializers.article:pk=1) field_value was '%r'" % key with self.assertRaisesMessage(DeserializationError, expected): for obj in serializers.deserialize('jsonl', test_string): obj.save() def test_helpful_error_message_for_many2many_natural2(self): """ Invalid many-to-many keys throws a helpful error message where a natural many-to-many key has only a single value. """ test_strings = [ """{ "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"} }""" ] test_string = "\n".join([s.replace("\n", "") for s in test_strings]) expected = "(serializers.article:pk=1) field_value was 'doesnotexist'" with self.assertRaisesMessage(DeserializationError, expected): for obj in serializers.deserialize('jsonl', test_string, ignore=False): obj.save() 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('jsonl', test_string, ignore=False)) class JsonSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): serializer_name = "jsonl" 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"} }""" ] fwd_ref_str = "\n".join([s.replace("\n", "") for s in fwd_ref_str])