# Migrating to Nautobot from NetBox

## Review the Release Notes

Be sure to carefully review all [release notes](../../release-notes/) that have been published. In particular, the [Nautobot 1.0 release notes](../../release-notes/version-1.0/) include an overview of key changes between NetBox 2.10 and Nautobot 1.0, while later release notes highlight incremental changes between Nautobot versions.

## Install Nautobot

Install Nautobot as described in [the documentation](../../installation/).

## Configure Nautobot

Although Nautobot will run perfectly well with a default configuration (such as generated by `nautobot-server init`, you may want to replicate aspects of your previous NetBox configuration to Nautobot. Refer to the [configuration documentation](../../configuration/) for details on the available options.

## Migrate Database Contents Using `nautobot-netbox-importer`

Due to a number of significant infrastructural changes between the applications, you cannot simply point Nautobot at your existing NetBox PostgreSQL database and have it automatically load your data. Fortunately, Network to Code (NTC) and collaborators have developed a Nautobot plugin, `nautobot-netbox-importer`, that can be used to import a NetBox database dump file into Nautobot. For full details, refer to [the plugin's own documentation](https://github.com/nautobot/nautobot-plugin-netbox-importer/), but here is a brief overview:

1. Export your NetBox database to a JSON file.
2. Install the importer plugin.
3. Enable the importer plugin.
4. Run the plugin's import command to import the data.
5. Connect to Nautobot and verify that your data has been successfully imported.

## Migrate Files from NetBox to Nautobot

Uploaded media (device images, etc.) are stored on the filesystem rather than in the database and hence need to be migrated separately. The same is true for custom scripts and reports that you may wish to import.

### Copy Uploaded Media

The exact command will depend on where your [`MEDIA_ROOT`](../../configuration/optional-settings/#media_root) is configured in NetBox as well as where it's configured in Nautobot, but in general it will be:

```no-highlight
cp -pr $NETBOX_MEDIA_ROOT/* $NAUTOBOT_MEDIA_ROOT/*
```

### Copy Custom Scripts and Reports

Similarly, the exact commands depend on your `SCRIPTS_ROOT` and `REPORTS_ROOT` settings in NetBox and your [`JOBS_ROOT`](../../configuration/optional-settings/#jobs_root) in Nautobot, but in general they will be:

```no-highlight
cp -pr $NETBOX_SCRIPTS_ROOT/* $NAUTOBOT_JOBS_ROOT/
cp -pr $NETBOX_REPORTS_ROOT/* $NAUTOBOT_JOBS_ROOT/
```

### Update Scripts, Reports, and Plugins for Nautobot compatibility

Depending on the complexity of your scripts, reports, or plugins, and how tightly integrated with NetBox they were, it may be simple or complex to port them to be compatible with Nautobot, and we cannot possibly provide a generalized step-by-step guide that would cover all possibilities. One change that you will certainly have to make to even begin this process, however, is updating the Python module names for any modules that were being imported from NetBox:

- `circuits.* -> nautobot.circuits.*`
- `dcim.* -> nautobot.dcim.*`
- `extras.* -> nautobot.extras.*`
- `ipam.* -> nautobot.ipam.*`
- `netbox.* -> nautobot.core.*`
- `tenancy.* -> nautobot.tenancy.*`
- `utilities.* -> nautobot.utilities.*`
- `virtualization.* -> nautobot.virtualization.*`

### Update Your other Integration Code

If you have developed any custom integrations or plugins you may need to update some of your calls. Please see the data model changes below for guidance.

## Data Model Changes

The following backwards-incompatible changes have been made to the data model in Nautobot. 

### Status Fields

!!! tip
    Status names are now lower-cased when setting the `status` field on CSV imports. The `slug` value is used for create/update of objects and for filtering in the API.

A new [`Status`](../models/extras/status.md) model has been added to represent the `status` field for many models. Each status has a human-readable `name` field (e.g. `Active`), and a `slug` field (e.g. `active`).

### Display name

Several models such as device type and VLAN exposed a `display_name` property, which has now been renamed to `display`. In fact, there are several other instances, especially in the REST API, where the `display_name` field was used and as such, all instances have been renamed to `display`.

#### CSV Imports

When using CSV import to define a `status` field on imported objects, such as when importing Devices or Prefixes, the `Status.slug` field is used.

For example, the built-in **Active** status has a slug of `active`, so the `active` value would be used for import.

#### Default Choices

Because `status` fields are now stored in the database, they cannot have a default value, just like other similar objects like Device Roles or Device Types. In cases where `status` was not required to be set because it would use the default value, you must now provide a `status` yourself.  

!!! note
	For consistency in the API, the `slug` value of a `status` is used when creating or updating an object.

#### Choices in Code

All `*StatusChoices` enums used for populated `status` field choices (such as `nautobot.dcim.choices.DeviceStatusChoices`) are deprecated. Any code you have that is leveraging these will now result in an error when performing lookups on objects with `status` fields.

Anywhere you have code like this:

```python
from dcim.choices import DeviceStatusChoices
from dcim.models import Device

Device.objects.filter(status=DeviceStatusChoices.STATUS_PLANNED)
```

Update it to this:

```python
from extras.models import Status
from dcim.models import Device

Device.objects.filter(status=Status.objects.get(slug="planned"))
```

### UUID Primary Database Keys

!!! tip
    Primary key (aka ID) fields are no longer auto-incrementing integers and are now randomly-generated UUIDs.

Database keys are now defined as randomly-generated [Universally Unique Identifiers](https://tools.ietf.org/html/rfc4122.html) (UUIDs) instead of integers, protecting against certain classes of data-traversal attacks.

### Merge of UserConfig data into User model

There is no longer a distinct `UserConfig` model; instead, user configuration and preferences are stored directly on the `User` model under the key `config_data`.

### Custom Fields

!!! tip
	You can no longer rename or change the type of a custom field.

Custom Fields have been overhauled for asserting data integrity and improving user experience.

- Custom Fields can no longer be renamed or have their type changed after they have been created.
- Choices for Custom Fields are now stored as discrete `CustomFieldChoice` database objects. Choices that are in active use cannot be deleted.

### IPAM Network Field Types

!!! tip
	IPAM network objects have been overhauled and network membership queries using nested filter expressions and custom lookups will need to be updated in your code.

All IPAM objects with network field types (`ipam.Aggregate`, `ipam.IPAddress`, and `ipam.Prefix`) are no longer hard-coded to use PostgreSQL-only `inet` or `cidr` field types and are now using a custom implementation leveraging SQL-standard `varbinary` field types.

#### Technical Details

Below is a summary of the underlying technical changes to network fields. These will be explained in more detail in the following sections.

- For `IPAddress`, the `address` field was exploded out to `host`, `broadcast`, and `prefix_length` fields; `address` was converted into a computed field.
- For `Aggregate` and `Prefix` objects, the `prefix` field was exploded out to `network`, `broadcast`, and `prefix_length` fields; `prefix` was converted into a computed field.
- The `host`, `network`, and `broadcast` fields are now of a `varbinary` database type, which is represented as a packed binary integer (for example, the host `1.1.1.1` is packed as `b"\x01\x01\x01\x01"`)
- Network membership queries are accomplished by triangulating the "position" of an address using the IP, broadcast, and prefix length of the source and target addresses.

!!! note
	You should never have to worry about the binary nature of how the network fields are stored in the database! The Django database ORM takes care of it all!

#### Changes to `IPAddress`

The following fields have changed when working with `ipam.IPAddress` objects:

##### `address` is now a computed field

This field is computed from `{host}/{prefix_length}` and is represented as a `netaddr.IPNetwork` object. 

```python
>>> ip = IPAddress(address="1.1.1.1/30")
>>> ip.address
IPNetwork('1.1.1.1/30')
```

While this field is now a virtual field, it can still be used in many ways. 

It can be used to create objects:

```python
>>> ip = IPAddress.objects.create(address="1.1.1.1/30")
```

It can be used in `.get()` and `.filter()` lookups where `address` is the primary argument:

```python
>>> IPAddress.objects.get(address="1.1.1.1/30")
IPNetwork('1.1.1.1/30')
>>> IPAddress.objects.filter(address="1.1.1.1/30")
<IPAddressQuerySet [<IPAddress: 1.1.1.1/30>]>
```

!!! note
	If you use a `prefix_length` other than `/32` (IPv4) or `/128` (IPv6) it **must** be included in your lookups

This field *cannot be used in **nested** filter expressions*: 

```python
>>> Device.objects.filter(primary_ip4__address="1.1.1.1")
django.core.exceptions.FieldError: Related Field got invalid lookup: address
```

##### `host` contains the IP address 

The IP (host) component of the address is now stored in the `host` field. 

```python
>>> ip.host
'1.1.1.1'
```

This field *can* be used in nested filter expressions, for example:

```python
>>> Device.objects.filter(primary_ip4__host="1.1.1.1")
```

##### `prefix_length` contains the prefix length 

This is an integer, such as `30` for `/30`.

```python
>>> ip.prefix_length
30
```

For IP addresses with a prefix length other than a host prefix, you will need to filter using `host` and `prefix_length` fields for greater accuracy.

For example, if you have multiple `IPAddress` objects with the same `host` value but different `prefix_length`:

```python
>>> IPAddress.objects.create(address="1.1.1.1/32")
<IPAddress: 1.1.1.1/32>
>>> IPAddress.objects.filter(host="1.1.1.1")
<IPAddressQuerySet [<IPAddress: 1.1.1.1/30>, <IPAddress: 1.1.1.1/32>]>
>>> IPAddress.objects.filter(host="1.1.1.1", prefix_length=30)
<IPAddressQuerySet [<IPAddress: 1.1.1.1/30>]>
```

##### `broadcast` contains the broadcast address

If the prefix length is that of a host prefix (e.g. `/32`), `broadcast` will be the same as the `host` :

```python
>>> IPAddress.objects.get(address="1.1.1.1/32").broadcast
'1.1.1.1'
```

If the prefix length is any larger (e.g. `/24`), `broadcast` will be that of the containing network for that prefix length (e.g. `1.1.1.255`):

```python
>>> IPAddress.objects.create(address="1.1.1.1/24").broadcast
'1.1.1.255'
```

!!! note
	This field is largely for internal use only for facilitating network membership queries and it is not recommend that you use it for filtering.

#### Changes to `Aggregate` and `Prefix`

The following fields have changed when working with `ipam.Aggregate` and `ipam.Prefix` objects. These objects share the same field changes. 

For these examples we will be using `Prefix` objects, but they apply just as equally to `Aggregate` objects.

##### `prefix` is now a computed field

This field is computed from `{network}/{prefix_length}` and is represented as a `netaddr.IPNetwork` object. 

While this field is now a virtual field, it can still be used in many ways. 

It can be used to create objects:

```python
>>> net = Prefix.objects.create(prefix="1.1.1.0/24")
```

It can be used in `.get()` and `.filter()` lookups where `prefix` is the primary argument:

```python
>>> Prefix.objects.get(prefix="1.1.1.0/24")
<Prefix: 1.1.1.0/24>
>>> Prefix.objects.filter(prefix="1.1.1.0/24")
<PrefixQuerySet [<Prefix: 1.1.1.0/24>]>
```

This field *cannot be used in **nested** filter expressions*!

##### `network` contains the network address 

The network component of the address is now stored in the `network` field. 

```python
>>> net.network
'1.1.1.0'
```

This field *can* be used in nested filter expressions, for example:

```python
>>> Prefix.objects.filter(network__lte="1.1.1.0")
```

##### `prefix_length` contains the prefix length 

This is an integer, such as `24` for `/24`.

```python
>>> net.prefix_length
24
```

It's highly likely that you will have multiple objects with the same `network` address but varying prefix lengths, so you will need to filter using `network` and `prefix_length` fields for greater accuracy.

For example, if you have multiple `Prefix` objects with the same `network` value but different `prefix_length`:

```python
>>> Prefix.objects.create(prefix="1.1.1.0/25")
<Prefix: 1.1.1.0/25>
>>> Prefix.objects.filter(network="1.1.1.0")
<PrefixQuerySet [<Prefix: 1.1.1.0/24>, <Prefix: 1.1.1.0/25>]>
>>> Prefix.objects.filter(network="1.1.1.0", prefix_length=25)
<PrefixQuerySet [<Prefix: 1.1.1.0/25>]>
```

##### `broadcast` contains the broadcast address

The `broadcast` will be derived from the `prefix_length` and will be that of the last network address for that prefix length (e.g. `1.1.1.255`):

```python
>>> Prefix.objects.get(prefix="1.1.1.0/24").broadcast
'1.1.1.255'
```

!!! note
	This field is largely for internal use only for facilitating network membership queries and it is not recommend that you use it for filtering.

#### Membership Lookups

Network membership queries of IPAM objects can no longer be performed using nested filter expressions. In most cases, where custom lookups were used before (such as `prefix__net_contained`), model manager methods must be used instead. In a few cases, nested model field lookups may be used with one of the newly-added database fields (such as `prefix_length`).

Each custom filter lookup from NetBox will be enumerated here illustrating how it was done before, and how it is now done within Nautobot.

---

!!! important
	The following lookups only apply to `Aggregate` and `Prefix` objects

##### net_contained

*Returns target networks that are contained by the source network.*

NetBox:

```python
Prefix.objects.filter(prefix__net_contained=str(instance.prefix))
```

Nautobot:

```python
Prefix.objects.net_contained(instance.prefix)
```

##### net_contained_or_equals

*Returns target networks that are contained or equal to the source network.*

NetBox:

```python
Prefix.objects.filter(prefix__net_contained_or_equals=str(self.prefix))
```

Nautobot:

```python
Prefix.objects.net_contained_or_equals(self.prefix)
```

##### net_contains

*Returns target networks that contain the source network.*

NetBox:

```python
Prefix.objects.filter(prefix__net_contains=str(instance.prefix))
```

Nautobot:

```python
Prefix.objects.net_contains(instance.prefix)
```

##### net_contains_or_equals

*Returns target networks that contain or equal the source network.*

NetBox:

```python
 Aggregate.objects.filter(prefix__net_contains_or_equals=str(self.prefix))
```

Nautobot:

```python
Aggregate.objects.net_contains_or_equals(self.prefix)
```

##### net_equals

*Returns target networks that equal the source network.*

NetBox:

```python
Prefix.objects.filter(prefix=str(self.prefix))
```

 Nautobot:

```python
Prefix.objects.net_equals(self.prefix)
```

---

!!! important
	The following lookups only apply to `IPAddress` objects.

##### net_host

*Returns target IP addresses that match the host of the source IP address.*

!!! note
	This custom lookup does not have a manager method equivalent. This pattern should use the `host` field for filtering.

NetBox:

```python
IPAddress.objects.filter(vrf=self.vrf, address__net_host=str(self.address.ip))
```

Nautobot:

```python
IPAddress.objects.filter(vrf=self.vrf, host=self.host)
```

##### net_host_contained

*Returns target IP addresses that are contained by the source prefix.*

NetBox:

```python
IPAddress.objects.filter(address__net_host_contained=str(self.prefix))
```

Nautobot:

```python
IPAddress.objects.net_host_contained(self.prefix)
```

##### net_in

*Returns target IP addresses that are members of any of the provided source prefixes.*

NetBox:

```python
IPAddress.objects.filter(address__net_in=[n1, n2, n3])
```

Nautobot:

```python
IPAddress.objects.net_in([n1, n2, n3])
```

##### ip_family

*Returns target addresses matching the specified family.*

NetBox:

```python
IPAddress.objects.filter(address__family=family)
```

Nautobot:

```python
IPAddress.objects.ip_family(family)
```

---

!!! important
	The following lookups apply to all network objects.

##### net_mask_length

*Returns target addresses matching the source address prefix length.*

!!! note
	This custom lookup does not have a manager method equivalent. This pattern should use the `prefix_length` field for filtering.

NetBox:

```python
IPAddress.objects.filter(address__net_mask_length=value)
# or
Prefix.objects.filter(prefix__net_mask_length=value)
```

Nautobot:

```python
IPAddress.objects.filter(prefix_length=value)
# or
Prefix.objects.filter(prefix_length=value)
```

## REST API Changes

The following backwards-incompatible changes have been made to the REST API in Nautobot. 

### Display field

In several endpoints such as device type and VLAN, a `display_name` field is used to expose a human friendly string value for the object. This field has been renamed to `display` and has been standardized across all model API endpoints.

### Custom Field Choices

Custom field choices are exposed in Nautobot at a dedicated endpoint: `/api/extras/custom-field-choices/`. This replaces the `choices` field on on the CustomField model and the subsequent endpoint: `/api/extras/custom-fields/`
