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

1""" 

2Module for abstract serializer/unserializer base classes. 

3""" 

4import pickle 

5import warnings 

6from io import StringIO 

7 

8from django.core.exceptions import ObjectDoesNotExist 

9from django.db import models 

10from django.utils.deprecation import RemovedInDjango50Warning 

11 

12DEFER_FIELD = object() 

13 

14 

15class PickleSerializer: 

16 """ 

17 Simple wrapper around pickle to be used in signing.dumps()/loads() and 

18 cache backends. 

19 """ 

20 

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 

28 

29 def dumps(self, obj): 

30 return pickle.dumps(obj, self.protocol) 

31 

32 def loads(self, data): 

33 return pickle.loads(data) 

34 

35 

36class SerializerDoesNotExist(KeyError): 

37 """The requested serializer was not found.""" 

38 

39 pass 

40 

41 

42class SerializationError(Exception): 

43 """Something bad happened during serialization.""" 

44 

45 pass 

46 

47 

48class DeserializationError(Exception): 

49 """Something bad happened during deserialization.""" 

50 

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 ) 

61 

62 

63class M2MDeserializationError(Exception): 

64 """Something bad happened during deserialization of a ManyToManyField.""" 

65 

66 def __init__(self, original_exc, pk): 

67 self.original_exc = original_exc 

68 self.pk = pk 

69 

70 

71class ProgressBar: 

72 progress_width = 75 

73 

74 def __init__(self, output, total_count): 

75 self.output = output 

76 self.total_count = total_count 

77 self.prev_done = 0 

78 

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() 

94 

95 

96class Serializer: 

97 """ 

98 Abstract serializer base class. 

99 """ 

100 

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 

106 

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 

123 

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) 

129 

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() 

173 

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 ) 

181 

182 def end_serialization(self): 

183 """ 

184 Called when serializing of the queryset ends. 

185 """ 

186 pass 

187 

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 ) 

195 

196 def end_object(self, obj): 

197 """ 

198 Called when serializing of an object ends. 

199 """ 

200 pass 

201 

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 ) 

209 

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 ) 

217 

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 ) 

225 

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() 

233 

234 

235class Deserializer: 

236 """ 

237 Abstract base deserializer class. 

238 """ 

239 

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 

249 

250 def __iter__(self): 

251 return self 

252 

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 ) 

258 

259 

260class DeserializedObject: 

261 """ 

262 A deserialized model. 

263 

264 Basically a container for holding the pre-saved deserialized data along 

265 with the many-to-many data saved with the object. 

266 

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 """ 

271 

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 

276 

277 def __repr__(self): 

278 return "<%s: %s(pk=%s)>" % ( 

279 self.__class__.__name__, 

280 self.object._meta.label, 

281 self.object.pk, 

282 ) 

283 

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) 

292 

293 # prevent a second (possibly accidental) call to save() from saving 

294 # the m2m data twice. 

295 self.m2m_data = None 

296 

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() 

323 

324 

325def build_instance(Model, data, db): 

326 """ 

327 Build a model instance. 

328 

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) 

349 

350 

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"): 

354 

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) 

364 

365 else: 

366 

367 def m2m_convert(v): 

368 return model._meta.pk.to_python(v) 

369 

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) 

384 

385 

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)