Browse Source

Fixes #1263: Differentiate add and edit permissions for objects

Jeremy Stretch 7 years ago
parent
commit
469c52be28

+ 4 - 4
netbox/circuits/urls.py

@@ -10,7 +10,7 @@ urlpatterns = [
 
     # Providers
     url(r'^providers/$', views.ProviderListView.as_view(), name='provider_list'),
-    url(r'^providers/add/$', views.ProviderEditView.as_view(), name='provider_add'),
+    url(r'^providers/add/$', views.ProviderCreateView.as_view(), name='provider_add'),
     url(r'^providers/import/$', views.ProviderBulkImportView.as_view(), name='provider_import'),
     url(r'^providers/edit/$', views.ProviderBulkEditView.as_view(), name='provider_bulk_edit'),
     url(r'^providers/delete/$', views.ProviderBulkDeleteView.as_view(), name='provider_bulk_delete'),
@@ -20,13 +20,13 @@ urlpatterns = [
 
     # Circuit types
     url(r'^circuit-types/$', views.CircuitTypeListView.as_view(), name='circuittype_list'),
-    url(r'^circuit-types/add/$', views.CircuitTypeEditView.as_view(), name='circuittype_add'),
+    url(r'^circuit-types/add/$', views.CircuitTypeCreateView.as_view(), name='circuittype_add'),
     url(r'^circuit-types/delete/$', views.CircuitTypeBulkDeleteView.as_view(), name='circuittype_bulk_delete'),
     url(r'^circuit-types/(?P<slug>[\w-]+)/edit/$', views.CircuitTypeEditView.as_view(), name='circuittype_edit'),
 
     # Circuits
     url(r'^circuits/$', views.CircuitListView.as_view(), name='circuit_list'),
-    url(r'^circuits/add/$', views.CircuitEditView.as_view(), name='circuit_add'),
+    url(r'^circuits/add/$', views.CircuitCreateView.as_view(), name='circuit_add'),
     url(r'^circuits/import/$', views.CircuitBulkImportView.as_view(), name='circuit_import'),
     url(r'^circuits/edit/$', views.CircuitBulkEditView.as_view(), name='circuit_bulk_edit'),
     url(r'^circuits/delete/$', views.CircuitBulkDeleteView.as_view(), name='circuit_bulk_delete'),
@@ -36,7 +36,7 @@ urlpatterns = [
     url(r'^circuits/(?P<pk>\d+)/terminations/swap/$', views.circuit_terminations_swap, name='circuit_terminations_swap'),
 
     # Circuit terminations
-    url(r'^circuits/(?P<circuit>\d+)/terminations/add/$', views.CircuitTerminationEditView.as_view(), name='circuittermination_add'),
+    url(r'^circuits/(?P<circuit>\d+)/terminations/add/$', views.CircuitTerminationCreateView.as_view(), name='circuittermination_add'),
     url(r'^circuit-terminations/(?P<pk>\d+)/edit/$', views.CircuitTerminationEditView.as_view(), name='circuittermination_edit'),
     url(r'^circuit-terminations/(?P<pk>\d+)/delete/$', views.CircuitTerminationDeleteView.as_view(), name='circuittermination_delete'),
 

+ 24 - 8
netbox/circuits/views.py

@@ -49,14 +49,18 @@ class ProviderView(View):
         })
 
 
-class ProviderEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'circuits.change_provider'
+class ProviderCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'circuits.add_provider'
     model = Provider
     form_class = forms.ProviderForm
     template_name = 'circuits/provider_edit.html'
     default_return_url = 'circuits:provider_list'
 
 
+class ProviderEditView(ProviderCreateView):
+    permission_required = 'circuits.change_provider'
+
+
 class ProviderDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'circuits.delete_provider'
     model = Provider
@@ -96,8 +100,8 @@ class CircuitTypeListView(ObjectListView):
     template_name = 'circuits/circuittype_list.html'
 
 
-class CircuitTypeEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'circuits.change_circuittype'
+class CircuitTypeCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'circuits.add_circuittype'
     model = CircuitType
     form_class = forms.CircuitTypeForm
 
@@ -105,6 +109,10 @@ class CircuitTypeEditView(PermissionRequiredMixin, ObjectEditView):
         return reverse('circuits:circuittype_list')
 
 
+class CircuitTypeEditView(CircuitTypeCreateView):
+    permission_required = 'circuits.change_circuittype'
+
+
 class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'circuits.delete_circuittype'
     cls = CircuitType
@@ -146,14 +154,18 @@ class CircuitView(View):
         })
 
 
-class CircuitEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'circuits.change_circuit'
+class CircuitCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'circuits.add_circuit'
     model = Circuit
     form_class = forms.CircuitForm
     template_name = 'circuits/circuit_edit.html'
     default_return_url = 'circuits:circuit_list'
 
 
+class CircuitEditView(CircuitCreateView):
+    permission_required = 'circuits.change_circuit'
+
+
 class CircuitDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'circuits.delete_circuit'
     model = Circuit
@@ -232,8 +244,8 @@ def circuit_terminations_swap(request, pk):
 # Circuit terminations
 #
 
-class CircuitTerminationEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'circuits.change_circuittermination'
+class CircuitTerminationCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'circuits.add_circuittermination'
     model = CircuitTermination
     form_class = forms.CircuitTerminationForm
     template_name = 'circuits/circuittermination_edit.html'
@@ -247,6 +259,10 @@ class CircuitTerminationEditView(PermissionRequiredMixin, ObjectEditView):
         return obj.circuit.get_absolute_url()
 
 
+class CircuitTerminationEditView(CircuitTerminationCreateView):
+    permission_required = 'circuits.change_circuittermination'
+
+
 class CircuitTerminationDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'circuits.delete_circuittermination'
     model = CircuitTermination

+ 12 - 12
netbox/dcim/urls.py

@@ -3,7 +3,7 @@ from __future__ import unicode_literals
 from django.conf.urls import url
 
 from extras.views import ImageAttachmentEditView
-from ipam.views import ServiceEditView
+from ipam.views import ServiceCreateView
 from secrets.views import secret_add
 from .models import Device, Rack, Site
 from . import views
@@ -14,13 +14,13 @@ urlpatterns = [
 
     # Regions
     url(r'^regions/$', views.RegionListView.as_view(), name='region_list'),
-    url(r'^regions/add/$', views.RegionEditView.as_view(), name='region_add'),
+    url(r'^regions/add/$', views.RegionCreateView.as_view(), name='region_add'),
     url(r'^regions/delete/$', views.RegionBulkDeleteView.as_view(), name='region_bulk_delete'),
     url(r'^regions/(?P<pk>\d+)/edit/$', views.RegionEditView.as_view(), name='region_edit'),
 
     # Sites
     url(r'^sites/$', views.SiteListView.as_view(), name='site_list'),
-    url(r'^sites/add/$', views.SiteEditView.as_view(), name='site_add'),
+    url(r'^sites/add/$', views.SiteCreateView.as_view(), name='site_add'),
     url(r'^sites/import/$', views.SiteBulkImportView.as_view(), name='site_import'),
     url(r'^sites/edit/$', views.SiteBulkEditView.as_view(), name='site_bulk_edit'),
     url(r'^sites/(?P<slug>[\w-]+)/$', views.SiteView.as_view(), name='site'),
@@ -30,13 +30,13 @@ urlpatterns = [
 
     # Rack groups
     url(r'^rack-groups/$', views.RackGroupListView.as_view(), name='rackgroup_list'),
-    url(r'^rack-groups/add/$', views.RackGroupEditView.as_view(), name='rackgroup_add'),
+    url(r'^rack-groups/add/$', views.RackGroupCreateView.as_view(), name='rackgroup_add'),
     url(r'^rack-groups/delete/$', views.RackGroupBulkDeleteView.as_view(), name='rackgroup_bulk_delete'),
     url(r'^rack-groups/(?P<pk>\d+)/edit/$', views.RackGroupEditView.as_view(), name='rackgroup_edit'),
 
     # Rack roles
     url(r'^rack-roles/$', views.RackRoleListView.as_view(), name='rackrole_list'),
-    url(r'^rack-roles/add/$', views.RackRoleEditView.as_view(), name='rackrole_add'),
+    url(r'^rack-roles/add/$', views.RackRoleCreateView.as_view(), name='rackrole_add'),
     url(r'^rack-roles/delete/$', views.RackRoleBulkDeleteView.as_view(), name='rackrole_bulk_delete'),
     url(r'^rack-roles/(?P<pk>\d+)/edit/$', views.RackRoleEditView.as_view(), name='rackrole_edit'),
 
@@ -56,18 +56,18 @@ urlpatterns = [
     url(r'^racks/(?P<pk>\d+)/$', views.RackView.as_view(), name='rack'),
     url(r'^racks/(?P<pk>\d+)/edit/$', views.RackEditView.as_view(), name='rack_edit'),
     url(r'^racks/(?P<pk>\d+)/delete/$', views.RackDeleteView.as_view(), name='rack_delete'),
-    url(r'^racks/(?P<rack>\d+)/reservations/add/$', views.RackReservationEditView.as_view(), name='rack_add_reservation'),
+    url(r'^racks/(?P<rack>\d+)/reservations/add/$', views.RackReservationCreateView.as_view(), name='rack_add_reservation'),
     url(r'^racks/(?P<object_id>\d+)/images/add/$', ImageAttachmentEditView.as_view(), name='rack_add_image', kwargs={'model': Rack}),
 
     # Manufacturers
     url(r'^manufacturers/$', views.ManufacturerListView.as_view(), name='manufacturer_list'),
-    url(r'^manufacturers/add/$', views.ManufacturerEditView.as_view(), name='manufacturer_add'),
+    url(r'^manufacturers/add/$', views.ManufacturerCreateView.as_view(), name='manufacturer_add'),
     url(r'^manufacturers/delete/$', views.ManufacturerBulkDeleteView.as_view(), name='manufacturer_bulk_delete'),
     url(r'^manufacturers/(?P<slug>[\w-]+)/edit/$', views.ManufacturerEditView.as_view(), name='manufacturer_edit'),
 
     # Device types
     url(r'^device-types/$', views.DeviceTypeListView.as_view(), name='devicetype_list'),
-    url(r'^device-types/add/$', views.DeviceTypeEditView.as_view(), name='devicetype_add'),
+    url(r'^device-types/add/$', views.DeviceTypeCreateView.as_view(), name='devicetype_add'),
     url(r'^device-types/edit/$', views.DeviceTypeBulkEditView.as_view(), name='devicetype_bulk_edit'),
     url(r'^device-types/delete/$', views.DeviceTypeBulkDeleteView.as_view(), name='devicetype_bulk_delete'),
     url(r'^device-types/(?P<pk>\d+)/$', views.DeviceTypeView.as_view(), name='devicetype'),
@@ -101,19 +101,19 @@ urlpatterns = [
 
     # Device roles
     url(r'^device-roles/$', views.DeviceRoleListView.as_view(), name='devicerole_list'),
-    url(r'^device-roles/add/$', views.DeviceRoleEditView.as_view(), name='devicerole_add'),
+    url(r'^device-roles/add/$', views.DeviceRoleCreateView.as_view(), name='devicerole_add'),
     url(r'^device-roles/delete/$', views.DeviceRoleBulkDeleteView.as_view(), name='devicerole_bulk_delete'),
     url(r'^device-roles/(?P<slug>[\w-]+)/edit/$', views.DeviceRoleEditView.as_view(), name='devicerole_edit'),
 
     # Platforms
     url(r'^platforms/$', views.PlatformListView.as_view(), name='platform_list'),
-    url(r'^platforms/add/$', views.PlatformEditView.as_view(), name='platform_add'),
+    url(r'^platforms/add/$', views.PlatformCreateView.as_view(), name='platform_add'),
     url(r'^platforms/delete/$', views.PlatformBulkDeleteView.as_view(), name='platform_bulk_delete'),
     url(r'^platforms/(?P<slug>[\w-]+)/edit/$', views.PlatformEditView.as_view(), name='platform_edit'),
 
     # Devices
     url(r'^devices/$', views.DeviceListView.as_view(), name='device_list'),
-    url(r'^devices/add/$', views.DeviceEditView.as_view(), name='device_add'),
+    url(r'^devices/add/$', views.DeviceCreateView.as_view(), name='device_add'),
     url(r'^devices/import/$', views.DeviceBulkImportView.as_view(), name='device_import'),
     url(r'^devices/import/child-devices/$', views.ChildDeviceBulkImportView.as_view(), name='device_import_child'),
     url(r'^devices/edit/$', views.DeviceBulkEditView.as_view(), name='device_bulk_edit'),
@@ -124,7 +124,7 @@ urlpatterns = [
     url(r'^devices/(?P<pk>\d+)/inventory/$', views.DeviceInventoryView.as_view(), name='device_inventory'),
     url(r'^devices/(?P<pk>\d+)/lldp-neighbors/$', views.DeviceLLDPNeighborsView.as_view(), name='device_lldp_neighbors'),
     url(r'^devices/(?P<pk>\d+)/add-secret/$', secret_add, name='device_addsecret'),
-    url(r'^devices/(?P<device>\d+)/services/assign/$', ServiceEditView.as_view(), name='service_assign'),
+    url(r'^devices/(?P<device>\d+)/services/assign/$', ServiceCreateView.as_view(), name='service_assign'),
     url(r'^devices/(?P<object_id>\d+)/images/add/$', ImageAttachmentEditView.as_view(), name='device_add_image', kwargs={'model': Device}),
 
     # Console ports

+ 66 - 23
netbox/dcim/views.py

@@ -1,6 +1,5 @@
 from __future__ import unicode_literals
 from copy import deepcopy
-from difflib import SequenceMatcher
 import re
 from natsort import natsorted
 from operator import attrgetter
@@ -152,8 +151,8 @@ class RegionListView(ObjectListView):
     template_name = 'dcim/region_list.html'
 
 
-class RegionEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'dcim.change_region'
+class RegionCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'dcim.add_region'
     model = Region
     form_class = forms.RegionForm
 
@@ -161,6 +160,10 @@ class RegionEditView(PermissionRequiredMixin, ObjectEditView):
         return reverse('dcim:region_list')
 
 
+class RegionEditView(RegionCreateView):
+    permission_required = 'dcim.change_region'
+
+
 class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_region'
     cls = Region
@@ -204,14 +207,18 @@ class SiteView(View):
         })
 
 
-class SiteEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'dcim.change_site'
+class SiteCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'dcim.add_site'
     model = Site
     form_class = forms.SiteForm
     template_name = 'dcim/site_edit.html'
     default_return_url = 'dcim:site_list'
 
 
+class SiteEditView(SiteCreateView):
+    permission_required = 'dcim.change_site'
+
+
 class SiteDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'dcim.delete_site'
     model = Site
@@ -246,8 +253,8 @@ class RackGroupListView(ObjectListView):
     template_name = 'dcim/rackgroup_list.html'
 
 
-class RackGroupEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'dcim.change_rackgroup'
+class RackGroupCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'dcim.add_rackgroup'
     model = RackGroup
     form_class = forms.RackGroupForm
 
@@ -255,6 +262,10 @@ class RackGroupEditView(PermissionRequiredMixin, ObjectEditView):
         return reverse('dcim:rackgroup_list')
 
 
+class RackGroupEditView(RackGroupCreateView):
+    permission_required = 'dcim.change_rackgroup'
+
+
 class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_rackgroup'
     cls = RackGroup
@@ -272,8 +283,8 @@ class RackRoleListView(ObjectListView):
     template_name = 'dcim/rackrole_list.html'
 
 
-class RackRoleEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'dcim.change_rackrole'
+class RackRoleCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'dcim.add_rackrole'
     model = RackRole
     form_class = forms.RackRoleForm
 
@@ -281,6 +292,10 @@ class RackRoleEditView(PermissionRequiredMixin, ObjectEditView):
         return reverse('dcim:rackrole_list')
 
 
+class RackRoleEditView(RackRoleCreateView):
+    permission_required = 'dcim.change_rackrole'
+
+
 class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_rackrole'
     cls = RackRole
@@ -374,14 +389,18 @@ class RackView(View):
         })
 
 
-class RackEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'dcim.change_rack'
+class RackCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'dcim.add_rack'
     model = Rack
     form_class = forms.RackForm
     template_name = 'dcim/rack_edit.html'
     default_return_url = 'dcim:rack_list'
 
 
+class RackEditView(RackCreateView):
+    permission_required = 'dcim.change_rack'
+
+
 class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'dcim.delete_rack'
     model = Rack
@@ -423,8 +442,8 @@ class RackReservationListView(ObjectListView):
     template_name = 'dcim/rackreservation_list.html'
 
 
-class RackReservationEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'dcim.change_rackreservation'
+class RackReservationCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'dcim.add_rackreservation'
     model = RackReservation
     form_class = forms.RackReservationForm
 
@@ -438,6 +457,10 @@ class RackReservationEditView(PermissionRequiredMixin, ObjectEditView):
         return obj.rack.get_absolute_url()
 
 
+class RackReservationEditView(RackReservationCreateView):
+    permission_required = 'dcim.change_rackreservation'
+
+
 class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'dcim.delete_rackreservation'
     model = RackReservation
@@ -462,8 +485,8 @@ class ManufacturerListView(ObjectListView):
     template_name = 'dcim/manufacturer_list.html'
 
 
-class ManufacturerEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'dcim.change_manufacturer'
+class ManufacturerCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'dcim.add_manufacturer'
     model = Manufacturer
     form_class = forms.ManufacturerForm
 
@@ -471,6 +494,10 @@ class ManufacturerEditView(PermissionRequiredMixin, ObjectEditView):
         return reverse('dcim:manufacturer_list')
 
 
+class ManufacturerEditView(ManufacturerCreateView):
+    permission_required = 'dcim.change_manufacturer'
+
+
 class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_manufacturer'
     cls = Manufacturer
@@ -542,14 +569,18 @@ class DeviceTypeView(View):
         })
 
 
-class DeviceTypeEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'dcim.change_devicetype'
+class DeviceTypeCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'dcim.add_devicetype'
     model = DeviceType
     form_class = forms.DeviceTypeForm
     template_name = 'dcim/devicetype_edit.html'
     default_return_url = 'dcim:devicetype_list'
 
 
+class DeviceTypeEditView(DeviceTypeCreateView):
+    permission_required = 'dcim.change_devicetype'
+
+
 class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'dcim.delete_devicetype'
     model = DeviceType
@@ -686,8 +717,8 @@ class DeviceRoleListView(ObjectListView):
     template_name = 'dcim/devicerole_list.html'
 
 
-class DeviceRoleEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'dcim.change_devicerole'
+class DeviceRoleCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'dcim.add_devicerole'
     model = DeviceRole
     form_class = forms.DeviceRoleForm
 
@@ -695,6 +726,10 @@ class DeviceRoleEditView(PermissionRequiredMixin, ObjectEditView):
         return reverse('dcim:devicerole_list')
 
 
+class DeviceRoleEditView(DeviceRoleCreateView):
+    permission_required = 'dcim.change_devicerole'
+
+
 class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_devicerole'
     cls = DeviceRole
@@ -711,8 +746,8 @@ class PlatformListView(ObjectListView):
     template_name = 'dcim/platform_list.html'
 
 
