nautobot 2.4.2__py3-none-any.whl → 2.4.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nautobot/apps/filters.py +2 -0
- nautobot/circuits/filters.py +1 -1
- nautobot/circuits/templates/circuits/inc/circuit_termination.html +1 -1
- nautobot/circuits/tests/integration/test_circuit.py +135 -0
- nautobot/circuits/tests/test_models.py +5 -3
- nautobot/circuits/views.py +4 -1
- nautobot/cloud/api/views.py +3 -3
- nautobot/cloud/filters.py +3 -6
- nautobot/cloud/tests/test_filters.py +21 -0
- nautobot/core/admin.py +2 -0
- nautobot/core/constants.py +0 -1
- nautobot/core/forms/__init__.py +2 -0
- nautobot/core/forms/forms.py +2 -1
- nautobot/core/forms/widgets.py +8 -0
- nautobot/core/jobs/__init__.py +2 -1
- nautobot/core/management/commands/generate_performance_test_endpoints.py +271 -0
- nautobot/core/models/utils.py +6 -1
- nautobot/core/templates/generic/object_bulk_delete.html +1 -1
- nautobot/core/templates/generic/object_bulk_edit.html +1 -1
- nautobot/core/templates/generic/object_bulk_import.html +1 -1
- nautobot/core/templates/generic/object_create.html +5 -0
- nautobot/core/templates/generic/object_delete.html +1 -1
- nautobot/core/templates/generic/object_detail.html +1 -1
- nautobot/core/templates/generic/object_edit.html +1 -1
- nautobot/core/templates/inc/javascript.html +3 -0
- nautobot/core/templates/widgets/clearable_file.html +5 -0
- nautobot/core/templatetags/helpers.py +3 -3
- nautobot/core/templatetags/ui_framework.py +20 -4
- nautobot/core/testing/forms.py +1 -1
- nautobot/core/testing/integration.py +37 -7
- nautobot/core/tests/test_api.py +1 -1
- nautobot/core/tests/test_commands.py +31 -0
- nautobot/core/tests/test_graphql.py +3 -3
- nautobot/core/tests/test_jobs.py +4 -1
- nautobot/core/tests/test_utils.py +17 -2
- nautobot/core/ui/object_detail.py +1 -1
- nautobot/core/utils/lookup.py +12 -1
- nautobot/core/views/generic.py +9 -1
- nautobot/core/views/mixins.py +9 -1
- nautobot/dcim/api/serializers.py +36 -0
- nautobot/dcim/api/views.py +12 -11
- nautobot/dcim/elevations.py +17 -4
- nautobot/dcim/factory.py +9 -1
- nautobot/dcim/filters/__init__.py +27 -1
- nautobot/dcim/forms.py +16 -7
- nautobot/dcim/models/devices.py +12 -7
- nautobot/dcim/signals.py +26 -0
- nautobot/dcim/templates/dcim/cable_trace.html +4 -4
- nautobot/dcim/templates/dcim/consoleport.html +14 -4
- nautobot/dcim/templates/dcim/consoleserverport.html +14 -4
- nautobot/dcim/templates/dcim/device/lldp_neighbors.html +3 -3
- nautobot/dcim/templates/dcim/frontport.html +7 -2
- nautobot/dcim/templates/dcim/interface.html +9 -4
- nautobot/dcim/templates/dcim/powerfeed.html +8 -3
- nautobot/dcim/templates/dcim/poweroutlet.html +14 -4
- nautobot/dcim/templates/dcim/powerport.html +14 -4
- nautobot/dcim/templates/dcim/rearport.html +7 -2
- nautobot/dcim/templates/dcim/virtualdevicecontext_retrieve.html +0 -62
- nautobot/dcim/templates/dcim/virtualdevicecontext_update.html +6 -0
- nautobot/dcim/tests/integration/test_fileinputpicker.py +87 -0
- nautobot/dcim/tests/test_api.py +176 -0
- nautobot/dcim/tests/test_filters.py +56 -3
- nautobot/dcim/tests/test_models.py +41 -1
- nautobot/dcim/views.py +24 -14
- nautobot/extras/api/mixins.py +1 -1
- nautobot/extras/api/views.py +4 -4
- nautobot/extras/filters/__init__.py +4 -0
- nautobot/extras/forms/forms.py +4 -0
- nautobot/extras/jobs.py +8 -1
- nautobot/extras/models/datasources.py +7 -3
- nautobot/extras/plugins/__init__.py +26 -1
- nautobot/extras/templates/extras/inc/jobresult.html +12 -13
- nautobot/extras/templates/extras/job.html +1 -0
- nautobot/extras/templates/extras/objectchange.html +28 -12
- nautobot/extras/tests/test_api.py +16 -15
- nautobot/extras/tests/test_dynamicgroups.py +14 -0
- nautobot/extras/tests/test_filters.py +2 -0
- nautobot/extras/tests/test_plugins.py +32 -1
- nautobot/extras/tests/test_views.py +209 -11
- nautobot/extras/utils.py +30 -0
- nautobot/extras/views.py +32 -14
- nautobot/ipam/api/serializers.py +7 -8
- nautobot/ipam/api/views.py +5 -5
- nautobot/ipam/factory.py +27 -8
- nautobot/ipam/filters.py +67 -29
- nautobot/ipam/formfields.py +51 -0
- nautobot/ipam/forms.py +15 -7
- nautobot/ipam/migrations/0051_added_optional_vrf_relationship_to_vdc.py +41 -0
- nautobot/ipam/models.py +63 -5
- nautobot/ipam/tables.py +21 -7
- nautobot/ipam/tests/test_api.py +107 -66
- nautobot/ipam/tests/test_filters.py +145 -5
- nautobot/ipam/tests/test_views.py +15 -2
- nautobot/project-static/bootstrap-filestyle-1.2.3/bootstrap-filestyle.min.js +11 -0
- nautobot/project-static/css/base.css +11 -0
- nautobot/project-static/css/dark.css +2 -1
- nautobot/project-static/docs/apps/index.html +1 -1
- nautobot/project-static/docs/apps/nautobot-apps.html +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +62 -0
- nautobot/project-static/docs/development/apps/api/configuration-view.html +0 -3
- nautobot/project-static/docs/development/apps/api/models/graphql.html +9 -13
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +94 -1
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +2 -5
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +0 -3
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +0 -3
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +0 -3
- nautobot/project-static/docs/development/apps/api/prometheus.html +0 -3
- nautobot/project-static/docs/development/apps/api/setup.html +1 -1
- nautobot/project-static/docs/development/apps/api/testing.html +0 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +0 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +0 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +0 -3
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +0 -3
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +1 -7
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +0 -7
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +0 -4
- nautobot/project-static/docs/development/apps/api/views/notes.html +0 -3
- nautobot/project-static/docs/development/apps/index.html +2 -35
- nautobot/project-static/docs/development/apps/migration/code-updates.html +7 -6
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +2 -2
- nautobot/project-static/docs/development/apps/migration/from-v1.html +3 -3
- nautobot/project-static/docs/development/core/application-registry.html +0 -6
- nautobot/project-static/docs/development/core/best-practices.html +1 -28
- nautobot/project-static/docs/development/core/bootstrap-ui.html +1 -1
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +65 -11
- nautobot/project-static/docs/development/core/getting-started.html +14 -18
- nautobot/project-static/docs/development/core/homepage.html +0 -3
- nautobot/project-static/docs/development/core/index.html +1 -1
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +3 -3
- nautobot/project-static/docs/development/core/model-checklist.html +1 -1
- nautobot/project-static/docs/development/core/navigation-menu.html +1 -1
- nautobot/project-static/docs/development/core/release-checklist.html +1 -1
- nautobot/project-static/docs/development/core/settings.html +1 -1
- nautobot/project-static/docs/development/core/style-guide.html +4 -9
- nautobot/project-static/docs/development/core/templates.html +0 -3
- nautobot/project-static/docs/development/core/testing.html +0 -9
- nautobot/project-static/docs/development/jobs/index.html +11 -30
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +3 -2
- nautobot/project-static/docs/index.html +3 -2
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +2 -20
- nautobot/project-static/docs/release-notes/version-1.0.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.1.html +2 -2
- nautobot/project-static/docs/release-notes/version-1.2.html +3 -3
- nautobot/project-static/docs/release-notes/version-1.3.html +1 -1
- nautobot/project-static/docs/release-notes/version-1.4.html +17 -17
- nautobot/project-static/docs/release-notes/version-1.5.html +8 -8
- nautobot/project-static/docs/release-notes/version-1.6.html +4 -4
- nautobot/project-static/docs/release-notes/version-2.0.html +10 -10
- nautobot/project-static/docs/release-notes/version-2.1.html +7 -7
- nautobot/project-static/docs/release-notes/version-2.2.html +1 -1
- nautobot/project-static/docs/release-notes/version-2.3.html +4 -4
- nautobot/project-static/docs/release-notes/version-2.4.html +379 -0
- nautobot/project-static/docs/requirements.txt +1 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +290 -290
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +3 -3
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +4 -4
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +1 -1
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +3 -13
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +5 -5
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +3 -18
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +1 -1
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +4 -4
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +15 -15
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +2 -2
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/index.html +0 -16
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +1 -1
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +7 -10
- nautobot/project-static/docs/user-guide/administration/installation/services.html +1 -12
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +3 -3
- nautobot/project-static/docs/user-guide/administration/security/index.html +1 -1
- nautobot/project-static/docs/user-guide/administration/security/notices.html +1 -0
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +5 -35
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/tables/v2-code-location-changes.yaml +1 -1
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +12 -9
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +1 -17
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +1 -7
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +0 -6
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +0 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +0 -4
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +0 -8
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +15 -15
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +1 -1
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +0 -6
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +3 -15
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +1 -27
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +0 -8
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +3 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +0 -8
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +0 -7
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +2 -2
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +6 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +0 -14
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +1 -10
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +0 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +0 -14
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +0 -19
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +3 -9
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +0 -8
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +0 -4
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +1 -13
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +0 -5
- nautobot/project-static/js/dropdown.js +28 -0
- nautobot/project-static/js/editor.js +292 -0
- nautobot/project-static/monaco-editor-0.52.2/README.md +81 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/base/browser/ui/codicons/codicon/codicon.ttf +0 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/base/worker/workerMain.js +31 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/basic-languages/xml/xml.js +10 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/basic-languages/yaml/yaml.js +10 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/editor/editor.main.css +8 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/editor/editor.main.js +798 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/language/json/jsonMode.js +19 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/language/json/jsonWorker.js +42 -0
- nautobot/project-static/monaco-editor-0.52.2/vs/loader.js +11 -0
- nautobot/tenancy/filters/__init__.py +3 -5
- nautobot/tenancy/forms.py +9 -0
- nautobot/tenancy/templates/tenancy/tenant_create.html +21 -0
- nautobot/tenancy/templates/tenancy/tenant_edit.html +2 -21
- nautobot/tenancy/templates/tenancy/tenantgroup.html +2 -44
- nautobot/tenancy/templates/tenancy/tenantgroup_retrieve.html +1 -0
- nautobot/tenancy/tests/test_filters.py +10 -0
- nautobot/tenancy/tests/test_views.py +5 -1
- nautobot/tenancy/urls.py +7 -79
- nautobot/tenancy/views.py +51 -80
- nautobot/virtualization/views.py +0 -1
- nautobot/wireless/api/serializers.py +6 -1
- nautobot/wireless/api/views.py +3 -3
- nautobot/wireless/tables.py +9 -4
- nautobot/wireless/tests/test_api.py +5 -9
- {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/METADATA +9 -9
- {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/RECORD +267 -246
- {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/NOTICE +0 -0
- {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/WHEEL +0 -0
- {nautobot-2.4.2.dist-info → nautobot-2.4.4.dist-info}/entry_points.txt +0 -0
nautobot/core/utils/lookup.py
CHANGED
|
@@ -250,6 +250,11 @@ def get_model_for_view_name(view_name):
|
|
|
250
250
|
Return the model class associated with the given view_name e.g. "circuits:circuit_detail", "dcim:device_list" and etc.
|
|
251
251
|
If the app_label or model_name contained by the given view_name is invalid, this will return `None`.
|
|
252
252
|
"""
|
|
253
|
+
if view_name == "users-api:group-detail":
|
|
254
|
+
return Group
|
|
255
|
+
if view_name == "extras-api:contenttype-detail":
|
|
256
|
+
return ContentType
|
|
257
|
+
|
|
253
258
|
split_view_name = view_name.split(":")
|
|
254
259
|
if len(split_view_name) == 2:
|
|
255
260
|
app_label, model_name = split_view_name # dcim, device_list
|
|
@@ -257,7 +262,13 @@ def get_model_for_view_name(view_name):
|
|
|
257
262
|
_, app_label, model_name = split_view_name # plugins, app_name, model_list
|
|
258
263
|
else:
|
|
259
264
|
raise ValueError(f"Unexpected View Name: {view_name}")
|
|
260
|
-
|
|
265
|
+
|
|
266
|
+
delimiter = "_"
|
|
267
|
+
if app_label.endswith("-api"):
|
|
268
|
+
app_label = app_label.replace("-api", "")
|
|
269
|
+
delimiter = "-"
|
|
270
|
+
|
|
271
|
+
model_name = model_name.split(delimiter)[0] # device
|
|
261
272
|
|
|
262
273
|
try:
|
|
263
274
|
model = apps.get_model(app_label=app_label, model_name=model_name)
|
nautobot/core/views/generic.py
CHANGED
|
@@ -214,8 +214,16 @@ class ObjectListView(ObjectPermissionRequiredMixin, View):
|
|
|
214
214
|
resolved_path = resolve(request.path)
|
|
215
215
|
list_url = f"{resolved_path.app_name}:{resolved_path.url_name}"
|
|
216
216
|
|
|
217
|
+
skip_user_and_global_default_saved_view = False
|
|
218
|
+
if self.filterset is not None:
|
|
219
|
+
skip_user_and_global_default_saved_view = get_filterable_params_from_filter_params(
|
|
220
|
+
request.GET.copy(),
|
|
221
|
+
self.non_filter_params,
|
|
222
|
+
self.filterset(),
|
|
223
|
+
)
|
|
224
|
+
|
|
217
225
|
# If the user clicks on the clear view button, we do not check for global or user defaults
|
|
218
|
-
if not clear_view and not request.GET.get("saved_view"):
|
|
226
|
+
if not skip_user_and_global_default_saved_view and not clear_view and not request.GET.get("saved_view"):
|
|
219
227
|
# Check if there is a default for this view for this specific user
|
|
220
228
|
if not isinstance(user, AnonymousUser):
|
|
221
229
|
try:
|
nautobot/core/views/mixins.py
CHANGED
|
@@ -723,8 +723,16 @@ class ObjectListViewMixin(NautobotViewSetMixin, mixins.ListModelMixin):
|
|
|
723
723
|
if response is not None:
|
|
724
724
|
return response
|
|
725
725
|
|
|
726
|
+
skip_user_and_global_default_saved_view = False
|
|
727
|
+
if self.filterset_class is not None:
|
|
728
|
+
skip_user_and_global_default_saved_view = get_filterable_params_from_filter_params(
|
|
729
|
+
request.GET.copy(),
|
|
730
|
+
self.non_filter_params,
|
|
731
|
+
self.filterset_class(),
|
|
732
|
+
)
|
|
733
|
+
|
|
726
734
|
# If the user clicks on the clear view button, we do not check for global or user defaults
|
|
727
|
-
if not clear_view and not request.GET.get("saved_view"):
|
|
735
|
+
if not skip_user_and_global_default_saved_view and not clear_view and not request.GET.get("saved_view"):
|
|
728
736
|
# Check if there is a default for this view for this specific user
|
|
729
737
|
app_label, model_name = queryset.model._meta.label.split(".")
|
|
730
738
|
view_name = f"{app_label}:{model_name.lower()}_list"
|
nautobot/dcim/api/serializers.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import contextlib
|
|
2
2
|
|
|
3
3
|
from django.contrib.contenttypes.models import ContentType
|
|
4
|
+
from django.core.exceptions import ValidationError
|
|
4
5
|
from drf_spectacular.utils import extend_schema_field
|
|
5
6
|
from rest_framework import serializers
|
|
6
7
|
from rest_framework.validators import UniqueTogetherValidator, UniqueValidator
|
|
@@ -560,11 +561,46 @@ class DeviceSerializer(TaggedModelSerializerMixin, NautobotModelSerializer):
|
|
|
560
561
|
)
|
|
561
562
|
validator(attrs, self)
|
|
562
563
|
|
|
564
|
+
# Validate parent bay
|
|
565
|
+
if parent_bay := attrs.get("parent_bay", None):
|
|
566
|
+
if parent_bay.installed_device and parent_bay.installed_device != self.instance:
|
|
567
|
+
raise ValidationError(
|
|
568
|
+
{
|
|
569
|
+
"installed_device": f"Cannot install device; parent bay is already taken ({parent_bay.installed_device})"
|
|
570
|
+
}
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
if self.instance:
|
|
574
|
+
parent_bay.installed_device = self.instance
|
|
575
|
+
parent_bay.full_clean()
|
|
576
|
+
|
|
563
577
|
# Enforce model validation
|
|
564
578
|
super().validate(attrs)
|
|
565
579
|
|
|
566
580
|
return attrs
|
|
567
581
|
|
|
582
|
+
def create(self, validated_data):
|
|
583
|
+
instance = super().create(validated_data)
|
|
584
|
+
self.update_parent_bay(validated_data, instance)
|
|
585
|
+
return instance
|
|
586
|
+
|
|
587
|
+
def update(self, instance, validated_data):
|
|
588
|
+
instance = super().update(instance, validated_data)
|
|
589
|
+
self.update_parent_bay(validated_data, instance)
|
|
590
|
+
return instance
|
|
591
|
+
|
|
592
|
+
def update_parent_bay(self, validated_data, instance):
|
|
593
|
+
update_parent_bay = "parent_bay" in validated_data.keys()
|
|
594
|
+
parent_bay = validated_data.get("parent_bay")
|
|
595
|
+
if update_parent_bay:
|
|
596
|
+
if parent_bay:
|
|
597
|
+
parent_bay.installed_device = instance
|
|
598
|
+
parent_bay.save()
|
|
599
|
+
elif hasattr(instance, "parent_bay"):
|
|
600
|
+
parent_bay = instance.parent_bay
|
|
601
|
+
parent_bay.installed_device = None
|
|
602
|
+
parent_bay.validated_save()
|
|
603
|
+
|
|
568
604
|
|
|
569
605
|
class DeviceNAPALMSerializer(serializers.Serializer):
|
|
570
606
|
method = serializers.DictField()
|
nautobot/dcim/api/views.py
CHANGED
|
@@ -74,6 +74,7 @@ from nautobot.dcim.models import (
|
|
|
74
74
|
)
|
|
75
75
|
from nautobot.extras.api.views import (
|
|
76
76
|
ConfigContextQuerySetMixin,
|
|
77
|
+
CustomFieldModelViewSet,
|
|
77
78
|
NautobotModelViewSet,
|
|
78
79
|
)
|
|
79
80
|
from nautobot.extras.choices import SecretsGroupAccessTypeChoices, SecretsGroupSecretTypeChoices
|
|
@@ -182,7 +183,7 @@ class RackGroupViewSet(NautobotModelViewSet):
|
|
|
182
183
|
|
|
183
184
|
|
|
184
185
|
class RackViewSet(NautobotModelViewSet):
|
|
185
|
-
queryset = Rack.objects.select_related("rack_group__location").annotate(
|
|
186
|
+
queryset = Rack.objects.select_related("role", "status", "rack_group__location").annotate(
|
|
186
187
|
device_count=count_related(Device, "rack"),
|
|
187
188
|
power_feed_count=count_related(PowerFeed, "rack"),
|
|
188
189
|
)
|
|
@@ -300,13 +301,13 @@ class DeviceTypeViewSet(NautobotModelViewSet):
|
|
|
300
301
|
#
|
|
301
302
|
|
|
302
303
|
|
|
303
|
-
class ConsolePortTemplateViewSet(
|
|
304
|
+
class ConsolePortTemplateViewSet(CustomFieldModelViewSet):
|
|
304
305
|
queryset = ConsolePortTemplate.objects.select_related("device_type__manufacturer", "module_type__manufacturer")
|
|
305
306
|
serializer_class = serializers.ConsolePortTemplateSerializer
|
|
306
307
|
filterset_class = filters.ConsolePortTemplateFilterSet
|
|
307
308
|
|
|
308
309
|
|
|
309
|
-
class ConsoleServerPortTemplateViewSet(
|
|
310
|
+
class ConsoleServerPortTemplateViewSet(CustomFieldModelViewSet):
|
|
310
311
|
queryset = ConsoleServerPortTemplate.objects.select_related(
|
|
311
312
|
"device_type__manufacturer", "module_type__manufacturer"
|
|
312
313
|
)
|
|
@@ -314,43 +315,43 @@ class ConsoleServerPortTemplateViewSet(NautobotModelViewSet):
|
|
|
314
315
|
filterset_class = filters.ConsoleServerPortTemplateFilterSet
|
|
315
316
|
|
|
316
317
|
|
|
317
|
-
class PowerPortTemplateViewSet(
|
|
318
|
+
class PowerPortTemplateViewSet(CustomFieldModelViewSet):
|
|
318
319
|
queryset = PowerPortTemplate.objects.select_related("device_type__manufacturer", "module_type__manufacturer")
|
|
319
320
|
serializer_class = serializers.PowerPortTemplateSerializer
|
|
320
321
|
filterset_class = filters.PowerPortTemplateFilterSet
|
|
321
322
|
|
|
322
323
|
|
|
323
|
-
class PowerOutletTemplateViewSet(
|
|
324
|
+
class PowerOutletTemplateViewSet(CustomFieldModelViewSet):
|
|
324
325
|
queryset = PowerOutletTemplate.objects.select_related("device_type__manufacturer", "module_type__manufacturer")
|
|
325
326
|
serializer_class = serializers.PowerOutletTemplateSerializer
|
|
326
327
|
filterset_class = filters.PowerOutletTemplateFilterSet
|
|
327
328
|
|
|
328
329
|
|
|
329
|
-
class InterfaceTemplateViewSet(
|
|
330
|
+
class InterfaceTemplateViewSet(CustomFieldModelViewSet):
|
|
330
331
|
queryset = InterfaceTemplate.objects.select_related("device_type__manufacturer", "module_type__manufacturer")
|
|
331
332
|
serializer_class = serializers.InterfaceTemplateSerializer
|
|
332
333
|
filterset_class = filters.InterfaceTemplateFilterSet
|
|
333
334
|
|
|
334
335
|
|
|
335
|
-
class FrontPortTemplateViewSet(
|
|
336
|
+
class FrontPortTemplateViewSet(CustomFieldModelViewSet):
|
|
336
337
|
queryset = FrontPortTemplate.objects.select_related("device_type__manufacturer", "module_type__manufacturer")
|
|
337
338
|
serializer_class = serializers.FrontPortTemplateSerializer
|
|
338
339
|
filterset_class = filters.FrontPortTemplateFilterSet
|
|
339
340
|
|
|
340
341
|
|
|
341
|
-
class RearPortTemplateViewSet(
|
|
342
|
+
class RearPortTemplateViewSet(CustomFieldModelViewSet):
|
|
342
343
|
queryset = RearPortTemplate.objects.select_related("device_type__manufacturer", "module_type__manufacturer")
|
|
343
344
|
serializer_class = serializers.RearPortTemplateSerializer
|
|
344
345
|
filterset_class = filters.RearPortTemplateFilterSet
|
|
345
346
|
|
|
346
347
|
|
|
347
|
-
class DeviceBayTemplateViewSet(
|
|
348
|
+
class DeviceBayTemplateViewSet(CustomFieldModelViewSet):
|
|
348
349
|
queryset = DeviceBayTemplate.objects.select_related("device_type__manufacturer")
|
|
349
350
|
serializer_class = serializers.DeviceBayTemplateSerializer
|
|
350
351
|
filterset_class = filters.DeviceBayTemplateFilterSet
|
|
351
352
|
|
|
352
353
|
|
|
353
|
-
class ModuleBayTemplateViewSet(
|
|
354
|
+
class ModuleBayTemplateViewSet(CustomFieldModelViewSet):
|
|
354
355
|
queryset = ModuleBayTemplate.objects.select_related("device_type__manufacturer", "module_type__manufacturer")
|
|
355
356
|
serializer_class = serializers.ModuleBayTemplateSerializer
|
|
356
357
|
filterset_class = filters.ModuleBayTemplateFilterSet
|
|
@@ -831,7 +832,7 @@ class VirtualDeviceContextViewSet(NautobotModelViewSet):
|
|
|
831
832
|
filterset_class = filters.VirtualDeviceContextFilterSet
|
|
832
833
|
|
|
833
834
|
|
|
834
|
-
class InterfaceVDCAssignmentViewSet(
|
|
835
|
+
class InterfaceVDCAssignmentViewSet(ModelViewSet):
|
|
835
836
|
queryset = InterfaceVDCAssignment.objects.all()
|
|
836
837
|
serializer_class = serializers.InterfaceVDCAssignmentSerializer
|
|
837
838
|
filterset_class = filters.InterfaceVDCAssignmentFilterSet
|
nautobot/dcim/elevations.py
CHANGED
|
@@ -96,17 +96,30 @@ class RackElevationSVG:
|
|
|
96
96
|
device_fullname = str(device) + device_bay_details
|
|
97
97
|
device_shortname = settings.UI_RACK_VIEW_TRUNCATE_FUNCTION(str(device)) + device_bay_details
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
role_color = device.role.color
|
|
100
|
+
status_color = device.status.color
|
|
101
|
+
device_reverse_url = reverse("dcim:device", kwargs={"pk": device.pk})
|
|
102
|
+
status_reverse_url = reverse("extras:status", kwargs={"pk": device.status.pk})
|
|
101
103
|
link = drawing.add(
|
|
102
104
|
drawing.a(
|
|
103
|
-
href=f"{self.base_url}{
|
|
105
|
+
href=f"{self.base_url}{device_reverse_url}",
|
|
104
106
|
target="_top",
|
|
105
107
|
fill="black",
|
|
106
108
|
)
|
|
107
109
|
)
|
|
108
110
|
link.set_desc(self._get_device_description(device))
|
|
109
|
-
link.add(drawing.rect(start, end, style=f"fill: #{
|
|
111
|
+
link.add(drawing.rect(start, end, style=f"fill: #{role_color}", class_="slot"))
|
|
112
|
+
|
|
113
|
+
status_rect = drawing.add(
|
|
114
|
+
drawing.a(
|
|
115
|
+
href=f"{self.base_url}{status_reverse_url}",
|
|
116
|
+
target="_top",
|
|
117
|
+
fill="black",
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
status_rect.set_desc(device.status.name)
|
|
121
|
+
status_end = (end[0] / 20, end[1]) # width, y
|
|
122
|
+
status_rect.add(drawing.rect(start, status_end, style=f"fill: #{status_color}"))
|
|
110
123
|
|
|
111
124
|
# Embed front device type image if one exists
|
|
112
125
|
if self.include_images and device.device_type.front_image:
|
nautobot/dcim/factory.py
CHANGED
|
@@ -64,7 +64,7 @@ from nautobot.dcim.models import (
|
|
|
64
64
|
)
|
|
65
65
|
from nautobot.extras.models import ExternalIntegration, Role, Status
|
|
66
66
|
from nautobot.extras.utils import FeatureQuery
|
|
67
|
-
from nautobot.ipam.models import Prefix, VLAN, VLANGroup
|
|
67
|
+
from nautobot.ipam.models import Prefix, VLAN, VLANGroup, VRF
|
|
68
68
|
from nautobot.tenancy.models import Tenant
|
|
69
69
|
from nautobot.virtualization.models import Cluster
|
|
70
70
|
|
|
@@ -1008,3 +1008,11 @@ class VirtualDeviceContextFactory(PrimaryModelFactory):
|
|
|
1008
1008
|
self.interfaces.set(extracted)
|
|
1009
1009
|
else:
|
|
1010
1010
|
self.interfaces.set(get_random_instances(Interface.objects.filter(device=self.device)))
|
|
1011
|
+
|
|
1012
|
+
@factory.post_generation
|
|
1013
|
+
def vrfs(self, create, extracted, **kwargs):
|
|
1014
|
+
if create:
|
|
1015
|
+
if extracted:
|
|
1016
|
+
self.vrfs.set(extracted)
|
|
1017
|
+
else:
|
|
1018
|
+
self.vrfs.set(get_random_instances(VRF.objects.all()))
|
|
@@ -101,7 +101,7 @@ from nautobot.extras.filters import (
|
|
|
101
101
|
from nautobot.extras.models import ExternalIntegration, SecretsGroup
|
|
102
102
|
from nautobot.extras.utils import FeatureQuery
|
|
103
103
|
from nautobot.ipam.models import IPAddress, VLAN, VLANGroup
|
|
104
|
-
from nautobot.tenancy.filters import TenancyModelFilterSetMixin
|
|
104
|
+
from nautobot.tenancy.filters.mixins import TenancyModelFilterSetMixin
|
|
105
105
|
from nautobot.tenancy.models import Tenant
|
|
106
106
|
from nautobot.virtualization.models import Cluster, VirtualMachine
|
|
107
107
|
from nautobot.wireless.models import RadioProfile, WirelessNetwork
|
|
@@ -362,6 +362,12 @@ class RackGroupFilterSet(LocatableModelFilterSetMixin, NautobotFilterSet, NameSe
|
|
|
362
362
|
to_field_name="name",
|
|
363
363
|
label="Parent (name or ID)",
|
|
364
364
|
)
|
|
365
|
+
ancestors = NaturalKeyOrPKMultipleChoiceFilter(
|
|
366
|
+
queryset=Location.objects.all(),
|
|
367
|
+
to_field_name="name",
|
|
368
|
+
label="Location(s) and ancestors thereof (name or ID)",
|
|
369
|
+
method="_ancestors",
|
|
370
|
+
)
|
|
365
371
|
children = NaturalKeyOrPKMultipleChoiceFilter(
|
|
366
372
|
queryset=RackGroup.objects.all(),
|
|
367
373
|
to_field_name="name",
|
|
@@ -392,6 +398,26 @@ class RackGroupFilterSet(LocatableModelFilterSetMixin, NautobotFilterSet, NameSe
|
|
|
392
398
|
model = RackGroup
|
|
393
399
|
fields = ["id", "name", "description", "racks"]
|
|
394
400
|
|
|
401
|
+
def generate_query__ancestors(self, value):
|
|
402
|
+
"""Helper method used by _ancestors() method."""
|
|
403
|
+
if value:
|
|
404
|
+
locations = Location.objects.filter(pk__in=[v.pk for v in value])
|
|
405
|
+
pk_list = []
|
|
406
|
+
for location in locations:
|
|
407
|
+
parent_locations = location.ancestors(include_self=True)
|
|
408
|
+
pk_list.extend([v.pk for v in parent_locations])
|
|
409
|
+
params = Q(location__pk__in=pk_list)
|
|
410
|
+
return params
|
|
411
|
+
return Q()
|
|
412
|
+
|
|
413
|
+
@extend_schema_field({"type": "string"})
|
|
414
|
+
def _ancestors(self, queryset, name, value):
|
|
415
|
+
"""FilterSet method for, given a location, getting RackGroups that exist with in the parent Location(s) and the location itself."""
|
|
416
|
+
if value:
|
|
417
|
+
params = self.generate_query__ancestors(value)
|
|
418
|
+
return queryset.filter(params)
|
|
419
|
+
return queryset
|
|
420
|
+
|
|
395
421
|
|
|
396
422
|
class RackFilterSet(
|
|
397
423
|
NautobotFilterSet,
|
nautobot/dcim/forms.py
CHANGED
|
@@ -17,6 +17,7 @@ from nautobot.core.forms import (
|
|
|
17
17
|
AutoPositionPatternField,
|
|
18
18
|
BootstrapMixin,
|
|
19
19
|
BulkEditNullBooleanSelect,
|
|
20
|
+
ClearableFileInput,
|
|
20
21
|
ColorSelect,
|
|
21
22
|
CommentField,
|
|
22
23
|
DatePicker,
|
|
@@ -509,7 +510,7 @@ class RackForm(LocatableModelFormMixin, NautobotModelForm, TenancyForm):
|
|
|
509
510
|
rack_group = DynamicModelChoiceField(
|
|
510
511
|
queryset=RackGroup.objects.all(),
|
|
511
512
|
required=False,
|
|
512
|
-
query_params={"
|
|
513
|
+
query_params={"ancestors": "$location"},
|
|
513
514
|
)
|
|
514
515
|
comments = CommentField()
|
|
515
516
|
|
|
@@ -850,12 +851,8 @@ class DeviceTypeForm(NautobotModelForm):
|
|
|
850
851
|
widgets = {
|
|
851
852
|
"subdevice_role": StaticSelect2(),
|
|
852
853
|
# Exclude SVG images (unsupported by PIL)
|
|
853
|
-
"front_image":
|
|
854
|
-
|
|
855
|
-
),
|
|
856
|
-
"rear_image": forms.ClearableFileInput(
|
|
857
|
-
attrs={"accept": "image/bmp,image/gif,image/jpeg,image/png,image/tiff"}
|
|
858
|
-
),
|
|
854
|
+
"front_image": ClearableFileInput(attrs={"accept": "image/bmp,image/gif,image/jpeg,image/png,image/tiff"}),
|
|
855
|
+
"rear_image": ClearableFileInput(attrs={"accept": "image/bmp,image/gif,image/jpeg,image/png,image/tiff"}),
|
|
859
856
|
}
|
|
860
857
|
|
|
861
858
|
|
|
@@ -5301,6 +5298,11 @@ class VirtualDeviceContextForm(NautobotModelForm):
|
|
|
5301
5298
|
required=True,
|
|
5302
5299
|
query_params={"content_types": VirtualDeviceContext._meta.label_lower},
|
|
5303
5300
|
)
|
|
5301
|
+
vrfs = DynamicModelMultipleChoiceField(
|
|
5302
|
+
queryset=VRF.objects.all(),
|
|
5303
|
+
required=False,
|
|
5304
|
+
label="VRFs",
|
|
5305
|
+
)
|
|
5304
5306
|
|
|
5305
5307
|
class Meta:
|
|
5306
5308
|
model = VirtualDeviceContext
|
|
@@ -5311,6 +5313,7 @@ class VirtualDeviceContextForm(NautobotModelForm):
|
|
|
5311
5313
|
"status",
|
|
5312
5314
|
"identifier",
|
|
5313
5315
|
"interfaces",
|
|
5316
|
+
"vrfs",
|
|
5314
5317
|
"primary_ip4",
|
|
5315
5318
|
"primary_ip6",
|
|
5316
5319
|
"tenant",
|
|
@@ -5326,11 +5329,15 @@ class VirtualDeviceContextForm(NautobotModelForm):
|
|
|
5326
5329
|
self.fields["device"].disabled = True
|
|
5327
5330
|
self.fields["device"].required = False
|
|
5328
5331
|
|
|
5332
|
+
self.initial["vrfs"] = self.instance.vrfs.values_list("id", flat=True)
|
|
5333
|
+
|
|
5329
5334
|
def save(self, commit=True):
|
|
5330
5335
|
instance = super().save(commit)
|
|
5331
5336
|
if commit:
|
|
5332
5337
|
interfaces = self.cleaned_data["interfaces"]
|
|
5333
5338
|
instance.interfaces.set(interfaces)
|
|
5339
|
+
vrfs = self.cleaned_data["vrfs"]
|
|
5340
|
+
instance.vrfs.set(vrfs)
|
|
5334
5341
|
return instance
|
|
5335
5342
|
|
|
5336
5343
|
|
|
@@ -5348,6 +5355,8 @@ class VirtualDeviceContextBulkEditForm(
|
|
|
5348
5355
|
remove_interfaces = DynamicModelMultipleChoiceField(
|
|
5349
5356
|
queryset=Interface.objects.all(), required=False, query_params={"device": "$device"}
|
|
5350
5357
|
)
|
|
5358
|
+
add_vrfs = DynamicModelMultipleChoiceField(queryset=VRF.objects.all(), required=False)
|
|
5359
|
+
remove_vrfs = DynamicModelMultipleChoiceField(queryset=VRF.objects.all(), required=False)
|
|
5351
5360
|
|
|
5352
5361
|
class Meta:
|
|
5353
5362
|
model = VirtualDeviceContext
|
nautobot/dcim/models/devices.py
CHANGED
|
@@ -670,11 +670,17 @@ class Device(PrimaryModel, ConfigContextModel):
|
|
|
670
670
|
|
|
671
671
|
# Validate location
|
|
672
672
|
if self.location is not None:
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
673
|
+
if self.rack is not None:
|
|
674
|
+
device_location = self.location
|
|
675
|
+
# Rack's location must be a child location or the same location as that of the parent device.
|
|
676
|
+
# Location is a required field on rack.
|
|
677
|
+
rack_location = self.rack.location
|
|
678
|
+
if device_location not in rack_location.ancestors(include_self=True):
|
|
679
|
+
raise ValidationError(
|
|
680
|
+
{
|
|
681
|
+
"rack": f'Rack "{self.rack}" does not belong to location "{self.location}" and its descendants.'
|
|
682
|
+
}
|
|
683
|
+
)
|
|
678
684
|
|
|
679
685
|
# self.cluster is validated somewhat later, see below
|
|
680
686
|
|
|
@@ -1419,11 +1425,10 @@ class Controller(PrimaryModel):
|
|
|
1419
1425
|
"controller_device": ("Cannot assign both a device and a device redundancy group to a controller."),
|
|
1420
1426
|
},
|
|
1421
1427
|
)
|
|
1422
|
-
|
|
1423
1428
|
if self.location:
|
|
1424
1429
|
if ContentType.objects.get_for_model(self) not in self.location.location_type.content_types.all():
|
|
1425
1430
|
raise ValidationError(
|
|
1426
|
-
{"location": f'
|
|
1431
|
+
{"location": f'Controllers may not associate to locations of type "{self.location.location_type}".'}
|
|
1427
1432
|
)
|
|
1428
1433
|
|
|
1429
1434
|
def get_capabilities_display(self):
|
nautobot/dcim/signals.py
CHANGED
|
@@ -16,6 +16,7 @@ from .models import (
|
|
|
16
16
|
DeviceRedundancyGroup,
|
|
17
17
|
Interface,
|
|
18
18
|
InterfaceVDCAssignment,
|
|
19
|
+
LocationType,
|
|
19
20
|
PathEndpoint,
|
|
20
21
|
PowerPanel,
|
|
21
22
|
Rack,
|
|
@@ -355,3 +356,28 @@ def handle_controller_managed_device_group_controller_change(instance, raw=False
|
|
|
355
356
|
group.controller = instance.controller
|
|
356
357
|
group.save()
|
|
357
358
|
logger.debug("Updated controller from parent %s for child %s", instance, group)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@receiver(m2m_changed, sender=LocationType.content_types.through)
|
|
362
|
+
def content_type_changed(instance, action, **kwargs):
|
|
363
|
+
"""
|
|
364
|
+
Prevents removal of a ContentType from LocationType if it's in use by any models
|
|
365
|
+
associated with the locations.
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
if action != "pre_remove":
|
|
369
|
+
return
|
|
370
|
+
|
|
371
|
+
removed_content_types = ContentType.objects.filter(pk__in=kwargs.get("pk_set", []))
|
|
372
|
+
|
|
373
|
+
for content_type in removed_content_types:
|
|
374
|
+
model_class = content_type.model_class()
|
|
375
|
+
|
|
376
|
+
if model_class.objects.filter(location__location_type=instance).exists():
|
|
377
|
+
raise ValidationError(
|
|
378
|
+
{
|
|
379
|
+
"content_types": (
|
|
380
|
+
f"Cannot remove the content type {content_type} as currently at least one {model_class._meta.verbose_name} is associated to a location of this location type. "
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
)
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
{% for near_end, cable, far_end in traced_path %}
|
|
14
14
|
|
|
15
15
|
{# Near end #}
|
|
16
|
-
{% if near_end.device %}
|
|
17
|
-
{% include 'dcim/trace/device.html' with device=near_end.
|
|
16
|
+
{% if near_end.device or near_end.module %}
|
|
17
|
+
{% include 'dcim/trace/device.html' with device=near_end.parent %}
|
|
18
18
|
{% include 'dcim/trace/termination.html' with termination=near_end %}
|
|
19
19
|
{% elif near_end.power_panel %}
|
|
20
20
|
{% include 'dcim/trace/powerpanel.html' with powerpanel=near_end.power_panel %}
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
{% endif %}
|
|
31
31
|
|
|
32
32
|
{# Far end #}
|
|
33
|
-
{% if far_end.device %}
|
|
33
|
+
{% if far_end.device or far_end.module %}
|
|
34
34
|
{% include 'dcim/trace/termination.html' with termination=far_end %}
|
|
35
35
|
{% if forloop.last %}
|
|
36
|
-
{% include 'dcim/trace/device.html' with device=far_end.
|
|
36
|
+
{% include 'dcim/trace/device.html' with device=far_end.parent %}
|
|
37
37
|
{% endif %}
|
|
38
38
|
{% elif far_end.power_panel %}
|
|
39
39
|
{% include 'dcim/trace/termination.html' with termination=far_end %}
|
|
@@ -8,8 +8,13 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
<table class="table table-hover panel-body attr-table">
|
|
10
10
|
<tr>
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
{% if object.device %}
|
|
12
|
+
<td>Device</td>
|
|
13
|
+
<td>{{ object.device|hyperlinked_object }}</td>
|
|
14
|
+
{% else %}
|
|
15
|
+
<td>Module</td>
|
|
16
|
+
<td>{{ object.module|hyperlinked_object }}</td>
|
|
17
|
+
{% endif %}
|
|
13
18
|
</tr>
|
|
14
19
|
<tr>
|
|
15
20
|
<td>Name</td>
|
|
@@ -49,8 +54,13 @@
|
|
|
49
54
|
</tr>
|
|
50
55
|
{% if object.connected_endpoint %}
|
|
51
56
|
<tr>
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
{% if object.connected_endpoint.device %}
|
|
58
|
+
<td>Device</td>
|
|
59
|
+
<td>{{ object.connected_endpoint.device|hyperlinked_object }}</td>
|
|
60
|
+
{% else %}
|
|
61
|
+
<td>Module</td>
|
|
62
|
+
<td>{{ object.connected_endpoint.module|hyperlinked_object }}</td>
|
|
63
|
+
{% endif %}
|
|
54
64
|
</tr>
|
|
55
65
|
<tr>
|
|
56
66
|
<td>Console Server Port</td>
|
|
@@ -8,8 +8,13 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
<table class="table table-hover panel-body attr-table">
|
|
10
10
|
<tr>
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
{% if object.device %}
|
|
12
|
+
<td>Device</td>
|
|
13
|
+
<td>{{ object.device|hyperlinked_object }}</td>
|
|
14
|
+
{% else %}
|
|
15
|
+
<td>Module</td>
|
|
16
|
+
<td>{{ object.module|hyperlinked_object }}</td>
|
|
17
|
+
{% endif %}
|
|
13
18
|
</tr>
|
|
14
19
|
<tr>
|
|
15
20
|
<td>Name</td>
|
|
@@ -49,8 +54,13 @@
|
|
|
49
54
|
</tr>
|
|
50
55
|
{% if object.connected_endpoint %}
|
|
51
56
|
<tr>
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
{% if object.connected_endpoint.device %}
|
|
58
|
+
<td>Device</td>
|
|
59
|
+
<td>{{ object.connected_endpoint.device|hyperlinked_object }}</td>
|
|
60
|
+
{% else %}
|
|
61
|
+
<td>Module</td>
|
|
62
|
+
<td>{{ object.connected_endpoint.module|hyperlinked_object }}</td>
|
|
63
|
+
{% endif %}
|
|
54
64
|
</tr>
|
|
55
65
|
<tr>
|
|
56
66
|
<td>Console Port</td>
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
{% for iface in interfaces %}
|
|
25
25
|
<tr data-interface-name="{{ iface.name }}">
|
|
26
26
|
<td>{{ iface }}</td>
|
|
27
|
-
{% if iface.connected_endpoint.device %}
|
|
28
|
-
<td class="configured_device" data="{{ iface.connected_endpoint.
|
|
29
|
-
{{ iface.connected_endpoint.
|
|
27
|
+
{% if iface.connected_endpoint.device or iface.connected_endpoint.module %}
|
|
28
|
+
<td class="configured_device" data="{{ iface.connected_endpoint.parent }}" data-chassis="{{ iface.connected_endpoint.parent.virtual_chassis.name }}">
|
|
29
|
+
{{ iface.connected_endpoint.parent|hyperlinked_object }}
|
|
30
30
|
</td>
|
|
31
31
|
<td class="configured_interface" data-interface-name="{{ iface.connected_endpoint }}">
|
|
32
32
|
<span title="{{ iface.connected_endpoint.get_type_display }}">{{ iface.connected_endpoint }}</span>
|
|
@@ -8,8 +8,13 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
<table class="table table-hover panel-body attr-table">
|
|
10
10
|
<tr>
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
{% if object.device %}
|
|
12
|
+
<td>Device</td>
|
|
13
|
+
<td>{{ object.device|hyperlinked_object }}</td>
|
|
14
|
+
{% else %}
|
|
15
|
+
<td>Module</td>
|
|
16
|
+
<td>{{ object.module|hyperlinked_object }}</td>
|
|
17
|
+
{% endif %}
|
|
13
18
|
</tr>
|
|
14
19
|
<tr>
|
|
15
20
|
<td>Name</td>
|
|
@@ -101,11 +101,16 @@
|
|
|
101
101
|
</a>
|
|
102
102
|
</td>
|
|
103
103
|
</tr>
|
|
104
|
-
{% if object.connected_endpoint.device %}
|
|
104
|
+
{% if object.connected_endpoint.device or object.connected_endpoint.module %}
|
|
105
105
|
{% with iface=object.connected_endpoint %}
|
|
106
106
|
<tr>
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
{% if iface.device %}
|
|
108
|
+
<td>Device</td>
|
|
109
|
+
<td>{{ iface.device|hyperlinked_object }}</td>
|
|
110
|
+
{% else %}
|
|
111
|
+
<td>Module</td>
|
|
112
|
+
<td>{{ iface.module|hyperlinked_object }}</td>
|
|
113
|
+
{% endif %}
|
|
109
114
|
</tr>
|
|
110
115
|
<tr>
|
|
111
116
|
<td>Interface</td>
|
|
@@ -201,7 +206,7 @@
|
|
|
201
206
|
<tbody>
|
|
202
207
|
{% for member in object.member_interfaces.all %}
|
|
203
208
|
<tr>
|
|
204
|
-
<td>{{ member.
|
|
209
|
+
<td>{{ member.parent|hyperlinked_object }}</td>
|
|
205
210
|
<td>{{ member|hyperlinked_object }}</td>
|
|
206
211
|
<td>
|
|
207
212
|
{{ member.get_type_display }}
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
<td>Connected Device</td>
|
|
40
40
|
<td>
|
|
41
41
|
{% if object.connected_endpoint %}
|
|
42
|
-
{{ object.connected_endpoint.
|
|
42
|
+
{{ object.connected_endpoint.parent|hyperlinked_object }}
|
|
43
43
|
({{ object.connected_endpoint }})
|
|
44
44
|
{% else %}
|
|
45
45
|
<span class="text-muted">None</span>
|
|
@@ -110,8 +110,13 @@
|
|
|
110
110
|
</tr>
|
|
111
111
|
{% if object.connected_endpoint %}
|
|
112
112
|
<tr>
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
{% if object.connected_endpoint.device %}
|
|
114
|
+
<td>Device</td>
|
|
115
|
+
<td>{{ object.connected_endpoint.device|hyperlinked_object }}</td>
|
|
116
|
+
{% else %}
|
|
117
|
+
<td>Module</td>
|
|
118
|
+
<td>{{ object.connected_endpoint.module|hyperlinked_object }}</td>
|
|
119
|
+
{% endif %}
|
|
115
120
|
</tr>
|
|
116
121
|
<tr>
|
|
117
122
|
<td>Power Port</td>
|