[WEB - 549] dev: formatting and removing unused imports (#3782)

* dev: formatting and removing unused imports

* dev: remove unused imports and format all the files

* fix: linting errors

* dev: format using ruff

* dev: remove unused variables
This commit is contained in:
Nikhil 2024-03-06 20:48:30 +05:30 committed by GitHub
parent c16a5b9b71
commit 1fa47a6c04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
88 changed files with 782 additions and 2371 deletions

View File

@ -6,8 +6,6 @@ from plane.db.models import (
Project, Project,
ProjectIdentifier, ProjectIdentifier,
WorkspaceMember, WorkspaceMember,
State,
Estimate,
) )
from .base import BaseSerializer from .base import BaseSerializer

View File

@ -1,6 +1,5 @@
# Python imports # Python imports
import zoneinfo import zoneinfo
import json
from urllib.parse import urlparse from urllib.parse import urlparse
@ -115,13 +114,13 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
if isinstance(e, ObjectDoesNotExist): if isinstance(e, ObjectDoesNotExist):
return Response( return Response(
{"error": f"The required object does not exist."}, {"error": "The required object does not exist."},
status=status.HTTP_404_NOT_FOUND, status=status.HTTP_404_NOT_FOUND,
) )
if isinstance(e, KeyError): if isinstance(e, KeyError):
return Response( return Response(
{"error": f" The required key does not exist."}, {"error": " The required key does not exist."},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )

View File

@ -2,7 +2,7 @@
import json import json
# Django imports # Django imports
from django.db.models import Q, Count, Sum, Prefetch, F, OuterRef, Func from django.db.models import Q, Count, Sum, F, OuterRef, Func
from django.utils import timezone from django.utils import timezone
from django.core import serializers from django.core import serializers
@ -321,7 +321,9 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
and Cycle.objects.filter( and Cycle.objects.filter(
project_id=project_id, project_id=project_id,
workspace__slug=slug, workspace__slug=slug,
external_source=request.data.get("external_source", cycle.external_source), external_source=request.data.get(
"external_source", cycle.external_source
),
external_id=request.data.get("external_id"), external_id=request.data.get("external_id"),
).exists() ).exists()
): ):

View File

