Explorar el Código

Merge pull request #736 from digitalocean/develop

Release v1.7.3
Jeremy Stretch hace 9 años
padre
commit
8eb140fd65

+ 4 - 4
netbox/circuits/views.py

@@ -42,7 +42,7 @@ class ProviderEditView(PermissionRequiredMixin, ObjectEditView):
     model = Provider
     form_class = forms.ProviderForm
     template_name = 'circuits/provider_edit.html'
-    cancel_url = 'circuits:provider_list'
+    obj_list_url = 'circuits:provider_list'
 
 
 class ProviderDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@@ -88,8 +88,8 @@ class CircuitTypeEditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'circuits.change_circuittype'
     model = CircuitType
     form_class = forms.CircuitTypeForm
-    success_url = 'circuits:circuittype_list'
-    cancel_url = 'circuits:circuittype_list'
+    obj_list_url = 'circuits:circuittype_list'
+    use_obj_view = False
 
 
 class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@@ -126,7 +126,7 @@ class CircuitEditView(PermissionRequiredMixin, ObjectEditView):
     form_class = forms.CircuitForm
     fields_initial = ['site']
     template_name = 'circuits/circuit_edit.html'
-    cancel_url = 'circuits:circuit_list'
+    obj_list_url = 'circuits:circuit_list'
 
 
 class CircuitDeleteView(PermissionRequiredMixin, ObjectDeleteView):

+ 5 - 1
netbox/dcim/admin.py

@@ -183,10 +183,14 @@ class DeviceAdmin(admin.ModelAdmin):
         DeviceBayAdmin,
         ModuleAdmin,
     ]
-    list_display = ['display_name', 'device_type', 'device_role', 'primary_ip', 'rack', 'position', 'asset_tag',
+    list_display = ['display_name', 'device_type_full_name', 'device_role', 'primary_ip', 'rack', 'position', 'asset_tag',
                     'serial']
     list_filter = ['device_role']
 
     def get_queryset(self, request):
         qs = super(DeviceAdmin, self).get_queryset(request)
         return qs.select_related('device_type__manufacturer', 'device_role', 'primary_ip4', 'primary_ip6', 'rack')
+
+    def device_type_full_name(self, obj):
+        return obj.device_type.full_name
+    device_type_full_name.short_description = 'Device type'

+ 4 - 1
netbox/dcim/filters.py

@@ -260,8 +260,11 @@ class DeviceFilter(CustomFieldFilterSet, django_filters.FilterSet):
         ).distinct()
 
     def _mac_address(self, queryset, value):
+        value = value.strip()
+        if not value:
+            return queryset
         try:
-            return queryset.filter(interfaces__mac_address=value.strip()).distinct()
+            return queryset.filter(interfaces__mac_address=value).distinct()
         except AddrFormatError:
             return queryset.none()
 

+ 1 - 1
netbox/dcim/forms.py

@@ -612,7 +612,7 @@ class DeviceFilterForm(BootstrapMixin, CustomFieldFilterForm):
     platform = FilterChoiceField(queryset=Platform.objects.annotate(filter_count=Count('devices')),
                                  to_field_name='slug', null_option=(0, 'None'))
     status = forms.NullBooleanField(required=False, widget=forms.Select(choices=FORM_STATUS_CHOICES))
-    mac_address = forms.CharField(label='MAC address')
+    mac_address = forms.CharField(required=False, label='MAC address')
 
 
 #

+ 5 - 1
netbox/dcim/models.py

@@ -561,7 +561,7 @@ class DeviceType(models.Model):
         ]
 
     def __unicode__(self):
-        return u'{} {}'.format(self.manufacturer, self.model)
+        return self.model
 
     def __init__(self, *args, **kwargs):
         super(DeviceType, self).__init__(*args, **kwargs)
@@ -617,6 +617,10 @@ class DeviceType(models.Model):
                 'u_height': "Child device types must be 0U."
             })
 
+    @property
+    def full_name(self):
+        return u'{} {}'.format(self.manufacturer.name, self.model)
+
     @property
     def is_parent_device(self):
         return bool(self.subdevice_role)

+ 4 - 2
netbox/dcim/tables.py

@@ -294,7 +294,8 @@ class PlatformTable(BaseTable):
     name = tables.LinkColumn(verbose_name='Name')
     device_count = tables.Column(verbose_name='Devices')
     slug = tables.Column(verbose_name='Slug')
-    actions = tables.TemplateColumn(template_code=PLATFORM_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='')
+    actions = tables.TemplateColumn(template_code=PLATFORM_ACTIONS, attrs={'td': {'class': 'text-right'}},
+                                    verbose_name='')
 
     class Meta(BaseTable.Meta):
         model = Platform
@@ -313,7 +314,8 @@ class DeviceTable(BaseTable):
     site = tables.Column(accessor=Accessor('rack.site'), verbose_name='Site')
     rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
     device_role = tables.TemplateColumn(DEVICE_ROLE, verbose_name='Role')
-    device_type = tables.Column(verbose_name='Type')
+    device_type = tables.LinkColumn('dcim:devicetype', args=[Accessor('device_type.pk')], verbose_name='Type',
+                                    text=lambda record: record.device_type.full_name)
     primary_ip = tables.TemplateColumn(orderable=False, verbose_name='IP Address',
                                        template_code="{{ record.primary_ip.address.ip }}")
 

+ 14 - 15
netbox/dcim/views.py

@@ -8,7 +8,6 @@ from django.contrib.auth.decorators import permission_required
 from django.contrib.auth.mixins import PermissionRequiredMixin
 from django.core.exceptions import ValidationError
 from django.core.urlresolvers import reverse
-from django.db import transaction
 from django.db.models import Count
 from django.http import HttpResponseRedirect
 from django.shortcuts import get_object_or_404, redirect, render
@@ -99,7 +98,7 @@ class SiteEditView(PermissionRequiredMixin, ObjectEditView):
     model = Site
     form_class = forms.SiteForm
     template_name = 'dcim/site_edit.html'
-    cancel_url = 'dcim:site_list'
+    obj_list_url = 'dcim:site_list'
 
 
 class SiteDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@@ -141,8 +140,8 @@ class RackGroupEditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'dcim.change_rackgroup'
     model = RackGroup
     form_class = forms.RackGroupForm
-    success_url = 'dcim:rackgroup_list'
-    cancel_url = 'dcim:rackgroup_list'
+    obj_list_url = 'dcim:rackgroup_list'
+    use_obj_view = False
 
 
 class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@@ -166,8 +165,8 @@ class RackRoleEditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'dcim.change_rackrole'
     model = RackRole
     form_class = forms.RackRoleForm
-    success_url = 'dcim:rackrole_list'
-    cancel_url = 'dcim:rackrole_list'
+    obj_list_url = 'dcim:rackrole_list'
+    use_obj_view = False
 
 
 class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@@ -214,7 +213,7 @@ class RackEditView(PermissionRequiredMixin, ObjectEditView):
     model = Rack
     form_class = forms.RackForm
     template_name = 'dcim/rack_edit.html'
-    cancel_url = 'dcim:rack_list'
+    obj_list_url = 'dcim:rack_list'
 
 
 class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@@ -260,8 +259,8 @@ class ManufacturerEditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'dcim.change_manufacturer'
     model = Manufacturer
     form_class = forms.ManufacturerForm
-    success_url = 'dcim:manufacturer_list'
-    cancel_url = 'dcim:manufacturer_list'
+    obj_list_url = 'dcim:manufacturer_list'
+    use_obj_view = False
 
 
 class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@@ -332,7 +331,7 @@ class DeviceTypeEditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'dcim.change_devicetype'
     model = DeviceType
     form_class = forms.DeviceTypeForm
-    cancel_url = 'dcim:devicetype_list'
+    obj_list_url = 'dcim:devicetype_list'
 
 
 class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@@ -497,8 +496,8 @@ class DeviceRoleEditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'dcim.change_devicerole'
     model = DeviceRole
     form_class = forms.DeviceRoleForm
-    success_url = 'dcim:devicerole_list'
-    cancel_url = 'dcim:devicerole_list'
+    obj_list_url = 'dcim:devicerole_list'
+    use_obj_view = False
 
 
 class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@@ -522,8 +521,8 @@ class PlatformEditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'dcim.change_platform'
     model = Platform
     form_class = forms.PlatformForm
-    success_url = 'dcim:platform_list'
-    cancel_url = 'dcim:platform_list'
+    obj_list_url = 'dcim:platform_list'
+    use_obj_view = False
 
 
 class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@@ -617,7 +616,7 @@ class DeviceEditView(PermissionRequiredMixin, ObjectEditView):
     form_class = forms.DeviceForm
     fields_initial = ['site', 'rack', 'position', 'face', 'device_bay']
     template_name = 'dcim/device_edit.html'
-    cancel_url = 'dcim:device_list'
+    obj_list_url = 'dcim:device_list'
 
 
 class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView):

+ 1 - 1
netbox/extras/forms.py

@@ -49,7 +49,7 @@ def get_custom_fields_for_model(content_type, filterable_only=False, bulk_edit=F
         # Select
         elif cf.type == CF_TYPE_SELECT:
             choices = [(cfc.pk, cfc) for cfc in cf.choices.all()]
-            if bulk_edit or filterable_only:
+            if not cf.required or bulk_edit or filterable_only:
                 choices = [(None, '---------')] + choices
             field = forms.TypedChoiceField(choices=choices, coerce=int, required=cf.required)
 

+ 11 - 12
netbox/ipam/views.py

@@ -1,4 +1,3 @@
-from collections import OrderedDict
 from django_tables2 import RequestConfig
 import netaddr
 
@@ -117,7 +116,7 @@ class VRFEditView(PermissionRequiredMixin, ObjectEditView):
     model = VRF
     form_class = forms.VRFForm
     template_name = 'ipam/vrf_edit.html'
-    cancel_url = 'ipam:vrf_list'
+    obj_list_url = 'ipam:vrf_list'
 
 
 class VRFDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@@ -241,8 +240,8 @@ class RIREditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'ipam.change_rir'
     model = RIR
     form_class = forms.RIRForm
-    success_url = 'ipam:rir_list'
-    cancel_url = 'ipam:rir_list'
+    obj_list_url = 'ipam:rir_list'
+    use_obj_view = False
 
 
 class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@@ -307,7 +306,7 @@ class AggregateEditView(PermissionRequiredMixin, ObjectEditView):
     model = Aggregate
     form_class = forms.AggregateForm
     template_name = 'ipam/aggregate_edit.html'
-    cancel_url = 'ipam:aggregate_list'
+    obj_list_url = 'ipam:aggregate_list'
 
 
 class AggregateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@@ -353,8 +352,8 @@ class RoleEditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'ipam.change_role'
     model = Role
     form_class = forms.RoleForm
-    success_url = 'ipam:role_list'
-    cancel_url = 'ipam:role_list'
+    obj_list_url = 'ipam:role_list'
+    use_obj_view = False
 
 
 class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@@ -438,7 +437,7 @@ class PrefixEditView(PermissionRequiredMixin, ObjectEditView):
     form_class = forms.PrefixForm
     template_name = 'ipam/prefix_edit.html'
     fields_initial = ['vrf', 'tenant', 'site', 'prefix', 'vlan']
-    cancel_url = 'ipam:prefix_list'
+    obj_list_url = 'ipam:prefix_list'
 
 
 class PrefixDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@@ -602,7 +601,7 @@ class IPAddressEditView(PermissionRequiredMixin, ObjectEditView):
     form_class = forms.IPAddressForm
     fields_initial = ['address', 'vrf']
     template_name = 'ipam/ipaddress_edit.html'
-    cancel_url = 'ipam:ipaddress_list'
+    obj_list_url = 'ipam:ipaddress_list'
 
 
 class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView):
@@ -665,8 +664,8 @@ class VLANGroupEditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'ipam.change_vlangroup'
     model = VLANGroup
     form_class = forms.VLANGroupForm
-    success_url = 'ipam:vlangroup_list'
-    cancel_url = 'ipam:vlangroup_list'
+    obj_list_url = 'ipam:vlangroup_list'
+    use_obj_view = False
 
 
 class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@@ -705,7 +704,7 @@ class VLANEditView(PermissionRequiredMixin, ObjectEditView):
     model = VLAN
     form_class = forms.VLANForm
     template_name = 'ipam/vlan_edit.html'
-    cancel_url = 'ipam:vlan_list'
+    obj_list_url = 'ipam:vlan_list'
 
 
 class VLANDeleteView(PermissionRequiredMixin, ObjectDeleteView):

+ 3 - 1
netbox/netbox/settings.py

@@ -12,7 +12,7 @@ except ImportError:
                                "the documentation.")
 
 
-VERSION = '1.7.2-r1'
+VERSION = '1.7.3'
 
 # Import local configuration
 for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
@@ -185,6 +185,8 @@ SECRETS_MIN_PUBKEY_SIZE = 2048
 REST_FRAMEWORK = {
     'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',)
 }
+if LOGIN_REQUIRED:
+    REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] = ('rest_framework.permissions.IsAuthenticated',)
 
 # Swagger settings (API docs)
 SWAGGER_SETTINGS = {

+ 2 - 2
netbox/secrets/views.py

@@ -30,8 +30,8 @@ class SecretRoleEditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'secrets.change_secretrole'
     model = SecretRole
     form_class = forms.SecretRoleForm
-    success_url = 'secrets:secretrole_list'
-    cancel_url = 'secrets:secretrole_list'
+    obj_list_url = 'secrets:secretrole_list'
+    use_obj_view = False
 
 
 class SecretRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):

+ 0 - 0
netbox/tempfile


+ 2 - 2
netbox/templates/dcim/device.html

@@ -56,7 +56,7 @@
                 <tr>
                     <td>Device Type</td>
                     <td>
-                        <span><a href="{% url 'dcim:devicetype' pk=device.device_type.pk %}">{{ device.device_type }}</a> ({{ device.device_type.u_height }}U)</span>
+                        <span><a href="{% url 'dcim:devicetype' pk=device.device_type.pk %}">{{ device.device_type.full_name }}</a> ({{ device.device_type.u_height }}U)</span>
                     </td>
                 </tr>
                 <tr>
@@ -293,7 +293,7 @@
                             <td>
                                 <a href="{% url 'dcim:rack' pk=rd.rack.pk %}">Rack {{ rd.rack }}</a>
                             </td>
-                            <td>{{ rd.device_type }}</td>
+                            <td>{{ rd.device_type.full_name }}</td>
                         </tr>
                     {% endfor %}
                 </table>

+ 1 - 1
netbox/templates/dcim/device_bulk_add_component.html

@@ -24,7 +24,7 @@
                     {% for device in selected_devices %}
                         <tr>
                             <td><a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a></td>
-                            <td>{{ device.device_type }}</td>
+                            <td>{{ device.device_type.full_name }}</td>
                             <td>{{ device.device_role }}</td>
                         </tr>
                     {% endfor %}

+ 1 - 1
netbox/templates/dcim/device_bulk_edit.html

@@ -14,7 +14,7 @@
     {% for device in selected_objects %}
         <tr>
             <td><a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a></td>
-            <td>{{ device.device_type }}</td>
+            <td>{{ device.device_type.full_name }}</td>
             <td>{{ device.device_role }}</td>
             <td>{{ device.tenant }}</td>
             <td>{{ device.serial }}</td>

+ 1 - 1
netbox/templates/dcim/device_inventory.html

@@ -13,7 +13,7 @@
             <table class="table table-hover panel-body">
                 <tr>
                     <td>Model</td>
-                    <td>{{ device.device_type }}</td>
+                    <td>{{ device.device_type.full_name }}</td>
                 </tr>
                 <tr>
                     <td>Serial Number</td>

+ 2 - 2
netbox/templates/dcim/devicetype.html

@@ -2,7 +2,7 @@
 {% load helpers %}
 {% load render_table from django_tables2 %}
 
-{% block title %}{{ devicetype }}{% endblock %}
+{% block title %}{{ devicetype.manufacturer }} {{ devicetype.model }}{% endblock %}
 
 {% block content %}
 <div class="row">
@@ -32,7 +32,7 @@
     </div>
 {% endif %}
 
-<h1>{{ devicetype }}</h1>
+<h1>{{ devicetype.manufacturer }} {{ devicetype.model }}</h1>
 <div class="row">
     <div class="col-md-6">
         <div class="panel panel-default">

+ 1 - 1
netbox/templates/dcim/inc/_devicebay.html

@@ -12,7 +12,7 @@
             <a href="{% url 'dcim:device' pk=devicebay.installed_device.pk %}">{{ devicebay.installed_device }}</a>
         </td>
         <td>
-            <span>{{ devicebay.installed_device.device_type }}</span>
+            <span>{{ devicebay.installed_device.device_type.full_name }}</span>
         </td>
     {% else %}
         <td colspan="2">

+ 1 - 1
netbox/templates/dcim/inc/_rack_elevation.html

@@ -24,7 +24,7 @@
                 <li class="occupied h{{ u.device.device_type.u_height }}u"{% ifequal u.device.face face_id %} style="background-color: #{{ u.device.device_role.color }}"{% endifequal %}>
                     {% ifequal u.device.face face_id %}
                         <a href="{% url 'dcim:device' pk=u.device.pk %}" data-toggle="popover" data-trigger="hover" data-container="body" data-html="true"
-                           data-content="{{ u.device.device_role }}<br />{{ u.device.device_type }} ({{ u.device.device_type.u_height }}U)">
+                           data-content="{{ u.device.device_role }}<br />{{ u.device.device_type.full_name }} ({{ u.device.device_type.u_height }}U)">
                             {{ u.device.name|default:u.device.device_role }}
                             {% if u.device.devicebay_count %}
                                 ({{ u.device.get_children.count }}/{{ u.device.devicebay_count }})

+ 8 - 2
netbox/templates/dcim/rack.html

@@ -153,8 +153,14 @@
                                 <a href="{% url 'dcim:device' pk=device.pk %}">{{ device.name }}</a>
                             </td>
                             <td>{{ device.device_role }}</td>
-                            <td>{{ device.device_type }}</td>
-                            <td>{% if device.parent_bay %}<a href="{{ device.parent_bay.device.get_absolute_url }}">{{ device.parent_bay }}</a>{% endif %}</td>
+                            <td>{{ device.device_type.full_name }}</td>
+                            <td>
+                                {% if device.parent_bay %}
+                                    <a href="{{ device.parent_bay.device.get_absolute_url }}">{{ device.parent_bay }}</a>
+                                {% else %}
+                                    <span class="text-muted">N/A</span>
+                                {% endif %}
+                            </td>
                         </tr>
                     {% endfor %}
                 </table>

+ 3 - 3
netbox/tenancy/views.py

@@ -28,8 +28,8 @@ class TenantGroupEditView(PermissionRequiredMixin, ObjectEditView):
     permission_required = 'tenancy.change_tenantgroup'
     model = TenantGroup
     form_class = forms.TenantGroupForm
