Skip to content

nautobot.apps.models

Data model classes and utilities for app implementation.

nautobot.apps.models.BaseModel

Bases: models.Model

Base model class that all models should inherit from.

This abstract base provides globally common fields and functionality.

Here we define the primary key to be a UUID field and set its default to automatically generate a random UUID value. Note however, this does not operate in the same way as a traditional auto incrementing field for which the value is issued by the database upon initial insert. In the case of the UUID field, Django creates the value upon object instantiation. This means the canonical pattern in Django of checking self.pk is None to tell if an object has been created in the actual database does not work because the object will always have the value populated prior to being saved to the database for the first time. An alternate pattern of checking not self.present_in_database can be used for the same purpose in most cases.

Source code in nautobot/core/models/__init__.py
class BaseModel(models.Model):
    """
    Base model class that all models should inherit from.

    This abstract base provides globally common fields and functionality.

    Here we define the primary key to be a UUID field and set its default to
    automatically generate a random UUID value. Note however, this does not
    operate in the same way as a traditional auto incrementing field for which
    the value is issued by the database upon initial insert. In the case of
    the UUID field, Django creates the value upon object instantiation. This
    means the canonical pattern in Django of checking `self.pk is None` to tell
    if an object has been created in the actual database does not work because
    the object will always have the value populated prior to being saved to the
    database for the first time. An alternate pattern of checking `not self.present_in_database`
    can be used for the same purpose in most cases.
    """

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, unique=True, editable=False)

    objects = BaseManager.from_queryset(RestrictedQuerySet)()

    @property
    def present_in_database(self):
        """
        True if the record exists in the database, False if it does not.
        """
        return not self._state.adding

    def get_absolute_url(self, api=False):
        """
        Return the canonical URL for this object in either the UI or the REST API.
        """

        # Iterate the pk-like fields and try to get a URL, or return None.
        fields = ["pk", "slug"]  # TODO: Eventually all PKs
        actions = ["retrieve", "detail", ""]  # TODO: Eventually all retrieve

        for field in fields:
            if not hasattr(self, field):
                continue

            for action in actions:
                route = get_route_for_model(self, action, api=api)

                try:
                    return reverse(route, kwargs={field: getattr(self, field)})
                except NoReverseMatch:
                    continue

        return AttributeError(f"Cannot find a URL for {self} ({self._meta.app_label}.{self._meta.model_name})")

    class Meta:
        abstract = True

    def validated_save(self):
        """
        Perform model validation during instance save.

        This is a convenience method that first calls `self.full_clean()` and then `self.save()`
        which in effect enforces model validation prior to saving the instance, without having
        to manually make these calls seperately. This is a slight departure from Django norms,
        but is intended to offer an optional, simplified interface for performing this common
        workflow. The intended use is for user defined Jobs run via the `nautobot-server nbshell`
        command.
        """
        self.full_clean()
        self.save()

    def natural_key(self) -> list:
        """
        Smarter default implementation of natural key construction.

        1. Handles nullable foreign keys (https://github.com/wq/django-natural-keys/issues/18)
        2. Handles variadic natural-keys (e.g. Location model - [name, parent__name, parent__parent__name, ...].)
        """
        vals = []
        for lookups in [lookup.split("__") for lookup in self.natural_key_field_lookups]:
            val = self
            for lookup in lookups:
                val = getattr(val, lookup)
                if val is None:
                    break
            vals.append(val)
        # Strip trailing Nones from vals
        while vals and vals[-1] is None:
            vals.pop()
        return vals

    @property
    def natural_key_slug(self) -> str:
        """
        Automatic "slug" string derived from this model's natural key, suitable for use in URLs etc.

        A less naïve implementation than django-natural-keys provides by default, based around URL percent-encoding.
        """
        return construct_natural_key_slug(self.natural_key())

    @classproperty  # https://github.com/PyCQA/pylint-django/issues/240
    def natural_key_field_lookups(cls):  # pylint: disable=no-self-argument
        """
        List of lookups (possibly including nested lookups for related models) that make up this model's natural key.

        BaseModel provides a "smart" implementation that tries to determine this automatically,
        but you can also explicitly set `natural_key_field_names` on a given model subclass if desired.

        This property is based on a consolidation of `django-natural-keys` `ForeignKeyModel.get_natural_key_info()`,
        `ForeignKeyModel.get_natural_key_def()`, and `ForeignKeyModel.get_natural_key_fields()`.

        Unlike `get_natural_key_def()`, this doesn't auto-exclude all AutoField and BigAutoField fields,
        but instead explicitly discounts the `id` field (only) as a candidate.
        """
        # First, figure out which local fields comprise the natural key:
        natural_key_field_names = []
        if hasattr(cls, "natural_key_field_names"):
            natural_key_field_names = cls.natural_key_field_names
        else:
            # Does this model have any new-style UniqueConstraints? If so, pick the first one
            for constraint in cls._meta.constraints:
                if isinstance(constraint, models.UniqueConstraint):
                    natural_key_field_names = constraint.fields
                    break
            else:
                # Else, does this model have any old-style unique_together? If so, pick the first one.
                if cls._meta.unique_together:
                    natural_key_field_names = cls._meta.unique_together[0]
                else:
                    # Else, do we have any individual unique=True fields? If so, pick the first one.
                    unique_fields = [field for field in cls._meta.fields if field.unique and field.name != "id"]
                    if unique_fields:
                        natural_key_field_names = (unique_fields[0].name,)

        if not natural_key_field_names:
            raise AttributeError(
                f"Unable to identify an intrinsic natural-key definition for {cls.__name__}. "
                "If there isn't at least one UniqueConstraint, unique_together, or field with unique=True, "
                "you probably need to explicitly declare the 'natural_key_field_names' for this model, "
                "or potentially override the default 'natural_key_field_lookups' implementation for this model."
            )

        # Next, for any natural key fields that have related models, get the natural key for the related model if known
        natural_key_field_lookups = []
        for field_name in natural_key_field_names:
            field = cls._meta.get_field(field_name)
            if getattr(field, "remote_field", None) is None:
                # Not a related field, so the field name is the field lookup
                natural_key_field_lookups.append(field_name)
                continue

            related_model = field.remote_field.model
            related_natural_key_field_lookups = None
            if hasattr(related_model, "natural_key_field_lookups"):
                # TODO: generic handling for self-referential case, as seen in Location
                related_natural_key_field_lookups = related_model.natural_key_field_lookups
            else:
                # Related model isn't a Nautobot model and so doesn't have a `natural_key_field_lookups`.
                # The common case we've encountered so far is the contenttypes.ContentType model:
                if related_model._meta.app_label == "contenttypes" and related_model._meta.model_name == "contenttype":
                    related_natural_key_field_lookups = ["app_label", "model"]
                # Additional special cases can be added here

            if not related_natural_key_field_lookups:
                raise AttributeError(
                    f"Unable to determine the related natural-key fields for {related_model.__name__} "
                    f"(as referenced from {cls.__name__}.{field_name}). If the related model is a non-Nautobot "
                    "model (such as ContentType) then it may be appropriate to add special-case handling for this "
                    "model in BaseModel.natural_key_field_lookups; alternately you may be able to solve this for "
                    f"a single special case by explicitly defining {cls.__name__}.natural_key_field_lookups."
                )

            for field_lookup in related_natural_key_field_lookups:
                natural_key_field_lookups.append(f"{field_name}__{field_lookup}")

        return natural_key_field_lookups

    @classmethod
    def natural_key_args_to_kwargs(cls, args):
        """
        Helper function to map a list of natural key field values to actual kwargs suitable for lookup and filtering.

        Based on `django-natural-keys` `NaturalKeyQuerySet.natural_key_kwargs()` method.
        """
        args = list(args)
        natural_key_field_lookups = cls.natural_key_field_lookups
        # Because `natural_key` strips trailing `None` from the natural key to handle the variadic-natural-key case,
        # we may need to add trailing `None` back on to make the number of args match back up.
        while len(args) < len(natural_key_field_lookups):
            args.append(None)
        # However, if we have *too many* args, that's just incorrect usage:
        if len(args) > len(natural_key_field_lookups):
            raise ValueError(
                f"Wrong number of natural-key args for {cls.__name__}.natural_key_args_to_kwargs() -- "
                f"expected no more than {len(natural_key_field_lookups)} but got {len(args)}."
            )
        return dict(zip(natural_key_field_lookups, args))

