| | 1 | import decimal |
| | 2 | import json |
| | 3 | import re |
| | 4 | |
| | 5 | from django.core import serializers |
| | 6 | from django.core.serializers.base import DeserializationError |
| | 7 | from django.db import models |
| | 8 | from django.test import TestCase, TransactionTestCase |
| | 9 | from django.test.utils import isolate_apps |
| | 10 | |
| | 11 | from .models import Score |
| | 12 | from .tests import SerializersTestBase, SerializersTransactionTestBase |
| | 13 | |
| | 14 | |
| | 15 | class JsonlSerializerTestCase(SerializersTestBase, TestCase): |
| | 16 | serializer_name = "jsonl" |
| | 17 | pkless_str = """ |
| | 18 | {"pk": null, "model": "serializers.category", "fields": {"name": "Reference"}} |
| | 19 | {"model": "serializers.category","fields": {"name": "Non-fiction"}} |
| | 20 | """ |
| | 21 | mapping_ordering_str = """{"model": "serializers.article", "pk": %(article_pk)s, """ \ |
| | 22 | """"fields": {"author": %(author_pk)s, "headline": "Poker has no place on ESPN", """ \ |
| | 23 | """"pub_date": "2006-06-16T11:00:00", """ \ |
| | 24 | """"categories": [%(first_category_pk)s, %(second_category_pk)s], "meta_data": []}}\n""" |
| | 25 | |
| | 26 | def test_deterministic_mapping_ordering(self): |
| | 27 | super(JsonlSerializerTestCase, self).test_deterministic_mapping_ordering() |
| | 28 | |
| | 29 | @staticmethod |
| | 30 | def _validate_output(serial_str): |
| | 31 | try: |
| | 32 | [json.loads(line) for line in serial_str.splitlines()] |
| | 33 | except Exception: |
| | 34 | return False |
| | 35 | else: |
| | 36 | return True |
| | 37 | |
| | 38 | @staticmethod |
| | 39 | def _get_pk_values(serial_str): |
| | 40 | serial_list = [json.loads(line) for line in serial_str.splitlines()] |
| | 41 | return [obj_dict['pk'] for obj_dict in serial_list] |
| | 42 | |
| | 43 | @staticmethod |
| | 44 | def _get_field_values(serial_str, field_name): |
| | 45 | serial_list = [json.loads(line) for line in serial_str.splitlines()] |
| | 46 | return [obj_dict['fields'][field_name] for obj_dict in serial_list if field_name in obj_dict['fields']] |
| | 47 | |
| | 48 | def test_indentation_whitespace(self): |
| | 49 | s = serializers.json.Serializer() |
| | 50 | json_data = s.serialize([Score(score=5.0), Score(score=6.0)], indent=2) |
| | 51 | for line in json_data.splitlines(): |
| | 52 | if re.search(r'.+,\s*$', line): |
| | 53 | self.assertEqual(line, line.rstrip()) |
| | 54 | |
| | 55 | @isolate_apps('serializers') |
| | 56 | def test_custom_encoder(self): |
| | 57 | class ScoreDecimal(models.Model): |
| | 58 | score = models.DecimalField() |
| | 59 | |
| | 60 | class CustomJSONEncoder(json.JSONEncoder): |
| | 61 | def default(self, o): |
| | 62 | if isinstance(o, decimal.Decimal): |
| | 63 | return str(o) |
| | 64 | return super().default(o) |
| | 65 | |
| | 66 | s = serializers.json.Serializer() |
| | 67 | json_data = s.serialize( |
| | 68 | [ScoreDecimal(score=decimal.Decimal(1.0))], cls=CustomJSONEncoder |
| | 69 | ) |
| | 70 | self.assertIn('"fields": {"score": "1"}', json_data) |
| | 71 | |
| | 72 | def test_json_deserializer_exception(self): |
| | 73 | with self.assertRaises(DeserializationError): |
| | 74 | for obj in serializers.deserialize("json", """[{"pk":1}"""): |
| | 75 | pass |
| | 76 | |
| | 77 | def test_helpful_error_message_invalid_pk(self): |
| | 78 | """ |
| | 79 | If there is an invalid primary key, the error message should contain |
| | 80 | the model associated with it. |
| | 81 | """ |
| | 82 | test_string = """[{ |
| | 83 | "pk": "badpk", |
| | 84 | "model": "serializers.player", |
| | 85 | "fields": { |
| | 86 | "name": "Bob", |
| | 87 | "rank": 1, |
| | 88 | "team": "Team" |
| | 89 | } |
| | 90 | }]""" |
| | 91 | with self.assertRaisesMessage(DeserializationError, "(serializers.player:pk=badpk)"): |
| | 92 | list(serializers.deserialize('json', test_string)) |
| | 93 | |
| | 94 | def test_helpful_error_message_invalid_field(self): |
| | 95 | """ |
| | 96 | If there is an invalid field value, the error message should contain |
| | 97 | the model associated with it. |
| | 98 | """ |
| | 99 | test_string = """[{ |
| | 100 | "pk": "1", |
| | 101 | "model": "serializers.player", |
| | 102 | "fields": { |
| | 103 | "name": "Bob", |
| | 104 | "rank": "invalidint", |
| | 105 | "team": "Team" |
| | 106 | } |
| | 107 | }]""" |
| | 108 | expected = "(serializers.player:pk=1) field_value was 'invalidint'" |
| | 109 | with self.assertRaisesMessage(DeserializationError, expected): |
| | 110 | list(serializers.deserialize('json', test_string)) |
| | 111 | |
| | 112 | def test_helpful_error_message_for_foreign_keys(self): |
| | 113 | """ |
| | 114 | Invalid foreign keys with a natural key should throw a helpful error |
| | 115 | message, such as what the failing key is. |
| | 116 | """ |
| | 117 | test_string = """[{ |
| | 118 | "pk": 1, |
| | 119 | "model": "serializers.category", |
| | 120 | "fields": { |
| | 121 | "name": "Unknown foreign key", |
| | 122 | "meta_data": [ |
| | 123 | "doesnotexist", |
| | 124 | "metadata" |
| | 125 | ] |
| | 126 | } |
| | 127 | }]""" |
| | 128 | key = ["doesnotexist", "metadata"] |
| | 129 | expected = "(serializers.category:pk=1) field_value was '%r'" % key |
| | 130 | with self.assertRaisesMessage(DeserializationError, expected): |
| | 131 | list(serializers.deserialize('json', test_string)) |
| | 132 | |
| | 133 | def test_helpful_error_message_for_many2many_non_natural(self): |
| | 134 | """ |
| | 135 | Invalid many-to-many keys should throw a helpful error message. |
| | 136 | """ |
| | 137 | test_string = """[{ |
| | 138 | "pk": 1, |
| | 139 | "model": "serializers.article", |
| | 140 | "fields": { |
| | 141 | "author": 1, |
| | 142 | "headline": "Unknown many to many", |
| | 143 | "pub_date": "2014-09-15T10:35:00", |
| | 144 | "categories": [1, "doesnotexist"] |
| | 145 | } |
| | 146 | }, { |
| | 147 | "pk": 1, |
| | 148 | "model": "serializers.author", |
| | 149 | "fields": { |
| | 150 | "name": "Agnes" |
| | 151 | } |
| | 152 | }, { |
| | 153 | "pk": 1, |
| | 154 | "model": "serializers.category", |
| | 155 | "fields": { |
| | 156 | "name": "Reference" |
| | 157 | } |
| | 158 | }]""" |
| | 159 | expected = "(serializers.article:pk=1) field_value was 'doesnotexist'" |
| | 160 | with self.assertRaisesMessage(DeserializationError, expected): |
| | 161 | list(serializers.deserialize('json', test_string)) |
| | 162 | |
| | 163 | def test_helpful_error_message_for_many2many_natural1(self): |
| | 164 | """ |
| | 165 | Invalid many-to-many keys should throw a helpful error message. |
| | 166 | This tests the code path where one of a list of natural keys is invalid. |
| | 167 | """ |
| | 168 | test_string = """[{ |
| | 169 | "pk": 1, |
| | 170 | "model": "serializers.categorymetadata", |
| | 171 | "fields": { |
| | 172 | "kind": "author", |
| | 173 | "name": "meta1", |
| | 174 | "value": "Agnes" |
| | 175 | } |
| | 176 | }, { |
| | 177 | "pk": 1, |
| | 178 | "model": "serializers.article", |
| | 179 | "fields": { |
| | 180 | "author": 1, |
| | 181 | "headline": "Unknown many to many", |
| | 182 | "pub_date": "2014-09-15T10:35:00", |
| | 183 | "meta_data": [ |
| | 184 | ["author", "meta1"], |
| | 185 | ["doesnotexist", "meta1"], |
| | 186 | ["author", "meta1"] |
| | 187 | ] |
| | 188 | } |
| | 189 | }, { |
| | 190 | "pk": 1, |
| | 191 | "model": "serializers.author", |
| | 192 | "fields": { |
| | 193 | "name": "Agnes" |
| | 194 | } |
| | 195 | }]""" |
| | 196 | key = ["doesnotexist", "meta1"] |
| | 197 | expected = "(serializers.article:pk=1) field_value was '%r'" % key |
| | 198 | with self.assertRaisesMessage(DeserializationError, expected): |
| | 199 | for obj in serializers.deserialize('json', test_string): |
| | 200 | obj.save() |
| | 201 | |
| | 202 | def test_helpful_error_message_for_many2many_natural2(self): |
| | 203 | """ |
| | 204 | Invalid many-to-many keys should throw a helpful error message. This |
| | 205 | tests the code path where a natural many-to-many key has only a single |
| | 206 | value. |
| | 207 | """ |
| | 208 | test_string = """[{ |
| | 209 | "pk": 1, |
| | 210 | "model": "serializers.article", |
| | 211 | "fields": { |
| | 212 | "author": 1, |
| | 213 | "headline": "Unknown many to many", |
| | 214 | "pub_date": "2014-09-15T10:35:00", |
| | 215 | "meta_data": [1, "doesnotexist"] |
| | 216 | } |
| | 217 | }, { |
| | 218 | "pk": 1, |
| | 219 | "model": "serializers.categorymetadata", |
| | 220 | "fields": { |
| | 221 | "kind": "author", |
| | 222 | "name": "meta1", |
| | 223 | "value": "Agnes" |
| | 224 | } |
| | 225 | }, { |
| | 226 | "pk": 1, |
| | 227 | "model": "serializers.author", |
| | 228 | "fields": { |
| | 229 | "name": "Agnes" |
| | 230 | } |
| | 231 | }]""" |
| | 232 | expected = "(serializers.article:pk=1) field_value was 'doesnotexist'" |
| | 233 | with self.assertRaisesMessage(DeserializationError, expected): |
| | 234 | for obj in serializers.deserialize('json', test_string, ignore=False): |
| | 235 | obj.save() |
| | 236 | |
| | 237 | |
| | 238 | class JsonSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): |
| | 239 | serializer_name = "json" |
| | 240 | fwd_ref_str = """[ |
| | 241 | { |
| | 242 | "pk": 1, |
| | 243 | "model": "serializers.article", |
| | 244 | "fields": { |
| | 245 | "headline": "Forward references pose no problem", |
| | 246 | "pub_date": "2006-06-16T15:00:00", |
| | 247 | "categories": [1], |
| | 248 | "author": 1 |
| | 249 | } |
| | 250 | }, |
| | 251 | { |
| | 252 | "pk": 1, |
| | 253 | "model": "serializers.category", |
| | 254 | "fields": { |
| | 255 | "name": "Reference" |
| | 256 | } |
| | 257 | }, |
| | 258 | { |
| | 259 | "pk": 1, |
| | 260 | "model": "serializers.author", |
| | 261 | "fields": { |
| | 262 | "name": "Agnes" |
| | 263 | } |
| | 264 | }]""" |