diff --git a/netbox/ipam/filtersets.py b/netbox/ipam/filtersets.py
index d618c8eab..bca3f08c9 100644
--- a/netbox/ipam/filtersets.py
+++ b/netbox/ipam/filtersets.py
@@ -209,6 +209,12 @@ class PrefixFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
         method='search_contains',
         label='Prefixes which contain this prefix or IP',
     )
+    depth = django_filters.NumberFilter(
+        field_name='_depth'
+    )
+    children = django_filters.NumberFilter(
+        field_name='_children'
+    )
     mask_length = django_filters.NumberFilter(
         field_name='prefix',
         lookup_expr='net_mask_length'
diff --git a/netbox/ipam/migrations/0047_prefix_depth_children.py b/netbox/ipam/migrations/0047_prefix_depth_children.py
new file mode 100644
index 000000000..4c49b1358
--- /dev/null
+++ b/netbox/ipam/migrations/0047_prefix_depth_children.py
@@ -0,0 +1,21 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ipam', '0046_set_vlangroup_scope_types'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='prefix',
+            name='_children',
+            field=models.PositiveBigIntegerField(default=0, editable=False),
+        ),
+        migrations.AddField(
+            model_name='prefix',
+            name='_depth',
+            field=models.PositiveSmallIntegerField(default=0, editable=False),
+        ),
+    ]
diff --git a/netbox/ipam/migrations/0048_prefix_populate_depth_children.py b/netbox/ipam/migrations/0048_prefix_populate_depth_children.py
new file mode 100644
index 000000000..b265e7f6f
--- /dev/null
+++ b/netbox/ipam/migrations/0048_prefix_populate_depth_children.py
@@ -0,0 +1,86 @@
+from django.db import migrations
+
+
+def push_to_stack(stack, prefix):
+    # Increment child count on parent nodes
+    for n in stack:
+        n['children'] += 1
+    stack.append({
+        'pk': prefix['pk'],
+        'prefix': prefix['prefix'],
+        'children': 0,
+    })
+
+
+def populate_prefix_hierarchy(apps, schema_editor):
+    """
+    Populate _depth and _children attrs for all Prefixes.
+    """
+    Prefix = apps.get_model('ipam', 'Prefix')
+    VRF = apps.get_model('ipam', 'VRF')
+
+    total_count = Prefix.objects.count()
+    print(f'\nUpdating {total_count} prefixes...')
+
+    # Iterate through all VRFs and the global table
+    vrfs = [None] + list(VRF.objects.values_list('pk', flat=True))
+    for vrf in vrfs:
+
+        stack = []
+        update_queue = []
+
+        # Iterate through all Prefixes in the VRF, growing and shrinking the stack as we go
+        prefixes = Prefix.objects.filter(vrf=vrf).values('pk', 'prefix')
+        for i, p in enumerate(prefixes):
+
+            # Grow the stack if this is a child of the most recent prefix
+            if not stack or p['prefix'] in stack[-1]['prefix']:
+                push_to_stack(stack, p)
+
+            # If this is a sibling or parent of the most recent prefix, pop nodes from the
+            # stack until we reach a parent prefix (or the root)
+            else:
+                while stack and p['prefix'] not in stack[-1]['prefix'] and p['prefix'] != stack[-1]['prefix']:
+                    node = stack.pop()
+                    update_queue.append(
+                        Prefix(
+                            pk=node['pk'],
+                            _depth=len(stack),
+                            _children=node['children']
+                        )
+                    )
+                push_to_stack(stack, p)
+
+                # Flush the update queue once it reaches 100 Prefixes
+                if len(update_queue) >= 100:
+                    Prefix.objects.bulk_update(update_queue, ['_depth', '_children'])
+                    update_queue = []
+                    print(f'  [{i}/{total_count}]')
+
+        # Clear out any prefixes remaining in the stack
+        while stack:
+            node = stack.pop()
+            update_queue.append(
+                Prefix(
+                    pk=node['pk'],
+                    _depth=len(stack),
+                    _children=node['children']
+                )
+            )
+
+        # Final flush of any remaining Prefixes
+        Prefix.objects.bulk_update(update_queue, ['_depth', '_children'])
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ipam', '0047_prefix_depth_children'),
+    ]
+
+    operations = [
+        migrations.RunPython(
+            code=populate_prefix_hierarchy,
+            reverse_code=migrations.RunPython.noop
+        ),
+    ]
diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py
index 7df84c98b..c6c8cf74c 100644
--- a/netbox/ipam/models/ip.py
+++ b/netbox/ipam/models/ip.py
@@ -293,6 +293,16 @@ class Prefix(PrimaryModel):
         blank=True
     )
 
