Просмотр исходного кода

Move get_object() to BaseObjectView

jeremystretch 4 лет назад
Родитель
Сommit
a74ed33b0e

+ 0 - 1
docs/plugins/development/generic-views.md

@@ -71,7 +71,6 @@ Below is the class definition for NetBox's BaseMultiObjectView. The attributes a
     selection:
       members:
         - get_table
-        - export_yaml
         - export_table
         - export_template
     rendering:

+ 1 - 0
mkdocs.yml

@@ -28,6 +28,7 @@ plugins:
             - django.setup()
           rendering:
             heading_level: 3
+            members_order: source
             show_root_heading: true
             show_root_full_path: false
             show_root_toc_entry: false

+ 12 - 0
netbox/netbox/views/generic/base.py

@@ -1,3 +1,4 @@
+from django.shortcuts import get_object_or_404
 from django.views.generic import View
 
 from utilities.views import ObjectPermissionRequiredMixin
@@ -14,6 +15,15 @@ class BaseObjectView(ObjectPermissionRequiredMixin, View):
     queryset = None
     template_name = None
 
+    def get_object(self, **kwargs):
+        """
+        Return the object being viewed or modified. The object is identified by an arbitrary set of keyword arguments
+        gleaned from the URL, which are passed to `get_object_or_404()`. (Typically, only a primary key is needed.)
+
+        If no matching object is found, return a 404 response.
+        """
+        return get_object_or_404(self.queryset, **kwargs)
+
     def get_extra_context(self, request, instance):
         """
         Return any additional context data to include when rendering the template.
@@ -31,9 +41,11 @@ class BaseMultiObjectView(ObjectPermissionRequiredMixin, View):
 
     Attributes:
         queryset: Django QuerySet from which the object(s) will be fetched
+        table: The django-tables2 Table class used to render the objects list
         template_name: The name of the HTML template file to render
     """
     queryset = None
+    table = None
     template_name = None
 
     def get_extra_context(self, request):

+ 71 - 57
netbox/netbox/views/generic/bulk_views.py

@@ -43,13 +43,11 @@ class ObjectListView(BaseMultiObjectView):
     Attributes:
         filterset: A django-filter FilterSet that is applied to the queryset
         filterset_form: The form class used to render filter options
-        table: The django-tables2 Table used to render the objects list
         action_buttons: A list of buttons to include at the top of the page
     """
     template_name = 'generic/object_list.html'
     filterset = None
     filterset_form = None
-    table = None
     action_buttons = ('add', 'import', 'export')
 
     def get_required_permission(self):
@@ -70,6 +68,61 @@ class ObjectListView(BaseMultiObjectView):
 
         return table
 
+    #
+    # Export methods
+    #
+
+    def export_yaml(self):
+        """
+        Export the queryset of objects as concatenated YAML documents.
+        """
+        yaml_data = [obj.to_yaml() for obj in self.queryset]
+
+        return '---\n'.join(yaml_data)
+
+    def export_table(self, table, columns=None, filename=None):
+        """
+        Export all table data in CSV format.
+
+        Args:
+            table: The Table instance to export
+            columns: A list of specific columns to include. If None, all columns will be exported.
+            filename: The name of the file attachment sent to the client. If None, will be determined automatically
+                from the queryset model name.
+        """
+        exclude_columns = {'pk', 'actions'}
+        if columns:
+            all_columns = [col_name for col_name, _ in table.selected_columns + table.available_columns]
+            exclude_columns.update({
+                col for col in all_columns if col not in columns
+            })
+        exporter = TableExport(
+            export_format=TableExport.CSV,
+            table=table,
+            exclude_columns=exclude_columns
+        )
+        return exporter.response(
+            filename=filename or f'netbox_{self.queryset.model._meta.verbose_name_plural}.csv'
+        )
+
+    def export_template(self, template, request):
+        """
+        Render an ExportTemplate using the current queryset.
+
+        Args:
+            template: ExportTemplate instance
+            request: The current request
+        """
+        try:
+            return template.render_to_response(self.queryset)
+        except Exception as e:
+            messages.error(request, f"There was an error rendering the selected export template ({template.name}): {e}")
+            return redirect(request.path)
+
+    #
+    # Request handlers
+    #
+
     def get(self, request):
         """
         GET request handler.
