Просмотр исходного кода

Closes #6874: Add tenant assignment for locations

jeremystretch 4 лет назад
Родитель
Сommit
5a6190e321

+ 1 - 0
docs/release-notes/version-3.1.md

@@ -6,6 +6,7 @@
 ### Enhancements
 
 * [#1337](https://github.com/netbox-community/netbox/issues/1337) - Add WWN field to interfaces
+* [#6874](https://github.com/netbox-community/netbox/issues/6874) - Add tenant assignment for locations
 
 ### Other Changes
 

+ 3 - 2
netbox/dcim/api/serializers.py

@@ -138,14 +138,15 @@ class LocationSerializer(NestedGroupModelSerializer):
     url = serializers.HyperlinkedIdentityField(view_name='dcim-api:location-detail')
     site = NestedSiteSerializer()
     parent = NestedLocationSerializer(required=False, allow_null=True)
+    tenant = NestedTenantSerializer(required=False, allow_null=True)
     rack_count = serializers.IntegerField(read_only=True)
     device_count = serializers.IntegerField(read_only=True)
 
     class Meta:
         model = Location
         fields = [
-            'id', 'url', 'display', 'name', 'slug', 'site', 'parent', 'description', 'custom_fields', 'created',
-            'last_updated', 'rack_count', 'device_count', '_depth',
+            'id', 'url', 'display', 'name', 'slug', 'site', 'parent', 'tenant', 'description', 'custom_fields',
+            'created', 'last_updated', 'rack_count', 'device_count', '_depth',
         ]
 
 

+ 5 - 1
netbox/dcim/forms/bulk_edit.py

@@ -148,13 +148,17 @@ class LocationBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):
             'site_id': '$site'
         }
     )
+    tenant = DynamicModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False
+    )
     description = forms.CharField(
         max_length=200,
         required=False
     )
 
     class Meta:
-        nullable_fields = ['parent', 'description']
+        nullable_fields = ['parent', 'tenant', 'description']
 
 
 class RackRoleBulkEditForm(BootstrapMixin, CustomFieldModelBulkEditForm):

+ 7 - 1
netbox/dcim/forms/bulk_import.py

@@ -120,10 +120,16 @@ class LocationCSVForm(CustomFieldModelCSVForm):
             'invalid_choice': 'Location not found.',
         }
     )
+    tenant = CSVModelChoiceField(
+        queryset=Tenant.objects.all(),
+        required=False,
+        to_field_name='name',
+        help_text='Assigned tenant'
+    )
 
     class Meta:
         model = Location
-        fields = ('site', 'parent', 'name', 'slug', 'description')
+        fields = ('site', 'parent', 'name', 'slug', 'tenant', 'description')
 
 
 class RackRoleCSVForm(CustomFieldModelCSVForm):

+ 6 - 1
netbox/dcim/forms/filtersets.py

@@ -175,8 +175,13 @@ class SiteFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterFo
     tag = TagFilterField(model)
 
 
-class LocationFilterForm(BootstrapMixin, CustomFieldModelFilterForm):
+class LocationFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldModelFilterForm):
     model = Location
+    field_groups = [
+        ['q'],
+        ['region_id', 'site_group_id', 'site_id', 'parent_id'],
+        ['tenant_group_id', 'tenant_id'],
+    ]
     q = forms.CharField(
         required=False,
         widget=forms.TextInput(attrs={'placeholder': _('All Fields')}),

+ 8 - 2
netbox/dcim/forms/models.py

@@ -157,7 +157,7 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
         }
 
 
-class LocationForm(BootstrapMixin, CustomFieldModelForm):
+class LocationForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
     region = DynamicModelChoiceField(
         queryset=Region.objects.all(),
         required=False,
@@ -191,7 +191,13 @@ class LocationForm(BootstrapMixin, CustomFieldModelForm):
     class Meta:
         model = Location
         fields = (
-            'region', 'site_group', 'site', 'parent', 'name', 'slug', 'description',
+            'region', 'site_group', 'site', 'parent', 'name', 'slug', 'description', 'tenant_group', 'tenant',
+        )
+        fieldsets = (
+            ('Location', (
+                'region', 'site_group', 'site', 'parent', 'name', 'slug', 'description',
+            )),
+            ('Tenancy', ('tenant_group', 'tenant')),
         )
 
 

+ 18 - 0
netbox/dcim/migrations/0135_location_tenant.py

@@ -0,0 +1,18 @@
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('tenancy', '0002_tenant_ordering'),
+        ('dcim', '0134_interface_wwn'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='location',
+            name='tenant',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='locations', to='tenancy.tenant'),
+        ),
+    ]

+ 7 - 1
netbox/dcim/models/sites.py

@@ -7,7 +7,6 @@ from timezone_field import TimeZoneField
 
 from dcim.choices import *
 from dcim.constants import *
-from django.core.exceptions import ValidationError
 from dcim.fields import ASNField
 from extras.utils import extras_features
 from netbox.models import NestedGroupModel, PrimaryModel
@@ -281,6 +280,13 @@ class Location(NestedGroupModel):
         null=True,
         db_index=True
     )
+    tenant = models.ForeignKey(
+        to='tenancy.Tenant',
+        on_delete=models.PROTECT,
+        related_name='locations',
+        blank=True,
+        null=True
+    )
     description = models.CharField(
         max_length=200,
         blank=True

+ 3 - 2
netbox/dcim/tables/sites.py

@@ -103,6 +103,7 @@ class LocationTable(BaseTable):
     site = tables.Column(
         linkify=True
     )
+    tenant = TenantColumn()
     rack_count = LinkedCountColumn(
         viewname='dcim:rack_list',
         url_params={'location_id': 'pk'},
@@ -120,5 +121,5 @@ class LocationTable(BaseTable):
 
     class Meta(BaseTable.Meta):
         model = Location
-        fields = ('pk', 'name', 'site', 'rack_count', 'device_count', 'description', 'slug', 'actions')
-        default_columns = ('pk', 'name', 'site', 'rack_count', 'device_count', 'description', 'actions')
+        fields = ('pk', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description', 'slug', 'actions')
+        default_columns = ('pk', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description', 'actions')

+ 11 - 9
netbox/dcim/tests/test_views.py

@@ -12,6 +12,7 @@ from dcim.choices import *
 from dcim.constants import *
 from dcim.models import *
 from ipam.models import VLAN
+from tenancy.models import Tenant
 from utilities.testing import ViewTestCases, create_tags, create_test_device
 
 
@@ -157,13 +158,13 @@ class LocationTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
     @classmethod
     def setUpTestData(cls):
 
-        site = Site(name='Site 1', slug='site-1')
-        site.save()
+        site = Site.objects.create(name='Site 1', slug='site-1')
+        tenant = Tenant.objects.create(name='Tenant 1', slug='tenant-1')
 
         locations = (
-            Location(name='Location 1', slug='location-1', site=site),
-            Location(name='Location 2', slug='location-2', site=site),
-            Location(name='Location 3', slug='location-3', site=site),
+            Location(name='Location 1', slug='location-1', site=site, tenant=tenant),
+            Location(name='Location 2', slug='location-2', site=site, tenant=tenant),
+            Location(name='Location 3', slug='location-3', site=site, tenant=tenant),
         )
         for location in locations:
             location.save()
@@ -172,14 +173,15 @@ class LocationTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
             'name': 'Location X',
             'slug': 'location-x',
             'site': site.pk,
+            'tenant': tenant.pk,
             'description': 'A new location',
         }
 
         cls.csv_data = (
-            "site,name,slug,description",
-            "Site 1,Location 4,location-4,Fourth location",
-            "Site 1,Location 5,location-5,Fifth location",
-            "Site 1,Location 6,location-6,Sixth location",
+            "site,tenant,name,slug,description",
+            "Site 1,Tenant 1,Location 4,location-4,Fourth location",
+            "Site 1,Tenant 1,Location 5,location-5,Fifth location",
+            "Site 1,Tenant 1,Location 6,location-6,Sixth location",
         )
 
         cls.bulk_edit_data = {

+ 13 - 0
netbox/templates/dcim/location.html

@@ -40,6 +40,19 @@
               {% endif %}
             </td>
           </tr>
+          <tr>
+              <th scope="row">Tenant</th>
+              <td>
+                  {% if object.tenant %}
+                      {% if object.tenant.group %}
+                          <a href="{{ object.tenant.group.get_absolute_url }}">{{ object.tenant.group }}</a> /
+                      {% endif %}
+                      <a href="{{ object.tenant.get_absolute_url }}">{{ object.tenant }}</a>
+                  {% else %}
+                      <span class="text-muted">None</span>
+                  {% endif %}
+              </td>
+          </tr>
           <tr>
             <th scope="row">Racks</th>
             <td>