forked from github/plane
[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:
parent
c16a5b9b71
commit
1fa47a6c04
@ -6,8 +6,6 @@ from plane.db.models import (
|
||||
Project,
|
||||
ProjectIdentifier,
|
||||
WorkspaceMember,
|
||||
State,
|
||||
Estimate,
|
||||
)
|
||||
from .base import BaseSerializer
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
# Python imports
|
||||
import zoneinfo
|
||||
import json
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
@ -115,13 +114,13 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
||||
|
||||
if isinstance(e, ObjectDoesNotExist):
|
||||
return Response(
|
||||
{"error": f"The required object does not exist."},
|
||||
{"error": "The required object does not exist."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
if isinstance(e, KeyError):
|
||||
return Response(
|
||||
{"error": f" The required key does not exist."},
|
||||
{"error": " The required key does not exist."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
import json
|
||||
|
||||
# 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.core import serializers
|
||||
|
||||
@ -321,7 +321,9 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
|
||||
and Cycle.objects.filter(
|
||||
project_id=project_id,
|
||||
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"),
|
||||
).exists()
|
||||
):
|
||||
|
@ -119,7 +119,7 @@ class InboxIssueAPIEndpoint(BaseAPIView):
|
||||
)
|
||||
|
||||
# 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",
|
||||
"medium",
|
||||
"high",
|
||||
|
@ -1,22 +1,22 @@
|
||||
# Python imports
|
||||
import json
|
||||
from itertools import chain
|
||||
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
|
||||
# Django imports
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import (
|
||||
OuterRef,
|
||||
Func,
|
||||
Q,
|
||||
F,
|
||||
Case,
|
||||
When,
|
||||
Value,
|
||||
CharField,
|
||||
Max,
|
||||
Exists,
|
||||
F,
|
||||
Func,
|
||||
Max,
|
||||
OuterRef,
|
||||
Q,
|
||||
Value,
|
||||
When,
|
||||
)
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.utils import timezone
|
||||
|
||||
# Third party imports
|
||||
@ -24,30 +24,31 @@ from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
# 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 (
|
||||
IssueActivitySerializer,
|
||||
IssueCommentSerializer,
|
||||
IssueLinkSerializer,
|
||||
IssueSerializer,
|
||||
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):
|
||||
@ -653,7 +654,6 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
|
||||
)
|
||||
|
||||
def post(self, request, slug, project_id, issue_id):
|
||||
|
||||
# Validation check if the issue already exists
|
||||
if (
|
||||
request.data.get("external_id")
|
||||
@ -679,7 +679,6 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
|
||||
status=status.HTTP_409_CONFLICT,
|
||||
)
|
||||
|
||||
|
||||
serializer = IssueCommentSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
@ -717,7 +716,10 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
|
||||
# Validation check if the issue already exists
|
||||
if (
|
||||
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(
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
@ -735,7 +737,6 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
|
||||
status=status.HTTP_409_CONFLICT,
|
||||
)
|
||||
|
||||
|
||||
serializer = IssueCommentSerializer(
|
||||
issue_comment, data=request.data, partial=True
|
||||
)
|
||||
|
@ -178,7 +178,9 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
|
||||
and Module.objects.filter(
|
||||
project_id=project_id,
|
||||
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"),
|
||||
).exists()
|
||||
):
|
||||
|
@ -11,7 +11,6 @@ from rest_framework.serializers import ValidationError
|
||||
from plane.db.models import (
|
||||
Workspace,
|
||||
Project,
|
||||
ProjectFavorite,
|
||||
ProjectMember,
|
||||
ProjectDeployBoard,
|
||||
State,
|
||||
@ -150,7 +149,7 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
||||
serializer.save()
|
||||
|
||||
# Add the user as Administrator to the project
|
||||
project_member = ProjectMember.objects.create(
|
||||
_ = ProjectMember.objects.create(
|
||||
project_id=serializer.data["id"],
|
||||
member=request.user,
|
||||
role=20,
|
||||
@ -245,12 +244,12 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
||||
{"name": "The project name is already taken"},
|
||||
status=status.HTTP_410_GONE,
|
||||
)
|
||||
except Workspace.DoesNotExist as e:
|
||||
except Workspace.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "Workspace does not exist"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
except ValidationError as e:
|
||||
except ValidationError:
|
||||
return Response(
|
||||
{"identifier": "The project identifier is already taken"},
|
||||
status=status.HTTP_410_GONE,
|
||||
@ -307,7 +306,7 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
|
||||
{"error": "Project does not exist"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
except ValidationError as e:
|
||||
except ValidationError:
|
||||
return Response(
|
||||
{"identifier": "The project identifier is already taken"},
|
||||
status=status.HTTP_410_GONE,
|
||||
|
@ -66,8 +66,10 @@ class StateAPIEndpoint(BaseAPIView):
|
||||
|
||||
serializer.save(project_id=project_id)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
except IntegrityError as e:
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
except IntegrityError:
|
||||
state = State.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
@ -136,7 +138,9 @@ class StateAPIEndpoint(BaseAPIView):
|
||||
and State.objects.filter(
|
||||
project_id=project_id,
|
||||
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"),
|
||||
).exists()
|
||||
):
|
||||
|
@ -111,7 +111,10 @@ from .inbox import (
|
||||
|
||||
from .analytic import AnalyticViewSerializer
|
||||
|
||||
from .notification import NotificationSerializer, UserNotificationPreferenceSerializer
|
||||
from .notification import (
|
||||
NotificationSerializer,
|
||||
UserNotificationPreferenceSerializer,
|
||||
)
|
||||
|
||||
from .exporter import ExporterHistorySerializer
|
||||
|
||||
|
@ -11,6 +11,7 @@ from plane.db.models import (
|
||||
CycleUserProperties,
|
||||
)
|
||||
|
||||
|
||||
class CycleWriteSerializer(BaseSerializer):
|
||||
def validate(self, data):
|
||||
if (
|
||||
@ -47,7 +48,6 @@ class CycleSerializer(BaseSerializer):
|
||||
# active | draft | upcoming | completed
|
||||
status = serializers.CharField(read_only=True)
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Cycle
|
||||
fields = [
|
||||
|
@ -18,9 +18,4 @@ class WidgetSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Widget
|
||||
fields = [
|
||||
"id",
|
||||
"key",
|
||||
"is_visible",
|
||||
"widget_filters"
|
||||
]
|
||||
fields = ["id", "key", "is_visible", "widget_filters"]
|
||||
|
@ -74,5 +74,3 @@ class WorkspaceEstimateSerializer(BaseSerializer):
|
||||
"name",
|
||||
"description",
|
||||
]
|
||||
|
||||
|
||||
|
@ -9,7 +9,7 @@ from rest_framework import serializers
|
||||
# Module imports
|
||||
from .base import BaseSerializer, DynamicBaseSerializer
|
||||
from .user import UserLiteSerializer
|
||||
from .state import StateSerializer, StateLiteSerializer
|
||||
from .state import StateLiteSerializer
|
||||
from .project import ProjectLiteSerializer
|
||||
from .workspace import WorkspaceLiteSerializer
|
||||
from plane.db.models import (
|
||||
@ -33,7 +33,6 @@ from plane.db.models import (
|
||||
IssueVote,
|
||||
IssueRelation,
|
||||
State,
|
||||
Project,
|
||||
)
|
||||
|
||||
|
||||
@ -472,7 +471,6 @@ class IssueLinkSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class IssueLinkLiteSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = IssueLink
|
||||
fields = [
|
||||
@ -503,7 +501,6 @@ class IssueAttachmentSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class IssueAttachmentLiteSerializer(DynamicBaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = IssueAttachment
|
||||
fields = [
|
||||
@ -532,7 +529,6 @@ class IssueReactionSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class IssueReactionLiteSerializer(DynamicBaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = IssueReaction
|
||||
fields = [
|
||||
@ -628,15 +624,18 @@ class IssueSerializer(DynamicBaseSerializer):
|
||||
# ids
|
||||
cycle_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||
module_ids = serializers.ListField(
|
||||
child=serializers.UUIDField(), required=False,
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
)
|
||||
|
||||
# Many to many
|
||||
label_ids = serializers.ListField(
|
||||
child=serializers.UUIDField(), required=False,
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
)
|
||||
assignee_ids = serializers.ListField(
|
||||
child=serializers.UUIDField(), required=False,
|
||||
child=serializers.UUIDField(),
|
||||
required=False,
|
||||
)
|
||||
|
||||
# Count items
|
||||
@ -676,19 +675,7 @@ class IssueSerializer(DynamicBaseSerializer):
|
||||
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 Meta:
|
||||
model = Issue
|
||||
fields = [
|
||||
|
@ -3,7 +3,6 @@ from rest_framework import serializers
|
||||
|
||||
# Module imports
|
||||
from .base import BaseSerializer, DynamicBaseSerializer
|
||||
from .user import UserLiteSerializer
|
||||
from .project import ProjectLiteSerializer
|
||||
|
||||
from plane.db.models import (
|
||||
@ -142,7 +141,6 @@ class ModuleIssueSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class ModuleLinkSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = ModuleLink
|
||||
fields = "__all__"
|
||||
@ -215,13 +213,11 @@ class ModuleSerializer(DynamicBaseSerializer):
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
|
||||
class ModuleDetailSerializer(ModuleSerializer):
|
||||
|
||||
link_module = ModuleLinkSerializer(read_only=True, many=True)
|
||||
|
||||
class Meta(ModuleSerializer.Meta):
|
||||
fields = ModuleSerializer.Meta.fields + ['link_module']
|
||||
fields = ModuleSerializer.Meta.fields + ["link_module"]
|
||||
|
||||
|
||||
class ModuleFavoriteSerializer(BaseSerializer):
|
||||
|
@ -15,7 +15,6 @@ class NotificationSerializer(BaseSerializer):
|
||||
|
||||
|
||||
class UserNotificationPreferenceSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = UserNotificationPreference
|
||||
fields = "__all__"
|
||||
|
@ -3,7 +3,7 @@ from rest_framework import serializers
|
||||
|
||||
# Module imports
|
||||
from .base import BaseSerializer
|
||||
from .issue import IssueFlatSerializer, LabelLiteSerializer
|
||||
from .issue import LabelLiteSerializer
|
||||
from .workspace import WorkspaceLiteSerializer
|
||||
from .project import ProjectLiteSerializer
|
||||
from plane.db.models import (
|
||||
@ -12,8 +12,6 @@ from plane.db.models import (
|
||||
PageFavorite,
|
||||
PageLabel,
|
||||
Label,
|
||||
Issue,
|
||||
Module,
|
||||
)
|
||||
|
||||
|
||||
|
@ -4,7 +4,6 @@ from rest_framework import serializers
|
||||
# Module import
|
||||
from .base import BaseSerializer
|
||||
from plane.db.models import User, Workspace, WorkspaceMemberInvite
|
||||
from plane.license.models import InstanceAdmin, Instance
|
||||
|
||||
|
||||
class UserSerializer(BaseSerializer):
|
||||
@ -99,13 +98,13 @@ class UserMeSettingsSerializer(BaseSerializer):
|
||||
).first()
|
||||
return {
|
||||
"last_workspace_id": obj.last_workspace_id,
|
||||
"last_workspace_slug": workspace.slug
|
||||
if workspace is not None
|
||||
else "",
|
||||
"last_workspace_slug": (
|
||||
workspace.slug if workspace is not None else ""
|
||||
),
|
||||
"fallback_workspace_id": obj.last_workspace_id,
|
||||
"fallback_workspace_slug": workspace.slug
|
||||
if workspace is not None
|
||||
else "",
|
||||
"fallback_workspace_slug": (
|
||||
workspace.slug if workspace is not None else ""
|
||||
),
|
||||
"invites": workspace_invites,
|
||||
}
|
||||
else:
|
||||
@ -120,12 +119,16 @@ class UserMeSettingsSerializer(BaseSerializer):
|
||||
return {
|
||||
"last_workspace_id": None,
|
||||
"last_workspace_slug": None,
|
||||
"fallback_workspace_id": fallback_workspace.id
|
||||
"fallback_workspace_id": (
|
||||
fallback_workspace.id
|
||||
if fallback_workspace is not None
|
||||
else None,
|
||||
"fallback_workspace_slug": fallback_workspace.slug
|
||||
else None
|
||||
),
|
||||
"fallback_workspace_slug": (
|
||||
fallback_workspace.slug
|
||||
if fallback_workspace is not None
|
||||
else None,
|
||||
else None
|
||||
),
|
||||
"invites": workspace_invites,
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Python imports
|
||||
import urllib
|
||||
import socket
|
||||
import ipaddress
|
||||
from urllib.parse import urlparse
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -164,9 +164,6 @@ from .webhook import (
|
||||
WebhookSecretRegenerateEndpoint,
|
||||
)
|
||||
|
||||
from .dashboard import (
|
||||
DashboardEndpoint,
|
||||
WidgetsEndpoint
|
||||
)
|
||||
from .dashboard import DashboardEndpoint, WidgetsEndpoint
|
||||
|
||||
from .error_404 import custom_404_view
|
||||
|
@ -1,5 +1,5 @@
|
||||
# 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.utils import timezone
|
||||
|
||||
@ -10,7 +10,7 @@ from rest_framework.response import Response
|
||||
# Module imports
|
||||
from plane.app.views import BaseAPIView, BaseViewSet
|
||||
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.utils.analytics_plot import build_graph_plot
|
||||
from plane.bgtasks.analytic_plot_export import analytic_export_task
|
||||
@ -51,8 +51,8 @@ class AnalyticsEndpoint(BaseAPIView):
|
||||
if (
|
||||
not x_axis
|
||||
or not y_axis
|
||||
or not x_axis in valid_xaxis_segment
|
||||
or not y_axis in valid_yaxis
|
||||
or x_axis not in valid_xaxis_segment
|
||||
or y_axis not in valid_yaxis
|
||||
):
|
||||
return Response(
|
||||
{
|
||||
@ -266,8 +266,8 @@ class ExportAnalyticsEndpoint(BaseAPIView):
|
||||
if (
|
||||
not x_axis
|
||||
or not y_axis
|
||||
or not x_axis in valid_xaxis_segment
|
||||
or not y_axis in valid_yaxis
|
||||
or x_axis not in valid_xaxis_segment
|
||||
or y_axis not in valid_yaxis
|
||||
):
|
||||
return Response(
|
||||
{
|
||||
|
@ -43,7 +43,7 @@ class ApiTokenEndpoint(BaseAPIView):
|
||||
)
|
||||
|
||||
def get(self, request, slug, pk=None):
|
||||
if pk == None:
|
||||
if pk is None:
|
||||
api_tokens = APIToken.objects.filter(
|
||||
user=request.user, workspace__slug=slug
|
||||
)
|
||||
|
@ -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.core.validators import validate_email
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.conf import settings
|
||||
|
||||
## Third Party Imports
|
||||
from rest_framework import status
|
||||
@ -172,7 +171,7 @@ class ResetPasswordEndpoint(BaseAPIView):
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
except DjangoUnicodeDecodeError as indentifier:
|
||||
except DjangoUnicodeDecodeError:
|
||||
return Response(
|
||||
{"error": "token is not valid, please check the new one"},
|
||||
status=status.HTTP_401_UNAUTHORIZED,
|
||||
|
@ -7,7 +7,6 @@ import json
|
||||
from django.utils import timezone
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_email
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
|
||||
# Third party imports
|
||||
@ -65,7 +64,7 @@ class SignUpEndpoint(BaseAPIView):
|
||||
email = email.strip().lower()
|
||||
try:
|
||||
validate_email(email)
|
||||
except ValidationError as e:
|
||||
except ValidationError:
|
||||
return Response(
|
||||
{"error": "Please provide a valid email address."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
@ -151,7 +150,7 @@ class SignInEndpoint(BaseAPIView):
|
||||
email = email.strip().lower()
|
||||
try:
|
||||
validate_email(email)
|
||||
except ValidationError as e:
|
||||
except ValidationError:
|
||||
return Response(
|
||||
{"error": "Please provide a valid email address."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
@ -238,9 +237,11 @@ class SignInEndpoint(BaseAPIView):
|
||||
[
|
||||
WorkspaceMember(
|
||||
workspace_id=project_member_invite.workspace_id,
|
||||
role=project_member_invite.role
|
||||
role=(
|
||||
project_member_invite.role
|
||||
if project_member_invite.role in [5, 10, 15]
|
||||
else 15,
|
||||
else 15
|
||||
),
|
||||
member=user,
|
||||
created_by_id=project_member_invite.created_by_id,
|
||||
)
|
||||
@ -254,9 +255,11 @@ class SignInEndpoint(BaseAPIView):
|
||||
[
|
||||
ProjectMember(
|
||||
workspace_id=project_member_invite.workspace_id,
|
||||
role=project_member_invite.role
|
||||
role=(
|
||||
project_member_invite.role
|
||||
if project_member_invite.role in [5, 10, 15]
|
||||
else 15,
|
||||
else 15
|
||||
),
|
||||
member=user,
|
||||
created_by_id=project_member_invite.created_by_id,
|
||||
)
|
||||
@ -392,9 +395,11 @@ class MagicSignInEndpoint(BaseAPIView):
|
||||
[
|
||||
WorkspaceMember(
|
||||
workspace_id=project_member_invite.workspace_id,
|
||||
role=project_member_invite.role
|
||||
role=(
|
||||
project_member_invite.role
|
||||
if project_member_invite.role in [5, 10, 15]
|
||||
else 15,
|
||||
else 15
|
||||
),
|
||||
member=user,
|
||||
created_by_id=project_member_invite.created_by_id,
|
||||
)
|
||||
@ -408,9 +413,11 @@ class MagicSignInEndpoint(BaseAPIView):
|
||||
[
|
||||
ProjectMember(
|
||||
workspace_id=project_member_invite.workspace_id,
|
||||
role=project_member_invite.role
|
||||
role=(
|
||||
project_member_invite.role
|
||||
if project_member_invite.role in [5, 10, 15]
|
||||
else 15,
|
||||
else 15
|
||||
),
|
||||
member=user,
|
||||
created_by_id=project_member_invite.created_by_id,
|
||||
)
|
||||
|
@ -1,6 +1,5 @@
|
||||
# Python imports
|
||||
import zoneinfo
|
||||
import json
|
||||
|
||||
# Django imports
|
||||
from django.urls import resolve
|
||||
@ -8,11 +7,9 @@ from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.db import IntegrityError
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
|
||||
# Third part imports
|
||||
from rest_framework import status
|
||||
from rest_framework import status
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.exceptions import APIException
|
||||
@ -119,14 +116,14 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
|
||||
|
||||
if isinstance(e, ObjectDoesNotExist):
|
||||
return Response(
|
||||
{"error": f"The required object does not exist."},
|
||||
{"error": "The required object does not exist."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
if isinstance(e, KeyError):
|
||||
capture_exception(e)
|
||||
return Response(
|
||||
{"error": f"The required key does not exist."},
|
||||
{"error": "The required key does not exist."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@ -226,13 +223,13 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
||||
|
||||
if isinstance(e, ObjectDoesNotExist):
|
||||
return Response(
|
||||
{"error": f"The required object does not exist."},
|
||||
{"error": "The required object does not exist."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
if isinstance(e, KeyError):
|
||||
return Response(
|
||||
{"error": f"The required key does not exist."},
|
||||
{"error": "The required key does not exist."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
import os
|
||||
|
||||
# Django imports
|
||||
from django.conf import settings
|
||||
|
||||
# Third party imports
|
||||
from rest_framework.permissions import AllowAny
|
||||
|
@ -10,7 +10,6 @@ from django.db.models import (
|
||||
OuterRef,
|
||||
Count,
|
||||
Prefetch,
|
||||
Sum,
|
||||
Case,
|
||||
When,
|
||||
Value,
|
||||
@ -22,7 +21,7 @@ from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.gzip import gzip_page
|
||||
from django.contrib.postgres.aggregates import ArrayAgg
|
||||
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
|
||||
|
||||
# Third party imports
|
||||
@ -328,14 +327,14 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
}
|
||||
|
||||
if data[0]["start_date"] and data[0]["end_date"]:
|
||||
data[0]["distribution"]["completion_chart"] = (
|
||||
burndown_plot(
|
||||
data[0]["distribution"][
|
||||
"completion_chart"
|
||||
] = burndown_plot(
|
||||
queryset=queryset.first(),
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
cycle_id=data[0]["id"],
|
||||
)
|
||||
)
|
||||
|
||||
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):
|
||||
queryset = (
|
||||
self.get_queryset()
|
||||
.filter(workspace__slug=slug, project_id=project_id, pk=pk)
|
||||
queryset = self.get_queryset().filter(
|
||||
workspace__slug=slug, project_id=project_id, pk=pk
|
||||
)
|
||||
cycle = queryset.first()
|
||||
request_data = request.data
|
||||
@ -883,7 +881,9 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
|
||||
# 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
|
||||
issue_activity.delay(
|
||||
type="cycle.activity.created",
|
||||
|
@ -9,7 +9,6 @@ from django.db.models import (
|
||||
F,
|
||||
Exists,
|
||||
OuterRef,
|
||||
Max,
|
||||
Subquery,
|
||||
JSONField,
|
||||
Func,
|
||||
@ -18,7 +17,7 @@ from django.db.models import (
|
||||
)
|
||||
from django.contrib.postgres.aggregates import ArrayAgg
|
||||
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.utils import timezone
|
||||
|
||||
|
@ -50,7 +50,7 @@ class ExportIssuesEndpoint(BaseAPIView):
|
||||
)
|
||||
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,
|
||||
)
|
||||
|
@ -8,7 +8,6 @@ from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
# Django imports
|
||||
from django.conf import settings
|
||||
|
||||
# Module imports
|
||||
from .base import BaseAPIView
|
||||
|
@ -213,7 +213,7 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
)
|
||||
|
||||
# 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",
|
||||
"medium",
|
||||
"high",
|
||||
@ -428,7 +428,10 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
)
|
||||
).first()
|
||||
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)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
@ -24,7 +24,7 @@ from django.views.decorators.gzip import gzip_page
|
||||
from django.db import IntegrityError
|
||||
from django.contrib.postgres.aggregates import ArrayAgg
|
||||
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
|
||||
|
||||
# Third Party imports
|
||||
@ -82,7 +82,6 @@ from plane.utils.cache import invalidate_cache
|
||||
|
||||
|
||||
class IssueListEndpoint(BaseAPIView):
|
||||
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
@ -96,7 +95,9 @@ class IssueListEndpoint(BaseAPIView):
|
||||
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 = (
|
||||
Issue.issue_objects.filter(
|
||||
@ -1662,12 +1663,19 @@ class IssueArchiveViewSet(BaseViewSet):
|
||||
)
|
||||
if issue.state.group not in ["completed", "cancelled"]:
|
||||
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,
|
||||
)
|
||||
issue_activity.delay(
|
||||
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),
|
||||
issue_id=str(issue.id),
|
||||
project_id=str(project_id),
|
||||
@ -1681,8 +1689,9 @@ class IssueArchiveViewSet(BaseViewSet):
|
||||
issue.archived_at = timezone.now().date()
|
||||
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):
|
||||
issue = Issue.objects.get(
|
||||
@ -2209,12 +2218,6 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
@method_decorator(gzip_page)
|
||||
def list(self, request, slug, project_id):
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
fields = [
|
||||
field
|
||||
for field in request.GET.get("fields", "").split(",")
|
||||
if field
|
||||
]
|
||||
|
||||
# Custom ordering for priority and state
|
||||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||
state_order = [
|
||||
@ -2373,7 +2376,9 @@ class IssueDraftViewSet(BaseViewSet):
|
||||
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():
|
||||
serializer.save()
|
||||
|
@ -17,7 +17,10 @@ from plane.db.models import (
|
||||
WorkspaceMember,
|
||||
UserNotificationPreference,
|
||||
)
|
||||
from plane.app.serializers import NotificationSerializer, UserNotificationPreferenceSerializer
|
||||
from plane.app.serializers import (
|
||||
NotificationSerializer,
|
||||
UserNotificationPreferenceSerializer,
|
||||
)
|
||||
|
||||
|
||||
class NotificationViewSet(BaseViewSet, BasePaginator):
|
||||
|
@ -5,7 +5,6 @@ import os
|
||||
|
||||
# Django imports
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
# Third Party modules
|
||||
from rest_framework.response import Response
|
||||
@ -250,9 +249,11 @@ class OauthEndpoint(BaseAPIView):
|
||||
[
|
||||
WorkspaceMember(
|
||||
workspace_id=project_member_invite.workspace_id,
|
||||
role=project_member_invite.role
|
||||
role=(
|
||||
project_member_invite.role
|
||||
if project_member_invite.role in [5, 10, 15]
|
||||
else 15,
|
||||
else 15
|
||||
),
|
||||
member=user,
|
||||
created_by_id=project_member_invite.created_by_id,
|
||||
)
|
||||
@ -266,9 +267,11 @@ class OauthEndpoint(BaseAPIView):
|
||||
[
|
||||
ProjectMember(
|
||||
workspace_id=project_member_invite.workspace_id,
|
||||
role=project_member_invite.role
|
||||
role=(
|
||||
project_member_invite.role
|
||||
if project_member_invite.role in [5, 10, 15]
|
||||
else 15,
|
||||
else 15
|
||||
),
|
||||
member=user,
|
||||
created_by_id=project_member_invite.created_by_id,
|
||||
)
|
||||
@ -391,9 +394,11 @@ class OauthEndpoint(BaseAPIView):
|
||||
[
|
||||
WorkspaceMember(
|
||||
workspace_id=project_member_invite.workspace_id,
|
||||
role=project_member_invite.role
|
||||
role=(
|
||||
project_member_invite.role
|
||||
if project_member_invite.role in [5, 10, 15]
|
||||
else 15,
|
||||
else 15
|
||||
),
|
||||
member=user,
|
||||
created_by_id=project_member_invite.created_by_id,
|
||||
)
|
||||
@ -407,9 +412,11 @@ class OauthEndpoint(BaseAPIView):
|
||||
[
|
||||
ProjectMember(
|
||||
workspace_id=project_member_invite.workspace_id,
|
||||
role=project_member_invite.role
|
||||
role=(
|
||||
project_member_invite.role
|
||||
if project_member_invite.role in [5, 10, 15]
|
||||
else 15,
|
||||
else 15
|
||||
),
|
||||
member=user,
|
||||
created_by_id=project_member_invite.created_by_id,
|
||||
)
|
||||
|
@ -1,22 +1,29 @@
|
||||
# Python imports
|
||||
from datetime import date, datetime, timedelta
|
||||
from datetime import datetime
|
||||
|
||||
# Django imports
|
||||
from django.db import connection
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.gzip import gzip_page
|
||||
|
||||
# Third party imports
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
from plane.app.permissions import ProjectEntityPermission
|
||||
from plane.app.serializers import (IssueLiteSerializer, PageFavoriteSerializer,
|
||||
PageLogSerializer, PageSerializer,
|
||||
SubPageSerializer)
|
||||
from plane.db.models import (Issue, IssueActivity, IssueAssignee, Page,
|
||||
PageFavorite, PageLog, ProjectMember)
|
||||
from plane.app.serializers import (
|
||||
PageFavoriteSerializer,
|
||||
PageLogSerializer,
|
||||
PageSerializer,
|
||||
SubPageSerializer,
|
||||
)
|
||||
from plane.db.models import (
|
||||
Page,
|
||||
PageFavorite,
|
||||
PageLog,
|
||||
ProjectMember,
|
||||
)
|
||||
|
||||
# Module imports
|
||||
from .base import BaseAPIView, BaseViewSet
|
||||
|
@ -1,72 +1,70 @@
|
||||
# Python imports
|
||||
import jwt
|
||||
import boto3
|
||||
from datetime import datetime
|
||||
|
||||
import boto3
|
||||
import jwt
|
||||
from django.conf import settings
|
||||
|
||||
# Django imports
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_email
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import (
|
||||
Prefetch,
|
||||
Q,
|
||||
Exists,
|
||||
OuterRef,
|
||||
F,
|
||||
Func,
|
||||
OuterRef,
|
||||
Prefetch,
|
||||
Q,
|
||||
Subquery,
|
||||
)
|
||||
from django.core.validators import validate_email
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
# Third Party imports
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework import serializers
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
|
||||
# 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 (
|
||||
WorkspaceUserPermission,
|
||||
ProjectBasePermission,
|
||||
ProjectMemberPermission,
|
||||
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 (
|
||||
Project,
|
||||
ProjectMember,
|
||||
Workspace,
|
||||
ProjectMemberInvite,
|
||||
User,
|
||||
WorkspaceMember,
|
||||
State,
|
||||
TeamMember,
|
||||
ProjectFavorite,
|
||||
ProjectIdentifier,
|
||||
Module,
|
||||
Cycle,
|
||||
Inbox,
|
||||
ProjectDeployBoard,
|
||||
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 .base import BaseAPIView, BaseViewSet, WebhookMixin
|
||||
|
||||
|
||||
class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
serializer_class = ProjectListSerializer
|
||||
model = Project
|
||||
@ -173,10 +171,7 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
for field in request.GET.get("fields", "").split(",")
|
||||
if field
|
||||
]
|
||||
projects = (
|
||||
self.get_queryset()
|
||||
.order_by("sort_order", "name")
|
||||
)
|
||||
projects = self.get_queryset().order_by("sort_order", "name")
|
||||
if request.GET.get("per_page", False) and request.GET.get(
|
||||
"cursor", False
|
||||
):
|
||||
@ -298,12 +293,12 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
{"name": "The project name is already taken"},
|
||||
status=status.HTTP_410_GONE,
|
||||
)
|
||||
except Workspace.DoesNotExist as e:
|
||||
except Workspace.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "Workspace does not exist"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
except serializers.ValidationError as e:
|
||||
except serializers.ValidationError:
|
||||
return Response(
|
||||
{"identifier": "The project identifier is already taken"},
|
||||
status=status.HTTP_410_GONE,
|
||||
@ -362,7 +357,7 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
{"error": "Project does not exist"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
except serializers.ValidationError as e:
|
||||
except serializers.ValidationError:
|
||||
return Response(
|
||||
{"identifier": "The project identifier is already taken"},
|
||||
status=status.HTTP_410_GONE,
|
||||
@ -576,9 +571,11 @@ class ProjectJoinEndpoint(BaseAPIView):
|
||||
_ = WorkspaceMember.objects.create(
|
||||
workspace_id=project_invite.workspace_id,
|
||||
member=user,
|
||||
role=15
|
||||
role=(
|
||||
15
|
||||
if project_invite.role >= 15
|
||||
else project_invite.role,
|
||||
else project_invite.role
|
||||
),
|
||||
)
|
||||
else:
|
||||
# Else make him active
|
||||
@ -685,9 +682,14 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||
)
|
||||
|
||||
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
|
||||
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.is_active = True
|
||||
bulk_project_members.append(project_member)
|
||||
@ -710,9 +712,9 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||
role=member.get("role", 10),
|
||||
project_id=project_id,
|
||||
workspace_id=project.workspace_id,
|
||||
sort_order=sort_order[0] - 10000
|
||||
if len(sort_order)
|
||||
else 65535,
|
||||
sort_order=(
|
||||
sort_order[0] - 10000 if len(sort_order) else 65535
|
||||
),
|
||||
)
|
||||
)
|
||||
bulk_issue_props.append(
|
||||
@ -733,7 +735,10 @@ class ProjectMemberViewSet(BaseViewSet):
|
||||
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)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
@ -254,7 +254,8 @@ class IssueSearchEndpoint(BaseAPIView):
|
||||
if parent == "true" and issue_id:
|
||||
issue = Issue.issue_objects.get(pk=issue_id)
|
||||
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:
|
||||
issue = Issue.issue_objects.get(pk=issue_id)
|
||||
issues = issues.filter(
|
||||
|
@ -1,23 +1,22 @@
|
||||
# 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
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
# Module imports
|
||||
from plane.app.serializers import (
|
||||
UserSerializer,
|
||||
IssueActivitySerializer,
|
||||
UserMeSerializer,
|
||||
UserMeSettingsSerializer,
|
||||
UserSerializer,
|
||||
)
|
||||
|
||||
from plane.app.views.base import BaseViewSet, BaseAPIView
|
||||
from plane.db.models import User, IssueActivity, WorkspaceMember, ProjectMember
|
||||
from plane.app.views.base import BaseAPIView, BaseViewSet
|
||||
from plane.db.models import IssueActivity, ProjectMember, User, WorkspaceMember
|
||||
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.paginator import BasePaginator
|
||||
|
||||
|
||||
class UserEndpoint(BaseViewSet):
|
||||
|
@ -15,11 +15,8 @@ from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.gzip import gzip_page
|
||||
from django.contrib.postgres.aggregates import ArrayAgg
|
||||
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.contrib.postgres.aggregates import ArrayAgg
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.db.models import Value, UUIDField
|
||||
|
||||
# Third party imports
|
||||
from rest_framework.response import Response
|
||||
@ -146,11 +143,6 @@ class GlobalViewIssuesViewSet(BaseViewSet):
|
||||
@method_decorator(gzip_page)
|
||||
def list(self, request, slug):
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
fields = [
|
||||
field
|
||||
for field in request.GET.get("fields", "").split(",")
|
||||
if field
|
||||
]
|
||||
|
||||
# Custom ordering for priority and state
|
||||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||
|
@ -41,7 +41,7 @@ class WebhookEndpoint(BaseAPIView):
|
||||
raise IntegrityError
|
||||
|
||||
def get(self, request, slug, pk=None):
|
||||
if pk == None:
|
||||
if pk is None:
|
||||
webhooks = Webhook.objects.filter(workspace__slug=slug)
|
||||
serializer = WebhookSerializer(
|
||||
webhooks,
|
||||
|
@ -31,7 +31,7 @@ from django.db.models.functions import ExtractWeek, Cast, ExtractDay
|
||||
from django.db.models.fields import DateField
|
||||
from django.contrib.postgres.aggregates import ArrayAgg
|
||||
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
|
||||
|
||||
# Third party modules
|
||||
@ -1109,7 +1109,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
||||
workspace__slug=slug,
|
||||
assignees__in=[user_id],
|
||||
project__project_projectmember__member=request.user,
|
||||
project__project_projectmember__is_active=True
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
.filter(**filters)
|
||||
.annotate(state_group=F("state__group"))
|
||||
@ -1125,7 +1125,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
||||
workspace__slug=slug,
|
||||
assignees__in=[user_id],
|
||||
project__project_projectmember__member=request.user,
|
||||
project__project_projectmember__is_active=True
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
.filter(**filters)
|
||||
.values("priority")
|
||||
@ -1184,7 +1184,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
||||
assignees__in=[user_id],
|
||||
state__group="completed",
|
||||
project__project_projectmember__member=request.user,
|
||||
project__project_projectmember__is_active=True
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
.filter(**filters)
|
||||
.count()
|
||||
@ -1195,7 +1195,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
|
||||
workspace__slug=slug,
|
||||
subscriber_id=user_id,
|
||||
project__project_projectmember__member=request.user,
|
||||
project__project_projectmember__is_active=True
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
.filter(**filters)
|
||||
.count()
|
||||
@ -1442,7 +1442,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
||||
| Q(issue_subscribers__subscriber_id=user_id),
|
||||
workspace__slug=slug,
|
||||
project__project_projectmember__member=request.user,
|
||||
project__project_projectmember__is_active=True
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
.filter(**filters)
|
||||
.select_related("workspace", "project", "state", "parent")
|
||||
@ -1575,7 +1575,7 @@ class WorkspaceLabelsEndpoint(BaseAPIView):
|
||||
labels = Label.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project__project_projectmember__member=request.user,
|
||||
project__project_projectmember__is_active=True
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
serializer = LabelSerializer(labels, many=True).data
|
||||
return Response(serializer, status=status.HTTP_200_OK)
|
||||
@ -1591,7 +1591,7 @@ class WorkspaceStatesEndpoint(BaseAPIView):
|
||||
states = State.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project__project_projectmember__member=request.user,
|
||||
project__project_projectmember__is_active=True
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
serializer = StateSerializer(states, many=True).data
|
||||
return Response(serializer, status=status.HTTP_200_OK)
|
||||
|
@ -1,8 +1,6 @@
|
||||
# Python imports
|
||||
import csv
|
||||
import io
|
||||
import requests
|
||||
import json
|
||||
|
||||
# Django imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
|
@ -17,17 +17,20 @@ from plane.db.models import EmailNotificationLog, User, Issue
|
||||
from plane.license.utils.instance_value import get_email_configuration
|
||||
from plane.settings.redis import redis_instance
|
||||
|
||||
|
||||
# acquire and delete redis lock
|
||||
def acquire_lock(lock_id, expire_time=300):
|
||||
redis_client = redis_instance()
|
||||
"""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):
|
||||
"""Release a lock."""
|
||||
redis_client = redis_instance()
|
||||
redis_client.delete(lock_id)
|
||||
|
||||
|
||||
@shared_task
|
||||
def stack_email_notification():
|
||||
# get all email notifications
|
||||
@ -66,9 +69,7 @@ def stack_email_notification():
|
||||
receiver_notification.get("entity_identifier"), {}
|
||||
).setdefault(
|
||||
str(receiver_notification.get("triggered_by_id")), []
|
||||
).append(
|
||||
receiver_notification.get("data")
|
||||
)
|
||||
).append(receiver_notification.get("data"))
|
||||
# append processed notifications
|
||||
processed_notifications.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
|
||||
if old_value:
|
||||
data.setdefault(actor_id, {}).setdefault(
|
||||
field, {}
|
||||
).setdefault("old_value", []).append(
|
||||
old_value
|
||||
) if old_value not in data.setdefault(
|
||||
actor_id, {}
|
||||
).setdefault(
|
||||
field, {}
|
||||
).get(
|
||||
"old_value", []
|
||||
) else None
|
||||
(
|
||||
data.setdefault(actor_id, {})
|
||||
.setdefault(field, {})
|
||||
.setdefault("old_value", [])
|
||||
.append(old_value)
|
||||
if old_value
|
||||
not in data.setdefault(actor_id, {})
|
||||
.setdefault(field, {})
|
||||
.get("old_value", [])
|
||||
else None
|
||||
)
|
||||
|
||||
# Append new_value if it's not empty and not already in the list
|
||||
if new_value:
|
||||
data.setdefault(actor_id, {}).setdefault(
|
||||
field, {}
|
||||
).setdefault("new_value", []).append(
|
||||
new_value
|
||||
) if new_value not in data.setdefault(
|
||||
actor_id, {}
|
||||
).setdefault(
|
||||
field, {}
|
||||
).get(
|
||||
"new_value", []
|
||||
) else None
|
||||
(
|
||||
data.setdefault(actor_id, {})
|
||||
.setdefault(field, {})
|
||||
.setdefault("new_value", [])
|
||||
.append(new_value)
|
||||
if new_value
|
||||
not in data.setdefault(actor_id, {})
|
||||
.setdefault(field, {})
|
||||
.get("new_value", [])
|
||||
else None
|
||||
)
|
||||
|
||||
if not data.get("actor_id", {}).get("activity_time", False):
|
||||
data[actor_id]["activity_time"] = str(
|
||||
@ -136,17 +137,19 @@ def create_payload(notification_data):
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def process_mention(mention_component):
|
||||
soup = BeautifulSoup(mention_component, 'html.parser')
|
||||
mentions = soup.find_all('mention-component')
|
||||
soup = BeautifulSoup(mention_component, "html.parser")
|
||||
mentions = soup.find_all("mention-component")
|
||||
for mention in mentions:
|
||||
user_id = mention['id']
|
||||
user_id = mention["id"]
|
||||
user = User.objects.get(pk=user_id)
|
||||
user_name = user.display_name
|
||||
highlighted_name = f"@{user_name}"
|
||||
mention.replace_with(highlighted_name)
|
||||
return str(soup)
|
||||
|
||||
|
||||
def process_html_content(content):
|
||||
processed_content_list = []
|
||||
for html_content in content:
|
||||
@ -169,7 +172,7 @@ def send_email_notification(
|
||||
if acquire_lock(lock_id=lock_id):
|
||||
# get the 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)
|
||||
|
||||
# Get email configurations
|
||||
@ -206,8 +209,12 @@ def send_email_notification(
|
||||
}
|
||||
)
|
||||
if mention:
|
||||
mention["new_value"] = process_html_content(mention.get("new_value"))
|
||||
mention["old_value"] = process_html_content(mention.get("old_value"))
|
||||
mention["new_value"] = process_html_content(
|
||||
mention.get("new_value")
|
||||
)
|
||||
mention["old_value"] = process_html_content(
|
||||
mention.get("old_value")
|
||||
)
|
||||
comments.append(
|
||||
{
|
||||
"actor_comments": mention,
|
||||
@ -220,7 +227,9 @@ def send_email_notification(
|
||||
)
|
||||
activity_time = changes.pop("activity_time")
|
||||
# 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:
|
||||
template_data.append(
|
||||
@ -242,7 +251,9 @@ def send_email_notification(
|
||||
summary = "Updates were made to the issue by"
|
||||
|
||||
# Send the mail
|
||||
subject = f"{issue.project.identifier}-{issue.sequence_id} {issue.name}"
|
||||
subject = (
|
||||
f"{issue.project.identifier}-{issue.sequence_id} {issue.name}"
|
||||
)
|
||||
context = {
|
||||
"data": template_data,
|
||||
"summary": summary,
|
||||
|
@ -144,12 +144,17 @@ def generate_table_row(issue):
|
||||
issue["description_stripped"],
|
||||
issue["state__name"],
|
||||
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"]
|
||||
else "",
|
||||
if issue["created_by__first_name"]
|
||||
and issue["created_by__last_name"]
|
||||
else ""
|
||||
),
|
||||
(
|
||||
f"{issue['assignees__first_name']} {issue['assignees__last_name']}"
|
||||
if issue["assignees__first_name"] and issue["assignees__last_name"]
|
||||
else "",
|
||||
else ""
|
||||
),
|
||||
issue["labels__name"],
|
||||
issue["issue_cycle__cycle__name"],
|
||||
dateConverter(issue["issue_cycle__cycle__start_date"]),
|
||||
@ -172,12 +177,17 @@ def generate_json_row(issue):
|
||||
"Description": issue["description_stripped"],
|
||||
"State": issue["state__name"],
|
||||
"Priority": issue["priority"],
|
||||
"Created By": f"{issue['created_by__first_name']} {issue['created_by__last_name']}"
|
||||
if issue["created_by__first_name"] and issue["created_by__last_name"]
|
||||
else "",
|
||||
"Assignee": f"{issue['assignees__first_name']} {issue['assignees__last_name']}"
|
||||
"Created By": (
|
||||
f"{issue['created_by__first_name']} {issue['created_by__last_name']}"
|
||||
if issue["created_by__first_name"]
|
||||
and issue["created_by__last_name"]
|
||||
else ""
|
||||
),
|
||||
"Assignee": (
|
||||
f"{issue['assignees__first_name']} {issue['assignees__last_name']}"
|
||||
if issue["assignees__first_name"] and issue["assignees__last_name"]
|
||||
else "",
|
||||
else ""
|
||||
),
|
||||
"Labels": issue["labels__name"],
|
||||
"Cycle Name": issue["issue_cycle__cycle__name"],
|
||||
"Cycle Start Date": dateConverter(
|
||||
@ -292,7 +302,7 @@ def issue_export_task(
|
||||
workspace__id=workspace_id,
|
||||
project_id__in=project_ids,
|
||||
project__project_projectmember__member=exporter_instance.initiated_by_id,
|
||||
project__project_projectmember__is_active=True
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
.select_related(
|
||||
"project", "workspace", "state", "parent", "created_by"
|
||||
|
@ -1,7 +1,4 @@
|
||||
# Python import
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
|
||||
# Django imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
@ -20,9 +17,7 @@ from plane.license.utils.instance_value import get_email_configuration
|
||||
@shared_task
|
||||
def forgot_password(first_name, email, uidb64, token, current_site):
|
||||
try:
|
||||
relative_link = (
|
||||
f"/accounts/reset-password/?uidb64={uidb64}&token={token}&email={email}"
|
||||
)
|
||||
relative_link = f"/accounts/reset-password/?uidb64={uidb64}&token={token}&email={email}"
|
||||
abs_url = str(current_site) + relative_link
|
||||
|
||||
(
|
||||
|
@ -53,7 +53,7 @@ def track_name(
|
||||
field="name",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"updated the name to",
|
||||
comment="updated the name to",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
@ -96,7 +96,7 @@ def track_description(
|
||||
field="description",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"updated the description to",
|
||||
comment="updated the description to",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
@ -130,22 +130,26 @@ def track_parent(
|
||||
issue_id=issue_id,
|
||||
actor_id=actor_id,
|
||||
verb="updated",
|
||||
old_value=f"{old_parent.project.identifier}-{old_parent.sequence_id}"
|
||||
old_value=(
|
||||
f"{old_parent.project.identifier}-{old_parent.sequence_id}"
|
||||
if old_parent is not None
|
||||
else "",
|
||||
new_value=f"{new_parent.project.identifier}-{new_parent.sequence_id}"
|
||||
else ""
|
||||
),
|
||||
new_value=(
|
||||
f"{new_parent.project.identifier}-{new_parent.sequence_id}"
|
||||
if new_parent is not None
|
||||
else "",
|
||||
else ""
|
||||
),
|
||||
field="parent",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"updated the parent issue to",
|
||||
old_identifier=old_parent.id
|
||||
if old_parent is not None
|
||||
else None,
|
||||
new_identifier=new_parent.id
|
||||
if new_parent is not None
|
||||
else None,
|
||||
comment="updated the parent issue to",
|
||||
old_identifier=(
|
||||
old_parent.id if old_parent is not None else None
|
||||
),
|
||||
new_identifier=(
|
||||
new_parent.id if new_parent is not None else None
|
||||
),
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
@ -173,7 +177,7 @@ def track_priority(
|
||||
field="priority",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"updated the priority to",
|
||||
comment="updated the priority to",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
@ -206,7 +210,7 @@ def track_state(
|
||||
field="state",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"updated the state to",
|
||||
comment="updated the state to",
|
||||
old_identifier=old_state.id,
|
||||
new_identifier=new_state.id,
|
||||
epoch=epoch,
|
||||
@ -233,16 +237,20 @@ def track_target_date(
|
||||
issue_id=issue_id,
|
||||
actor_id=actor_id,
|
||||
verb="updated",
|
||||
old_value=current_instance.get("target_date")
|
||||
old_value=(
|
||||
current_instance.get("target_date")
|
||||
if current_instance.get("target_date") is not None
|
||||
else "",
|
||||
new_value=requested_data.get("target_date")
|
||||
else ""
|
||||
),
|
||||
new_value=(
|
||||
requested_data.get("target_date")
|
||||
if requested_data.get("target_date") is not None
|
||||
else "",
|
||||
else ""
|
||||
),
|
||||
field="target_date",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"updated the target date to",
|
||||
comment="updated the target date to",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
@ -265,16 +273,20 @@ def track_start_date(
|
||||
issue_id=issue_id,
|
||||
actor_id=actor_id,
|
||||
verb="updated",
|
||||
old_value=current_instance.get("start_date")
|
||||
old_value=(
|
||||
current_instance.get("start_date")
|
||||
if current_instance.get("start_date") is not None
|
||||
else "",
|
||||
new_value=requested_data.get("start_date")
|
||||
else ""
|
||||
),
|
||||
new_value=(
|
||||
requested_data.get("start_date")
|
||||
if requested_data.get("start_date") is not None
|
||||
else "",
|
||||
else ""
|
||||
),
|
||||
field="start_date",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"updated the start date to ",
|
||||
comment="updated the start date to ",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
@ -334,7 +346,7 @@ def track_labels(
|
||||
field="labels",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"removed label ",
|
||||
comment="removed label ",
|
||||
old_identifier=label.id,
|
||||
new_identifier=None,
|
||||
epoch=epoch,
|
||||
@ -364,7 +376,6 @@ def track_assignees(
|
||||
else set()
|
||||
)
|
||||
|
||||
|
||||
added_assignees = requested_assignees - current_assignees
|
||||
dropped_assginees = current_assignees - requested_assignees
|
||||
|
||||
@ -381,7 +392,7 @@ def track_assignees(
|
||||
field="assignees",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"added assignee ",
|
||||
comment="added assignee ",
|
||||
new_identifier=assignee.id,
|
||||
epoch=epoch,
|
||||
)
|
||||
@ -414,7 +425,7 @@ def track_assignees(
|
||||
field="assignees",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"removed assignee ",
|
||||
comment="removed assignee ",
|
||||
old_identifier=assignee.id,
|
||||
epoch=epoch,
|
||||
)
|
||||
@ -439,16 +450,20 @@ def track_estimate_points(
|
||||
issue_id=issue_id,
|
||||
actor_id=actor_id,
|
||||
verb="updated",
|
||||
old_value=current_instance.get("estimate_point")
|
||||
old_value=(
|
||||
current_instance.get("estimate_point")
|
||||
if current_instance.get("estimate_point") is not None
|
||||
else "",
|
||||
new_value=requested_data.get("estimate_point")
|
||||
else ""
|
||||
),
|
||||
new_value=(
|
||||
requested_data.get("estimate_point")
|
||||
if requested_data.get("estimate_point") is not None
|
||||
else "",
|
||||
else ""
|
||||
),
|
||||
field="estimate_point",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"updated the estimate point to ",
|
||||
comment="updated the estimate point to ",
|
||||
epoch=epoch,
|
||||
)
|
||||
)
|
||||
@ -529,7 +544,7 @@ def track_closed_to(
|
||||
field="state",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"Plane updated the state to ",
|
||||
comment="Plane updated the state to ",
|
||||
old_identifier=None,
|
||||
new_identifier=updated_state.id,
|
||||
epoch=epoch,
|
||||
@ -552,7 +567,7 @@ def create_issue_activity(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"created the issue",
|
||||
comment="created the issue",
|
||||
verb="created",
|
||||
actor_id=actor_id,
|
||||
epoch=epoch,
|
||||
@ -635,7 +650,7 @@ def delete_issue_activity(
|
||||
IssueActivity(
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"deleted the issue",
|
||||
comment="deleted the issue",
|
||||
verb="deleted",
|
||||
actor_id=actor_id,
|
||||
field="issue",
|
||||
@ -666,7 +681,7 @@ def create_comment_activity(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"created a comment",
|
||||
comment="created a comment",
|
||||
verb="created",
|
||||
actor_id=actor_id,
|
||||
field="comment",
|
||||
@ -703,7 +718,7 @@ def update_comment_activity(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"updated a comment",
|
||||
comment="updated a comment",
|
||||
verb="updated",
|
||||
actor_id=actor_id,
|
||||
field="comment",
|
||||
@ -732,7 +747,7 @@ def delete_comment_activity(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"deleted the comment",
|
||||
comment="deleted the comment",
|
||||
verb="deleted",
|
||||
actor_id=actor_id,
|
||||
field="comment",
|
||||
@ -932,7 +947,11 @@ def delete_module_issue_activity(
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
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,
|
||||
)
|
||||
)
|
||||
@ -960,7 +979,7 @@ def create_link_activity(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"created a link",
|
||||
comment="created a link",
|
||||
verb="created",
|
||||
actor_id=actor_id,
|
||||
field="link",
|
||||
@ -994,7 +1013,7 @@ def update_link_activity(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"updated a link",
|
||||
comment="updated a link",
|
||||
verb="updated",
|
||||
actor_id=actor_id,
|
||||
field="link",
|
||||
@ -1026,7 +1045,7 @@ def delete_link_activity(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"deleted the link",
|
||||
comment="deleted the link",
|
||||
verb="deleted",
|
||||
actor_id=actor_id,
|
||||
field="link",
|
||||
@ -1059,7 +1078,7 @@ def create_attachment_activity(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"created an attachment",
|
||||
comment="created an attachment",
|
||||
verb="created",
|
||||
actor_id=actor_id,
|
||||
field="attachment",
|
||||
@ -1085,7 +1104,7 @@ def delete_attachment_activity(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"deleted the attachment",
|
||||
comment="deleted the attachment",
|
||||
verb="deleted",
|
||||
actor_id=actor_id,
|
||||
field="attachment",
|
||||
@ -1362,12 +1381,15 @@ def create_issue_relation_activity(
|
||||
verb="created",
|
||||
old_value="",
|
||||
new_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
||||
field="blocking"
|
||||
field=(
|
||||
"blocking"
|
||||
if requested_data.get("relation_type") == "blocked_by"
|
||||
else (
|
||||
"blocked_by"
|
||||
if requested_data.get("relation_type") == "blocking"
|
||||
if requested_data.get("relation_type")
|
||||
== "blocking"
|
||||
else requested_data.get("relation_type")
|
||||
)
|
||||
),
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
@ -1418,12 +1440,14 @@ def delete_issue_relation_activity(
|
||||
verb="deleted",
|
||||
old_value=f"{issue.project.identifier}-{issue.sequence_id}",
|
||||
new_value="",
|
||||
field="blocking"
|
||||
field=(
|
||||
"blocking"
|
||||
if requested_data.get("relation_type") == "blocked_by"
|
||||
else (
|
||||
"blocked_by"
|
||||
if requested_data.get("relation_type") == "blocking"
|
||||
else requested_data.get("relation_type")
|
||||
)
|
||||
),
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
@ -1449,7 +1473,7 @@ def create_draft_issue_activity(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"drafted the issue",
|
||||
comment="drafted the issue",
|
||||
field="draft",
|
||||
verb="created",
|
||||
actor_id=actor_id,
|
||||
@ -1476,14 +1500,14 @@ def update_draft_issue_activity(
|
||||
)
|
||||
if (
|
||||
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(
|
||||
IssueActivity(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"created the issue",
|
||||
comment="created the issue",
|
||||
verb="updated",
|
||||
actor_id=actor_id,
|
||||
epoch=epoch,
|
||||
@ -1495,7 +1519,7 @@ def update_draft_issue_activity(
|
||||
issue_id=issue_id,
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"updated the draft issue",
|
||||
comment="updated the draft issue",
|
||||
field="draft",
|
||||
verb="updated",
|
||||
actor_id=actor_id,
|
||||
@ -1518,7 +1542,7 @@ def delete_draft_issue_activity(
|
||||
IssueActivity(
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
comment=f"deleted the draft issue",
|
||||
comment="deleted the draft issue",
|
||||
field="draft",
|
||||
verb="deleted",
|
||||
actor_id=actor_id,
|
||||
@ -1557,7 +1581,7 @@ def issue_activity(
|
||||
try:
|
||||
issue.updated_at = timezone.now()
|
||||
issue.save(update_fields=["updated_at"])
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
ACTIVITY_MAPPER = {
|
||||
|
@ -79,7 +79,10 @@ def archive_old_issues():
|
||||
issue_activity.delay(
|
||||
type="issue.activity.updated",
|
||||
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),
|
||||
issue_id=issue.id,
|
||||
|
@ -1,7 +1,4 @@
|
||||
# Python imports
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
|
||||
# Django imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
|
@ -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.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):
|
||||
@ -92,7 +94,9 @@ def extract_mentions_as_subscribers(project_id, issue_id, mentions):
|
||||
project_id=project_id,
|
||||
).exists()
|
||||
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()
|
||||
and not Issue.objects.filter(
|
||||
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)
|
||||
html = data.get("description_html")
|
||||
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]
|
||||
|
||||
return list(set(mentions))
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
@ -134,11 +140,13 @@ def extract_comment_mentions(comment_value):
|
||||
try:
|
||||
mentions = []
|
||||
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:
|
||||
mentions.append(mention_tag["id"])
|
||||
return list(set(mentions))
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
|
||||
@ -157,7 +165,13 @@ def get_new_comment_mentions(new_value, old_value):
|
||||
|
||||
|
||||
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(
|
||||
workspace=project.workspace,
|
||||
@ -304,9 +318,11 @@ def notifications(
|
||||
# add the user to issue subscriber
|
||||
try:
|
||||
_ = 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
|
||||
|
||||
project = Project.objects.get(pk=project_id)
|
||||
@ -336,8 +352,11 @@ def notifications(
|
||||
|
||||
for issue_activity in issue_activities_created:
|
||||
# If activity done in blocking then blocked by email should not go
|
||||
if issue_activity.get("issue_detail").get("id") != issue_id:
|
||||
continue;
|
||||
if (
|
||||
issue_activity.get("issue_detail").get("id")
|
||||
!= issue_id
|
||||
):
|
||||
continue
|
||||
|
||||
# Do not send notification for description update
|
||||
if issue_activity.get("field") == "description":
|
||||
@ -471,7 +490,9 @@ def notifications(
|
||||
if issue_comment is not None
|
||||
else ""
|
||||
),
|
||||
"activity_time": issue_activity.get("created_at"),
|
||||
"activity_time": issue_activity.get(
|
||||
"created_at"
|
||||
),
|
||||
},
|
||||
},
|
||||
)
|
||||
@ -552,7 +573,9 @@ def notifications(
|
||||
"old_value": str(
|
||||
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(
|
||||
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"
|
||||
)
|
||||
),
|
||||
"activity_time": issue_activity.get("created_at"),
|
||||
"activity_time": issue_activity.get(
|
||||
"created_at"
|
||||
),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Python import
|
||||
import os
|
||||
|
||||
# Django imports
|
||||
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.send()
|
||||
return
|
||||
except (Project.DoesNotExist, ProjectMemberInvite.DoesNotExist) as e:
|
||||
except (Project.DoesNotExist, ProjectMemberInvite.DoesNotExist):
|
||||
return
|
||||
except Exception as e:
|
||||
# Print logs if in DEBUG mode
|
||||
|
@ -215,9 +215,11 @@ def send_webhook(event, payload, kw, action, slug, bulk, current_site):
|
||||
event_data = [
|
||||
get_model_data(
|
||||
event=event,
|
||||
event_id=payload.get("id")
|
||||
event_id=(
|
||||
payload.get("id")
|
||||
if isinstance(payload, dict)
|
||||
else None,
|
||||
else None
|
||||
),
|
||||
many=False,
|
||||
)
|
||||
]
|
||||
@ -244,7 +246,9 @@ def send_webhook(event, payload, kw, action, slug, bulk, current_site):
|
||||
|
||||
|
||||
@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
|
||||
(
|
||||
EMAIL_HOST,
|
||||
@ -258,7 +262,9 @@ def send_webhook_deactivation_email(webhook_id, receiver_id, current_site, reaso
|
||||
receiver = User.objects.get(pk=receiver_id)
|
||||
webhook = Webhook.objects.get(pk=webhook_id)
|
||||
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
|
||||
context = {
|
||||
|
@ -1,7 +1,4 @@
|
||||
# Python imports
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
|
||||
# Django imports
|
||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||
@ -12,8 +9,6 @@ from django.conf import settings
|
||||
# Third party imports
|
||||
from celery import shared_task
|
||||
from sentry_sdk import capture_exception
|
||||
from slack_sdk import WebClient
|
||||
from slack_sdk.errors import SlackApiError
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import Workspace, WorkspaceMemberInvite, User
|
||||
@ -83,7 +78,7 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor):
|
||||
msg.send()
|
||||
|
||||
return
|
||||
except (Workspace.DoesNotExist, WorkspaceMemberInvite.DoesNotExist) as e:
|
||||
except (Workspace.DoesNotExist, WorkspaceMemberInvite.DoesNotExist):
|
||||
print("Workspace or WorkspaceMember Invite Does not exists")
|
||||
return
|
||||
except Exception as e:
|
||||
|
@ -2,7 +2,6 @@ import os
|
||||
from celery import Celery
|
||||
from plane.settings.redis import redis_instance
|
||||
from celery.schedules import crontab
|
||||
from django.utils.timezone import timedelta
|
||||
|
||||
# Set the default Django settings module for the 'celery' program.
|
||||
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": {
|
||||
"task": "plane.bgtasks.email_notification_task.stack_email_notification",
|
||||
"schedule": crontab(minute='*/5')
|
||||
"schedule": crontab(minute="*/5"),
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -52,5 +52,5 @@ class Command(BaseCommand):
|
||||
user.save()
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f"User password updated succesfully")
|
||||
self.style.SUCCESS("User password updated succesfully")
|
||||
)
|
||||
|
@ -4,15 +4,18 @@ from django.core.management.base import BaseCommand
|
||||
from django.db.migrations.executor import MigrationExecutor
|
||||
from django.db import connections, DEFAULT_DB_ALIAS
|
||||
|
||||
|
||||
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):
|
||||
while self._pending_migrations():
|
||||
self.stdout.write("Waiting for database migrations to complete...")
|
||||
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):
|
||||
connection = connections[DEFAULT_DB_ALIAS]
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 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):
|
||||
|
@ -7,71 +7,204 @@ import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0053_auto_20240102_1315'),
|
||||
("db", "0053_auto_20240102_1315"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Dashboard',
|
||||
name="Dashboard",
|
||||
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')),
|
||||
('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')),
|
||||
(
|
||||
"created_at",
|
||||
models.DateTimeField(
|
||||
auto_now_add=True, verbose_name="Created At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"updated_at",
|
||||
models.DateTimeField(
|
||||
auto_now=True, verbose_name="Last Modified At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"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={
|
||||
'verbose_name': 'Dashboard',
|
||||
'verbose_name_plural': 'Dashboards',
|
||||
'db_table': 'dashboards',
|
||||
'ordering': ('-created_at',),
|
||||
"verbose_name": "Dashboard",
|
||||
"verbose_name_plural": "Dashboards",
|
||||
"db_table": "dashboards",
|
||||
"ordering": ("-created_at",),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Widget',
|
||||
name="Widget",
|
||||
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')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
|
||||
('key', models.CharField(max_length=255)),
|
||||
('filters', models.JSONField(default=dict)),
|
||||
(
|
||||
"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"
|
||||
),
|
||||
),
|
||||
(
|
||||
"updated_at",
|
||||
models.DateTimeField(
|
||||
auto_now=True, verbose_name="Last Modified At"
|
||||
),
|
||||
),
|
||||
("key", models.CharField(max_length=255)),
|
||||
("filters", models.JSONField(default=dict)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Widget',
|
||||
'verbose_name_plural': 'Widgets',
|
||||
'db_table': 'widgets',
|
||||
'ordering': ('-created_at',),
|
||||
"verbose_name": "Widget",
|
||||
"verbose_name_plural": "Widgets",
|
||||
"db_table": "widgets",
|
||||
"ordering": ("-created_at",),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DashboardWidget',
|
||||
name="DashboardWidget",
|
||||
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')),
|
||||
('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')),
|
||||
(
|
||||
"created_at",
|
||||
models.DateTimeField(
|
||||
auto_now_add=True, verbose_name="Created At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"updated_at",
|
||||
models.DateTimeField(
|
||||
auto_now=True, verbose_name="Last Modified At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"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={
|
||||
'verbose_name': 'Dashboard Widget',
|
||||
'verbose_name_plural': 'Dashboard Widgets',
|
||||
'db_table': 'dashboard_widgets',
|
||||
'ordering': ('-created_at',),
|
||||
'unique_together': {('widget', 'dashboard')},
|
||||
"verbose_name": "Dashboard Widget",
|
||||
"verbose_name_plural": "Dashboard Widgets",
|
||||
"db_table": "dashboard_widgets",
|
||||
"ordering": ("-created_at",),
|
||||
"unique_together": {("widget", "dashboard")},
|
||||
},
|
||||
),
|
||||
]
|
||||
|
@ -62,7 +62,7 @@ def create_dashboards(apps, schema_editor):
|
||||
type_identifier="home",
|
||||
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,
|
||||
)
|
||||
@ -78,11 +78,13 @@ def create_dashboard_widgets(apps, schema_editor):
|
||||
widget_id=widget_id,
|
||||
dashboard_id=dashboard_id,
|
||||
)
|
||||
for widget_id in Widget.objects.values_list('id', flat=True)
|
||||
for dashboard_id in Dashboard.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)
|
||||
]
|
||||
|
||||
DashboardWidget.objects.bulk_create(updated_dashboard_widget, batch_size=2000)
|
||||
DashboardWidget.objects.bulk_create(
|
||||
updated_dashboard_widget, batch_size=2000
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -2,12 +2,17 @@
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
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")
|
||||
|
||||
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(
|
||||
UserNotificationPreference(
|
||||
user_id=user_id,
|
||||
@ -18,11 +23,10 @@ def create_notification_preferences(apps, schema_editor):
|
||||
bulk_notification_preferences, batch_size=1000, ignore_conflicts=True
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("db", "0056_usernotificationpreference_emailnotificationlog"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_notification_preferences)
|
||||
]
|
||||
operations = [migrations.RunPython(create_notification_preferences)]
|
||||
|
@ -5,19 +5,22 @@ import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0057_auto_20240122_0901'),
|
||||
("db", "0057_auto_20240122_0901"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='moduleissue',
|
||||
name='issue',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='issue_module', to='db.issue'),
|
||||
model_name="moduleissue",
|
||||
name="issue",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="issue_module",
|
||||
to="db.issue",
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='moduleissue',
|
||||
unique_together={('issue', 'module')},
|
||||
name="moduleissue",
|
||||
unique_together={("issue", "module")},
|
||||
),
|
||||
]
|
||||
|
@ -24,10 +24,9 @@ def widgets_filter_change(apps, schema_editor):
|
||||
# Bulk update the widgets
|
||||
Widget.objects.bulk_update(widgets_to_update, ["filters"], batch_size=10)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('db', '0058_alter_moduleissue_issue_and_more'),
|
||||
]
|
||||
operations = [
|
||||
migrations.RunPython(widgets_filter_change)
|
||||
("db", "0058_alter_moduleissue_issue_and_more"),
|
||||
]
|
||||
operations = [migrations.RunPython(widgets_filter_change)]
|
||||
|
@ -4,15 +4,14 @@ from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0059_auto_20240208_0957'),
|
||||
("db", "0059_auto_20240208_0957"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='cycle',
|
||||
name='progress_snapshot',
|
||||
model_name="cycle",
|
||||
name="progress_snapshot",
|
||||
field=models.JSONField(default=dict),
|
||||
),
|
||||
]
|
||||
|
@ -1,12 +1,10 @@
|
||||
# Python imports
|
||||
import uuid
|
||||
|
||||
# Django imports
|
||||
from django.db import models
|
||||
|
||||
|
||||
class TimeAuditModel(models.Model):
|
||||
|
||||
"""To path when the record was created and last modified"""
|
||||
|
||||
created_at = models.DateTimeField(
|
||||
@ -22,7 +20,6 @@ class TimeAuditModel(models.Model):
|
||||
|
||||
|
||||
class UserAuditModel(models.Model):
|
||||
|
||||
"""To path when the record was created and last modified"""
|
||||
|
||||
created_by = models.ForeignKey(
|
||||
@ -45,7 +42,6 @@ class UserAuditModel(models.Model):
|
||||
|
||||
|
||||
class AuditModel(TimeAuditModel, UserAuditModel):
|
||||
|
||||
"""To path when the record was created and last modified"""
|
||||
|
||||
class Meta:
|
||||
|
@ -85,7 +85,11 @@ from .inbox import Inbox, InboxIssue
|
||||
|
||||
from .analytic import AnalyticView
|
||||
|
||||
from .notification import Notification, UserNotificationPreference, EmailNotificationLog
|
||||
from .notification import (
|
||||
Notification,
|
||||
UserNotificationPreference,
|
||||
EmailNotificationLog,
|
||||
)
|
||||
|
||||
from .exporter import ExporterHistory
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
# Django models
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
from .base import BaseModel
|
||||
|
||||
|
@ -2,12 +2,12 @@ import uuid
|
||||
|
||||
# Django imports
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
# Module imports
|
||||
from . import BaseModel
|
||||
from ..mixins import TimeAuditModel
|
||||
|
||||
|
||||
class Dashboard(BaseModel):
|
||||
DASHBOARD_CHOICES = (
|
||||
("workspace", "Workspace"),
|
||||
@ -45,7 +45,11 @@ class Dashboard(BaseModel):
|
||||
|
||||
class Widget(TimeAuditModel):
|
||||
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)
|
||||
filters = models.JSONField(default=dict)
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Python imports
|
||||
import uuid
|
||||
|
||||
# Django imports
|
||||
from django.db import models
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Python imports
|
||||
import uuid
|
||||
|
||||
# Django imports
|
||||
from django.db import models
|
||||
|
@ -5,6 +5,7 @@ from django.conf import settings
|
||||
# Module imports
|
||||
from . import BaseModel
|
||||
|
||||
|
||||
class Notification(BaseModel):
|
||||
workspace = models.ForeignKey(
|
||||
"db.Workspace", related_name="notifications", on_delete=models.CASCADE
|
||||
@ -105,10 +106,19 @@ class UserNotificationPreference(BaseModel):
|
||||
"""Return the user"""
|
||||
return f"<{self.user}>"
|
||||
|
||||
|
||||
class EmailNotificationLog(BaseModel):
|
||||
# receiver
|
||||
receiver = models.ForeignKey(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")
|
||||
receiver = models.ForeignKey(
|
||||
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_identifier = models.UUIDField(null=True)
|
||||
entity_name = models.CharField(max_length=255)
|
||||
|
@ -138,13 +138,13 @@ class User(AbstractBaseUser, PermissionsMixin):
|
||||
super(User, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def create_user_notification(sender, instance, created, **kwargs):
|
||||
# create preferences
|
||||
if created and not instance.is_bot:
|
||||
# Module imports
|
||||
from plane.db.models import UserNotificationPreference
|
||||
|
||||
UserNotificationPreference.objects.create(
|
||||
user=instance,
|
||||
property_change=False,
|
||||
|
@ -116,9 +116,7 @@ class InstanceAdminEndpoint(BaseAPIView):
|
||||
@invalidate_cache(path="/api/instances/", user=False)
|
||||
def delete(self, request, pk):
|
||||
instance = Instance.objects.first()
|
||||
instance_admin = InstanceAdmin.objects.filter(
|
||||
instance=instance, pk=pk
|
||||
).delete()
|
||||
InstanceAdmin.objects.filter(instance=instance, pk=pk).delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@ -204,7 +202,7 @@ class InstanceAdminSignInEndpoint(BaseAPIView):
|
||||
email = email.strip().lower()
|
||||
try:
|
||||
validate_email(email)
|
||||
except ValidationError as e:
|
||||
except ValidationError:
|
||||
return Response(
|
||||
{"error": "Please provide a valid email address."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
|
@ -3,7 +3,6 @@ import os
|
||||
|
||||
# Django imports
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.conf import settings
|
||||
|
||||
# Module imports
|
||||
from plane.license.models import InstanceConfiguration
|
||||
|
@ -1,6 +1,5 @@
|
||||
# Python imports
|
||||
import json
|
||||
import requests
|
||||
import secrets
|
||||
|
||||
# Django imports
|
||||
@ -56,9 +55,9 @@ class Command(BaseCommand):
|
||||
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:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(f"Instance already registered")
|
||||
self.style.SUCCESS("Instance already registered")
|
||||
)
|
||||
return
|
||||
|
@ -1,4 +1,4 @@
|
||||
from plane.db.models import APIToken, APIActivityLog
|
||||
from plane.db.models import APIActivityLog
|
||||
|
||||
|
||||
class APITokenLogMiddleware:
|
||||
@ -39,6 +39,5 @@ class APITokenLogMiddleware:
|
||||
except Exception as e:
|
||||
print(e)
|
||||
# If the token does not exist, you can decide whether to log this as an invalid attempt
|
||||
pass
|
||||
|
||||
return None
|
||||
|
@ -1,5 +1,7 @@
|
||||
"""Development settings"""
|
||||
|
||||
import os
|
||||
|
||||
from .common import * # noqa
|
||||
|
||||
DEBUG = True
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""Production settings"""
|
||||
|
||||
import os
|
||||
from .common import * # noqa
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
|
@ -1,4 +1,3 @@
|
||||
import os
|
||||
import redis
|
||||
from django.conf import settings
|
||||
from urllib.parse import urlparse
|
||||
|
@ -1,4 +1,5 @@
|
||||
"""Test Settings"""
|
||||
|
||||
from .common import * # noqa
|
||||
|
||||
DEBUG = True
|
||||
|
@ -10,7 +10,6 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
|
||||
# Third part imports
|
||||
from rest_framework import status
|
||||
from rest_framework import status
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.exceptions import APIException
|
||||
@ -85,11 +84,8 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
|
||||
)
|
||||
|
||||
if isinstance(e, ObjectDoesNotExist):
|
||||
model_name = str(exc).split(" matching query does not exist.")[
|
||||
0
|
||||
]
|
||||
return Response(
|
||||
{"error": f"The required object does not exist."},
|
||||
{"error": "The required object does not exist."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
@ -179,7 +175,7 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
||||
|
||||
if isinstance(e, ObjectDoesNotExist):
|
||||
return Response(
|
||||
{"error": f"The required object does not exist."},
|
||||
{"error": "The required object does not exist."},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
|
@ -134,7 +134,7 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
||||
)
|
||||
|
||||
# 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",
|
||||
"medium",
|
||||
"high",
|
||||
|
@ -9,7 +9,6 @@ from django.db.models import (
|
||||
Func,
|
||||
F,
|
||||
Q,
|
||||
Count,
|
||||
Case,
|
||||
Value,
|
||||
CharField,
|
||||
@ -514,8 +513,12 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
|
||||
]
|
||||
|
||||
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
|
||||
).exists():
|
||||
return Response(
|
||||
{"error": "Project is not published"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
|
@ -12,7 +12,6 @@ from rest_framework.permissions import AllowAny
|
||||
# Module imports
|
||||
from .base import BaseAPIView
|
||||
from plane.app.serializers import ProjectDeployBoardSerializer
|
||||
from plane.app.permissions import ProjectMemberPermission
|
||||
from plane.db.models import (
|
||||
Project,
|
||||
ProjectDeployBoard,
|
||||
|
@ -5,7 +5,6 @@ import re
|
||||
from django.db.models import Q
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import Issue
|
||||
|
||||
|
||||
def search_issues(query, queryset):
|
||||
|
@ -193,7 +193,7 @@ class BasePaginator:
|
||||
cursor_result = paginator.get_result(
|
||||
limit=per_page, cursor=input_cursor
|
||||
)
|
||||
except BadPaginationError as e:
|
||||
except BadPaginationError:
|
||||
raise ParseError(detail="Error in parsing")
|
||||
|
||||
# Serialize result according to the on_result function
|
||||
|
@ -1,3 +1 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
|
@ -16,3 +16,10 @@ exclude = '''
|
||||
| venv
|
||||
)/
|
||||
'''
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 79
|
||||
exclude = [
|
||||
"**/__init__.py",
|
||||
]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user