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