Browse Source

Applied tenancy to sites, racks, and devices

Jeremy Stretch 8 years ago
parent
commit
82a98f0e8f

+ 10 - 6
netbox/dcim/api/serializers.py

@@ -6,6 +6,7 @@ from dcim.models import (
     DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, Platform, PowerOutlet,
     PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RACK_FACE_FRONT, RACK_FACE_REAR, Site,
 )
+from tenancy.api.serializers import TenantNestedSerializer
 
 
 #
@@ -13,10 +14,11 @@ from dcim.models import (
 #
 
 class SiteSerializer(serializers.ModelSerializer):
+    tenant = TenantNestedSerializer()
 
     class Meta:
         model = Site
-        fields = ['id', 'name', 'slug', 'facility', 'asn', 'physical_address', 'shipping_address', 'comments',
+        fields = ['id', 'name', 'slug', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address', 'comments',
                   'count_prefixes', 'count_vlans', 'count_racks', 'count_devices', 'count_circuits']
 
 
@@ -52,10 +54,11 @@ class RackGroupNestedSerializer(RackGroupSerializer):
 class RackSerializer(serializers.ModelSerializer):
     site = SiteNestedSerializer()
     group = RackGroupNestedSerializer()
+    tenant = TenantNestedSerializer()
 
     class Meta:
         model = Rack
-        fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'u_height', 'comments']
+        fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'u_height', 'comments']
 
 
 class RackNestedSerializer(RackSerializer):
@@ -69,8 +72,8 @@ class RackDetailSerializer(RackSerializer):
     rear_units = serializers.SerializerMethodField()
 
     class Meta(RackSerializer.Meta):
-        fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'u_height', 'comments', 'front_units',
-                  'rear_units']
+        fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'u_height', 'comments',
+                  'front_units', 'rear_units']
 
     def get_front_units(self, obj):
         units = obj.get_rack_units(face=RACK_FACE_FRONT)
@@ -218,6 +221,7 @@ class DeviceIPAddressNestedSerializer(serializers.ModelSerializer):
 class DeviceSerializer(serializers.ModelSerializer):
     device_type = DeviceTypeNestedSerializer()
     device_role = DeviceRoleNestedSerializer()
+    tenant = TenantNestedSerializer()
     platform = PlatformNestedSerializer()
     rack = RackNestedSerializer()
     primary_ip = DeviceIPAddressNestedSerializer()
@@ -227,8 +231,8 @@ class DeviceSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = Device
-        fields = ['id', 'name', 'display_name', 'device_type', 'device_role', 'platform', 'serial', 'rack', 'position',
-                  'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments']
+        fields = ['id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'rack',
+                  'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments']
 
     def get_parent_device(self, obj):
         try:

+ 19 - 10
netbox/dcim/forms.py

@@ -4,6 +4,7 @@ from django import forms
 from django.db.models import Count, Q
 
 from ipam.models import IPAddress
+from tenancy.models import Tenant
 from utilities.forms import (
     APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, ExpandableNameField,
     FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
@@ -48,7 +49,7 @@ class SiteForm(forms.ModelForm, BootstrapMixin):
 
     class Meta:
         model = Site
-        fields = ['name', 'slug', 'facility', 'asn', 'physical_address', 'shipping_address', 'comments']
+        fields = ['name', 'slug', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address', 'comments']
         widgets = {
             'physical_address': SmallTextarea(attrs={'rows': 3}),
             'shipping_address': SmallTextarea(attrs={'rows': 3}),
@@ -63,10 +64,12 @@ class SiteForm(forms.ModelForm, BootstrapMixin):
 
 
 class SiteFromCSVForm(forms.ModelForm):
+    tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
+                                    error_messages={'invalid_choice': 'Tenant not found.'})
 
     class Meta:
         model = Site
-        fields = ['name', 'slug', 'facility', 'asn']
+        fields = ['name', 'slug', 'tenant', 'facility', 'asn']
 
 
 class SiteImportForm(BulkImportForm, BootstrapMixin):
@@ -107,7 +110,7 @@ class RackForm(forms.ModelForm, BootstrapMixin):
 
     class Meta:
         model = Rack
-        fields = ['site', 'group', 'name', 'facility_id', 'u_height', 'comments']
+        fields = ['site', 'group', 'name', 'facility_id', 'tenant', 'u_height', 'comments']
         help_texts = {
             'site': "The site at which the rack exists",
             'name': "Organizational rack name",
@@ -135,10 +138,12 @@ class RackFromCSVForm(forms.ModelForm):
     site = forms.ModelChoiceField(queryset=Site.objects.all(), to_field_name='name',
                                   error_messages={'invalid_choice': 'Site not found.'})
     group_name = forms.CharField(required=False)
+    tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
+                                    error_messages={'invalid_choice': 'Tenant not found.'})
 
     class Meta:
         model = Rack
-        fields = ['site', 'group_name', 'name', 'facility_id', 'u_height']
+        fields = ['site', 'group_name', 'name', 'facility_id', 'tenant', 'u_height']
 
     def clean(self):
 
@@ -161,6 +166,7 @@ class RackBulkEditForm(forms.Form, BootstrapMixin):
     pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
     site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
     group = forms.ModelChoiceField(queryset=RackGroup.objects.all(), required=False)
+    tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False)
     u_height = forms.IntegerField(required=False, label='Height (U)')
     comments = CommentField()
 
@@ -203,8 +209,8 @@ class DeviceTypeForm(forms.ModelForm, BootstrapMixin):
 
     class Meta:
         model = DeviceType
-        fields = ['manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
-                  'is_network_device', 'subdevice_role']
+        fields = ['manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server',
+                  'is_pdu', 'is_network_device', 'subdevice_role']
 
 
 class DeviceTypeBulkEditForm(forms.Form, BootstrapMixin):
@@ -324,7 +330,7 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
 
     class Meta:
         model = Device
-        fields = ['name', 'device_role', 'device_type', 'serial', 'site', 'rack', 'position', 'face', 'status',
+        fields = ['name', 'device_role', 'tenant', 'device_type', 'serial', 'site', 'rack', 'position', 'face', 'status',
                   'platform', 'primary_ip4', 'primary_ip6', 'comments']
         help_texts = {
             'device_role': "The function this device serves",
@@ -410,6 +416,8 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
 class BaseDeviceFromCSVForm(forms.ModelForm):
     device_role = forms.ModelChoiceField(queryset=DeviceRole.objects.all(), to_field_name='name',
                                          error_messages={'invalid_choice': 'Invalid device role.'})
+    tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
+                                    error_messages={'invalid_choice': 'Tenant not found.'})
     manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(), to_field_name='name',
                                           error_messages={'invalid_choice': 'Invalid manufacturer.'})
     model_name = forms.CharField()
@@ -441,8 +449,8 @@ class DeviceFromCSVForm(BaseDeviceFromCSVForm):
     face = forms.CharField(required=False)
 
     class Meta(BaseDeviceFromCSVForm.Meta):
-        fields = ['name', 'device_role', 'manufacturer', 'model_name', 'platform', 'serial', 'site', 'rack_name',
-                  'position', 'face']
+        fields = ['name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'site',
+                  'rack_name', 'position', 'face']
 
     def clean(self):
 
@@ -477,7 +485,7 @@ class ChildDeviceFromCSVForm(BaseDeviceFromCSVForm):
     device_bay_name = forms.CharField(required=False)
 
     class Meta(BaseDeviceFromCSVForm.Meta):
-        fields = ['name', 'device_role', 'manufacturer', 'model_name', 'platform', 'serial', 'parent',
+        fields = ['name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'parent',
                   'device_bay_name']
 
     def clean(self):
@@ -512,6 +520,7 @@ class DeviceBulkEditForm(forms.Form, BootstrapMixin):
     pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)
     device_type = forms.ModelChoiceField(queryset=DeviceType.objects.all(), required=False, label='Type')
     device_role = forms.ModelChoiceField(queryset=DeviceRole.objects.all(), required=False, label='Role')
+    tenant = forms.ModelChoiceField(queryset=Tenant.objects.all(), required=False, label='Tenant')
     platform = forms.ModelChoiceField(queryset=Platform.objects.all(), required=False, label='Platform')
     platform_delete = forms.BooleanField(required=False, label='Set platform to "none"')
     status = forms.ChoiceField(choices=FORM_STATUS_CHOICES, required=False, initial='', label='Status')

+ 32 - 0
netbox/dcim/migrations/0012_site_rack_device_add_tenant.py

@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.8 on 2016-07-26 20:06
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('tenancy', '0001_initial'),
+        ('dcim', '0011_devicetype_part_number'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='device',
+            name='tenant',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='tenancy.Tenant'),
+        ),
+        migrations.AddField(
+            model_name='rack',
+            name='tenant',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='tenancy.Tenant'),
+        ),
+        migrations.AddField(
+            model_name='site',
+            name='tenant',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='tenancy.Tenant'),
+        ),
+    ]

