mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
* Fix #235: Enable global vlan Decouple site/vlan, make site optional for vlan/vlangroup Change html generation code to check site existence before dereference Create site search function, if site is None for a VLAN, view it as global VLAN * commit1 * commit2 * commit3 * Add migration file for VLAN&VLAN group * Revert unintentional commits
This commit is contained in:
committed by
Jeremy Stretch
parent
2876ef7607
commit
aba9748ffd
@ -262,37 +262,47 @@ class IPAddressFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
|
||||
|
||||
class VLANGroupFilter(django_filters.FilterSet):
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
site_id = NullableModelMultipleChoiceFilter(
|
||||
name='site',
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
method='site_search',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
site = NullableModelMultipleChoiceFilter(
|
||||
name='site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
method='site_search',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
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):
|
||||
q = django_filters.MethodFilter(
|
||||
action='search',
|
||||
label='Search',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
site_id = NullableModelMultipleChoiceFilter(
|
||||
name='site',
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
method='site_search',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
site = NullableModelMultipleChoiceFilter(
|
||||
name='site__slug',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
method='site_search',
|
||||
)
|
||||
group_id = NullableModelMultipleChoiceFilter(
|
||||
name='group',
|
||||
@ -349,6 +359,12 @@ class VLANFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
pass
|
||||
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):
|
||||
device_id = django_filters.ModelMultipleChoiceFilter(
|
||||
|
@ -153,7 +153,8 @@ class RoleForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
class PrefixForm(BootstrapMixin, CustomFieldForm):
|
||||
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',
|
||||
widget=APISelect(api_url='/api/ipam/vlans/?site_id={{site}}',
|
||||
display_field='display_name'))
|
||||
@ -173,7 +174,7 @@ class PrefixForm(BootstrapMixin, CustomFieldForm):
|
||||
elif self.initial.get('site'):
|
||||
self.fields['vlan'].queryset = VLAN.objects.filter(site=self.initial['site'])
|
||||
else:
|
||||
self.fields['vlan'].choices = []
|
||||
self.fields['vlan'].queryset = VLAN.objects.filter(site=None)
|
||||
|
||||
|
||||
class PrefixFromCSVForm(forms.ModelForm):
|
||||
@ -508,7 +509,8 @@ class VLANGroupForm(BootstrapMixin, forms.ModelForm):
|
||||
|
||||
|
||||
class VLANGroupFilterForm(BootstrapMixin, forms.Form):
|
||||
site = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('vlan_groups')), to_field_name='slug')
|
||||
site = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('vlan_groups')), to_field_name='slug',
|
||||
null_option=(0, 'Global'))
|
||||
|
||||
|
||||
#
|
||||
@ -532,7 +534,7 @@ class VLANForm(BootstrapMixin, CustomFieldForm):
|
||||
'role': "The primary function of this VLAN",
|
||||
}
|
||||
widgets = {
|
||||
'site': forms.Select(attrs={'filter-for': 'group'}),
|
||||
'site': forms.Select(attrs={'filter-for': 'group', 'default_value': '0'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -545,11 +547,11 @@ class VLANForm(BootstrapMixin, CustomFieldForm):
|
||||
elif self.initial.get('site'):
|
||||
self.fields['group'].queryset = VLANGroup.objects.filter(site=self.initial['site'])
|
||||
else:
|
||||
self.fields['group'].choices = []
|
||||
self.fields['group'].queryset = VLANGroup.objects.filter(site=None)
|
||||
|
||||
|
||||
class VLANFromCSVForm(forms.ModelForm):
|
||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), to_field_name='name',
|
||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, to_field_name='name',
|
||||
error_messages={'invalid_choice': 'Site not found.'})
|
||||
group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False, to_field_name='name',
|
||||
error_messages={'invalid_choice': 'VLAN group not found.'})
|
||||
@ -599,7 +601,8 @@ def vlan_status_choices():
|
||||
class VLANFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
model = VLAN
|
||||
q = forms.CharField(required=False, label='Search')
|
||||
site = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('vlans')), to_field_name='slug')
|
||||
site = FilterChoiceField(queryset=Site.objects.annotate(filter_count=Count('vlans')), to_field_name='slug',
|
||||
null_option=(0, 'Global'))
|
||||
group_id = FilterChoiceField(queryset=VLANGroup.objects.annotate(filter_count=Count('vlans')), label='VLAN group',
|
||||
null_option=(0, 'None'))
|
||||
tenant = FilterChoiceField(queryset=Tenant.objects.annotate(filter_count=Count('vlans')), to_field_name='slug',
|
||||
|
26
netbox/ipam/migrations/0015_auto_20170219_0726.py
Normal file
26
netbox/ipam/migrations/0015_auto_20170219_0726.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-02-19 07:26
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0014_ipaddress_status_add_deprecated'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='vlan',
|
||||
name='site',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vlans', to='dcim.Site'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vlangroup',
|
||||
name='site',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vlan_groups', to='dcim.Site'),
|
||||
),
|
||||
]
|
@ -485,7 +485,7 @@ class VLANGroup(models.Model):
|
||||
"""
|
||||
name = models.CharField(max_length=50)
|
||||
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:
|
||||
ordering = ['site', 'name']
|
||||
@ -497,7 +497,8 @@ class VLANGroup(models.Model):
|
||||
verbose_name_plural = 'VLAN groups'
|
||||
|
||||
def __str__(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):
|
||||
return "{}?group_id={}".format(reverse('ipam:vlan_list'), self.pk)
|
||||
@ -513,7 +514,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
|
||||
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)
|
||||
vid = models.PositiveSmallIntegerField(verbose_name='ID', validators=[
|
||||
MinValueValidator(1),
|
||||
@ -551,7 +552,7 @@ class VLAN(CreatedUpdatedModel, CustomFieldModel):
|
||||
|
||||
def to_csv(self):
|
||||
return csv_format([
|
||||
self.site.name,
|
||||
self.site.name if self.site else None,
|
||||
self.group.name if self.group else None,
|
||||
self.vid,
|
||||
self.name,
|
||||
|
@ -8,7 +8,11 @@
|
||||
<div class="col-sm-8 col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'ipam:vlan_list' %}">VLANs</a></li>
|
||||
<li><a href="{% url 'ipam:vlan_list' %}?site={{ vlan.site.slug }}">{{ vlan.site }}</a></li>
|
||||
{% if vlan.site %}
|
||||
<li><a href="{% url 'ipam:vlan_list' %}?site={{ vlan.site.slug }}">{{ vlan.site }}</a></li>
|
||||
{% else %}
|
||||
<li><a href="{% url 'ipam:vlan_list' %}?site_id=0">Global</a></li>
|
||||
{% endif %}
|
||||
{% if vlan.group %}
|
||||
<li><a href="{% url 'ipam:vlan_list' %}?site={{ vlan.site.slug }}&group={{ vlan.group.slug }}">{{ vlan.group.name }}</a></li>
|
||||
{% endif %}
|
||||
@ -53,7 +57,13 @@
|
||||
<table class="table table-hover panel-body attr-table">
|
||||
<tr>
|
||||
<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>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Group</td>
|
||||
|
Reference in New Issue
Block a user