Przeglądaj źródła

#8334: Move object changelog & journaling to generic views

jeremystretch 4 lat temu
rodzic
commit
d42c59792f

+ 22 - 4
docs/plugins/development/views.md

@@ -146,9 +146,9 @@ A `PluginMenuButton` has the following attributes:
 !!! note
     Any buttons associated within a menu item will be shown only if the user has permission to view the link, regardless of what permissions are set on the buttons.
 
-## Object Views Reference
+## Object Views
 
-Below is the class definition for NetBox's BaseObjectView. The attributes and methods defined here are available on all generic views which handle a single object.
+Below are the class definitions for NetBox's object views. These views handle CRUD actions for individual objects. The view, add/edit, and delete views each inherit from `BaseObjectView`, which is not intended to be used directly.
 
 ::: netbox.views.generic.base.BaseObjectView
     rendering:
@@ -177,9 +177,9 @@ Below is the class definition for NetBox's BaseObjectView. The attributes and me
     rendering:
       show_source: false
 
-## Multi-Object Views Reference
+## Multi-Object Views
 
-Below is the class definition for NetBox's BaseMultiObjectView. The attributes and methods defined here are available on all generic views which deal with multiple objects.
+Below are the class definitions for NetBox's multi-object views. These views handle simultaneous actions for sets objects. The list, import, edit, and delete views each inherit from `BaseMultiObjectView`, which is not intended to be used directly.
 
 ::: netbox.views.generic.base.BaseMultiObjectView
     rendering:
@@ -212,3 +212,21 @@ Below is the class definition for NetBox's BaseMultiObjectView. The attributes a
         - get_form
     rendering:
       show_source: false
+
+## Feature Views
+
+These views are provided to enable or enhance certain NetBox model features, such as change logging or journaling. These typically do not need to be subclassed: They can be used directly e.g. in a URL path.
+
+::: netbox.views.generic.ObjectChangeLogView
+    selection:
+      members:
+        - get_form
+    rendering:
+      show_source: false
+
+::: netbox.views.generic.ObjectJournalView
+    selection:
+      members:
+        - get_form
+    rendering:
+      show_source: false

+ 1 - 1
netbox/circuits/urls.py

@@ -1,7 +1,7 @@
 from django.urls import path
 
 from dcim.views import CableCreateView, PathTraceView
-from extras.views import ObjectChangeLogView, ObjectJournalView
+from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
 from . import views
 from .models import *
 

+ 1 - 1
netbox/dcim/urls.py

@@ -1,6 +1,6 @@
 from django.urls import path
 
-from extras.views import ObjectChangeLogView, ObjectJournalView
+from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
 from . import views
 from .models import *
 

+ 8 - 7
netbox/extras/urls.py

@@ -1,6 +1,7 @@
 from django.urls import path
 
 from extras import models, views
+from netbox.views.generic import ObjectChangeLogView
 
 
 app_name = 'extras'
@@ -15,7 +16,7 @@ urlpatterns = [
     path('custom-fields/<int:pk>/', views.CustomFieldView.as_view(), name='customfield'),
     path('custom-fields/<int:pk>/edit/', views.CustomFieldEditView.as_view(), name='customfield_edit'),
     path('custom-fields/<int:pk>/delete/', views.CustomFieldDeleteView.as_view(), name='customfield_delete'),
-    path('custom-fields/<int:pk>/changelog/', views.ObjectChangeLogView.as_view(), name='customfield_changelog',
+    path('custom-fields/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='customfield_changelog',
          kwargs={'model': models.CustomField}),
 
     # Custom links
@@ -27,7 +28,7 @@ urlpatterns = [
     path('custom-links/<int:pk>/', views.CustomLinkView.as_view(), name='customlink'),
     path('custom-links/<int:pk>/edit/', views.CustomLinkEditView.as_view(), name='customlink_edit'),
     path('custom-links/<int:pk>/delete/', views.CustomLinkDeleteView.as_view(), name='customlink_delete'),
-    path('custom-links/<int:pk>/changelog/', views.ObjectChangeLogView.as_view(), name='customlink_changelog',
+    path('custom-links/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='customlink_changelog',
          kwargs={'model': models.CustomLink}),
 
     # Export templates
@@ -39,7 +40,7 @@ urlpatterns = [
     path('export-templates/<int:pk>/', views.ExportTemplateView.as_view(), name='exporttemplate'),
     path('export-templates/<int:pk>/edit/', views.ExportTemplateEditView.as_view(), name='exporttemplate_edit'),
     path('export-templates/<int:pk>/delete/', views.ExportTemplateDeleteView.as_view(), name='exporttemplate_delete'),
-    path('export-templates/<int:pk>/changelog/', views.ObjectChangeLogView.as_view(), name='exporttemplate_changelog',
+    path('export-templates/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='exporttemplate_changelog',
          kwargs={'model': models.ExportTemplate}),
 
     # Webhooks
@@ -51,7 +52,7 @@ urlpatterns = [
     path('webhooks/<int:pk>/', views.WebhookView.as_view(), name='webhook'),
     path('webhooks/<int:pk>/edit/', views.WebhookEditView.as_view(), name='webhook_edit'),
     path('webhooks/<int:pk>/delete/', views.WebhookDeleteView.as_view(), name='webhook_delete'),
-    path('webhooks/<int:pk>/changelog/', views.ObjectChangeLogView.as_view(), name='webhook_changelog',
+    path('webhooks/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='webhook_changelog',
          kwargs={'model': models.Webhook}),
 
     # Tags
@@ -63,7 +64,7 @@ urlpatterns = [
     path('tags/<int:pk>/', views.TagView.as_view(), name='tag'),
     path('tags/<int:pk>/edit/', views.TagEditView.as_view(), name='tag_edit'),
     path('tags/<int:pk>/delete/', views.TagDeleteView.as_view(), name='tag_delete'),
-    path('tags/<int:pk>/changelog/', views.ObjectChangeLogView.as_view(), name='tag_changelog',
+    path('tags/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='tag_changelog',
          kwargs={'model': models.Tag}),
 
     # Config contexts
@@ -74,7 +75,7 @@ urlpatterns = [
     path('config-contexts/<int:pk>/', views.ConfigContextView.as_view(), name='configcontext'),
     path('config-contexts/<int:pk>/edit/', views.ConfigContextEditView.as_view(), name='configcontext_edit'),
     path('config-contexts/<int:pk>/delete/', views.ConfigContextDeleteView.as_view(), name='configcontext_delete'),
-    path('config-contexts/<int:pk>/changelog/', views.ObjectChangeLogView.as_view(), name='configcontext_changelog',
+    path('config-contexts/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='configcontext_changelog',
          kwargs={'model': models.ConfigContext}),
 
     # Image attachments
@@ -90,7 +91,7 @@ urlpatterns = [
     path('journal-entries/<int:pk>/', views.JournalEntryView.as_view(), name='journalentry'),
     path('journal-entries/<int:pk>/edit/', views.JournalEntryEditView.as_view(), name='journalentry_edit'),
     path('journal-entries/<int:pk>/delete/', views.JournalEntryDeleteView.as_view(), name='journalentry_delete'),
-    path('journal-entries/<int:pk>/changelog/', views.ObjectChangeLogView.as_view(), name='journalentry_changelog',
+    path('journal-entries/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='journalentry_changelog',
          kwargs={'model': models.JournalEntry}),
 
     # Change logging

+ 0 - 92
netbox/extras/views.py

@@ -422,49 +422,6 @@ class ObjectChangeView(generic.ObjectView):
         }
 
 
-class ObjectChangeLogView(View):
-    """
-    Present a history of changes made to a particular object.
-
-    base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
-    """
-    base_template = None
-
-    def get(self, request, model, **kwargs):
-
-        # Handle QuerySet restriction of parent object if needed
-        if hasattr(model.objects, 'restrict'):
-            obj = get_object_or_404(model.objects.restrict(request.user, 'view'), **kwargs)
-        else:
-            obj = get_object_or_404(model, **kwargs)
-
-        # Gather all changes for this object (and its related objects)
-        content_type = ContentType.objects.get_for_model(model)
-        objectchanges = ObjectChange.objects.restrict(request.user, 'view').prefetch_related(
-            'user', 'changed_object_type'
-        ).filter(
-            Q(changed_object_type=content_type, changed_object_id=obj.pk) |
-            Q(related_object_type=content_type, related_object_id=obj.pk)
-        )
-        objectchanges_table = tables.ObjectChangeTable(
-            data=objectchanges,
-            orderable=False
-        )
-        objectchanges_table.configure(request)
-
-        # Default to using "<app>/<model>.html" as the template, if it exists. Otherwise,
-        # fall back to using base.html.
-        if self.base_template is None:
-            self.base_template = f"{model._meta.app_label}/{model._meta.model_name}.html"
-
-        return render(request, 'extras/object_changelog.html', {
-            'object': obj,
-            'table': objectchanges_table,
-            'base_template': self.base_template,
-            'active_tab': 'changelog',
-        })
-
-
 #
 # Image attachments
 #
@@ -547,55 +504,6 @@ class JournalEntryBulkDeleteView(generic.BulkDeleteView):
     table = tables.JournalEntryTable
 
 
-class ObjectJournalView(View):
-    """
-    Show all journal entries for an object.
-
-    base_template: The name of the template to extend. If not provided, "<app>/<model>.html" will be used.
-    """
-    base_template = None
-
-    def get(self, request, model, **kwargs):
-
-        # Handle QuerySet restriction of parent object if needed
-        if hasattr(model.objects, 'restrict'):
-            obj = get_object_or_404(model.objects.restrict(request.user, 'view'), **kwargs)
-        else:
-            obj = get_object_or_404(model, **kwargs)
-
-        # Gather all changes for this object (and its related objects)
-        content_type = ContentType.objects.get_for_model(model)
-        journalentries = JournalEntry.objects.restrict(request.user, 'view').prefetch_related('created_by').filter(
-            assigned_object_type=content_type,
-            assigned_object_id=obj.pk
-        )
-        journalentry_table = tables.ObjectJournalTable(journalentries)
-        journalentry_table.configure(request)
-
-        if request.user.has_perm('extras.add_journalentry'):
-            form = forms.JournalEntryForm(
-                initial={
-                    'assigned_object_type': ContentType.objects.get_for_model(obj),
-                    'assigned_object_id': obj.pk
-                }
-            )
-        else:
-            form = None
-
-        # Default to using "<app>/<model>.html" as the template, if it exists. Otherwise,
-        # fall back to using base.html.
-        if self.base_template is None:
-            self.base_template = f"{model._meta.app_label}/{model._meta.model_name}.html"
-
-        return render(request, 'extras/object_journal.html', {
-            'object': obj,
-            'form': form,
-            'table': journalentry_table,
-            'base_template': self.base_template,
-            'active_tab': 'journal',
-        })
-
-
 #
 # Reports
 #

+ 1 - 1
netbox/ipam/urls.py

@@ -1,6 +1,6 @@
 from django.urls import path
 
-from extras.views import ObjectChangeLogView, ObjectJournalView
+from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
 from . import views
 from .models import *
 

+ 2 - 1
netbox/netbox/views/generic/__init__.py

@@ -1,2 +1,3 @@
-from .object_views import *
 from .bulk_views import *
+from .feature_views import *
+from .object_views import *

+ 112 - 0
netbox/netbox/views/generic/feature_views.py

@@ -0,0 +1,112 @@
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import Q
+from django.shortcuts import get_object_or_404, render
+from django.views.generic import View
+
+from extras import forms, tables
+from extras.models import *
+
+__all__ = (
+    'ObjectChangeLogView',
+    'ObjectJournalView',
+)
+
+
+class ObjectChangeLogView(View):
+    """
+    Present a history of changes made to a particular object. The model class must be passed as a keyword argument
+    when referencing this view in a URL path. For example:
+
+        path('sites/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='site_changelog', kwargs={'model': Site}),
+
+    Attributes:
+        base_template: The name of the template to extend. If not provided, "{app}/{model}.html" will be used.
+    """
+    base_template = None
+
+    def get(self, request, model, **kwargs):
+
+        # Handle QuerySet restriction of parent object if needed
+        if hasattr(model.objects, 'restrict'):
+            obj = get_object_or_404(model.objects.restrict(request.user, 'view'), **kwargs)
+        else:
+            obj = get_object_or_404(model, **kwargs)
+
+        # Gather all changes for this object (and its related objects)
+        content_type = ContentType.objects.get_for_model(model)
+        objectchanges = ObjectChange.objects.restrict(request.user, 'view').prefetch_related(
+            'user', 'changed_object_type'
+        ).filter(
+            Q(changed_object_type=content_type, changed_object_id=obj.pk) |
+            Q(related_object_type=content_type, related_object_id=obj.pk)
+        )
+        objectchanges_table = tables.ObjectChangeTable(
+            data=objectchanges,
+            orderable=False
+        )
+        objectchanges_table.configure(request)
+
+        # Default to using "<app>/<model>.html" as the template, if it exists. Otherwise,
+        # fall back to using base.html.
+        if self.base_template is None:
+            self.base_template = f"{model._meta.app_label}/{model._meta.model_name}.html"
+
+        return render(request, 'extras/object_changelog.html', {
+            'object': obj,
+            'table': objectchanges_table,
+            'base_template': self.base_template,
+            'active_tab': 'changelog',
+        })
+
+
+class ObjectJournalView(View):
+    """
+    Show all journal entries for an object. The model class must be passed as a keyword argument when referencing this
+    view in a URL path. For example:
+
+        path('sites/<int:pk>/journal/', ObjectJournalView.as_view(), name='site_journal', kwargs={'model': Site}),
+
+    Attributes:
+        base_template: The name of the template to extend. If not provided, "{app}/{model}.html" will be used.
+    """
+    base_template = None
+
+    def get(self, request, model, **kwargs):
+
+        # Handle QuerySet restriction of parent object if needed
+        if hasattr(model.objects, 'restrict'):
+            obj = get_object_or_404(model.objects.restrict(request.user, 'view'), **kwargs)
+        else:
+            obj = get_object_or_404(model, **kwargs)
+
+        # Gather all changes for this object (and its related objects)
+        content_type = ContentType.objects.get_for_model(model)
+        journalentries = JournalEntry.objects.restrict(request.user, 'view').prefetch_related('created_by').filter(
+            assigned_object_type=content_type,
+            assigned_object_id=obj.pk
+        )
+        journalentry_table = tables.ObjectJournalTable(journalentries)
+        journalentry_table.configure(request)
+
+        if request.user.has_perm('extras.add_journalentry'):
+            form = forms.JournalEntryForm(
+                initial={
+                    'assigned_object_type': ContentType.objects.get_for_model(obj),
+                    'assigned_object_id': obj.pk
+                }
+            )
+        else:
+            form = None
+
+        # Default to using "<app>/<model>.html" as the template, if it exists. Otherwise,
+        # fall back to using base.html.
+        if self.base_template is None:
+            self.base_template = f"{model._meta.app_label}/{model._meta.model_name}.html"
+
+        return render(request, 'extras/object_journal.html', {
+            'object': obj,
+            'form': form,
+            'table': journalentry_table,
+            'base_template': self.base_template,
+            'active_tab': 'journal',
+        })

+ 1 - 1
netbox/tenancy/urls.py

@@ -1,6 +1,6 @@
 from django.urls import path
 
-from extras.views import ObjectChangeLogView, ObjectJournalView
+from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
 from . import views
 from .models import *
 

+ 1 - 1
netbox/virtualization/urls.py

@@ -1,6 +1,6 @@
 from django.urls import path
 
-from extras.views import ObjectChangeLogView, ObjectJournalView
+from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
 from . import views
 from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
 

+ 1 - 1
netbox/wireless/urls.py

@@ -1,6 +1,6 @@
 from django.urls import path
 
-from extras.views import ObjectChangeLogView, ObjectJournalView
+from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
 from . import views
 from .models import *