+    # Cached depth & child counts
+    _depth = models.PositiveSmallIntegerField(
+        default=0,
+        editable=False
+    )
+    _children = models.PositiveBigIntegerField(
+        default=0,
+        editable=False
+    )
+
     objects = PrefixQuerySet.as_manager()
 
     csv_headers = [
@@ -306,6 +316,13 @@ class Prefix(PrimaryModel):
         ordering = (F('vrf').asc(nulls_first=True), 'prefix', 'pk')  # (vrf, prefix) may be non-unique
         verbose_name_plural = 'prefixes'
 
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+        # Cache the original prefix and VRF so we can check if they have changed on post_save
+        self._prefix = self.prefix
+        self._vrf = self.vrf
+
     def __str__(self):
         return str(self.prefix)
 
@@ -373,6 +390,14 @@ class Prefix(PrimaryModel):
             return self.prefix.version
         return None
 
+    @property
+    def depth(self):
+        return self._depth
+
+    @property
+    def children(self):
+        return self._children
+
     def _set_prefix_length(self, value):
         """
         Expose the IPNetwork object's prefixlen attribute on the parent model so that it can be manipulated directly,
@@ -385,6 +410,26 @@ class Prefix(PrimaryModel):
     def get_status_class(self):
         return PrefixStatusChoices.CSS_CLASSES.get(self.status)
 
+    def get_parents(self, include_self=False):
+        """
+        Return all containing Prefixes in the hierarchy.
+        """
+        lookup = 'net_contains_or_equals' if include_self else 'net_contains'
+        return Prefix.objects.filter(**{
+            'vrf': self.vrf,
+            f'prefix__{lookup}': self.prefix
+        })
+
+    def get_children(self, include_self=False):
+        """
+        Return all covered Prefixes in the hierarchy.
+        """
+        lookup = 'net_contained_or_equal' if include_self else 'net_contained'
+        return Prefix.objects.filter(**{
+            'vrf': self.vrf,
+            f'prefix__{lookup}': self.prefix
+        })
+
     def get_duplicates(self):
         return Prefix.objects.filter(vrf=self.vrf, prefix=str(self.prefix)).exclude(pk=self.pk)
 
diff --git a/netbox/ipam/querysets.py b/netbox/ipam/querysets.py
index 784d58342..7edac2eff 100644
--- a/netbox/ipam/querysets.py
+++ b/netbox/ipam/querysets.py
@@ -1,27 +1,32 @@
 from django.contrib.contenttypes.models import ContentType
 from django.db.models import Q
+from django.db.models.expressions import RawSQL
 
 from utilities.querysets import RestrictedQuerySet
 
 
 class PrefixQuerySet(RestrictedQuerySet):
 
-    def annotate_tree(self):
+    def annotate_hierarchy(self):
         """
-        Annotate the number of parent and child prefixes for each Prefix. Raw SQL is needed for these subqueries
-        because we need to cast NULL VRF values to integers for comparison. (NULL != NULL).
+        Annotate the depth and number of child prefixes for each Prefix. Cast null VRF values to zero for
+        comparison. (NULL != NULL).
         """
-        return self.extra(
-            select={
-                'parents': 'SELECT COUNT(U0."prefix") AS "c" '
-                           'FROM "ipam_prefix" U0 '
-                           'WHERE (U0."prefix" >> "ipam_prefix"."prefix" '
-                           'AND COALESCE(U0."vrf_id", 0) = COALESCE("ipam_prefix"."vrf_id", 0))',
-                'children': 'SELECT COUNT(U1."prefix") AS "c" '
-                            'FROM "ipam_prefix" U1 '
-                            'WHERE (U1."prefix" << "ipam_prefix"."prefix" '
-                            'AND COALESCE(U1."vrf_id", 0) = COALESCE("ipam_prefix"."vrf_id", 0))',
-            }
+        return self.annotate(
+            hierarchy_depth=RawSQL(
+                'SELECT COUNT(DISTINCT U0."prefix") AS "c" '
+                'FROM "ipam_prefix" U0 '
+                'WHERE (U0."prefix" >> "ipam_prefix"."prefix" '
+                'AND COALESCE(U0."vrf_id", 0) = COALESCE("ipam_prefix"."vrf_id", 0))',
+                ()
+            ),
+            hierarchy_children=RawSQL(
+                'SELECT COUNT(U1."prefix") AS "c" '
+                'FROM "ipam_prefix" U1 '
+                'WHERE (U1."prefix" << "ipam_prefix"."prefix" '
+                'AND COALESCE(U1."vrf_id", 0) = COALESCE("ipam_prefix"."vrf_id", 0))',
+                ()
+            )
         )
 
 
diff --git a/netbox/ipam/signals.py b/netbox/ipam/signals.py
index a8fce8310..f8673b10e 100644
--- a/netbox/ipam/signals.py
+++ b/netbox/ipam/signals.py
@@ -1,9 +1,52 @@
-from django.db.models.signals import pre_delete
+from django.db.models.signals import post_delete, post_save, pre_delete
 from django.dispatch import receiver
 
 from dcim.models import Device
 from virtualization.models import VirtualMachine
-from .models import IPAddress
+from .models import IPAddress, Prefix
+
+
+def update_parents_children(prefix):
+    """
+    Update depth on prefix & containing prefixes
+    """
+    parents = prefix.get_parents(include_self=True).annotate_hierarchy()
+    for parent in parents:
+        parent._children = parent.hierarchy_children
+    Prefix.objects.bulk_update(parents, ['_children'])
+
+
+def update_children_depth(prefix):
+    """
+    Update children count on prefix & contained prefixes
+    """
+    children = prefix.get_children(include_self=True).annotate_hierarchy()
+    for child in children:
+        child._depth = child.hierarchy_depth
+    Prefix.objects.bulk_update(children, ['_depth'])
+
+
+@receiver(post_save, sender=Prefix)
+def handle_prefix_saved(instance, created, **kwargs):
+
+    # Prefix has changed (or new instance has been created)
+    if created or instance.vrf != instance._vrf or instance.prefix != instance._prefix:
+
+        update_parents_children(instance)
+        update_children_depth(instance)
+
+        # If this is not a new prefix, clean up parent/children of previous prefix
+        if not created:
+            old_prefix = Prefix(vrf=instance._vrf, prefix=instance._prefix)
+            update_parents_children(old_prefix)
+            update_children_depth(old_prefix)
+
+
+@receiver(post_delete, sender=Prefix)
+def handle_prefix_deleted(instance, **kwargs):
+
+    update_parents_children(instance)
+    update_children_depth(instance)
 
 
 @receiver(pre_delete, sender=IPAddress)
diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py
index 82e8751a9..12c835e6c 100644
--- a/netbox/ipam/tables.py
+++ b/netbox/ipam/tables.py
@@ -15,7 +15,7 @@ AVAILABLE_LABEL = mark_safe('<span class="label label-success">Available</span>'
 
 PREFIX_LINK = """
 {% load helpers %}
-{% for i in record.parents|as_range %}
+{% for i in record.depth|as_range %}
     <i class="mdi mdi-circle-small"></i>
 {% endfor %}
 <a href="{% if record.pk %}{% url 'ipam:prefix' pk=record.pk %}{% else %}{% url 'ipam:prefix_add' %}?prefix={{ record }}{% if object.vrf %}&vrf={{ object.vrf.pk }}{% endif %}{% if object.site %}&site={{ object.site.pk }}{% endif %}{% if object.tenant %}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}{% endif %}{% endif %}">{{ record.prefix }}</a>
@@ -262,6 +262,14 @@ class PrefixTable(BaseTable):
         template_code=PREFIX_LINK,
         attrs={'td': {'class': 'text-nowrap'}}
     )
