mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge branch 'develop' into 2921-tags-select2
This commit is contained in:
@@ -31,11 +31,12 @@ class TenantSerializer(TaggitSerializer, CustomFieldModelSerializer):
|
||||
virtualmachine_count = serializers.IntegerField(read_only=True)
|
||||
vlan_count = serializers.IntegerField(read_only=True)
|
||||
vrf_count = serializers.IntegerField(read_only=True)
|
||||
cluster_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Tenant
|
||||
fields = [
|
||||
'id', 'name', 'slug', 'group', 'description', 'comments', 'tags', 'custom_fields', 'created',
|
||||
'last_updated', 'circuit_count', 'device_count', 'ipaddress_count', 'prefix_count', 'rack_count',
|
||||
'site_count', 'virtualmachine_count', 'vlan_count', 'vrf_count',
|
||||
'site_count', 'virtualmachine_count', 'vlan_count', 'vrf_count', 'cluster_count',
|
||||
]
|
||||
|
@@ -27,7 +27,7 @@ class TenantGroupViewSet(ModelViewSet):
|
||||
tenant_count=get_subquery(Tenant, 'group')
|
||||
)
|
||||
serializer_class = serializers.TenantGroupSerializer
|
||||
filterset_class = filters.TenantGroupFilter
|
||||
filterset_class = filters.TenantGroupFilterSet
|
||||
|
||||
|
||||
#
|
||||
@@ -49,4 +49,4 @@ class TenantViewSet(CustomFieldModelViewSet):
|
||||
vrf_count=get_subquery(VRF, 'tenant')
|
||||
)
|
||||
serializer_class = serializers.TenantSerializer
|
||||
filterset_class = filters.TenantFilter
|
||||
filterset_class = filters.TenantFilterSet
|
||||
|
@@ -7,19 +7,20 @@ from .models import Tenant, TenantGroup
|
||||
|
||||
|
||||
__all__ = (
|
||||
'TenantFilter',
|
||||
'TenantGroupFilter',
|
||||
'TenancyFilterSet',
|
||||
'TenantFilterSet',
|
||||
'TenantGroupFilterSet',
|
||||
)
|
||||
|
||||
|
||||
class TenantGroupFilter(NameSlugSearchFilterSet):
|
||||
class TenantGroupFilterSet(NameSlugSearchFilterSet):
|
||||
|
||||
class Meta:
|
||||
model = TenantGroup
|
||||
fields = ['id', 'name', 'slug']
|
||||
|
||||
|
||||
class TenantFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
class TenantFilterSet(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
id__in = NumericInFilter(
|
||||
field_name='id',
|
||||
lookup_expr='in'
|
||||
@@ -53,3 +54,31 @@ class TenantFilter(CustomFieldFilterSet, CreatedUpdatedFilterSet):
|
||||
Q(description__icontains=value) |
|
||||
Q(comments__icontains=value)
|
||||
)
|
||||
|
||||
|
||||
class TenancyFilterSet(django_filters.FilterSet):
|
||||
"""
|
||||
An inheritable FilterSet for models which support Tenant assignment.
|
||||
"""
|
||||
tenant_group_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='tenant__group__id',
|
||||
queryset=TenantGroup.objects.all(),
|
||||
to_field_name='id',
|
||||
label='Tenant Group (ID)',
|
||||
)
|
||||
tenant_group = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='tenant__group__slug',
|
||||
queryset=TenantGroup.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant Group (slug)',
|
||||
)
|
||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Tenant.objects.all(),
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='tenant__slug',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
||||
|
@@ -1,28 +0,0 @@
|
||||
import django_filters
|
||||
|
||||
from .models import Tenant, TenantGroup
|
||||
|
||||
|
||||
class TenancyFilterSet(django_filters.FilterSet):
|
||||
tenant_group_id = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='tenant__group__id',
|
||||
queryset=TenantGroup.objects.all(),
|
||||
to_field_name='id',
|
||||
label='Tenant Group (ID)',
|
||||
)
|
||||
tenant_group = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='tenant__group__slug',
|
||||
queryset=TenantGroup.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant Group (slug)',
|
||||
)
|
||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
||||
queryset=Tenant.objects.all(),
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name='tenant__slug',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
@@ -0,0 +1,45 @@
|
||||
import django.db.models.deletion
|
||||
import taggit.managers
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('tenancy', '0001_initial'), ('tenancy', '0002_tenant_group_optional'), ('tenancy', '0003_unicode_literals'), ('tenancy', '0004_tags'), ('tenancy', '0005_change_logging')]
|
||||
|
||||
dependencies = [
|
||||
('taggit', '0002_auto_20150616_2121'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TenantGroup',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50, unique=True)),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('created', models.DateField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tenant',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('name', models.CharField(max_length=30, unique=True)),
|
||||
('slug', models.SlugField(unique=True)),
|
||||
('description', models.CharField(blank=True, help_text='Long-form name (optional)', max_length=100)),
|
||||
('comments', models.TextField(blank=True)),
|
||||
('group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tenants', to='tenancy.TenantGroup')),
|
||||
('tags', taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['group', 'name'],
|
||||
},
|
||||
),
|
||||
]
|
@@ -1,26 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.14 on 2018-07-31 02:12
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
replaces = [('tenancy', '0002_tenant_group_optional'), ('tenancy', '0003_unicode_literals')]
|
||||
|
||||
dependencies = [
|
||||
('tenancy', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='tenant',
|
||||
name='group',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tenants', to='tenancy.TenantGroup'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tenant',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, help_text='Long-form name (optional)', max_length=100),
|
||||
),
|
||||
]
|
@@ -7,6 +7,12 @@ from extras.models import CustomFieldModel, TaggedItem
|
||||
from utilities.models import ChangeLoggedModel
|
||||
|
||||
|
||||
__all__ = (
|
||||
'Tenant',
|
||||
'TenantGroup',
|
||||
)
|
||||
|
||||
|
||||
class TenantGroup(ChangeLoggedModel):
|
||||
"""
|
||||
An arbitrary collection of Tenants.
|
||||
@@ -73,6 +79,9 @@ class Tenant(ChangeLoggedModel, CustomFieldModel):
|
||||
tags = TaggableManager(through=TaggedItem)
|
||||
|
||||
csv_headers = ['name', 'slug', 'group', 'description', 'comments']
|
||||
clone_fields = [
|
||||
'group', 'description',
|
||||
]
|
||||
|
||||
class Meta:
|
||||
ordering = ['group', 'name']
|
||||
|
@@ -5,6 +5,23 @@ from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.testing import APITestCase
|
||||
|
||||
|
||||
class AppTest(APITestCase):
|
||||
|
||||
def test_root(self):
|
||||
|
||||
url = reverse('tenancy-api:api-root')
|
||||
response = self.client.get('{}?format=api'.format(url), **self.header)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_choices(self):
|
||||
|
||||
url = reverse('tenancy-api:field-choice-list')
|
||||
response = self.client.get(url, **self.header)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TenantGroupTest(APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
@@ -6,7 +6,7 @@ from tenancy.models import Tenant, TenantGroup
|
||||
|
||||
class TenantGroupTestCase(TestCase):
|
||||
queryset = TenantGroup.objects.all()
|
||||
filterset = TenantGroupFilter
|
||||
filterset = TenantGroupFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
@@ -34,7 +34,7 @@ class TenantGroupTestCase(TestCase):
|
||||
|
||||
class TenantTestCase(TestCase):
|
||||
queryset = Tenant.objects.all()
|
||||
filterset = TenantFilter
|
||||
filterset = TenantFilterSet
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
@@ -10,7 +10,12 @@ from utilities.testing import create_test_user
|
||||
class TenantGroupTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
user = create_test_user(permissions=['tenancy.view_tenantgroup'])
|
||||
user = create_test_user(
|
||||
permissions=[
|
||||
'tenancy.view_tenantgroup',
|
||||
'tenancy.add_tenantgroup',
|
||||
]
|
||||
)
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
@@ -27,11 +32,30 @@ class TenantGroupTestCase(TestCase):
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_tenantgroup_import(self):
|
||||
|
||||
csv_data = (
|
||||
"name,slug",
|
||||
"Tenant Group 4,tenant-group-4",
|
||||
"Tenant Group 5,tenant-group-5",
|
||||
"Tenant Group 6,tenant-group-6",
|
||||
)
|
||||
|
||||
response = self.client.post(reverse('tenancy:tenantgroup_import'), {'csv': '\n'.join(csv_data)})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(TenantGroup.objects.count(), 6)
|
||||
|
||||
|
||||
class TenantTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
user = create_test_user(permissions=['tenancy.view_tenant'])
|
||||
user = create_test_user(
|
||||
permissions=[
|
||||
'tenancy.view_tenant',
|
||||
'tenancy.add_tenant',
|
||||
]
|
||||
)
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
@@ -59,3 +83,17 @@ class TenantTestCase(TestCase):
|
||||
tenant = Tenant.objects.first()
|
||||
response = self.client.get(tenant.get_absolute_url(), follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_tenant_import(self):
|
||||
|
||||
csv_data = (
|
||||
"name,slug",
|
||||
"Tenant 4,tenant-4",
|
||||
"Tenant 5,tenant-5",
|
||||
"Tenant 6,tenant-6",
|
||||
)
|
||||
|
||||
response = self.client.post(reverse('tenancy:tenant_import'), {'csv': '\n'.join(csv_data)})
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(Tenant.objects.count(), 6)
|
||||
|
@@ -9,7 +9,7 @@ from ipam.models import IPAddress, Prefix, VLAN, VRF
|
||||
from utilities.views import (
|
||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||
)
|
||||
from virtualization.models import VirtualMachine
|
||||
from virtualization.models import VirtualMachine, Cluster
|
||||
from . import filters, forms, tables
|
||||
from .models import Tenant, TenantGroup
|
||||
|
||||
@@ -57,8 +57,8 @@ class TenantGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
class TenantListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'tenancy.view_tenant'
|
||||
queryset = Tenant.objects.prefetch_related('group')
|
||||
filter = filters.TenantFilter
|
||||
filter_form = forms.TenantFilterForm
|
||||
filterset = filters.TenantFilterSet
|
||||
filterset_form = forms.TenantFilterForm
|
||||
table = tables.TenantTable
|
||||
template_name = 'tenancy/tenant_list.html'
|
||||
|
||||
@@ -80,6 +80,7 @@ class TenantView(PermissionRequiredMixin, View):
|
||||
'vlan_count': VLAN.objects.filter(tenant=tenant).count(),
|
||||
'circuit_count': Circuit.objects.filter(tenant=tenant).count(),
|
||||
'virtualmachine_count': VirtualMachine.objects.filter(tenant=tenant).count(),
|
||||
'cluster_count': Cluster.objects.filter(tenant=tenant).count(),
|
||||
}
|
||||
|
||||
return render(request, 'tenancy/tenant.html', {
|
||||
@@ -116,7 +117,7 @@ class TenantBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
class TenantBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'tenancy.change_tenant'
|
||||
queryset = Tenant.objects.prefetch_related('group')
|
||||
filter = filters.TenantFilter
|
||||
filterset = filters.TenantFilterSet
|
||||
table = tables.TenantTable
|
||||
form = forms.TenantBulkEditForm
|
||||
default_return_url = 'tenancy:tenant_list'
|
||||
@@ -125,6 +126,6 @@ class TenantBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
class TenantBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'tenancy.delete_tenant'
|
||||
queryset = Tenant.objects.prefetch_related('group')
|
||||
filter = filters.TenantFilter
|
||||
filterset = filters.TenantFilterSet
|
||||
table = tables.TenantTable
|
||||
default_return_url = 'tenancy:tenant_list'
|
||||
|
Reference in New Issue
Block a user