mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 01:41:22 -06:00
#8334: Move object changelog & journaling to generic views
This commit is contained in:
parent
7c105019d8
commit
d42c59792f
@ -146,9 +146,9 @@ A `PluginMenuButton` has the following attributes:
|
|||||||
!!! note
|
!!! 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.
|
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
|
::: netbox.views.generic.base.BaseObjectView
|
||||||
rendering:
|
rendering:
|
||||||
@ -177,9 +177,9 @@ Below is the class definition for NetBox's BaseObjectView. The attributes and me
|
|||||||
rendering:
|
rendering:
|
||||||
show_source: false
|
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
|
::: netbox.views.generic.base.BaseMultiObjectView
|
||||||
rendering:
|
rendering:
|
||||||
@ -212,3 +212,21 @@ Below is the class definition for NetBox's BaseMultiObjectView. The attributes a
|
|||||||
- get_form
|
- get_form
|
||||||
rendering:
|
rendering:
|
||||||
show_source: false
|
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,7 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from dcim.views import CableCreateView, PathTraceView
|
from dcim.views import CableCreateView, PathTraceView
|
||||||
from extras.views import ObjectChangeLogView, ObjectJournalView
|
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
|
||||||
from . import views
|
from . import views
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from extras.views import ObjectChangeLogView, ObjectJournalView
|
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
|
||||||
from . import views
|
from . import views
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from extras import models, views
|
from extras import models, views
|
||||||
|
from netbox.views.generic import ObjectChangeLogView
|
||||||
|
|
||||||
|
|
||||||
app_name = 'extras'
|
app_name = 'extras'
|
||||||
@ -15,7 +16,7 @@ urlpatterns = [
|
|||||||
path('custom-fields/<int:pk>/', views.CustomFieldView.as_view(), name='customfield'),
|
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>/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>/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}),
|
kwargs={'model': models.CustomField}),
|
||||||
|
|
||||||
# Custom links
|
# Custom links
|
||||||
@ -27,7 +28,7 @@ urlpatterns = [
|
|||||||
path('custom-links/<int:pk>/', views.CustomLinkView.as_view(), name='customlink'),
|
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>/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>/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}),
|
kwargs={'model': models.CustomLink}),
|
||||||
|
|
||||||
# Export templates
|
# Export templates
|
||||||
@ -39,7 +40,7 @@ urlpatterns = [
|
|||||||
path('export-templates/<int:pk>/', views.ExportTemplateView.as_view(), name='exporttemplate'),
|
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>/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>/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}),
|
kwargs={'model': models.ExportTemplate}),
|
||||||
|
|
||||||
# Webhooks
|
# Webhooks
|
||||||
@ -51,7 +52,7 @@ urlpatterns = [
|
|||||||
path('webhooks/<int:pk>/', views.WebhookView.as_view(), name='webhook'),
|
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>/edit/', views.WebhookEditView.as_view(), name='webhook_edit'),
|
||||||
path('webhooks/<int:pk>/delete/', views.WebhookDeleteView.as_view(), name='webhook_delete'),
|
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}),
|
kwargs={'model': models.Webhook}),
|
||||||
|
|
||||||
# Tags
|
# Tags
|
||||||
@ -63,7 +64,7 @@ urlpatterns = [
|
|||||||
path('tags/<int:pk>/', views.TagView.as_view(), name='tag'),
|
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>/edit/', views.TagEditView.as_view(), name='tag_edit'),
|
||||||
path('tags/<int:pk>/delete/', views.TagDeleteView.as_view(), name='tag_delete'),
|
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}),
|
kwargs={'model': models.Tag}),
|
||||||
|
|
||||||
# Config contexts
|
# Config contexts
|
||||||
@ -74,7 +75,7 @@ urlpatterns = [
|
|||||||
path('config-contexts/<int:pk>/', views.ConfigContextView.as_view(), name='configcontext'),
|
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>/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>/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}),
|
kwargs={'model': models.ConfigContext}),
|
||||||
|
|
||||||
# Image attachments
|
# Image attachments
|
||||||
@ -90,7 +91,7 @@ urlpatterns = [
|
|||||||
path('journal-entries/<int:pk>/', views.JournalEntryView.as_view(), name='journalentry'),
|
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>/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>/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}),
|
kwargs={'model': models.JournalEntry}),
|
||||||
|
|
||||||
# Change logging
|
# Change logging
|
||||||
|
@ -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
|
# Image attachments
|
||||||
#
|
#
|
||||||
@ -547,55 +504,6 @@ class JournalEntryBulkDeleteView(generic.BulkDeleteView):
|
|||||||
table = tables.JournalEntryTable
|
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
|
# Reports
|
||||||
#
|
#
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from extras.views import ObjectChangeLogView, ObjectJournalView
|
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
|
||||||
from . import views
|
from . import views
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
from .object_views import *
|
|
||||||
from .bulk_views import *
|
from .bulk_views import *
|
||||||
|
from .feature_views import *
|
||||||
|
from .object_views import *
|
||||||
|
112
netbox/netbox/views/generic/feature_views.py
Normal file
112
netbox/netbox/views/generic/feature_views.py
Normal file
@ -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,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from extras.views import ObjectChangeLogView, ObjectJournalView
|
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
|
||||||
from . import views
|
from . import views
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from extras.views import ObjectChangeLogView, ObjectJournalView
|
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
|
||||||
from . import views
|
from . import views
|
||||||
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from extras.views import ObjectChangeLogView, ObjectJournalView
|
from netbox.views.generic import ObjectChangeLogView, ObjectJournalView
|
||||||
from . import views
|
from . import views
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user