Add denormalized relations

This commit is contained in:
Jeremy Stretch 2024-10-07 16:22:06 -04:00
parent f28f1646b0
commit f34818df71
4 changed files with 130 additions and 7 deletions

View File

@ -1,5 +1,7 @@
from django.apps import AppConfig
from netbox import denormalized
class IPAMConfig(AppConfig):
name = "ipam"
@ -8,6 +10,16 @@ class IPAMConfig(AppConfig):
def ready(self):
from netbox.models.features import register_models
from . import signals, search # noqa: F401
from .models import Prefix
# Register models
register_models(*self.get_models())
# Register denormalized fields
denormalized.register(Prefix, '_site', {
'_region': 'region',
'_sitegroup': 'group',
})
denormalized.register(Prefix, '_location', {
'_site': 'site',
})

View File

@ -23,7 +23,7 @@ class Migration(migrations.Migration):
]
operations = [
# Add the scope GenericForeignKey
# Add the `scope` GenericForeignKey
migrations.AddField(
model_name='prefix',
name='scope_id',
@ -46,10 +46,4 @@ class Migration(migrations.Migration):
code=copy_site_assignments,
reverse_code=migrations.RunPython.noop
),
# Delete the site ForeignKey
migrations.RemoveField(
model_name='prefix',
name='site',
),
]

View File

@ -0,0 +1,61 @@
import django.db.models.deletion
from django.db import migrations, models
def populate_denormalized_fields(apps, schema_editor):
"""
Copy site ForeignKey values to the scope GFK.
"""
Prefix = apps.get_model('ipam', 'Prefix')
prefixes = Prefix.objects.filter(site__isnull=False).prefetch_related('site')
for prefix in prefixes:
prefix._region_id = prefix.site.region_id
prefix._sitegroup_id = prefix.site.group_id
prefix._site_id = prefix.site_id
# Note: Location cannot be set prior to migration
Prefix.objects.bulk_update(prefixes, ['_region', '_sitegroup', '_site'])
class Migration(migrations.Migration):
dependencies = [
('dcim', '0192_poweroutlet_color'),
('ipam', '0071_prefix_scope'),
]
operations = [
migrations.AddField(
model_name='prefix',
name='_location',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.location'),
),
migrations.AddField(
model_name='prefix',
name='_region',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.region'),
),
migrations.AddField(
model_name='prefix',
name='_site',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.site'),
),
migrations.AddField(
model_name='prefix',
name='_sitegroup',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.sitegroup'),
),
# Populate denormalized FK values
migrations.RunPython(
code=populate_denormalized_fields,
reverse_code=migrations.RunPython.noop
),
# Delete the site ForeignKey
migrations.RemoveField(
model_name='prefix',
name='site',
),
]

View File

@ -1,4 +1,5 @@
import netaddr
from django.apps import apps
from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.exceptions import ValidationError
from django.db import models
@ -270,6 +271,32 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, PrimaryModel):
help_text=_("Treat as fully utilized")
)
# Cached associations to enable efficient filtering
_location = models.ForeignKey(
to='dcim.Location',
on_delete=models.CASCADE,
blank=True,
null=True
)
_site = models.ForeignKey(
to='dcim.Site',
on_delete=models.CASCADE,
blank=True,
null=True
)
_region = models.ForeignKey(
to='dcim.Region',
on_delete=models.CASCADE,
blank=True,
null=True
)
_sitegroup = models.ForeignKey(
to='dcim.SiteGroup',
on_delete=models.CASCADE,
blank=True,
null=True
)
# Cached depth & child counts
_depth = models.PositiveSmallIntegerField(
default=0,
@ -331,8 +358,37 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, PrimaryModel):
# Clear host bits from prefix
self.prefix = self.prefix.cidr
# Cache objects associated with the terminating object (for filtering)
self.cache_related_objects()
super().save(*args, **kwargs)
def cache_related_objects(self):
if self.scope is None:
return
scope_type = self.scope_type.model_class()
if scope_type == apps.get_model('dcim', 'region'):
self._region = self.scope
self._sitegroup = None
self._site = None
self._location = None
elif scope_type == apps.get_model('dcim', 'sitegroup'):
self._region = None
self._sitegroup = self.scope
self._site = None
self._location = None
elif scope_type == apps.get_model('dcim', 'site'):
self._region = self.scope.region
self._sitegroup = self.scope.group
self._site = self.scope
self._location = None
elif scope_type == apps.get_model('dcim', 'location'):
self._region = self.scope.site.region
self._sitegroup = self.scope.site.group
self._site = self.scope.site
self._location = self.scope
cache_related_objects.alters_data = True
@property
def family(self):
return self.prefix.version if self.prefix else None