Move serialize_object() & deserialize_object() to utilities.serialization

This commit is contained in:
Jeremy Stretch 2024-03-21 13:05:21 -04:00
parent bbb8b7d010
commit b92d3245c8
6 changed files with 80 additions and 76 deletions

View File

@ -1,9 +1,6 @@
import logging
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
from django.utils.module_loading import import_string
from django.utils.translation import gettext as _
@ -15,9 +12,9 @@ from netbox.constants import RQ_QUEUE_DEFAULT
from netbox.registry import registry
from utilities.api import get_serializer_for_model
from utilities.rqworker import get_rq_retry
from utilities.utils import serialize_object
from utilities.serialization import serialize_object
from .choices import *
from .models import EventRule, ScriptModule
from .models import EventRule
logger = logging.getLogger('netbox.events_processor')

View File

@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _
from extras.choices import ChangeActionChoices
from netbox.models import ChangeLoggedModel
from netbox.models.features import *
from utilities.utils import deserialize_object
from utilities.serialization import deserialize_object
__all__ = (
'Branch',

View File

@ -17,7 +17,7 @@ from netbox.config import get_config
from netbox.registry import registry
from netbox.signals import post_clean
from utilities.json import CustomFieldJSONEncoder
from utilities.utils import serialize_object
from utilities.serialization import serialize_object
from utilities.views import register_model_view
__all__ = (

View File

@ -6,7 +6,7 @@ from django.db.models.signals import m2m_changed, pre_delete, post_save
from extras.choices import ChangeActionChoices
from extras.models import StagedChange
from utilities.utils import serialize_object
from utilities.serialization import serialize_object
logger = logging.getLogger('netbox.staging')

View File

@ -0,0 +1,75 @@
import json
from django.contrib.contenttypes.models import ContentType
from django.core import serializers
from mptt.models import MPTTModel
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. Private fields (prefaced with an underscore) are
implicitly excluded.
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 []
# Exclude any MPTTModel fields
if issubclass(obj.__class__, MPTTModel):
for field in ['level', 'lft', 'rght', 'tree_id']:
data.pop(field)
# Include custom_field_data as "custom_fields"
if hasattr(obj, 'custom_field_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 excluded and private (prefixes with an underscore) attributes
for key in list(data.keys()):
if key in exclude or (isinstance(key, str) and key.startswith('_')):
data.pop(key)
# Append any extra data
if extra is not None:
data.update(extra)
return data
def deserialize_object(model, fields, 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)
if 'custom_fields' in fields:
fields['custom_field_data'] = fields.pop('custom_fields')
data = {
'model': '.'.join(content_type.natural_key()),
'pk': pk,
'fields': fields,
}
instance = list(serializers.deserialize('python', [data]))[0]
return instance

View File

@ -1,12 +1,9 @@
import datetime
import decimal
import json
from itertools import count, groupby
from urllib.parse import urlencode
import nh3
from django.contrib.contenttypes.models import ContentType
from django.core import serializers
from django.db.models import Count, ManyToOneRel, OuterRef, Subquery
from django.db.models.functions import Coalesce
from django.http import QueryDict
@ -14,9 +11,7 @@ from django.utils import timezone
from django.utils.datastructures import MultiValueDict
from django.utils.timezone import localtime
from jinja2.sandbox import SandboxedEnvironment
from mptt.models import MPTTModel
from extras.utils import is_taggable
from netbox.config import get_config
from .constants import HTML_ALLOWED_ATTRIBUTES, HTML_ALLOWED_TAGS
from .string import title
@ -96,69 +91,6 @@ def count_related(model, field):
return Coalesce(subquery, 0)
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. Private fields (prefaced with an underscore) are
implicitly excluded.
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 []
# Exclude any MPTTModel fields
if issubclass(obj.__class__, MPTTModel):
for field in ['level', 'lft', 'rght', 'tree_id']:
data.pop(field)
# Include custom_field_data as "custom_fields"
if hasattr(obj, 'custom_field_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 excluded and private (prefixes with an underscore) attributes
for key in list(data.keys()):
if key in exclude or (isinstance(key, str) and key.startswith('_')):
data.pop(key)
# Append any extra data
if extra is not None:
data.update(extra)
return data
def deserialize_object(model, fields, 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)
if 'custom_fields' in fields:
fields['custom_field_data'] = fields.pop('custom_fields')
data = {
'model': '.'.join(content_type.natural_key()),
'pk': pk,
'fields': fields,
}
instance = list(serializers.deserialize('python', [data]))[0]
return instance
def dict_to_filter_params(d, prefix=''):
"""
Translate a dictionary of attributes to a nested set of parameters suitable for QuerySet filtering. For example: