#2434: Refactor ComponentCreateView to use generic form validation method

New validate_form method on ComponentCreateView handles validation generically, which any post() method on ComponentCreateView can use to validate the form but handle the response differently as needed.
This commit is contained in:
checktheroads 2021-05-08 12:01:25 -07:00
parent 515aed7022
commit 2d2719cfb2
2 changed files with 38 additions and 72 deletions

View File

@ -1914,65 +1914,22 @@ class InterfaceCreateView(generic.ComponentCreateView):
template_name = 'dcim/device_component_add.html' template_name = 'dcim/device_component_add.html'
def post(self, request): def post(self, request):
"""
Override inherited post() method to handle request to assign newly created
interface objects (first object) to an IP Address object.
"""
logger = logging.getLogger('netbox.dcim.views.InterfaceCreateView') logger = logging.getLogger('netbox.dcim.views.InterfaceCreateView')
form = self.form(request.POST, initial=request.GET) form = self.form(request.POST, initial=request.GET)
self.validate_form(request, form)
if form.is_valid(): if form.is_valid():
if '_addanother' in request.POST:
new_components = [] return redirect(request.get_full_path())
data = deepcopy(request.POST) elif '_assignip' in request.POST and len(self.created_objects) >= 1 and request.user.has_perm('ipam.add_ipaddress'):
first_obj = self.created_objects[0].pk
names = form.cleaned_data['name_pattern'] return redirect(f'/ipam/ip-addresses/add/?interface={first_obj}&return_url={self.get_return_url(request)}')
labels = form.cleaned_data.get('label_pattern') else:
for i, name in enumerate(names): return redirect(self.get_return_url(request))
label = labels[i] if labels else None
# Initialize the individual component form
data['name'] = name
data['label'] = label
if hasattr(form, 'get_iterative_data'):
data.update(form.get_iterative_data(i))
component_form = self.model_form(data)
if component_form.is_valid():
new_components.append(component_form)
else:
for field, errors in component_form.errors.as_data().items():
# Assign errors on the child form's name/label field to name_pattern/label_pattern on the parent form
if field == 'name':
field = 'name_pattern'
elif field == 'label':
field = 'label_pattern'
for e in errors:
form.add_error(field, '{}: {}'.format(name, ', '.join(e)))
if not form.errors:
try:
# Create the new components
new_objs = []
with transaction.atomic():
for component_form in new_components:
obj = component_form.save()
new_objs.append(obj)
# Enforce object-level permissions
if self.queryset.filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs):
raise ObjectDoesNotExist
messages.success(request, "Added {} {}".format(
len(new_components), self.queryset.model._meta.verbose_name_plural
))
if '_addanother' in request.POST:
return redirect(request.get_full_path())
elif '_assignip' in request.POST and len(new_objs) >= 1 and request.user.has_perm('ipam.add_ipaddress'):
first_obj = new_objs[0].pk
return redirect(f'/ipam/ip-addresses/add/?interface={first_obj}&return_url={self.get_return_url(request)}')
else:
return redirect(self.get_return_url(request))
except ObjectDoesNotExist:
msg = "Component creation failed due to object-level permissions violation"
logger.debug(msg)
form.add_error(None, msg)
return render(request, self.template_name, { return render(request, self.template_name, {
'component_type': self.queryset.model._meta.verbose_name, 'component_type': self.queryset.model._meta.verbose_name,

View File

@ -1088,6 +1088,7 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
form = None form = None
model_form = None model_form = None
template_name = None template_name = None
created_objects = []
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')
@ -1105,25 +1106,47 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
def post(self, request): def post(self, request):
logger = logging.getLogger('netbox.views.ComponentCreateView') logger = logging.getLogger('netbox.views.ComponentCreateView')
form = self.form(request.POST, initial=request.GET) form = self.form(request.POST, initial=request.GET)
self.validate_form(request, form)
if form.is_valid(): if form.is_valid():
if '_addanother' in request.POST:
return redirect(request.get_full_path())
else:
return redirect(self.get_return_url(request))
return render(request, self.template_name, {
'component_type': self.queryset.model._meta.verbose_name,
'form': form,
'return_url': self.get_return_url(request),
})
def validate_form(self, request, form):
"""
Validate form values and set errors on the form object as they are detected. If
no errors are found, signal success messages.
"""
logger = logging.getLogger('netbox.views.ComponentCreateView')
if form.is_valid():
new_components = [] new_components = []
data = deepcopy(request.POST) data = deepcopy(request.POST)
names = form.cleaned_data['name_pattern'] names = form.cleaned_data['name_pattern']
labels = form.cleaned_data.get('label_pattern') labels = form.cleaned_data.get('label_pattern')
for i, name in enumerate(names): for i, name in enumerate(names):
label = labels[i] if labels else None label = labels[i] if labels else None
# Initialize the individual component form # Initialize the individual component form
data['name'] = name data['name'] = name
data['label'] = label data['label'] = label
if hasattr(form, 'get_iterative_data'): if hasattr(form, 'get_iterative_data'):
data.update(form.get_iterative_data(i)) data.update(form.get_iterative_data(i))
component_form = self.model_form(data) component_form = self.model_form(data)
if component_form.is_valid(): if component_form.is_valid():
new_components.append(component_form) new_components.append(component_form)
else: else:
for field, errors in component_form.errors.as_data().items(): for field, errors in component_form.errors.as_data().items():
# Assign errors on the child form's name/label field to name_pattern/label_pattern on the parent form # Assign errors on the child form's name/label field to name_pattern/label_pattern on the parent form
@ -1135,40 +1158,26 @@ class ComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View
form.add_error(field, '{}: {}'.format(name, ', '.join(e))) form.add_error(field, '{}: {}'.format(name, ', '.join(e)))
if not form.errors: if not form.errors:
try: try:
with transaction.atomic(): with transaction.atomic():
# Create the new components # Create the new components
new_objs = []
for component_form in new_components: for component_form in new_components:
obj = component_form.save() obj = component_form.save()
new_objs.append(obj) self.created_objects.append(obj)
# Enforce object-level permissions # Enforce object-level permissions
if self.queryset.filter(pk__in=[obj.pk for obj in new_objs]).count() != len(new_objs): if self.queryset.filter(pk__in=[obj.pk for obj in self.created_objects]).count() != len(self.created_objects):
raise ObjectDoesNotExist raise ObjectDoesNotExist
messages.success(request, "Added {} {}".format( messages.success(request, "Added {} {}".format(
len(new_components), self.queryset.model._meta.verbose_name_plural len(new_components), self.queryset.model._meta.verbose_name_plural
)) ))
if '_addanother' in request.POST:
return redirect(request.get_full_path())
else:
return redirect(self.get_return_url(request))
except ObjectDoesNotExist: except ObjectDoesNotExist:
msg = "Component creation failed due to object-level permissions violation" msg = "Component creation failed due to object-level permissions violation"
logger.debug(msg) logger.debug(msg)
form.add_error(None, msg) form.add_error(None, msg)
return render(request, self.template_name, {
'component_type': self.queryset.model._meta.verbose_name,
'form': form,
'return_url': self.get_return_url(request),
})
class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View): class BulkComponentCreateView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
""" """