2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-03 12:10:59 +00:00

Setting caching (#3178)

* Revert "Remove stat context variables"

This reverts commit 0989c308d0.

* Add a caching framework for inventree settings

- Actions that use "settings" require a DB hit every time
- For example the part.full_name() method looks at the PART_NAME_FORMAT setting
- This means 1 DB hit for every part which is serialized!!

* Fixes for DebugToolbar integration

- Requires different INTERNAL_IPS when running behind docker
- Some issues with TEMPLATES framework

* Revert "Revert "Remove stat context variables""

This reverts commit 52e6359265.

* Add unit tests for settings caching

* Update existing unit tests to handle cache framework

* Fix for unit test

* Re-enable cache for default part values

* Clear cache for further unit tests
This commit is contained in:
Oliver
2022-06-12 10:56:16 +10:00
committed by GitHub
parent 90aa7b8444
commit 6eddcd3c23
9 changed files with 164 additions and 31 deletions

View File

@ -21,7 +21,8 @@ from django.contrib.auth.models import Group, User
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.humanize.templatetags.humanize import naturaltime
from django.core.exceptions import ValidationError
from django.core.cache import cache
from django.core.exceptions import AppRegistryNotReady, ValidationError
from django.core.validators import MinValueValidator, URLValidator
from django.db import models, transaction
from django.db.utils import IntegrityError, OperationalError
@ -69,11 +70,56 @@ class BaseInvenTreeSetting(models.Model):
"""Enforce validation and clean before saving."""
self.key = str(self.key).upper()
do_cache = kwargs.pop('cache', True)
self.clean(**kwargs)
self.validate_unique(**kwargs)
# Update this setting in the cache
if do_cache:
self.save_to_cache()
super().save()
@property
def cache_key(self):
"""Generate a unique cache key for this settings object"""
return self.__class__.create_cache_key(self.key, **self.get_kwargs())
def save_to_cache(self):
"""Save this setting object to cache"""
ckey = self.cache_key
logger.debug(f"Saving setting '{ckey}' to cache")
try:
cache.set(
ckey,
self,
timeout=3600
)
except TypeError:
# Some characters cause issues with caching; ignore and move on
pass
@classmethod
def create_cache_key(cls, setting_key, **kwargs):
"""Create a unique cache key for a particular setting object.
The cache key uses the following elements to ensure the key is 'unique':
- The name of the class
- The unique KEY string
- Any key:value kwargs associated with the particular setting type (e.g. user-id)
"""
key = f"{str(cls.__name__)}:{setting_key}"
for k, v in kwargs.items():
key += f"_{k}:{v}"
return key
@classmethod
def allValues(cls, user=None, exclude_hidden=False):
"""Return a dict of "all" defined global settings.
@ -220,11 +266,12 @@ class BaseInvenTreeSetting(models.Model):
- Key is case-insensitive
- Returns None if no match is made
First checks the cache to see if this object has recently been accessed,
and returns the cached version if so.
"""
key = str(key).strip().upper()
settings = cls.objects.all()
filters = {
'key__iexact': key,
}
@ -253,7 +300,25 @@ class BaseInvenTreeSetting(models.Model):
if method is not None:
filters['method'] = method
# Perform cache lookup by default
do_cache = kwargs.pop('cache', True)
ckey = cls.create_cache_key(key, **kwargs)
if do_cache:
try:
# First attempt to find the setting object in the cache
cached_setting = cache.get(ckey)
if cached_setting is not None:
return cached_setting
except AppRegistryNotReady:
# Cache is not ready yet
do_cache = False
try:
settings = cls.objects.all()
setting = settings.filter(**filters).first()
except (ValueError, cls.DoesNotExist):
setting = None
@ -282,6 +347,10 @@ class BaseInvenTreeSetting(models.Model):
# It might be the case that the database isn't created yet
pass
if setting and do_cache:
# Cache this setting object
setting.save_to_cache()
return setting
@classmethod
@ -1507,11 +1576,6 @@ class InvenTreeUserSetting(BaseInvenTreeSetting):
help_text=_('User'),
)
@classmethod
def get_setting_object(cls, key, user=None):
"""Return setting object for provided user."""
return super().get_setting_object(key, user=user)
def validate_unique(self, exclude=None, **kwargs):
"""Return if the setting (including key) is unique."""
return super().validate_unique(exclude=exclude, user=self.user)