diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py
index 0a016e4c6..d6db8c9bc 100644
--- a/netbox/ipam/forms.py
+++ b/netbox/ipam/forms.py
@@ -518,8 +518,6 @@ class VLANGroupFilterForm(BootstrapMixin, forms.Form):
#
class VLANForm(BootstrapMixin, CustomFieldForm):
- site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, label='Site',
- widget=forms.Select(attrs={'filter-for': 'group', 'default_value': '0'}))
group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False, label='Group', widget=APISelect(
api_url='/api/ipam/vlan-groups/?site_id={{site}}',
))
@@ -535,6 +533,9 @@ class VLANForm(BootstrapMixin, CustomFieldForm):
'status': "Operational status of this VLAN",
'role': "The primary function of this VLAN",
}
+ widgets = {
+ 'site': forms.Select(attrs={'filter-for': 'group', 'default_value': '0'}),
+ }
def __init__(self, *args, **kwargs):
diff --git a/netbox/ipam/models.py b/netbox/ipam/models.py
index 8421cb083..fa9e7834b 100644
--- a/netbox/ipam/models.py
+++ b/netbox/ipam/models.py
@@ -514,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.SET_NULL, blank=True, null=True)
+ 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),
diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js
index 348e7aaba..e421f6283 100644
--- a/netbox/project-static/js/forms.js
+++ b/netbox/project-static/js/forms.js
@@ -9,6 +9,14 @@ $(document).ready(function() {
$('#select_all').prop('checked', false);
}
});
+ // Enable hidden buttons when "select all" is checked
+ $('#select_all').click(function (event) {
+ if ($(this).is(':checked')) {
+ $('#select_all_box').find('button').prop('disabled', '');
+ } else {
+ $('#select_all_box').find('button').prop('disabled', 'disabled');
+ }
+ });
// Uncheck the "toggle all" checkbox if an item is unchecked
$('input:checkbox[name=pk]').click(function (event) {
if (!$(this).attr('checked')) {
@@ -60,45 +68,38 @@ $(document).ready(function() {
});
// API select widget
- $('select[filter-for]').change(function () {
- var choice = $(this).val()
+ $('select[filter-for]').change(function() {
// Resolve child field by ID specified in parent
var child_name = $(this).attr('filter-for');
var child_field = $('#id_' + child_name);
+ var child_selected = child_field.val();
- // Wipe out any existing options within the child field
+ // Wipe out any existing options within the child field and create a default option
child_field.empty();
- child_field.append($("").attr("value", "").text(""));
-
- if (!choice && $(this).attr('default_value')) {
- choice = $(this).attr('default_value')
- }
- if (choice) {
+ child_field.append($("").attr("value", "").text("---------"));
+ if ($(this).val() || $(this).attr('nullable') == 'true') {
var api_url = child_field.attr('api-url');
var disabled_indicator = child_field.attr('disabled-indicator');
var initial_value = child_field.attr('initial');
var display_field = child_field.attr('display-field') || 'name';
- // Gather the values of all other filter fields for this child
- $("select[filter-for='" + child_name + "']").each(function() {
- var filter_field = $(this);
- var choice = filter_field.val()
- if (!choice && filter_field.attr('default_value')) {
- choice = filter_field.attr('default_value')
+ // Determine the filter fields needed to make an API call
+ var filter_regex = /\{\{([a-z_]+)\}\}/g;
+ var match;
+ while (match = filter_regex.exec(api_url)) {
+ var filter_field = $('#id_' + match[1]);
+ if (filter_field.val()) {
+ api_url = api_url.replace(match[0], filter_field.val());
+ } else if ($(this).attr('nullable') == 'true') {
+ api_url = api_url.replace(match[0], '0');
}
- if (choice) {
- api_url = api_url.replace('{{' + filter_field.attr('name') + '}}', choice);
- } else {
- // Not all filters have been selected yet
- return false;
- }
-
- });
+ }
// If all URL variables have been replaced, make the API call
if (api_url.search('{{') < 0) {
+ console.log(child_name + ": Fetching " + api_url);
$.ajax({
url: api_url,
dataType: 'json',
@@ -106,7 +107,9 @@ $(document).ready(function() {
$.each(response, function (index, choice) {
var option = $("").attr("value", choice.id).text(choice[display_field]);
if (disabled_indicator && choice[disabled_indicator] && choice.id != initial_value) {
- option.attr("disabled", "disabled")
+ option.attr("disabled", "disabled");
+ } else if (choice.id == child_selected) {
+ option.attr("selected", "selected");
}
child_field.append(option);
});
@@ -120,4 +123,4 @@ $(document).ready(function() {
child_field.change();
});
-});
\ No newline at end of file
+});