@@ -135,57 +188,6 @@ class ObjectListView(BaseMultiObjectView):
 
         return render(request, self.template_name, context)
 
-    #
-    # Export methods
-    #
-
-    def export_yaml(self):
-        """
-        Export the queryset of objects as concatenated YAML documents.
-        """
-        yaml_data = [obj.to_yaml() for obj in self.queryset]
-
-        return '---\n'.join(yaml_data)
-
-    def export_table(self, table, columns=None, filename=None):
-        """
-        Export all table data in CSV format.
-
-        Args:
-            table: The Table instance to export
-            columns: A list of specific columns to include. If None, all columns will be exported.
-            filename: The name of the file attachment sent to the client. If None, will be determined automatically
-                from the queryset model name.
-        """
-        exclude_columns = {'pk', 'actions'}
-        if columns:
-            all_columns = [col_name for col_name, _ in table.selected_columns + table.available_columns]
-            exclude_columns.update({
-                col for col in all_columns if col not in columns
-            })
-        exporter = TableExport(
-            export_format=TableExport.CSV,
-            table=table,
-            exclude_columns=exclude_columns
-        )
-        return exporter.response(
-            filename=filename or f'netbox_{self.queryset.model._meta.verbose_name_plural}.csv'
-        )
-
-    def export_template(self, template, request):
-        """
-        Render an ExportTemplate using the current queryset.
-
-        Args:
-            template: ExportTemplate instance
-            request: The current request
-        """
-        try:
-            return template.render_to_response(self.queryset)
-        except Exception as e:
-            messages.error(request, f"There was an error rendering the selected export template ({template.name}): {e}")
-            return redirect(request.path)
-
 
 class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):
     """
@@ -227,6 +229,10 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):
 
         return new_objects
 
+    #
+    # Request handlers
+    #
+
     def get(self, request):
         # Set initial values for visible form fields from query args
         initial = {}
@@ -297,12 +303,10 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
 
     Attributes:
         model_form: The form used to create each imported object
-        table: The django-tables2 Table used to render the list of imported objects
         widget_attrs: A dict of attributes to apply to the import widget (e.g. to require a session key)
     """
     template_name = 'generic/object_bulk_import.html'
     model_form = None
-    table = None
     widget_attrs = {}
 
     def _import_form(self, *args, **kwargs):
@@ -361,6 +365,10 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
     def get_required_permission(self):
         return get_permission_for_model(self.queryset.model, 'add')
 
