Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Updates custom relationships to be compatible with Nautobot 2 #120

Merged
merged 3 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions nautobot_design_builder/design.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from nautobot_design_builder import errors
from nautobot_design_builder import ext
from nautobot_design_builder.logging import LoggingMixin
from nautobot_design_builder.fields import field_factory
from nautobot_design_builder.fields import CustomRelationshipField, field_factory


class Journal:
Expand Down Expand Up @@ -448,8 +448,18 @@ def refresh_custom_relationships(self):
for direction in Relationship.objects.get_for_model(self.model_class):
for relationship in direction:
field = field_factory(self, relationship)
field.__set_name__(self, relationship.slug)
setattr(self.__class__, relationship.slug, field)

# make sure not to mask non-custom relationship fields that
# may have the same key name or field name
for attr_name in [field.key_name, field.field_name]:
if hasattr(self.__class__, attr_name):
# if there is already an attribute with the same name,
# delete it if it is a custom relationship, that way
# we reload the config from the database.
if isinstance(getattr(self.__class__, attr_name), CustomRelationshipField):
delattr(self.__class__, attr_name)
if not hasattr(self.__class__, attr_name):
setattr(self.__class__, attr_name, field)

def __str__(self):
"""Get the model class name."""
Expand Down
10 changes: 10 additions & 0 deletions nautobot_design_builder/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,12 @@

from taggit.managers import TaggableManager

from nautobot.core.graphql.utils import str_to_var_name
from nautobot.extras.models import Relationship, RelationshipAssociation

from nautobot_design_builder.errors import DesignImplementationError
from nautobot_design_builder.debug import debug_set
from nautobot_design_builder.util import nautobot_version

if TYPE_CHECKING:
from .design import ModelInstance
Expand Down Expand Up @@ -282,10 +284,18 @@ def __init__(self, model_class, relationship: Relationship):
relationship (Relationship): The Nautobot custom relationship backing this field.
"""
self.relationship = relationship
field_name = ""
if self.relationship.source_type == ContentType.objects.get_for_model(model_class.model_class):
self.related_model = relationship.destination_type.model_class()
field_name = str(self.relationship.get_label("source"))
else:
self.related_model = relationship.source_type.model_class()
field_name = str(self.relationship.get_label("destination"))
self.__set_name__(model_class, str_to_var_name(field_name))
if nautobot_version < "2.0.0":
self.key_name = self.relationship.slug
else:
self.key_name = self.relationship.key

@debug_set
def __set__(self, obj: "ModelInstance", values): # noqa:D105
Expand Down
9 changes: 9 additions & 0 deletions nautobot_design_builder/tests/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ def check_equal(test, check, index):
test.assertEqual(value0[0], value1[0], msg=f"Check {index}")
test.assertEqual(value0, value1, msg=f"Check {index}")

@staticmethod
def check_count_equal(test, check, index):
"""Check that two values are equal."""
value0 = _get_value(check[0])
value1 = _get_value(check[1])
if len(value0) == 1 and len(value1) == 1:
test.assertEqual(value0[0], value1[0], msg=f"Check {index}")
test.assertCountEqual(value0, value1, msg=f"Check {index}")

@staticmethod
def check_model_exists(test, check, index):
"""Check that a model exists."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
extensions:
- "nautobot_design_builder.contrib.ext.LookupExtension"
designs:
- relationships:
- name: "Device to VLANS"
slug: "device-to-vlans"
type: "many-to-many"
"!lookup:source_type":
app_label: "dcim"
model: "device"
"!lookup:destination_type":
app_label: "ipam"
model: "vlan"
manufacturers:
- name: "manufacturer1"

device_types:
- manufacturer__name: "manufacturer1"
model: "model name"
u_height: 1

device_roles:
- name: "device role"

sites:
- name: "site_1"
status__name: "Active"

vlans:
- "!create_or_update:vid": 42
name: "The Answer"
status__name: "Active"

devices:
- name: "device_1"
site__name: "site_1"
status__name: "Active"
device_type__model: "model name"
device_role__name: "device role"
vlans:
- "!get:vid": 42
- vid: "43"
name: "Better Answer"
status__name: "Active"
checks:
- model_exists:
model: "nautobot.ipam.models.VLAN"
query: {vid: "43"}

- equal:
- model: "nautobot.extras.models.RelationshipAssociation"
query: {relationship__name: "Device to VLANS"}
attribute: "destination"
- model: "nautobot.ipam.models.VLAN"
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
extensions:
- "nautobot_design_builder.contrib.ext.LookupExtension"
designs:
- relationships:
- label: "Device to VLANS"
key: "device_to_vlans"
type: "many-to-many"
"!lookup:source_type":
app_label: "dcim"
model: "device"
"!lookup:destination_type":
app_label: "ipam"
model: "vlan"

manufacturers:
- name: "manufacturer1"

device_types:
- manufacturer__name: "manufacturer1"
model: "model name"
u_height: 1

roles:
- name: "device role"
content_types:
- "!get:app_label": "dcim"
"!get:model": "device"

location_types:
- name: "Site"
content_types:
- "!get:app_label": "dcim"
"!get:model": "device"
locations:
- name: "site_1"
status__name: "Active"

vlans:
- "!create_or_update:vid": 42
name: "The Answer"
status__name: "Active"

devices:
- name: "device_1"
location__name: "site_1"
status__name: "Active"
device_type__model: "model name"
role__name: "device role"
device_to_vlans:
- "!get:vid": 42
- vid: "43"
name: "Better Answer"
status__name: "Active"
checks:
- model_exists:
model: "nautobot.ipam.models.VLAN"
query: {vid: "43"}

- count_equal:
- model: "nautobot.extras.models.RelationshipAssociation"
query: {relationship__label: "Device to VLANS"}
attribute: "destination"
- model: "nautobot.ipam.models.VLAN"
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
extensions:
- "nautobot_design_builder.contrib.ext.LookupExtension"
designs:
- relationships:
- label: "Device to VLANS"
key: "device_to_vlans"
type: "many-to-many"
"!lookup:source_type":
app_label: "dcim"
model: "device"
"!lookup:destination_type":
app_label: "ipam"
model: "vlan"

manufacturers:
- name: "manufacturer1"

device_types:
- manufacturer__name: "manufacturer1"
model: "model name"
u_height: 1

roles:
- name: "device role"
content_types:
- "!get:app_label": "dcim"
"!get:model": "device"

location_types:
- name: "Site"
content_types:
- "!get:app_label": "dcim"
"!get:model": "device"
locations:
- name: "site_1"
status__name: "Active"

vlans:
- "!create_or_update:vid": 42
name: "The Answer"
status__name: "Active"

devices:
- name: "device_1"
location__name: "site_1"
status__name: "Active"
device_type__model: "model name"
role__name: "device role"
vlans:
- "!get:vid": 42
- vid: "43"
name: "Better Answer"
status__name: "Active"
checks:
- model_exists:
model: "nautobot.ipam.models.VLAN"
query: {vid: "43"}

- count_equal:
- model: "nautobot.extras.models.RelationshipAssociation"
query: {relationship__label: "Device to VLANS"}
attribute: "destination"
- model: "nautobot.ipam.models.VLAN"
Loading