-class PlatformEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'dcim.change_platform'
+class PlatformCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'dcim.add_platform'
     model = Platform
     form_class = forms.PlatformForm
 
@@ -720,6 +755,10 @@ class PlatformEditView(PermissionRequiredMixin, ObjectEditView):
         return reverse('dcim:platform_list')
 
 
+class PlatformEditView(PlatformCreateView):
+    permission_required = 'dcim.change_platform'
+
+
 class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'dcim.delete_platform'
     cls = Platform
@@ -843,14 +882,18 @@ class DeviceLLDPNeighborsView(View):
         })
 
 
-class DeviceEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'dcim.change_device'
+class DeviceCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'dcim.add_device'
     model = Device
     form_class = forms.DeviceForm
     template_name = 'dcim/device_edit.html'
     default_return_url = 'dcim:device_list'
 
 
+class DeviceEditView(DeviceCreateView):
+    permission_required = 'dcim.change_device'
+
+
 class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'dcim.delete_device'
     model = Device

+ 8 - 8
netbox/ipam/urls.py

@@ -10,7 +10,7 @@ urlpatterns = [
 
     # VRFs
     url(r'^vrfs/$', views.VRFListView.as_view(), name='vrf_list'),
-    url(r'^vrfs/add/$', views.VRFEditView.as_view(), name='vrf_add'),
+    url(r'^vrfs/add/$', views.VRFCreateView.as_view(), name='vrf_add'),
     url(r'^vrfs/import/$', views.VRFBulkImportView.as_view(), name='vrf_import'),
     url(r'^vrfs/edit/$', views.VRFBulkEditView.as_view(), name='vrf_bulk_edit'),
     url(r'^vrfs/delete/$', views.VRFBulkDeleteView.as_view(), name='vrf_bulk_delete'),
@@ -20,13 +20,13 @@ urlpatterns = [
 
     # RIRs
     url(r'^rirs/$', views.RIRListView.as_view(), name='rir_list'),
-    url(r'^rirs/add/$', views.RIREditView.as_view(), name='rir_add'),
+    url(r'^rirs/add/$', views.RIRCreateView.as_view(), name='rir_add'),
     url(r'^rirs/delete/$', views.RIRBulkDeleteView.as_view(), name='rir_bulk_delete'),
     url(r'^rirs/(?P<slug>[\w-]+)/edit/$', views.RIREditView.as_view(), name='rir_edit'),
 
     # Aggregates
     url(r'^aggregates/$', views.AggregateListView.as_view(), name='aggregate_list'),
-    url(r'^aggregates/add/$', views.AggregateEditView.as_view(), name='aggregate_add'),
+    url(r'^aggregates/add/$', views.AggregateCreateView.as_view(), name='aggregate_add'),
     url(r'^aggregates/import/$', views.AggregateBulkImportView.as_view(), name='aggregate_import'),
     url(r'^aggregates/edit/$', views.AggregateBulkEditView.as_view(), name='aggregate_bulk_edit'),
     url(r'^aggregates/delete/$', views.AggregateBulkDeleteView.as_view(), name='aggregate_bulk_delete'),
@@ -36,13 +36,13 @@ urlpatterns = [
 
     # Roles
     url(r'^roles/$', views.RoleListView.as_view(), name='role_list'),
-    url(r'^roles/add/$', views.RoleEditView.as_view(), name='role_add'),
+    url(r'^roles/add/$', views.RoleCreateView.as_view(), name='role_add'),
     url(r'^roles/delete/$', views.RoleBulkDeleteView.as_view(), name='role_bulk_delete'),
     url(r'^roles/(?P<slug>[\w-]+)/edit/$', views.RoleEditView.as_view(), name='role_edit'),
 
     # Prefixes
     url(r'^prefixes/$', views.PrefixListView.as_view(), name='prefix_list'),
-    url(r'^prefixes/add/$', views.PrefixEditView.as_view(), name='prefix_add'),
+    url(r'^prefixes/add/$', views.PrefixCreateView.as_view(), name='prefix_add'),
     url(r'^prefixes/import/$', views.PrefixBulkImportView.as_view(), name='prefix_import'),
     url(r'^prefixes/edit/$', views.PrefixBulkEditView.as_view(), name='prefix_bulk_edit'),
     url(r'^prefixes/delete/$', views.PrefixBulkDeleteView.as_view(), name='prefix_bulk_delete'),
@@ -53,7 +53,7 @@ urlpatterns = [
 
     # IP addresses
     url(r'^ip-addresses/$', views.IPAddressListView.as_view(), name='ipaddress_list'),
-    url(r'^ip-addresses/add/$', views.IPAddressEditView.as_view(), name='ipaddress_add'),
+    url(r'^ip-addresses/add/$', views.IPAddressCreateView.as_view(), name='ipaddress_add'),
     url(r'^ip-addresses/bulk-add/$', views.IPAddressBulkAddView.as_view(), name='ipaddress_bulk_add'),
     url(r'^ip-addresses/import/$', views.IPAddressBulkImportView.as_view(), name='ipaddress_import'),
     url(r'^ip-addresses/edit/$', views.IPAddressBulkEditView.as_view(), name='ipaddress_bulk_edit'),
@@ -64,13 +64,13 @@ urlpatterns = [
 
     # VLAN groups
     url(r'^vlan-groups/$', views.VLANGroupListView.as_view(), name='vlangroup_list'),
-    url(r'^vlan-groups/add/$', views.VLANGroupEditView.as_view(), name='vlangroup_add'),
+    url(r'^vlan-groups/add/$', views.VLANGroupCreateView.as_view(), name='vlangroup_add'),
     url(r'^vlan-groups/delete/$', views.VLANGroupBulkDeleteView.as_view(), name='vlangroup_bulk_delete'),
     url(r'^vlan-groups/(?P<pk>\d+)/edit/$', views.VLANGroupEditView.as_view(), name='vlangroup_edit'),
 
     # VLANs
     url(r'^vlans/$', views.VLANListView.as_view(), name='vlan_list'),
-    url(r'^vlans/add/$', views.VLANEditView.as_view(), name='vlan_add'),
+    url(r'^vlans/add/$', views.VLANCreateView.as_view(), name='vlan_add'),
     url(r'^vlans/import/$', views.VLANBulkImportView.as_view(), name='vlan_import'),
     url(r'^vlans/edit/$', views.VLANBulkEditView.as_view(), name='vlan_bulk_edit'),
     url(r'^vlans/delete/$', views.VLANBulkDeleteView.as_view(), name='vlan_bulk_delete'),

+ 54 - 18
netbox/ipam/views.py

@@ -114,14 +114,18 @@ class VRFView(View):
         })
 
 
-class VRFEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'ipam.change_vrf'
+class VRFCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'ipam.add_vrf'
     model = VRF
     form_class = forms.VRFForm
     template_name = 'ipam/vrf_edit.html'
     default_return_url = 'ipam:vrf_list'
 
 
+class VRFEditView(VRFCreateView):
+    permission_required = 'ipam.change_vrf'
+
+
 class VRFDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'ipam.delete_vrf'
     model = VRF
@@ -239,8 +243,8 @@ class RIRListView(ObjectListView):
         }
 
 
-class RIREditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'ipam.change_rir'
+class RIRCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'ipam.add_rir'
     model = RIR
     form_class = forms.RIRForm
 
@@ -248,6 +252,10 @@ class RIREditView(PermissionRequiredMixin, ObjectEditView):
         return reverse('ipam:rir_list')
 
 
+class RIREditView(RIRCreateView):
+    permission_required = 'ipam.change_rir'
+
+
 class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'ipam.delete_rir'
     cls = RIR
@@ -324,14 +332,18 @@ class AggregateView(View):
         })
 
 
-class AggregateEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'ipam.change_aggregate'
+class AggregateCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'ipam.add_aggregate'
     model = Aggregate
     form_class = forms.AggregateForm
     template_name = 'ipam/aggregate_edit.html'
     default_return_url = 'ipam:aggregate_list'
 
 
+class AggregateEditView(AggregateCreateView):
+    permission_required = 'ipam.change_aggregate'
+
+
 class AggregateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'ipam.delete_aggregate'
     model = Aggregate
@@ -371,8 +383,8 @@ class RoleListView(ObjectListView):
     template_name = 'ipam/role_list.html'
 
 
-class RoleEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'ipam.change_role'
+class RoleCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'ipam.add_role'
     model = Role
     form_class = forms.RoleForm
 
@@ -380,6 +392,10 @@ class RoleEditView(PermissionRequiredMixin, ObjectEditView):
         return reverse('ipam:role_list')
 
 
+class RoleEditView(RoleCreateView):
+    permission_required = 'ipam.change_role'
+
+
 class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'ipam.delete_role'
     cls = Role
@@ -519,14 +535,18 @@ class PrefixIPAddressesView(View):
         })
 
 
-class PrefixEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'ipam.change_prefix'
+class PrefixCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'ipam.add_prefix'
     model = Prefix
     form_class = forms.PrefixForm
     template_name = 'ipam/prefix_edit.html'
     default_return_url = 'ipam:prefix_list'
 
 
+class PrefixEditView(PrefixCreateView):
+    permission_required = 'ipam.change_prefix'
+
+
 class PrefixDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'ipam.delete_prefix'
     model = Prefix
@@ -612,14 +632,18 @@ class IPAddressView(View):
         })
 
 
-class IPAddressEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'ipam.change_ipaddress'
+class IPAddressCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'ipam.add_ipaddress'
     model = IPAddress
     form_class = forms.IPAddressForm
     template_name = 'ipam/ipaddress_edit.html'
     default_return_url = 'ipam:ipaddress_list'
 
 
+class IPAddressEditView(IPAddressCreateView):
+    permission_required = 'ipam.change_ipaddress'
+
+
 class IPAddressDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'ipam.delete_ipaddress'
     model = IPAddress
@@ -683,8 +707,8 @@ class VLANGroupListView(ObjectListView):
     template_name = 'ipam/vlangroup_list.html'
 
 
-class VLANGroupEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'ipam.change_vlangroup'
+class VLANGroupCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'ipam.add_vlangroup'
     model = VLANGroup
     form_class = forms.VLANGroupForm
 
@@ -692,6 +716,10 @@ class VLANGroupEditView(PermissionRequiredMixin, ObjectEditView):
         return reverse('ipam:vlangroup_list')
 
 
+class VLANGroupEditView(VLANGroupCreateView):
+    permission_required = 'ipam.change_vlangroup'
+
+
 class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'ipam.delete_vlangroup'
     cls = VLANGroup
@@ -728,14 +756,18 @@ class VLANView(View):
         })
 
 
-class VLANEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'ipam.change_vlan'
+class VLANCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'ipam.add_vlan'
     model = VLAN
     form_class = forms.VLANForm
     template_name = 'ipam/vlan_edit.html'
     default_return_url = 'ipam:vlan_list'
 
 
+class VLANEditView(VLANCreateView):
+    permission_required = 'ipam.change_vlan'
+
+
 class VLANDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'ipam.delete_vlan'
     model = VLAN
@@ -769,8 +801,8 @@ class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Services
 #
 
-class ServiceEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'ipam.change_service'
+class ServiceCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'ipam.add_service'
     model = Service
     form_class = forms.ServiceForm
     template_name = 'ipam/service_edit.html'
@@ -784,6 +816,10 @@ class ServiceEditView(PermissionRequiredMixin, ObjectEditView):
         return obj.device.get_absolute_url()
 
 
+class ServiceEditView(ServiceCreateView):
+    permission_required = 'ipam.change_service'
+
+
 class ServiceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'ipam.delete_service'
     model = Service

+ 1 - 1
netbox/secrets/urls.py