+    #
+    # Request handlers
+    #
+
     def get(self, request):
 
         return render(request, self.template_name, {
@@ -427,12 +435,10 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
 
     Attributes:
         filterset: FilterSet to apply when deleting by QuerySet
-        table: The table used to display devices being edited
         form: The form class used to edit objects in bulk
     """
     template_name = 'generic/object_bulk_edit.html'
     filterset = None
-    table = None
     form = None
 
     def get_required_permission(self):
@@ -495,6 +501,10 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
 
         return updated_objects
 
+    #
+    # Request handlers
+    #
+
     def get(self, request):
         return redirect(self.get_return_url(request))
 
@@ -692,6 +702,10 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
 
         return BulkDeleteForm
 
+    #
+    # Request handlers
+    #
+
     def get(self, request):
         return redirect(self.get_return_url(request))
 

+ 34 - 36
netbox/netbox/views/generic/object_views.py

@@ -6,7 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist
 from django.db import transaction
 from django.db.models import ProtectedError
 from django.forms.widgets import HiddenInput
-from django.shortcuts import get_object_or_404, redirect, render
+from django.shortcuts import redirect, render
 from django.urls import reverse
 from django.utils.html import escape
 from django.utils.http import is_safe_url
@@ -42,13 +42,6 @@ class ObjectView(BaseObjectView):
     def get_required_permission(self):
         return get_permission_for_model(self.queryset.model, 'view')
 
-    def get_object(self, **kwargs):
-        """
-        Return the object being viewed, identified by the keyword arguments passed. If no matching object is found,
-        raise a 404 error.
-        """
-        return get_object_or_404(self.queryset, **kwargs)
-
     def get_template_name(self):
         """
         Return self.template_name if defined. Otherwise, dynamically resolve the template name using the queryset
@@ -59,6 +52,10 @@ class ObjectView(BaseObjectView):
         model_opts = self.queryset.model._meta
         return f'{model_opts.app_label}/{model_opts.model_name}.html'
 
+    #
+    # Request handlers
+    #
+
     def get(self, request, **kwargs):
         """
         GET request handler. `*args` and `**kwargs` are passed to identify the object being queried.
@@ -105,6 +102,10 @@ class ObjectChildrenView(ObjectView):
         """
         return queryset
 
+    #
+    # Request handlers
+    #
+
     def get(self, request, *args, **kwargs):
         """
         GET handler for rendering child objects.
@@ -202,6 +203,10 @@ class ObjectImportView(GetReturnURLMixin, BaseObjectView):
 
         return obj
 
+    #
+    # Request handlers
+    #
+
     def get(self, request):
         form = ImportForm()
 
@@ -303,21 +308,12 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView):
 
     def get_object(self, **kwargs):
         """
-        Return an instance for editing. If a PK has been specified, this will be an existing object.
-
-        Args:
-            kwargs: URL path kwargs
+        Return an object for editing. If no keyword arguments have been specified, this will be a new instance.
         """
-        if 'pk' in kwargs:
-            obj = get_object_or_404(self.queryset, **kwargs)
-
-            # Take a snapshot of change-logged models
-            if hasattr(obj, 'snapshot'):
-                obj.snapshot()
-
-            return obj
-
-        return self.queryset.model()
+        if not kwargs:
+            # We're creating a new object
+            return self.queryset.model()
+        return super().get_object(**kwargs)
 
     def alter_object(self, obj, request, url_args, url_kwargs):
         """
@@ -332,6 +328,10 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView):
         """
         return obj
 
+    #
+    # Request handlers
+    #
+
     def get(self, request, *args, **kwargs):
         """
         GET request handler.
@@ -363,6 +363,11 @@ class ObjectEditView(GetReturnURLMixin, BaseObjectView):
         """
         logger = logging.getLogger('netbox.views.ObjectEditView')
         obj = self.get_object(**kwargs)
+
+        # Take a snapshot for change logging (if editing an existing object)
+        if obj.pk and hasattr(obj, 'snapshot'):
+            obj.snapshot()
+
         obj = self.alter_object(obj, request, args, kwargs)
 
         form = self.model_form(
@@ -438,20 +443,9 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
     def get_required_permission(self):
         return get_permission_for_model(self.queryset.model, 'delete')
 
-    def get_object(self, **kwargs):
-        """
-        Return an instance for deletion. If a PK has been specified, this will be an existing object.
-
-        Args:
-            kwargs: URL path kwargs
-        """
-        obj = get_object_or_404(self.queryset, **kwargs)
-
-        # Take a snapshot of change-logged models
-        if hasattr(obj, 'snapshot'):
-            obj.snapshot()
-
-        return obj
+    #
+    # Request handlers
+    #
 
     def get(self, request, *args, **kwargs):
         """
@@ -494,6 +488,10 @@ class ObjectDeleteView(GetReturnURLMixin, BaseObjectView):
         obj = self.get_object(**kwargs)
         form = ConfirmationForm(request.POST)
 
+        # Take a snapshot of change-logged models
+        if hasattr(obj, 'snapshot'):
+            obj.snapshot()
+
         if form.is_valid():
             logger.debug("Form validation was successful")