Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

base.py 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. """
  2. Module for abstract serializer/unserializer base classes.
  3. """
  4. from io import StringIO
  5. from django.db import models
  6. class SerializerDoesNotExist(KeyError):
  7. """The requested serializer was not found."""
  8. pass
  9. class SerializationError(Exception):
  10. """Something bad happened during serialization."""
  11. pass
  12. class DeserializationError(Exception):
  13. """Something bad happened during deserialization."""
  14. @classmethod
  15. def WithData(cls, original_exc, model, fk, field_value):
  16. """
  17. Factory method for creating a deserialization error which has a more
  18. explanatory message.
  19. """
  20. return cls("%s: (%s:pk=%s) field_value was '%s'" % (original_exc, model, fk, field_value))
  21. class M2MDeserializationError(Exception):
  22. """Something bad happened during deserialization of a ManyToManyField."""
  23. def __init__(self, original_exc, pk):
  24. self.original_exc = original_exc
  25. self.pk = pk
  26. class ProgressBar:
  27. progress_width = 75
  28. def __init__(self, output, total_count):
  29. self.output = output
  30. self.total_count = total_count
  31. self.prev_done = 0
  32. def update(self, count):
  33. if not self.output:
  34. return
  35. perc = count * 100 // self.total_count
  36. done = perc * self.progress_width // 100
  37. if self.prev_done >= done:
  38. return
  39. self.prev_done = done
  40. cr = '' if self.total_count == 1 else '\r'
  41. self.output.write(cr + '[' + '.' * done + ' ' * (self.progress_width - done) + ']')
  42. if done == self.progress_width:
  43. self.output.write('\n')
  44. self.output.flush()
  45. class Serializer:
  46. """
  47. Abstract serializer base class.
  48. """
  49. # Indicates if the implemented serializer is only available for
  50. # internal Django use.
  51. internal_use_only = False
  52. progress_class = ProgressBar
  53. stream_class = StringIO
  54. def serialize(self, queryset, *, stream=None, fields=None, use_natural_foreign_keys=False,
  55. use_natural_primary_keys=False, progress_output=None, object_count=0, **options):
  56. """
  57. Serialize a queryset.
  58. """
  59. self.options = options
  60. self.stream = stream if stream is not None else self.stream_class()
  61. self.selected_fields = fields
  62. self.use_natural_foreign_keys = use_natural_foreign_keys
  63. self.use_natural_primary_keys = use_natural_primary_keys
  64. progress_bar = self.progress_class(progress_output, object_count)
  65. self.start_serialization()
  66. self.first = True
  67. for count, obj in enumerate(queryset, start=1):
  68. self.start_object(obj)
  69. # Use the concrete parent class' _meta instead of the object's _meta
  70. # This is to avoid local_fields problems for proxy models. Refs #17717.
  71. concrete_model = obj._meta.concrete_model
  72. # When using natural primary keys, retrieve the pk field of the
  73. # parent for multi-table inheritance child models. That field must
  74. # be serialized, otherwise deserialization isn't possible.
  75. if self.use_natural_primary_keys:
  76. pk = concrete_model._meta.pk
  77. pk_parent = pk if pk.remote_field and pk.remote_field.parent_link else None
  78. else:
  79. pk_parent = None
  80. for field in concrete_model._meta.local_fields:
  81. if field.serialize or field is pk_parent:
  82. if field.remote_field is None:
  83. if self.selected_fields is None or field.attname in self.selected_fields:
  84. self.handle_field(obj, field)
  85. else:
  86. if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
  87. self.handle_fk_field(obj, field)
  88. for field in concrete_model._meta.many_to_many:
  89. if field.serialize:
  90. if self.selected_fields is None or field.attname in self.selected_fields:
  91. self.handle_m2m_field(obj, field)
  92. self.end_object(obj)
  93. progress_bar.update(count)
  94. self.first = self.first and False
  95. self.end_serialization()
  96. return self.getvalue()
  97. def start_serialization(self):
  98. """
  99. Called when serializing of the queryset starts.
  100. """
  101. raise NotImplementedError('subclasses of Serializer must provide a start_serialization() method')
  102. def end_serialization(self):
  103. """
  104. Called when serializing of the queryset ends.
  105. """
  106. pass
  107. def start_object(self, obj):
  108. """
  109. Called when serializing of an object starts.
  110. """
  111. raise NotImplementedError('subclasses of Serializer must provide a start_object() method')
  112. def end_object(self, obj):
  113. """
  114. Called when serializing of an object ends.
  115. """
  116. pass
  117. def handle_field(self, obj, field):
  118. """
  119. Called to handle each individual (non-relational) field on an object.
  120. """
  121. raise NotImplementedError('subclasses of Serializer must provide an handle_field() method')
  122. def handle_fk_field(self, obj, field):
  123. """
  124. Called to handle a ForeignKey field.
  125. """
  126. raise NotImplementedError('subclasses of Serializer must provide an handle_fk_field() method')
  127. def handle_m2m_field(self, obj, field):
  128. """
  129. Called to handle a ManyToManyField.
  130. """
  131. raise NotImplementedError('subclasses of Serializer must provide an handle_m2m_field() method')
  132. def getvalue(self):
  133. """
  134. Return the fully serialized queryset (or None if the output stream is
  135. not seekable).
  136. """
  137. if callable(getattr(self.stream, 'getvalue', None)):
  138. return self.stream.getvalue()
  139. class Deserializer:
  140. """
  141. Abstract base deserializer class.
  142. """
  143. def __init__(self, stream_or_string, **options):
  144. """
  145. Init this serializer given a stream or a string
  146. """
  147. self.options = options
  148. if isinstance(stream_or_string, str):
  149. self.stream = StringIO(stream_or_string)
  150. else:
  151. self.stream = stream_or_string
  152. def __iter__(self):
  153. return self
  154. def __next__(self):
  155. """Iteration interface -- return the next item in the stream"""
  156. raise NotImplementedError('subclasses of Deserializer must provide a __next__() method')
  157. class DeserializedObject:
  158. """
  159. A deserialized model.
  160. Basically a container for holding the pre-saved deserialized data along
  161. with the many-to-many data saved with the object.
  162. Call ``save()`` to save the object (with the many-to-many data) to the
  163. database; call ``save(save_m2m=False)`` to save just the object fields
  164. (and not touch the many-to-many stuff.)
  165. """
  166. def __init__(self, obj, m2m_data=None):
  167. self.object = obj
  168. self.m2m_data = m2m_data
  169. def __repr__(self):
  170. return "<%s: %s(pk=%s)>" % (
  171. self.__class__.__name__,
  172. self.object._meta.label,
  173. self.object.pk,
  174. )
  175. def save(self, save_m2m=True, using=None, **kwargs):
  176. # Call save on the Model baseclass directly. This bypasses any
  177. # model-defined save. The save is also forced to be raw.
  178. # raw=True is passed to any pre/post_save signals.
  179. models.Model.save_base(self.object, using=using, raw=True, **kwargs)
  180. if self.m2m_data and save_m2m:
  181. for accessor_name, object_list in self.m2m_data.items():
  182. getattr(self.object, accessor_name).set(object_list)
  183. # prevent a second (possibly accidental) call to save() from saving
  184. # the m2m data twice.
  185. self.m2m_data = None
  186. def build_instance(Model, data, db):
  187. """
  188. Build a model instance.
  189. If the model instance doesn't have a primary key and the model supports
  190. natural keys, try to retrieve it from the database.
  191. """
  192. obj = Model(**data)
  193. if (obj.pk is None and hasattr(Model, 'natural_key') and
  194. hasattr(Model._default_manager, 'get_by_natural_key')):
  195. natural_key = obj.natural_key()
  196. try:
  197. obj.pk = Model._default_manager.db_manager(db).get_by_natural_key(*natural_key).pk
  198. except Model.DoesNotExist:
  199. pass
  200. return obj
  201. def deserialize_m2m_values(field, field_value, using):
  202. model = field.remote_field.model
  203. if hasattr(model._default_manager, 'get_by_natural_key'):
  204. def m2m_convert(value):
  205. if hasattr(value, '__iter__') and not isinstance(value, str):
  206. return model._default_manager.db_manager(using).get_by_natural_key(*value).pk
  207. else:
  208. return model._meta.pk.to_python(value)
  209. else:
  210. def m2m_convert(v):
  211. return model._meta.pk.to_python(v)
  212. try:
  213. values = []
  214. for pk in field_value:
  215. values.append(m2m_convert(pk))
  216. return values
  217. except Exception as e:
  218. raise M2MDeserializationError(e, pk)
  219. def deserialize_fk_value(field, field_value, using):
  220. if field_value is None:
  221. return None
  222. model = field.remote_field.model
  223. default_manager = model._default_manager
  224. field_name = field.remote_field.field_name
  225. if (hasattr(default_manager, 'get_by_natural_key') and
  226. hasattr(field_value, '__iter__') and not isinstance(field_value, str)):
  227. obj = default_manager.db_manager(using).get_by_natural_key(*field_value)
  228. value = getattr(obj, field_name)
  229. # If this is a natural foreign key to an object that has a FK/O2O as
  230. # the foreign key, use the FK value.
  231. if model._meta.pk.remote_field:
  232. value = value.pk
  233. return value
  234. return model._meta.get_field(field_name).to_python(field_value)