1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Change VLANGroup site to scope (GFK)

This commit is contained in:
Jeremy Stretch
2021-03-09 14:13:50 -05:00
parent c6641ec1de
commit 4dae781be0
7 changed files with 169 additions and 59 deletions

View File

@ -114,14 +114,20 @@ class RoleSerializer(OrganizationalModelSerializer):
class VLANGroupSerializer(OrganizationalModelSerializer): class VLANGroupSerializer(OrganizationalModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlangroup-detail') url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlangroup-detail')
site = NestedSiteSerializer(required=False, allow_null=True) scope_type = ContentTypeField(
queryset=ContentType.objects.filter(
app_label='dcim',
model__in=['region', 'sitegroup', 'site', 'location', 'rack']
)
)
scope = serializers.SerializerMethodField(read_only=True)
vlan_count = serializers.IntegerField(read_only=True) vlan_count = serializers.IntegerField(read_only=True)
class Meta: class Meta:
model = VLANGroup model = VLANGroup
fields = [ fields = [
'id', 'url', 'name', 'slug', 'site', 'description', 'custom_fields', 'created', 'last_updated', 'id', 'url', 'name', 'slug', 'scope_type', 'scope_id', 'scope', 'description', 'custom_fields', 'created',
'vlan_count', 'last_updated', 'vlan_count',
] ]
validators = [] validators = []
@ -138,6 +144,14 @@ class VLANGroupSerializer(OrganizationalModelSerializer):
return data return data
def get_scope(self, obj):
if obj.scope_id is None:
return None
serializer = get_serializer_for_model(obj.scope, prefix='Nested')
context = {'request': self.context['request']}
return serializer(obj.scope, context=context).data
class VLANSerializer(TaggedObjectSerializer, CustomFieldModelSerializer): class VLANSerializer(TaggedObjectSerializer, CustomFieldModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail') url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')

View File

@ -283,7 +283,7 @@ class IPAddressViewSet(CustomFieldModelViewSet):
# #
class VLANGroupViewSet(CustomFieldModelViewSet): class VLANGroupViewSet(CustomFieldModelViewSet):
queryset = VLANGroup.objects.prefetch_related('site').annotate( queryset = VLANGroup.objects.annotate(
vlan_count=count_related(VLAN, 'group') vlan_count=count_related(VLAN, 'group')
) )
serializer_class = serializers.VLANGroupSerializer serializer_class = serializers.VLANGroupSerializer

View File

@ -1,5 +1,6 @@
import django_filters import django_filters
import netaddr import netaddr
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db.models import Q from django.db.models import Q
from netaddr.core import AddrFormatError from netaddr.core import AddrFormatError
@ -8,8 +9,8 @@ from dcim.models import Device, Interface, Region, Site, SiteGroup
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
from tenancy.filters import TenancyFilterSet from tenancy.filters import TenancyFilterSet
from utilities.filters import ( from utilities.filters import (
BaseFilterSet, MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet, NumericArrayFilter, TagFilter, BaseFilterSet, ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet,
TreeNodeMultipleChoiceFilter, NumericArrayFilter, TagFilter, TreeNodeMultipleChoiceFilter,
) )
from virtualization.models import VirtualMachine, VMInterface from virtualization.models import VirtualMachine, VMInterface
from .choices import * from .choices import *
@ -535,46 +536,32 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilter
class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet): class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
region_id = TreeNodeMultipleChoiceFilter( scope_type = ContentTypeFilter()
queryset=Region.objects.all(), region = django_filters.NumberFilter(
field_name='site__region', method='filter_scope'
lookup_expr='in',
label='Region (ID)',
) )
region = TreeNodeMultipleChoiceFilter( sitegroup = django_filters.NumberFilter(
queryset=Region.objects.all(), method='filter_scope'
field_name='site__region',
lookup_expr='in',
to_field_name='slug',
label='Region (slug)',
) )
site_group_id = TreeNodeMultipleChoiceFilter( site = django_filters.NumberFilter(
queryset=SiteGroup.objects.all(), method='filter_scope'
field_name='site__group',
lookup_expr='in',
label='Site group (ID)',
) )
site_group = TreeNodeMultipleChoiceFilter( location = django_filters.NumberFilter(
queryset=SiteGroup.objects.all(), method='filter_scope'
field_name='site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (slug)',
) )
site_id = django_filters.ModelMultipleChoiceFilter( rack = django_filters.NumberFilter(
queryset=Site.objects.all(), method='filter_scope'
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
field_name='site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
) )
class Meta: class Meta:
model = VLANGroup model = VLANGroup
fields = ['id', 'name', 'slug', 'description'] fields = ['id', 'name', 'slug', 'description', 'scope_id']
def filter_scope(self, queryset, name, value):
return queryset.filter(
scope_type=ContentType.objects.get(app_label='dcim', model=name),
scope_id=value
)
class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet): class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):

