mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-22 20:12:00 -06:00
Merge pull request #3066 from digitalocean/323-view-permissions
Closes #323
This commit is contained in:
commit
573af6a236
23
CHANGELOG.md
23
CHANGELOG.md
@ -16,8 +16,31 @@ NetBox now makes use of its own `Tag` model instead of the vanilla model which s
|
||||
lives in the `extras` app and thus any permissions that you may have configured using "Taggit | Tag" should be changed
|
||||
to now use "Extras | Tag."
|
||||
|
||||
### View Permissions
|
||||
|
||||
Django 2.1 introduced the ability to enforce view-only permissions for different object types. NetBox now enforces
|
||||
these by default. You can grant view permission to a user or group by assigning the "can view" permission for the
|
||||
desired object(s).
|
||||
|
||||
To exempt certain objects from the enforcement of view permissions, so that any user (including anonymous users) can
|
||||
view them, add them to the new `EXEMPT_VIEW_PERMISSIONS` setting in `configuration.py`:
|
||||
|
||||
```
|
||||
EXEMPT_VIEW_PERMISSIONS = [
|
||||
'dcim.site',
|
||||
'ipam.prefix',
|
||||
]
|
||||
```
|
||||
|
||||
To exclude _all_ objects, effectively disabling view permissions, set:
|
||||
|
||||
```
|
||||
EXEMPT_VIEW_PERMISSIONS = ['*']
|
||||
```
|
||||
|
||||
## Enhancements
|
||||
|
||||
* [#323](https://github.com/digitalocean/netbox/issues/323) - Enforce per-object type view permissions
|
||||
* [#1792](https://github.com/digitalocean/netbox/issues/1792) - Add CustomFieldChoices API endpoint
|
||||
* [#2324](https://github.com/digitalocean/netbox/issues/2324) - Add `color` option for tags
|
||||
* [#2643](https://github.com/digitalocean/netbox/issues/2643) - Add `description` field to console/power components and device bays
|
||||
|
@ -89,6 +89,30 @@ In order to send email, NetBox needs an email server configured. The following i
|
||||
|
||||
---
|
||||
|
||||
## EXEMPT_VIEW_PERMISSIONS
|
||||
|
||||
Default: Empty list
|
||||
|
||||
A list of models to exempt from the enforcement of view permissions. Models listed here will be viewable by all users and by anonymous users.
|
||||
|
||||
List models in the form `<app>.<model>`. For example:
|
||||
|
||||
```
|
||||
EXEMPT_VIEW_PERMISSIONS = [
|
||||
'dcim.site',
|
||||
'dcim.region',
|
||||
'ipam.prefix',
|
||||
]
|
||||
```
|
||||
|
||||
To exempt _all_ models from view permission enforcement, set the following. (Note that `EXEMPT_VIEW_PERMISSIONS` must be an iterable.)
|
||||
|
||||
```
|
||||
EXEMPT_VIEW_PERMISSIONS = ['*']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# ENFORCE_GLOBAL_UNIQUE
|
||||
|
||||
Default: False
|
||||
|
@ -4,13 +4,15 @@ from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from circuits.models import Circuit, CircuitType, Provider
|
||||
from utilities.testing import create_test_user
|
||||
|
||||
|
||||
class ProviderTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['circuits.view_provider'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
Provider.objects.bulk_create([
|
||||
Provider(name='Provider 1', slug='provider-1', asn=65001),
|
||||
@ -38,8 +40,9 @@ class ProviderTestCase(TestCase):
|
||||
class CircuitTypeTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['circuits.view_circuittype'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
CircuitType.objects.bulk_create([
|
||||
CircuitType(name='Circuit Type 1', slug='circuit-type-1'),
|
||||
@ -58,8 +61,9 @@ class CircuitTypeTestCase(TestCase):
|
||||
class CircuitTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['circuits.view_circuit'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
provider = Provider(name='Provider 1', slug='provider-1', asn=65001)
|
||||
provider.save()
|
||||
@ -84,8 +88,8 @@ class CircuitTestCase(TestCase):
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_provider(self):
|
||||
def test_circuit(self):
|
||||
|
||||
provider = Provider.objects.first()
|
||||
response = self.client.get(provider.get_absolute_url())
|
||||
circuit = Circuit.objects.first()
|
||||
response = self.client.get(circuit.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -20,7 +20,8 @@ from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||
# Providers
|
||||
#
|
||||
|
||||
class ProviderListView(ObjectListView):
|
||||
class ProviderListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'circuits.view_provider'
|
||||
queryset = Provider.objects.annotate(count_circuits=Count('circuits'))
|
||||
filter = filters.ProviderFilter
|
||||
filter_form = forms.ProviderFilterForm
|
||||
@ -28,7 +29,8 @@ class ProviderListView(ObjectListView):
|
||||
template_name = 'circuits/provider_list.html'
|
||||
|
||||
|
||||
class ProviderView(View):
|
||||
class ProviderView(PermissionRequiredMixin, View):
|
||||
permission_required = 'circuits.view_provider'
|
||||
|
||||
def get(self, request, slug):
|
||||
|
||||
@ -93,7 +95,8 @@ class ProviderBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Circuit Types
|
||||
#
|
||||
|
||||
class CircuitTypeListView(ObjectListView):
|
||||
class CircuitTypeListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'circuits.view_circuittype'
|
||||
queryset = CircuitType.objects.annotate(circuit_count=Count('circuits'))
|
||||
table = tables.CircuitTypeTable
|
||||
template_name = 'circuits/circuittype_list.html'
|
||||
@ -128,7 +131,8 @@ class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Circuits
|
||||
#
|
||||
|
||||
class CircuitListView(ObjectListView):
|
||||
class CircuitListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'circuits.view_circuit'
|
||||
queryset = Circuit.objects.select_related(
|
||||
'provider', 'type', 'tenant'
|
||||
).prefetch_related(
|
||||
@ -140,7 +144,8 @@ class CircuitListView(ObjectListView):
|
||||
template_name = 'circuits/circuit_list.html'
|
||||
|
||||
|
||||
class CircuitView(View):
|
||||
class CircuitView(PermissionRequiredMixin, View):
|
||||
permission_required = 'circuits.view_circuit'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import urllib.parse
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
@ -9,13 +8,15 @@ from dcim.models import (
|
||||
Cable, Device, DeviceRole, DeviceType, Interface, InventoryItem, Manufacturer, Platform, Rack, RackGroup,
|
||||
RackReservation, RackRole, Site, Region, VirtualChassis,
|
||||
)
|
||||
from utilities.testing import create_test_user
|
||||
|
||||
|
||||
class RegionTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_region'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
# Create three Regions
|
||||
for i in range(1, 4):
|
||||
@ -32,8 +33,9 @@ class RegionTestCase(TestCase):
|
||||
class SiteTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_site'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
region = Region(name='Region 1', slug='region-1')
|
||||
region.save()
|
||||
@ -64,8 +66,9 @@ class SiteTestCase(TestCase):
|
||||
class RackGroupTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_rackgroup'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
@ -84,11 +87,12 @@ class RackGroupTestCase(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class RackTypeTestCase(TestCase):
|
||||
class RackRoleTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_rackrole'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
RackRole.objects.bulk_create([
|
||||
RackRole(name='Rack Role 1', slug='rack-role-1'),
|
||||
@ -107,12 +111,9 @@ class RackTypeTestCase(TestCase):
|
||||
class RackReservationTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_rackreservation'])
|
||||
self.client = Client()
|
||||
|
||||
User = get_user_model()
|
||||
user = User(username='testuser', email='testuser@example.com')
|
||||
user.save()
|
||||
self.client.force_login(user)
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
@ -137,8 +138,9 @@ class RackReservationTestCase(TestCase):
|
||||
class RackTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_rack'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
@ -169,8 +171,9 @@ class RackTestCase(TestCase):
|
||||
class ManufacturerTypeTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_manufacturer'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
Manufacturer.objects.bulk_create([
|
||||
Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
|
||||
@ -189,8 +192,9 @@ class ManufacturerTypeTestCase(TestCase):
|
||||
class DeviceTypeTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_devicetype'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
manufacturer = Manufacturer(name='Manufacturer 1', slug='manufacturer-1')
|
||||
manufacturer.save()
|
||||
@ -221,8 +225,9 @@ class DeviceTypeTestCase(TestCase):
|
||||
class DeviceRoleTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_devicerole'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
DeviceRole.objects.bulk_create([
|
||||
DeviceRole(name='Device Role 1', slug='device-role-1'),
|
||||
@ -241,8 +246,9 @@ class DeviceRoleTestCase(TestCase):
|
||||
class PlatformTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_platform'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
Platform.objects.bulk_create([
|
||||
Platform(name='Platform 1', slug='platform-1'),
|
||||
@ -261,8 +267,9 @@ class PlatformTestCase(TestCase):
|
||||
class DeviceTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_device'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
@ -303,8 +310,9 @@ class DeviceTestCase(TestCase):
|
||||
class InventoryItemTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_inventoryitem'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
@ -337,18 +345,13 @@ class InventoryItemTestCase(TestCase):
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_inventoryitem(self):
|
||||
|
||||
inventoryitem = InventoryItem.objects.first()
|
||||
response = self.client.get(inventoryitem.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class CableTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_cable'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
@ -401,11 +404,12 @@ class CableTestCase(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class VirtualMachineTestCase(TestCase):
|
||||
class VirtualChassisTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['dcim.view_virtualchassis'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
site = Site.objects.create(name='Site 1', slug='site-1')
|
||||
manufacturer = Manufacturer.objects.create(name='Manufacturer', slug='manufacturer-1')
|
||||
@ -450,9 +454,3 @@ class VirtualMachineTestCase(TestCase):
|
||||
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_virtualchassis(self):
|
||||
|
||||
virtualchassis = VirtualChassis.objects.first()
|
||||
response = self.client.get(virtualchassis.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -138,7 +138,8 @@ class BulkDisconnectView(GetReturnURLMixin, View):
|
||||
# Regions
|
||||
#
|
||||
|
||||
class RegionListView(ObjectListView):
|
||||
class RegionListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_region'
|
||||
queryset = Region.objects.add_related_count(
|
||||
Region.objects.all(),
|
||||
Site,
|
||||
@ -182,7 +183,8 @@ class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Sites
|
||||
#
|
||||
|
||||
class SiteListView(ObjectListView):
|
||||
class SiteListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_site'
|
||||
queryset = Site.objects.select_related('region', 'tenant')
|
||||
filter = filters.SiteFilter
|
||||
filter_form = forms.SiteFilterForm
|
||||
@ -190,7 +192,8 @@ class SiteListView(ObjectListView):
|
||||
template_name = 'dcim/site_list.html'
|
||||
|
||||
|
||||
class SiteView(View):
|
||||
class SiteView(PermissionRequiredMixin, View):
|
||||
permission_required = 'dcim.view_site'
|
||||
|
||||
def get(self, request, slug):
|
||||
|
||||
@ -254,7 +257,8 @@ class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
# Rack groups
|
||||
#
|
||||
|
||||
class RackGroupListView(ObjectListView):
|
||||
class RackGroupListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_rackgroup'
|
||||
queryset = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks'))
|
||||
filter = filters.RackGroupFilter
|
||||
filter_form = forms.RackGroupFilterForm
|
||||
@ -292,7 +296,8 @@ class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Rack roles
|
||||
#
|
||||
|
||||
class RackRoleListView(ObjectListView):
|
||||
class RackRoleListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_rackrole'
|
||||
queryset = RackRole.objects.annotate(rack_count=Count('racks'))
|
||||
table = tables.RackRoleTable
|
||||
template_name = 'dcim/rackrole_list.html'
|
||||
@ -327,7 +332,8 @@ class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Racks
|
||||
#
|
||||
|
||||
class RackListView(ObjectListView):
|
||||
class RackListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_rack'
|
||||
queryset = Rack.objects.select_related(
|
||||
'site', 'group', 'tenant', 'role'
|
||||
).prefetch_related(
|
||||
@ -341,10 +347,11 @@ class RackListView(ObjectListView):
|
||||
template_name = 'dcim/rack_list.html'
|
||||
|
||||
|
||||
class RackElevationListView(View):
|
||||
class RackElevationListView(PermissionRequiredMixin, View):
|
||||
"""
|
||||
Display a set of rack elevations side-by-side.
|
||||
"""
|
||||
permission_required = 'dcim.view_rack'
|
||||
|
||||
def get(self, request):
|
||||
|
||||
@ -382,7 +389,8 @@ class RackElevationListView(View):
|
||||
})
|
||||
|
||||
|
||||
class RackView(View):
|
||||
class RackView(PermissionRequiredMixin, View):
|
||||
permission_required = 'dcim.view_rack'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -454,7 +462,8 @@ class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Rack reservations
|
||||
#
|
||||
|
||||
class RackReservationListView(ObjectListView):
|
||||
class RackReservationListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_rackreservation'
|
||||
queryset = RackReservation.objects.all()
|
||||
filter = filters.RackReservationFilter
|
||||
filter_form = forms.RackReservationFilterForm
|
||||
@ -510,7 +519,8 @@ class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Manufacturers
|
||||
#
|
||||
|
||||
class ManufacturerListView(ObjectListView):
|
||||
class ManufacturerListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_manufacturer'
|
||||
queryset = Manufacturer.objects.annotate(
|
||||
devicetype_count=Count('device_types', distinct=True),
|
||||
platform_count=Count('platforms', distinct=True),
|
||||
@ -548,7 +558,8 @@ class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Device types
|
||||
#
|
||||
|
||||
class DeviceTypeListView(ObjectListView):
|
||||
class DeviceTypeListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_devicetype'
|
||||
queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances'))
|
||||
filter = filters.DeviceTypeFilter
|
||||
filter_form = forms.DeviceTypeFilterForm
|
||||
@ -556,7 +567,8 @@ class DeviceTypeListView(ObjectListView):
|
||||
template_name = 'dcim/devicetype_list.html'
|
||||
|
||||
|
||||
class DeviceTypeView(View):
|
||||
class DeviceTypeView(PermissionRequiredMixin, View):
|
||||
permission_required = 'dcim.view_devicetype'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -812,7 +824,8 @@ class DeviceBayTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Device roles
|
||||
#
|
||||
|
||||
class DeviceRoleListView(ObjectListView):
|
||||
class DeviceRoleListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_devicerole'
|
||||
queryset = DeviceRole.objects.all()
|
||||
table = tables.DeviceRoleTable
|
||||
template_name = 'dcim/devicerole_list.html'
|
||||
@ -847,7 +860,8 @@ class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Platforms
|
||||
#
|
||||
|
||||
class PlatformListView(ObjectListView):
|
||||
class PlatformListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_platform'
|
||||
queryset = Platform.objects.all()
|
||||
table = tables.PlatformTable
|
||||
template_name = 'dcim/platform_list.html'
|
||||
@ -882,7 +896,8 @@ class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Devices
|
||||
#
|
||||
|
||||
class DeviceListView(ObjectListView):
|
||||
class DeviceListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_device'
|
||||
queryset = Device.objects.select_related(
|
||||
'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6'
|
||||
)
|
||||
@ -892,7 +907,8 @@ class DeviceListView(ObjectListView):
|
||||
template_name = 'dcim/device_list.html'
|
||||
|
||||
|
||||
class DeviceView(View):
|
||||
class DeviceView(PermissionRequiredMixin, View):
|
||||
permission_required = 'dcim.view_device'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -972,7 +988,8 @@ class DeviceView(View):
|
||||
})
|
||||
|
||||
|
||||
class DeviceInventoryView(View):
|
||||
class DeviceInventoryView(PermissionRequiredMixin, View):
|
||||
permission_required = 'dcim.view_device'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -993,7 +1010,7 @@ class DeviceInventoryView(View):
|
||||
|
||||
|
||||
class DeviceStatusView(PermissionRequiredMixin, View):
|
||||
permission_required = 'dcim.napalm_read'
|
||||
permission_required = ('dcim.view_device', 'dcim.napalm_read')
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -1006,7 +1023,7 @@ class DeviceStatusView(PermissionRequiredMixin, View):
|
||||
|
||||
|
||||
class DeviceLLDPNeighborsView(PermissionRequiredMixin, View):
|
||||
permission_required = 'dcim.napalm_read'
|
||||
permission_required = ('dcim.view_device', 'dcim.napalm_read')
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -1023,7 +1040,7 @@ class DeviceLLDPNeighborsView(PermissionRequiredMixin, View):
|
||||
|
||||
|
||||
class DeviceConfigView(PermissionRequiredMixin, View):
|
||||
permission_required = 'dcim.napalm_read'
|
||||
permission_required = ('dcim.view_device', 'dcim.napalm_read')
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -1035,7 +1052,8 @@ class DeviceConfigView(PermissionRequiredMixin, View):
|
||||
})
|
||||
|
||||
|
||||
class DeviceConfigContextView(ObjectConfigContextView):
|
||||
class DeviceConfigContextView(PermissionRequiredMixin, ObjectConfigContextView):
|
||||
permission_required = 'dcim.view_device'
|
||||
object_class = Device
|
||||
base_template = 'dcim/device.html'
|
||||
|
||||
@ -1258,7 +1276,8 @@ class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Interfaces
|
||||
#
|
||||
|
||||
class InterfaceView(View):
|
||||
class InterfaceView(PermissionRequiredMixin, View):
|
||||
permission_required = 'dcim.view_interface'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -1639,7 +1658,8 @@ class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateVie
|
||||
# Cables
|
||||
#
|
||||
|
||||
class CableListView(ObjectListView):
|
||||
class CableListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_cable'
|
||||
queryset = Cable.objects.prefetch_related(
|
||||
'termination_a', 'termination_b'
|
||||
)
|
||||
@ -1649,7 +1669,8 @@ class CableListView(ObjectListView):
|
||||
template_name = 'dcim/cable_list.html'
|
||||
|
||||
|
||||
class CableView(View):
|
||||
class CableView(PermissionRequiredMixin, View):
|
||||
permission_required = 'dcim.view_cable'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -1660,10 +1681,11 @@ class CableView(View):
|
||||
})
|
||||
|
||||
|
||||
class CableTraceView(View):
|
||||
class CableTraceView(PermissionRequiredMixin, View):
|
||||
"""
|
||||
Trace a cable path beginning from the given termination.
|
||||
"""
|
||||
permission_required = 'dcim.view_cable'
|
||||
|
||||
def get(self, request, model, pk):
|
||||
|
||||
@ -1792,7 +1814,8 @@ class CableBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Connections
|
||||
#
|
||||
|
||||
class ConsoleConnectionsListView(ObjectListView):
|
||||
class ConsoleConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = ('dcim.view_consoleport', 'dcim.view_consoleserverport')
|
||||
queryset = ConsolePort.objects.select_related(
|
||||
'device', 'connected_endpoint__device'
|
||||
).filter(
|
||||
@ -1822,7 +1845,8 @@ class ConsoleConnectionsListView(ObjectListView):
|
||||
return csv_data
|
||||
|
||||
|
||||
class PowerConnectionsListView(ObjectListView):
|
||||
class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = ('dcim.view_powerport', 'dcim.view_poweroutlet')
|
||||
queryset = PowerPort.objects.select_related(
|
||||
'device', '_connected_poweroutlet__device'
|
||||
).filter(
|
||||
@ -1852,7 +1876,8 @@ class PowerConnectionsListView(ObjectListView):
|
||||
return csv_data
|
||||
|
||||
|
||||
class InterfaceConnectionsListView(ObjectListView):
|
||||
class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.interface'
|
||||
queryset = Interface.objects.select_related(
|
||||
'device', 'cable', '_connected_interface__device'
|
||||
).filter(
|
||||
@ -1894,7 +1919,8 @@ class InterfaceConnectionsListView(ObjectListView):
|
||||
# Inventory items
|
||||
#
|
||||
|
||||
class InventoryItemListView(ObjectListView):
|
||||
class InventoryItemListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_inventoryitem'
|
||||
queryset = InventoryItem.objects.select_related('device', 'manufacturer')
|
||||
filter = filters.InventoryItemFilter
|
||||
filter_form = forms.InventoryItemFilterForm
|
||||
@ -1949,7 +1975,8 @@ class InventoryItemBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Virtual chassis
|
||||
#
|
||||
|
||||
class VirtualChassisListView(ObjectListView):
|
||||
class VirtualChassisListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_virtualchassis'
|
||||
queryset = VirtualChassis.objects.select_related('master').annotate(member_count=Count('members'))
|
||||
table = tables.VirtualChassisTable
|
||||
filter = filters.VirtualChassisFilter
|
||||
@ -2184,7 +2211,8 @@ class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin,
|
||||
# Power panels
|
||||
#
|
||||
|
||||
class PowerPanelListView(ObjectListView):
|
||||
class PowerPanelListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_powerpanel'
|
||||
queryset = PowerPanel.objects.select_related(
|
||||
'site', 'rack_group'
|
||||
).annotate(
|
||||
@ -2196,7 +2224,8 @@ class PowerPanelListView(ObjectListView):
|
||||
template_name = 'dcim/powerpanel_list.html'
|
||||
|
||||
|
||||
class PowerPanelView(View):
|
||||
class PowerPanelView(PermissionRequiredMixin, View):
|
||||
permission_required = 'dcim.view_powerpanel'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -2253,7 +2282,8 @@ class PowerPanelBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Power feeds
|
||||
#
|
||||
|
||||
class PowerFeedListView(ObjectListView):
|
||||
class PowerFeedListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'dcim.view_powerfeed'
|
||||
queryset = PowerFeed.objects.select_related(
|
||||
'power_panel', 'rack'
|
||||
)
|
||||
@ -2263,7 +2293,8 @@ class PowerFeedListView(ObjectListView):
|
||||
template_name = 'dcim/powerfeed_list.html'
|
||||
|
||||
|
||||
class PowerFeedView(View):
|
||||
class PowerFeedView(PermissionRequiredMixin, View):
|
||||
permission_required = 'dcim.view_powerfeed'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
|
@ -7,6 +7,7 @@ from django.urls import reverse
|
||||
|
||||
from dcim.models import Site
|
||||
from extras.models import ConfigContext, ObjectChange, Tag
|
||||
from utilities.testing import create_test_user
|
||||
|
||||
|
||||
class TagTestCase(TestCase):
|
||||
@ -35,8 +36,9 @@ class TagTestCase(TestCase):
|
||||
class ConfigContextTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['extras.view_configcontext'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
@ -70,11 +72,9 @@ class ConfigContextTestCase(TestCase):
|
||||
class ObjectChangeTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['extras.view_objectchange'])
|
||||
self.client = Client()
|
||||
|
||||
user = User(username='testuser', email='testuser@example.com')
|
||||
user.save()
|
||||
self.client.force_login(user)
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
@ -96,7 +96,8 @@ class TagBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Config contexts
|
||||
#
|
||||
|
||||
class ConfigContextListView(ObjectListView):
|
||||
class ConfigContextListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'extras.view_configcontext'
|
||||
queryset = ConfigContext.objects.all()
|
||||
filter = filters.ConfigContextFilter
|
||||
filter_form = ConfigContextFilterForm
|
||||
@ -104,7 +105,8 @@ class ConfigContextListView(ObjectListView):
|
||||
template_name = 'extras/configcontext_list.html'
|
||||
|
||||
|
||||
class ConfigContextView(View):
|
||||
class ConfigContextView(PermissionRequiredMixin, View):
|
||||
permission_required = 'extras.view_configcontext'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -173,7 +175,8 @@ class ObjectConfigContextView(View):
|
||||
# Change logging
|
||||
#
|
||||
|
||||
class ObjectChangeListView(ObjectListView):
|
||||
class ObjectChangeListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'extras.view_objectchange'
|
||||
queryset = ObjectChange.objects.select_related('user', 'changed_object_type')
|
||||
filter = filters.ObjectChangeFilter
|
||||
filter_form = ObjectChangeFilterForm
|
||||
@ -181,7 +184,8 @@ class ObjectChangeListView(ObjectListView):
|
||||
template_name = 'extras/objectchange_list.html'
|
||||
|
||||
|
||||
class ObjectChangeView(View):
|
||||
class ObjectChangeView(PermissionRequiredMixin, View):
|
||||
permission_required = 'extras.view_objectchange'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -272,10 +276,11 @@ class ImageAttachmentDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||
# Reports
|
||||
#
|
||||
|
||||
class ReportListView(View):
|
||||
class ReportListView(PermissionRequiredMixin, View):
|
||||
"""
|
||||
Retrieve all of the available reports from disk and the recorded ReportResult (if any) for each.
|
||||
"""
|
||||
permission_required = 'extras.view_reportresult'
|
||||
|
||||
def get(self, request):
|
||||
|
||||
@ -295,10 +300,11 @@ class ReportListView(View):
|
||||
})
|
||||
|
||||
|
||||
class ReportView(View):
|
||||
class ReportView(PermissionRequiredMixin, View):
|
||||
"""
|
||||
Display a single Report and its associated ReportResult (if any).
|
||||
"""
|
||||
permission_required = 'extras.view_reportresult'
|
||||
|
||||
def get(self, request, name):
|
||||
|
||||
|
@ -7,13 +7,15 @@ from django.urls import reverse
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||
from ipam.constants import IP_PROTOCOL_TCP
|
||||
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, Service, VLAN, VLANGroup, VRF
|
||||
from utilities.testing import create_test_user
|
||||
|
||||
|
||||
class VRFTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['ipam.view_vrf'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
VRF.objects.bulk_create([
|
||||
VRF(name='VRF 1', rd='65000:1'),
|
||||
@ -41,8 +43,9 @@ class VRFTestCase(TestCase):
|
||||
class RIRTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['ipam.view_rir'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
RIR.objects.bulk_create([
|
||||
RIR(name='RIR 1', slug='rir-1'),
|
||||
@ -57,18 +60,13 @@ class RIRTestCase(TestCase):
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_rir(self):
|
||||
|
||||
rir = RIR.objects.first()
|
||||
response = self.client.get(rir.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class AggregateTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['ipam.view_aggregate'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
rir = RIR(name='RIR 1', slug='rir-1')
|
||||
rir.save()
|
||||
@ -99,8 +97,9 @@ class AggregateTestCase(TestCase):
|
||||
class RoleTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['ipam.view_role'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
Role.objects.bulk_create([
|
||||
Role(name='Role 1', slug='role-1'),
|
||||
@ -119,8 +118,9 @@ class RoleTestCase(TestCase):
|
||||
class PrefixTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['ipam.view_prefix'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
@ -151,8 +151,9 @@ class PrefixTestCase(TestCase):
|
||||
class IPAddressTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['ipam.view_ipaddress'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
vrf = VRF(name='VRF 1', rd='65000:1')
|
||||
vrf.save()
|
||||
@ -183,8 +184,9 @@ class IPAddressTestCase(TestCase):
|
||||
class VLANGroupTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['ipam.view_vlangroup'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
@ -209,8 +211,9 @@ class VLANGroupTestCase(TestCase):
|
||||
class VLANTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['ipam.view_vlan'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
vlangroup = VLANGroup(name='VLAN Group 1', slug='vlan-group-1')
|
||||
vlangroup.save()
|
||||
@ -241,8 +244,9 @@ class VLANTestCase(TestCase):
|
||||
class ServiceTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['ipam.view_service'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
|
@ -113,7 +113,8 @@ def add_available_vlans(vlan_group, vlans):
|
||||
# VRFs
|
||||
#
|
||||
|
||||
class VRFListView(ObjectListView):
|
||||
class VRFListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'ipam.view_vrf'
|
||||
queryset = VRF.objects.select_related('tenant')
|
||||
filter = filters.VRFFilter
|
||||
filter_form = forms.VRFFilterForm
|
||||
@ -121,7 +122,8 @@ class VRFListView(ObjectListView):
|
||||
template_name = 'ipam/vrf_list.html'
|
||||
|
||||
|
||||
class VRFView(View):
|
||||
class VRFView(PermissionRequiredMixin, View):
|
||||
permission_required = 'ipam.view_vrf'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -180,7 +182,8 @@ class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# RIRs
|
||||
#
|
||||
|
||||
class RIRListView(ObjectListView):
|
||||
class RIRListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'ipam.view_rir'
|
||||
queryset = RIR.objects.annotate(aggregate_count=Count('aggregates'))
|
||||
filter = filters.RIRFilter
|
||||
filter_form = forms.RIRFilterForm
|
||||
@ -286,7 +289,8 @@ class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Aggregates
|
||||
#
|
||||
|
||||
class AggregateListView(ObjectListView):
|
||||
class AggregateListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'ipam.view_aggregate'
|
||||
queryset = Aggregate.objects.select_related('rir').extra(select={
|
||||
'child_count': 'SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix',
|
||||
})
|
||||
@ -312,7 +316,8 @@ class AggregateListView(ObjectListView):
|
||||
}
|
||||
|
||||
|
||||
class AggregateView(View):
|
||||
class AggregateView(PermissionRequiredMixin, View):
|
||||
permission_required = 'ipam.view_aggregate'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -398,7 +403,8 @@ class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Prefix/VLAN roles
|
||||
#
|
||||
|
||||
class RoleListView(ObjectListView):
|
||||
class RoleListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'ipam.view_role'
|
||||
queryset = Role.objects.all()
|
||||
table = tables.RoleTable
|
||||
template_name = 'ipam/role_list.html'
|
||||
@ -433,7 +439,8 @@ class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Prefixes
|
||||
#
|
||||
|
||||
class PrefixListView(ObjectListView):
|
||||
class PrefixListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'ipam.view_prefix'
|
||||
queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
||||
filter = filters.PrefixFilter
|
||||
filter_form = forms.PrefixFilterForm
|
||||
@ -446,7 +453,8 @@ class PrefixListView(ObjectListView):
|
||||
return self.queryset.annotate_depth(limit=limit)
|
||||
|
||||
|
||||
class PrefixView(View):
|
||||
class PrefixView(PermissionRequiredMixin, View):
|
||||
permission_required = 'ipam.view_prefix'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -489,7 +497,8 @@ class PrefixView(View):
|
||||
})
|
||||
|
||||
|
||||
class PrefixPrefixesView(View):
|
||||
class PrefixPrefixesView(PermissionRequiredMixin, View):
|
||||
permission_required = 'ipam.view_prefix'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -531,7 +540,8 @@ class PrefixPrefixesView(View):
|
||||
})
|
||||
|
||||
|
||||
class PrefixIPAddressesView(View):
|
||||
class PrefixIPAddressesView(PermissionRequiredMixin, View):
|
||||
permission_required = 'ipam.view_prefix'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -617,7 +627,8 @@ class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# IP addresses
|
||||
#
|
||||
|
||||
class IPAddressListView(ObjectListView):
|
||||
class IPAddressListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'ipam.view_ipaddress'
|
||||
queryset = IPAddress.objects.select_related(
|
||||
'vrf__tenant', 'tenant', 'nat_inside'
|
||||
).prefetch_related(
|
||||
@ -629,7 +640,8 @@ class IPAddressListView(ObjectListView):
|
||||
template_name = 'ipam/ipaddress_list.html'
|
||||
|
||||
|
||||
class IPAddressView(View):
|
||||
class IPAddressView(PermissionRequiredMixin, View):
|
||||
permission_required = 'ipam.view_ipaddress'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -788,7 +800,8 @@ class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# VLAN groups
|
||||
#
|
||||
|
||||
class VLANGroupListView(ObjectListView):
|
||||
class VLANGroupListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'ipam.view_vlangroup'
|
||||
queryset = VLANGroup.objects.select_related('site').annotate(vlan_count=Count('vlans'))
|
||||
filter = filters.VLANGroupFilter
|
||||
filter_form = forms.VLANGroupFilterForm
|
||||
@ -822,7 +835,9 @@ class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
default_return_url = 'ipam:vlangroup_list'
|
||||
|
||||
|
||||
class VLANGroupVLANsView(View):
|
||||
class VLANGroupVLANsView(PermissionRequiredMixin, View):
|
||||
permission_required = 'ipam.view_vlangroup'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
vlan_group = get_object_or_404(VLANGroup.objects.all(), pk=pk)
|
||||
@ -861,7 +876,8 @@ class VLANGroupVLANsView(View):
|
||||
# VLANs
|
||||
#
|
||||
|
||||
class VLANListView(ObjectListView):
|
||||
class VLANListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'ipam.view_vlan'
|
||||
queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role').prefetch_related('prefixes')
|
||||
filter = filters.VLANFilter
|
||||
filter_form = forms.VLANFilterForm
|
||||
@ -869,7 +885,8 @@ class VLANListView(ObjectListView):
|
||||
template_name = 'ipam/vlan_list.html'
|
||||
|
||||
|
||||
class VLANView(View):
|
||||
class VLANView(PermissionRequiredMixin, View):
|
||||
permission_required = 'ipam.view_vlan'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -886,7 +903,8 @@ class VLANView(View):
|
||||
})
|
||||
|
||||
|
||||
class VLANMembersView(View):
|
||||
class VLANMembersView(PermissionRequiredMixin, View):
|
||||
permission_required = 'ipam.view_vlan'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -954,7 +972,8 @@ class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Services
|
||||
#
|
||||
|
||||
class ServiceListView(ObjectListView):
|
||||
class ServiceListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'ipam.view_service'
|
||||
queryset = Service.objects.select_related('device', 'virtual_machine')
|
||||
filter = filters.ServiceFilter
|
||||
filter_form = forms.ServiceFilterForm
|
||||
@ -962,7 +981,8 @@ class ServiceListView(ObjectListView):
|
||||
template_name = 'ipam/service_list.html'
|
||||
|
||||
|
||||
class ServiceView(View):
|
||||
class ServiceView(PermissionRequiredMixin, View):
|
||||
permission_required = 'ipam.view_service'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
|
@ -55,16 +55,31 @@ class TokenPermissions(DjangoModelPermissions):
|
||||
Custom permissions handler which extends the built-in DjangoModelPermissions to validate a Token's write ability
|
||||
for unsafe requests (POST/PUT/PATCH/DELETE).
|
||||
"""
|
||||
# Override the stock perm_map to enforce view permissions
|
||||
perms_map = {
|
||||
'GET': ['%(app_label)s.view_%(model_name)s'],
|
||||
'OPTIONS': [],
|
||||
'HEAD': ['%(app_label)s.view_%(model_name)s'],
|
||||
'POST': ['%(app_label)s.add_%(model_name)s'],
|
||||
'PUT': ['%(app_label)s.change_%(model_name)s'],
|
||||
'PATCH': ['%(app_label)s.change_%(model_name)s'],
|
||||
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# LOGIN_REQUIRED determines whether read-only access is provided to anonymous users.
|
||||
self.authenticated_users_only = settings.LOGIN_REQUIRED
|
||||
|
||||
super().__init__()
|
||||
|
||||
def has_permission(self, request, view):
|
||||
|
||||
# If token authentication is in use, verify that the token allows write operations (for unsafe methods).
|
||||
if request.method not in SAFE_METHODS and isinstance(request.auth, Token):
|
||||
if not request.auth.write_enabled:
|
||||
return False
|
||||
|
||||
return super().has_permission(request, view)
|
||||
|
||||
|
||||
|
@ -83,6 +83,14 @@ EMAIL = {
|
||||
# (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True.
|
||||
ENFORCE_GLOBAL_UNIQUE = False
|
||||
|
||||
# Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and
|
||||
# by anonymous users. List models in the form `<app>.<model>`. Add '*' to this list to exempt all models.
|
||||
EXEMPT_VIEW_PERMISSIONS = [
|
||||
# 'dcim.site',
|
||||
# 'dcim.region',
|
||||
# 'ipam.prefix',
|
||||
]
|
||||
|
||||
# Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs:
|
||||
# https://docs.djangoproject.com/en/1.11/topics/logging/
|
||||
LOGGING = {}
|
||||
|
@ -51,8 +51,9 @@ CORS_ORIGIN_WHITELIST = getattr(configuration, 'CORS_ORIGIN_WHITELIST', [])
|
||||
DATE_FORMAT = getattr(configuration, 'DATE_FORMAT', 'N j, Y')
|
||||
DATETIME_FORMAT = getattr(configuration, 'DATETIME_FORMAT', 'N j, Y g:i a')
|
||||
DEBUG = getattr(configuration, 'DEBUG', False)
|
||||
ENFORCE_GLOBAL_UNIQUE = getattr(configuration, 'ENFORCE_GLOBAL_UNIQUE', False)
|
||||
EMAIL = getattr(configuration, 'EMAIL', {})
|
||||
ENFORCE_GLOBAL_UNIQUE = getattr(configuration, 'ENFORCE_GLOBAL_UNIQUE', False)
|
||||
EXEMPT_VIEW_PERMISSIONS = getattr(configuration, 'EXEMPT_VIEW_PERMISSIONS', [])
|
||||
LOGGING = getattr(configuration, 'LOGGING', {})
|
||||
LOGIN_REQUIRED = getattr(configuration, 'LOGIN_REQUIRED', False)
|
||||
LOGIN_TIMEOUT = getattr(configuration, 'LOGIN_TIMEOUT', None)
|
||||
@ -93,7 +94,7 @@ if LDAP_CONFIGURED:
|
||||
# Prepend LDAPBackend to the default ModelBackend
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'django_auth_ldap.backend.LDAPBackend',
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
'utilities.auth_backends.ViewExemptModelBackend',
|
||||
]
|
||||
# Optionally disable strict certificate checking
|
||||
if LDAP_IGNORE_CERT_ERRORS:
|
||||
@ -107,6 +108,10 @@ if LDAP_CONFIGURED:
|
||||
"LDAP authentication has been configured, but django-auth-ldap is not installed. You can remove "
|
||||
"netbox/ldap_config.py to disable LDAP."
|
||||
)
|
||||
else:
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'utilities.auth_backends.ViewExemptModelBackend',
|
||||
]
|
||||
|
||||
# Database
|
||||
configuration.DATABASE.update({'ENGINE': 'django.db.backends.postgresql'})
|
||||
|
@ -1,25 +1,19 @@
|
||||
import urllib.parse
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||
from secrets.models import Secret, SecretRole
|
||||
from utilities.testing import create_test_user
|
||||
|
||||
|
||||
class SecretRoleTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
TEST_USERNAME = 'testuser'
|
||||
TEST_PASSWORD = 'testpassword'
|
||||
|
||||
User = get_user_model()
|
||||
User.objects.create(username=TEST_USERNAME, email='testuser@example.com', password=TEST_PASSWORD)
|
||||
|
||||
user = create_test_user(permissions=['secrets.view_secretrole'])
|
||||
self.client = Client()
|
||||
self.client.login(username=TEST_USERNAME, password=TEST_PASSWORD)
|
||||
self.client.force_login(user)
|
||||
|
||||
SecretRole.objects.bulk_create([
|
||||
SecretRole(name='Secret Role 1', slug='secret-role-1'),
|
||||
@ -29,7 +23,7 @@ class SecretRoleTestCase(TestCase):
|
||||
|
||||
def test_secretrole_list(self):
|
||||
|
||||
url = reverse('secrets:secret_list')
|
||||
url = reverse('secrets:secretrole_list')
|
||||
|
||||
response = self.client.get(url, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@ -38,8 +32,9 @@ class SecretRoleTestCase(TestCase):
|
||||
class SecretTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['secrets.view_secret'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
site = Site(name='Site 1', slug='site-1')
|
||||
site.save()
|
||||
@ -75,7 +70,7 @@ class SecretTestCase(TestCase):
|
||||
response = self.client.get('{}?{}'.format(url, urllib.parse.urlencode(params)), follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_configcontext(self):
|
||||
def test_secret(self):
|
||||
|
||||
secret = Secret.objects.first()
|
||||
response = self.client.get(secret.get_absolute_url(), follow=True)
|
||||
|
@ -32,7 +32,8 @@ def get_session_key(request):
|
||||
# Secret roles
|
||||
#
|
||||
|
||||
class SecretRoleListView(ObjectListView):
|
||||
class SecretRoleListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'secrets.view_secretrole'
|
||||
queryset = SecretRole.objects.annotate(secret_count=Count('secrets'))
|
||||
table = tables.SecretRoleTable
|
||||
template_name = 'secrets/secretrole_list.html'
|
||||
@ -67,8 +68,8 @@ class SecretRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Secrets
|
||||
#
|
||||
|
||||
@method_decorator(login_required, name='dispatch')
|
||||
class SecretListView(ObjectListView):
|
||||
class SecretListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'secrets.view_secret'
|
||||
queryset = Secret.objects.select_related('role', 'device')
|
||||
filter = filters.SecretFilter
|
||||
filter_form = forms.SecretFilterForm
|
||||
@ -76,8 +77,8 @@ class SecretListView(ObjectListView):
|
||||
template_name = 'secrets/secret_list.html'
|
||||
|
||||
|
||||
@method_decorator(login_required, name='dispatch')
|
||||
class SecretView(View):
|
||||
class SecretView(PermissionRequiredMixin, View):
|
||||
permission_required = 'secrets.view_secret'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -198,7 +199,7 @@ class SecretDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||
|
||||
|
||||
class SecretBulkImportView(BulkImportView):
|
||||
permission_required = 'ipam.add_vlan'
|
||||
permission_required = 'secrets.add_secret'
|
||||
model_form = forms.SecretCSVForm
|
||||
table = tables.SecretTable
|
||||
template_name = 'secrets/secret_import.html'
|
||||
|
@ -45,9 +45,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ circuit.get_absolute_url }}">Circuit</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'circuits:circuit_changelog' pk=circuit.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'circuits:circuit_changelog' pk=circuit.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -51,9 +51,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ provider.get_absolute_url }}">Provider</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'circuits:provider_changelog' slug=provider.slug %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'circuits:provider_changelog' slug=provider.slug %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -27,9 +27,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ cable.get_absolute_url }}">Cable</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:cable_changelog' pk=cable.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:cable_changelog' pk=cable.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -86,12 +86,16 @@
|
||||
{% include 'dcim/inc/device_napalm_tabs.html' %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<li role="presentation"{% if active_tab == 'config-context' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:device_configcontext' pk=device.pk %}">Config Context</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:device_changelog' pk=device.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_configcontext %}
|
||||
<li role="presentation"{% if active_tab == 'config-context' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:device_configcontext' pk=device.pk %}">Config Context</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:device_changelog' pk=device.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -50,9 +50,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ devicetype.get_absolute_url }}">Device Type</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:devicetype_changelog' pk=devicetype.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:devicetype_changelog' pk=devicetype.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -32,9 +32,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ interface.get_absolute_url }}">Interface</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:interface_changelog' pk=interface.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:interface_changelog' pk=interface.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -47,9 +47,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ rack.get_absolute_url }}">Rack</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:rack_changelog' pk=rack.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:rack_changelog' pk=rack.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -56,9 +56,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ site.get_absolute_url }}">Site</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:site_changelog' slug=site.slug %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'dcim:site_changelog' slug=site.slug %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -42,9 +42,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ tag.get_absolute_url }}">Tag</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'extras:tag_changelog' slug=tag.slug %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'extras:tag_changelog' slug=tag.slug %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -10,16 +10,20 @@
|
||||
<strong>Organization</strong>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.site_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'dcim:site_list' %}">Sites</a></h4>
|
||||
<p class="list-group-item-text text-muted">Geographic locations</p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.tenant_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'tenancy:tenant_list' %}">Tenants</a></h4>
|
||||
<p class="list-group-item-text text-muted">Customers or departments</p>
|
||||
</div>
|
||||
{% if perms.dcim.view_site %}
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.site_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'dcim:site_list' %}">Sites</a></h4>
|
||||
<p class="list-group-item-text text-muted">Geographic locations</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perms.tenancy.view_tenant %}
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.tenant_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'tenancy:tenant_list' %}">Tenants</a></h4>
|
||||
<p class="list-group-item-text text-muted">Customers or departments</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
@ -27,26 +31,38 @@
|
||||
<strong>DCIM</strong>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.rack_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'dcim:rack_list' %}">Racks</a></h4>
|
||||
<p class="list-group-item-text text-muted">Equipment racks, optionally organized by group</p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.device_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'dcim:device_list' %}">Devices</a></h4>
|
||||
<p class="list-group-item-text text-muted">Rack-mounted network equipment, servers, and other devices</p>
|
||||
</div>
|
||||
{% if perms.dcim.view_rack %}
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.rack_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'dcim:rack_list' %}">Racks</a></h4>
|
||||
<p class="list-group-item-text text-muted">Equipment racks, optionally organized by group</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perms.dcim.view_device %}
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.device_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'dcim:device_list' %}">Devices</a></h4>
|
||||
<p class="list-group-item-text text-muted">Rack-mounted network equipment, servers, and other devices</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="list-group-item">
|
||||
<h4 class="list-group-item-heading">Connections</h4>
|
||||
<span class="badge pull-right">{{ stats.cable_count }}</span>
|
||||
<p style="padding-left: 20px;"><a href="{% url 'dcim:cable_list' %}">Cables</a></p>
|
||||
<span class="badge pull-right">{{ stats.interface_connections_count }}</span>
|
||||
<p style="padding-left: 20px;"><a href="{% url 'dcim:interface_connections_list' %}">Interfaces</a></p>
|
||||
<span class="badge pull-right">{{ stats.console_connections_count }}</span>
|
||||
<p style="padding-left: 20px;"><a href="{% url 'dcim:console_connections_list' %}">Console</a></p>
|
||||
<span class="badge pull-right">{{ stats.power_connections_count }}</span>
|
||||
<p class="list-group-item-text" style="padding-left: 20px;"><a href="{% url 'dcim:power_connections_list' %}">Power</a></p>
|
||||
{% if perms.dcim.view_cable %}
|
||||
<span class="badge pull-right">{{ stats.cable_count }}</span>
|
||||
<p style="padding-left: 20px;"><a href="{% url 'dcim:cable_list' %}">Cables</a></p>
|
||||
{% endif %}
|
||||
{% if perms.dcim.view_interface %}
|
||||
<span class="badge pull-right">{{ stats.interface_connections_count }}</span>
|
||||
<p style="padding-left: 20px;"><a href="{% url 'dcim:interface_connections_list' %}">Interfaces</a></p>
|
||||
{% endif %}
|
||||
{% if perms.dcim.view_consoleport and perms.dcim.view_consoleserverport %}
|
||||
<span class="badge pull-right">{{ stats.console_connections_count }}</span>
|
||||
<p style="padding-left: 20px;"><a href="{% url 'dcim:console_connections_list' %}">Console</a></p>
|
||||
{% endif %}
|
||||
{% if perms.dcim.view_powerport and perms.dcim.view_poweroutlet %}
|
||||
<span class="badge pull-right">{{ stats.power_connections_count }}</span>
|
||||
<p class="list-group-item-text" style="padding-left: 20px;"><a href="{% url 'dcim:power_connections_list' %}">Power</a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -55,16 +71,20 @@
|
||||
<strong>Virtualization</strong>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.cluster_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'virtualization:cluster_list' %}">Clusters</a></h4>
|
||||
<p class="list-group-item-text text-muted">Clusters of physical hosts in which VMs reside</p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.virtualmachine_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'virtualization:virtualmachine_list' %}">Virtual Machines</a></h4>
|
||||
<p class="list-group-item-text text-muted">Virtual compute instances running inside clusters</p>
|
||||
</div>
|
||||
{% if perms.virtualization.view_cluster %}
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.cluster_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'virtualization:cluster_list' %}">Clusters</a></h4>
|
||||
<p class="list-group-item-text text-muted">Clusters of physical hosts in which VMs reside</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perms.virtualization.view_virtualmachine %}
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.virtualmachine_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'virtualization:virtualmachine_list' %}">Virtual Machines</a></h4>
|
||||
<p class="list-group-item-text text-muted">Virtual compute instances running inside clusters</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -74,31 +94,41 @@
|
||||
<strong>IPAM</strong>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.vrf_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'ipam:vrf_list' %}">VRFs</a></h4>
|
||||
<p class="list-group-item-text text-muted">Virtual routing and forwarding tables</p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.aggregate_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'ipam:aggregate_list' %}">Aggregates</a></h4>
|
||||
<p class="list-group-item-text text-muted">Top-level IP allocations</p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.prefix_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'ipam:prefix_list' %}">Prefixes</a></h4>
|
||||
<p class="list-group-item-text text-muted">IPv4 and IPv6 network assignments</p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.ipaddress_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'ipam:ipaddress_list' %}">IP Addresses</a></h4>
|
||||
<p class="list-group-item-text text-muted">Individual IPv4 and IPv6 addresses</p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.vlan_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'ipam:vlan_list' %}">VLANs</a></h4>
|
||||
<p class="list-group-item-text text-muted">Layer two domains, identified by VLAN ID</p>
|
||||
</div>
|
||||
{% if perms.ipam.view_vrf %}
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.vrf_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'ipam:vrf_list' %}">VRFs</a></h4>
|
||||
<p class="list-group-item-text text-muted">Virtual routing and forwarding tables</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perms.ipam.view_aggregate %}
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.aggregate_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'ipam:aggregate_list' %}">Aggregates</a></h4>
|
||||
<p class="list-group-item-text text-muted">Top-level IP allocations</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perms.ipam.view_prefix %}
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.prefix_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'ipam:prefix_list' %}">Prefixes</a></h4>
|
||||
<p class="list-group-item-text text-muted">IPv4 and IPv6 network assignments</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perms.ipam.view_ipaddress %}
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.ipaddress_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'ipam:ipaddress_list' %}">IP Addresses</a></h4>
|
||||
<p class="list-group-item-text text-muted">Individual IPv4 and IPv6 addresses</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perms.ipam.view_vlan %}
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.vlan_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'ipam:vlan_list' %}">VLANs</a></h4>
|
||||
<p class="list-group-item-text text-muted">Layer two domains, identified by VLAN ID</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
@ -106,16 +136,20 @@
|
||||
<strong>Circuits</strong>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.provider_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'circuits:provider_list' %}">Providers</a></h4>
|
||||
<p class="list-group-item-text text-muted">Organizations which provide circuit connectivity</p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.circuit_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'circuits:circuit_list' %}">Circuits</a></h4>
|
||||
<p class="list-group-item-text text-muted">Communication links for Internet transit, peering, and other services</p>
|
||||
</div>
|
||||
{% if perms.circuits.view_provider %}
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.provider_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'circuits:provider_list' %}">Providers</a></h4>
|
||||
<p class="list-group-item-text text-muted">Organizations which provide circuit connectivity</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perms.circuits.view_circuit %}
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.circuit_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'circuits:circuit_list' %}">Circuits</a></h4>
|
||||
<p class="list-group-item-text text-muted">Communication links for Internet transit, peering, and other services</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.secrets %}
|
||||
@ -134,26 +168,28 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Global Topology Maps</strong>
|
||||
</div>
|
||||
{% if topology_maps %}
|
||||
<table class="table table-hover panel-body">
|
||||
{% for tm in topology_maps %}
|
||||
<tr>
|
||||
<td><i class="fa fa-fw fa-map-o"></i> <a href="{% url 'extras-api:topologymap-render' pk=tm.pk %}" target="_blank">{{ tm }}</a></td>
|
||||
<td>{{ tm.description }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="panel-body text-muted">
|
||||
None
|
||||
{% if perms.extras.view_topologymap %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Global Topology Maps</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if report_results %}
|
||||
{% if topology_maps %}
|
||||
<table class="table table-hover panel-body">
|
||||
{% for tm in topology_maps %}
|
||||
<tr>
|
||||
<td><i class="fa fa-fw fa-map-o"></i> <a href="{% url 'extras-api:topologymap-render' pk=tm.pk %}" target="_blank">{{ tm }}</a></td>
|
||||
<td>{{ tm.description }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="panel-body text-muted">
|
||||
None
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if report_results and perms.extras.view_reportresult %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Reports</strong>
|
||||
@ -168,44 +204,46 @@
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Changelog</strong>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Changelog</strong>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
{% for change in changelog %}
|
||||
{% with action=change.get_action_display|lower %}
|
||||
<div class="list-group-item">
|
||||
{% if action == 'created' %}
|
||||
<span class="label label-success"><i class="fa fa-plus"></i></span>
|
||||
{% elif action == 'updated' %}
|
||||
<span class="label label-warning"><i class="fa fa-pencil"></i></span>
|
||||
{% elif action == 'deleted' %}
|
||||
<span class="label label-danger"><i class="fa fa-trash"></i></span>
|
||||
{% endif %}
|
||||
{{ change.changed_object_type.name|bettertitle }}
|
||||
{% if change.changed_object.get_absolute_url %}
|
||||
<a href="{{ change.changed_object.get_absolute_url }}">{{ change.changed_object }}</a>
|
||||
{% else %}
|
||||
{{ change.changed_object|default:change.object_repr }}
|
||||
{% endif %}
|
||||
<br />
|
||||
<small>
|
||||
<span class="text-muted">{{ change.user|default:change.user_name }} -</span>
|
||||
<a href="{{ change.get_absolute_url }}" class="text-muted">{{ change.time|date:'SHORT_DATETIME_FORMAT' }}</a>
|
||||
</small>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% if forloop.last %}
|
||||
<div class="list-group-item text-right">
|
||||
<a href="{% url 'extras:objectchange_list' %}">View All Changes</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<div class="list-group-item text-muted">No change history found</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
{% for change in changelog %}
|
||||
{% with action=change.get_action_display|lower %}
|
||||
<div class="list-group-item">
|
||||
{% if action == 'created' %}
|
||||
<span class="label label-success"><i class="fa fa-plus"></i></span>
|
||||
{% elif action == 'updated' %}
|
||||
<span class="label label-warning"><i class="fa fa-pencil"></i></span>
|
||||
{% elif action == 'deleted' %}
|
||||
<span class="label label-danger"><i class="fa fa-trash"></i></span>
|
||||
{% endif %}
|
||||
{{ change.changed_object_type.name|bettertitle }}
|
||||
{% if change.changed_object.get_absolute_url %}
|
||||
<a href="{{ change.changed_object.get_absolute_url }}">{{ change.changed_object }}</a>
|
||||
{% else %}
|
||||
{{ change.changed_object|default:change.object_repr }}
|
||||
{% endif %}
|
||||
<br />
|
||||
<small>
|
||||
<span class="text-muted">{{ change.user|default:change.user_name }} -</span>
|
||||
<a href="{{ change.get_absolute_url }}" class="text-muted">{{ change.time|date:'SHORT_DATETIME_FORMAT' }}</a>
|
||||
</small>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% if forloop.last %}
|
||||
<div class="list-group-item text-right">
|
||||
<a href="{% url 'extras:objectchange_list' %}">View All Changes</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<div class="list-group-item text-muted">No change history found</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -20,7 +20,7 @@
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Organization <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-header">Sites</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_site %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_site %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:site_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -29,7 +29,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:site_list' %}">Sites</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_region %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_region %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:region_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -40,7 +40,7 @@
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Tenancy</li>
|
||||
<li>
|
||||
<li{% if not perms.tenancy.view_tenant %} class="disabled"{% endif %}>
|
||||
{% if perms.tenancy.add_tenant %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'tenancy:tenant_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -49,7 +49,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'tenancy:tenant_list' %}">Tenants</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.tenancy.view_tenantgroup %} class="disabled"{% endif %}>
|
||||
{% if perms.tenancy.add_tenantgroup %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'tenancy:tenantgroup_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -60,16 +60,16 @@
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Miscellaneous</li>
|
||||
<li>
|
||||
<li{% if not perms.extras.view_tag %} class="disabled"{% endif %}>
|
||||
<a href="{% url 'extras:tag_list' %}">Tags</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.extras.view_configcontext %} class="disabled"{% endif %}>
|
||||
<a href="{% url 'extras:configcontext_list' %}">Config Contexts</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.extras.view_reportresult %} class="disabled"{% endif %}>
|
||||
<a href="{% url 'extras:report_list' %}">Reports</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.extras.view_objectchange %} class="disabled"{% endif %}>
|
||||
<a href="{% url 'extras:objectchange_list' %}">Changelog</a>
|
||||
</li>
|
||||
</ul>
|
||||
@ -78,7 +78,7 @@
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Racks <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-header">Racks</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_rack %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_rack %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:rack_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -87,7 +87,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:rack_list' %}">Racks</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_rackgroup %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_rackgroup %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:rackgroup_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -96,7 +96,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:rackgroup_list' %}">Rack Groups</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_rackrole %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_rackrole %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:rackrole_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -105,10 +105,10 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:rackrole_list' %}">Rack Roles</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_rack %} class="disabled"{% endif %}>
|
||||
<a href="{% url 'dcim:rack_elevation_list' %}">Elevations</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_rackreservation %} class="disabled"{% endif %}>
|
||||
<a href="{% url 'dcim:rackreservation_list' %}">Reservations</a>
|
||||
</li>
|
||||
</ul>
|
||||
@ -117,7 +117,7 @@
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Devices <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-header">Devices</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_device %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_device %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:device_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -126,7 +126,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:device_list' %}">Devices</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_devicerole %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_devicerole %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:devicerole_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -135,7 +135,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:devicerole_list' %}">Device Roles</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_platform %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_platform %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:platform_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -144,12 +144,12 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:platform_list' %}">Platforms</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_virtualchassis %} class="disabled"{% endif %}>
|
||||
<a href="{% url 'dcim:virtualchassis_list' %}">Virtual Chassis</a>
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Device Types</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_devicetype %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_devicetype %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:devicetype_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -158,7 +158,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:devicetype_list' %}">Device Types</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_manufacturer %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_manufacturer %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:manufacturer_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -169,7 +169,7 @@
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Inventory</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_inventoryitem %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_inventoryitem %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:inventoryitem_import' %}" class="btn btn-xs btn-info" title="Import"><i class="fa fa-download"></i></a>
|
||||
@ -179,7 +179,7 @@
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Connections</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_cable %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_cable %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:cable_import' %}" class="btn btn-xs btn-info" title="Import"><i class="fa fa-download"></i></a>
|
||||
@ -187,13 +187,13 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:cable_list' %}">Cables</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_consoleport or not perms.dcim.view_consoleserverport %} class="disabled"{% endif %}>
|
||||
<a href="{% url 'dcim:console_connections_list' %}">Console Connections</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_powerport or not perms.dcim.view_poweroutlet %} class="disabled"{% endif %}>
|
||||
<a href="{% url 'dcim:power_connections_list' %}">Power Connections</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_interface %} class="disabled"{% endif %}>
|
||||
<a href="{% url 'dcim:interface_connections_list' %}">Interface Connections</a>
|
||||
</li>
|
||||
</ul>
|
||||
@ -202,7 +202,7 @@
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">IPAM <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-header">IP Addresses</li>
|
||||
<li>
|
||||
<li{% if not perms.ipam.view_ipaddress %} class="disabled"{% endif %}>
|
||||
{% if perms.ipam.add_ipaddress %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'ipam:ipaddress_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -213,7 +213,7 @@
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Prefixes</li>
|
||||
<li>
|
||||
<li{% if not perms.ipam.view_prefix %} class="disabled"{% endif %}>
|
||||
{% if perms.ipam.add_prefix %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'ipam:prefix_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -222,7 +222,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'ipam:prefix_list' %}">Prefixes</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.ipam.view_role %} class="disabled"{% endif %}>
|
||||
{% if perms.ipam.add_role %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'ipam:role_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -233,7 +233,7 @@
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Aggregates</li>
|
||||
<li>
|
||||
<li{% if not perms.ipam.view_aggregate %} class="disabled"{% endif %}>
|
||||
{% if perms.ipam.add_aggregate %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'ipam:aggregate_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -242,7 +242,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'ipam:aggregate_list' %}">Aggregates</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.ipam.view_rir %} class="disabled"{% endif %}>
|
||||
{% if perms.ipam.add_rir %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'ipam:rir_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -253,7 +253,7 @@
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">VRFs</li>
|
||||
<li>
|
||||
<li{% if not perms.ipam.view_vrf %} class="disabled"{% endif %}>
|
||||
{% if perms.ipam.add_vrf %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'ipam:vrf_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -264,7 +264,7 @@
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">VLANs</li>
|
||||
<li>
|
||||
<li{% if not perms.ipam.view_vlan %} class="disabled"{% endif %}>
|
||||
{% if perms.ipam.add_vlan %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'ipam:vlan_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -273,7 +273,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'ipam:vlan_list' %}">VLANs</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.ipam.view_vlangroup %} class="disabled"{% endif %}>
|
||||
{% if perms.ipam.add_vlangroup %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'ipam:vlangroup_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -284,7 +284,7 @@
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Services</li>
|
||||
<li>
|
||||
<li{% if not perms.ipam.view_service %} class="disabled"{% endif %}>
|
||||
<a href="{% url 'ipam:service_list' %}">Services</a>
|
||||
</li>
|
||||
</ul>
|
||||
@ -293,7 +293,7 @@
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Virtualization <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-header">Virtual Machines</li>
|
||||
<li>
|
||||
<li{% if not perms.virtualization.view_virtualmachine %} class="disabled"{% endif %}>
|
||||
{% if perms.virtualization.add_virtualmachine %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'virtualization:virtualmachine_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -304,7 +304,7 @@
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Clusters</li>
|
||||
<li>
|
||||
<li{% if not perms.virtualization.view_cluster %} class="disabled"{% endif %}>
|
||||
{% if perms.virtualization.add_cluster %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'virtualization:cluster_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -313,7 +313,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'virtualization:cluster_list' %}">Clusters</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.virtualization.view_clustertype %} class="disabled"{% endif %}>
|
||||
{% if perms.virtualization.add_clustertype %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'virtualization:clustertype_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -322,7 +322,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'virtualization:clustertype_list' %}">Cluster Types</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.virtualization.view_clustergroup %} class="disabled"{% endif %}>
|
||||
{% if perms.virtualization.add_clustergroup %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'virtualization:clustergroup_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -337,7 +337,7 @@
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Circuits <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-header">Circuits</li>
|
||||
<li>
|
||||
<li{% if not perms.circuits.view_circuit %} class="disabled"{% endif %}>
|
||||
{% if perms.circuits.add_circuit %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'circuits:circuit_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -346,7 +346,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'circuits:circuit_list' %}">Circuits</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.circuits.view_circuittype %} class="disabled"{% endif %}>
|
||||
{% if perms.circuits.add_circuittype %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'circuits:circuittype_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -357,7 +357,7 @@
|
||||
</li>
|
||||
<li class="divider"></li>
|
||||
<li class="dropdown-header">Providers</li>
|
||||
<li>
|
||||
<li{% if not perms.circuits.view_provider %} class="disabled"{% endif %}>
|
||||
{% if perms.circuits.add_provider %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'circuits:provider_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -371,7 +371,7 @@
|
||||
<li class="dropdown{% if request.path|contains:'/dcim/power' %} active{% endif %}">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Power <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_powerfeed %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_powerfeed %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:powerfeed_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
@ -380,7 +380,7 @@
|
||||
{% endif %}
|
||||
<a href="{% url 'dcim:powerfeed_list' %}">Power Feeds</a>
|
||||
</li>
|
||||
<li>
|
||||
<li{% if not perms.dcim.view_powerpanel %} class="disabled"{% endif %}>
|
||||
{% if perms.dcim.add_powerpanel %}
|
||||
<div class="buttons pull-right">
|
||||
<a href="{% url 'dcim:powerpanel_add' %}" class="btn btn-xs btn-success" title="Add"><i class="fa fa-plus"></i></a>
|
||||
|
@ -43,9 +43,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ aggregate.get_absolute_url }}">Aggregate</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:aggregate_changelog' pk=aggregate.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:aggregate_changelog' pk=aggregate.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -45,9 +45,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ ipaddress.get_absolute_url }}">IP Address</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:ipaddress_changelog' pk=ipaddress.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:ipaddress_changelog' pk=ipaddress.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -59,12 +59,16 @@
|
||||
<li role="presentation"{% if active_tab == 'prefixes' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:prefix_prefixes' pk=prefix.pk %}">Child Prefixes <span class="badge">{{ prefix.get_child_prefixes.count }}</span></a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'ip-addresses' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:prefix_ipaddresses' pk=prefix.pk %}">IP Addresses <span class="badge">{{ prefix.get_child_ips.count }}</span></a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:prefix_changelog' pk=prefix.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.ipam.view_ipaddress %}
|
||||
<li role="presentation"{% if active_tab == 'ip-addresses' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:prefix_ipaddresses' pk=prefix.pk %}">IP Addresses <span class="badge">{{ prefix.get_child_ips.count }}</span></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:prefix_changelog' pk=prefix.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -51,9 +51,11 @@
|
||||
<li role="presentation"{% if active_tab == 'members' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:vlan_members' pk=vlan.pk %}">Members <span class="badge">{{ vlan.get_members.count }}</span></a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:vlan_changelog' pk=vlan.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:vlan_changelog' pk=vlan.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -42,9 +42,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ vrf.get_absolute_url }}">VRF</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:vrf_changelog' pk=vrf.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'ipam:vrf_changelog' pk=vrf.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -33,9 +33,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ secret.get_absolute_url }}">Secret</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'secrets:secret_changelog' pk=secret.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'secrets:secret_changelog' pk=secret.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -45,9 +45,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ tenant.get_absolute_url }}">Tenant</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'tenancy:tenant_changelog' slug=tenant.slug %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'tenancy:tenant_changelog' slug=tenant.slug %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -45,9 +45,11 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ cluster.get_absolute_url }}">Cluster</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'virtualization:cluster_changelog' pk=cluster.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'virtualization:cluster_changelog' pk=cluster.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -44,12 +44,16 @@
|
||||
<li role="presentation"{% if not active_tab %} class="active"{% endif %}>
|
||||
<a href="{{ virtualmachine.get_absolute_url }}">Virtual Machine</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'config-context' %} class="active"{% endif %}>
|
||||
<a href="{% url 'virtualization:virtualmachine_configcontext' pk=virtualmachine.pk %}">Config Context</a>
|
||||
</li>
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'virtualization:virtualmachine_changelog' pk=virtualmachine.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% if perms.extras.view_configcontext %}
|
||||
<li role="presentation"{% if active_tab == 'config-context' %} class="active"{% endif %}>
|
||||
<a href="{% url 'virtualization:virtualmachine_configcontext' pk=virtualmachine.pk %}">Config Context</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.extras.view_objectchange %}
|
||||
<li role="presentation"{% if active_tab == 'changelog' %} class="active"{% endif %}>
|
||||
<a href="{% url 'virtualization:virtualmachine_changelog' pk=virtualmachine.pk %}">Changelog</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -4,13 +4,15 @@ from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from tenancy.models import Tenant, TenantGroup
|
||||
from utilities.testing import create_test_user
|
||||
|
||||
|
||||
class TenantGroupTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['tenancy.view_tenantgroup'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
TenantGroup.objects.bulk_create([
|
||||
TenantGroup(name='Tenant Group 1', slug='tenant-group-1'),
|
||||
@ -29,8 +31,9 @@ class TenantGroupTestCase(TestCase):
|
||||
class TenantTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['tenancy.view_tenant'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
tenantgroup = TenantGroup(name='Tenant Group 1', slug='tenant-group-1')
|
||||
tenantgroup.save()
|
||||
|
@ -18,7 +18,8 @@ from .models import Tenant, TenantGroup
|
||||
# Tenant groups
|
||||
#
|
||||
|
||||
class TenantGroupListView(ObjectListView):
|
||||
class TenantGroupListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'tenancy.view_tenantgroup'
|
||||
queryset = TenantGroup.objects.annotate(tenant_count=Count('tenants'))
|
||||
table = tables.TenantGroupTable
|
||||
template_name = 'tenancy/tenantgroup_list.html'
|
||||
@ -53,7 +54,8 @@ class TenantGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Tenants
|
||||
#
|
||||
|
||||
class TenantListView(ObjectListView):
|
||||
class TenantListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'tenancy.view_tenant'
|
||||
queryset = Tenant.objects.select_related('group')
|
||||
filter = filters.TenantFilter
|
||||
filter_form = forms.TenantFilterForm
|
||||
@ -61,7 +63,8 @@ class TenantListView(ObjectListView):
|
||||
template_name = 'tenancy/tenant_list.html'
|
||||
|
||||
|
||||
class TenantView(View):
|
||||
class TenantView(PermissionRequiredMixin, View):
|
||||
permission_required = 'tenancy.view_tenant'
|
||||
|
||||
def get(self, request, slug):
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import login as auth_login, logout as auth_logout, update_session_auth_hash
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
|
||||
from django.http import HttpResponseForbidden, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
@ -74,8 +73,7 @@ class LogoutView(View):
|
||||
# User profiles
|
||||
#
|
||||
|
||||
@method_decorator(login_required, name='dispatch')
|
||||
class ProfileView(View):
|
||||
class ProfileView(LoginRequiredMixin, View):
|
||||
template_name = 'users/profile.html'
|
||||
|
||||
def get(self, request):
|
||||
@ -85,8 +83,7 @@ class ProfileView(View):
|
||||
})
|
||||
|
||||
|
||||
@method_decorator(login_required, name='dispatch')
|
||||
class ChangePasswordView(View):
|
||||
class ChangePasswordView(LoginRequiredMixin, View):
|
||||
template_name = 'users/change_password.html'
|
||||
|
||||
def get(self, request):
|
||||
@ -111,8 +108,7 @@ class ChangePasswordView(View):
|
||||
})
|
||||
|
||||
|
||||
@method_decorator(login_required, name='dispatch')
|
||||
class UserKeyView(View):
|
||||
class UserKeyView(LoginRequiredMixin, View):
|
||||
template_name = 'users/userkey.html'
|
||||
|
||||
def get(self, request):
|
||||
@ -127,10 +123,9 @@ class UserKeyView(View):
|
||||
})
|
||||
|
||||
|
||||
class UserKeyEditView(View):
|
||||
class UserKeyEditView(LoginRequiredMixin, View):
|
||||
template_name = 'users/userkey_edit.html'
|
||||
|
||||
@method_decorator(login_required)
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
self.userkey = UserKey.objects.get(user=request.user)
|
||||
@ -164,7 +159,6 @@ class UserKeyEditView(View):
|
||||
})
|
||||
|
||||
|
||||
@method_decorator(login_required, name='dispatch')
|
||||
class SessionKeyDeleteView(LoginRequiredMixin, View):
|
||||
|
||||
def get(self, request):
|
||||
|
28
netbox/utilities/auth_backends.py
Normal file
28
netbox/utilities/auth_backends.py
Normal file
@ -0,0 +1,28 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
|
||||
|
||||
class ViewExemptModelBackend(ModelBackend):
|
||||
"""
|
||||
Custom implementation of Django's stock ModelBackend which allows for the exemption of arbitrary models from view
|
||||
permission enforcement.
|
||||
"""
|
||||
def has_perm(self, user_obj, perm, obj=None):
|
||||
|
||||
# If this is a view permission, check whether the model has been exempted from enforcement
|
||||
try:
|
||||
app, codename = perm.split('.')
|
||||
action, model = codename.split('_')
|
||||
if action == 'view':
|
||||
if (
|
||||
# All models are exempt from view permission enforcement
|
||||
'*' in settings.EXEMPT_VIEW_PERMISSIONS
|
||||
) or (
|
||||
# This specific model is exempt from view permission enforcement
|
||||
'{}.{}'.format(app, model) in settings.EXEMPT_VIEW_PERMISSIONS
|
||||
):
|
||||
return True
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return super().has_perm(user_obj, perm, obj)
|
@ -1,4 +1,4 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.models import Permission, User
|
||||
from rest_framework.test import APITestCase as _APITestCase
|
||||
|
||||
from users.models import Token
|
||||
@ -22,3 +22,16 @@ class APITestCase(_APITestCase):
|
||||
self.assertEqual(response.status_code, expected_status, err_message.format(
|
||||
expected_status, response.status_code, response.data
|
||||
))
|
||||
|
||||
|
||||
def create_test_user(username='testuser', permissions=list()):
|
||||
"""
|
||||
Create a User with the given permissions.
|
||||
"""
|
||||
user = User.objects.create_user(username=username)
|
||||
for perm_name in permissions:
|
||||
app, codename = perm_name.split('.')
|
||||
perm = Permission.objects.get(content_type__app_label=app, codename=codename)
|
||||
user.user_permissions.add(perm)
|
||||
|
||||
return user
|
||||
|
@ -3,14 +3,16 @@ import urllib.parse
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from utilities.testing import create_test_user
|
||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||
|
||||
|
||||
class ClusterGroupTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['virtualization.view_clustergroup'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
ClusterGroup.objects.bulk_create([
|
||||
ClusterGroup(name='Cluster Group 1', slug='cluster-group-1'),
|
||||
@ -29,8 +31,9 @@ class ClusterGroupTestCase(TestCase):
|
||||
class ClusterTypeTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['virtualization.view_clustertype'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
ClusterType.objects.bulk_create([
|
||||
ClusterType(name='Cluster Type 1', slug='cluster-type-1'),
|
||||
@ -49,8 +52,9 @@ class ClusterTypeTestCase(TestCase):
|
||||
class ClusterTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['virtualization.view_cluster'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
clustergroup = ClusterGroup(name='Cluster Group 1', slug='cluster-group-1')
|
||||
clustergroup.save()
|
||||
@ -85,8 +89,9 @@ class ClusterTestCase(TestCase):
|
||||
class VirtualMachineTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
user = create_test_user(permissions=['virtualization.view_virtualmachine'])
|
||||
self.client = Client()
|
||||
self.client.force_login(user)
|
||||
|
||||
clustertype = ClusterType(name='Cluster Type 1', slug='cluster-type-1')
|
||||
clustertype.save()
|
||||
|
@ -22,7 +22,8 @@ from .models import Cluster, ClusterGroup, ClusterType, VirtualMachine
|
||||
# Cluster types
|
||||
#
|
||||
|
||||
class ClusterTypeListView(ObjectListView):
|
||||
class ClusterTypeListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'virtualization.view_clustertype'
|
||||
queryset = ClusterType.objects.annotate(cluster_count=Count('clusters'))
|
||||
table = tables.ClusterTypeTable
|
||||
template_name = 'virtualization/clustertype_list.html'
|
||||
@ -57,7 +58,8 @@ class ClusterTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Cluster groups
|
||||
#
|
||||
|
||||
class ClusterGroupListView(ObjectListView):
|
||||
class ClusterGroupListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'virtualization.view_clustergroup'
|
||||
queryset = ClusterGroup.objects.annotate(cluster_count=Count('clusters'))
|
||||
table = tables.ClusterGroupTable
|
||||
template_name = 'virtualization/clustergroup_list.html'
|
||||
@ -92,7 +94,8 @@ class ClusterGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
# Clusters
|
||||
#
|
||||
|
||||
class ClusterListView(ObjectListView):
|
||||
class ClusterListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'virtualization.view_cluster'
|
||||
queryset = Cluster.objects.select_related('type', 'group', 'site')
|
||||
table = tables.ClusterTable
|
||||
filter = filters.ClusterFilter
|
||||
@ -100,7 +103,8 @@ class ClusterListView(ObjectListView):
|
||||
template_name = 'virtualization/cluster_list.html'
|
||||
|
||||
|
||||
class ClusterView(View):
|
||||
class ClusterView(PermissionRequiredMixin, View):
|
||||
permission_required = 'virtualization.view_cluster'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -247,7 +251,8 @@ class ClusterRemoveDevicesView(PermissionRequiredMixin, View):
|
||||
# Virtual machines
|
||||
#
|
||||
|
||||
class VirtualMachineListView(ObjectListView):
|
||||
class VirtualMachineListView(PermissionRequiredMixin, ObjectListView):
|
||||
permission_required = 'virtualization.view_virtualmachine'
|
||||
queryset = VirtualMachine.objects.select_related('cluster', 'tenant', 'role', 'primary_ip4', 'primary_ip6')
|
||||
filter = filters.VirtualMachineFilter
|
||||
filter_form = forms.VirtualMachineFilterForm
|
||||
@ -255,7 +260,8 @@ class VirtualMachineListView(ObjectListView):
|
||||
template_name = 'virtualization/virtualmachine_list.html'
|
||||
|
||||
|
||||
class VirtualMachineView(View):
|
||||
class VirtualMachineView(PermissionRequiredMixin, View):
|
||||
permission_required = 'virtualization.view_virtualmachine'
|
||||
|
||||
def get(self, request, pk):
|
||||
|
||||
@ -270,7 +276,8 @@ class VirtualMachineView(View):
|
||||
})
|
||||
|
||||
|
||||
class VirtualMachineConfigContextView(ObjectConfigContextView):
|
||||
class VirtualMachineConfigContextView(PermissionRequiredMixin, ObjectConfigContextView):
|
||||
permission_required = 'virtualization.view_virtualmachine'
|
||||
object_class = VirtualMachine
|
||||
base_template = 'virtualization/virtualmachine.html'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user