Skip to content

Commit

Permalink
Merge pull request #5985 from netbox-community/5284-vlangroup-scope
Browse files Browse the repository at this point in the history
Closes #5284: Allow VLANGroup assignment beyond sites
  • Loading branch information
jeremystretch authored Mar 16, 2021
2 parents 1aa22d1 + f64f205 commit ee7f7c8
Show file tree
Hide file tree
Showing 13 changed files with 427 additions and 143 deletions.
9 changes: 9 additions & 0 deletions docs/release-notes/version-2.11.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ When exporting a list of objects in NetBox, users now have the option of selecti

The legacy static export behavior has been retained to ensure backward compatibility for dependent integrations. However, users are strongly encouraged to adapt custom export templates where needed as this functionality will be removed in v2.12.

#### Variable Scope Support for VLAN Groups ([#5284](https://github.com/netbox-community/netbox/issues/5284))

In previous releases, VLAN groups could be assigned only to a site. To afford more flexibility in conveying the true scope of an L2 domain, a VLAN group can now be assigned to a region, site group (new in v2.11), site, location, or rack. VLANs assigned to a group will be available only to devices and virtual machines which exist within its scope.

For example, a VLAN within a group assigned to a location will be available only to devices assigned to that location (or one of its child locations), or to a rack within that location.

#### New Site Group Model ([#5892](https://github.com/netbox-community/netbox/issues/5892))

This release introduces the new Site Group model, which can be used to organize sites similar to the existing Region model. Whereas regions are intended for geographically arranging sites into countries, states, and so on, the new site group model can be used to organize sites by role or other arbitrary classification. Using regions and site groups in conjunction provides two dimensions along which sites can be organized, offering greater flexibility to the user.
Expand Down Expand Up @@ -116,3 +122,6 @@ The ObjectChange model (which is used to record the creation, modification, and
* Renamed `object_data` to `postchange_data`
* extras.Webhook
* Added the `/api/extras/webhooks/` endpoint
* ipam.VLANGroup
* Added the `scope_type`, `scope_id`, and `scope` fields (`scope` is a generic foreign key)
* Dropped the `site` foreign key field
21 changes: 18 additions & 3 deletions netbox/ipam/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,21 @@ class Meta:

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']
),
required=False
)
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 = []

Expand All @@ -137,6 +144,14 @@ def validate(self, 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(PrimaryModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
Expand Down
2 changes: 1 addition & 1 deletion netbox/ipam/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
59 changes: 26 additions & 33 deletions netbox/ipam/filters.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -8,8 +9,8 @@
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 *
Expand Down Expand Up @@ -535,46 +536,38 @@ def _assigned_to_interface(self, queryset, name, value):


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)',
rack = django_filters.NumberFilter(
method='filter_scope'
)
site = django_filters.ModelMultipleChoiceFilter(
field_name='site__slug',
queryset=Site.objects.all(),
to_field_name='slug',
label='Site (slug)',
clustergroup = django_filters.NumberFilter(
method='filter_scope'
)
cluster = 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(model=name),
scope_id=value
)


class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
Expand Down
Loading

0 comments on commit ee7f7c8

Please sign in to comment.