Model Development Checklist¶
A general best-practices checklist to follow when adding a new data model in Nautobot or adding new fields to an existing model.
Bootstrapping a new Model¶
Data Model¶
- Implement model in
nautobot.<app>.modelsmodule- Use appropriate base class and mixin(s)
- Use appropriate
@extras_featuresdecorator values - Define appropriate uniqueness constraint(s)
- Define appropriate
__str__()logic - optional Define appropriate additional
clean()logic- Be sure to always call
super().clean()as well!
- Be sure to always call
- optional Define appropriate
verbose_name/verbose_name_pluralif defaults aren't optimal
- Generate database schema migration(s) with
invoke makemigrations <app> -n <migration_name> - optional Add data migration(s) to populate default records, migrate data from existing models, etc.
- Remember: data migrations must not share a file with schema migrations or vice versa!
- Specify appropriate migration dependencies (for example, against another app that you're using models from). If you're retrieving models in a data migration with
apps.get_model("app_label.model_name"), your migration should have a dependency that ensures the model is in the desired state before your migration runs. This may only require a dependency on the migration where that model was created or, if a specific field is required on the referenced model, the migration where that field was introduced.
- optional Enhance
ConfigContextif the new model can be used as a filter criteria for assigning Devices or Virtual Machines to Config Contexts- Update ConfigContextQuerySet, ConfigContextFilterSet, ConfigContext forms, edit and detail views and HTML templates
- optional Expose any new relevant Python APIs in
nautobot.appsnamespace for App consumption
Extras Features¶
cable_terminations: Models that can be connected to another model with aCableconfig_context_owners: Models that can be assigned to theownerGenericForeignKey field on aConfigContextcustom_fields: (DEPRECATED - Usesnautobot.extras.utils.populate_model_features_registryto populate Model Features Registry) Models that support ComputedFields and CustomFields, used for limiting the choices for theCustomField.content_typesandComputedField.content_typefieldscustom_links: Models that can displayCustomLinkson the object's detail view (by default, all models that usegeneric/object_retrieve.htmlas a base template support custom links)custom_validators: Models that can support customcleanlogic by implementing aCustomValidatordynamic_groups: Models that can be assigned to aDynamicGroup, used for limiting the choices for theDynamicGroup.content_typeform fieldexport_template_owners: Models that can be assigned to theownerGenericForeignKey field on anExportTemplateexport_templates: Models that can be exported using anExportTemplate, used for limiting the choices for theExportTemplate.content_typefieldgraphql: Models that should be exposed through the GraphQL API, used to build the list of registered models to build the GraphQL schemajob_results: No longer used.locations: Models that support a foreign key toLocation, used for limiting the choices for theLocationType.content_typesfieldrelationships: (DEPRECATED - Usesnautobot.extras.utils.populate_model_features_registryto populate Model Features Registry) Models that support custom relationshipsstatuses: Models that support a foreign key toStatus, used for limiting the choices for theStatus.content_typesfieldwebhooks: Models that can be used to trigger webhooks, used for limiting the choices for theWebhook.content_typesfield
Most new models should use the custom_links, custom_validators, export_templates, graphql, and webhooks features at minimum.
REST API¶
- Implement
<Model>FilterSetclass innautobot.<app>.filtersmodule- Use appropriate base class and mixin(s)
- Define
Meta.fieldsappropriately
- Implement API
<Model>Serializerclass innautobot.<app>.api.serializersmodule- Use appropriate base class and mixin(s)
- Implement API
<Model>ViewSetclass innautobot.<app>.api.viewsmodule- Use appropriate base class and mixin(s)
- Use appropriate
select_related/prefetch_relatedfor performance
- Add API viewset to
nautobot.<app>.api.urlsmodule
UI¶
- Implement UI
<Model>Tableclass innautobot.<app>.tablesmodule- Use appropriate base class and mixin(s)
- In the vast majority of cases, a table's
Meta.fieldsshould have"pk"as the first entry and (if present as a column)"actions"as the last entry, so that these two columns appear correctly at the far left and far right of the table.
- Implement
<Model>Formclass innautobot.<app>.formsmodule- Use appropriate base class and mixin(s)
- Implement
<Model>FilterFormclass innautobot.<app>.formsmodule- Use appropriate base class and mixin(s)
- Implement
<Model>BulkEditFormclass innautobot.<app>.formsmodule- Use appropriate base class and mixin(s)
- Implement
nautobot/<app>/templates/<app>/<model>_retrieve.htmldetail template - Implement
NautobotUIViewSetsubclass innautobot.<app>.viewsmodule- Use appropriate base class and mixin(s)
- Use appropriate
select_related/prefetch_relatedfor performance
- Add UI viewset to
nautobot.<app>.urlsmodule - Add menu item in
nautobot.<app>.navigationmodule - optional Add model link and counter to home page panel in
nautobot.<app>.homepagemodule - optional Add model to
nautobot.<app>.apps.<App>Config.searchable_modelsto include it in global search
Test Coverage¶
- Implement
<Model>Factoryclass innautobot.<app>.factorymodule- Add
<Model>Factoryinvocation tonautobot-server generate_test_datacommand
- Add
- Implement
<Model>TestCaseclass innautobot.<app>.tests.test_apimodule- Use appropriate
APITestCasesbase class and mixin(s)
- Use appropriate
- Implement
<Model>TestCaseclass innautobot.<app>.tests.test_filtersmodule- Use appropriate
FilterTestCasesbase class and mixin(s)
- Use appropriate
- optional Implement
<Model>TestCaseclass innautobot.<app>.tests.test_formsmodule- Use appropriate
FormTestCasesbase class and mixin(s)
- Use appropriate
- Implement
<Model>TestCaseclass innautobot.<app>.tests.test_modelsmodule- Use appropriate
ModelTestCasesbase class and mixin(s)
- Use appropriate
- Implement
<Model>TestCaseclass innautobot.<app>.tests.test_viewsmodule- Use appropriate base class and mixin(s)
Documentation¶
- Write
nautobot/docs/user-guide/core-data-model/<app>/<model>.mduser documentation- Add document to
mkdocs.yml - Add redirect from
models/<app>/<model>.mdinmkdocs.yml(this is needed so that the "question mark" button on the model's create/edit page will resolve correctly)
- Add document to
- Write an overview of the new model in the release-note
Release Overviewsection
Adding any new Field to a Model¶
- Name the field appropriately
- For CharFields, use
CHARFIELD_MAX_LENGTHas appropriate - Generate schema migration
- Updating an existing migration is preferred if the model/migration hasn't yet shipped in a release. Once a new release has been published, its migrations may not be altered (other than for the purpose of correcting a bug).
- Add field to
<Model>Factory - Add field to
<Model>Form.fields - Add field to
<Model>BulkEditForm.fields - Add field filter(s) to
<Model>FilterSet- Follow best practices for FilterSet fields
- optional Add field to
<Model>FilterSetqfilter
- Add field to
<Model>FilterForm.fields - Add field column to
<Model>Table.fields- Keep
"pk"as the first column and"actions"(if present) as the last column - optional Add field column to
<Model>Table.default_columnsif desired
- Keep
- Add field to
nautobot/<app>/templates/<app>/<model>_retrieve.htmldetail template - Add field to test data in appropriate unit tests (including view, filter, model, and API tests)
- Add field testing in
test_api.<Model>TestCase - Add field testing in
test_filters.<Model>TestCase - Add field testing in
test_models.<Model>TestCase - Add field testing in
test_views.<Model>TestCase - optional Add field testing in
test_forms.<Model>TestCaseif applicable
- Add field testing in
- Validate that the field appears correctly in GraphQL
- If the field is not compatible with GraphQL or shouldn't be included in GraphQL it's possible to exclude a specific field in the GraphQL Type Object associated with this specific model. You can refer to the graphene-django documentation for additional information.
- Add field to model documentation with an appropriate version annotation
- optional Add field to
<Model>Serializerif default representation isn't optimal - optional Add field to
<Model>Serializer.fieldsif it's not usingfields = ["__all__"] - optional Add field to any custom create/edit template
- optional Update model
clean()with any new logic needed
Adding a Foreign Key from ModelA to ModelB¶
- Specify appropriate
related_name - Select appropriate
on_deletebehavior (SET_NULL,CASCADE,PROTECT) - Add a
DynamicModelChoiceFieldto<ModelA>Form - Add a
NaturalKeyOrPKMultipleChoiceFilterto<ModelA>FilterSet - Add a
DynamicModelMultipleChoiceField(required=False, ...)to<ModelA>FilterForm - Add a
DynamicModelChoiceField(required=False, ...)to<ModelA>BulkEditForm - Add
select_relatedto API<ModelA>ViewSet.queryset - Add
select_relatedto UI<ModelA>UIViewSet.queryset - Validate that the reverse relation is accessible through GraphQL on ModelB.
- optional Add
has_<related_name>RelatedMembershipBooleanFilterfilter to<ModelB>FilterSet - optional Add
annotate(<model_a>_count=count_related(ModelA,...)to<ModelB>ViewSet.queryset- Add
<model_a>_countfield to<ModelB>Serializer
- Add
- optional Add
<model_a>_count = LinkedCountColumn(...)to<ModelB>Tableand<ModelB>Table.fields - optional Add related object table to ModelB detail view
- For Role-related models, you must add the related object table to the Role detail view as we have a test that checks this
Adding a Status or Role field to a Model¶
- Use
StatusFieldorRoleFieldin place of a genericForeignKey - Add data migration providing default Status/Role records for this model
- Remember: data migrations must not share a file with schema migrations or vice versa!
- Specify appropriate migration dependencies (for example, against another app that you're using models from)
Adding a Many-to-Many from ModelA to ModelB¶
The through table should generally be treated as a new model (see above), but the following are most important.
- Specify appropriate
related_name - Add a
DynamicModelMultipleChoiceFieldto<ModelA>Form - Add
prefetch_relatedto API<ModelA>ViewSet.queryset - Add
prefetch_relatedto UI<ModelA>UIViewSet.queryset - Add REST API endpoint for managing the M2M
- Serializer
- FilterSet
- ViewSet
- URLs
- Tests
- Add related object table to ModelA detail view
- Validate that the forward relation is accessible through GraphQL on ModelA.
- Validate that the reverse relation is accessible through GraphQL on ModelB.
- optional Add
annotate(<model_a>_count=count_related(ModelA,...)to<ModelB>ViewSet.queryset- Add
<model_a>_countfield to<ModelB>Serializer
- Add
- optional Add
<model_a>_count = LinkedCountColumn(...)to<ModelB>Tableand<ModelB>Table.fields - optional Add related object table to ModelB detail view