mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
[WEB - 1552]chore: attach pages to multiple projects (#4741)
* dev: pages migrations * dev: page models * dev: api migrations * chore: apis for pages migrations * chore: dropped project id from page label and logs * dev: pages logger exception --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
parent
61d8586f7f
commit
cb593538e4
@ -8,6 +8,8 @@ from plane.db.models import (
|
|||||||
PageLog,
|
PageLog,
|
||||||
PageLabel,
|
PageLabel,
|
||||||
Label,
|
Label,
|
||||||
|
ProjectPage,
|
||||||
|
Project,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -18,6 +20,7 @@ class PageSerializer(BaseSerializer):
|
|||||||
write_only=True,
|
write_only=True,
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
project = serializers.UUIDField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Page
|
model = Page
|
||||||
@ -33,17 +36,16 @@ class PageSerializer(BaseSerializer):
|
|||||||
"is_locked",
|
"is_locked",
|
||||||
"archived_at",
|
"archived_at",
|
||||||
"workspace",
|
"workspace",
|
||||||
"project",
|
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
"created_by",
|
"created_by",
|
||||||
"updated_by",
|
"updated_by",
|
||||||
"view_props",
|
"view_props",
|
||||||
"logo_props",
|
"logo_props",
|
||||||
|
"project",
|
||||||
]
|
]
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
"workspace",
|
"workspace",
|
||||||
"project",
|
|
||||||
"owned_by",
|
"owned_by",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -57,11 +59,23 @@ class PageSerializer(BaseSerializer):
|
|||||||
project_id = self.context["project_id"]
|
project_id = self.context["project_id"]
|
||||||
owned_by_id = self.context["owned_by_id"]
|
owned_by_id = self.context["owned_by_id"]
|
||||||
description_html = self.context["description_html"]
|
description_html = self.context["description_html"]
|
||||||
|
|
||||||
|
# Get the workspace id from the project
|
||||||
|
project = Project.objects.get(pk=project_id)
|
||||||
|
|
||||||
page = Page.objects.create(
|
page = Page.objects.create(
|
||||||
**validated_data,
|
**validated_data,
|
||||||
description_html=description_html,
|
description_html=description_html,
|
||||||
project_id=project_id,
|
|
||||||
owned_by_id=owned_by_id,
|
owned_by_id=owned_by_id,
|
||||||
|
workspace_id=project.workspace_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
ProjectPage.objects.create(
|
||||||
|
workspace_id=page.workspace_id,
|
||||||
|
project_id=project_id,
|
||||||
|
page_id=page.id,
|
||||||
|
created_by_id=page.created_by_id,
|
||||||
|
updated_by_id=page.updated_by_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
if labels is not None:
|
if labels is not None:
|
||||||
@ -70,7 +84,6 @@ class PageSerializer(BaseSerializer):
|
|||||||
PageLabel(
|
PageLabel(
|
||||||
label=label,
|
label=label,
|
||||||
page=page,
|
page=page,
|
||||||
project_id=project_id,
|
|
||||||
workspace_id=page.workspace_id,
|
workspace_id=page.workspace_id,
|
||||||
created_by_id=page.created_by_id,
|
created_by_id=page.created_by_id,
|
||||||
updated_by_id=page.updated_by_id,
|
updated_by_id=page.updated_by_id,
|
||||||
@ -90,7 +103,6 @@ class PageSerializer(BaseSerializer):
|
|||||||
PageLabel(
|
PageLabel(
|
||||||
label=label,
|
label=label,
|
||||||
page=instance,
|
page=instance,
|
||||||
project_id=instance.project_id,
|
|
||||||
workspace_id=instance.workspace_id,
|
workspace_id=instance.workspace_id,
|
||||||
created_by_id=instance.created_by_id,
|
created_by_id=instance.created_by_id,
|
||||||
updated_by_id=instance.updated_by_id,
|
updated_by_id=instance.updated_by_id,
|
||||||
@ -120,7 +132,6 @@ class SubPageSerializer(BaseSerializer):
|
|||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
"workspace",
|
"workspace",
|
||||||
"project",
|
|
||||||
"page",
|
"page",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -141,6 +152,5 @@ class PageLogSerializer(BaseSerializer):
|
|||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
"workspace",
|
"workspace",
|
||||||
"project",
|
|
||||||
"page",
|
"page",
|
||||||
]
|
]
|
||||||
|
@ -6,7 +6,7 @@ from django.core.serializers.json import DjangoJSONEncoder
|
|||||||
|
|
||||||
# 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, Subquery
|
||||||
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
|
||||||
from django.http import StreamingHttpResponse
|
from django.http import StreamingHttpResponse
|
||||||
@ -15,6 +15,7 @@ from django.http import StreamingHttpResponse
|
|||||||
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 (
|
from plane.app.serializers import (
|
||||||
PageLogSerializer,
|
PageLogSerializer,
|
||||||
@ -27,6 +28,7 @@ from plane.db.models import (
|
|||||||
PageLog,
|
PageLog,
|
||||||
UserFavorite,
|
UserFavorite,
|
||||||
ProjectMember,
|
ProjectMember,
|
||||||
|
ProjectPage,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
@ -66,28 +68,31 @@ class PageViewSet(BaseViewSet):
|
|||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
entity_type="page",
|
entity_type="page",
|
||||||
entity_identifier=OuterRef("pk"),
|
entity_identifier=OuterRef("pk"),
|
||||||
project_id=self.kwargs.get("project_id"),
|
|
||||||
workspace__slug=self.kwargs.get("slug"),
|
workspace__slug=self.kwargs.get("slug"),
|
||||||
)
|
)
|
||||||
|
project_subquery = ProjectPage.objects.filter(
|
||||||
|
page_id=OuterRef("id"), project_id=self.kwargs.get("project_id")
|
||||||
|
).values_list("project_id", flat=True)[:1]
|
||||||
return self.filter_queryset(
|
return self.filter_queryset(
|
||||||
super()
|
super()
|
||||||
.get_queryset()
|
.get_queryset()
|
||||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
.filter(project_id=self.kwargs.get("project_id"))
|
|
||||||
.filter(
|
.filter(
|
||||||
project__project_projectmember__member=self.request.user,
|
projects__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
projects__project_projectmember__is_active=True,
|
||||||
project__archived_at__isnull=True,
|
projects__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
.filter(parent__isnull=True)
|
.filter(parent__isnull=True)
|
||||||
.filter(Q(owned_by=self.request.user) | Q(access=0))
|
.filter(Q(owned_by=self.request.user) | Q(access=0))
|
||||||
.select_related("project")
|
.prefetch_related("projects")
|
||||||
.select_related("workspace")
|
.select_related("workspace")
|
||||||
.select_related("owned_by")
|
.select_related("owned_by")
|
||||||
.annotate(is_favorite=Exists(subquery))
|
.annotate(is_favorite=Exists(subquery))
|
||||||
.order_by(self.request.GET.get("order_by", "-created_at"))
|
.order_by(self.request.GET.get("order_by", "-created_at"))
|
||||||
.prefetch_related("labels")
|
.prefetch_related("labels")
|
||||||
.order_by("-is_favorite", "-created_at")
|
.order_by("-is_favorite", "-created_at")
|
||||||
|
.annotate(project=Subquery(project_subquery))
|
||||||
|
.filter(project=self.kwargs.get("project_id"))
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -115,7 +120,9 @@ class PageViewSet(BaseViewSet):
|
|||||||
def partial_update(self, request, slug, project_id, pk):
|
def partial_update(self, request, slug, project_id, pk):
|
||||||
try:
|
try:
|
||||||
page = Page.objects.get(
|
page = Page.objects.get(
|
||||||
pk=pk, workspace__slug=slug, project_id=project_id
|
pk=pk,
|
||||||
|
workspace__slug=slug,
|
||||||
|
projects__id=project_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
if page.is_locked:
|
if page.is_locked:
|
||||||
@ -127,7 +134,9 @@ class PageViewSet(BaseViewSet):
|
|||||||
parent = request.data.get("parent", None)
|
parent = request.data.get("parent", None)
|
||||||
if parent:
|
if parent:
|
||||||
_ = Page.objects.get(
|
_ = Page.objects.get(
|
||||||
pk=parent, workspace__slug=slug, project_id=project_id
|
pk=parent,
|
||||||
|
workspace__slug=slug,
|
||||||
|
projects__id=project_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Only update access if the page owner is the requesting user
|
# Only update access if the page owner is the requesting user
|
||||||
@ -187,7 +196,7 @@ class PageViewSet(BaseViewSet):
|
|||||||
|
|
||||||
def lock(self, request, slug, project_id, pk):
|
def lock(self, request, slug, project_id, pk):
|
||||||
page = Page.objects.filter(
|
page = Page.objects.filter(
|
||||||
pk=pk, workspace__slug=slug, project_id=project_id
|
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
page.is_locked = True
|
page.is_locked = True
|
||||||
@ -196,7 +205,7 @@ class PageViewSet(BaseViewSet):
|
|||||||
|
|
||||||
def unlock(self, request, slug, project_id, pk):
|
def unlock(self, request, slug, project_id, pk):
|
||||||
page = Page.objects.filter(
|
page = Page.objects.filter(
|
||||||
pk=pk, workspace__slug=slug, project_id=project_id
|
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
page.is_locked = False
|
page.is_locked = False
|
||||||
@ -211,7 +220,7 @@ class PageViewSet(BaseViewSet):
|
|||||||
|
|
||||||
def archive(self, request, slug, project_id, pk):
|
def archive(self, request, slug, project_id, pk):
|
||||||
page = Page.objects.get(
|
page = Page.objects.get(
|
||||||
pk=pk, workspace__slug=slug, project_id=project_id
|
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# only the owner or admin can archive the page
|
# only the owner or admin can archive the page
|
||||||
@ -238,7 +247,7 @@ class PageViewSet(BaseViewSet):
|
|||||||
|
|
||||||
def unarchive(self, request, slug, project_id, pk):
|
def unarchive(self, request, slug, project_id, pk):
|
||||||
page = Page.objects.get(
|
page = Page.objects.get(
|
||||||
pk=pk, workspace__slug=slug, project_id=project_id
|
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# only the owner or admin can un archive the page
|
# only the owner or admin can un archive the page
|
||||||
@ -267,7 +276,7 @@ class PageViewSet(BaseViewSet):
|
|||||||
|
|
||||||
def destroy(self, request, slug, project_id, pk):
|
def destroy(self, request, slug, project_id, pk):
|
||||||
page = Page.objects.get(
|
page = Page.objects.get(
|
||||||
pk=pk, workspace__slug=slug, project_id=project_id
|
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# only the owner and admin can delete the page
|
# only the owner and admin can delete the page
|
||||||
@ -380,7 +389,6 @@ class SubPagesEndpoint(BaseAPIView):
|
|||||||
pages = (
|
pages = (
|
||||||
PageLog.objects.filter(
|
PageLog.objects.filter(
|
||||||
page_id=page_id,
|
page_id=page_id,
|
||||||
project_id=project_id,
|
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
entity_name__in=["forward_link", "back_link"],
|
entity_name__in=["forward_link", "back_link"],
|
||||||
)
|
)
|
||||||
@ -399,7 +407,7 @@ class PagesDescriptionViewSet(BaseViewSet):
|
|||||||
|
|
||||||
def retrieve(self, request, slug, project_id, pk):
|
def retrieve(self, request, slug, project_id, pk):
|
||||||
page = Page.objects.get(
|
page = Page.objects.get(
|
||||||
pk=pk, workspace__slug=slug, project_id=project_id
|
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||||
)
|
)
|
||||||
binary_data = page.description_binary
|
binary_data = page.description_binary
|
||||||
|
|
||||||
@ -419,7 +427,7 @@ class PagesDescriptionViewSet(BaseViewSet):
|
|||||||
|
|
||||||
def partial_update(self, request, slug, project_id, pk):
|
def partial_update(self, request, slug, project_id, pk):
|
||||||
page = Page.objects.get(
|
page = Page.objects.get(
|
||||||
pk=pk, workspace__slug=slug, project_id=project_id
|
pk=pk, workspace__slug=slug, projects__id=project_id
|
||||||
)
|
)
|
||||||
|
|
||||||
base64_data = request.data.get("description_binary")
|
base64_data = request.data.get("description_binary")
|
||||||
|
@ -147,9 +147,9 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
pages = Page.objects.filter(
|
pages = Page.objects.filter(
|
||||||
q,
|
q,
|
||||||
project__project_projectmember__member=self.request.user,
|
projects__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
projects__project_projectmember__is_active=True,
|
||||||
project__archived_at__isnull=True,
|
projects__archived_at__isnull=True,
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -249,7 +249,7 @@ class IssueSearchEndpoint(BaseAPIView):
|
|||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
project__project_projectmember__member=self.request.user,
|
project__project_projectmember__member=self.request.user,
|
||||||
project__project_projectmember__is_active=True,
|
project__project_projectmember__is_active=True,
|
||||||
project__archived_at__isnull=True
|
project__archived_at__isnull=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if workspace_search == "false":
|
if workspace_search == "false":
|
||||||
|
@ -278,7 +278,6 @@ def create_page_labels(workspace, project, user_id, pages_count):
|
|||||||
PageLabel(
|
PageLabel(
|
||||||
page_id=page,
|
page_id=page,
|
||||||
label_id=label,
|
label_id=label,
|
||||||
project=project,
|
|
||||||
workspace=workspace,
|
workspace=workspace,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -59,7 +59,6 @@ def page_transaction(new_value, old_value, page_id):
|
|||||||
entity_identifier=mention["entity_identifier"],
|
entity_identifier=mention["entity_identifier"],
|
||||||
entity_name=mention["entity_name"],
|
entity_name=mention["entity_name"],
|
||||||
workspace_id=page.workspace_id,
|
workspace_id=page.workspace_id,
|
||||||
project_id=page.project_id,
|
|
||||||
created_at=timezone.now(),
|
created_at=timezone.now(),
|
||||||
updated_at=timezone.now(),
|
updated_at=timezone.now(),
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,257 @@
|
|||||||
|
# Generated by Django 4.2.11 on 2024-06-07 12:04
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_pages(apps, schema_editor):
|
||||||
|
ProjectPage = apps.get_model("db", "ProjectPage")
|
||||||
|
Page = apps.get_model("db", "Page")
|
||||||
|
ProjectPage.objects.bulk_create(
|
||||||
|
[
|
||||||
|
ProjectPage(
|
||||||
|
workspace_id=page.get("workspace_id"),
|
||||||
|
project_id=page.get("project_id"),
|
||||||
|
page_id=page.get("id"),
|
||||||
|
created_by_id=page.get("created_by_id"),
|
||||||
|
updated_by_id=page.get("updated_by_id"),
|
||||||
|
)
|
||||||
|
for page in Page.objects.values(
|
||||||
|
"workspace_id",
|
||||||
|
"project_id",
|
||||||
|
"id",
|
||||||
|
"created_by_id",
|
||||||
|
"updated_by_id",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
batch_size=1000,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("db", "0067_issue_estimate"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="page",
|
||||||
|
name="is_global",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="ProjectPage",
|
||||||
|
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="%(class)s_created_by",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="Created By",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"page",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="project_pages",
|
||||||
|
to="db.page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"project",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="project_pages",
|
||||||
|
to="db.project",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"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_pages",
|
||||||
|
to="db.workspace",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Project Page",
|
||||||
|
"verbose_name_plural": "Project Pages",
|
||||||
|
"db_table": "project_pages",
|
||||||
|
"ordering": ("-created_at",),
|
||||||
|
"unique_together": {("project", "page")},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="TeamPage",
|
||||||
|
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="%(class)s_created_by",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="Created By",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"page",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="team_pages",
|
||||||
|
to="db.page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"team",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="team_pages",
|
||||||
|
to="db.team",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"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="team_pages",
|
||||||
|
to="db.workspace",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Team Page",
|
||||||
|
"verbose_name_plural": "Team Pages",
|
||||||
|
"db_table": "team_pages",
|
||||||
|
"ordering": ("-created_at",),
|
||||||
|
"unique_together": {("team", "page")},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="page",
|
||||||
|
name="projects",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="pages", through="db.ProjectPage", to="db.project"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="page",
|
||||||
|
name="teams",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="pages", through="db.TeamPage", to="db.team"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_pages),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="page",
|
||||||
|
name="project",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="page",
|
||||||
|
name="workspace",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="pages",
|
||||||
|
to="db.workspace",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="pagelabel",
|
||||||
|
name="project",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="pagelog",
|
||||||
|
name="project",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="pagelabel",
|
||||||
|
name="workspace",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="workspace_page_label",
|
||||||
|
to="db.workspace",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="pagelog",
|
||||||
|
name="workspace",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="workspace_page_log",
|
||||||
|
to="db.workspace",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -50,7 +50,7 @@ from .notification import (
|
|||||||
Notification,
|
Notification,
|
||||||
UserNotificationPreference,
|
UserNotificationPreference,
|
||||||
)
|
)
|
||||||
from .page import Page, PageFavorite, PageLabel, PageLog
|
from .page import Page, PageFavorite, PageLabel, PageLog, ProjectPage
|
||||||
from .project import (
|
from .project import (
|
||||||
Project,
|
Project,
|
||||||
ProjectBaseModel,
|
ProjectBaseModel,
|
||||||
|
@ -9,13 +9,17 @@ from django.db import models
|
|||||||
from plane.utils.html_processor import strip_tags
|
from plane.utils.html_processor import strip_tags
|
||||||
|
|
||||||
from .project import ProjectBaseModel
|
from .project import ProjectBaseModel
|
||||||
|
from .base import BaseModel
|
||||||
|
|
||||||
|
|
||||||
def get_view_props():
|
def get_view_props():
|
||||||
return {"full_width": False}
|
return {"full_width": False}
|
||||||
|
|
||||||
|
|
||||||
class Page(ProjectBaseModel):
|
class Page(BaseModel):
|
||||||
|
workspace = models.ForeignKey(
|
||||||
|
"db.Workspace", on_delete=models.CASCADE, related_name="pages"
|
||||||
|
)
|
||||||
name = models.CharField(max_length=255, blank=True)
|
name = models.CharField(max_length=255, blank=True)
|
||||||
description = models.JSONField(default=dict, blank=True)
|
description = models.JSONField(default=dict, blank=True)
|
||||||
description_binary = models.BinaryField(null=True)
|
description_binary = models.BinaryField(null=True)
|
||||||
@ -44,6 +48,13 @@ class Page(ProjectBaseModel):
|
|||||||
is_locked = models.BooleanField(default=False)
|
is_locked = models.BooleanField(default=False)
|
||||||
view_props = models.JSONField(default=get_view_props)
|
view_props = models.JSONField(default=get_view_props)
|
||||||
logo_props = models.JSONField(default=dict)
|
logo_props = models.JSONField(default=dict)
|
||||||
|
is_global = models.BooleanField(default=False)
|
||||||
|
projects = models.ManyToManyField(
|
||||||
|
"db.Project", related_name="pages", through="db.ProjectPage"
|
||||||
|
)
|
||||||
|
teams = models.ManyToManyField(
|
||||||
|
"db.Team", related_name="pages", through="db.TeamPage"
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Page"
|
verbose_name = "Page"
|
||||||
@ -56,7 +67,7 @@ class Page(ProjectBaseModel):
|
|||||||
return f"{self.owned_by.email} <{self.name}>"
|
return f"{self.owned_by.email} <{self.name}>"
|
||||||
|
|
||||||
|
|
||||||
class PageLog(ProjectBaseModel):
|
class PageLog(BaseModel):
|
||||||
TYPE_CHOICES = (
|
TYPE_CHOICES = (
|
||||||
("to_do", "To Do"),
|
("to_do", "To Do"),
|
||||||
("issue", "issue"),
|
("issue", "issue"),
|
||||||
@ -81,6 +92,9 @@ class PageLog(ProjectBaseModel):
|
|||||||
choices=TYPE_CHOICES,
|
choices=TYPE_CHOICES,
|
||||||
verbose_name="Transaction Type",
|
verbose_name="Transaction Type",
|
||||||
)
|
)
|
||||||
|
workspace = models.ForeignKey(
|
||||||
|
"db.Workspace", on_delete=models.CASCADE, related_name="workspace_page_log"
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ["page", "transaction"]
|
unique_together = ["page", "transaction"]
|
||||||
@ -171,13 +185,18 @@ class PageFavorite(ProjectBaseModel):
|
|||||||
return f"{self.user.email} <{self.page.name}>"
|
return f"{self.user.email} <{self.page.name}>"
|
||||||
|
|
||||||
|
|
||||||
class PageLabel(ProjectBaseModel):
|
class PageLabel(BaseModel):
|
||||||
label = models.ForeignKey(
|
label = models.ForeignKey(
|
||||||
"db.Label", on_delete=models.CASCADE, related_name="page_labels"
|
"db.Label", on_delete=models.CASCADE, related_name="page_labels"
|
||||||
)
|
)
|
||||||
page = models.ForeignKey(
|
page = models.ForeignKey(
|
||||||
"db.Page", on_delete=models.CASCADE, related_name="page_labels"
|
"db.Page", on_delete=models.CASCADE, related_name="page_labels"
|
||||||
)
|
)
|
||||||
|
workspace = models.ForeignKey(
|
||||||
|
"db.Workspace",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="workspace_page_label",
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Page Label"
|
verbose_name = "Page Label"
|
||||||
@ -187,3 +206,44 @@ class PageLabel(ProjectBaseModel):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.page.name} {self.label.name}"
|
return f"{self.page.name} {self.label.name}"
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectPage(BaseModel):
|
||||||
|
project = models.ForeignKey(
|
||||||
|
"db.Project", on_delete=models.CASCADE, related_name="project_pages"
|
||||||
|
)
|
||||||
|
page = models.ForeignKey(
|
||||||
|
"db.Page", on_delete=models.CASCADE, related_name="project_pages"
|
||||||
|
)
|
||||||
|
workspace = models.ForeignKey(
|
||||||
|
"db.Workspace", on_delete=models.CASCADE, related_name="project_pages"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ["project", "page"]
|
||||||
|
verbose_name = "Project Page"
|
||||||
|
verbose_name_plural = "Project Pages"
|
||||||
|
db_table = "project_pages"
|
||||||
|
ordering = ("-created_at",)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.project.name} {self.page.name}"
|
||||||
|
|
||||||
|
|
||||||
|
class TeamPage(BaseModel):
|
||||||
|
team = models.ForeignKey(
|
||||||
|
"db.Team", on_delete=models.CASCADE, related_name="team_pages"
|
||||||
|
)
|
||||||
|
page = models.ForeignKey(
|
||||||
|
"db.Page", on_delete=models.CASCADE, related_name="team_pages"
|
||||||
|
)
|
||||||
|
workspace = models.ForeignKey(
|
||||||
|
"db.Workspace", on_delete=models.CASCADE, related_name="team_pages"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ["team", "page"]
|
||||||
|
verbose_name = "Team Page"
|
||||||
|
verbose_name_plural = "Team Pages"
|
||||||
|
db_table = "team_pages"
|
||||||
|
ordering = ("-created_at",)
|
||||||
|
@ -10,14 +10,13 @@ from sentry_sdk import capture_exception
|
|||||||
|
|
||||||
|
|
||||||
def log_exception(e):
|
def log_exception(e):
|
||||||
print(e)
|
|
||||||
# Log the error
|
# Log the error
|
||||||
logger = logging.getLogger("plane")
|
logger = logging.getLogger("plane")
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
# Log traceback if running in Debug
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
logger.error(traceback.format_exc(e))
|
# Print the traceback if in debug mode
|
||||||
|
traceback.print_exc(e)
|
||||||
|
|
||||||
# Capture in sentry if configured
|
# Capture in sentry if configured
|
||||||
capture_exception(e)
|
capture_exception(e)
|
||||||
|
Loading…
Reference in New Issue
Block a user