[WEB - 471] dev: caching users and workspace apis (#3707)

* dev: caching users and workspace apis

* dev: cache user and config apis

* dev: update caching function to use user_id instead of token

* dev: update caching layer

* dev: update caching logic

* dev: format caching file

* dev: refactor caching to include name space and user id as key

* dev: cache project cover image endpoint
This commit is contained in:
Nikhil 2024-03-06 20:39:50 +05:30 committed by GitHub
parent 549f6d0943
commit ed8782757d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 189 additions and 44 deletions

View File

@ -12,13 +12,14 @@ from rest_framework.response import Response
# Module imports # Module imports
from .base import BaseAPIView from .base import BaseAPIView
from plane.license.utils.instance_value import get_configuration_value from plane.license.utils.instance_value import get_configuration_value
from plane.utils.cache import cache_response
class ConfigurationEndpoint(BaseAPIView): class ConfigurationEndpoint(BaseAPIView):
permission_classes = [ permission_classes = [
AllowAny, AllowAny,
] ]
@cache_response(60 * 60 * 2, user=False)
def get(self, request): def get(self, request):
# Get all the configuration # Get all the configuration
( (
@ -136,6 +137,7 @@ class MobileConfigurationEndpoint(BaseAPIView):
AllowAny, AllowAny,
] ]
@cache_response(60 * 60 * 2, user=False)
def get(self, request): def get(self, request):
( (
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_ID,

View File

@ -11,7 +11,7 @@ from plane.app.serializers import (
EstimatePointSerializer, EstimatePointSerializer,
EstimateReadSerializer, EstimateReadSerializer,
) )
from plane.utils.cache import invalidate_cache
class ProjectEstimatePointEndpoint(BaseAPIView): class ProjectEstimatePointEndpoint(BaseAPIView):
permission_classes = [ permission_classes = [
@ -49,6 +49,7 @@ class BulkEstimatePointEndpoint(BaseViewSet):
serializer = EstimateReadSerializer(estimates, many=True) serializer = EstimateReadSerializer(estimates, many=True)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
@invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False)
def create(self, request, slug, project_id): def create(self, request, slug, project_id):
if not request.data.get("estimate", False): if not request.data.get("estimate", False):
return Response( return Response(
@ -114,6 +115,7 @@ class BulkEstimatePointEndpoint(BaseViewSet):
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
) )
@invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False)
def partial_update(self, request, slug, project_id, estimate_id): def partial_update(self, request, slug, project_id, estimate_id):
if not request.data.get("estimate", False): if not request.data.get("estimate", False):
return Response( return Response(
@ -182,6 +184,7 @@ class BulkEstimatePointEndpoint(BaseViewSet):
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
) )
@invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False)
def destroy(self, request, slug, project_id, estimate_id): def destroy(self, request, slug, project_id, estimate_id):
estimate = Estimate.objects.get( estimate = Estimate.objects.get(
pk=estimate_id, workspace__slug=slug, project_id=project_id pk=estimate_id, workspace__slug=slug, project_id=project_id

View File

@ -78,6 +78,7 @@ from plane.bgtasks.issue_activites_task import issue_activity
from plane.utils.grouper import group_results from plane.utils.grouper import group_results
from plane.utils.issue_filters import issue_filters from plane.utils.issue_filters import issue_filters
from collections import defaultdict from collections import defaultdict
from plane.utils.cache import invalidate_cache
class IssueListEndpoint(BaseAPIView): class IssueListEndpoint(BaseAPIView):
@ -1001,6 +1002,21 @@ class LabelViewSet(BaseViewSet):
ProjectMemberPermission, ProjectMemberPermission,
] ]
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(project__project_projectmember__member=self.request.user)
.select_related("project")
.select_related("workspace")
.select_related("parent")
.distinct()
.order_by("sort_order")
)
@invalidate_cache(path="/api/workspaces/:slug/labels/", url_params=True, user=False)
def create(self, request, slug, project_id): def create(self, request, slug, project_id):
try: try:
serializer = LabelSerializer(data=request.data) serializer = LabelSerializer(data=request.data)
@ -1020,22 +1036,13 @@ class LabelViewSet(BaseViewSet):
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
def get_queryset(self): @invalidate_cache(path="/api/workspaces/:slug/labels/", url_params=True, user=False)
return self.filter_queryset( def partial_update(self, request, *args, **kwargs):
super() return super().partial_update(request, *args, **kwargs)
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug")) @invalidate_cache(path="/api/workspaces/:slug/labels/", url_params=True, user=False)
.filter(project_id=self.kwargs.get("project_id")) def destroy(self, request, *args, **kwargs):
.filter( return super().destroy(request, *args, **kwargs)
project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True,
)
.select_related("project")
.select_related("workspace")
.select_related("parent")
.distinct()
.order_by("sort_order")
)
class BulkDeleteIssuesEndpoint(BaseAPIView): class BulkDeleteIssuesEndpoint(BaseAPIView):

View File

@ -65,7 +65,7 @@ from plane.db.models import (
) )
from plane.bgtasks.project_invitation_task import project_invitation from plane.bgtasks.project_invitation_task import project_invitation
from plane.utils.cache import cache_response
class ProjectViewSet(WebhookMixin, BaseViewSet): class ProjectViewSet(WebhookMixin, BaseViewSet):
serializer_class = ProjectListSerializer serializer_class = ProjectListSerializer
@ -1045,6 +1045,8 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
AllowAny, AllowAny,
] ]
# Cache the below api for 24 hours
@cache_response(60 * 60 * 24, user=False)
def get(self, request): def get(self, request):
files = [] files = []
s3 = boto3.client( s3 = boto3.client(

View File

@ -9,14 +9,13 @@ from rest_framework.response import Response
from rest_framework import status from rest_framework import status
# Module imports # Module imports
from . import BaseViewSet, BaseAPIView from . import BaseViewSet
from plane.app.serializers import StateSerializer from plane.app.serializers import StateSerializer
from plane.app.permissions import ( from plane.app.permissions import (
ProjectEntityPermission, ProjectEntityPermission,
WorkspaceEntityPermission,
) )
from plane.db.models import State, Issue from plane.db.models import State, Issue
from plane.utils.cache import invalidate_cache
class StateViewSet(BaseViewSet): class StateViewSet(BaseViewSet):
serializer_class = StateSerializer serializer_class = StateSerializer
@ -41,6 +40,7 @@ class StateViewSet(BaseViewSet):
.distinct() .distinct()
) )
@invalidate_cache(path="workspaces/:slug/states/", url_params=True, user=False)
def create(self, request, slug, project_id): def create(self, request, slug, project_id):
serializer = StateSerializer(data=request.data) serializer = StateSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
@ -61,6 +61,7 @@ class StateViewSet(BaseViewSet):
return Response(state_dict, status=status.HTTP_200_OK) return Response(state_dict, status=status.HTTP_200_OK)
return Response(states, status=status.HTTP_200_OK) return Response(states, status=status.HTTP_200_OK)
@invalidate_cache(path="workspaces/:slug/states/", url_params=True, user=False)
def mark_as_default(self, request, slug, project_id, pk): def mark_as_default(self, request, slug, project_id, pk):
# Select all the states which are marked as default # Select all the states which are marked as default
_ = State.objects.filter( _ = State.objects.filter(
@ -71,6 +72,7 @@ class StateViewSet(BaseViewSet):
).update(default=True) ).update(default=True)
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@invalidate_cache(path="workspaces/:slug/states/", url_params=True, user=False)
def destroy(self, request, slug, project_id, pk): def destroy(self, request, slug, project_id, pk):
state = State.objects.get( state = State.objects.get(
~Q(name="Triage"), ~Q(name="Triage"),

View File

@ -1,8 +1,10 @@
# Django imports
from django.db.models import Q, Count, Case, When, IntegerField
# Third party imports # Third party imports
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status from rest_framework import status
# Module imports # Module imports
from plane.app.serializers import ( from plane.app.serializers import (
UserSerializer, UserSerializer,
@ -15,9 +17,7 @@ from plane.app.views.base import BaseViewSet, BaseAPIView
from plane.db.models import User, IssueActivity, WorkspaceMember, ProjectMember from plane.db.models import User, IssueActivity, WorkspaceMember, ProjectMember
from plane.license.models import Instance, InstanceAdmin from plane.license.models import Instance, InstanceAdmin
from plane.utils.paginator import BasePaginator from plane.utils.paginator import BasePaginator
from plane.utils.cache import cache_response, invalidate_cache
from django.db.models import Q, F, Count, Case, When, IntegerField
class UserEndpoint(BaseViewSet): class UserEndpoint(BaseViewSet):
@ -27,6 +27,7 @@ class UserEndpoint(BaseViewSet):
def get_object(self): def get_object(self):
return self.request.user return self.request.user
@cache_response(60 * 60)
def retrieve(self, request): def retrieve(self, request):
serialized_data = UserMeSerializer(request.user).data serialized_data = UserMeSerializer(request.user).data
return Response( return Response(
@ -34,10 +35,12 @@ class UserEndpoint(BaseViewSet):
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
) )
@cache_response(60 * 60)
def retrieve_user_settings(self, request): def retrieve_user_settings(self, request):
serialized_data = UserMeSettingsSerializer(request.user).data serialized_data = UserMeSettingsSerializer(request.user).data
return Response(serialized_data, status=status.HTTP_200_OK) return Response(serialized_data, status=status.HTTP_200_OK)
@cache_response(60 * 60)
def retrieve_instance_admin(self, request): def retrieve_instance_admin(self, request):
instance = Instance.objects.first() instance = Instance.objects.first()
is_admin = InstanceAdmin.objects.filter( is_admin = InstanceAdmin.objects.filter(
@ -47,6 +50,11 @@ class UserEndpoint(BaseViewSet):
{"is_instance_admin": is_admin}, status=status.HTTP_200_OK {"is_instance_admin": is_admin}, status=status.HTTP_200_OK
) )
@invalidate_cache(path="/api/users/me/")
def partial_update(self, request, *args, **kwargs):
return super().partial_update(request, *args, **kwargs)
@invalidate_cache(path="/api/users/me/")
def deactivate(self, request): def deactivate(self, request):
# Check all workspace user is active # Check all workspace user is active
user = self.get_object() user = self.get_object()
@ -145,6 +153,8 @@ class UserEndpoint(BaseViewSet):
class UpdateUserOnBoardedEndpoint(BaseAPIView): class UpdateUserOnBoardedEndpoint(BaseAPIView):
@invalidate_cache(path="/api/users/me/")
def patch(self, request): def patch(self, request):
user = User.objects.get(pk=request.user.id, is_active=True) user = User.objects.get(pk=request.user.id, is_active=True)
user.is_onboarded = request.data.get("is_onboarded", False) user.is_onboarded = request.data.get("is_onboarded", False)
@ -155,6 +165,8 @@ class UpdateUserOnBoardedEndpoint(BaseAPIView):
class UpdateUserTourCompletedEndpoint(BaseAPIView): class UpdateUserTourCompletedEndpoint(BaseAPIView):
@invalidate_cache(path="/api/users/me/")
def patch(self, request): def patch(self, request):
user = User.objects.get(pk=request.user.id, is_active=True) user = User.objects.get(pk=request.user.id, is_active=True)
user.is_tour_completed = request.data.get("is_tour_completed", False) user.is_tour_completed = request.data.get("is_tour_completed", False)
@ -165,6 +177,7 @@ class UpdateUserTourCompletedEndpoint(BaseAPIView):
class UserActivityEndpoint(BaseAPIView, BasePaginator): class UserActivityEndpoint(BaseAPIView, BasePaginator):
def get(self, request): def get(self, request):
queryset = IssueActivity.objects.filter( queryset = IssueActivity.objects.filter(
actor=request.user actor=request.user

View File

@ -57,6 +57,8 @@ from plane.app.serializers import (
WorkspaceEstimateSerializer, WorkspaceEstimateSerializer,
StateSerializer, StateSerializer,
LabelSerializer, LabelSerializer,
CycleSerializer,
ModuleSerializer,
) )
from plane.app.views.base import BaseAPIView from plane.app.views.base import BaseAPIView
from . import BaseViewSet from . import BaseViewSet
@ -77,7 +79,6 @@ from plane.db.models import (
Label, Label,
WorkspaceMember, WorkspaceMember,
CycleIssue, CycleIssue,
IssueReaction,
WorkspaceUserProperties, WorkspaceUserProperties,
Estimate, Estimate,
EstimatePoint, EstimatePoint,
@ -91,17 +92,11 @@ from plane.app.permissions import (
WorkspaceEntityPermission, WorkspaceEntityPermission,
WorkspaceViewerPermission, WorkspaceViewerPermission,
WorkspaceUserPermission, WorkspaceUserPermission,
ProjectLitePermission,
) )
from plane.bgtasks.workspace_invitation_task import workspace_invitation from plane.bgtasks.workspace_invitation_task import workspace_invitation
from plane.utils.issue_filters import issue_filters from plane.utils.issue_filters import issue_filters
from plane.bgtasks.event_tracking_task import workspace_invite_event from plane.bgtasks.event_tracking_task import workspace_invite_event
from plane.app.serializers.module import ( from plane.utils.cache import cache_response, invalidate_cache
ModuleSerializer,
)
from plane.app.serializers.cycle import (
CycleSerializer,
)
class WorkSpaceViewSet(BaseViewSet): class WorkSpaceViewSet(BaseViewSet):
@ -151,7 +146,8 @@ class WorkSpaceViewSet(BaseViewSet):
.annotate(total_issues=issue_count) .annotate(total_issues=issue_count)
.select_related("owner") .select_related("owner")
) )
@invalidate_cache(path="/api/workspaces/", user=False)
@invalidate_cache(path="/api/users/me/workspaces/")
def create(self, request): def create(self, request):
try: try:
serializer = WorkSpaceSerializer(data=request.data) serializer = WorkSpaceSerializer(data=request.data)
@ -197,6 +193,20 @@ class WorkSpaceViewSet(BaseViewSet):
status=status.HTTP_410_GONE, status=status.HTTP_410_GONE,
) )
@cache_response(60 * 60 * 2)
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
@invalidate_cache(path="/api/workspaces/", user=False)
@invalidate_cache(path="/api/users/me/workspaces/")
def partial_update(self, request, *args, **kwargs):
return super().partial_update(request, *args, **kwargs)
@invalidate_cache(path="/api/workspaces/", user=False)
@invalidate_cache(path="/api/users/me/workspaces/")
def destroy(self, request, *args, **kwargs):
return super().destroy(request, *args, **kwargs)
class UserWorkSpacesEndpoint(BaseAPIView): class UserWorkSpacesEndpoint(BaseAPIView):
search_fields = [ search_fields = [
@ -206,6 +216,7 @@ class UserWorkSpacesEndpoint(BaseAPIView):
"owner", "owner",
] ]
@cache_response(60 * 60 * 2)
def get(self, request): def get(self, request):
fields = [ fields = [
field field
@ -403,6 +414,8 @@ class WorkspaceJoinEndpoint(BaseAPIView):
] ]
"""Invitation response endpoint the user can respond to the invitation""" """Invitation response endpoint the user can respond to the invitation"""
@invalidate_cache(path="/api/workspaces/", user=False)
@invalidate_cache(path="/api/users/me/workspaces/")
def post(self, request, slug, pk): def post(self, request, slug, pk):
workspace_invite = WorkspaceMemberInvite.objects.get( workspace_invite = WorkspaceMemberInvite.objects.get(
pk=pk, workspace__slug=slug pk=pk, workspace__slug=slug
@ -499,6 +512,9 @@ class UserWorkspaceInvitationsViewSet(BaseViewSet):
.annotate(total_members=Count("workspace__workspace_member")) .annotate(total_members=Count("workspace__workspace_member"))
) )
@invalidate_cache(path="/api/workspaces/", user=False)
@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(
@ -569,6 +585,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
.select_related("member") .select_related("member")
) )
@cache_response(60 * 60 * 2)
def list(self, request, slug): def list(self, request, slug):
workspace_member = WorkspaceMember.objects.get( workspace_member = WorkspaceMember.objects.get(
member=request.user, member=request.user,
@ -593,6 +610,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
) )
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
@invalidate_cache(path="/api/workspaces/:slug/members/", url_params=True, user=False)
def partial_update(self, request, slug, pk): def partial_update(self, request, slug, pk):
workspace_member = WorkspaceMember.objects.get( workspace_member = WorkspaceMember.objects.get(
pk=pk, pk=pk,
@ -635,6 +653,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@invalidate_cache(path="/api/workspaces/:slug/members/", url_params=True, user=False)
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(
@ -699,6 +718,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
workspace_member.save() workspace_member.save()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@invalidate_cache(path="/api/workspaces/:slug/members/", url_params=True, user=False)
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,
@ -1550,6 +1570,7 @@ class WorkspaceLabelsEndpoint(BaseAPIView):
WorkspaceViewerPermission, WorkspaceViewerPermission,
] ]
@cache_response(60 * 60 * 2)
def get(self, request, slug): def get(self, request, slug):
labels = Label.objects.filter( labels = Label.objects.filter(
workspace__slug=slug, workspace__slug=slug,
@ -1565,6 +1586,7 @@ class WorkspaceStatesEndpoint(BaseAPIView):
WorkspaceEntityPermission, WorkspaceEntityPermission,
] ]
@cache_response(60 * 60 * 2)
def get(self, request, slug): def get(self, request, slug):
states = State.objects.filter( states = State.objects.filter(
workspace__slug=slug, workspace__slug=slug,
@ -1580,6 +1602,7 @@ class WorkspaceEstimatesEndpoint(BaseAPIView):
WorkspaceEntityPermission, WorkspaceEntityPermission,
] ]
@cache_response(60 * 60 * 2)
def get(self, request, slug): def get(self, request, slug):
estimate_ids = Project.objects.filter( estimate_ids = Project.objects.filter(
workspace__slug=slug, estimate__isnull=False workspace__slug=slug, estimate__isnull=False

View File

@ -1,17 +1,11 @@
# Python imports # Python imports
import json
import os
import requests
import uuid import uuid
import random
import string
# Django imports # Django imports
from django.utils import timezone from django.utils import timezone
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
from django.core.validators import validate_email from django.core.validators import validate_email
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.conf import settings
# Third party imports # Third party imports
from rest_framework import status from rest_framework import status
@ -30,9 +24,9 @@ from plane.license.api.serializers import (
from plane.license.api.permissions import ( from plane.license.api.permissions import (
InstanceAdminPermission, InstanceAdminPermission,
) )
from plane.db.models import User, WorkspaceMember, ProjectMember from plane.db.models import User
from plane.license.utils.encryption import encrypt_data from plane.license.utils.encryption import encrypt_data
from plane.utils.cache import cache_response, invalidate_cache
class InstanceEndpoint(BaseAPIView): class InstanceEndpoint(BaseAPIView):
def get_permissions(self): def get_permissions(self):
@ -44,6 +38,7 @@ class InstanceEndpoint(BaseAPIView):
AllowAny(), AllowAny(),
] ]
@cache_response(60 * 60 * 2, user=False)
def get(self, request): def get(self, request):
instance = Instance.objects.first() instance = Instance.objects.first()
# get the instance # get the instance
@ -58,6 +53,7 @@ class InstanceEndpoint(BaseAPIView):
data["is_activated"] = True data["is_activated"] = True
return Response(data, status=status.HTTP_200_OK) return Response(data, status=status.HTTP_200_OK)
@invalidate_cache(path="/api/instances/", user=False)
def patch(self, request): def patch(self, request):
# Get the instance # Get the instance
instance = Instance.objects.first() instance = Instance.objects.first()
@ -75,6 +71,7 @@ class InstanceAdminEndpoint(BaseAPIView):
InstanceAdminPermission, InstanceAdminPermission,
] ]
@invalidate_cache(path="/api/instances/", user=False)
# Create an instance admin # Create an instance admin
def post(self, request): def post(self, request):
email = request.data.get("email", False) email = request.data.get("email", False)
@ -104,6 +101,7 @@ class InstanceAdminEndpoint(BaseAPIView):
serializer = InstanceAdminSerializer(instance_admin) serializer = InstanceAdminSerializer(instance_admin)
return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.data, status=status.HTTP_201_CREATED)
@cache_response(60 * 60 * 2)
def get(self, request): def get(self, request):
instance = Instance.objects.first() instance = Instance.objects.first()
if instance is None: if instance is None:
@ -115,6 +113,7 @@ class InstanceAdminEndpoint(BaseAPIView):
serializer = InstanceAdminSerializer(instance_admins, many=True) serializer = InstanceAdminSerializer(instance_admins, many=True)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
@invalidate_cache(path="/api/instances/", user=False)
def delete(self, request, pk): def delete(self, request, pk):
instance = Instance.objects.first() instance = Instance.objects.first()
instance_admin = InstanceAdmin.objects.filter( instance_admin = InstanceAdmin.objects.filter(
@ -128,6 +127,7 @@ class InstanceConfigurationEndpoint(BaseAPIView):
InstanceAdminPermission, InstanceAdminPermission,
] ]
@cache_response(60 * 60 * 2, user=False)
def get(self, request): def get(self, request):
instance_configurations = InstanceConfiguration.objects.all() instance_configurations = InstanceConfiguration.objects.all()
serializer = InstanceConfigurationSerializer( serializer = InstanceConfigurationSerializer(
@ -135,6 +135,8 @@ class InstanceConfigurationEndpoint(BaseAPIView):
) )
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
@invalidate_cache(path="/api/configs/", user=False)
@invalidate_cache(path="/api/mobile-configs/", user=False)
def patch(self, request): def patch(self, request):
configurations = InstanceConfiguration.objects.filter( configurations = InstanceConfiguration.objects.filter(
key__in=request.data.keys() key__in=request.data.keys()
@ -170,6 +172,7 @@ class InstanceAdminSignInEndpoint(BaseAPIView):
AllowAny, AllowAny,
] ]
@invalidate_cache(path="/api/instances/", user=False)
def post(self, request): def post(self, request):
# Check instance first # Check instance first
instance = Instance.objects.first() instance = Instance.objects.first()
@ -260,6 +263,7 @@ class SignUpScreenVisitedEndpoint(BaseAPIView):
AllowAny, AllowAny,
] ]
@invalidate_cache(path="/api/instances/", user=False)
def post(self, request): def post(self, request):
instance = Instance.objects.first() instance = Instance.objects.first()
if instance is None: if instance is None:

View File

@ -1,4 +1,5 @@
"""Development settings""" """Development settings"""
from .common import * # noqa from .common import * # noqa
DEBUG = True DEBUG = True
@ -14,7 +15,11 @@ EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
CACHES = { CACHES = {
"default": { "default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache", "BACKEND": "django_redis.cache.RedisCache",
"LOCATION": REDIS_URL,
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
} }
} }

View File

@ -0,0 +1,84 @@
from django.core.cache import cache
# from django.utils.encoding import force_bytes
# import hashlib
from functools import wraps
from rest_framework.response import Response
def generate_cache_key(custom_path, auth_header=None):
"""Generate a cache key with the given params"""
if auth_header:
key_data = f"{custom_path}:{auth_header}"
else:
key_data = custom_path
return key_data
def cache_response(timeout=60 * 60, path=None, user=True):
"""decorator to create cache per user"""
def decorator(view_func):
@wraps(view_func)
def _wrapped_view(instance, request, *args, **kwargs):
# Function to generate cache key
auth_header = (
None if request.user.is_anonymous else str(request.user.id) if user else None
)
custom_path = path if path is not None else request.get_full_path()
key = generate_cache_key(custom_path, auth_header)
cached_result = cache.get(key)
if cached_result is not None:
print("Cache Hit")
return Response(
cached_result["data"], status=cached_result["status"]
)
print("Cache Miss")
response = view_func(instance, request, *args, **kwargs)
if response.status_code == 200:
cache.set(
key,
{"data": response.data, "status": response.status_code},
timeout,
)
return response
return _wrapped_view
return decorator
def invalidate_cache(path=None, url_params=False, user=True):
"""invalidate cache per user"""
def decorator(view_func):
@wraps(view_func)
def _wrapped_view(instance, request, *args, **kwargs):
# Invalidate cache before executing the view function
if url_params:
path_with_values = path
for key, value in kwargs.items():
path_with_values = path_with_values.replace(
f":{key}", str(value)
)
custom_path = path_with_values
else:
custom_path = (
path if path is not None else request.get_full_path()
)
auth_header = (
None if request.user.is_anonymous else str(request.user.id) if user else None
)
key = generate_cache_key(custom_path, auth_header)
cache.delete(key)
print("Invalidating cache")
# Execute the view function
return view_func(instance, request, *args, **kwargs)
return _wrapped_view
return decorator