@@ -10,7 +10,7 @@ urlpatterns = [
 
     # Secret roles
     url(r'^secret-roles/$', views.SecretRoleListView.as_view(), name='secretrole_list'),
-    url(r'^secret-roles/add/$', views.SecretRoleEditView.as_view(), name='secretrole_add'),
+    url(r'^secret-roles/add/$', views.SecretRoleCreateView.as_view(), name='secretrole_add'),
     url(r'^secret-roles/delete/$', views.SecretRoleBulkDeleteView.as_view(), name='secretrole_bulk_delete'),
     url(r'^secret-roles/(?P<slug>[\w-]+)/edit/$', views.SecretRoleEditView.as_view(), name='secretrole_edit'),
 

+ 6 - 2
netbox/secrets/views.py

@@ -40,8 +40,8 @@ class SecretRoleListView(ObjectListView):
     template_name = 'secrets/secretrole_list.html'
 
 
-class SecretRoleEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'secrets.change_secretrole'
+class SecretRoleCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'secrets.add_secretrole'
     model = SecretRole
     form_class = forms.SecretRoleForm
 
@@ -49,6 +49,10 @@ class SecretRoleEditView(PermissionRequiredMixin, ObjectEditView):
         return reverse('secrets:secretrole_list')
 
 
+class SecretRoleEditView(SecretRoleCreateView):
+    permission_required = 'secrets.change_secretrole'
+
+
 class SecretRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'secrets.delete_secretrole'
     cls = SecretRole

+ 2 - 2
netbox/tenancy/urls.py

@@ -10,13 +10,13 @@ urlpatterns = [
 
     # Tenant groups
     url(r'^tenant-groups/$', views.TenantGroupListView.as_view(), name='tenantgroup_list'),
-    url(r'^tenant-groups/add/$', views.TenantGroupEditView.as_view(), name='tenantgroup_add'),
+    url(r'^tenant-groups/add/$', views.TenantGroupCreateView.as_view(), name='tenantgroup_add'),
     url(r'^tenant-groups/delete/$', views.TenantGroupBulkDeleteView.as_view(), name='tenantgroup_bulk_delete'),
     url(r'^tenant-groups/(?P<slug>[\w-]+)/edit/$', views.TenantGroupEditView.as_view(), name='tenantgroup_edit'),
 
     # Tenants
     url(r'^tenants/$', views.TenantListView.as_view(), name='tenant_list'),
-    url(r'^tenants/add/$', views.TenantEditView.as_view(), name='tenant_add'),
+    url(r'^tenants/add/$', views.TenantCreateView.as_view(), name='tenant_add'),
     url(r'^tenants/import/$', views.TenantBulkImportView.as_view(), name='tenant_import'),
     url(r'^tenants/edit/$', views.TenantBulkEditView.as_view(), name='tenant_bulk_edit'),
     url(r'^tenants/delete/$', views.TenantBulkDeleteView.as_view(), name='tenant_bulk_delete'),

+ 12 - 4
netbox/tenancy/views.py

@@ -26,8 +26,8 @@ class TenantGroupListView(ObjectListView):
     template_name = 'tenancy/tenantgroup_list.html'
 
 
-class TenantGroupEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'tenancy.change_tenantgroup'
+class TenantGroupCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'tenancy.add_tenantgroup'
     model = TenantGroup
     form_class = forms.TenantGroupForm
 
@@ -35,6 +35,10 @@ class TenantGroupEditView(PermissionRequiredMixin, ObjectEditView):
         return reverse('tenancy:tenantgroup_list')
 
 
+class TenantGroupEditView(TenantGroupCreateView):
+    permission_required = 'tenancy.change_tenantgroup'
+
+
 class TenantGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     permission_required = 'tenancy.delete_tenantgroup'
     cls = TenantGroup
@@ -81,14 +85,18 @@ class TenantView(View):
         })
 
 
-class TenantEditView(PermissionRequiredMixin, ObjectEditView):
-    permission_required = 'tenancy.change_tenant'
+class TenantCreateView(PermissionRequiredMixin, ObjectEditView):
+    permission_required = 'tenancy.add_tenant'
     model = Tenant
     form_class = forms.TenantForm
     template_name = 'tenancy/tenant_edit.html'
     default_return_url = 'tenancy:tenant_list'
 
 
+class TenantEditView(TenantCreateView):
+    permission_required = 'tenancy.change_tenant'
+
+
 class TenantDeleteView(PermissionRequiredMixin, ObjectDeleteView):
     permission_required = 'tenancy.delete_tenant'
     model = Tenant