+    depth = tables.Column(
+        accessor=Accessor('_depth'),
+        verbose_name='Depth'
+    )
+    children = tables.Column(
+        accessor=Accessor('_children'),
+        verbose_name='Children'
+    )
     status = ChoiceFieldColumn(
         default=AVAILABLE_LABEL
     )
@@ -287,7 +295,8 @@ class PrefixTable(BaseTable):
     class Meta(BaseTable.Meta):
         model = Prefix
         fields = (
-            'pk', 'prefix', 'status', 'children', 'vrf', 'tenant', 'site', 'vlan', 'role', 'is_pool', 'description',
+            'pk', 'prefix', 'status', 'depth', 'children', 'vrf', 'tenant', 'site', 'vlan', 'role', 'is_pool',
+            'description',
         )
         default_columns = ('pk', 'prefix', 'status', 'vrf', 'tenant', 'site', 'vlan', 'role', 'description')
         row_attrs = {
diff --git a/netbox/ipam/tests/test_models.py b/netbox/ipam/tests/test_models.py
index a47862165..4fefdec54 100644
--- a/netbox/ipam/tests/test_models.py
+++ b/netbox/ipam/tests/test_models.py
@@ -1,4 +1,4 @@
-import netaddr
+from netaddr import IPNetwork, IPSet
 from django.core.exceptions import ValidationError
 from django.test import TestCase, override_settings
 
@@ -10,27 +10,27 @@ class TestAggregate(TestCase):
 
     def test_get_utilization(self):
         rir = RIR.objects.create(name='RIR 1', slug='rir-1')
-        aggregate = Aggregate(prefix=netaddr.IPNetwork('10.0.0.0/8'), rir=rir)
+        aggregate = Aggregate(prefix=IPNetwork('10.0.0.0/8'), rir=rir)
         aggregate.save()
 
         # 25% utilization
         Prefix.objects.bulk_create((
-            Prefix(prefix=netaddr.IPNetwork('10.0.0.0/12')),
-            Prefix(prefix=netaddr.IPNetwork('10.16.0.0/12')),
-            Prefix(prefix=netaddr.IPNetwork('10.32.0.0/12')),
-            Prefix(prefix=netaddr.IPNetwork('10.48.0.0/12')),
+            Prefix(prefix=IPNetwork('10.0.0.0/12')),
+            Prefix(prefix=IPNetwork('10.16.0.0/12')),
+            Prefix(prefix=IPNetwork('10.32.0.0/12')),
+            Prefix(prefix=IPNetwork('10.48.0.0/12')),
         ))
         self.assertEqual(aggregate.get_utilization(), 25)
 
         # 50% utilization
         Prefix.objects.bulk_create((
-            Prefix(prefix=netaddr.IPNetwork('10.64.0.0/10')),
+            Prefix(prefix=IPNetwork('10.64.0.0/10')),
         ))
         self.assertEqual(aggregate.get_utilization(), 50)
 
         # 100% utilization
         Prefix.objects.bulk_create((
-            Prefix(prefix=netaddr.IPNetwork('10.128.0.0/9')),
+            Prefix(prefix=IPNetwork('10.128.0.0/9')),
         ))
         self.assertEqual(aggregate.get_utilization(), 100)
 
@@ -39,9 +39,9 @@ class TestPrefix(TestCase):
 
     def test_get_duplicates(self):
         prefixes = Prefix.objects.bulk_create((
-            Prefix(prefix=netaddr.IPNetwork('192.0.2.0/24')),
-            Prefix(prefix=netaddr.IPNetwork('192.0.2.0/24')),
-            Prefix(prefix=netaddr.IPNetwork('192.0.2.0/24')),
+            Prefix(prefix=IPNetwork('192.0.2.0/24')),
+            Prefix(prefix=IPNetwork('192.0.2.0/24')),
+            Prefix(prefix=IPNetwork('192.0.2.0/24')),
         ))
         duplicate_prefix_pks = [p.pk for p in prefixes[0].get_duplicates()]
 
@@ -54,11 +54,11 @@ class TestPrefix(TestCase):
             VRF(name='VRF 3'),
         ))
         prefixes = Prefix.objects.bulk_create((
-            Prefix(prefix=netaddr.IPNetwork('10.0.0.0/16'), status=PrefixStatusChoices.STATUS_CONTAINER),
-            Prefix(prefix=netaddr.IPNetwork('10.0.0.0/24'), vrf=None),
-            Prefix(prefix=netaddr.IPNetwork('10.0.1.0/24'), vrf=vrfs[0]),
-            Prefix(prefix=netaddr.IPNetwork('10.0.2.0/24'), vrf=vrfs[1]),
-            Prefix(prefix=netaddr.IPNetwork('10.0.3.0/24'), vrf=vrfs[2]),
+            Prefix(prefix=IPNetwork('10.0.0.0/16'), status=PrefixStatusChoices.STATUS_CONTAINER),
+            Prefix(prefix=IPNetwork('10.0.0.0/24'), vrf=None),
+            Prefix(prefix=IPNetwork('10.0.1.0/24'), vrf=vrfs[0]),
+            Prefix(prefix=IPNetwork('10.0.2.0/24'), vrf=vrfs[1]),
+            Prefix(prefix=IPNetwork('10.0.3.0/24'), vrf=vrfs[2]),
         ))
         child_prefix_pks = {p.pk for p in prefixes[0].get_child_prefixes()}
 
