mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
dev: cache apis for workspace, projects and issues.
This commit is contained in:
parent
e4bad543a4
commit
5b09083f93
@ -78,6 +78,7 @@ class CycleSerializer(BaseSerializer):
|
||||
"workspace_id",
|
||||
"project_id",
|
||||
# model fields
|
||||
"name",
|
||||
"description",
|
||||
"start_date",
|
||||
"end_date",
|
||||
|
@ -18,7 +18,7 @@ from plane.db.models import (
|
||||
|
||||
|
||||
class WorkSpaceSerializer(DynamicBaseSerializer):
|
||||
owner_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
owner = UserLiteSerializer(read_only=True)
|
||||
total_members = serializers.IntegerField(read_only=True)
|
||||
total_issues = serializers.IntegerField(read_only=True)
|
||||
|
||||
@ -48,7 +48,7 @@ class WorkSpaceSerializer(DynamicBaseSerializer):
|
||||
"updated_by",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"owner_id",
|
||||
"owner",
|
||||
]
|
||||
|
||||
|
||||
|
@ -11,13 +11,14 @@ from plane.app.serializers import (
|
||||
EstimatePointSerializer,
|
||||
EstimateReadSerializer,
|
||||
)
|
||||
|
||||
from plane.utils.cache import cache_path_response, invalidate_path_cache
|
||||
|
||||
class ProjectEstimatePointEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
|
||||
@cache_path_response(60 * 60 * 2)
|
||||
def get(self, request, slug, project_id):
|
||||
project = Project.objects.get(workspace__slug=slug, pk=project_id)
|
||||
if project.estimate_id is not None:
|
||||
@ -38,6 +39,7 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
||||
model = Estimate
|
||||
serializer_class = EstimateSerializer
|
||||
|
||||
@cache_path_response(60 * 60 * 2)
|
||||
def list(self, request, slug, project_id):
|
||||
estimates = (
|
||||
Estimate.objects.filter(
|
||||
@ -49,6 +51,9 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
||||
serializer = EstimateReadSerializer(estimates, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_path_cache("/api/workspaces/:slug/estimates/", True)
|
||||
@invalidate_path_cache("/api/workspaces/:slug/projects/:project_id/estimates/", True)
|
||||
@invalidate_path_cache("/api/workspaces/:slug/projects/:project_id/project-estimates/", True)
|
||||
def create(self, request, slug, project_id):
|
||||
if not request.data.get("estimate", False):
|
||||
return Response(
|
||||
@ -114,6 +119,9 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
@invalidate_path_cache("/api/workspaces/:slug/estimates/", True)
|
||||
@invalidate_path_cache("/api/workspaces/:slug/projects/:project_id/estimates/", True)
|
||||
@invalidate_path_cache("/api/workspaces/:slug/projects/:project_id/project-estimates/", True)
|
||||
def partial_update(self, request, slug, project_id, estimate_id):
|
||||
if not request.data.get("estimate", False):
|
||||
return Response(
|
||||
|
@ -81,7 +81,7 @@ from plane.bgtasks.issue_activites_task import issue_activity
|
||||
from plane.utils.grouper import group_results
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from collections import defaultdict
|
||||
|
||||
from plane.utils.cache import cache_path_response, invalidate_path_cache
|
||||
|
||||
class IssueViewSet(WebhookMixin, BaseViewSet):
|
||||
def get_serializer_class(self):
|
||||
@ -1111,6 +1111,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
||||
)
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
@cache_path_response(60 * 60 * 3)
|
||||
def list(self, request, slug, project_id):
|
||||
fields = [
|
||||
field
|
||||
@ -1217,6 +1218,7 @@ class IssueArchiveViewSet(BaseViewSet):
|
||||
)
|
||||
return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_path_cache("/api/workspaces/:slug/projects/:project_id/archived-issues/", True)
|
||||
def unarchive(self, request, slug, project_id, pk=None):
|
||||
issue = Issue.objects.get(
|
||||
workspace__slug=slug,
|
||||
|
@ -65,7 +65,7 @@ from plane.db.models import (
|
||||
)
|
||||
|
||||
from plane.bgtasks.project_invitation_task import project_invitation
|
||||
|
||||
from plane.utils.cache import cache_path_response, invalidate_path_cache
|
||||
|
||||
class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
serializer_class = ProjectListSerializer
|
||||
@ -662,6 +662,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||
.select_related("workspace", "workspace__owner")
|
||||
)
|
||||
|
||||
@invalidate_path_cache("/api/workspaces/:slug/projects/:project_id/members/", True)
|
||||
def create(self, request, slug, project_id):
|
||||
members = request.data.get("members", [])
|
||||
|
||||
@ -738,6 +739,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||
serializer = ProjectMemberRoleSerializer(project_members, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@cache_path_response(60 * 60 * 2)
|
||||
def list(self, request, slug, project_id):
|
||||
# Get the list of project members for the project
|
||||
project_members = ProjectMember.objects.filter(
|
||||
@ -752,6 +754,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_path_cache("/api/workspaces/:slug/projects/:project_id/members/", True)
|
||||
def partial_update(self, request, slug, project_id, pk):
|
||||
project_member = ProjectMember.objects.get(
|
||||
pk=pk,
|
||||
@ -792,6 +795,7 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@invalidate_path_cache("/api/workspaces/:slug/projects/:project_id/members/", True)
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
project_member = ProjectMember.objects.get(
|
||||
workspace__slug=slug,
|
||||
|
@ -16,7 +16,7 @@ from plane.app.permissions import (
|
||||
WorkspaceEntityPermission,
|
||||
)
|
||||
from plane.db.models import State, Issue
|
||||
|
||||
from plane.utils.cache import cache_path_response, invalidate_path_cache
|
||||
|
||||
class StateViewSet(BaseViewSet):
|
||||
serializer_class = StateSerializer
|
||||
@ -38,6 +38,8 @@ class StateViewSet(BaseViewSet):
|
||||
.distinct()
|
||||
)
|
||||
|
||||
@invalidate_path_cache()
|
||||
@invalidate_path_cache("workspaces/:slug/states/", True)
|
||||
def create(self, request, slug, project_id):
|
||||
serializer = StateSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
@ -45,6 +47,7 @@ class StateViewSet(BaseViewSet):
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@cache_path_response(60 * 60 * 1)
|
||||
def list(self, request, slug, project_id):
|
||||
states = StateSerializer(self.get_queryset(), many=True).data
|
||||
grouped = request.GET.get("grouped", False)
|
||||
@ -58,6 +61,8 @@ class StateViewSet(BaseViewSet):
|
||||
return Response(state_dict, status=status.HTTP_200_OK)
|
||||
return Response(states, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_path_cache("/api/workspaces/:slug/projects/:project_id/states/", True)
|
||||
@invalidate_path_cache("workspaces/:slug/states/", True)
|
||||
def mark_as_default(self, request, slug, project_id, pk):
|
||||
# Select all the states which are marked as default
|
||||
_ = State.objects.filter(
|
||||
@ -68,6 +73,8 @@ class StateViewSet(BaseViewSet):
|
||||
).update(default=True)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@invalidate_path_cache("/api/workspaces/:slug/projects/:project_id/states/", True)
|
||||
@invalidate_path_cache("workspaces/:slug/states/", True)
|
||||
def destroy(self, request, slug, project_id, pk):
|
||||
state = State.objects.get(
|
||||
~Q(name="Triage"),
|
||||
|
@ -83,6 +83,11 @@ from plane.app.permissions import (
|
||||
from plane.bgtasks.workspace_invitation_task import workspace_invitation
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.bgtasks.event_tracking_task import workspace_invite_event
|
||||
from plane.utils.cache import (
|
||||
cache_path_response,
|
||||
invalidate_path_cache,
|
||||
cache_user_response,
|
||||
)
|
||||
|
||||
|
||||
class WorkSpaceViewSet(BaseViewSet):
|
||||
@ -542,6 +547,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
.select_related("member")
|
||||
)
|
||||
|
||||
@cache_path_response(60 * 5)
|
||||
def list(self, request, slug):
|
||||
workspace_member = WorkspaceMember.objects.get(
|
||||
member=request.user,
|
||||
@ -566,6 +572,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
@invalidate_path_cache("/api/workspaces/:slug/members/", True)
|
||||
def partial_update(self, request, slug, pk):
|
||||
workspace_member = WorkspaceMember.objects.get(
|
||||
pk=pk,
|
||||
@ -608,6 +615,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@invalidate_path_cache("/api/workspaces/:slug/members/", True)
|
||||
def destroy(self, request, slug, pk):
|
||||
# Check the user role who is deleting the user
|
||||
workspace_member = WorkspaceMember.objects.get(
|
||||
@ -672,6 +680,7 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||
workspace_member.save()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@invalidate_path_cache("/api/workspaces/:slug/members/", True)
|
||||
def leave(self, request, slug):
|
||||
workspace_member = WorkspaceMember.objects.get(
|
||||
workspace__slug=slug,
|
||||
@ -868,6 +877,8 @@ class UserLastProjectWithWorkspaceEndpoint(BaseAPIView):
|
||||
|
||||
|
||||
class WorkspaceMemberUserEndpoint(BaseAPIView):
|
||||
|
||||
@cache_user_response(60 * 60)
|
||||
def get(self, request, slug):
|
||||
workspace_member = WorkspaceMember.objects.get(
|
||||
member=request.user,
|
||||
@ -1433,6 +1444,7 @@ class WorkspaceLabelsEndpoint(BaseAPIView):
|
||||
WorkspaceViewerPermission,
|
||||
]
|
||||
|
||||
@cache_path_response(60 * 60 * 1)
|
||||
def get(self, request, slug):
|
||||
labels = Label.objects.filter(
|
||||
workspace__slug=slug,
|
||||
@ -1447,6 +1459,7 @@ class WorkspaceStatesEndpoint(BaseAPIView):
|
||||
WorkspaceEntityPermission,
|
||||
]
|
||||
|
||||
@cache_path_response(60 * 60 * 1)
|
||||
def get(self, request, slug):
|
||||
states = State.objects.filter(
|
||||
workspace__slug=slug,
|
||||
@ -1461,6 +1474,7 @@ class WorkspaceEstimatesEndpoint(BaseAPIView):
|
||||
WorkspaceEntityPermission,
|
||||
]
|
||||
|
||||
@cache_path_response(60 * 60 * 1)
|
||||
def get(self, request, slug):
|
||||
estimate_ids = Project.objects.filter(
|
||||
workspace__slug=slug, estimate__isnull=False
|
||||
|
@ -14,7 +14,11 @@ EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": REDIS_URL,
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,9 +30,6 @@ def cache_user_response(timeout, path=None):
|
||||
|
||||
if response.status_code == 200:
|
||||
cache.set(key, {'data': response.data, 'status': response.status_code}, timeout)
|
||||
response['Cache-Control'] = f'max-age={timeout}'
|
||||
expires_time = datetime.utcnow() + timedelta(seconds=timeout)
|
||||
response['Expires'] = http_date(expires_time.timestamp())
|
||||
|
||||
return response
|
||||
return _wrapped_view
|
||||
@ -59,6 +56,7 @@ def cache_path_response(timeout, path=None):
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(instance, request, *args, **kwargs):
|
||||
# Function to generate cache key
|
||||
print(request.get_full_path())
|
||||
custom_path = path if path is not None else request.get_full_path()
|
||||
key = generate_cache_key(custom_path, None)
|
||||
cached_result = cache.get(key)
|
||||
@ -69,20 +67,25 @@ def cache_path_response(timeout, path=None):
|
||||
|
||||
if response.status_code == 200:
|
||||
cache.set(key, {'data': response.data, 'status': response.status_code}, timeout)
|
||||
response['Cache-Control'] = f'max-age={timeout}'
|
||||
expires_time = datetime.utcnow() + timedelta(seconds=timeout)
|
||||
response['Expires'] = http_date(expires_time.timestamp())
|
||||
|
||||
return response
|
||||
return _wrapped_view
|
||||
return decorator
|
||||
|
||||
def invalidate_path_cache(path=None):
|
||||
def invalidate_path_cache(path=None, include_url_params=False):
|
||||
def decorator(view_func):
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(instance, request, *args, **kwargs):
|
||||
# Invalidate cache before executing the view function
|
||||
custom_path = path if path is not None else request.get_full_path()
|
||||
if include_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()
|
||||
|
||||
key = generate_cache_key(custom_path, None)
|
||||
cache.delete(key)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user