mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Move get_object() to BaseObjectView
This commit is contained in:
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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")
|
||||
|
||||
|
Reference in New Issue
Block a user