diff --git a/base_requirements.txt b/base_requirements.txt index 2aa97f708..2a5265504 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -2,6 +2,10 @@ # https://github.com/django/django Django +# Django caching using Redis +# https://github.com/Suor/django-cacheops +django-cacheops + # Django middleware which permits cross-domain API requests # https://github.com/OttoYiu/django-cors-headers django-cors-headers @@ -18,10 +22,6 @@ django-filter # https://github.com/django-mptt/django-mptt django-mptt -# Django caching using Redis -# https://github.com/niwinz/django-redis -django-redis - # Abstraction models for rendering and paginating HTML tables # https://github.com/jieter/django-tables2 django-tables2 diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 952c5a778..29408620b 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -197,7 +197,6 @@ class SiteListView(PermissionRequiredMixin, ObjectListView): class SiteView(PermissionRequiredMixin, View): permission_required = 'dcim.view_site' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, slug): site = get_object_or_404(Site.objects.select_related('region', 'tenant__group'), slug=slug) @@ -356,7 +355,6 @@ class RackElevationListView(PermissionRequiredMixin, View): """ permission_required = 'dcim.view_rack' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request): racks = Rack.objects.select_related( @@ -396,7 +394,6 @@ class RackElevationListView(PermissionRequiredMixin, View): class RackView(PermissionRequiredMixin, View): permission_required = 'dcim.view_rack' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): rack = get_object_or_404(Rack.objects.select_related('site__region', 'tenant__group', 'group', 'role'), pk=pk) @@ -575,7 +572,6 @@ class DeviceTypeListView(PermissionRequiredMixin, ObjectListView): class DeviceTypeView(PermissionRequiredMixin, View): permission_required = 'dcim.view_devicetype' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): devicetype = get_object_or_404(DeviceType, pk=pk) @@ -916,7 +912,6 @@ class DeviceListView(PermissionRequiredMixin, ObjectListView): class DeviceView(PermissionRequiredMixin, View): permission_required = 'dcim.view_device' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): device = get_object_or_404(Device.objects.select_related( @@ -998,7 +993,6 @@ class DeviceView(PermissionRequiredMixin, View): class DeviceInventoryView(PermissionRequiredMixin, View): permission_required = 'dcim.view_device' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): device = get_object_or_404(Device, pk=pk) @@ -1020,7 +1014,6 @@ class DeviceInventoryView(PermissionRequiredMixin, View): class DeviceStatusView(PermissionRequiredMixin, View): permission_required = ('dcim.view_device', 'dcim.napalm_read') - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): device = get_object_or_404(Device, pk=pk) @@ -1034,7 +1027,6 @@ class DeviceStatusView(PermissionRequiredMixin, View): class DeviceLLDPNeighborsView(PermissionRequiredMixin, View): permission_required = ('dcim.view_device', 'dcim.napalm_read') - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): device = get_object_or_404(Device, pk=pk) @@ -1052,7 +1044,6 @@ class DeviceLLDPNeighborsView(PermissionRequiredMixin, View): class DeviceConfigView(PermissionRequiredMixin, View): permission_required = ('dcim.view_device', 'dcim.napalm_read') - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): device = get_object_or_404(Device, pk=pk) @@ -1290,7 +1281,6 @@ class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class InterfaceView(PermissionRequiredMixin, View): permission_required = 'dcim.view_interface' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): interface = get_object_or_404(Interface, pk=pk) @@ -1511,7 +1501,6 @@ class DeviceBayDeleteView(PermissionRequiredMixin, ObjectDeleteView): class DeviceBayPopulateView(PermissionRequiredMixin, View): permission_required = 'dcim.change_devicebay' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): device_bay = get_object_or_404(DeviceBay, pk=pk) @@ -1546,7 +1535,6 @@ class DeviceBayPopulateView(PermissionRequiredMixin, View): class DeviceBayDepopulateView(PermissionRequiredMixin, View): permission_required = 'dcim.change_devicebay' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): device_bay = get_object_or_404(DeviceBay, pk=pk) @@ -1686,7 +1674,6 @@ class CableListView(PermissionRequiredMixin, ObjectListView): class CableView(PermissionRequiredMixin, View): permission_required = 'dcim.view_cable' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): cable = get_object_or_404(Cable, pk=pk) @@ -1702,7 +1689,6 @@ class CableTraceView(PermissionRequiredMixin, View): """ permission_required = 'dcim.view_cable' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, model, pk): obj = get_object_or_404(model, pk=pk) @@ -1742,7 +1728,6 @@ class CableCreateView(PermissionRequiredMixin, GetReturnURLMixin, View): return super().dispatch(request, *args, **kwargs) - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, *args, **kwargs): # Parse initial data manually to avoid setting field values as lists @@ -2059,7 +2044,6 @@ class VirtualChassisCreateView(PermissionRequiredMixin, View): class VirtualChassisEditView(PermissionRequiredMixin, GetReturnURLMixin, View): permission_required = 'dcim.change_virtualchassis' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): virtual_chassis = get_object_or_404(VirtualChassis, pk=pk) @@ -2128,7 +2112,6 @@ class VirtualChassisDeleteView(PermissionRequiredMixin, ObjectDeleteView): class VirtualChassisAddMemberView(PermissionRequiredMixin, GetReturnURLMixin, View): permission_required = 'dcim.change_virtualchassis' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): virtual_chassis = get_object_or_404(VirtualChassis, pk=pk) @@ -2183,7 +2166,6 @@ class VirtualChassisAddMemberView(PermissionRequiredMixin, GetReturnURLMixin, Vi class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin, View): permission_required = 'dcim.change_virtualchassis' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): device = get_object_or_404(Device, pk=pk, virtual_chassis__isnull=False) @@ -2247,7 +2229,6 @@ class PowerPanelListView(PermissionRequiredMixin, ObjectListView): class PowerPanelView(PermissionRequiredMixin, View): permission_required = 'dcim.view_powerpanel' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): powerpanel = get_object_or_404(PowerPanel.objects.select_related('site', 'rack_group'), pk=pk) @@ -2317,7 +2298,6 @@ class PowerFeedListView(PermissionRequiredMixin, ObjectListView): class PowerFeedView(PermissionRequiredMixin, View): permission_required = 'dcim.view_powerfeed' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): powerfeed = get_object_or_404(PowerFeed.objects.select_related('power_panel', 'rack'), pk=pk) diff --git a/netbox/extras/views.py b/netbox/extras/views.py index cb9b40cdd..005123ffd 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -43,7 +43,6 @@ class TagListView(ObjectListView): class TagView(View): - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, slug): tag = get_object_or_404(Tag, slug=slug) @@ -111,7 +110,6 @@ class ConfigContextListView(PermissionRequiredMixin, ObjectListView): class ConfigContextView(PermissionRequiredMixin, View): permission_required = 'extras.view_configcontext' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): configcontext = get_object_or_404(ConfigContext, pk=pk) @@ -159,7 +157,6 @@ class ObjectConfigContextView(View): object_class = None base_template = None - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): obj = get_object_or_404(self.object_class, pk=pk) @@ -192,7 +189,6 @@ class ObjectChangeListView(PermissionRequiredMixin, ObjectListView): class ObjectChangeView(PermissionRequiredMixin, View): permission_required = 'extras.view_objectchange' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): objectchange = get_object_or_404(ObjectChange, pk=pk) @@ -215,7 +211,6 @@ class ObjectChangeLogView(View): Present a history of changes made to a particular object. """ - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, model, **kwargs): # Get object my model and kwargs (e.g. slug='foo') @@ -289,7 +284,6 @@ class ReportListView(PermissionRequiredMixin, View): """ permission_required = 'extras.view_reportresult' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request): reports = get_reports() @@ -314,7 +308,6 @@ class ReportView(PermissionRequiredMixin, View): """ permission_required = 'extras.view_reportresult' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, name): # Retrieve the Report by "." diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index c2ab13301..94a8f7898 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -127,7 +127,6 @@ class VRFListView(PermissionRequiredMixin, ObjectListView): class VRFView(PermissionRequiredMixin, View): permission_required = 'ipam.view_vrf' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): vrf = get_object_or_404(VRF.objects.all(), pk=pk) @@ -322,7 +321,6 @@ class AggregateListView(PermissionRequiredMixin, ObjectListView): class AggregateView(PermissionRequiredMixin, View): permission_required = 'ipam.view_aggregate' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): aggregate = get_object_or_404(Aggregate, pk=pk) @@ -460,7 +458,6 @@ class PrefixListView(PermissionRequiredMixin, ObjectListView): class PrefixView(PermissionRequiredMixin, View): permission_required = 'ipam.view_prefix' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): prefix = get_object_or_404(Prefix.objects.select_related( @@ -505,7 +502,6 @@ class PrefixView(PermissionRequiredMixin, View): class PrefixPrefixesView(PermissionRequiredMixin, View): permission_required = 'ipam.view_prefix' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): prefix = get_object_or_404(Prefix.objects.all(), pk=pk) @@ -549,7 +545,6 @@ class PrefixPrefixesView(PermissionRequiredMixin, View): class PrefixIPAddressesView(PermissionRequiredMixin, View): permission_required = 'ipam.view_prefix' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): prefix = get_object_or_404(Prefix.objects.all(), pk=pk) @@ -650,7 +645,6 @@ class IPAddressListView(PermissionRequiredMixin, ObjectListView): class IPAddressView(PermissionRequiredMixin, View): permission_required = 'ipam.view_ipaddress' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): ipaddress = get_object_or_404(IPAddress.objects.select_related('vrf__tenant', 'tenant'), pk=pk) @@ -734,7 +728,6 @@ class IPAddressAssignView(PermissionRequiredMixin, View): return super().dispatch(request, *args, **kwargs) - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request): form = forms.IPAddressAssignForm() @@ -847,7 +840,6 @@ class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class VLANGroupVLANsView(PermissionRequiredMixin, View): permission_required = 'ipam.view_vlangroup' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): vlan_group = get_object_or_404(VLANGroup.objects.all(), pk=pk) @@ -898,7 +890,6 @@ class VLANListView(PermissionRequiredMixin, ObjectListView): class VLANView(PermissionRequiredMixin, View): permission_required = 'ipam.view_vlan' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): vlan = get_object_or_404(VLAN.objects.select_related( @@ -917,7 +908,6 @@ class VLANView(PermissionRequiredMixin, View): class VLANMembersView(PermissionRequiredMixin, View): permission_required = 'ipam.view_vlan' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): vlan = get_object_or_404(VLAN.objects.all(), pk=pk) @@ -996,7 +986,6 @@ class ServiceListView(PermissionRequiredMixin, ObjectListView): class ServiceView(PermissionRequiredMixin, View): permission_required = 'ipam.view_service' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): service = get_object_or_404(Service, pk=pk) diff --git a/netbox/netbox/configuration.example.py b/netbox/netbox/configuration.example.py index df8e1afd0..adfb8f854 100644 --- a/netbox/netbox/configuration.example.py +++ b/netbox/netbox/configuration.example.py @@ -31,6 +31,7 @@ REDIS = { 'PORT': 6379, 'PASSWORD': '', 'DATABASE': 0, + 'CACHE_DATABASE': 1, 'DEFAULT_TIMEOUT': 300, 'SSL': False, } @@ -60,16 +61,7 @@ BANNER_LOGIN = '' # BASE_PATH = 'netbox/' BASE_PATH = '' -# The fraction of entries that are culled when CACHE_MAX_ENTRIES is reached. The actual ratio is 1 / CACHE_CULL_FREQUENCY, -# so set CACHE_CULL_FREQUENCY to 2 to cull half the entries when CACHE_MAX_ENTRIES is reached. This setting should be an -# integer and defaults to 3 -CACHE_CULL_FREQUENCY = 3 - -# Max number of entries (unique pages) to store in the cache at a time. -CACHE_MAX_ENTRIES = 300 - -# Cache timeout in seconds. Set to `None` to enforce an infinate timeout. Set to 0 to dissable caching by immediatly -# expiring keys. Defaults to 900 (15 minutes) +# Cache timeout in seconds. Set to 0 to dissable caching. Defaults to 900 (15 minutes) CACHE_TIMEOUT = 900 # Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90) diff --git a/netbox/netbox/settings.py b/netbox/netbox/settings.py index 58bd209e0..c05898525 100644 --- a/netbox/netbox/settings.py +++ b/netbox/netbox/settings.py @@ -138,6 +138,7 @@ REDIS_HOST = REDIS.get('HOST', 'localhost') REDIS_PORT = REDIS.get('PORT', 6379) REDIS_PASSWORD = REDIS.get('PASSWORD', '') REDIS_DATABASE = REDIS.get('DATABASE', 0) +REDIS_CACHE_DATABASE = REDIS.get('CACHE_DATABASE', 1) REDIS_DEFAULT_TIMEOUT = REDIS.get('DEFAULT_TIMEOUT', 300) REDIS_SSL = REDIS.get('SSL', False) @@ -159,8 +160,8 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.humanize', + 'cacheops', 'corsheaders', - 'django_redis', 'debug_toolbar', 'django_filters', 'django_tables2', @@ -231,21 +232,30 @@ else: if REDIS_PASSWORD: REDIS_CACHE_CON_STRING = '{}@{}'.format(REDIS_PASSWORD, REDIS_CACHE_CON_STRING) -REDIS_CACHE_CON_STRING = '{}{}:{}/{}'.format(REDIS_CACHE_CON_STRING, REDIS_HOST, REDIS_PORT, REDIS_DATABASE) -CACHE_BACKEND = 'django_redis.cache.RedisCache' +REDIS_CACHE_CON_STRING = '{}{}:{}/{}'.format(REDIS_CACHE_CON_STRING, REDIS_HOST, REDIS_PORT, REDIS_CACHE_DATABASE) -CACHES = { - "default": { - "BACKEND": CACHE_BACKEND, - "LOCATION": REDIS_CACHE_CON_STRING, - 'TIMEOUT': CACHE_TIMEOUT, - "OPTIONS": { - "CLIENT_CLASS": "django_redis.client.DefaultClient", - "MAX_ENTRIES": CACHE_MAX_ENTRIES, - "CULL_FREQUENCY": CACHE_CULL_FREQUENCY - } - } +if not CACHE_TIMEOUT: + CACHEOPS_ENABLED = False +else: + CACHEOPS_ENABLED = True + +CACHEOPS_REDIS = REDIS_CACHE_CON_STRING +CACHEOPS_DEFAULTS = { + 'timeout': CACHE_TIMEOUT } +CACHEOPS = { + 'auth.user': {'ops': 'get', 'timeout': 60 * 15}, + 'auth.*': {'ops': ('fetch', 'get')}, + 'auth.permission': {'ops': 'all'}, + 'dcim.*': {'ops': 'all'}, + 'ipam.*': {'ops': 'all'}, + 'extras.*': {'ops': 'all'}, + 'secrets.*': {'ops': 'all'}, + 'users.*': {'ops': 'all'}, + 'tenancy.*': {'ops': 'all'}, + 'virtualization.*': {'ops': 'all'}, +} +CACHEOPS_DEGRADE_ON_FAILURE = True # WSGI WSGI_APPLICATION = 'netbox.wsgi.application' diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py index dd826cb19..ae6d8a28d 100644 --- a/netbox/netbox/views.py +++ b/netbox/netbox/views.py @@ -171,7 +171,6 @@ SEARCH_TYPES = OrderedDict(( class HomeView(View): template_name = 'home.html' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request): connected_consoleports = ConsolePort.objects.filter( @@ -231,7 +230,6 @@ class HomeView(View): class SearchView(View): - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request): # No query @@ -285,7 +283,6 @@ class APIRootView(APIView): def get_view_name(self): return "API Root" - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, format=None): return Response(OrderedDict(( diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py index aeab49c8b..ef4d9351c 100644 --- a/netbox/secrets/views.py +++ b/netbox/secrets/views.py @@ -82,7 +82,6 @@ class SecretListView(PermissionRequiredMixin, ObjectListView): class SecretView(PermissionRequiredMixin, View): permission_required = 'secrets.view_secret' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): secret = get_object_or_404(Secret, pk=pk) diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 627f32855..1b4c21814 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -69,7 +69,6 @@ class TenantListView(PermissionRequiredMixin, ObjectListView): class TenantView(PermissionRequiredMixin, View): permission_required = 'tenancy.view_tenant' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, slug): tenant = get_object_or_404(Tenant, slug=slug) diff --git a/netbox/utilities/views.py b/netbox/utilities/views.py index 59a6fcafd..06547e802 100644 --- a/netbox/utilities/views.py +++ b/netbox/utilities/views.py @@ -108,7 +108,6 @@ class ObjectListView(View): return csv_data - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request): model = self.queryset.model @@ -716,7 +715,6 @@ class ComponentCreateView(View): model_form = None template_name = None - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): parent = get_object_or_404(self.parent_model, pk=pk) diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index fdb4bbc10..c9494132d 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -109,7 +109,6 @@ class ClusterListView(PermissionRequiredMixin, ObjectListView): class ClusterView(PermissionRequiredMixin, View): permission_required = 'virtualization.view_cluster' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): cluster = get_object_or_404(Cluster, pk=pk) @@ -172,7 +171,6 @@ class ClusterAddDevicesView(PermissionRequiredMixin, View): form = forms.ClusterAddDevicesForm template_name = 'virtualization/cluster_add_devices.html' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): cluster = get_object_or_404(Cluster, pk=pk) @@ -268,7 +266,6 @@ class VirtualMachineListView(PermissionRequiredMixin, ObjectListView): class VirtualMachineView(PermissionRequiredMixin, View): permission_required = 'virtualization.view_virtualmachine' - @method_decorator(cache_page(settings.CACHE_TIMEOUT)) def get(self, request, pk): virtualmachine = get_object_or_404(VirtualMachine.objects.select_related('tenant__group'), pk=pk) diff --git a/requirements.txt b/requirements.txt index 50d0f2ee0..29b2f8f94 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,9 @@ Django>=2.2,<2.3 +django-cacheops==4.1 django-cors-headers==2.5.2 django-debug-toolbar==1.11 django-filter==2.1.0 django-mptt==0.9.1 -django-redis==4.5.0 django-tables2==2.0.6 django-taggit==1.1.0 django-taggit-serializer==0.1.7