fix: user and workspace views

This commit is contained in:
NarayanBavisetti 2024-01-31 11:50:40 +05:30
parent f63a04c1ab
commit a77839a942
14 changed files with 459 additions and 418 deletions

View File

@ -1,238 +0,0 @@
# All the python scripts that are used for back migrations
import uuid
import random
from django.contrib.auth.hashers import make_password
from plane.db.models import ProjectIdentifier
from plane.db.models import (
Issue,
IssueComment,
User,
Project,
ProjectMember,
Label,
Integration,
)
# Update description and description html values for old descriptions
def update_description():
try:
issues = Issue.objects.all()
updated_issues = []
for issue in issues:
issue.description_html = f"<p>{issue.description}</p>"
issue.description_stripped = issue.description
updated_issues.append(issue)
Issue.objects.bulk_update(
updated_issues,
["description_html", "description_stripped"],
batch_size=100,
)
print("Success")
except Exception as e:
print(e)
print("Failed")
def update_comments():
try:
issue_comments = IssueComment.objects.all()
updated_issue_comments = []
for issue_comment in issue_comments:
issue_comment.comment_html = (
f"<p>{issue_comment.comment_stripped}</p>"
)
updated_issue_comments.append(issue_comment)
IssueComment.objects.bulk_update(
updated_issue_comments, ["comment_html"], batch_size=100
)
print("Success")
except Exception as e:
print(e)
print("Failed")
def update_project_identifiers():
try:
project_identifiers = ProjectIdentifier.objects.filter(
workspace_id=None
).select_related("project", "project__workspace")
updated_identifiers = []
for identifier in project_identifiers:
identifier.workspace_id = identifier.project.workspace_id
updated_identifiers.append(identifier)
ProjectIdentifier.objects.bulk_update(
updated_identifiers, ["workspace_id"], batch_size=50
)
print("Success")
except Exception as e:
print(e)
print("Failed")
def update_user_empty_password():
try:
users = User.objects.filter(password="")
updated_users = []
for user in users:
user.password = make_password(uuid.uuid4().hex)
user.is_password_autoset = True
updated_users.append(user)
User.objects.bulk_update(updated_users, ["password"], batch_size=50)
print("Success")
except Exception as e:
print(e)
print("Failed")
def updated_issue_sort_order():
try:
issues = Issue.objects.all()
updated_issues = []
for issue in issues:
issue.sort_order = issue.sequence_id * random.randint(100, 500)
updated_issues.append(issue)
Issue.objects.bulk_update(
updated_issues, ["sort_order"], batch_size=100
)
print("Success")
except Exception as e:
print(e)
print("Failed")
def update_project_cover_images():
try:
project_cover_images = [
"https://images.unsplash.com/photo-1677432658720-3d84f9d657b4?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1661107564401-57497d8fe86f?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
"https://images.unsplash.com/photo-1677352241429-dc90cfc7a623?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
"https://images.unsplash.com/photo-1677196728306-eeafea692454?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1331&q=80",
"https://images.unsplash.com/photo-1660902179734-c94c944f7830?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1255&q=80",
"https://images.unsplash.com/photo-1672243775941-10d763d9adef?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1677040628614-53936ff66632?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1676920410907-8d5f8dd4b5ba?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
"https://images.unsplash.com/photo-1676846328604-ce831c481346?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1155&q=80",
"https://images.unsplash.com/photo-1676744843212-09b7e64c3a05?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1676798531090-1608bedeac7b?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1597088758740-56fd7ec8a3f0?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1169&q=80",
"https://images.unsplash.com/photo-1676638392418-80aad7c87b96?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=774&q=80",
"https://images.unsplash.com/photo-1649639194967-2fec0b4ea7bc?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1675883086902-b453b3f8146e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=774&q=80",
"https://images.unsplash.com/photo-1675887057159-40fca28fdc5d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1173&q=80",
"https://images.unsplash.com/photo-1675373980203-f84c5a672aa5?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1675191475318-d2bf6bad1200?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
"https://images.unsplash.com/photo-1675456230532-2194d0c4bcc0?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80",
"https://images.unsplash.com/photo-1675371788315-60fa0ef48267?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1332&q=80",
]
projects = Project.objects.all()
updated_projects = []
for project in projects:
project.cover_image = project_cover_images[random.randint(0, 19)]
updated_projects.append(project)
Project.objects.bulk_update(
updated_projects, ["cover_image"], batch_size=100
)
print("Success")
except Exception as e:
print(e)
print("Failed")
def update_user_view_property():
try:
project_members = ProjectMember.objects.all()
updated_project_members = []
for project_member in project_members:
project_member.default_props = {
"filters": {"type": None},
"orderBy": "-created_at",
"collapsed": True,
"issueView": "list",
"filterIssue": None,
"groupByProperty": None,
"showEmptyGroups": True,
}
updated_project_members.append(project_member)
ProjectMember.objects.bulk_update(
updated_project_members, ["default_props"], batch_size=100
)
print("Success")
except Exception as e:
print(e)
print("Failed")
def update_label_color():
try:
labels = Label.objects.filter(color="")
updated_labels = []
for label in labels:
label.color = "#" + "%06x" % random.randint(0, 0xFFFFFF)
updated_labels.append(label)
Label.objects.bulk_update(updated_labels, ["color"], batch_size=100)
print("Success")
except Exception as e:
print(e)
print("Failed")
def create_slack_integration():
try:
_ = Integration.objects.create(
provider="slack", network=2, title="Slack"
)
print("Success")
except Exception as e:
print(e)
print("Failed")
def update_integration_verified():
try:
integrations = Integration.objects.all()
updated_integrations = []
for integration in integrations:
integration.verified = True
updated_integrations.append(integration)
Integration.objects.bulk_update(
updated_integrations, ["verified"], batch_size=10
)
print("Success")
except Exception as e:
print(e)
print("Failed")
def update_start_date():
try:
issues = Issue.objects.filter(
state__group__in=["started", "completed"]
)
updated_issues = []
for issue in issues:
issue.start_date = issue.created_at.date()
updated_issues.append(issue)
Issue.objects.bulk_update(
updated_issues, ["start_date"], batch_size=500
)
print("Success")
except Exception as e:
print(e)
print("Failed")

View File

@ -36,9 +36,8 @@ from .project import (
)
from .state import StateSerializer, StateLiteSerializer
from .view import (
GlobalViewSerializer,
IssueViewSerializer,
IssueViewFavoriteSerializer,
ViewSerializer,
ViewFavoriteSerializer,
)
from .cycle import (
CycleSerializer,

View File

@ -3,69 +3,33 @@ from rest_framework import serializers
# Module imports
from .base import BaseSerializer, DynamicBaseSerializer
from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer
from plane.db.models import GlobalView, IssueView, IssueViewFavorite
from plane.db.models import View, ViewFavorite
from plane.utils.issue_filters import issue_filters
class GlobalViewSerializer(BaseSerializer):
workspace_detail = WorkspaceLiteSerializer(
source="workspace", read_only=True
)
class Meta:
model = GlobalView
fields = "__all__"
read_only_fields = [
"workspace",
"query",
]
def create(self, validated_data):
query_params = validated_data.get("query_data", {})
if bool(query_params):
validated_data["query"] = issue_filters(query_params, "POST")
else:
validated_data["query"] = dict()
return GlobalView.objects.create(**validated_data)
def update(self, instance, validated_data):
query_params = validated_data.get("query_data", {})
if bool(query_params):
validated_data["query"] = issue_filters(query_params, "POST")
else:
validated_data["query"] = dict()
validated_data["query"] = issue_filters(query_params, "PATCH")
return super().update(instance, validated_data)
class IssueViewSerializer(DynamicBaseSerializer):
class ViewSerializer(DynamicBaseSerializer):
is_favorite = serializers.BooleanField(read_only=True)
project_detail = ProjectLiteSerializer(source="project", read_only=True)
workspace_detail = WorkspaceLiteSerializer(
source="workspace", read_only=True
)
class Meta:
model = IssueView
model = View
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"query",
"access",
]
def create(self, validated_data):
query_params = validated_data.get("query_data", {})
query_params = validated_data.get("filters", {})
if bool(query_params):
validated_data["query"] = issue_filters(query_params, "POST")
else:
validated_data["query"] = {}
return IssueView.objects.create(**validated_data)
return View.objects.create(**validated_data)
def update(self, instance, validated_data):
query_params = validated_data.get("query_data", {})
query_params = validated_data.get("filters", {})
if bool(query_params):
validated_data["query"] = issue_filters(query_params, "POST")
else:
@ -74,11 +38,10 @@ class IssueViewSerializer(DynamicBaseSerializer):
return super().update(instance, validated_data)
class IssueViewFavoriteSerializer(BaseSerializer):
view_detail = IssueViewSerializer(source="issue_view", read_only=True)
class ViewFavoriteSerializer(BaseSerializer):
class Meta:
model = IssueViewFavorite
model = ViewFavorite
fields = "__all__"
read_only_fields = [
"workspace",

View File

@ -14,6 +14,8 @@ from plane.app.views import (
UserActivityGraphEndpoint,
UserIssueCompletedGraphEndpoint,
UserWorkspaceDashboardEndpoint,
UserWorkspaceViewViewSet,
UserProjectViewViewSet,
## End Workspaces
)
@ -95,5 +97,47 @@ urlpatterns = [
SetUserPasswordEndpoint.as_view(),
name="set-password",
),
path(
"users/me/workspaces/<str:slug>/views/",
UserWorkspaceViewViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="user-workspace-views",
),
path(
"users/me/workspaces/<str:slug>/views/<uuid:pk>/",
UserWorkspaceViewViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="user-workspace-views",
),
path(
"users/me/workspaces/<str:slug>/projects/<uuid:project_id>/views/",
UserProjectViewViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="user-project-views",
),
path(
"users/me/workspaces/<str:slug>/projects/<uuid:project_id>/views/<uuid:pk>/",
UserProjectViewViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="user-project-views",
),
## End User Graph
]

View File

@ -2,17 +2,16 @@ from django.urls import path
from plane.app.views import (
IssueViewViewSet,
GlobalViewViewSet,
GlobalViewIssuesViewSet,
IssueViewFavoriteViewSet,
ProjectViewViewSet,
WorkspaceViewViewSet,
ViewFavoriteViewSet,
)
urlpatterns = [
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/views/",
IssueViewViewSet.as_view(
ProjectViewViewSet.as_view(
{
"get": "list",
"post": "create",
@ -22,7 +21,7 @@ urlpatterns = [
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/views/<uuid:pk>/",
IssueViewViewSet.as_view(
ProjectViewViewSet.as_view(
{
"get": "retrieve",
"put": "update",
@ -34,7 +33,7 @@ urlpatterns = [
),
path(
"workspaces/<str:slug>/views/",
GlobalViewViewSet.as_view(
WorkspaceViewViewSet.as_view(
{
"get": "list",
"post": "create",
@ -44,10 +43,9 @@ urlpatterns = [
),
path(
"workspaces/<str:slug>/views/<uuid:pk>/",
GlobalViewViewSet.as_view(
WorkspaceViewViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
@ -56,7 +54,7 @@ urlpatterns = [
),
path(
"workspaces/<str:slug>/issues/",
GlobalViewIssuesViewSet.as_view(
WorkspaceViewViewSet.as_view(
{
"get": "list",
}
@ -65,7 +63,7 @@ urlpatterns = [
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/",
IssueViewFavoriteViewSet.as_view(
ViewFavoriteViewSet.as_view(
{
"get": "list",
"post": "create",
@ -75,7 +73,7 @@ urlpatterns = [
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/<uuid:view_id>/",
IssueViewFavoriteViewSet.as_view(
ViewFavoriteViewSet.as_view(
{
"delete": "destroy",
}

View File

@ -52,10 +52,12 @@ from .workspace import (
)
from .state import StateViewSet
from .view import (
GlobalViewViewSet,
GlobalViewIssuesViewSet,
IssueViewViewSet,
IssueViewFavoriteViewSet,
WorkspaceViewViewSet,
ProjectViewViewSet,
ViewFavoriteViewSet,
UserWorkspaceViewViewSet,
UserProjectViewViewSet,
ProjectViewViewSet,
)
from .cycle import (
CycleViewSet,

View File

@ -17,7 +17,7 @@ from plane.db.models import (
Cycle,
Module,
Page,
IssueView,
View,
)
from plane.utils.issue_search import search_issues
@ -161,7 +161,7 @@ class GlobalSearchEndpoint(BaseAPIView):
for field in fields:
q |= Q(**{f"{field}__icontains": query})
issue_views = IssueView.objects.filter(
issue_views = View.objects.filter(
q,
project__project_projectmember__member=self.request.user,
workspace__slug=slug,

View File

@ -10,68 +10,308 @@ from django.db.models import (
When,
Exists,
Max,
Q,
)
from django.utils.decorators import method_decorator
from django.views.decorators.gzip import gzip_page
from django.db.models import Prefetch, OuterRef, Exists
# Third party imports
from rest_framework.response import Response
from rest_framework import status
# Module imports
from . import BaseViewSet, BaseAPIView
from . import BaseViewSet
from plane.app.serializers import (
GlobalViewSerializer,
IssueViewSerializer,
ViewSerializer,
IssueSerializer,
IssueViewFavoriteSerializer,
ViewFavoriteSerializer,
)
from plane.app.permissions import (
WorkspaceEntityPermission,
ProjectEntityPermission,
WorkspaceViewerPermission,
ProjectLitePermission,
)
from plane.db.models import (
Workspace,
GlobalView,
IssueView,
View,
Issue,
IssueViewFavorite,
IssueReaction,
ViewFavorite,
IssueLink,
IssueAttachment,
IssueSubscriber,
)
from plane.utils.issue_filters import issue_filters
from plane.utils.grouper import group_results
class GlobalViewViewSet(BaseViewSet):
serializer_class = IssueViewSerializer
model = IssueView
class UserWorkspaceViewViewSet(BaseViewSet):
serializer_class = ViewSerializer
model = View
permission_classes = [
WorkspaceEntityPermission,
]
def perform_create(self, serializer):
workspace = Workspace.objects.get(slug=self.kwargs.get("slug"))
serializer.save(workspace_id=workspace.id)
serializer.save(
workspace_id=workspace.id, access=0, owned_by=self.request.user
)
def get_queryset(self):
subquery = ViewFavorite.objects.filter(
user=self.request.user,
view_id=OuterRef("pk"),
workspace__slug=self.kwargs.get("slug"),
)
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project__isnull=True)
.filter(Q(owned_by=self.request.user) & Q(access=0))
.select_related("workspace")
.annotate(is_favorite=Exists(subquery))
.order_by(self.request.GET.get("order_by", "-created_at"))
.distinct()
)
def perform_update(self, serializer):
view = View.objects.get(pk=self.kwargs.get("pk"))
if view.is_locked:
return Response(
{"error": "You cannot update the view"},
status=status.HTTP_403_FORBIDDEN,
)
if view.owned_by == self.request.user:
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(
{"error": "You cannot update the view"},
status=status.HTTP_403_FORBIDDEN,
)
class GlobalViewIssuesViewSet(BaseViewSet):
def lock(self, request, slug, pk):
view = View.objects.get(pk=pk, workspace__slug=slug)
if view.owned_by != self.request.user:
return Response(
{"error": "You cannot lock the view"},
status=status.HTTP_403_FORBIDDEN,
)
view.is_locked = True
view.save()
return Response(status=status.HTTP_200_OK)
def unlock(self, request, slug, pk):
view = View.objects.get(pk=pk, workspace__slug=slug)
if view.owned_by != self.request.user:
return Response(
{"error": "You cannot un lock the view"},
status=status.HTTP_403_FORBIDDEN,
)
view.is_locked = False
view.save()
return Response(status=status.HTTP_200_OK)
def destroy(self, request, slug, pk):
view = View.objects.get(workspace__slug=slug, pk=pk)
if view.owned_by != self.request.user:
return Response(
{"error": "You cannot delete the view"},
status=status.HTTP_403_FORBIDDEN,
)
view.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class WorkspaceViewViewSet(BaseViewSet):
serializer_class = ViewSerializer
model = View
permission_classes = [
WorkspaceEntityPermission,
]
def perform_create(self, serializer):
workspace = Workspace.objects.get(slug=self.kwargs.get("slug"))
serializer.save(workspace_id=workspace.id, owned_by=self.request.user)
def get_queryset(self):
subquery = ViewFavorite.objects.filter(
user=self.request.user,
view_id=OuterRef("pk"),
workspace__slug=self.kwargs.get("slug"),
)
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project__isnull=True)
.filter(Q(access=1))
.select_related("workspace")
.annotate(is_favorite=Exists(subquery))
.order_by(self.request.GET.get("order_by", "-created_at"))
.distinct()
)
def lock(self, request, slug, pk):
view = View.objects.get(pk=pk, workspace__slug=slug)
if view.owned_by != self.request.user:
return Response(
{"error": "You cannot lock the view"},
status=status.HTTP_403_FORBIDDEN,
)
view.is_locked = True
view.save()
return Response(status=status.HTTP_200_OK)
def unlock(self, request, slug, pk):
view = View.objects.get(pk=pk, workspace__slug=slug)
if view.owned_by != self.request.user:
return Response(
{"error": "You cannot un lock the view"},
status=status.HTTP_403_FORBIDDEN,
)
view.is_locked = False
view.save()
return Response(status=status.HTTP_200_OK)
class UserProjectViewViewSet(BaseViewSet):
serializer_class = ViewSerializer
model = View
permission_classes = [
ProjectEntityPermission,
]
def perform_create(self, serializer):
workspace = Workspace.objects.get(slug=self.kwargs.get("slug"))
serializer.save(
workspace_id=workspace.id,
project_id=self.kwargs.get("project_id"),
access=0,
owned_by=self.request.user,
)
def get_queryset(self):
subquery = ViewFavorite.objects.filter(
user=self.request.user,
view_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
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)
.filter(Q(owned_by=self.request.user) & Q(access=0))
.select_related("workspace")
.annotate(is_favorite=Exists(subquery))
.order_by(self.request.GET.get("order_by", "-created_at"))
.distinct()
)
def perform_update(self, serializer):
view = View.objects.get(pk=self.kwargs.get("pk"))
if view.owned_by == self.request.user:
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(
{"error": "You cannot update the view"},
status=status.HTTP_403_FORBIDDEN,
)
def destroy(self, request, slug, project_id, pk):
view = View.objects.get(
workspace__slug=slug, project_id=project_id, pk=pk
)
if view.owned_by != self.request.user:
return Response(
{"error": "You cannot delete the view"},
status=status.HTTP_403_FORBIDDEN,
)
view.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
def lock(self, request, slug, pk):
view = View.objects.get(pk=pk, workspace__slug=slug)
if view.owned_by != self.request.user:
return Response(
{"error": "You cannot lock the view"},
status=status.HTTP_403_FORBIDDEN,
)
view.is_locked = True
view.save()
return Response(status=status.HTTP_200_OK)
def unlock(self, request, slug, pk):
view = View.objects.get(pk=pk, workspace__slug=slug)
if view.owned_by != self.request.user:
return Response(
{"error": "You cannot un lock the view"},
status=status.HTTP_403_FORBIDDEN,
)
view.is_locked = False
view.save()
return Response(status=status.HTTP_200_OK)
class ProjectViewViewSet(BaseViewSet):
serializer_class = ViewSerializer
model = View
permission_classes = [
ProjectEntityPermission,
]
def perform_create(self, serializer):
workspace = Workspace.objects.get(slug=self.kwargs.get("slug"))
serializer.save(
workspace_id=workspace.id,
project_id=self.kwargs.get("project_id"),
owned_by=self.request.user,
)
def get_queryset(self):
subquery = ViewFavorite.objects.filter(
user=self.request.user,
view_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
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)
.filter(Q(access=1))
.select_related("workspace")
.annotate(is_favorite=Exists(subquery))
.order_by(self.request.GET.get("order_by", "-created_at"))
.distinct()
)
def lock(self, request, slug, pk):
view = View.objects.get(pk=pk, workspace__slug=slug)
if view.owned_by != self.request.user:
return Response(
{"error": "You cannot lock the view"},
status=status.HTTP_403_FORBIDDEN,
)
view.is_locked = True
view.save()
return Response(status=status.HTTP_200_OK)
def unlock(self, request, slug, pk):
view = View.objects.get(pk=pk, workspace__slug=slug)
if view.owned_by != self.request.user:
return Response(
{"error": "You cannot un lock the view"},
status=status.HTTP_403_FORBIDDEN,
)
view.is_locked = False
view.save()
return Response(status=status.HTTP_200_OK)
class WorkspaceViewIssuesViewSet(BaseViewSet):
permission_classes = [
WorkspaceEntityPermission,
]
@ -87,41 +327,9 @@ class GlobalViewIssuesViewSet(BaseViewSet):
.values("count")
)
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project__project_projectmember__member=self.request.user)
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.prefetch_related(
Prefetch(
"issue_reactions",
queryset=IssueReaction.objects.select_related("actor"),
)
)
)
@method_decorator(gzip_page)
def list(self, request, slug):
filters = issue_filters(request.query_params, "GET")
fields = [
field
for field in request.GET.get("fields", "").split(",")
if field
]
# Custom ordering for priority and state
priority_order = ["urgent", "high", "medium", "low", "none"]
state_order = [
"backlog",
"unstarted",
"started",
"completed",
"cancelled",
]
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = (
self.get_queryset()
.filter(**filters)
.filter(project__project_projectmember__member=self.request.user)
.annotate(cycle_id=F("issue_cycle__cycle_id"))
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
@ -147,6 +355,29 @@ class GlobalViewIssuesViewSet(BaseViewSet):
)
)
@method_decorator(gzip_page)
def list(self, request, slug):
filters = issue_filters(request.query_params, "GET")
fields = [
field
for field in request.GET.get("fields", "").split(",")
if field
]
# Custom ordering for priority and state
priority_order = ["urgent", "high", "medium", "low", "none"]
state_order = [
"backlog",
"unstarted",
"started",
"completed",
"cancelled",
]
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = (self.get_queryset().filter(**filters))
# Priority Ordering
if order_by_param == "priority" or order_by_param == "-priority":
priority_order = (
@ -213,52 +444,9 @@ class GlobalViewIssuesViewSet(BaseViewSet):
return Response(serializer.data, status=status.HTTP_200_OK)
class IssueViewViewSet(BaseViewSet):
serializer_class = IssueViewSerializer
model = IssueView
permission_classes = [
ProjectEntityPermission,
]
def perform_create(self, serializer):
serializer.save(project_id=self.kwargs.get("project_id"))
def get_queryset(self):
subquery = IssueViewFavorite.objects.filter(
user=self.request.user,
view_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
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")
.annotate(is_favorite=Exists(subquery))
.order_by("-is_favorite", "name")
.distinct()
)
def list(self, request, slug, project_id):
queryset = self.get_queryset()
fields = [
field
for field in request.GET.get("fields", "").split(",")
if field
]
views = IssueViewSerializer(
queryset, many=True, fields=fields if fields else None
).data
return Response(views, status=status.HTTP_200_OK)
class IssueViewFavoriteViewSet(BaseViewSet):
serializer_class = IssueViewFavoriteSerializer
model = IssueViewFavorite
class ViewFavoriteViewSet(BaseViewSet):
serializer_class = ViewFavoriteSerializer
model = ViewFavorite
def get_queryset(self):
return self.filter_queryset(
@ -270,18 +458,18 @@ class IssueViewFavoriteViewSet(BaseViewSet):
)
def create(self, request, slug, project_id):
serializer = IssueViewFavoriteSerializer(data=request.data)
serializer = ViewFavoriteSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, project_id=project_id)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, slug, project_id, view_id):
view_favourite = IssueViewFavorite.objects.get(
view_favorite = ViewFavorite.objects.get(
project=project_id,
user=request.user,
workspace__slug=slug,
view_id=view_id,
)
view_favourite.delete()
view_favorite.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@ -1,11 +1,12 @@
# Generated by Django 4.2.7 on 2024-01-02 13:15
from plane.db.models import WorkspaceUserProperties, ProjectMember, IssueView
from plane.db.models import ProjectMember
from django.db import migrations
def workspace_user_properties(apps, schema_editor):
WorkspaceMember = apps.get_model("db", "WorkspaceMember")
WorkspaceUserProperties = apps.get_model("db", "WorkspaceUserProperties")
updated_workspace_user_properties = []
for workspace_members in WorkspaceMember.objects.all():
updated_workspace_user_properties.append(
@ -49,6 +50,7 @@ def project_user_properties(apps, schema_editor):
def issue_view(apps, schema_editor):
GlobalView = apps.get_model("db", "GlobalView")
IssueView = apps.get_model("db", "IssueView")
updated_issue_views = []
for global_view in GlobalView.objects.all():

View File

@ -0,0 +1,70 @@
# Generated by Django 4.2.7 on 2024-01-30 07:49
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django.db.models import F
def views_owned_by(apps, schema_editor):
View = apps.get_model("db", "View")
View.objects.update(owned_by=F('created_by'))
class Migration(migrations.Migration):
dependencies = [
('db', '0058_alter_moduleissue_issue_and_more'),
]
operations = [
migrations.RenameModel(
old_name='IssueView',
new_name='View',
),
migrations.AlterModelTable(
name='view',
table='views',
),
migrations.RenameModel(
old_name='IssueViewFavorite',
new_name='ViewFavorite',
),
migrations.AlterField(
model_name='viewfavorite',
name='project',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='project_%(class)s', to='db.project'),
),
migrations.AlterModelTable(
name='workspaceuserproperties',
table='workspace_user_properties',
),
migrations.AlterModelOptions(
name='view',
options={'ordering': ('-created_at',), 'verbose_name': 'View', 'verbose_name_plural': 'Views'},
),
migrations.AddField(
model_name='view',
name='is_locked',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='view',
name='is_pinned',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='view',
name='owned_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='views', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='view',
name='access',
field=models.PositiveSmallIntegerField(choices=[(0, 'Private'), (1, 'Public'), (2, 'Shared')], default=1),
),
migrations.AlterField(
model_name='viewfavorite',
name='view',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='view_favorites', to='db.view'),
),
migrations.RunPython(views_owned_by)
]

View File

@ -52,7 +52,7 @@ from .state import State
from .cycle import Cycle, CycleIssue, CycleFavorite, CycleUserProperties
from .view import GlobalView, IssueView, IssueViewFavorite
from .view import View, ViewFavorite
from .module import (
Module,

View File

@ -3,7 +3,7 @@ from django.db import models
from django.conf import settings
# Module import
from . import ProjectBaseModel, BaseModel, WorkspaceBaseModel
from . import BaseModel, WorkspaceBaseModel
def get_default_filters():
@ -84,7 +84,7 @@ class GlobalView(BaseModel):
return f"{self.name} <{self.workspace.name}>"
class IssueView(WorkspaceBaseModel):
class View(WorkspaceBaseModel):
name = models.CharField(max_length=255, verbose_name="View Name")
description = models.TextField(verbose_name="View Description", blank=True)
query = models.JSONField(verbose_name="View Query")
@ -94,29 +94,44 @@ class IssueView(WorkspaceBaseModel):
default=get_default_display_properties
)
access = models.PositiveSmallIntegerField(
default=1, choices=((0, "Private"), (1, "Public"))
default=1, choices=((0, "Private"), (1, "Public"), (2, "Shared"))
)
owned_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="views", null=True, blank=True
)
sort_order = models.FloatField(default=65535)
is_locked = models.BooleanField(default=False)
is_pinned = models.BooleanField(default=False)
class Meta:
verbose_name = "Issue View"
verbose_name_plural = "Issue Views"
db_table = "issue_views"
verbose_name = "View"
verbose_name_plural = "Views"
db_table = "views"
ordering = ("-created_at",)
def save(self, *args, **kwargs):
if self._state.adding:
largest_sort_order = View.objects.filter(
workspace=self.workspace
).aggregate(largest=models.Max("sort_order"))["largest"]
if largest_sort_order is not None:
self.sort_order = largest_sort_order + 10000
super(View, self).save(*args, **kwargs)
def __str__(self):
"""Return name of the View"""
return f"{self.name} <{self.project.name}>"
class IssueViewFavorite(ProjectBaseModel):
class ViewFavorite(WorkspaceBaseModel):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="user_view_favorites",
)
view = models.ForeignKey(
"db.IssueView", on_delete=models.CASCADE, related_name="view_favorites"
"db.View", on_delete=models.CASCADE, related_name="view_favorites"
)
class Meta:

View File

@ -326,7 +326,7 @@ class WorkspaceUserProperties(BaseModel):
unique_together = ["workspace", "user"]
verbose_name = "Workspace User Property"
verbose_name_plural = "Workspace User Property"
db_table = "Workspace_user_properties"
db_table = "workspace_user_properties"
ordering = ("-created_at",)
def __str__(self):

View File

@ -282,10 +282,8 @@ if REDIS_SSL:
redis_url = os.environ.get("REDIS_URL")
broker_url = f"{redis_url}?ssl_cert_reqs={ssl.CERT_NONE.name}&ssl_ca_certs={certifi.where()}"
CELERY_BROKER_URL = broker_url
CELERY_RESULT_BACKEND = broker_url
else:
CELERY_BROKER_URL = REDIS_URL
CELERY_RESULT_BACKEND = REDIS_URL
CELERY_IMPORTS = (
"plane.bgtasks.issue_automation_task",