1
0
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:
jeremystretch
2022-01-21 14:41:37 -05:00
parent e03593d86f
commit a74ed33b0e
5 changed files with 118 additions and 94 deletions

View File

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

View File

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

View File

@ -1,3 +1,4 @@
from django.shortcuts import get_object_or_404
from django.views.generic import View from django.views.generic import View
from utilities.views import ObjectPermissionRequiredMixin from utilities.views import ObjectPermissionRequiredMixin
@ -14,6 +15,15 @@ class BaseObjectView(ObjectPermissionRequiredMixin, View):
queryset = None queryset = None
template_name = 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): def get_extra_context(self, request, instance):
""" """
Return any additional context data to include when rendering the template. Return any additional context data to include when rendering the template.
@ -31,9 +41,11 @@ class BaseMultiObjectView(ObjectPermissionRequiredMixin, View):
Attributes: Attributes:
queryset: Django QuerySet from which the object(s) will be fetched 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 template_name: The name of the HTML template file to render
""" """
queryset = None queryset = None
table = None
template_name = None template_name = None
def get_extra_context(self, request): def get_extra_context(self, request):

View File

@ -43,13 +43,11 @@ class ObjectListView(BaseMultiObjectView):
Attributes: Attributes:
filterset: A django-filter FilterSet that is applied to the queryset filterset: A django-filter FilterSet that is applied to the queryset
filterset_form: The form class used to render filter options 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 action_buttons: A list of buttons to include at the top of the page
""" """
template_name = 'generic/object_list.html' template_name = 'generic/object_list.html'
filterset = None filterset = None
filterset_form = None filterset_form = None
table = None
action_buttons = ('add', 'import', 'export') action_buttons = ('add', 'import', 'export')
def get_required_permission(self): def get_required_permission(self):
@ -70,6 +68,61 @@ class ObjectListView(BaseMultiObjectView):
return table 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): def get(self, request):
""" """
GET request handler. GET request handler.
@ -135,57 +188,6 @@ class ObjectListView(BaseMultiObjectView):
return render(request, self.template_name, context) 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): class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):
""" """
@ -227,6 +229,10 @@ class BulkCreateView(GetReturnURLMixin, BaseMultiObjectView):
return new_objects return new_objects
#
# Request handlers
#
def get(self, request): def get(self, request):
# Set initial values for visible form fields from query args # Set initial values for visible form fields from query args
initial = {} initial = {}
@ -297,12 +303,10 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
Attributes: Attributes:
model_form: The form used to create each imported object 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) 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' template_name = 'generic/object_bulk_import.html'
model_form = None model_form = None
table = None
widget_attrs = {} widget_attrs = {}
def _import_form(self, *args, **kwargs): def _import_form(self, *args, **kwargs):
@ -361,6 +365,10 @@ class BulkImportView(GetReturnURLMixin, BaseMultiObjectView):
def get_required_permission(self): def get_required_permission(self):
return get_permission_for_model(self.queryset.model, 'add') return get_permission_for_model(self.queryset.model, 'add')
#
# Request handlers
#
def get(self, request): def get(self, request):
return render(request, self.template_name, { return render(request, self.template_name, {
@ -427,12 +435,10 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
Attributes: Attributes:
filterset: FilterSet to apply when deleting by QuerySet 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 form: The form class used to edit objects in bulk
""" """
template_name = 'generic/object_bulk_edit.html' template_name = 'generic/object_bulk_edit.html'
filterset = None filterset = None
table = None
form = None form = None
def get_required_permission(self): def get_required_permission(self):
@ -495,6 +501,10 @@ class BulkEditView(GetReturnURLMixin, BaseMultiObjectView):
return updated_objects return updated_objects
#
# Request handlers
#
def get(self, request): def get(self, request):
return redirect(self.get_return_url(request)) return redirect(self.get_return_url(request))
@ -692,6 +702,10 @@ class BulkDeleteView(GetReturnURLMixin, BaseMultiObjectView):
return BulkDeleteForm return BulkDeleteForm
#
# Request handlers
#
def get(self, request): def get(self, request):
return redirect(self.get_return_url(request)) return redirect(self.get_return_url(request))

View File

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