Compare commits

...

2 Commits

Author SHA1 Message Date
pablohashescobar
1604f4585d dev: create project settings 2023-10-12 13:51:32 +05:30
pablohashescobar
03f9ca45d8 dev: create model for project setting 2023-10-11 19:24:45 +05:30
12 changed files with 393 additions and 71 deletions

View File

@ -11,7 +11,9 @@ from .workspace import (
) )
from .project import ( from .project import (
ProjectSerializer, ProjectSerializer,
ProjectSettingSerializer,
ProjectDetailSerializer, ProjectDetailSerializer,
ProjectSettingDetailSerializer,
ProjectMemberSerializer, ProjectMemberSerializer,
ProjectMemberInviteSerializer, ProjectMemberInviteSerializer,
ProjectIdentifierSerializer, ProjectIdentifierSerializer,
@ -20,7 +22,7 @@ from .project import (
ProjectMemberLiteSerializer, ProjectMemberLiteSerializer,
ProjectDeployBoardSerializer, ProjectDeployBoardSerializer,
ProjectMemberAdminSerializer, ProjectMemberAdminSerializer,
ProjectPublicMemberSerializer ProjectPublicMemberSerializer,
) )
from .state import StateSerializer, StateLiteSerializer from .state import StateSerializer, StateLiteSerializer
from .view import GlobalViewSerializer, IssueViewSerializer, IssueViewFavoriteSerializer from .view import GlobalViewSerializer, IssueViewSerializer, IssueViewFavoriteSerializer

View File

@ -10,6 +10,7 @@ from plane.api.serializers.workspace import WorkSpaceSerializer, WorkspaceLiteSe
from plane.api.serializers.user import UserLiteSerializer, UserAdminLiteSerializer from plane.api.serializers.user import UserLiteSerializer, UserAdminLiteSerializer
from plane.db.models import ( from plane.db.models import (
Project, Project,
ProjectSetting,
ProjectMember, ProjectMember,
ProjectMemberInvite, ProjectMemberInvite,
ProjectIdentifier, ProjectIdentifier,
@ -96,8 +97,6 @@ class ProjectLiteSerializer(BaseSerializer):
class ProjectDetailSerializer(BaseSerializer): class ProjectDetailSerializer(BaseSerializer):
workspace = WorkSpaceSerializer(read_only=True) workspace = WorkSpaceSerializer(read_only=True)
default_assignee = UserLiteSerializer(read_only=True)
project_lead = UserLiteSerializer(read_only=True)
is_favorite = serializers.BooleanField(read_only=True) is_favorite = serializers.BooleanField(read_only=True)
total_members = serializers.IntegerField(read_only=True) total_members = serializers.IntegerField(read_only=True)
total_cycles = serializers.IntegerField(read_only=True) total_cycles = serializers.IntegerField(read_only=True)
@ -178,12 +177,12 @@ class ProjectDeployBoardSerializer(BaseSerializer):
fields = "__all__" fields = "__all__"
read_only_fields = [ read_only_fields = [
"workspace", "workspace",
"project", "anchor", "project",
"anchor",
] ]
class ProjectPublicMemberSerializer(BaseSerializer): class ProjectPublicMemberSerializer(BaseSerializer):
class Meta: class Meta:
model = ProjectPublicMember model = ProjectPublicMember
fields = "__all__" fields = "__all__"
@ -192,3 +191,26 @@ class ProjectPublicMemberSerializer(BaseSerializer):
"project", "project",
"member", "member",
] ]
class ProjectSettingSerializer(BaseSerializer):
class Meta:
model = ProjectSetting
fields = "__all__"
read_only_fields = [
"workspace",
"project",
]
class ProjectSettingDetailSerializer(BaseSerializer):
default_assignee = UserLiteSerializer(read_only=True)
project_lead = UserLiteSerializer(read_only=True)
class Meta:
model = ProjectSetting
fields = "__all__"
read_only_fields = [
"workspace",
"project",
]

View File

@ -59,6 +59,7 @@ from plane.api.views import (
## End File Assets ## End File Assets
# Projects # Projects
ProjectViewSet, ProjectViewSet,
ProjectSettingViewSet,
InviteProjectEndpoint, InviteProjectEndpoint,
ProjectMemberViewSet, ProjectMemberViewSet,
ProjectMemberEndpoint, ProjectMemberEndpoint,
@ -481,6 +482,26 @@ urlpatterns = [
), ),
name="project", name="project",
), ),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/settings/",
ProjectSettingViewSet.as_view(
{
"get": "list",
"post": "create",
}
),
name="project",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/settings/",
ProjectSettingViewSet.as_view(
{
"get": "retrieve",
"patch": "partial_update",
}
),
name="project",
),
path( path(
"workspaces/<str:slug>/project-identifiers/", "workspaces/<str:slug>/project-identifiers/",
ProjectIdentifierEndpoint.as_view(), ProjectIdentifierEndpoint.as_view(),

View File

@ -1,5 +1,6 @@
from .project import ( from .project import (
ProjectViewSet, ProjectViewSet,
ProjectSettingViewSet,
ProjectMemberViewSet, ProjectMemberViewSet,
UserProjectInvitationsViewset, UserProjectInvitationsViewset,
InviteProjectEndpoint, InviteProjectEndpoint,

View File

@ -9,7 +9,7 @@ from sentry_sdk import capture_exception
# Module imports # Module imports
from .base import BaseViewSet, BaseAPIView from .base import BaseViewSet, BaseAPIView
from plane.api.permissions import ProjectEntityPermission from plane.api.permissions import ProjectEntityPermission
from plane.db.models import Project, Estimate, EstimatePoint from plane.db.models import ProjectSetting, Estimate, EstimatePoint
from plane.api.serializers import ( from plane.api.serializers import (
EstimateSerializer, EstimateSerializer,
EstimatePointSerializer, EstimatePointSerializer,
@ -24,10 +24,10 @@ class ProjectEstimatePointEndpoint(BaseAPIView):
def get(self, request, slug, project_id): def get(self, request, slug, project_id):
try: try:
project = Project.objects.get(workspace__slug=slug, pk=project_id) project_setting = ProjectSetting.objects.get(workspace__slug=slug, pk=project_id)
if project.estimate_id is not None: if project_setting.estimate_id is not None:
estimate_points = EstimatePoint.objects.filter( estimate_points = EstimatePoint.objects.filter(
estimate_id=project.estimate_id, estimate_id=project_setting.estimate_id,
project_id=project_id, project_id=project_id,
workspace__slug=slug, workspace__slug=slug,
) )

View File

@ -63,6 +63,7 @@ from plane.api.permissions import (
) )
from plane.db.models import ( from plane.db.models import (
Project, Project,
ProjectSetting,
Issue, Issue,
IssueActivity, IssueActivity,
IssueComment, IssueComment,
@ -293,13 +294,14 @@ class IssueViewSet(BaseViewSet):
def create(self, request, slug, project_id): def create(self, request, slug, project_id):
try: try:
project = Project.objects.get(pk=project_id) project = Project.objects.get(pk=project_id)
project_setting = ProjectSetting.objects.get(workspace__slug=slug, project_id=project_id)
serializer = IssueCreateSerializer( serializer = IssueCreateSerializer(
data=request.data, data=request.data,
context={ context={
"project_id": project_id, "project_id": project_id,
"workspace_id": project.workspace_id, "workspace_id": project.workspace_id,
"default_assignee_id": project.default_assignee_id, "default_assignee_id": project_setting.default_assignee_id,
}, },
) )
@ -2565,13 +2567,13 @@ class IssueDraftViewSet(BaseViewSet):
def create(self, request, slug, project_id): def create(self, request, slug, project_id):
try: try:
project = Project.objects.get(pk=project_id) project = Project.objects.get(pk=project_id)
project_setting = ProjectSetting.objects.get(workspace__slug=slug, project_id=project_id)
serializer = IssueCreateSerializer( serializer = IssueCreateSerializer(
data=request.data, data=request.data,
context={ context={
"project_id": project_id, "project_id": project_id,
"workspace_id": project.workspace_id, "workspace_id": project.workspace_id,
"default_assignee_id": project.default_assignee_id, "default_assignee_id": project_setting.default_assignee_id,
}, },
) )

View File

@ -29,11 +29,12 @@ from sentry_sdk import capture_exception
from .base import BaseViewSet, BaseAPIView from .base import BaseViewSet, BaseAPIView
from plane.api.serializers import ( from plane.api.serializers import (
ProjectSerializer, ProjectSerializer,
ProjectSettingDetailSerializer,
ProjectSettingSerializer,
ProjectMemberSerializer, ProjectMemberSerializer,
ProjectDetailSerializer, ProjectDetailSerializer,
ProjectMemberInviteSerializer, ProjectMemberInviteSerializer,
ProjectFavoriteSerializer, ProjectFavoriteSerializer,
IssueLiteSerializer,
ProjectDeployBoardSerializer, ProjectDeployBoardSerializer,
ProjectMemberAdminSerializer, ProjectMemberAdminSerializer,
) )
@ -67,6 +68,7 @@ from plane.db.models import (
ModuleMember, ModuleMember,
Inbox, Inbox,
ProjectDeployBoard, ProjectDeployBoard,
ProjectSetting,
) )
from plane.bgtasks.project_invitation_task import project_invitation from plane.bgtasks.project_invitation_task import project_invitation
@ -98,7 +100,7 @@ class ProjectViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug")) .filter(workspace__slug=self.kwargs.get("slug"))
.filter(Q(project_projectmember__member=self.request.user) | Q(network=2)) .filter(Q(project_projectmember__member=self.request.user) | Q(network=2))
.select_related( .select_related(
"workspace", "workspace__owner", "default_assignee", "project_lead" "workspace", "workspace__owner",
) )
.annotate(is_favorite=Exists(subquery)) .annotate(is_favorite=Exists(subquery))
.annotate( .annotate(
@ -210,20 +212,29 @@ class ProjectViewSet(BaseViewSet):
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
project_lead = request.data.get("project_lead", None)
# Create Project Setting
_ = ProjectSetting.objects.create(
project_id=serializer.data["id"],
project_lead_id=request.data.get("project_lead", None)
)
# Add the user as Administrator to the project # Add the user as Administrator to the project
project_member = ProjectMember.objects.create( project_member = ProjectMember.objects.create(
project_id=serializer.data["id"], member=request.user, role=20 project_id=serializer.data["id"], member=request.user, role=20
) )
if serializer.data["project_lead"] is not None and str( if project_lead is not None and str(
serializer.data["project_lead"] project_lead
) != str(request.user.id): ) != str(request.user.id):
ProjectMember.objects.create( ProjectMember.objects.create(
project_id=serializer.data["id"], project_id=serializer.data["id"],
member_id=serializer.data["project_lead"], member_id=project_lead,
role=20, role=20,
) )
# Default states # Default states
states = [ states = [
{ {
@ -307,7 +318,7 @@ class ProjectViewSet(BaseViewSet):
status=status.HTTP_410_GONE, status=status.HTTP_410_GONE,
) )
except Exception as e: except Exception as e:
capture_exception(e) print(e)
return Response( return Response(
{"error": "Something went wrong please try again later"}, {"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
@ -973,7 +984,7 @@ class ProjectFavoritesViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug")) .filter(workspace__slug=self.kwargs.get("slug"))
.filter(user=self.request.user) .filter(user=self.request.user)
.select_related( .select_related(
"project", "project__project_lead", "project__default_assignee" "project",
) )
.select_related("workspace", "workspace__owner") .select_related("workspace", "workspace__owner")
) )
@ -1246,3 +1257,69 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
except Exception as e: except Exception as e:
capture_exception(e) capture_exception(e)
return Response([], status=status.HTTP_200_OK) return Response([], status=status.HTTP_200_OK)
class ProjectSettingViewSet(BaseViewSet):
model = ProjectSetting
permission_classes = [
ProjectBasePermission,
]
def get_serializer_class(self, *args, **kwargs):
if self.action in ["create", "partial_update"]:
return ProjectSettingSerializer
return ProjectSettingDetailSerializer
def get_queryset(self):
return (
super()
.get_queryset()
.filter(
workspace__slug=self.kwargs.get("slug"),
project_id=self.kwargs.get("project_id"),
)
)
def perform_create(self, serializer):
serializer.save(project_id=self.kwargs.get("project_id"))
def list(self, request, slug, project_id):
try:
project_setting = self.get_queryset().first()
if project_setting is not None:
serializer = ProjectSettingDetailSerializer(project_setting)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(
{"error": "Project setting does not exists"},
status=status.HTTP_404_NOT_FOUND,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
def partial_update(self, request, slug, project_id):
try:
project_setting = self.get_queryset().first()
# Check if it is None
if project_setting is not None:
serializer = ProjectSettingSerializer(
project_setting, data=request.data, partial=True
)
if serializer.is_valid():
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(
{"error": "Project setting does not exists"},
status=status.HTTP_404_NOT_FOUND,
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)

View File

@ -12,7 +12,7 @@ from celery import shared_task
from sentry_sdk import capture_exception from sentry_sdk import capture_exception
# Module imports # Module imports
from plane.db.models import Issue, Project, State from plane.db.models import Issue, ProjectSetting, State
from plane.bgtasks.issue_activites_task import issue_activity from plane.bgtasks.issue_activites_task import issue_activity
@ -25,11 +25,11 @@ def archive_and_close_old_issues():
def archive_old_issues(): def archive_old_issues():
try: try:
# Get all the projects whose archive_in is greater than 0 # Get all the projects whose archive_in is greater than 0
projects = Project.objects.filter(archive_in__gt=0) project_settings = ProjectSetting.objects.filter(archive_in__gt=0)
for project in projects: for project_setting in project_settings:
project_id = project.id project_id = project_setting.project_id
archive_in = project.archive_in archive_in = project_setting.archive_in
# Get all the issues whose updated_at in less that the archive_in month # Get all the issues whose updated_at in less that the archive_in month
issues = Issue.issue_objects.filter( issues = Issue.issue_objects.filter(
@ -75,7 +75,7 @@ def archive_old_issues():
issue_activity.delay( issue_activity.delay(
type="issue.activity.updated", type="issue.activity.updated",
requested_data=json.dumps({"archived_at": str(archive_at)}), requested_data=json.dumps({"archived_at": str(archive_at)}),
actor_id=str(project.created_by_id), actor_id=str(project_setting.created_by_id),
issue_id=issue.id, issue_id=issue.id,
project_id=project_id, project_id=project_id,
current_instance=None, current_instance=None,
@ -95,13 +95,13 @@ def archive_old_issues():
def close_old_issues(): def close_old_issues():
try: try:
# Get all the projects whose close_in is greater than 0 # Get all the projects whose close_in is greater than 0
projects = Project.objects.filter(close_in__gt=0).select_related( project_settings = ProjectSetting.objects.filter(close_in__gt=0).select_related(
"default_state" "close_state"
) )
for project in projects: for project_setting in project_settings:
project_id = project.id project_id = project_setting.project_id
close_in = project.close_in close_in = project_setting.close_in
# Get all the issues whose updated_at in less that the close_in month # Get all the issues whose updated_at in less that the close_in month
issues = Issue.issue_objects.filter( issues = Issue.issue_objects.filter(
@ -130,10 +130,10 @@ def close_old_issues():
# Check if Issues # Check if Issues
if issues: if issues:
if project.default_state is None: if project_setting.close_state is None:
close_state = State.objects.filter(group="cancelled").first() close_state = State.objects.filter(group="cancelled").first()
else: else:
close_state = project.default_state close_state = project_setting.close_state
issues_to_update = [] issues_to_update = []
for issue in issues: for issue in issues:
@ -147,7 +147,7 @@ def close_old_issues():
issue_activity.delay( issue_activity.delay(
type="issue.activity.updated", type="issue.activity.updated",
requested_data=json.dumps({"closed_to": str(issue.state_id)}), requested_data=json.dumps({"closed_to": str(issue.state_id)}),
actor_id=str(project.created_by_id), actor_id=str(project_setting.created_by_id),
issue_id=issue.id, issue_id=issue.id,
project_id=project_id, project_id=project_id,
current_instance=None, current_instance=None,

View File

@ -0,0 +1,124 @@
# Generated by Django 4.2.3 on 2023-10-11 13:34
import uuid
from django.db import migrations, models
import django.db.models.deletion
from django.conf import settings
def create_project_settings(apps, schema_editor):
Project = apps.get_model("db", "Project")
ProjectSetting = apps.get_model("db", "ProjectSetting")
ProjectSetting.objects.bulk_create(
[
ProjectSetting(
project=project,
workspace_id=project.workspace_id,
archive_in=project.archive_in,
close_in=project.close_in,
cycle_view=project.cycle_view,
default_assignee=project.default_assignee,
default_state=project.default_state,
estimate=project.estimate,
inbox_view=project.inbox_view,
issue_views_view=project.issue_views_view,
module_view=project.module_view,
page_view=project.page_view,
project_lead=project.project_lead,
)
for project in Project.objects.all()
],
batch_size=1000,
)
class Migration(migrations.Migration):
dependencies = [
('db', '0045_issueactivity_epoch_workspacemember_issue_props_and_more'),
]
operations = [
migrations.CreateModel(
name='ProjectSetting',
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)),
('module_view', models.BooleanField(default=True)),
('cycle_view', models.BooleanField(default=True)),
('issue_views_view', models.BooleanField(default=True)),
('page_view', models.BooleanField(default=True)),
('inbox_view', models.BooleanField(default=False)),
('archive_in', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(12)])),
('close_in', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(12)])),
('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')),
('default_assignee', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='default_assignee', to=settings.AUTH_USER_MODEL)),
('default_state', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_state', to='db.state')),
('estimate', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='projects', to='db.estimate')),
('project', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='project_settings', to='db.project')),
('project_lead', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='project_lead', 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')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_settings', to='db.workspace')),
],
options={
'verbose_name': 'Project Settings',
'verbose_name_plural': 'Project Settings',
'db_table': 'project_settings',
'ordering': ('-created_at',),
},
),
migrations.RunPython(create_project_settings),
migrations.RemoveField(
model_name='project',
name='archive_in',
),
migrations.RemoveField(
model_name='project',
name='close_in',
),
migrations.RemoveField(
model_name='project',
name='cycle_view',
),
migrations.RemoveField(
model_name='project',
name='default_assignee',
),
migrations.RemoveField(
model_name='project',
name='default_state',
),
migrations.RemoveField(
model_name='project',
name='description_html',
),
migrations.RemoveField(
model_name='project',
name='description_text',
),
migrations.RemoveField(
model_name='project',
name='estimate',
),
migrations.RemoveField(
model_name='project',
name='inbox_view',
),
migrations.RemoveField(
model_name='project',
name='issue_views_view',
),
migrations.RemoveField(
model_name='project',
name='module_view',
),
migrations.RemoveField(
model_name='project',
name='page_view',
),
migrations.RemoveField(
model_name='project',
name='project_lead',
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 4.2.3 on 2023-10-12 07:13
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('db', '0046_auto_20231011_1334'),
]
operations = [
migrations.RemoveField(
model_name='projectsetting',
name='default_state',
),
migrations.AddField(
model_name='projectsetting',
name='close_state',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='close_state', to='db.state'),
),
]

View File

@ -13,6 +13,7 @@ from .workspace import (
from .project import ( from .project import (
Project, Project,
ProjectSetting,
ProjectMember, ProjectMember,
ProjectBaseModel, ProjectBaseModel,
ProjectMemberInvite, ProjectMemberInvite,

View File

@ -38,7 +38,7 @@ def get_default_props():
}, },
"display_filters": { "display_filters": {
"group_by": None, "group_by": None,
"order_by": '-created_at', "order_by": "-created_at",
"type": None, "type": None,
"sub_issue": True, "sub_issue": True,
"show_empty_groups": True, "show_empty_groups": True,
@ -56,12 +56,6 @@ class Project(BaseModel):
NETWORK_CHOICES = ((0, "Secret"), (2, "Public")) NETWORK_CHOICES = ((0, "Secret"), (2, "Public"))
name = models.CharField(max_length=255, verbose_name="Project Name") name = models.CharField(max_length=255, verbose_name="Project Name")
description = models.TextField(verbose_name="Project Description", blank=True) description = models.TextField(verbose_name="Project Description", blank=True)
description_text = models.JSONField(
verbose_name="Project Description RT", blank=True, null=True
)
description_html = models.JSONField(
verbose_name="Project Description HTML", blank=True, null=True
)
network = models.PositiveSmallIntegerField(default=2, choices=NETWORK_CHOICES) network = models.PositiveSmallIntegerField(default=2, choices=NETWORK_CHOICES)
workspace = models.ForeignKey( workspace = models.ForeignKey(
"db.WorkSpace", on_delete=models.CASCADE, related_name="workspace_project" "db.WorkSpace", on_delete=models.CASCADE, related_name="workspace_project"
@ -70,40 +64,40 @@ class Project(BaseModel):
max_length=12, max_length=12,
verbose_name="Project Identifier", verbose_name="Project Identifier",
) )
default_assignee = models.ForeignKey( # default_assignee = models.ForeignKey(
settings.AUTH_USER_MODEL, # settings.AUTH_USER_MODEL,
on_delete=models.CASCADE, # on_delete=models.CASCADE,
related_name="default_assignee", # related_name="default_assignee",
null=True, # null=True,
blank=True, # blank=True,
) # )
project_lead = models.ForeignKey( # project_lead = models.ForeignKey(
settings.AUTH_USER_MODEL, # settings.AUTH_USER_MODEL,
on_delete=models.CASCADE, # on_delete=models.CASCADE,
related_name="project_lead", # related_name="project_lead",
null=True, # null=True,
blank=True, # blank=True,
) # )
emoji = models.CharField(max_length=255, null=True, blank=True) emoji = models.CharField(max_length=255, null=True, blank=True)
icon_prop = models.JSONField(null=True) icon_prop = models.JSONField(null=True)
module_view = models.BooleanField(default=True) # module_view = models.BooleanField(default=True)
cycle_view = models.BooleanField(default=True) # cycle_view = models.BooleanField(default=True)
issue_views_view = models.BooleanField(default=True) # issue_views_view = models.BooleanField(default=True)
page_view = models.BooleanField(default=True) # page_view = models.BooleanField(default=True)
inbox_view = models.BooleanField(default=False) # inbox_view = models.BooleanField(default=False)
cover_image = models.URLField(blank=True, null=True, max_length=800) cover_image = models.URLField(blank=True, null=True, max_length=800)
estimate = models.ForeignKey( # estimate = models.ForeignKey(
"db.Estimate", on_delete=models.SET_NULL, related_name="projects", null=True # "db.Estimate", on_delete=models.SET_NULL, related_name="projects", null=True
) # )
archive_in = models.IntegerField( # archive_in = models.IntegerField(
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)] # default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
) # )
close_in = models.IntegerField( # close_in = models.IntegerField(
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)] # default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
) # )
default_state = models.ForeignKey( # default_state = models.ForeignKey(
"db.State", on_delete=models.SET_NULL, null=True, related_name="default_state" # "db.State", on_delete=models.SET_NULL, null=True, related_name="default_state"
) # )
def __str__(self): def __str__(self):
"""Return name of the project""" """Return name of the project"""
@ -121,6 +115,60 @@ class Project(BaseModel):
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
class ProjectSetting(BaseModel):
workspace = models.ForeignKey(
"db.Workspace", on_delete=models.CASCADE, related_name="project_settings"
)
project = models.OneToOneField(
"db.Project", on_delete=models.CASCADE, related_name="project_settings"
)
default_assignee = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="default_assignee",
null=True,
blank=True,
)
project_lead = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="project_lead",
null=True,
blank=True,
)
module_view = models.BooleanField(default=True)
cycle_view = models.BooleanField(default=True)
issue_views_view = models.BooleanField(default=True)
page_view = models.BooleanField(default=True)
inbox_view = models.BooleanField(default=False)
estimate = models.ForeignKey(
"db.Estimate", on_delete=models.SET_NULL, related_name="projects", null=True
)
archive_in = models.IntegerField(
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
)
close_in = models.IntegerField(
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
)
close_state = models.ForeignKey(
"db.State", on_delete=models.SET_NULL, null=True, related_name="close_state"
)
def save(self, *args, **kwargs):
self.workspace = self.project.workspace
super(ProjectSetting, self).save(*args, **kwargs)
def __str__(self):
"""Return name of the project"""
return f"{self.project.name} <{self.workspace.name}>"
class Meta:
verbose_name = "Project Settings"
verbose_name_plural = "Project Settings"
db_table = "project_settings"
ordering = ("-created_at",)
class ProjectBaseModel(BaseModel): class ProjectBaseModel(BaseModel):
project = models.ForeignKey( project = models.ForeignKey(
Project, on_delete=models.CASCADE, related_name="project_%(class)s" Project, on_delete=models.CASCADE, related_name="project_%(class)s"