Merge pull request #4353 from netbox-community/3939-nested-tenantgroups

Closes #3939: Nested tenant groups
This commit is contained in:
Jeremy Stretch 2020-03-11 21:20:14 -04:00 committed by GitHub
commit b92e518370
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 289 additions and 88 deletions

View File

@ -139,7 +139,8 @@ class CircuitTestCase(TestCase):
TenantGroup(name='Tenant group 2', slug='tenant-group-2'), TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant group 3', slug='tenant-group-3'), TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
) )
TenantGroup.objects.bulk_create(tenant_groups) for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = ( tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]), Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),

View File

@ -81,7 +81,8 @@ class SiteTestCase(TestCase):
TenantGroup(name='Tenant group 2', slug='tenant-group-2'), TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant group 3', slug='tenant-group-3'), TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
) )
TenantGroup.objects.bulk_create(tenant_groups) for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = ( tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]), Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
@ -311,7 +312,8 @@ class RackTestCase(TestCase):
TenantGroup(name='Tenant group 2', slug='tenant-group-2'), TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant group 3', slug='tenant-group-3'), TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
) )
TenantGroup.objects.bulk_create(tenant_groups) for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = ( tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]), Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
@ -471,7 +473,8 @@ class RackReservationTestCase(TestCase):
TenantGroup(name='Tenant group 2', slug='tenant-group-2'), TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant group 3', slug='tenant-group-3'), TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
) )
TenantGroup.objects.bulk_create(tenant_groups) for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = ( tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]), Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
@ -1187,7 +1190,8 @@ class DeviceTestCase(TestCase):
TenantGroup(name='Tenant group 2', slug='tenant-group-2'), TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant group 3', slug='tenant-group-3'), TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
) )
TenantGroup.objects.bulk_create(tenant_groups) for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = ( tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]), Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),

View File

@ -402,8 +402,10 @@ class ConfigContextTest(APITestCase):
role2 = DeviceRole.objects.create(name='Test Role 2', slug='test-role-2') role2 = DeviceRole.objects.create(name='Test Role 2', slug='test-role-2')
platform1 = Platform.objects.create(name='Test Platform 1', slug='test-platform-1') platform1 = Platform.objects.create(name='Test Platform 1', slug='test-platform-1')
platform2 = Platform.objects.create(name='Test Platform 2', slug='test-platform-2') platform2 = Platform.objects.create(name='Test Platform 2', slug='test-platform-2')
tenantgroup1 = TenantGroup.objects.create(name='Test Tenant Group 1', slug='test-tenant-group-1') tenantgroup1 = TenantGroup(name='Test Tenant Group 1', slug='test-tenant-group-1')
tenantgroup2 = TenantGroup.objects.create(name='Test Tenant Group 2', slug='test-tenant-group-2') tenantgroup1.save()
tenantgroup2 = TenantGroup(name='Test Tenant Group 2', slug='test-tenant-group-2')
tenantgroup2.save()
tenant1 = Tenant.objects.create(name='Test Tenant 1', slug='test-tenant-1') tenant1 = Tenant.objects.create(name='Test Tenant 1', slug='test-tenant-1')
tenant2 = Tenant.objects.create(name='Test Tenant 2', slug='test-tenant-2') tenant2 = Tenant.objects.create(name='Test Tenant 2', slug='test-tenant-2')
tag1 = Tag.objects.create(name='Test Tag 1', slug='test-tag-1') tag1 = Tag.objects.create(name='Test Tag 1', slug='test-tag-1')

View File

@ -128,7 +128,8 @@ class ConfigContextTestCase(TestCase):
TenantGroup(name='Tenant Group 2', slug='tenant-group-2'), TenantGroup(name='Tenant Group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant Group 3', slug='tenant-group-3'), TenantGroup(name='Tenant Group 3', slug='tenant-group-3'),
) )
TenantGroup.objects.bulk_create(tenant_groups) for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = ( tenants = (
Tenant(name='Tenant 1', slug='tenant-1'), Tenant(name='Tenant 1', slug='tenant-1'),

View File

@ -20,7 +20,8 @@ class VRFTestCase(TestCase):
TenantGroup(name='Tenant group 2', slug='tenant-group-2'), TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant group 3', slug='tenant-group-3'), TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
) )
TenantGroup.objects.bulk_create(tenant_groups) for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = ( tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]), Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
@ -222,7 +223,8 @@ class PrefixTestCase(TestCase):
TenantGroup(name='Tenant group 2', slug='tenant-group-2'), TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant group 3', slug='tenant-group-3'), TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
) )
TenantGroup.objects.bulk_create(tenant_groups) for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = ( tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]), Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
@ -379,7 +381,8 @@ class IPAddressTestCase(TestCase):
TenantGroup(name='Tenant group 2', slug='tenant-group-2'), TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant group 3', slug='tenant-group-3'), TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
) )
TenantGroup.objects.bulk_create(tenant_groups) for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = ( tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]), Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
@ -593,7 +596,8 @@ class VLANTestCase(TestCase):
TenantGroup(name='Tenant group 2', slug='tenant-group-2'), TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant group 3', slug='tenant-group-3'), TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
) )
TenantGroup.objects.bulk_create(tenant_groups) for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = ( tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]), Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),

View File

@ -12,11 +12,12 @@ from .nested_serializers import *
# #
class TenantGroupSerializer(ValidatedModelSerializer): class TenantGroupSerializer(ValidatedModelSerializer):
parent = NestedTenantGroupSerializer(required=False, allow_null=True)
tenant_count = serializers.IntegerField(read_only=True) tenant_count = serializers.IntegerField(read_only=True)
class Meta: class Meta:
model = TenantGroup model = TenantGroup
fields = ['id', 'name', 'slug', 'tenant_count'] fields = ['id', 'name', 'slug', 'parent', 'tenant_count']
class TenantSerializer(TaggitSerializer, CustomFieldModelSerializer): class TenantSerializer(TaggitSerializer, CustomFieldModelSerializer):

View File

@ -2,7 +2,7 @@ import django_filters
from django.db.models import Q from django.db.models import Q
from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet from extras.filters import CustomFieldFilterSet, CreatedUpdatedFilterSet
from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter
from .models import Tenant, TenantGroup from .models import Tenant, TenantGroup
@ -14,6 +14,16 @@ __all__ = (
class TenantGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class TenantGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
parent_id = django_filters.ModelMultipleChoiceFilter(
queryset=TenantGroup.objects.all(),
label='Tenant group (ID)',
)
parent = django_filters.ModelMultipleChoiceFilter(
field_name='parent__slug',
queryset=TenantGroup.objects.all(),
to_field_name='slug',
label='Tenant group group (slug)',
)
class Meta: class Meta:
model = TenantGroup model = TenantGroup
@ -25,15 +35,18 @@ class TenantFilterSet(BaseFilterSet, CustomFieldFilterSet, CreatedUpdatedFilterS
method='search', method='search',
label='Search', label='Search',
) )
group_id = django_filters.ModelMultipleChoiceFilter( group_id = TreeNodeMultipleChoiceFilter(
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
label='Group (ID)', field_name='group',
lookup_expr='in',
label='Tenant group (ID)',
) )
group = django_filters.ModelMultipleChoiceFilter( group = TreeNodeMultipleChoiceFilter(
field_name='group__slug',
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
field_name='group',
lookup_expr='in',
to_field_name='slug', to_field_name='slug',
label='Group (slug)', label='Tenant group (slug)',
) )
tag = TagFilter() tag = TagFilter()
@ -56,16 +69,17 @@ class TenancyFilterSet(django_filters.FilterSet):
""" """
An inheritable FilterSet for models which support Tenant assignment. An inheritable FilterSet for models which support Tenant assignment.
""" """
tenant_group_id = django_filters.ModelMultipleChoiceFilter( tenant_group_id = TreeNodeMultipleChoiceFilter(
field_name='tenant__group__id',
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
to_field_name='id', field_name='tenant__group',
lookup_expr='in',
label='Tenant Group (ID)', label='Tenant Group (ID)',
) )
tenant_group = django_filters.ModelMultipleChoiceFilter( tenant_group = TreeNodeMultipleChoiceFilter(
field_name='tenant__group__slug',
queryset=TenantGroup.objects.all(), queryset=TenantGroup.objects.all(),
field_name='tenant__group',
to_field_name='slug', to_field_name='slug',
lookup_expr='in',
label='Tenant Group (slug)', label='Tenant Group (slug)',
) )
tenant_id = django_filters.ModelMultipleChoiceFilter( tenant_id = django_filters.ModelMultipleChoiceFilter(
@ -73,8 +87,8 @@ class TenancyFilterSet(django_filters.FilterSet):
label='Tenant (ID)', label='Tenant (ID)',
) )
tenant = django_filters.ModelMultipleChoiceFilter( tenant = django_filters.ModelMultipleChoiceFilter(
field_name='tenant__slug',
queryset=Tenant.objects.all(), queryset=Tenant.objects.all(),
field_name='tenant__slug',
to_field_name='slug', to_field_name='slug',
label='Tenant (slug)', label='Tenant (slug)',
) )

View File

@ -16,16 +16,32 @@ from .models import Tenant, TenantGroup
# #
class TenantGroupForm(BootstrapMixin, forms.ModelForm): class TenantGroupForm(BootstrapMixin, forms.ModelForm):
parent = DynamicModelChoiceField(
queryset=TenantGroup.objects.all(),
required=False,
widget=APISelect(
api_url="/api/tenancy/tenant-groups/"
)
)
slug = SlugField() slug = SlugField()
class Meta: class Meta:
model = TenantGroup model = TenantGroup
fields = [ fields = [
'name', 'slug', 'parent', 'name', 'slug',
] ]
class TenantGroupCSVForm(forms.ModelForm): class TenantGroupCSVForm(forms.ModelForm):
parent = forms.ModelChoiceField(
queryset=TenantGroup.objects.all(),
required=False,
to_field_name='name',
help_text='Name of parent tenant group',
error_messages={
'invalid_choice': 'Tenant group not found.',
}
)
slug = SlugField() slug = SlugField()
class Meta: class Meta:

View File

@ -0,0 +1,43 @@
from django.db import migrations, models
import django.db.models.deletion
import mptt.fields
class Migration(migrations.Migration):
dependencies = [
('tenancy', '0006_custom_tag_models'),
]
operations = [
migrations.AddField(
model_name='tenantgroup',
name='parent',
field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='tenancy.TenantGroup'),
),
migrations.AddField(
model_name='tenantgroup',
name='level',
field=models.PositiveIntegerField(default=0, editable=False),
preserve_default=False,
),
migrations.AddField(
model_name='tenantgroup',
name='lft',
field=models.PositiveIntegerField(default=1, editable=False),
preserve_default=False,
),
migrations.AddField(
model_name='tenantgroup',
name='rght',
field=models.PositiveIntegerField(default=2, editable=False),
preserve_default=False,
),
# tree_id will be set to a valid value during the following migration (which needs to be a separate migration)
migrations.AddField(
model_name='tenantgroup',
name='tree_id',
field=models.PositiveIntegerField(db_index=True, default=0, editable=False),
preserve_default=False,
),
]

View File

@ -0,0 +1,21 @@
from django.db import migrations
def rebuild_mptt(apps, schema_editor):
TenantGroup = apps.get_model('tenancy', 'TenantGroup')
for i, tenantgroup in enumerate(TenantGroup.objects.all(), start=1):
TenantGroup.objects.filter(pk=tenantgroup.pk).update(tree_id=i)
class Migration(migrations.Migration):
dependencies = [
('tenancy', '0007_nested_tenantgroups'),
]
operations = [
migrations.RunPython(
code=rebuild_mptt,
reverse_code=migrations.RunPython.noop
),
]

View File

