Răsfoiți Sursa

Closes #2495: Enable deep-merging of config context data

Jeremy Stretch 7 ani în urmă
părinte
comite
686a65880e
4 a modificat fișierele cu 108 adăugiri și 4 ștergeri
  1. 1 0
      CHANGELOG.md
  2. 4 4
      netbox/extras/models.py
  3. 89 0
      netbox/utilities/tests/test_utils.py
  4. 14 0
      netbox/utilities/utils.py

+ 1 - 0
CHANGELOG.md

@@ -3,6 +3,7 @@ v2.4.9 (FUTURE)
 ## Enhancements
 ## Enhancements
 
 
 * [#2089](https://github.com/digitalocean/netbox/issues/2089) - Add SONET interface form factors
 * [#2089](https://github.com/digitalocean/netbox/issues/2089) - Add SONET interface form factors
+* [#2495](https://github.com/digitalocean/netbox/issues/2495) - Enable deep-merging of config context data
 * [#2597](https://github.com/digitalocean/netbox/issues/2597) - Add FibreChannel SFP28 (32GFC) interface form factor
 * [#2597](https://github.com/digitalocean/netbox/issues/2597) - Add FibreChannel SFP28 (32GFC) interface form factor
 
 
 ## Bug Fixes
 ## Bug Fixes

+ 4 - 4
netbox/extras/models.py

@@ -18,7 +18,7 @@ from django.utils.encoding import python_2_unicode_compatible
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 
 
 from dcim.constants import CONNECTION_STATUS_CONNECTED
 from dcim.constants import CONNECTION_STATUS_CONNECTED
-from utilities.utils import foreground_color
+from utilities.utils import deepmerge, foreground_color
 from .constants import *
 from .constants import *
 from .querysets import ConfigContextQuerySet
 from .querysets import ConfigContextQuerySet
 
 
@@ -727,11 +727,11 @@ class ConfigContextModel(models.Model):
         # Compile all config data, overwriting lower-weight values with higher-weight values where a collision occurs
         # Compile all config data, overwriting lower-weight values with higher-weight values where a collision occurs
         data = OrderedDict()
         data = OrderedDict()
         for context in ConfigContext.objects.get_for_object(self):
         for context in ConfigContext.objects.get_for_object(self):
-            data.update(context.data)
+            data = deepmerge(data, context.data)
 
 
-        # If the object has local config context data defined, that data overwrites all rendered data
+        # If the object has local config context data defined, merge it last
         if self.local_context_data is not None:
         if self.local_context_data is not None:
-            data.update(self.local_context_data)
+            data = deepmerge(data, self.local_context_data)
 
 
         return data
         return data
 
 

+ 89 - 0
netbox/utilities/tests/test_utils.py

@@ -0,0 +1,89 @@
+from django.test import TestCase
+
+from utilities.utils import deepmerge
+
+
+class DeepMergeTest(TestCase):
+    """
+    Validate the behavior of the deepmerge() utility.
+    """
+
+    def setUp(self):
+        return
+
+    def test_deepmerge(self):
+
+        dict1 = {
+            'active': True,
+            'foo': 123,
+            'fruits': {
+                'orange': 1,
+                'apple': 2,
+                'pear': 3,
+            },
+            'vegetables': None,
+            'dairy': {
+                'milk': 1,
+                'cheese': 2,
+            },
+            'deepnesting': {
+                'foo': {
+                    'a': 10,
+                    'b': 20,
+                    'c': 30,
+                },
+            },
+        }
+
+        dict2 = {
+            'active': False,
+            'bar': 456,
+            'fruits': {
+                'banana': 4,
+                'grape': 5,
+            },
+            'vegetables': {
+                'celery': 1,
+                'carrots': 2,
+                'corn': 3,
+            },
+            'dairy': None,
+            'deepnesting': {
+                'foo': {
+                    'a': 100,
+                    'd': 40,
+                },
+            },
+        }
+
+        merged = {
+            'active': False,
+            'foo': 123,
+            'bar': 456,
+            'fruits': {
+                'orange': 1,
+                'apple': 2,
+                'pear': 3,
+                'banana': 4,
+                'grape': 5,
+            },
+            'vegetables': {
+                'celery': 1,
+                'carrots': 2,
+                'corn': 3,
+            },
+            'dairy': None,
+            'deepnesting': {
+                'foo': {
+                    'a': 100,
+                    'b': 20,
+                    'c': 30,
+                    'd': 40,
+                },
+            },
+        }
+
+        self.assertEqual(
+            deepmerge(dict1, dict2),
+            merged
+        )

+ 14 - 0
netbox/utilities/utils.py

@@ -1,5 +1,6 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
+from collections import OrderedDict
 import datetime
 import datetime
 import json
 import json
 import six
 import six
@@ -109,3 +110,16 @@ def serialize_object(obj, extra=None):
         data.update(extra)
         data.update(extra)
 
 
     return data
     return data
+
+
+def deepmerge(original, new):
+    """
+    Deep merge two dictionaries (new into original) and return a new dict
+    """
+    merged = OrderedDict(original)
+    for key, val in new.items():
+        if key in original and isinstance(original[key], dict) and isinstance(val, dict):
+            merged[key] = deepmerge(original[key], val)
+        else:
+            merged[key] = val
+    return merged