View File

@ -1,7 +1,8 @@
from django import forms from django import forms
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from dcim.models import Device, Interface, Rack, Region, Site, SiteGroup from dcim.models import Device, Interface, Location, Rack, Region, Site, SiteGroup
from extras.forms import ( from extras.forms import (
AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldModelCSVForm, CustomFieldModelForm, CustomFieldFilterForm, AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldModelCSVForm, CustomFieldModelForm, CustomFieldFilterForm,
) )
@ -1126,18 +1127,70 @@ class VLANGroupForm(BootstrapMixin, CustomFieldModelForm):
site = DynamicModelChoiceField( site = DynamicModelChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,
initial_params={
'locations': '$location'
},
query_params={ query_params={
'region_id': '$region', 'region_id': '$region',
'group_id': '$site_group', 'group_id': '$site_group',
} }
) )
location = DynamicModelChoiceField(
queryset=Location.objects.all(),
required=False,
initial_params={
'racks': '$rack'
},
query_params={
'site_id': '$site',
}
)
rack = DynamicModelChoiceField(
queryset=Rack.objects.all(),
required=False,
query_params={
'site_id': '$site',
'location_id': '$location',
}
)
slug = SlugField() slug = SlugField()
class Meta: class Meta:
model = VLANGroup model = VLANGroup
fields = [ fields = [
'region', 'site', 'name', 'slug', 'description', 'name', 'slug', 'description', 'region', 'site_group', 'site', 'location', 'rack',
] ]
fieldsets = (
('VLAN Group', ('name', 'slug', 'description')),
('Scope', ('region', 'site_group', 'site', 'location', 'rack')),
)
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance')
initial = kwargs.get('initial', {})
if instance is not None and instance.scope:
if type(instance.scope) is Rack:
initial['rack'] = instance.scope
elif type(instance.scope) is Location:
initial['location'] = instance.scope
elif type(instance.scope) is Site:
initial['site'] = instance.scope
elif type(instance.scope) is SiteGroup:
initial['site_group'] = instance.scope
elif type(instance.scope) is Region:
initial['region'] = instance.scope
kwargs['initial'] = initial
super().__init__(*args, **kwargs)
def clean(self):
super().clean()
# Assign scope object
self.instance.scope = self.cleaned_data['rack'] or self.cleaned_data['location'] or self.cleaned_data['site'] \
or self.cleaned_data['site_group'] or self.cleaned_data['region'] or None
class VLANGroupCSVForm(CustomFieldModelCSVForm): class VLANGroupCSVForm(CustomFieldModelCSVForm):
@ -1155,25 +1208,31 @@ class VLANGroupCSVForm(CustomFieldModelCSVForm):
class VLANGroupFilterForm(BootstrapMixin, forms.Form): class VLANGroupFilterForm(BootstrapMixin, forms.Form):
region_id = DynamicModelMultipleChoiceField( region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(), queryset=Region.objects.all(),
required=False, required=False,
label=_('Region') label=_('Region')
) )
site_group_id = DynamicModelMultipleChoiceField( sitegroup = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(), queryset=SiteGroup.objects.all(),
required=False, required=False,
label=_('Site group') label=_('Site group')
) )
site_id = DynamicModelMultipleChoiceField( site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(), queryset=Site.objects.all(),
required=False, required=False,
null_option='None',
query_params={
'region_id': '$region_id'
},
label=_('Site') label=_('Site')
) )
location = DynamicModelMultipleChoiceField(
queryset=Location.objects.all(),
required=False,
label=_('Location')
)
rack = DynamicModelMultipleChoiceField(
queryset=Rack.objects.all(),
required=False,
label=_('Rack')
)
# #

