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