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:
Shawn Peng 2017-01-12 18:52:20 -08:00
parent 424c2a59d6
commit c2793e90dc
5 changed files with 45 additions and 15 deletions

View File

@ -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(

View File

@ -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):

View File

@ -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),

View File

@ -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;

View File

@ -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>