|
1 """ |
|
2 A test spanning all the capabilities of all the serializers. |
|
3 |
|
4 This class defines sample data and a dynamically generated |
|
5 test case that is capable of testing the capabilities of |
|
6 the serializers. This includes all valid data values, plus |
|
7 forward, backwards and self references. |
|
8 """ |
|
9 |
|
10 |
|
11 import datetime |
|
12 import decimal |
|
13 try: |
|
14 from cStringIO import StringIO |
|
15 except ImportError: |
|
16 from StringIO import StringIO |
|
17 |
|
18 from django.conf import settings |
|
19 from django.core import serializers, management |
|
20 from django.db import transaction, DEFAULT_DB_ALIAS |
|
21 from django.test import TestCase |
|
22 from django.utils.functional import curry |
|
23 |
|
24 from models import * |
|
25 |
|
26 # A set of functions that can be used to recreate |
|
27 # test data objects of various kinds. |
|
28 # The save method is a raw base model save, to make |
|
29 # sure that the data in the database matches the |
|
30 # exact test case. |
|
31 def data_create(pk, klass, data): |
|
32 instance = klass(id=pk) |
|
33 instance.data = data |
|
34 models.Model.save_base(instance, raw=True) |
|
35 return [instance] |
|
36 |
|
37 def generic_create(pk, klass, data): |
|
38 instance = klass(id=pk) |
|
39 instance.data = data[0] |
|
40 models.Model.save_base(instance, raw=True) |
|
41 for tag in data[1:]: |
|
42 instance.tags.create(data=tag) |
|
43 return [instance] |
|
44 |
|
45 def fk_create(pk, klass, data): |
|
46 instance = klass(id=pk) |
|
47 setattr(instance, 'data_id', data) |
|
48 models.Model.save_base(instance, raw=True) |
|
49 return [instance] |
|
50 |
|
51 def m2m_create(pk, klass, data): |
|
52 instance = klass(id=pk) |
|
53 models.Model.save_base(instance, raw=True) |
|
54 instance.data = data |
|
55 return [instance] |
|
56 |
|
57 def im2m_create(pk, klass, data): |
|
58 instance = klass(id=pk) |
|
59 models.Model.save_base(instance, raw=True) |
|
60 return [instance] |
|
61 |
|
62 def im_create(pk, klass, data): |
|
63 instance = klass(id=pk) |
|
64 instance.right_id = data['right'] |
|
65 instance.left_id = data['left'] |
|
66 if 'extra' in data: |
|
67 instance.extra = data['extra'] |
|
68 models.Model.save_base(instance, raw=True) |
|
69 return [instance] |
|
70 |
|
71 def o2o_create(pk, klass, data): |
|
72 instance = klass() |
|
73 instance.data_id = data |
|
74 models.Model.save_base(instance, raw=True) |
|
75 return [instance] |
|
76 |
|
77 def pk_create(pk, klass, data): |
|
78 instance = klass() |
|
79 instance.data = data |
|
80 models.Model.save_base(instance, raw=True) |
|
81 return [instance] |
|
82 |
|
83 def inherited_create(pk, klass, data): |
|
84 instance = klass(id=pk,**data) |
|
85 # This isn't a raw save because: |
|
86 # 1) we're testing inheritance, not field behaviour, so none |
|
87 # of the field values need to be protected. |
|
88 # 2) saving the child class and having the parent created |
|
89 # automatically is easier than manually creating both. |
|
90 models.Model.save(instance) |
|
91 created = [instance] |
|
92 for klass,field in instance._meta.parents.items(): |
|
93 created.append(klass.objects.get(id=pk)) |
|
94 return created |
|
95 |
|
96 # A set of functions that can be used to compare |
|
97 # test data objects of various kinds |
|
98 def data_compare(testcase, pk, klass, data): |
|
99 instance = klass.objects.get(id=pk) |
|
100 testcase.assertEqual(data, instance.data, |
|
101 "Objects with PK=%d not equal; expected '%s' (%s), got '%s' (%s)" % ( |
|
102 pk, data, type(data), instance.data, type(instance.data)) |
|
103 ) |
|
104 |
|
105 def generic_compare(testcase, pk, klass, data): |
|
106 instance = klass.objects.get(id=pk) |
|
107 testcase.assertEqual(data[0], instance.data) |
|
108 testcase.assertEqual(data[1:], [t.data for t in instance.tags.order_by('id')]) |
|
109 |
|
110 def fk_compare(testcase, pk, klass, data): |
|
111 instance = klass.objects.get(id=pk) |
|
112 testcase.assertEqual(data, instance.data_id) |
|
113 |
|
114 def m2m_compare(testcase, pk, klass, data): |
|
115 instance = klass.objects.get(id=pk) |
|
116 testcase.assertEqual(data, [obj.id for obj in instance.data.order_by('id')]) |
|
117 |
|
118 def im2m_compare(testcase, pk, klass, data): |
|
119 instance = klass.objects.get(id=pk) |
|
120 #actually nothing else to check, the instance just should exist |
|
121 |
|
122 def im_compare(testcase, pk, klass, data): |
|
123 instance = klass.objects.get(id=pk) |
|
124 testcase.assertEqual(data['left'], instance.left_id) |
|
125 testcase.assertEqual(data['right'], instance.right_id) |
|
126 if 'extra' in data: |
|
127 testcase.assertEqual(data['extra'], instance.extra) |
|
128 else: |
|
129 testcase.assertEqual("doesn't matter", instance.extra) |
|
130 |
|
131 def o2o_compare(testcase, pk, klass, data): |
|
132 instance = klass.objects.get(data=data) |
|
133 testcase.assertEqual(data, instance.data_id) |
|
134 |
|
135 def pk_compare(testcase, pk, klass, data): |
|
136 instance = klass.objects.get(data=data) |
|
137 testcase.assertEqual(data, instance.data) |
|
138 |
|
139 def inherited_compare(testcase, pk, klass, data): |
|
140 instance = klass.objects.get(id=pk) |
|
141 for key,value in data.items(): |
|
142 testcase.assertEqual(value, getattr(instance,key)) |
|
143 |
|
144 # Define some data types. Each data type is |
|
145 # actually a pair of functions; one to create |
|
146 # and one to compare objects of that type |
|
147 data_obj = (data_create, data_compare) |
|
148 generic_obj = (generic_create, generic_compare) |
|
149 fk_obj = (fk_create, fk_compare) |
|
150 m2m_obj = (m2m_create, m2m_compare) |
|
151 im2m_obj = (im2m_create, im2m_compare) |
|
152 im_obj = (im_create, im_compare) |
|
153 o2o_obj = (o2o_create, o2o_compare) |
|
154 pk_obj = (pk_create, pk_compare) |
|
155 inherited_obj = (inherited_create, inherited_compare) |
|
156 |
|
157 test_data = [ |
|
158 # Format: (data type, PK value, Model Class, data) |
|
159 (data_obj, 1, BooleanData, True), |
|
160 (data_obj, 2, BooleanData, False), |
|
161 (data_obj, 10, CharData, "Test Char Data"), |
|
162 (data_obj, 11, CharData, ""), |
|
163 (data_obj, 12, CharData, "None"), |
|
164 (data_obj, 13, CharData, "null"), |
|
165 (data_obj, 14, CharData, "NULL"), |
|
166 (data_obj, 15, CharData, None), |
|
167 # (We use something that will fit into a latin1 database encoding here, |
|
168 # because that is still the default used on many system setups.) |
|
169 (data_obj, 16, CharData, u'\xa5'), |
|
170 (data_obj, 20, DateData, datetime.date(2006,6,16)), |
|
171 (data_obj, 21, DateData, None), |
|
172 (data_obj, 30, DateTimeData, datetime.datetime(2006,6,16,10,42,37)), |
|
173 (data_obj, 31, DateTimeData, None), |
|
174 (data_obj, 40, EmailData, "hovercraft@example.com"), |
|
175 (data_obj, 41, EmailData, None), |
|
176 (data_obj, 42, EmailData, ""), |
|
177 (data_obj, 50, FileData, 'file:///foo/bar/whiz.txt'), |
|
178 # (data_obj, 51, FileData, None), |
|
179 (data_obj, 52, FileData, ""), |
|
180 (data_obj, 60, FilePathData, "/foo/bar/whiz.txt"), |
|
181 (data_obj, 61, FilePathData, None), |
|
182 (data_obj, 62, FilePathData, ""), |
|
183 (data_obj, 70, DecimalData, decimal.Decimal('12.345')), |
|
184 (data_obj, 71, DecimalData, decimal.Decimal('-12.345')), |
|
185 (data_obj, 72, DecimalData, decimal.Decimal('0.0')), |
|
186 (data_obj, 73, DecimalData, None), |
|
187 (data_obj, 74, FloatData, 12.345), |
|
188 (data_obj, 75, FloatData, -12.345), |
|
189 (data_obj, 76, FloatData, 0.0), |
|
190 (data_obj, 77, FloatData, None), |
|
191 (data_obj, 80, IntegerData, 123456789), |
|
192 (data_obj, 81, IntegerData, -123456789), |
|
193 (data_obj, 82, IntegerData, 0), |
|
194 (data_obj, 83, IntegerData, None), |
|
195 #(XX, ImageData |
|
196 (data_obj, 90, IPAddressData, "127.0.0.1"), |
|
197 (data_obj, 91, IPAddressData, None), |
|
198 (data_obj, 100, NullBooleanData, True), |
|
199 (data_obj, 101, NullBooleanData, False), |
|
200 (data_obj, 102, NullBooleanData, None), |
|
201 (data_obj, 110, PhoneData, "212-634-5789"), |
|
202 (data_obj, 111, PhoneData, None), |
|
203 (data_obj, 120, PositiveIntegerData, 123456789), |
|
204 (data_obj, 121, PositiveIntegerData, None), |
|
205 (data_obj, 130, PositiveSmallIntegerData, 12), |
|
206 (data_obj, 131, PositiveSmallIntegerData, None), |
|
207 (data_obj, 140, SlugData, "this-is-a-slug"), |
|
208 (data_obj, 141, SlugData, None), |
|
209 (data_obj, 142, SlugData, ""), |
|
210 (data_obj, 150, SmallData, 12), |
|
211 (data_obj, 151, SmallData, -12), |
|
212 (data_obj, 152, SmallData, 0), |
|
213 (data_obj, 153, SmallData, None), |
|
214 (data_obj, 160, TextData, """This is a long piece of text. |
|
215 It contains line breaks. |
|
216 Several of them. |
|
217 The end."""), |
|
218 (data_obj, 161, TextData, ""), |
|
219 (data_obj, 162, TextData, None), |
|
220 (data_obj, 170, TimeData, datetime.time(10,42,37)), |
|
221 (data_obj, 171, TimeData, None), |
|
222 (data_obj, 180, USStateData, "MA"), |
|
223 (data_obj, 181, USStateData, None), |
|
224 (data_obj, 182, USStateData, ""), |
|
225 (data_obj, 190, XMLData, "<foo></foo>"), |
|
226 (data_obj, 191, XMLData, None), |
|
227 (data_obj, 192, XMLData, ""), |
|
228 |
|
229 (generic_obj, 200, GenericData, ['Generic Object 1', 'tag1', 'tag2']), |
|
230 (generic_obj, 201, GenericData, ['Generic Object 2', 'tag2', 'tag3']), |
|
231 |
|
232 (data_obj, 300, Anchor, "Anchor 1"), |
|
233 (data_obj, 301, Anchor, "Anchor 2"), |
|
234 (data_obj, 302, UniqueAnchor, "UAnchor 1"), |
|
235 |
|
236 (fk_obj, 400, FKData, 300), # Post reference |
|
237 (fk_obj, 401, FKData, 500), # Pre reference |
|
238 (fk_obj, 402, FKData, None), # Empty reference |
|
239 |
|
240 (m2m_obj, 410, M2MData, []), # Empty set |
|
241 (m2m_obj, 411, M2MData, [300,301]), # Post reference |
|
242 (m2m_obj, 412, M2MData, [500,501]), # Pre reference |
|
243 (m2m_obj, 413, M2MData, [300,301,500,501]), # Pre and Post reference |
|
244 |
|
245 (o2o_obj, None, O2OData, 300), # Post reference |
|
246 (o2o_obj, None, O2OData, 500), # Pre reference |
|
247 |
|
248 (fk_obj, 430, FKSelfData, 431), # Pre reference |
|
249 (fk_obj, 431, FKSelfData, 430), # Post reference |
|
250 (fk_obj, 432, FKSelfData, None), # Empty reference |
|
251 |
|
252 (m2m_obj, 440, M2MSelfData, []), |
|
253 (m2m_obj, 441, M2MSelfData, []), |
|
254 (m2m_obj, 442, M2MSelfData, [440, 441]), |
|
255 (m2m_obj, 443, M2MSelfData, [445, 446]), |
|
256 (m2m_obj, 444, M2MSelfData, [440, 441, 445, 446]), |
|
257 (m2m_obj, 445, M2MSelfData, []), |
|
258 (m2m_obj, 446, M2MSelfData, []), |
|
259 |
|
260 (fk_obj, 450, FKDataToField, "UAnchor 1"), |
|
261 (fk_obj, 451, FKDataToField, "UAnchor 2"), |
|
262 (fk_obj, 452, FKDataToField, None), |
|
263 |
|
264 (fk_obj, 460, FKDataToO2O, 300), |
|
265 |
|
266 (im2m_obj, 470, M2MIntermediateData, None), |
|
267 |
|
268 #testing post- and prereferences and extra fields |
|
269 (im_obj, 480, Intermediate, {'right': 300, 'left': 470}), |
|
270 (im_obj, 481, Intermediate, {'right': 300, 'left': 490}), |
|
271 (im_obj, 482, Intermediate, {'right': 500, 'left': 470}), |
|
272 (im_obj, 483, Intermediate, {'right': 500, 'left': 490}), |
|
273 (im_obj, 484, Intermediate, {'right': 300, 'left': 470, 'extra': "extra"}), |
|
274 (im_obj, 485, Intermediate, {'right': 300, 'left': 490, 'extra': "extra"}), |
|
275 (im_obj, 486, Intermediate, {'right': 500, 'left': 470, 'extra': "extra"}), |
|
276 (im_obj, 487, Intermediate, {'right': 500, 'left': 490, 'extra': "extra"}), |
|
277 |
|
278 (im2m_obj, 490, M2MIntermediateData, []), |
|
279 |
|
280 (data_obj, 500, Anchor, "Anchor 3"), |
|
281 (data_obj, 501, Anchor, "Anchor 4"), |
|
282 (data_obj, 502, UniqueAnchor, "UAnchor 2"), |
|
283 |
|
284 (pk_obj, 601, BooleanPKData, True), |
|
285 (pk_obj, 602, BooleanPKData, False), |
|
286 (pk_obj, 610, CharPKData, "Test Char PKData"), |
|
287 # (pk_obj, 620, DatePKData, datetime.date(2006,6,16)), |
|
288 # (pk_obj, 630, DateTimePKData, datetime.datetime(2006,6,16,10,42,37)), |
|
289 (pk_obj, 640, EmailPKData, "hovercraft@example.com"), |
|
290 # (pk_obj, 650, FilePKData, 'file:///foo/bar/whiz.txt'), |
|
291 (pk_obj, 660, FilePathPKData, "/foo/bar/whiz.txt"), |
|
292 (pk_obj, 670, DecimalPKData, decimal.Decimal('12.345')), |
|
293 (pk_obj, 671, DecimalPKData, decimal.Decimal('-12.345')), |
|
294 (pk_obj, 672, DecimalPKData, decimal.Decimal('0.0')), |
|
295 (pk_obj, 673, FloatPKData, 12.345), |
|
296 (pk_obj, 674, FloatPKData, -12.345), |
|
297 (pk_obj, 675, FloatPKData, 0.0), |
|
298 (pk_obj, 680, IntegerPKData, 123456789), |
|
299 (pk_obj, 681, IntegerPKData, -123456789), |
|
300 (pk_obj, 682, IntegerPKData, 0), |
|
301 # (XX, ImagePKData |
|
302 (pk_obj, 690, IPAddressPKData, "127.0.0.1"), |
|
303 # (pk_obj, 700, NullBooleanPKData, True), |
|
304 # (pk_obj, 701, NullBooleanPKData, False), |
|
305 (pk_obj, 710, PhonePKData, "212-634-5789"), |
|
306 (pk_obj, 720, PositiveIntegerPKData, 123456789), |
|
307 (pk_obj, 730, PositiveSmallIntegerPKData, 12), |
|
308 (pk_obj, 740, SlugPKData, "this-is-a-slug"), |
|
309 (pk_obj, 750, SmallPKData, 12), |
|
310 (pk_obj, 751, SmallPKData, -12), |
|
311 (pk_obj, 752, SmallPKData, 0), |
|
312 # (pk_obj, 760, TextPKData, """This is a long piece of text. |
|
313 # It contains line breaks. |
|
314 # Several of them. |
|
315 # The end."""), |
|
316 # (pk_obj, 770, TimePKData, datetime.time(10,42,37)), |
|
317 (pk_obj, 780, USStatePKData, "MA"), |
|
318 # (pk_obj, 790, XMLPKData, "<foo></foo>"), |
|
319 |
|
320 (data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006,6,16,10,42,37)), |
|
321 (data_obj, 810, ModifyingSaveData, 42), |
|
322 |
|
323 (inherited_obj, 900, InheritAbstractModel, {'child_data':37,'parent_data':42}), |
|
324 (inherited_obj, 910, ExplicitInheritBaseModel, {'child_data':37,'parent_data':42}), |
|
325 (inherited_obj, 920, InheritBaseModel, {'child_data':37,'parent_data':42}), |
|
326 |
|
327 (data_obj, 1000, BigIntegerData, 9223372036854775807), |
|
328 (data_obj, 1001, BigIntegerData, -9223372036854775808), |
|
329 (data_obj, 1002, BigIntegerData, 0), |
|
330 (data_obj, 1003, BigIntegerData, None), |
|
331 (data_obj, 1004, LengthModel, 0), |
|
332 (data_obj, 1005, LengthModel, 1), |
|
333 ] |
|
334 |
|
335 # Because Oracle treats the empty string as NULL, Oracle is expected to fail |
|
336 # when field.empty_strings_allowed is True and the value is None; skip these |
|
337 # tests. |
|
338 if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] == 'django.db.backends.oracle': |
|
339 test_data = [data for data in test_data |
|
340 if not (data[0] == data_obj and |
|
341 data[2]._meta.get_field('data').empty_strings_allowed and |
|
342 data[3] is None)] |
|
343 |
|
344 # Regression test for #8651 -- a FK to an object iwth PK of 0 |
|
345 # This won't work on MySQL since it won't let you create an object |
|
346 # with a primary key of 0, |
|
347 if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] != 'django.db.backends.mysql': |
|
348 test_data.extend([ |
|
349 (data_obj, 0, Anchor, "Anchor 0"), |
|
350 (fk_obj, 465, FKData, 0), |
|
351 ]) |
|
352 |
|
353 # Dynamically create serializer tests to ensure that all |
|
354 # registered serializers are automatically tested. |
|
355 class SerializerTests(TestCase): |
|
356 pass |
|
357 |
|
358 def serializerTest(format, self): |
|
359 |
|
360 # Create all the objects defined in the test data |
|
361 objects = [] |
|
362 instance_count = {} |
|
363 for (func, pk, klass, datum) in test_data: |
|
364 objects.extend(func[0](pk, klass, datum)) |
|
365 |
|
366 # Get a count of the number of objects created for each class |
|
367 for klass in instance_count: |
|
368 instance_count[klass] = klass.objects.count() |
|
369 |
|
370 # Add the generic tagged objects to the object list |
|
371 objects.extend(Tag.objects.all()) |
|
372 |
|
373 # Serialize the test database |
|
374 serialized_data = serializers.serialize(format, objects, indent=2) |
|
375 |
|
376 for obj in serializers.deserialize(format, serialized_data): |
|
377 obj.save() |
|
378 |
|
379 # Assert that the deserialized data is the same |
|
380 # as the original source |
|
381 for (func, pk, klass, datum) in test_data: |
|
382 func[1](self, pk, klass, datum) |
|
383 |
|
384 # Assert that the number of objects deserialized is the |
|
385 # same as the number that was serialized. |
|
386 for klass, count in instance_count.items(): |
|
387 self.assertEquals(count, klass.objects.count()) |
|
388 |
|
389 def fieldsTest(format, self): |
|
390 obj = ComplexModel(field1='first', field2='second', field3='third') |
|
391 obj.save_base(raw=True) |
|
392 |
|
393 # Serialize then deserialize the test database |
|
394 serialized_data = serializers.serialize(format, [obj], indent=2, fields=('field1','field3')) |
|
395 result = serializers.deserialize(format, serialized_data).next() |
|
396 |
|
397 # Check that the deserialized object contains data in only the serialized fields. |
|
398 self.assertEqual(result.object.field1, 'first') |
|
399 self.assertEqual(result.object.field2, '') |
|
400 self.assertEqual(result.object.field3, 'third') |
|
401 |
|
402 def streamTest(format, self): |
|
403 obj = ComplexModel(field1='first',field2='second',field3='third') |
|
404 obj.save_base(raw=True) |
|
405 |
|
406 # Serialize the test database to a stream |
|
407 stream = StringIO() |
|
408 serializers.serialize(format, [obj], indent=2, stream=stream) |
|
409 |
|
410 # Serialize normally for a comparison |
|
411 string_data = serializers.serialize(format, [obj], indent=2) |
|
412 |
|
413 # Check that the two are the same |
|
414 self.assertEqual(string_data, stream.getvalue()) |
|
415 stream.close() |
|
416 |
|
417 for format in serializers.get_serializer_formats(): |
|
418 setattr(SerializerTests, 'test_' + format + '_serializer', curry(serializerTest, format)) |
|
419 setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format)) |
|
420 if format != 'python': |
|
421 setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format)) |