1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Merge branch 'develop' into v2-develop

Conflicts:
	netbox/dcim/forms.py
	netbox/dcim/views.py
	netbox/ipam/forms.py
	netbox/templates/_base.html
	netbox/utilities/views.py
This commit is contained in:
Jeremy Stretch
2017-04-13 15:42:50 -04:00
26 changed files with 290 additions and 378 deletions

View File

@@ -6,7 +6,7 @@ from extras.forms import CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFi
from tenancy.models import Tenant
from utilities.forms import (
APISelect, BootstrapMixin, BulkImportForm, CSVDataField, ExpandableIPAddressField, FilterChoiceField, Livesearch,
SlugField, add_blank_choice,
ReturnURLForm, SlugField, add_blank_choice,
)
from .models import (
@@ -210,28 +210,33 @@ class PrefixFromCSVForm(forms.ModelForm):
site = self.cleaned_data.get('site')
vlan_group_name = self.cleaned_data.get('vlan_group_name')
vlan_vid = self.cleaned_data.get('vlan_vid')
# Validate VLAN
vlan_group = None
vlan = None
# Validate VLAN group
if vlan_group_name:
try:
vlan_group = VLANGroup.objects.get(site=site, name=vlan_group_name)
except VLANGroup.DoesNotExist:
self.add_error('vlan_group_name', "Invalid VLAN group ({} - {}).".format(site, vlan_group_name))
if vlan_vid and vlan_group:
if site:
self.add_error('vlan_group_name', "Invalid VLAN group ({} - {}).".format(site, vlan_group_name))
else:
self.add_error('vlan_group_name', "Invalid global VLAN group ({}).".format(vlan_group_name))
# Validate VLAN
if vlan_vid:
try:
self.instance.vlan = VLAN.objects.get(group=vlan_group, vid=vlan_vid)
self.instance.vlan = VLAN.objects.get(site=site, group=vlan_group, vid=vlan_vid)
except VLAN.DoesNotExist:
self.add_error('vlan_vid', "Invalid VLAN ID ({} - {}).".format(vlan_group, vlan_vid))
elif vlan_vid and site:
try:
self.instance.vlan = VLAN.objects.get(site=site, vid=vlan_vid)
except VLAN.DoesNotExist:
self.add_error('vlan_vid', "Invalid VLAN ID ({}) for site {}.".format(vlan_vid, site))
if site:
self.add_error('vlan_vid', "Invalid VLAN ID ({}) for site {}.".format(vlan_vid, site))
elif vlan_group:
self.add_error('vlan_vid', "Invalid VLAN ID ({}) for group {}.".format(vlan_vid, vlan_group_name))
elif not vlan_group_name:
self.add_error('vlan_vid', "Invalid global VLAN ID ({}).".format(vlan_vid))
except VLAN.MultipleObjectsReturned:
self.add_error('vlan_vid', "Multiple VLANs found ({} - VID {})".format(site, vlan_vid))
elif vlan_vid:
self.add_error('vlan_vid', "Must specify site and/or VLAN group when assigning a VLAN.")
self.instance.vlan = vlan
def save(self, *args, **kwargs):
@@ -302,21 +307,46 @@ class PrefixFilterForm(BootstrapMixin, CustomFieldFilterForm):
# IP addresses
#
class IPAddressForm(BootstrapMixin, CustomFieldForm):
nat_site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site',
widget=forms.Select(attrs={'filter-for': 'nat_device'}))
nat_device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, label='Device',
widget=APISelect(api_url='/api/dcim/devices/?site_id={{nat_site}}',
display_field='display_name',
attrs={'filter-for': 'nat_inside'}))
livesearch = forms.CharField(required=False, label='IP Address', widget=Livesearch(
query_key='q', query_url='ipam-api:ipaddress-list', field_to_update='nat_inside', obj_label='address')
class IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm):
interface_site = forms.ModelChoiceField(
queryset=Site.objects.all(), required=False, label='Site', widget=forms.Select(
attrs={'filter-for': 'interface_rack'}
)
)
interface_rack = forms.ModelChoiceField(
queryset=Rack.objects.all(), required=False, label='Rack', widget=APISelect(
api_url='/api/dcim/racks/?site_id={{interface_site}}', display_field='display_name',
attrs={'filter-for': 'interface_device'}
)
)
interface_device = forms.ModelChoiceField(
queryset=Device.objects.all(), required=False, label='Device', widget=APISelect(
api_url='/api/dcim/devices/?site_id={{interface_site}}&rack_id={{interface_rack}}',
display_field='display_name', attrs={'filter-for': 'interface'}
)
)
nat_site = forms.ModelChoiceField(
queryset=Site.objects.all(), required=False, label='Site', widget=forms.Select(
attrs={'filter-for': 'nat_device'}
)
)
nat_device = forms.ModelChoiceField(
queryset=Device.objects.all(), required=False, label='Device', widget=APISelect(
api_url='/api/dcim/devices/?site_id={{nat_site}}', display_field='display_name',
attrs={'filter-for': 'nat_inside'}
)
)
livesearch = forms.CharField(
required=False, label='IP Address', widget=Livesearch(
query_key='q', query_url='ipam-api:ipaddress_list', field_to_update='nat_inside', obj_label='address'
)
)
class Meta:
model = IPAddress
fields = ['address', 'vrf', 'tenant', 'status', 'nat_inside', 'description']
fields = ['address', 'vrf', 'tenant', 'status', 'interface', 'nat_inside', 'description']
widgets = {
'interface': APISelect(api_url='/api/dcim/devices/interfaces/?device_id={{interface_device}}'),
'nat_inside': APISelect(api_url='/api/ipam/ip-addresses/?device_id={{nat_device}}', display_field='address')
}
@@ -325,8 +355,37 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
self.fields['vrf'].empty_label = 'Global'
if self.instance.nat_inside:
# If an interface has been assigned, initialize site, rack, and device
if self.instance.interface:
self.initial['interface_site'] = self.instance.interface.device.site
self.initial['interface_rack'] = self.instance.interface.device.rack
self.initial['interface_device'] = self.instance.interface.device
# Limit rack choices
if self.is_bound and self.data.get('interface_site'):
self.fields['interface_rack'].queryset = Rack.objects.filter(site__pk=self.data['interface_site'])
elif self.initial.get('interface_site'):
self.fields['interface_rack'].queryset = Rack.objects.filter(site=self.initial['interface_site'])
else:
self.fields['interface_rack'].choices = []
# Limit device choices
if self.is_bound and self.data.get('interface_rack'):
self.fields['interface_device'].queryset = Device.objects.filter(rack=self.data['interface_rack'])
elif self.initial.get('interface_rack'):
self.fields['interface_device'].queryset = Device.objects.filter(rack=self.initial['interface_rack'])
else:
self.fields['interface_device'].choices = []
# Limit interface choices
if self.is_bound and self.data.get('interface_device'):
self.fields['interface'].queryset = Interface.objects.filter(device=self.data['interface_device'])
elif self.initial.get('interface_device'):
self.fields['interface'].queryset = Interface.objects.filter(device=self.initial['interface_device'])
else:
self.fields['interface'].choices = []
if self.instance.nat_inside:
nat_inside = self.instance.nat_inside
# If the IP is assigned to an interface, populate site/device fields accordingly
if self.instance.nat_inside.interface:
@@ -340,9 +399,7 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
)
else:
self.fields['nat_inside'].queryset = IPAddress.objects.filter(pk=nat_inside.pk)
else:
# Initialize nat_device choices if nat_site is set
if self.is_bound and self.data.get('nat_site'):
self.fields['nat_device'].queryset = Device.objects.filter(site__pk=self.data['nat_site'])
@@ -350,7 +407,6 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
self.fields['nat_device'].queryset = Device.objects.filter(site=self.initial['nat_site'])
else:
self.fields['nat_device'].choices = []
# Initialize nat_inside choices if nat_device is set
if self.is_bound and self.data.get('nat_device'):
self.fields['nat_inside'].queryset = IPAddress.objects.filter(

View File

@@ -58,8 +58,6 @@ urlpatterns = [
url(r'^ip-addresses/delete/$', views.IPAddressBulkDeleteView.as_view(), name='ipaddress_bulk_delete'),
url(r'^ip-addresses/(?P<pk>\d+)/$', views.ipaddress, name='ipaddress'),
url(r'^ip-addresses/(?P<pk>\d+)/edit/$', views.IPAddressEditView.as_view(), name='ipaddress_edit'),
url(r'^ip-addresses/(?P<pk>\d+)/assign/$', views.ipaddress_assign, name='ipaddress_assign'),
url(r'^ip-addresses/(?P<pk>\d+)/remove/$', views.ipaddress_remove, name='ipaddress_remove'),
url(r'^ip-addresses/(?P<pk>\d+)/delete/$', views.IPAddressDeleteView.as_view(), name='ipaddress_delete'),
# VLAN groups

View File

@@ -244,7 +244,7 @@ class RIREditView(PermissionRequiredMixin, ObjectEditView):
model = RIR
form_class = forms.RIRForm
def get_return_url(self, obj):
def get_return_url(self, request, obj):
return reverse('ipam:rir_list')
@@ -370,7 +370,7 @@ class RoleEditView(PermissionRequiredMixin, ObjectEditView):
model = Role
form_class = forms.RoleForm
def get_return_url(self, obj):
def get_return_url(self, request, obj):
return reverse('ipam:role_list')
@@ -464,7 +464,6 @@ class PrefixEditView(PermissionRequiredMixin, ObjectEditView):
model = Prefix
form_class = forms.PrefixForm
template_name = 'ipam/prefix_edit.html'
fields_initial = ['vrf', 'tenant', 'site', 'prefix', 'vlan']
default_return_url = 'ipam:prefix_list'
@@ -572,80 +571,10 @@ def ipaddress(request, pk):
})
@permission_required(['dcim.change_device', 'ipam.change_ipaddress'])
def ipaddress_assign(request, pk):
ipaddress = get_object_or_404(IPAddress, pk=pk)
if request.method == 'POST':
form = forms.IPAddressAssignForm(request.POST)
if form.is_valid():
interface = form.cleaned_data['interface']
ipaddress.interface = interface
ipaddress.save()
messages.success(request, u"Assigned IP address {} to interface {}.".format(ipaddress, ipaddress.interface))
if form.cleaned_data['set_as_primary']:
device = interface.device
if ipaddress.family == 4:
device.primary_ip4 = ipaddress
elif ipaddress.family == 6:
device.primary_ip6 = ipaddress
device.save()
return redirect('ipam:ipaddress', pk=ipaddress.pk)
else:
assert False, form.errors
else:
form = forms.IPAddressAssignForm()
return render(request, 'ipam/ipaddress_assign.html', {
'ipaddress': ipaddress,
'form': form,
'return_url': reverse('ipam:ipaddress', kwargs={'pk': ipaddress.pk}),
})
@permission_required(['dcim.change_device', 'ipam.change_ipaddress'])
def ipaddress_remove(request, pk):
ipaddress = get_object_or_404(IPAddress, pk=pk)
if request.method == 'POST':
form = ConfirmationForm(request.POST)
if form.is_valid():
device = ipaddress.interface.device
ipaddress.interface = None
ipaddress.save()
messages.success(request, u"Removed IP address {} from {}.".format(ipaddress, device))
if device.primary_ip4 == ipaddress.pk:
device.primary_ip4 = None
device.save()
elif device.primary_ip6 == ipaddress.pk:
device.primary_ip6 = None
device.save()
return redirect('ipam:ipaddress', pk=ipaddress.pk)
else:
form = ConfirmationForm()
return render(request, 'ipam/ipaddress_unassign.html', {
'ipaddress': ipaddress,
'form': form,
'return_url': reverse('ipam:ipaddress', kwargs={'pk': ipaddress.pk}),
})
class IPAddressEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'ipam.change_ipaddress'
model = IPAddress
form_class = forms.IPAddressForm
fields_initial = ['address', 'vrf']
template_name = 'ipam/ipaddress_edit.html'
default_return_url = 'ipam:ipaddress_list'
@@ -718,7 +647,7 @@ class VLANGroupEditView(PermissionRequiredMixin, ObjectEditView):
model = VLANGroup
form_class = forms.VLANGroupForm
def get_return_url(self, obj):
def get_return_url(self, request, obj):
return reverse('ipam:vlangroup_list')
@@ -807,7 +736,7 @@ class ServiceEditView(PermissionRequiredMixin, ObjectEditView):
obj.device = get_object_or_404(Device, pk=url_kwargs['device'])
return obj
def get_return_url(self, obj):
def get_return_url(self, request, obj):
return obj.device.get_absolute_url()