1"""
2A Python "serializer". Doesn't do much serializing per se -- just converts to
3and from basic Python data types (lists, dicts, strings, etc.). Useful as a basis for
4other serializers.
5"""
6
7from django.apps import apps
8from django.core.serializers import base
9from django.db import DEFAULT_DB_ALIAS, models
10from django.db.models import CompositePrimaryKey
11from django.utils.encoding import is_protected_type
12
13
14class Serializer(base.Serializer):
15 """
16 Serialize a QuerySet to basic Python objects.
17 """
18
19 internal_use_only = True
20
21 def start_serialization(self):
22 self._current = None
23 self.objects = []
24
25 def end_serialization(self):
26 pass
27
28 def start_object(self, obj):
29 self._current = {}
30
31 def end_object(self, obj):
32 self.objects.append(self.get_dump_object(obj))
33 self._current = None
34
35 def get_dump_object(self, obj):
36 data = {"model": str(obj._meta)}
37 if not self.use_natural_primary_keys or not hasattr(obj, "natural_key"):
38 data["pk"] = self._value_from_field(obj, obj._meta.pk)
39 data["fields"] = self._current
40 return data
41
42 def _value_from_field(self, obj, field):
43 if isinstance(field, CompositePrimaryKey):
44 return [self._value_from_field(obj, f) for f in field]
45 value = field.value_from_object(obj)
46 # Protected types (i.e., primitives like None, numbers, dates,
47 # and Decimals) are passed through as is. All other values are
48 # converted to string first.
49 return value if is_protected_type(value) else field.value_to_string(obj)
50
51 def handle_field(self, obj, field):
52 self._current[field.name] = self._value_from_field(obj, field)
53
54 def handle_fk_field(self, obj, field):
55 if self.use_natural_foreign_keys and hasattr(
56 field.remote_field.model, "natural_key"
57 ):
58 related = getattr(obj, field.name)
59 if related:
60 value = related.natural_key()
61 else:
62 value = None
63 else:
64 value = self._value_from_field(obj, field)
65 self._current[field.name] = value
66
67 def handle_m2m_field(self, obj, field):
68 if field.remote_field.through._meta.auto_created:
69 if self.use_natural_foreign_keys and hasattr(
70 field.remote_field.model, "natural_key"
71 ):
72
73 def m2m_value(value):
74 return value.natural_key()
75
76 def queryset_iterator(obj, field):
77 attr = getattr(obj, field.name)
78 chunk_size = (
79 2000 if getattr(attr, "prefetch_cache_name", None) else None
80 )
81 return attr.iterator(chunk_size)
82
83 else:
84
85 def m2m_value(value):
86 return self._value_from_field(value, value._meta.pk)
87
88 def queryset_iterator(obj, field):
89 query_set = getattr(obj, field.name).select_related(None).only("pk")
90 chunk_size = 2000 if query_set._prefetch_related_lookups else None
91 return query_set.iterator(chunk_size=chunk_size)
92
93 m2m_iter = getattr(obj, "_prefetched_objects_cache", {}).get(
94 field.name,
95 queryset_iterator(obj, field),
96 )
97 self._current[field.name] = [m2m_value(related) for related in m2m_iter]
98
99 def getvalue(self):
100 return self.objects
101
102
103class Deserializer(base.Deserializer):
104 """
105 Deserialize simple Python objects back into Django ORM instances.
106
107 It's expected that you pass the Python objects themselves (instead of a
108 stream or a string) to the constructor
109 """
110
111 def __init__(
112 self, object_list, *, using=DEFAULT_DB_ALIAS, ignorenonexistent=False, **options
113 ):
114 super().__init__(object_list, **options)
115 self.handle_forward_references = options.pop("handle_forward_references", False)
116 self.using = using
117 self.ignorenonexistent = ignorenonexistent
118 self.field_names_cache = {} # Model: <list of field_names>
119 self._iterator = None
120
121 def __iter__(self):
122 for obj in self.stream:
123 yield from self._handle_object(obj)
124
125 def __next__(self):
126 if self._iterator is None:
127 self._iterator = iter(self)
128 return next(self._iterator)
129
130 def _handle_object(self, obj):
131 data = {}
132 m2m_data = {}
133 deferred_fields = {}
134
135 # Look up the model and starting build a dict of data for it.
136 try:
137 Model = self._get_model_from_node(obj["model"])
138 except base.DeserializationError:
139 if self.ignorenonexistent:
140 return
141 raise
142 if "pk" in obj:
143 try:
144 data[Model._meta.pk.attname] = Model._meta.pk.to_python(obj.get("pk"))
145 except Exception as e:
146 raise base.DeserializationError.WithData(
147 e, obj["model"], obj.get("pk"), None
148 )
149
150 if Model not in self.field_names_cache:
151 self.field_names_cache[Model] = {f.name for f in Model._meta.get_fields()}
152 field_names = self.field_names_cache[Model]
153
154 # Handle each field
155 for field_name, field_value in obj["fields"].items():
156 if self.ignorenonexistent and field_name not in field_names:
157 # skip fields no longer on model
158 continue
159
160 field = Model._meta.get_field(field_name)
161
162 # Handle M2M relations
163 if field.remote_field and isinstance(
164 field.remote_field, models.ManyToManyRel
165 ):
166 try:
167 values = self._handle_m2m_field_node(field, field_value)
168 if values == base.DEFER_FIELD:
169 deferred_fields[field] = field_value
170 else:
171 m2m_data[field.name] = values
172 except base.M2MDeserializationError as e:
173 raise base.DeserializationError.WithData(
174 e.original_exc, obj["model"], obj.get("pk"), e.pk
175 )
176
177 # Handle FK fields
178 elif field.remote_field and isinstance(
179 field.remote_field, models.ManyToOneRel
180 ):
181 try:
182 value = self._handle_fk_field_node(field, field_value)
183 if value == base.DEFER_FIELD:
184 deferred_fields[field] = field_value
185 else:
186 data[field.attname] = value
187 except Exception as e:
188 raise base.DeserializationError.WithData(
189 e, obj["model"], obj.get("pk"), field_value
190 )
191
192 # Handle all other fields
193 else:
194 try:
195 data[field.name] = field.to_python(field_value)
196 except Exception as e:
197 raise base.DeserializationError.WithData(
198 e, obj["model"], obj.get("pk"), field_value
199 )
200
201 model_instance = base.build_instance(Model, data, self.using)
202 yield base.DeserializedObject(model_instance, m2m_data, deferred_fields)
203
204 def _handle_m2m_field_node(self, field, field_value):
205 return base.deserialize_m2m_values(
206 field, field_value, self.using, self.handle_forward_references
207 )
208
209 def _handle_fk_field_node(self, field, field_value):
210 return base.deserialize_fk_value(
211 field, field_value, self.using, self.handle_forward_references
212 )
213
214 @staticmethod
215 def _get_model_from_node(model_identifier):
216 """Look up a model from an "app_label.model_name" string."""
217 try:
218 return apps.get_model(model_identifier)
219 except (LookupError, TypeError):
220 raise base.DeserializationError(
221 f"Invalid model identifier: {model_identifier}"
222 )