Add logging output to utility views

This commit is contained in:
Jeremy Stretch 2020-03-02 16:38:51 -05:00
parent 28e3b7af18
commit ca1186dca1
2 changed files with 87 additions and 33 deletions

View File

@ -0,0 +1,7 @@
# Logging Configuration
### Available Loggers
| Name | Function |
|------------------|----------|
| `netbox.views.*` | Views which handle business logic for the web UI |

View File

@ -1,3 +1,4 @@
import logging
import sys import sys
from copy import deepcopy from copy import deepcopy
@ -219,35 +220,36 @@ class ObjectEditView(GetReturnURLMixin, View):
# given some parameter from the request URL. # given some parameter from the request URL.
return obj return obj
def get(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.obj = self.alter_obj(self.get_object(kwargs), request, args, kwargs)
obj = self.get_object(kwargs) return super().dispatch(request, *args, **kwargs)
obj = self.alter_obj(obj, request, args, kwargs)
def get(self, request, *args, **kwargs):
# Parse initial data manually to avoid setting field values as lists # Parse initial data manually to avoid setting field values as lists
initial_data = {k: request.GET[k] for k in request.GET} initial_data = {k: request.GET[k] for k in request.GET}
form = self.model_form(instance=obj, initial=initial_data) form = self.model_form(instance=self.obj, initial=initial_data)
return render(request, self.template_name, { return render(request, self.template_name, {
'obj': obj, 'obj': self.obj,
'obj_type': self.model._meta.verbose_name, 'obj_type': self.model._meta.verbose_name,
'form': form, 'form': form,
'return_url': self.get_return_url(request, obj), 'return_url': self.get_return_url(request, self.obj),
}) })
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
logger = logging.getLogger('netbox.views.ObjectEditView')
obj = self.get_object(kwargs) form = self.model_form(request.POST, request.FILES, instance=self.obj)
obj = self.alter_obj(obj, request, args, kwargs)
form = self.model_form(request.POST, request.FILES, instance=obj)
if form.is_valid(): if form.is_valid():
obj_created = not form.instance.pk logger.debug("Form validation was successful")
obj = form.save()
obj = form.save()
msg = '{} {}'.format( msg = '{} {}'.format(
'Created' if obj_created else 'Modified', 'Created' if not form.instance.pk else 'Modified',
self.model._meta.verbose_name self.model._meta.verbose_name
) )
logger.info(f"{msg} {obj} (PK: {obj.pk})")
if hasattr(obj, 'get_absolute_url'): if hasattr(obj, 'get_absolute_url'):
msg = '{} <a href="{}">{}</a>'.format(msg, obj.get_absolute_url(), escape(obj)) msg = '{} <a href="{}">{}</a>'.format(msg, obj.get_absolute_url(), escape(obj))
else: else:
@ -269,11 +271,14 @@ class ObjectEditView(GetReturnURLMixin, View):
else: else:
return redirect(self.get_return_url(request, obj)) return redirect(self.get_return_url(request, obj))
else:
logger.debug("Form validation failed")
return render(request, self.template_name, { return render(request, self.template_name, {
'obj': obj, 'obj': self.obj,
'obj_type': self.model._meta.verbose_name, 'obj_type': self.model._meta.verbose_name,
'form': form, 'form': form,
'return_url': self.get_return_url(request, obj), 'return_url': self.get_return_url(request, self.obj),
}) })
@ -295,7 +300,6 @@ class ObjectDeleteView(GetReturnURLMixin, View):
return get_object_or_404(self.model, pk=kwargs['pk']) return get_object_or_404(self.model, pk=kwargs['pk'])
def get(self, request, **kwargs): def get(self, request, **kwargs):
obj = self.get_object(kwargs) obj = self.get_object(kwargs)
form = ConfirmationForm(initial=request.GET) form = ConfirmationForm(initial=request.GET)
@ -307,18 +311,22 @@ class ObjectDeleteView(GetReturnURLMixin, View):
}) })
def post(self, request, **kwargs): def post(self, request, **kwargs):
logger = logging.getLogger('netbox.views.ObjectDeleteView')
obj = self.get_object(kwargs) obj = self.get_object(kwargs)
form = ConfirmationForm(request.POST) form = ConfirmationForm(request.POST)
if form.is_valid(): if form.is_valid():
logger.debug("Form validation was successful")
try: try:
obj.delete() obj.delete()
except ProtectedError as e: except ProtectedError as e:
logger.info("Caught ProtectedError while attempting to delete object")
handle_protectederror(obj, request, e) handle_protectederror(obj, request, e)
return redirect(obj.get_absolute_url()) return redirect(obj.get_absolute_url())
msg = 'Deleted {} {}'.format(self.model._meta.verbose_name, obj) msg = 'Deleted {} {}'.format(self.model._meta.verbose_name, obj)
logger.info(msg)
messages.success(request, msg) messages.success(request, msg)
return_url = form.cleaned_data.get('return_url') return_url = form.cleaned_data.get('return_url')
@ -327,6 +335,9 @@ class ObjectDeleteView(GetReturnURLMixin, View):
else: else:
return redirect(self.get_return_url(request, obj)) return redirect(self.get_return_url(request, obj))
else:
logger.debug("Form validation failed")
return render(request, self.template_name, { return render(request, self.template_name, {
'obj': obj, 'obj': obj,
'form': form, 'form': form,
@ -350,7 +361,6 @@ class BulkCreateView(GetReturnURLMixin, View):
template_name = None template_name = None
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 = {}
for field in getattr(self.model_form._meta, 'fields', []): for field in getattr(self.model_form._meta, 'fields', []):
@ -368,13 +378,13 @@ class BulkCreateView(GetReturnURLMixin, View):
}) })
def post(self, request): def post(self, request):
logger = logging.getLogger('netbox.views.BulkCreateView')
model = self.model_form._meta.model model = self.model_form._meta.model
form = self.form(request.POST) form = self.form(request.POST)
model_form = self.model_form(request.POST) model_form = self.model_form(request.POST)
if form.is_valid(): if form.is_valid():
logger.debug("Form validation was successful")
pattern = form.cleaned_data['pattern'] pattern = form.cleaned_data['pattern']
new_objs = [] new_objs = []
@ -392,6 +402,7 @@ class BulkCreateView(GetReturnURLMixin, View):
# Validate each new object independently. # Validate each new object independently.
if model_form.is_valid(): if model_form.is_valid():
obj = model_form.save() obj = model_form.save()
logger.debug(f"Created {obj} (PK: {obj.pk})")
new_objs.append(obj) new_objs.append(obj)
else: else:
# Copy any errors on the pattern target field to the pattern form. # Copy any errors on the pattern target field to the pattern form.
@ -403,6 +414,7 @@ class BulkCreateView(GetReturnURLMixin, View):
# If we make it to this point, validation has succeeded on all new objects. # If we make it to this point, validation has succeeded on all new objects.
msg = "Added {} {}".format(len(new_objs), model._meta.verbose_name_plural) msg = "Added {} {}".format(len(new_objs), model._meta.verbose_name_plural)
logger.info(msg)
messages.success(request, msg) messages.success(request, msg)
if '_addanother' in request.POST: if '_addanother' in request.POST:
@ -412,6 +424,9 @@ class BulkCreateView(GetReturnURLMixin, View):
except IntegrityError: except IntegrityError:
pass pass
else:
logger.debug("Form validation failed")
return render(request, self.template_name, { return render(request, self.template_name, {
'form': form, 'form': form,
'model_form': model_form, 'model_form': model_form,
@ -430,7 +445,6 @@ class ObjectImportView(GetReturnURLMixin, View):
template_name = 'utilities/obj_import.html' template_name = 'utilities/obj_import.html'
def get(self, request): def get(self, request):
form = ImportForm() form = ImportForm()
return render(request, self.template_name, { return render(request, self.template_name, {
@ -440,9 +454,11 @@ class ObjectImportView(GetReturnURLMixin, View):
}) })
def post(self, request): def post(self, request):
logger = logging.getLogger('netbox.views.ObjectImportView')
form = ImportForm(request.POST) form = ImportForm(request.POST)
if form.is_valid(): if form.is_valid():
logger.debug("Import form validation was successful")
# Initialize model form # Initialize model form
data = form.cleaned_data['data'] data = form.cleaned_data['data']
@ -463,9 +479,11 @@ class ObjectImportView(GetReturnURLMixin, View):
# Save the primary object # Save the primary object
obj = model_form.save() obj = model_form.save()
logger.debug(f"Created {obj} (PK: {obj.pk})")
# Iterate through the related object forms (if any), validating and saving each instance. # Iterate through the related object forms (if any), validating and saving each instance.
for field_name, related_object_form in self.related_object_forms.items(): for field_name, related_object_form in self.related_object_forms.items():
logger.debug("Processing form for related objects: {related_object_form}")
for i, rel_obj_data in enumerate(data.get(field_name, list())): for i, rel_obj_data in enumerate(data.get(field_name, list())):
@ -489,7 +507,7 @@ class ObjectImportView(GetReturnURLMixin, View):
pass pass
if not model_form.errors: if not model_form.errors:
logger.info(f"Import object {obj} (PK: {obj.pk})")
messages.success(request, mark_safe('Imported object: <a href="{}">{}</a>'.format( messages.success(request, mark_safe('Imported object: <a href="{}">{}</a>'.format(
obj.get_absolute_url(), obj obj.get_absolute_url(), obj
))) )))
@ -504,6 +522,7 @@ class ObjectImportView(GetReturnURLMixin, View):
return redirect(self.get_return_url(request, obj)) return redirect(self.get_return_url(request, obj))
else: else:
logger.debug("Model form validation failed")
# Replicate model form errors for display # Replicate model form errors for display
for field, errors in model_form.errors.items(): for field, errors in model_form.errors.items():
@ -513,6 +532,9 @@ class ObjectImportView(GetReturnURLMixin, View):
else: else:
form.add_error(None, "{}: {}".format(field, err)) form.add_error(None, "{}: {}".format(field, err))
else:
logger.debug("Import form validation failed")
return render(request, self.template_name, { return render(request, self.template_name, {
'form': form, 'form': form,
'obj_type': self.model._meta.verbose_name, 'obj_type': self.model._meta.verbose_name,
@ -560,14 +582,14 @@ class BulkImportView(GetReturnURLMixin, View):
}) })
def post(self, request): def post(self, request):
logger = logging.getLogger('netbox.views.BulkImportView')
new_objs = [] new_objs = []
form = self._import_form(request.POST) form = self._import_form(request.POST)
if form.is_valid(): if form.is_valid():
logger.debug("Form validation was successful")
try: try:
# Iterate through CSV data and bind each row to a new model form instance. # Iterate through CSV data and bind each row to a new model form instance.
with transaction.atomic(): with transaction.atomic():
for row, data in enumerate(form.cleaned_data['csv'], start=1): for row, data in enumerate(form.cleaned_data['csv'], start=1):
@ -585,6 +607,7 @@ class BulkImportView(GetReturnURLMixin, View):
if new_objs: if new_objs:
msg = 'Imported {} {}'.format(len(new_objs), new_objs[0]._meta.verbose_name_plural) msg = 'Imported {} {}'.format(len(new_objs), new_objs[0]._meta.verbose_name_plural)
logger.info(msg)
messages.success(request, msg) messages.success(request, msg)
return render(request, "import_success.html", { return render(request, "import_success.html", {
@ -595,6 +618,9 @@ class BulkImportView(GetReturnURLMixin, View):
except ValidationError: except ValidationError:
pass pass
else:
logger.debug("Form validation failed")
return render(request, self.template_name, { return render(request, self.template_name, {
'form': form, 'form': form,
'fields': self.model_form().fields, 'fields': self.model_form().fields,
@ -623,7 +649,7 @@ class BulkEditView(GetReturnURLMixin, View):
return redirect(self.get_return_url(request)) return redirect(self.get_return_url(request))
def post(self, request, **kwargs): def post(self, request, **kwargs):
logger = logging.getLogger('netbox.views.BulkEditView')
model = self.queryset.model model = self.queryset.model
# Create a mutable copy of the POST data # Create a mutable copy of the POST data
@ -635,8 +661,9 @@ class BulkEditView(GetReturnURLMixin, View):
if '_apply' in request.POST: if '_apply' in request.POST:
form = self.form(model, request.POST) form = self.form(model, request.POST)
if form.is_valid():
if form.is_valid():
logger.debug("Form validation was successful")
custom_fields = form.custom_fields if hasattr(form, 'custom_fields') else [] custom_fields = form.custom_fields if hasattr(form, 'custom_fields') else []
standard_fields = [ standard_fields = [
field for field in form.fields if field not in custom_fields + ['pk'] field for field in form.fields if field not in custom_fields + ['pk']
@ -676,6 +703,7 @@ class BulkEditView(GetReturnURLMixin, View):
obj.full_clean() obj.full_clean()
obj.save() obj.save()
logger.debug(f"Saved {obj} (PK: {obj.pk})")
# Update custom fields # Update custom fields
obj_type = ContentType.objects.get_for_model(model) obj_type = ContentType.objects.get_for_model(model)
@ -696,6 +724,7 @@ class BulkEditView(GetReturnURLMixin, View):
) )
cfv.value = form.cleaned_data[name] cfv.value = form.cleaned_data[name]
cfv.save() cfv.save()
logger.debug(f"Saved custom fields for {obj} (PK: {obj.pk})")
# Add/remove tags # Add/remove tags
if form.cleaned_data.get('add_tags', None): if form.cleaned_data.get('add_tags', None):
@ -707,6 +736,7 @@ class BulkEditView(GetReturnURLMixin, View):
if updated_count: if updated_count:
msg = 'Updated {} {}'.format(updated_count, model._meta.verbose_name_plural) msg = 'Updated {} {}'.format(updated_count, model._meta.verbose_name_plural)
logger.info(msg)
messages.success(self.request, msg) messages.success(self.request, msg)
return redirect(self.get_return_url(request)) return redirect(self.get_return_url(request))
@ -714,6 +744,9 @@ class BulkEditView(GetReturnURLMixin, View):
except ValidationError as e: except ValidationError as e:
messages.error(self.request, "{} failed validation: {}".format(obj, e)) messages.error(self.request, "{} failed validation: {}".format(obj, e))
else:
logger.debug("Form validation failed")
else: else:
# Pass the PK list as initial data to avoid binding the form # Pass the PK list as initial data to avoid binding the form
initial_data = querydict_to_dict(post_data) initial_data = querydict_to_dict(post_data)
@ -753,7 +786,7 @@ class BulkDeleteView(GetReturnURLMixin, View):
return redirect(self.get_return_url(request)) return redirect(self.get_return_url(request))
def post(self, request, **kwargs): def post(self, request, **kwargs):
logger = logging.getLogger('netbox.views.BulkDeleteView')
model = self.queryset.model model = self.queryset.model
# Are we deleting *all* objects in the queryset or just a selected subset? # Are we deleting *all* objects in the queryset or just a selected subset?
@ -770,19 +803,25 @@ class BulkDeleteView(GetReturnURLMixin, View):
if '_confirm' in request.POST: if '_confirm' in request.POST:
form = form_cls(request.POST) form = form_cls(request.POST)
if form.is_valid(): if form.is_valid():
logger.debug("Form validation was successful")
# Delete objects # Delete objects
queryset = model.objects.filter(pk__in=pk_list) queryset = model.objects.filter(pk__in=pk_list)
try: try:
deleted_count = queryset.delete()[1][model._meta.label] deleted_count = queryset.delete()[1][model._meta.label]
except ProtectedError as e: except ProtectedError as e:
logger.info("Caught ProtectedError while attempting to delete objects")
handle_protectederror(list(queryset), request, e) handle_protectederror(list(queryset), request, e)
return redirect(self.get_return_url(request)) return redirect(self.get_return_url(request))
msg = 'Deleted {} {}'.format(deleted_count, model._meta.verbose_name_plural) msg = 'Deleted {} {}'.format(deleted_count, model._meta.verbose_name_plural)
logger.info(msg)
messages.success(request, msg) messages.success(request, msg)
return redirect(self.get_return_url(request)) return redirect(self.get_return_url(request))
else:
logger.debug("Form validation failed")
else: else:
form = form_cls(initial={ form = form_cls(initial={
'pk': pk_list, 'pk': pk_list,
@ -806,12 +845,12 @@ class BulkDeleteView(GetReturnURLMixin, View):
""" """
Provide a standard bulk delete form if none has been specified for the view Provide a standard bulk delete form if none has been specified for the view
""" """
class BulkDeleteForm(ConfirmationForm): class BulkDeleteForm(ConfirmationForm):
pk = ModelMultipleChoiceField(queryset=self.queryset, widget=MultipleHiddenInput) pk = ModelMultipleChoiceField(queryset=self.queryset, widget=MultipleHiddenInput)
if self.form: if self.form:
return self.form return self.form
return BulkDeleteForm return BulkDeleteForm
@ -900,7 +939,7 @@ class BulkComponentCreateView(GetReturnURLMixin, View):
template_name = 'utilities/obj_bulk_add_component.html' template_name = 'utilities/obj_bulk_add_component.html'
def post(self, request): def post(self, request):
logger = logging.getLogger('netbox.views.BulkComponentCreateView')
parent_model_name = self.parent_model._meta.verbose_name_plural parent_model_name = self.parent_model._meta.verbose_name_plural
model_name = self.model._meta.verbose_name_plural model_name = self.model._meta.verbose_name_plural
@ -918,10 +957,13 @@ class BulkComponentCreateView(GetReturnURLMixin, View):
if '_create' in request.POST: if '_create' in request.POST:
form = self.form(request.POST) form = self.form(request.POST)
if form.is_valid(): if form.is_valid():
logger.debug("Form validation was successful")
new_components = [] new_components = []
data = deepcopy(form.cleaned_data) data = deepcopy(form.cleaned_data)
for obj in data['pk']: for obj in data['pk']:
names = data['name_pattern'] names = data['name_pattern']
@ -941,15 +983,20 @@ class BulkComponentCreateView(GetReturnURLMixin, View):
if not form.errors: if not form.errors:
self.model.objects.bulk_create(new_components) self.model.objects.bulk_create(new_components)
msg = "Added {} {} to {} {}.".format(
messages.success(request, "Added {} {} to {} {}.".format(
len(new_components), len(new_components),
model_name, model_name,
len(form.cleaned_data['pk']), len(form.cleaned_data['pk']),
parent_model_name parent_model_name
)) )
logger.info(msg)
messages.success(request, msg)
return redirect(self.get_return_url(request)) return redirect(self.get_return_url(request))
else:
logger.debug("Form validation failed")
else: else:
form = self.form(initial={'pk': pk_list}) form = self.form(initial={'pk': pk_list})