View File

@ -0,0 +1,36 @@
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('ipam', '0044_standardize_models'),
]
operations = [
migrations.RenameField(
model_name='vlangroup',
old_name='site',
new_name='scope_id',
),
migrations.AlterField(
model_name='vlangroup',
name='scope_id',
field=models.PositiveBigIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='vlangroup',
name='scope_type',
field=models.ForeignKey(blank=True, limit_choices_to=models.Q(('app_label', 'dcim'), ('model__in', ['region', 'sitegroup', 'site', 'location', 'rack'])), null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype'),
),
migrations.AlterModelOptions(
name='vlangroup',
options={'ordering': ('name', 'pk'), 'verbose_name': 'VLAN group', 'verbose_name_plural': 'VLAN groups'},
),
migrations.AlterUniqueTogether(
name='vlangroup',
unique_together={('scope_type', 'scope_id', 'name'), ('scope_type', 'scope_id', 'slug')},
),
]

View File

@ -1,3 +1,5 @@
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models from django.db import models
@ -31,13 +33,24 @@ class VLANGroup(OrganizationalModel):
slug = models.SlugField( slug = models.SlugField(
max_length=100 max_length=100
) )
site = models.ForeignKey( scope_type = models.ForeignKey(
to='dcim.Site', to=ContentType,
on_delete=models.PROTECT, on_delete=models.CASCADE,
related_name='vlan_groups', limit_choices_to=Q(
app_label='dcim',
model__in=['region', 'sitegroup', 'site', 'location', 'rack']
),
blank=True, blank=True,
null=True null=True
) )
scope_id = models.PositiveBigIntegerField(
blank=True,
null=True
)
scope = GenericForeignKey(
ct_field='scope_type',
fk_field='scope_id'
)
description = models.CharField( description = models.CharField(
max_length=200, max_length=200,
blank=True blank=True
@ -45,13 +58,13 @@ class VLANGroup(OrganizationalModel):
objects = RestrictedQuerySet.as_manager() objects = RestrictedQuerySet.as_manager()
csv_headers = ['name', 'slug', 'site', 'description'] csv_headers = ['name', 'slug', 'scope_type', 'scope_id', 'description']
class Meta: class Meta:
ordering = ('site', 'name', 'pk') # (site, name) may be non-unique ordering = ('name', 'pk') # Name may be non-unique
unique_together = [ unique_together = [
['site', 'name'], ['scope_type', 'scope_id', 'name'],
['site', 'slug'], ['scope_type', 'scope_id', 'slug'],
] ]
verbose_name = 'VLAN group' verbose_name = 'VLAN group'
verbose_name_plural = 'VLAN groups' verbose_name_plural = 'VLAN groups'
@ -66,7 +79,8 @@ class VLANGroup(OrganizationalModel):
return ( return (
self.name, self.name,
self.slug, self.slug,
self.site.name if self.site else None, f'{self.scope_type.app_label}.{self.scope_type.model}',
self.scope_id,
self.description, self.description,
) )

View File

@ -414,7 +414,7 @@ class InterfaceIPAddressTable(BaseTable):
class VLANGroupTable(BaseTable): class VLANGroupTable(BaseTable):
pk = ToggleColumn() pk = ToggleColumn()
name = tables.Column(linkify=True) name = tables.Column(linkify=True)
site = tables.Column( scope = tables.Column(
linkify=True linkify=True
) )
vlan_count = LinkedCountColumn( vlan_count = LinkedCountColumn(
@ -429,8 +429,8 @@ class VLANGroupTable(BaseTable):
class Meta(BaseTable.Meta): class Meta(BaseTable.Meta):
model = VLANGroup model = VLANGroup
fields = ('pk', 'name', 'site', 'vlan_count', 'slug', 'description', 'actions') fields = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'slug', 'description', 'actions')
default_columns = ('pk', 'name', 'site', 'vlan_count', 'description', 'actions') default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'description', 'actions')
# #