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

* dev: formatting and removing unused imports

* dev: remove unused imports and format all the files

* fix: linting errors

* dev: format using ruff

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

View File

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

View File

@ -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,
)

View File

@ -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()
):

View File

@ -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",

View File

@ -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
)

View File

@ -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()
):

View File

@ -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,

View File

@ -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()
):

View File

@ -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

View File

@ -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 = [

View File

@ -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"]

View File

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

View File

@ -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 = [

View File

@ -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):

View File

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

View File

@ -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,
)

View File

@ -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
if fallback_workspace is not None
else None,
"fallback_workspace_slug": fallback_workspace.slug
if fallback_workspace is not None
else None,
"fallback_workspace_id": (
fallback_workspace.id
if fallback_workspace is not None
else None
),
"fallback_workspace_slug": (
fallback_workspace.slug
if fallback_workspace is not None
else None
),
"invites": workspace_invites,
}

View File

@ -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

View File

@ -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

View File

@ -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(
{

View File

@ -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
)

View File

@ -16,7 +16,6 @@ from django.contrib.auth.hashers import make_password
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from django.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,

View File

@ -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
if project_member_invite.role in [5, 10, 15]
else 15,
role=(
project_member_invite.role
if project_member_invite.role in [5, 10, 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
if project_member_invite.role in [5, 10, 15]
else 15,
role=(
project_member_invite.role
if project_member_invite.role in [5, 10, 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
if project_member_invite.role in [5, 10, 15]
else 15,
role=(
project_member_invite.role
if project_member_invite.role in [5, 10, 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
if project_member_invite.role in [5, 10, 15]
else 15,
role=(
project_member_invite.role
if project_member_invite.role in [5, 10, 15]
else 15
),
member=user,
created_by_id=project_member_invite.created_by_id,
)

View File

@ -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,
)

View File

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

View File

@ -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,13 +327,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
}
if data[0]["start_date"] and data[0]["end_date"]:
data[0]["distribution"]["completion_chart"] = (
burndown_plot(
queryset=queryset.first(),
slug=slug,
project_id=project_id,
cycle_id=data[0]["id"],
)
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",

View File

@ -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

View File

@ -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,
)

View File

@ -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

View File

@ -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,8 +428,11 @@ 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)

View File

@ -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()

View File

@ -366,7 +366,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
if serializer.is_valid():
serializer.save()
module = queryset.values(
module = queryset.values(
# Required fields
"id",
"workspace_id",

View File

@ -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):

View File

@ -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
if project_member_invite.role in [5, 10, 15]
else 15,
role=(
project_member_invite.role
if project_member_invite.role in [5, 10, 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
if project_member_invite.role in [5, 10, 15]
else 15,
role=(
project_member_invite.role
if project_member_invite.role in [5, 10, 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
if project_member_invite.role in [5, 10, 15]
else 15,
role=(
project_member_invite.role
if project_member_invite.role in [5, 10, 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
if project_member_invite.role in [5, 10, 15]
else 15,
role=(
project_member_invite.role
if project_member_invite.role in [5, 10, 15]
else 15
),
member=user,
created_by_id=project_member_invite.created_by_id,
)

View File

@ -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

View File

@ -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
if project_invite.role >= 15
else project_invite.role,
role=(
15
if project_invite.role >= 15
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)

View File

@ -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(

View File

@ -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):

View File

@ -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"]

View File

@ -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,

View File

@ -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)

View File

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

View File

@ -17,17 +17,20 @@ from plane.db.models import EmailNotificationLog, User, Issue
from plane.license.utils.instance_value import get_email_configuration
from plane.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,22 +137,24 @@ 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:
processed_content = process_mention(html_content)
processed_content_list.append(processed_content)
processed_content_list.append(processed_content)
return processed_content_list
@ -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(
@ -237,12 +246,14 @@ def send_email_notification(
},
"activity_time": str(formatted_time),
}
)
)
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,
@ -257,7 +268,7 @@ def send_email_notification(
},
"issue_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/{str(issue.id)}",
"project_url": f"{base_api}/{str(issue.project.workspace.slug)}/projects/{str(issue.project.id)}/issues/",
"workspace":str(issue.project.workspace.slug),
"workspace": str(issue.project.workspace.slug),
"project": str(issue.project.name),
"user_preference": f"{base_api}/profile/preferences/email",
"comments": comments,

View File

@ -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 "",
f"{issue['assignees__first_name']} {issue['assignees__last_name']}"
if issue["assignees__first_name"] and issue["assignees__last_name"]
else "",
(
f"{issue['created_by__first_name']} {issue['created_by__last_name']}"
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 ""
),
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']}"
if issue["assignees__first_name"] and issue["assignees__last_name"]
else "",
"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 ""
),
"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"

View File

@ -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
(

View File

@ -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}"
if old_parent is not None
else "",
new_value=f"{new_parent.project.identifier}-{new_parent.sequence_id}"
if new_parent is not None
else "",
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}"
if new_parent is not None
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")
if current_instance.get("target_date") is not None
else "",
new_value=requested_data.get("target_date")
if requested_data.get("target_date") is not None
else "",
old_value=(
current_instance.get("target_date")
if current_instance.get("target_date") is not None
else ""
),
new_value=(
requested_data.get("target_date")
if requested_data.get("target_date") is not None
else ""
),
field="target_date",
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")
if current_instance.get("start_date") is not None
else "",
new_value=requested_data.get("start_date")
if requested_data.get("start_date") is not None
else "",
old_value=(
current_instance.get("start_date")
if current_instance.get("start_date") is not None
else ""
),
new_value=(
requested_data.get("start_date")
if requested_data.get("start_date") is not None
else ""
),
field="start_date",
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")
if current_instance.get("estimate_point") is not None
else "",
new_value=requested_data.get("estimate_point")
if requested_data.get("estimate_point") is not None
else "",
old_value=(
current_instance.get("estimate_point")
if current_instance.get("estimate_point") is not None
else ""
),
new_value=(
requested_data.get("estimate_point")
if requested_data.get("estimate_point") is not None
else ""
),
field="estimate_point",
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"
if requested_data.get("relation_type") == "blocked_by"
else (
"blocked_by"
if requested_data.get("relation_type") == "blocking"
else requested_data.get("relation_type")
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,
@ -1418,12 +1440,14 @@ def delete_issue_relation_activity(
verb="deleted",
old_value=f"{issue.project.identifier}-{issue.sequence_id}",
new_value="",
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")
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 = {

View File

@ -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,

View File

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

View File

@ -40,7 +40,9 @@ def update_mentions_for_issue(issue, project, new_mentions, removed_mention):
)
IssueMention.objects.bulk_create(aggregated_issue_mentions, batch_size=100)
IssueMention.objects.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)
@ -334,11 +350,14 @@ def notifications(
user_id=subscriber
)
for issue_activity in issue_activities_created:
for issue_activity in issue_activities_created:
# If activity done in blocking then blocked by email should not go
if 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":
continue
@ -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"
),
},
},
)

View File

@ -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

View File

@ -159,7 +159,7 @@ def webhook_task(self, webhook, slug, event, event_data, action, current_site):
)
# Retry logic
if self.request.retries >= self.max_retries:
Webhook.objects.filter(pk=webhook.id).update(is_active=False)
Webhook.objects.filter(pk=webhook.id).update(is_active=False)
if webhook:
# send email for the deactivation of the webhook
send_webhook_deactivation_email(
@ -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")
if isinstance(payload, dict)
else None,
event_id=(
payload.get("id")
if isinstance(payload, dict)
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,
@ -256,15 +260,17 @@ def send_webhook_deactivation_email(webhook_id, receiver_id, current_site, reaso
) = get_email_configuration()
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."
webhook = Webhook.objects.get(pk=webhook_id)
subject = "Webhook Deactivated"
message = (
f"Webhook {webhook.url} has been deactivated due to failed requests."
)
# Send the mail
context = {
"email": receiver.email,
"message": message,
"webhook_url":f"{current_site}/{str(webhook.workspace.slug)}/settings/webhooks/{str(webhook.id)}",
"webhook_url": f"{current_site}/{str(webhook.workspace.slug)}/settings/webhooks/{str(webhook.id)}",
}
html_content = render_to_string(
"emails/notifications/webhook-deactivate.html", context

View File

@ -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:

View File

@ -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"),
},
}

View File

@ -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")
)

View File

@ -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]

View File

@ -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):

View File

@ -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")},
},
),
]

View File

@ -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):

View File

@ -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)]

View File

@ -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")},
),
]

View File

@ -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)]

View File

@ -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),
),
]

View File

@ -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:

View File

@ -85,10 +85,14 @@ 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
from .webhook import Webhook, WebhookLog
from .dashboard import Dashboard, DashboardWidget, Widget
from .dashboard import Dashboard, DashboardWidget, Widget

View File

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

View File

@ -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)

View File

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

View File

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

View File

@ -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)

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
)

View File

@ -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",

View File

@ -9,7 +9,6 @@ from django.db.models import (
Func,
F,
Q,
Count,
Case,
Value,
CharField,
@ -514,9 +513,13 @@ 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")

View File

@ -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,

View File

@ -463,7 +463,7 @@ def filter_start_target_date_issues(params, filter, method):
filter["target_date__isnull"] = False
filter["start_date__isnull"] = False
return filter
def issue_filters(query_params, method):
filter = {}

View File

@ -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):

View File

@ -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

View File

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

View File

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