@ -119,7 +119,7 @@ class InboxIssueAPIEndpoint(BaseAPIView):
) )
# Check for valid priority # Check for valid priority
if not request.data.get("issue", {}).get("priority", "none") in [ if request.data.get("issue", {}).get("priority", "none") not in [
"low", "low",
"medium", "medium",
"high", "high",

View File

@ -1,22 +1,22 @@
# Python imports # Python imports
import json import json
from itertools import chain
from django.core.serializers.json import DjangoJSONEncoder
# Django imports # Django imports
from django.db import IntegrityError from django.db import IntegrityError
from django.db.models import ( from django.db.models import (
OuterRef,
Func,
Q,
F,
Case, Case,
When,
Value,
CharField, CharField,
Max,
Exists, Exists,
F,
Func,
Max,
OuterRef,
Q,
Value,
When,
) )
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import timezone from django.utils import timezone
# Third party imports # Third party imports
@ -24,30 +24,31 @@ from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
# Module imports # Module imports
from .base import BaseAPIView, WebhookMixin
from plane.app.permissions import (
ProjectEntityPermission,
ProjectMemberPermission,
ProjectLitePermission,
)
from plane.db.models import (
Issue,
IssueAttachment,
IssueLink,
Project,
Label,
ProjectMember,
IssueComment,
IssueActivity,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.api.serializers import ( from plane.api.serializers import (
IssueActivitySerializer,
IssueCommentSerializer,
IssueLinkSerializer,
IssueSerializer, IssueSerializer,
LabelSerializer, LabelSerializer,
IssueLinkSerializer,
IssueCommentSerializer,
IssueActivitySerializer,
) )
from plane.app.permissions import (
ProjectEntityPermission,
ProjectLitePermission,
ProjectMemberPermission,
)
from plane.bgtasks.issue_activites_task import issue_activity
from plane.db.models import (
Issue,
IssueActivity,
IssueAttachment,
IssueComment,
IssueLink,
Label,
Project,
ProjectMember,
)
from .base import BaseAPIView, WebhookMixin
class IssueAPIEndpoint(WebhookMixin, BaseAPIView): class IssueAPIEndpoint(WebhookMixin, BaseAPIView):
@ -653,7 +654,6 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
) )
def post(self, request, slug, project_id, issue_id): def post(self, request, slug, project_id, issue_id):
# Validation check if the issue already exists # Validation check if the issue already exists
if ( if (
request.data.get("external_id") request.data.get("external_id")
@ -679,7 +679,6 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
status=status.HTTP_409_CONFLICT, status=status.HTTP_409_CONFLICT,
) )
serializer = IssueCommentSerializer(data=request.data) serializer = IssueCommentSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
serializer.save( serializer.save(
@ -717,7 +716,10 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
# Validation check if the issue already exists # Validation check if the issue already exists
if ( if (
request.data.get("external_id") request.data.get("external_id")
and (issue_comment.external_id != str(request.data.get("external_id"))) and (
issue_comment.external_id
!= str(request.data.get("external_id"))
)
and IssueComment.objects.filter( and IssueComment.objects.filter(
project_id=project_id, project_id=project_id,
workspace__slug=slug, workspace__slug=slug,
@ -735,7 +737,6 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
status=status.HTTP_409_CONFLICT, status=status.HTTP_409_CONFLICT,
) )
serializer = IssueCommentSerializer( serializer = IssueCommentSerializer(
issue_comment, data=request.data, partial=True issue_comment, data=request.data, partial=True
) )

View File

@ -178,7 +178,9 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
and Module.objects.filter( and Module.objects.filter(
project_id=project_id, project_id=project_id,
workspace__slug=slug, workspace__slug=slug,
external_source=request.data.get("external_source", module.external_source), external_source=request.data.get(
"external_source", module.external_source
),
external_id=request.data.get("external_id"), external_id=request.data.get("external_id"),
).exists() ).exists()
): ):

View File

@ -11,7 +11,6 @@ from rest_framework.serializers import ValidationError
from plane.db.models import ( from plane.db.models import (
Workspace, Workspace,
Project, Project,
ProjectFavorite,
ProjectMember, ProjectMember,
ProjectDeployBoard, ProjectDeployBoard,
State, State,
@ -150,7 +149,7 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
serializer.save() serializer.save()
# Add the user as Administrator to the project # Add the user as Administrator to the project
project_member = ProjectMember.objects.create( _ = ProjectMember.objects.create(
project_id=serializer.data["id"], project_id=serializer.data["id"],
member=request.user, member=request.user,
role=20, role=20,
@ -245,12 +244,12 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
{"name": "The project name is already taken"}, {"name": "The project name is already taken"},
status=status.HTTP_410_GONE, status=status.HTTP_410_GONE,
) )
except Workspace.DoesNotExist as e: except Workspace.DoesNotExist:
return Response( return Response(
{"error": "Workspace does not exist"}, {"error": "Workspace does not exist"},
status=status.HTTP_404_NOT_FOUND, status=status.HTTP_404_NOT_FOUND,
) )
except ValidationError as e: except ValidationError:
return Response( return Response(
{"identifier": "The project identifier is already taken"}, {"identifier": "The project identifier is already taken"},
status=status.HTTP_410_GONE, status=status.HTTP_410_GONE,
@ -307,7 +306,7 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
{"error": "Project does not exist"}, {"error": "Project does not exist"},
status=status.HTTP_404_NOT_FOUND, status=status.HTTP_404_NOT_FOUND,
) )
except ValidationError as e: except ValidationError:
return Response( return Response(
{"identifier": "The project identifier is already taken"}, {"identifier": "The project identifier is already taken"},
status=status.HTTP_410_GONE, status=status.HTTP_410_GONE,

View File

@ -66,8 +66,10 @@ class StateAPIEndpoint(BaseAPIView):
serializer.save(project_id=project_id) serializer.save(project_id=project_id)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(
except IntegrityError as e: serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
except IntegrityError:
state = State.objects.filter( state = State.objects.filter(
workspace__slug=slug, workspace__slug=slug,
project_id=project_id, project_id=project_id,
@ -136,7 +138,9 @@ class StateAPIEndpoint(BaseAPIView):
and State.objects.filter( and State.objects.filter(
project_id=project_id, project_id=project_id,
workspace__slug=slug, workspace__slug=slug,
external_source=request.data.get("external_source", state.external_source), external_source=request.data.get(
"external_source", state.external_source
),
external_id=request.data.get("external_id"), external_id=request.data.get("external_id"),
).exists() ).exists()
): ):

View File

@ -111,7 +111,10 @@ from .inbox import (
from .analytic import AnalyticViewSerializer from .analytic import AnalyticViewSerializer
from .notification import NotificationSerializer, UserNotificationPreferenceSerializer from .notification import (
NotificationSerializer,
UserNotificationPreferenceSerializer,
)
from .exporter import ExporterHistorySerializer from .exporter import ExporterHistorySerializer

View File

@ -11,6 +11,7 @@ from plane.db.models import (
CycleUserProperties, CycleUserProperties,
) )
class CycleWriteSerializer(BaseSerializer): class CycleWriteSerializer(BaseSerializer):
def validate(self, data): def validate(self, data):
if ( if (
@ -47,7 +48,6 @@ class CycleSerializer(BaseSerializer):
# active | draft | upcoming | completed # active | draft | upcoming | completed
status = serializers.CharField(read_only=True) status = serializers.CharField(read_only=True)
class Meta: class Meta:
model = Cycle model = Cycle
fields = [ fields = [

View File

@ -18,9 +18,4 @@ class WidgetSerializer(BaseSerializer):
class Meta: class Meta:
model = Widget model = Widget
fields = [ fields = ["id", "key", "is_visible", "widget_filters"]
"id",
"key",
"is_visible",
"widget_filters"
]

View File

@ -74,5 +74,3 @@ class WorkspaceEstimateSerializer(BaseSerializer):
"name", "name",
"description", "description",
] ]

View File

@ -9,7 +9,7 @@ from rest_framework import serializers
# Module imports # Module imports
from .base import BaseSerializer, DynamicBaseSerializer from .base import BaseSerializer, DynamicBaseSerializer
from .user import UserLiteSerializer from .user import UserLiteSerializer
from .state import StateSerializer, StateLiteSerializer from .state import StateLiteSerializer
from .project import ProjectLiteSerializer from .project import ProjectLiteSerializer
from .workspace import WorkspaceLiteSerializer from .workspace import WorkspaceLiteSerializer
from plane.db.models import ( from plane.db.models import (
@ -33,7 +33,6 @@ from plane.db.models import (
IssueVote, IssueVote,
IssueRelation, IssueRelation,
State, State,
Project,
) )
@ -472,7 +471,6 @@ class IssueLinkSerializer(BaseSerializer):
class IssueLinkLiteSerializer(BaseSerializer): class IssueLinkLiteSerializer(BaseSerializer):
class Meta: class Meta:
model = IssueLink model = IssueLink
fields = [ fields = [
@ -503,7 +501,6 @@ class IssueAttachmentSerializer(BaseSerializer):
class IssueAttachmentLiteSerializer(DynamicBaseSerializer): class IssueAttachmentLiteSerializer(DynamicBaseSerializer):
class Meta: class Meta:
model = IssueAttachment model = IssueAttachment
fields = [ fields = [
@ -532,7 +529,6 @@ class IssueReactionSerializer(BaseSerializer):
class IssueReactionLiteSerializer(DynamicBaseSerializer): class IssueReactionLiteSerializer(DynamicBaseSerializer):
class Meta: class Meta:
model = IssueReaction model = IssueReaction
fields = [ fields = [
@ -628,15 +624,18 @@ class IssueSerializer(DynamicBaseSerializer):
# ids # ids
cycle_id = serializers.PrimaryKeyRelatedField(read_only=True) cycle_id = serializers.PrimaryKeyRelatedField(read_only=True)
module_ids = serializers.ListField( module_ids = serializers.ListField(
child=serializers.UUIDField(), required=False, child=serializers.UUIDField(),
required=False,
) )
# Many to many # Many to many
label_ids = serializers.ListField( label_ids = serializers.ListField(
child=serializers.UUIDField(), required=False, child=serializers.UUIDField(),
required=False,
) )
assignee_ids = serializers.ListField( assignee_ids = serializers.ListField(
child=serializers.UUIDField(), required=False, child=serializers.UUIDField(),
required=False,
) )
# Count items # Count items
@ -676,19 +675,7 @@ class IssueSerializer(DynamicBaseSerializer):
read_only_fields = fields read_only_fields = fields
class IssueDetailSerializer(IssueSerializer):
description_html = serializers.CharField()
is_subscribed = serializers.BooleanField(read_only=True)
class Meta(IssueSerializer.Meta):
fields = IssueSerializer.Meta.fields + [
"description_html",
"is_subscribed",
]
class IssueLiteSerializer(DynamicBaseSerializer): class IssueLiteSerializer(DynamicBaseSerializer):
class Meta: class Meta:
model = Issue model = Issue
fields = [ fields = [

View File

@ -3,7 +3,6 @@ from rest_framework import serializers
# Module imports # Module imports
from .base import BaseSerializer, DynamicBaseSerializer from .base import BaseSerializer, DynamicBaseSerializer
from .user import UserLiteSerializer
from .project import ProjectLiteSerializer from .project import ProjectLiteSerializer
from plane.db.models import ( from plane.db.models import (
@ -142,7 +141,6 @@ class ModuleIssueSerializer(BaseSerializer):
class ModuleLinkSerializer(BaseSerializer): class ModuleLinkSerializer(BaseSerializer):
class Meta: class Meta:
model = ModuleLink model = ModuleLink
fields = "__all__" fields = "__all__"
@ -215,13 +213,11 @@ class ModuleSerializer(DynamicBaseSerializer):
read_only_fields = fields read_only_fields = fields
class ModuleDetailSerializer(ModuleSerializer): class ModuleDetailSerializer(ModuleSerializer):
link_module = ModuleLinkSerializer(read_only=True, many=True) link_module = ModuleLinkSerializer(read_only=True, many=True)
class Meta(ModuleSerializer.Meta): class Meta(ModuleSerializer.Meta):
fields = ModuleSerializer.Meta.fields + ['link_module'] fields = ModuleSerializer.Meta.fields + ["link_module"]
class ModuleFavoriteSerializer(BaseSerializer): class ModuleFavoriteSerializer(BaseSerializer):

View File

@ -15,7 +15,6 @@ class NotificationSerializer(BaseSerializer):
class UserNotificationPreferenceSerializer(BaseSerializer): class UserNotificationPreferenceSerializer(BaseSerializer):
class Meta: class Meta:
model = UserNotificationPreference model = UserNotificationPreference
fields = "__all__" fields = "__all__"

View File

@ -3,7 +3,7 @@ from rest_framework import serializers
# Module imports # Module imports
from .base import BaseSerializer from .base import BaseSerializer
from .issue import IssueFlatSerializer, LabelLiteSerializer from .issue import LabelLiteSerializer
from .workspace import WorkspaceLiteSerializer from .workspace import WorkspaceLiteSerializer
from .project import ProjectLiteSerializer from .project import ProjectLiteSerializer
from plane.db.models import ( from plane.db.models import (
@ -12,8 +12,6 @@ from plane.db.models import (
PageFavorite, PageFavorite,
PageLabel, PageLabel,
Label, Label,
Issue,
Module,
) )

View File

@ -4,7 +4,6 @@ from rest_framework import serializers
# Module import # Module import
from .base import BaseSerializer from .base import BaseSerializer
from plane.db.models import User, Workspace, WorkspaceMemberInvite from plane.db.models import User, Workspace, WorkspaceMemberInvite
from plane.license.models import InstanceAdmin, Instance
class UserSerializer(BaseSerializer): class UserSerializer(BaseSerializer):
@ -99,13 +98,13 @@ class UserMeSettingsSerializer(BaseSerializer):
).first() ).first()
return { return {
"last_workspace_id": obj.last_workspace_id, "last_workspace_id": obj.last_workspace_id,
"last_workspace_slug": workspace.slug "last_workspace_slug": (
if workspace is not None workspace.slug if workspace is not None else ""
else "", ),
"fallback_workspace_id": obj.last_workspace_id, "fallback_workspace_id": obj.last_workspace_id,
"fallback_workspace_slug": workspace.slug "fallback_workspace_slug": (
if workspace is not None workspace.slug if workspace is not None else ""
else "", ),
"invites": workspace_invites, "invites": workspace_invites,
} }
else: else:
@ -120,12 +119,16 @@ class UserMeSettingsSerializer(BaseSerializer):
return { return {
"last_workspace_id": None, "last_workspace_id": None,
"last_workspace_slug": None, "last_workspace_slug": None,
"fallback_workspace_id": fallback_workspace.id "fallback_workspace_id": (
if fallback_workspace is not None fallback_workspace.id
else None, if fallback_workspace is not None
"fallback_workspace_slug": fallback_workspace.slug else None
if fallback_workspace is not None ),
else None, "fallback_workspace_slug": (
fallback_workspace.slug
if fallback_workspace is not None
else None
),
"invites": workspace_invites, "invites": workspace_invites,
} }

View File

@ -1,5 +1,4 @@
# Python imports # Python imports
import urllib
import socket import socket
import ipaddress import ipaddress
from urllib.parse import urlparse from urllib.parse import urlparse

File diff suppressed because it is too large Load Diff

View File

@ -164,9 +164,6 @@ from .webhook import (
WebhookSecretRegenerateEndpoint, WebhookSecretRegenerateEndpoint,
) )
from .dashboard import ( from .dashboard import DashboardEndpoint, WidgetsEndpoint
DashboardEndpoint,
WidgetsEndpoint
)
from .error_404 import custom_404_view from .error_404 import custom_404_view

View File

@ -1,5 +1,5 @@
# Django imports # Django imports
from django.db.models import Count, Sum, F, Q from django.db.models import Count, Sum, F
from django.db.models.functions import ExtractMonth from django.db.models.functions import ExtractMonth
from django.utils import timezone from django.utils import timezone
@ -10,7 +10,7 @@ from rest_framework.response import Response
# Module imports # Module imports
from plane.app.views import BaseAPIView, BaseViewSet from plane.app.views import BaseAPIView, BaseViewSet
from plane.app.permissions import WorkSpaceAdminPermission from plane.app.permissions import WorkSpaceAdminPermission
from plane.db.models import Issue, AnalyticView, Workspace, State, Label from plane.db.models import Issue, AnalyticView, Workspace
from plane.app.serializers import AnalyticViewSerializer from plane.app.serializers import AnalyticViewSerializer
from plane.utils.analytics_plot import build_graph_plot from plane.utils.analytics_plot import build_graph_plot
from plane.bgtasks.analytic_plot_export import analytic_export_task from plane.bgtasks.analytic_plot_export import analytic_export_task
@ -51,8 +51,8 @@ class AnalyticsEndpoint(BaseAPIView):
if ( if (
not x_axis not x_axis
or not y_axis or not y_axis
or not x_axis in valid_xaxis_segment or x_axis not in valid_xaxis_segment
or not y_axis in valid_yaxis or y_axis not in valid_yaxis
): ):
return Response( return Response(
{ {
@ -266,8 +266,8 @@ class ExportAnalyticsEndpoint(BaseAPIView):
if ( if (
not x_axis not x_axis
or not y_axis or not y_axis
or not x_axis in valid_xaxis_segment or x_axis not in valid_xaxis_segment
or not y_axis in valid_yaxis or y_axis not in valid_yaxis
): ):
return Response( return Response(
{ {

View File

@ -43,7 +43,7 @@ class ApiTokenEndpoint(BaseAPIView):
) )
def get(self, request, slug, pk=None): def get(self, request, slug, pk=None):
if pk == None: if pk is None:
api_tokens = APIToken.objects.filter( api_tokens = APIToken.objects.filter(
user=request.user, workspace__slug=slug user=request.user, workspace__slug=slug
) )

View File

@ -16,7 +16,6 @@ from django.contrib.auth.hashers import make_password
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.core.validators import validate_email from django.core.validators import validate_email
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.conf import settings
## Third Party Imports ## Third Party Imports
from rest_framework import status from rest_framework import status
@ -172,7 +171,7 @@ class ResetPasswordEndpoint(BaseAPIView):
serializer.errors, status=status.HTTP_400_BAD_REQUEST serializer.errors, status=status.HTTP_400_BAD_REQUEST
) )
except DjangoUnicodeDecodeError as indentifier: except DjangoUnicodeDecodeError:
return Response( return Response(
{"error": "token is not valid, please check the new one"}, {"error": "token is not valid, please check the new one"},
status=status.HTTP_401_UNAUTHORIZED, status=status.HTTP_401_UNAUTHORIZED,

View File

@ -7,7 +7,6 @@ import json
from django.utils import timezone from django.utils import timezone
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import validate_email from django.core.validators import validate_email
from django.conf import settings
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
# Third party imports # Third party imports
@ -65,7 +64,7 @@ class SignUpEndpoint(BaseAPIView):
email = email.strip().lower() email = email.strip().lower()
try: try:
validate_email(email) validate_email(email)
except ValidationError as e: except ValidationError:
return Response( return Response(
{"error": "Please provide a valid email address."}, {"error": "Please provide a valid email address."},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
@ -151,7 +150,7 @@ class SignInEndpoint(BaseAPIView):
email = email.strip().lower() email = email.strip().lower()
try: try:
validate_email(email) validate_email(email)
except ValidationError as e: except ValidationError:
return Response( return Response(
{"error": "Please provide a valid email address."}, {"error": "Please provide a valid email address."},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
@ -238,9 +237,11 @@ class SignInEndpoint(BaseAPIView):
[ [
WorkspaceMember( WorkspaceMember(
workspace_id=project_member_invite.workspace_id, workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role role=(
if project_member_invite.role in [5, 10, 15] project_member_invite.role
else 15, if project_member_invite.role in [5, 10, 15]
else 15
),
member=user, member=user,
created_by_id=project_member_invite.created_by_id, created_by_id=project_member_invite.created_by_id,
) )
@ -254,9 +255,11 @@ class SignInEndpoint(BaseAPIView):
[ [
ProjectMember( ProjectMember(
workspace_id=project_member_invite.workspace_id, workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role role=(
if project_member_invite.role in [5, 10, 15] project_member_invite.role
else 15, if project_member_invite.role in [5, 10, 15]
else 15
),
member=user, member=user,
created_by_id=project_member_invite.created_by_id, created_by_id=project_member_invite.created_by_id,
) )
@ -392,9 +395,11 @@ class MagicSignInEndpoint(BaseAPIView):
[ [
WorkspaceMember( WorkspaceMember(
workspace_id=project_member_invite.workspace_id, workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role role=(
if project_member_invite.role in [5, 10, 15] project_member_invite.role
else 15, if project_member_invite.role in [5, 10, 15]
else 15
),
member=user, member=user,
created_by_id=project_member_invite.created_by_id, created_by_id=project_member_invite.created_by_id,
) )
@ -408,9 +413,11 @@ class MagicSignInEndpoint(BaseAPIView):
[ [
ProjectMember( ProjectMember(
workspace_id=project_member_invite.workspace_id, workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role role=(
if project_member_invite.role in [5, 10, 15] project_member_invite.role
else 15, if project_member_invite.role in [5, 10, 15]
else 15
),
member=user, member=user,
created_by_id=project_member_invite.created_by_id, created_by_id=project_member_invite.created_by_id,
) )

View File

@ -1,6 +1,5 @@
# Python imports # Python imports
import zoneinfo import zoneinfo
import json
# Django imports # Django imports
from django.urls import resolve from django.urls import resolve
@ -8,11 +7,9 @@ from django.conf import settings
from django.utils import timezone from django.utils import timezone
from django.db import IntegrityError from django.db import IntegrityError
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.serializers.json import DjangoJSONEncoder
# Third part imports # Third part imports
from rest_framework import status from rest_framework import status
from rest_framework import status
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.exceptions import APIException from rest_framework.exceptions import APIException
@ -119,14 +116,14 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
if isinstance(e, ObjectDoesNotExist): if isinstance(e, ObjectDoesNotExist):
return Response( return Response(
{"error": f"The required object does not exist."}, {"error": "The required object does not exist."},
status=status.HTTP_404_NOT_FOUND, status=status.HTTP_404_NOT_FOUND,
) )
if isinstance(e, KeyError): if isinstance(e, KeyError):
capture_exception(e) capture_exception(e)
return Response( return Response(
{"error": f"The required key does not exist."}, {"error": "The required key does not exist."},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
@ -226,13 +223,13 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
if isinstance(e, ObjectDoesNotExist): if isinstance(e, ObjectDoesNotExist):
return Response( return Response(
{"error": f"The required object does not exist."}, {"error": "The required object does not exist."},
status=status.HTTP_404_NOT_FOUND, status=status.HTTP_404_NOT_FOUND,
) )
if isinstance(e, KeyError): if isinstance(e, KeyError):
return Response( return Response(
{"error": f"The required key does not exist."}, {"error": "The required key does not exist."},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )

View File

@ -2,7 +2,6 @@
import os import os
# Django imports # Django imports
from django.conf import settings
# Third party imports # Third party imports
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny

View File

@ -10,7 +10,6 @@ from django.db.models import (
OuterRef, OuterRef,
Count, Count,
Prefetch, Prefetch,
Sum,
Case, Case,
When, When,
Value, Value,
@ -22,7 +21,7 @@ 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.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db.models import Value, UUIDField from django.db.models import UUIDField
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
# Third party imports # Third party imports
@ -328,13 +327,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
} }
if data[0]["start_date"] and data[0]["end_date"]: if data[0]["start_date"] and data[0]["end_date"]:
data[0]["distribution"]["completion_chart"] = ( data[0]["distribution"][
burndown_plot( "completion_chart"
queryset=queryset.first(), ] = burndown_plot(
slug=slug, queryset=queryset.first(),
project_id=project_id, slug=slug,
cycle_id=data[0]["id"], project_id=project_id,
) cycle_id=data[0]["id"],
) )
return Response(data, status=status.HTTP_200_OK) return Response(data, status=status.HTTP_200_OK)
@ -427,9 +426,8 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
) )
def partial_update(self, request, slug, project_id, pk): def partial_update(self, request, slug, project_id, pk):
queryset = ( queryset = self.get_queryset().filter(
self.get_queryset() workspace__slug=slug, project_id=project_id, pk=pk
.filter(workspace__slug=slug, project_id=project_id, pk=pk)
) )
cycle = queryset.first() cycle = queryset.first()
request_data = request.data request_data = request.data
@ -883,7 +881,9 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
) )
# Update the cycle issues # Update the cycle issues
CycleIssue.objects.bulk_update(updated_records, ["cycle_id"], batch_size=100) CycleIssue.objects.bulk_update(
updated_records, ["cycle_id"], batch_size=100
)
# Capture Issue Activity # Capture Issue Activity
issue_activity.delay( issue_activity.delay(
type="cycle.activity.created", type="cycle.activity.created",

View File

@ -9,7 +9,6 @@ from django.db.models import (
F, F,
Exists, Exists,
OuterRef, OuterRef,
Max,
Subquery, Subquery,
JSONField, JSONField,
Func, Func,
@ -18,7 +17,7 @@ from django.db.models import (
) )
from django.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db.models import Value, UUIDField from django.db.models import UUIDField
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.utils import timezone from django.utils import timezone

View File

@ -50,7 +50,7 @@ class ExportIssuesEndpoint(BaseAPIView):
) )
return Response( return Response(
{ {
"message": f"Once the export is ready you will be able to download it" "message": "Once the export is ready you will be able to download it"
}, },
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
) )

View File

@ -8,7 +8,6 @@ from rest_framework.response import Response
from rest_framework import status from rest_framework import status
# Django imports # Django imports
from django.conf import settings
# Module imports # Module imports
from .base import BaseAPIView from .base import BaseAPIView

View File

@ -213,7 +213,7 @@ class InboxIssueViewSet(BaseViewSet):
) )
# Check for valid priority # Check for valid priority
if not request.data.get("issue", {}).get("priority", "none") in [ if request.data.get("issue", {}).get("priority", "none") not in [
"low", "low",
"medium", "medium",
"high", "high",
@ -428,7 +428,10 @@ class InboxIssueViewSet(BaseViewSet):
) )
).first() ).first()
if issue is None: if issue is None:
return Response({"error": "Requested object was not found"}, status=status.HTTP_404_NOT_FOUND) return Response(
{"error": "Requested object was not found"},
status=status.HTTP_404_NOT_FOUND,
)
serializer = IssueDetailSerializer(issue) serializer = IssueDetailSerializer(issue)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)

View File

@ -24,7 +24,7 @@ from django.views.decorators.gzip import gzip_page
from django.db import IntegrityError from django.db import IntegrityError
from django.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db.models import Value, UUIDField from django.db.models import UUIDField
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
# Third Party imports # Third Party imports
@ -82,7 +82,6 @@ from plane.utils.cache import invalidate_cache
class IssueListEndpoint(BaseAPIView): class IssueListEndpoint(BaseAPIView):
permission_classes = [ permission_classes = [
ProjectEntityPermission, ProjectEntityPermission,
] ]
@ -96,7 +95,9 @@ class IssueListEndpoint(BaseAPIView):
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
issue_ids = [issue_id for issue_id in issue_ids.split(",") if issue_id != ""] issue_ids = [
issue_id for issue_id in issue_ids.split(",") if issue_id != ""
]
queryset = ( queryset = (
Issue.issue_objects.filter( Issue.issue_objects.filter(
@ -1662,12 +1663,19 @@ class IssueArchiveViewSet(BaseViewSet):
) )
if issue.state.group not in ["completed", "cancelled"]: if issue.state.group not in ["completed", "cancelled"]:
return Response( return Response(
{"error": "Can only archive completed or cancelled state group issue"}, {
"error": "Can only archive completed or cancelled state group issue"
},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
issue_activity.delay( issue_activity.delay(
type="issue.activity.updated", type="issue.activity.updated",
requested_data=json.dumps({"archived_at": str(timezone.now().date()), "automation": False}), requested_data=json.dumps(
{
"archived_at": str(timezone.now().date()),
"automation": False,
}
),
actor_id=str(request.user.id), actor_id=str(request.user.id),
issue_id=str(issue.id), issue_id=str(issue.id),
project_id=str(project_id), project_id=str(project_id),
@ -1681,8 +1689,9 @@ class IssueArchiveViewSet(BaseViewSet):
issue.archived_at = timezone.now().date() issue.archived_at = timezone.now().date()
issue.save() issue.save()
return Response({"archived_at": str(issue.archived_at)}, status=status.HTTP_200_OK) return Response(
{"archived_at": str(issue.archived_at)}, status=status.HTTP_200_OK
)
def unarchive(self, request, slug, project_id, pk=None): def unarchive(self, request, slug, project_id, pk=None):
issue = Issue.objects.get( issue = Issue.objects.get(
@ -2209,12 +2218,6 @@ class IssueDraftViewSet(BaseViewSet):
@method_decorator(gzip_page) @method_decorator(gzip_page)
def list(self, request, slug, project_id): def list(self, request, slug, project_id):
filters = issue_filters(request.query_params, "GET") 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 # Custom ordering for priority and state
priority_order = ["urgent", "high", "medium", "low", "none"] priority_order = ["urgent", "high", "medium", "low", "none"]
state_order = [ state_order = [
@ -2373,7 +2376,9 @@ class IssueDraftViewSet(BaseViewSet):
status=status.HTTP_404_NOT_FOUND, status=status.HTTP_404_NOT_FOUND,
) )
serializer = IssueCreateSerializer(issue, data=request.data, partial=True) serializer = IssueCreateSerializer(
issue, data=request.data, partial=True
)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()

View File

@ -17,7 +17,10 @@ from plane.db.models import (
WorkspaceMember, WorkspaceMember,
UserNotificationPreference, UserNotificationPreference,
) )
from plane.app.serializers import NotificationSerializer, UserNotificationPreferenceSerializer from plane.app.serializers import (
NotificationSerializer,
UserNotificationPreferenceSerializer,
)
class NotificationViewSet(BaseViewSet, BasePaginator): class NotificationViewSet(BaseViewSet, BasePaginator):

View File

@ -5,7 +5,6 @@ import os
# Django imports # Django imports
from django.utils import timezone from django.utils import timezone
from django.conf import settings
# Third Party modules # Third Party modules
from rest_framework.response import Response from rest_framework.response import Response
@ -250,9 +249,11 @@ class OauthEndpoint(BaseAPIView):
[ [
WorkspaceMember( WorkspaceMember(
workspace_id=project_member_invite.workspace_id, workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role role=(
if project_member_invite.role in [5, 10, 15] project_member_invite.role
else 15, if project_member_invite.role in [5, 10, 15]
else 15
),
member=user, member=user,
created_by_id=project_member_invite.created_by_id, created_by_id=project_member_invite.created_by_id,
) )
@ -266,9 +267,11 @@ class OauthEndpoint(BaseAPIView):
[ [
ProjectMember( ProjectMember(
workspace_id=project_member_invite.workspace_id, workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role role=(
if project_member_invite.role in [5, 10, 15] project_member_invite.role
else 15, if project_member_invite.role in [5, 10, 15]
else 15
),
member=user, member=user,
created_by_id=project_member_invite.created_by_id, created_by_id=project_member_invite.created_by_id,
) )
@ -391,9 +394,11 @@ class OauthEndpoint(BaseAPIView):
[ [
WorkspaceMember( WorkspaceMember(
workspace_id=project_member_invite.workspace_id, workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role role=(
if project_member_invite.role in [5, 10, 15] project_member_invite.role
else 15, if project_member_invite.role in [5, 10, 15]
else 15
),
member=user, member=user,
created_by_id=project_member_invite.created_by_id, created_by_id=project_member_invite.created_by_id,
) )
@ -407,9 +412,11 @@ class OauthEndpoint(BaseAPIView):
[ [
ProjectMember( ProjectMember(
workspace_id=project_member_invite.workspace_id, workspace_id=project_member_invite.workspace_id,
role=project_member_invite.role role=(
if project_member_invite.role in [5, 10, 15] project_member_invite.role
else 15, if project_member_invite.role in [5, 10, 15]
else 15
),
member=user, member=user,
created_by_id=project_member_invite.created_by_id, created_by_id=project_member_invite.created_by_id,
) )

View File

@ -1,22 +1,29 @@
# Python imports # Python imports
from datetime import date, datetime, timedelta from datetime import datetime
# Django imports # Django imports
from django.db import connection from django.db import connection
from django.db.models import Exists, OuterRef, Q from django.db.models import Exists, OuterRef, Q
from django.utils import timezone
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
# Third party imports # Third party imports
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 ProjectEntityPermission from plane.app.permissions import ProjectEntityPermission
from plane.app.serializers import (IssueLiteSerializer, PageFavoriteSerializer, from plane.app.serializers import (
PageLogSerializer, PageSerializer, PageFavoriteSerializer,
SubPageSerializer) PageLogSerializer,
from plane.db.models import (Issue, IssueActivity, IssueAssignee, Page, PageSerializer,
PageFavorite, PageLog, ProjectMember) SubPageSerializer,
)
from plane.db.models import (
Page,
PageFavorite,
PageLog,
ProjectMember,
)
# Module imports # Module imports
from .base import BaseAPIView, BaseViewSet from .base import BaseAPIView, BaseViewSet

View File

@ -1,72 +1,70 @@
# Python imports # Python imports
import jwt
import boto3
from datetime import datetime from datetime import datetime
import boto3
import jwt
from django.conf import settings
# Django imports # Django imports
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.db import IntegrityError from django.db import IntegrityError
from django.db.models import ( from django.db.models import (
Prefetch,
Q,
Exists, Exists,
OuterRef,
F, F,
Func, Func,
OuterRef,
Prefetch,
Q,
Subquery, Subquery,
) )
from django.core.validators import validate_email
from django.conf import settings
from django.utils import timezone from django.utils import timezone
# Third Party imports # Third Party imports
from rest_framework.response import Response from rest_framework import serializers, status
from rest_framework import status
from rest_framework import serializers
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.response import Response
# Module imports # Module imports
from .base import BaseViewSet, BaseAPIView, WebhookMixin
from plane.app.serializers import (
ProjectSerializer,
ProjectListSerializer,
ProjectMemberSerializer,
ProjectDetailSerializer,
ProjectMemberInviteSerializer,
ProjectFavoriteSerializer,
ProjectDeployBoardSerializer,
ProjectMemberAdminSerializer,
ProjectMemberRoleSerializer,
)
from plane.app.permissions import ( from plane.app.permissions import (
WorkspaceUserPermission,
ProjectBasePermission, ProjectBasePermission,
ProjectMemberPermission,
ProjectLitePermission, ProjectLitePermission,
ProjectMemberPermission,
WorkspaceUserPermission,
) )
from plane.app.serializers import (
ProjectDeployBoardSerializer,
ProjectFavoriteSerializer,
ProjectListSerializer,
ProjectMemberAdminSerializer,
ProjectMemberInviteSerializer,
ProjectMemberRoleSerializer,
ProjectMemberSerializer,
ProjectSerializer,
)
from plane.bgtasks.project_invitation_task import project_invitation
from plane.db.models import ( from plane.db.models import (
Project,
ProjectMember,
Workspace,
ProjectMemberInvite,
User,
WorkspaceMember,
State,
TeamMember,
ProjectFavorite,
ProjectIdentifier,
Module,
Cycle, Cycle,
Inbox, Inbox,
ProjectDeployBoard,
IssueProperty, IssueProperty,
Module,
Project,
ProjectDeployBoard,
ProjectFavorite,
ProjectIdentifier,
ProjectMember,
ProjectMemberInvite,
State,
TeamMember,
User,
Workspace,
WorkspaceMember,
) )
from plane.bgtasks.project_invitation_task import project_invitation
from plane.utils.cache import cache_response from plane.utils.cache import cache_response
from .base import BaseAPIView, BaseViewSet, WebhookMixin
class ProjectViewSet(WebhookMixin, BaseViewSet): class ProjectViewSet(WebhookMixin, BaseViewSet):
serializer_class = ProjectListSerializer serializer_class = ProjectListSerializer
model = Project model = Project
@ -173,10 +171,7 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
for field in request.GET.get("fields", "").split(",") for field in request.GET.get("fields", "").split(",")
if field if field
] ]
projects = ( projects = self.get_queryset().order_by("sort_order", "name")
self.get_queryset()
.order_by("sort_order", "name")
)
if request.GET.get("per_page", False) and request.GET.get( if request.GET.get("per_page", False) and request.GET.get(
"cursor", False "cursor", False
): ):
@ -298,12 +293,12 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
{"name": "The project name is already taken"}, {"name": "The project name is already taken"},
status=status.HTTP_410_GONE, status=status.HTTP_410_GONE,
) )
except Workspace.DoesNotExist as e: except Workspace.DoesNotExist:
return Response( return Response(
{"error": "Workspace does not exist"}, {"error": "Workspace does not exist"},
status=status.HTTP_404_NOT_FOUND, status=status.HTTP_404_NOT_FOUND,
) )
except serializers.ValidationError as e: except serializers.ValidationError:
return Response( return Response(
{"identifier": "The project identifier is already taken"}, {"identifier": "The project identifier is already taken"},
status=status.HTTP_410_GONE, status=status.HTTP_410_GONE,
@ -362,7 +357,7 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
{"error": "Project does not exist"}, {"error": "Project does not exist"},
status=status.HTTP_404_NOT_FOUND, status=status.HTTP_404_NOT_FOUND,
) )
except serializers.ValidationError as e: except serializers.ValidationError:
return Response( return Response(
{"identifier": "The project identifier is already taken"}, {"identifier": "The project identifier is already taken"},
status=status.HTTP_410_GONE, status=status.HTTP_410_GONE,
@ -576,9 +571,11 @@ class ProjectJoinEndpoint(BaseAPIView):
_ = WorkspaceMember.objects.create( _ = WorkspaceMember.objects.create(
workspace_id=project_invite.workspace_id, workspace_id=project_invite.workspace_id,
member=user, member=user,
role=15 role=(
if project_invite.role >= 15 15
else project_invite.role, if project_invite.role >= 15
else project_invite.role
),
) )
else: else:
# Else make him active # Else make him active
@ -685,9 +682,14 @@ class ProjectMemberViewSet(BaseViewSet):
) )
bulk_project_members = [] bulk_project_members = []
member_roles = {member.get("member_id"): member.get("role") for member in members} member_roles = {
member.get("member_id"): member.get("role") for member in members
}
# Update roles in the members array based on the member_roles dictionary # Update roles in the members array based on the member_roles dictionary
for project_member in ProjectMember.objects.filter(project_id=project_id, member_id__in=[member.get("member_id") for member in members]): for project_member in ProjectMember.objects.filter(
project_id=project_id,
member_id__in=[member.get("member_id") for member in members],
):
project_member.role = member_roles[str(project_member.member_id)] project_member.role = member_roles[str(project_member.member_id)]
project_member.is_active = True project_member.is_active = True
bulk_project_members.append(project_member) bulk_project_members.append(project_member)
@ -710,9 +712,9 @@ class ProjectMemberViewSet(BaseViewSet):
role=member.get("role", 10), role=member.get("role", 10),
project_id=project_id, project_id=project_id,
workspace_id=project.workspace_id, workspace_id=project.workspace_id,
sort_order=sort_order[0] - 10000 sort_order=(
if len(sort_order) sort_order[0] - 10000 if len(sort_order) else 65535
else 65535, ),
) )
) )
bulk_issue_props.append( bulk_issue_props.append(
@ -733,7 +735,10 @@ class ProjectMemberViewSet(BaseViewSet):
bulk_issue_props, batch_size=10, ignore_conflicts=True bulk_issue_props, batch_size=10, ignore_conflicts=True
) )
project_members = ProjectMember.objects.filter(project_id=project_id, member_id__in=[member.get("member_id") for member in members]) project_members = ProjectMember.objects.filter(
project_id=project_id,
member_id__in=[member.get("member_id") for member in members],
)
serializer = ProjectMemberRoleSerializer(project_members, many=True) serializer = ProjectMemberRoleSerializer(project_members, many=True)
return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.data, status=status.HTTP_201_CREATED)

View File

@ -254,7 +254,8 @@ class IssueSearchEndpoint(BaseAPIView):
if parent == "true" and issue_id: if parent == "true" and issue_id:
issue = Issue.issue_objects.get(pk=issue_id) issue = Issue.issue_objects.get(pk=issue_id)
issues = issues.filter( issues = issues.filter(
~Q(pk=issue_id), ~Q(pk=issue.parent_id), ~Q(parent_id=issue_id)) ~Q(pk=issue_id), ~Q(pk=issue.parent_id), ~Q(parent_id=issue_id)
)
if issue_relation == "true" and issue_id: if issue_relation == "true" and issue_id:
issue = Issue.issue_objects.get(pk=issue_id) issue = Issue.issue_objects.get(pk=issue_id)
issues = issues.filter( issues = issues.filter(

View File

@ -1,23 +1,22 @@
# Django imports # Django imports
from django.db.models import Q, Count, Case, When, IntegerField from django.db.models import Case, Count, IntegerField, Q, When
# Third party imports # Third party imports
from rest_framework.response import Response
from rest_framework import status from rest_framework import status
from rest_framework.response import Response
# Module imports # Module imports
from plane.app.serializers import ( from plane.app.serializers import (
UserSerializer,
IssueActivitySerializer, IssueActivitySerializer,
UserMeSerializer, UserMeSerializer,
UserMeSettingsSerializer, UserMeSettingsSerializer,
UserSerializer,
) )
from plane.app.views.base import BaseAPIView, BaseViewSet
from plane.app.views.base import BaseViewSet, BaseAPIView from plane.db.models import IssueActivity, ProjectMember, User, WorkspaceMember
from plane.db.models import User, IssueActivity, WorkspaceMember, ProjectMember
from plane.license.models import Instance, InstanceAdmin from plane.license.models import Instance, InstanceAdmin
from plane.utils.paginator import BasePaginator
from plane.utils.cache import cache_response, invalidate_cache from plane.utils.cache import cache_response, invalidate_cache
from plane.utils.paginator import BasePaginator
class UserEndpoint(BaseViewSet): class UserEndpoint(BaseViewSet):

View File

@ -15,11 +15,8 @@ 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.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db.models import Value, UUIDField from django.db.models import UUIDField
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
from django.db.models import Value, UUIDField
# Third party imports # Third party imports
from rest_framework.response import Response from rest_framework.response import Response
@ -146,11 +143,6 @@ class GlobalViewIssuesViewSet(BaseViewSet):
@method_decorator(gzip_page) @method_decorator(gzip_page)
def list(self, request, slug): def list(self, request, slug):
filters = issue_filters(request.query_params, "GET") 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 # Custom ordering for priority and state
priority_order = ["urgent", "high", "medium", "low", "none"] priority_order = ["urgent", "high", "medium", "low", "none"]

View File

@ -41,7 +41,7 @@ class WebhookEndpoint(BaseAPIView):
raise IntegrityError raise IntegrityError
def get(self, request, slug, pk=None): def get(self, request, slug, pk=None):
if pk == None: if pk is None:
webhooks = Webhook.objects.filter(workspace__slug=slug) webhooks = Webhook.objects.filter(workspace__slug=slug)
serializer = WebhookSerializer( serializer = WebhookSerializer(
webhooks, webhooks,

View File

@ -31,7 +31,7 @@ from django.db.models.functions import ExtractWeek, Cast, ExtractDay
from django.db.models.fields import DateField from django.db.models.fields import DateField
from django.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
from django.db.models import Value, UUIDField from django.db.models import UUIDField
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
# Third party modules # Third party modules
@ -1109,7 +1109,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
workspace__slug=slug, workspace__slug=slug,
assignees__in=[user_id], assignees__in=[user_id],
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True project__project_projectmember__is_active=True,
) )
.filter(**filters) .filter(**filters)
.annotate(state_group=F("state__group")) .annotate(state_group=F("state__group"))
@ -1125,7 +1125,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
workspace__slug=slug, workspace__slug=slug,
assignees__in=[user_id], assignees__in=[user_id],
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True project__project_projectmember__is_active=True,
) )
.filter(**filters) .filter(**filters)
.values("priority") .values("priority")
@ -1184,7 +1184,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
assignees__in=[user_id], assignees__in=[user_id],
state__group="completed", state__group="completed",
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True project__project_projectmember__is_active=True,
) )
.filter(**filters) .filter(**filters)
.count() .count()
@ -1195,7 +1195,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
workspace__slug=slug, workspace__slug=slug,
subscriber_id=user_id, subscriber_id=user_id,
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True project__project_projectmember__is_active=True,
) )
.filter(**filters) .filter(**filters)
.count() .count()
@ -1442,7 +1442,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
| Q(issue_subscribers__subscriber_id=user_id), | Q(issue_subscribers__subscriber_id=user_id),
workspace__slug=slug, workspace__slug=slug,
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True project__project_projectmember__is_active=True,
) )
.filter(**filters) .filter(**filters)
.select_related("workspace", "project", "state", "parent") .select_related("workspace", "project", "state", "parent")
@ -1575,7 +1575,7 @@ class WorkspaceLabelsEndpoint(BaseAPIView):
labels = Label.objects.filter( labels = Label.objects.filter(
workspace__slug=slug, workspace__slug=slug,
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True project__project_projectmember__is_active=True,
) )
serializer = LabelSerializer(labels, many=True).data serializer = LabelSerializer(labels, many=True).data
return Response(serializer, status=status.HTTP_200_OK) return Response(serializer, status=status.HTTP_200_OK)
@ -1591,7 +1591,7 @@ class WorkspaceStatesEndpoint(BaseAPIView):
states = State.objects.filter( states = State.objects.filter(
workspace__slug=slug, workspace__slug=slug,
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True project__project_projectmember__is_active=True,
) )
serializer = StateSerializer(states, many=True).data serializer = StateSerializer(states, many=True).data
return Response(serializer, status=status.HTTP_200_OK) return Response(serializer, status=status.HTTP_200_OK)

View File

@ -1,8 +1,6 @@
# Python imports # Python imports
import csv import csv
import io import io
import requests
import json
# Django imports # Django imports
from django.core.mail import EmailMultiAlternatives, get_connection from django.core.mail import EmailMultiAlternatives, get_connection

View File

@ -17,17 +17,20 @@ from plane.db.models import EmailNotificationLog, User, Issue
from plane.license.utils.instance_value import get_email_configuration from plane.license.utils.instance_value import get_email_configuration
from plane.settings.redis import redis_instance from plane.settings.redis import redis_instance
# acquire and delete redis lock # acquire and delete redis lock
def acquire_lock(lock_id, expire_time=300): def acquire_lock(lock_id, expire_time=300):
redis_client = redis_instance() redis_client = redis_instance()
"""Attempt to acquire a lock with a specified expiration time.""" """Attempt to acquire a lock with a specified expiration time."""
return redis_client.set(lock_id, 'true', nx=True, ex=expire_time) return redis_client.set(lock_id, "true", nx=True, ex=expire_time)
def release_lock(lock_id): def release_lock(lock_id):
"""Release a lock.""" """Release a lock."""
redis_client = redis_instance() redis_client = redis_instance()
redis_client.delete(lock_id) redis_client.delete(lock_id)
@shared_task @shared_task
def stack_email_notification(): def stack_email_notification():
# get all email notifications # get all email notifications
@ -66,9 +69,7 @@ def stack_email_notification():
receiver_notification.get("entity_identifier"), {} receiver_notification.get("entity_identifier"), {}
).setdefault( ).setdefault(
str(receiver_notification.get("triggered_by_id")), [] str(receiver_notification.get("triggered_by_id")), []
).append( ).append(receiver_notification.get("data"))
receiver_notification.get("data")
)
# append processed notifications # append processed notifications
processed_notifications.append(receiver_notification.get("id")) processed_notifications.append(receiver_notification.get("id"))
email_notification_ids.append(receiver_notification.get("id")) email_notification_ids.append(receiver_notification.get("id"))
@ -101,31 +102,31 @@ def create_payload(notification_data):
# Append old_value if it's not empty and not already in the list # Append old_value if it's not empty and not already in the list
if old_value: if old_value:
data.setdefault(actor_id, {}).setdefault( (
field, {} data.setdefault(actor_id, {})
).setdefault("old_value", []).append( .setdefault(field, {})
old_value .setdefault("old_value", [])
) if old_value not in data.setdefault( .append(old_value)
actor_id, {} if old_value
).setdefault( not in data.setdefault(actor_id, {})
field, {} .setdefault(field, {})
).get( .get("old_value", [])
"old_value", [] else None
) else None )
# Append new_value if it's not empty and not already in the list # Append new_value if it's not empty and not already in the list
if new_value: if new_value:
data.setdefault(actor_id, {}).setdefault( (
field, {} data.setdefault(actor_id, {})
).setdefault("new_value", []).append( .setdefault(field, {})
new_value .setdefault("new_value", [])
) if new_value not in data.setdefault( .append(new_value)
actor_id, {} if new_value
).setdefault( not in data.setdefault(actor_id, {})
field, {} .setdefault(field, {})
).get( .get("new_value", [])
"new_value", [] else None
) else None )
if not data.get("actor_id", {}).get("activity_time", False): if not data.get("actor_id", {}).get("activity_time", False):
data[actor_id]["activity_time"] = str( data[actor_id]["activity_time"] = str(
@ -136,17 +137,19 @@ def create_payload(notification_data):
return data return data
def process_mention(mention_component): def process_mention(mention_component):
soup = BeautifulSoup(mention_component, 'html.parser') soup = BeautifulSoup(mention_component, "html.parser")
mentions = soup.find_all('mention-component') mentions = soup.find_all("mention-component")
for mention in mentions: for mention in mentions:
user_id = mention['id'] user_id = mention["id"]
user = User.objects.get(pk=user_id) user = User.objects.get(pk=user_id)
user_name = user.display_name user_name = user.display_name
highlighted_name = f"@{user_name}" highlighted_name = f"@{user_name}"
mention.replace_with(highlighted_name) mention.replace_with(highlighted_name)
return str(soup) return str(soup)
def process_html_content(content): def process_html_content(content):
processed_content_list = [] processed_content_list = []
for html_content in content: for html_content in content:
@ -169,7 +172,7 @@ def send_email_notification(
if acquire_lock(lock_id=lock_id): if acquire_lock(lock_id=lock_id):
# get the redis instance # get the redis instance
ri = redis_instance() ri = redis_instance()
base_api = (ri.get(str(issue_id)).decode()) base_api = ri.get(str(issue_id)).decode()
data = create_payload(notification_data=notification_data) data = create_payload(notification_data=notification_data)
# Get email configurations # Get email configurations
@ -206,8 +209,12 @@ def send_email_notification(
} }
) )
if mention: if mention:
mention["new_value"] = process_html_content(mention.get("new_value")) mention["new_value"] = process_html_content(
mention["old_value"] = process_html_content(mention.get("old_value")) mention.get("new_value")
)
mention["old_value"] = process_html_content(
mention.get("old_value")
)
comments.append( comments.append(
{ {
"actor_comments": mention, "actor_comments": mention,
@ -220,7 +227,9 @@ def send_email_notification(
) )
activity_time = changes.pop("activity_time") activity_time = changes.pop("activity_time")
# Parse the input string into a datetime object # Parse the input string into a datetime object
formatted_time = datetime.strptime(activity_time, "%Y-%m-%d %H:%M:%S").strftime("%H:%M %p") formatted_time = datetime.strptime(
activity_time, "%Y-%m-%d %H:%M:%S"
).strftime("%H:%M %p")
if changes: if changes:
template_data.append( template_data.append(
@ -237,12 +246,14 @@ def send_email_notification(
}, },
"activity_time": str(formatted_time), "activity_time": str(formatted_time),
} }
) )
summary = "Updates were made to the issue by" summary = "Updates were made to the issue by"
# Send the mail # Send the mail
subject = f"{issue.project.identifier}-{issue.sequence_id} {issue.name}" subject = (
f"{issue.project.identifier}-{issue.sequence_id} {issue.name}"
)
context = { context = {
"data": template_data, "data": template_data,
"summary": summary, "summary": summary,
@ -257,7 +268,7 @@ def send_email_notification(
}, },
"issue_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/{str(issue.id)}", "issue_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/{str(issue.id)}",
"project_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/", "project_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/",
"workspace":str(issue.project.workspace.slug), "workspace": str(issue.project.workspace.slug),
"project": str(issue.project.name), "project": str(issue.project.name),
"user_preference": f"{base_api}/profile/preferences/email", "user_preference": f"{base_api}/profile/preferences/email",
"comments": comments, "comments": comments,

View File

@ -144,12 +144,17 @@ def generate_table_row(issue):
issue["description_stripped"], issue["description_stripped"],
issue["state__name"], issue["state__name"],
issue["priority"], issue["priority"],
f"{issue['created_by__first_name']} {issue['created_by__last_name']}" (
if issue["created_by__first_name"] and issue["created_by__last_name"] f"{issue['created_by__first_name']} {issue['created_by__last_name']}"
else "", if issue["created_by__first_name"]
f"{issue['assignees__first_name']} {issue['assignees__last_name']}" and issue["created_by__last_name"]
if issue["assignees__first_name"] and issue["assignees__last_name"] else ""
else "", ),
(
f"{issue['assignees__first_name']} {issue['assignees__last_name']}"
if issue["assignees__first_name"] and issue["assignees__last_name"]
else ""
),
issue["labels__name"], issue["labels__name"],
issue["issue_cycle__cycle__name"], issue["issue_cycle__cycle__name"],
dateConverter(issue["issue_cycle__cycle__start_date"]), dateConverter(issue["issue_cycle__cycle__start_date"]),
@ -172,12 +177,17 @@ def generate_json_row(issue):
"Description": issue["description_stripped"], "Description": issue["description_stripped"],
"State": issue["state__name"], "State": issue["state__name"],
"Priority": issue["priority"], "Priority": issue["priority"],
"Created By": f"{issue['created_by__first_name']} {issue['created_by__last_name']}" "Created By": (
if issue["created_by__first_name"] and issue["created_by__last_name"] f"{issue['created_by__first_name']} {issue['created_by__last_name']}"
else "", if issue["created_by__first_name"]
"Assignee": f"{issue['assignees__first_name']} {issue['assignees__last_name']}" and issue["created_by__last_name"]
if issue["assignees__first_name"] and issue["assignees__last_name"] else ""
else "", ),
"Assignee": (
f"{issue['assignees__first_name']} {issue['assignees__last_name']}"
if issue["assignees__first_name"] and issue["assignees__last_name"]
else ""
),
"Labels": issue["labels__name"], "Labels": issue["labels__name"],
"Cycle Name": issue["issue_cycle__cycle__name"], "Cycle Name": issue["issue_cycle__cycle__name"],
"Cycle Start Date": dateConverter( "Cycle Start Date": dateConverter(
@ -292,7 +302,7 @@ def issue_export_task(
workspace__id=workspace_id, workspace__id=workspace_id,
project_id__in=project_ids, project_id__in=project_ids,
project__project_projectmember__member=exporter_instance.initiated_by_id, project__project_projectmember__member=exporter_instance.initiated_by_id,
project__project_projectmember__is_active=True project__project_projectmember__is_active=True,
) )
.select_related( .select_related(
"project", "workspace", "state", "parent", "created_by" "project", "workspace", "state", "parent", "created_by"

View File

@ -1,7 +1,4 @@
# Python import # Python import
import os
import requests
import json
# Django imports # Django imports
from django.core.mail import EmailMultiAlternatives, get_connection from django.core.mail import EmailMultiAlternatives, get_connection
@ -20,9 +17,7 @@ from plane.license.utils.instance_value import get_email_configuration
@shared_task @shared_task
def forgot_password(first_name, email, uidb64, token, current_site): def forgot_password(first_name, email, uidb64, token, current_site):
try: try:
relative_link = ( relative_link = f"/accounts/reset-password/?uidb64={uidb64}&token={token}&email={email}"
f"/accounts/reset-password/?uidb64={uidb64}&token={token}&email={email}"
)
abs_url = str(current_site) + relative_link abs_url = str(current_site) + relative_link
( (

View File

@ -53,7 +53,7 @@ def track_name(
field="name", field="name",
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"updated the name to", comment="updated the name to",
epoch=epoch, epoch=epoch,
) )
) )
@ -96,7 +96,7 @@ def track_description(
field="description", field="description",
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"updated the description to", comment="updated the description to",
epoch=epoch, epoch=epoch,
) )
) )
@ -130,22 +130,26 @@ def track_parent(
issue_id=issue_id, issue_id=issue_id,
actor_id=actor_id, actor_id=actor_id,
verb="updated", verb="updated",
old_value=f"{old_parent.project.identifier}-{old_parent.sequence_id}" old_value=(
if old_parent is not None f"{old_parent.project.identifier}-{old_parent.sequence_id}"
else "", if old_parent is not None
new_value=f"{new_parent.project.identifier}-{new_parent.sequence_id}" else ""
if new_parent is not None ),
else "", new_value=(
f"{new_parent.project.identifier}-{new_parent.sequence_id}"
if new_parent is not None
else ""
),
field="parent", field="parent",
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"updated the parent issue to", comment="updated the parent issue to",
old_identifier=old_parent.id old_identifier=(
if old_parent is not None old_parent.id if old_parent is not None else None
else None, ),
new_identifier=new_parent.id new_identifier=(
if new_parent is not None new_parent.id if new_parent is not None else None
else None, ),
epoch=epoch, epoch=epoch,
) )
) )
@ -173,7 +177,7 @@ def track_priority(
field="priority", field="priority",
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"updated the priority to", comment="updated the priority to",
epoch=epoch, epoch=epoch,
) )
) )
@ -206,7 +210,7 @@ def track_state(
field="state", field="state",
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"updated the state to", comment="updated the state to",
old_identifier=old_state.id, old_identifier=old_state.id,
new_identifier=new_state.id, new_identifier=new_state.id,
epoch=epoch, epoch=epoch,
@ -233,16 +237,20 @@ def track_target_date(
issue_id=issue_id, issue_id=issue_id,
actor_id=actor_id, actor_id=actor_id,
verb="updated", verb="updated",
old_value=current_instance.get("target_date") old_value=(
if current_instance.get("target_date") is not None current_instance.get("target_date")
else "", if current_instance.get("target_date") is not None
new_value=requested_data.get("target_date") else ""
if requested_data.get("target_date") is not None ),
else "", new_value=(
requested_data.get("target_date")
if requested_data.get("target_date") is not None
else ""
),
field="target_date", field="target_date",
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"updated the target date to", comment="updated the target date to",
epoch=epoch, epoch=epoch,
) )
) )
@ -265,16 +273,20 @@ def track_start_date(
issue_id=issue_id, issue_id=issue_id,
actor_id=actor_id, actor_id=actor_id,
verb="updated", verb="updated",
old_value=current_instance.get("start_date") old_value=(
if current_instance.get("start_date") is not None current_instance.get("start_date")
else "", if current_instance.get("start_date") is not None
new_value=requested_data.get("start_date") else ""
if requested_data.get("start_date") is not None ),
else "", new_value=(
requested_data.get("start_date")
if requested_data.get("start_date") is not None
else ""
),
field="start_date", field="start_date",
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"updated the start date to ", comment="updated the start date to ",
epoch=epoch, epoch=epoch,
) )
) )
@ -334,7 +346,7 @@ def track_labels(
field="labels", field="labels",
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"removed label ", comment="removed label ",
old_identifier=label.id, old_identifier=label.id,
new_identifier=None, new_identifier=None,
epoch=epoch, epoch=epoch,
@ -364,7 +376,6 @@ def track_assignees(
else set() else set()
) )
added_assignees = requested_assignees - current_assignees added_assignees = requested_assignees - current_assignees
dropped_assginees = current_assignees - requested_assignees dropped_assginees = current_assignees - requested_assignees
@ -381,7 +392,7 @@ def track_assignees(
field="assignees", field="assignees",
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"added assignee ", comment="added assignee ",
new_identifier=assignee.id, new_identifier=assignee.id,
epoch=epoch, epoch=epoch,
) )
@ -414,7 +425,7 @@ def track_assignees(
field="assignees", field="assignees",
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"removed assignee ", comment="removed assignee ",
old_identifier=assignee.id, old_identifier=assignee.id,
epoch=epoch, epoch=epoch,
) )
@ -439,16 +450,20 @@ def track_estimate_points(
issue_id=issue_id, issue_id=issue_id,
actor_id=actor_id, actor_id=actor_id,
verb="updated", verb="updated",
old_value=current_instance.get("estimate_point") old_value=(
if current_instance.get("estimate_point") is not None current_instance.get("estimate_point")
else "", if current_instance.get("estimate_point") is not None
new_value=requested_data.get("estimate_point") else ""
if requested_data.get("estimate_point") is not None ),
else "", new_value=(
requested_data.get("estimate_point")
if requested_data.get("estimate_point") is not None
else ""
),
field="estimate_point", field="estimate_point",
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"updated the estimate point to ", comment="updated the estimate point to ",
epoch=epoch, epoch=epoch,
) )
) )
@ -529,7 +544,7 @@ def track_closed_to(
field="state", field="state",
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"Plane updated the state to ", comment="Plane updated the state to ",
old_identifier=None, old_identifier=None,
new_identifier=updated_state.id, new_identifier=updated_state.id,
epoch=epoch, epoch=epoch,
@ -552,7 +567,7 @@ def create_issue_activity(
issue_id=issue_id, issue_id=issue_id,
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"created the issue", comment="created the issue",
verb="created", verb="created",
actor_id=actor_id, actor_id=actor_id,
epoch=epoch, epoch=epoch,
@ -635,7 +650,7 @@ def delete_issue_activity(
IssueActivity( IssueActivity(
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"deleted the issue", comment="deleted the issue",
verb="deleted", verb="deleted",
actor_id=actor_id, actor_id=actor_id,
field="issue", field="issue",
@ -666,7 +681,7 @@ def create_comment_activity(
issue_id=issue_id, issue_id=issue_id,
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"created a comment", comment="created a comment",
verb="created", verb="created",
actor_id=actor_id, actor_id=actor_id,
field="comment", field="comment",
@ -703,7 +718,7 @@ def update_comment_activity(
issue_id=issue_id, issue_id=issue_id,
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"updated a comment", comment="updated a comment",
verb="updated", verb="updated",
actor_id=actor_id, actor_id=actor_id,
field="comment", field="comment",
@ -732,7 +747,7 @@ def delete_comment_activity(
issue_id=issue_id, issue_id=issue_id,
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"deleted the comment", comment="deleted the comment",
verb="deleted", verb="deleted",
actor_id=actor_id, actor_id=actor_id,
field="comment", field="comment",
@ -932,7 +947,11 @@ def delete_module_issue_activity(
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"removed this issue from {module_name}", comment=f"removed this issue from {module_name}",
old_identifier=requested_data.get("module_id") if requested_data.get("module_id") is not None else None, old_identifier=(
requested_data.get("module_id")
if requested_data.get("module_id") is not None
else None
),
epoch=epoch, epoch=epoch,
) )
) )
@ -960,7 +979,7 @@ def create_link_activity(
issue_id=issue_id, issue_id=issue_id,
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"created a link", comment="created a link",
verb="created", verb="created",
actor_id=actor_id, actor_id=actor_id,
field="link", field="link",
@ -994,7 +1013,7 @@ def update_link_activity(
issue_id=issue_id, issue_id=issue_id,
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"updated a link", comment="updated a link",
verb="updated", verb="updated",
actor_id=actor_id, actor_id=actor_id,
field="link", field="link",
@ -1026,7 +1045,7 @@ def delete_link_activity(
issue_id=issue_id, issue_id=issue_id,
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"deleted the link", comment="deleted the link",
verb="deleted", verb="deleted",
actor_id=actor_id, actor_id=actor_id,
field="link", field="link",
@ -1059,7 +1078,7 @@ def create_attachment_activity(
issue_id=issue_id, issue_id=issue_id,
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"created an attachment", comment="created an attachment",
verb="created", verb="created",
actor_id=actor_id, actor_id=actor_id,
field="attachment", field="attachment",
@ -1085,7 +1104,7 @@ def delete_attachment_activity(
issue_id=issue_id, issue_id=issue_id,
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"deleted the attachment", comment="deleted the attachment",
verb="deleted", verb="deleted",
actor_id=actor_id, actor_id=actor_id,
field="attachment", field="attachment",
@ -1362,12 +1381,15 @@ def create_issue_relation_activity(
verb="created", verb="created",
old_value="", old_value="",
new_value=f"{issue.project.identifier}-{issue.sequence_id}", new_value=f"{issue.project.identifier}-{issue.sequence_id}",
field="blocking" field=(
if requested_data.get("relation_type") == "blocked_by" "blocking"
else ( if requested_data.get("relation_type") == "blocked_by"
"blocked_by" else (
if requested_data.get("relation_type") == "blocking" "blocked_by"
else requested_data.get("relation_type") if requested_data.get("relation_type")
== "blocking"
else requested_data.get("relation_type")
)
), ),
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
@ -1418,12 +1440,14 @@ def delete_issue_relation_activity(
verb="deleted", verb="deleted",
old_value=f"{issue.project.identifier}-{issue.sequence_id}", old_value=f"{issue.project.identifier}-{issue.sequence_id}",
new_value="", new_value="",
field="blocking" field=(
if requested_data.get("relation_type") == "blocked_by" "blocking"
else ( if requested_data.get("relation_type") == "blocked_by"
"blocked_by" else (
if requested_data.get("relation_type") == "blocking" "blocked_by"
else requested_data.get("relation_type") if requested_data.get("relation_type") == "blocking"
else requested_data.get("relation_type")
)
), ),
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
@ -1449,7 +1473,7 @@ def create_draft_issue_activity(
issue_id=issue_id, issue_id=issue_id,
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"drafted the issue", comment="drafted the issue",
field="draft", field="draft",
verb="created", verb="created",
actor_id=actor_id, actor_id=actor_id,
@ -1476,14 +1500,14 @@ def update_draft_issue_activity(
) )
if ( if (
requested_data.get("is_draft") is not None requested_data.get("is_draft") is not None
and requested_data.get("is_draft") == False and requested_data.get("is_draft") is False
): ):
issue_activities.append( issue_activities.append(
IssueActivity( IssueActivity(
issue_id=issue_id, issue_id=issue_id,
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"created the issue", comment="created the issue",
verb="updated", verb="updated",
actor_id=actor_id, actor_id=actor_id,
epoch=epoch, epoch=epoch,
@ -1495,7 +1519,7 @@ def update_draft_issue_activity(
issue_id=issue_id, issue_id=issue_id,
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"updated the draft issue", comment="updated the draft issue",
field="draft", field="draft",
verb="updated", verb="updated",
actor_id=actor_id, actor_id=actor_id,
@ -1518,7 +1542,7 @@ def delete_draft_issue_activity(
IssueActivity( IssueActivity(
project_id=project_id, project_id=project_id,
workspace_id=workspace_id, workspace_id=workspace_id,
comment=f"deleted the draft issue", comment="deleted the draft issue",
field="draft", field="draft",
verb="deleted", verb="deleted",
actor_id=actor_id, actor_id=actor_id,
@ -1557,7 +1581,7 @@ def issue_activity(
try: try:
issue.updated_at = timezone.now() issue.updated_at = timezone.now()
issue.save(update_fields=["updated_at"]) issue.save(update_fields=["updated_at"])
except Exception as e: except Exception:
pass pass
ACTIVITY_MAPPER = { ACTIVITY_MAPPER = {

View File

@ -79,7 +79,10 @@ def archive_old_issues():
issue_activity.delay( issue_activity.delay(
type="issue.activity.updated", type="issue.activity.updated",
requested_data=json.dumps( requested_data=json.dumps(
{"archived_at": str(archive_at), "automation": True} {
"archived_at": str(archive_at),
"automation": True,
}
), ),
actor_id=str(project.created_by_id), actor_id=str(project.created_by_id),
issue_id=issue.id, issue_id=issue.id,

View File

@ -1,7 +1,4 @@
# Python imports # Python imports
import os
import requests
import json
# Django imports # Django imports
from django.core.mail import EmailMultiAlternatives, get_connection from django.core.mail import EmailMultiAlternatives, get_connection

View File

@ -40,7 +40,9 @@ def update_mentions_for_issue(issue, project, new_mentions, removed_mention):
) )
IssueMention.objects.bulk_create(aggregated_issue_mentions, batch_size=100) IssueMention.objects.bulk_create(aggregated_issue_mentions, batch_size=100)
IssueMention.objects.filter(issue=issue, mention__in=removed_mention).delete() IssueMention.objects.filter(
issue=issue, mention__in=removed_mention
).delete()
def get_new_mentions(requested_instance, current_instance): def get_new_mentions(requested_instance, current_instance):
@ -92,7 +94,9 @@ def extract_mentions_as_subscribers(project_id, issue_id, mentions):
project_id=project_id, project_id=project_id,
).exists() ).exists()
and not IssueAssignee.objects.filter( and not IssueAssignee.objects.filter(
project_id=project_id, issue_id=issue_id, assignee_id=mention_id project_id=project_id,
issue_id=issue_id,
assignee_id=mention_id,
).exists() ).exists()
and not Issue.objects.filter( and not Issue.objects.filter(
project_id=project_id, pk=issue_id, created_by_id=mention_id project_id=project_id, pk=issue_id, created_by_id=mention_id
@ -120,12 +124,14 @@ def extract_mentions(issue_instance):
data = json.loads(issue_instance) data = json.loads(issue_instance)
html = data.get("description_html") html = data.get("description_html")
soup = BeautifulSoup(html, "html.parser") soup = BeautifulSoup(html, "html.parser")
mention_tags = soup.find_all("mention-component", attrs={"target": "users"}) mention_tags = soup.find_all(
"mention-component", attrs={"target": "users"}
)
mentions = [mention_tag["id"] for mention_tag in mention_tags] mentions = [mention_tag["id"] for mention_tag in mention_tags]
return list(set(mentions)) return list(set(mentions))
except Exception as e: except Exception:
return [] return []
@ -134,11 +140,13 @@ def extract_comment_mentions(comment_value):
try: try:
mentions = [] mentions = []
soup = BeautifulSoup(comment_value, "html.parser") soup = BeautifulSoup(comment_value, "html.parser")
mentions_tags = soup.find_all("mention-component", attrs={"target": "users"}) mentions_tags = soup.find_all(
"mention-component", attrs={"target": "users"}
)
for mention_tag in mentions_tags: for mention_tag in mentions_tags:
mentions.append(mention_tag["id"]) mentions.append(mention_tag["id"])
return list(set(mentions)) return list(set(mentions))
except Exception as e: except Exception:
return [] return []
@ -157,7 +165,13 @@ def get_new_comment_mentions(new_value, old_value):
def create_mention_notification( def create_mention_notification(
project, notification_comment, issue, actor_id, mention_id, issue_id, activity project,
notification_comment,
issue,
actor_id,
mention_id,
issue_id,
activity,
): ):
return Notification( return Notification(
workspace=project.workspace, workspace=project.workspace,
@ -304,9 +318,11 @@ def notifications(
# add the user to issue subscriber # add the user to issue subscriber
try: try:
_ = IssueSubscriber.objects.get_or_create( _ = IssueSubscriber.objects.get_or_create(
project_id=project_id, issue_id=issue_id, subscriber_id=actor_id project_id=project_id,
issue_id=issue_id,
subscriber_id=actor_id,
) )
except Exception as e: except Exception:
pass pass
project = Project.objects.get(pk=project_id) project = Project.objects.get(pk=project_id)
@ -336,8 +352,11 @@ def notifications(
for issue_activity in issue_activities_created: for issue_activity in issue_activities_created:
# If activity done in blocking then blocked by email should not go # If activity done in blocking then blocked by email should not go
if issue_activity.get("issue_detail").get("id") != issue_id: if (
continue; issue_activity.get("issue_detail").get("id")
!= issue_id
):
continue
# Do not send notification for description update # Do not send notification for description update
if issue_activity.get("field") == "description": if issue_activity.get("field") == "description":
@ -471,7 +490,9 @@ def notifications(
if issue_comment is not None if issue_comment is not None
else "" else ""
), ),
"activity_time": issue_activity.get("created_at"), "activity_time": issue_activity.get(
"created_at"
),
}, },
}, },
) )
@ -552,7 +573,9 @@ def notifications(
"old_value": str( "old_value": str(
issue_activity.get("old_value") issue_activity.get("old_value")
), ),
"activity_time": issue_activity.get("created_at"), "activity_time": issue_activity.get(
"created_at"
),
}, },
}, },
) )
@ -640,7 +663,9 @@ def notifications(
"old_value": str( "old_value": str(
last_activity.old_value last_activity.old_value
), ),
"activity_time": issue_activity.get("created_at"), "activity_time": issue_activity.get(
"created_at"
),
}, },
}, },
) )
@ -697,7 +722,9 @@ def notifications(
"old_value" "old_value"
) )
), ),
"activity_time": issue_activity.get("created_at"), "activity_time": issue_activity.get(
"created_at"
),
}, },
}, },
) )

View File

@ -1,5 +1,4 @@
# Python import # Python import
import os
# Django imports # Django imports
from django.core.mail import EmailMultiAlternatives, get_connection from django.core.mail import EmailMultiAlternatives, get_connection
@ -75,7 +74,7 @@ def project_invitation(email, project_id, token, current_site, invitor):
msg.attach_alternative(html_content, "text/html") msg.attach_alternative(html_content, "text/html")
msg.send() msg.send()
return return
except (Project.DoesNotExist, ProjectMemberInvite.DoesNotExist) as e: except (Project.DoesNotExist, ProjectMemberInvite.DoesNotExist):
return return
except Exception as e: except Exception as e:
# Print logs if in DEBUG mode # Print logs if in DEBUG mode

View File

@ -215,9 +215,11 @@ def send_webhook(event, payload, kw, action, slug, bulk, current_site):
event_data = [ event_data = [
get_model_data( get_model_data(
event=event, event=event,
event_id=payload.get("id") event_id=(
if isinstance(payload, dict) payload.get("id")
else None, if isinstance(payload, dict)
else None
),
many=False, many=False,
) )
] ]
@ -244,7 +246,9 @@ def send_webhook(event, payload, kw, action, slug, bulk, current_site):
@shared_task @shared_task
def send_webhook_deactivation_email(webhook_id, receiver_id, current_site, reason): def send_webhook_deactivation_email(
webhook_id, receiver_id, current_site, reason
):
# Get email configurations # Get email configurations
( (
EMAIL_HOST, EMAIL_HOST,
@ -257,14 +261,16 @@ def send_webhook_deactivation_email(webhook_id, receiver_id, current_site, reaso
receiver = User.objects.get(pk=receiver_id) receiver = User.objects.get(pk=receiver_id)
webhook = Webhook.objects.get(pk=webhook_id) webhook = Webhook.objects.get(pk=webhook_id)
subject="Webhook Deactivated" subject = "Webhook Deactivated"
message=f"Webhook {webhook.url} has been deactivated due to failed requests." message = (
f"Webhook {webhook.url} has been deactivated due to failed requests."
)
# Send the mail # Send the mail
context = { context = {
"email": receiver.email, "email": receiver.email,
"message": message, "message": message,
"webhook_url":f"{current_site}/{str(webhook.workspace.slug)}/settings/webhooks/{str(webhook.id)}", "webhook_url": f"{current_site}/{str(webhook.workspace.slug)}/settings/webhooks/{str(webhook.id)}",
} }
html_content = render_to_string( html_content = render_to_string(
"emails/notifications/webhook-deactivate.html", context "emails/notifications/webhook-deactivate.html", context

View File

@ -1,7 +1,4 @@
# Python imports # Python imports
import os
import requests
import json
# Django imports # Django imports
from django.core.mail import EmailMultiAlternatives, get_connection from django.core.mail import EmailMultiAlternatives, get_connection
@ -12,8 +9,6 @@ from django.conf import settings
# Third party imports # Third party imports
from celery import shared_task from celery import shared_task
from sentry_sdk import capture_exception from sentry_sdk import capture_exception
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
# Module imports # Module imports
from plane.db.models import Workspace, WorkspaceMemberInvite, User from plane.db.models import Workspace, WorkspaceMemberInvite, User
@ -83,7 +78,7 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor):
msg.send() msg.send()
return return
except (Workspace.DoesNotExist, WorkspaceMemberInvite.DoesNotExist) as e: except (Workspace.DoesNotExist, WorkspaceMemberInvite.DoesNotExist):
print("Workspace or WorkspaceMember Invite Does not exists") print("Workspace or WorkspaceMember Invite Does not exists")
return return
except Exception as e: except Exception as e:

View File

@ -2,7 +2,6 @@ import os
from celery import Celery from celery import Celery
from plane.settings.redis import redis_instance from plane.settings.redis import redis_instance
from celery.schedules import crontab from celery.schedules import crontab
from django.utils.timezone import timedelta
# Set the default Django settings module for the 'celery' program. # Set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
@ -31,7 +30,7 @@ app.conf.beat_schedule = {
}, },
"check-every-five-minutes-to-send-email-notifications": { "check-every-five-minutes-to-send-email-notifications": {
"task": "plane.bgtasks.email_notification_task.stack_email_notification", "task": "plane.bgtasks.email_notification_task.stack_email_notification",
"schedule": crontab(minute='*/5') "schedule": crontab(minute="*/5"),
}, },
} }

View File

@ -52,5 +52,5 @@ class Command(BaseCommand):
user.save() user.save()
self.stdout.write( self.stdout.write(
self.style.SUCCESS(f"User password updated succesfully") self.style.SUCCESS("User password updated succesfully")
) )

View File

@ -4,15 +4,18 @@ from django.core.management.base import BaseCommand
from django.db.migrations.executor import MigrationExecutor from django.db.migrations.executor import MigrationExecutor
from django.db import connections, DEFAULT_DB_ALIAS from django.db import connections, DEFAULT_DB_ALIAS
class Command(BaseCommand): class Command(BaseCommand):
help = 'Wait for database migrations to complete before starting Celery worker/beat' help = "Wait for database migrations to complete before starting Celery worker/beat"
def handle(self, *args, **kwargs): def handle(self, *args, **kwargs):
while self._pending_migrations(): while self._pending_migrations():
self.stdout.write("Waiting for database migrations to complete...") self.stdout.write("Waiting for database migrations to complete...")
time.sleep(10) # wait for 10 seconds before checking again time.sleep(10) # wait for 10 seconds before checking again
self.stdout.write(self.style.SUCCESS("No migrations Pending. Starting processes ...")) self.stdout.write(
self.style.SUCCESS("No migrations Pending. Starting processes ...")
)
def _pending_migrations(self): def _pending_migrations(self):
connection = connections[DEFAULT_DB_ALIAS] connection = connections[DEFAULT_DB_ALIAS]

View File

@ -1,6 +1,6 @@
# Generated by Django 4.2.3 on 2023-07-20 09:35 # Generated by Django 4.2.3 on 2023-07-20 09:35
from django.db import migrations, models from django.db import migrations
def restructure_theming(apps, schema_editor): def restructure_theming(apps, schema_editor):

View File

@ -7,71 +7,204 @@ import uuid
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('db', '0053_auto_20240102_1315'), ("db", "0053_auto_20240102_1315"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Dashboard', name="Dashboard",
fields=[ 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')), "created_at",
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), models.DateTimeField(
('name', models.CharField(max_length=255)), auto_now_add=True, verbose_name="Created At"
('description_html', models.TextField(blank=True, default='<p></p>')), ),
('identifier', models.UUIDField(null=True)), ),
('is_default', models.BooleanField(default=False)), (
('type_identifier', models.CharField(choices=[('workspace', 'Workspace'), ('project', 'Project'), ('home', 'Home'), ('team', 'Team'), ('user', 'User')], default='home', max_length=30, verbose_name='Dashboard Type')), "updated_at",
('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')), models.DateTimeField(
('owned_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dashboards', to=settings.AUTH_USER_MODEL)), auto_now=True, verbose_name="Last Modified At"
('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')), ),
),
(
"id",
models.UUIDField(
db_index=True,
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
("name", models.CharField(max_length=255)),
(
"description_html",
models.TextField(blank=True, default="<p></p>"),
),
("identifier", models.UUIDField(null=True)),
("is_default", models.BooleanField(default=False)),
(
"type_identifier",
models.CharField(
choices=[
("workspace", "Workspace"),
("project", "Project"),
("home", "Home"),
("team", "Team"),
("user", "User"),
],
default="home",
max_length=30,
verbose_name="Dashboard Type",
),
),
(
"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",
),
),
(
"owned_by",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="dashboards",
to=settings.AUTH_USER_MODEL,
),
),
(
"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",
),
),
], ],
options={ options={
'verbose_name': 'Dashboard', "verbose_name": "Dashboard",
'verbose_name_plural': 'Dashboards', "verbose_name_plural": "Dashboards",
'db_table': 'dashboards', "db_table": "dashboards",
'ordering': ('-created_at',), "ordering": ("-created_at",),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Widget', name="Widget",
fields=[ fields=[
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), (
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), "id",
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), models.UUIDField(
('key', models.CharField(max_length=255)), db_index=True,
('filters', models.JSONField(default=dict)), default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
(
"created_at",
models.DateTimeField(
auto_now_add=True, verbose_name="Created At"
),
),
(
"updated_at",
models.DateTimeField(
auto_now=True, verbose_name="Last Modified At"
),
),
("key", models.CharField(max_length=255)),
("filters", models.JSONField(default=dict)),
], ],
options={ options={
'verbose_name': 'Widget', "verbose_name": "Widget",
'verbose_name_plural': 'Widgets', "verbose_name_plural": "Widgets",
'db_table': 'widgets', "db_table": "widgets",
'ordering': ('-created_at',), "ordering": ("-created_at",),
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='DashboardWidget', name="DashboardWidget",
fields=[ 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')), "created_at",
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), models.DateTimeField(
('is_visible', models.BooleanField(default=True)), auto_now_add=True, verbose_name="Created At"
('sort_order', models.FloatField(default=65535)), ),
('filters', models.JSONField(default=dict)), ),
('properties', 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')), "updated_at",
('dashboard', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dashboard_widgets', to='db.dashboard')), models.DateTimeField(
('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')), auto_now=True, verbose_name="Last Modified At"
('widget', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dashboard_widgets', to='db.widget')), ),
),
(
"id",
models.UUIDField(
db_index=True,
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
unique=True,
),
),
("is_visible", models.BooleanField(default=True)),
("sort_order", models.FloatField(default=65535)),
("filters", models.JSONField(default=dict)),
("properties", 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",
),
),
(
"dashboard",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="dashboard_widgets",
to="db.dashboard",
),
),
(
"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",
),
),
(
"widget",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="dashboard_widgets",
to="db.widget",
),
),
], ],
options={ options={
'verbose_name': 'Dashboard Widget', "verbose_name": "Dashboard Widget",
'verbose_name_plural': 'Dashboard Widgets', "verbose_name_plural": "Dashboard Widgets",
'db_table': 'dashboard_widgets', "db_table": "dashboard_widgets",
'ordering': ('-created_at',), "ordering": ("-created_at",),
'unique_together': {('widget', 'dashboard')}, "unique_together": {("widget", "dashboard")},
}, },
), ),
] ]

View File

@ -62,7 +62,7 @@ def create_dashboards(apps, schema_editor):
type_identifier="home", type_identifier="home",
is_default=True, is_default=True,
) )
for user_id in User.objects.values_list('id', flat=True) for user_id in User.objects.values_list("id", flat=True)
], ],
batch_size=2000, batch_size=2000,
) )
@ -78,11 +78,13 @@ def create_dashboard_widgets(apps, schema_editor):
widget_id=widget_id, widget_id=widget_id,
dashboard_id=dashboard_id, dashboard_id=dashboard_id,
) )
for widget_id in Widget.objects.values_list('id', flat=True) for widget_id in Widget.objects.values_list("id", flat=True)
for dashboard_id in Dashboard.objects.values_list('id', flat=True) for dashboard_id in Dashboard.objects.values_list("id", flat=True)
] ]
DashboardWidget.objects.bulk_create(updated_dashboard_widget, batch_size=2000) DashboardWidget.objects.bulk_create(
updated_dashboard_widget, batch_size=2000
)
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -2,12 +2,17 @@
from django.db import migrations from django.db import migrations
def create_notification_preferences(apps, schema_editor): def create_notification_preferences(apps, schema_editor):
UserNotificationPreference = apps.get_model("db", "UserNotificationPreference") UserNotificationPreference = apps.get_model(
"db", "UserNotificationPreference"
)
User = apps.get_model("db", "User") User = apps.get_model("db", "User")
bulk_notification_preferences = [] bulk_notification_preferences = []
for user_id in User.objects.filter(is_bot=False).values_list("id", flat=True): for user_id in User.objects.filter(is_bot=False).values_list(
"id", flat=True
):
bulk_notification_preferences.append( bulk_notification_preferences.append(
UserNotificationPreference( UserNotificationPreference(
user_id=user_id, user_id=user_id,
@ -18,11 +23,10 @@ def create_notification_preferences(apps, schema_editor):
bulk_notification_preferences, batch_size=1000, ignore_conflicts=True bulk_notification_preferences, batch_size=1000, ignore_conflicts=True
) )
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("db", "0056_usernotificationpreference_emailnotificationlog"), ("db", "0056_usernotificationpreference_emailnotificationlog"),
] ]
operations = [ operations = [migrations.RunPython(create_notification_preferences)]
migrations.RunPython(create_notification_preferences)
]

