mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-29 11:56:25 -06:00
Enable global vlan
1. Decouple site/vlan, make site optional for vlan/vlangroup 2. Change html generation code to check site existence before dereference 3. Create site search function, if site is None for a VLAN, view it as global VLAN
This commit is contained in:
parent
424c2a59d6
commit
c2793e90dc
@ -262,37 +262,47 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
|
|
||||||
|
|
||||||
class VLANGroupFilter(django_filters.FilterSet):
|
class VLANGroupFilter(django_filters.FilterSet):
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = NullableModelMultipleChoiceFilter(
|
||||||
name='site',
|
name='site',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
|
method='site_search',
|
||||||
)
|
)
|
||||||
site = django_filters.ModelMultipleChoiceFilter(
|
site = NullableModelMultipleChoiceFilter(
|
||||||
name='site__slug',
|
name='site__slug',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
|
method='site_search',
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
|
|
||||||
|
def site_search(self, queryset, name, value):
|
||||||
|
q = Q(**{name: None})
|
||||||
|
for v in value:
|
||||||
|
q |= Q(**{name: v})
|
||||||
|
return queryset.filter(q)
|
||||||
|
|
||||||
|
|
||||||
class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||||
q = django_filters.MethodFilter(
|
q = django_filters.MethodFilter(
|
||||||
action='search',
|
action='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
)
|
)
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
site_id = NullableModelMultipleChoiceFilter(
|
||||||
name='site',
|
name='site',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
label='Site (ID)',
|
label='Site (ID)',
|
||||||
|
method='site_search',
|
||||||
)
|
)
|
||||||
site = django_filters.ModelMultipleChoiceFilter(
|
site = NullableModelMultipleChoiceFilter(
|
||||||
name='site__slug',
|
name='site__slug',
|
||||||
queryset=Site.objects.all(),
|
queryset=Site.objects.all(),
|
||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
|
method='site_search',
|
||||||
)
|
)
|
||||||
group_id = NullableModelMultipleChoiceFilter(
|
group_id = NullableModelMultipleChoiceFilter(
|
||||||
name='group',
|
name='group',
|
||||||
@ -349,6 +359,12 @@ class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
|||||||
pass
|
pass
|
||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
def site_search(self, queryset, name, value):
|
||||||
|
q = Q(**{name: None})
|
||||||
|
for v in value:
|
||||||
|
q |= Q(**{name: v})
|
||||||
|
return queryset.filter(q)
|
||||||
|
|
||||||
|
|
||||||
class ServiceFilter(django_filters.FilterSet):
|
class ServiceFilter(django_filters.FilterSet):
|
||||||
device_id = django_filters.ModelMultipleChoiceFilter(
|
device_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
@ -151,7 +151,8 @@ class RoleForm(BootstrapMixin, forms.ModelForm):
|
|||||||
|
|
||||||
class PrefixForm(BootstrapMixin, CustomFieldForm):
|
class PrefixForm(BootstrapMixin, CustomFieldForm):
|
||||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site',
|
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site',
|
||||||
widget=forms.Select(attrs={'filter-for': 'vlan'}))
|
widget=forms.Select(attrs={'filter-for': 'vlan',
|
||||||
|
'default_value': '0'}))
|
||||||
vlan = forms.ModelChoiceField(queryset=VLAN.objects.all(), required=False, label='VLAN',
|
vlan = forms.ModelChoiceField(queryset=VLAN.objects.all(), required=False, label='VLAN',
|
||||||
widget=APISelect(api_url='/api/ipam/vlans/?site_id={{site}}',
|
widget=APISelect(api_url='/api/ipam/vlans/?site_id={{site}}',
|
||||||
display_field='display_name'))
|
display_field='display_name'))
|
||||||
@ -171,7 +172,7 @@ class PrefixForm(BootstrapMixin, CustomFieldForm):
|
|||||||
elif self.initial.get('site'):
|
elif self.initial.get('site'):
|
||||||
self.fields['vlan'].queryset = VLAN.objects.filter(site=self.initial['site'])
|
self.fields['vlan'].queryset = VLAN.objects.filter(site=self.initial['site'])
|
||||||
else:
|
else:
|
||||||
self.fields['vlan'].choices = []
|
self.fields['vlan'].queryset = VLAN.objects.filter(site=None)
|
||||||
|
|
||||||
|
|
||||||
class PrefixFromCSVForm(forms.ModelForm):
|
class PrefixFromCSVForm(forms.ModelForm):
|
||||||
@ -490,7 +491,7 @@ class VLANForm(BootstrapMixin, CustomFieldForm):
|
|||||||
'role': "The primary function of this VLAN",
|
'role': "The primary function of this VLAN",
|
||||||
}
|
}
|
||||||
widgets = {
|
widgets = {
|
||||||
'site': forms.Select(attrs={'filter-for': 'group'}),
|
'site': forms.Select(attrs={'filter-for': 'group', 'default_value': '0'}),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -503,7 +504,7 @@ class VLANForm(BootstrapMixin, CustomFieldForm):
|
|||||||
elif self.initial.get('site'):
|
elif self.initial.get('site'):
|
||||||
self.fields['group'].queryset = VLANGroup.objects.filter(site=self.initial['site'])
|
self.fields['group'].queryset = VLANGroup.objects.filter(site=self.initial['site'])
|
||||||
else:
|
else:
|
||||||
self.fields['group'].choices = []
|
self.fields['group'].queryset = VLANGroup.objects.filter(site=None)
|
||||||
|
|
||||||
|
|
||||||
class VLANFromCSVForm(forms.ModelForm):
|
class VLANFromCSVForm(forms.ModelForm):
|
||||||
|
@ -460,7 +460,7 @@ class VLANGroup(models.Model):
|
|||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
slug = models.SlugField()
|
slug = models.SlugField()
|
||||||
site = models.ForeignKey('dcim.Site', related_name='vlan_groups')
|
site = models.ForeignKey('dcim.Site', related_name='vlan_groups', on_delete=models.SET_NULL, blank=True, null=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['site', 'name']
|
ordering = ['site', 'name']
|
||||||
@ -472,7 +472,8 @@ class VLANGroup(models.Model):
|
|||||||
verbose_name_plural = 'VLAN groups'
|
verbose_name_plural = 'VLAN groups'
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u'{} - {}'.format(self.site.name, self.name)
|
site_name = self.site.name if self.site else '__global'
|
||||||
|
return u'{} - {}'.format(site_name, self.name)
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return "{}?group_id={}".format(reverse('ipam:vlan_list'), self.pk)
|
return "{}?group_id={}".format(reverse('ipam:vlan_list'), self.pk)
|
||||||
@ -487,7 +488,7 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel):
|
|||||||
Like Prefixes, each VLAN is assigned an operational status and optionally a user-defined Role. A VLAN can have zero
|
Like Prefixes, each VLAN is assigned an operational status and optionally a user-defined Role. A VLAN can have zero
|
||||||
or more Prefixes assigned to it.
|
or more Prefixes assigned to it.
|
||||||
"""
|
"""
|
||||||
site = models.ForeignKey('dcim.Site', related_name='vlans', on_delete=models.PROTECT)
|
site = models.ForeignKey('dcim.Site', related_name='vlans', on_delete=models.PROTECT, blank=True, null=True)
|
||||||
group = models.ForeignKey('VLANGroup', related_name='vlans', blank=True, null=True, on_delete=models.PROTECT)
|
group = models.ForeignKey('VLANGroup', related_name='vlans', blank=True, null=True, on_delete=models.PROTECT)
|
||||||
vid = models.PositiveSmallIntegerField(verbose_name='ID', validators=[
|
vid = models.PositiveSmallIntegerField(verbose_name='ID', validators=[
|
||||||
MinValueValidator(1),
|
MinValueValidator(1),
|
||||||
|
@ -61,6 +61,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
// API select widget
|
// API select widget
|
||||||
$('select[filter-for]').change(function () {
|
$('select[filter-for]').change(function () {
|
||||||
|
var choice = $(this).val()
|
||||||
|
|
||||||
// Resolve child field by ID specified in parent
|
// Resolve child field by ID specified in parent
|
||||||
var child_name = $(this).attr('filter-for');
|
var child_name = $(this).attr('filter-for');
|
||||||
@ -70,7 +71,10 @@ $(document).ready(function() {
|
|||||||
child_field.empty();
|
child_field.empty();
|
||||||
child_field.append($("<option></option>").attr("value", "").text(""));
|
child_field.append($("<option></option>").attr("value", "").text(""));
|
||||||
|
|
||||||
if ($(this).val()) {
|
if (!choice && $(this).attr('default_value')) {
|
||||||
|
choice = $(this).attr('default_value')
|
||||||
|
}
|
||||||
|
if (choice) {
|
||||||
|
|
||||||
var api_url = child_field.attr('api-url');
|
var api_url = child_field.attr('api-url');
|
||||||
var disabled_indicator = child_field.attr('disabled-indicator');
|
var disabled_indicator = child_field.attr('disabled-indicator');
|
||||||
@ -80,8 +84,12 @@ $(document).ready(function() {
|
|||||||
// Gather the values of all other filter fields for this child
|
// Gather the values of all other filter fields for this child
|
||||||
$("select[filter-for='" + child_name + "']").each(function() {
|
$("select[filter-for='" + child_name + "']").each(function() {
|
||||||
var filter_field = $(this);
|
var filter_field = $(this);
|
||||||
if (filter_field.val()) {
|
var choice = filter_field.val()
|
||||||
api_url = api_url.replace('{{' + filter_field.attr('name') + '}}', filter_field.val());
|
if (!choice && filter_field.attr('default_value')) {
|
||||||
|
choice = filter_field.attr('default_value')
|
||||||
|
}
|
||||||
|
if (choice) {
|
||||||
|
api_url = api_url.replace('{{' + filter_field.attr('name') + '}}', choice);
|
||||||
} else {
|
} else {
|
||||||
// Not all filters have been selected yet
|
// Not all filters have been selected yet
|
||||||
return false;
|
return false;
|
||||||
|
@ -53,7 +53,11 @@
|
|||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Site</td>
|
<td>Site</td>
|
||||||
<td><a href="{% url 'dcim:site' slug=vlan.site.slug %}">{{ vlan.site }}</a></td>
|
<td>
|
||||||
|
{% if vlan.site %}
|
||||||
|
<a href="{% url 'dcim:site' slug=vlan.site.slug %}">{{ vlan.site }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Group</td>
|
<td>Group</td>
|
||||||
|
Loading…
Reference in New Issue
Block a user