@@ -79,13 +79,13 @@ class TestPrefix(TestCase):
             VRF(name='VRF 3'),
         ))
         parent_prefix = Prefix.objects.create(
-            prefix=netaddr.IPNetwork('10.0.0.0/16'), status=PrefixStatusChoices.STATUS_CONTAINER
+            prefix=IPNetwork('10.0.0.0/16'), status=PrefixStatusChoices.STATUS_CONTAINER
         )
         ips = IPAddress.objects.bulk_create((
-            IPAddress(address=netaddr.IPNetwork('10.0.0.1/24'), vrf=None),
-            IPAddress(address=netaddr.IPNetwork('10.0.1.1/24'), vrf=vrfs[0]),
-            IPAddress(address=netaddr.IPNetwork('10.0.2.1/24'), vrf=vrfs[1]),
-            IPAddress(address=netaddr.IPNetwork('10.0.3.1/24'), vrf=vrfs[2]),
+            IPAddress(address=IPNetwork('10.0.0.1/24'), vrf=None),
+            IPAddress(address=IPNetwork('10.0.1.1/24'), vrf=vrfs[0]),
+            IPAddress(address=IPNetwork('10.0.2.1/24'), vrf=vrfs[1]),
+            IPAddress(address=IPNetwork('10.0.3.1/24'), vrf=vrfs[2]),
         ))
         child_ip_pks = {p.pk for p in parent_prefix.get_child_ips()}
 
@@ -102,16 +102,16 @@ class TestPrefix(TestCase):
     def test_get_available_prefixes(self):
 
         prefixes = Prefix.objects.bulk_create((
-            Prefix(prefix=netaddr.IPNetwork('10.0.0.0/16')),  # Parent prefix
-            Prefix(prefix=netaddr.IPNetwork('10.0.0.0/20')),
-            Prefix(prefix=netaddr.IPNetwork('10.0.32.0/20')),
-            Prefix(prefix=netaddr.IPNetwork('10.0.128.0/18')),
+            Prefix(prefix=IPNetwork('10.0.0.0/16')),  # Parent prefix
+            Prefix(prefix=IPNetwork('10.0.0.0/20')),
+            Prefix(prefix=IPNetwork('10.0.32.0/20')),
+            Prefix(prefix=IPNetwork('10.0.128.0/18')),
         ))
