Merge pull request #35 from pablohashescobar/build/backend_recent_merges

build: Build/backend recent merges
This commit is contained in:
Vamsi Kurama 2022-12-16 03:19:10 +05:30 committed by GitHub
commit eae7e68947
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 574 additions and 5 deletions

View File

@ -38,3 +38,5 @@ from .issue import (
IssueFlatSerializer, IssueFlatSerializer,
IssueStateSerializer, IssueStateSerializer,
) )
from .module import ModuleWriteSerializer, ModuleSerializer, ModuleIssueSerializer

View File

@ -7,6 +7,7 @@ from .user import UserLiteSerializer
from .state import StateSerializer from .state import StateSerializer
from .user import UserLiteSerializer from .user import UserLiteSerializer
from .project import ProjectSerializer from .project import ProjectSerializer
from .workspace import WorkSpaceSerializer
from plane.db.models import ( from plane.db.models import (
User, User,
Issue, Issue,
@ -19,8 +20,8 @@ from plane.db.models import (
IssueLabel, IssueLabel,
Label, Label,
IssueBlocker, IssueBlocker,
Cycle,
CycleIssue, CycleIssue,
Cycle,
) )
@ -54,6 +55,9 @@ class IssueStateSerializer(BaseSerializer):
class IssueCreateSerializer(BaseSerializer): class IssueCreateSerializer(BaseSerializer):
state_detail = StateSerializer(read_only=True, source="state") 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")
workspace_detail = WorkSpaceSerializer(read_only=True, source="workspace")
assignees_list = serializers.ListField( assignees_list = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()), child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
@ -213,6 +217,8 @@ class IssueActivitySerializer(BaseSerializer):
class IssueCommentSerializer(BaseSerializer): class IssueCommentSerializer(BaseSerializer):
actor_detail = UserLiteSerializer(read_only=True, source="actor") actor_detail = UserLiteSerializer(read_only=True, source="actor")
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
project_detail = ProjectSerializer(read_only=True, source="project")
class Meta: class Meta:
model = IssueComment model = IssueComment
@ -305,7 +311,6 @@ class IssueAssigneeSerializer(BaseSerializer):
class CycleBaseSerializer(BaseSerializer): class CycleBaseSerializer(BaseSerializer):
class Meta: class Meta:
model = Cycle model = Cycle
fields = "__all__" fields = "__all__"
@ -318,6 +323,7 @@ class CycleBaseSerializer(BaseSerializer):
"updated_at", "updated_at",
] ]
class IssueCycleDetailSerializer(BaseSerializer): class IssueCycleDetailSerializer(BaseSerializer):
cycle_detail = CycleBaseSerializer(read_only=True, source="cycle") cycle_detail = CycleBaseSerializer(read_only=True, source="cycle")
@ -335,7 +341,6 @@ class IssueCycleDetailSerializer(BaseSerializer):
] ]
class IssueSerializer(BaseSerializer): class IssueSerializer(BaseSerializer):
project_detail = ProjectSerializer(read_only=True, source="project") project_detail = ProjectSerializer(read_only=True, source="project")
state_detail = StateSerializer(read_only=True, source="state") state_detail = StateSerializer(read_only=True, source="state")

View File