+ 7 - 0
netbox/dcim/models.py

@@ -8,6 +8,7 @@ from django.db import models
 from django.db.models import Count, Q, ObjectDoesNotExist
 
 from extras.rpc import RPC_CLIENTS
+from tenancy.models import Tenant
 from utilities.fields import NullableCharField
 from utilities.managers import NaturalOrderByManager
 from utilities.models import CreatedUpdatedModel
@@ -152,6 +153,7 @@ class Site(CreatedUpdatedModel):
     """
     name = models.CharField(max_length=50, unique=True)
     slug = models.SlugField(unique=True)
+    tenant = models.ForeignKey(Tenant, blank=True, null=True, on_delete=models.PROTECT)
     facility = models.CharField(max_length=50, blank=True)
     asn = ASNField(blank=True, null=True, verbose_name='ASN')
     physical_address = models.CharField(max_length=200, blank=True)
@@ -173,6 +175,7 @@ class Site(CreatedUpdatedModel):
         return ','.join([
             self.name,
             self.slug,
+            self.tenant.name if self.tenant else '',
             self.facility,
             str(self.asn),
         ])
@@ -237,6 +240,7 @@ class Rack(CreatedUpdatedModel):
     facility_id = NullableCharField(max_length=30, blank=True, null=True, verbose_name='Facility ID')
     site = models.ForeignKey('Site', related_name='racks', on_delete=models.PROTECT)
     group = models.ForeignKey('RackGroup', related_name='racks', blank=True, null=True, on_delete=models.SET_NULL)
+    tenant = models.ForeignKey(Tenant, blank=True, null=True, on_delete=models.PROTECT)
     u_height = models.PositiveSmallIntegerField(default=42, verbose_name='Height (U)')
     comments = models.TextField(blank=True)
 
@@ -272,6 +276,7 @@ class Rack(CreatedUpdatedModel):
             self.group.name if self.group else '',
             self.name,
             self.facility_id or '',
+            self.tenant.name if self.tenant else '',
             str(self.u_height),
         ])
 
@@ -631,6 +636,7 @@ class Device(CreatedUpdatedModel):
     """
     device_type = models.ForeignKey('DeviceType', related_name='instances', on_delete=models.PROTECT)
     device_role = models.ForeignKey('DeviceRole', related_name='devices', on_delete=models.PROTECT)
+    tenant = models.ForeignKey(Tenant, blank=True, null=True, on_delete=models.PROTECT)
     platform = models.ForeignKey('Platform', related_name='devices', blank=True, null=True, on_delete=models.SET_NULL)
     name = NullableCharField(max_length=50, blank=True, null=True, unique=True)
     serial = models.CharField(max_length=50, blank=True, verbose_name='Serial number')