@ -1,10 +1,12 @@
from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.fields import GenericRelation
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from mptt.models import MPTTModel, TreeForeignKey
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
from extras.models import CustomFieldModel, TaggedItem from extras.models import CustomFieldModel, ObjectChange, TaggedItem
from utilities.models import ChangeLoggedModel from utilities.models import ChangeLoggedModel
from utilities.utils import serialize_object
__all__ = ( __all__ = (
@ -13,7 +15,7 @@ __all__ = (
) )
class TenantGroup(ChangeLoggedModel): class TenantGroup(MPTTModel, ChangeLoggedModel):
""" """
An arbitrary collection of Tenants. An arbitrary collection of Tenants.
""" """
@ -24,12 +26,23 @@ class TenantGroup(ChangeLoggedModel):
slug = models.SlugField( slug = models.SlugField(
unique=True unique=True
) )
parent = TreeForeignKey(
to='self',
on_delete=models.CASCADE,
related_name='children',
blank=True,
null=True,
db_index=True
)
csv_headers = ['name', 'slug'] csv_headers = ['name', 'slug', 'parent']
class Meta: class Meta:
ordering = ['name'] ordering = ['name']
class MPTTMeta:
order_insertion_by = ['name']
def __str__(self): def __str__(self):
return self.name return self.name
@ -40,6 +53,16 @@ class TenantGroup(ChangeLoggedModel):
return ( return (
self.name, self.name,
self.slug, self.slug,
self.parent.name if self.parent else '',
)
def to_objectchange(self, action):
# Remove MPTT-internal fields
return ObjectChange(
changed_object=self,
object_repr=str(self),
action=action,
object_data=serialize_object(self, exclude=['level', 'lft', 'rght', 'tree_id'])
) )

View File

@ -3,6 +3,16 @@ import django_tables2 as tables
from utilities.tables import BaseTable, ToggleColumn from utilities.tables import BaseTable, ToggleColumn
from .models import Tenant, TenantGroup from .models import Tenant, TenantGroup
MPTT_LINK = """
{% if record.get_children %}
<span style="padding-left: {{ record.get_ancestors|length }}0px "><i class="fa fa-caret-right"></i>
{% else %}
<span style="padding-left: {{ record.get_ancestors|length }}9px">
{% endif %}
<a href="{{ record.get_absolute_url }}">{{ record.name }}</a>
</span>
"""
TENANTGROUP_ACTIONS = """ TENANTGROUP_ACTIONS = """
<a href="{% url 'tenancy:tenantgroup_changelog' slug=record.slug %}" class="btn btn-default btn-xs" title="Change log"> <a href="{% url 'tenancy:tenantgroup_changelog' slug=record.slug %}" class="btn btn-default btn-xs" title="Change log">
<i class="fa fa-history"></i> <i class="fa fa-history"></i>
@ -27,11 +37,18 @@ COL_TENANT = """
class TenantGroupTable(BaseTable): class TenantGroupTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.LinkColumn(verbose_name='Name') name = tables.TemplateColumn(
tenant_count = tables.Column(verbose_name='Tenants') template_code=MPTT_LINK,
slug = tables.Column(verbose_name='Slug') orderable=False
)
tenant_count = tables.Column(
verbose_name='Tenants'
)
slug = tables.Column()
actions = tables.TemplateColumn( actions = tables.TemplateColumn(
template_code=TENANTGROUP_ACTIONS, attrs={'td': {'class': 'text-right noprint'}}, verbose_name='' template_code=TENANTGROUP_ACTIONS,
attrs={'td': {'class': 'text-right noprint'}},
verbose_name=''
) )
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):

View File

@ -28,23 +28,34 @@ class TenantGroupTest(APITestCase):
super().setUp() super().setUp()
self.tenantgroup1 = TenantGroup.objects.create(name='Test Tenant Group 1', slug='test-tenant-group-1') self.parent_tenant_groups = (
self.tenantgroup2 = TenantGroup.objects.create(name='Test Tenant Group 2', slug='test-tenant-group-2') TenantGroup(name='Parent Tenant Group 1', slug='parent-tenant-group-1'),
self.tenantgroup3 = TenantGroup.objects.create(name='Test Tenant Group 3', slug='test-tenant-group-3') TenantGroup(name='Parent Tenant Group 2', slug='parent-tenant-group-2'),
)
for tenantgroup in self.parent_tenant_groups:
tenantgroup.save()
self.tenant_groups = (
TenantGroup(name='Tenant Group 1', slug='tenant-group-1', parent=self.parent_tenant_groups[0]),
TenantGroup(name='Tenant Group 2', slug='tenant-group-2', parent=self.parent_tenant_groups[0]),
TenantGroup(name='Tenant Group 3', slug='tenant-group-3', parent=self.parent_tenant_groups[0]),
)
for tenantgroup in self.tenant_groups:
tenantgroup.save()
def test_get_tenantgroup(self): def test_get_tenantgroup(self):
url = reverse('tenancy-api:tenantgroup-detail', kwargs={'pk': self.tenantgroup1.pk}) url = reverse('tenancy-api:tenantgroup-detail', kwargs={'pk': self.tenant_groups[0].pk})
response = self.client.get(url, **self.header) response = self.client.get(url, **self.header)
self.assertEqual(response.data['name'], self.tenantgroup1.name) self.assertEqual(response.data['name'], self.tenant_groups[0].name)
def test_list_tenantgroups(self): def test_list_tenantgroups(self):
url = reverse('tenancy-api:tenantgroup-list') url = reverse('tenancy-api:tenantgroup-list')
response = self.client.get(url, **self.header) response = self.client.get(url, **self.header)
self.assertEqual(response.data['count'], 3) self.assertEqual(response.data['count'], 5)
def test_list_tenantgroups_brief(self): def test_list_tenantgroups_brief(self):
@ -59,33 +70,38 @@ class TenantGroupTest(APITestCase):
def test_create_tenantgroup(self): def test_create_tenantgroup(self):
data = { data = {
'name': 'Test Tenant Group 4', 'name': 'Tenant Group 4',
'slug': 'test-tenant-group-4', 'slug': 'tenant-group-4',
'parent': self.parent_tenant_groups[0].pk,
} }
url = reverse('tenancy-api:tenantgroup-list') url = reverse('tenancy-api:tenantgroup-list')
response = self.client.post(url, data, format='json', **self.header) response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED) self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(TenantGroup.objects.count(), 4) self.assertEqual(TenantGroup.objects.count(), 6)
tenantgroup4 = TenantGroup.objects.get(pk=response.data['id']) tenantgroup4 = TenantGroup.objects.get(pk=response.data['id'])
self.assertEqual(tenantgroup4.name, data['name']) self.assertEqual(tenantgroup4.name, data['name'])
self.assertEqual(tenantgroup4.slug, data['slug']) self.assertEqual(tenantgroup4.slug, data['slug'])
self.assertEqual(tenantgroup4.parent_id, data['parent'])
def test_create_tenantgroup_bulk(self): def test_create_tenantgroup_bulk(self):
data = [ data = [
{ {
'name': 'Test Tenant Group 4', 'name': 'Tenant Group 4',
'slug': 'test-tenant-group-4', 'slug': 'tenant-group-4',
'parent': self.parent_tenant_groups[0].pk,
}, },
{ {
'name': 'Test Tenant Group 5', 'name': 'Tenant Group 5',
'slug': 'test-tenant-group-5', 'slug': 'tenant-group-5',
'parent': self.parent_tenant_groups[0].pk,
}, },
{ {
'name': 'Test Tenant Group 6', 'name': 'Tenant Group 6',
'slug': 'test-tenant-group-6', 'slug': 'tenant-group-6',
'parent': self.parent_tenant_groups[0].pk,
}, },
] ]
@ -93,7 +109,7 @@ class TenantGroupTest(APITestCase):
response = self.client.post(url, data, format='json', **self.header) response = self.client.post(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_201_CREATED) self.assertHttpStatus(response, status.HTTP_201_CREATED)
self.assertEqual(TenantGroup.objects.count(), 6) self.assertEqual(TenantGroup.objects.count(), 8)
self.assertEqual(response.data[0]['name'], data[0]['name']) self.assertEqual(response.data[0]['name'], data[0]['name'])
self.assertEqual(response.data[1]['name'], data[1]['name']) self.assertEqual(response.data[1]['name'], data[1]['name'])
self.assertEqual(response.data[2]['name'], data[2]['name']) self.assertEqual(response.data[2]['name'], data[2]['name'])
@ -101,26 +117,28 @@ class TenantGroupTest(APITestCase):
def test_update_tenantgroup(self): def test_update_tenantgroup(self):
data = { data = {
'name': 'Test Tenant Group X', 'name': 'Tenant Group X',
'slug': 'test-tenant-group-x', 'slug': 'tenant-group-x',
'parent': self.parent_tenant_groups[1].pk,
} }
url = reverse('tenancy-api:tenantgroup-detail', kwargs={'pk': self.tenantgroup1.pk}) url = reverse('tenancy-api:tenantgroup-detail', kwargs={'pk': self.tenant_groups[0].pk})
response = self.client.put(url, data, format='json', **self.header) response = self.client.put(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK) self.assertHttpStatus(response, status.HTTP_200_OK)
self.assertEqual(TenantGroup.objects.count(), 3) self.assertEqual(TenantGroup.objects.count(), 5)
tenantgroup1 = TenantGroup.objects.get(pk=response.data['id']) tenantgroup1 = TenantGroup.objects.get(pk=response.data['id'])
self.assertEqual(tenantgroup1.name, data['name']) self.assertEqual(tenantgroup1.name, data['name'])
self.assertEqual(tenantgroup1.slug, data['slug']) self.assertEqual(tenantgroup1.slug, data['slug'])
self.assertEqual(tenantgroup1.parent_id, data['parent'])
def test_delete_tenantgroup(self): def test_delete_tenantgroup(self):
url = reverse('tenancy-api:tenantgroup-detail', kwargs={'pk': self.tenantgroup1.pk}) url = reverse('tenancy-api:tenantgroup-detail', kwargs={'pk': self.tenant_groups[0].pk})
response = self.client.delete(url, **self.header) response = self.client.delete(url, **self.header)
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
self.assertEqual(TenantGroup.objects.count(), 2) self.assertEqual(TenantGroup.objects.count(), 4)
class TenantTest(APITestCase): class TenantTest(APITestCase):
@ -129,18 +147,26 @@ class TenantTest(APITestCase):
super().setUp() super().setUp()
self.tenantgroup1 = TenantGroup.objects.create(name='Test Tenant Group 1', slug='test-tenant-group-1') self.tenant_groups = (
self.tenantgroup2 = TenantGroup.objects.create(name='Test Tenant Group 2', slug='test-tenant-group-2') TenantGroup(name='Tenant Group 1', slug='tenant-group-1'),
self.tenant1 = Tenant.objects.create(name='Test Tenant 1', slug='test-tenant-1', group=self.tenantgroup1) TenantGroup(name='Tenant Group 2', slug='tenant-group-2'),
self.tenant2 = Tenant.objects.create(name='Test Tenant 2', slug='test-tenant-2', group=self.tenantgroup1) )
self.tenant3 = Tenant.objects.create(name='Test Tenant 3', slug='test-tenant-3', group=self.tenantgroup1) for tenantgroup in self.tenant_groups:
tenantgroup.save()
self.tenants = (
Tenant(name='Test Tenant 1', slug='test-tenant-1', group=self.tenant_groups[0]),
Tenant(name='Test Tenant 2', slug='test-tenant-2', group=self.tenant_groups[0]),
Tenant(name='Test Tenant 3', slug='test-tenant-3', group=self.tenant_groups[0]),
)
Tenant.objects.bulk_create(self.tenants)
def test_get_tenant(self): def test_get_tenant(self):
url = reverse('tenancy-api:tenant-detail', kwargs={'pk': self.tenant1.pk}) url = reverse('tenancy-api:tenant-detail', kwargs={'pk': self.tenants[0].pk})
response = self.client.get(url, **self.header) response = self.client.get(url, **self.header)
self.assertEqual(response.data['name'], self.tenant1.name) self.assertEqual(response.data['name'], self.tenants[0].name)
def test_list_tenants(self): def test_list_tenants(self):
@ -164,7 +190,7 @@ class TenantTest(APITestCase):
data = { data = {
'name': 'Test Tenant 4', 'name': 'Test Tenant 4',
'slug': 'test-tenant-4', 'slug': 'test-tenant-4',
'group': self.tenantgroup1.pk, 'group': self.tenant_groups[0].pk,
} }
url = reverse('tenancy-api:tenant-list') url = reverse('tenancy-api:tenant-list')
@ -208,10 +234,10 @@ class TenantTest(APITestCase):
data = { data = {
'name': 'Test Tenant X', 'name': 'Test Tenant X',
'slug': 'test-tenant-x', 'slug': 'test-tenant-x',
'group': self.tenantgroup2.pk, 'group': self.tenant_groups[1].pk,
} }
url = reverse('tenancy-api:tenant-detail', kwargs={'pk': self.tenant1.pk}) url = reverse('tenancy-api:tenant-detail', kwargs={'pk': self.tenants[0].pk})
response = self.client.put(url, data, format='json', **self.header) response = self.client.put(url, data, format='json', **self.header)
self.assertHttpStatus(response, status.HTTP_200_OK) self.assertHttpStatus(response, status.HTTP_200_OK)
@ -223,7 +249,7 @@ class TenantTest(APITestCase):
def test_delete_tenant(self): def test_delete_tenant(self):
url = reverse('tenancy-api:tenant-detail', kwargs={'pk': self.tenant1.pk}) url = reverse('tenancy-api:tenant-detail', kwargs={'pk': self.tenants[0].pk})
response = self.client.delete(url, **self.header) response = self.client.delete(url, **self.header)
self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT) self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)

View File

@ -11,12 +11,21 @@ class TenantGroupTestCase(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
groups = ( parent_tenant_groups = (
TenantGroup(name='Tenant Group 1', slug='tenant-group-1'), TenantGroup(name='Parent Tenant Group 1', slug='parent-tenant-group-1'),
TenantGroup(name='Tenant Group 2', slug='tenant-group-2'), TenantGroup(name='Parent Tenant Group 2', slug='parent-tenant-group-2'),
TenantGroup(name='Tenant Group 3', slug='tenant-group-3'), TenantGroup(name='Parent Tenant Group 3', slug='parent-tenant-group-3'),
) )
TenantGroup.objects.bulk_create(groups) for tenantgroup in parent_tenant_groups:
tenantgroup.save()
tenant_groups = (
TenantGroup(name='Tenant Group 1', slug='tenant-group-1', parent=parent_tenant_groups[0]),
TenantGroup(name='Tenant Group 2', slug='tenant-group-2', parent=parent_tenant_groups[1]),
TenantGroup(name='Tenant Group 3', slug='tenant-group-3', parent=parent_tenant_groups[2]),
)
for tenantgroup in tenant_groups:
tenantgroup.save()
def test_id(self): def test_id(self):
id_list = self.queryset.values_list('id', flat=True)[:2] id_list = self.queryset.values_list('id', flat=True)[:2]
@ -31,6 +40,13 @@ class TenantGroupTestCase(TestCase):
params = {'slug': ['tenant-group-1', 'tenant-group-2']} params = {'slug': ['tenant-group-1', 'tenant-group-2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2) self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_parent(self):
parent_groups = TenantGroup.objects.filter(name__startswith='Parent')[:2]
params = {'parent_id': [parent_groups[0].pk, parent_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'parent': [parent_groups[0].slug, parent_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class TenantTestCase(TestCase): class TenantTestCase(TestCase):
queryset = Tenant.objects.all() queryset = Tenant.objects.all()
@ -39,17 +55,18 @@ class TenantTestCase(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
groups = ( tenant_groups = (
TenantGroup(name='Tenant Group 1', slug='tenant-group-1'), TenantGroup(name='Tenant Group 1', slug='tenant-group-1'),
TenantGroup(name='Tenant Group 2', slug='tenant-group-2'), TenantGroup(name='Tenant Group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant Group 3', slug='tenant-group-3'), TenantGroup(name='Tenant Group 3', slug='tenant-group-3'),
) )
TenantGroup.objects.bulk_create(groups) for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = ( tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=groups[0]), Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
Tenant(name='Tenant 2', slug='tenant-2', group=groups[1]), Tenant(name='Tenant 2', slug='tenant-2', group=tenant_groups[1]),
Tenant(name='Tenant 3', slug='tenant-3', group=groups[2]), Tenant(name='Tenant 3', slug='tenant-3', group=tenant_groups[2]),
) )
Tenant.objects.bulk_create(tenants) Tenant.objects.bulk_create(tenants)

View File

@ -8,11 +8,13 @@ class TenantGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
TenantGroup.objects.bulk_create([ tenant_groups = (
TenantGroup(name='Tenant Group 1', slug='tenant-group-1'), TenantGroup(name='Tenant Group 1', slug='tenant-group-1'),
TenantGroup(name='Tenant Group 2', slug='tenant-group-2'), TenantGroup(name='Tenant Group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant Group 3', slug='tenant-group-3'), TenantGroup(name='Tenant Group 3', slug='tenant-group-3'),
]) )
for tenanantgroup in tenant_groups:
tenanantgroup.save()
cls.form_data = { cls.form_data = {
'name': 'Tenant Group X', 'name': 'Tenant Group X',
@ -33,22 +35,23 @@ class TenantTestCase(ViewTestCases.PrimaryObjectViewTestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
tenantgroups = ( tenant_groups = (
TenantGroup(name='Tenant Group 1', slug='tenant-group-1'), TenantGroup(name='Tenant Group 1', slug='tenant-group-1'),
TenantGroup(name='Tenant Group 2', slug='tenant-group-2'), TenantGroup(name='Tenant Group 2', slug='tenant-group-2'),
) )
TenantGroup.objects.bulk_create(tenantgroups) for tenanantgroup in tenant_groups:
tenanantgroup.save()
Tenant.objects.bulk_create([ Tenant.objects.bulk_create([
Tenant(name='Tenant 1', slug='tenant-1', group=tenantgroups[0]), Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
Tenant(name='Tenant 2', slug='tenant-2', group=tenantgroups[0]), Tenant(name='Tenant 2', slug='tenant-2', group=tenant_groups[0]),
Tenant(name='Tenant 3', slug='tenant-3', group=tenantgroups[0]), Tenant(name='Tenant 3', slug='tenant-3', group=tenant_groups[0]),
]) ])
cls.form_data = { cls.form_data = {
'name': 'Tenant X', 'name': 'Tenant X',
'slug': 'tenant-x', 'slug': 'tenant-x',
'group': tenantgroups[1].pk, 'group': tenant_groups[1].pk,
'description': 'A new tenant', 'description': 'A new tenant',
'comments': 'Some comments', 'comments': 'Some comments',
'tags': 'Alpha,Bravo,Charlie', 'tags': 'Alpha,Bravo,Charlie',
@ -62,5 +65,5 @@ class TenantTestCase(ViewTestCases.PrimaryObjectViewTestCase):
) )
cls.bulk_edit_data = { cls.bulk_edit_data = {
'group': tenantgroups[1].pk, 'group': tenant_groups[1].pk,
} }

View File

@ -20,7 +20,13 @@ from .models import Tenant, TenantGroup
class TenantGroupListView(PermissionRequiredMixin, ObjectListView): class TenantGroupListView(PermissionRequiredMixin, ObjectListView):
permission_required = 'tenancy.view_tenantgroup' permission_required = 'tenancy.view_tenantgroup'
queryset = TenantGroup.objects.annotate(tenant_count=Count('tenants')) queryset = TenantGroup.objects.add_related_count(
TenantGroup.objects.all(),
Tenant,
'group',
'tenant_count',
cumulative=True
)
table = tables.TenantGroupTable table = tables.TenantGroupTable

View File

@ -105,7 +105,8 @@ class ClusterTestCase(TestCase):
TenantGroup(name='Tenant group 2', slug='tenant-group-2'), TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant group 3', slug='tenant-group-3'), TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
) )
TenantGroup.objects.bulk_create(tenant_groups) for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = ( tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]), Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
@ -231,7 +232,8 @@ class VirtualMachineTestCase(TestCase):
TenantGroup(name='Tenant group 2', slug='tenant-group-2'), TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant group 3', slug='tenant-group-3'), TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
) )
TenantGroup.objects.bulk_create(tenant_groups) for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = ( tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]), Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),