@ -0,0 +1,136 @@
# Third Party imports
from rest_framework import serializers
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from .project import ProjectSerializer
from .issue import IssueFlatSerializer
from plane.db.models import User, Module, ModuleMember, ModuleIssue
class ModuleWriteSerializer(BaseSerializer):
members_list = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=User.objects.all()),
write_only=True,
required=False,
)
class Meta:
model = Module
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
def create(self, validated_data):
members = validated_data.pop("members_list", None)
project = self.context["project"]
module = Module.objects.create(**validated_data, project=project)
if members is not None:
ModuleMember.objects.bulk_create(
[
ModuleMember(
module=module,
member=member,
project=project,
workspace=project.workspace,
created_by=module.created_by,
updated_by=module.updated_by,
)
for member in members
],
batch_size=10,
)
return module
def update(self, instance, validated_data):
members = validated_data.pop("members_list", None)
project = self.context["project"]
module = Module.objects.create(**validated_data, project=project)
if members is not None:
ModuleMember.objects.bulk_create(
[
ModuleMember(
module=module,
member=member,
project=project,
workspace=project.workspace,
created_by=module.created_by,
updated_by=module.updated_by,
)
for member in members
],
batch_size=10,
)
return super().update(instance, validated_data)
class ModuleFlatSerializer(BaseSerializer):
class Meta:
model = Module
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]
class ModuleIssueSerializer(BaseSerializer):
module_detail = ModuleFlatSerializer(read_only=True, source="module")
issue_detail = IssueFlatSerializer(read_only=True, source="issue")
class Meta:
model = ModuleIssue
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
"module",
]
class ModuleSerializer(BaseSerializer):
project_detail = ProjectSerializer(read_only=True, source="project")
lead_detail = UserLiteSerializer(read_only=True, source="lead")
members_detail = UserLiteSerializer(read_only=True, many=True, source="members")
module_issues = ModuleIssueSerializer(read_only=True, many=True)
class Meta:
model = Module
fields = "__all__"
read_only_fields = [
"workspace",
"project",
"created_by",
"updated_by",
"created_at",
"updated_at",
]

View File

@ -54,6 +54,8 @@ from plane.api.views import (
BulkDeleteIssuesEndpoint, BulkDeleteIssuesEndpoint,
BulkAssignIssuesToCycleEndpoint, BulkAssignIssuesToCycleEndpoint,
ProjectUserViewsEndpoint, ProjectUserViewsEndpoint,
ModuleViewSet,
ModuleIssueViewSet,
UserLastProjectWithWorkspaceEndpoint, UserLastProjectWithWorkspaceEndpoint,
UserWorkSpaceIssues, UserWorkSpaceIssues,
) )
@ -587,6 +589,52 @@ urlpatterns = [
name="File Assets", name="File Assets",
), ),
## End File Assets ## End File Assets
## Modules
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/",
ModuleViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-modules",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:pk>/",
ModuleViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-modules",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-issues/",
ModuleIssueViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project-module-issues",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/module-issues/<uuid:pk>/",
ModuleIssueViewSet.as_view(
{
"get": "retrieve",
"put": "update",
"patch": "partial_update",
"delete": "destroy",
}
),
name="project-module-issues",
),
## End Modules
# path( # path(
# "issues/<int:pk>/all/", # "issues/<int:pk>/all/",
# IssueViewSet.as_view({"get": "list_issue_history_comments"}), # IssueViewSet.as_view({"get": "list_issue_history_comments"}),

View File

@ -66,3 +66,5 @@ from .authentication import (
MagicSignInEndpoint, MagicSignInEndpoint,
MagicSignInGenerateEndpoint, MagicSignInGenerateEndpoint,
) )
from .module import ModuleViewSet, ModuleIssueViewSet

View File

