Browse Source

Rewrote all DeviceType component template deletion views to utilize BulkDeleteView()

Jeremy Stretch 8 years ago
parent
commit
d47bf4ab6b

+ 8 - 0
netbox/dcim/forms.py

@@ -251,6 +251,10 @@ class ConsolePortTemplateForm(forms.ModelForm, BootstrapMixin):
         fields = ['name_pattern']
         fields = ['name_pattern']
 
 
 
 
+class ConsolePortTemplateBulkDeleteForm(ConfirmationForm):
+    pk = forms.ModelMultipleChoiceField(queryset=ConsolePortTemplate.objects.all(), widget=forms.MultipleHiddenInput)
+
+
 class ConsoleServerPortTemplateForm(forms.ModelForm, BootstrapMixin):
 class ConsoleServerPortTemplateForm(forms.ModelForm, BootstrapMixin):
     name_pattern = ExpandableNameField(label='Name')
     name_pattern = ExpandableNameField(label='Name')
 
 
@@ -259,6 +263,10 @@ class ConsoleServerPortTemplateForm(forms.ModelForm, BootstrapMixin):
         fields = ['name_pattern']
         fields = ['name_pattern']
 
 
 
 
+class ConsoleServerPortTemplateBulkDeleteForm(ConfirmationForm):
+    pk = forms.ModelMultipleChoiceField(queryset=ConsoleServerPortTemplate.objects.all(), widget=forms.MultipleHiddenInput)
+
+
 class PowerPortTemplateForm(forms.ModelForm, BootstrapMixin):
 class PowerPortTemplateForm(forms.ModelForm, BootstrapMixin):
     name_pattern = ExpandableNameField(label='Name')
     name_pattern = ExpandableNameField(label='Name')
 
 

+ 23 - 25
netbox/dcim/urls.py

