mirror of
https://github.com/makeplane/plane.git
synced 2025-12-16 11:57:56 +01:00
chore(deps): upgrade psycopg packages to version 3.3.0 (#8222)
* chore(deps): upgrade psycopg packages to version 3.3.0 * chore: update Python version to 3.12.x in CI workflow * refactor: clean up imports and improve code formatting across multiple files
This commit is contained in:
@@ -31,7 +31,7 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.12.x"
|
||||||
- name: Install Pylint
|
- name: Install Pylint
|
||||||
run: python -m pip install ruff
|
run: python -m pip install ruff
|
||||||
- name: Install API Dependencies
|
- name: Install API Dependencies
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from plane.api.views import ProjectMemberListCreateAPIEndpoint, ProjectMemberDetailAPIEndpoint, WorkspaceMemberAPIEndpoint
|
from plane.api.views import (
|
||||||
|
ProjectMemberListCreateAPIEndpoint,
|
||||||
|
ProjectMemberDetailAPIEndpoint,
|
||||||
|
WorkspaceMemberAPIEndpoint,
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Project members
|
# Project members
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ from django.utils import timezone
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
|
||||||
from rest_framework.filters import SearchFilter
|
|
||||||
from rest_framework.viewsets import ModelViewSet
|
from rest_framework.viewsets import ModelViewSet
|
||||||
from rest_framework.exceptions import APIException
|
from rest_framework.exceptions import APIException
|
||||||
from rest_framework.generics import GenericAPIView
|
from rest_framework.generics import GenericAPIView
|
||||||
|
|||||||
@@ -65,9 +65,7 @@ from plane.utils.openapi import (
|
|||||||
ADMIN_ONLY_RESPONSE,
|
ADMIN_ONLY_RESPONSE,
|
||||||
REQUIRED_FIELDS_RESPONSE,
|
REQUIRED_FIELDS_RESPONSE,
|
||||||
MODULE_ISSUE_NOT_FOUND_RESPONSE,
|
MODULE_ISSUE_NOT_FOUND_RESPONSE,
|
||||||
ARCHIVED_RESPONSE,
|
|
||||||
CANNOT_ARCHIVE_RESPONSE,
|
CANNOT_ARCHIVE_RESPONSE,
|
||||||
UNARCHIVED_RESPONSE,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -129,9 +129,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
return (
|
return (
|
||||||
cycles.order_by("-created_at")
|
cycles.order_by("-created_at")
|
||||||
.distinct()
|
.distinct()
|
||||||
.values(
|
.values("name", "id", "project_id", "project__identifier", "workspace__slug")
|
||||||
"name", "id", "project_id", "project__identifier", "workspace__slug"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def filter_modules(self, query, slug, project_id, workspace_search):
|
def filter_modules(self, query, slug, project_id, workspace_search):
|
||||||
@@ -155,9 +153,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
return (
|
return (
|
||||||
modules.order_by("-created_at")
|
modules.order_by("-created_at")
|
||||||
.distinct()
|
.distinct()
|
||||||
.values(
|
.values("name", "id", "project_id", "project__identifier", "workspace__slug")
|
||||||
"name", "id", "project_id", "project__identifier", "workspace__slug"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def filter_pages(self, query, slug, project_id, workspace_search):
|
def filter_pages(self, query, slug, project_id, workspace_search):
|
||||||
@@ -177,9 +173,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
.annotate(
|
.annotate(
|
||||||
project_ids=Coalesce(
|
project_ids=Coalesce(
|
||||||
ArrayAgg(
|
ArrayAgg("projects__id", distinct=True, filter=~Q(projects__id=True)),
|
||||||
"projects__id", distinct=True, filter=~Q(projects__id=True)
|
|
||||||
),
|
|
||||||
Value([], output_field=ArrayField(UUIDField())),
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -196,20 +190,16 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if workspace_search == "false" and project_id:
|
if workspace_search == "false" and project_id:
|
||||||
project_subquery = ProjectPage.objects.filter(
|
project_subquery = ProjectPage.objects.filter(page_id=OuterRef("id"), project_id=project_id).values_list(
|
||||||
page_id=OuterRef("id"), project_id=project_id
|
"project_id", flat=True
|
||||||
).values_list("project_id", flat=True)[:1]
|
)[:1]
|
||||||
|
|
||||||
pages = pages.annotate(project_id=Subquery(project_subquery)).filter(
|
pages = pages.annotate(project_id=Subquery(project_subquery)).filter(project_id=project_id)
|
||||||
project_id=project_id
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
pages.order_by("-created_at")
|
pages.order_by("-created_at")
|
||||||
.distinct()
|
.distinct()
|
||||||
.values(
|
.values("name", "id", "project_ids", "project_identifiers", "workspace__slug")
|
||||||
"name", "id", "project_ids", "project_identifiers", "workspace__slug"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def filter_views(self, query, slug, project_id, workspace_search):
|
def filter_views(self, query, slug, project_id, workspace_search):
|
||||||
@@ -233,9 +223,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
return (
|
return (
|
||||||
issue_views.order_by("-created_at")
|
issue_views.order_by("-created_at")
|
||||||
.distinct()
|
.distinct()
|
||||||
.values(
|
.values("name", "id", "project_id", "project__identifier", "workspace__slug")
|
||||||
"name", "id", "project_id", "project__identifier", "workspace__slug"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def filter_intakes(self, query, slug, project_id, workspace_search):
|
def filter_intakes(self, query, slug, project_id, workspace_search):
|
||||||
@@ -294,9 +282,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
|
|
||||||
# Determine which entities to search
|
# Determine which entities to search
|
||||||
if entities_param:
|
if entities_param:
|
||||||
requested_entities = [
|
requested_entities = [e.strip() for e in entities_param.split(",") if e.strip()]
|
||||||
e.strip() for e in entities_param.split(",") if e.strip()
|
|
||||||
]
|
|
||||||
requested_entities = [e for e in requested_entities if e in MODELS_MAPPER]
|
requested_entities = [e for e in requested_entities if e in MODELS_MAPPER]
|
||||||
else:
|
else:
|
||||||
requested_entities = list(MODELS_MAPPER.keys())
|
requested_entities = list(MODELS_MAPPER.keys())
|
||||||
@@ -306,9 +292,7 @@ class GlobalSearchEndpoint(BaseAPIView):
|
|||||||
for entity in requested_entities:
|
for entity in requested_entities:
|
||||||
func = MODELS_MAPPER.get(entity)
|
func = MODELS_MAPPER.get(entity)
|
||||||
if func:
|
if func:
|
||||||
results[entity] = func(
|
results[entity] = func(query or None, slug, project_id, workspace_search)
|
||||||
query or None, slug, project_id, workspace_search
|
|
||||||
)
|
|
||||||
|
|
||||||
return Response({"results": results}, status=status.HTTP_200_OK)
|
return Response({"results": results}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
@@ -320,7 +304,6 @@ class SearchEndpoint(BaseAPIView):
|
|||||||
query_types = [qt.strip() for qt in query_types]
|
query_types = [qt.strip() for qt in query_types]
|
||||||
count = int(request.query_params.get("count", 5))
|
count = int(request.query_params.get("count", 5))
|
||||||
project_id = request.query_params.get("project_id", None)
|
project_id = request.query_params.get("project_id", None)
|
||||||
issue_id = request.query_params.get("issue_id", None)
|
|
||||||
|
|
||||||
response_data = {}
|
response_data = {}
|
||||||
|
|
||||||
@@ -367,14 +350,10 @@ class SearchEndpoint(BaseAPIView):
|
|||||||
.order_by("-created_at")
|
.order_by("-created_at")
|
||||||
)
|
)
|
||||||
|
|
||||||
users = (
|
users = users.distinct().values(
|
||||||
users
|
"member__avatar_url",
|
||||||
.distinct()
|
"member__display_name",
|
||||||
.values(
|
"member__id",
|
||||||
"member__avatar_url",
|
|
||||||
"member__display_name",
|
|
||||||
"member__id",
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
response_data["user_mention"] = list(users[:count])
|
response_data["user_mention"] = list(users[:count])
|
||||||
@@ -389,15 +368,12 @@ class SearchEndpoint(BaseAPIView):
|
|||||||
projects = (
|
projects = (
|
||||||
Project.objects.filter(
|
Project.objects.filter(
|
||||||
q,
|
q,
|
||||||
Q(project_projectmember__member=self.request.user)
|
Q(project_projectmember__member=self.request.user) | Q(network=2),
|
||||||
| Q(network=2),
|
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
)
|
)
|
||||||
.order_by("-created_at")
|
.order_by("-created_at")
|
||||||
.distinct()
|
.distinct()
|
||||||
.values(
|
.values("name", "id", "identifier", "logo_props", "workspace__slug")[:count]
|
||||||
"name", "id", "identifier", "logo_props", "workspace__slug"
|
|
||||||
)[:count]
|
|
||||||
)
|
)
|
||||||
response_data["project"] = list(projects)
|
response_data["project"] = list(projects)
|
||||||
|
|
||||||
@@ -456,20 +432,16 @@ class SearchEndpoint(BaseAPIView):
|
|||||||
.annotate(
|
.annotate(
|
||||||
status=Case(
|
status=Case(
|
||||||
When(
|
When(
|
||||||
Q(start_date__lte=timezone.now())
|
Q(start_date__lte=timezone.now()) & Q(end_date__gte=timezone.now()),
|
||||||
& Q(end_date__gte=timezone.now()),
|
|
||||||
then=Value("CURRENT"),
|
then=Value("CURRENT"),
|
||||||
),
|
),
|
||||||
When(
|
When(
|
||||||
start_date__gt=timezone.now(),
|
start_date__gt=timezone.now(),
|
||||||
then=Value("UPCOMING"),
|
then=Value("UPCOMING"),
|
||||||
),
|
),
|
||||||
|
When(end_date__lt=timezone.now(), then=Value("COMPLETED")),
|
||||||
When(
|
When(
|
||||||
end_date__lt=timezone.now(), then=Value("COMPLETED")
|
Q(start_date__isnull=True) & Q(end_date__isnull=True),
|
||||||
),
|
|
||||||
When(
|
|
||||||
Q(start_date__isnull=True)
|
|
||||||
& Q(end_date__isnull=True),
|
|
||||||
then=Value("DRAFT"),
|
then=Value("DRAFT"),
|
||||||
),
|
),
|
||||||
default=Value("DRAFT"),
|
default=Value("DRAFT"),
|
||||||
@@ -587,9 +559,7 @@ class SearchEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.order_by("-created_at")
|
.order_by("-created_at")
|
||||||
.values(
|
.values("member__avatar_url", "member__display_name", "member__id")[:count]
|
||||||
"member__avatar_url", "member__display_name", "member__id"
|
|
||||||
)[:count]
|
|
||||||
)
|
)
|
||||||
response_data["user_mention"] = list(users)
|
response_data["user_mention"] = list(users)
|
||||||
|
|
||||||
@@ -603,15 +573,12 @@ class SearchEndpoint(BaseAPIView):
|
|||||||
projects = (
|
projects = (
|
||||||
Project.objects.filter(
|
Project.objects.filter(
|
||||||
q,
|
q,
|
||||||
Q(project_projectmember__member=self.request.user)
|
Q(project_projectmember__member=self.request.user) | Q(network=2),
|
||||||
| Q(network=2),
|
|
||||||
workspace__slug=slug,
|
workspace__slug=slug,
|
||||||
)
|
)
|
||||||
.order_by("-created_at")
|
.order_by("-created_at")
|
||||||
.distinct()
|
.distinct()
|
||||||
.values(
|
.values("name", "id", "identifier", "logo_props", "workspace__slug")[:count]
|
||||||
"name", "id", "identifier", "logo_props", "workspace__slug"
|
|
||||||
)[:count]
|
|
||||||
)
|
)
|
||||||
response_data["project"] = list(projects)
|
response_data["project"] = list(projects)
|
||||||
|
|
||||||
@@ -668,20 +635,16 @@ class SearchEndpoint(BaseAPIView):
|
|||||||
.annotate(
|
.annotate(
|
||||||
status=Case(
|
status=Case(
|
||||||
When(
|
When(
|
||||||
Q(start_date__lte=timezone.now())
|
Q(start_date__lte=timezone.now()) & Q(end_date__gte=timezone.now()),
|
||||||
& Q(end_date__gte=timezone.now()),
|
|
||||||
then=Value("CURRENT"),
|
then=Value("CURRENT"),
|
||||||
),
|
),
|
||||||
When(
|
When(
|
||||||
start_date__gt=timezone.now(),
|
start_date__gt=timezone.now(),
|
||||||
then=Value("UPCOMING"),
|
then=Value("UPCOMING"),
|
||||||
),
|
),
|
||||||
|
When(end_date__lt=timezone.now(), then=Value("COMPLETED")),
|
||||||
When(
|
When(
|
||||||
end_date__lt=timezone.now(), then=Value("COMPLETED")
|
Q(start_date__isnull=True) & Q(end_date__isnull=True),
|
||||||
),
|
|
||||||
When(
|
|
||||||
Q(start_date__isnull=True)
|
|
||||||
& Q(end_date__isnull=True),
|
|
||||||
then=Value("DRAFT"),
|
then=Value("DRAFT"),
|
||||||
),
|
),
|
||||||
default=Value("DRAFT"),
|
default=Value("DRAFT"),
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ class UserEndpoint(BaseViewSet):
|
|||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception:
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Failed to verify code. Please try again."},
|
{"error": "Failed to verify code. Please try again."},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import io
|
import io
|
||||||
import zipfile
|
import zipfile
|
||||||
from typing import List
|
from typing import List
|
||||||
from collections import defaultdict
|
|
||||||
import boto3
|
import boto3
|
||||||
from botocore.client import Config
|
from botocore.client import Config
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from django.db import IntegrityError
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from datetime import datetime, timedelta
|
from datetime import timedelta
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from plane.db.models import Cycle, Project, ProjectMember
|
from plane.db.models import Cycle, Project, ProjectMember
|
||||||
@@ -58,8 +57,6 @@ def create_cycle(db, project, create_user):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.contract
|
@pytest.mark.contract
|
||||||
class TestCycleListCreateAPIEndpoint:
|
class TestCycleListCreateAPIEndpoint:
|
||||||
"""Test Cycle List and Create API Endpoint"""
|
"""Test Cycle List and Create API Endpoint"""
|
||||||
@@ -85,7 +82,6 @@ class TestCycleListCreateAPIEndpoint:
|
|||||||
assert created_cycle.project == project
|
assert created_cycle.project == project
|
||||||
assert created_cycle.owned_by_id is not None
|
assert created_cycle.owned_by_id is not None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create_cycle_invalid_data(self, api_key_client, workspace, project):
|
def test_create_cycle_invalid_data(self, api_key_client, workspace, project):
|
||||||
"""Test cycle creation with invalid data"""
|
"""Test cycle creation with invalid data"""
|
||||||
@@ -197,7 +193,7 @@ class TestCycleListCreateAPIEndpoint:
|
|||||||
|
|
||||||
# Create cycles in different states
|
# Create cycles in different states
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
|
|
||||||
# Current cycle (started but not ended)
|
# Current cycle (started but not ended)
|
||||||
Cycle.objects.create(
|
Cycle.objects.create(
|
||||||
name="Current Cycle",
|
name="Current Cycle",
|
||||||
@@ -207,7 +203,7 @@ class TestCycleListCreateAPIEndpoint:
|
|||||||
end_date=now + timedelta(days=6),
|
end_date=now + timedelta(days=6),
|
||||||
owned_by=create_user,
|
owned_by=create_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Upcoming cycle
|
# Upcoming cycle
|
||||||
Cycle.objects.create(
|
Cycle.objects.create(
|
||||||
name="Upcoming Cycle",
|
name="Upcoming Cycle",
|
||||||
@@ -217,7 +213,7 @@ class TestCycleListCreateAPIEndpoint:
|
|||||||
end_date=now + timedelta(days=8),
|
end_date=now + timedelta(days=8),
|
||||||
owned_by=create_user,
|
owned_by=create_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Completed cycle
|
# Completed cycle
|
||||||
Cycle.objects.create(
|
Cycle.objects.create(
|
||||||
name="Completed Cycle",
|
name="Completed Cycle",
|
||||||
@@ -227,7 +223,7 @@ class TestCycleListCreateAPIEndpoint:
|
|||||||
end_date=now - timedelta(days=3),
|
end_date=now - timedelta(days=3),
|
||||||
owned_by=create_user,
|
owned_by=create_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Draft cycle
|
# Draft cycle
|
||||||
Cycle.objects.create(
|
Cycle.objects.create(
|
||||||
name="Draft Cycle",
|
name="Draft Cycle",
|
||||||
@@ -320,7 +316,9 @@ class TestCycleDetailAPIEndpoint:
|
|||||||
assert response.status_code in [status.HTTP_400_BAD_REQUEST, status.HTTP_200_OK]
|
assert response.status_code in [status.HTTP_400_BAD_REQUEST, status.HTTP_200_OK]
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_update_cycle_with_external_id_conflict(self, api_key_client, workspace, project, create_cycle, create_user ):
|
def test_update_cycle_with_external_id_conflict(
|
||||||
|
self, api_key_client, workspace, project, create_cycle, create_user
|
||||||
|
):
|
||||||
"""Test cycle update with conflicting external ID"""
|
"""Test cycle update with conflicting external ID"""
|
||||||
url = self.get_cycle_detail_url(workspace.slug, project.id, create_cycle.id)
|
url = self.get_cycle_detail_url(workspace.slug, project.id, create_cycle.id)
|
||||||
|
|
||||||
@@ -363,7 +361,7 @@ class TestCycleDetailAPIEndpoint:
|
|||||||
response = api_key_client.get(url)
|
response = api_key_client.get(url)
|
||||||
|
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
|
||||||
# Check that metrics are included in response
|
# Check that metrics are included in response
|
||||||
cycle_data = response.data
|
cycle_data = response.data
|
||||||
assert "total_issues" in cycle_data
|
assert "total_issues" in cycle_data
|
||||||
@@ -372,11 +370,11 @@ class TestCycleDetailAPIEndpoint:
|
|||||||
assert "started_issues" in cycle_data
|
assert "started_issues" in cycle_data
|
||||||
assert "unstarted_issues" in cycle_data
|
assert "unstarted_issues" in cycle_data
|
||||||
assert "backlog_issues" in cycle_data
|
assert "backlog_issues" in cycle_data
|
||||||
|
|
||||||
# All should be 0 for a new cycle
|
# All should be 0 for a new cycle
|
||||||
assert cycle_data["total_issues"] == 0
|
assert cycle_data["total_issues"] == 0
|
||||||
assert cycle_data["completed_issues"] == 0
|
assert cycle_data["completed_issues"] == 0
|
||||||
assert cycle_data["cancelled_issues"] == 0
|
assert cycle_data["cancelled_issues"] == 0
|
||||||
assert cycle_data["started_issues"] == 0
|
assert cycle_data["started_issues"] == 0
|
||||||
assert cycle_data["unstarted_issues"] == 0
|
assert cycle_data["unstarted_issues"] == 0
|
||||||
assert cycle_data["backlog_issues"] == 0
|
assert cycle_data["backlog_issues"] == 0
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ Django==4.2.27
|
|||||||
# rest framework
|
# rest framework
|
||||||
djangorestframework==3.15.2
|
djangorestframework==3.15.2
|
||||||
# postgres
|
# postgres
|
||||||
psycopg==3.2.9
|
psycopg==3.3.0
|
||||||
psycopg-binary==3.2.9
|
psycopg-binary==3.3.0
|
||||||
psycopg-c==3.2.9
|
psycopg-c==3.3.0
|
||||||
dj-database-url==2.1.0
|
dj-database-url==2.1.0
|
||||||
# mongo
|
# mongo
|
||||||
pymongo==4.6.3
|
pymongo==4.6.3
|
||||||
|
|||||||
Reference in New Issue
Block a user