@ -34,6 +34,74 @@ 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)
## Raise exception if any of the above are missing
if not email or not password:
return Response(
{"error": "Both email and password are required"},
status=status.HTTP_400_BAD_REQUEST,
)
email = email.strip().lower()
try:
validate_email(email)
except ValidationError as e:
return Response(
{"error": "Please provide a valid email address."},
status=status.HTTP_400_BAD_REQUEST,
)
user = User.objects.filter(email=email).first()
if user is not None:
return Response(
{"error": "Email ID is already taken"},
status=status.HTTP_400_BAD_REQUEST,
)
user = User.objects.create(email=email)
user.set_password(password)
# settings last actives for the user
user.last_active = timezone.now()
user.last_login_time = timezone.now()
user.last_login_ip = request.META.get("REMOTE_ADDR")
user.last_login_uagent = request.META.get("HTTP_USER_AGENT")
user.token_updated_at = timezone.now()
user.save()
serialized_user = UserSerializer(user).data
access_token, refresh_token = get_tokens_for_user(user)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
"user": serialized_user,
}
return Response(data, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
return Response(
{
"error": "Something went wrong. Please try again later or contact the support team."
},
status=status.HTTP_400_BAD_REQUEST,
)
class SignInEndpoint(BaseAPIView): class SignInEndpoint(BaseAPIView):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
@ -104,7 +172,6 @@ class SignInEndpoint(BaseAPIView):
status=status.HTTP_403_FORBIDDEN, status=status.HTTP_403_FORBIDDEN,
) )
except Exception as e: except Exception as e:
print(e)
capture_exception(e) capture_exception(e)
return Response( return Response(
{ {
@ -218,7 +285,6 @@ class MagicSignInGenerateEndpoint(BaseAPIView):
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
except Exception as e: except Exception as e:
print(e)
capture_exception(e) capture_exception(e)
return Response( return Response(
{"error": "Something went wrong please try again later"}, {"error": "Something went wrong please try again later"},

View File

@ -0,0 +1,109 @@
# Django Imports
from django.db import IntegrityError
from django.db.models import Prefetch
# Third party imports
from rest_framework.response import Response
from rest_framework import status
# Module imports
from . import BaseViewSet
from plane.api.serializers import (
ModuleWriteSerializer,
ModuleSerializer,
ModuleIssueSerializer,
)
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import Module, ModuleIssue, Project
class ModuleViewSet(BaseViewSet):
model = Module
permission_classes = [
ProjectEntityPermission,
]
def get_serializer_class(self):
return (
ModuleWriteSerializer
if self.action in ["create", "update", "partial_update"]
else ModuleSerializer
)
def get_queryset(self):
return (
super()
.get_queryset()
.filter(project_id=self.kwargs.get("project_id"))
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("project")
.select_related("workspace")
.select_related("lead")
.prefetch_related("members")
.prefetch_related(
Prefetch(
"module_issues",
queryset=ModuleIssue.objects.select_related("module", "issue"),
)
)
)
def create(self, request, slug, project_id):
try:
project = Project.objects.get(workspace__slug=slug, pk=project_id)
serializer = ModuleWriteSerializer(
data=request.data, context={"project": project}
)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except Project.DoesNotExist:
return Response(
{"error": "Project was not found"}, status=status.HTTP_404_NOT_FOUND
)
except IntegrityError as e:
if "already exists" in str(e):
return Response(
{"name": "The module name is already taken"},
status=status.HTTP_410_GONE,
)
class ModuleIssueViewSet(BaseViewSet):
serializer_class = ModuleIssueSerializer
model = ModuleIssue
filterset_fields = [
"issue__id",
"workspace__id",
]
permission_classes = [
ProjectEntityPermission,
]
def perform_create(self, serializer):
serializer.save(
project_id=self.kwargs.get("project_id"),
module_id=self.kwargs.get("module_id"),
)
def get_queryset(self):
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(module_id=self.kwargs.get("module_id"))
.filter(project__project_projectmember__member=self.request.user)
.select_related("project")
.select_related("workspace")
.select_related("module")
.select_related("issue")
.distinct()
)

View File

@ -0,0 +1,110 @@
# Generated by Django 3.2.14 on 2022-12-15 21:29
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('db', '0010_auto_20221213_2348'),
]
operations = [
migrations.CreateModel(
name='Module',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=255, verbose_name='Module Name')),
('description', models.TextField(blank=True, verbose_name='Module Description')),
('description_text', models.JSONField(blank=True, null=True, verbose_name='Module Description RT')),
('description_html', models.JSONField(blank=True, null=True, verbose_name='Module Description HTML')),
('start_date', models.DateField(null=True)),
('target_date', models.DateField(null=True)),
('status', models.CharField(choices=[('backlog', 'Backlog'), ('planned', 'Planned'), ('in-progress', 'In Progress'), ('paused', 'Paused'), ('completed', 'Completed'), ('cancelled', 'Cancelled')], default='planned', max_length=20)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='module_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('lead', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='module_leads', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Module',
'verbose_name_plural': 'Modules',
'db_table': 'module',
'ordering': ('-created_at',),
},
),
migrations.AddField(
model_name='project',
name='icon',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.CreateModel(
name='ModuleMember',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='modulemember_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('module', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='db.module')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_modulemember', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='modulemember_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_modulemember', to='db.workspace')),
],
options={
'verbose_name': 'Module Member',
'verbose_name_plural': 'Module Members',
'db_table': 'module_member',
'ordering': ('-created_at',),
'unique_together': {('module', 'member')},
},
),
migrations.AddField(
model_name='module',
name='members',
field=models.ManyToManyField(blank=True, related_name='module_members', through='db.ModuleMember', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='module',
name='project',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_module', to='db.project'),
),
migrations.AddField(
model_name='module',
name='updated_by',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='module_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By'),
),
migrations.AddField(
model_name='module',
name='workspace',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_module', to='db.workspace'),
),
migrations.CreateModel(
name='ModuleIssue',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='moduleissue_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='module_issues', to='db.issue')),
('module', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='module_issues', to='db.module')),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_moduleissue', to='db.project')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='moduleissue_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workspace_moduleissue', to='db.workspace')),
],
options={
'verbose_name': 'Module Issue',
'verbose_name_plural': 'Module Issues',
'db_table': 'module_issues',
'ordering': ('-created_at',),
'unique_together': {('module', 'issue')},
},
),
migrations.AlterUniqueTogether(
name='module',
unique_together={('name', 'project')},
),
]

