Blog Detail
When developing a Nautobot App, there are multiple ways to integrate any new data models belonging to that App with the core data models provided by Nautobot itself. I’m writing to share a few quick tips about which approaches to choose.
Classes of Data Relationships
There are four basic classes of data relationships you might wish to implement in your App:
- One to One: Each record of type A relates to at most one record of type B and vice versa. For example, a
VirtualChassis
has at most oneDevice
serving as the primary for that chassis, and aDevice
is the primary for at most oneVirtualChassis
. - One to Many: Each record of type A relates to any number of records of type B, but each record of type B relates to at most one record of type A. For example, a
Location
may have manyRacks
, but eachRack
has only oneLocation
. - Many to One: The reverse of the previous class. I’m calling it out as a separate item, because in some cases it needs to be handled differently when developing an App.
- Many to Many: Any number of records of type A relate to any number of records of type B. For example, a
VRF
might have many associatedRouteTarget
records as its import and export targets, and aRouteTarget
might be reused across manyVRF
records.
Options for Implementing Data Relationships in Nautobot
The first, and seemingly easiest, approach to implement would be something like a CharField
on your App’s model (or a String
-type CustomField
added to a core model) that identifies a related record(s) by its name
, slug
, or similar natural key. I’m including this only for completeness, as really you should never do this. It has many drawbacks, notably in terms of data validation and consistency. For example, there’s no inherent guarantee that the related record exists in the first place, or that it will continue to exist so long as you have a reference to it. Nautobot is built atop a relational database and as such has first-class support for representing and tracking object relationships at the database level. You should take advantage of these features instead!
The next, and most traditional, approach is to represent data relationships using native database features such as foreign keys. This has a lot of advantages, including database validation, data consistency, and optimal performance. In most cases, this will be your preferred approach when developing new data models in your App, but there are a few cases where it isn’t possible.
The final approach, which is specific to Nautobot, is to make use of Nautobot’s Relationship
feature, which allows a user or developer to define arbitrary data relationships between any two models. This is an extremely powerful and flexible feature, and is especially useful to a Nautobot user who wishes to associate existing models in a new way, but from an App developer standpoint, it should often be your fallback choice rather than your first choice, because it lacks some of the performance advantages of native database constructs.
Implementing One-to-One Data Relationships
A one-to-one relationship between App data models, or between an App model and a core Nautobot model, should generally be implemented as a Django OneToOneField
on the appropriate App data model. This is a special case of a ForeignKey
and provides all of the same inherent performance and data consistency benefits. You can use Django features such as on_delete=models.PROTECT
or on_delete=models.CASCADE
to control how your data model will automatically respond when the other related model is deleted.
An example from the nautobot-firewall-models
App:
class CapircaPolicy(PrimaryModel):
"""CapircaPolicy model."""
device = models.OneToOneField(
to="dcim.Device",
blank=True,
null=True,
on_delete=models.CASCADE,
related_name="capirca_policy",
)
In this example, each CapircaPolicy
maps to at most one Device
, and vice versa. Deleting a Device
will result in its associated CapircaPolicy
being automatically deleted as well.
If, and only if, your App needs to define a new relationship between two core Nautobot models, you cannot use a OneToOneField
because an App cannot directly modify a core model. In this case, your fallback option would be to create a one-to-one Relationship
record as the way of adding this data relationship. This is a pretty rare case, so I don’t have a real-world example to point to, but it would conceptually be implemented using the nautobot_database_ready
signal:
def handle_nautobot_database_ready(sender, *, apps, **kwargs):
Relationship.objects.get_or_create(
slug="originating_device_to_vrf",
defaults={
"name": "Originating Device to VRF",
"type": RelationshipTypeChoices.TYPE_ONE_TO_ONE,
"source_type": ContentType.objects.get_for_model(Device),
"destination_type": ContentType.objects.get_for_model(VRF),
},
)
Implementing One-to-Many and Many-to-One Data Relationships
A one-to-many or many-to-one data relationship between two App models should be implemented as a standard Django ForeignKey
field from the “many” model to the “one” model. The same approach works for a many-to-one relationship from an App model to a core Nautobot model.
An example from the nautobot-device-lifecycle-mgmt
App:
class SoftwareLCM(PrimaryModel):
"""Software Life-Cycle Management model."""
device_platform = models.ForeignKey(
to="dcim.Platform",
on_delete=models.CASCADE,
verbose_name="Device Platform"
)
In this example, many SoftwareLCM
may all map to a single Platform
, and deleting a Platform
will automatically delete all such SoftwareLCM
records.
Because, again, an App cannot directly modify a core model, this approach cannot be used for a one-to-many relation from an App model to a core model, or between two core models, because it would require adding a ForeignKey
on the core model itself. In this case, you’ll need to create a Relationship
, as in this example from the nautobot-ssot
App’s Infoblox integration:
def nautobot_database_ready_callback(sender, *, apps, **kwargs):
# ...
# add Prefix -> VLAN Relationship
relationship_dict = {
"name": "Prefix -> VLAN",
"slug": "prefix_to_vlan",
"type": RelationshipTypeChoices.TYPE_ONE_TO_MANY,
"source_type": ContentType.objects.get_for_model(Prefix),
"source_label": "Prefix",
"destination_type": ContentType.objects.get_for_model(VLAN),
"destination_label": "VLAN",
}
Relationship.objects.get_or_create(name=relationship_dict["name"], defaults=relationship_dict)
Implementing Many-to-Many Data Relationships
A many-to-many data relationship involving App models should be implemented via a Django ManyToManyField
. An example from the nautobot-circuit-maintenance
App:
class NotificationSource(OrganizationalModel):
# ...
providers = models.ManyToManyField(
Provider,
help_text="The Provider(s) that this Notification Source applies to.",
blank=True,
)
One NotificationSource
can provide notifications for many different Providers
, and any given Provider
may have multiple distinct NotificationSources
.
Once again, the only exception is when a relationship between two core Nautobot models is desired, in which case use of a Relationship
would be required. This is another fairly rare case and so I don’t have a real-world example to point to here, but it would follow the similar pattern to the other Relationship
examples above.
Conclusion and Summary
Here’s a handy table summarizing which approach to take for various data relationships:
Model A | Model B | Cardinality | Recommended Approach |
---|---|---|---|
App model | App model | One-to-One | OneToOneField on either model |
App model | App model | One-to-Many | ForeignKey on model B |
App model | App model | Many-to-One | ForeignKey on model A |
App model | App model | Many-to-Many | ManyToManyField on either model |
App model | Core model | One-to-One | OneToOneField on model A |
App model | Core model | One-to-Many | Relationship definition |
App model | Core model | Many-to-One | ForeignKey on model A |
App model | Core model | Many-to-Many | ManyToManyField on model A |
Core model | Core model | One-to-One | Relationship definition |
Core model | Core model | One-to-Many | Relationship definition |
Core model | Core model | Many-to-One | Relationship definition |
Core model | Core model | Many-to-Many | Relationship definition |
Conclusion
I hope you’ve found this post useful. Go forth and model some data!
-Glenn
Contact Us to Learn More
Share details about yourself & someone from our team will reach out to you ASAP!