Cache attributes of related objects that are used for q filtering #543

Closed
opened 2026-04-05 16:41:46 +02:00 by MrUnknownDE · 0 comments
Owner

Originally created by @jeremystretch on 1/16/2026

NetBox version

v4.5.0

Feature type

New functionality

Proposed functionality

There are many instances throughout NetBox where the general-purpose q filter for a model references attributes on a related object. For example, the q filter for Device references the name of the assigned virtual chassis (if any) and the primary IPv4 and IPv6 addresses:

def search(self, queryset, name, value):
    if not value.strip():
        return queryset
    return queryset.filter(
        Q(name__icontains=value) |
        Q(virtual_chassis__name__icontains=value) |  # Requires a JOIN
        Q(serial__icontains=value.strip()) |
        Q(asset_tag__icontains=value.strip()) |
        Q(description__icontains=value.strip()) |
        Q(comments__icontains=value) |
        Q(primary_ip4__address__startswith=value) |  # Requires a JOIN
        Q(primary_ip6__address__startswith=value)    # Requires a JOIN
    ).distinct()

Instead of referencing these fields on related objects (which requires SQL JOINs`), we can copy the values of these fields locally on the model for efficient reference. The existing denormalization framework can be leveraged to accomplish this:

denormalized.register(Device, 'virtual_chassis', {
    '_virtual_chassis_name': 'name',
})
denormalized.register(Device, 'primary_ip4', {
    '_primary_ip4_address': 'address',
})
denormalized.register(Device, 'primary_ip6', {
    '_primary_ip6_address': 'address',
})

Use case

Each of these evaluations requires a SQL JOIN, which can significantly degrade performance in large databases as captured in bug #21196. Limiting the query to the primary table will greatly improve filtering performance.

Database changes

Add private IP address fields on the Device model to store the denormalized values:

    # Denormalized fields
    _virtual_chassis_name = models.CharField(
        blank=True,
    )
    _primary_ip4_address = IPAddressField(
        blank=True,
        null=True,
    )
    _primary_ip6_address = IPAddressField(
        blank=True,
        null=True,
    )

A migration is needed to add these fields on the model and to backfill existing IP address assignments.

External dependencies

N/A

*Originally created by @jeremystretch on 1/16/2026* ### NetBox version v4.5.0 ### Feature type New functionality ### Proposed functionality There are many instances throughout NetBox where the general-purpose `q` filter for a model references attributes on a related object. For example, the `q` filter for Device references the name of the assigned virtual chassis (if any) and the primary IPv4 and IPv6 addresses: ```python def search(self, queryset, name, value): if not value.strip(): return queryset return queryset.filter( Q(name__icontains=value) | Q(virtual_chassis__name__icontains=value) | # Requires a JOIN Q(serial__icontains=value.strip()) | Q(asset_tag__icontains=value.strip()) | Q(description__icontains=value.strip()) | Q(comments__icontains=value) | Q(primary_ip4__address__startswith=value) | # Requires a JOIN Q(primary_ip6__address__startswith=value) # Requires a JOIN ).distinct() ``` Instead of referencing these fields on related objects (which requires SQL `JOIN`s`), we can copy the values of these fields locally on the model for efficient reference. The existing denormalization framework can be leveraged to accomplish this: ```python denormalized.register(Device, 'virtual_chassis', { '_virtual_chassis_name': 'name', }) denormalized.register(Device, 'primary_ip4', { '_primary_ip4_address': 'address', }) denormalized.register(Device, 'primary_ip6', { '_primary_ip6_address': 'address', }) ``` ### Use case Each of these evaluations requires a SQL `JOIN`, which can significantly degrade performance in large databases as captured in bug #21196. Limiting the query to the primary table will greatly improve filtering performance. ### Database changes Add private IP address fields on the Device model to store the denormalized values: ```python # Denormalized fields _virtual_chassis_name = models.CharField( blank=True, ) _primary_ip4_address = IPAddressField( blank=True, null=True, ) _primary_ip6_address = IPAddressField( blank=True, null=True, ) ``` A migration is needed to add these fields on the model and to backfill existing IP address assignments. ### External dependencies N/A
MrUnknownDE added the netboxnetboxstatus: acceptedcomplexity: mediumnetboxtype: featurenetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxnetboxstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedstatus: acceptedcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumcomplexity: mediumtype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: featuretype: feature labels 2026-04-05 16:42:02 +02:00
Sign in to join this conversation.
No Label complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium complexity: medium netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox netbox status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted status: accepted type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature type: feature
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/netbox#543