View File

@ -36,3 +36,5 @@ from .cycle import Cycle, CycleIssue
from .shortcut import Shortcut from .shortcut import Shortcut
from .view import View from .view import View
from .module import Module, ModuleMember, ModuleIssue

View File

@ -0,0 +1,88 @@
# Django imports
from django.db import models
from django.conf import settings
# Module imports
from . import ProjectBaseModel
class Module(ProjectBaseModel):
name = models.CharField(max_length=255, verbose_name="Module Name")
description = models.TextField(verbose_name="Module Description", blank=True)
description_text = models.JSONField(
verbose_name="Module Description RT", blank=True, null=True
)
description_html = models.JSONField(
verbose_name="Module Description HTML", blank=True, null=True
)
start_date = models.DateField(null=True)
target_date = models.DateField(null=True)
status = models.CharField(
choices=(
("backlog", "Backlog"),
("planned", "Planned"),
("in-progress", "In Progress"),
("paused", "Paused"),
("completed", "Completed"),
("cancelled", "Cancelled"),
),
default="planned",
max_length=20,
)
lead = models.ForeignKey(
"db.User", on_delete=models.SET_NULL, related_name="module_leads", null=True
)
members = models.ManyToManyField(
settings.AUTH_USER_MODEL,
blank=True,
related_name="module_members",
through="ModuleMember",
through_fields=("module", "member"),
)
class Meta:
unique_together = ["name", "project"]
verbose_name = "Module"
verbose_name_plural = "Modules"
db_table = "module"
ordering = ("-created_at",)
def __str__(self):
return f"{self.name} {self.start_date} {self.target_date}"
class ModuleMember(ProjectBaseModel):
module = models.ForeignKey("db.Module", on_delete=models.CASCADE)
member = models.ForeignKey("db.User", on_delete=models.CASCADE)
class Meta:
unique_together = ["module", "member"]
verbose_name = "Module Member"
verbose_name_plural = "Module Members"
db_table = "module_member"
ordering = ("-created_at",)
def __str__(self):
return f"{self.module.name} {self.member}"
class ModuleIssue(ProjectBaseModel):
module = models.ForeignKey(
"db.Module", on_delete=models.CASCADE, related_name="module_issues"
)
issue = models.ForeignKey(
"db.Issue", on_delete=models.CASCADE, related_name="module_issues"
)
class Meta:
unique_together = ["module", "issue"]
verbose_name = "Module Issue"
verbose_name_plural = "Module Issues"
db_table = "module_issues"
ordering = ("-created_at",)
def __str__(self):
return f"{self.module.name} {self.issue.name}"

View File

@ -53,6 +53,7 @@ class Project(BaseModel):
null=True, null=True,
blank=True, blank=True,
) )
icon = models.CharField(max_length=255, null=True, blank=True)
def __str__(self): def __str__(self):
"""Return name of the project""" """Return name of the project"""