Procházet zdrojové kódy

Transition ObjectListView to use ObjectPermissionRequiredMixin

Jeremy Stretch před 5 roky
rodič
revize
993ee8c900

+ 3 - 6
netbox/circuits/views.py

@@ -23,8 +23,7 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
 # Providers
 #
 
-class ProviderListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'circuits.view_provider'
+class ProviderListView(ObjectListView):
     queryset = Provider.objects.annotate(count_circuits=Count('circuits'))
     filterset = filters.ProviderFilterSet
     filterset_form = forms.ProviderFilterForm
@@ -107,8 +106,7 @@ class ProviderBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Circuit Types
 #
 
-class CircuitTypeListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'circuits.view_circuittype'
+class CircuitTypeListView(ObjectListView):
     queryset = CircuitType.objects.annotate(circuit_count=Count('circuits'))
     table = tables.CircuitTypeTable
 
@@ -143,8 +141,7 @@ class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Circuits
 #
 
-class CircuitListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'circuits.view_circuit'
+class CircuitListView(ObjectListView):
     _terminations = CircuitTermination.objects.filter(circuit=OuterRef('pk'))
     queryset = Circuit.objects.prefetch_related(
         'provider', 'type', 'tenant', 'terminations__site'

+ 27 - 52
netbox/dcim/views.py

@@ -141,8 +141,7 @@ class BulkDisconnectView(GetReturnURLMixin, View):
 # Regions
 #
 
-class RegionListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_region'
+class RegionListView(ObjectListView):
     queryset = Region.objects.add_related_count(
         Region.objects.all(),
         Site,
@@ -186,8 +185,7 @@ class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Sites
 #
 
-class SiteListView(ObjectPermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_site'
+class SiteListView(ObjectListView):
     queryset = Site.objects.prefetch_related('region', 'tenant')
     filterset = filters.SiteFilterSet
     filterset_form = forms.SiteFilterForm
@@ -267,8 +265,7 @@ class SiteBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Rack groups
 #
 
-class RackGroupListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_rackgroup'
+class RackGroupListView(ObjectListView):
     queryset = RackGroup.objects.add_related_count(
         RackGroup.objects.all(),
         Rack,
@@ -312,8 +309,7 @@ class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Rack roles
 #
 
-class RackRoleListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_rackrole'
+class RackRoleListView(ObjectListView):
     queryset = RackRole.objects.annotate(rack_count=Count('racks'))
     table = tables.RackRoleTable
 
@@ -348,8 +344,7 @@ class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Racks
 #
 
-class RackListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_rack'
+class RackListView(ObjectListView):
     queryset = Rack.objects.prefetch_related(
         'site', 'group', 'tenant', 'role', 'devices__device_type'
     ).annotate(
@@ -476,8 +471,7 @@ class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Rack reservations
 #
 
-class RackReservationListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_rackreservation'
+class RackReservationListView(ObjectListView):
     queryset = RackReservation.objects.prefetch_related('rack__site')
     filterset = filters.RackReservationFilterSet
     filterset_form = forms.RackReservationFilterForm
@@ -561,8 +555,7 @@ class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Manufacturers
 #
 
-class ManufacturerListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_manufacturer'
+class ManufacturerListView(ObjectListView):
     queryset = Manufacturer.objects.annotate(
         devicetype_count=Count('device_types', distinct=True),
         inventoryitem_count=Count('inventory_items', distinct=True),
@@ -601,8 +594,7 @@ class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Device types
 #
 
-class DeviceTypeListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_devicetype'
+class DeviceTypeListView(ObjectListView):
     queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
     filterset = filters.DeviceTypeFilterSet
     filterset_form = forms.DeviceTypeFilterForm
@@ -1026,8 +1018,7 @@ class DeviceBayTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Device roles
 #
 
-class DeviceRoleListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_devicerole'
+class DeviceRoleListView(ObjectListView):
     queryset = DeviceRole.objects.all()
     table = tables.DeviceRoleTable
 
@@ -1062,8 +1053,7 @@ class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Platforms
 #
 
-class PlatformListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_platform'
+class PlatformListView(ObjectListView):
     queryset = Platform.objects.all()
     table = tables.PlatformTable
 
@@ -1098,8 +1088,7 @@ class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Devices
 #
 
-class DeviceListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_device'
+class DeviceListView(ObjectListView):
     queryset = Device.objects.prefetch_related(
         'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6'
     )
@@ -1323,8 +1312,7 @@ class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Console ports
 #
 
-class ConsolePortListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_consoleport'
+class ConsolePortListView(ObjectListView):
     queryset = ConsolePort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
     filterset = filters.ConsolePortFilterSet
     filterset_form = forms.ConsolePortFilterForm
@@ -1379,8 +1367,7 @@ class ConsolePortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Console server ports
 #
 
-class ConsoleServerPortListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_consoleserverport'
+class ConsoleServerPortListView(ObjectListView):
     queryset = ConsoleServerPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
     filterset = filters.ConsoleServerPortFilterSet
     filterset_form = forms.ConsoleServerPortFilterForm
@@ -1447,8 +1434,7 @@ class ConsoleServerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Power ports
 #
 
-class PowerPortListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_powerport'
+class PowerPortListView(ObjectListView):
     queryset = PowerPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
     filterset = filters.PowerPortFilterSet
     filterset_form = forms.PowerPortFilterForm
@@ -1503,8 +1489,7 @@ class PowerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Power outlets
 #
 
-class PowerOutletListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_poweroutlet'
+class PowerOutletListView(ObjectListView):
     queryset = PowerOutlet.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
     filterset = filters.PowerOutletFilterSet
     filterset_form = forms.PowerOutletFilterForm
@@ -1571,8 +1556,7 @@ class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Interfaces
 #
 
-class InterfaceListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_interface'
+class InterfaceListView(ObjectListView):
     queryset = Interface.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
     filterset = filters.InterfaceFilterSet
     filterset_form = forms.InterfaceFilterForm
@@ -1676,8 +1660,7 @@ class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Front ports
 #
 
-class FrontPortListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_frontport'
+class FrontPortListView(ObjectListView):
     queryset = FrontPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
     filterset = filters.FrontPortFilterSet
     filterset_form = forms.FrontPortFilterForm
@@ -1744,8 +1727,7 @@ class FrontPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Rear ports
 #
 
-class RearPortListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_rearport'
+class RearPortListView(ObjectListView):
     queryset = RearPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
     filterset = filters.RearPortFilterSet
     filterset_form = forms.RearPortFilterForm
@@ -1812,8 +1794,7 @@ class RearPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Device bays
 #
 
-class DeviceBayListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_devicebay'
+class DeviceBayListView(ObjectListView):
     queryset = DeviceBay.objects.prefetch_related(
         'device', 'device__site', 'installed_device', 'installed_device__site'
     )
@@ -2045,8 +2026,7 @@ class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateVie
 # Cables
 #
 
-class CableListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_cable'
+class CableListView(ObjectListView):
     queryset = Cable.objects.prefetch_related(
         'termination_a', 'termination_b'
     )
@@ -2215,7 +2195,7 @@ class CableBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Connections
 #
 
-class ConsoleConnectionsListView(PermissionRequiredMixin, ObjectListView):
+class ConsoleConnectionsListView(ObjectListView):
     permission_required = ('dcim.view_consoleport', 'dcim.view_consoleserverport')
     queryset = ConsolePort.objects.prefetch_related(
         'device', 'connected_endpoint__device'
@@ -2247,7 +2227,7 @@ class ConsoleConnectionsListView(PermissionRequiredMixin, ObjectListView):
         return '\n'.join(csv_data)
 
 
-class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
+class PowerConnectionsListView(ObjectListView):
     permission_required = ('dcim.view_powerport', 'dcim.view_poweroutlet')
     queryset = PowerPort.objects.prefetch_related(
         'device', '_connected_poweroutlet__device'
@@ -2279,8 +2259,7 @@ class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
         return '\n'.join(csv_data)
 
 
-class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_interface'
+class InterfaceConnectionsListView(ObjectListView):
     queryset = Interface.objects.prefetch_related(
         'device', 'cable', '_connected_interface__device'
     ).filter(
@@ -2319,8 +2298,7 @@ class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
 # Inventory items
 #
 
-class InventoryItemListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_inventoryitem'
+class InventoryItemListView(ObjectListView):
     queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
     filterset = filters.InventoryItemFilterSet
     filterset_form = forms.InventoryItemFilterForm
@@ -2376,8 +2354,7 @@ class InventoryItemBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Virtual chassis
 #
 
-class VirtualChassisListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_virtualchassis'
+class VirtualChassisListView(ObjectListView):
     queryset = VirtualChassis.objects.prefetch_related('master').annotate(member_count=Count('members'))
     table = tables.VirtualChassisTable
     filterset = filters.VirtualChassisFilterSet
@@ -2644,8 +2621,7 @@ class VirtualChassisBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Power panels
 #
 
-class PowerPanelListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_powerpanel'
+class PowerPanelListView(ObjectListView):
     queryset = PowerPanel.objects.prefetch_related(
         'site', 'rack_group'
     ).annotate(
@@ -2724,8 +2700,7 @@ class PowerPanelBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Power feeds
 #
 
-class PowerFeedListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'dcim.view_powerfeed'
+class PowerFeedListView(ObjectListView):
     queryset = PowerFeed.objects.prefetch_related(
         'power_panel', 'rack'
     )

+ 3 - 6
netbox/extras/views.py

@@ -25,8 +25,7 @@ from .tables import ConfigContextTable, ObjectChangeTable, TagTable, TaggedItemT
 # Tags
 #
 
-class TagListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'extras.view_tag'
+class TagListView(ObjectListView):
     queryset = Tag.objects.annotate(
         items=Count('extras_taggeditem_items', distinct=True)
     ).order_by(
@@ -106,8 +105,7 @@ class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Config contexts
 #
 
-class ConfigContextListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'extras.view_configcontext'
+class ConfigContextListView(ObjectListView):
     queryset = ConfigContext.objects.all()
     filterset = filters.ConfigContextFilterSet
     filterset_form = forms.ConfigContextFilterForm
@@ -200,8 +198,7 @@ class ObjectConfigContextView(View):
 # Change logging
 #
 
-class ObjectChangeListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'extras.view_objectchange'
+class ObjectChangeListView(ObjectListView):
     queryset = ObjectChange.objects.prefetch_related('user', 'changed_object_type')
     filterset = filters.ObjectChangeFilterSet
     filterset_form = forms.ObjectChangeFilterForm

+ 9 - 18
netbox/ipam/views.py

@@ -113,8 +113,7 @@ def add_available_vlans(vlan_group, vlans):
 # VRFs
 #
 
-class VRFListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'ipam.view_vrf'
+class VRFListView(ObjectListView):
     queryset = VRF.objects.prefetch_related('tenant')
     filterset = filters.VRFFilterSet
     filterset_form = forms.VRFFilterForm
@@ -182,8 +181,7 @@ class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # RIRs
 #
 
-class RIRListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'ipam.view_rir'
+class RIRListView(ObjectListView):
     queryset = RIR.objects.annotate(aggregate_count=Count('aggregates'))
     filterset = filters.RIRFilterSet
     filterset_form = forms.RIRFilterForm
@@ -290,8 +288,7 @@ class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Aggregates
 #
 
-class AggregateListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'ipam.view_aggregate'
+class AggregateListView(ObjectListView):
     queryset = Aggregate.objects.prefetch_related('rir').annotate(
         child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
     )
@@ -409,8 +406,7 @@ class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Prefix/VLAN roles
 #
 
-class RoleListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'ipam.view_role'
+class RoleListView(ObjectListView):
     queryset = Role.objects.all()
     table = tables.RoleTable
 
@@ -445,8 +441,7 @@ class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Prefixes
 #
 
-class PrefixListView(ObjectPermissionRequiredMixin, ObjectListView):
-    permission_required = 'ipam.view_prefix'
+class PrefixListView(ObjectListView):
     queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
     filterset = filters.PrefixFilterSet
     filterset_form = forms.PrefixFilterForm
@@ -638,8 +633,7 @@ class PrefixBulkDeleteView(ObjectPermissionRequiredMixin, BulkDeleteView):
 # IP addresses
 #
 
-class IPAddressListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'ipam.view_ipaddress'
+class IPAddressListView(ObjectListView):
     queryset = IPAddress.objects.prefetch_related(
         'vrf__tenant', 'tenant', 'nat_inside', 'interface__device', 'interface__virtual_machine'
     )
@@ -813,8 +807,7 @@ class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # VLAN groups
 #
 
-class VLANGroupListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'ipam.view_vlangroup'
+class VLANGroupListView(ObjectListView):
     queryset = VLANGroup.objects.prefetch_related('site').annotate(vlan_count=Count('vlans'))
     filterset = filters.VLANGroupFilterSet
     filterset_form = forms.VLANGroupFilterForm
@@ -889,8 +882,7 @@ class VLANGroupVLANsView(PermissionRequiredMixin, View):
 # VLANs
 #
 
-class VLANListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'ipam.view_vlan'
+class VLANListView(ObjectListView):
     queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role').prefetch_related('prefixes')
     filterset = filters.VLANFilterSet
     filterset_form = forms.VLANFilterForm
@@ -985,8 +977,7 @@ class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Services
 #
 
-class ServiceListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'ipam.view_service'
+class ServiceListView(ObjectListView):
     queryset = Service.objects.prefetch_related('device', 'virtual_machine')
     filterset = filters.ServiceFilterSet
     filterset_form = forms.ServiceFilterForm

+ 2 - 4
netbox/secrets/views.py

@@ -30,8 +30,7 @@ def get_session_key(request):
 # Secret roles
 #
 
-class SecretRoleListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'secrets.view_secretrole'
+class SecretRoleListView(ObjectListView):
     queryset = SecretRole.objects.annotate(secret_count=Count('secrets'))
     table = tables.SecretRoleTable
 
@@ -66,8 +65,7 @@ class SecretRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Secrets
 #
 
-class SecretListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'secrets.view_secret'
+class SecretListView(ObjectListView):
     queryset = Secret.objects.prefetch_related('role', 'device')
     filterset = filters.SecretFilterSet
     filterset_form = forms.SecretFilterForm

+ 2 - 4
netbox/tenancy/views.py

@@ -18,8 +18,7 @@ from .models import Tenant, TenantGroup
 # Tenant groups
 #
 
-class TenantGroupListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'tenancy.view_tenantgroup'
+class TenantGroupListView(ObjectListView):
     queryset = TenantGroup.objects.add_related_count(
         TenantGroup.objects.all(),
         Tenant,
@@ -60,8 +59,7 @@ class TenantGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 #  Tenants
 #
 
-class TenantListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'tenancy.view_tenant'
+class TenantListView(ObjectListView):
     queryset = Tenant.objects.prefetch_related('group')
     filterset = filters.TenantFilterSet
     filterset_form = forms.TenantFilterForm

+ 19 - 14
netbox/utilities/views.py

@@ -27,6 +27,7 @@ from extras.querysets import CustomFieldQueryset
 from users.models import ObjectPermission
 from utilities.exceptions import AbortTransaction
 from utilities.forms import BootstrapMixin, CSVDataField, TableConfigForm
+from utilities.permissions import get_permission_for_model
 from utilities.utils import csv_format, prepare_cloned_fields
 from .error_handlers import handle_protectederror
 from .forms import ConfirmationForm, ImportForm
@@ -45,11 +46,15 @@ class ObjectPermissionRequiredMixin(AccessMixin):
     """
     permission_required = None
 
+    def get_required_permission(self):
+        return self.permission_required
+
     def has_permission(self):
         user = self.request.user
+        permission_required = self.get_required_permission()
 
         # First, check that the user is granted the required permission at either the model or object level.
-        if not user.has_perm(self.permission_required):
+        if not user.has_perm(permission_required):
             return False
 
         # Superusers implicitly have all permissions
@@ -58,23 +63,18 @@ class ObjectPermissionRequiredMixin(AccessMixin):
 
         # Determine whether the permission is model-level or object-level. Model-level permissions grant the
         # specified action to *all* objects, so no further action is needed.
-        if self.permission_required in {*user._user_perm_cache, *user._group_perm_cache}:
+        if permission_required in {*user._user_perm_cache, *user._group_perm_cache}:
             return True
 
         # If the permission is granted only at the object level, filter the view's queryset to return only objects
         # on which the user is permitted to perform the specified action.
-        attrs = ObjectPermission.objects.get_attr_constraints(user, self.permission_required)
+        attrs = ObjectPermission.objects.get_attr_constraints(user, permission_required)
         if attrs:
             # Update the view's QuerySet to filter only the permitted objects
             self.queryset = self.queryset.filter(attrs)
             return True
 
     def dispatch(self, request, *args, **kwargs):
-        if self.permission_required is None:
-            raise ImproperlyConfigured(
-                '{0} is missing the permission_required attribute. Define {0}.permission_required, or override '
-                '{0}.get_permission_required().'.format(self.__class__.__name__)
-            )
 
         if not hasattr(self, 'queryset'):
             raise ImproperlyConfigured(
@@ -118,15 +118,15 @@ class GetReturnURLMixin(object):
 # Generic views
 #
 
-class ObjectListView(View):
+class ObjectListView(ObjectPermissionRequiredMixin, View):
     """
     List a series of objects.
 
-    queryset: The queryset of objects to display
-    filter: A django-filter FilterSet that is applied to the queryset
-    filter_form: The form used to render filter options
-    table: The django-tables2 Table used to render the objects list
-    template_name: The name of the template
+    :param queryset: The queryset of objects to display
+    :param filter: A django-filter FilterSet that is applied to the queryset
+    :param filter_form: The form used to render filter options
+    :param table: The django-tables2 Table used to render the objects list
+    :param template_name: The name of the template
     """
     queryset = None
     filterset = None
@@ -135,6 +135,11 @@ class ObjectListView(View):
     template_name = 'utilities/obj_list.html'
     action_buttons = ('add', 'import', 'export')
 
+    def get_required_permission(self):
+        if getattr(self, 'permission_required') is not None:
+            return self.permission_required
+        return get_permission_for_model(self.queryset.model, 'view')
+
     def queryset_to_yaml(self):
         """
         Export the queryset of objects as concatenated YAML documents.

+ 4 - 8
netbox/virtualization/views.py

@@ -22,8 +22,7 @@ from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
 # Cluster types
 #
 
-class ClusterTypeListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'virtualization.view_clustertype'
+class ClusterTypeListView(ObjectListView):
     queryset = ClusterType.objects.annotate(cluster_count=Count('clusters'))
     table = tables.ClusterTypeTable
 
@@ -58,8 +57,7 @@ class ClusterTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Cluster groups
 #
 
-class ClusterGroupListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'virtualization.view_clustergroup'
+class ClusterGroupListView(ObjectListView):
     queryset = ClusterGroup.objects.annotate(cluster_count=Count('clusters'))
     table = tables.ClusterGroupTable
 
@@ -94,8 +92,7 @@ class ClusterGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
 # Clusters
 #
 
-class ClusterListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'virtualization.view_cluster'
+class ClusterListView(ObjectListView):
     queryset = Cluster.objects.prefetch_related('type', 'group', 'site', 'tenant')
     table = tables.ClusterTable
     filterset = filters.ClusterFilterSet
@@ -251,8 +248,7 @@ class ClusterRemoveDevicesView(PermissionRequiredMixin, View):
 # Virtual machines
 #
 
-class VirtualMachineListView(PermissionRequiredMixin, ObjectListView):
-    permission_required = 'virtualization.view_virtualmachine'
+class VirtualMachineListView(ObjectListView):
     queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role', 'primary_ip4', 'primary_ip6')
     filterset = filters.VirtualMachineFilterSet
     filterset_form = forms.VirtualMachineFilterForm