From 19f577ccaf13685aaac2a493c878f8d5549c94c4 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Mon, 18 Mar 2024 09:09:50 -0500 Subject: [PATCH] Closes: #13918 - Add facility field (#15456) * Fixes: #13918 - Add facilities field to Location model. * Stupidly forgot to `git add` * Fix errant reference to site. * Misc cleanup --------- Co-authored-by: Jeremy Stretch --- docs/models/dcim/location.md | 4 ++++ netbox/dcim/api/serializers_/sites.py | 4 ++-- netbox/dcim/filtersets.py | 3 ++- netbox/dcim/forms/bulk_import.py | 2 +- netbox/dcim/forms/model_forms.py | 4 ++-- .../dcim/migrations/0186_location_facility.py | 18 ++++++++++++++++++ netbox/dcim/models/sites.py | 8 +++++++- netbox/dcim/search.py | 3 ++- netbox/dcim/tables/sites.py | 8 +++++--- netbox/dcim/tests/test_filtersets.py | 10 +++++++--- netbox/dcim/tests/test_views.py | 1 + netbox/templates/dcim/location.html | 4 ++++ 12 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 netbox/dcim/migrations/0186_location_facility.py diff --git a/docs/models/dcim/location.md b/docs/models/dcim/location.md index 96ab13039..cf957ca5b 100644 --- a/docs/models/dcim/location.md +++ b/docs/models/dcim/location.md @@ -26,3 +26,7 @@ The location's operational status. !!! tip Additional statuses may be defined by setting `Location.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter. + +### Facility + +Data center or facility designation for identifying the location. diff --git a/netbox/dcim/api/serializers_/sites.py b/netbox/dcim/api/serializers_/sites.py index 6fb3811ba..8063278a7 100644 --- a/netbox/dcim/api/serializers_/sites.py +++ b/netbox/dcim/api/serializers_/sites.py @@ -92,7 +92,7 @@ class LocationSerializer(NestedGroupModelSerializer): class Meta: model = Location fields = [ - 'id', 'url', 'display', 'name', 'slug', 'site', 'parent', 'status', 'tenant', 'description', 'tags', - 'custom_fields', 'created', 'last_updated', 'rack_count', 'device_count', '_depth', + 'id', 'url', 'display', 'name', 'slug', 'site', 'parent', 'status', 'tenant', 'facility', 'description', + 'tags', 'custom_fields', 'created', 'last_updated', 'rack_count', 'device_count', '_depth', ] brief_fields = ('id', 'url', 'display', 'name', 'slug', 'description', 'rack_count', '_depth') diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py index aa8a68296..2ff9f49ae 100644 --- a/netbox/dcim/filtersets.py +++ b/netbox/dcim/filtersets.py @@ -270,13 +270,14 @@ class LocationFilterSet(TenancyFilterSet, ContactModelFilterSet, OrganizationalM class Meta: model = Location - fields = ('id', 'name', 'slug', 'status', 'description') + fields = ('id', 'name', 'slug', 'status', 'facility', 'description') def search(self, queryset, name, value): if not value.strip(): return queryset return queryset.filter( Q(name__icontains=value) | + Q(facility__icontains=value) | Q(description__icontains=value) ) diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py index 47974096f..d49973082 100644 --- a/netbox/dcim/forms/bulk_import.py +++ b/netbox/dcim/forms/bulk_import.py @@ -157,7 +157,7 @@ class LocationImportForm(NetBoxModelImportForm): class Meta: model = Location - fields = ('site', 'parent', 'name', 'slug', 'status', 'tenant', 'description', 'tags') + fields = ('site', 'parent', 'name', 'slug', 'status', 'tenant', 'facility', 'description', 'tags') def __init__(self, data=None, *args, **kwargs): super().__init__(data, *args, **kwargs) diff --git a/netbox/dcim/forms/model_forms.py b/netbox/dcim/forms/model_forms.py index 6c33ea8d6..92740ec45 100644 --- a/netbox/dcim/forms/model_forms.py +++ b/netbox/dcim/forms/model_forms.py @@ -179,14 +179,14 @@ class LocationForm(TenancyForm, NetBoxModelForm): slug = SlugField() fieldsets = ( - (_('Location'), ('site', 'parent', 'name', 'slug', 'status', 'description', 'tags')), + (_('Location'), ('site', 'parent', 'name', 'slug', 'status', 'facility', 'description', 'tags')), (_('Tenancy'), ('tenant_group', 'tenant')), ) class Meta: model = Location fields = ( - 'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant', 'tags', + 'site', 'parent', 'name', 'slug', 'status', 'description', 'tenant_group', 'tenant', 'facility', 'tags', ) diff --git a/netbox/dcim/migrations/0186_location_facility.py b/netbox/dcim/migrations/0186_location_facility.py new file mode 100644 index 000000000..759ee813b --- /dev/null +++ b/netbox/dcim/migrations/0186_location_facility.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.4 on 2024-03-17 02:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dcim', '0185_gfk_indexes'), + ] + + operations = [ + migrations.AddField( + model_name='location', + name='facility', + field=models.CharField(blank=True, max_length=50), + ), + ] diff --git a/netbox/dcim/models/sites.py b/netbox/dcim/models/sites.py index d2797bf95..c1da807ad 100644 --- a/netbox/dcim/models/sites.py +++ b/netbox/dcim/models/sites.py @@ -275,6 +275,12 @@ class Location(ContactsMixin, ImageAttachmentsMixin, NestedGroupModel): blank=True, null=True ) + facility = models.CharField( + verbose_name=_('facility'), + max_length=50, + blank=True, + help_text=_('Local facility ID or description') + ) # Generic relations vlan_groups = GenericRelation( @@ -284,7 +290,7 @@ class Location(ContactsMixin, ImageAttachmentsMixin, NestedGroupModel): related_query_name='location' ) - clone_fields = ('site', 'parent', 'status', 'tenant', 'description') + clone_fields = ('site', 'parent', 'status', 'tenant', 'facility', 'description') prerequisite_models = ( 'dcim.Site', ) diff --git a/netbox/dcim/search.py b/netbox/dcim/search.py index 18cf75a9a..b349bcac0 100644 --- a/netbox/dcim/search.py +++ b/netbox/dcim/search.py @@ -132,10 +132,11 @@ class LocationIndex(SearchIndex): model = models.Location fields = ( ('name', 100), + ('facility', 100), ('slug', 110), ('description', 500), ) - display_attrs = ('site', 'status', 'tenant', 'description') + display_attrs = ('site', 'status', 'tenant', 'facility', 'description') @register_search diff --git a/netbox/dcim/tables/sites.py b/netbox/dcim/tables/sites.py index a0a71ab30..e179ec43a 100644 --- a/netbox/dcim/tables/sites.py +++ b/netbox/dcim/tables/sites.py @@ -152,7 +152,9 @@ class LocationTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable): class Meta(NetBoxTable.Meta): model = Location fields = ( - 'pk', 'id', 'name', 'site', 'status', 'tenant', 'tenant_group', 'rack_count', 'device_count', 'description', - 'slug', 'contacts', 'tags', 'actions', 'created', 'last_updated', + 'pk', 'id', 'name', 'site', 'status', 'facility', 'tenant', 'tenant_group', 'rack_count', 'device_count', + 'description', 'slug', 'contacts', 'tags', 'actions', 'created', 'last_updated', + ) + default_columns = ( + 'pk', 'name', 'site', 'status', 'facility', 'tenant', 'rack_count', 'device_count', 'description' ) - default_columns = ('pk', 'name', 'site', 'status', 'tenant', 'rack_count', 'device_count', 'description') diff --git a/netbox/dcim/tests/test_filtersets.py b/netbox/dcim/tests/test_filtersets.py index 1e46d66ac..fffa82a10 100644 --- a/netbox/dcim/tests/test_filtersets.py +++ b/netbox/dcim/tests/test_filtersets.py @@ -359,9 +359,9 @@ class LocationTestCase(TestCase, ChangeLoggedFilterSetTests): location.save() locations = ( - Location(name='Location 1A', slug='location-1a', site=sites[0], parent=parent_locations[0], status=LocationStatusChoices.STATUS_PLANNED, description='foobar1'), - Location(name='Location 2A', slug='location-2a', site=sites[1], parent=parent_locations[1], status=LocationStatusChoices.STATUS_STAGING, description='foobar2'), - Location(name='Location 3A', slug='location-3a', site=sites[2], parent=parent_locations[2], status=LocationStatusChoices.STATUS_DECOMMISSIONING, description='foobar3'), + Location(name='Location 1A', slug='location-1a', site=sites[0], parent=parent_locations[0], status=LocationStatusChoices.STATUS_PLANNED, facility='Facility 1', description='foobar1'), + Location(name='Location 2A', slug='location-2a', site=sites[1], parent=parent_locations[1], status=LocationStatusChoices.STATUS_STAGING, facility='Facility 2', description='foobar2'), + Location(name='Location 3A', slug='location-3a', site=sites[2], parent=parent_locations[2], status=LocationStatusChoices.STATUS_DECOMMISSIONING, facility='Facility 3', description='foobar3'), ) for location in locations: location.save() @@ -390,6 +390,10 @@ class LocationTestCase(TestCase, ChangeLoggedFilterSetTests): params = {'status': [LocationStatusChoices.STATUS_PLANNED, LocationStatusChoices.STATUS_STAGING]} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_facility(self): + params = {'facility': ['Facility 1', 'Facility 2']} + self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) + def test_description(self): params = {'description': ['foobar1', 'foobar2']} self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index e9e5a557b..e3437cefc 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -213,6 +213,7 @@ class LocationTestCase(ViewTestCases.OrganizationalObjectViewTestCase): 'slug': 'location-x', 'site': site.pk, 'status': LocationStatusChoices.STATUS_PLANNED, + 'facility': 'Facility X', 'tenant': tenant.pk, 'description': 'A new location', 'tags': [t.pk for t in tags], diff --git a/netbox/templates/dcim/location.html b/netbox/templates/dcim/location.html index db387b164..9f2b766ea 100644 --- a/netbox/templates/dcim/location.html +++ b/netbox/templates/dcim/location.html @@ -54,6 +54,10 @@ {{ object.tenant|linkify|placeholder }} + + {% trans "Facility" %} + {{ object.facility|placeholder }} + {% include 'inc/panels/tags.html' %}