serialization.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import json
  2. from django.apps import apps
  3. from django.contrib.contenttypes.models import ContentType
  4. from django.core import serializers
  5. from extras.utils import is_taggable
  6. __all__ = (
  7. 'deserialize_object',
  8. 'serialize_object',
  9. )
  10. def serialize_object(obj, resolve_tags=True, extra=None, exclude=None):
  11. """
  12. Return a generic JSON representation of an object using Django's built-in serializer. (This is used for things like
  13. change logging, not the REST API.) Optionally include a dictionary to supplement the object data. A list of keys
  14. can be provided to exclude them from the returned dictionary.
  15. Args:
  16. obj: The object to serialize
  17. resolve_tags: If true, any assigned tags will be represented by their names
  18. extra: Any additional data to include in the serialized output. Keys provided in this mapping will
  19. override object attributes.
  20. exclude: An iterable of attributes to exclude from the serialized output
  21. """
  22. json_str = serializers.serialize('json', [obj])
  23. data = json.loads(json_str)[0]['fields']
  24. exclude = exclude or []
  25. # Include custom_field_data as "custom_fields"
  26. if 'custom_field_data' in data:
  27. data['custom_fields'] = data.pop('custom_field_data')
  28. # Resolve any assigned tags to their names. Check for tags cached on the instance;
  29. # fall back to using the manager.
  30. if resolve_tags and is_taggable(obj):
  31. tags = getattr(obj, '_tags', None) or obj.tags.all()
  32. data['tags'] = sorted([tag.name for tag in tags])
  33. # Skip any excluded attributes
  34. for key in list(data.keys()):
  35. if key in exclude:
  36. data.pop(key)
  37. # Append any extra data
  38. if extra is not None:
  39. data.update(extra)
  40. return data
  41. def deserialize_object(model, data, pk=None):
  42. """
  43. Instantiate an object from the given model and field data. Functions as
  44. the complement to serialize_object().
  45. """
  46. content_type = ContentType.objects.get_for_model(model)
  47. data = data.copy()
  48. m2m_data = {}
  49. # Account for custom field data
  50. if 'custom_fields' in data:
  51. data['custom_field_data'] = data.pop('custom_fields')
  52. # Pop any assigned tags to handle the M2M relationships manually
  53. if is_taggable(model) and data.get('tags'):
  54. Tag = apps.get_model('extras', 'Tag')
  55. m2m_data['tags'] = Tag.objects.filter(name__in=data.pop('tags'))
  56. # Separate any non-field attributes for assignment after deserialization of the object
  57. model_fields = [
  58. field.name for field in model._meta.get_fields()
  59. ]
  60. attrs = {
  61. name: data.pop(name) for name in list(data.keys())
  62. if name not in model_fields
  63. }
  64. # Employ Django's native Python deserializer to produce the instance
  65. data = {
  66. 'model': '.'.join(content_type.natural_key()),
  67. 'pk': pk,
  68. 'fields': data,
  69. }
  70. instance = list(serializers.deserialize('python', [data]))[0]
  71. # Assign non-field attributes
  72. for name, value in attrs.items():
  73. setattr(instance.object, name, value)
  74. # Apply any additional M2M assignments
  75. instance.m2m_data.update(**m2m_data)
  76. return instance