@@ -724,6 +730,7 @@ class Device(CreatedUpdatedModel):
         return ','.join([
             self.name or '',
             self.device_role.name,
+            self.tenant.name if self.tenant else '',
             self.device_type.manufacturer.name,
             self.device_type.model,
             self.platform.name if self.platform else '',

+ 13 - 7
netbox/dcim/tables.py

@@ -61,6 +61,7 @@ UTILIZATION_GRAPH = """
 class SiteTable(BaseTable):
     name = tables.LinkColumn('dcim:site', args=[Accessor('slug')], verbose_name='Name')
     facility = tables.Column(verbose_name='Facility')
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
     asn = tables.Column(verbose_name='ASN')
     rack_count = tables.Column(accessor=Accessor('count_racks'), orderable=False, verbose_name='Racks')
     device_count = tables.Column(accessor=Accessor('count_devices'), orderable=False, verbose_name='Devices')
@@ -70,7 +71,7 @@ class SiteTable(BaseTable):
 
     class Meta(BaseTable.Meta):
         model = Site
-        fields = ('name', 'facility', 'asn', 'rack_count', 'device_count', 'prefix_count', 'vlan_count',
+        fields = ('name', 'facility', 'tenant', 'asn', 'rack_count', 'device_count', 'prefix_count', 'vlan_count',
                   'circuit_count')
 
 
@@ -101,14 +102,16 @@ class RackTable(BaseTable):
     site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
     group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
     facility_id = tables.Column(verbose_name='Facility ID')
-    u_height = tables.Column(verbose_name='Height (U)')
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
+    u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
     devices = tables.Column(accessor=Accessor('device_count'), verbose_name='Devices')
-    u_consumed = tables.Column(accessor=Accessor('u_consumed'), verbose_name='Used (U)')
+    u_consumed = tables.TemplateColumn("{{ record.u_consumed|default:'0' }}U", verbose_name='Used')
     utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
 
     class Meta(BaseTable.Meta):
         model = Rack
-        fields = ('pk', 'name', 'site', 'group', 'facility_id', 'u_height', 'devices', 'u_consumed', 'utilization')
+        fields = ('pk', 'name', 'site', 'group', 'facility_id', 'tenant', 'u_height', 'devices', 'u_consumed',
+                  'utilization')
 
 
 class RackImportTable(BaseTable):
@@ -116,11 +119,12 @@ class RackImportTable(BaseTable):
     site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
     group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
     facility_id = tables.Column(verbose_name='Facility ID')
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
     u_height = tables.Column(verbose_name='Height (U)')
 
     class Meta(BaseTable.Meta):
         model = Rack
-        fields = ('site', 'group', 'name', 'facility_id', 'u_height')
+        fields = ('site', 'group', 'name', 'facility_id', 'tenant', 'u_height')
 
 
 #
@@ -259,6 +263,7 @@ class DeviceTable(BaseTable):
     pk = ToggleColumn()
     status = tables.TemplateColumn(template_code=STATUS_ICON, verbose_name='')
     name = tables.TemplateColumn(template_code=DEVICE_LINK, verbose_name='Name')
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
     site = tables.Column(accessor=Accessor('rack.site'), verbose_name='Site')
     rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
     device_role = tables.Column(verbose_name='Role')
@@ -268,11 +273,12 @@ class DeviceTable(BaseTable):
 
     class Meta(BaseTable.Meta):
         model = Device
-        fields = ('pk', 'name', 'status', 'site', 'rack', 'device_role', 'device_type', 'primary_ip')
+        fields = ('pk', 'name', 'status', 'tenant', 'site', 'rack', 'device_role', 'device_type', 'primary_ip')
 
 
 class DeviceImportTable(BaseTable):
     name = tables.TemplateColumn(template_code=DEVICE_LINK, verbose_name='Name')
+    tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
     site = tables.Column(accessor=Accessor('rack.site'), verbose_name='Site')
     rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
     position = tables.Column(verbose_name='Position')
@@ -281,7 +287,7 @@ class DeviceImportTable(BaseTable):
 
     class Meta(BaseTable.Meta):
         model = Device
-        fields = ('name', 'site', 'rack', 'position', 'device_role', 'device_type')
+        fields = ('name', 'tenant', 'site', 'rack', 'position', 'device_role', 'device_type')
         empty_text = False
 
 

+ 1 - 1
netbox/dcim/views.py

@@ -61,7 +61,7 @@ def expand_pattern(string):
 #
 
 class SiteListView(ObjectListView):
-    queryset = Site.objects.all()
+    queryset = Site.objects.select_related('tenant')
     filter = filters.SiteFilter
     table = tables.SiteTable
     template_name = 'dcim/site_list.html'

+ 10 - 0
netbox/templates/dcim/device.html

@@ -15,6 +15,16 @@
             </div>
             <table class="table table-hover panel-body">
                 <tr>
+                    <td>Tenant</td>
+                    <td>
+                        {% if device.tenant %}
+                            <a href="{{ device.tenant.get_absolute_url }}">{{ device.tenant }}</a>
+                        {% else %}
+                            <span class="text-muted">None</span>
+                        {% endif %}
+                    </td>
+                </tr>
+                <tr>
                     <td>Site</td>
                     <td>
                         <a href="{% url 'dcim:site' slug=device.rack.site.slug %}">{{ device.rack.site }}</a>

+ 1 - 0
netbox/templates/dcim/device_bulk_edit.html

@@ -9,6 +9,7 @@
             <td><a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a></td>
             <td>{{ device.device_type }}</td>
             <td>{{ device.device_role }}</td>
+            <td>{{ device.tenant }}</td>
             <td>{{ device.serial }}</td>
         </tr>
     {% endfor %}

+ 1 - 0
netbox/templates/dcim/device_edit.html

@@ -7,6 +7,7 @@
         <div class="panel-body">
             {% render_field form.name %}
             {% render_field form.device_role %}
+            {% render_field form.tenant %}
         </div>
     </div>
     <div class="panel panel-default">

+ 6 - 1
netbox/templates/dcim/device_import.html

@@ -37,6 +37,11 @@
 					<td>ToR Switch</td>
 				</tr>
 				<tr>
+					<td>Tenant</td>
+					<td>Name of tenant (optional)</td>
+					<td>Pied Piper</td>
+				</tr>
+				<tr>
 					<td>Device manufacturer</td>
 					<td>Hardware manufacturer</td>
 					<td>Juniper</td>
@@ -79,7 +84,7 @@
 			</tbody>
 		</table>
 		<h4>Example</h4>
-		<pre>rack101_sw1,ToR Switch,Juniper,EX4300-48T,Juniper Junos,CAB00577291,Ashburn-VA,R101,21,Rear</pre>
+		<pre>rack101_sw1,ToR Switch,Pied Piper,Juniper,EX4300-48T,Juniper Junos,CAB00577291,Ashburn-VA,R101,21,Rear</pre>
 	</div>
 </div>
 {% endblock %}

+ 10 - 0
netbox/templates/dcim/rack.html

@@ -87,6 +87,16 @@
                     </td>
                 </tr>
                 <tr>
+                    <td>Tenant</td>
+                    <td>
+                        {% if rack.tenant %}
+                            <a href="{{ rack.tenant.get_absolute_url }}">{{ rack.tenant }}</a>
+                        {% else %}
+                            <span class="text-muted">None</span>
+                        {% endif %}
+                    </td>
+                </tr>
+                <tr>
                     <td>Height</td>
                     <td>{{ rack.u_height }}U</td>
                 </tr>

+ 1 - 0
netbox/templates/dcim/rack_bulk_edit.html

@@ -9,6 +9,7 @@
             <td><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack }}</a></td>
             <td>{{ rack.facility_id }}</td>
             <td>{{ rack.site }}</td>
+            <td>{{ rack.tenant }}</td>
             <td>{{ rack.u_height }}</td>
         </tr>
     {% endfor %}

+ 1 - 0
netbox/templates/dcim/rack_edit.html

@@ -9,6 +9,7 @@
             {% render_field form.group %}
             {% render_field form.name %}
             {% render_field form.facility_id %}
+            {% render_field form.tenant %}
             {% render_field form.u_height %}
         </div>
     </div>

+ 6 - 1
netbox/templates/dcim/rack_import.html

@@ -49,6 +49,11 @@
 					<td>J12.100</td>
 				</tr>
 				<tr>
+					<td>Tenant</td>
+					<td>Name of tenant (optional)</td>
+					<td>Pied Piper</td>
+				</tr>
+				<tr>
 					<td>Height</td>
 					<td>Height in rack units</td>
 					<td>42</td>
@@ -56,7 +61,7 @@
 			</tbody>
 		</table>
 		<h4>Example</h4>
-		<pre>DC-4,Cage 1400,R101,J12.100,42</pre>
+		<pre>DC-4,Cage 1400,R101,J12.100,Pied Piper,42</pre>
 	</div>
 </div>
 {% endblock %}

+ 10 - 0
netbox/templates/dcim/site.html

@@ -53,6 +53,16 @@
             </div>
             <table class="table table-hover panel-body">
                 <tr>
+                    <td>Tenant</td>
+                    <td>
+                        {% if site.tenant %}
+                            <a href="{{ site.tenant.get_absolute_url }}">{{ site.tenant }}</a>
+                        {% else %}
+                            <span class="text-muted">None</span>
+                        {% endif %}
+                    </td>
+                </tr>
+                <tr>
                     <td>Facility</td>
                     <td>{{ site.facility }}</td>
                 </tr>

+ 1 - 0
netbox/templates/dcim/site_edit.html

@@ -7,6 +7,7 @@
         <div class="panel-body">
             {% render_field form.name %}
             {% render_field form.slug %}
+            {% render_field form.tenant %}
             {% render_field form.facility %}
             {% render_field form.asn %}
             {% render_field form.physical_address %}

+ 6 - 1
netbox/templates/dcim/site_import.html

@@ -39,6 +39,11 @@
 					<td>ash4-south</td>
 				</tr>
 				<tr>
+					<td>Tenant</td>
+					<td>Name of tenant (optional)</td>
+					<td>Pied Piper</td>
+				</tr>
+				<tr>
 					<td>Facility</td>
 					<td>Name of the hosting facility (optional)</td>
 					<td>Equinix DC6</td>
@@ -51,7 +56,7 @@
 			</tbody>
 		</table>
 		<h4>Example</h4>
-		<pre>ASH-4 South,ash4-south,Equinix DC6,65000</pre>
+		<pre>ASH-4 South,ash4-south,Pied Piper,Equinix DC6,65000</pre>
 	</div>
 </div>
 {% endblock %}