Przeglądaj źródła

Closes #9903: Implement a mechanism for automatically updating denormalized fields

jeremystretch 3 lat temu
rodzic
commit
e96620260a

+ 1 - 0
docs/release-notes/version-3.3.md

@@ -124,6 +124,7 @@ Custom field UI visibility has no impact on API operation.
 
 * [#9261](https://github.com/netbox-community/netbox/issues/9261) - `NetBoxTable` no longer automatically clears pre-existing calls to `prefetch_related()` on its queryset
 * [#9434](https://github.com/netbox-community/netbox/issues/9434) - Enabled `django-rich` test runner for more user-friendly output
+* [#9903](https://github.com/netbox-community/netbox/issues/9903) - Implement a mechanism for automatically updating denormalized fields
 
 ### REST API Changes
 

+ 1 - 0
netbox/extras/registry.py

@@ -28,3 +28,4 @@ registry = Registry()
 registry['model_features'] = {
     feature: collections.defaultdict(set) for feature in EXTRAS_FEATURES
 }
+registry['denormalized_fields'] = collections.defaultdict(list)

+ 54 - 0
netbox/netbox/denormalized.py

@@ -0,0 +1,54 @@
+import logging
+
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+
+from extras.registry import registry
+
+
+logger = logging.getLogger('netbox.denormalized')
+
+
+def register(model, field_name, mappings):
+    """
+    Register a denormalized model field to ensure that it is kept up-to-date with the related object.
+
+    Args:
+        model: The class being updated
+        field_name: The name of the field related to the triggering instance
+        mappings: Dictionary mapping of local to remote fields
+    """
+    logger.debug(f'Registering denormalized field {model}.{field_name}')
+
+    field = model._meta.get_field(field_name)
+    rel_model = field.related_model
+
+    registry['denormalized_fields'][rel_model].append(
+        (model, field_name, mappings)
+    )
+
+
+@receiver(post_save)
+def update_denormalized_fields(sender, instance, created, raw, **kwargs):
+    """
+    Check if the sender has denormalized fields registered, and update them as necessary.
+    """
+    # Skip for new objects or those being populated from raw data
+    if created or raw:
+        return
+
+    # Look up any denormalized fields referencing this model from the application registry
+    for model, field_name, mappings in registry['denormalized_fields'].get(sender, []):
+        logger.debug(f'Updating denormalized values for {model}.{field_name}')
+        filter_params = {
+            field_name: instance.pk,
+        }
+        update_params = {
+            # Map the denormalized field names to the instance's values
+            denorm: getattr(instance, origin) for denorm, origin in mappings.items()
+        }
+
+        # TODO: Improve efficiency here by placing conditions on the query?
+        # Update all the denormalized fields with the triggering object's new values
+        count = model.objects.filter(**filter_params).update(**update_params)
+        logger.debug(f'Updated {count} rows')