From 2520d9f4009944d070118db5642d5b9e1a708090 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 5 Jun 2017 15:53:03 -0400 Subject: [PATCH] Converted console/power import views to new scheme --- netbox/dcim/forms.py | 229 +++++++++--------- netbox/dcim/views.py | 12 +- .../dcim/console_connections_import.html | 45 ---- .../dcim/interface_connections_import.html | 45 ---- .../dcim/power_connections_import.html | 45 ---- netbox/utilities/forms.py | 25 -- netbox/utilities/views.py | 55 ----- 7 files changed, 125 insertions(+), 331 deletions(-) delete mode 100644 netbox/templates/dcim/console_connections_import.html delete mode 100644 netbox/templates/dcim/interface_connections_import.html delete mode 100644 netbox/templates/dcim/power_connections_import.html diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index ce6fed468..bff89a3b5 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -14,8 +14,8 @@ from tenancy.forms import TenancyForm from tenancy.models import Tenant from utilities.forms import ( APISelect, add_blank_choice, ArrayFieldSelectMultiple, BootstrapMixin, BulkEditForm, BulkEditNullBooleanSelect, - BulkImportForm, ChainedFieldsMixin, ChainedModelChoiceField, CommentField, CSVDataField, ExpandableNameField, - FilterChoiceField, FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField, + ChainedFieldsMixin, ChainedModelChoiceField, CommentField, ExpandableNameField, FilterChoiceField, + FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField, FilterTreeNodeMultipleChoiceField, ) from .formfields import MACAddressFormField @@ -945,75 +945,81 @@ class ConsolePortCreateForm(DeviceComponentForm): name_pattern = ExpandableNameField(label='Name') -class ConsoleConnectionCSVForm(forms.Form): +class ConsoleConnectionCSVForm(forms.ModelForm): console_server = FlexibleModelChoiceField( queryset=Device.objects.filter(device_type__is_console_server=True), to_field_name='name', + help_text='Console server name or PK', error_messages={ 'invalid_choice': 'Console server not found', } ) - cs_port = forms.CharField() - device = FlexibleModelChoiceField(queryset=Device.objects.all(), to_field_name='name', - error_messages={'invalid_choice': 'Device not found'}) - console_port = forms.CharField() + cs_port = forms.CharField( + help_text='Console server port name' + ) + device = FlexibleModelChoiceField( + queryset=Device.objects.all(), + to_field_name='name', + help_text='Device name or PK', + error_messages={ + 'invalid_choice': 'Device not found', + } + ) + console_port = forms.CharField( + help_text='Console port name' + ) status = forms.CharField(validators=[validate_connection_status]) - def clean(self): + class Meta: + model = ConsolePort + fields = ['console_server', 'cs_port', 'device', 'console_port'] - # Validate console server port - if self.cleaned_data.get('console_server'): - try: - cs_port = ConsoleServerPort.objects.get(device=self.cleaned_data['console_server'], - name=self.cleaned_data['cs_port']) - if ConsolePort.objects.filter(cs_port=cs_port): - raise forms.ValidationError("Console server port is already occupied (by {} {})" - .format(cs_port.connected_console.device, cs_port.connected_console)) - except ConsoleServerPort.DoesNotExist: - raise forms.ValidationError("Invalid console server port ({} {})" - .format(self.cleaned_data['console_server'], self.cleaned_data['cs_port'])) + def clean_console_port(self): - # Validate console port - if self.cleaned_data.get('device'): - try: - console_port = ConsolePort.objects.get(device=self.cleaned_data['device'], - name=self.cleaned_data['console_port']) - if console_port.cs_port: - raise forms.ValidationError("Console port is already connected (to {} {})" - .format(console_port.cs_port.device, console_port.cs_port)) - except ConsolePort.DoesNotExist: - raise forms.ValidationError("Invalid console port ({} {})" - .format(self.cleaned_data['device'], self.cleaned_data['console_port'])) + console_port_name = self.cleaned_data.get('console_port') + if not self.cleaned_data.get('device') or not console_port_name: + return None + try: + # Retrieve console port by name + consoleport = ConsolePort.objects.get( + device=self.cleaned_data['device'], name=console_port_name + ) + # Check if the console port is already connected + if consoleport.cs_port is not None: + raise forms.ValidationError("{} {} is already connected".format( + self.cleaned_data['device'], console_port_name + )) + except ConsolePort.DoesNotExist: + raise forms.ValidationError("Invalid console port ({} {})".format( + self.cleaned_data['device'], console_port_name + )) -class ConsoleConnectionImportForm(BootstrapMixin, BulkImportForm): - csv = CSVDataField(csv_form=ConsoleConnectionCSVForm) + self.instance = consoleport + return consoleport - def clean(self): - records = self.cleaned_data.get('csv') - if not records: - return + def clean_cs_port(self): - connection_list = [] + cs_port_name = self.cleaned_data.get('cs_port') + if not self.cleaned_data.get('console_server') or not cs_port_name: + return None - for i, record in enumerate(records, start=1): - form = self.fields['csv'].csv_form(data=record) - if form.is_valid(): - console_port = ConsolePort.objects.get(device=form.cleaned_data['device'], - name=form.cleaned_data['console_port']) - console_port.cs_port = ConsoleServerPort.objects.get(device=form.cleaned_data['console_server'], - name=form.cleaned_data['cs_port']) - if form.cleaned_data['status'] == 'planned': - console_port.connection_status = CONNECTION_STATUS_PLANNED - else: - console_port.connection_status = CONNECTION_STATUS_CONNECTED - connection_list.append(console_port) - else: - for field, errors in form.errors.items(): - for e in errors: - self.add_error('csv', "Record {} {}: {}".format(i, field, e)) + try: + # Retrieve console server port by name + cs_port = ConsoleServerPort.objects.get( + device=self.cleaned_data['console_server'], name=cs_port_name + ) + # Check if the console server port is already connected + if ConsolePort.objects.filter(cs_port=cs_port).count(): + raise forms.ValidationError("{} {} is already connected".format( + self.cleaned_data['console_server'], cs_port_name + )) + except ConsoleServerPort.DoesNotExist: + raise forms.ValidationError("Invalid console server port ({} {})".format( + self.cleaned_data['console_server'], cs_port_name + )) - self.cleaned_data['csv'] = connection_list + return cs_port class ConsolePortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm): @@ -1193,76 +1199,81 @@ class PowerPortCreateForm(DeviceComponentForm): name_pattern = ExpandableNameField(label='Name') -class PowerConnectionCSVForm(forms.Form): +class PowerConnectionCSVForm(forms.ModelForm): pdu = FlexibleModelChoiceField( queryset=Device.objects.filter(device_type__is_pdu=True), to_field_name='name', + help_text='PDU name or PK', error_messages={ 'invalid_choice': 'PDU not found.', } ) - power_outlet = forms.CharField() - device = FlexibleModelChoiceField(queryset=Device.objects.all(), to_field_name='name', - error_messages={'invalid_choice': 'Device not found'}) - power_port = forms.CharField() + power_outlet = forms.CharField( + help_text='Power outlet name' + ) + device = FlexibleModelChoiceField( + queryset=Device.objects.all(), + to_field_name='name', + help_text='Device name or PK', + error_messages={ + 'invalid_choice': 'Device not found', + } + ) + power_port = forms.CharField( + help_text='Power port name' + ) status = forms.CharField(validators=[validate_connection_status]) - def clean(self): + class Meta: + model = PowerPort + fields = ['pdu', 'power_outlet', 'device', 'power_port'] - # Validate power outlet - if self.cleaned_data.get('pdu'): - try: - power_outlet = PowerOutlet.objects.get(device=self.cleaned_data['pdu'], - name=self.cleaned_data['power_outlet']) - if PowerPort.objects.filter(power_outlet=power_outlet): - raise forms.ValidationError("Power outlet is already occupied (by {} {})" - .format(power_outlet.connected_port.device, - power_outlet.connected_port)) - except PowerOutlet.DoesNotExist: - raise forms.ValidationError("Invalid PDU port ({} {})" - .format(self.cleaned_data['pdu'], self.cleaned_data['power_outlet'])) + def clean_power_port(self): - # Validate power port - if self.cleaned_data.get('device'): - try: - power_port = PowerPort.objects.get(device=self.cleaned_data['device'], - name=self.cleaned_data['power_port']) - if power_port.power_outlet: - raise forms.ValidationError("Power port is already connected (to {} {})" - .format(power_port.power_outlet.device, power_port.power_outlet)) - except PowerPort.DoesNotExist: - raise forms.ValidationError("Invalid power port ({} {})" - .format(self.cleaned_data['device'], self.cleaned_data['power_port'])) + power_port_name = self.cleaned_data.get('power_port') + if not self.cleaned_data.get('device') or not power_port_name: + return None + try: + # Retrieve power port by name + powerport = PowerPort.objects.get( + device=self.cleaned_data['device'], name=power_port_name + ) + # Check if the power port is already connected + if powerport.power_outlet is not None: + raise forms.ValidationError("{} {} is already connected".format( + self.cleaned_data['device'], power_port_name + )) + except PowerPort.DoesNotExist: + raise forms.ValidationError("Invalid power port ({} {})".format( + self.cleaned_data['device'], power_port_name + )) -class PowerConnectionImportForm(BootstrapMixin, BulkImportForm): - csv = CSVDataField(csv_form=PowerConnectionCSVForm) + self.instance = powerport + return powerport - def clean(self): - records = self.cleaned_data.get('csv') - if not records: - return + def clean_power_outlet(self): - connection_list = [] + power_outlet_name = self.cleaned_data.get('power_outlet') + if not self.cleaned_data.get('pdu') or not power_outlet_name: + return None - for i, record in enumerate(records, start=1): - form = self.fields['csv'].csv_form(data=record) - if form.is_valid(): - power_port = PowerPort.objects.get(device=form.cleaned_data['device'], - name=form.cleaned_data['power_port']) - power_port.power_outlet = PowerOutlet.objects.get(device=form.cleaned_data['pdu'], - name=form.cleaned_data['power_outlet']) - if form.cleaned_data['status'] == 'planned': - power_port.connection_status = CONNECTION_STATUS_PLANNED - else: - power_port.connection_status = CONNECTION_STATUS_CONNECTED - connection_list.append(power_port) - else: - for field, errors in form.errors.items(): - for e in errors: - self.add_error('csv', "Record {} {}: {}".format(i, field, e)) + try: + # Retrieve power outlet by name + power_outlet = PowerOutlet.objects.get( + device=self.cleaned_data['pdu'], name=power_outlet_name + ) + # Check if the power outlet is already connected + if PowerPort.objects.filter(power_outlet=power_outlet).count(): + raise forms.ValidationError("{} {} is already connected".format( + self.cleaned_data['pdu'], power_outlet_name + )) + except PowerOutlet.DoesNotExist: + raise forms.ValidationError("Invalid power outlet ({} {})".format( + self.cleaned_data['pdu'], power_outlet_name + )) - self.cleaned_data['csv'] = connection_list + return power_outlet class PowerPortConnectionForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm): @@ -1629,7 +1640,7 @@ class InterfaceConnectionCSVForm(forms.ModelForm): try: # Retrieve interface by name interface = Interface.objects.get( - device=self.cleaned_data['device_a'], name=self.cleaned_data['interface_a'] + device=self.cleaned_data['device_a'], name=interface_name ) # Check for an existing connection to this interface if InterfaceConnection.objects.filter(Q(interface_a=interface) | Q(interface_b=interface)).count(): @@ -1652,7 +1663,7 @@ class InterfaceConnectionCSVForm(forms.ModelForm): try: # Retrieve interface by name interface = Interface.objects.get( - device=self.cleaned_data['device_b'], name=self.cleaned_data['interface_b'] + device=self.cleaned_data['device_b'], name=interface_name ) # Check for an existing connection to this interface if InterfaceConnection.objects.filter(Q(interface_a=interface) | Q(interface_b=interface)).count(): diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 58ea57580..0aade9cb3 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -23,7 +23,7 @@ from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_S from utilities.forms import ConfirmationForm from utilities.paginator import EnhancedPaginator from utilities.views import ( - BulkDeleteView, BulkEditView, BulkImportView, BulkImportView2, ObjectDeleteView, ObjectEditView, ObjectListView, + BulkDeleteView, BulkEditView, BulkImportView2, ObjectDeleteView, ObjectEditView, ObjectListView, ) from . import filters, forms, tables from .models import ( @@ -1012,11 +1012,10 @@ class ConsolePortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): parent_cls = Device -class ConsoleConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView): +class ConsoleConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView2): permission_required = 'dcim.change_consoleport' - form = forms.ConsoleConnectionImportForm + model_form = forms.ConsoleConnectionCSVForm table = tables.ConsoleConnectionTable - template_name = 'dcim/console_connections_import.html' default_return_url = 'dcim:console_connections_list' @@ -1235,11 +1234,10 @@ class PowerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): parent_cls = Device -class PowerConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView): +class PowerConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView2): permission_required = 'dcim.change_powerport' - form = forms.PowerConnectionImportForm + model_form = forms.PowerConnectionCSVForm table = tables.PowerConnectionTable - template_name = 'dcim/power_connections_import.html' default_return_url = 'dcim:power_connections_list' diff --git a/netbox/templates/dcim/console_connections_import.html b/netbox/templates/dcim/console_connections_import.html deleted file mode 100644 index 3dc0f96a4..000000000 --- a/netbox/templates/dcim/console_connections_import.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends 'utilities/obj_import.html' %} - -{% block title %}Console Connections Import{% endblock %} - -{% block instructions %} -

CSV Format

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescriptionExample
Console serverDevice name or {ID}abc1-cs3
Console server portFull CS port namePort 35
DeviceDevice name or {ID}abc1-switch7
Console PortConsole port nameConsole
Connection Status"planned" or "connected"planned
-

Example

-
abc1-cs3,Port 35,abc1-switch7,Console,planned
-{% endblock %} diff --git a/netbox/templates/dcim/interface_connections_import.html b/netbox/templates/dcim/interface_connections_import.html deleted file mode 100644 index 4cfd82e58..000000000 --- a/netbox/templates/dcim/interface_connections_import.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends 'utilities/obj_import.html' %} - -{% block title %}Interface Connections Import{% endblock %} - -{% block instructions %} -

CSV Format

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescriptionExample
Device ADevice name or {ID}abc1-core1
Interface AInterface namexe-0/0/6
Device BDevice name or {ID}abc1-switch7
Interface BInterface namexe-0/0/0
Connection Status"planned" or "connected"planned
-

Example

-
abc1-core1,xe-0/0/6,abc1-switch7,xe-0/0/0,planned
-{% endblock %} diff --git a/netbox/templates/dcim/power_connections_import.html b/netbox/templates/dcim/power_connections_import.html deleted file mode 100644 index bd60834da..000000000 --- a/netbox/templates/dcim/power_connections_import.html +++ /dev/null @@ -1,45 +0,0 @@ -{% extends 'utilities/obj_import.html' %} - -{% block title %}Power Connections Import{% endblock %} - -{% block instructions %} -