-    success_url = 'tenancy:tenantgroup_list'
-    cancel_url = 'tenancy:tenantgroup_list'
+    obj_list_url = 'tenancy:tenantgroup_list'
+    use_obj_view = False
 
 
 class TenantGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
@@ -83,7 +83,7 @@ class TenantEditView(PermissionRequiredMixin, ObjectEditView):
     form_class = forms.TenantForm
     fields_initial = ['group']
     template_name = 'tenancy/tenant_edit.html'
-    cancel_url = 'tenancy:tenant_list'
+    obj_list_url = 'tenancy:tenant_list'
 
 
 class TenantDeleteView(PermissionRequiredMixin, ObjectDeleteView):

+ 5 - 1
netbox/utilities/middleware.py

@@ -2,6 +2,7 @@ from django.http import HttpResponseRedirect
 from django.conf import settings
 
 
+BASE_PATH = getattr(settings, 'BASE_PATH', False)
 LOGIN_REQUIRED = getattr(settings, 'LOGIN_REQUIRED', False)
 
 
@@ -11,5 +12,8 @@ class LoginRequiredMiddleware:
     """
     def process_request(self, request):
         if LOGIN_REQUIRED and not request.user.is_authenticated():
-            if request.path_info != settings.LOGIN_URL:
+            # Redirect unauthenticated requests to the login page. API requests are exempt from redirection as the API
+            # performs its own authentication.
+            api_path = '/{}api/'.format(BASE_PATH)
+            if not request.path_info.startswith(api_path) and request.path_info != settings.LOGIN_URL:
                 return HttpResponseRedirect('{}?next={}'.format(settings.LOGIN_URL, request.path_info))

+ 12 - 16
netbox/utilities/views.py

@@ -119,8 +119,8 @@ class ObjectEditView(View):
     form_class = None
     fields_initial = []
     template_name = 'utilities/obj_edit.html'
-    success_url = None
-    cancel_url = None
+    obj_list_url = None
+    use_obj_view = True
 
     def get_object(self, kwargs):
         # Look up object by slug if one has been provided. Otherwise, use PK.
@@ -129,12 +129,13 @@ class ObjectEditView(View):
         else:
             return get_object_or_404(self.model, pk=kwargs['pk'])
 
-    def get_cancel_url(self, obj):
-        if hasattr(obj, 'get_absolute_url'):
-            return obj.get_absolute_url()
-        if hasattr(obj, 'get_parent_url'):
-            return obj.get_parent_url()
-        return reverse(self.cancel_url)
+    def get_redirect_url(self, obj):
+        if obj and self.use_obj_view:
+            if hasattr(obj, 'get_absolute_url'):
+                return obj.get_absolute_url()
+            if hasattr(obj, 'get_parent_url'):
+                return obj.get_parent_url()
+        return reverse(self.obj_list_url)
 
     def get(self, request, *args, **kwargs):
 
@@ -149,7 +150,7 @@ class ObjectEditView(View):
             'obj': obj,
             'obj_type': self.model._meta.verbose_name,
             'form': form,
-            'cancel_url': self.get_cancel_url(obj),
+            'cancel_url': self.get_redirect_url(obj),
         })
 
     def post(self, request, *args, **kwargs):
@@ -179,18 +180,13 @@ class ObjectEditView(View):
 
             if '_addanother' in request.POST:
                 return redirect(request.path)
-            elif self.success_url:
-                return redirect(self.success_url)
-            elif hasattr(obj, 'get_absolute_url'):
-                return redirect(obj.get_absolute_url())
-            elif hasattr(obj, 'get_parent_url'):
-                return redirect(obj.get_parent_url())
+            return redirect(self.get_redirect_url(obj))
 
         return render(request, self.template_name, {
             'obj': obj,
             'obj_type': self.model._meta.verbose_name,
             'form': form,
-            'cancel_url': self.get_cancel_url(obj),
+            'cancel_url': self.get_redirect_url(obj),
         })