Improved handling of return_url for object edit/delete views; removed manual definitions of initial data fields

This commit is contained in:
Jeremy Stretch 2017-04-13 13:11:23 -04:00
parent d5c3f9e780
commit f70f0f8d62
9 changed files with 65 additions and 63 deletions

View File

@ -95,7 +95,7 @@ class CircuitTypeEditView(PermissionRequiredMixin, ObjectEditView):
model = CircuitType model = CircuitType
form_class = forms.CircuitTypeForm form_class = forms.CircuitTypeForm
def get_return_url(self, obj): def get_return_url(self, request, obj):
return reverse('circuits:circuittype_list') return reverse('circuits:circuittype_list')
@ -142,7 +142,6 @@ class CircuitEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'circuits.change_circuit' permission_required = 'circuits.change_circuit'
model = Circuit model = Circuit
form_class = forms.CircuitForm form_class = forms.CircuitForm
fields_initial = ['provider']
template_name = 'circuits/circuit_edit.html' template_name = 'circuits/circuit_edit.html'
default_return_url = 'circuits:circuit_list' default_return_url = 'circuits:circuit_list'
@ -230,7 +229,6 @@ class CircuitTerminationEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'circuits.change_circuittermination' permission_required = 'circuits.change_circuittermination'
model = CircuitTermination model = CircuitTermination
form_class = forms.CircuitTerminationForm form_class = forms.CircuitTerminationForm
fields_initial = ['term_side']
template_name = 'circuits/circuittermination_edit.html' template_name = 'circuits/circuittermination_edit.html'
def alter_obj(self, obj, request, url_args, url_kwargs): def alter_obj(self, obj, request, url_args, url_kwargs):
@ -238,7 +236,7 @@ class CircuitTerminationEditView(PermissionRequiredMixin, ObjectEditView):
obj.circuit = get_object_or_404(Circuit, pk=url_kwargs['circuit']) obj.circuit = get_object_or_404(Circuit, pk=url_kwargs['circuit'])
return obj return obj
def get_return_url(self, obj): def get_return_url(self, request, obj):
return obj.circuit.get_absolute_url() return obj.circuit.get_absolute_url()

View File

@ -1708,7 +1708,7 @@ class IPAddressForm(BootstrapMixin, CustomFieldForm):
self.fields['vrf'].empty_label = 'Global' self.fields['vrf'].empty_label = 'Global'
interfaces = device.interfaces.all() interfaces = device.interfaces.order_naturally(method=device.device_type.interface_ordering)
self.fields['interface'].queryset = interfaces self.fields['interface'].queryset = interfaces
self.fields['interface'].required = True self.fields['interface'].required = True

View File

@ -124,13 +124,13 @@ class ComponentCreateView(View):
class ComponentEditView(ObjectEditView): class ComponentEditView(ObjectEditView):
def get_return_url(self, obj): def get_return_url(self, request, obj):
return obj.device.get_absolute_url() return obj.device.get_absolute_url()
class ComponentDeleteView(ObjectDeleteView): class ComponentDeleteView(ObjectDeleteView):
def get_return_url(self, obj): def get_return_url(self, request, obj):
return obj.device.get_absolute_url() return obj.device.get_absolute_url()
@ -149,7 +149,7 @@ class RegionEditView(PermissionRequiredMixin, ObjectEditView):
model = Region model = Region
form_class = forms.RegionForm form_class = forms.RegionForm
def get_return_url(self, obj): def get_return_url(self, request, obj):
return reverse('dcim:region_list') return reverse('dcim:region_list')
@ -242,7 +242,7 @@ class RackGroupEditView(PermissionRequiredMixin, ObjectEditView):
model = RackGroup model = RackGroup
form_class = forms.RackGroupForm form_class = forms.RackGroupForm
def get_return_url(self, obj): def get_return_url(self, request, obj):
return reverse('dcim:rackgroup_list') return reverse('dcim:rackgroup_list')
@ -268,7 +268,7 @@ class RackRoleEditView(PermissionRequiredMixin, ObjectEditView):
model = RackRole model = RackRole
form_class = forms.RackRoleForm form_class = forms.RackRoleForm
def get_return_url(self, obj): def get_return_url(self, request, obj):
return reverse('dcim:rackrole_list') return reverse('dcim:rackrole_list')
@ -379,7 +379,7 @@ class RackReservationEditView(PermissionRequiredMixin, ObjectEditView):
obj.user = request.user obj.user = request.user
return obj return obj
def get_return_url(self, obj): def get_return_url(self, request, obj):
return obj.rack.get_absolute_url() return obj.rack.get_absolute_url()
@ -387,7 +387,7 @@ class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView):
permission_required = 'dcim.delete_rackreservation' permission_required = 'dcim.delete_rackreservation'
model = RackReservation model = RackReservation
def get_return_url(self, obj): def get_return_url(self, request, obj):
return obj.rack.get_absolute_url() return obj.rack.get_absolute_url()
@ -412,7 +412,7 @@ class ManufacturerEditView(PermissionRequiredMixin, ObjectEditView):
model = Manufacturer model = Manufacturer
form_class = forms.ManufacturerForm form_class = forms.ManufacturerForm
def get_return_url(self, obj): def get_return_url(self, request, obj):
return reverse('dcim:manufacturer_list') return reverse('dcim:manufacturer_list')
@ -632,7 +632,7 @@ class DeviceRoleEditView(PermissionRequiredMixin, ObjectEditView):
model = DeviceRole model = DeviceRole
form_class = forms.DeviceRoleForm form_class = forms.DeviceRoleForm
def get_return_url(self, obj): def get_return_url(self, request, obj):
return reverse('dcim:devicerole_list') return reverse('dcim:devicerole_list')
@ -657,7 +657,7 @@ class PlatformEditView(PermissionRequiredMixin, ObjectEditView):
model = Platform model = Platform
form_class = forms.PlatformForm form_class = forms.PlatformForm
def get_return_url(self, obj): def get_return_url(self, request, obj):
return reverse('dcim:platform_list') return reverse('dcim:platform_list')
@ -750,7 +750,6 @@ class DeviceEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'dcim.change_device' permission_required = 'dcim.change_device'
model = Device model = Device
form_class = forms.DeviceForm form_class = forms.DeviceForm
fields_initial = ['site', 'rack', 'position', 'face', 'device_bay']
template_name = 'dcim/device_edit.html' template_name = 'dcim/device_edit.html'
default_return_url = 'dcim:device_list' default_return_url = 'dcim:device_list'

View File

@ -244,7 +244,7 @@ class RIREditView(PermissionRequiredMixin, ObjectEditView):
model = RIR model = RIR
form_class = forms.RIRForm form_class = forms.RIRForm
def get_return_url(self, obj): def get_return_url(self, request, obj):
return reverse('ipam:rir_list') return reverse('ipam:rir_list')
@ -370,7 +370,7 @@ class RoleEditView(PermissionRequiredMixin, ObjectEditView):
model = Role model = Role
form_class = forms.RoleForm form_class = forms.RoleForm
def get_return_url(self, obj): def get_return_url(self, request, obj):
return reverse('ipam:role_list') return reverse('ipam:role_list')
@ -464,7 +464,6 @@ class PrefixEditView(PermissionRequiredMixin, ObjectEditView):
model = Prefix model = Prefix
form_class = forms.PrefixForm form_class = forms.PrefixForm
template_name = 'ipam/prefix_edit.html' template_name = 'ipam/prefix_edit.html'
fields_initial = ['vrf', 'tenant', 'site', 'prefix', 'vlan']
default_return_url = 'ipam:prefix_list' default_return_url = 'ipam:prefix_list'
@ -645,7 +644,6 @@ class IPAddressEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'ipam.change_ipaddress' permission_required = 'ipam.change_ipaddress'
model = IPAddress model = IPAddress
form_class = forms.IPAddressForm form_class = forms.IPAddressForm
fields_initial = ['address', 'vrf']
template_name = 'ipam/ipaddress_edit.html' template_name = 'ipam/ipaddress_edit.html'
default_return_url = 'ipam:ipaddress_list' default_return_url = 'ipam:ipaddress_list'
@ -718,7 +716,7 @@ class VLANGroupEditView(PermissionRequiredMixin, ObjectEditView):
model = VLANGroup model = VLANGroup
form_class = forms.VLANGroupForm form_class = forms.VLANGroupForm
def get_return_url(self, obj): def get_return_url(self, request, obj):
return reverse('ipam:vlangroup_list') return reverse('ipam:vlangroup_list')
@ -807,7 +805,7 @@ class ServiceEditView(PermissionRequiredMixin, ObjectEditView):
obj.device = get_object_or_404(Device, pk=url_kwargs['device']) obj.device = get_object_or_404(Device, pk=url_kwargs['device'])
return obj return obj
def get_return_url(self, obj): def get_return_url(self, request, obj):
return obj.device.get_absolute_url() return obj.device.get_absolute_url()

View File

@ -30,7 +30,7 @@ class SecretRoleEditView(PermissionRequiredMixin, ObjectEditView):
model = SecretRole model = SecretRole
form_class = forms.SecretRoleForm form_class = forms.SecretRoleForm
def get_return_url(self, obj): def get_return_url(self, request, obj):
return reverse('secrets:secretrole_list') return reverse('secrets:secretrole_list')

View File

@ -6,13 +6,13 @@
<div class="col-md-6 col-md-offset-3"> <div class="col-md-6 col-md-offset-3">
<form action="." method="post" class="form"> <form action="." method="post" class="form">
{% csrf_token %} {% csrf_token %}
{% for field in form.hidden_fields %}
{{ field }}
{% endfor %}
<div class="panel panel-{{ panel_class|default:"danger" }}"> <div class="panel panel-{{ panel_class|default:"danger" }}">
<div class="panel-heading">{% block title %}{% endblock %}</div> <div class="panel-heading">{% block title %}{% endblock %}</div>
<div class="panel-body"> <div class="panel-body">
{% block message %}<p>Are you sure?</p>{% endblock %} {% block message %}<p>Are you sure?</p>{% endblock %}
{% for field in form.hidden_fields %}
{{ field }}
{% endfor %}
<div class="form-group"> <div class="form-group">
<div class="checkbox{% if form.confirm.errors %} has-error{% endif %}"> <div class="checkbox{% if form.confirm.errors %} has-error{% endif %}">
<label for="{{ form.confirm.id_for_label }}"> <label for="{{ form.confirm.id_for_label }}">

View File

@ -29,7 +29,7 @@ class TenantGroupEditView(PermissionRequiredMixin, ObjectEditView):
model = TenantGroup model = TenantGroup
form_class = forms.TenantGroupForm form_class = forms.TenantGroupForm
def get_return_url(self, obj): def get_return_url(self, request, obj):
return reverse('tenancy:tenantgroup_list') return reverse('tenancy:tenantgroup_list')
@ -81,7 +81,6 @@ class TenantEditView(PermissionRequiredMixin, ObjectEditView):
permission_required = 'tenancy.change_tenant' permission_required = 'tenancy.change_tenant'
model = Tenant model = Tenant
form_class = forms.TenantForm form_class = forms.TenantForm
fields_initial = ['group']
template_name = 'tenancy/tenant_edit.html' template_name = 'tenancy/tenant_edit.html'
default_return_url = 'tenancy:tenant_list' default_return_url = 'tenancy:tenant_list'

View File

@ -437,13 +437,18 @@ class BootstrapMixin(forms.BaseForm):
field.widget.attrs['placeholder'] = field.label field.widget.attrs['placeholder'] = field.label
class ConfirmationForm(BootstrapMixin, forms.Form): class ReturnURLForm(forms.Form):
""" """
A generic confirmation form. The form is not valid unless the confirm field is checked. An optional return_url can Provides a hidden return URL field to control where the user is directed after the form is submitted.
be specified to direct the user to a specific URL after the action has been taken. """
return_url = forms.CharField(required=False, widget=forms.HiddenInput())
class ConfirmationForm(BootstrapMixin, ReturnURLForm):
"""
A generic confirmation form. The form is not valid unless the confirm field is checked.
""" """
confirm = forms.BooleanField(required=True) confirm = forms.BooleanField(required=True)
return_url = forms.CharField(required=False, widget=forms.HiddenInput())
class BulkEditForm(forms.Form): class BulkEditForm(forms.Form):

View File

@ -41,6 +41,23 @@ class CustomFieldQueryset:
yield obj yield obj
class GetReturnURLMixin(object):
"""
Provides logic for determining where a user should be redirected after processing a form.
"""
default_return_url = None
def get_return_url(self, request, obj):
query_param = request.GET.get('return_url')
if query_param and is_safe_url(url=query_param, host=request.get_host()):
return query_param
elif obj.pk and hasattr(obj, 'get_absolute_url'):
return obj.get_absolute_url()
elif self.default_return_url is not None:
return reverse(self.default_return_url)
return reverse('home')
class ObjectListView(View): class ObjectListView(View):
""" """
List a series of objects. List a series of objects.
@ -130,21 +147,18 @@ class ObjectListView(View):
return {} return {}
class ObjectEditView(View): class ObjectEditView(GetReturnURLMixin, View):
""" """
Create or edit a single object. Create or edit a single object.
model: The model of the object being edited model: The model of the object being edited
form_class: The form used to create or edit the object form_class: The form used to create or edit the object
fields_initial: A set of fields that will be prepopulated in the form from the request parameters
template_name: The name of the template template_name: The name of the template
default_return_url: The name of the URL used to display a list of this object type default_return_url: The name of the URL used to display a list of this object type
""" """
model = None model = None
form_class = None form_class = None
fields_initial = []
template_name = 'utilities/obj_edit.html' template_name = 'utilities/obj_edit.html'
default_return_url = 'home'
def get_object(self, kwargs): def get_object(self, kwargs):
# Look up object by slug or PK. Return None if neither was provided. # Look up object by slug or PK. Return None if neither was provided.
@ -159,24 +173,17 @@ class ObjectEditView(View):
# given some parameter from the request URL. # given some parameter from the request URL.
return obj return obj
def get_return_url(self, obj):
# Determine where to redirect the user after updating an object (or aborting an update).
if obj.pk and hasattr(obj, 'get_absolute_url'):
return obj.get_absolute_url()
return reverse(self.default_return_url)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
obj = self.get_object(kwargs) obj = self.get_object(kwargs)
obj = self.alter_obj(obj, request, args, kwargs) obj = self.alter_obj(obj, request, args, kwargs)
initial_data = {k: request.GET[k] for k in self.fields_initial if k in request.GET} form = self.form_class(instance=obj, initial=request.GET)
form = self.form_class(instance=obj, initial=initial_data)
return render(request, self.template_name, { return render(request, self.template_name, {
'obj': obj, 'obj': 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(obj), 'return_url': self.get_return_url(request, obj),
}) })
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@ -207,17 +214,22 @@ class ObjectEditView(View):
if '_addanother' in request.POST: if '_addanother' in request.POST:
return redirect(request.path) return redirect(request.path)
return redirect(self.get_return_url(obj))
return_url = form.cleaned_data.get('return_url')
if return_url is not None and is_safe_url(url=return_url, host=request.get_host()):
return redirect(return_url)
else:
return redirect(self.get_return_url(request, obj))
return render(request, self.template_name, { return render(request, self.template_name, {
'obj': obj, 'obj': 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(obj), 'return_url': self.get_return_url(request, obj),
}) })
class ObjectDeleteView(View): class ObjectDeleteView(GetReturnURLMixin, View):
""" """
Delete a single object. Delete a single object.
@ -227,7 +239,6 @@ class ObjectDeleteView(View):
""" """
model = None model = None
template_name = 'utilities/obj_delete.html' template_name = 'utilities/obj_delete.html'
default_return_url = 'home'
def get_object(self, kwargs): def get_object(self, kwargs):
# Look up object by slug if one has been provided. Otherwise, use PK. # Look up object by slug if one has been provided. Otherwise, use PK.
@ -236,24 +247,16 @@ class ObjectDeleteView(View):
else: else:
return get_object_or_404(self.model, pk=kwargs['pk']) return get_object_or_404(self.model, pk=kwargs['pk'])
def get_return_url(self, obj):
if obj.pk and hasattr(obj, 'get_absolute_url'):
return obj.get_absolute_url()
return reverse(self.default_return_url)
def get(self, request, **kwargs): def get(self, request, **kwargs):
obj = self.get_object(kwargs) obj = self.get_object(kwargs)
initial_data = { form = ConfirmationForm(initial=request.GET)
'return_url': request.GET.get('return_url'),
}
form = ConfirmationForm(initial=initial_data)
return render(request, self.template_name, { return render(request, self.template_name, {
'obj': obj, 'obj': obj,
'form': form, 'form': form,
'obj_type': self.model._meta.verbose_name, 'obj_type': self.model._meta.verbose_name,
'return_url': request.GET.get('return_url') or self.get_return_url(obj), 'return_url': self.get_return_url(request, obj),
}) })
def post(self, request, **kwargs): def post(self, request, **kwargs):
@ -272,17 +275,17 @@ class ObjectDeleteView(View):
messages.success(request, msg) messages.success(request, msg)
UserAction.objects.log_delete(request.user, obj, msg) UserAction.objects.log_delete(request.user, obj, msg)
return_url = form.cleaned_data['return_url'] return_url = form.cleaned_data.get('return_url')
if return_url and is_safe_url(url=return_url, host=request.get_host()): if return_url is not None and is_safe_url(url=return_url, host=request.get_host()):
return redirect(return_url) return redirect(return_url)
else: else:
return redirect(self.get_return_url(obj)) return redirect(self.get_return_url(request, obj))
return render(request, self.template_name, { return render(request, self.template_name, {
'obj': obj, 'obj': obj,
'form': form, 'form': form,
'obj_type': self.model._meta.verbose_name, 'obj_type': self.model._meta.verbose_name,
'return_url': request.GET.get('return_url') or self.get_return_url(obj), 'return_url': self.get_return_url(request, obj),
}) })