CSV Format

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FieldDescriptionExample
PDUDevice name or {ID}abc1-pdu1
Power OutletPower outlet nameAC4
DeviceDevice name or {ID}abc1-switch7
Power PortPower port namePSU0
Connection Status"planned" or "connected"connected
-

Example

-
abc1-pdu1,AC4,abc1-switch7,PSU0,connected
-{% endblock %} diff --git a/netbox/utilities/forms.py b/netbox/utilities/forms.py index 6c07aad7f..56b7c24b7 100644 --- a/netbox/utilities/forms.py +++ b/netbox/utilities/forms.py @@ -539,28 +539,3 @@ class BulkEditForm(forms.Form): self.nullable_fields = [field for field in self.Meta.nullable_fields] else: self.nullable_fields = [] - - -class BulkImportForm(forms.Form): - - def clean(self): - fields, records = self.cleaned_data.get('csv').split('\n', 1) - if not records: - return - - obj_list = [] - - for i, record in enumerate(records, start=1): - obj_form = self.fields['csv'].csv_form(data=record) - if obj_form.is_valid(): - obj = obj_form.save(commit=False) - obj_list.append(obj) - else: - for field, errors in obj_form.errors.items(): - for e in errors: - if field == '__all__': - self.add_error('csv', "Record {}: {}".format(i, e)) - else: - self.add_error('csv', "Record {} ({}): {}".format(i, field, e)) - - self.cleaned_data['csv'] = obj_list diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 766b0676d..529244c48 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -369,61 +369,6 @@ class BulkAddView(View): }) -class BulkImportView(View): - """ - Import objects in bulk (CSV format). - - form: Form class - table: The django-tables2 Table used to render the list of imported objects - template_name: The name of the template - default_return_url: The name of the URL to use for the cancel button - """ - form = None - table = None - template_name = None - default_return_url = None - - def get(self, request): - - return render(request, self.template_name, { - 'form': self.form(), - 'return_url': self.default_return_url, - }) - - def post(self, request): - - form = self.form(request.POST) - if form.is_valid(): - new_objs = [] - try: - with transaction.atomic(): - for obj in form.cleaned_data['csv']: - self.save_obj(obj) - new_objs.append(obj) - - obj_table = self.table(new_objs) - if new_objs: - msg = 'Imported {} {}'.format(len(new_objs), new_objs[0]._meta.verbose_name_plural) - messages.success(request, msg) - UserAction.objects.log_import(request.user, ContentType.objects.get_for_model(new_objs[0]), msg) - - return render(request, "import_success.html", { - 'table': obj_table, - 'return_url': self.default_return_url, - }) - - except IntegrityError as e: - form.add_error('csv', "Record {}: {}".format(len(new_objs) + 1, e.__cause__)) - - return render(request, self.template_name, { - 'form': form, - 'return_url': self.default_return_url, - }) - - def save_obj(self, obj): - obj.save() - - class BulkImportView2(View): """ Import objects in bulk (CSV format).