|
1 # -*- coding: utf-8 -*- |
|
2 from datetime import datetime |
|
3 from StringIO import StringIO |
|
4 from xml.dom import minidom |
|
5 |
|
6 from django.core import serializers |
|
7 from django.db import transaction |
|
8 from django.test import TestCase, TransactionTestCase, Approximate |
|
9 from django.utils import simplejson |
|
10 |
|
11 from models import Category, Author, Article, AuthorProfile, Actor, \ |
|
12 Movie, Score, Player, Team |
|
13 |
|
14 class SerializersTestBase(object): |
|
15 @staticmethod |
|
16 def _comparison_value(value): |
|
17 return value |
|
18 |
|
19 def setUp(self): |
|
20 sports = Category.objects.create(name="Sports") |
|
21 music = Category.objects.create(name="Music") |
|
22 op_ed = Category.objects.create(name="Op-Ed") |
|
23 |
|
24 self.joe = Author.objects.create(name="Joe") |
|
25 self.jane = Author.objects.create(name="Jane") |
|
26 |
|
27 self.a1 = Article( |
|
28 author=self.jane, |
|
29 headline="Poker has no place on ESPN", |
|
30 pub_date=datetime(2006, 6, 16, 11, 00) |
|
31 ) |
|
32 self.a1.save() |
|
33 self.a1.categories = [sports, op_ed] |
|
34 |
|
35 self.a2 = Article( |
|
36 author=self.joe, |
|
37 headline="Time to reform copyright", |
|
38 pub_date=datetime(2006, 6, 16, 13, 00, 11, 345) |
|
39 ) |
|
40 self.a2.save() |
|
41 self.a2.categories = [music, op_ed] |
|
42 |
|
43 def test_serialize(self): |
|
44 """Tests that basic serialization works.""" |
|
45 serial_str = serializers.serialize(self.serializer_name, |
|
46 Article.objects.all()) |
|
47 self.assertTrue(self._validate_output(serial_str)) |
|
48 |
|
49 def test_serializer_roundtrip(self): |
|
50 """Tests that serialized content can be deserialized.""" |
|
51 serial_str = serializers.serialize(self.serializer_name, |
|
52 Article.objects.all()) |
|
53 models = list(serializers.deserialize(self.serializer_name, serial_str)) |
|
54 self.assertEqual(len(models), 2) |
|
55 |
|
56 def test_altering_serialized_output(self): |
|
57 """ |
|
58 Tests the ability to create new objects by |
|
59 modifying serialized content. |
|
60 """ |
|
61 old_headline = "Poker has no place on ESPN" |
|
62 new_headline = "Poker has no place on television" |
|
63 serial_str = serializers.serialize(self.serializer_name, |
|
64 Article.objects.all()) |
|
65 serial_str = serial_str.replace(old_headline, new_headline) |
|
66 models = list(serializers.deserialize(self.serializer_name, serial_str)) |
|
67 |
|
68 # Prior to saving, old headline is in place |
|
69 self.assertTrue(Article.objects.filter(headline=old_headline)) |
|
70 self.assertFalse(Article.objects.filter(headline=new_headline)) |
|
71 |
|
72 for model in models: |
|
73 model.save() |
|
74 |
|
75 # After saving, new headline is in place |
|
76 self.assertTrue(Article.objects.filter(headline=new_headline)) |
|
77 self.assertFalse(Article.objects.filter(headline=old_headline)) |
|
78 |
|
79 def test_one_to_one_as_pk(self): |
|
80 """ |
|
81 Tests that if you use your own primary key field |
|
82 (such as a OneToOneField), it doesn't appear in the |
|
83 serialized field list - it replaces the pk identifier. |
|
84 """ |
|
85 profile = AuthorProfile(author=self.joe, |
|
86 date_of_birth=datetime(1970,1,1)) |
|
87 profile.save() |
|
88 serial_str = serializers.serialize(self.serializer_name, |
|
89 AuthorProfile.objects.all()) |
|
90 self.assertFalse(self._get_field_values(serial_str, 'author')) |
|
91 |
|
92 for obj in serializers.deserialize(self.serializer_name, serial_str): |
|
93 self.assertEqual(obj.object.pk, self._comparison_value(self.joe.pk)) |
|
94 |
|
95 def test_serialize_field_subset(self): |
|
96 """Tests that output can be restricted to a subset of fields""" |
|
97 valid_fields = ('headline','pub_date') |
|
98 invalid_fields = ("author", "categories") |
|
99 serial_str = serializers.serialize(self.serializer_name, |
|
100 Article.objects.all(), |
|
101 fields=valid_fields) |
|
102 for field_name in invalid_fields: |
|
103 self.assertFalse(self._get_field_values(serial_str, field_name)) |
|
104 |
|
105 for field_name in valid_fields: |
|
106 self.assertTrue(self._get_field_values(serial_str, field_name)) |
|
107 |
|
108 def test_serialize_unicode(self): |
|
109 """Tests that unicode makes the roundtrip intact""" |
|
110 actor_name = u"Za\u017c\u00f3\u0142\u0107" |
|
111 movie_title = u'G\u0119\u015bl\u0105 ja\u017a\u0144' |
|
112 ac = Actor(name=actor_name) |
|
113 mv = Movie(title=movie_title, actor=ac) |
|
114 ac.save() |
|
115 mv.save() |
|
116 |
|
117 serial_str = serializers.serialize(self.serializer_name, [mv]) |
|
118 self.assertEqual(self._get_field_values(serial_str, "title")[0], movie_title) |
|
119 self.assertEqual(self._get_field_values(serial_str, "actor")[0], actor_name) |
|
120 |
|
121 obj_list = list(serializers.deserialize(self.serializer_name, serial_str)) |
|
122 mv_obj = obj_list[0].object |
|
123 self.assertEqual(mv_obj.title, movie_title) |
|
124 |
|
125 def test_serialize_with_null_pk(self): |
|
126 """ |
|
127 Tests that serialized data with no primary key results |
|
128 in a model instance with no id |
|
129 """ |
|
130 category = Category(name="Reference") |
|
131 serial_str = serializers.serialize(self.serializer_name, [category]) |
|
132 pk_value = self._get_pk_values(serial_str)[0] |
|
133 self.assertFalse(pk_value) |
|
134 |
|
135 cat_obj = list(serializers.deserialize(self.serializer_name, |
|
136 serial_str))[0].object |
|
137 self.assertEqual(cat_obj.id, None) |
|
138 |
|
139 def test_float_serialization(self): |
|
140 """Tests that float values serialize and deserialize intact""" |
|
141 sc = Score(score=3.4) |
|
142 sc.save() |
|
143 serial_str = serializers.serialize(self.serializer_name, [sc]) |
|
144 deserial_objs = list(serializers.deserialize(self.serializer_name, |
|
145 serial_str)) |
|
146 self.assertEqual(deserial_objs[0].object.score, Approximate(3.4, places=1)) |
|
147 |
|
148 def test_custom_field_serialization(self): |
|
149 """Tests that custom fields serialize and deserialize intact""" |
|
150 team_str = "Spartak Moskva" |
|
151 player = Player() |
|
152 player.name = "Soslan Djanaev" |
|
153 player.rank = 1 |
|
154 player.team = Team(team_str) |
|
155 player.save() |
|
156 serial_str = serializers.serialize(self.serializer_name, |
|
157 Player.objects.all()) |
|
158 team = self._get_field_values(serial_str, "team") |
|
159 self.assertTrue(team) |
|
160 self.assertEqual(team[0], team_str) |
|
161 |
|
162 deserial_objs = list(serializers.deserialize(self.serializer_name, serial_str)) |
|
163 self.assertEqual(deserial_objs[0].object.team.to_string(), |
|
164 player.team.to_string()) |
|
165 |
|
166 def test_pre_1000ad_date(self): |
|
167 """Tests that year values before 1000AD are properly formatted""" |
|
168 # Regression for #12524 -- dates before 1000AD get prefixed |
|
169 # 0's on the year |
|
170 a = Article.objects.create( |
|
171 author = self.jane, |
|
172 headline = "Nobody remembers the early years", |
|
173 pub_date = datetime(1, 2, 3, 4, 5, 6)) |
|
174 |
|
175 serial_str = serializers.serialize(self.serializer_name, [a]) |
|
176 date_values = self._get_field_values(serial_str, "pub_date") |
|
177 self.assertEquals(date_values[0], "0001-02-03 04:05:06") |
|
178 |
|
179 def test_pkless_serialized_strings(self): |
|
180 """ |
|
181 Tests that serialized strings without PKs |
|
182 can be turned into models |
|
183 """ |
|
184 deserial_objs = list(serializers.deserialize(self.serializer_name, |
|
185 self.pkless_str)) |
|
186 for obj in deserial_objs: |
|
187 self.assertFalse(obj.object.id) |
|
188 obj.save() |
|
189 self.assertEqual(Category.objects.all().count(), 4) |
|
190 |
|
191 |
|
192 class SerializersTransactionTestBase(object): |
|
193 def test_forward_refs(self): |
|
194 """ |
|
195 Tests that objects ids can be referenced before they are |
|
196 defined in the serialization data. |
|
197 """ |
|
198 # The deserialization process needs to be contained |
|
199 # within a transaction in order to test forward reference |
|
200 # handling. |
|
201 transaction.enter_transaction_management() |
|
202 transaction.managed(True) |
|
203 objs = serializers.deserialize(self.serializer_name, self.fwd_ref_str) |
|
204 for obj in objs: |
|
205 obj.save() |
|
206 transaction.commit() |
|
207 transaction.leave_transaction_management() |
|
208 |
|
209 for model_cls in (Category, Author, Article): |
|
210 self.assertEqual(model_cls.objects.all().count(), 1) |
|
211 art_obj = Article.objects.all()[0] |
|
212 self.assertEqual(art_obj.categories.all().count(), 1) |
|
213 self.assertEqual(art_obj.author.name, "Agnes") |
|
214 |
|
215 |
|
216 class XmlSerializerTestCase(SerializersTestBase, TestCase): |
|
217 serializer_name = "xml" |
|
218 pkless_str = """<?xml version="1.0" encoding="utf-8"?> |
|
219 <django-objects version="1.0"> |
|
220 <object model="serializers.category"> |
|
221 <field type="CharField" name="name">Reference</field> |
|
222 </object> |
|
223 </django-objects>""" |
|
224 |
|
225 @staticmethod |
|
226 def _comparison_value(value): |
|
227 # The XML serializer handles everything as strings, so comparisons |
|
228 # need to be performed on the stringified value |
|
229 return unicode(value) |
|
230 |
|
231 @staticmethod |
|
232 def _validate_output(serial_str): |
|
233 try: |
|
234 minidom.parseString(serial_str) |
|
235 except Exception: |
|
236 return False |
|
237 else: |
|
238 return True |
|
239 |
|
240 @staticmethod |
|
241 def _get_pk_values(serial_str): |
|
242 ret_list = [] |
|
243 dom = minidom.parseString(serial_str) |
|
244 fields = dom.getElementsByTagName("object") |
|
245 for field in fields: |
|
246 ret_list.append(field.getAttribute("pk")) |
|
247 return ret_list |
|
248 |
|
249 @staticmethod |
|
250 def _get_field_values(serial_str, field_name): |
|
251 ret_list = [] |
|
252 dom = minidom.parseString(serial_str) |
|
253 fields = dom.getElementsByTagName("field") |
|
254 for field in fields: |
|
255 if field.getAttribute("name") == field_name: |
|
256 temp = [] |
|
257 for child in field.childNodes: |
|
258 temp.append(child.nodeValue) |
|
259 ret_list.append("".join(temp)) |
|
260 return ret_list |
|
261 |
|
262 class XmlSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): |
|
263 serializer_name = "xml" |
|
264 fwd_ref_str = """<?xml version="1.0" encoding="utf-8"?> |
|
265 <django-objects version="1.0"> |
|
266 <object pk="1" model="serializers.article"> |
|
267 <field to="serializers.author" name="author" rel="ManyToOneRel">1</field> |
|
268 <field type="CharField" name="headline">Forward references pose no problem</field> |
|
269 <field type="DateTimeField" name="pub_date">2006-06-16 15:00:00</field> |
|
270 <field to="serializers.category" name="categories" rel="ManyToManyRel"> |
|
271 <object pk="1"></object> |
|
272 </field> |
|
273 </object> |
|
274 <object pk="1" model="serializers.author"> |
|
275 <field type="CharField" name="name">Agnes</field> |
|
276 </object> |
|
277 <object pk="1" model="serializers.category"> |
|
278 <field type="CharField" name="name">Reference</field></object> |
|
279 </django-objects>""" |
|
280 |
|
281 |
|
282 class JsonSerializerTestCase(SerializersTestBase, TestCase): |
|
283 serializer_name = "json" |
|
284 pkless_str = """[{"pk": null, "model": "serializers.category", "fields": {"name": "Reference"}}]""" |
|
285 |
|
286 @staticmethod |
|
287 def _validate_output(serial_str): |
|
288 try: |
|
289 simplejson.loads(serial_str) |
|
290 except Exception: |
|
291 return False |
|
292 else: |
|
293 return True |
|
294 |
|
295 @staticmethod |
|
296 def _get_pk_values(serial_str): |
|
297 ret_list = [] |
|
298 serial_list = simplejson.loads(serial_str) |
|
299 for obj_dict in serial_list: |
|
300 ret_list.append(obj_dict["pk"]) |
|
301 return ret_list |
|
302 |
|
303 @staticmethod |
|
304 def _get_field_values(serial_str, field_name): |
|
305 ret_list = [] |
|
306 serial_list = simplejson.loads(serial_str) |
|
307 for obj_dict in serial_list: |
|
308 if field_name in obj_dict["fields"]: |
|
309 ret_list.append(obj_dict["fields"][field_name]) |
|
310 return ret_list |
|
311 |
|
312 class JsonSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): |
|
313 serializer_name = "json" |
|
314 fwd_ref_str = """[ |
|
315 { |
|
316 "pk": 1, |
|
317 "model": "serializers.article", |
|
318 "fields": { |
|
319 "headline": "Forward references pose no problem", |
|
320 "pub_date": "2006-06-16 15:00:00", |
|
321 "categories": [1], |
|
322 "author": 1 |
|
323 } |
|
324 }, |
|
325 { |
|
326 "pk": 1, |
|
327 "model": "serializers.category", |
|
328 "fields": { |
|
329 "name": "Reference" |
|
330 } |
|
331 }, |
|
332 { |
|
333 "pk": 1, |
|
334 "model": "serializers.author", |
|
335 "fields": { |
|
336 "name": "Agnes" |
|
337 } |
|
338 }]""" |
|
339 |
|
340 try: |
|
341 import yaml |
|
342 except ImportError: |
|
343 pass |
|
344 else: |
|
345 class YamlSerializerTestCase(SerializersTestBase, TestCase): |
|
346 serializer_name = "yaml" |
|
347 fwd_ref_str = """- fields: |
|
348 headline: Forward references pose no problem |
|
349 pub_date: 2006-06-16 15:00:00 |
|
350 categories: [1] |
|
351 author: 1 |
|
352 pk: 1 |
|
353 model: serializers.article |
|
354 - fields: |
|
355 name: Reference |
|
356 pk: 1 |
|
357 model: serializers.category |
|
358 - fields: |
|
359 name: Agnes |
|
360 pk: 1 |
|
361 model: serializers.author""" |
|
362 |
|
363 pkless_str = """- fields: |
|
364 name: Reference |
|
365 pk: null |
|
366 model: serializers.category""" |
|
367 |
|
368 @staticmethod |
|
369 def _validate_output(serial_str): |
|
370 try: |
|
371 yaml.load(StringIO(serial_str)) |
|
372 except Exception: |
|
373 return False |
|
374 else: |
|
375 return True |
|
376 |
|
377 @staticmethod |
|
378 def _get_pk_values(serial_str): |
|
379 ret_list = [] |
|
380 stream = StringIO(serial_str) |
|
381 for obj_dict in yaml.load(stream): |
|
382 ret_list.append(obj_dict["pk"]) |
|
383 return ret_list |
|
384 |
|
385 @staticmethod |
|
386 def _get_field_values(serial_str, field_name): |
|
387 ret_list = [] |
|
388 stream = StringIO(serial_str) |
|
389 for obj_dict in yaml.load(stream): |
|
390 if "fields" in obj_dict and field_name in obj_dict["fields"]: |
|
391 field_value = obj_dict["fields"][field_name] |
|
392 # yaml.load will return non-string objects for some |
|
393 # of the fields we are interested in, this ensures that |
|
394 # everything comes back as a string |
|
395 if isinstance(field_value, basestring): |
|
396 ret_list.append(field_value) |
|
397 else: |
|
398 ret_list.append(str(field_value)) |
|
399 return ret_list |
|
400 |
|
401 class YamlSerializerTransactionTestCase(SerializersTransactionTestBase, TransactionTestCase): |
|
402 serializer_name = "yaml" |
|
403 fwd_ref_str = """- fields: |
|
404 headline: Forward references pose no problem |
|
405 pub_date: 2006-06-16 15:00:00 |
|
406 categories: [1] |
|
407 author: 1 |
|
408 pk: 1 |
|
409 model: serializers.article |
|
410 - fields: |
|
411 name: Reference |
|
412 pk: 1 |
|
413 model: serializers.category |
|
414 - fields: |
|
415 name: Agnes |
|
416 pk: 1 |
|
417 model: serializers.author""" |