diff --git a/netbox/ipam/forms.py b/netbox/ipam/forms.py index 33cfc2211..8734107d8 100644 --- a/netbox/ipam/forms.py +++ b/netbox/ipam/forms.py @@ -236,7 +236,6 @@ class PrefixFromCSVForm(forms.ModelForm): 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)) - self.instance.vlan = vlan def save(self, *args, **kwargs): @@ -316,7 +315,7 @@ class IPAddressForm(BootstrapMixin, ReturnURLForm, CustomFieldForm): 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'} + attrs={'filter-for': 'interface_device', 'nullable': 'true'} ) ) interface_device = forms.ModelChoiceField( @@ -429,65 +428,6 @@ class IPAddressBulkAddForm(BootstrapMixin, CustomFieldForm): fields = ['address_pattern', 'vrf', 'tenant', 'status', 'description'] -class IPAddressAssignForm(BootstrapMixin, forms.Form): - site = forms.ModelChoiceField( - queryset=Site.objects.all(), - label='Site', - required=False, - widget=forms.Select( - attrs={'filter-for': 'rack'} - ) - ) - rack = forms.ModelChoiceField( - queryset=Rack.objects.all(), - label='Rack', - required=False, - widget=APISelect( - api_url='/api/dcim/racks/?site_id={{site}}', - display_field='display_name', - attrs={'filter-for': 'device', 'nullable': 'true'} - ) - ) - device = forms.ModelChoiceField( - queryset=Device.objects.all(), - label='Device', - required=False, - widget=APISelect( - api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}', - display_field='display_name', - attrs={'filter-for': 'interface'} - ) - ) - livesearch = forms.CharField( - required=False, - label='Device', - widget=Livesearch( - query_key='q', - query_url='dcim-api:device-list', - field_to_update='device' - ) - ) - interface = forms.ModelChoiceField( - queryset=Interface.objects.all(), - label='Interface', - widget=APISelect( - api_url='/api/dcim/interfaces/?device_id={{device}}' - ) - ) - set_as_primary = forms.BooleanField( - label='Set as primary IP for device', - required=False - ) - - def __init__(self, *args, **kwargs): - - super(IPAddressAssignForm, self).__init__(*args, **kwargs) - - self.fields['rack'].choices = [] - self.fields['device'].choices = [] - self.fields['interface'].choices = [] - - class IPAddressFromCSVForm(forms.ModelForm): vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd', error_messages={'invalid_choice': 'VRF not found.'}) diff --git a/netbox/project-static/js/forms.js b/netbox/project-static/js/forms.js index c2a32f4db..5959335db 100644 --- a/netbox/project-static/js/forms.js +++ b/netbox/project-static/js/forms.js @@ -88,20 +88,21 @@ $(document).ready(function() { // Determine the filter fields needed to make an API call var filter_regex = /\{\{([a-z_]+)\}\}/g; var match; + var rendered_url = api_url; 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'); + rendered_url = rendered_url.replace(match[0], filter_field.val()); + } else if (filter_field.attr('nullable') == 'true') { + rendered_url = rendered_url.replace(match[0], '0'); } } // If all URL variables have been replaced, make the API call - if (api_url.search('{{') < 0) { - console.log(child_name + ": Fetching " + api_url); + if (rendered_url.search('{{') < 0) { + console.log(child_name + ": Fetching " + rendered_url); $.ajax({ - url: api_url, + url: rendered_url, dataType: 'json', success: function(response, status) { $.each(response.results, function(index, choice) { diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 0b328ecb5..ef36680eb 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -329,13 +329,21 @@ class BulkAddView(View): new_objs = [] try: with transaction.atomic(): + # Validate and save each object individually for value in pattern: model_form_data[pattern_target] = value model_form = self.model_form(model_form_data) - obj = model_form.save() - new_objs.append(obj) - except ValidationError as e: - form.add_error(None, e) + if model_form.is_valid(): + obj = model_form.save() + new_objs.append(obj) + else: + for error in model_form.errors.as_data().values(): + form.add_error(None, error) + # Abort the creation of all objects if errors exist + if form.errors: + raise ValidationError("Validation of one or more model forms failed.") + except ValidationError: + pass if not form.errors: msg = u"Added {} {}".format(len(new_objs), model._meta.verbose_name_plural)