diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index 2b5aaee7e..e51f50812 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -829,6 +829,70 @@ class RackReservationForm(BootstrapMixin, TenancyForm, forms.ModelForm): return unit_choices +class RackReservationCSVForm(forms.ModelForm): + site = forms.ModelChoiceField( + queryset=Site.objects.all(), + to_field_name='name', + help_text='Name of parent site', + error_messages={ + 'invalid_choice': 'Invalid site name.', + } + ) + rack_group = forms.ModelChoiceField( + queryset=RackGroup.objects.all(), + to_field_name='name', + required=False, + help_text='Name of rack group', + error_messages={ + 'invalid_choice': 'Invalid rack group name.', + } + ) + rack = forms.CharField( + required=True, + help_text='Name of parent rack' + ) + units = forms.CharField( + required=True, + help_text='Rack units' + ) + tenant = forms.ModelChoiceField( + queryset=Tenant.objects.all(), + required=False, + to_field_name='name', + help_text='Name of assigned tenant', + error_messages={ + 'invalid_choice': 'Tenant not found.', + } + ) + + class Meta: + model = RackReservation + # fields = RackReservation.csv_headers + fields = ['site', 'rack_group', 'rack', 'units', 'tenant', 'description'] # Can't set user + help_texts = { + } + + def clean(self): + + super().clean() + + site = self.cleaned_data.get('site') + rack_group = self.cleaned_data.get('rack_group') + rack_name = self.cleaned_data.get('rack') + + # Validate rack + if site and rack_group and rack_name: + try: + self.instance.rack = Rack.objects.get(site=site, group__name=rack_group, name=rack_name) + except Rack.DoesNotExist: + raise forms.ValidationError("Rack {} not found in site {} group {}".format(rack_name, site, rack_group)) + elif site and rack_name: + try: + self.instance.rack = Rack.objects.get(site=site, group__isnull=True, name=rack_name) + except Rack.DoesNotExist: + raise forms.ValidationError("Rack {} not found in site {} (no group)".format(rack_name, site)) + + class RackReservationBulkEditForm(BootstrapMixin, BulkEditForm): pk = forms.ModelMultipleChoiceField( queryset=RackReservation.objects.all(), diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index 8b84c79d8..af785f0d2 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -761,6 +761,8 @@ class RackReservation(ChangeLoggedModel): max_length=100 ) + csv_headers = ['site', 'rack_group', 'rack', 'units', 'tenant', 'user', 'description'] + class Meta: ordering = ['created'] @@ -793,6 +795,17 @@ class RackReservation(ChangeLoggedModel): ) }) + def to_csv(self): + return ( + self.rack.site.name, + self.rack.group if self.rack.group else None, + self.rack.name, + ','.join([str(u) for u in self.units]), + self.tenant.name if self.tenant else None, + self.user.username, + self.description + ) + @property def unit_list(self): """ diff --git a/netbox/dcim/urls.py b/netbox/dcim/urls.py index 130a79199..456905691 100644 --- a/netbox/dcim/urls.py +++ b/netbox/dcim/urls.py @@ -51,6 +51,7 @@ urlpatterns = [ # Rack reservations path('rack-reservations/', views.RackReservationListView.as_view(), name='rackreservation_list'), + path('rack-reservations/import/', views.RackReservationImportView.as_view(), name='rackreservation_import'), path('rack-reservations/edit/', views.RackReservationBulkEditView.as_view(), name='rackreservation_bulk_edit'), path('rack-reservations/delete/', views.RackReservationBulkDeleteView.as_view(), name='rackreservation_bulk_delete'), path('rack-reservations//edit/', views.RackReservationEditView.as_view(), name='rackreservation_edit'), diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 0a6888884..36838acd5 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -470,7 +470,7 @@ class RackReservationListView(PermissionRequiredMixin, ObjectListView): filterset = filters.RackReservationFilterSet filterset_form = forms.RackReservationFilterForm table = tables.RackReservationTable - action_buttons = () + action_buttons = ('export',) class RackReservationCreateView(PermissionRequiredMixin, ObjectEditView): @@ -500,6 +500,13 @@ class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView): return obj.rack.get_absolute_url() +class RackReservationImportView(PermissionRequiredMixin, BulkImportView): + permission_required = 'dcim.add_rackreservation' + model_form = forms.RackReservationCSVForm + table = tables.RackReservationTable + default_return_url = 'dcim:rackreservation_list' + + class RackReservationBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_rackreservation' queryset = RackReservation.objects.prefetch_related('rack', 'user')