-        missing_prefixes = netaddr.IPSet([
-            netaddr.IPNetwork('10.0.16.0/20'),
-            netaddr.IPNetwork('10.0.48.0/20'),
-            netaddr.IPNetwork('10.0.64.0/18'),
-            netaddr.IPNetwork('10.0.192.0/18'),
+        missing_prefixes = IPSet([
+            IPNetwork('10.0.16.0/20'),
+            IPNetwork('10.0.48.0/20'),
+            IPNetwork('10.0.64.0/18'),
+            IPNetwork('10.0.192.0/18'),
         ])
         available_prefixes = prefixes[0].get_available_prefixes()
 
@@ -119,17 +119,17 @@ class TestPrefix(TestCase):
 
     def test_get_available_ips(self):
 
-        parent_prefix = Prefix.objects.create(prefix=netaddr.IPNetwork('10.0.0.0/28'))
+        parent_prefix = Prefix.objects.create(prefix=IPNetwork('10.0.0.0/28'))
         IPAddress.objects.bulk_create((
-            IPAddress(address=netaddr.IPNetwork('10.0.0.1/26')),
-            IPAddress(address=netaddr.IPNetwork('10.0.0.3/26')),
-            IPAddress(address=netaddr.IPNetwork('10.0.0.5/26')),
-            IPAddress(address=netaddr.IPNetwork('10.0.0.7/26')),
-            IPAddress(address=netaddr.IPNetwork('10.0.0.9/26')),
-            IPAddress(address=netaddr.IPNetwork('10.0.0.11/26')),
-            IPAddress(address=netaddr.IPNetwork('10.0.0.13/26')),
+            IPAddress(address=IPNetwork('10.0.0.1/26')),
+            IPAddress(address=IPNetwork('10.0.0.3/26')),
+            IPAddress(address=IPNetwork('10.0.0.5/26')),
+            IPAddress(address=IPNetwork('10.0.0.7/26')),
+            IPAddress(address=IPNetwork('10.0.0.9/26')),
+            IPAddress(address=IPNetwork('10.0.0.11/26')),
+            IPAddress(address=IPNetwork('10.0.0.13/26')),
         ))
-        missing_ips = netaddr.IPSet([
+        missing_ips = IPSet([
             '10.0.0.2/32',
             '10.0.0.4/32',
             '10.0.0.6/32',
@@ -145,39 +145,39 @@ class TestPrefix(TestCase):
     def test_get_first_available_prefix(self):
 
         prefixes = Prefix.objects.bulk_create((
-            Prefix(prefix=netaddr.IPNetwork('10.0.0.0/16')),  # Parent prefix
-            Prefix(prefix=netaddr.IPNetwork('10.0.0.0/24')),
-            Prefix(prefix=netaddr.IPNetwork('10.0.1.0/24')),
-            Prefix(prefix=netaddr.IPNetwork('10.0.2.0/24')),
+            Prefix(prefix=IPNetwork('10.0.0.0/16')),  # Parent prefix
+            Prefix(prefix=IPNetwork('10.0.0.0/24')),
+            Prefix(prefix=IPNetwork('10.0.1.0/24')),
+            Prefix(prefix=IPNetwork('10.0.2.0/24')),
         ))
-        self.assertEqual(prefixes[0].get_first_available_prefix(), netaddr.IPNetwork('10.0.3.0/24'))
+        self.assertEqual(prefixes[0].get_first_available_prefix(), IPNetwork('10.0.3.0/24'))
 
-        Prefix.objects.create(prefix=netaddr.IPNetwork('10.0.3.0/24'))
-        self.assertEqual(prefixes[0].get_first_available_prefix(), netaddr.IPNetwork('10.0.4.0/22'))
+        Prefix.objects.create(prefix=IPNetwork('10.0.3.0/24'))
+        self.assertEqual(prefixes[0].get_first_available_prefix(), IPNetwork('10.0.4.0/22'))
 
     def test_get_first_available_ip(self):
 
-        parent_prefix = Prefix.objects.create(prefix=netaddr.IPNetwork('10.0.0.0/24'))
+        parent_prefix = Prefix.objects.create(prefix=IPNetwork('10.0.0.0/24'))
         IPAddress.objects.bulk_create((
-            IPAddress(address=netaddr.IPNetwork('10.0.0.1/24')),
-            IPAddress(address=netaddr.IPNetwork('10.0.0.2/24')),
-            IPAddress(address=netaddr.IPNetwork('10.0.0.3/24')),
+            IPAddress(address=IPNetwork('10.0.0.1/24')),
+            IPAddress(address=IPNetwork('10.0.0.2/24')),
+            IPAddress(address=IPNetwork('10.0.0.3/24')),
         ))
         self.assertEqual(parent_prefix.get_first_available_ip(), '10.0.0.4/24')
 
-        IPAddress.objects.create(address=netaddr.IPNetwork('10.0.0.4/24'))
+        IPAddress.objects.create(address=IPNetwork('10.0.0.4/24'))
         self.assertEqual(parent_prefix.get_first_available_ip(), '10.0.0.5/24')
 
     def test_get_utilization(self):
 
         # Container Prefix
         prefix = Prefix.objects.create(
-            prefix=netaddr.IPNetwork('10.0.0.0/24'),
+            prefix=IPNetwork('10.0.0.0/24'),
             status=PrefixStatusChoices.STATUS_CONTAINER
         )
         Prefix.objects.bulk_create((
-            Prefix(prefix=netaddr.IPNetwork('10.0.0.0/26')),
-            Prefix(prefix=netaddr.IPNetwork('10.0.0.128/26')),
+            Prefix(prefix=IPNetwork('10.0.0.0/26')),
+            Prefix(prefix=IPNetwork('10.0.0.128/26')),
         ))
         self.assertEqual(prefix.get_utilization(), 50)
 
@@ -186,7 +186,7 @@ class TestPrefix(TestCase):
         prefix.save()
         IPAddress.objects.bulk_create(
             # Create 32 IPAddresses within the Prefix
-            [IPAddress(address=netaddr.IPNetwork('10.0.0.{}/24'.format(i))) for i in range(1, 33)]
+            [IPAddress(address=IPNetwork('10.0.0.{}/24'.format(i))) for i in range(1, 33)]
         )
         self.assertEqual(prefix.get_utilization(), 12)  # ~= 12%
 
@@ -196,36 +196,234 @@ class TestPrefix(TestCase):
 
     @override_settings(ENFORCE_GLOBAL_UNIQUE=False)
     def test_duplicate_global(self):
-        Prefix.objects.create(prefix=netaddr.IPNetwork('192.0.2.0/24'))
-        duplicate_prefix = Prefix(prefix=netaddr.IPNetwork('192.0.2.0/24'))
+        Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'))
+        duplicate_prefix = Prefix(prefix=IPNetwork('192.0.2.0/24'))
         self.assertIsNone(duplicate_prefix.clean())
 
     @override_settings(ENFORCE_GLOBAL_UNIQUE=True)
     def test_duplicate_global_unique(self):
-        Prefix.objects.create(prefix=netaddr.IPNetwork('192.0.2.0/24'))
-        duplicate_prefix = Prefix(prefix=netaddr.IPNetwork('192.0.2.0/24'))
+        Prefix.objects.create(prefix=IPNetwork('192.0.2.0/24'))
+        duplicate_prefix = Prefix(prefix=IPNetwork('192.0.2.0/24'))
         self.assertRaises(ValidationError, duplicate_prefix.clean)
 
     def test_duplicate_vrf(self):
         vrf = VRF.objects.create(name='Test', rd='1:1', enforce_unique=False)
-        Prefix.objects.create(vrf=vrf, prefix=netaddr.IPNetwork('192.0.2.0/24'))
-        duplicate_prefix = Prefix(vrf=vrf, prefix=netaddr.IPNetwork('192.0.2.0/24'))
+        Prefix.objects.create(vrf=vrf, prefix=IPNetwork('192.0.2.0/24'))
+        duplicate_prefix = Prefix(vrf=vrf, prefix=IPNetwork('192.0.2.0/24'))
         self.assertIsNone(duplicate_prefix.clean())
 
     def test_duplicate_vrf_unique(self):
         vrf = VRF.objects.create(name='Test', rd='1:1', enforce_unique=True)
-        Prefix.objects.create(vrf=vrf, prefix=netaddr.IPNetwork('192.0.2.0/24'))
-        duplicate_prefix = Prefix(vrf=vrf, prefix=netaddr.IPNetwork('192.0.2.0/24'))
+        Prefix.objects.create(vrf=vrf, prefix=IPNetwork('192.0.2.0/24'))
+        duplicate_prefix = Prefix(vrf=vrf, prefix=IPNetwork('192.0.2.0/24'))
         self.assertRaises(ValidationError, duplicate_prefix.clean)
 
 
+class TestPrefixHierarchy(TestCase):
+    """
+    Test the automatic updating of depth and child count in response to changes made within
+    the prefix hierarchy.
+    """
+    @classmethod
+    def setUpTestData(cls):
+
+        prefixes = (
+
+            # IPv4
+            Prefix(prefix='10.0.0.0/8', _depth=0, _children=2),
+            Prefix(prefix='10.0.0.0/16', _depth=1, _children=1),
+            Prefix(prefix='10.0.0.0/24', _depth=2, _children=0),
+
+            # IPv6
+            Prefix(prefix='2001:db8::/32', _depth=0, _children=2),
+            Prefix(prefix='2001:db8::/40', _depth=1, _children=1),
+            Prefix(prefix='2001:db8::/48', _depth=2, _children=0),
+
+        )
+        Prefix.objects.bulk_create(prefixes)
+
+    def test_create_prefix4(self):
+        # Create 10.0.0.0/12
+        Prefix(prefix='10.0.0.0/12').save()
+
+        prefixes = Prefix.objects.filter(prefix__family=4)
+        self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
+        self.assertEqual(prefixes[0]._depth, 0)
+        self.assertEqual(prefixes[0]._children, 3)
+        self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/12'))
+        self.assertEqual(prefixes[1]._depth, 1)
+        self.assertEqual(prefixes[1]._children, 2)
+        self.assertEqual(prefixes[2].prefix, IPNetwork('10.0.0.0/16'))
+        self.assertEqual(prefixes[2]._depth, 2)
+        self.assertEqual(prefixes[2]._children, 1)
+        self.assertEqual(prefixes[3].prefix, IPNetwork('10.0.0.0/24'))
+        self.assertEqual(prefixes[3]._depth, 3)
+        self.assertEqual(prefixes[3]._children, 0)
+
+    def test_create_prefix6(self):
+        # Create 2001:db8::/36
+        Prefix(prefix='2001:db8::/36').save()
+
+        prefixes = Prefix.objects.filter(prefix__family=6)
+        self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
+        self.assertEqual(prefixes[0]._depth, 0)
+        self.assertEqual(prefixes[0]._children, 3)
+        self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/36'))
+        self.assertEqual(prefixes[1]._depth, 1)
+        self.assertEqual(prefixes[1]._children, 2)
+        self.assertEqual(prefixes[2].prefix, IPNetwork('2001:db8::/40'))
+        self.assertEqual(prefixes[2]._depth, 2)
+        self.assertEqual(prefixes[2]._children, 1)
+        self.assertEqual(prefixes[3].prefix, IPNetwork('2001:db8::/48'))
+        self.assertEqual(prefixes[3]._depth, 3)
+        self.assertEqual(prefixes[3]._children, 0)
+
+    def test_update_prefix4(self):
+        # Change 10.0.0.0/24 to 10.0.0.0/12
+        p = Prefix.objects.get(prefix='10.0.0.0/24')
+        p.prefix = '10.0.0.0/12'
+        p.save()
+
+        prefixes = Prefix.objects.filter(prefix__family=4)
+        self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
+        self.assertEqual(prefixes[0]._depth, 0)
+        self.assertEqual(prefixes[0]._children, 2)
+        self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/12'))
+        self.assertEqual(prefixes[1]._depth, 1)
+        self.assertEqual(prefixes[1]._children, 1)
+        self.assertEqual(prefixes[2].prefix, IPNetwork('10.0.0.0/16'))
+        self.assertEqual(prefixes[2]._depth, 2)
+        self.assertEqual(prefixes[2]._children, 0)
+
+    def test_update_prefix6(self):
+        # Change 2001:db8::/48 to 2001:db8::/36
+        p = Prefix.objects.get(prefix='2001:db8::/48')
+        p.prefix = '2001:db8::/36'
+        p.save()
+
+        prefixes = Prefix.objects.filter(prefix__family=6)
+        self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
+        self.assertEqual(prefixes[0]._depth, 0)
+        self.assertEqual(prefixes[0]._children, 2)
+        self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/36'))
+        self.assertEqual(prefixes[1]._depth, 1)
+        self.assertEqual(prefixes[1]._children, 1)
+        self.assertEqual(prefixes[2].prefix, IPNetwork('2001:db8::/40'))
+        self.assertEqual(prefixes[2]._depth, 2)
+        self.assertEqual(prefixes[2]._children, 0)
+
+    def test_update_prefix_vrf4(self):
+        vrf = VRF(name='VRF A')
+        vrf.save()
+
+        # Move 10.0.0.0/16 to a VRF
+        p = Prefix.objects.get(prefix='10.0.0.0/16')
+        p.vrf = vrf
+        p.save()
+
+        prefixes = Prefix.objects.filter(vrf__isnull=True, prefix__family=4)
+        self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
+        self.assertEqual(prefixes[0]._depth, 0)
+        self.assertEqual(prefixes[0]._children, 1)
+        self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/24'))
+        self.assertEqual(prefixes[1]._depth, 1)
+        self.assertEqual(prefixes[1]._children, 0)
+
+        prefixes = Prefix.objects.filter(vrf=vrf)
+        self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/16'))
+        self.assertEqual(prefixes[0]._depth, 0)
+        self.assertEqual(prefixes[0]._children, 0)
+
+    def test_update_prefix_vrf6(self):
+        vrf = VRF(name='VRF A')
+        vrf.save()
+
+        # Move 2001:db8::/40 to a VRF
+        p = Prefix.objects.get(prefix='2001:db8::/40')
+        p.vrf = vrf
+        p.save()
+
+        prefixes = Prefix.objects.filter(vrf__isnull=True, prefix__family=6)
+        self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
+        self.assertEqual(prefixes[0]._depth, 0)
+        self.assertEqual(prefixes[0]._children, 1)
+        self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/48'))
+        self.assertEqual(prefixes[1]._depth, 1)
+        self.assertEqual(prefixes[1]._children, 0)
+
+        prefixes = Prefix.objects.filter(vrf=vrf)
+        self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/40'))
+        self.assertEqual(prefixes[0]._depth, 0)
+        self.assertEqual(prefixes[0]._children, 0)
+
+    def test_delete_prefix4(self):
+        # Delete 10.0.0.0/16
+        Prefix.objects.filter(prefix='10.0.0.0/16').delete()
+
+        prefixes = Prefix.objects.filter(prefix__family=4)
+        self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
+        self.assertEqual(prefixes[0]._depth, 0)
+        self.assertEqual(prefixes[0]._children, 1)
+        self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/24'))
+        self.assertEqual(prefixes[1]._depth, 1)
+        self.assertEqual(prefixes[1]._children, 0)
+
+    def test_delete_prefix6(self):
+        # Delete 2001:db8::/40
+        Prefix.objects.filter(prefix='2001:db8::/40').delete()
+
+        prefixes = Prefix.objects.filter(prefix__family=6)
+        self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
+        self.assertEqual(prefixes[0]._depth, 0)
+        self.assertEqual(prefixes[0]._children, 1)
+        self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/48'))
+        self.assertEqual(prefixes[1]._depth, 1)
+        self.assertEqual(prefixes[1]._children, 0)
+
+    def test_duplicate_prefix4(self):
+        # Duplicate 10.0.0.0/16
+        Prefix(prefix='10.0.0.0/16').save()
+
+        prefixes = Prefix.objects.filter(prefix__family=4)
+        self.assertEqual(prefixes[0].prefix, IPNetwork('10.0.0.0/8'))
+        self.assertEqual(prefixes[0]._depth, 0)
+        self.assertEqual(prefixes[0]._children, 3)
+        self.assertEqual(prefixes[1].prefix, IPNetwork('10.0.0.0/16'))
+        self.assertEqual(prefixes[1]._depth, 1)
+        self.assertEqual(prefixes[1]._children, 1)
+        self.assertEqual(prefixes[2].prefix, IPNetwork('10.0.0.0/16'))
+        self.assertEqual(prefixes[2]._depth, 1)
+        self.assertEqual(prefixes[2]._children, 1)
+        self.assertEqual(prefixes[3].prefix, IPNetwork('10.0.0.0/24'))
+        self.assertEqual(prefixes[3]._depth, 2)
+        self.assertEqual(prefixes[3]._children, 0)
+
+    def test_duplicate_prefix6(self):
+        # Duplicate 2001:db8::/40
+        Prefix(prefix='2001:db8::/40').save()
+
+        prefixes = Prefix.objects.filter(prefix__family=6)
+        self.assertEqual(prefixes[0].prefix, IPNetwork('2001:db8::/32'))
+        self.assertEqual(prefixes[0]._depth, 0)
+        self.assertEqual(prefixes[0]._children, 3)
+        self.assertEqual(prefixes[1].prefix, IPNetwork('2001:db8::/40'))
+        self.assertEqual(prefixes[1]._depth, 1)
+        self.assertEqual(prefixes[1]._children, 1)
+        self.assertEqual(prefixes[2].prefix, IPNetwork('2001:db8::/40'))
+        self.assertEqual(prefixes[2]._depth, 1)
+        self.assertEqual(prefixes[2]._children, 1)
+        self.assertEqual(prefixes[3].prefix, IPNetwork('2001:db8::/48'))
+        self.assertEqual(prefixes[3]._depth, 2)
+        self.assertEqual(prefixes[3]._children, 0)
+
+
 class TestIPAddress(TestCase):
 
     def test_get_duplicates(self):
         ips = IPAddress.objects.bulk_create((
-            IPAddress(address=netaddr.IPNetwork('192.0.2.1/24')),
-            IPAddress(address=netaddr.IPNetwork('192.0.2.1/24')),
-            IPAddress(address=netaddr.IPNetwork('192.0.2.1/24')),
+            IPAddress(address=IPNetwork('192.0.2.1/24')),
+            IPAddress(address=IPNetwork('192.0.2.1/24')),
+            IPAddress(address=IPNetwork('192.0.2.1/24')),
         ))
         duplicate_ip_pks = [p.pk for p in ips[0].get_duplicates()]
 
@@ -237,44 +435,44 @@ class TestIPAddress(TestCase):
 
     @override_settings(ENFORCE_GLOBAL_UNIQUE=False)
     def test_duplicate_global(self):
-        IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'))
-        duplicate_ip = IPAddress(address=netaddr.IPNetwork('192.0.2.1/24'))
+        IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'))
+        duplicate_ip = IPAddress(address=IPNetwork('192.0.2.1/24'))
         self.assertIsNone(duplicate_ip.clean())
 
     @override_settings(ENFORCE_GLOBAL_UNIQUE=True)
     def test_duplicate_global_unique(self):
-        IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'))
-        duplicate_ip = IPAddress(address=netaddr.IPNetwork('192.0.2.1/24'))
+        IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'))
+        duplicate_ip = IPAddress(address=IPNetwork('192.0.2.1/24'))
         self.assertRaises(ValidationError, duplicate_ip.clean)
 
     def test_duplicate_vrf(self):
         vrf = VRF.objects.create(name='Test', rd='1:1', enforce_unique=False)
-        IPAddress.objects.create(vrf=vrf, address=netaddr.IPNetwork('192.0.2.1/24'))
-        duplicate_ip = IPAddress(vrf=vrf, address=netaddr.IPNetwork('192.0.2.1/24'))
+        IPAddress.objects.create(vrf=vrf, address=IPNetwork('192.0.2.1/24'))
+        duplicate_ip = IPAddress(vrf=vrf, address=IPNetwork('192.0.2.1/24'))
         self.assertIsNone(duplicate_ip.clean())
 
     def test_duplicate_vrf_unique(self):
         vrf = VRF.objects.create(name='Test', rd='1:1', enforce_unique=True)
-        IPAddress.objects.create(vrf=vrf, address=netaddr.IPNetwork('192.0.2.1/24'))
-        duplicate_ip = IPAddress(vrf=vrf, address=netaddr.IPNetwork('192.0.2.1/24'))
+        IPAddress.objects.create(vrf=vrf, address=IPNetwork('192.0.2.1/24'))
+        duplicate_ip = IPAddress(vrf=vrf, address=IPNetwork('192.0.2.1/24'))
         self.assertRaises(ValidationError, duplicate_ip.clean)
 
     @override_settings(ENFORCE_GLOBAL_UNIQUE=True)
     def test_duplicate_nonunique_nonrole_role(self):
-        IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'))
-        duplicate_ip = IPAddress(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP)
+        IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'))
+        duplicate_ip = IPAddress(address=IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP)
         self.assertRaises(ValidationError, duplicate_ip.clean)
 
     @override_settings(ENFORCE_GLOBAL_UNIQUE=True)
     def test_duplicate_nonunique_role_nonrole(self):
-        IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP)
-        duplicate_ip = IPAddress(address=netaddr.IPNetwork('192.0.2.1/24'))
+        IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP)
+        duplicate_ip = IPAddress(address=IPNetwork('192.0.2.1/24'))
         self.assertRaises(ValidationError, duplicate_ip.clean)
 
     @override_settings(ENFORCE_GLOBAL_UNIQUE=True)
     def test_duplicate_nonunique_role(self):
-        IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP)
-        IPAddress.objects.create(address=netaddr.IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP)
+        IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP)
+        IPAddress.objects.create(address=IPNetwork('192.0.2.1/24'), role=IPAddressRoleChoices.ROLE_VIP)
 
 
 class TestVLANGroup(TestCase):
diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py
index 168933af7..7c1a06cf3 100644
--- a/netbox/ipam/views.py
+++ b/netbox/ipam/views.py
@@ -238,7 +238,7 @@ class AggregateView(generic.ObjectView):
             'site', 'role'
         ).order_by(
             'prefix'
-        ).annotate_tree()
+        )
 
         # Add available prefixes to the table if requested
         if request.GET.get('show_available', 'true') == 'true':
@@ -352,7 +352,7 @@ class RoleBulkDeleteView(generic.BulkDeleteView):
 #
 
 class PrefixListView(generic.ObjectListView):
-    queryset = Prefix.objects.annotate_tree()
+    queryset = Prefix.objects.all()
     filterset = filtersets.PrefixFilterSet
     filterset_form = forms.PrefixFilterForm
     table = tables.PrefixDetailTable
@@ -377,7 +377,7 @@ class PrefixView(generic.ObjectView):
             prefix__net_contains=str(instance.prefix)
         ).prefetch_related(
             'site', 'role'
-        ).annotate_tree()
+        )
         parent_prefix_table = tables.PrefixTable(list(parent_prefixes), orderable=False)
         parent_prefix_table.exclude = ('vrf',)
 
@@ -407,7 +407,7 @@ class PrefixPrefixesView(generic.ObjectView):
         # Child prefixes table
         child_prefixes = instance.get_child_prefixes().restrict(request.user, 'view').prefetch_related(
             'site', 'vlan', 'role',
-        ).annotate_tree()
+        )
 
         # Add available prefixes to the table if requested
         if child_prefixes and request.GET.get('show_available', 'true') == 'true':