Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/core/serializers/base.py: 26%
192 statements
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
1"""
2Module for abstract serializer/unserializer base classes.
3"""
4import pickle
5import warnings
6from io import StringIO
8from django.core.exceptions import ObjectDoesNotExist
9from django.db import models
10from django.utils.deprecation import RemovedInDjango50Warning
12DEFER_FIELD = object()
15class PickleSerializer:
16 """
17 Simple wrapper around pickle to be used in signing.dumps()/loads() and
18 cache backends.
19 """
21 def __init__(self, protocol=None):
22 warnings.warn(
23 "PickleSerializer is deprecated due to its security risk. Use "
24 "JSONSerializer instead.",
25 RemovedInDjango50Warning,
26 )
27 self.protocol = pickle.HIGHEST_PROTOCOL if protocol is None else protocol
29 def dumps(self, obj):
30 return pickle.dumps(obj, self.protocol)
32 def loads(self, data):
33 return pickle.loads(data)
36class SerializerDoesNotExist(KeyError):
37 """The requested serializer was not found."""
39 pass
42class SerializationError(Exception):
43 """Something bad happened during serialization."""
45 pass
48class DeserializationError(Exception):
49 """Something bad happened during deserialization."""
51 @classmethod
52 def WithData(cls, original_exc, model, fk, field_value):
53 """
54 Factory method for creating a deserialization error which has a more
55 explanatory message.
56 """
57 return cls(
58 "%s: (%s:pk=%s) field_value was '%s'"
59 % (original_exc, model, fk, field_value)
60 )
63class M2MDeserializationError(Exception):
64 """Something bad happened during deserialization of a ManyToManyField."""
66 def __init__(self, original_exc, pk):
67 self.original_exc = original_exc
68 self.pk = pk
71class ProgressBar:
72 progress_width = 75
74 def __init__(self, output, total_count):
75 self.output = output
76 self.total_count = total_count
77 self.prev_done = 0
79 def update(self, count):
80 if not self.output:
81 return
82 perc = count * 100 // self.total_count
83 done = perc * self.progress_width // 100
84 if self.prev_done >= done:
85 return
86 self.prev_done = done
87 cr = "" if self.total_count == 1 else "\r"
88 self.output.write(
89 cr + "[" + "." * done + " " * (self.progress_width - done) + "]"
90 )
91 if done == self.progress_width:
92 self.output.write("\n")
93 self.output.flush()
96class Serializer:
97 """
98 Abstract serializer base class.
99 """
101 # Indicates if the implemented serializer is only available for
102 # internal Django use.
103 internal_use_only = False
104 progress_class = ProgressBar
105 stream_class = StringIO
107 def serialize(
108 self,
109 queryset,
110 *,
111 stream=None,
112 fields=None,
113 use_natural_foreign_keys=False,
114 use_natural_primary_keys=False,
115 progress_output=None,
116 object_count=0,
117 **options,
118 ):
119 """
120 Serialize a queryset.
121 """
122 self.options = options
124 self.stream = stream if stream is not None else self.stream_class()
125 self.selected_fields = fields
126 self.use_natural_foreign_keys = use_natural_foreign_keys
127 self.use_natural_primary_keys = use_natural_primary_keys
128 progress_bar = self.progress_class(progress_output, object_count)
130 self.start_serialization()
131 self.first = True
132 for count, obj in enumerate(queryset, start=1):
133 self.start_object(obj)
134 # Use the concrete parent class' _meta instead of the object's _meta
135 # This is to avoid local_fields problems for proxy models. Refs #17717.
136 concrete_model = obj._meta.concrete_model
137 # When using natural primary keys, retrieve the pk field of the
138 # parent for multi-table inheritance child models. That field must
139 # be serialized, otherwise deserialization isn't possible.
140 if self.use_natural_primary_keys:
141 pk = concrete_model._meta.pk
142 pk_parent = (
143 pk if pk.remote_field and pk.remote_field.parent_link else None
144 )
145 else:
146 pk_parent = None
147 for field in concrete_model._meta.local_fields:
148 if field.serialize or field is pk_parent:
149 if field.remote_field is None:
150 if (
151 self.selected_fields is None
152 or field.attname in self.selected_fields
153 ):
154 self.handle_field(obj, field)
155 else:
156 if (
157 self.selected_fields is None
158 or field.attname[:-3] in self.selected_fields
159 ):
160 self.handle_fk_field(obj, field)
161 for field in concrete_model._meta.local_many_to_many:
162 if field.serialize:
163 if (
164 self.selected_fields is None
165 or field.attname in self.selected_fields
166 ):
167 self.handle_m2m_field(obj, field)
168 self.end_object(obj)
169 progress_bar.update(count)
170 self.first = self.first and False
171 self.end_serialization()
172 return self.getvalue()
174 def start_serialization(self):
175 """
176 Called when serializing of the queryset starts.
177 """
178 raise NotImplementedError(
179 "subclasses of Serializer must provide a start_serialization() method"
180 )
182 def end_serialization(self):
183 """
184 Called when serializing of the queryset ends.
185 """
186 pass
188 def start_object(self, obj):
189 """
190 Called when serializing of an object starts.
191 """
192 raise NotImplementedError(
193 "subclasses of Serializer must provide a start_object() method"
194 )
196 def end_object(self, obj):
197 """
198 Called when serializing of an object ends.
199 """
200 pass
202 def handle_field(self, obj, field):
203 """
204 Called to handle each individual (non-relational) field on an object.
205 """
206 raise NotImplementedError(
207 "subclasses of Serializer must provide a handle_field() method"
208 )
210 def handle_fk_field(self, obj, field):
211 """
212 Called to handle a ForeignKey field.
213 """
214 raise NotImplementedError(
215 "subclasses of Serializer must provide a handle_fk_field() method"
216 )
218 def handle_m2m_field(self, obj, field):
219 """
220 Called to handle a ManyToManyField.
221 """
222 raise NotImplementedError(
223 "subclasses of Serializer must provide a handle_m2m_field() method"
224 )
226 def getvalue(self):
227 """
228 Return the fully serialized queryset (or None if the output stream is
229 not seekable).
230 """
231 if callable(getattr(self.stream, "getvalue", None)):
232 return self.stream.getvalue()
235class Deserializer:
236 """
237 Abstract base deserializer class.
238 """
240 def __init__(self, stream_or_string, **options):
241 """
242 Init this serializer given a stream or a string
243 """
244 self.options = options
245 if isinstance(stream_or_string, str):
246 self.stream = StringIO(stream_or_string)
247 else:
248 self.stream = stream_or_string
250 def __iter__(self):
251 return self
253 def __next__(self):
254 """Iteration interface -- return the next item in the stream"""
255 raise NotImplementedError(
256 "subclasses of Deserializer must provide a __next__() method"
257 )
260class DeserializedObject:
261 """
262 A deserialized model.
264 Basically a container for holding the pre-saved deserialized data along
265 with the many-to-many data saved with the object.
267 Call ``save()`` to save the object (with the many-to-many data) to the
268 database; call ``save(save_m2m=False)`` to save just the object fields
269 (and not touch the many-to-many stuff.)
270 """
272 def __init__(self, obj, m2m_data=None, deferred_fields=None):
273 self.object = obj
274 self.m2m_data = m2m_data
275 self.deferred_fields = deferred_fields
277 def __repr__(self):
278 return "<%s: %s(pk=%s)>" % (
279 self.__class__.__name__,
280 self.object._meta.label,
281 self.object.pk,
282 )
284 def save(self, save_m2m=True, using=None, **kwargs):
285 # Call save on the Model baseclass directly. This bypasses any
286 # model-defined save. The save is also forced to be raw.
287 # raw=True is passed to any pre/post_save signals.
288 models.Model.save_base(self.object, using=using, raw=True, **kwargs)
289 if self.m2m_data and save_m2m:
290 for accessor_name, object_list in self.m2m_data.items():
291 getattr(self.object, accessor_name).set(object_list)
293 # prevent a second (possibly accidental) call to save() from saving
294 # the m2m data twice.
295 self.m2m_data = None
297 def save_deferred_fields(self, using=None):
298 self.m2m_data = {}
299 for field, field_value in self.deferred_fields.items():
300 opts = self.object._meta
301 label = opts.app_label + "." + opts.model_name
302 if isinstance(field.remote_field, models.ManyToManyRel):
303 try:
304 values = deserialize_m2m_values(
305 field, field_value, using, handle_forward_references=False
306 )
307 except M2MDeserializationError as e:
308 raise DeserializationError.WithData(
309 e.original_exc, label, self.object.pk, e.pk
310 )
311 self.m2m_data[field.name] = values
312 elif isinstance(field.remote_field, models.ManyToOneRel):
313 try:
314 value = deserialize_fk_value(
315 field, field_value, using, handle_forward_references=False
316 )
317 except Exception as e:
318 raise DeserializationError.WithData(
319 e, label, self.object.pk, field_value
320 )
321 setattr(self.object, field.attname, value)
322 self.save()
325def build_instance(Model, data, db):
326 """
327 Build a model instance.
329 If the model instance doesn't have a primary key and the model supports
330 natural keys, try to retrieve it from the database.
331 """
332 default_manager = Model._meta.default_manager
333 pk = data.get(Model._meta.pk.attname)
334 if (
335 pk is None
336 and hasattr(default_manager, "get_by_natural_key")
337 and hasattr(Model, "natural_key")
338 ):
339 obj = Model(**data)
340 obj._state.db = db
341 natural_key = obj.natural_key()
342 try:
343 data[Model._meta.pk.attname] = Model._meta.pk.to_python(
344 default_manager.db_manager(db).get_by_natural_key(*natural_key).pk
345 )
346 except Model.DoesNotExist:
347 pass
348 return Model(**data)
351def deserialize_m2m_values(field, field_value, using, handle_forward_references):
352 model = field.remote_field.model
353 if hasattr(model._default_manager, "get_by_natural_key"):
355 def m2m_convert(value):
356 if hasattr(value, "__iter__") and not isinstance(value, str):
357 return (
358 model._default_manager.db_manager(using)
359 .get_by_natural_key(*value)
360 .pk
361 )
362 else:
363 return model._meta.pk.to_python(value)
365 else:
367 def m2m_convert(v):
368 return model._meta.pk.to_python(v)
370 try:
371 pks_iter = iter(field_value)
372 except TypeError as e:
373 raise M2MDeserializationError(e, field_value)
374 try:
375 values = []
376 for pk in pks_iter:
377 values.append(m2m_convert(pk))
378 return values
379 except Exception as e:
380 if isinstance(e, ObjectDoesNotExist) and handle_forward_references:
381 return DEFER_FIELD
382 else:
383 raise M2MDeserializationError(e, pk)
386def deserialize_fk_value(field, field_value, using, handle_forward_references):
387 if field_value is None:
388 return None
389 model = field.remote_field.model
390 default_manager = model._default_manager
391 field_name = field.remote_field.field_name
392 if (
393 hasattr(default_manager, "get_by_natural_key")
394 and hasattr(field_value, "__iter__")
395 and not isinstance(field_value, str)
396 ):
397 try:
398 obj = default_manager.db_manager(using).get_by_natural_key(*field_value)
399 except ObjectDoesNotExist:
400 if handle_forward_references:
401 return DEFER_FIELD
402 else:
403 raise
404 value = getattr(obj, field_name)
405 # If this is a natural foreign key to an object that has a FK/O2O as
406 # the foreign key, use the FK value.
407 if model._meta.pk.remote_field:
408 value = value.pk
409 return value
410 return model._meta.get_field(field_name).to_python(field_value)