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:
@ -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')
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
36
netbox/ipam/migrations/0045_vlangroup_scope.py
Normal file
36
netbox/ipam/migrations/0045_vlangroup_scope.py
Normal 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')},
|
||||||
|
),
|
||||||
|
]
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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')
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
Reference in New Issue
Block a user