'Using cache invalidation for a user permission django

I am currently working on a cache for user permissions in Django. The structure for enabling users/organizations access to certain areas of the application looks like this: enter image description here

Meaning a User can either have access to a feature via his organization or as a user himself. What I want is a key-value cache that safes all the features a user/organization member has access to as a string. So whenever a user request access to a certain view the permission class first fetches all features for that user from the cache. I have implemented that as follows:

class UserPermissionCache:
    def __init__(self):
        self.redis = StrictRedis(
            host=settings.REDIS_RATE_LIMITING_URL,
            port=settings.REDIS_RATE_LIMITING_PORT,
            db=settings.REDIS_RATE_LIMITING_DB,
        )
        self.validation = datetime.now().strftime("%m%d%Y%H%M%S")

    def status_key(self, member):
        return f"user:features:{self.validation}:{member.uuid}"

    def set_user_features(self, user, key):

        features = Feature.objects.filter(
            Q(enabled_for_all=True)
            | Q(enabled_for_users=user)
            | Q(enabled_for_organizations=user.only_organization)
        )

        result = FEATURE_STRING_SEPERATOR.join(
            feature.code for feature in features
        )

        if not self.redis.set(key, result, settings.USER_FEATURES_TIMEOUT):
            logging.warning(f"Couldn't set features for user: {user.uuid}")

        return result

    def get_user_features(self, member):

        key = self.status_key(member)

        try:
            result = self.redis.get(key)
        except ConnectionError:
            logger.error(
                "ConnectionError, Failed to fetch feature permission for user",
                extra={"member": member.uuid},
            )
            return None

        if result is None:
            result = self.set_user_features(member, key)
            result = result.split(FEATURE_STRING_SEPERATOR)
        else:
            result = result.decode("utf-8").split(FEATURE_STRING_SEPERATOR)

        return result

    def reset_user_features(self, sender, **kwargs):
        for member in organization_members:
            key = self.status_key(member)
            self.redis.delete(key)

    def update_validation_key(self, sender, instance, **kwargs):
        self.validation = datetime.now().strftime("%m%d%Y%H%M%S")


user_permission_cache = UserPermissionCache()

m2m_changed.connect(user_permission_cache.reset_user_features, sender=Feature.enabled_for_organizations.through)
post_save.connect(user_permission_cache.update_validation_key, sender=Feature)

My Problem is now cache validation. I have the set the timeout for an entry in the cache to 15 min. Whenever I want to add features to organizations or change a feature as an admin I want the cache to update as well so every user gets immediate access. I tried to solve this by adding a time to the key. Whenever a Feature gets updated I change the key as well which means that the cache cant find the old key-value pair anymore and will just create a new entry with the updated feature. I just dont know what to do whenever a view is called to add features to an organization. For that I use the m2m_changed Signal. I was hoping there is a better solution then this.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source