Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89a5088f31 | |||
| 9789757d2d | |||
| 2aee627616 |
+5
-10
@@ -21,20 +21,15 @@ AWS_S3_BUCKET_NAME="uploads"
|
||||
FILE_SIZE_LIMIT=5242880
|
||||
|
||||
# GPT settings
|
||||
OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint
|
||||
OPENAI_API_KEY="sk-" # add your openai key here
|
||||
GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access
|
||||
OPENAI_API_BASE="https://api.openai.com/v1" # deprecated
|
||||
OPENAI_API_KEY="sk-" # deprecated
|
||||
GPT_ENGINE="gpt-3.5-turbo" # deprecated
|
||||
|
||||
# Settings related to Docker
|
||||
DOCKERIZED=1
|
||||
DOCKERIZED=1 # deprecated
|
||||
|
||||
# set to 1 If using the pre-configured minio setup
|
||||
USE_MINIO=1
|
||||
|
||||
# Nginx Configuration
|
||||
NGINX_PORT=80
|
||||
|
||||
# Set it to 0, to disable it
|
||||
ENABLE_WEBHOOK=1
|
||||
|
||||
# Set it to 0, to disable it
|
||||
ENABLE_API=1
|
||||
@@ -44,7 +44,6 @@ ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||
ENV DJANGO_SETTINGS_MODULE plane.settings.production
|
||||
ENV DOCKERIZED 1
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
|
||||
+9
-10
@@ -31,12 +31,10 @@ AWS_S3_BUCKET_NAME="uploads"
|
||||
FILE_SIZE_LIMIT=5242880
|
||||
|
||||
# GPT settings
|
||||
OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint
|
||||
OPENAI_API_KEY="sk-" # add your openai key here
|
||||
GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access
|
||||
OPENAI_API_BASE="https://api.openai.com/v1" # deprecated
|
||||
OPENAI_API_KEY="sk-" # deprecated
|
||||
GPT_ENGINE="gpt-3.5-turbo" # deprecated
|
||||
|
||||
# Settings related to Docker
|
||||
DOCKERIZED=1
|
||||
# set to 1 If using the pre-configured minio setup
|
||||
USE_MINIO=1
|
||||
|
||||
@@ -115,15 +113,16 @@ AWS_S3_BUCKET_NAME="uploads"
|
||||
FILE_SIZE_LIMIT=5242880
|
||||
|
||||
# GPT settings
|
||||
OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint
|
||||
OPENAI_API_KEY="sk-" # add your openai key here
|
||||
GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access
|
||||
OPENAI_API_BASE="https://api.openai.com/v1" # deprecated
|
||||
OPENAI_API_KEY="sk-" # deprecated
|
||||
GPT_ENGINE="gpt-3.5-turbo" # deprecated
|
||||
|
||||
# Settings related to Docker
|
||||
DOCKERIZED=1 # Deprecated
|
||||
|
||||
# Github
|
||||
GITHUB_CLIENT_SECRET="" # For fetching release notes
|
||||
|
||||
# Settings related to Docker
|
||||
DOCKERIZED=1
|
||||
# set to 1 If using the pre-configured minio setup
|
||||
USE_MINIO=1
|
||||
|
||||
|
||||
+4
-10
@@ -1,7 +1,7 @@
|
||||
# Backend
|
||||
# Debug value for api server use it as 0 for production use
|
||||
DEBUG=0
|
||||
CORS_ALLOWED_ORIGINS="http://localhost"
|
||||
CORS_ALLOWED_ORIGINS=""
|
||||
|
||||
# Error logs
|
||||
SENTRY_DSN=""
|
||||
@@ -38,9 +38,9 @@ AWS_S3_BUCKET_NAME="uploads"
|
||||
FILE_SIZE_LIMIT=5242880
|
||||
|
||||
# GPT settings
|
||||
OPENAI_API_BASE="https://api.openai.com/v1" # change if using a custom endpoint
|
||||
OPENAI_API_KEY="sk-" # add your openai key here
|
||||
GPT_ENGINE="gpt-3.5-turbo" # use "gpt-4" if you have access
|
||||
OPENAI_API_BASE="https://api.openai.com/v1" # deprecated
|
||||
OPENAI_API_KEY="sk-" # deprecated
|
||||
GPT_ENGINE="gpt-3.5-turbo" # deprecated
|
||||
|
||||
# Github
|
||||
GITHUB_CLIENT_SECRET="" # For fetching release notes
|
||||
@@ -70,12 +70,6 @@ ENABLE_MAGIC_LINK_LOGIN="0"
|
||||
# Email redirections and minio domain settings
|
||||
WEB_URL="http://localhost"
|
||||
|
||||
# Set it to 0, to disable it
|
||||
ENABLE_WEBHOOK=1
|
||||
|
||||
# Set it to 0, to disable it
|
||||
ENABLE_API=1
|
||||
|
||||
# Gunicorn Workers
|
||||
GUNICORN_WORKERS=2
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ from .integration import (
|
||||
|
||||
from .importer import ImporterSerializer
|
||||
|
||||
from .page import PageSerializer, PageLogSerializer, SubPageSerializer, PageFavoriteSerializer
|
||||
from .page import PageSerializer, PageBlockSerializer, PageFavoriteSerializer
|
||||
|
||||
from .estimate import (
|
||||
EstimateSerializer,
|
||||
|
||||
@@ -6,7 +6,28 @@ from .base import BaseSerializer
|
||||
from .issue import IssueFlatSerializer, LabelLiteSerializer
|
||||
from .workspace import WorkspaceLiteSerializer
|
||||
from .project import ProjectLiteSerializer
|
||||
from plane.db.models import Page, PageLog, PageFavorite, PageLabel, Label, Issue, Module
|
||||
from plane.db.models import Page, PageBlock, PageFavorite, PageLabel, Label
|
||||
|
||||
|
||||
class PageBlockSerializer(BaseSerializer):
|
||||
issue_detail = IssueFlatSerializer(source="issue", read_only=True)
|
||||
project_detail = ProjectLiteSerializer(source="project", read_only=True)
|
||||
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = PageBlock
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"page",
|
||||
]
|
||||
|
||||
class PageBlockLiteSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = PageBlock
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class PageSerializer(BaseSerializer):
|
||||
@@ -17,6 +38,7 @@ class PageSerializer(BaseSerializer):
|
||||
write_only=True,
|
||||
required=False,
|
||||
)
|
||||
blocks = PageBlockLiteSerializer(read_only=True, many=True)
|
||||
project_detail = ProjectLiteSerializer(source="project", read_only=True)
|
||||
workspace_detail = WorkspaceLiteSerializer(source="workspace", read_only=True)
|
||||
|
||||
@@ -80,41 +102,6 @@ class PageSerializer(BaseSerializer):
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class SubPageSerializer(BaseSerializer):
|
||||
entity_details = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = PageLog
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"page",
|
||||
]
|
||||
|
||||
def get_entity_details(self, obj):
|
||||
entity_name = obj.entity_name
|
||||
if entity_name == 'forward_link' or entity_name == 'back_link':
|
||||
try:
|
||||
page = Page.objects.get(pk=obj.entity_identifier)
|
||||
return PageSerializer(page).data
|
||||
except Page.DoesNotExist:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
class PageLogSerializer(BaseSerializer):
|
||||
|
||||
class Meta:
|
||||
model = PageLog
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
"project",
|
||||
"page",
|
||||
]
|
||||
|
||||
|
||||
class PageFavoriteSerializer(BaseSerializer):
|
||||
page_detail = PageSerializer(source="page", read_only=True)
|
||||
|
||||
|
||||
@@ -49,10 +49,6 @@ urlpatterns = [
|
||||
*user_urls,
|
||||
*view_urls,
|
||||
*workspace_urls,
|
||||
*api_urls,
|
||||
*webhook_urls,
|
||||
]
|
||||
|
||||
if settings.ENABLE_WEBHOOK:
|
||||
urlpatterns += webhook_urls
|
||||
|
||||
if settings.ENABLE_API:
|
||||
urlpatterns += api_urls
|
||||
|
||||
@@ -3,9 +3,9 @@ from django.urls import path
|
||||
|
||||
from plane.api.views import (
|
||||
PageViewSet,
|
||||
PageBlockViewSet,
|
||||
PageFavoriteViewSet,
|
||||
PageLogEndpoint,
|
||||
SubPagesEndpoint,
|
||||
CreateIssueFromPageBlockEndpoint,
|
||||
)
|
||||
|
||||
|
||||
@@ -31,6 +31,27 @@ urlpatterns = [
|
||||
),
|
||||
name="project-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/page-blocks/",
|
||||
PageBlockViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="project-page-blocks",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/page-blocks/<uuid:pk>/",
|
||||
PageBlockViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="project-page-blocks",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-pages/",
|
||||
PageFavoriteViewSet.as_view(
|
||||
@@ -51,83 +72,8 @@ urlpatterns = [
|
||||
name="user-favorite-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="project-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:pk>/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="project-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/archive/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "archive",
|
||||
}
|
||||
),
|
||||
name="project-page-archive",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/unarchive/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "unarchive",
|
||||
}
|
||||
),
|
||||
name="project-page-unarchive",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-pages/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"get": "archive_list",
|
||||
}
|
||||
),
|
||||
name="project-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/lock/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "lock",
|
||||
}
|
||||
),
|
||||
name="project-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/unlock/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "unlock",
|
||||
}
|
||||
),
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/transactions/",
|
||||
PageLogEndpoint.as_view(),
|
||||
name="page-transactions",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/transactions/<uuid:transaction>/",
|
||||
PageLogEndpoint.as_view(),
|
||||
name="page-transactions",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/sub-pages/",
|
||||
SubPagesEndpoint.as_view(),
|
||||
name="sub-page",
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/page-blocks/<uuid:page_block_id>/issues/",
|
||||
CreateIssueFromPageBlockEndpoint.as_view(),
|
||||
name="page-block-issues",
|
||||
),
|
||||
]
|
||||
|
||||
@@ -124,10 +124,9 @@ from plane.api.views import (
|
||||
## End Modules
|
||||
# Pages
|
||||
PageViewSet,
|
||||
PageLogEndpoint,
|
||||
SubPagesEndpoint,
|
||||
PageBlockViewSet,
|
||||
PageFavoriteViewSet,
|
||||
CreateIssueFromBlockEndpoint,
|
||||
CreateIssueFromPageBlockEndpoint,
|
||||
## End Pages
|
||||
# Api Tokens
|
||||
ApiTokenEndpoint,
|
||||
@@ -1223,81 +1222,25 @@ urlpatterns = [
|
||||
name="project-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/archive/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "archive",
|
||||
}
|
||||
),
|
||||
name="project-page-archive",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/unarchive/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "unarchive",
|
||||
}
|
||||
),
|
||||
name="project-page-unarchive"
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-pages/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"get": "archive_list",
|
||||
}
|
||||
),
|
||||
name="project-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/lock/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "lock",
|
||||
}
|
||||
),
|
||||
name="project-pages",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/unlock/",
|
||||
PageViewSet.as_view(
|
||||
{
|
||||
"post": "unlock",
|
||||
}
|
||||
)
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/transactions/",
|
||||
PageLogEndpoint.as_view(), name="page-transactions"
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/transactions/<uuid:transaction>/",
|
||||
PageLogEndpoint.as_view(), name="page-transactions"
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/sub-pages/",
|
||||
SubPagesEndpoint.as_view(), name="sub-page"
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/",
|
||||
BulkEstimatePointEndpoint.as_view(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/page-blocks/",
|
||||
PageBlockViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="bulk-create-estimate-points",
|
||||
name="project-page-blocks",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/",
|
||||
BulkEstimatePointEndpoint.as_view(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/page-blocks/<uuid:pk>/",
|
||||
PageBlockViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="bulk-create-estimate-points",
|
||||
name="project-page-blocks",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/user-favorite-pages/",
|
||||
@@ -1320,7 +1263,7 @@ urlpatterns = [
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/pages/<uuid:page_id>/page-blocks/<uuid:page_block_id>/issues/",
|
||||
CreateIssueFromBlockEndpoint.as_view(),
|
||||
CreateIssueFromPageBlockEndpoint.as_view(),
|
||||
name="page-block-issues",
|
||||
),
|
||||
## End Pages
|
||||
|
||||
@@ -138,10 +138,9 @@ from .importer import (
|
||||
|
||||
from .page import (
|
||||
PageViewSet,
|
||||
PageBlockViewSet,
|
||||
PageFavoriteViewSet,
|
||||
PageLogEndpoint,
|
||||
SubPagesEndpoint,
|
||||
CreateIssueFromBlockEndpoint,
|
||||
CreateIssueFromPageBlockEndpoint,
|
||||
)
|
||||
|
||||
from .search import GlobalSearchEndpoint, IssueSearchEndpoint
|
||||
|
||||
@@ -51,7 +51,6 @@ class WebhookMixin:
|
||||
self.webhook_event
|
||||
and self.request.method in ["POST", "PATCH", "DELETE"]
|
||||
and response.status_code in [200, 201, 204]
|
||||
and settings.ENABLE_WEBHOOK
|
||||
):
|
||||
send_webhook.delay(
|
||||
event=self.webhook_event,
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
# Python imports
|
||||
from datetime import timedelta, date, datetime
|
||||
from datetime import timedelta, date
|
||||
|
||||
# Django imports
|
||||
from django.db import connection
|
||||
from django.db.models import Exists, OuterRef, Q, Prefetch
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.gzip import gzip_page
|
||||
from django.db.models import (
|
||||
OuterRef,
|
||||
Func,
|
||||
F,
|
||||
Q,
|
||||
Exists,
|
||||
)
|
||||
|
||||
# Third party imports
|
||||
from rest_framework import status
|
||||
@@ -25,37 +15,20 @@ from .base import BaseViewSet, BaseAPIView
|
||||
from plane.api.permissions import ProjectEntityPermission
|
||||
from plane.db.models import (
|
||||
Page,
|
||||
PageBlock,
|
||||
PageFavorite,
|
||||
Issue,
|
||||
IssueAssignee,
|
||||
IssueActivity,
|
||||
PageLog,
|
||||
)
|
||||
from plane.api.serializers import (
|
||||
PageSerializer,
|
||||
PageBlockSerializer,
|
||||
PageFavoriteSerializer,
|
||||
PageLogSerializer,
|
||||
IssueLiteSerializer,
|
||||
SubPageSerializer,
|
||||
)
|
||||
|
||||
|
||||
def unarchive_archive_page_and_descendants(page_id, archived_at):
|
||||
# Your SQL query
|
||||
sql = """
|
||||
WITH RECURSIVE descendants AS (
|
||||
SELECT id FROM pages WHERE id = %s
|
||||
UNION ALL
|
||||
SELECT pages.id FROM pages, descendants WHERE pages.parent_id = descendants.id
|
||||
)
|
||||
UPDATE pages SET archived_at = %s WHERE id IN (SELECT id FROM descendants);
|
||||
"""
|
||||
|
||||
# Execute the SQL query
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(sql, [page_id, archived_at])
|
||||
|
||||
|
||||
class PageViewSet(BaseViewSet):
|
||||
serializer_class = PageSerializer
|
||||
model = Page
|
||||
@@ -79,7 +52,6 @@ class PageViewSet(BaseViewSet):
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(project_id=self.kwargs.get("project_id"))
|
||||
.filter(project__project_projectmember__member=self.request.user)
|
||||
.filter(parent__isnull=True)
|
||||
.filter(Q(owned_by=self.request.user) | Q(access=0))
|
||||
.select_related("project")
|
||||
.select_related("workspace")
|
||||
@@ -87,10 +59,23 @@ class PageViewSet(BaseViewSet):
|
||||
.annotate(is_favorite=Exists(subquery))
|
||||
.order_by(self.request.GET.get("order_by", "-created_at"))
|
||||
.prefetch_related("labels")
|
||||
.order_by("-is_favorite", "-created_at")
|
||||
.order_by("name", "-is_favorite")
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"blocks",
|
||||
queryset=PageBlock.objects.select_related(
|
||||
"page", "issue", "workspace", "project"
|
||||
),
|
||||
)
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(
|
||||
project_id=self.kwargs.get("project_id"), owned_by=self.request.user
|
||||
)
|
||||
|
||||
def create(self, request, slug, project_id):
|
||||
serializer = PageSerializer(
|
||||
data=request.data,
|
||||
@@ -103,88 +88,34 @@ class PageViewSet(BaseViewSet):
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def partial_update(self, request, slug, project_id, pk):
|
||||
try:
|
||||
page = Page.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
|
||||
|
||||
if page.is_locked:
|
||||
return Response(
|
||||
{"error": "Page is locked"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
parent = request.data.get("parent", None)
|
||||
if parent:
|
||||
_ = Page.objects.get(
|
||||
pk=parent, workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
|
||||
# Only update access if the page owner is the requesting user
|
||||
if (
|
||||
page.access != request.data.get("access", page.access)
|
||||
and page.owned_by_id != request.user.id
|
||||
):
|
||||
return Response(
|
||||
{
|
||||
"error": "Access cannot be updated since this page is owned by someone else"
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
serializer = PageSerializer(page, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
except Page.DoesNotExist:
|
||||
page = Page.objects.get(pk=pk, workspace__slug=slug, project_id=project_id)
|
||||
# Only update access if the page owner is the requesting user
|
||||
if (
|
||||
page.access != request.data.get("access", page.access)
|
||||
and page.owned_by_id != request.user.id
|
||||
):
|
||||
return Response(
|
||||
{
|
||||
"error": "Access cannot be updated since this page is owned by someone else"
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
def lock(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
||||
|
||||
# only the owner can lock the page
|
||||
if request.user.id != page.owned_by_id:
|
||||
return Response(
|
||||
{"error": "Only the page owner can lock the page"},
|
||||
)
|
||||
|
||||
page.is_locked = True
|
||||
page.save()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def unlock(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
||||
|
||||
# only the owner can unlock the page
|
||||
if request.user.id != page.owned_by_id:
|
||||
return Response(
|
||||
{"error": "Only the page owner can unlock the page"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
page.is_locked = False
|
||||
page.save()
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
serializer = PageSerializer(page, data=request.data, partial=True)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def list(self, request, slug, project_id):
|
||||
queryset = self.get_queryset().filter(archived_at__isnull=True)
|
||||
queryset = self.get_queryset()
|
||||
page_view = request.GET.get("page_view", False)
|
||||
|
||||
if not page_view:
|
||||
return Response(
|
||||
{"error": "Page View parameter is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
return Response({"error": "Page View parameter is required"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# All Pages
|
||||
if page_view == "all":
|
||||
return Response(
|
||||
PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response(PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK)
|
||||
|
||||
# Recent pages
|
||||
if page_view == "recent":
|
||||
@@ -192,130 +123,66 @@ class PageViewSet(BaseViewSet):
|
||||
day_before = current_time - timedelta(days=1)
|
||||
todays_pages = queryset.filter(updated_at__date=date.today())
|
||||
yesterdays_pages = queryset.filter(updated_at__date=day_before)
|
||||
earlier_this_week = queryset.filter(
|
||||
updated_at__date__range=(
|
||||
earlier_this_week = queryset.filter( updated_at__date__range=(
|
||||
(timezone.now() - timedelta(days=7)),
|
||||
(timezone.now() - timedelta(days=2)),
|
||||
)
|
||||
)
|
||||
))
|
||||
return Response(
|
||||
{
|
||||
"today": PageSerializer(todays_pages, many=True).data,
|
||||
"yesterday": PageSerializer(yesterdays_pages, many=True).data,
|
||||
"earlier_this_week": PageSerializer(
|
||||
earlier_this_week, many=True
|
||||
).data,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
{
|
||||
"today": PageSerializer(todays_pages, many=True).data,
|
||||
"yesterday": PageSerializer(yesterdays_pages, many=True).data,
|
||||
"earlier_this_week": PageSerializer(earlier_this_week, many=True).data,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
# Favorite Pages
|
||||
if page_view == "favorite":
|
||||
queryset = queryset.filter(is_favorite=True)
|
||||
return Response(
|
||||
PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
return Response(PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK)
|
||||
|
||||
# My pages
|
||||
if page_view == "created_by_me":
|
||||
queryset = queryset.filter(owned_by=request.user)
|
||||
return Response(
|
||||
PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK
|
||||
)
|
||||
return Response(PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK)
|
||||
|
||||
# Created by other Pages
|
||||
if page_view == "created_by_other":
|
||||
queryset = queryset.filter(~Q(owned_by=request.user), access=0)
|
||||
return Response(
|
||||
PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK
|
||||
)
|
||||
queryset = queryset.filter(~Q(owned_by=request.user), access=0)
|
||||
return Response(PageSerializer(queryset, many=True).data, status=status.HTTP_200_OK)
|
||||
|
||||
return Response(
|
||||
{"error": "No matching view found"}, status=status.HTTP_400_BAD_REQUEST
|
||||
return Response({"error": "No matching view found"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class PageBlockViewSet(BaseViewSet):
|
||||
serializer_class = PageBlockSerializer
|
||||
model = PageBlock
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
|
||||
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(page_id=self.kwargs.get("page_id"))
|
||||
.filter(project__project_projectmember__member=self.request.user)
|
||||
.select_related("project")
|
||||
.select_related("workspace")
|
||||
.select_related("page")
|
||||
.select_related("issue")
|
||||
.order_by("sort_order")
|
||||
.distinct()
|
||||
)
|
||||
|
||||
def archive(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
||||
|
||||
if page.owned_by_id != request.user.id:
|
||||
return Response(
|
||||
{"error": "Only the owner of the page can archive a page"},
|
||||
status=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
|
||||
unarchive_archive_page_and_descendants(page_id, datetime.now())
|
||||
|
||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def unarchive(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.get(pk=page_id, workspace__slug=slug, project_id=project_id)
|
||||
|
||||
if page.owned_by_id != request.user.id:
|
||||
return Response(
|
||||
{"error": "Only the owner of the page can unarchive a page"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
page.parent = None
|
||||
page.save()
|
||||
|
||||
unarchive_archive_page_and_descendants(page_id, None)
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def archive_list(self, request, slug, project_id):
|
||||
pages = (
|
||||
Page.objects.filter(
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
)
|
||||
.filter(archived_at__isnull=False)
|
||||
.filter(parent_id__isnull=True)
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
page_id=self.kwargs.get("page_id"),
|
||||
)
|
||||
|
||||
return Response(
|
||||
PageSerializer(pages, many=True).data, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
unarchive_archive_page_and_descendants(page_id, datetime.now())
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def unarchive(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.get(
|
||||
project_id=project_id,
|
||||
owned_by_id=request.user.id,
|
||||
workspace__slug=slug,
|
||||
pk=page_id,
|
||||
)
|
||||
|
||||
page.parent = None
|
||||
page.save()
|
||||
|
||||
unarchive_archive_page_and_descendants(page_id, None)
|
||||
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def archive_list(self, request, slug, project_id):
|
||||
pages = (
|
||||
Page.objects.filter(
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
)
|
||||
.filter(archived_at__isnull=False)
|
||||
.filter(parent_id__isnull=True)
|
||||
)
|
||||
|
||||
if not pages:
|
||||
return Response(
|
||||
{"error": "No pages found"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
return Response(
|
||||
PageSerializer(pages, many=True).data, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
|
||||
|
||||
class PageFavoriteViewSet(BaseViewSet):
|
||||
permission_classes = [
|
||||
@@ -329,7 +196,6 @@ class PageFavoriteViewSet(BaseViewSet):
|
||||
return self.filter_queryset(
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(archived_at__isnull=True)
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(user=self.request.user)
|
||||
.select_related("page", "page__owned_by")
|
||||
@@ -352,62 +218,24 @@ class PageFavoriteViewSet(BaseViewSet):
|
||||
page_favorite.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
class PageLogEndpoint(BaseAPIView):
|
||||
class CreateIssueFromPageBlockEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
|
||||
serializer_class = PageLogSerializer
|
||||
model = PageLog
|
||||
|
||||
def post(self, request, slug, project_id, page_id):
|
||||
serializer = PageLogSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(project_id=project_id, page_id=page_id)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def patch(self, request, slug, project_id, page_id, transaction):
|
||||
page_transaction = PageLog.objects.get(
|
||||
def post(self, request, slug, project_id, page_id, page_block_id):
|
||||
page_block = PageBlock.objects.get(
|
||||
pk=page_block_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
page_id=page_id,
|
||||
transaction=transaction,
|
||||
)
|
||||
serializer = PageLogSerializer(
|
||||
page_transaction, data=request.data, partial=True
|
||||
)
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def delete(self, request, slug, project_id, page_id, transaction):
|
||||
transaction = PageLog.objects.get(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
page_id=page_id,
|
||||
transaction=transaction,
|
||||
)
|
||||
# Delete the transaction object
|
||||
transaction.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class CreateIssueFromBlockEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
|
||||
def post(self, request, slug, project_id, page_id):
|
||||
page = Page.objects.get(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
pk=page_id,
|
||||
)
|
||||
issue = Issue.objects.create(
|
||||
name=request.data.get("name"),
|
||||
name=page_block.name,
|
||||
project_id=project_id,
|
||||
description=page_block.description,
|
||||
description_html=page_block.description_html,
|
||||
description_stripped=page_block.description_stripped,
|
||||
)
|
||||
_ = IssueAssignee.objects.create(
|
||||
issue=issue, assignee=request.user, project_id=project_id
|
||||
@@ -417,31 +245,11 @@ class CreateIssueFromBlockEndpoint(BaseAPIView):
|
||||
issue=issue,
|
||||
actor=request.user,
|
||||
project_id=project_id,
|
||||
comment=f"created the issue from {page.name} block",
|
||||
comment=f"created the issue from {page_block.name} block",
|
||||
verb="created",
|
||||
)
|
||||
|
||||
page_block.issue = issue
|
||||
page_block.save()
|
||||
|
||||
return Response(IssueLiteSerializer(issue).data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class SubPagesEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
|
||||
@method_decorator(gzip_page)
|
||||
def get(self, request, slug, project_id, page_id):
|
||||
pages = (
|
||||
PageLog.objects.filter(
|
||||
page_id=page_id,
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
entity_name__in=["forward_link", "back_link"],
|
||||
)
|
||||
.filter(archived_at__isnull=True)
|
||||
.select_related("project")
|
||||
.select_related("workspace")
|
||||
)
|
||||
return Response(
|
||||
SubPageSerializer(pages, many=True).data, status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
@@ -427,7 +427,7 @@ class ProjectInvitationsViewset(BaseViewSet):
|
||||
project_invitations = ProjectMemberInvite.objects.bulk_create(
|
||||
project_invitations, batch_size=10, ignore_conflicts=True
|
||||
)
|
||||
current_site = f"{request.scheme}://{request.get_host()}",
|
||||
current_site = request.META.get('HTTP_ORIGIN')
|
||||
|
||||
# Send invitations
|
||||
for invitation in project_invitations:
|
||||
|
||||
@@ -319,7 +319,7 @@ class WorkspaceInvitationsViewset(BaseViewSet):
|
||||
workspace_invitations, batch_size=10, ignore_conflicts=True
|
||||
)
|
||||
|
||||
current_site = f"{request.scheme}://{request.get_host()}",
|
||||
current_site = request.META.get('HTTP_ORIGIN')
|
||||
|
||||
# Send invitations
|
||||
for invitation in workspace_invitations:
|
||||
|
||||
@@ -12,7 +12,7 @@ from celery import shared_task
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import Issue, Project, State, Page
|
||||
from plane.db.models import Issue, Project, State
|
||||
from plane.bgtasks.issue_activites_task import issue_activity
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ from plane.bgtasks.issue_activites_task import issue_activity
|
||||
def archive_and_close_old_issues():
|
||||
archive_old_issues()
|
||||
close_old_issues()
|
||||
delete_archived_pages()
|
||||
|
||||
|
||||
def archive_old_issues():
|
||||
@@ -68,7 +67,7 @@ def archive_old_issues():
|
||||
issues_to_update.append(issue)
|
||||
|
||||
# Bulk Update the issues and log the activity
|
||||
if issues_to_update:
|
||||
if issues_to_update:
|
||||
Issue.objects.bulk_update(
|
||||
issues_to_update, ["archived_at"], batch_size=100
|
||||
)
|
||||
@@ -81,7 +80,7 @@ def archive_old_issues():
|
||||
project_id=project_id,
|
||||
current_instance=json.dumps({"archived_at": None}),
|
||||
subscriber=False,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
epoch=int(timezone.now().timestamp())
|
||||
)
|
||||
for issue in issues_to_update
|
||||
]
|
||||
@@ -143,21 +142,17 @@ def close_old_issues():
|
||||
|
||||
# Bulk Update the issues and log the activity
|
||||
if issues_to_update:
|
||||
Issue.objects.bulk_update(
|
||||
issues_to_update, ["state"], batch_size=100
|
||||
)
|
||||
Issue.objects.bulk_update(issues_to_update, ["state"], batch_size=100)
|
||||
[
|
||||
issue_activity.delay(
|
||||
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),
|
||||
issue_id=issue.id,
|
||||
project_id=project_id,
|
||||
current_instance=None,
|
||||
subscriber=False,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
epoch=int(timezone.now().timestamp())
|
||||
)
|
||||
for issue in issues_to_update
|
||||
]
|
||||
@@ -167,20 +162,3 @@ def close_old_issues():
|
||||
print(e)
|
||||
capture_exception(e)
|
||||
return
|
||||
|
||||
|
||||
def delete_archived_pages():
|
||||
try:
|
||||
pages_to_delete = Page.objects.filter(
|
||||
archived_at__isnull=False,
|
||||
archived_at__lte=(timezone.now() - timedelta(days=30)),
|
||||
)
|
||||
|
||||
pages_to_delete._raw_delete(pages_to_delete.db)
|
||||
return
|
||||
except Exception as e:
|
||||
if settings.DEBUG:
|
||||
print(e)
|
||||
capture_exception(e)
|
||||
return
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# Generated by Django 4.2.5 on 2023-11-13 12:53
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0047_webhook_apitoken_description_apitoken_expired_at_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PageLog',
|
||||
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)),
|
||||
('transaction', models.UUIDField(default=uuid.uuid4)),
|
||||
('entity_identifier', models.UUIDField(null=True)),
|
||||
('entity_name', models.CharField(choices=[('to_do', 'To Do'), ('issue', 'issue'), ('image', 'Image'), ('video', 'Video'), ('file', 'File'), ('link', 'Link'), ('cycle', 'Cycle'), ('module', 'Module'), ('back_link', 'Back Link'), ('forward_link', 'Forward Link'), ('mention', 'Mention')], max_length=30, verbose_name='Transaction Type')),
|
||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
||||
('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='page_log', to='db.page')),
|
||||
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_%(class)s', 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='workspace_%(class)s', to='db.workspace')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Page Log',
|
||||
'verbose_name_plural': 'Page Logs',
|
||||
'db_table': 'page_logs',
|
||||
'ordering': ('-created_at',),
|
||||
'unique_together': {('page', 'transaction')}
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='page',
|
||||
name='archived_at',
|
||||
field=models.DateField(null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='page',
|
||||
name='is_locked',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='page',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child_page', to='db.page'),
|
||||
),
|
||||
]
|
||||
@@ -1,72 +0,0 @@
|
||||
# Generated by Django 4.2.5 on 2023-11-15 09:16
|
||||
|
||||
# Python imports
|
||||
import uuid
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def update_pages(apps, schema_editor):
|
||||
try:
|
||||
Page = apps.get_model("db", "Page")
|
||||
PageBlock = apps.get_model("db", "PageBlock")
|
||||
PageLog = apps.get_model("db", "PageLog")
|
||||
|
||||
updated_pages = []
|
||||
page_logs = []
|
||||
|
||||
# looping through all the pages
|
||||
for page in Page.objects.all():
|
||||
page_blocks = PageBlock.objects.filter(
|
||||
page_id=page.id, project_id=page.project_id, workspace_id=page.workspace_id
|
||||
).order_by("sort_order")
|
||||
|
||||
if page_blocks:
|
||||
# looping through all the page blocks in a page
|
||||
for page_block in page_blocks:
|
||||
if page_block.issue is not None:
|
||||
project_identifier = page.project.identifier
|
||||
sequence_id = page_block.issue.sequence_id
|
||||
transaction = uuid.uuid4().hex
|
||||
embed_component = f'<issue-embed-component id="{transaction}" entity_name="issue" entity_identifier="{page_block.issue_id}" sequence_id="{sequence_id}" project_identifier="{project_identifier}" title="{page_block.name}"></issue-embed-component>'
|
||||
page.description_html += embed_component
|
||||
|
||||
# create the page transaction for the issue
|
||||
page_logs.append(
|
||||
PageLog(
|
||||
page_id=page_block.page_id,
|
||||
transaction=transaction,
|
||||
entity_identifier=page_block.issue_id,
|
||||
entity_name="issue",
|
||||
project_id=page.project_id,
|
||||
workspace_id=page.workspace_id,
|
||||
created_by_id=page_block.created_by_id,
|
||||
updated_by_id=page_block.updated_by_id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
# adding the page block name and description to the page description
|
||||
page.description_html += f"<h2>{page_block.name}</h2>"
|
||||
page.description_html += page_block.description_html
|
||||
|
||||
updated_pages.append(page)
|
||||
|
||||
Page.objects.bulk_update(
|
||||
updated_pages,
|
||||
["description_html"],
|
||||
batch_size=100,
|
||||
)
|
||||
PageLog.objects.bulk_create(page_logs, batch_size=100)
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("db", "0048_auto_20231116_0713"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(update_pages),
|
||||
]
|
||||
@@ -68,7 +68,7 @@ from .integration import (
|
||||
|
||||
from .importer import Importer
|
||||
|
||||
from .page import Page, PageLog, PageFavorite, PageLabel
|
||||
from .page import Page, PageBlock, PageFavorite, PageLabel
|
||||
|
||||
from .estimate import Estimate, EstimatePoint
|
||||
|
||||
|
||||
@@ -132,7 +132,25 @@ class Issue(ProjectBaseModel):
|
||||
self.state = default_state
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
from plane.db.models import State, PageBlock
|
||||
|
||||
# Check if the current issue state and completed state id are same
|
||||
if self.state.group == "completed":
|
||||
self.completed_at = timezone.now()
|
||||
# check if there are any page blocks
|
||||
PageBlock.objects.filter(issue_id=self.id).filter().update(
|
||||
completed_at=timezone.now()
|
||||
)
|
||||
else:
|
||||
PageBlock.objects.filter(issue_id=self.id).filter().update(
|
||||
completed_at=None
|
||||
)
|
||||
self.completed_at = None
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
if self._state.adding:
|
||||
# Get the maximum display_id value from the database
|
||||
last_id = IssueSequence.objects.filter(project=self.project).aggregate(
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import uuid
|
||||
|
||||
# Django imports
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
@@ -24,15 +22,6 @@ class Page(ProjectBaseModel):
|
||||
labels = models.ManyToManyField(
|
||||
"db.Label", blank=True, related_name="pages", through="db.PageLabel"
|
||||
)
|
||||
parent = models.ForeignKey(
|
||||
"self",
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="child_page",
|
||||
)
|
||||
archived_at = models.DateField(null=True)
|
||||
is_locked = models.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Page"
|
||||
@@ -45,42 +34,6 @@ class Page(ProjectBaseModel):
|
||||
return f"{self.owned_by.email} <{self.name}>"
|
||||
|
||||
|
||||
class PageLog(ProjectBaseModel):
|
||||
TYPE_CHOICES = (
|
||||
("to_do", "To Do"),
|
||||
("issue", "issue"),
|
||||
("image", "Image"),
|
||||
("video", "Video"),
|
||||
("file", "File"),
|
||||
("link", "Link"),
|
||||
("cycle","Cycle"),
|
||||
("module", "Module"),
|
||||
("back_link", "Back Link"),
|
||||
("forward_link", "Forward Link"),
|
||||
("mention", "Mention"),
|
||||
)
|
||||
transaction = models.UUIDField(default=uuid.uuid4)
|
||||
page = models.ForeignKey(
|
||||
Page, related_name="page_log", on_delete=models.CASCADE
|
||||
)
|
||||
entity_identifier = models.UUIDField(null=True)
|
||||
entity_name = models.CharField(
|
||||
max_length=30,
|
||||
choices=TYPE_CHOICES,
|
||||
verbose_name="Transaction Type",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = ["page", "transaction"]
|
||||
verbose_name = "Page Log"
|
||||
verbose_name_plural = "Page Logs"
|
||||
db_table = "page_logs"
|
||||
ordering = ("-created_at",)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.page.name} {self.type}"
|
||||
|
||||
|
||||
class PageBlock(ProjectBaseModel):
|
||||
page = models.ForeignKey("db.Page", on_delete=models.CASCADE, related_name="blocks")
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
@@ -5,6 +5,7 @@ import ssl
|
||||
import certifi
|
||||
from datetime import timedelta
|
||||
from urllib.parse import urlparse
|
||||
|
||||
# Django imports
|
||||
from django.core.management.utils import get_random_secret_key
|
||||
|
||||
@@ -26,12 +27,6 @@ DEBUG = False
|
||||
# Allowed Hosts
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
# To access webhook
|
||||
ENABLE_WEBHOOK = os.environ.get("ENABLE_WEBHOOK", "1") == "1"
|
||||
|
||||
# To access plane api through api tokens
|
||||
ENABLE_API = os.environ.get("ENABLE_API", "1") == "1"
|
||||
|
||||
# Redirect if / is not present
|
||||
APPEND_SLASH = True
|
||||
|
||||
@@ -118,7 +113,13 @@ CSRF_COOKIE_SECURE = True
|
||||
|
||||
# CORS Settings
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
CORS_ALLOWED_ORIGINS = os.environ.get("CORS_ALLOWED_ORIGINS", "").split(",")
|
||||
cors_origins_raw = os.environ.get("CORS_ALLOWED_ORIGINS", "")
|
||||
# filter out empty strings
|
||||
cors_allowed_origins = [origin.strip() for origin in cors_origins_raw.split(",") if origin.strip()]
|
||||
if cors_allowed_origins:
|
||||
CORS_ALLOWED_ORIGINS = cors_allowed_origins
|
||||
else:
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
|
||||
# Application Settings
|
||||
WSGI_APPLICATION = "plane.wsgi.application"
|
||||
@@ -229,7 +230,9 @@ STORAGES = {
|
||||
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
|
||||
},
|
||||
}
|
||||
STORAGES["default"] = {"BACKEND": "storages.backends.s3boto3.S3Boto3Storage"}
|
||||
STORAGES["default"] = {
|
||||
"BACKEND": "storages.backends.s3boto3.S3Boto3Storage",
|
||||
}
|
||||
AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "access-key")
|
||||
AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "secret-key")
|
||||
AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME", "uploads")
|
||||
@@ -245,7 +248,6 @@ if AWS_S3_ENDPOINT_URL:
|
||||
AWS_S3_URL_PROTOCOL = f"{parsed_url.scheme}:"
|
||||
|
||||
|
||||
|
||||
# JWT Auth Configuration
|
||||
SIMPLE_JWT = {
|
||||
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=10080),
|
||||
@@ -328,17 +330,5 @@ GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False)
|
||||
ANALYTICS_SECRET_KEY = os.environ.get("ANALYTICS_SECRET_KEY", False)
|
||||
ANALYTICS_BASE_API = os.environ.get("ANALYTICS_BASE_API", False)
|
||||
|
||||
# Open AI Settings
|
||||
OPENAI_API_BASE = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1")
|
||||
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", False)
|
||||
GPT_ENGINE = os.environ.get("GPT_ENGINE", "gpt-3.5-turbo")
|
||||
|
||||
# Scout Settings
|
||||
SCOUT_MONITOR = os.environ.get("SCOUT_MONITOR", False)
|
||||
SCOUT_KEY = os.environ.get("SCOUT_KEY", "")
|
||||
SCOUT_NAME = "Plane"
|
||||
|
||||
# Set the variable true if running in docker environment
|
||||
DOCKERIZED = int(os.environ.get("DOCKERIZED", 1)) == 1
|
||||
# Use Minio settings
|
||||
USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1
|
||||
|
||||
|
||||
@@ -3,10 +3,6 @@ from .common import * # noqa
|
||||
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
"*",
|
||||
]
|
||||
|
||||
# Debug Toolbar settings
|
||||
INSTALLED_APPS += ("debug_toolbar",)
|
||||
MIDDLEWARE += ("debug_toolbar.middleware.DebugToolbarMiddleware",)
|
||||
@@ -24,13 +20,9 @@ CACHES = {
|
||||
|
||||
INTERNAL_IPS = ("127.0.0.1",)
|
||||
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
|
||||
MEDIA_URL = "/uploads/"
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "uploads")
|
||||
|
||||
# For local settings
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
"http://localhost:3000",
|
||||
"http://127.0.0.1:3000",
|
||||
|
||||
@@ -11,3 +11,8 @@ INSTALLED_APPS += ("scout_apm.django",)
|
||||
|
||||
# Honor the 'X-Forwarded-Proto' header for request.is_secure()
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
|
||||
# Scout Settings
|
||||
SCOUT_MONITOR = os.environ.get("SCOUT_MONITOR", False)
|
||||
SCOUT_KEY = os.environ.get("SCOUT_KEY", "")
|
||||
SCOUT_NAME = "Plane"
|
||||
|
||||
@@ -11,11 +11,10 @@ from django.conf import settings
|
||||
urlpatterns = [
|
||||
path("", TemplateView.as_view(template_name="index.html")),
|
||||
path("api/", include("plane.api.urls")),
|
||||
path("api/v1/", include("plane.proxy.urls")),
|
||||
path("", include("plane.web.urls")),
|
||||
]
|
||||
|
||||
if settings.ENABLE_API:
|
||||
urlpatterns += path("api/v1/", include("plane.proxy.urls")),
|
||||
|
||||
if settings.DEBUG:
|
||||
import debug_toolbar
|
||||
|
||||
@@ -2,21 +2,16 @@ version: "3.8"
|
||||
|
||||
x-app-env : &app-env
|
||||
environment:
|
||||
- NGINX_PORT=${NGINX_PORT:-80}
|
||||
- WEB_URL=${WEB_URL:-http://localhost}
|
||||
- NGINX_PORT=${NGINX_PORT:-84}
|
||||
- DEBUG=${DEBUG:-0}
|
||||
- DJANGO_SETTINGS_MODULE=${DJANGO_SETTINGS_MODULE:-plane.settings.selfhosted}
|
||||
- NEXT_PUBLIC_ENABLE_OAUTH=${NEXT_PUBLIC_ENABLE_OAUTH:-0}
|
||||
- NEXT_PUBLIC_DEPLOY_URL=${NEXT_PUBLIC_DEPLOY_URL:-http://localhost/spaces}
|
||||
- SENTRY_DSN=${SENTRY_DSN:-""}
|
||||
- GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET:-""}
|
||||
- DOCKERIZED=${DOCKERIZED:-1}
|
||||
# BASE WEBHOOK
|
||||
- ENABLE_WEBHOOK=${ENABLE_WEBHOOK:-1}
|
||||
# BASE API
|
||||
- ENABLE_API=${ENABLE_API:-1}
|
||||
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-http://localhost}
|
||||
# Gunicorn Workers
|
||||
- DOCKERIZED=${DOCKERIZED:-1} # deprecated
|
||||
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-""}
|
||||
# Gunicorn Workers
|
||||
- GUNICORN_WORKERS=${GUNICORN_WORKERS:-2}
|
||||
#DB SETTINGS
|
||||
- PGHOST=${PGHOST:-plane-db}
|
||||
@@ -40,7 +35,7 @@ x-app-env : &app-env
|
||||
- EMAIL_USE_SSL=${EMAIL_USE_SSL:-0}
|
||||
- DEFAULT_EMAIL=${DEFAULT_EMAIL:-captain@plane.so}
|
||||
- DEFAULT_PASSWORD=${DEFAULT_PASSWORD:-password123}
|
||||
# OPENAI SETTINGS
|
||||
# OPENAI SETTINGS - Deprecated can be configured through admin panel
|
||||
- OPENAI_API_BASE=${OPENAI_API_BASE:-https://api.openai.com/v1}
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY:-"sk-"}
|
||||
- GPT_ENGINE=${GPT_ENGINE:-"gpt-3.5-turbo"}
|
||||
|
||||
@@ -5,20 +5,15 @@ SPACE_REPLICAS=1
|
||||
API_REPLICAS=1
|
||||
|
||||
NGINX_PORT=80
|
||||
WEB_URL=http://localhost
|
||||
DEBUG=0
|
||||
DJANGO_SETTINGS_MODULE=plane.settings.selfhosted
|
||||
NEXT_PUBLIC_ENABLE_OAUTH=0
|
||||
NEXT_PUBLIC_DEPLOY_URL=http://localhost/spaces
|
||||
SENTRY_DSN=""
|
||||
GITHUB_CLIENT_SECRET=""
|
||||
DOCKERIZED=1
|
||||
CORS_ALLOWED_ORIGINS="http://localhost"
|
||||
DOCKERIZED=1 # deprecated
|
||||
CORS_ALLOWED_ORIGINS=""
|
||||
|
||||
# Webhook
|
||||
ENABLE_WEBHOOK=1
|
||||
# API
|
||||
ENABLE_API=1
|
||||
|
||||
#DB SETTINGS
|
||||
PGHOST=plane-db
|
||||
@@ -46,9 +41,9 @@ DEFAULT_EMAIL=captain@plane.so
|
||||
DEFAULT_PASSWORD=password123
|
||||
|
||||
# OPENAI SETTINGS
|
||||
OPENAI_API_BASE=https://api.openai.com/v1
|
||||
OPENAI_API_KEY="sk-"
|
||||
GPT_ENGINE="gpt-3.5-turbo"
|
||||
OPENAI_API_BASE=https://api.openai.com/v1 # deprecated
|
||||
OPENAI_API_KEY="sk-" # deprecated
|
||||
GPT_ENGINE="gpt-3.5-turbo" # deprecated
|
||||
|
||||
# LOGIN/SIGNUP SETTINGS
|
||||
ENABLE_SIGNUP=1
|
||||
|
||||
@@ -90,40 +90,36 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-x-3 gap-y-2 px-4 pt-4">
|
||||
<div className="flex items-center gap-x-8 gap-y-2 px-4 pt-4">
|
||||
<Menu as="div" className="relative col-span-4 text-left flex-grow h-full truncate">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Menu.Button className="text-custom-sidebar-text-200 rounded-md hover:bg-custom-sidebar-background-80 text-sm font-medium focus:outline-none w-full h-full truncate">
|
||||
<div
|
||||
className={`flex items-center justify-between gap-x-2 rounded p-1 truncate ${
|
||||
sidebarCollapsed ? "justify-center" : ""
|
||||
}`}
|
||||
className={`flex items-center gap-x-2 rounded p-1 truncate ${sidebarCollapsed ? "justify-center" : ""}`}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`relative grid h-6 w-6 place-items-center uppercase flex-shrink-0 ${
|
||||
!activeWorkspace?.logo && "rounded bg-custom-primary-500 text-white"
|
||||
}`}
|
||||
>
|
||||
{activeWorkspace?.logo && activeWorkspace.logo !== "" ? (
|
||||
<img
|
||||
src={activeWorkspace.logo}
|
||||
className="absolute top-0 left-0 h-full w-full object-cover rounded"
|
||||
alt="Workspace Logo"
|
||||
/>
|
||||
) : (
|
||||
activeWorkspace?.name?.charAt(0) ?? "..."
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!sidebarCollapsed && (
|
||||
<h4 className="text-custom-text-100 font-medium text-base truncate">
|
||||
{activeWorkspace?.name ? activeWorkspace.name : "Loading..."}
|
||||
</h4>
|
||||
<div
|
||||
className={`relative grid h-6 w-6 place-items-center uppercase flex-shrink-0 ${
|
||||
!activeWorkspace?.logo && "rounded bg-custom-primary-500 text-white"
|
||||
}`}
|
||||
>
|
||||
{activeWorkspace?.logo && activeWorkspace.logo !== "" ? (
|
||||
<img
|
||||
src={activeWorkspace.logo}
|
||||
className="absolute top-0 left-0 h-full w-full object-cover rounded"
|
||||
alt="Workspace Logo"
|
||||
/>
|
||||
) : (
|
||||
activeWorkspace?.name?.charAt(0) ?? "..."
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!sidebarCollapsed && (
|
||||
<h4 className="text-custom-text-100 font-medium text-base truncate">
|
||||
{activeWorkspace?.name ? activeWorkspace.name : "Loading..."}
|
||||
</h4>
|
||||
)}
|
||||
|
||||
{!sidebarCollapsed && (
|
||||
<ChevronDown
|
||||
className={`h-4 w-4 mx-1 flex-shrink-0 ${
|
||||
|
||||
Reference in New Issue
Block a user