@@ -50,31 +50,29 @@ urlpatterns = [
     url(r'^device-types/(?P<pk>\d+)/edit/$', views.DeviceTypeEditView.as_view(), name='devicetype_edit'),
     url(r'^device-types/(?P<pk>\d+)/edit/$', views.DeviceTypeEditView.as_view(), name='devicetype_edit'),
     url(r'^device-types/(?P<pk>\d+)/delete/$', views.DeviceTypeDeleteView.as_view(), name='devicetype_delete'),
     url(r'^device-types/(?P<pk>\d+)/delete/$', views.DeviceTypeDeleteView.as_view(), name='devicetype_delete'),
 
 
-    # Component templates
+    # Console port templates
-    url(r'^device-types/(?P<pk>\d+)/console-ports/add/$', views.ConsolePortTemplateAddView.as_view(),
+    url(r'^device-types/(?P<pk>\d+)/console-ports/add/$', views.ConsolePortTemplateAddView.as_view(), name='devicetype_add_consoleport'),
-        name='devicetype_add_consoleport'),
+    url(r'^device-types/(?P<pk>\d+)/console-ports/delete/$', views.ConsolePortTemplateBulkDeleteView.as_view(), name='devicetype_delete_consoleport'),
-    url(r'^device-types/(?P<pk>\d+)/console-ports/delete/$', views.component_template_delete,
+
-        {'model': ConsolePortTemplate}, name='devicetype_delete_consoleport'),
+    # Console server port templates
-    url(r'^device-types/(?P<pk>\d+)/console-server-ports/add/$', views.ConsoleServerPortTemplateAddView.as_view(),
+    url(r'^device-types/(?P<pk>\d+)/console-server-ports/add/$', views.ConsoleServerPortTemplateAddView.as_view(), name='devicetype_add_consoleserverport'),
-        name='devicetype_add_consoleserverport'),
+    url(r'^device-types/(?P<pk>\d+)/console-server-ports/delete/$', views.ConsoleServerPortTemplateBulkDeleteView.as_view(), name='devicetype_delete_consoleserverport'),
-    url(r'^device-types/(?P<pk>\d+)/console-server-ports/delete/$', views.component_template_delete,
+
-        {'model': ConsoleServerPortTemplate}, name='devicetype_delete_consoleserverport'),
+    # Power port templates
-    url(r'^device-types/(?P<pk>\d+)/power-ports/add/$', views.PowerPortTemplateAddView.as_view(),
+    url(r'^device-types/(?P<pk>\d+)/power-ports/add/$', views.PowerPortTemplateAddView.as_view(), name='devicetype_add_powerport'),
-        name='devicetype_add_powerport'),
+    url(r'^device-types/(?P<pk>\d+)/power-ports/delete/$', views.PowerPortTemplateBulkDeleteView.as_view(), name='devicetype_delete_powerport'),
-    url(r'^device-types/(?P<pk>\d+)/power-ports/delete/$', views.component_template_delete,
+
-        {'model': PowerPortTemplate}, name='devicetype_delete_powerport'),
+    # Power outlet templates
-    url(r'^device-types/(?P<pk>\d+)/power-outlets/add/$', views.PowerOutletTemplateAddView.as_view(),
+    url(r'^device-types/(?P<pk>\d+)/power-outlets/add/$', views.PowerOutletTemplateAddView.as_view(), name='devicetype_add_poweroutlet'),
-        name='devicetype_add_poweroutlet'),
+    url(r'^device-types/(?P<pk>\d+)/power-outlets/delete/$', views.PowerOutletTemplateBulkDeleteView.as_view(), name='devicetype_delete_poweroutlet'),
-    url(r'^device-types/(?P<pk>\d+)/power-outlets/delete/$', views.component_template_delete,
+
-        {'model': PowerOutletTemplate}, name='devicetype_delete_poweroutlet'),
+    # Interface templates
-    url(r'^device-types/(?P<pk>\d+)/interfaces/add/$', views.InterfaceTemplateAddView.as_view(),
+    url(r'^device-types/(?P<pk>\d+)/interfaces/add/$', views.InterfaceTemplateAddView.as_view(), name='devicetype_add_interface'),
-        name='devicetype_add_interface'),
+    url(r'^device-types/(?P<pk>\d+)/interfaces/delete/$', views.InterfaceTemplateBulkDeleteView.as_view(), name='devicetype_delete_interface'),
-    url(r'^device-types/(?P<pk>\d+)/interfaces/delete/$', views.component_template_delete,
+
-        {'model': InterfaceTemplate}, name='devicetype_delete_interface'),
+    # Device bay templates
-    url(r'^device-types/(?P<pk>\d+)/device-bays/add/$', views.DeviceBayTemplateAddView.as_view(),
+    url(r'^device-types/(?P<pk>\d+)/device-bays/add/$', views.DeviceBayTemplateAddView.as_view(), name='devicetype_add_devicebay'),
-        name='devicetype_add_devicebay'),
+    url(r'^device-types/(?P<pk>\d+)/device-bays/delete/$', views.DeviceBayTemplateBulkDeleteView.as_view(), name='devicetype_delete_devicebay'),
-    url(r'^device-types/(?P<pk>\d+)/device-bays/delete/$', views.component_template_delete,
-        {'model': DeviceBayTemplate}, name='devicetype_delete_devicebay'),
 
 
     # Device roles
     # Device roles
     url(r'^device-roles/$', views.DeviceRoleListView.as_view(), name='devicerole_list'),
     url(r'^device-roles/$', views.DeviceRoleListView.as_view(), name='devicerole_list'),

+ 35 - 40
netbox/dcim/views.py

@@ -7,8 +7,7 @@ from django.contrib.auth.decorators import permission_required
 from django.contrib.auth.mixins import PermissionRequiredMixin
 from django.contrib.auth.mixins import PermissionRequiredMixin
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
-from django.db.models import Count, ProtectedError, Sum
+from django.db.models import Count, Sum
-from django.forms import ModelMultipleChoiceField, MultipleHiddenInput
 from django.http import HttpResponseRedirect
 from django.http import HttpResponseRedirect
 from django.shortcuts import get_object_or_404, redirect, render
 from django.shortcuts import get_object_or_404, redirect, render
 from django.utils.http import urlencode
 from django.utils.http import urlencode
@@ -17,7 +16,6 @@ from django.views.generic import View
 from ipam.models import Prefix, IPAddress, VLAN
 from ipam.models import Prefix, IPAddress, VLAN
 from circuits.models import Circuit
 from circuits.models import Circuit
 from extras.models import TopologyMap
 from extras.models import TopologyMap
-from utilities.error_handlers import handle_protectederror
 from utilities.forms import ConfirmationForm
 from utilities.forms import ConfirmationForm
 from utilities.views import (
 from utilities.views import (
     BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
     BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
@@ -396,68 +394,65 @@ class ConsolePortTemplateAddView(ComponentTemplateCreateView):
     form = forms.ConsolePortTemplateForm
     form = forms.ConsolePortTemplateForm
 
 
 
 
+class ConsolePortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
+    permission_required = 'dcim.delete_consoleporttemplate'
+    cls = ConsolePortTemplate
+    parent_cls = DeviceType
+
+
 class ConsoleServerPortTemplateAddView(ComponentTemplateCreateView):
 class ConsoleServerPortTemplateAddView(ComponentTemplateCreateView):
     model = ConsoleServerPortTemplate
     model = ConsoleServerPortTemplate
     form = forms.ConsoleServerPortTemplateForm
     form = forms.ConsoleServerPortTemplateForm
 
 
 
 
+class ConsoleServerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
+    permission_required = 'dcim.delete_consoleserverporttemplate'
+    cls = ConsoleServerPortTemplate
+    parent_cls = DeviceType
+
+
 class PowerPortTemplateAddView(ComponentTemplateCreateView):
 class PowerPortTemplateAddView(ComponentTemplateCreateView):
     model = PowerPortTemplate
     model = PowerPortTemplate
     form = forms.PowerPortTemplateForm
     form = forms.PowerPortTemplateForm
 
 
 
 
+class PowerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
+    permission_required = 'dcim.delete_powerporttemplate'
+    cls = PowerPortTemplate
+    parent_cls = DeviceType
+
+
 class PowerOutletTemplateAddView(ComponentTemplateCreateView):
 class PowerOutletTemplateAddView(ComponentTemplateCreateView):
     model = PowerOutletTemplate
     model = PowerOutletTemplate
     form = forms.PowerOutletTemplateForm
     form = forms.PowerOutletTemplateForm
 
 
 
 
+class PowerOutletTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
+    permission_required = 'dcim.delete_poweroutlettemplate'
+    cls = PowerOutletTemplate
+    parent_cls = DeviceType
+
+
 class InterfaceTemplateAddView(ComponentTemplateCreateView):
 class InterfaceTemplateAddView(ComponentTemplateCreateView):
     model = InterfaceTemplate
     model = InterfaceTemplate
     form = forms.InterfaceTemplateForm
     form = forms.InterfaceTemplateForm
 
 
 
 
+class InterfaceTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
+    permission_required = 'dcim.delete_interfacetemplate'
+    cls = InterfaceTemplate
+    parent_cls = DeviceType
+
+
 class DeviceBayTemplateAddView(ComponentTemplateCreateView):
 class DeviceBayTemplateAddView(ComponentTemplateCreateView):
     model = DeviceBayTemplate
     model = DeviceBayTemplate
     form = forms.DeviceBayTemplateForm
     form = forms.DeviceBayTemplateForm
 
 
 
 
-def component_template_delete(request, pk, model):
+class DeviceBayTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
-
+    permission_required = 'dcim.delete_devicebaytemplate'
-    devicetype = get_object_or_404(DeviceType, pk=pk)
+    cls = DeviceBayTemplate
-
+    parent_cls = DeviceType
-    class ComponentTemplateBulkDeleteForm(ConfirmationForm):
-        pk = ModelMultipleChoiceField(queryset=model.objects.all(), widget=MultipleHiddenInput)
-
-    if '_confirm' in request.POST:
-        form = ComponentTemplateBulkDeleteForm(request.POST)
-        if form.is_valid():
-
-            # Delete component templates
-            objects_to_delete = model.objects.filter(pk__in=[v.id for v in form.cleaned_data['pk']])
-            try:
-                deleted_count = objects_to_delete.count()
-                objects_to_delete.delete()
-            except ProtectedError, e:
-                handle_protectederror(list(objects_to_delete), request, e)
-                return redirect('dcim:devicetype', {'pk': devicetype.pk})
-
-            messages.success(request, "Deleted {} {}".format(deleted_count, model._meta.verbose_name_plural))
-            return redirect('dcim:devicetype', pk=devicetype.pk)
-
-    else:
-        form = ComponentTemplateBulkDeleteForm(initial={'pk': request.POST.getlist('pk')})
-
-    selected_objects = model.objects.filter(pk__in=request.POST.getlist('pk'))
-    if not selected_objects:
-        messages.warning(request, "No {} were selected for deletion.".format(model._meta.verbose_name_plural))
-        return redirect('dcim:devicetype', pk=devicetype.pk)
-
-    return render(request, 'dcim/component_template_delete.html', {
-        'devicetype': devicetype,
-        'form': form,
-        'selected_objects': selected_objects,
-        'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
-    })
 
 
 
 
 #
 #

+ 6 - 2
netbox/templates/utilities/confirm_bulk_delete.html

@@ -5,11 +5,15 @@
 
 
 {% block message %}
 {% block message %}
     <p>
     <p>
-        Are you sure you want to delete these {{ obj_type_plural|default:"objects" }}?
+        Are you sure you want to delete these {{ obj_type_plural|default:"objects" }}{% if parent_obj %} from <a href="{{ parent_obj.get_absolute_url }}">{{ parent_obj }}</a>{% endif %}?
     </p>
     </p>
     <ul>
     <ul>
         {% for obj in selected_objects %}
         {% for obj in selected_objects %}
-            <li><a href="{{ obj.get_absolute_url }}">{{ obj }}</a></li>
+            {% if obj.get_absolute_url %}
+                <li><a href="{{ obj.get_absolute_url }}">{{ obj }}</a></li>
+            {% else %}
+                <li>{{ obj }}</li>
+            {% endif %}
         {% endfor %}
         {% endfor %}
     </ul>
     </ul>
 {% endblock %}
 {% endblock %}

+ 31 - 6
netbox/utilities/views.py

@@ -3,9 +3,11 @@ from django_tables2 import RequestConfig
 from django.contrib import messages
 from django.contrib import messages
 from django.contrib.admin.views.decorators import staff_member_required
 from django.contrib.admin.views.decorators import staff_member_required
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import ImproperlyConfigured
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
 from django.db import transaction, IntegrityError
 from django.db import transaction, IntegrityError
 from django.db.models import ProtectedError
 from django.db.models import ProtectedError
+from django.forms import ModelMultipleChoiceField, MultipleHiddenInput
 from django.http import HttpResponse, HttpResponseRedirect
 from django.http import HttpResponse, HttpResponseRedirect
 from django.shortcuts import get_object_or_404, redirect, render
 from django.shortcuts import get_object_or_404, redirect, render
 from django.template import TemplateSyntaxError
 from django.template import TemplateSyntaxError
@@ -309,6 +311,7 @@ class BulkEditView(View):
 
 
 class BulkDeleteView(View):
 class BulkDeleteView(View):
     cls = None
     cls = None
+    parent_cls = None
     form = None
     form = None
     template_name = 'utilities/confirm_bulk_delete.html'
     template_name = 'utilities/confirm_bulk_delete.html'
     default_redirect_url = None
     default_redirect_url = None
@@ -317,24 +320,35 @@ class BulkDeleteView(View):
     def dispatch(self, *args, **kwargs):
     def dispatch(self, *args, **kwargs):
         return super(BulkDeleteView, self).dispatch(*args, **kwargs)
         return super(BulkDeleteView, self).dispatch(*args, **kwargs)
 
 
-    def get(self, request, *args, **kwargs):
-        return redirect(self.default_redirect_url)
-
     def post(self, request, *args, **kwargs):
     def post(self, request, *args, **kwargs):
 
 
+        # Attempt to derive parent object if a parent class has been given
+        if self.parent_cls:
+            parent_obj = get_object_or_404(self.parent_cls, **kwargs)
+        else:
+            parent_obj = None
+
+        # Determine URL to redirect users upon deletion of objects
         posted_redirect_url = request.POST.get('redirect_url')
         posted_redirect_url = request.POST.get('redirect_url')
         if posted_redirect_url and is_safe_url(url=posted_redirect_url, host=request.get_host()):
         if posted_redirect_url and is_safe_url(url=posted_redirect_url, host=request.get_host()):
             redirect_url = posted_redirect_url
             redirect_url = posted_redirect_url
-        else:
+        elif parent_obj:
+            redirect_url = parent_obj.get_absolute_url()
+        elif self.default_redirect_url:
             redirect_url = reverse(self.default_redirect_url)
             redirect_url = reverse(self.default_redirect_url)
+        else:
+            raise ImproperlyConfigured('No redirect URL has been provided.')
 
 
+        # Are we deleting *all* objects in the queryset or just a selected subset?
         if request.POST.get('_all'):
         if request.POST.get('_all'):
             pk_list = [x for x in request.POST.get('pk_all').split(',') if x]
             pk_list = [x for x in request.POST.get('pk_all').split(',') if x]
         else:
         else:
             pk_list = request.POST.getlist('pk')
             pk_list = request.POST.getlist('pk')
 
 
+        form_cls = self.get_form()
+
         if '_confirm' in request.POST:
         if '_confirm' in request.POST:
-            form = self.form(request.POST)
+            form = form_cls(request.POST)
             if form.is_valid():
             if form.is_valid():
 
 
                 # Delete objects
                 # Delete objects
@@ -351,7 +365,7 @@ class BulkDeleteView(View):
                 return redirect(redirect_url)
                 return redirect(redirect_url)
 
 
         else:
         else:
-            form = self.form(initial={'pk': pk_list})
+            form = form_cls(initial={'pk': pk_list})
 
 
         selected_objects = self.cls.objects.filter(pk__in=pk_list)
         selected_objects = self.cls.objects.filter(pk__in=pk_list)
         if not selected_objects:
         if not selected_objects:
@@ -360,7 +374,18 @@ class BulkDeleteView(View):
 
 
         return render(request, self.template_name, {
         return render(request, self.template_name, {
             'form': form,
             'form': form,
+            'parent_obj': parent_obj,
             'obj_type_plural': self.cls._meta.verbose_name_plural,
             'obj_type_plural': self.cls._meta.verbose_name_plural,
             'selected_objects': selected_objects,
             'selected_objects': selected_objects,
             'cancel_url': redirect_url,
             'cancel_url': redirect_url,
         })
         })
+
+    def get_form(self):
+        """Provide a standard bulk delete form if none has been specified for the view"""
+
+        class BulkDeleteForm(ConfirmationForm):
+            pk = ModelMultipleChoiceField(queryset=self.cls.objects.all(), widget=MultipleHiddenInput)
+
+        if self.form:
+            return self.form
+        return BulkDeleteForm