release: Stage Release (#251)
* feat: manual ordering for issues in kanban * refactor: issues folder structure * refactor: modules and states folder structure * refactor: datepicker code * fix: create issue modal bug * feat: custom progress bar added * refactor: created global component for kanban board * refactor: update cycle and module issue create * refactor: return modules created * refactor: integrated global kanban view everywhere * refactor: integrated global list view everywhere * refactor: removed unnecessary api calls * refactor: update nomenclature for consistency * refactor: global select component for issue view * refactor: track cycles and modules for issue * fix: tracking new cycles and modules in activities * feat: segregate api token workspace * fix: workpsace id during token creation * refactor: update model association to cascade on delete * feat: sentry integrated (#235) * feat: sentry integrated * fix: removed unnecessary env variable * fix: update remirror description to save empty string and empty paragraph (#237) * Update README.md * fix: description and comment_json default value to remove warnings * feat: link option in remirror (#240) * feat: link option in remirror * fix: removed link import from remirror toolbar * feat: module and cycle settings under project * fix: module issue assignment * fix: module issue updation and activity logging * fix: typo while creating module issues * fix: string comparison for update operation * fix: ui fixes (#246) * style: shortcut command label bg color change * sidebar shortcut ui fix --------- Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com> * fix: update empty passwords to hashed string and add hashing for magic sign in * refactor: remove print logs from back migrations * build(deps): bump django in /apiserver/requirements Bumps [django](https://github.com/django/django) from 3.2.16 to 3.2.17. - [Release notes](https://github.com/django/django/releases) - [Commits](https://github.com/django/django/compare/3.2.16...3.2.17) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * feat: cycles and modules toggle in settings, refactor: folder structure (#247) * feat: link option in remirror * fix: removed link import from remirror toolbar * refactor: constants folder * refactor: layouts folder structure * fix: issue view context * feat: cycles and modules toggle in settings --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: pablohashescobar <nikhilschacko@gmail.com> Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com> Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia1001@gmail.com> Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Co-authored-by: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Co-authored-by: sphynxux <122926002+sphynxux@users.noreply.github.com> Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
6966666bf5
commit
d3b73dc32f
177 changed files with 4767 additions and 5404 deletions
|
|
@ -1,11 +1,13 @@
|
|||
# All the python scripts that are used for back migrations
|
||||
import uuid
|
||||
from plane.db.models import ProjectIdentifier
|
||||
from plane.db.models import Issue, IssueComment
|
||||
from plane.db.models import Issue, IssueComment, User
|
||||
from django.contrib.auth.hashers import make_password
|
||||
|
||||
|
||||
# Update description and description html values for old descriptions
|
||||
def update_description():
|
||||
try:
|
||||
|
||||
issues = Issue.objects.all()
|
||||
updated_issues = []
|
||||
|
||||
|
|
@ -25,7 +27,6 @@ def update_description():
|
|||
|
||||
def update_comments():
|
||||
try:
|
||||
|
||||
issue_comments = IssueComment.objects.all()
|
||||
updated_issue_comments = []
|
||||
|
||||
|
|
@ -44,9 +45,11 @@ def update_comments():
|
|||
|
||||
def update_project_identifiers():
|
||||
try:
|
||||
project_identifiers = ProjectIdentifier.objects.filter(workspace_id=None).select_related("project", "project__workspace")
|
||||
project_identifiers = ProjectIdentifier.objects.filter(
|
||||
workspace_id=None
|
||||
).select_related("project", "project__workspace")
|
||||
updated_identifiers = []
|
||||
|
||||
|
||||
for identifier in project_identifiers:
|
||||
identifier.workspace_id = identifier.project.workspace_id
|
||||
updated_identifiers.append(identifier)
|
||||
|
|
@ -58,3 +61,21 @@ def update_project_identifiers():
|
|||
except Exception as e:
|
||||
print(e)
|
||||
print("Failed")
|
||||
|
||||
|
||||
def update_user_empty_password():
|
||||
try:
|
||||
users = User.objects.filter(password="")
|
||||
updated_users = []
|
||||
|
||||
for user in users:
|
||||
user.password = make_password(uuid.uuid4().hex)
|
||||
user.is_password_autoset = True
|
||||
updated_users.append(user)
|
||||
|
||||
User.objects.bulk_update(updated_users, ["password"], batch_size=50)
|
||||
print("Success")
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("Failed")
|
||||
|
|
|
|||
|
|
@ -40,12 +40,12 @@ class IssueFlatSerializer(BaseSerializer):
|
|||
"start_date",
|
||||
"target_date",
|
||||
"sequence_id",
|
||||
"sort_order",
|
||||
]
|
||||
|
||||
|
||||
# Issue Serializer with state details
|
||||
class IssueStateSerializer(BaseSerializer):
|
||||
|
||||
state_detail = StateSerializer(read_only=True, source="state")
|
||||
project_detail = ProjectSerializer(read_only=True, source="project")
|
||||
|
||||
|
|
@ -57,7 +57,6 @@ class IssueStateSerializer(BaseSerializer):
|
|||
##TODO: Find a better way to write this serializer
|
||||
## Find a better approach to save manytomany?
|
||||
class IssueCreateSerializer(BaseSerializer):
|
||||
|
||||
state_detail = StateSerializer(read_only=True, source="state")
|
||||
created_by_detail = UserLiteSerializer(read_only=True, source="created_by")
|
||||
project_detail = ProjectSerializer(read_only=True, source="project")
|
||||
|
|
@ -176,7 +175,6 @@ class IssueCreateSerializer(BaseSerializer):
|
|||
return issue
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
|
||||
blockers = validated_data.pop("blockers_list", None)
|
||||
assignees = validated_data.pop("assignees_list", None)
|
||||
labels = validated_data.pop("labels_list", None)
|
||||
|
|
@ -254,7 +252,6 @@ class IssueCreateSerializer(BaseSerializer):
|
|||
|
||||
|
||||
class IssueActivitySerializer(BaseSerializer):
|
||||
|
||||
actor_detail = UserLiteSerializer(read_only=True, source="actor")
|
||||
|
||||
class Meta:
|
||||
|
|
@ -263,7 +260,6 @@ class IssueActivitySerializer(BaseSerializer):
|
|||
|
||||
|
||||
class IssueCommentSerializer(BaseSerializer):
|
||||
|
||||
actor_detail = UserLiteSerializer(read_only=True, source="actor")
|
||||
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
|
||||
project_detail = ProjectSerializer(read_only=True, source="project")
|
||||
|
|
@ -319,7 +315,6 @@ class LabelSerializer(BaseSerializer):
|
|||
|
||||
|
||||
class IssueLabelSerializer(BaseSerializer):
|
||||
|
||||
# label_details = LabelSerializer(read_only=True, source="label")
|
||||
|
||||
class Meta:
|
||||
|
|
@ -332,7 +327,6 @@ class IssueLabelSerializer(BaseSerializer):
|
|||
|
||||
|
||||
class BlockedIssueSerializer(BaseSerializer):
|
||||
|
||||
blocked_issue_detail = IssueFlatSerializer(source="block", read_only=True)
|
||||
|
||||
class Meta:
|
||||
|
|
@ -341,7 +335,6 @@ class BlockedIssueSerializer(BaseSerializer):
|
|||
|
||||
|
||||
class BlockerIssueSerializer(BaseSerializer):
|
||||
|
||||
blocker_issue_detail = IssueFlatSerializer(source="blocked_by", read_only=True)
|
||||
|
||||
class Meta:
|
||||
|
|
@ -350,7 +343,6 @@ class BlockerIssueSerializer(BaseSerializer):
|
|||
|
||||
|
||||
class IssueAssigneeSerializer(BaseSerializer):
|
||||
|
||||
assignee_details = UserLiteSerializer(read_only=True, source="assignee")
|
||||
|
||||
class Meta:
|
||||
|
|
@ -373,7 +365,6 @@ class CycleBaseSerializer(BaseSerializer):
|
|||
|
||||
|
||||
class IssueCycleDetailSerializer(BaseSerializer):
|
||||
|
||||
cycle_detail = CycleBaseSerializer(read_only=True, source="cycle")
|
||||
|
||||
class Meta:
|
||||
|
|
@ -404,7 +395,6 @@ class ModuleBaseSerializer(BaseSerializer):
|
|||
|
||||
|
||||
class IssueModuleDetailSerializer(BaseSerializer):
|
||||
|
||||
module_detail = ModuleBaseSerializer(read_only=True, source="module")
|
||||
|
||||
class Meta:
|
||||
|
|
|
|||
|
|
@ -15,12 +15,16 @@ from plane.api.serializers import APITokenSerializer
|
|||
class ApiTokenEndpoint(BaseAPIView):
|
||||
def post(self, request):
|
||||
try:
|
||||
|
||||
label = request.data.get("label", str(uuid4().hex))
|
||||
workspace = request.data.get("workspace", False)
|
||||
|
||||
if not workspace:
|
||||
return Response(
|
||||
{"error": "Workspace is required"}, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
api_token = APIToken.objects.create(
|
||||
label=label,
|
||||
user=request.user,
|
||||
label=label, user=request.user, workspace_id=workspace
|
||||
)
|
||||
|
||||
serializer = APITokenSerializer(api_token)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from django.utils import timezone
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import validate_email
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.hashers import make_password
|
||||
|
||||
# Third party imports
|
||||
from rest_framework.response import Response
|
||||
|
|
@ -35,12 +36,10 @@ def get_tokens_for_user(user):
|
|||
|
||||
|
||||
class SignUpEndpoint(BaseAPIView):
|
||||
|
||||
permission_classes = (AllowAny,)
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
|
||||
email = request.data.get("email", False)
|
||||
password = request.data.get("password", False)
|
||||
|
||||
|
|
@ -216,14 +215,12 @@ class SignOutEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class MagicSignInGenerateEndpoint(BaseAPIView):
|
||||
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
|
||||
email = request.data.get("email", False)
|
||||
|
||||
if not email:
|
||||
|
|
@ -269,7 +266,6 @@ class MagicSignInGenerateEndpoint(BaseAPIView):
|
|||
ri.set(key, json.dumps(value), ex=expiry)
|
||||
|
||||
else:
|
||||
|
||||
value = {"current_attempt": 0, "email": email, "token": token}
|
||||
expiry = 600
|
||||
|
||||
|
|
@ -293,14 +289,12 @@ class MagicSignInGenerateEndpoint(BaseAPIView):
|
|||
|
||||
|
||||
class MagicSignInEndpoint(BaseAPIView):
|
||||
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
|
||||
user_token = request.data.get("token", "").strip().lower()
|
||||
key = request.data.get("key", False)
|
||||
|
||||
|
|
@ -313,19 +307,20 @@ class MagicSignInEndpoint(BaseAPIView):
|
|||
ri = redis_instance()
|
||||
|
||||
if ri.exists(key):
|
||||
|
||||
data = json.loads(ri.get(key))
|
||||
|
||||
token = data["token"]
|
||||
email = data["email"]
|
||||
|
||||
if str(token) == str(user_token):
|
||||
|
||||
if User.objects.filter(email=email).exists():
|
||||
user = User.objects.get(email=email)
|
||||
else:
|
||||
user = User.objects.create(
|
||||
email=email, username=uuid.uuid4().hex
|
||||
email=email,
|
||||
username=uuid.uuid4().hex,
|
||||
password=make_password(uuid.uuid4().hex),
|
||||
is_password_autoset=True,
|
||||
)
|
||||
|
||||
user.last_active = timezone.now()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
# Python imports
|
||||
import json
|
||||
|
||||
# Django imports
|
||||
from django.db.models import OuterRef, Func, F
|
||||
from django.core import serializers
|
||||
|
||||
# Third party imports
|
||||
from rest_framework.response import Response
|
||||
|
|
@ -11,10 +15,10 @@ from . import BaseViewSet
|
|||
from plane.api.serializers import CycleSerializer, CycleIssueSerializer
|
||||
from plane.api.permissions import ProjectEntityPermission
|
||||
from plane.db.models import Cycle, CycleIssue, Issue
|
||||
from plane.bgtasks.issue_activites_task import issue_activity
|
||||
|
||||
|
||||
class CycleViewSet(BaseViewSet):
|
||||
|
||||
serializer_class = CycleSerializer
|
||||
model = Cycle
|
||||
permission_classes = [
|
||||
|
|
@ -41,7 +45,6 @@ class CycleViewSet(BaseViewSet):
|
|||
|
||||
|
||||
class CycleIssueViewSet(BaseViewSet):
|
||||
|
||||
serializer_class = CycleIssueSerializer
|
||||
model = CycleIssue
|
||||
|
||||
|
|
@ -79,7 +82,6 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
|
||||
def create(self, request, slug, project_id, cycle_id):
|
||||
try:
|
||||
|
||||
issues = request.data.get("issues", [])
|
||||
|
||||
if not len(issues):
|
||||
|
|
@ -91,29 +93,77 @@ class CycleIssueViewSet(BaseViewSet):
|
|||
workspace__slug=slug, project_id=project_id, pk=cycle_id
|
||||
)
|
||||
|
||||
issues = Issue.objects.filter(
|
||||
pk__in=issues, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
# Get all CycleIssues already created
|
||||
cycle_issues = list(CycleIssue.objects.filter(issue_id__in=issues))
|
||||
records_to_update = []
|
||||
update_cycle_issue_activity = []
|
||||
record_to_create = []
|
||||
|
||||
# Delete old records in order to maintain the database integrity
|
||||
CycleIssue.objects.filter(issue_id__in=issues).delete()
|
||||
for issue in issues:
|
||||
cycle_issue = [
|
||||
cycle_issue
|
||||
for cycle_issue in cycle_issues
|
||||
if str(cycle_issue.issue_id) in issues
|
||||
]
|
||||
# Update only when cycle changes
|
||||
if len(cycle_issue):
|
||||
if cycle_issue[0].cycle_id != cycle_id:
|
||||
update_cycle_issue_activity.append(
|
||||
{
|
||||
"old_cycle_id": str(cycle_issue[0].cycle_id),
|
||||
"new_cycle_id": str(cycle_id),
|
||||
"issue_id": str(cycle_issue[0].issue_id),
|
||||
}
|
||||
)
|
||||
cycle_issue[0].cycle_id = cycle_id
|
||||
records_to_update.append(cycle_issue[0])
|
||||
else:
|
||||
record_to_create.append(
|
||||
CycleIssue(
|
||||
project_id=project_id,
|
||||
workspace=cycle.workspace,
|
||||
created_by=request.user,
|
||||
updated_by=request.user,
|
||||
cycle=cycle,
|
||||
issue_id=issue,
|
||||
)
|
||||
)
|
||||
|
||||
CycleIssue.objects.bulk_create(
|
||||
[
|
||||
CycleIssue(
|
||||
project_id=project_id,
|
||||
workspace=cycle.workspace,
|
||||
created_by=request.user,
|
||||
updated_by=request.user,
|
||||
cycle=cycle,
|
||||
issue=issue,
|
||||
)
|
||||
for issue in issues
|
||||
],
|
||||
record_to_create,
|
||||
batch_size=10,
|
||||
ignore_conflicts=True,
|
||||
)
|
||||
return Response({"message": "Success"}, status=status.HTTP_200_OK)
|
||||
CycleIssue.objects.bulk_update(
|
||||
records_to_update,
|
||||
["cycle"],
|
||||
batch_size=10,
|
||||
)
|
||||
|
||||
# Capture Issue Activity
|
||||
issue_activity.delay(
|
||||
{
|
||||
"type": "issue.activity",
|
||||
"requested_data": json.dumps({"cycles_list": issues}),
|
||||
"actor_id": str(self.request.user.id),
|
||||
"issue_id": str(self.kwargs.get("pk", None)),
|
||||
"project_id": str(self.kwargs.get("project_id", None)),
|
||||
"current_instance": json.dumps(
|
||||
{
|
||||
"updated_cycle_issues": update_cycle_issue_activity,
|
||||
"created_cycle_issues": serializers.serialize(
|
||||
"json", record_to_create
|
||||
),
|
||||
}
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
# Return all Cycle Issues
|
||||
return Response(
|
||||
CycleIssueSerializer(self.get_queryset(), many=True).data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
except Cycle.DoesNotExist:
|
||||
return Response(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
# Python imports
|
||||
import json
|
||||
|
||||
# Django Imports
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Prefetch, F, OuterRef, Func
|
||||
from django.core import serializers
|
||||
|
||||
# Third party imports
|
||||
from rest_framework.response import Response
|
||||
|
|
@ -22,10 +26,10 @@ from plane.db.models import (
|
|||
Issue,
|
||||
ModuleLink,
|
||||
)
|
||||
from plane.bgtasks.issue_activites_task import issue_activity
|
||||
|
||||
|
||||
class ModuleViewSet(BaseViewSet):
|
||||
|
||||
model = Module
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
|
|
@ -95,7 +99,6 @@ class ModuleViewSet(BaseViewSet):
|
|||
|
||||
|
||||
class ModuleIssueViewSet(BaseViewSet):
|
||||
|
||||
serializer_class = ModuleIssueSerializer
|
||||
model = ModuleIssue
|
||||
|
||||
|
|
@ -148,29 +151,77 @@ class ModuleIssueViewSet(BaseViewSet):
|
|||
workspace__slug=slug, project_id=project_id, pk=module_id
|
||||
)
|
||||
|
||||
issues = Issue.objects.filter(
|
||||
pk__in=issues, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
module_issues = list(ModuleIssue.objects.filter(issue_id__in=issues))
|
||||
|
||||
# Delete old records in order to maintain the database integrity
|
||||
ModuleIssue.objects.filter(issue_id__in=issues).delete()
|
||||
update_module_issue_activity = []
|
||||
records_to_update = []
|
||||
record_to_create = []
|
||||
|
||||
for issue in issues:
|
||||
module_issue = [
|
||||
module_issue
|
||||
for module_issue in module_issues
|
||||
if str(module_issue.issue_id) in issues
|
||||
]
|
||||
|
||||
if len(module_issue):
|
||||
if module_issue[0].module_id != module_id:
|
||||
update_module_issue_activity.append(
|
||||
{
|
||||
"old_module_id": str(module_issue[0].module_id),
|
||||
"new_module_id": str(module_id),
|
||||
"issue_id": str(module_issue[0].issue_id),
|
||||
}
|
||||
)
|
||||
module_issue[0].module_id = module_id
|
||||
records_to_update.append(module_issue[0])
|
||||
else:
|
||||
record_to_create.append(
|
||||
ModuleIssue(
|
||||
module=module,
|
||||
issue_id=issue,
|
||||
project_id=project_id,
|
||||
workspace=module.workspace,
|
||||
created_by=request.user,
|
||||
updated_by=request.user,
|
||||
)
|
||||
)
|
||||
|
||||
ModuleIssue.objects.bulk_create(
|
||||
[
|
||||
ModuleIssue(
|
||||
module=module,
|
||||
issue=issue,
|
||||
project_id=project_id,
|
||||
workspace=module.workspace,
|
||||
created_by=request.user,
|
||||
updated_by=request.user,
|
||||
)
|
||||
for issue in issues
|
||||
],
|
||||
record_to_create,
|
||||
batch_size=10,
|
||||
ignore_conflicts=True,
|
||||
)
|
||||
return Response({"message": "Success"}, status=status.HTTP_200_OK)
|
||||
|
||||
ModuleIssue.objects.bulk_update(
|
||||
records_to_update,
|
||||
["module"],
|
||||
batch_size=10,
|
||||
)
|
||||
|
||||
# Capture Issue Activity
|
||||
issue_activity.delay(
|
||||
{
|
||||
"type": "issue.activity",
|
||||
"requested_data": json.dumps({"modules_list": issues}),
|
||||
"actor_id": str(self.request.user.id),
|
||||
"issue_id": str(self.kwargs.get("pk", None)),
|
||||
"project_id": str(self.kwargs.get("project_id", None)),
|
||||
"current_instance": json.dumps(
|
||||
{
|
||||
"updated_module_issues": update_module_issue_activity,
|
||||
"created_module_issues": serializers.serialize(
|
||||
"json", record_to_create
|
||||
),
|
||||
}
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
return Response(
|
||||
ModuleIssueSerializer(self.get_queryset(), many=True).data,
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
except Module.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "Module Does not exists"}, status=status.HTTP_400_BAD_REQUEST
|
||||
|
|
|
|||
|
|
@ -6,7 +6,16 @@ from django_rq import job
|
|||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import User, Issue, Project, Label, IssueActivity, State
|
||||
from plane.db.models import (
|
||||
User,
|
||||
Issue,
|
||||
Project,
|
||||
Label,
|
||||
IssueActivity,
|
||||
State,
|
||||
Cycle,
|
||||
Module,
|
||||
)
|
||||
|
||||
|
||||
# Track Chnages in name
|
||||
|
|
@ -44,7 +53,6 @@ def track_parent(
|
|||
issue_activities,
|
||||
):
|
||||
if current_instance.get("parent") != requested_data.get("parent"):
|
||||
|
||||
if requested_data.get("parent") == None:
|
||||
old_parent = Issue.objects.get(pk=current_instance.get("parent"))
|
||||
issue_activities.append(
|
||||
|
|
@ -134,7 +142,6 @@ def track_state(
|
|||
issue_activities,
|
||||
):
|
||||
if current_instance.get("state") != requested_data.get("state"):
|
||||
|
||||
new_state = State.objects.get(pk=requested_data.get("state", None))
|
||||
old_state = State.objects.get(pk=current_instance.get("state", None))
|
||||
|
||||
|
|
@ -167,7 +174,6 @@ def track_description(
|
|||
if current_instance.get("description_html") != requested_data.get(
|
||||
"description_html"
|
||||
):
|
||||
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_id,
|
||||
|
|
@ -274,7 +280,6 @@ def track_labels(
|
|||
):
|
||||
# Label Addition
|
||||
if len(requested_data.get("labels_list")) > len(current_instance.get("labels")):
|
||||
|
||||
for label in requested_data.get("labels_list"):
|
||||
if label not in current_instance.get("labels"):
|
||||
label = Label.objects.get(pk=label)
|
||||
|
|
@ -296,7 +301,6 @@ def track_labels(
|
|||
|
||||
# Label Removal
|
||||
if len(requested_data.get("labels_list")) < len(current_instance.get("labels")):
|
||||
|
||||
for label in current_instance.get("labels"):
|
||||
if label not in requested_data.get("labels_list"):
|
||||
label = Label.objects.get(pk=label)
|
||||
|
|
@ -326,12 +330,10 @@ def track_assignees(
|
|||
actor,
|
||||
issue_activities,
|
||||
):
|
||||
|
||||
# Assignee Addition
|
||||
if len(requested_data.get("assignees_list")) > len(
|
||||
current_instance.get("assignees")
|
||||
):
|
||||
|
||||
for assignee in requested_data.get("assignees_list"):
|
||||
if assignee not in current_instance.get("assignees"):
|
||||
assignee = User.objects.get(pk=assignee)
|
||||
|
|
@ -354,7 +356,6 @@ def track_assignees(
|
|||
if len(requested_data.get("assignees_list")) < len(
|
||||
current_instance.get("assignees")
|
||||
):
|
||||
|
||||
for assignee in current_instance.get("assignees"):
|
||||
if assignee not in requested_data.get("assignees_list"):
|
||||
assignee = User.objects.get(pk=assignee)
|
||||
|
|
@ -386,7 +387,6 @@ def track_blocks(
|
|||
if len(requested_data.get("blocks_list")) > len(
|
||||
current_instance.get("blocked_issues")
|
||||
):
|
||||
|
||||
for block in requested_data.get("blocks_list"):
|
||||
if (
|
||||
len(
|
||||
|
|
@ -418,7 +418,6 @@ def track_blocks(
|
|||
if len(requested_data.get("blocks_list")) < len(
|
||||
current_instance.get("blocked_issues")
|
||||
):
|
||||
|
||||
for blocked in current_instance.get("blocked_issues"):
|
||||
if blocked.get("block") not in requested_data.get("blocks_list"):
|
||||
issue = Issue.objects.get(pk=blocked.get("block"))
|
||||
|
|
@ -450,7 +449,6 @@ def track_blockings(
|
|||
if len(requested_data.get("blockers_list")) > len(
|
||||
current_instance.get("blocker_issues")
|
||||
):
|
||||
|
||||
for block in requested_data.get("blockers_list"):
|
||||
if (
|
||||
len(
|
||||
|
|
@ -482,7 +480,6 @@ def track_blockings(
|
|||
if len(requested_data.get("blockers_list")) < len(
|
||||
current_instance.get("blocker_issues")
|
||||
):
|
||||
|
||||
for blocked in current_instance.get("blocker_issues"):
|
||||
if blocked.get("blocked_by") not in requested_data.get("blockers_list"):
|
||||
issue = Issue.objects.get(pk=blocked.get("blocked_by"))
|
||||
|
|
@ -502,6 +499,119 @@ def track_blockings(
|
|||
)
|
||||
|
||||
|
||||
def track_cycles(
|
||||
requested_data,
|
||||
current_instance,
|
||||
issue_id,
|
||||
project,
|
||||
actor,
|
||||
issue_activities,
|
||||
):
|
||||
# Updated Records:
|
||||
updated_records = current_instance.get("updated_cycle_issues", [])
|
||||
created_records = json.loads(current_instance.get("created_cycle_issues", []))
|
||||
|
||||
for updated_record in updated_records:
|
||||
old_cycle = Cycle.objects.filter(
|
||||
pk=updated_record.get("old_cycle_id", None)
|
||||
).first()
|
||||
new_cycle = Cycle.objects.filter(
|
||||
pk=updated_record.get("new_cycle_id", None)
|
||||
).first()
|
||||
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=updated_record.get("issue_id"),
|
||||
actor=actor,
|
||||
verb="updated",
|
||||
old_value=old_cycle.name,
|
||||
new_value=new_cycle.name,
|
||||
field="cycles",
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"{actor.email} updated cycle from {old_cycle.name} to {new_cycle.name}",
|
||||
old_identifier=old_cycle.id,
|
||||
new_identifier=new_cycle.id,
|
||||
)
|
||||
)
|
||||
|
||||
for created_record in created_records:
|
||||
cycle = Cycle.objects.filter(
|
||||
pk=created_record.get("fields").get("cycle")
|
||||
).first()
|
||||
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=created_record.get("fields").get("issue"),
|
||||
actor=actor,
|
||||
verb="created",
|
||||
old_value="",
|
||||
new_value=cycle.name,
|
||||
field="cycles",
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"{actor.email} added cycle {cycle.name}",
|
||||
new_identifier=cycle.id,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def track_modules(
|
||||
requested_data,
|
||||
current_instance,
|
||||
issue_id,
|
||||
project,
|
||||
actor,
|
||||
issue_activities,
|
||||
):
|
||||
# Updated Records:
|
||||
updated_records = current_instance.get("updated_module_issues", [])
|
||||
created_records = json.loads(current_instance.get("created_module_issues", []))
|
||||
|
||||
for updated_record in updated_records:
|
||||
old_module = Module.objects.filter(
|
||||
pk=updated_record.get("old_module_id", None)
|
||||
).first()
|
||||
new_module = Module.objects.filter(
|
||||
pk=updated_record.get("new_module_id", None)
|
||||
).first()
|
||||
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=updated_record.get("issue_id"),
|
||||
actor=actor,
|
||||
verb="updated",
|
||||
old_value=old_module.name,
|
||||
new_value=new_module.name,
|
||||
field="modules",
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"{actor.email} updated module from {old_module.name} to {new_module.name}",
|
||||
old_identifier=old_module.id,
|
||||
new_identifier=new_module.id,
|
||||
)
|
||||
)
|
||||
|
||||
for created_record in created_records:
|
||||
module = Module.objects.filter(
|
||||
pk=created_record.get("fields").get("module")
|
||||
).first()
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=created_record.get("fields").get("issue"),
|
||||
actor=actor,
|
||||
verb="created",
|
||||
old_value="",
|
||||
new_value=module.name,
|
||||
field="modules",
|
||||
project=project,
|
||||
workspace=project.workspace,
|
||||
comment=f"{actor.email} added module {module.name}",
|
||||
new_identifier=module.id,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Receive message from room group
|
||||
@job("default")
|
||||
def issue_activity(event):
|
||||
|
|
@ -510,7 +620,7 @@ def issue_activity(event):
|
|||
|
||||
requested_data = json.loads(event.get("requested_data"))
|
||||
current_instance = json.loads(event.get("current_instance"))
|
||||
issue_id = event.get("issue_id")
|
||||
issue_id = event.get("issue_id", None)
|
||||
actor_id = event.get("actor_id")
|
||||
project_id = event.get("project_id")
|
||||
|
||||
|
|
@ -530,6 +640,8 @@ def issue_activity(event):
|
|||
"assignees_list": track_assignees,
|
||||
"blocks_list": track_blocks,
|
||||
"blockers_list": track_blockings,
|
||||
"cycles_list": track_cycles,
|
||||
"modules_list": track_modules,
|
||||
}
|
||||
|
||||
for key in requested_data:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ def generate_token():
|
|||
|
||||
|
||||
class APIToken(BaseModel):
|
||||
|
||||
token = models.CharField(max_length=255, unique=True, default=generate_token)
|
||||
label = models.CharField(max_length=255, default=generate_label_token)
|
||||
user = models.ForeignKey(
|
||||
|
|
@ -28,6 +27,9 @@ class APIToken(BaseModel):
|
|||
user_type = models.PositiveSmallIntegerField(
|
||||
choices=((0, "Human"), (1, "Bot")), default=0
|
||||
)
|
||||
workspace = models.ForeignKey(
|
||||
"db.Workspace", related_name="api_tokens", on_delete=models.CASCADE, null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "API Token"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from django.dispatch import receiver
|
|||
from . import ProjectBaseModel
|
||||
from plane.utils.html_processor import strip_tags
|
||||
|
||||
|
||||
# TODO: Handle identifiers for Bulk Inserts - nk
|
||||
class Issue(ProjectBaseModel):
|
||||
PRIORITY_CHOICES = (
|
||||
|
|
@ -32,8 +33,8 @@ class Issue(ProjectBaseModel):
|
|||
related_name="state_issue",
|
||||
)
|
||||
name = models.CharField(max_length=255, verbose_name="Issue Name")
|
||||
description = models.JSONField(blank=True, null=True)
|
||||
description_html = models.TextField(blank=True, null=True)
|
||||
description = models.JSONField(blank=True, default=dict)
|
||||
description_html = models.TextField(blank=True, default="<p></p>")
|
||||
description_stripped = models.TextField(blank=True, null=True)
|
||||
priority = models.CharField(
|
||||
max_length=30,
|
||||
|
|
@ -56,6 +57,7 @@ class Issue(ProjectBaseModel):
|
|||
labels = models.ManyToManyField(
|
||||
"db.Label", blank=True, related_name="labels", through="IssueLabel"
|
||||
)
|
||||
sort_order = models.FloatField(default=65535)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Issue"
|
||||
|
|
@ -196,8 +198,8 @@ class TimelineIssue(ProjectBaseModel):
|
|||
|
||||
class IssueComment(ProjectBaseModel):
|
||||
comment_stripped = models.TextField(verbose_name="Comment", blank=True)
|
||||
comment_json = models.JSONField(blank=True, null=True)
|
||||
comment_html = models.TextField(blank=True)
|
||||
comment_json = models.JSONField(blank=True, default=dict)
|
||||
comment_html = models.TextField(blank=True, default="<p></p>")
|
||||
attachments = ArrayField(models.URLField(), size=10, blank=True, default=list)
|
||||
issue = models.ForeignKey(Issue, on_delete=models.CASCADE)
|
||||
# System can also create comment
|
||||
|
|
@ -246,7 +248,6 @@ class IssueProperty(ProjectBaseModel):
|
|||
|
||||
|
||||
class Label(ProjectBaseModel):
|
||||
|
||||
parent = models.ForeignKey(
|
||||
"self",
|
||||
on_delete=models.CASCADE,
|
||||
|
|
@ -256,7 +257,7 @@ class Label(ProjectBaseModel):
|
|||
)
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True)
|
||||
colour = models.CharField(max_length=255, blank=True)
|
||||
color = models.CharField(max_length=255, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Label"
|
||||
|
|
@ -269,7 +270,6 @@ class Label(ProjectBaseModel):
|
|||
|
||||
|
||||
class IssueLabel(ProjectBaseModel):
|
||||
|
||||
issue = models.ForeignKey(
|
||||
"db.Issue", on_delete=models.CASCADE, related_name="label_issue"
|
||||
)
|
||||
|
|
@ -288,7 +288,6 @@ class IssueLabel(ProjectBaseModel):
|
|||
|
||||
|
||||
class IssueSequence(ProjectBaseModel):
|
||||
|
||||
issue = models.ForeignKey(
|
||||
Issue, on_delete=models.SET_NULL, related_name="issue_sequence", null=True
|
||||
)
|
||||
|
|
@ -305,7 +304,6 @@ class IssueSequence(ProjectBaseModel):
|
|||
# TODO: Find a better method to save the model
|
||||
@receiver(post_save, sender=Issue)
|
||||
def create_issue_sequence(sender, instance, created, **kwargs):
|
||||
|
||||
if created:
|
||||
IssueSequence.objects.create(
|
||||
issue=instance, sequence=instance.sequence_id, project=instance.project
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ def get_default_props():
|
|||
|
||||
|
||||
class Project(BaseModel):
|
||||
|
||||
NETWORK_CHOICES = ((0, "Secret"), (2, "Public"))
|
||||
name = models.CharField(max_length=255, verbose_name="Project Name")
|
||||
description = models.TextField(verbose_name="Project Description", blank=True)
|
||||
|
|
@ -63,6 +62,8 @@ class Project(BaseModel):
|
|||
blank=True,
|
||||
)
|
||||
icon = models.CharField(max_length=255, null=True, blank=True)
|
||||
module_view = models.BooleanField(default=True)
|
||||
cycle_view = models.BooleanField(default=True)
|
||||
|
||||
def __str__(self):
|
||||
"""Return name of the project"""
|
||||
|
|
@ -82,7 +83,6 @@ class Project(BaseModel):
|
|||
|
||||
|
||||
class ProjectBaseModel(BaseModel):
|
||||
|
||||
project = models.ForeignKey(
|
||||
Project, on_delete=models.CASCADE, related_name="project_%(class)s"
|
||||
)
|
||||
|
|
@ -117,7 +117,6 @@ class ProjectMemberInvite(ProjectBaseModel):
|
|||
|
||||
|
||||
class ProjectMember(ProjectBaseModel):
|
||||
|
||||
member = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
|
|
@ -141,9 +140,9 @@ class ProjectMember(ProjectBaseModel):
|
|||
"""Return members of the project"""
|
||||
return f"{self.member.email} <{self.project.name}>"
|
||||
|
||||
|
||||
# TODO: Remove workspace relation later
|
||||
class ProjectIdentifier(AuditModel):
|
||||
|
||||
workspace = models.ForeignKey(
|
||||
"db.Workspace", models.CASCADE, related_name="project_identifiers", null=True
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# base requirements
|
||||
|
||||
Django==3.2.16
|
||||
Django==3.2.17
|
||||
django-braces==1.15.0
|
||||
django-taggit==2.1.0
|
||||
psycopg2==2.9.3
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue