chore: issue version migrations updates
This commit is contained in:
parent
d2758fe5e6
commit
0d70397639
14 changed files with 473 additions and 281 deletions
|
|
@ -13,7 +13,6 @@ from .user import (
|
||||||
from .workspace import (
|
from .workspace import (
|
||||||
WorkSpaceSerializer,
|
WorkSpaceSerializer,
|
||||||
WorkSpaceMemberSerializer,
|
WorkSpaceMemberSerializer,
|
||||||
TeamSerializer,
|
|
||||||
WorkSpaceMemberInviteSerializer,
|
WorkSpaceMemberInviteSerializer,
|
||||||
WorkspaceLiteSerializer,
|
WorkspaceLiteSerializer,
|
||||||
WorkspaceThemeSerializer,
|
WorkspaceThemeSerializer,
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,8 @@ from .base import BaseSerializer, DynamicBaseSerializer
|
||||||
from .user import UserLiteSerializer, UserAdminLiteSerializer
|
from .user import UserLiteSerializer, UserAdminLiteSerializer
|
||||||
|
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
User,
|
|
||||||
Workspace,
|
Workspace,
|
||||||
WorkspaceMember,
|
WorkspaceMember,
|
||||||
Team,
|
|
||||||
TeamMember,
|
|
||||||
WorkspaceMemberInvite,
|
WorkspaceMemberInvite,
|
||||||
WorkspaceTheme,
|
WorkspaceTheme,
|
||||||
WorkspaceUserProperties,
|
WorkspaceUserProperties,
|
||||||
|
|
@ -97,52 +94,6 @@ class WorkSpaceMemberInviteSerializer(BaseSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class TeamSerializer(BaseSerializer):
|
|
||||||
members_detail = UserLiteSerializer(read_only=True, source="members", many=True)
|
|
||||||
members = serializers.ListField(
|
|
||||||
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
|
|
||||||
write_only=True,
|
|
||||||
required=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Team
|
|
||||||
fields = "__all__"
|
|
||||||
read_only_fields = [
|
|
||||||
"workspace",
|
|
||||||
"created_by",
|
|
||||||
"updated_by",
|
|
||||||
"created_at",
|
|
||||||
"updated_at",
|
|
||||||
]
|
|
||||||
|
|
||||||
def create(self, validated_data, **kwargs):
|
|
||||||
if "members" in validated_data:
|
|
||||||
members = validated_data.pop("members")
|
|
||||||
workspace = self.context["workspace"]
|
|
||||||
team = Team.objects.create(**validated_data, workspace=workspace)
|
|
||||||
team_members = [
|
|
||||||
TeamMember(member=member, team=team, workspace=workspace)
|
|
||||||
for member in members
|
|
||||||
]
|
|
||||||
TeamMember.objects.bulk_create(team_members, batch_size=10)
|
|
||||||
return team
|
|
||||||
team = Team.objects.create(**validated_data)
|
|
||||||
return team
|
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
|
||||||
if "members" in validated_data:
|
|
||||||
members = validated_data.pop("members")
|
|
||||||
TeamMember.objects.filter(team=instance).delete()
|
|
||||||
team_members = [
|
|
||||||
TeamMember(member=member, team=instance, workspace=instance.workspace)
|
|
||||||
for member in members
|
|
||||||
]
|
|
||||||
TeamMember.objects.bulk_create(team_members, batch_size=10)
|
|
||||||
return super().update(instance, validated_data)
|
|
||||||
return super().update(instance, validated_data)
|
|
||||||
|
|
||||||
|
|
||||||
class WorkspaceThemeSerializer(BaseSerializer):
|
class WorkspaceThemeSerializer(BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WorkspaceTheme
|
model = WorkspaceTheme
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ from plane.app.views import (
|
||||||
ProjectMemberViewSet,
|
ProjectMemberViewSet,
|
||||||
ProjectMemberUserEndpoint,
|
ProjectMemberUserEndpoint,
|
||||||
ProjectJoinEndpoint,
|
ProjectJoinEndpoint,
|
||||||
AddTeamToProjectEndpoint,
|
|
||||||
ProjectUserViewsEndpoint,
|
ProjectUserViewsEndpoint,
|
||||||
ProjectIdentifierEndpoint,
|
ProjectIdentifierEndpoint,
|
||||||
ProjectFavoritesViewSet,
|
ProjectFavoritesViewSet,
|
||||||
|
|
@ -83,11 +82,6 @@ urlpatterns = [
|
||||||
ProjectMemberViewSet.as_view({"post": "leave"}),
|
ProjectMemberViewSet.as_view({"post": "leave"}),
|
||||||
name="project-member",
|
name="project-member",
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/team-invite/",
|
|
||||||
AddTeamToProjectEndpoint.as_view(),
|
|
||||||
name="projects",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/project-views/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/project-views/",
|
||||||
ProjectUserViewsEndpoint.as_view(),
|
ProjectUserViewsEndpoint.as_view(),
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ from plane.app.views import (
|
||||||
WorkspaceMemberUserEndpoint,
|
WorkspaceMemberUserEndpoint,
|
||||||
WorkspaceMemberUserViewsEndpoint,
|
WorkspaceMemberUserViewsEndpoint,
|
||||||
WorkSpaceAvailabilityCheckEndpoint,
|
WorkSpaceAvailabilityCheckEndpoint,
|
||||||
TeamMemberViewSet,
|
|
||||||
UserLastProjectWithWorkspaceEndpoint,
|
UserLastProjectWithWorkspaceEndpoint,
|
||||||
WorkspaceThemeViewSet,
|
WorkspaceThemeViewSet,
|
||||||
WorkspaceUserProfileStatsEndpoint,
|
WorkspaceUserProfileStatsEndpoint,
|
||||||
|
|
@ -69,7 +68,9 @@ urlpatterns = [
|
||||||
# user workspace invitations
|
# user workspace invitations
|
||||||
path(
|
path(
|
||||||
"users/me/workspaces/invitations/",
|
"users/me/workspaces/invitations/",
|
||||||
UserWorkspaceInvitationsViewSet.as_view({"get": "list", "post": "create"}),
|
UserWorkspaceInvitationsViewSet.as_view(
|
||||||
|
{"get": "list", "post": "create"}
|
||||||
|
),
|
||||||
name="user-workspace-invitations",
|
name="user-workspace-invitations",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
|
|
@ -100,23 +101,6 @@ urlpatterns = [
|
||||||
WorkSpaceMemberViewSet.as_view({"post": "leave"}),
|
WorkSpaceMemberViewSet.as_view({"post": "leave"}),
|
||||||
name="leave-workspace-members",
|
name="leave-workspace-members",
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
"workspaces/<str:slug>/teams/",
|
|
||||||
TeamMemberViewSet.as_view({"get": "list", "post": "create"}),
|
|
||||||
name="workspace-team-members",
|
|
||||||
),
|
|
||||||
path(
|
|
||||||
"workspaces/<str:slug>/teams/<uuid:pk>/",
|
|
||||||
TeamMemberViewSet.as_view(
|
|
||||||
{
|
|
||||||
"put": "update",
|
|
||||||
"patch": "partial_update",
|
|
||||||
"delete": "destroy",
|
|
||||||
"get": "retrieve",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
name="workspace-team-members",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"users/last-visited-workspace/",
|
"users/last-visited-workspace/",
|
||||||
UserLastProjectWithWorkspaceEndpoint.as_view(),
|
UserLastProjectWithWorkspaceEndpoint.as_view(),
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ from .project.invite import (
|
||||||
|
|
||||||
from .project.member import (
|
from .project.member import (
|
||||||
ProjectMemberViewSet,
|
ProjectMemberViewSet,
|
||||||
AddTeamToProjectEndpoint,
|
|
||||||
ProjectMemberUserEndpoint,
|
ProjectMemberUserEndpoint,
|
||||||
UserProjectRolesEndpoint,
|
UserProjectRolesEndpoint,
|
||||||
)
|
)
|
||||||
|
|
@ -49,7 +48,6 @@ from .workspace.favorite import (
|
||||||
|
|
||||||
from .workspace.member import (
|
from .workspace.member import (
|
||||||
WorkSpaceMemberViewSet,
|
WorkSpaceMemberViewSet,
|
||||||
TeamMemberViewSet,
|
|
||||||
WorkspaceMemberUserEndpoint,
|
WorkspaceMemberUserEndpoint,
|
||||||
WorkspaceProjectMemberEndpoint,
|
WorkspaceProjectMemberEndpoint,
|
||||||
WorkspaceMemberUserViewsEndpoint,
|
WorkspaceMemberUserViewsEndpoint,
|
||||||
|
|
@ -88,8 +86,6 @@ from .cycle.base import (
|
||||||
CycleFavoriteViewSet,
|
CycleFavoriteViewSet,
|
||||||
TransferCycleIssueEndpoint,
|
TransferCycleIssueEndpoint,
|
||||||
CycleUserPropertiesEndpoint,
|
CycleUserPropertiesEndpoint,
|
||||||
CycleViewSet,
|
|
||||||
TransferCycleIssueEndpoint,
|
|
||||||
CycleAnalyticsEndpoint,
|
CycleAnalyticsEndpoint,
|
||||||
CycleProgressEndpoint,
|
CycleProgressEndpoint,
|
||||||
)
|
)
|
||||||
|
|
@ -206,6 +202,5 @@ from .dashboard.base import DashboardEndpoint, WidgetsEndpoint
|
||||||
|
|
||||||
from .error_404 import custom_404_view
|
from .error_404 import custom_404_view
|
||||||
|
|
||||||
from .exporter.base import ExportIssuesEndpoint
|
|
||||||
from .notification.base import MarkAllReadNotificationViewSet
|
from .notification.base import MarkAllReadNotificationViewSet
|
||||||
from .user.base import AccountEndpoint, ProfileEndpoint, UserSessionEndpoint
|
from .user.base import AccountEndpoint, ProfileEndpoint, UserSessionEndpoint
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ from plane.app.serializers import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from plane.app.permissions import (
|
from plane.app.permissions import (
|
||||||
ProjectBasePermission,
|
|
||||||
ProjectMemberPermission,
|
ProjectMemberPermission,
|
||||||
ProjectLitePermission,
|
ProjectLitePermission,
|
||||||
WorkspaceUserPermission,
|
WorkspaceUserPermission,
|
||||||
|
|
@ -20,8 +19,6 @@ from plane.app.permissions import (
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
Project,
|
Project,
|
||||||
ProjectMember,
|
ProjectMember,
|
||||||
Workspace,
|
|
||||||
TeamMember,
|
|
||||||
IssueUserProperty,
|
IssueUserProperty,
|
||||||
WorkspaceMember,
|
WorkspaceMember,
|
||||||
)
|
)
|
||||||
|
|
@ -86,7 +83,10 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||||
workspace_member_role = WorkspaceMember.objects.get(
|
workspace_member_role = WorkspaceMember.objects.get(
|
||||||
workspace__slug=slug, member=member, is_active=True
|
workspace__slug=slug, member=member, is_active=True
|
||||||
).role
|
).role
|
||||||
if workspace_member_role in [20] and member_roles.get(member) in [5, 15]:
|
if workspace_member_role in [20] and member_roles.get(member) in [
|
||||||
|
5,
|
||||||
|
15,
|
||||||
|
]:
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"error": "You cannot add a user with role lower than the workspace role"
|
"error": "You cannot add a user with role lower than the workspace role"
|
||||||
|
|
@ -94,7 +94,10 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
if workspace_member_role in [5] and member_roles.get(member) in [15, 20]:
|
if workspace_member_role in [5] and member_roles.get(member) in [
|
||||||
|
15,
|
||||||
|
20,
|
||||||
|
]:
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"error": "You cannot add a user with role higher than the workspace role"
|
"error": "You cannot add a user with role higher than the workspace role"
|
||||||
|
|
@ -132,7 +135,8 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||||
sort_order = [
|
sort_order = [
|
||||||
project_member.get("sort_order")
|
project_member.get("sort_order")
|
||||||
for project_member in project_members
|
for project_member in project_members
|
||||||
if str(project_member.get("member_id")) == str(member.get("member_id"))
|
if str(project_member.get("member_id"))
|
||||||
|
== str(member.get("member_id"))
|
||||||
]
|
]
|
||||||
# Create a new project member
|
# Create a new project member
|
||||||
bulk_project_members.append(
|
bulk_project_members.append(
|
||||||
|
|
@ -141,7 +145,9 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||||
role=member.get("role", 5),
|
role=member.get("role", 5),
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
workspace_id=project.workspace_id,
|
workspace_id=project.workspace_id,
|
||||||
sort_order=(sort_order[0] - 10000 if len(sort_order) else 65535),
|
sort_order=(
|
||||||
|
sort_order[0] - 10000 if len(sort_order) else 65535
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Create a new issue property
|
# Create a new issue property
|
||||||
|
|
@ -232,7 +238,9 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||||
> requested_project_member.role
|
> requested_project_member.role
|
||||||
):
|
):
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "You cannot update a role that is higher than your own role"},
|
{
|
||||||
|
"error": "You cannot update a role that is higher than your own role"
|
||||||
|
},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -272,7 +280,9 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||||
# User cannot deactivate higher role
|
# User cannot deactivate higher role
|
||||||
if requesting_project_member.role < project_member.role:
|
if requesting_project_member.role < project_member.role:
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "You cannot remove a user having role higher than you"},
|
{
|
||||||
|
"error": "You cannot remove a user having role higher than you"
|
||||||
|
},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -293,7 +303,10 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||||
if (
|
if (
|
||||||
project_member.role == 20
|
project_member.role == 20
|
||||||
and not ProjectMember.objects.filter(
|
and not ProjectMember.objects.filter(
|
||||||
workspace__slug=slug, project_id=project_id, role=20, is_active=True
|
workspace__slug=slug,
|
||||||
|
project_id=project_id,
|
||||||
|
role=20,
|
||||||
|
is_active=True,
|
||||||
).count()
|
).count()
|
||||||
> 1
|
> 1
|
||||||
):
|
):
|
||||||
|
|
@ -309,53 +322,6 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
class AddTeamToProjectEndpoint(BaseAPIView):
|
|
||||||
permission_classes = [ProjectBasePermission]
|
|
||||||
|
|
||||||
def post(self, request, slug, project_id):
|
|
||||||
team_members = TeamMember.objects.filter(
|
|
||||||
workspace__slug=slug, team__in=request.data.get("teams", [])
|
|
||||||
).values_list("member", flat=True)
|
|
||||||
|
|
||||||
if len(team_members) == 0:
|
|
||||||
return Response(
|
|
||||||
{"error": "No such team exists"}, status=status.HTTP_400_BAD_REQUEST
|
|
||||||
)
|
|
||||||
|
|
||||||
workspace = Workspace.objects.get(slug=slug)
|
|
||||||
|
|
||||||
project_members = []
|
|
||||||
issue_props = []
|
|
||||||
for member in team_members:
|
|
||||||
project_members.append(
|
|
||||||
ProjectMember(
|
|
||||||
project_id=project_id,
|
|
||||||
member_id=member,
|
|
||||||
workspace=workspace,
|
|
||||||
created_by=request.user,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
issue_props.append(
|
|
||||||
IssueUserProperty(
|
|
||||||
project_id=project_id,
|
|
||||||
user_id=member,
|
|
||||||
workspace=workspace,
|
|
||||||
created_by=request.user,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
ProjectMember.objects.bulk_create(
|
|
||||||
project_members, batch_size=10, ignore_conflicts=True
|
|
||||||
)
|
|
||||||
|
|
||||||
_ = IssueUserProperty.objects.bulk_create(
|
|
||||||
issue_props, batch_size=10, ignore_conflicts=True
|
|
||||||
)
|
|
||||||
|
|
||||||
serializer = ProjectMemberSerializer(project_members, many=True)
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectMemberUserEndpoint(BaseAPIView):
|
class ProjectMemberUserEndpoint(BaseAPIView):
|
||||||
def get(self, request, slug, project_id):
|
def get(self, request, slug, project_id):
|
||||||
project_member = ProjectMember.objects.get(
|
project_member = ProjectMember.objects.get(
|
||||||
|
|
@ -378,6 +344,7 @@ class UserProjectRolesEndpoint(BaseAPIView):
|
||||||
).values("project_id", "role")
|
).values("project_id", "role")
|
||||||
|
|
||||||
project_members = {
|
project_members = {
|
||||||
str(member["project_id"]): member["role"] for member in project_members
|
str(member["project_id"]): member["role"]
|
||||||
|
for member in project_members
|
||||||
}
|
}
|
||||||
return Response(project_members, status=status.HTTP_200_OK)
|
return Response(project_members, status=status.HTTP_200_OK)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
# Django imports
|
# Django imports
|
||||||
from django.db.models import CharField, Count, Q, OuterRef, Subquery, IntegerField
|
from django.db.models import (
|
||||||
|
Count,
|
||||||
|
Q,
|
||||||
|
OuterRef,
|
||||||
|
Subquery,
|
||||||
|
IntegerField,
|
||||||
|
)
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.db.models.functions import Cast
|
|
||||||
|
|
||||||
# Third party modules
|
# Third party modules
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from plane.app.permissions import (
|
from plane.app.permissions import (
|
||||||
WorkSpaceAdminPermission,
|
|
||||||
WorkspaceEntityPermission,
|
WorkspaceEntityPermission,
|
||||||
allow_permission,
|
allow_permission,
|
||||||
ROLE,
|
ROLE,
|
||||||
|
|
@ -17,8 +21,6 @@ from plane.app.permissions import (
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.app.serializers import (
|
from plane.app.serializers import (
|
||||||
ProjectMemberRoleSerializer,
|
ProjectMemberRoleSerializer,
|
||||||
TeamSerializer,
|
|
||||||
UserLiteSerializer,
|
|
||||||
WorkspaceMemberAdminSerializer,
|
WorkspaceMemberAdminSerializer,
|
||||||
WorkspaceMemberMeSerializer,
|
WorkspaceMemberMeSerializer,
|
||||||
WorkSpaceMemberSerializer,
|
WorkSpaceMemberSerializer,
|
||||||
|
|
@ -27,9 +29,6 @@ from plane.app.views.base import BaseAPIView
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
Project,
|
Project,
|
||||||
ProjectMember,
|
ProjectMember,
|
||||||
Team,
|
|
||||||
User,
|
|
||||||
Workspace,
|
|
||||||
WorkspaceMember,
|
WorkspaceMember,
|
||||||
DraftIssue,
|
DraftIssue,
|
||||||
)
|
)
|
||||||
|
|
@ -120,7 +119,9 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||||
|
|
||||||
if requesting_workspace_member.role < workspace_member.role:
|
if requesting_workspace_member.role < workspace_member.role:
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "You cannot remove a user having role higher than you"},
|
{
|
||||||
|
"error": "You cannot remove a user having role higher than you"
|
||||||
|
},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -147,7 +148,9 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||||
|
|
||||||
# Deactivate the users from the projects where the user is part of
|
# Deactivate the users from the projects where the user is part of
|
||||||
_ = ProjectMember.objects.filter(
|
_ = ProjectMember.objects.filter(
|
||||||
workspace__slug=slug, member_id=workspace_member.member_id, is_active=True
|
workspace__slug=slug,
|
||||||
|
member_id=workspace_member.member_id,
|
||||||
|
is_active=True,
|
||||||
).update(is_active=False)
|
).update(is_active=False)
|
||||||
|
|
||||||
workspace_member.is_active = False
|
workspace_member.is_active = False
|
||||||
|
|
@ -161,7 +164,9 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||||
multiple=True,
|
multiple=True,
|
||||||
)
|
)
|
||||||
@invalidate_cache(path="/api/users/me/settings/")
|
@invalidate_cache(path="/api/users/me/settings/")
|
||||||
@invalidate_cache(path="api/users/me/workspaces/", user=False, multiple=True)
|
@invalidate_cache(
|
||||||
|
path="api/users/me/workspaces/", user=False, multiple=True
|
||||||
|
)
|
||||||
@allow_permission(
|
@allow_permission(
|
||||||
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
|
||||||
)
|
)
|
||||||
|
|
@ -208,7 +213,9 @@ class WorkSpaceMemberViewSet(BaseViewSet):
|
||||||
|
|
||||||
# # Deactivate the users from the projects where the user is part of
|
# # Deactivate the users from the projects where the user is part of
|
||||||
_ = ProjectMember.objects.filter(
|
_ = ProjectMember.objects.filter(
|
||||||
workspace__slug=slug, member_id=workspace_member.member_id, is_active=True
|
workspace__slug=slug,
|
||||||
|
member_id=workspace_member.member_id,
|
||||||
|
is_active=True,
|
||||||
).update(is_active=False)
|
).update(is_active=False)
|
||||||
|
|
||||||
# # Deactivate the user
|
# # Deactivate the user
|
||||||
|
|
@ -272,7 +279,9 @@ class WorkspaceProjectMemberEndpoint(BaseAPIView):
|
||||||
project_members = ProjectMember.objects.filter(
|
project_members = ProjectMember.objects.filter(
|
||||||
workspace__slug=slug, project_id__in=project_ids, is_active=True
|
workspace__slug=slug, project_id__in=project_ids, is_active=True
|
||||||
).select_related("project", "member", "workspace")
|
).select_related("project", "member", "workspace")
|
||||||
project_members = ProjectMemberRoleSerializer(project_members, many=True).data
|
project_members = ProjectMemberRoleSerializer(
|
||||||
|
project_members, many=True
|
||||||
|
).data
|
||||||
|
|
||||||
project_members_dict = dict()
|
project_members_dict = dict()
|
||||||
|
|
||||||
|
|
@ -284,53 +293,3 @@ class WorkspaceProjectMemberEndpoint(BaseAPIView):
|
||||||
project_members_dict[str(project_id)].append(project_member)
|
project_members_dict[str(project_id)].append(project_member)
|
||||||
|
|
||||||
return Response(project_members_dict, status=status.HTTP_200_OK)
|
return Response(project_members_dict, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class TeamMemberViewSet(BaseViewSet):
|
|
||||||
serializer_class = TeamSerializer
|
|
||||||
model = Team
|
|
||||||
permission_classes = [WorkSpaceAdminPermission]
|
|
||||||
|
|
||||||
search_fields = ["member__display_name", "member__first_name"]
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
return self.filter_queryset(
|
|
||||||
super()
|
|
||||||
.get_queryset()
|
|
||||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
|
||||||
.select_related("workspace", "workspace__owner")
|
|
||||||
.prefetch_related("members")
|
|
||||||
)
|
|
||||||
|
|
||||||
def create(self, request, slug):
|
|
||||||
members = list(
|
|
||||||
WorkspaceMember.objects.filter(
|
|
||||||
workspace__slug=slug,
|
|
||||||
member__id__in=request.data.get("members", []),
|
|
||||||
is_active=True,
|
|
||||||
)
|
|
||||||
.annotate(member_str_id=Cast("member", output_field=CharField()))
|
|
||||||
.distinct()
|
|
||||||
.values_list("member_str_id", flat=True)
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(members) != len(request.data.get("members", [])):
|
|
||||||
users = list(set(request.data.get("members", [])).difference(members))
|
|
||||||
users = User.objects.filter(pk__in=users)
|
|
||||||
|
|
||||||
serializer = UserLiteSerializer(users, many=True)
|
|
||||||
return Response(
|
|
||||||
{
|
|
||||||
"error": f"{len(users)} of the member(s) are not a part of the workspace",
|
|
||||||
"members": serializer.data,
|
|
||||||
},
|
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
|
||||||
|
|
||||||
workspace = Workspace.objects.get(slug=slug)
|
|
||||||
|
|
||||||
serializer = TeamSerializer(data=request.data, context={"workspace": workspace})
|
|
||||||
if serializer.is_valid():
|
|
||||||
serializer.save()
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,242 @@
|
||||||
|
# Generated by Django 4.2.15 on 2024-11-27 09:07
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import plane.db.models.webhook
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("db", "0085_intake_intakeissue_remove_inboxissue_created_by_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="IssueVersion",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"created_at",
|
||||||
|
models.DateTimeField(auto_now_add=True, verbose_name="Created At"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"updated_at",
|
||||||
|
models.DateTimeField(
|
||||||
|
auto_now=True, verbose_name="Last Modified At"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"deleted_at",
|
||||||
|
models.DateTimeField(
|
||||||
|
blank=True, null=True, verbose_name="Deleted At"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.UUIDField(
|
||||||
|
db_index=True,
|
||||||
|
default=uuid.uuid4,
|
||||||
|
editable=False,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
unique=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("parent", models.UUIDField(blank=True, null=True)),
|
||||||
|
("state", models.UUIDField(blank=True, null=True)),
|
||||||
|
("estimate_point", models.UUIDField(blank=True, null=True)),
|
||||||
|
("name", models.CharField(max_length=255, verbose_name="Issue Name")),
|
||||||
|
("description", models.JSONField(blank=True, default=dict)),
|
||||||
|
("description_html", models.TextField(blank=True, default="<p></p>")),
|
||||||
|
("description_stripped", models.TextField(blank=True, null=True)),
|
||||||
|
("description_binary", models.BinaryField(null=True)),
|
||||||
|
(
|
||||||
|
"priority",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("urgent", "Urgent"),
|
||||||
|
("high", "High"),
|
||||||
|
("medium", "Medium"),
|
||||||
|
("low", "Low"),
|
||||||
|
("none", "None"),
|
||||||
|
],
|
||||||
|
default="none",
|
||||||
|
max_length=30,
|
||||||
|
verbose_name="Issue Priority",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("start_date", models.DateField(blank=True, null=True)),
|
||||||
|
("target_date", models.DateField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"sequence_id",
|
||||||
|
models.IntegerField(default=1, verbose_name="Issue Sequence ID"),
|
||||||
|
),
|
||||||
|
("sort_order", models.FloatField(default=65535)),
|
||||||
|
("completed_at", models.DateTimeField(null=True)),
|
||||||
|
("archived_at", models.DateField(null=True)),
|
||||||
|
("is_draft", models.BooleanField(default=False)),
|
||||||
|
(
|
||||||
|
"external_source",
|
||||||
|
models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"external_id",
|
||||||
|
models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
("type", models.UUIDField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"last_saved_at",
|
||||||
|
models.DateTimeField(default=django.utils.timezone.now),
|
||||||
|
),
|
||||||
|
("owned_by", models.UUIDField()),
|
||||||
|
(
|
||||||
|
"assignees",
|
||||||
|
django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.UUIDField(),
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
size=None,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"labels",
|
||||||
|
django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.UUIDField(),
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
size=None,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("cycle", models.UUIDField(blank=True, null=True)),
|
||||||
|
(
|
||||||
|
"modules",
|
||||||
|
django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.UUIDField(),
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
size=None,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("properties", models.JSONField(default=dict)),
|
||||||
|
("meta", models.JSONField(default=dict)),
|
||||||
|
(
|
||||||
|
"created_by",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="%(class)s_created_by",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="Created By",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"issue",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="versions",
|
||||||
|
to="db.issue",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"project",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="project_%(class)s",
|
||||||
|
to="db.project",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"updated_by",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="%(class)s_updated_by",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="Last Modified By",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"workspace",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="workspace_%(class)s",
|
||||||
|
to="db.workspace",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Issue Version",
|
||||||
|
"verbose_name_plural": "Issue Versions",
|
||||||
|
"db_table": "issue_versions",
|
||||||
|
"ordering": ("-created_at",),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="teampage",
|
||||||
|
unique_together=None,
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="teampage",
|
||||||
|
name="created_by",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="teampage",
|
||||||
|
name="page",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="teampage",
|
||||||
|
name="team",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="teampage",
|
||||||
|
name="updated_by",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="teampage",
|
||||||
|
name="workspace",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="page",
|
||||||
|
name="teams",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="team",
|
||||||
|
name="members",
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="fileasset",
|
||||||
|
name="entity_identifier",
|
||||||
|
field=models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="webhook",
|
||||||
|
name="is_internal",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="fileasset",
|
||||||
|
name="entity_type",
|
||||||
|
field=models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="webhook",
|
||||||
|
name="url",
|
||||||
|
field=models.URLField(
|
||||||
|
max_length=1024,
|
||||||
|
validators=[
|
||||||
|
plane.db.models.webhook.validate_schema,
|
||||||
|
plane.db.models.webhook.validate_domain,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name="TeamMember",
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name="TeamPage",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -61,8 +61,6 @@ from .user import Account, Profile, User
|
||||||
from .view import IssueView
|
from .view import IssueView
|
||||||
from .webhook import Webhook, WebhookLog
|
from .webhook import Webhook, WebhookLog
|
||||||
from .workspace import (
|
from .workspace import (
|
||||||
Team,
|
|
||||||
TeamMember,
|
|
||||||
Workspace,
|
Workspace,
|
||||||
WorkspaceBaseModel,
|
WorkspaceBaseModel,
|
||||||
WorkspaceMember,
|
WorkspaceMember,
|
||||||
|
|
|
||||||
|
|
@ -44,25 +44,44 @@ class FileAsset(BaseModel):
|
||||||
"db.User", on_delete=models.CASCADE, null=True, related_name="assets"
|
"db.User", on_delete=models.CASCADE, null=True, related_name="assets"
|
||||||
)
|
)
|
||||||
workspace = models.ForeignKey(
|
workspace = models.ForeignKey(
|
||||||
"db.Workspace", on_delete=models.CASCADE, null=True, related_name="assets"
|
"db.Workspace",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
related_name="assets",
|
||||||
)
|
)
|
||||||
draft_issue = models.ForeignKey(
|
draft_issue = models.ForeignKey(
|
||||||
"db.DraftIssue", on_delete=models.CASCADE, null=True, related_name="assets"
|
"db.DraftIssue",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
related_name="assets",
|
||||||
)
|
)
|
||||||
project = models.ForeignKey(
|
project = models.ForeignKey(
|
||||||
"db.Project", on_delete=models.CASCADE, null=True, related_name="assets"
|
"db.Project",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
related_name="assets",
|
||||||
)
|
)
|
||||||
issue = models.ForeignKey(
|
issue = models.ForeignKey(
|
||||||
"db.Issue", on_delete=models.CASCADE, null=True, related_name="assets"
|
"db.Issue", on_delete=models.CASCADE, null=True, related_name="assets"
|
||||||
)
|
)
|
||||||
comment = models.ForeignKey(
|
comment = models.ForeignKey(
|
||||||
"db.IssueComment", on_delete=models.CASCADE, null=True, related_name="assets"
|
"db.IssueComment",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
related_name="assets",
|
||||||
)
|
)
|
||||||
page = models.ForeignKey(
|
page = models.ForeignKey(
|
||||||
"db.Page", on_delete=models.CASCADE, null=True, related_name="assets"
|
"db.Page", on_delete=models.CASCADE, null=True, related_name="assets"
|
||||||
)
|
)
|
||||||
entity_type = models.CharField(
|
entity_type = models.CharField(
|
||||||
max_length=255, choices=EntityTypeContext.choices, null=True, blank=True
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
entity_identifier = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
is_deleted = models.BooleanField(default=False)
|
is_deleted = models.BooleanField(default=False)
|
||||||
is_archived = models.BooleanField(default=False)
|
is_archived = models.BooleanField(default=False)
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,12 @@ from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django import apps
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.utils.html_processor import strip_tags
|
from plane.utils.html_processor import strip_tags
|
||||||
from plane.db.mixins import SoftDeletionManager
|
from plane.db.mixins import SoftDeletionManager
|
||||||
|
from plane.utils.exception_logger import log_exception
|
||||||
from .project import ProjectBaseModel
|
from .project import ProjectBaseModel
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -656,3 +657,126 @@ class IssueVote(ProjectBaseModel):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.issue.name} {self.actor.email}"
|
return f"{self.issue.name} {self.actor.email}"
|
||||||
|
|
||||||
|
|
||||||
|
class IssueVersion(ProjectBaseModel):
|
||||||
|
issue = models.ForeignKey(
|
||||||
|
"db.Issue",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="versions",
|
||||||
|
)
|
||||||
|
PRIORITY_CHOICES = (
|
||||||
|
("urgent", "Urgent"),
|
||||||
|
("high", "High"),
|
||||||
|
("medium", "Medium"),
|
||||||
|
("low", "Low"),
|
||||||
|
("none", "None"),
|
||||||
|
)
|
||||||
|
parent = models.UUIDField(blank=True, null=True)
|
||||||
|
state = models.UUIDField(blank=True, null=True)
|
||||||
|
estimate_point = models.UUIDField(blank=True, null=True)
|
||||||
|
name = models.CharField(max_length=255, verbose_name="Issue Name")
|
||||||
|
description = models.JSONField(blank=True, default=dict)
|
||||||
|
description_html = models.TextField(blank=True, default="<p></p>")
|
||||||
|
description_stripped = models.TextField(blank=True, null=True)
|
||||||
|
description_binary = models.BinaryField(null=True)
|
||||||
|
priority = models.CharField(
|
||||||
|
max_length=30,
|
||||||
|
choices=PRIORITY_CHOICES,
|
||||||
|
verbose_name="Issue Priority",
|
||||||
|
default="none",
|
||||||
|
)
|
||||||
|
start_date = models.DateField(null=True, blank=True)
|
||||||
|
target_date = models.DateField(null=True, blank=True)
|
||||||
|
sequence_id = models.IntegerField(
|
||||||
|
default=1, verbose_name="Issue Sequence ID"
|
||||||
|
)
|
||||||
|
sort_order = models.FloatField(default=65535)
|
||||||
|
completed_at = models.DateTimeField(null=True)
|
||||||
|
archived_at = models.DateField(null=True)
|
||||||
|
is_draft = models.BooleanField(default=False)
|
||||||
|
external_source = models.CharField(max_length=255, null=True, blank=True)
|
||||||
|
external_id = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
type = models.UUIDField(blank=True, null=True)
|
||||||
|
last_saved_at = models.DateTimeField(default=timezone.now)
|
||||||
|
owned_by = models.UUIDField()
|
||||||
|
assignees = ArrayField(
|
||||||
|
models.UUIDField(),
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
)
|
||||||
|
labels = ArrayField(
|
||||||
|
models.UUIDField(),
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
)
|
||||||
|
cycle = models.UUIDField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
modules = ArrayField(
|
||||||
|
models.UUIDField(),
|
||||||
|
blank=True,
|
||||||
|
default=list,
|
||||||
|
)
|
||||||
|
properties = models.JSONField(default=dict)
|
||||||
|
meta = models.JSONField(default=dict)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Issue Version"
|
||||||
|
verbose_name_plural = "Issue Versions"
|
||||||
|
db_table = "issue_versions"
|
||||||
|
ordering = ("-created_at",)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} <{self.project.name}>"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def log_issue_version(cls, issue, user):
|
||||||
|
try:
|
||||||
|
"""
|
||||||
|
Log the issue version
|
||||||
|
"""
|
||||||
|
|
||||||
|
Module = apps.get_model("db.Module")
|
||||||
|
CycleIssue = apps.get_model("db.CycleIssue")
|
||||||
|
|
||||||
|
cycle_issue = CycleIssue.objects.filter(
|
||||||
|
issue=issue,
|
||||||
|
).first()
|
||||||
|
|
||||||
|
cls.objects.create(
|
||||||
|
issue=issue,
|
||||||
|
parent=issue.parent,
|
||||||
|
state=issue.state,
|
||||||
|
point=issue.point,
|
||||||
|
estimate_point=issue.estimate_point,
|
||||||
|
name=issue.name,
|
||||||
|
description=issue.description,
|
||||||
|
description_html=issue.description_html,
|
||||||
|
description_stripped=issue.description_stripped,
|
||||||
|
description_binary=issue.description_binary,
|
||||||
|
priority=issue.priority,
|
||||||
|
start_date=issue.start_date,
|
||||||
|
target_date=issue.target_date,
|
||||||
|
sequence_id=issue.sequence_id,
|
||||||
|
sort_order=issue.sort_order,
|
||||||
|
completed_at=issue.completed_at,
|
||||||
|
archived_at=issue.archived_at,
|
||||||
|
is_draft=issue.is_draft,
|
||||||
|
external_source=issue.external_source,
|
||||||
|
external_id=issue.external_id,
|
||||||
|
type=issue.type,
|
||||||
|
last_saved_at=issue.last_saved_at,
|
||||||
|
assignees=issue.assignees,
|
||||||
|
labels=issue.labels,
|
||||||
|
cycle=cycle_issue.cycle if cycle_issue else None,
|
||||||
|
modules=Module.objects.filter(issue=issue).values_list(
|
||||||
|
"id", flat=True
|
||||||
|
),
|
||||||
|
owned_by=user,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
log_exception(e)
|
||||||
|
return False
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,6 @@ class Page(BaseModel):
|
||||||
projects = models.ManyToManyField(
|
projects = models.ManyToManyField(
|
||||||
"db.Project", related_name="pages", through="db.ProjectPage"
|
"db.Project", related_name="pages", through="db.ProjectPage"
|
||||||
)
|
)
|
||||||
teams = models.ManyToManyField(
|
|
||||||
"db.Team", related_name="pages", through="db.TeamPage"
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Page"
|
verbose_name = "Page"
|
||||||
|
|
@ -160,32 +157,6 @@ class ProjectPage(BaseModel):
|
||||||
return f"{self.project.name} {self.page.name}"
|
return f"{self.project.name} {self.page.name}"
|
||||||
|
|
||||||
|
|
||||||
class TeamPage(BaseModel):
|
|
||||||
team = models.ForeignKey(
|
|
||||||
"db.Team", on_delete=models.CASCADE, related_name="team_pages"
|
|
||||||
)
|
|
||||||
page = models.ForeignKey(
|
|
||||||
"db.Page", on_delete=models.CASCADE, related_name="team_pages"
|
|
||||||
)
|
|
||||||
workspace = models.ForeignKey(
|
|
||||||
"db.Workspace", on_delete=models.CASCADE, related_name="team_pages"
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
unique_together = ["team", "page", "deleted_at"]
|
|
||||||
constraints = [
|
|
||||||
models.UniqueConstraint(
|
|
||||||
fields=["team", "page"],
|
|
||||||
condition=models.Q(deleted_at__isnull=True),
|
|
||||||
name="team_page_unique_team_page_when_deleted_at_null",
|
|
||||||
)
|
|
||||||
]
|
|
||||||
verbose_name = "Team Page"
|
|
||||||
verbose_name_plural = "Team Pages"
|
|
||||||
db_table = "team_pages"
|
|
||||||
ordering = ("-created_at",)
|
|
||||||
|
|
||||||
|
|
||||||
class PageVersion(BaseModel):
|
class PageVersion(BaseModel):
|
||||||
workspace = models.ForeignKey(
|
workspace = models.ForeignKey(
|
||||||
"db.Workspace", on_delete=models.CASCADE, related_name="page_versions"
|
"db.Workspace", on_delete=models.CASCADE, related_name="page_versions"
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,13 @@ def validate_domain(value):
|
||||||
|
|
||||||
class Webhook(BaseModel):
|
class Webhook(BaseModel):
|
||||||
workspace = models.ForeignKey(
|
workspace = models.ForeignKey(
|
||||||
"db.Workspace", on_delete=models.CASCADE, related_name="workspace_webhooks"
|
"db.Workspace",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="workspace_webhooks",
|
||||||
|
)
|
||||||
|
url = models.URLField(
|
||||||
|
validators=[validate_schema, validate_domain], max_length=1024
|
||||||
)
|
)
|
||||||
url = models.URLField(validators=[validate_schema, validate_domain])
|
|
||||||
is_active = models.BooleanField(default=True)
|
is_active = models.BooleanField(default=True)
|
||||||
secret_key = models.CharField(max_length=255, default=generate_token)
|
secret_key = models.CharField(max_length=255, default=generate_token)
|
||||||
project = models.BooleanField(default=False)
|
project = models.BooleanField(default=False)
|
||||||
|
|
@ -39,6 +43,7 @@ class Webhook(BaseModel):
|
||||||
module = models.BooleanField(default=False)
|
module = models.BooleanField(default=False)
|
||||||
cycle = models.BooleanField(default=False)
|
cycle = models.BooleanField(default=False)
|
||||||
issue_comment = models.BooleanField(default=False)
|
issue_comment = models.BooleanField(default=False)
|
||||||
|
is_internal = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.workspace.slug} {self.url}"
|
return f"{self.workspace.slug} {self.url}"
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,12 @@ def get_default_display_properties():
|
||||||
|
|
||||||
|
|
||||||
def get_issue_props():
|
def get_issue_props():
|
||||||
return {"subscribed": True, "assigned": True, "created": True, "all_issues": True}
|
return {
|
||||||
|
"subscribed": True,
|
||||||
|
"assigned": True,
|
||||||
|
"created": True,
|
||||||
|
"all_issues": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def slug_validator(value):
|
def slug_validator(value):
|
||||||
|
|
@ -131,7 +136,9 @@ class Workspace(BaseModel):
|
||||||
max_length=48, db_index=True, unique=True, validators=[slug_validator]
|
max_length=48, db_index=True, unique=True, validators=[slug_validator]
|
||||||
)
|
)
|
||||||
organization_size = models.CharField(max_length=20, blank=True, null=True)
|
organization_size = models.CharField(max_length=20, blank=True, null=True)
|
||||||
timezone = models.CharField(max_length=255, default="UTC", choices=TIMEZONE_CHOICES)
|
timezone = models.CharField(
|
||||||
|
max_length=255, default="UTC", choices=TIMEZONE_CHOICES
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Return name of the Workspace"""
|
"""Return name of the Workspace"""
|
||||||
|
|
@ -160,7 +167,10 @@ class WorkspaceBaseModel(BaseModel):
|
||||||
"db.Workspace", models.CASCADE, related_name="workspace_%(class)s"
|
"db.Workspace", models.CASCADE, related_name="workspace_%(class)s"
|
||||||
)
|
)
|
||||||
project = models.ForeignKey(
|
project = models.ForeignKey(
|
||||||
"db.Project", models.CASCADE, related_name="project_%(class)s", null=True
|
"db.Project",
|
||||||
|
models.CASCADE,
|
||||||
|
related_name="project_%(class)s",
|
||||||
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -174,7 +184,9 @@ class WorkspaceBaseModel(BaseModel):
|
||||||
|
|
||||||
class WorkspaceMember(BaseModel):
|
class WorkspaceMember(BaseModel):
|
||||||
workspace = models.ForeignKey(
|
workspace = models.ForeignKey(
|
||||||
"db.Workspace", on_delete=models.CASCADE, related_name="workspace_member"
|
"db.Workspace",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="workspace_member",
|
||||||
)
|
)
|
||||||
member = models.ForeignKey(
|
member = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
|
|
@ -209,7 +221,9 @@ class WorkspaceMember(BaseModel):
|
||||||
|
|
||||||
class WorkspaceMemberInvite(BaseModel):
|
class WorkspaceMemberInvite(BaseModel):
|
||||||
workspace = models.ForeignKey(
|
workspace = models.ForeignKey(
|
||||||
"db.Workspace", on_delete=models.CASCADE, related_name="workspace_member_invite"
|
"db.Workspace",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="workspace_member_invite",
|
||||||
)
|
)
|
||||||
email = models.CharField(max_length=255)
|
email = models.CharField(max_length=255)
|
||||||
accepted = models.BooleanField(default=False)
|
accepted = models.BooleanField(default=False)
|
||||||
|
|
@ -239,13 +253,6 @@ class WorkspaceMemberInvite(BaseModel):
|
||||||
class Team(BaseModel):
|
class Team(BaseModel):
|
||||||
name = models.CharField(max_length=255, verbose_name="Team Name")
|
name = models.CharField(max_length=255, verbose_name="Team Name")
|
||||||
description = models.TextField(verbose_name="Team Description", blank=True)
|
description = models.TextField(verbose_name="Team Description", blank=True)
|
||||||
members = models.ManyToManyField(
|
|
||||||
settings.AUTH_USER_MODEL,
|
|
||||||
blank=True,
|
|
||||||
related_name="members",
|
|
||||||
through="TeamMember",
|
|
||||||
through_fields=("team", "member"),
|
|
||||||
)
|
|
||||||
workspace = models.ForeignKey(
|
workspace = models.ForeignKey(
|
||||||
Workspace, on_delete=models.CASCADE, related_name="workspace_team"
|
Workspace, on_delete=models.CASCADE, related_name="workspace_team"
|
||||||
)
|
)
|
||||||
|
|
@ -270,40 +277,15 @@ class Team(BaseModel):
|
||||||
ordering = ("-created_at",)
|
ordering = ("-created_at",)
|
||||||
|
|
||||||
|
|
||||||
class TeamMember(BaseModel):
|
|
||||||
workspace = models.ForeignKey(
|
|
||||||
Workspace, on_delete=models.CASCADE, related_name="team_member"
|
|
||||||
)
|
|
||||||
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="team_member")
|
|
||||||
member = models.ForeignKey(
|
|
||||||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="team_member"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.team.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
unique_together = ["team", "member", "deleted_at"]
|
|
||||||
constraints = [
|
|
||||||
models.UniqueConstraint(
|
|
||||||
fields=["team", "member"],
|
|
||||||
condition=models.Q(deleted_at__isnull=True),
|
|
||||||
name="team_member_unique_team_member_when_deleted_at_null",
|
|
||||||
)
|
|
||||||
]
|
|
||||||
verbose_name = "Team Member"
|
|
||||||
verbose_name_plural = "Team Members"
|
|
||||||
db_table = "team_members"
|
|
||||||
ordering = ("-created_at",)
|
|
||||||
|
|
||||||
|
|
||||||
class WorkspaceTheme(BaseModel):
|
class WorkspaceTheme(BaseModel):
|
||||||
workspace = models.ForeignKey(
|
workspace = models.ForeignKey(
|
||||||
"db.Workspace", on_delete=models.CASCADE, related_name="themes"
|
"db.Workspace", on_delete=models.CASCADE, related_name="themes"
|
||||||
)
|
)
|
||||||
name = models.CharField(max_length=300)
|
name = models.CharField(max_length=300)
|
||||||
actor = models.ForeignKey(
|
actor = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="themes"
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="themes",
|
||||||
)
|
)
|
||||||
colors = models.JSONField(default=dict)
|
colors = models.JSONField(default=dict)
|
||||||
|
|
||||||
|
|
@ -338,7 +320,9 @@ class WorkspaceUserProperties(BaseModel):
|
||||||
)
|
)
|
||||||
filters = models.JSONField(default=get_default_filters)
|
filters = models.JSONField(default=get_default_filters)
|
||||||
display_filters = models.JSONField(default=get_default_display_filters)
|
display_filters = models.JSONField(default=get_default_display_filters)
|
||||||
display_properties = models.JSONField(default=get_default_display_properties)
|
display_properties = models.JSONField(
|
||||||
|
default=get_default_display_properties
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ["workspace", "user", "deleted_at"]
|
unique_together = ["workspace", "user", "deleted_at"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue