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

Add dedicated views for nested group models

Jeremy Stretch 4 лет назад
Родитель
Сommit
2820d26a0f

+ 3 - 3
netbox/dcim/models/sites.py

@@ -56,7 +56,7 @@ class Region(NestedGroupModel):
     csv_headers = ['name', 'slug', 'parent', 'description']
     csv_headers = ['name', 'slug', 'parent', 'description']
 
 
     def get_absolute_url(self):
     def get_absolute_url(self):
-        return "{}?region={}".format(reverse('dcim:site_list'), self.slug)
+        return reverse('dcim:region', args=[self.pk])
 
 
     def to_csv(self):
     def to_csv(self):
         return (
         return (
@@ -108,7 +108,7 @@ class SiteGroup(NestedGroupModel):
     csv_headers = ['name', 'slug', 'parent', 'description']
     csv_headers = ['name', 'slug', 'parent', 'description']
 
 
     def get_absolute_url(self):
     def get_absolute_url(self):
-        return "{}?group={}".format(reverse('dcim:site_list'), self.slug)
+        return reverse('dcim:sitegroup', args=[self.pk])
 
 
     def to_csv(self):
     def to_csv(self):
         return (
         return (
@@ -324,7 +324,7 @@ class Location(NestedGroupModel):
         ]
         ]
 
 
     def get_absolute_url(self):
     def get_absolute_url(self):
-        return "{}?location_id={}".format(reverse('dcim:rack_list'), self.pk)
+        return reverse('dcim:location', args=[self.pk])
 
 
     def to_csv(self):
     def to_csv(self):
         return (
         return (

+ 4 - 2
netbox/dcim/tables/racks.py

@@ -19,12 +19,14 @@ __all__ = (
 
 
 
 
 #
 #
-# Rack groups
+# Locations
 #
 #
 
 
 class LocationTable(BaseTable):
 class LocationTable(BaseTable):
     pk = ToggleColumn()
     pk = ToggleColumn()
-    name = MPTTColumn()
+    name = MPTTColumn(
+        linkify=True
+    )
     site = tables.Column(
     site = tables.Column(
         linkify=True
         linkify=True
     )
     )

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

@@ -17,7 +17,9 @@ __all__ = (
 
 
 class RegionTable(BaseTable):
 class RegionTable(BaseTable):
     pk = ToggleColumn()
     pk = ToggleColumn()
-    name = MPTTColumn()
+    name = MPTTColumn(
+        linkify=True
+    )
     site_count = tables.Column(
     site_count = tables.Column(
         verbose_name='Sites'
         verbose_name='Sites'
     )
     )
@@ -35,7 +37,9 @@ class RegionTable(BaseTable):
 
 
 class SiteGroupTable(BaseTable):
 class SiteGroupTable(BaseTable):
     pk = ToggleColumn()
     pk = ToggleColumn()
-    name = MPTTColumn()
+    name = MPTTColumn(
+        linkify=True
+    )
     site_count = tables.Column(
     site_count = tables.Column(
         verbose_name='Sites'
         verbose_name='Sites'
     )
     )

+ 3 - 0
netbox/dcim/urls.py

@@ -14,6 +14,7 @@ urlpatterns = [
     path('regions/import/', views.RegionBulkImportView.as_view(), name='region_import'),
     path('regions/import/', views.RegionBulkImportView.as_view(), name='region_import'),
     path('regions/edit/', views.RegionBulkEditView.as_view(), name='region_bulk_edit'),
     path('regions/edit/', views.RegionBulkEditView.as_view(), name='region_bulk_edit'),
     path('regions/delete/', views.RegionBulkDeleteView.as_view(), name='region_bulk_delete'),
     path('regions/delete/', views.RegionBulkDeleteView.as_view(), name='region_bulk_delete'),
+    path('regions/<int:pk>/', views.RegionView.as_view(), name='region'),
     path('regions/<int:pk>/edit/', views.RegionEditView.as_view(), name='region_edit'),
     path('regions/<int:pk>/edit/', views.RegionEditView.as_view(), name='region_edit'),
     path('regions/<int:pk>/delete/', views.RegionDeleteView.as_view(), name='region_delete'),
     path('regions/<int:pk>/delete/', views.RegionDeleteView.as_view(), name='region_delete'),
     path('regions/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='region_changelog', kwargs={'model': Region}),
     path('regions/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='region_changelog', kwargs={'model': Region}),
@@ -24,6 +25,7 @@ urlpatterns = [
     path('site-groups/import/', views.SiteGroupBulkImportView.as_view(), name='sitegroup_import'),
     path('site-groups/import/', views.SiteGroupBulkImportView.as_view(), name='sitegroup_import'),
     path('site-groups/edit/', views.SiteGroupBulkEditView.as_view(), name='sitegroup_bulk_edit'),
     path('site-groups/edit/', views.SiteGroupBulkEditView.as_view(), name='sitegroup_bulk_edit'),
     path('site-groups/delete/', views.SiteGroupBulkDeleteView.as_view(), name='sitegroup_bulk_delete'),
     path('site-groups/delete/', views.SiteGroupBulkDeleteView.as_view(), name='sitegroup_bulk_delete'),
+    path('site-groups/<int:pk>/', views.SiteGroupView.as_view(), name='sitegroup'),
     path('site-groups/<int:pk>/edit/', views.SiteGroupEditView.as_view(), name='sitegroup_edit'),
     path('site-groups/<int:pk>/edit/', views.SiteGroupEditView.as_view(), name='sitegroup_edit'),
     path('site-groups/<int:pk>/delete/', views.SiteGroupDeleteView.as_view(), name='sitegroup_delete'),
     path('site-groups/<int:pk>/delete/', views.SiteGroupDeleteView.as_view(), name='sitegroup_delete'),
     path('site-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='sitegroup_changelog', kwargs={'model': SiteGroup}),
     path('site-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='sitegroup_changelog', kwargs={'model': SiteGroup}),
@@ -47,6 +49,7 @@ urlpatterns = [
     path('locations/import/', views.LocationBulkImportView.as_view(), name='location_import'),
     path('locations/import/', views.LocationBulkImportView.as_view(), name='location_import'),
     path('locations/edit/', views.LocationBulkEditView.as_view(), name='location_bulk_edit'),
     path('locations/edit/', views.LocationBulkEditView.as_view(), name='location_bulk_edit'),
     path('locations/delete/', views.LocationBulkDeleteView.as_view(), name='location_bulk_delete'),
     path('locations/delete/', views.LocationBulkDeleteView.as_view(), name='location_bulk_delete'),
+    path('locations/<int:pk>/', views.LocationView.as_view(), name='location'),
     path('locations/<int:pk>/edit/', views.LocationEditView.as_view(), name='location_edit'),
     path('locations/<int:pk>/edit/', views.LocationEditView.as_view(), name='location_edit'),
     path('locations/<int:pk>/delete/', views.LocationDeleteView.as_view(), name='location_delete'),
     path('locations/<int:pk>/delete/', views.LocationDeleteView.as_view(), name='location_delete'),
     path('locations/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='location_changelog', kwargs={'model': Location}),
     path('locations/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='location_changelog', kwargs={'model': Location}),

+ 51 - 0
netbox/dcim/views.py

@@ -112,6 +112,23 @@ class RegionListView(generic.ObjectListView):
     table = tables.RegionTable
     table = tables.RegionTable
 
 
 
 
+class RegionView(generic.ObjectView):
+    queryset = Region.objects.all()
+
+    def get_extra_context(self, request, instance):
+        sites = Site.objects.restrict(request.user, 'view').filter(
+            region=instance
+        )
+
+        sites_table = tables.SiteTable(sites)
+        sites_table.columns.hide('region')
+        paginate_table(sites_table, request)
+
+        return {
+            'sites_table': sites_table,
+        }
+
+
 class RegionEditView(generic.ObjectEditView):
 class RegionEditView(generic.ObjectEditView):
     queryset = Region.objects.all()
     queryset = Region.objects.all()
     model_form = forms.RegionForm
     model_form = forms.RegionForm
@@ -169,6 +186,23 @@ class SiteGroupListView(generic.ObjectListView):
     table = tables.SiteGroupTable
     table = tables.SiteGroupTable
 
 
 
 
+class SiteGroupView(generic.ObjectView):
+    queryset = SiteGroup.objects.all()
+
+    def get_extra_context(self, request, instance):
+        sites = Site.objects.restrict(request.user, 'view').filter(
+            group=instance
+        )
+
+        sites_table = tables.SiteTable(sites)
+        sites_table.columns.hide('group')
+        paginate_table(sites_table, request)
+
+        return {
+            'sites_table': sites_table,
+        }
+
+
 class SiteGroupEditView(generic.ObjectEditView):
 class SiteGroupEditView(generic.ObjectEditView):
     queryset = SiteGroup.objects.all()
     queryset = SiteGroup.objects.all()
     model_form = forms.SiteGroupForm
     model_form = forms.SiteGroupForm
@@ -291,6 +325,23 @@ class LocationListView(generic.ObjectListView):
     table = tables.LocationTable
     table = tables.LocationTable
 
 
 
 
+class LocationView(generic.ObjectView):
+    queryset = Location.objects.all()
+
+    def get_extra_context(self, request, instance):
+        devices = Device.objects.restrict(request.user, 'view').filter(
+            location=instance
+        )
+
+        devices_table = tables.DeviceTable(devices)
+        devices_table.columns.hide('location')
+        paginate_table(devices_table, request)
+
+        return {
+            'devices_table': devices_table,
+        }
+
+
 class LocationEditView(generic.ObjectEditView):
 class LocationEditView(generic.ObjectEditView):
     queryset = Location.objects.all()
     queryset = Location.objects.all()
     model_form = forms.LocationForm
     model_form = forms.LocationForm

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

@@ -0,0 +1,73 @@
+{% extends 'generic/object.html' %}
+{% load helpers %}
+{% load plugins %}
+
+{% block breadcrumbs %}
+  <li><a href="{% url 'dcim:location_list' %}">Location</a></li>
+  {% for location in object.get_ancestors %}
+    <li><a href="{{ location.get_absolute_url }}">{{ location }}</a></li>
+  {% endfor %}
+  <li>{{ object }}</li>
+{% endblock %}
+
+{% block content %}
+<div class="row">
+	<div class="col-md-6">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <strong>Location</strong>
+      </div>
+      <table class="table table-hover panel-body attr-table">
+        <tr>
+          <td>Name</td>
+          <td>{{ object.name }}</td>
+        </tr>
+        <tr>
+          <td>Description</td>
+          <td>{{ object.description|placeholder }}</td>
+        </tr>
+        <tr>
+          <td>Parent</td>
+          <td>
+            {% if object.parent %}
+              <a href="{{ object.parent.get_absolute_url }}">{{ object.parent }}</a>
+            {% else %}
+              <span class="text-muted">&mdash;</span>
+            {% endif %}
+          </td>
+        </tr>
+        <tr>
+          <td>Devices</td>
+          <td>
+            <a href="{% url 'dcim:device_list' %}?location_id={{ object.pk }}">{{ devices_table.rows|length }}</a>
+          </td>
+        </tr>
+      </table>
+    </div>
+    {% plugin_left_page object %}
+  </div>
+	<div class="col-md-6">
+    {% include 'inc/custom_fields_panel.html' %}
+    {% plugin_right_page object %}
+	</div>
+</div>
+<div class="row">
+	<div class="col-md-12">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <strong>Devices</strong>
+      </div>
+      {% include 'inc/table.html' with table=devices_table %}
+      {% if perms.dcim.add_device %}
+        <div class="panel-footer text-right noprint">
+          <a href="{% url 'dcim:device_add' %}?location={{ object.pk }}" class="btn btn-xs btn-primary">
+            <span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add device
+          </a>
+        </div>
+      {% endif %}
+      </div>
+      {% include 'inc/paginator.html' with paginator=devices_table.paginator page=devices_table.page %}
+      {% plugin_full_width_page object %}
+  </div>
+</div>
+{% endblock %}

+ 73 - 0
netbox/templates/dcim/region.html

@@ -0,0 +1,73 @@
+{% extends 'generic/object.html' %}
+{% load helpers %}
+{% load plugins %}
+
+{% block breadcrumbs %}
+  <li><a href="{% url 'dcim:region_list' %}">Region</a></li>
+  {% for region in object.get_ancestors %}
+    <li><a href="{{ region.get_absolute_url }}">{{ region }}</a></li>
+  {% endfor %}
+  <li>{{ object }}</li>
+{% endblock %}
+
+{% block content %}
+<div class="row">
+	<div class="col-md-6">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <strong>Region</strong>
+      </div>
+      <table class="table table-hover panel-body attr-table">
+        <tr>
+          <td>Name</td>
+          <td>{{ object.name }}</td>
+        </tr>
+        <tr>
+          <td>Description</td>
+          <td>{{ object.description|placeholder }}</td>
+        </tr>
+        <tr>
+          <td>Parent</td>
+          <td>
+            {% if object.parent %}
+              <a href="{{ object.parent.get_absolute_url }}">{{ object.parent }}</a>
+            {% else %}
+              <span class="text-muted">&mdash;</span>
+            {% endif %}
+          </td>
+        </tr>
+        <tr>
+          <td>Sites</td>
+          <td>
+            <a href="{% url 'dcim:site_list' %}?region_id={{ object.pk }}">{{ sites_table.rows|length }}</a>
+          </td>
+        </tr>
+      </table>
+    </div>
+    {% plugin_left_page object %}
+  </div>
+	<div class="col-md-6">
+    {% include 'inc/custom_fields_panel.html' %}
+    {% plugin_right_page object %}
+	</div>
+</div>
+<div class="row">
+	<div class="col-md-12">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <strong>Sites</strong>
+      </div>
+      {% include 'inc/table.html' with table=sites_table %}
+      {% if perms.dcim.add_site %}
+        <div class="panel-footer text-right noprint">
+          <a href="{% url 'dcim:site_add' %}?region={{ object.pk }}" class="btn btn-xs btn-primary">
+            <span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add site
+          </a>
+        </div>
+      {% endif %}
+      </div>
+      {% include 'inc/paginator.html' with paginator=sites_table.paginator page=sites_table.page %}
+      {% plugin_full_width_page object %}
+  </div>
+</div>
+{% endblock %}

+ 73 - 0
netbox/templates/dcim/sitegroup.html

@@ -0,0 +1,73 @@
+{% extends 'generic/object.html' %}
+{% load helpers %}
+{% load plugins %}
+
+{% block breadcrumbs %}
+  <li><a href="{% url 'dcim:sitegroup_list' %}">Site Groups</a></li>
+  {% for sitegroup in object.get_ancestors %}
+    <li><a href="{{ sitegroup.get_absolute_url }}">{{ sitegroup }}</a></li>
+  {% endfor %}
+  <li>{{ object }}</li>
+{% endblock %}
+
+{% block content %}
+<div class="row">
+	<div class="col-md-6">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <strong>Site Group</strong>
+      </div>
+      <table class="table table-hover panel-body attr-table">
+        <tr>
+          <td>Name</td>
+          <td>{{ object.name }}</td>
+        </tr>
+        <tr>
+          <td>Description</td>
+          <td>{{ object.description|placeholder }}</td>
+        </tr>
+        <tr>
+          <td>Parent</td>
+          <td>
+            {% if object.parent %}
+              <a href="{{ object.parent.get_absolute_url }}">{{ object.parent }}</a>
+            {% else %}
+              <span class="text-muted">&mdash;</span>
+            {% endif %}
+          </td>
+        </tr>
+        <tr>
+          <td>Sites</td>
+          <td>
+            <a href="{% url 'dcim:site_list' %}?group_id={{ object.pk }}">{{ sites_table.rows|length }}</a>
+          </td>
+        </tr>
+      </table>
+    </div>
+    {% plugin_left_page object %}
+  </div>
+	<div class="col-md-6">
+    {% include 'inc/custom_fields_panel.html' %}
+    {% plugin_right_page object %}
+	</div>
+</div>
+<div class="row">
+	<div class="col-md-12">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <strong>Sites</strong>
+      </div>
+      {% include 'inc/table.html' with table=sites_table %}
+      {% if perms.dcim.add_site %}
+        <div class="panel-footer text-right noprint">
+          <a href="{% url 'dcim:site_add' %}?group={{ object.pk }}" class="btn btn-xs btn-primary">
+            <span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add site
+          </a>
+        </div>
+      {% endif %}
+      </div>
+      {% include 'inc/paginator.html' with paginator=sites_table.paginator page=sites_table.page %}
+      {% plugin_full_width_page object %}
+  </div>
+</div>
+{% endblock %}

+ 73 - 0
netbox/templates/tenancy/tenantgroup.html

@@ -0,0 +1,73 @@
+{% extends 'generic/object.html' %}
+{% load helpers %}
+{% load plugins %}
+
+{% block breadcrumbs %}
+  <li><a href="{% url 'tenancy:tenantgroup_list' %}">Tenant Groups</a></li>
+  {% for tenantgroup in object.get_ancestors %}
+    <li><a href="{{ tenantgroup.get_absolute_url }}">{{ tenantgroup }}</a></li>
+  {% endfor %}
+  <li>{{ object }}</li>
+{% endblock %}
+
+{% block content %}
+<div class="row">
+	<div class="col-md-6">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <strong>Tenant Group</strong>
+      </div>
+      <table class="table table-hover panel-body attr-table">
+        <tr>
+          <td>Name</td>
+          <td>{{ object.name }}</td>
+        </tr>
+        <tr>
+          <td>Description</td>
+          <td>{{ object.description|placeholder }}</td>
+        </tr>
+        <tr>
+          <td>Parent</td>
+          <td>
+            {% if object.parent %}
+              <a href="{{ object.parent.get_absolute_url }}">{{ object.parent }}</a>
+            {% else %}
+              <span class="text-muted">&mdash;</span>
+            {% endif %}
+          </td>
+        </tr>
+        <tr>
+          <td>Sites</td>
+          <td>
+            <a href="{% url 'tenancy:tenant_list' %}?group_id={{ object.pk }}">{{ tenants_table.rows|length }}</a>
+          </td>
+        </tr>
+      </table>
+    </div>
+    {% plugin_left_page object %}
+  </div>
+	<div class="col-md-6">
+    {% include 'inc/custom_fields_panel.html' %}
+    {% plugin_right_page object %}
+	</div>
+</div>
+<div class="row">
+	<div class="col-md-12">
+    <div class="panel panel-default">
+      <div class="panel-heading">
+        <strong>Tenants</strong>
+      </div>
+      {% include 'inc/table.html' with table=tenants_table %}
+      {% if perms.tenancy.add_tenant %}
+        <div class="panel-footer text-right noprint">
+          <a href="{% url 'tenancy:tenant_add' %}?group={{ object.pk }}" class="btn btn-xs btn-primary">
+            <span class="mdi mdi-plus-thick" aria-hidden="true"></span> Add tenant
+          </a>
+        </div>
+      {% endif %}
+      </div>
+      {% include 'inc/paginator.html' with paginator=tenants_table.paginator page=tenants_table.page %}
+      {% plugin_full_width_page object %}
+  </div>
+</div>
+{% endblock %}

+ 1 - 1
netbox/tenancy/models.py

@@ -45,7 +45,7 @@ class TenantGroup(NestedGroupModel):
         ordering = ['name']
         ordering = ['name']
 
 
     def get_absolute_url(self):
     def get_absolute_url(self):
-        return "{}?group={}".format(reverse('tenancy:tenant_list'), self.slug)
+        return reverse('tenancy:tenantgroup', args=[self.pk])
 
 
     def to_csv(self):
     def to_csv(self):
         return (
         return (

+ 3 - 1
netbox/tenancy/tables.py

@@ -35,7 +35,9 @@ class TenantColumn(tables.TemplateColumn):
 
 
 class TenantGroupTable(BaseTable):
 class TenantGroupTable(BaseTable):
     pk = ToggleColumn()
     pk = ToggleColumn()
-    name = MPTTColumn()
+    name = MPTTColumn(
+        linkify=True
+    )
     tenant_count = LinkedCountColumn(
     tenant_count = LinkedCountColumn(
         viewname='tenancy:tenant_list',
         viewname='tenancy:tenant_list',
         url_params={'group': 'slug'},
         url_params={'group': 'slug'},

+ 1 - 0
netbox/tenancy/urls.py

@@ -13,6 +13,7 @@ urlpatterns = [
     path('tenant-groups/import/', views.TenantGroupBulkImportView.as_view(), name='tenantgroup_import'),
     path('tenant-groups/import/', views.TenantGroupBulkImportView.as_view(), name='tenantgroup_import'),
     path('tenant-groups/edit/', views.TenantGroupBulkEditView.as_view(), name='tenantgroup_bulk_edit'),
     path('tenant-groups/edit/', views.TenantGroupBulkEditView.as_view(), name='tenantgroup_bulk_edit'),
     path('tenant-groups/delete/', views.TenantGroupBulkDeleteView.as_view(), name='tenantgroup_bulk_delete'),
     path('tenant-groups/delete/', views.TenantGroupBulkDeleteView.as_view(), name='tenantgroup_bulk_delete'),
+    path('tenant-groups/<int:pk>/', views.TenantGroupView.as_view(), name='tenantgroup'),
     path('tenant-groups/<int:pk>/edit/', views.TenantGroupEditView.as_view(), name='tenantgroup_edit'),
     path('tenant-groups/<int:pk>/edit/', views.TenantGroupEditView.as_view(), name='tenantgroup_edit'),
     path('tenant-groups/<int:pk>/delete/', views.TenantGroupDeleteView.as_view(), name='tenantgroup_delete'),
     path('tenant-groups/<int:pk>/delete/', views.TenantGroupDeleteView.as_view(), name='tenantgroup_delete'),
     path('tenant-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='tenantgroup_changelog', kwargs={'model': TenantGroup}),
     path('tenant-groups/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='tenantgroup_changelog', kwargs={'model': TenantGroup}),

+ 18 - 2
netbox/tenancy/views.py

@@ -1,9 +1,8 @@
-from django.shortcuts import get_object_or_404, render
-
 from circuits.models import Circuit
 from circuits.models import Circuit
 from dcim.models import Site, Rack, Device, RackReservation
 from dcim.models import Site, Rack, Device, RackReservation
 from ipam.models import IPAddress, Prefix, VLAN, VRF
 from ipam.models import IPAddress, Prefix, VLAN, VRF
 from netbox.views import generic
 from netbox.views import generic
+from utilities.tables import paginate_table
 from virtualization.models import VirtualMachine, Cluster
 from virtualization.models import VirtualMachine, Cluster
 from . import filters, forms, tables
 from . import filters, forms, tables
 from .models import Tenant, TenantGroup
 from .models import Tenant, TenantGroup
@@ -24,6 +23,23 @@ class TenantGroupListView(generic.ObjectListView):
     table = tables.TenantGroupTable
     table = tables.TenantGroupTable
 
 
 
 
+class TenantGroupView(generic.ObjectView):
+    queryset = TenantGroup.objects.all()
+
+    def get_extra_context(self, request, instance):
+        tenants = Tenant.objects.restrict(request.user, 'view').filter(
+            group=instance
+        )
+
+        tenants_table = tables.TenantTable(tenants)
+        tenants_table.columns.hide('group')
+        paginate_table(tenants_table, request)
+
+        return {
+            'tenants_table': tenants_table,
+        }
+
+
 class TenantGroupEditView(generic.ObjectEditView):
 class TenantGroupEditView(generic.ObjectEditView):
     queryset = TenantGroup.objects.all()
     queryset = TenantGroup.objects.all()
     model_form = forms.TenantGroupForm
     model_form = forms.TenantGroupForm