View File

@ -5,19 +5,22 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('db', '0057_auto_20240122_0901'), ("db", "0057_auto_20240122_0901"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='moduleissue', model_name="moduleissue",
name='issue', name="issue",
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issue_module', to='db.issue'), field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="issue_module",
to="db.issue",
),
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='moduleissue', name="moduleissue",
unique_together={('issue', 'module')}, unique_together={("issue", "module")},
), ),
] ]

View File

@ -24,10 +24,9 @@ def widgets_filter_change(apps, schema_editor):
# Bulk update the widgets # Bulk update the widgets
Widget.objects.bulk_update(widgets_to_update, ["filters"], batch_size=10) Widget.objects.bulk_update(widgets_to_update, ["filters"], batch_size=10)
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('db', '0058_alter_moduleissue_issue_and_more'), ("db", "0058_alter_moduleissue_issue_and_more"),
]
operations = [
migrations.RunPython(widgets_filter_change)
] ]
operations = [migrations.RunPython(widgets_filter_change)]

View File

@ -4,15 +4,14 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('db', '0059_auto_20240208_0957'), ("db", "0059_auto_20240208_0957"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='cycle', model_name="cycle",
name='progress_snapshot', name="progress_snapshot",
field=models.JSONField(default=dict), field=models.JSONField(default=dict),
), ),
] ]

