|
1 """ |
|
2 Module for abstract serializer/unserializer base classes. |
|
3 """ |
|
4 |
|
5 from StringIO import StringIO |
|
6 |
|
7 from django.db import models |
|
8 from django.utils.encoding import smart_str, smart_unicode |
|
9 from django.utils import datetime_safe |
|
10 |
|
11 class SerializationError(Exception): |
|
12 """Something bad happened during serialization.""" |
|
13 pass |
|
14 |
|
15 class DeserializationError(Exception): |
|
16 """Something bad happened during deserialization.""" |
|
17 pass |
|
18 |
|
19 class Serializer(object): |
|
20 """ |
|
21 Abstract serializer base class. |
|
22 """ |
|
23 |
|
24 # Indicates if the implemented serializer is only available for |
|
25 # internal Django use. |
|
26 internal_use_only = False |
|
27 |
|
28 def serialize(self, queryset, **options): |
|
29 """ |
|
30 Serialize a queryset. |
|
31 """ |
|
32 self.options = options |
|
33 |
|
34 self.stream = options.get("stream", StringIO()) |
|
35 self.selected_fields = options.get("fields") |
|
36 self.use_natural_keys = options.get("use_natural_keys", False) |
|
37 |
|
38 self.start_serialization() |
|
39 for obj in queryset: |
|
40 self.start_object(obj) |
|
41 for field in obj._meta.local_fields: |
|
42 if field.serialize: |
|
43 if field.rel is None: |
|
44 if self.selected_fields is None or field.attname in self.selected_fields: |
|
45 self.handle_field(obj, field) |
|
46 else: |
|
47 if self.selected_fields is None or field.attname[:-3] in self.selected_fields: |
|
48 self.handle_fk_field(obj, field) |
|
49 for field in obj._meta.many_to_many: |
|
50 if field.serialize: |
|
51 if self.selected_fields is None or field.attname in self.selected_fields: |
|
52 self.handle_m2m_field(obj, field) |
|
53 self.end_object(obj) |
|
54 self.end_serialization() |
|
55 return self.getvalue() |
|
56 |
|
57 def get_string_value(self, obj, field): |
|
58 """ |
|
59 Convert a field's value to a string. |
|
60 """ |
|
61 return smart_unicode(field.value_to_string(obj)) |
|
62 |
|
63 def start_serialization(self): |
|
64 """ |
|
65 Called when serializing of the queryset starts. |
|
66 """ |
|
67 raise NotImplementedError |
|
68 |
|
69 def end_serialization(self): |
|
70 """ |
|
71 Called when serializing of the queryset ends. |
|
72 """ |
|
73 pass |
|
74 |
|
75 def start_object(self, obj): |
|
76 """ |
|
77 Called when serializing of an object starts. |
|
78 """ |
|
79 raise NotImplementedError |
|
80 |
|
81 def end_object(self, obj): |
|
82 """ |
|
83 Called when serializing of an object ends. |
|
84 """ |
|
85 pass |
|
86 |
|
87 def handle_field(self, obj, field): |
|
88 """ |
|
89 Called to handle each individual (non-relational) field on an object. |
|
90 """ |
|
91 raise NotImplementedError |
|
92 |
|
93 def handle_fk_field(self, obj, field): |
|
94 """ |
|
95 Called to handle a ForeignKey field. |
|
96 """ |
|
97 raise NotImplementedError |
|
98 |
|
99 def handle_m2m_field(self, obj, field): |
|
100 """ |
|
101 Called to handle a ManyToManyField. |
|
102 """ |
|
103 raise NotImplementedError |
|
104 |
|
105 def getvalue(self): |
|
106 """ |
|
107 Return the fully serialized queryset (or None if the output stream is |
|
108 not seekable). |
|
109 """ |
|
110 if callable(getattr(self.stream, 'getvalue', None)): |
|
111 return self.stream.getvalue() |
|
112 |
|
113 class Deserializer(object): |
|
114 """ |
|
115 Abstract base deserializer class. |
|
116 """ |
|
117 |
|
118 def __init__(self, stream_or_string, **options): |
|
119 """ |
|
120 Init this serializer given a stream or a string |
|
121 """ |
|
122 self.options = options |
|
123 if isinstance(stream_or_string, basestring): |
|
124 self.stream = StringIO(stream_or_string) |
|
125 else: |
|
126 self.stream = stream_or_string |
|
127 # hack to make sure that the models have all been loaded before |
|
128 # deserialization starts (otherwise subclass calls to get_model() |
|
129 # and friends might fail...) |
|
130 models.get_apps() |
|
131 |
|
132 def __iter__(self): |
|
133 return self |
|
134 |
|
135 def next(self): |
|
136 """Iteration iterface -- return the next item in the stream""" |
|
137 raise NotImplementedError |
|
138 |
|
139 class DeserializedObject(object): |
|
140 """ |
|
141 A deserialized model. |
|
142 |
|
143 Basically a container for holding the pre-saved deserialized data along |
|
144 with the many-to-many data saved with the object. |
|
145 |
|
146 Call ``save()`` to save the object (with the many-to-many data) to the |
|
147 database; call ``save(save_m2m=False)`` to save just the object fields |
|
148 (and not touch the many-to-many stuff.) |
|
149 """ |
|
150 |
|
151 def __init__(self, obj, m2m_data=None): |
|
152 self.object = obj |
|
153 self.m2m_data = m2m_data |
|
154 |
|
155 def __repr__(self): |
|
156 return "<DeserializedObject: %s.%s(pk=%s)>" % ( |
|
157 self.object._meta.app_label, self.object._meta.object_name, self.object.pk) |
|
158 |
|
159 def save(self, save_m2m=True, using=None): |
|
160 # Call save on the Model baseclass directly. This bypasses any |
|
161 # model-defined save. The save is also forced to be raw. |
|
162 # This ensures that the data that is deserialized is literally |
|
163 # what came from the file, not post-processed by pre_save/save |
|
164 # methods. |
|
165 models.Model.save_base(self.object, using=using, raw=True) |
|
166 if self.m2m_data and save_m2m: |
|
167 for accessor_name, object_list in self.m2m_data.items(): |
|
168 setattr(self.object, accessor_name, object_list) |
|
169 |
|
170 # prevent a second (possibly accidental) call to save() from saving |
|
171 # the m2m data twice. |
|
172 self.m2m_data = None |