[WEB - 922]dev: fix workspace member caching (#4147)

* dev: fix workspace member caching

* fix: caching on debug
This commit is contained in:
Nikhil 2024-04-09 13:36:08 +05:30 committed by GitHub
parent 95580d0c62
commit 7e0520d1cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 70 additions and 41 deletions

View File

@ -1,36 +1,39 @@
# Python imports # Python imports
import jwt
from datetime import datetime from datetime import datetime
import jwt
# Django imports # Django imports
from django.conf import settings from django.conf import settings
from django.utils import timezone
from django.db.models import Count
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import validate_email from django.core.validators import validate_email
from django.db.models import Count
from django.utils import timezone
# Third party modules # Third party modules
from rest_framework import status from rest_framework import status
from rest_framework.response import Response
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.response import Response
# Module imports # Module imports
from plane.app.permissions import WorkSpaceAdminPermission
from plane.app.serializers import ( from plane.app.serializers import (
WorkSpaceMemberSerializer,
WorkSpaceMemberInviteSerializer, WorkSpaceMemberInviteSerializer,
WorkSpaceMemberSerializer,
) )
from plane.app.views.base import BaseAPIView from plane.app.views.base import BaseAPIView
from .. import BaseViewSet from plane.bgtasks.event_tracking_task import workspace_invite_event
from plane.bgtasks.workspace_invitation_task import workspace_invitation
from plane.db.models import ( from plane.db.models import (
User, User,
Workspace, Workspace,
WorkspaceMemberInvite,
WorkspaceMember, WorkspaceMember,
WorkspaceMemberInvite,
) )
from plane.app.permissions import WorkSpaceAdminPermission from plane.utils.cache import invalidate_cache, invalidate_cache_directly
from plane.bgtasks.workspace_invitation_task import workspace_invitation
from plane.bgtasks.event_tracking_task import workspace_invite_event from .. import BaseViewSet
from plane.utils.cache import invalidate_cache
class WorkspaceInvitationsViewset(BaseViewSet): class WorkspaceInvitationsViewset(BaseViewSet):
"""Endpoint for creating, listing and deleting workspaces""" """Endpoint for creating, listing and deleting workspaces"""
@ -265,9 +268,6 @@ class UserWorkspaceInvitationsViewSet(BaseViewSet):
@invalidate_cache(path="/api/workspaces/", user=False) @invalidate_cache(path="/api/workspaces/", user=False)
@invalidate_cache(path="/api/users/me/workspaces/") @invalidate_cache(path="/api/users/me/workspaces/")
@invalidate_cache(
path="/api/workspaces/:slug/members/", url_params=True, user=False
)
def create(self, request): def create(self, request):
invitations = request.data.get("invitations", []) invitations = request.data.get("invitations", [])
workspace_invitations = WorkspaceMemberInvite.objects.filter( workspace_invitations = WorkspaceMemberInvite.objects.filter(
@ -276,6 +276,12 @@ class UserWorkspaceInvitationsViewSet(BaseViewSet):
# If the user is already a member of workspace and was deactivated then activate the user # If the user is already a member of workspace and was deactivated then activate the user
for invitation in workspace_invitations: for invitation in workspace_invitations:
invalidate_cache_directly(
path=f"/api/workspaces/{invitation.workspace.slug}/members/",
user=False,
request=request,
multiple=True,
)
# Update the WorkspaceMember for this specific invitation # Update the WorkspaceMember for this specific invitation
WorkspaceMember.objects.filter( WorkspaceMember.objects.filter(
workspace_id=invitation.workspace_id, member=request.user workspace_id=invitation.workspace_id, member=request.user

View File

@ -102,7 +102,10 @@ class WorkSpaceMemberViewSet(BaseViewSet):
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
@invalidate_cache( @invalidate_cache(
path="/api/workspaces/:slug/members/", url_params=True, user=False path="/api/workspaces/:slug/members/",
url_params=True,
user=False,
multiple=True,
) )
def partial_update(self, request, slug, pk): def partial_update(self, request, slug, pk):
workspace_member = WorkspaceMember.objects.get( workspace_member = WorkspaceMember.objects.get(
@ -147,9 +150,15 @@ class WorkSpaceMemberViewSet(BaseViewSet):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@invalidate_cache( @invalidate_cache(
path="/api/workspaces/:slug/members/", url_params=True, user=False path="/api/workspaces/:slug/members/",
url_params=True,
user=False,
multiple=True,
) )
@invalidate_cache(path="/api/users/me/settings/") @invalidate_cache(path="/api/users/me/settings/")
@invalidate_cache(
path="/api/users/me/workspaces/", user=False, multiple=True
)
def destroy(self, request, slug, pk): def destroy(self, request, slug, pk):
# Check the user role who is deleting the user # Check the user role who is deleting the user
workspace_member = WorkspaceMember.objects.get( workspace_member = WorkspaceMember.objects.get(
@ -215,9 +224,15 @@ class WorkSpaceMemberViewSet(BaseViewSet):
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@invalidate_cache( @invalidate_cache(
path="/api/workspaces/:slug/members/", url_params=True, user=False path="/api/workspaces/:slug/members/",
url_params=True,
user=False,
multiple=True,
) )
@invalidate_cache(path="/api/users/me/settings/") @invalidate_cache(path="/api/users/me/settings/")
@invalidate_cache(
path="api/users/me/workspaces/", user=False, multiple=True
)
def leave(self, request, slug): def leave(self, request, slug):
workspace_member = WorkspaceMember.objects.get( workspace_member = WorkspaceMember.objects.get(
workspace__slug=slug, workspace__slug=slug,

View File

@ -33,12 +33,12 @@ def cache_response(timeout=60 * 60, path=None, user=True):
custom_path = path if path is not None else request.get_full_path() custom_path = path if path is not None else request.get_full_path()
key = generate_cache_key(custom_path, auth_header) key = generate_cache_key(custom_path, auth_header)
cached_result = cache.get(key) cached_result = cache.get(key)
if cached_result is not None: if cached_result is not None:
return Response( return Response(
cached_result["data"], status=cached_result["status"] cached_result["data"], status=cached_result["status"]
) )
response = view_func(instance, request, *args, **kwargs) response = view_func(instance, request, *args, **kwargs)
if response.status_code == 200 and not settings.DEBUG: if response.status_code == 200 and not settings.DEBUG:
cache.set( cache.set(
key, key,
@ -53,34 +53,42 @@ def cache_response(timeout=60 * 60, path=None, user=True):
return decorator return decorator
def invalidate_cache(path=None, url_params=False, user=True): def invalidate_cache_directly(
"""invalidate cache per user""" path=None, url_params=False, user=True, request=None, multiple=False
):
def decorator(view_func): if url_params and path:
@wraps(view_func)
def _wrapped_view(instance, request, *args, **kwargs):
# Invalidate cache before executing the view function
if url_params:
path_with_values = path path_with_values = path
for key, value in kwargs.items(): # Assuming `kwargs` could be passed directly if needed, otherwise, skip this part
path_with_values = path_with_values.replace( for key, value in request.resolver_match.kwargs.items():
f":{key}", str(value) path_with_values = path_with_values.replace(f":{key}", str(value))
)
custom_path = path_with_values custom_path = path_with_values
else: else:
custom_path = ( custom_path = path if path is not None else request.get_full_path()
path if path is not None else request.get_full_path()
)
auth_header = ( auth_header = (
None None
if request.user.is_anonymous if request.user.is_anonymous
else str(request.user.id) if user else None else str(request.user.id) if user else None
) )
key = generate_cache_key(custom_path, auth_header) key = generate_cache_key(custom_path, auth_header)
if multiple:
cache.delete_many(keys=cache.keys(f"*{key}*"))
else:
cache.delete(key) cache.delete(key)
# Execute the view function
def invalidate_cache(path=None, url_params=False, user=True, multiple=False):
def decorator(view_func):
@wraps(view_func)
def _wrapped_view(instance, request, *args, **kwargs):
# invalidate the cache
invalidate_cache_directly(
path=path,
url_params=url_params,
user=user,
request=request,
multiple=multiple,
)
return view_func(instance, request, *args, **kwargs) return view_func(instance, request, *args, **kwargs)
return _wrapped_view return _wrapped_view