View File

@ -1,12 +1,10 @@
# Python imports # Python imports
import uuid
# Django imports # Django imports
from django.db import models from django.db import models
class TimeAuditModel(models.Model): class TimeAuditModel(models.Model):
"""To path when the record was created and last modified""" """To path when the record was created and last modified"""
created_at = models.DateTimeField( created_at = models.DateTimeField(
@ -22,7 +20,6 @@ class TimeAuditModel(models.Model):
class UserAuditModel(models.Model): class UserAuditModel(models.Model):
"""To path when the record was created and last modified""" """To path when the record was created and last modified"""
created_by = models.ForeignKey( created_by = models.ForeignKey(
@ -45,7 +42,6 @@ class UserAuditModel(models.Model):
class AuditModel(TimeAuditModel, UserAuditModel): class AuditModel(TimeAuditModel, UserAuditModel):
"""To path when the record was created and last modified""" """To path when the record was created and last modified"""
class Meta: class Meta:

View File

@ -85,7 +85,11 @@ from .inbox import Inbox, InboxIssue
from .analytic import AnalyticView from .analytic import AnalyticView
from .notification import Notification, UserNotificationPreference, EmailNotificationLog from .notification import (
Notification,
UserNotificationPreference,
EmailNotificationLog,
)
from .exporter import ExporterHistory from .exporter import ExporterHistory

View File

@ -1,6 +1,5 @@
# Django models # Django models
from django.db import models from django.db import models
from django.conf import settings
from .base import BaseModel from .base import BaseModel

View File

@ -2,12 +2,12 @@ import uuid
# Django imports # Django imports
from django.db import models from django.db import models
from django.conf import settings
# Module imports # Module imports
from . import BaseModel from . import BaseModel
from ..mixins import TimeAuditModel from ..mixins import TimeAuditModel
class Dashboard(BaseModel): class Dashboard(BaseModel):
DASHBOARD_CHOICES = ( DASHBOARD_CHOICES = (
("workspace", "Workspace"), ("workspace", "Workspace"),
@ -45,7 +45,11 @@ class Dashboard(BaseModel):
class Widget(TimeAuditModel): class Widget(TimeAuditModel):
id = models.UUIDField( id = models.UUIDField(
default=uuid.uuid4, unique=True, editable=False, db_index=True, primary_key=True default=uuid.uuid4,
unique=True,
editable=False,
db_index=True,
primary_key=True,
) )
key = models.CharField(max_length=255) key = models.CharField(max_length=255)
filters = models.JSONField(default=dict) filters = models.JSONField(default=dict)

View File

@ -1,5 +1,4 @@
# Python imports # Python imports
import uuid
# Django imports # Django imports
from django.db import models from django.db import models

View File

@ -1,5 +1,4 @@
# Python imports # Python imports
import uuid
# Django imports # Django imports
from django.db import models from django.db import models

View File

@ -5,6 +5,7 @@ from django.conf import settings
# Module imports # Module imports
from . import BaseModel from . import BaseModel
class Notification(BaseModel): class Notification(BaseModel):
workspace = models.ForeignKey( workspace = models.ForeignKey(
"db.Workspace", related_name="notifications", on_delete=models.CASCADE "db.Workspace", related_name="notifications", on_delete=models.CASCADE
@ -105,10 +106,19 @@ class UserNotificationPreference(BaseModel):
"""Return the user""" """Return the user"""
return f"<{self.user}>" return f"<{self.user}>"
class EmailNotificationLog(BaseModel): class EmailNotificationLog(BaseModel):
# receiver # receiver
receiver = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="email_notifications") receiver = models.ForeignKey(
triggered_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="triggered_emails") settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="email_notifications",
)
triggered_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="triggered_emails",
)
# entity - can be issues, pages, etc. # entity - can be issues, pages, etc.
entity_identifier = models.UUIDField(null=True) entity_identifier = models.UUIDField(null=True)
entity_name = models.CharField(max_length=255) entity_name = models.CharField(max_length=255)

View File

@ -138,13 +138,13 @@ class User(AbstractBaseUser, PermissionsMixin):
super(User, self).save(*args, **kwargs) super(User, self).save(*args, **kwargs)
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def create_user_notification(sender, instance, created, **kwargs): def create_user_notification(sender, instance, created, **kwargs):
# create preferences # create preferences
if created and not instance.is_bot: if created and not instance.is_bot:
# Module imports # Module imports
from plane.db.models import UserNotificationPreference from plane.db.models import UserNotificationPreference
UserNotificationPreference.objects.create( UserNotificationPreference.objects.create(
user=instance, user=instance,
property_change=False, property_change=False,

View File

@ -116,9 +116,7 @@ class InstanceAdminEndpoint(BaseAPIView):
@invalidate_cache(path="/api/instances/", user=False) @invalidate_cache(path="/api/instances/", user=False)
def delete(self, request, pk): def delete(self, request, pk):
instance = Instance.objects.first() instance = Instance.objects.first()
instance_admin = InstanceAdmin.objects.filter( InstanceAdmin.objects.filter(instance=instance, pk=pk).delete()
instance=instance, pk=pk
).delete()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@ -204,7 +202,7 @@ class InstanceAdminSignInEndpoint(BaseAPIView):
email = email.strip().lower() email = email.strip().lower()
try: try:
validate_email(email) validate_email(email)
except ValidationError as e: except ValidationError:
return Response( return Response(
{"error": "Please provide a valid email address."}, {"error": "Please provide a valid email address."},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,

View File

@ -3,7 +3,6 @@ import os
# Django imports # Django imports
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.conf import settings
# Module imports # Module imports
from plane.license.models import InstanceConfiguration from plane.license.models import InstanceConfiguration

View File

@ -1,6 +1,5 @@
# Python imports # Python imports
import json import json
import requests
import secrets import secrets
# Django imports # Django imports
@ -56,9 +55,9 @@ class Command(BaseCommand):
user_count=payload.get("user_count", 0), user_count=payload.get("user_count", 0),
) )
self.stdout.write(self.style.SUCCESS(f"Instance registered")) self.stdout.write(self.style.SUCCESS("Instance registered"))
else: else:
self.stdout.write( self.stdout.write(
self.style.SUCCESS(f"Instance already registered") self.style.SUCCESS("Instance already registered")
) )
return return

View File

@ -1,4 +1,4 @@
from plane.db.models import APIToken, APIActivityLog from plane.db.models import APIActivityLog
class APITokenLogMiddleware: class APITokenLogMiddleware:
@ -39,6 +39,5 @@ class APITokenLogMiddleware:
except Exception as e: except Exception as e:
print(e) print(e)
# If the token does not exist, you can decide whether to log this as an invalid attempt # If the token does not exist, you can decide whether to log this as an invalid attempt
pass
return None return None

View File

@ -1,5 +1,7 @@
"""Development settings""" """Development settings"""
import os
from .common import * # noqa from .common import * # noqa
DEBUG = True DEBUG = True

View File

@ -1,4 +1,6 @@
"""Production settings""" """Production settings"""
import os
from .common import * # noqa from .common import * # noqa
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!

View File

@ -1,4 +1,3 @@
import os
import redis import redis
from django.conf import settings from django.conf import settings
from urllib.parse import urlparse from urllib.parse import urlparse

View File

@ -1,4 +1,5 @@
"""Test Settings""" """Test Settings"""
from .common import * # noqa from .common import * # noqa
DEBUG = True DEBUG = True

View File

@ -10,7 +10,6 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError
# Third part imports # Third part imports
from rest_framework import status from rest_framework import status
from rest_framework import status
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.exceptions import APIException from rest_framework.exceptions import APIException
@ -85,11 +84,8 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
) )
if isinstance(e, ObjectDoesNotExist): if isinstance(e, ObjectDoesNotExist):
model_name = str(exc).split(" matching query does not exist.")[
0
]
return Response( return Response(
{"error": f"The required object does not exist."}, {"error": "The required object does not exist."},
status=status.HTTP_404_NOT_FOUND, status=status.HTTP_404_NOT_FOUND,
) )
@ -179,7 +175,7 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
if isinstance(e, ObjectDoesNotExist): if isinstance(e, ObjectDoesNotExist):
return Response( return Response(
{"error": f"The required object does not exist."}, {"error": "The required object does not exist."},
status=status.HTTP_404_NOT_FOUND, status=status.HTTP_404_NOT_FOUND,
) )

View File

@ -134,7 +134,7 @@ class InboxIssuePublicViewSet(BaseViewSet):
) )
# Check for valid priority # Check for valid priority
if not request.data.get("issue", {}).get("priority", "none") in [ if request.data.get("issue", {}).get("priority", "none") not in [
"low", "low",
"medium", "medium",
"high", "high",

View File

@ -9,7 +9,6 @@ from django.db.models import (
Func, Func,
F, F,
Q, Q,
Count,
Case, Case,
Value, Value,
CharField, CharField,
@ -514,9 +513,13 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
] ]
def get(self, request, slug, project_id): def get(self, request, slug, project_id):
project_deploy_board = ProjectDeployBoard.objects.get( if not ProjectDeployBoard.objects.filter(
workspace__slug=slug, project_id=project_id workspace__slug=slug, project_id=project_id
) ).exists():
return Response(
{"error": "Project is not published"},
status=status.HTTP_404_NOT_FOUND,
)
filters = issue_filters(request.query_params, "GET") filters = issue_filters(request.query_params, "GET")

View File

@ -12,7 +12,6 @@ from rest_framework.permissions import AllowAny
# Module imports # Module imports
from .base import BaseAPIView from .base import BaseAPIView
from plane.app.serializers import ProjectDeployBoardSerializer from plane.app.serializers import ProjectDeployBoardSerializer
from plane.app.permissions import ProjectMemberPermission
from plane.db.models import ( from plane.db.models import (
Project, Project,
ProjectDeployBoard, ProjectDeployBoard,

View File

@ -5,7 +5,6 @@ import re
from django.db.models import Q from django.db.models import Q
# Module imports # Module imports
from plane.db.models import Issue
def search_issues(query, queryset): def search_issues(query, queryset):

View File

@ -193,7 +193,7 @@ class BasePaginator:
cursor_result = paginator.get_result( cursor_result = paginator.get_result(
limit=per_page, cursor=input_cursor limit=per_page, cursor=input_cursor
) )
except BadPaginationError as e: except BadPaginationError:
raise ParseError(detail="Error in parsing") raise ParseError(detail="Error in parsing")
# Serialize result according to the on_result function # Serialize result according to the on_result function

View File

@ -1,3 +1 @@
from django.shortcuts import render
# Create your views here. # Create your views here.

View File

@ -16,3 +16,10 @@ exclude = '''
| venv | venv
)/ )/
''' '''
[tool.ruff]
line-length = 79
exclude = [
"**/__init__.py",
]