| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- import json
- from django.apps import apps
- from django.contrib.contenttypes.models import ContentType
- from django.core import serializers
- from extras.utils import is_taggable
- __all__ = (
- 'deserialize_object',
- 'serialize_object',
- )
- def serialize_object(obj, resolve_tags=True, extra=None, exclude=None):
- """
- Return a generic JSON representation of an object using Django's built-in serializer. (This is used for things like
- change logging, not the REST API.) Optionally include a dictionary to supplement the object data. A list of keys
- can be provided to exclude them from the returned dictionary.
- Args:
- obj: The object to serialize
- resolve_tags: If true, any assigned tags will be represented by their names
- extra: Any additional data to include in the serialized output. Keys provided in this mapping will
- override object attributes.
- exclude: An iterable of attributes to exclude from the serialized output
- """
- json_str = serializers.serialize('json', [obj])
- data = json.loads(json_str)[0]['fields']
- exclude = exclude or []
- # Include custom_field_data as "custom_fields"
- if 'custom_field_data' in data:
- data['custom_fields'] = data.pop('custom_field_data')
- # Resolve any assigned tags to their names. Check for tags cached on the instance;
- # fall back to using the manager.
- if resolve_tags and is_taggable(obj):
- tags = getattr(obj, '_tags', None) or obj.tags.all()
- data['tags'] = sorted([tag.name for tag in tags])
- # Skip any excluded attributes
- for key in list(data.keys()):
- if key in exclude:
- data.pop(key)
- # Append any extra data
- if extra is not None:
- data.update(extra)
- return data
- def deserialize_object(model, data, pk=None):
- """
- Instantiate an object from the given model and field data. Functions as
- the complement to serialize_object().
- """
- content_type = ContentType.objects.get_for_model(model)
- data = data.copy()
- m2m_data = {}
- # Account for custom field data
- if 'custom_fields' in data:
- data['custom_field_data'] = data.pop('custom_fields')
- # Pop any assigned tags to handle the M2M relationships manually
- if is_taggable(model) and data.get('tags'):
- Tag = apps.get_model('extras', 'Tag')
- m2m_data['tags'] = Tag.objects.filter(name__in=data.pop('tags'))
- # Separate any non-field attributes for assignment after deserialization of the object
- model_fields = [
- field.name for field in model._meta.get_fields()
- ]
- attrs = {
- name: data.pop(name) for name in list(data.keys())
- if name not in model_fields
- }
- # Employ Django's native Python deserializer to produce the instance
- data = {
- 'model': '.'.join(content_type.natural_key()),
- 'pk': pk,
- 'fields': data,
- }
- instance = list(serializers.deserialize('python', [data]))[0]
- # Assign non-field attributes
- for name, value in attrs.items():
- setattr(instance.object, name, value)
- # Apply any additional M2M assignments
- instance.m2m_data.update(**m2m_data)
- return instance
|