From d6221c54e37cd45dd4d372572ca36d0ffb665fa6 Mon Sep 17 00:00:00 2001 From: Rakib Hasan Date: Tue, 21 Feb 2017 06:23:29 -0500 Subject: [PATCH 1/7] Fix for #260 Implemented all tree traversal methods like ancestors, parent, root, children, descendents etc. --- nsot/api/views.py | 34 ++++++++++++++++++++++++++++++++++ nsot/models.py | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/nsot/api/views.py b/nsot/api/views.py index 5aec8c4d..4a41c577 100644 --- a/nsot/api/views.py +++ b/nsot/api/views.py @@ -688,6 +688,40 @@ def networks(self, request, pk=None, site_pk=None, *args, **kwargs): return self.list(request, queryset=interface.networks, *args, **kwargs) + @detail_route(methods=['get']) + def parent(self, request, pk=None, site_pk=None, *args, **kwargs): + """Return parent of interface""" + interface = self.get_resource_object(pk, site_pk) + parent = [] if interface.parent is None else [interface.parent] + + return self.list(request, queryset=parent, *args, **kwargs) + + @detail_route(methods=['get']) + def ancestors(self, request, pk=None, site_pk=None, *args, **kwargs): + """Return all ancestors of interface""" + interface = self.get_resource_object(pk, site_pk) + return self.list(request, queryset=interface.get_ancestors(), *args, **kwargs) + + @detail_route(methods=['get']) + def children(self, request, pk=None, site_pk=None, *args, **kwargs): + interface = self.get_resource_object(pk, site_pk) + return self.list(request, queryset=interface.get_children(), *args, **kwargs) + + @detail_route(methods=['get']) + def descendants(self, request, pk=None, site_pk=None, *args, **kwargs): + interface = self.get_resource_object(pk, site_pk) + return self.list(request, queryset=interface.get_descendants(), *args, **kwargs) + + @detail_route(methods=['get']) + def siblings(self, request, pk=None, site_pk=None, *args, **kwargs): + interface = self.get_resource_object(pk, site_pk) + return self.list(request, queryset=interface.get_siblings(), *args, **kwargs) + + @detail_route(methods=['get']) + def root(self, request, pk=None, site_pk=None, *args, **kwargs): + interface = self.get_resource_object(pk, site_pk) + return self.list(request, queryset=interface.get_root(), *args, **kwargs) + @detail_route(methods=['get']) def circuit(self, request, pk=None, site_pk=None, *args, **kwargs): """Return the Circuit I am associated with""" diff --git a/nsot/models.py b/nsot/models.py index 8f10e5a6..59ad7df0 100644 --- a/nsot/models.py +++ b/nsot/models.py @@ -1239,7 +1239,7 @@ class Interface(Resource): ) parent = fields.ChainedForeignKey( - 'nsot.Interface', blank=True, null=True, related_name='children', + 'self', blank=True, null=True, related_name='children', default=None, db_index=True, on_delete=models.PROTECT, chained_field='device', chained_model_field='device', verbose_name='Parent', help_text='Unique ID of the parent Interface.', @@ -1396,8 +1396,43 @@ def set_addresses(self, addresses, overwrite=False, partial=False): self.clean_addresses() + def get_ancestors(self): + """Recursively get all parents of an interface""" + p = self.parent + ancestors = [] + while p is not None: + ancestors.append(p) + p = p.parent + return ancestors + + def get_children(self): + """Return the immediate children of an interface""" + return Interface.objects.filter(parent=self) + + def get_descendants(self): + """Recursively return all the children of an interface""" + s = list(self.get_children()) + children = [] + while len(s) > 0: + top = s.pop() + children.append(top) + for c in top.get_children(): + s.append(c) + return children + + def get_root(self): + """Return the parent of all ancestors of an interface""" + root = self.parent + while root is not None and root.parent is not None: + root = root.parent + return [] if root is None else [root] + + def get_siblings(self): + """Return the interfaces with same parent as an interface""" + return list(Interface.objects.filter(parent=self.parent).exclude(id=self.id)) + def get_assignments(self): - """Return a list of informatoin about my assigned addresses.""" + """Return a list of information about my assigned addresses.""" return [a.to_dict() for a in self.assignments.all()] def get_addresses(self): From 1cba1780f674b1f344a3051399ab4c863a446ae6 Mon Sep 17 00:00:00 2001 From: Rakib Hasan Date: Fri, 28 Apr 2017 20:50:36 -0400 Subject: [PATCH 2/7] added tests for interface tree traversal methods --- nsot/api/views.py | 4 -- tests/api_tests/test_interfaces.py | 89 ++++++++++++++++++++++++++++ tests/model_tests/test_interfaces.py | 44 ++++++++++++++ 3 files changed, 133 insertions(+), 4 deletions(-) diff --git a/nsot/api/views.py b/nsot/api/views.py index 4a41c577..6864c9e2 100644 --- a/nsot/api/views.py +++ b/nsot/api/views.py @@ -670,7 +670,6 @@ def addresses(self, request, pk=None, site_pk=None, *args, **kwargs): """Return a list of addresses for this Interface.""" interface = self.get_resource_object(pk, site_pk) addresses = interface.addresses.all() - return self.list(request, queryset=addresses, *args, **kwargs) @detail_route(methods=['get']) @@ -678,14 +677,12 @@ def assignments(self, request, pk=None, site_pk=None, *args, **kwargs): """Return a list of information about my assigned addresses.""" interface = self.get_resource_object(pk, site_pk) assignments = interface.assignments.all() - return self.list(request, queryset=assignments, *args, **kwargs) @detail_route(methods=['get']) def networks(self, request, pk=None, site_pk=None, *args, **kwargs): """Return all the containing Networks for my assigned addresses.""" interface = self.get_resource_object(pk, site_pk) - return self.list(request, queryset=interface.networks, *args, **kwargs) @detail_route(methods=['get']) @@ -693,7 +690,6 @@ def parent(self, request, pk=None, site_pk=None, *args, **kwargs): """Return parent of interface""" interface = self.get_resource_object(pk, site_pk) parent = [] if interface.parent is None else [interface.parent] - return self.list(request, queryset=parent, *args, **kwargs) @detail_route(methods=['get']) diff --git a/tests/api_tests/test_interfaces.py b/tests/api_tests/test_interfaces.py index 5b28a454..5cba504c 100644 --- a/tests/api_tests/test_interfaces.py +++ b/tests/api_tests/test_interfaces.py @@ -83,6 +83,95 @@ def test_creation(site, client): assert_success(client.get(ifc_uri), expected) +def test_tree_traversal(site, client): + """Test basic creation of an Interface.""" + ifc_uri = site.list_uri('interface') + dev_uri = site.list_uri('device') + + dev_resp = client.create(dev_uri, hostname='foo-bar1') + dev = get_result(dev_resp) + + ifc1_resp = client.create( + ifc_uri, device=dev['id'], name='eth0', parent_id=None, + mac_address=None, + ) + ifc1 = get_result(ifc1_resp) + ifc1_obj_uri = site.detail_uri('interface', id=ifc1['id']) + + assert_created(ifc1_resp, ifc1_obj_uri) + + # Create another interface with ifc1 as parent + ifc2_resp = client.create( + ifc_uri, device=dev['id'], name='eth0.0', parent_id=ifc1['id'], + mac_address=None + ) + ifc2 = get_result(ifc2_resp) + ifc2_obj_uri = site.detail_uri('interface', id=ifc2['id']) + + assert_created(ifc2_resp, ifc2_obj_uri) + + # Create another interface with ifc2 as parent + ifc3_resp = client.create( + ifc_uri, device = dev['id'], name='eth0.1', parent_id=ifc2['id'], + mac_address = None + ) + ifc3 = get_result(ifc3_resp) + ifc3_obj_uri = site.detail_uri('interface', id = ifc3['id']) + + assert_created(ifc3_resp, ifc3_obj_uri) + + # Create another interface with ifc2 as parent + ifc4_resp = client.create( + ifc_uri, device = dev['id'], name='eth0.2', parent_id=ifc2['id'], + mac_address = None + ) + ifc4 = get_result(ifc4_resp) + ifc4_obj_uri = site.detail_uri('interface', id = ifc4['id']) + + assert_created(ifc4_resp, ifc4_obj_uri) + + # Create another interface with ifc2 as parent + ifc5_resp = client.create( + ifc_uri, device = dev['id'], name='eth0.3', parent_id=ifc2['id'], + mac_address = None + ) + + ifc5 = get_result(ifc5_resp) + ifc5_obj_uri = site.detail_uri('interface', id = ifc5['id']) + + assert_created(ifc5_resp, ifc5_obj_uri) + + # test Ancestors by calling it on ifc3 + expected = [ifc1, ifc2] + uri = reverse('interface-ancestors', args = (site.id, ifc3['id'])) + assert_success(client.retrieve(uri), expected) + + # test children by calling it on ifc2 + expected = [ifc3, ifc4, ifc5] + uri = reverse('interface-children', args = (site.id, ifc2['id'])) + assert_success(client.retrieve(uri), expected) + + # test descendants by calling it on ifc1 + expected = [ifc2, ifc3, ifc4, ifc5] + uri = reverse('interface-descendants', args = (site.id, ifc1['id'])) + assert_success(client.retrieve(uri), expected) + + # test siblings by calling it on ifc3 + expected = [ifc4, ifc5] + uri = reverse('interface-siblings', args = (site.id, ifc3['id'])) + assert_success(client.retrieve(uri), expected) + + # test root by calling it on ifc4 + expected = [ifc1] + uri = reverse('interface-root', args=(site.id, ifc4['id'])) + assert_success(client.retrieve(uri), expected) + + # test parent by calling it on ifc5 + expected = [ifc2] + uri = reverse('interface-parent', args=(site.id, ifc5['id'])) + assert_success(client.retrieve(uri), expected) + + def test_creation_with_addresses(site, client): """Test creating an Interface w/ addresses.""" ifc_uri = site.list_uri('interface') diff --git a/tests/model_tests/test_interfaces.py b/tests/model_tests/test_interfaces.py index dac4972c..802727fd 100644 --- a/tests/model_tests/test_interfaces.py +++ b/tests/model_tests/test_interfaces.py @@ -32,6 +32,50 @@ def test_creation(device): iface.save() +def test_tree_methods(device): + iface = models.Interface.objects.create( + device = device, name = 'eth0' + ) + iface1 = models.Interface.objects.create( + device = device, name = 'eth0.0', parent = iface + ) + iface2 = models.Interface.objects.create( + device = device, name = 'eth0.1', parent = iface + ) + iface3 = models.Interface.objects.create( + device = device, name = 'eth0.2', parent = iface1 + ) + iface4 = models.Interface.objects.create( + device = device, name = 'eth0.3', parent = iface + ) + assert iface1.parent.id is iface.id + assert iface3.get_root()[0].id is iface.id + + children = [x.id for x in iface.get_children()] + expected = [iface1.id, iface2.id, iface4.id] + children.sort() + expected.sort() + assert children == expected + + descendants = [x.id for x in iface.get_descendants()] + expected = [iface1.id, iface2.id, iface3.id, iface4.id] + expected.sort() + descendants.sort() + assert descendants == expected + + ancestors = [x.id for x in iface3.get_ancestors()] + expected = [iface1.id, iface.id] + expected.sort() + ancestors.sort() + assert ancestors == expected + + siblings = [x.id for x in iface4.get_siblings()] + expected = [iface1.id, iface2.id] + siblings.sort() + expected.sort() + assert siblings == expected + + def test_speed(device): """Test interface speed.""" iface = models.Interface.objects.create(device=device, name='eth0') From aabe15b4b28306300faaa3674f74833d6ce1fd87 Mon Sep 17 00:00:00 2001 From: Rakib Hasan Date: Tue, 23 May 2017 16:10:50 -0400 Subject: [PATCH 3/7] Added changes requested on 05/11/2017 - used self.retrieve to return parent and root of interfaces - changed filter query on siblings to look for interfaces with same parent and device --- nsot/api/views.py | 12 +++++++++--- nsot/models.py | 8 ++++---- tests/api_tests/test_interfaces.py | 4 ++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/nsot/api/views.py b/nsot/api/views.py index 6864c9e2..fafb42ab 100644 --- a/nsot/api/views.py +++ b/nsot/api/views.py @@ -689,8 +689,12 @@ def networks(self, request, pk=None, site_pk=None, *args, **kwargs): def parent(self, request, pk=None, site_pk=None, *args, **kwargs): """Return parent of interface""" interface = self.get_resource_object(pk, site_pk) - parent = [] if interface.parent is None else [interface.parent] - return self.list(request, queryset=parent, *args, **kwargs) + parent = interface.parent + if parent is not None: + pk = interface.parent_id + else: + pk = None + return self.retrieve(request, pk, site_pk, *args, **kwargs) @detail_route(methods=['get']) def ancestors(self, request, pk=None, site_pk=None, *args, **kwargs): @@ -716,7 +720,9 @@ def siblings(self, request, pk=None, site_pk=None, *args, **kwargs): @detail_route(methods=['get']) def root(self, request, pk=None, site_pk=None, *args, **kwargs): interface = self.get_resource_object(pk, site_pk) - return self.list(request, queryset=interface.get_root(), *args, **kwargs) + root = interface.get_root() + pk = root.id + return self.retrieve(request, pk, site_pk, *args, **kwargs) @detail_route(methods=['get']) def circuit(self, request, pk=None, site_pk=None, *args, **kwargs): diff --git a/nsot/models.py b/nsot/models.py index 59ad7df0..6cfaebe5 100644 --- a/nsot/models.py +++ b/nsot/models.py @@ -1422,14 +1422,14 @@ def get_descendants(self): def get_root(self): """Return the parent of all ancestors of an interface""" - root = self.parent - while root is not None and root.parent is not None: + root = self + while root.parent is not None: root = root.parent - return [] if root is None else [root] + return root def get_siblings(self): """Return the interfaces with same parent as an interface""" - return list(Interface.objects.filter(parent=self.parent).exclude(id=self.id)) + return list(Interface.objects.filter(parent=self.parent, device=self.device).exclude(id=self.id)) def get_assignments(self): """Return a list of information about my assigned addresses.""" diff --git a/tests/api_tests/test_interfaces.py b/tests/api_tests/test_interfaces.py index 5cba504c..9a4b99ad 100644 --- a/tests/api_tests/test_interfaces.py +++ b/tests/api_tests/test_interfaces.py @@ -162,12 +162,12 @@ def test_tree_traversal(site, client): assert_success(client.retrieve(uri), expected) # test root by calling it on ifc4 - expected = [ifc1] + expected = ifc1 uri = reverse('interface-root', args=(site.id, ifc4['id'])) assert_success(client.retrieve(uri), expected) # test parent by calling it on ifc5 - expected = [ifc2] + expected = ifc2 uri = reverse('interface-parent', args=(site.id, ifc5['id'])) assert_success(client.retrieve(uri), expected) From 7063ab697fb79375bb9548522324cd2934bfec29 Mon Sep 17 00:00:00 2001 From: Rakib Hasan Date: Tue, 23 May 2017 16:37:20 -0400 Subject: [PATCH 4/7] Added tests to check the following - If someone calls the root view on an Interface object and its parent is None, then it should be returned as the root - Also added tests to check that if sibling is called on an Interface object, then Interface objects with same parent and device are returned --- tests/api_tests/test_interfaces.py | 35 ++++++++++++++++++++++++++++ tests/model_tests/test_interfaces.py | 4 ++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/tests/api_tests/test_interfaces.py b/tests/api_tests/test_interfaces.py index 9a4b99ad..03c03dfa 100644 --- a/tests/api_tests/test_interfaces.py +++ b/tests/api_tests/test_interfaces.py @@ -91,6 +91,9 @@ def test_tree_traversal(site, client): dev_resp = client.create(dev_uri, hostname='foo-bar1') dev = get_result(dev_resp) + dev_resp1 = client.create(dev_uri, hostname='foo-bar2') + dev1 = get_result(dev_resp1) + ifc1_resp = client.create( ifc_uri, device=dev['id'], name='eth0', parent_id=None, mac_address=None, @@ -141,6 +144,26 @@ def test_tree_traversal(site, client): assert_created(ifc5_resp, ifc5_obj_uri) + ifc6_resp = client.create( + ifc_uri, device = dev1['id'], name='eth0.4', parent_id=None, + mac_address = None + ) + + ifc6 = get_result(ifc6_resp) + ifc6_obj_uri = site.detail_uri('interface', id = ifc6['id']) + + assert_created(ifc6_resp, ifc6_obj_uri) + + ifc7_resp = client.create( + ifc_uri, device = dev1['id'], name='eth0.5', parent_id=None, + mac_address = None + ) + + ifc7 = get_result(ifc7_resp) + ifc7_obj_uri = site.detail_uri('interface', id = ifc7['id']) + + assert_created(ifc7_resp, ifc7_obj_uri) + # test Ancestors by calling it on ifc3 expected = [ifc1, ifc2] uri = reverse('interface-ancestors', args = (site.id, ifc3['id'])) @@ -166,11 +189,23 @@ def test_tree_traversal(site, client): uri = reverse('interface-root', args=(site.id, ifc4['id'])) assert_success(client.retrieve(uri), expected) + # test that root of ifc1 is ifc1 + expected = ifc1 + uri = reverse('interface-root', args=(site.id, ifc1['id'])) + assert_success(client.retrieve(uri), expected) + # test parent by calling it on ifc5 expected = ifc2 uri = reverse('interface-parent', args=(site.id, ifc5['id'])) assert_success(client.retrieve(uri), expected) + # test sibling for interfaces with None as parent and attached + # to different devices + expected = [ifc7] + uri = reverse('interface-siblings', args=(site.id, ifc6['id'])) + assert_success(client.retrieve(uri), expected) + + def test_creation_with_addresses(site, client): """Test creating an Interface w/ addresses.""" diff --git a/tests/model_tests/test_interfaces.py b/tests/model_tests/test_interfaces.py index 802727fd..440cec1f 100644 --- a/tests/model_tests/test_interfaces.py +++ b/tests/model_tests/test_interfaces.py @@ -49,7 +49,7 @@ def test_tree_methods(device): device = device, name = 'eth0.3', parent = iface ) assert iface1.parent.id is iface.id - assert iface3.get_root()[0].id is iface.id + assert iface3.get_root().id is iface.id children = [x.id for x in iface.get_children()] expected = [iface1.id, iface2.id, iface4.id] @@ -74,7 +74,7 @@ def test_tree_methods(device): siblings.sort() expected.sort() assert siblings == expected - + def test_speed(device): """Test interface speed.""" From 41e087a875f703ab7b5b0319cd126ca79741345b Mon Sep 17 00:00:00 2001 From: Rakib Hasan Date: Tue, 23 May 2017 18:34:32 -0400 Subject: [PATCH 5/7] Added validation check and tests for following -Added validation check to prevent users from adding interfaces with different device than its parent or add a parent to an existing interface that has a different device than itself -Also added test to check that when a user calls siblings view on an interface object, then only interfaces with same parent and device are returned --- nsot/api/views.py | 1 - nsot/models.py | 12 ++++++++++++ tests/api_tests/test_interfaces.py | 14 +++++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/nsot/api/views.py b/nsot/api/views.py index fafb42ab..87343f7a 100644 --- a/nsot/api/views.py +++ b/nsot/api/views.py @@ -662,7 +662,6 @@ def get_serializer_class(self): return serializers.InterfaceUpdateSerializer if self.request.method == 'PATCH': return serializers.InterfacePartialUpdateSerializer - return self.serializer_class @detail_route(methods=['get']) diff --git a/nsot/models.py b/nsot/models.py index 6cfaebe5..8607b5b9 100644 --- a/nsot/models.py +++ b/nsot/models.py @@ -1508,6 +1508,16 @@ def clean_device_hostname(self, device): """Extract hostname from device""" return device.hostname + def clean_parent(self, parent): + if parent is None: + return parent + if parent.device_hostname != self.device_hostname: + raise exc.ValidationError({ + 'parent': 'Parent\'s device does not match device with host name \"%s\"'%(self.device_hostname) + }) + return parent + + def clean_fields(self, exclude=None): self.site_id = self.clean_site(self.site_id) self.name = self.clean_name(self.name) @@ -1515,6 +1525,8 @@ def clean_fields(self, exclude=None): self.speed = self.clean_speed(self.speed) self.mac_address = self.clean_mac_address(self.mac_address) self.device_hostname = self.clean_device_hostname(self.device) + self.parent = self.clean_parent(self.parent) + def save(self, *args, **kwargs): # We don't want to validate unique because we want the IntegrityError diff --git a/tests/api_tests/test_interfaces.py b/tests/api_tests/test_interfaces.py index 03c03dfa..cffb645a 100644 --- a/tests/api_tests/test_interfaces.py +++ b/tests/api_tests/test_interfaces.py @@ -31,6 +31,9 @@ def test_creation(site, client): dev_resp = client.create(dev_uri, hostname='foo-bar1') dev = get_result(dev_resp) + dev_resp1 = client.create(dev_uri, hostname='foo-bar2') + dev1 = get_result(dev_resp1) + net_resp = client.create(net_uri, cidr='10.1.1.0/24') net = get_result(net_resp) @@ -56,6 +59,13 @@ def test_creation(site, client): assert_created(ifc1_resp, ifc1_obj_uri) + # Verify that creating a device with parent as + # ifc1 but device as foo-bar2 will cause error + assert_error( + client.create(ifc_uri, device=dev1['id'], name='eth0.1', parent_id=ifc1['id']), + status.HTTP_400_BAD_REQUEST + ) + # Make sure MAC is None assert ifc1['mac_address'] is None @@ -199,14 +209,12 @@ def test_tree_traversal(site, client): uri = reverse('interface-parent', args=(site.id, ifc5['id'])) assert_success(client.retrieve(uri), expected) - # test sibling for interfaces with None as parent and attached - # to different devices + # test sibling for interfaces with None as parent and attached to different devices expected = [ifc7] uri = reverse('interface-siblings', args=(site.id, ifc6['id'])) assert_success(client.retrieve(uri), expected) - def test_creation_with_addresses(site, client): """Test creating an Interface w/ addresses.""" ifc_uri = site.list_uri('interface') From b9f8c262682337b86020c76d25797d8462b93dfc Mon Sep 17 00:00:00 2001 From: Rakib Hasan Date: Thu, 25 May 2017 23:12:37 -0400 Subject: [PATCH 6/7] Changes include following - Updated docstrings for parent and added docstrings for other view functions - Changing ValidationError message for setting siblings to use a double quoted string - Also returning a queryset from get_siblings instead of a list --- nsot/api/views.py | 8 ++++++-- nsot/models.py | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/nsot/api/views.py b/nsot/api/views.py index 87343f7a..3e84d422 100644 --- a/nsot/api/views.py +++ b/nsot/api/views.py @@ -686,7 +686,7 @@ def networks(self, request, pk=None, site_pk=None, *args, **kwargs): @detail_route(methods=['get']) def parent(self, request, pk=None, site_pk=None, *args, **kwargs): - """Return parent of interface""" + """Return the parent of this Interface.""" interface = self.get_resource_object(pk, site_pk) parent = interface.parent if parent is not None: @@ -697,27 +697,31 @@ def parent(self, request, pk=None, site_pk=None, *args, **kwargs): @detail_route(methods=['get']) def ancestors(self, request, pk=None, site_pk=None, *args, **kwargs): - """Return all ancestors of interface""" + """Return all the ancestors of this Interface.""" interface = self.get_resource_object(pk, site_pk) return self.list(request, queryset=interface.get_ancestors(), *args, **kwargs) @detail_route(methods=['get']) def children(self, request, pk=None, site_pk=None, *args, **kwargs): + """Return all the children of this Interface.""" interface = self.get_resource_object(pk, site_pk) return self.list(request, queryset=interface.get_children(), *args, **kwargs) @detail_route(methods=['get']) def descendants(self, request, pk=None, site_pk=None, *args, **kwargs): + """Return all the descendants of this Interface.""" interface = self.get_resource_object(pk, site_pk) return self.list(request, queryset=interface.get_descendants(), *args, **kwargs) @detail_route(methods=['get']) def siblings(self, request, pk=None, site_pk=None, *args, **kwargs): + """Return all the siblings of this Interface.""" interface = self.get_resource_object(pk, site_pk) return self.list(request, queryset=interface.get_siblings(), *args, **kwargs) @detail_route(methods=['get']) def root(self, request, pk=None, site_pk=None, *args, **kwargs): + """Return the root of the tree this Interface is part of.""" interface = self.get_resource_object(pk, site_pk) root = interface.get_root() pk = root.id diff --git a/nsot/models.py b/nsot/models.py index 8607b5b9..db43abe2 100644 --- a/nsot/models.py +++ b/nsot/models.py @@ -1429,7 +1429,7 @@ def get_root(self): def get_siblings(self): """Return the interfaces with same parent as an interface""" - return list(Interface.objects.filter(parent=self.parent, device=self.device).exclude(id=self.id)) + return Interface.objects.filter(parent=self.parent, device=self.device).exclude(id=self.id) def get_assignments(self): """Return a list of information about my assigned addresses.""" @@ -1513,7 +1513,7 @@ def clean_parent(self, parent): return parent if parent.device_hostname != self.device_hostname: raise exc.ValidationError({ - 'parent': 'Parent\'s device does not match device with host name \"%s\"'%(self.device_hostname) + 'parent': "Parent's device does not match device with host name %r"%self.device_hostname }) return parent From 80c60e929b0252bde57e0ea40d8fd40cf4336c12 Mon Sep 17 00:00:00 2001 From: Rakib Hasan Date: Thu, 25 May 2017 23:28:08 -0400 Subject: [PATCH 7/7] Changes include following - Update the docstring of some Interface model functions - Return queryset from get_descendants and get_ancestors in Interface model --- nsot/api/views.py | 2 +- nsot/models.py | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/nsot/api/views.py b/nsot/api/views.py index 3e84d422..499fe8ca 100644 --- a/nsot/api/views.py +++ b/nsot/api/views.py @@ -703,7 +703,7 @@ def ancestors(self, request, pk=None, site_pk=None, *args, **kwargs): @detail_route(methods=['get']) def children(self, request, pk=None, site_pk=None, *args, **kwargs): - """Return all the children of this Interface.""" + """Return all the immediate children of this Interface.""" interface = self.get_resource_object(pk, site_pk) return self.list(request, queryset=interface.get_children(), *args, **kwargs) diff --git a/nsot/models.py b/nsot/models.py index db43abe2..18fd6e48 100644 --- a/nsot/models.py +++ b/nsot/models.py @@ -1397,38 +1397,40 @@ def set_addresses(self, addresses, overwrite=False, partial=False): self.clean_addresses() def get_ancestors(self): - """Recursively get all parents of an interface""" + """Return all ancestors of an Interface.""" p = self.parent ancestors = [] while p is not None: ancestors.append(p) p = p.parent - return ancestors + ancestor_ids = [a.id for a in ancestors] + return Interface.objects.filter(id__in=ancestor_ids) def get_children(self): - """Return the immediate children of an interface""" + """Return the immediate children of an Interface.""" return Interface.objects.filter(parent=self) def get_descendants(self): - """Recursively return all the children of an interface""" + """Return all the descendants of an Interface.""" s = list(self.get_children()) - children = [] + descendants = [] while len(s) > 0: top = s.pop() - children.append(top) + descendants.append(top) for c in top.get_children(): s.append(c) - return children + descendant_ids = [c.id for c in descendants] + return Interface.objects.filter(id__in=descendant_ids) def get_root(self): - """Return the parent of all ancestors of an interface""" + """Return the parent of all ancestors of an Interface.""" root = self while root.parent is not None: root = root.parent return root def get_siblings(self): - """Return the interfaces with same parent as an interface""" + """Return Interfaces with the same parent and device id as an Interface.""" return Interface.objects.filter(parent=self.parent, device=self.device).exclude(id=self.id) def get_assignments(self):