mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -06:00
commit
328958876a
@ -112,9 +112,8 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Site
|
model = Site
|
||||||
fields = [
|
fields = [
|
||||||
'name', 'slug', 'status', 'region', 'tenant_group', 'tenant', 'facility', 'asn', 'description',
|
'name', 'slug', 'status', 'region', 'tenant_group', 'tenant', 'facility', 'asn', 'time_zone', 'description',
|
||||||
'physical_address', 'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'time_zone',
|
'physical_address', 'shipping_address', 'contact_name', 'contact_phone', 'contact_email', 'comments',
|
||||||
'comments',
|
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'physical_address': SmallTextarea(attrs={'rows': 3}),
|
'physical_address': SmallTextarea(attrs={'rows': 3}),
|
||||||
@ -124,6 +123,8 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
'name': "Full name of the site",
|
'name': "Full name of the site",
|
||||||
'facility': "Data center provider and facility (e.g. Equinix NY7)",
|
'facility': "Data center provider and facility (e.g. Equinix NY7)",
|
||||||
'asn': "BGP autonomous system number",
|
'asn': "BGP autonomous system number",
|
||||||
|
'time_zone': "Local time zone",
|
||||||
|
'description': "Short description (will appear in sites list)",
|
||||||
'physical_address': "Physical location of the building (e.g. for GPS)",
|
'physical_address': "Physical location of the building (e.g. for GPS)",
|
||||||
'shipping_address': "If different from the physical address"
|
'shipping_address': "If different from the physical address"
|
||||||
}
|
}
|
||||||
@ -131,7 +132,7 @@ class SiteForm(BootstrapMixin, TenancyForm, CustomFieldForm):
|
|||||||
|
|
||||||
class SiteCSVForm(forms.ModelForm):
|
class SiteCSVForm(forms.ModelForm):
|
||||||
status = CSVChoiceField(
|
status = CSVChoiceField(
|
||||||
choices=DEVICE_STATUS_CHOICES,
|
choices=SITE_STATUS_CHOICES,
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Operational status'
|
help_text='Operational status'
|
||||||
)
|
)
|
||||||
@ -705,7 +706,7 @@ class PlatformCSVForm(forms.ModelForm):
|
|||||||
slug = SlugField()
|
slug = SlugField()
|
||||||
manufacturer = forms.ModelChoiceField(
|
manufacturer = forms.ModelChoiceField(
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
required=True,
|
required=False,
|
||||||
to_field_name='name',
|
to_field_name='name',
|
||||||
help_text='Manufacturer name',
|
help_text='Manufacturer name',
|
||||||
error_messages={
|
error_messages={
|
||||||
|
@ -43,13 +43,13 @@ class InterfaceQuerySet(QuerySet):
|
|||||||
}[method]
|
}[method]
|
||||||
|
|
||||||
TYPE_RE = r"SUBSTRING({} FROM '^([^0-9]+)')"
|
TYPE_RE = r"SUBSTRING({} FROM '^([^0-9]+)')"
|
||||||
ID_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)([0-9]+)$') AS integer)"
|
ID_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)(\d{{1,9}})$') AS integer)"
|
||||||
SLOT_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?([0-9]+)\/') AS integer)"
|
SLOT_RE = r"CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(\d{{1,9}})\/') AS integer)"
|
||||||
SUBSLOT_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:[0-9]+\/)([0-9]+)') AS integer), 0)"
|
SUBSLOT_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:\d{{1,9}}\/)(\d{{1,9}})') AS integer), 0)"
|
||||||
POSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:[0-9]+\/){{2}}([0-9]+)') AS integer), 0)"
|
POSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:\d{{1,9}}\/){{2}}(\d{{1,9}})') AS integer), 0)"
|
||||||
SUBPOSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:[0-9]+\/){{3}}([0-9]+)') AS integer), 0)"
|
SUBPOSITION_RE = r"COALESCE(CAST(SUBSTRING({} FROM '^(?:[^0-9]+)?(?:\d{{1,9}}\/){{3}}(\d{{1,9}})') AS integer), 0)"
|
||||||
CHANNEL_RE = r"COALESCE(CAST(SUBSTRING({} FROM ':([0-9]+)(\.[0-9]+)?$') AS integer), 0)"
|
CHANNEL_RE = r"COALESCE(CAST(SUBSTRING({} FROM ':(\d{{1,9}})(\.\d{{1,9}})?$') AS integer), 0)"
|
||||||
VC_RE = r"COALESCE(CAST(SUBSTRING({} FROM '\.([0-9]+)$') AS integer), 0)"
|
VC_RE = r"COALESCE(CAST(SUBSTRING({} FROM '\.(\d{{1,9}})$') AS integer), 0)"
|
||||||
|
|
||||||
fields = {
|
fields = {
|
||||||
'_type': RawSQL(TYPE_RE.format(sql_col), []),
|
'_type': RawSQL(TYPE_RE.format(sql_col), []),
|
||||||
|
@ -41,19 +41,21 @@ class BulkRenameView(View):
|
|||||||
"""
|
"""
|
||||||
An extendable view for renaming device components in bulk.
|
An extendable view for renaming device components in bulk.
|
||||||
"""
|
"""
|
||||||
model = None
|
queryset = None
|
||||||
form = None
|
form = None
|
||||||
template_name = 'dcim/bulk_rename.html'
|
template_name = 'dcim/bulk_rename.html'
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
|
|
||||||
|
model = self.queryset.model
|
||||||
|
|
||||||
return_url = request.GET.get('return_url')
|
return_url = request.GET.get('return_url')
|
||||||
if not return_url or not is_safe_url(url=return_url, host=request.get_host()):
|
if not return_url or not is_safe_url(url=return_url, host=request.get_host()):
|
||||||
return_url = 'home'
|
return_url = 'home'
|
||||||
|
|
||||||
if '_preview' in request.POST or '_apply' in request.POST:
|
if '_preview' in request.POST or '_apply' in request.POST:
|
||||||
form = self.form(request.POST, initial={'pk': request.POST.getlist('pk')})
|
form = self.form(request.POST, initial={'pk': request.POST.getlist('pk')})
|
||||||
selected_objects = self.model.objects.filter(pk__in=form.initial['pk'])
|
selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
for obj in selected_objects:
|
for obj in selected_objects:
|
||||||
@ -65,17 +67,17 @@ class BulkRenameView(View):
|
|||||||
obj.save()
|
obj.save()
|
||||||
messages.success(request, "Renamed {} {}".format(
|
messages.success(request, "Renamed {} {}".format(
|
||||||
len(selected_objects),
|
len(selected_objects),
|
||||||
self.model._meta.verbose_name_plural
|
model._meta.verbose_name_plural
|
||||||
))
|
))
|
||||||
return redirect(return_url)
|
return redirect(return_url)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
form = self.form(initial={'pk': request.POST.getlist('pk')})
|
form = self.form(initial={'pk': request.POST.getlist('pk')})
|
||||||
selected_objects = self.model.objects.filter(pk__in=form.initial['pk'])
|
selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'form': form,
|
'form': form,
|
||||||
'obj_type_plural': self.model._meta.verbose_name_plural,
|
'obj_type_plural': model._meta.verbose_name_plural,
|
||||||
'selected_objects': selected_objects,
|
'selected_objects': selected_objects,
|
||||||
'return_url': return_url,
|
'return_url': return_url,
|
||||||
})
|
})
|
||||||
@ -1316,7 +1318,7 @@ class ConsoleServerPortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|||||||
|
|
||||||
class ConsoleServerPortBulkRenameView(PermissionRequiredMixin, BulkRenameView):
|
class ConsoleServerPortBulkRenameView(PermissionRequiredMixin, BulkRenameView):
|
||||||
permission_required = 'dcim.change_consoleserverport'
|
permission_required = 'dcim.change_consoleserverport'
|
||||||
model = ConsoleServerPort
|
queryset = ConsoleServerPort.objects.all()
|
||||||
form = forms.ConsoleServerPortBulkRenameForm
|
form = forms.ConsoleServerPortBulkRenameForm
|
||||||
|
|
||||||
|
|
||||||
@ -1600,7 +1602,7 @@ class PowerOutletDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
|||||||
|
|
||||||
class PowerOutletBulkRenameView(PermissionRequiredMixin, BulkRenameView):
|
class PowerOutletBulkRenameView(PermissionRequiredMixin, BulkRenameView):
|
||||||
permission_required = 'dcim.change_poweroutlet'
|
permission_required = 'dcim.change_poweroutlet'
|
||||||
model = PowerOutlet
|
queryset = PowerOutlet.objects.all()
|
||||||
form = forms.PowerOutletBulkRenameForm
|
form = forms.PowerOutletBulkRenameForm
|
||||||
|
|
||||||
|
|
||||||
@ -1676,7 +1678,7 @@ class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
|||||||
|
|
||||||
class InterfaceBulkRenameView(PermissionRequiredMixin, BulkRenameView):
|
class InterfaceBulkRenameView(PermissionRequiredMixin, BulkRenameView):
|
||||||
permission_required = 'dcim.change_interface'
|
permission_required = 'dcim.change_interface'
|
||||||
model = Interface
|
queryset = Interface.objects.order_naturally()
|
||||||
form = forms.InterfaceBulkRenameForm
|
form = forms.InterfaceBulkRenameForm
|
||||||
|
|
||||||
|
|
||||||
@ -1783,7 +1785,7 @@ class DeviceBayDepopulateView(PermissionRequiredMixin, View):
|
|||||||
|
|
||||||
class DeviceBayBulkRenameView(PermissionRequiredMixin, BulkRenameView):
|
class DeviceBayBulkRenameView(PermissionRequiredMixin, BulkRenameView):
|
||||||
permission_required = 'dcim.change_devicebay'
|
permission_required = 'dcim.change_devicebay'
|
||||||
model = DeviceBay
|
queryset = DeviceBay.objects.all()
|
||||||
form = forms.DeviceBayBulkRenameForm
|
form = forms.DeviceBayBulkRenameForm
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,11 +43,18 @@ class CustomFieldFilter(django_filters.Filter):
|
|||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
||||||
# Apply the assigned filter logic (exact or loose)
|
# Apply the assigned filter logic (exact or loose)
|
||||||
queryset = queryset.filter(custom_field_values__field__name=self.name)
|
|
||||||
if self.cf_type == CF_TYPE_BOOLEAN or self.filter_logic == CF_FILTER_EXACT:
|
if self.cf_type == CF_TYPE_BOOLEAN or self.filter_logic == CF_FILTER_EXACT:
|
||||||
return queryset.filter(custom_field_values__serialized_value=value)
|
queryset = queryset.filter(
|
||||||
|
custom_field_values__field__name=self.name,
|
||||||
|
custom_field_values__serialized_value=value
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return queryset.filter(custom_field_values__serialized_value__icontains=value)
|
queryset = queryset.filter(
|
||||||
|
custom_field_values__field__name=self.name,
|
||||||
|
custom_field_values__serialized_value__icontains=value
|
||||||
|
)
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldFilterSet(django_filters.FilterSet):
|
class CustomFieldFilterSet(django_filters.FilterSet):
|
||||||
|
@ -508,7 +508,7 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
|
|||||||
|
|
||||||
ipaddress = super(IPAddressForm, self).save(*args, **kwargs)
|
ipaddress = super(IPAddressForm, self).save(*args, **kwargs)
|
||||||
|
|
||||||
# Assign this IPAddress as the primary for the associated Device.
|
# Assign/clear this IPAddress as the primary for the associated Device/VirtualMachine.
|
||||||
if self.cleaned_data['primary_for_parent']:
|
if self.cleaned_data['primary_for_parent']:
|
||||||
parent = self.cleaned_data['interface'].parent
|
parent = self.cleaned_data['interface'].parent
|
||||||
if ipaddress.address.version == 4:
|
if ipaddress.address.version == 4:
|
||||||
@ -516,14 +516,12 @@ class IPAddressForm(BootstrapMixin, TenancyForm, ReturnURLForm, CustomFieldForm)
|
|||||||
else:
|
else:
|
||||||
parent.primary_ip6 = ipaddress
|
parent.primary_ip6 = ipaddress
|
||||||
parent.save()
|
parent.save()
|
||||||
|
|
||||||
# Clear assignment as primary for device if set.
|
|
||||||
elif self.cleaned_data['interface']:
|
elif self.cleaned_data['interface']:
|
||||||
parent = self.cleaned_data['interface'].parent
|
parent = self.cleaned_data['interface'].parent
|
||||||
if ipaddress.address.version == 4 and parent.primary_ip4 == self:
|
if ipaddress.address.version == 4 and parent.primary_ip4 == ipaddress:
|
||||||
parent.primary_ip4 = None
|
parent.primary_ip4 = None
|
||||||
parent.save()
|
parent.save()
|
||||||
elif ipaddress.address.version == 6 and parent.primary_ip6 == self:
|
elif ipaddress.address.version == 6 and parent.primary_ip6 == ipaddress:
|
||||||
parent.primary_ip6 = None
|
parent.primary_ip6 = None
|
||||||
parent.save()
|
parent.save()
|
||||||
|
|
||||||
|
@ -329,7 +329,7 @@ class IPAddressAssignTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = ('address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface')
|
fields = ('address', 'vrf', 'status', 'role', 'tenant', 'parent', 'interface', 'description')
|
||||||
orderable = False
|
orderable = False
|
||||||
|
|
||||||
|
|
||||||
|
@ -729,8 +729,8 @@ class IPAddressAssignView(PermissionRequiredMixin, View):
|
|||||||
'vrf', 'tenant', 'interface__device', 'interface__virtual_machine'
|
'vrf', 'tenant', 'interface__device', 'interface__virtual_machine'
|
||||||
).filter(
|
).filter(
|
||||||
vrf=form.cleaned_data['vrf'],
|
vrf=form.cleaned_data['vrf'],
|
||||||
address__net_host=form.cleaned_data['address'],
|
address__istartswith=form.cleaned_data['address'],
|
||||||
)
|
)[:100] # Limit to 100 results
|
||||||
table = tables.IPAddressAssignTable(queryset)
|
table = tables.IPAddressAssignTable(queryset)
|
||||||
|
|
||||||
return render(request, 'ipam/ipaddress_assign.html', {
|
return render(request, 'ipam/ipaddress_assign.html', {
|
||||||
|
@ -22,7 +22,7 @@ if sys.version_info[0] < 3:
|
|||||||
DeprecationWarning
|
DeprecationWarning
|
||||||
)
|
)
|
||||||
|
|
||||||
VERSION = '2.3.2'
|
VERSION = '2.3.3'
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
{% render_field form.facility %}
|
{% render_field form.facility %}
|
||||||
{% render_field form.asn %}
|
{% render_field form.asn %}
|
||||||
{% render_field form.time_zone %}
|
{% render_field form.time_zone %}
|
||||||
|
{% render_field form.description %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{% if table %}
|
{% if table %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-10 col-md-offset-1" style="margin-top: 20px">
|
<div class="col-md-12" style="margin-top: 20px">
|
||||||
<h3>Search Results</h3>
|
<h3>Search Results</h3>
|
||||||
{% include 'utilities/obj_table.html' with table_template='panel_table.html' %}
|
{% include 'utilities/obj_table.html' with table_template='panel_table.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -205,7 +205,8 @@ class ArrayFieldSelectMultiple(SelectWithDisabled, forms.SelectMultiple):
|
|||||||
|
|
||||||
def optgroups(self, name, value, attrs=None):
|
def optgroups(self, name, value, attrs=None):
|
||||||
# Split the delimited string of values into a list
|
# Split the delimited string of values into a list
|
||||||
value = value[0].split(self.delimiter)
|
if value:
|
||||||
|
value = value[0].split(self.delimiter)
|
||||||
return super(ArrayFieldSelectMultiple, self).optgroups(name, value, attrs)
|
return super(ArrayFieldSelectMultiple, self).optgroups(name, value, attrs)
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
def value_from_datadict(self, data, files, name):
|
||||||
|
@ -14,7 +14,7 @@ def csv_format(data):
|
|||||||
for value in data:
|
for value in data:
|
||||||
|
|
||||||
# Represent None or False with empty string
|
# Represent None or False with empty string
|
||||||
if value in [None, False]:
|
if value is None or value is False:
|
||||||
csv.append('')
|
csv.append('')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -3,10 +3,10 @@ from __future__ import unicode_literals
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from dcim.api.serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer
|
from dcim.api.serializers import NestedDeviceRoleSerializer, NestedPlatformSerializer, NestedSiteSerializer
|
||||||
from dcim.constants import IFACE_FF_VIRTUAL
|
from dcim.constants import IFACE_FF_VIRTUAL, IFACE_MODE_CHOICES
|
||||||
from dcim.models import Interface
|
from dcim.models import Interface
|
||||||
from extras.api.customfields import CustomFieldModelSerializer
|
from extras.api.customfields import CustomFieldModelSerializer
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress, VLAN
|
||||||
from tenancy.api.serializers import NestedTenantSerializer
|
from tenancy.api.serializers import NestedTenantSerializer
|
||||||
from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
|
from utilities.api import ChoiceFieldSerializer, ValidatedModelSerializer
|
||||||
from virtualization.constants import VM_STATUS_CHOICES
|
from virtualization.constants import VM_STATUS_CHOICES
|
||||||
@ -133,13 +133,26 @@ class WritableVirtualMachineSerializer(CustomFieldModelSerializer):
|
|||||||
# VM interfaces
|
# VM interfaces
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# Cannot import ipam.api.serializers.NestedVLANSerializer due to circular dependency
|
||||||
|
class InterfaceVLANSerializer(serializers.ModelSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:vlan-detail')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = VLAN
|
||||||
|
fields = ['id', 'url', 'vid', 'name', 'display_name']
|
||||||
|
|
||||||
|
|
||||||
class InterfaceSerializer(serializers.ModelSerializer):
|
class InterfaceSerializer(serializers.ModelSerializer):
|
||||||
virtual_machine = NestedVirtualMachineSerializer()
|
virtual_machine = NestedVirtualMachineSerializer()
|
||||||
|
mode = ChoiceFieldSerializer(choices=IFACE_MODE_CHOICES)
|
||||||
|
untagged_vlan = InterfaceVLANSerializer()
|
||||||
|
tagged_vlans = InterfaceVLANSerializer(many=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'description',
|
'id', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'mode', 'untagged_vlan', 'tagged_vlans',
|
||||||
|
'description',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -157,5 +170,6 @@ class WritableInterfaceSerializer(ValidatedModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'virtual_machine', 'form_factor', 'enabled', 'mac_address', 'mtu', 'description',
|
'id', 'name', 'virtual_machine', 'form_factor', 'enabled', 'mac_address', 'mtu', 'mode', 'untagged_vlan',
|
||||||
|
'tagged_vlans', 'description',
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user