natural_key_slug: str property

Automatic "slug" string derived from this model's natural key, suitable for use in URLs etc.

A less naïve implementation than django-natural-keys provides by default, based around URL percent-encoding.

present_in_database property

True if the record exists in the database, False if it does not.

get_absolute_url(api=False)

Return the canonical URL for this object in either the UI or the REST API.

Source code in nautobot/core/models/__init__.py
def get_absolute_url(self, api=False):
    """
    Return the canonical URL for this object in either the UI or the REST API.
    """

    # Iterate the pk-like fields and try to get a URL, or return None.
    fields = ["pk", "slug"]  # TODO: Eventually all PKs
    actions = ["retrieve", "detail", ""]  # TODO: Eventually all retrieve

    for field in fields:
        if not hasattr(self, field):
            continue

        for action in actions:
            route = get_route_for_model(self, action, api=api)

            try:
                return reverse(route, kwargs={field: getattr(self, field)})
            except NoReverseMatch:
                continue

    return AttributeError(f"Cannot find a URL for {self} ({self._meta.app_label}.{self._meta.model_name})")

natural_key()

Smarter default implementation of natural key construction.

  1. Handles nullable foreign keys (https://github.com/wq/django-natural-keys/issues/18)
  2. Handles variadic natural-keys (e.g. Location model - [name, parent__name, parent__parent__name, ...].)
Source code in nautobot/core/models/__init__.py
def natural_key(self) -> list:
    """
    Smarter default implementation of natural key construction.

    1. Handles nullable foreign keys (https://github.com/wq/django-natural-keys/issues/18)
    2. Handles variadic natural-keys (e.g. Location model - [name, parent__name, parent__parent__name, ...].)
    """
    vals = []
    for lookups in [lookup.split("__") for lookup in self.natural_key_field_lookups]:
        val = self
        for lookup in lookups:
            val = getattr(val, lookup)
            if val is None:
                break
        vals.append(val)
    # Strip trailing Nones from vals
    while vals and vals[-1] is None:
        vals.pop()
    return vals

natural_key_args_to_kwargs(args) classmethod

Helper function to map a list of natural key field values to actual kwargs suitable for lookup and filtering.

Based on django-natural-keys NaturalKeyQuerySet.natural_key_kwargs() method.

Source code in nautobot/core/models/__init__.py
@classmethod
def natural_key_args_to_kwargs(cls, args):
    """
    Helper function to map a list of natural key field values to actual kwargs suitable for lookup and filtering.

    Based on `django-natural-keys` `NaturalKeyQuerySet.natural_key_kwargs()` method.
    """
    args = list(args)
    natural_key_field_lookups = cls.natural_key_field_lookups
    # Because `natural_key` strips trailing `None` from the natural key to handle the variadic-natural-key case,
    # we may need to add trailing `None` back on to make the number of args match back up.
    while len(args) < len(natural_key_field_lookups):
        args.append(None)
    # However, if we have *too many* args, that's just incorrect usage:
    if len(args) > len(natural_key_field_lookups):
        raise ValueError(
            f"Wrong number of natural-key args for {cls.__name__}.natural_key_args_to_kwargs() -- "
            f"expected no more than {len(natural_key_field_lookups)} but got {len(args)}."
        )
    return dict(zip(natural_key_field_lookups, args))

natural_key_field_lookups()

List of lookups (possibly including nested lookups for related models) that make up this model's natural key.

BaseModel provides a "smart" implementation that tries to determine this automatically, but you can also explicitly set natural_key_field_names on a given model subclass if desired.

This property is based on a consolidation of django-natural-keys ForeignKeyModel.get_natural_key_info(), ForeignKeyModel.get_natural_key_def(), and ForeignKeyModel.get_natural_key_fields().

Unlike get_natural_key_def(), this doesn't auto-exclude all AutoField and BigAutoField fields, but instead explicitly discounts the id field (only) as a candidate.

Source code in nautobot/core/models/__init__.py
@classproperty  # https://github.com/PyCQA/pylint-django/issues/240
def natural_key_field_lookups(cls):  # pylint: disable=no-self-argument
    """
    List of lookups (possibly including nested lookups for related models) that make up this model's natural key.

    BaseModel provides a "smart" implementation that tries to determine this automatically,
    but you can also explicitly set `natural_key_field_names` on a given model subclass if desired.

    This property is based on a consolidation of `django-natural-keys` `ForeignKeyModel.get_natural_key_info()`,
    `ForeignKeyModel.get_natural_key_def()`, and `ForeignKeyModel.get_natural_key_fields()`.

    Unlike `get_natural_key_def()`, this doesn't auto-exclude all AutoField and BigAutoField fields,
    but instead explicitly discounts the `id` field (only) as a candidate.
    """
    # First, figure out which local fields comprise the natural key:
    natural_key_field_names = []
    if hasattr(cls, "natural_key_field_names"):
        natural_key_field_names = cls.natural_key_field_names
    else:
        # Does this model have any new-style UniqueConstraints? If so, pick the first one
        for constraint in cls._meta.constraints:
            if isinstance(constraint, models.UniqueConstraint):
                natural_key_field_names = constraint.fields
                break
        else:
            # Else, does this model have any old-style unique_together? If so, pick the first one.
            if cls._meta.unique_together:
                natural_key_field_names = cls._meta.unique_together[0]
            else:
                # Else, do we have any individual unique=True fields? If so, pick the first one.
                unique_fields = [field for field in cls._meta.fields if field.unique and field.name != "id"]
                if unique_fields:
                    natural_key_field_names = (unique_fields[0].name,)

    if not natural_key_field_names:
        raise AttributeError(
            f"Unable to identify an intrinsic natural-key definition for {cls.__name__}. "
            "If there isn't at least one UniqueConstraint, unique_together, or field with unique=True, "
            "you probably need to explicitly declare the 'natural_key_field_names' for this model, "
            "or potentially override the default 'natural_key_field_lookups' implementation for this model."
        )

    # Next, for any natural key fields that have related models, get the natural key for the related model if known
    natural_key_field_lookups = []
    for field_name in natural_key_field_names:
        field = cls._meta.get_field(field_name)
        if getattr(field, "remote_field", None) is None:
            # Not a related field, so the field name is the field lookup
            natural_key_field_lookups.append(field_name)
            continue

        related_model = field.remote_field.model
        related_natural_key_field_lookups = None
        if hasattr(related_model, "natural_key_field_lookups"):
            # TODO: generic handling for self-referential case, as seen in Location
            related_natural_key_field_lookups = related_model.natural_key_field_lookups
        else:
            # Related model isn't a Nautobot model and so doesn't have a `natural_key_field_lookups`.
            # The common case we've encountered so far is the contenttypes.ContentType model:
            if related_model._meta.app_label == "contenttypes" and related_model._meta.model_name == "contenttype":
                related_natural_key_field_lookups = ["app_label", "model"]
            # Additional special cases can be added here

        if not related_natural_key_field_lookups:
            raise AttributeError(
                f"Unable to determine the related natural-key fields for {related_model.__name__} "
                f"(as referenced from {cls.__name__}.{field_name}). If the related model is a non-Nautobot "
                "model (such as ContentType) then it may be appropriate to add special-case handling for this "
                "model in BaseModel.natural_key_field_lookups; alternately you may be able to solve this for "
                f"a single special case by explicitly defining {cls.__name__}.natural_key_field_lookups."
            )

        for field_lookup in related_natural_key_field_lookups:
            natural_key_field_lookups.append(f"{field_name}__{field_lookup}")

    return natural_key_field_lookups

validated_save()

Perform model validation during instance save.

This is a convenience method that first calls self.full_clean() and then self.save() which in effect enforces model validation prior to saving the instance, without having to manually make these calls seperately. This is a slight departure from Django norms, but is intended to offer an optional, simplified interface for performing this common workflow. The intended use is for user defined Jobs run via the nautobot-server nbshell command.

Source code in nautobot/core/models/__init__.py
def validated_save(self):
    """
    Perform model validation during instance save.

    This is a convenience method that first calls `self.full_clean()` and then `self.save()`
    which in effect enforces model validation prior to saving the instance, without having
    to manually make these calls seperately. This is a slight departure from Django norms,
    but is intended to offer an optional, simplified interface for performing this common
    workflow. The intended use is for user defined Jobs run via the `nautobot-server nbshell`
    command.
    """
    self.full_clean()
    self.save()

nautobot.apps.models.CustomValidator

This class is used to register plugin custom model validators which act on specified models. It contains the clean method which is overridden by plugin authors to execute custom validation logic. Plugin authors must raise ValidationError within this method to trigger validation error messages which are propagated to the user. A convenience method validation_error(<message>) may be used for this purpose.

The model attribute on the class defines the model to which this validator is registered. It should be set as a string in the form <app_label>.<model_name>.

Source code in nautobot/extras/plugins/__init__.py
class CustomValidator:
    """
    This class is used to register plugin custom model validators which act on specified models. It contains the clean
    method which is overridden by plugin authors to execute custom validation logic. Plugin authors must raise
    ValidationError within this method to trigger validation error messages which are propagated to the user.
    A convenience method `validation_error(<message>)` may be used for this purpose.

    The `model` attribute on the class defines the model to which this validator is registered. It
    should be set as a string in the form `<app_label>.<model_name>`.
    """

    model = None

    def __init__(self, obj):
        self.context = {"object": obj}

    def validation_error(self, message):
        """
        Convenience method for raising `django.core.exceptions.ValidationError` which is required in order to
        trigger validation error messages which are propagated to the user.
        """
        raise ValidationError(message)

    def clean(self):
        """
        Implement custom model validation in the standard Django clean method pattern. The model instance is accessed
        with the `object` key within `self.context`, e.g. `self.context['object']`. ValidationError must be raised to
        prevent saving model instance changes, and propagate messages to the user. For convenience,
        `self.validation_error(<message>)` may be called to raise a ValidationError.
        """
        raise NotImplementedError

clean()

Implement custom model validation in the standard Django clean method pattern. The model instance is accessed with the object key within self.context, e.g. self.context['object']. ValidationError must be raised to prevent saving model instance changes, and propagate messages to the user. For convenience, self.validation_error(<message>) may be called to raise a ValidationError.

Source code in nautobot/extras/plugins/__init__.py
def clean(self):
    """
    Implement custom model validation in the standard Django clean method pattern. The model instance is accessed
    with the `object` key within `self.context`, e.g. `self.context['object']`. ValidationError must be raised to
    prevent saving model instance changes, and propagate messages to the user. For convenience,
    `self.validation_error(<message>)` may be called to raise a ValidationError.
    """
    raise NotImplementedError

validation_error(message)

Convenience method for raising django.core.exceptions.ValidationError which is required in order to trigger validation error messages which are propagated to the user.

Source code in nautobot/extras/plugins/__init__.py
def validation_error(self, message):
    """
    Convenience method for raising `django.core.exceptions.ValidationError` which is required in order to
    trigger validation error messages which are propagated to the user.
    """
    raise ValidationError(message)

nautobot.apps.models.OrganizationalModel

Bases: BaseModel, ChangeLoggedModel, CustomFieldModel, RelationshipModel, DynamicGroupMixin, NotesMixin

Base abstract model for all organizational models.

Organizational models aid the primary models by building structured relationships and logical groups, or categorizations. Organizational models do not typically represent concrete networking resources or assets, but rather they enable user specific use cases and metadata about network resources. Examples include Device Role, Rack Group, Status, Manufacturer, and Platform.

Source code in nautobot/core/models/generics.py
class OrganizationalModel(
    BaseModel, ChangeLoggedModel, CustomFieldModel, RelationshipModel, DynamicGroupMixin, NotesMixin
):
    """
    Base abstract model for all organizational models.

    Organizational models aid the primary models by building structured relationships
    and logical groups, or categorizations. Organizational models do not typically
    represent concrete networking resources or assets, but rather they enable user
    specific use cases and metadata about network resources. Examples include
    Device Role, Rack Group, Status, Manufacturer, and Platform.
    """

    class Meta:
        abstract = True

nautobot.apps.models.PrimaryModel

Bases: BaseModel, ChangeLoggedModel, CustomFieldModel, RelationshipModel, DynamicGroupMixin, NotesMixin

Base abstract model for all primary models.

A primary model is one which is materialistically relevant to the network datamodel. Such models form the basis of major elements of the data model, like Device, IP Address, Site, VLAN, Virtual Machine, etc. Primary models usually represent tangible or logical resources on the network, or within the organization.

Source code in nautobot/core/models/generics.py
class PrimaryModel(BaseModel, ChangeLoggedModel, CustomFieldModel, RelationshipModel, DynamicGroupMixin, NotesMixin):
    """
    Base abstract model for all primary models.

    A primary model is one which is materialistically relevant to the network datamodel.
    Such models form the basis of major elements of the data model, like Device,
    IP Address, Site, VLAN, Virtual Machine, etc. Primary models usually represent
    tangible or logical resources on the network, or within the organization.
    """

    tags = TagsField()

    class Meta:
        abstract = True

nautobot.apps.models.StatusField

Bases: ForeignKeyLimitedByContentTypes

Model database field that automatically limits custom choices.

The limit_choices_to for the field are automatically derived from
  • the content-type to which the field is attached (e.g. dcim.device)
Source code in nautobot/extras/models/statuses.py
class StatusField(ForeignKeyLimitedByContentTypes):
    """
    Model database field that automatically limits custom choices.

    The limit_choices_to for the field are automatically derived from:

        - the content-type to which the field is attached (e.g. `dcim.device`)
    """

    def __init__(self, *args, **kwargs):
        kwargs.setdefault("to", Status)
        kwargs.setdefault("on_delete", models.PROTECT)
        super().__init__(*args, **kwargs)

    def contribute_to_class(self, cls, *args, **kwargs):
        """
        Overload default so that we can assert that `.get_FOO_display` is
        attached to any model that is using a `StatusField`.

        Using `.contribute_to_class()` is how field objects get added to the model
        at during the instance preparation. This is also where any custom model
        methods are hooked in. So in short this method asserts that any time a
        `StatusField` is added to a model, that model also gets a
        `.get_status_display()` and a `.get_status_color()` method without
        having to define it on the model yourself.
        """
        super().contribute_to_class(cls, *args, **kwargs)

        def _get_FIELD_display(self, field):
            """
            Closure to replace default model method of the same name.

            Cargo-culted from `django.db.models.base.Model._get_FIELD_display`
            """
            choices = field.get_choices()
            value = getattr(self, field.attname)
            choices_dict = dict(make_hashable(choices))
            # force_str() to coerce lazy strings.
            return force_str(choices_dict.get(make_hashable(value), value), strings_only=True)

        # Install `.get_FOO_display()` onto the model using our own version.
        if f"get_{self.name}_display" not in cls.__dict__:
            setattr(
                cls,
                f"get_{self.name}_display",
                partialmethod(_get_FIELD_display, field=self),
            )

        def _get_FIELD_color(self, field):
            """
            Return `self.FOO.color` (where FOO is field name).

            I am added to the model via `StatusField.contribute_to_class()`.
            """
            field_method = getattr(self, field.name)
            return getattr(field_method, "color")

        # Install `.get_FOO_color()` onto the model using our own version.
        if f"get_{self.name}_color" not in cls.__dict__:
            setattr(
                cls,
                f"get_{self.name}_color",
                partialmethod(_get_FIELD_color, field=self),
            )

contribute_to_class(cls, *args, **kwargs)

Overload default so that we can assert that .get_FOO_display is attached to any model that is using a StatusField.

Using .contribute_to_class() is how field objects get added to the model at during the instance preparation. This is also where any custom model methods are hooked in. So in short this method asserts that any time a StatusField is added to a model, that model also gets a .get_status_display() and a .get_status_color() method without having to define it on the model yourself.

Source code in nautobot/extras/models/statuses.py
def contribute_to_class(self, cls, *args, **kwargs):
    """
    Overload default so that we can assert that `.get_FOO_display` is
    attached to any model that is using a `StatusField`.

    Using `.contribute_to_class()` is how field objects get added to the model
    at during the instance preparation. This is also where any custom model
    methods are hooked in. So in short this method asserts that any time a
    `StatusField` is added to a model, that model also gets a
    `.get_status_display()` and a `.get_status_color()` method without
    having to define it on the model yourself.
    """
    super().contribute_to_class(cls, *args, **kwargs)

    def _get_FIELD_display(self, field):
        """
        Closure to replace default model method of the same name.

        Cargo-culted from `django.db.models.base.Model._get_FIELD_display`
        """
        choices = field.get_choices()
        value = getattr(self, field.attname)
        choices_dict = dict(make_hashable(choices))
        # force_str() to coerce lazy strings.
        return force_str(choices_dict.get(make_hashable(value), value), strings_only=True)

    # Install `.get_FOO_display()` onto the model using our own version.
    if f"get_{self.name}_display" not in cls.__dict__:
        setattr(
            cls,
            f"get_{self.name}_display",
            partialmethod(_get_FIELD_display, field=self),
        )

    def _get_FIELD_color(self, field):
        """
        Return `self.FOO.color` (where FOO is field name).

        I am added to the model via `StatusField.contribute_to_class()`.
        """
        field_method = getattr(self, field.name)
        return getattr(field_method, "color")

    # Install `.get_FOO_color()` onto the model using our own version.
    if f"get_{self.name}_color" not in cls.__dict__:
        setattr(
            cls,
            f"get_{self.name}_color",
            partialmethod(_get_FIELD_color, field=self),
        )

nautobot.apps.models.VarbinaryIPField

Bases: models.BinaryField

IP network address

Source code in nautobot/ipam/fields.py
class VarbinaryIPField(models.BinaryField):
    """
    IP network address
    """

    description = "IP network address"

    def db_type(self, connection):
        """Returns the correct field type for a given database vendor."""

        # Use 'bytea' type for PostgreSQL.
        if connection.vendor == "postgresql":
            return "bytea"

        # Or 'varbinary' for everyone else.
        return "varbinary(16)"

    def value_to_string(self, obj):
        """IPField is serialized as str(IPAddress())"""
        value = self.value_from_object(obj)
        if not value:
            return value

        return str(self._parse_address(value))

    def _parse_address(self, value):
        """
        Parse `str`, `bytes` (varbinary), or `netaddr.IPAddress to `netaddr.IPAddress`.
        """
        try:
            int_value = int.from_bytes(value, "big")
            # Distinguish between
            # \x00\x00\x00\x01 (IPv4 0.0.0.1) and
            # \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01 (IPv6 ::1), among other cases
            version = 4 if len(value) == 4 else 6
            value = int_value
        except TypeError:
            version = None  # It's a string, IP version should be self-evident

        try:
            return netaddr.IPAddress(value, version=version)
        except netaddr.AddrFormatError:
            raise ValidationError(f"Invalid IP address format: {value}")
        except (TypeError, ValueError) as e:
            raise ValidationError(e)

    def from_db_value(self, value, expression, connection):
        """Converts DB (varbinary) to Python (str)."""
        return self.to_python(value)

    def to_python(self, value):
        """Converts `value` to Python (str)."""
        if isinstance(value, netaddr.IPAddress):
            return str(value)

        if value is None:
            return value

        return str(self._parse_address(value))

    def get_db_prep_value(self, value, connection, prepared=False):
        """Converts Python (str) to DB (varbinary)."""
        if value is None:
            return value

        # Parse the address and then pack it to binary.
        value = self._parse_address(value).packed

        # Use defaults for PostgreSQL
        if connection.vendor == "postgresql":
            return super().get_db_prep_value(value, connection, prepared)

        return value

    def form_class(self):
        return IPNetworkFormField

    def formfield(self, *args, **kwargs):
        defaults = {"form_class": self.form_class()}
        defaults.update(kwargs)
        return super().formfield(*args, **defaults)

db_type(connection)

Returns the correct field type for a given database vendor.

Source code in nautobot/ipam/fields.py
def db_type(self, connection):
    """Returns the correct field type for a given database vendor."""

    # Use 'bytea' type for PostgreSQL.
    if connection.vendor == "postgresql":
        return "bytea"

    # Or 'varbinary' for everyone else.
    return "varbinary(16)"

from_db_value(value, expression, connection)

Converts DB (varbinary) to Python (str).

Source code in nautobot/ipam/fields.py
def from_db_value(self, value, expression, connection):
    """Converts DB (varbinary) to Python (str)."""
    return self.to_python(value)

get_db_prep_value(value, connection, prepared=False)

Converts Python (str) to DB (varbinary).

Source code in nautobot/ipam/fields.py
def get_db_prep_value(self, value, connection, prepared=False):
    """Converts Python (str) to DB (varbinary)."""
    if value is None:
        return value

    # Parse the address and then pack it to binary.
    value = self._parse_address(value).packed

    # Use defaults for PostgreSQL
    if connection.vendor == "postgresql":
        return super().get_db_prep_value(value, connection, prepared)

    return value

to_python(value)

Converts value to Python (str).

Source code in nautobot/ipam/fields.py
def to_python(self, value):
    """Converts `value` to Python (str)."""
    if isinstance(value, netaddr.IPAddress):
        return str(value)

    if value is None:
        return value

    return str(self._parse_address(value))

value_to_string(obj)

IPField is serialized as str(IPAddress())

Source code in nautobot/ipam/fields.py
def value_to_string(self, obj):
    """IPField is serialized as str(IPAddress())"""
    value = self.value_from_object(obj)
    if not value:
        return value

    return str(self._parse_address(value))

nautobot.apps.models.extras_features(*features)

Decorator used to register extras provided features to a model

Source code in nautobot/extras/utils.py
def extras_features(*features):
    """
    Decorator used to register extras provided features to a model
    """

    def wrapper(model_class):
        # Initialize the model_features store if not already defined
        if "model_features" not in registry:
            registry["model_features"] = {f: collections.defaultdict(list) for f in EXTRAS_FEATURES}
        for feature in features:
            if feature in EXTRAS_FEATURES:
                app_label, model_name = model_class._meta.label_lower.split(".")
                registry["model_features"][feature][app_label].append(model_name)
            else:
                raise ValueError(f"{feature} is not a valid extras feature!")
        return model_class

    return wrapper