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):
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)
class Meta:
model = VLANGroup
fields = [
'id', 'url', 'name', 'slug', 'site', 'description', 'custom_fields', 'created', 'last_updated',
'vlan_count',
'id', 'url', 'name', 'slug', 'scope_type', 'scope_id', 'scope', 'description', 'custom_fields', 'created',
'last_updated', 'vlan_count',
]
validators = []
@ -138,6 +144,14 @@ class VLANGroupSerializer(OrganizationalModelSerializer):
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):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')

View File

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

View File

@ -1,5 +1,6 @@
import django_filters
import netaddr
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db.models import Q
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 tenancy.filters import TenancyFilterSet
from utilities.filters import (
BaseFilterSet, MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet, NumericArrayFilter, TagFilter,
TreeNodeMultipleChoiceFilter,
BaseFilterSet, ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet,
NumericArrayFilter, TagFilter, TreeNodeMultipleChoiceFilter,
)
from virtualization.models import VirtualMachine, VMInterface
from .choices import *
@ -535,46 +536,32 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilter
class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet):
region_id = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(),
field_name='site__region',
lookup_expr='in',
label='Region (ID)',
scope_type = ContentTypeFilter()
region = django_filters.NumberFilter(
method='filter_scope'
)
region = TreeNodeMultipleChoiceFilter(
queryset=Region.objects.all(),
field_name='site__region',
lookup_expr='in',
to_field_name='slug',
label='Region (slug)',
sitegroup = django_filters.NumberFilter(
method='filter_scope'
)
site_group_id = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
label='Site group (ID)',
site = django_filters.NumberFilter(
method='filter_scope'
)
site_group = TreeNodeMultipleChoiceFilter(
queryset=SiteGroup.objects.all(),
field_name='site__group',
lookup_expr='in',
to_field_name='slug',
label='Site group (slug)',
location = django_filters.NumberFilter(
method='filter_scope'
)
site_id = django_filters.ModelMultipleChoiceFilter(
queryset=Site.objects.all(),
label='Site (ID)',
)
site = django_filters.ModelMultipleChoiceFilter(
field_name='site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
rack = django_filters.NumberFilter(
method='filter_scope'
)
class Meta:
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):

View File

@ -1,7 +1,8 @@
from django import forms
from django.contrib.contenttypes.models import ContentType
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 (
AddRemoveTagsForm, CustomFieldBulkEditForm, CustomFieldModelCSVForm, CustomFieldModelForm, CustomFieldFilterForm,
)
@ -1126,18 +1127,70 @@ class VLANGroupForm(BootstrapMixin, CustomFieldModelForm):
site = DynamicModelChoiceField(
queryset=Site.objects.all(),
required=False,
initial_params={
'locations': '$location'
},
query_params={
'region_id': '$region',
'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()
class Meta:
model = VLANGroup
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):
@ -1155,25 +1208,31 @@ class VLANGroupCSVForm(CustomFieldModelCSVForm):
class VLANGroupFilterForm(BootstrapMixin, forms.Form):
region_id = DynamicModelMultipleChoiceField(
region = DynamicModelMultipleChoiceField(
queryset=Region.objects.all(),
required=False,
label=_('Region')
)
site_group_id = DynamicModelMultipleChoiceField(
sitegroup = DynamicModelMultipleChoiceField(
queryset=SiteGroup.objects.all(),
required=False,
label=_('Site group')
)
site_id = DynamicModelMultipleChoiceField(
site = DynamicModelMultipleChoiceField(
queryset=Site.objects.all(),
required=False,
null_option='None',
query_params={
'region_id': '$region_id'
},
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.validators import MaxValueValidator, MinValueValidator
from django.db import models
@ -31,13 +33,24 @@ class VLANGroup(OrganizationalModel):
slug = models.SlugField(
max_length=100
)
site = models.ForeignKey(
to='dcim.Site',
on_delete=models.PROTECT,
related_name='vlan_groups',
scope_type = models.ForeignKey(
to=ContentType,
on_delete=models.CASCADE,
limit_choices_to=Q(
app_label='dcim',
model__in=['region', 'sitegroup', 'site', 'location', 'rack']
),
blank=True,
null=True
)
scope_id = models.PositiveBigIntegerField(
blank=True,
null=True
)
scope = GenericForeignKey(
ct_field='scope_type',
fk_field='scope_id'
)
description = models.CharField(
max_length=200,
blank=True
@ -45,13 +58,13 @@ class VLANGroup(OrganizationalModel):
objects = RestrictedQuerySet.as_manager()
csv_headers = ['name', 'slug', 'site', 'description']
csv_headers = ['name', 'slug', 'scope_type', 'scope_id', 'description']
class Meta:
ordering = ('site', 'name', 'pk') # (site, name) may be non-unique
ordering = ('name', 'pk') # Name may be non-unique
unique_together = [
['site', 'name'],
['site', 'slug'],
['scope_type', 'scope_id', 'name'],
['scope_type', 'scope_id', 'slug'],
]
verbose_name = 'VLAN group'
verbose_name_plural = 'VLAN groups'
@ -66,7 +79,8 @@ class VLANGroup(OrganizationalModel):
return (
self.name,
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,
)

View File

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