Skip to content

DCIM

Replace Site and Region with Location Model

In Nautobot 2.0.0, all the Region and Site related data models are being migrated to use Location. Below is a comprehensive guide for Nautobot App developers to migrate their Region and Site related data models to Location.

We will be using ExampleModel as a relatively simple and hands-on example throughout this guide to better your understanding of the migration process.

Before you Begin

Warning

You must perform these steps before proceeding. Failing to follow them properly could result in data loss. Always backup your database before performing any migration steps.

Before you follow the guide, please make sure that these operations are completed:

  1. Make sure your working Nautobot is on Version 1.5.x with baseline migrations all run.
  2. Stop Nautobot Server.
  3. Create a backup of your Nautobot database.
  4. Update installed version of Nautobot to 2.0.0.
  5. Run nautobot-server migrate dcim 0034_migrate_region_and_site_data_to_locations. (This operation will ensure that ("dcim", "0034_migrate_region_and_site_data_to_locations") is the latest migration applied to your Nautobot instance and that ("dcim", "0034_remove_region_and_site") is not applied. Failure to complete this step will result in data loss)

After you complete those operations, follow the guide below for each of your installed apps to:

  1. Make all necessary migration files for each app.
  2. Run nautobot-server migrate [app_name] to apply those migration files to each app.
  3. Finally, Start Nautobot Server after all the migration files are applied and all your affected apps are updated.

If the ExampleModel currently has a site ForeignKey field but it does not have a location ForeignKey field, you will need to add the location field before any other migrations in this guide.

Note

You can skip this step only if your data models already have both a site (or region) field and a location field.

# models.py

class ExampleModel(OrganizationalModel):
    site = models.ForeignKey(
        to="dcim.Site",
        on_delete=models.PROTECT,
        blank=True,
        null=True,
    )
    name = models.CharField(max_length=20, help_text="The name of this Example.")
...

DO NOT delete the site ForeignKey field yet. As a first step, just add a ForeignKey to dcim.Location with all other arguments identical to the existing dcim.Site ForeignKey:

# models.py

class ExampleModel(OrganizationalModel):
    site = models.ForeignKey(
        to="dcim.Site",
        on_delete=models.PROTECT,
        blank=True,
        null=True,
    )
    location = models.ForeignKey(
        to="dcim.Location",
        on_delete=models.PROTECT,
        blank=True,
        null=True,
    )
    name = models.CharField(max_length=20, help_text="The name of this Example.")
...

Make the migration file by running nautobot-server makemigrations [app_name] -n [migration_name], for example:

nautobot-server makemigrations example_app -n add_location_field_to_example_model

Create an Empty Migration File and Write the Data Migration

After you make sure that all Site/Region related models have location fields on them, it is time to migrate Site/Region references in your data to Location.

Django doesn't automatically know how to do this; we have to create an empty migration file and write the migration script ourselves. This is also known as a data migration.

Create a migration file first by running nautobot-server makemigrations [app_name] -n [migration_file_name] --empty, for example:

nautobot-server makemigrations example_app -n migrate_app_data_from_site_to_location --empty

The empty migration file will look like this with the only dependency being our previous migration that added a location ForeignKey field to our ExampleModel:

# Generated by Django 3.2.17 on 2023-02-22 15:38
# 0008_migrate_example_model_data_from_site_to_location

from django.db import migrations

class Migration(migrations.Migration):
    dependencies = [
        ("example_app", "0007_add_location_field_to_example_model"),
    ]
    operations = []

Warning

First we need to add a mandatory dependency to a Nautobot 2.0 migration file, namely ("dcim", "0034_migrate_region_and_site_data_to_locations"). This dependent migration is very important as it creates the Location and LocationType records corresponding to the existing Site/Region records, which you will need to reference to migrate your data. Without it, your data migration might not work!

    dependencies = [
        # The dcim migration creates the Site Type and Region Type Locations that
        # your data models are migrating to. It has to be run **before** this migration.
        ("dcim", "0034_migrate_region_and_site_data_to_locations"),
        ("example_app", "0007_add_location_field_to_example_model"),
    ]

Before we write the function that will perform the data migration, please note that Nautobot's dcim 0029 migration helpfully added and populated a Foreign Key called migrated_location on all Region and Site records. migrated_location stores the new location records that have the same names and other attributes as their respective Sites. That means all you need to do is query ExampleModel instances that have non-null site fields and null location fields and point the location field on your object to the site's migrated_location attribute, for example:

example_model.location = example_model.site.migrated_location

Below is what the function might look like:

def migrate_example_model_data_to_locations(apps, schema_editor):
    # Always use the provided `apps` to look up models
    # rather than importing them directly!
    ExampleModel = apps.get_model("example_app", "examplemodel")
    LocationType = apps.get_model("dcim", "locationtype")
    Location = apps.get_model("dcim", "location")

    # Query ExampleModel instances with non-null site field
    example_models = ExampleModel.objects.filter(
        site__isnull=False, location__isnull=True
    ).select_related("site", "location")
    for example_model in example_models:
        # Point the location field to the corresponding
        # "Site" LocationType Location stored in migrate_location
        example_model.location = example_model.site.migrated_location
    ExampleModel.objects.bulk_update(example_models, ["location"], 1000)

Finally, we need to add migrations.RunPython to the operations attribute in the migration class to execute this function when the migration is applied:

    operations = [
        migrations.RunPython(
            # Execute the function
            code=migrate_example_model_data_to_locations,
            reverse_code=migrations.operations.special.RunPython.noop,
        )
    ]

The final migration file might look like this:

# Generated by Django 3.2.17 on 2023-02-22 15:38
# 0008_migrate_example_model_data_from_site_to_location

from django.db import migrations

def migrate_example_model_data_to_locations(apps, schema_editor):

    ExampleModel = apps.get_model("example_app", "examplemodel")
    LocationType = apps.get_model("dcim", "locationtype")
    Location = apps.get_model("dcim", "location")
    # Get "Site" LocationType
    site_location_type = LocationType.objects.get(name="Site")

    # Query ExampleModel instances with non-null site field
    example_models = ExampleModel.objects.filter(
        site__isnull=False, location__isnull=True
    ).select_related("site", "location")
    for example_model in example_models:
        # Point the location field to the corresponding "Site" LocationType Location
        # with the same name.
        example_model.location = example_model.site.migrated_location
    ExampleModel.objects.bulk_update(example_models, ["location"], 1000)

class Migration(migrations.Migration):
    dependencies = [
        # The dcim migration creates the Site Type and Region Type Locations that
        # your data models are migrating to.
        # Therefore, It has to be run **before** this migration.
        ("dcim", "0034_migrate_region_and_site_data_to_locations"),
        ("example_app", "0007_add_location_field_to_example_model"),
    ]
    operations = [
        migrations.RunPython(
            # Execute the function
            code=migrate_example_model_data_to_locations,
            reverse_code=migrations.operations.special.RunPython.noop,
        )
    ]

After the data migration is successful, we need to remove the site/region fields from your data model so that Nautobot will be able to remove the Site and Region models. Note that we need to remove those attributes in a separate migration file from the previous one, as it's never a good practice to combine data migrations and schema migrations in the same file. You can do this by simply removing the site/region attributes from your model class:

# models.py
class ExampleModel(OrganizationalModel):
    # note that site field is gone
    location = models.ForeignKey(
        to="dcim.Location",
        on_delete=models.PROTECT,
        blank=True,
        null=True,
    )
    name = models.CharField(max_length=20, help_text="The name of this Example.")
...

After removing the site attribute, make the migration file by running nautobot-server makemigrations [app_name] -n [migration_name], for example:

nautobot-server makemigrations example_app -n remove_site_field_from_example_model

The migration file might look like this:

# Generated by Django 3.2.17 on 2023-02-22 17:09
# 0009_remove_site_field_from_example_model.py

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ('example_app', '0008_migrate_example_model_data_from_site_to_location'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='examplemodel',
            name='site',
        ),
    ]

Important

Before you apply this migration, you have to add a run_before attribute in this migration file to make sure that you remove site/region fields before Site and Region models themselves are removed. Without it, your migration files might be out of order and your app will not start.

    # Ensure this migration is run before the migration that removes Region and Site Models
    run_before = [
        ("dcim", "0040_remove_region_and_site"),
    ]

The final migration file might look like this:

# Generated by Django 3.2.17 on 2023-02-22 17:09
# 0009_remove_site_field_from_example_model.py

from django.db import migrations

class Migration(migrations.Migration):

    # Ensure this migration is run before the migration that removes Region and Site Models
    run_before = [
        ("dcim", "0040_remove_region_and_site"),
    ]
    dependencies = [
        ("example_app", "0008_migrate_example_model_data_from_site_to_location"),
    ]

    operations = [
        migrations.RemoveField(
            model_name="examplemodel",
            name="site",
        ),
    ]

Apply the migration files by running nautobot-server migrate [app_name], for example:

nautobot-server migrate example_app