Просмотр исходного кода

Closes #20277: Add support for attribute assignment to deserialize_object() (#20281)

Jeremy Stretch 5 месяцев назад
Родитель
Сommit
9d0e80571c
2 измененных файлов с 70 добавлено и 6 удалено
  1. 21 6
      netbox/utilities/serialization.py
  2. 49 0
      netbox/utilities/tests/test_serialization.py

+ 21 - 6
netbox/utilities/serialization.py

@@ -51,30 +51,45 @@ def serialize_object(obj, resolve_tags=True, extra=None, exclude=None):
     return data
     return data
 
 
 
 
-def deserialize_object(model, fields, pk=None):
+def deserialize_object(model, data, pk=None):
     """
     """
     Instantiate an object from the given model and field data. Functions as
     Instantiate an object from the given model and field data. Functions as
     the complement to serialize_object().
     the complement to serialize_object().
     """
     """
     content_type = ContentType.objects.get_for_model(model)
     content_type = ContentType.objects.get_for_model(model)
+    data = data.copy()
     m2m_data = {}
     m2m_data = {}
 
 
     # Account for custom field data
     # Account for custom field data
-    if 'custom_fields' in fields:
-        fields['custom_field_data'] = fields.pop('custom_fields')
+    if 'custom_fields' in data:
+        data['custom_field_data'] = data.pop('custom_fields')
 
 
     # Pop any assigned tags to handle the M2M relationships manually
     # Pop any assigned tags to handle the M2M relationships manually
-    if is_taggable(model) and fields.get('tags'):
+    if is_taggable(model) and data.get('tags'):
         Tag = apps.get_model('extras', 'Tag')
         Tag = apps.get_model('extras', 'Tag')
-        m2m_data['tags'] = Tag.objects.filter(name__in=fields.pop('tags'))
+        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 = {
     data = {
         'model': '.'.join(content_type.natural_key()),
         'model': '.'.join(content_type.natural_key()),
         'pk': pk,
         'pk': pk,
-        'fields': fields,
+        'fields': data,
     }
     }
     instance = list(serializers.deserialize('python', [data]))[0]
     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
     # Apply any additional M2M assignments
     instance.m2m_data.update(**m2m_data)
     instance.m2m_data.update(**m2m_data)
 
 

+ 49 - 0
netbox/utilities/tests/test_serialization.py

@@ -0,0 +1,49 @@
+from django.test import TestCase
+
+from dcim.choices import SiteStatusChoices
+from dcim.models import Site
+from extras.models import Tag
+from utilities.serialization import deserialize_object, serialize_object
+
+
+class SerializationTestCase(TestCase):
+
+    @classmethod
+    def setUpTestData(cls):
+        tags = (
+            Tag(name='Tag 1', slug='tag-1'),
+            Tag(name='Tag 2', slug='tag-2'),
+            Tag(name='Tag 3', slug='tag-3'),
+        )
+        Tag.objects.bulk_create(tags)
+
+    def test_serialize_object(self):
+        site = Site.objects.create(
+            name='Site 1',
+            slug='site=1',
+            description='Ignore me',
+        )
+        site.tags.set(Tag.objects.all())
+
+        data = serialize_object(site, extra={'foo': 123}, exclude=['description'])
+        self.assertEqual(data['name'], site.name)
+        self.assertEqual(data['slug'], site.slug)
+        self.assertEqual(data['tags'], [tag.name for tag in Tag.objects.all()])
+        self.assertEqual(data['foo'], 123)
+        self.assertNotIn('description', data)
+
+    def test_deserialize_object(self):
+        data = {
+            'name': 'Site 1',
+            'slug': 'site-1',
+            'tags': ['Tag 1', 'Tag 2', 'Tag 3'],
+            'foo': 123,
+        }
+
+        instance = deserialize_object(Site, data, pk=123)
+        self.assertEqual(instance.object.pk, 123)
+        self.assertEqual(instance.object.name, data['name'])
+        self.assertEqual(instance.object.slug, data['slug'])
+        self.assertEqual(instance.object.status, SiteStatusChoices.STATUS_ACTIVE)  # Default field value
+        self.assertEqual(instance.object.foo, data['foo'])  # Non-field attribute
+        self.assertEqual(list(instance.m2m_data['tags']), list(Tag.objects.all()))