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):
|
||||
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')
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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')
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
|
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.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,
|
||||
)
|
||||
|
||||
|
@ -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')
|
||||
|
||||
|
||||
#
|
||||
|
Reference in New Issue
Block a user