mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: user and workspace views
This commit is contained in:
parent
f63a04c1ab
commit
a77839a942
@ -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")
|
|
@ -36,9 +36,8 @@ from .project import (
|
|||||||
)
|
)
|
||||||
from .state import StateSerializer, StateLiteSerializer
|
from .state import StateSerializer, StateLiteSerializer
|
||||||
from .view import (
|
from .view import (
|
||||||
GlobalViewSerializer,
|
ViewSerializer,
|
||||||
IssueViewSerializer,
|
ViewFavoriteSerializer,
|
||||||
IssueViewFavoriteSerializer,
|
|
||||||
)
|
)
|
||||||
from .cycle import (
|
from .cycle import (
|
||||||
CycleSerializer,
|
CycleSerializer,
|
||||||
|
@ -3,69 +3,33 @@ from rest_framework import serializers
|
|||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from .base import BaseSerializer, DynamicBaseSerializer
|
from .base import BaseSerializer, DynamicBaseSerializer
|
||||||
from .workspace import WorkspaceLiteSerializer
|
from plane.db.models import View, ViewFavorite
|
||||||
from .project import ProjectLiteSerializer
|
|
||||||
from plane.db.models import GlobalView, IssueView, IssueViewFavorite
|
|
||||||
from plane.utils.issue_filters import issue_filters
|
from plane.utils.issue_filters import issue_filters
|
||||||
|
|
||||||
|
|
||||||
class GlobalViewSerializer(BaseSerializer):
|
class ViewSerializer(DynamicBaseSerializer):
|
||||||
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):
|
|
||||||
is_favorite = serializers.BooleanField(read_only=True)
|
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:
|
class Meta:
|
||||||
model = IssueView
|
model = View
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
"workspace",
|
"workspace",
|
||||||
"project",
|
"project",
|
||||||
"query",
|
"query",
|
||||||
|
"access",
|
||||||
]
|
]
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
query_params = validated_data.get("query_data", {})
|
query_params = validated_data.get("filters", {})
|
||||||
if bool(query_params):
|
if bool(query_params):
|
||||||
validated_data["query"] = issue_filters(query_params, "POST")
|
validated_data["query"] = issue_filters(query_params, "POST")
|
||||||
else:
|
else:
|
||||||
validated_data["query"] = {}
|
validated_data["query"] = {}
|
||||||
return IssueView.objects.create(**validated_data)
|
return View.objects.create(**validated_data)
|
||||||
|
|
||||||
def update(self, instance, 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):
|
if bool(query_params):
|
||||||
validated_data["query"] = issue_filters(query_params, "POST")
|
validated_data["query"] = issue_filters(query_params, "POST")
|
||||||
else:
|
else:
|
||||||
@ -74,11 +38,10 @@ class IssueViewSerializer(DynamicBaseSerializer):
|
|||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
class IssueViewFavoriteSerializer(BaseSerializer):
|
class ViewFavoriteSerializer(BaseSerializer):
|
||||||
view_detail = IssueViewSerializer(source="issue_view", read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IssueViewFavorite
|
model = ViewFavorite
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
"workspace",
|
"workspace",
|
||||||
|
@ -14,6 +14,8 @@ from plane.app.views import (
|
|||||||
UserActivityGraphEndpoint,
|
UserActivityGraphEndpoint,
|
||||||
UserIssueCompletedGraphEndpoint,
|
UserIssueCompletedGraphEndpoint,
|
||||||
UserWorkspaceDashboardEndpoint,
|
UserWorkspaceDashboardEndpoint,
|
||||||
|
UserWorkspaceViewViewSet,
|
||||||
|
UserProjectViewViewSet,
|
||||||
## End Workspaces
|
## End Workspaces
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -95,5 +97,47 @@ urlpatterns = [
|
|||||||
SetUserPasswordEndpoint.as_view(),
|
SetUserPasswordEndpoint.as_view(),
|
||||||
name="set-password",
|
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
|
## End User Graph
|
||||||
]
|
]
|
||||||
|
@ -2,17 +2,16 @@ from django.urls import path
|
|||||||
|
|
||||||
|
|
||||||
from plane.app.views import (
|
from plane.app.views import (
|
||||||
IssueViewViewSet,
|
ProjectViewViewSet,
|
||||||
GlobalViewViewSet,
|
WorkspaceViewViewSet,
|
||||||
GlobalViewIssuesViewSet,
|
ViewFavoriteViewSet,
|
||||||
IssueViewFavoriteViewSet,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/views/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/views/",
|
||||||
IssueViewViewSet.as_view(
|
ProjectViewViewSet.as_view(
|
||||||
{
|
{
|
||||||
"get": "list",
|
"get": "list",
|
||||||
"post": "create",
|
"post": "create",
|
||||||
@ -22,7 +21,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/views/<uuid:pk>/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/views/<uuid:pk>/",
|
||||||
IssueViewViewSet.as_view(
|
ProjectViewViewSet.as_view(
|
||||||
{
|
{
|
||||||
"get": "retrieve",
|
"get": "retrieve",
|
||||||
"put": "update",
|
"put": "update",
|
||||||
@ -34,7 +33,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/views/",
|
"workspaces/<str:slug>/views/",
|
||||||
GlobalViewViewSet.as_view(
|
WorkspaceViewViewSet.as_view(
|
||||||
{
|
{
|
||||||
"get": "list",
|
"get": "list",
|
||||||
"post": "create",
|
"post": "create",
|
||||||
@ -44,10 +43,9 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/views/<uuid:pk>/",
|
"workspaces/<str:slug>/views/<uuid:pk>/",
|
||||||
GlobalViewViewSet.as_view(
|
WorkspaceViewViewSet.as_view(
|
||||||
{
|
{
|
||||||
"get": "retrieve",
|
"get": "retrieve",
|
||||||
"put": "update",
|
|
||||||
"patch": "partial_update",
|
"patch": "partial_update",
|
||||||
"delete": "destroy",
|
"delete": "destroy",
|
||||||
}
|
}
|
||||||
@ -56,7 +54,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/issues/",
|
"workspaces/<str:slug>/issues/",
|
||||||
GlobalViewIssuesViewSet.as_view(
|
WorkspaceViewViewSet.as_view(
|
||||||
{
|
{
|
||||||
"get": "list",
|
"get": "list",
|
||||||
}
|
}
|
||||||
@ -65,7 +63,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/",
|
||||||
IssueViewFavoriteViewSet.as_view(
|
ViewFavoriteViewSet.as_view(
|
||||||
{
|
{
|
||||||
"get": "list",
|
"get": "list",
|
||||||
"post": "create",
|
"post": "create",
|
||||||
@ -75,7 +73,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/<uuid:view_id>/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-views/<uuid:view_id>/",
|
||||||
IssueViewFavoriteViewSet.as_view(
|
ViewFavoriteViewSet.as_view(
|
||||||
{
|
{
|
||||||
"delete": "destroy",
|
"delete": "destroy",
|
||||||
}
|
}
|
||||||
|
@ -52,10 +52,12 @@ from .workspace import (
|
|||||||
)
|
)
|
||||||
from .state import StateViewSet
|
from .state import StateViewSet
|
||||||
from .view import (
|
from .view import (
|
||||||
GlobalViewViewSet,
|
WorkspaceViewViewSet,
|
||||||
GlobalViewIssuesViewSet,
|
ProjectViewViewSet,
|
||||||
IssueViewViewSet,
|
ViewFavoriteViewSet,
|
||||||
IssueViewFavoriteViewSet,
|
UserWorkspaceViewViewSet,
|
||||||
|
UserProjectViewViewSet,
|
||||||
|
ProjectViewViewSet,
|
||||||
)
|
)
|
||||||
from .cycle import (
|
from .cycle import (
|
||||||
CycleViewSet,
|
CycleViewSet,
|
||||||
|
@ -17,7 +17,7 @@ from plane.db.models import (
|
|||||||
Cycle,
|
Cycle,
|
||||||
Module,
|
Module,
|
||||||
Page,
|
Page,
|
||||||
IssueView,
|
View,
|
||||||
)
|
)
|
||||||
from plane.utils.issue_search import search_issues
|
from plane.utils.issue_search import search_issues
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
for field in fields:
|
for field in fields:
|
||||||
q |= Q(**{f"{field}__icontains": query})
|
q |= Q(**{f"{field}__icontains": query})
|
||||||
|
|
||||||
issue_views = IssueView.objects.filter(
|
issue_views = View.objects.filter(
|
||||||
q,
|
q,
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
|
@ -10,68 +10,308 @@ from django.db.models import (
|
|||||||
When,
|
When,
|
||||||
Exists,
|
Exists,
|
||||||
Max,
|
Max,
|
||||||
|
Q,
|
||||||
)
|
)
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.gzip import gzip_page
|
from django.views.decorators.gzip import gzip_page
|
||||||
from django.db.models import Prefetch, OuterRef, Exists
|
|
||||||
|
|
||||||
# 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 . import BaseViewSet, BaseAPIView
|
from . import BaseViewSet
|
||||||
from plane.app.serializers import (
|
from plane.app.serializers import (
|
||||||
GlobalViewSerializer,
|
ViewSerializer,
|
||||||
IssueViewSerializer,
|
|
||||||
IssueSerializer,
|
IssueSerializer,
|
||||||
IssueViewFavoriteSerializer,
|
ViewFavoriteSerializer,
|
||||||
)
|
)
|
||||||
from plane.app.permissions import (
|
from plane.app.permissions import (
|
||||||
WorkspaceEntityPermission,
|
WorkspaceEntityPermission,
|
||||||
ProjectEntityPermission,
|
ProjectEntityPermission,
|
||||||
WorkspaceViewerPermission,
|
|
||||||
ProjectLitePermission,
|
|
||||||
)
|
)
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
Workspace,
|
Workspace,
|
||||||
GlobalView,
|
View,
|
||||||
IssueView,
|
|
||||||
Issue,
|
Issue,
|
||||||
IssueViewFavorite,
|
ViewFavorite,
|
||||||
IssueReaction,
|
|
||||||
IssueLink,
|
IssueLink,
|
||||||
IssueAttachment,
|
IssueAttachment,
|
||||||
IssueSubscriber,
|
|
||||||
)
|
)
|
||||||
from plane.utils.issue_filters import issue_filters
|
from plane.utils.issue_filters import issue_filters
|
||||||
from plane.utils.grouper import group_results
|
|
||||||
|
|
||||||
|
|
||||||
class GlobalViewViewSet(BaseViewSet):
|
class UserWorkspaceViewViewSet(BaseViewSet):
|
||||||
serializer_class = IssueViewSerializer
|
serializer_class = ViewSerializer
|
||||||
model = IssueView
|
model = View
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
WorkspaceEntityPermission,
|
WorkspaceEntityPermission,
|
||||||
]
|
]
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
workspace = Workspace.objects.get(slug=self.kwargs.get("slug"))
|
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):
|
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(
|
return self.filter_queryset(
|
||||||
super()
|
super()
|
||||||
.get_queryset()
|
.get_queryset()
|
||||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
.filter(project__isnull=True)
|
.filter(project__isnull=True)
|
||||||
|
.filter(Q(owned_by=self.request.user) & Q(access=0))
|
||||||
.select_related("workspace")
|
.select_related("workspace")
|
||||||
|
.annotate(is_favorite=Exists(subquery))
|
||||||
.order_by(self.request.GET.get("order_by", "-created_at"))
|
.order_by(self.request.GET.get("order_by", "-created_at"))
|
||||||
.distinct()
|
.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 = [
|
permission_classes = [
|
||||||
WorkspaceEntityPermission,
|
WorkspaceEntityPermission,
|
||||||
]
|
]
|
||||||
@ -87,41 +327,9 @@ class GlobalViewIssuesViewSet(BaseViewSet):
|
|||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
|
.filter(project__project_projectmember__member=self.request.user)
|
||||||
.select_related("workspace", "project", "state", "parent")
|
.select_related("workspace", "project", "state", "parent")
|
||||||
.prefetch_related("assignees", "labels", "issue_module__module")
|
.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(cycle_id=F("issue_cycle__cycle_id"))
|
||||||
.annotate(
|
.annotate(
|
||||||
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
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
|
# Priority Ordering
|
||||||
if order_by_param == "priority" or order_by_param == "-priority":
|
if order_by_param == "priority" or order_by_param == "-priority":
|
||||||
priority_order = (
|
priority_order = (
|
||||||
@ -213,52 +444,9 @@ class GlobalViewIssuesViewSet(BaseViewSet):
|
|||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class IssueViewViewSet(BaseViewSet):
|
class ViewFavoriteViewSet(BaseViewSet):
|
||||||
serializer_class = IssueViewSerializer
|
serializer_class = ViewFavoriteSerializer
|
||||||
model = IssueView
|
model = ViewFavorite
|
||||||
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
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return self.filter_queryset(
|
return self.filter_queryset(
|
||||||
@ -270,18 +458,18 @@ class IssueViewFavoriteViewSet(BaseViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def create(self, request, slug, project_id):
|
def create(self, request, slug, project_id):
|
||||||
serializer = IssueViewFavoriteSerializer(data=request.data)
|
serializer = ViewFavoriteSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save(user=request.user, project_id=project_id)
|
serializer.save(user=request.user, project_id=project_id)
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
def destroy(self, request, slug, project_id, view_id):
|
def destroy(self, request, slug, project_id, view_id):
|
||||||
view_favourite = IssueViewFavorite.objects.get(
|
view_favorite = ViewFavorite.objects.get(
|
||||||
project=project_id,
|
project=project_id,
|
||||||
user=request.user,
|
user=request.user,
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
view_id=view_id,
|
view_id=view_id,
|
||||||
)
|
)
|
||||||
view_favourite.delete()
|
view_favorite.delete()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
# Generated by Django 4.2.7 on 2024-01-02 13:15
|
# 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
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
def workspace_user_properties(apps, schema_editor):
|
def workspace_user_properties(apps, schema_editor):
|
||||||
WorkspaceMember = apps.get_model("db", "WorkspaceMember")
|
WorkspaceMember = apps.get_model("db", "WorkspaceMember")
|
||||||
|
WorkspaceUserProperties = apps.get_model("db", "WorkspaceUserProperties")
|
||||||
updated_workspace_user_properties = []
|
updated_workspace_user_properties = []
|
||||||
for workspace_members in WorkspaceMember.objects.all():
|
for workspace_members in WorkspaceMember.objects.all():
|
||||||
updated_workspace_user_properties.append(
|
updated_workspace_user_properties.append(
|
||||||
@ -49,6 +50,7 @@ def project_user_properties(apps, schema_editor):
|
|||||||
|
|
||||||
def issue_view(apps, schema_editor):
|
def issue_view(apps, schema_editor):
|
||||||
GlobalView = apps.get_model("db", "GlobalView")
|
GlobalView = apps.get_model("db", "GlobalView")
|
||||||
|
IssueView = apps.get_model("db", "IssueView")
|
||||||
updated_issue_views = []
|
updated_issue_views = []
|
||||||
|
|
||||||
for global_view in GlobalView.objects.all():
|
for global_view in GlobalView.objects.all():
|
||||||
|
@ -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)
|
||||||
|
]
|
@ -52,7 +52,7 @@ from .state import State
|
|||||||
|
|
||||||
from .cycle import Cycle, CycleIssue, CycleFavorite, CycleUserProperties
|
from .cycle import Cycle, CycleIssue, CycleFavorite, CycleUserProperties
|
||||||
|
|
||||||
from .view import GlobalView, IssueView, IssueViewFavorite
|
from .view import View, ViewFavorite
|
||||||
|
|
||||||
from .module import (
|
from .module import (
|
||||||
Module,
|
Module,
|
||||||
|
@ -3,7 +3,7 @@ from django.db import models
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
# Module import
|
# Module import
|
||||||
from . import ProjectBaseModel, BaseModel, WorkspaceBaseModel
|
from . import BaseModel, WorkspaceBaseModel
|
||||||
|
|
||||||
|
|
||||||
def get_default_filters():
|
def get_default_filters():
|
||||||
@ -84,7 +84,7 @@ class GlobalView(BaseModel):
|
|||||||
return f"{self.name} <{self.workspace.name}>"
|
return f"{self.name} <{self.workspace.name}>"
|
||||||
|
|
||||||
|
|
||||||
class IssueView(WorkspaceBaseModel):
|
class View(WorkspaceBaseModel):
|
||||||
name = models.CharField(max_length=255, verbose_name="View Name")
|
name = models.CharField(max_length=255, verbose_name="View Name")
|
||||||
description = models.TextField(verbose_name="View Description", blank=True)
|
description = models.TextField(verbose_name="View Description", blank=True)
|
||||||
query = models.JSONField(verbose_name="View Query")
|
query = models.JSONField(verbose_name="View Query")
|
||||||
@ -94,29 +94,44 @@ class IssueView(WorkspaceBaseModel):
|
|||||||
default=get_default_display_properties
|
default=get_default_display_properties
|
||||||
)
|
)
|
||||||
access = models.PositiveSmallIntegerField(
|
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)
|
sort_order = models.FloatField(default=65535)
|
||||||
|
is_locked = models.BooleanField(default=False)
|
||||||
|
is_pinned = models.BooleanField(default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Issue View"
|
verbose_name = "View"
|
||||||
verbose_name_plural = "Issue Views"
|
verbose_name_plural = "Views"
|
||||||
db_table = "issue_views"
|
db_table = "views"
|
||||||
ordering = ("-created_at",)
|
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):
|
def __str__(self):
|
||||||
"""Return name of the View"""
|
"""Return name of the View"""
|
||||||
return f"{self.name} <{self.project.name}>"
|
return f"{self.name} <{self.project.name}>"
|
||||||
|
|
||||||
|
|
||||||
class IssueViewFavorite(ProjectBaseModel):
|
class ViewFavorite(WorkspaceBaseModel):
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="user_view_favorites",
|
related_name="user_view_favorites",
|
||||||
)
|
)
|
||||||
view = models.ForeignKey(
|
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:
|
class Meta:
|
||||||
|
@ -326,7 +326,7 @@ class WorkspaceUserProperties(BaseModel):
|
|||||||
unique_together = ["workspace", "user"]
|
unique_together = ["workspace", "user"]
|
||||||
verbose_name = "Workspace User Property"
|
verbose_name = "Workspace User Property"
|
||||||
verbose_name_plural = "Workspace User Property"
|
verbose_name_plural = "Workspace User Property"
|
||||||
db_table = "Workspace_user_properties"
|
db_table = "workspace_user_properties"
|
||||||
ordering = ("-created_at",)
|
ordering = ("-created_at",)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -282,10 +282,8 @@ if REDIS_SSL:
|
|||||||
redis_url = os.environ.get("REDIS_URL")
|
redis_url = os.environ.get("REDIS_URL")
|
||||||
broker_url = f"{redis_url}?ssl_cert_reqs={ssl.CERT_NONE.name}&ssl_ca_certs={certifi.where()}"
|
broker_url = f"{redis_url}?ssl_cert_reqs={ssl.CERT_NONE.name}&ssl_ca_certs={certifi.where()}"
|
||||||
CELERY_BROKER_URL = broker_url
|
CELERY_BROKER_URL = broker_url
|
||||||
CELERY_RESULT_BACKEND = broker_url
|
|
||||||
else:
|
else:
|
||||||
CELERY_BROKER_URL = REDIS_URL
|
CELERY_BROKER_URL = REDIS_URL
|
||||||
CELERY_RESULT_BACKEND = REDIS_URL
|
|
||||||
|
|
||||||
CELERY_IMPORTS = (
|
CELERY_IMPORTS = (
|
||||||
"plane.bgtasks.issue_automation_task",
|
"plane.bgtasks.issue_automation_task",
|
||||||
|
Loading…
Reference in New Issue
Block a user