mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-15 21:09:36 -06:00
Develop triggers for setting parents
This commit is contained in:
parent
56673f4d88
commit
42c2dc57f8
@ -1,4 +1,5 @@
|
|||||||
import netaddr
|
import netaddr
|
||||||
|
import pgtrigger
|
||||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.postgres.indexes import GistIndex
|
from django.contrib.postgres.indexes import GistIndex
|
||||||
@ -17,6 +18,7 @@ from ipam.fields import IPNetworkField, IPAddressField
|
|||||||
from ipam.lookups import Host
|
from ipam.lookups import Host
|
||||||
from ipam.managers import IPAddressManager
|
from ipam.managers import IPAddressManager
|
||||||
from ipam.querysets import PrefixQuerySet
|
from ipam.querysets import PrefixQuerySet
|
||||||
|
from ipam.triggers import ipam_prefix_delete_adjust_prefix_parent, ipam_prefix_insert_adjust_prefix_parent
|
||||||
from ipam.validators import DNSValidator
|
from ipam.validators import DNSValidator
|
||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from netbox.models import OrganizationalModel, PrimaryModel
|
from netbox.models import OrganizationalModel, PrimaryModel
|
||||||
@ -186,25 +188,6 @@ class Aggregate(ContactsMixin, GetAvailablePrefixesMixin, PrimaryModel):
|
|||||||
return min(utilization, 100)
|
return min(utilization, 100)
|
||||||
|
|
||||||
|
|
||||||
class Role(OrganizationalModel):
|
|
||||||
"""
|
|
||||||
A Role represents the functional role of a Prefix or VLAN; for example, "Customer," "Infrastructure," or
|
|
||||||
"Management."
|
|
||||||
"""
|
|
||||||
weight = models.PositiveSmallIntegerField(
|
|
||||||
verbose_name=_('weight'),
|
|
||||||
default=1000
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('weight', 'name')
|
|
||||||
verbose_name = _('role')
|
|
||||||
verbose_name_plural = _('roles')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, PrimaryModel):
|
class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be scoped to certain
|
A Prefix represents an IPv4 or IPv6 network, including mask length. Prefixes can optionally be scoped to certain
|
||||||
@ -306,6 +289,20 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
|||||||
opclasses=['inet_ops'],
|
opclasses=['inet_ops'],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
triggers = [
|
||||||
|
pgtrigger.Trigger(
|
||||||
|
name='ipam_prefix_delete',
|
||||||
|
operation=pgtrigger.Delete,
|
||||||
|
when=pgtrigger.Before,
|
||||||
|
func=ipam_prefix_delete_adjust_prefix_parent,
|
||||||
|
),
|
||||||
|
pgtrigger.Trigger(
|
||||||
|
name='ipam_prefix_insert',
|
||||||
|
operation=pgtrigger.Insert,
|
||||||
|
when=pgtrigger.After,
|
||||||
|
func=ipam_prefix_insert_adjust_prefix_parent,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -546,6 +543,25 @@ class Prefix(ContactsMixin, GetAvailablePrefixesMixin, CachedScopeMixin, Primary
|
|||||||
return prefixes.last()
|
return prefixes.last()
|
||||||
|
|
||||||
|
|
||||||
|
class Role(OrganizationalModel):
|
||||||
|
"""
|
||||||
|
A Role represents the functional role of a Prefix or VLAN; for example, "Customer," "Infrastructure," or
|
||||||
|
"Management."
|
||||||
|
"""
|
||||||
|
weight = models.PositiveSmallIntegerField(
|
||||||
|
verbose_name=_('weight'),
|
||||||
|
default=1000
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('weight', 'name')
|
||||||
|
verbose_name = _('role')
|
||||||
|
verbose_name_plural = _('roles')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class IPRange(ContactsMixin, PrimaryModel):
|
class IPRange(ContactsMixin, PrimaryModel):
|
||||||
"""
|
"""
|
||||||
A range of IP addresses, defined by start and end addresses.
|
A range of IP addresses, defined by start and end addresses.
|
||||||
|
|||||||
@ -29,123 +29,12 @@ def update_children_depth(prefix):
|
|||||||
Prefix.objects.bulk_update(children, ['_depth'], batch_size=100)
|
Prefix.objects.bulk_update(children, ['_depth'], batch_size=100)
|
||||||
|
|
||||||
|
|
||||||
def update_object_prefix(prefix, child_model=IPAddress):
|
|
||||||
filter = Q(prefix=prefix)
|
|
||||||
|
|
||||||
if child_model == IPAddress:
|
|
||||||
filter |= Q(address__net_contained_or_equal=prefix.prefix, vrf=prefix.vrf)
|
|
||||||
elif child_model == IPRange:
|
|
||||||
filter |= Q(
|
|
||||||
start_address__net_contained_or_equal=prefix.prefix,
|
|
||||||
end_address__net_contained_or_equal=prefix.prefix,
|
|
||||||
vrf=prefix.vrf
|
|
||||||
)
|
|
||||||
|
|
||||||
addresses = child_model.objects.filter(filter)
|
|
||||||
for address in addresses:
|
|
||||||
# If addresses prefix is not set then this model is the only option
|
|
||||||
if not address.prefix:
|
|
||||||
address.prefix = prefix
|
|
||||||
# This address has a different VRF so the prefix cannot be the parent prefix
|
|
||||||
elif address.prefix != address.find_prefix(address):
|
|
||||||
address.prefix = address.find_prefix(address)
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Update the addresses
|
|
||||||
child_model.objects.bulk_update(addresses, ['prefix'], batch_size=100)
|
|
||||||
|
|
||||||
|
|
||||||
def delete_object_prefix(prefix, child_model, child_objects):
|
|
||||||
if not prefix.parent or prefix.vrf != prefix.parent.vrf:
|
|
||||||
# Prefix will be Set Null
|
|
||||||
return
|
|
||||||
|
|
||||||
# Set prefix to prefix parent
|
|
||||||
for address in child_objects:
|
|
||||||
address.prefix = prefix.parent
|
|
||||||
|
|
||||||
# Run a bulk update
|
|
||||||
child_model.objects.bulk_update(child_objects, ['prefix'], batch_size=100)
|
|
||||||
|
|
||||||
|
|
||||||
def update_ipaddress_prefix(prefix, delete=False):
|
|
||||||
if delete:
|
|
||||||
delete_object_prefix(prefix, IPAddress, prefix.ip_addresses.all())
|
|
||||||
else:
|
|
||||||
update_object_prefix(prefix, child_model=IPAddress)
|
|
||||||
|
|
||||||
|
|
||||||
def update_iprange_prefix(prefix, delete=False):
|
|
||||||
if delete:
|
|
||||||
delete_object_prefix(prefix, IPRange, prefix.ip_ranges.all())
|
|
||||||
else:
|
|
||||||
update_object_prefix(prefix, child_model=IPRange)
|
|
||||||
|
|
||||||
|
|
||||||
def update_prefix_parents(prefix, delete=False, created=False):
|
|
||||||
if delete:
|
|
||||||
# Set prefix to prefix parent
|
|
||||||
prefixes = prefix.children.all()
|
|
||||||
for address in prefixes:
|
|
||||||
address.parent = prefix.parent
|
|
||||||
|
|
||||||
# Run a bulk update
|
|
||||||
Prefix.objects.bulk_update(prefixes, ['parent'], batch_size=100)
|
|
||||||
else:
|
|
||||||
# Build filter to get prefixes that will be impacted by this change:
|
|
||||||
# * Parent prefix is this prefixes parent, and;
|
|
||||||
# * Prefix is contained by this prefix, and;
|
|
||||||
# * Prefix is either within this VRF or there is no VRF and this prefix is a container prefix
|
|
||||||
filter = Q(
|
|
||||||
parent=prefix.parent,
|
|
||||||
vrf=prefix.vrf,
|
|
||||||
prefix__net_contained=str(prefix.prefix)
|
|
||||||
)
|
|
||||||
is_container = False
|
|
||||||
if prefix.status == PrefixStatusChoices.STATUS_CONTAINER and prefix.vrf is None:
|
|
||||||
is_container = True
|
|
||||||
filter |= Q(
|
|
||||||
parent=prefix.parent,
|
|
||||||
vrf=None,
|
|
||||||
prefix__net_contained=str(prefix.prefix),
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get all impacted prefixes. Ensure we use distinct() to weed out duplicate prefixes from joins
|
|
||||||
prefixes = Prefix.objects.filter(filter)
|
|
||||||
# Include children
|
|
||||||
if not created:
|
|
||||||
prefixes |= prefix.children.all()
|
|
||||||
|
|
||||||
for pfx in prefixes.distinct():
|
|
||||||
# Update parent criteria:
|
|
||||||
# * This prefix contains the child prefix, has a parent that is the prefixes parent and is "In-VRF"
|
|
||||||
# * This prefix does not contain the child prefix
|
|
||||||
if pfx.vrf != prefix.vrf and not (prefix.vrf is None and is_container):
|
|
||||||
# Prefix is contained but not in-VRF
|
|
||||||
# print(f'{pfx} is no longer "in-VRF"')
|
|
||||||
pfx.parent = prefix.parent
|
|
||||||
elif pfx.prefix in prefix.prefix and pfx.parent != prefix and pfx.parent == prefix.parent:
|
|
||||||
# Prefix is in-scope
|
|
||||||
# print(f'{pfx} is in {prefix}')
|
|
||||||
pfx.parent = prefix
|
|
||||||
elif pfx.prefix not in prefix.prefix and pfx.parent == prefix:
|
|
||||||
# Prefix has fallen out of scope
|
|
||||||
# print(f'{pfx} is not in {prefix}')
|
|
||||||
pfx.parent = prefix.parent
|
|
||||||
rows = Prefix.objects.bulk_update(prefixes, ['parent'], batch_size=100)
|
|
||||||
print(rows)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Prefix)
|
@receiver(post_save, sender=Prefix)
|
||||||
def handle_prefix_saved(instance, created, **kwargs):
|
def handle_prefix_saved(instance, created, **kwargs):
|
||||||
|
|
||||||
# Prefix has changed (or new instance has been created)
|
# Prefix has changed (or new instance has been created)
|
||||||
if created or instance.vrf_id != instance._vrf_id or instance.prefix != instance._prefix:
|
if created or instance.vrf_id != instance._vrf_id or instance.prefix != instance._prefix:
|
||||||
|
|
||||||
update_ipaddress_prefix(instance)
|
|
||||||
update_iprange_prefix(instance)
|
|
||||||
update_prefix_parents(instance, created=created)
|
|
||||||
update_parents_children(instance)
|
update_parents_children(instance)
|
||||||
update_children_depth(instance)
|
update_children_depth(instance)
|
||||||
|
|
||||||
@ -156,13 +45,6 @@ def handle_prefix_saved(instance, created, **kwargs):
|
|||||||
update_children_depth(old_prefix)
|
update_children_depth(old_prefix)
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=Prefix)
|
|
||||||
def pre_handle_prefix_deleted(instance, **kwargs):
|
|
||||||
update_ipaddress_prefix(instance, delete=True)
|
|
||||||
update_iprange_prefix(instance, delete=True)
|
|
||||||
update_prefix_parents(instance, delete=True)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_delete, sender=Prefix)
|
@receiver(post_delete, sender=Prefix)
|
||||||
def handle_prefix_deleted(instance, **kwargs):
|
def handle_prefix_deleted(instance, **kwargs):
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
from time import sleep
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
@ -653,6 +655,120 @@ class TestPrefixHierarchy(TestCase):
|
|||||||
self.assertEqual(prefixes[3]._children, 0)
|
self.assertEqual(prefixes[3]._children, 0)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTriggers(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'),
|
||||||
|
Prefix(prefix='10.0.0.0/16'),
|
||||||
|
Prefix(prefix='10.0.0.0/24'),
|
||||||
|
Prefix(prefix='192.168.0.0/16'),
|
||||||
|
# IPv6
|
||||||
|
Prefix(prefix='2001:db8::/32'),
|
||||||
|
Prefix(prefix='2001:db8::/40'),
|
||||||
|
Prefix(prefix='2001:db8::/48'),
|
||||||
|
)
|
||||||
|
|
||||||
|
for prefix in prefixes:
|
||||||
|
prefix.clean()
|
||||||
|
prefix.save()
|
||||||
|
|
||||||
|
vrfs = (
|
||||||
|
VRF(name='VRF A'),
|
||||||
|
VRF(name='VRF B'),
|
||||||
|
)
|
||||||
|
|
||||||
|
for prefix in prefixes:
|
||||||
|
prefix.clean()
|
||||||
|
prefix.save()
|
||||||
|
|
||||||
|
for vrf in vrfs:
|
||||||
|
vrf.clean()
|
||||||
|
vrf.save()
|
||||||
|
|
||||||
|
def test_current_hierarchy(self):
|
||||||
|
self.assertIsNone(Prefix.objects.get(prefix='10.0.0.0/8').parent)
|
||||||
|
self.assertIsNone(Prefix.objects.get(prefix='192.168.0.0/16').parent)
|
||||||
|
self.assertIsNone(Prefix.objects.get(prefix='2001:db8::/32').parent)
|
||||||
|
|
||||||
|
self.assertIsNotNone(Prefix.objects.get(prefix='10.0.0.0/16').parent)
|
||||||
|
self.assertIsNotNone(Prefix.objects.get(prefix='10.0.0.0/24').parent)
|
||||||
|
|
||||||
|
self.assertIsNotNone(Prefix.objects.get(prefix='2001:db8::/40').parent)
|
||||||
|
self.assertIsNotNone(Prefix.objects.get(prefix='2001:db8::/48').parent)
|
||||||
|
|
||||||
|
def test_basic_insert(self):
|
||||||
|
pfx = Prefix.objects.create(prefix='2001:db8::/44')
|
||||||
|
self.assertIsNotNone(Prefix.objects.get(prefix='2001:db8::/48').parent)
|
||||||
|
self.assertEqual(Prefix.objects.get(prefix='2001:db8::/48').parent, pfx)
|
||||||
|
|
||||||
|
def test_vrf_insert(self):
|
||||||
|
vrf = VRF.objects.get(name='VRF A')
|
||||||
|
pfx = Prefix.objects.create(prefix='2001:db8::/44', vrf=vrf)
|
||||||
|
parent = Prefix.objects.get(prefix='2001:db8::/40')
|
||||||
|
self.assertIsNotNone(Prefix.objects.get(prefix='2001:db8::/48').parent)
|
||||||
|
self.assertNotEqual(Prefix.objects.get(prefix='2001:db8::/48').parent, pfx)
|
||||||
|
self.assertEqual(Prefix.objects.get(prefix='2001:db8::/48').parent, parent)
|
||||||
|
|
||||||
|
prefixes = (
|
||||||
|
Prefix(prefix='10.2.0.0/16', vrf=vrf),
|
||||||
|
Prefix(prefix='10.2.0.0/24', vrf=vrf),
|
||||||
|
)
|
||||||
|
|
||||||
|
for prefix in prefixes:
|
||||||
|
prefix.clean()
|
||||||
|
prefix.save()
|
||||||
|
|
||||||
|
self.assertIsNone(Prefix.objects.get(pk=prefixes[0].pk).parent)
|
||||||
|
self.assertEqual(Prefix.objects.get(pk=prefixes[1].pk).parent, prefixes[0])
|
||||||
|
|
||||||
|
new_pfx = Prefix.objects.create(prefix='10.2.0.0/23', vrf=vrf)
|
||||||
|
|
||||||
|
self.assertIsNone(Prefix.objects.get(pk=prefixes[0].pk).parent)
|
||||||
|
self.assertEqual(new_pfx.parent, prefixes[0])
|
||||||
|
self.assertEqual(Prefix.objects.get(pk=prefixes[1].pk).parent, new_pfx)
|
||||||
|
|
||||||
|
def test_basic_delete(self):
|
||||||
|
prefixes = (
|
||||||
|
Prefix(prefix='10.2.0.0/16'),
|
||||||
|
Prefix(prefix='10.2.0.0/23'),
|
||||||
|
Prefix(prefix='10.2.0.0/24'),
|
||||||
|
)
|
||||||
|
for prefix in prefixes:
|
||||||
|
prefix.clean()
|
||||||
|
prefix.save()
|
||||||
|
|
||||||
|
def test_vrf_delete(self):
|
||||||
|
vrf = VRF.objects.get(name='VRF A')
|
||||||
|
|
||||||
|
prefixes = (
|
||||||
|
Prefix(prefix='10.2.0.0/16', vrf=vrf),
|
||||||
|
Prefix(prefix='10.2.0.0/23', vrf=vrf),
|
||||||
|
Prefix(prefix='10.2.0.0/24', vrf=vrf),
|
||||||
|
)
|
||||||
|
|
||||||
|
for prefix in prefixes:
|
||||||
|
prefix.clean()
|
||||||
|
prefix.save()
|
||||||
|
|
||||||
|
self.assertIsNone(prefixes[0].parent)
|
||||||
|
self.assertEqual(prefixes[1].parent, prefixes[0])
|
||||||
|
self.assertEqual(prefixes[2].parent, prefixes[1])
|
||||||
|
|
||||||
|
prefixes[1].delete()
|
||||||
|
prefixes[2].refresh_from_db()
|
||||||
|
|
||||||
|
self.assertIsNone(prefixes[0].parent)
|
||||||
|
self.assertEqual(prefixes[2].parent, prefixes[0])
|
||||||
|
|
||||||
|
|
||||||
class TestIPAddress(TestCase):
|
class TestIPAddress(TestCase):
|
||||||
"""
|
"""
|
||||||
Test the automatic updating of depth and child count in response to changes made within
|
Test the automatic updating of depth and child count in response to changes made within
|
||||||
|
|||||||
108
netbox/ipam/triggers.py
Normal file
108
netbox/ipam/triggers.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
ipam_prefix_delete_adjust_prefix_parent = """
|
||||||
|
-- Update Child Prefix's with Prefix's PARENT
|
||||||
|
UPDATE ipam_prefix SET parent_id=OLD.parent_id WHERE parent_id=OLD.id;
|
||||||
|
RETURN OLD;
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
ipam_prefix_delete_adjust_ipaddress_prefix = """
|
||||||
|
-- Update IP Address with prefix's PARENT
|
||||||
|
UPDATE ipam_ipaddress SET prefix_id=OLD.parent_id WHERE prefix_id=OLD.id;
|
||||||
|
RETURN OLD;
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
ipam_prefix_delete_adjust_iprange_prefix = """
|
||||||
|
-- Update IP Range with prefix's PARENT
|
||||||
|
UPDATE ipam_iprange SET prefix_id=OLD.parent_id WHERE prefix_id=OLD.id;
|
||||||
|
RETURN OLD;
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
ipam_prefix_insert_adjust_prefix_parent = """
|
||||||
|
UPDATE ipam_prefix
|
||||||
|
SET parent_id=NEW.id
|
||||||
|
WHERE
|
||||||
|
prefix << NEW.prefix
|
||||||
|
AND
|
||||||
|
(
|
||||||
|
(vrf_id = NEW.vrf_id OR (vrf_id IS NULL AND NEW.vrf_id IS NULL))
|
||||||
|
OR
|
||||||
|
(
|
||||||
|
NEW.vrf_id IS NULL
|
||||||
|
AND
|
||||||
|
NEW.status = 'container'
|
||||||
|
AND
|
||||||
|
NOT EXISTS(
|
||||||
|
SELECT 1 FROM ipam_prefix p WHERE p.prefix >> ipam_prefix.prefix AND p.vrf_id = ipam_prefix.vrf_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND id != NEW.id
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM ipam_prefix p
|
||||||
|
WHERE
|
||||||
|
p.prefix >> ipam_prefix.prefix
|
||||||
|
AND p.prefix << NEW.prefix
|
||||||
|
AND (
|
||||||
|
(p.vrf_id = ipam_prefix.vrf_id OR (p.vrf_id IS NULL AND ipam_prefix.vrf_id IS NULL))
|
||||||
|
OR
|
||||||
|
(p.vrf_id IS NULL AND p.status = 'container')
|
||||||
|
)
|
||||||
|
AND p.id != NEW.id
|
||||||
|
)
|
||||||
|
;
|
||||||
|
RETURN NEW;
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
ipam_prefix_insert_adjust_ipaddress_prefix = """
|
||||||
|
UPDATE ipam_prefix
|
||||||
|
SET prefix_id=NEW.id
|
||||||
|
WHERE
|
||||||
|
NEW.prefix >> ipaddress.address
|
||||||
|
AND
|
||||||
|
(
|
||||||
|
(NEW.vrf = ipaddress.vrf_id OR (NEW.vrf_id IS NULL and ipaddress.vrf_id IS NULL))
|
||||||
|
OR
|
||||||
|
(NEW.vrf_id IS NULL AND NEW.status = 'container')
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
ipaddress.prefix_id IS NULL
|
||||||
|
OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 from prefix p WHERE
|
||||||
|
p.id = ipaddress.prefix_id
|
||||||
|
AND NEW.prefix << p.prefix
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND
|
||||||
|
-- Check to ensure current parent PREFIX is not in a VRF
|
||||||
|
NOT EXISTS (
|
||||||
|
SELECT 1 from prefix p WHERE (
|
||||||
|
p.id = ipaddress.prefix_id
|
||||||
|
AND
|
||||||
|
p.vrf_id IS NOT NULL
|
||||||
|
AND
|
||||||
|
ipaddress.vrf_id IS NOT NULL
|
||||||
|
AND
|
||||||
|
(
|
||||||
|
NEW.vrf_id IS NULL AND NEW.status = 'container'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND
|
||||||
|
NOT EXISTS (
|
||||||
|
SELECT 1 FROM prefix p
|
||||||
|
WHERE
|
||||||
|
p.prefix >> ipaddress.address
|
||||||
|
AND p.id != NEW.id
|
||||||
|
AND p.prefix << NEW.prefix
|
||||||
|
AND (
|
||||||
|
(p.vrf_id = ipaddress.vrf_id OR (p.vrf_id IS NULL AND ipaddress.vrf_id IS NULL))
|
||||||
|
OR
|
||||||
|
(p.vrf_id IS NULL AND NEW.status = 'container')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
RETURN NEW;
|
||||||
|
"""
|
||||||
@ -425,6 +425,7 @@ INSTALLED_APPS = [
|
|||||||
'sorl.thumbnail',
|
'sorl.thumbnail',
|
||||||
'taggit',
|
'taggit',
|
||||||
'timezone_field',
|
'timezone_field',
|
||||||
|
'pgtrigger',
|
||||||
'core',
|
'core',
|
||||||
'account',
|
'account',
|
||||||
'circuits',
|
'circuits',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user