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