chore: format files in API server (#8292)

This commit is contained in:
sriram veeraghanta 2025-12-10 23:50:01 +05:30 committed by GitHub
parent 647813a6ab
commit 97e21ba21c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 173 additions and 373 deletions

View file

@ -55,4 +55,4 @@ from .asset import (
) )
from .invite import WorkspaceInviteSerializer from .invite import WorkspaceInviteSerializer
from .member import ProjectMemberSerializer from .member import ProjectMemberSerializer
from .sticky import StickySerializer from .sticky import StickySerializer

View file

@ -17,7 +17,7 @@ from plane.utils.content_validator import (
from .base import BaseSerializer from .base import BaseSerializer
class ProjectCreateSerializer(BaseSerializer): class ProjectCreateSerializer(BaseSerializer):
""" """
Serializer for creating projects with workspace validation. Serializer for creating projects with workspace validation.

View file

@ -15,4 +15,4 @@ router.register(r"invitations", WorkspaceInvitationsViewset, basename="workspace
# Wrap the router URLs with the workspace slug path # Wrap the router URLs with the workspace slug path
urlpatterns = [ urlpatterns = [
path("workspaces/<str:slug>/", include(router.urls)), path("workspaces/<str:slug>/", include(router.urls)),
] ]

View file

@ -195,9 +195,7 @@ class CycleListCreateAPIEndpoint(BaseAPIView):
# Current Cycle # Current Cycle
if cycle_view == "current": if cycle_view == "current":
queryset = queryset.filter( queryset = queryset.filter(start_date__lte=timezone.now(), end_date__gte=timezone.now())
start_date__lte=timezone.now(), end_date__gte=timezone.now()
)
data = CycleSerializer( data = CycleSerializer(
queryset, queryset,
many=True, many=True,
@ -254,9 +252,7 @@ class CycleListCreateAPIEndpoint(BaseAPIView):
# Incomplete Cycles # Incomplete Cycles
if cycle_view == "incomplete": if cycle_view == "incomplete":
queryset = queryset.filter( queryset = queryset.filter(Q(end_date__gte=timezone.now()) | Q(end_date__isnull=True))
Q(end_date__gte=timezone.now()) | Q(end_date__isnull=True)
)
return self.paginate( return self.paginate(
request=request, request=request,
queryset=(queryset), queryset=(queryset),
@ -302,17 +298,10 @@ class CycleListCreateAPIEndpoint(BaseAPIView):
Create a new development cycle with specified name, description, and date range. Create a new development cycle with specified name, description, and date range.
Supports external ID tracking for integration purposes. Supports external ID tracking for integration purposes.
""" """
if ( if (request.data.get("start_date", None) is None and request.data.get("end_date", None) is None) or (
request.data.get("start_date", None) is None request.data.get("start_date", None) is not None and request.data.get("end_date", None) is not None
and request.data.get("end_date", None) is None
) or (
request.data.get("start_date", None) is not None
and request.data.get("end_date", None) is not None
): ):
serializer = CycleCreateSerializer(data=request.data, context={"request": request})
serializer = CycleCreateSerializer(
data=request.data, context={"request": request}
)
if serializer.is_valid(): if serializer.is_valid():
if ( if (
request.data.get("external_id") request.data.get("external_id")
@ -355,9 +344,7 @@ class CycleListCreateAPIEndpoint(BaseAPIView):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else: else:
return Response( return Response(
{ {"error": "Both start date and end date are either required or are to be null"},
"error": "Both start date and end date are either required or are to be null"
},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
@ -505,9 +492,7 @@ class CycleDetailAPIEndpoint(BaseAPIView):
""" """
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk) cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
current_instance = json.dumps( current_instance = json.dumps(CycleSerializer(cycle).data, cls=DjangoJSONEncoder)
CycleSerializer(cycle).data, cls=DjangoJSONEncoder
)
if cycle.archived_at: if cycle.archived_at:
return Response( return Response(
@ -520,20 +505,14 @@ class CycleDetailAPIEndpoint(BaseAPIView):
if cycle.end_date is not None and cycle.end_date < timezone.now(): if cycle.end_date is not None and cycle.end_date < timezone.now():
if "sort_order" in request_data: if "sort_order" in request_data:
# Can only change sort order # Can only change sort order
request_data = { request_data = {"sort_order": request_data.get("sort_order", cycle.sort_order)}
"sort_order": request_data.get("sort_order", cycle.sort_order)
}
else: else:
return Response( return Response(
{ {"error": "The Cycle has already been completed so it cannot be edited"},
"error": "The Cycle has already been completed so it cannot be edited"
},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
serializer = CycleUpdateSerializer( serializer = CycleUpdateSerializer(cycle, data=request.data, partial=True, context={"request": request})
cycle, data=request.data, partial=True, context={"request": request}
)
if serializer.is_valid(): if serializer.is_valid():
if ( if (
request.data.get("external_id") request.data.get("external_id")
@ -541,9 +520,7 @@ class CycleDetailAPIEndpoint(BaseAPIView):
and Cycle.objects.filter( and Cycle.objects.filter(
project_id=project_id, project_id=project_id,
workspace__slug=slug, workspace__slug=slug,
external_source=request.data.get( external_source=request.data.get("external_source", cycle.external_source),
"external_source", cycle.external_source
),
external_id=request.data.get("external_id"), external_id=request.data.get("external_id"),
).exists() ).exists()
): ):
@ -600,11 +577,7 @@ class CycleDetailAPIEndpoint(BaseAPIView):
status=status.HTTP_403_FORBIDDEN, status=status.HTTP_403_FORBIDDEN,
) )
cycle_issues = list( cycle_issues = list(CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list("issue", flat=True))
CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list(
"issue", flat=True
)
)
issue_activity.delay( issue_activity.delay(
type="cycle.activity.deleted", type="cycle.activity.deleted",
@ -624,9 +597,7 @@ class CycleDetailAPIEndpoint(BaseAPIView):
# Delete the cycle # Delete the cycle
cycle.delete() cycle.delete()
# Delete the user favorite cycle # Delete the user favorite cycle
UserFavorite.objects.filter( UserFavorite.objects.filter(entity_type="cycle", entity_identifier=pk, project_id=project_id).delete()
entity_type="cycle", entity_identifier=pk, project_id=project_id
).delete()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@ -764,9 +735,7 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
return self.paginate( return self.paginate(
request=request, request=request,
queryset=(self.get_queryset()), queryset=(self.get_queryset()),
on_results=lambda cycles: CycleSerializer( on_results=lambda cycles: CycleSerializer(cycles, many=True, fields=self.fields, expand=self.expand).data,
cycles, many=True, fields=self.fields, expand=self.expand
).data,
) )
@cycle_docs( @cycle_docs(
@ -785,9 +754,7 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
Move a completed cycle to archived status for historical tracking. Move a completed cycle to archived status for historical tracking.
Only cycles that have ended can be archived. Only cycles that have ended can be archived.
""" """
cycle = Cycle.objects.get( cycle = Cycle.objects.get(pk=cycle_id, project_id=project_id, workspace__slug=slug)
pk=cycle_id, project_id=project_id, workspace__slug=slug
)
if cycle.end_date >= timezone.now(): if cycle.end_date >= timezone.now():
return Response( return Response(
{"error": "Only completed cycles can be archived"}, {"error": "Only completed cycles can be archived"},
@ -818,9 +785,7 @@ class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
Restore an archived cycle to active status, making it available for regular use. Restore an archived cycle to active status, making it available for regular use.
The cycle will reappear in active cycle lists. The cycle will reappear in active cycle lists.
""" """
cycle = Cycle.objects.get( cycle = Cycle.objects.get(pk=cycle_id, project_id=project_id, workspace__slug=slug)
pk=cycle_id, project_id=project_id, workspace__slug=slug
)
cycle.archived_at = None cycle.archived_at = None
cycle.save() cycle.save()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@ -883,9 +848,7 @@ class CycleIssueListCreateAPIEndpoint(BaseAPIView):
# List # List
order_by = request.GET.get("order_by", "created_at") order_by = request.GET.get("order_by", "created_at")
issues = ( issues = (
Issue.issue_objects.filter( Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id, issue_cycle__deleted_at__isnull=True)
issue_cycle__cycle_id=cycle_id, issue_cycle__deleted_at__isnull=True
)
.annotate( .annotate(
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
.order_by() .order_by()
@ -922,9 +885,7 @@ class CycleIssueListCreateAPIEndpoint(BaseAPIView):
return self.paginate( return self.paginate(
request=request, request=request,
queryset=(issues), queryset=(issues),
on_results=lambda issues: IssueSerializer( on_results=lambda issues: IssueSerializer(issues, many=True, fields=self.fields, expand=self.expand).data,
issues, many=True, fields=self.fields, expand=self.expand
).data,
) )
@cycle_docs( @cycle_docs(
@ -958,9 +919,7 @@ class CycleIssueListCreateAPIEndpoint(BaseAPIView):
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
cycle = Cycle.objects.get( cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=cycle_id)
workspace__slug=slug, project_id=project_id, pk=cycle_id
)
if cycle.end_date is not None and cycle.end_date < timezone.now(): if cycle.end_date is not None and cycle.end_date < timezone.now():
return Response( return Response(
@ -972,13 +931,9 @@ class CycleIssueListCreateAPIEndpoint(BaseAPIView):
) )
# Get all CycleWorkItems already created # Get all CycleWorkItems already created
cycle_issues = list( cycle_issues = list(CycleIssue.objects.filter(~Q(cycle_id=cycle_id), issue_id__in=issues))
CycleIssue.objects.filter(~Q(cycle_id=cycle_id), issue_id__in=issues)
)
existing_issues = [ existing_issues = [
str(cycle_issue.issue_id) str(cycle_issue.issue_id) for cycle_issue in cycle_issues if str(cycle_issue.issue_id) in issues
for cycle_issue in cycle_issues
if str(cycle_issue.issue_id) in issues
] ]
new_issues = list(set(issues) - set(existing_issues)) new_issues = list(set(issues) - set(existing_issues))
@ -1029,9 +984,7 @@ class CycleIssueListCreateAPIEndpoint(BaseAPIView):
current_instance=json.dumps( current_instance=json.dumps(
{ {
"updated_cycle_issues": update_cycle_issue_activity, "updated_cycle_issues": update_cycle_issue_activity,
"created_cycle_issues": serializers.serialize( "created_cycle_issues": serializers.serialize("json", created_records),
"json", created_records
),
} }
), ),
epoch=int(timezone.now().timestamp()), epoch=int(timezone.now().timestamp()),
@ -1107,9 +1060,7 @@ class CycleIssueDetailAPIEndpoint(BaseAPIView):
cycle_id=cycle_id, cycle_id=cycle_id,
issue_id=issue_id, issue_id=issue_id,
) )
serializer = CycleIssueSerializer( serializer = CycleIssueSerializer(cycle_issue, fields=self.fields, expand=self.expand)
cycle_issue, fields=self.fields, expand=self.expand
)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
@cycle_docs( @cycle_docs(
@ -1214,7 +1165,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
{"error": "New Cycle Id is required"}, {"error": "New Cycle Id is required"},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
old_cycle = Cycle.objects.get( old_cycle = Cycle.objects.get(
workspace__slug=slug, workspace__slug=slug,
project_id=project_id, project_id=project_id,

View file

@ -154,7 +154,6 @@ class ProjectMemberListCreateAPIEndpoint(BaseAPIView):
# API endpoint to get and update a project member # API endpoint to get and update a project member
class ProjectMemberDetailAPIEndpoint(ProjectMemberListCreateAPIEndpoint): class ProjectMemberDetailAPIEndpoint(ProjectMemberListCreateAPIEndpoint):
@extend_schema( @extend_schema(
operation_id="get_project_member", operation_id="get_project_member",
summary="Get project member", summary="Get project member",

View file

@ -9,4 +9,4 @@ urlpatterns = [
ExportIssuesEndpoint.as_view(), ExportIssuesEndpoint.as_view(),
name="export-issues", name="export-issues",
), ),
] ]

View file

@ -45,9 +45,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
# Save the new avatar # Save the new avatar
user.avatar_asset_id = asset_id user.avatar_asset_id = asset_id
user.save() user.save()
invalidate_cache_directly( invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request)
path="/api/users/me/", url_params=False, user=True, request=request
)
invalidate_cache_directly( invalidate_cache_directly(
path="/api/users/me/settings/", path="/api/users/me/settings/",
url_params=False, url_params=False,
@ -65,9 +63,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
# Save the new cover image # Save the new cover image
user.cover_image_asset_id = asset_id user.cover_image_asset_id = asset_id
user.save() user.save()
invalidate_cache_directly( invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request)
path="/api/users/me/", url_params=False, user=True, request=request
)
invalidate_cache_directly( invalidate_cache_directly(
path="/api/users/me/settings/", path="/api/users/me/settings/",
url_params=False, url_params=False,
@ -83,9 +79,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
user = User.objects.get(id=asset.user_id) user = User.objects.get(id=asset.user_id)
user.avatar_asset_id = None user.avatar_asset_id = None
user.save() user.save()
invalidate_cache_directly( invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request)
path="/api/users/me/", url_params=False, user=True, request=request
)
invalidate_cache_directly( invalidate_cache_directly(
path="/api/users/me/settings/", path="/api/users/me/settings/",
url_params=False, url_params=False,
@ -98,9 +92,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
user = User.objects.get(id=asset.user_id) user = User.objects.get(id=asset.user_id)
user.cover_image_asset_id = None user.cover_image_asset_id = None
user.save() user.save()
invalidate_cache_directly( invalidate_cache_directly(path="/api/users/me/", url_params=False, user=True, request=request)
path="/api/users/me/", url_params=False, user=True, request=request
)
invalidate_cache_directly( invalidate_cache_directly(
path="/api/users/me/settings/", path="/api/users/me/settings/",
url_params=False, url_params=False,
@ -160,9 +152,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
# Get the presigned URL # Get the presigned URL
storage = S3Storage(request=request) storage = S3Storage(request=request)
# Generate a presigned URL to share an S3 object # Generate a presigned URL to share an S3 object
presigned_url = storage.generate_presigned_post( presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size_limit)
object_name=asset_key, file_type=type, file_size=size_limit
)
# Return the presigned URL # Return the presigned URL
return Response( return Response(
{ {
@ -199,9 +189,7 @@ class UserAssetsV2Endpoint(BaseAPIView):
asset.is_deleted = True asset.is_deleted = True
asset.deleted_at = timezone.now() asset.deleted_at = timezone.now()
# get the entity and save the asset id for the request field # get the entity and save the asset id for the request field
self.entity_asset_delete( self.entity_asset_delete(entity_type=asset.entity_type, asset=asset, request=request)
entity_type=asset.entity_type, asset=asset, request=request
)
asset.save(update_fields=["is_deleted", "deleted_at"]) asset.save(update_fields=["is_deleted", "deleted_at"])
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@ -265,18 +253,14 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
workspace.logo = "" workspace.logo = ""
workspace.logo_asset_id = asset_id workspace.logo_asset_id = asset_id
workspace.save() workspace.save()
invalidate_cache_directly( invalidate_cache_directly(path="/api/workspaces/", url_params=False, user=False, request=request)
path="/api/workspaces/", url_params=False, user=False, request=request
)
invalidate_cache_directly( invalidate_cache_directly(
path="/api/users/me/workspaces/", path="/api/users/me/workspaces/",
url_params=False, url_params=False,
user=True, user=True,
request=request, request=request,
) )
invalidate_cache_directly( invalidate_cache_directly(path="/api/instances/", url_params=False, user=False, request=request)
path="/api/instances/", url_params=False, user=False, request=request
)
return return
# Project Cover # Project Cover
@ -303,18 +287,14 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
return return
workspace.logo_asset_id = None workspace.logo_asset_id = None
workspace.save() workspace.save()
invalidate_cache_directly( invalidate_cache_directly(path="/api/workspaces/", url_params=False, user=False, request=request)
path="/api/workspaces/", url_params=False, user=False, request=request
)
invalidate_cache_directly( invalidate_cache_directly(
path="/api/users/me/workspaces/", path="/api/users/me/workspaces/",
url_params=False, url_params=False,
user=True, user=True,
request=request, request=request,
) )
invalidate_cache_directly( invalidate_cache_directly(path="/api/instances/", url_params=False, user=False, request=request)
path="/api/instances/", url_params=False, user=False, request=request
)
return return
# Project Cover # Project Cover
elif entity_type == FileAsset.EntityTypeContext.PROJECT_COVER: elif entity_type == FileAsset.EntityTypeContext.PROJECT_COVER:
@ -375,17 +355,13 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
workspace=workspace, workspace=workspace,
created_by=request.user, created_by=request.user,
entity_type=entity_type, entity_type=entity_type,
**self.get_entity_id_field( **self.get_entity_id_field(entity_type=entity_type, entity_id=entity_identifier),
entity_type=entity_type, entity_id=entity_identifier
),
) )
# Get the presigned URL # Get the presigned URL
storage = S3Storage(request=request) storage = S3Storage(request=request)
# Generate a presigned URL to share an S3 object # Generate a presigned URL to share an S3 object
presigned_url = storage.generate_presigned_post( presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size_limit)
object_name=asset_key, file_type=type, file_size=size_limit
)
# Return the presigned URL # Return the presigned URL
return Response( return Response(
{ {
@ -422,9 +398,7 @@ class WorkspaceFileAssetEndpoint(BaseAPIView):
asset.is_deleted = True asset.is_deleted = True
asset.deleted_at = timezone.now() asset.deleted_at = timezone.now()
# get the entity and save the asset id for the request field # get the entity and save the asset id for the request field
self.entity_asset_delete( self.entity_asset_delete(entity_type=asset.entity_type, asset=asset, request=request)
entity_type=asset.entity_type, asset=asset, request=request
)
asset.save(update_fields=["is_deleted", "deleted_at"]) asset.save(update_fields=["is_deleted", "deleted_at"])
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@ -587,9 +561,7 @@ class ProjectAssetEndpoint(BaseAPIView):
# Get the presigned URL # Get the presigned URL
storage = S3Storage(request=request) storage = S3Storage(request=request)
# Generate a presigned URL to share an S3 object # Generate a presigned URL to share an S3 object
presigned_url = storage.generate_presigned_post( presigned_url = storage.generate_presigned_post(object_name=asset_key, file_type=type, file_size=size_limit)
object_name=asset_key, file_type=type, file_size=size_limit
)
# Return the presigned URL # Return the presigned URL
return Response( return Response(
{ {
@ -619,9 +591,7 @@ class ProjectAssetEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def delete(self, request, slug, project_id, pk): def delete(self, request, slug, project_id, pk):
# Get the asset # Get the asset
asset = FileAsset.objects.get( asset = FileAsset.objects.get(id=pk, workspace__slug=slug, project_id=project_id)
id=pk, workspace__slug=slug, project_id=project_id
)
# Check deleted assets # Check deleted assets
asset.is_deleted = True asset.is_deleted = True
asset.deleted_at = timezone.now() asset.deleted_at = timezone.now()
@ -632,9 +602,7 @@ class ProjectAssetEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def get(self, request, slug, project_id, pk): def get(self, request, slug, project_id, pk):
# get the asset id # get the asset id
asset = FileAsset.objects.get( asset = FileAsset.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
workspace__slug=slug, project_id=project_id, pk=pk
)
# Check if the asset is uploaded # Check if the asset is uploaded
if not asset.is_uploaded: if not asset.is_uploaded:
@ -667,9 +635,7 @@ class ProjectBulkAssetEndpoint(BaseAPIView):
# Check if the asset ids are provided # Check if the asset ids are provided
if not asset_ids: if not asset_ids:
return Response( return Response({"error": "No asset ids provided."}, status=status.HTTP_400_BAD_REQUEST)
{"error": "No asset ids provided."}, status=status.HTTP_400_BAD_REQUEST
)
# get the asset id # get the asset id
assets = FileAsset.objects.filter(id__in=asset_ids, workspace__slug=slug) assets = FileAsset.objects.filter(id__in=asset_ids, workspace__slug=slug)
@ -723,14 +689,11 @@ class AssetCheckEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE") @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
def get(self, request, slug, asset_id): def get(self, request, slug, asset_id):
asset = FileAsset.all_objects.filter( asset = FileAsset.all_objects.filter(id=asset_id, workspace__slug=slug, deleted_at__isnull=True).exists()
id=asset_id, workspace__slug=slug, deleted_at__isnull=True
).exists()
return Response({"exists": asset}, status=status.HTTP_200_OK) return Response({"exists": asset}, status=status.HTTP_200_OK)
class DuplicateAssetEndpoint(BaseAPIView): class DuplicateAssetEndpoint(BaseAPIView):
throttle_classes = [AssetRateThrottle] throttle_classes = [AssetRateThrottle]
def get_entity_id_field(self, entity_type, entity_id): def get_entity_id_field(self, entity_type, entity_id):
@ -772,11 +735,7 @@ class DuplicateAssetEndpoint(BaseAPIView):
entity_id = request.data.get("entity_id", None) entity_id = request.data.get("entity_id", None)
entity_type = request.data.get("entity_type", None) entity_type = request.data.get("entity_type", None)
if not entity_type or entity_type not in FileAsset.EntityTypeContext.values:
if (
not entity_type
or entity_type not in FileAsset.EntityTypeContext.values
):
return Response( return Response(
{"error": "Invalid entity type or entity id"}, {"error": "Invalid entity type or entity id"},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
@ -786,23 +745,15 @@ class DuplicateAssetEndpoint(BaseAPIView):
if project_id: if project_id:
# check if project exists in the workspace # check if project exists in the workspace
if not Project.objects.filter(id=project_id, workspace=workspace).exists(): if not Project.objects.filter(id=project_id, workspace=workspace).exists():
return Response( return Response({"error": "Project not found"}, status=status.HTTP_404_NOT_FOUND)
{"error": "Project not found"}, status=status.HTTP_404_NOT_FOUND
)
storage = S3Storage(request=request) storage = S3Storage(request=request)
original_asset = FileAsset.objects.filter( original_asset = FileAsset.objects.filter(id=asset_id, is_uploaded=True).first()
id=asset_id, is_uploaded=True
).first()
if not original_asset: if not original_asset:
return Response( return Response({"error": "Asset not found"}, status=status.HTTP_404_NOT_FOUND)
{"error": "Asset not found"}, status=status.HTTP_404_NOT_FOUND
)
destination_key = ( destination_key = f"{workspace.id}/{uuid.uuid4().hex}-{original_asset.attributes.get('name')}"
f"{workspace.id}/{uuid.uuid4().hex}-{original_asset.attributes.get('name')}"
)
duplicated_asset = FileAsset.objects.create( duplicated_asset = FileAsset.objects.create(
attributes={ attributes={
"name": original_asset.attributes.get("name"), "name": original_asset.attributes.get("name"),
@ -822,9 +773,7 @@ class DuplicateAssetEndpoint(BaseAPIView):
# Update the is_uploaded field for all newly created assets # Update the is_uploaded field for all newly created assets
FileAsset.objects.filter(id=duplicated_asset.id).update(is_uploaded=True) FileAsset.objects.filter(id=duplicated_asset.id).update(is_uploaded=True)
return Response( return Response({"asset_id": str(duplicated_asset.id)}, status=status.HTTP_200_OK)
{"asset_id": str(duplicated_asset.id)}, status=status.HTTP_200_OK
)
class WorkspaceAssetDownloadEndpoint(BaseAPIView): class WorkspaceAssetDownloadEndpoint(BaseAPIView):

View file

@ -97,9 +97,7 @@ class CycleViewSet(BaseViewSet):
.prefetch_related( .prefetch_related(
Prefetch( Prefetch(
"issue_cycle__issue__assignees", "issue_cycle__issue__assignees",
queryset=User.objects.only( queryset=User.objects.only("avatar_asset", "first_name", "id").distinct(),
"avatar_asset", "first_name", "id"
).distinct(),
) )
) )
.prefetch_related( .prefetch_related(
@ -150,8 +148,7 @@ class CycleViewSet(BaseViewSet):
.annotate( .annotate(
status=Case( status=Case(
When( When(
Q(start_date__lte=current_time_in_utc) Q(start_date__lte=current_time_in_utc) & Q(end_date__gte=current_time_in_utc),
& Q(end_date__gte=current_time_in_utc),
then=Value("CURRENT"), then=Value("CURRENT"),
), ),
When(start_date__gt=current_time_in_utc, then=Value("UPCOMING")), When(start_date__gt=current_time_in_utc, then=Value("UPCOMING")),
@ -170,11 +167,7 @@ class CycleViewSet(BaseViewSet):
"issue_cycle__issue__assignees__id", "issue_cycle__issue__assignees__id",
distinct=True, distinct=True,
filter=~Q(issue_cycle__issue__assignees__id__isnull=True) filter=~Q(issue_cycle__issue__assignees__id__isnull=True)
& ( & (Q(issue_cycle__issue__issue_assignee__deleted_at__isnull=True)),
Q(
issue_cycle__issue__issue_assignee__deleted_at__isnull=True
)
),
), ),
Value([], output_field=ArrayField(UUIDField())), Value([], output_field=ArrayField(UUIDField())),
) )
@ -205,9 +198,7 @@ class CycleViewSet(BaseViewSet):
# Current Cycle # Current Cycle
if cycle_view == "current": if cycle_view == "current":
queryset = queryset.filter( queryset = queryset.filter(start_date__lte=current_time_in_utc, end_date__gte=current_time_in_utc)
start_date__lte=current_time_in_utc, end_date__gte=current_time_in_utc
)
data = queryset.values( data = queryset.values(
# necessary fields # necessary fields
@ -274,16 +265,10 @@ class CycleViewSet(BaseViewSet):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER]) @allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def create(self, request, slug, project_id): def create(self, request, slug, project_id):
if ( if (request.data.get("start_date", None) is None and request.data.get("end_date", None) is None) or (
request.data.get("start_date", None) is None request.data.get("start_date", None) is not None and request.data.get("end_date", None) is not None
and request.data.get("end_date", None) is None
) or (
request.data.get("start_date", None) is not None
and request.data.get("end_date", None) is not None
): ):
serializer = CycleWriteSerializer( serializer = CycleWriteSerializer(data=request.data, context={"project_id": project_id})
data=request.data, context={"project_id": project_id}
)
if serializer.is_valid(): if serializer.is_valid():
serializer.save(project_id=project_id, owned_by=request.user) serializer.save(project_id=project_id, owned_by=request.user)
cycle = ( cycle = (
@ -323,9 +308,7 @@ class CycleViewSet(BaseViewSet):
project_timezone = project.timezone project_timezone = project.timezone
datetime_fields = ["start_date", "end_date"] datetime_fields = ["start_date", "end_date"]
cycle = user_timezone_converter( cycle = user_timezone_converter(cycle, datetime_fields, project_timezone)
cycle, datetime_fields, project_timezone
)
# Send the model activity # Send the model activity
model_activity.delay( model_activity.delay(
@ -341,17 +324,13 @@ class CycleViewSet(BaseViewSet):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else: else:
return Response( return Response(
{ {"error": "Both start date and end date are either required or are to be null"},
"error": "Both start date and end date are either required or are to be null"
},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
@allow_permission([ROLE.ADMIN, ROLE.MEMBER]) @allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def partial_update(self, request, slug, project_id, pk): def partial_update(self, request, slug, project_id, pk):
queryset = self.get_queryset().filter( queryset = self.get_queryset().filter(workspace__slug=slug, project_id=project_id, pk=pk)
workspace__slug=slug, project_id=project_id, pk=pk
)
cycle = queryset.first() cycle = queryset.first()
if cycle.archived_at: if cycle.archived_at:
return Response( return Response(
@ -359,29 +338,21 @@ class CycleViewSet(BaseViewSet):
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
current_instance = json.dumps( current_instance = json.dumps(CycleSerializer(cycle).data, cls=DjangoJSONEncoder)
CycleSerializer(cycle).data, cls=DjangoJSONEncoder
)
request_data = request.data request_data = request.data
if cycle.end_date is not None and cycle.end_date < timezone.now(): if cycle.end_date is not None and cycle.end_date < timezone.now():
if "sort_order" in request_data: if "sort_order" in request_data:
# Can only change sort order for a completed cycle`` # Can only change sort order for a completed cycle``
request_data = { request_data = {"sort_order": request_data.get("sort_order", cycle.sort_order)}
"sort_order": request_data.get("sort_order", cycle.sort_order)
}
else: else:
return Response( return Response(
{ {"error": "The Cycle has already been completed so it cannot be edited"},
"error": "The Cycle has already been completed so it cannot be edited"
},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
serializer = CycleWriteSerializer( serializer = CycleWriteSerializer(cycle, data=request.data, partial=True, context={"project_id": project_id})
cycle, data=request.data, partial=True, context={"project_id": project_id}
)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
cycle = queryset.values( cycle = queryset.values(
@ -481,9 +452,7 @@ class CycleViewSet(BaseViewSet):
) )
if data is None: if data is None:
return Response( return Response({"error": "Cycle not found"}, status=status.HTTP_404_NOT_FOUND)
{"error": "Cycle not found"}, status=status.HTTP_404_NOT_FOUND
)
queryset = queryset.first() queryset = queryset.first()
# Fetch the project timezone # Fetch the project timezone
@ -505,11 +474,7 @@ class CycleViewSet(BaseViewSet):
def destroy(self, request, slug, project_id, pk): def destroy(self, request, slug, project_id, pk):
cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk) cycle = Cycle.objects.get(workspace__slug=slug, project_id=project_id, pk=pk)
cycle_issues = list( cycle_issues = list(CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list("issue", flat=True))
CycleIssue.objects.filter(cycle_id=self.kwargs.get("pk")).values_list(
"issue", flat=True
)
)
issue_activity.delay( issue_activity.delay(
type="cycle.activity.deleted", type="cycle.activity.deleted",
@ -560,9 +525,7 @@ class CycleDateCheckEndpoint(BaseAPIView):
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
start_date = convert_to_utc( start_date = convert_to_utc(date=str(start_date), project_id=project_id, is_start_date=True)
date=str(start_date), project_id=project_id, is_start_date=True
)
end_date = convert_to_utc( end_date = convert_to_utc(
date=str(end_date), date=str(end_date),
project_id=project_id, project_id=project_id,
@ -666,12 +629,8 @@ class CycleUserPropertiesEndpoint(BaseAPIView):
) )
cycle_properties.filters = request.data.get("filters", cycle_properties.filters) cycle_properties.filters = request.data.get("filters", cycle_properties.filters)
cycle_properties.rich_filters = request.data.get( cycle_properties.rich_filters = request.data.get("rich_filters", cycle_properties.rich_filters)
"rich_filters", cycle_properties.rich_filters cycle_properties.display_filters = request.data.get("display_filters", cycle_properties.display_filters)
)
cycle_properties.display_filters = request.data.get(
"display_filters", cycle_properties.display_filters
)
cycle_properties.display_properties = request.data.get( cycle_properties.display_properties = request.data.get(
"display_properties", cycle_properties.display_properties "display_properties", cycle_properties.display_properties
) )
@ -695,13 +654,9 @@ class CycleUserPropertiesEndpoint(BaseAPIView):
class CycleProgressEndpoint(BaseAPIView): class CycleProgressEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def get(self, request, slug, project_id, cycle_id): def get(self, request, slug, project_id, cycle_id):
cycle = Cycle.objects.filter( cycle = Cycle.objects.filter(workspace__slug=slug, project_id=project_id, id=cycle_id).first()
workspace__slug=slug, project_id=project_id, id=cycle_id
).first()
if not cycle: if not cycle:
return Response( return Response({"error": "Cycle not found"}, status=status.HTTP_404_NOT_FOUND)
{"error": "Cycle not found"}, status=status.HTTP_404_NOT_FOUND
)
aggregate_estimates = ( aggregate_estimates = (
Issue.issue_objects.filter( Issue.issue_objects.filter(
estimate_point__estimate__type="points", estimate_point__estimate__type="points",
@ -747,9 +702,7 @@ class CycleProgressEndpoint(BaseAPIView):
output_field=FloatField(), output_field=FloatField(),
) )
), ),
total_estimate_points=Sum( total_estimate_points=Sum("value_as_float", default=Value(0), output_field=FloatField()),
"value_as_float", default=Value(0), output_field=FloatField()
),
) )
) )
if cycle.progress_snapshot: if cycle.progress_snapshot:
@ -809,22 +762,11 @@ class CycleProgressEndpoint(BaseAPIView):
return Response( return Response(
{ {
"backlog_estimate_points": aggregate_estimates["backlog_estimate_point"] "backlog_estimate_points": aggregate_estimates["backlog_estimate_point"] or 0,
or 0, "unstarted_estimate_points": aggregate_estimates["unstarted_estimate_point"] or 0,
"unstarted_estimate_points": aggregate_estimates[ "started_estimate_points": aggregate_estimates["started_estimate_point"] or 0,
"unstarted_estimate_point" "cancelled_estimate_points": aggregate_estimates["cancelled_estimate_point"] or 0,
] "completed_estimate_points": aggregate_estimates["completed_estimate_points"] or 0,
or 0,
"started_estimate_points": aggregate_estimates["started_estimate_point"]
or 0,
"cancelled_estimate_points": aggregate_estimates[
"cancelled_estimate_point"
]
or 0,
"completed_estimate_points": aggregate_estimates[
"completed_estimate_points"
]
or 0,
"total_estimate_points": aggregate_estimates["total_estimate_points"], "total_estimate_points": aggregate_estimates["total_estimate_points"],
"backlog_issues": backlog_issues, "backlog_issues": backlog_issues,
"total_issues": total_issues, "total_issues": total_issues,
@ -842,9 +784,7 @@ class CycleAnalyticsEndpoint(BaseAPIView):
def get(self, request, slug, project_id, cycle_id): def get(self, request, slug, project_id, cycle_id):
analytic_type = request.GET.get("type", "issues") analytic_type = request.GET.get("type", "issues")
cycle = ( cycle = (
Cycle.objects.filter( Cycle.objects.filter(workspace__slug=slug, project_id=project_id, id=cycle_id)
workspace__slug=slug, project_id=project_id, id=cycle_id
)
.annotate( .annotate(
total_issues=Count( total_issues=Count(
"issue_cycle__issue__id", "issue_cycle__issue__id",
@ -927,9 +867,7 @@ class CycleAnalyticsEndpoint(BaseAPIView):
) )
) )
.values("display_name", "assignee_id", "avatar_url") .values("display_name", "assignee_id", "avatar_url")
.annotate( .annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
)
.annotate( .annotate(
completed_estimates=Sum( completed_estimates=Sum(
Cast("estimate_point__value", FloatField()), Cast("estimate_point__value", FloatField()),
@ -964,9 +902,7 @@ class CycleAnalyticsEndpoint(BaseAPIView):
.annotate(color=F("labels__color")) .annotate(color=F("labels__color"))
.annotate(label_id=F("labels__id")) .annotate(label_id=F("labels__id"))
.values("label_name", "color", "label_id") .values("label_name", "color", "label_id")
.annotate( .annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
total_estimates=Sum(Cast("estimate_point__value", FloatField()))
)
.annotate( .annotate(
completed_estimates=Sum( completed_estimates=Sum(
Cast("estimate_point__value", FloatField()), Cast("estimate_point__value", FloatField()),
@ -1068,11 +1004,7 @@ class CycleAnalyticsEndpoint(BaseAPIView):
.annotate(color=F("labels__color")) .annotate(color=F("labels__color"))
.annotate(label_id=F("labels__id")) .annotate(label_id=F("labels__id"))
.values("label_name", "color", "label_id") .values("label_name", "color", "label_id")
.annotate( .annotate(total_issues=Count("label_id", filter=Q(archived_at__isnull=True, is_draft=False)))
total_issues=Count(
"label_id", filter=Q(archived_at__isnull=True, is_draft=False)
)
)
.annotate( .annotate(
completed_issues=Count( completed_issues=Count(
"label_id", "label_id",

View file

@ -39,9 +39,7 @@ class LabelViewSet(BaseViewSet):
@allow_permission([ROLE.ADMIN]) @allow_permission([ROLE.ADMIN])
def create(self, request, slug, project_id): def create(self, request, slug, project_id):
try: try:
serializer = LabelSerializer( serializer = LabelSerializer(data=request.data, context={"project_id": project_id})
data=request.data, context={"project_id": project_id}
)
if serializer.is_valid(): if serializer.is_valid():
serializer.save(project_id=project_id) serializer.save(project_id=project_id)
return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.data, status=status.HTTP_201_CREATED)

View file

@ -495,14 +495,12 @@ class PagesDescriptionViewSet(BaseViewSet):
permission_classes = [ProjectPagePermission] permission_classes = [ProjectPagePermission]
def retrieve(self, request, slug, project_id, page_id): def retrieve(self, request, slug, project_id, page_id):
page = ( page = Page.objects.get(
Page.objects.get( Q(owned_by=self.request.user) | Q(access=0),
Q(owned_by=self.request.user) | Q(access=0), pk=page_id,
pk=page_id, workspace__slug=slug,
workspace__slug=slug, projects__id=project_id,
projects__id=project_id, project_pages__deleted_at__isnull=True,
project_pages__deleted_at__isnull=True,
)
) )
binary_data = page.description_binary binary_data = page.description_binary
@ -517,14 +515,12 @@ class PagesDescriptionViewSet(BaseViewSet):
return response return response
def partial_update(self, request, slug, project_id, page_id): def partial_update(self, request, slug, project_id, page_id):
page = ( page = Page.objects.get(
Page.objects.get( Q(owned_by=self.request.user) | Q(access=0),
Q(owned_by=self.request.user) | Q(access=0), pk=page_id,
pk=page_id, workspace__slug=slug,
workspace__slug=slug, projects__id=project_id,
projects__id=project_id, project_pages__deleted_at__isnull=True,
project_pages__deleted_at__isnull=True,
)
) )
if page.is_locked: if page.is_locked:

View file

@ -90,9 +90,9 @@ class Adapter:
"""Check if sign up is enabled or not and raise exception if not enabled""" """Check if sign up is enabled or not and raise exception if not enabled"""
# Get configuration value # Get configuration value
(ENABLE_SIGNUP,) = get_configuration_value([ (ENABLE_SIGNUP,) = get_configuration_value(
{"key": "ENABLE_SIGNUP", "default": os.environ.get("ENABLE_SIGNUP", "1")} [{"key": "ENABLE_SIGNUP", "default": os.environ.get("ENABLE_SIGNUP", "1")}]
]) )
# Check if sign up is disabled and invite is present or not # Check if sign up is disabled and invite is present or not
if ENABLE_SIGNUP == "0" and not WorkspaceMemberInvite.objects.filter(email=email).exists(): if ENABLE_SIGNUP == "0" and not WorkspaceMemberInvite.objects.filter(email=email).exists():

View file

@ -101,9 +101,7 @@ class GiteaOAuthProvider(OauthAdapter):
else None else None
), ),
"refresh_token_expired_at": ( "refresh_token_expired_at": (
datetime.fromtimestamp( datetime.fromtimestamp(token_response.get("refresh_token_expired_at"), tz=pytz.utc)
token_response.get("refresh_token_expired_at"), tz=pytz.utc
)
if token_response.get("refresh_token_expired_at") if token_response.get("refresh_token_expired_at")
else None else None
), ),
@ -168,4 +166,4 @@ class GiteaOAuthProvider(OauthAdapter):
"is_password_autoset": True, "is_password_autoset": True,
}, },
} }
) )

View file

@ -37,9 +37,7 @@ class GiteaOauthInitiateEndpoint(View):
params = exc.get_error_dict() params = exc.get_error_dict()
if next_path: if next_path:
params["next_path"] = str(validate_next_path(next_path)) params["next_path"] = str(validate_next_path(next_path))
url = urljoin( url = urljoin(base_host(request=request, is_app=True), "?" + urlencode(params))
base_host(request=request, is_app=True), "?" + urlencode(params)
)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
try: try:
state = uuid.uuid4().hex state = uuid.uuid4().hex
@ -51,9 +49,7 @@ class GiteaOauthInitiateEndpoint(View):
params = e.get_error_dict() params = e.get_error_dict()
if next_path: if next_path:
params["next_path"] = str(validate_next_path(next_path)) params["next_path"] = str(validate_next_path(next_path))
url = urljoin( url = urljoin(base_host(request=request, is_app=True), "?" + urlencode(params))
base_host(request=request, is_app=True), "?" + urlencode(params)
)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -87,9 +83,7 @@ class GiteaCallbackEndpoint(View):
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
try: try:
provider = GiteaOAuthProvider( provider = GiteaOAuthProvider(request=request, code=code, callback=post_user_auth_workflow)
request=request, code=code, callback=post_user_auth_workflow
)
user = provider.authenticate() user = provider.authenticate()
# Login the user and record his device info # Login the user and record his device info
user_login(request=request, user=user, is_app=True) user_login(request=request, user=user, is_app=True)

View file

@ -88,7 +88,6 @@ def page_transaction(new_description_html, old_description_html, page_id):
has_existing_logs = PageLog.objects.filter(page_id=page_id).exists() has_existing_logs = PageLog.objects.filter(page_id=page_id).exists()
# Extract all components in a single pass (optimized) # Extract all components in a single pass (optimized)
old_components = extract_all_components(old_description_html) old_components = extract_all_components(old_description_html)
new_components = extract_all_components(new_description_html) new_components = extract_all_components(new_description_html)
@ -125,12 +124,9 @@ def page_transaction(new_description_html, old_description_html, page_id):
) )
) )
# Bulk insert and cleanup # Bulk insert and cleanup
if new_transactions: if new_transactions:
PageLog.objects.bulk_create( PageLog.objects.bulk_create(new_transactions, batch_size=50, ignore_conflicts=True)
new_transactions, batch_size=50, ignore_conflicts=True
)
if deleted_transaction_ids: if deleted_transaction_ids:
PageLog.objects.filter(transaction__in=deleted_transaction_ids).delete() PageLog.objects.filter(transaction__in=deleted_transaction_ids).delete()

View file

@ -107,56 +107,60 @@ def create_project_and_member(workspace: Workspace, bot_user: User) -> Dict[int,
) )
# Create project members # Create project members
ProjectMember.objects.bulk_create([ ProjectMember.objects.bulk_create(
ProjectMember( [
project=project, ProjectMember(
member_id=workspace_member["member_id"], project=project,
role=workspace_member["role"], member_id=workspace_member["member_id"],
workspace_id=workspace.id, role=workspace_member["role"],
created_by_id=bot_user.id, workspace_id=workspace.id,
) created_by_id=bot_user.id,
for workspace_member in workspace_members )
]) for workspace_member in workspace_members
]
)
# Create issue user properties # Create issue user properties
IssueUserProperty.objects.bulk_create([ IssueUserProperty.objects.bulk_create(
IssueUserProperty( [
project=project, IssueUserProperty(
user_id=workspace_member["member_id"], project=project,
workspace_id=workspace.id, user_id=workspace_member["member_id"],
display_filters={ workspace_id=workspace.id,
"layout": "list", display_filters={
"calendar": {"layout": "month", "show_weekends": False}, "layout": "list",
"group_by": "state", "calendar": {"layout": "month", "show_weekends": False},
"order_by": "sort_order", "group_by": "state",
"sub_issue": True, "order_by": "sort_order",
"sub_group_by": None, "sub_issue": True,
"show_empty_groups": True, "sub_group_by": None,
}, "show_empty_groups": True,
display_properties={ },
"key": True, display_properties={
"link": True, "key": True,
"cycle": False, "link": True,
"state": True, "cycle": False,
"labels": False, "state": True,
"modules": False, "labels": False,
"assignee": True, "modules": False,
"due_date": False, "assignee": True,
"estimate": True, "due_date": False,
"priority": True, "estimate": True,
"created_on": True, "priority": True,
"issue_type": True, "created_on": True,
"start_date": False, "issue_type": True,
"updated_on": True, "start_date": False,
"customer_count": True, "updated_on": True,
"sub_issue_count": False, "customer_count": True,
"attachment_count": False, "sub_issue_count": False,
"customer_request_count": True, "attachment_count": False,
}, "customer_request_count": True,
created_by_id=bot_user.id, },
) created_by_id=bot_user.id,
for workspace_member in workspace_members )
]) for workspace_member in workspace_members
]
)
# update map # update map
projects_map[project_id] = project.id projects_map[project_id] = project.id
logger.info(f"Task: workspace_seed_task -> Project {project_id} created") logger.info(f"Task: workspace_seed_task -> Project {project_id} created")

View file

@ -134,8 +134,10 @@ class InstanceAdminSignUpEndpoint(View):
}, },
) )
url = urljoin( url = urljoin(
base_host(request=request, is_admin=True, ), base_host(
request=request,
is_admin=True,
),
"?" + urlencode(exc.get_error_dict()), "?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)

View file

@ -10,9 +10,7 @@ class TestLabelSerializer:
@pytest.mark.django_db @pytest.mark.django_db
def test_label_serializer_create_valid_data(self, db, workspace): def test_label_serializer_create_valid_data(self, db, workspace):
"""Test creating a label with valid data""" """Test creating a label with valid data"""
project = Project.objects.create( project = Project.objects.create(name="Test Project", identifier="TEST", workspace=workspace)
name="Test Project", identifier="TEST", workspace=workspace
)
serializer = LabelSerializer( serializer = LabelSerializer(
data={"name": "Test Label"}, data={"name": "Test Label"},
@ -30,14 +28,10 @@ class TestLabelSerializer:
@pytest.mark.django_db @pytest.mark.django_db
def test_label_serializer_create_duplicate_name(self, db, workspace): def test_label_serializer_create_duplicate_name(self, db, workspace):
"""Test creating a label with a duplicate name""" """Test creating a label with a duplicate name"""
project = Project.objects.create( project = Project.objects.create(name="Test Project", identifier="TEST", workspace=workspace)
name="Test Project", identifier="TEST", workspace=workspace
)
Label.objects.create(name="Test Label", project=project) Label.objects.create(name="Test Label", project=project)
serializer = LabelSerializer( serializer = LabelSerializer(data={"name": "Test Label"}, context={"project_id": project.id})
data={"name": "Test Label"}, context={"project_id": project.id}
)
assert not serializer.is_valid() assert not serializer.is_valid()
assert serializer.errors == {"name": ["LABEL_NAME_ALREADY_EXISTS"]} assert serializer.errors == {"name": ["LABEL_NAME_ALREADY_EXISTS"]}

View file

@ -56,9 +56,7 @@ def validate_binary_data(data):
# Check for suspicious text patterns (HTML/JS) # Check for suspicious text patterns (HTML/JS)
try: try:
decoded_text = binary_data.decode("utf-8", errors="ignore")[:200] decoded_text = binary_data.decode("utf-8", errors="ignore")[:200]
if any( if any(pattern in decoded_text.lower() for pattern in SUSPICIOUS_BINARY_PATTERNS):
pattern in decoded_text.lower() for pattern in SUSPICIOUS_BINARY_PATTERNS
):
return False, "Binary data contains suspicious content patterns" return False, "Binary data contains suspicious content patterns"
except Exception: except Exception:
pass # Binary data might not be decodable as text, which is fine pass # Binary data might not be decodable as text, which is fine

View file

@ -51,9 +51,7 @@ def transfer_cycle_issues(
dict: Response data with success or error message dict: Response data with success or error message
""" """
# Get the new cycle # Get the new cycle
new_cycle = Cycle.objects.filter( new_cycle = Cycle.objects.filter(workspace__slug=slug, project_id=project_id, pk=new_cycle_id).first()
workspace__slug=slug, project_id=project_id, pk=new_cycle_id
).first()
# Check if new cycle is already completed # Check if new cycle is already completed
if new_cycle.end_date is not None and new_cycle.end_date < timezone.now(): if new_cycle.end_date is not None and new_cycle.end_date < timezone.now():
@ -216,9 +214,7 @@ def transfer_cycle_issues(
assignee_estimate_distribution = [ assignee_estimate_distribution = [
{ {
"display_name": item["display_name"], "display_name": item["display_name"],
"assignee_id": ( "assignee_id": (str(item["assignee_id"]) if item["assignee_id"] else None),
str(item["assignee_id"]) if item["assignee_id"] else None
),
"avatar_url": item.get("avatar_url"), "avatar_url": item.get("avatar_url"),
"total_estimates": item["total_estimates"], "total_estimates": item["total_estimates"],
"completed_estimates": item["completed_estimates"], "completed_estimates": item["completed_estimates"],
@ -310,9 +306,7 @@ def transfer_cycle_issues(
) )
) )
.values("display_name", "assignee_id", "avatar_url") .values("display_name", "assignee_id", "avatar_url")
.annotate( .annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)))
total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False))
)
.annotate( .annotate(
completed_issues=Count( completed_issues=Count(
"id", "id",
@ -360,9 +354,7 @@ def transfer_cycle_issues(
.annotate(color=F("labels__color")) .annotate(color=F("labels__color"))
.annotate(label_id=F("labels__id")) .annotate(label_id=F("labels__id"))
.values("label_name", "color", "label_id") .values("label_name", "color", "label_id")
.annotate( .annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)))
total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False))
)
.annotate( .annotate(
completed_issues=Count( completed_issues=Count(
"id", "id",
@ -409,9 +401,7 @@ def transfer_cycle_issues(
) )
# Get the current cycle and save progress snapshot # Get the current cycle and save progress snapshot
current_cycle = Cycle.objects.filter( current_cycle = Cycle.objects.filter(workspace__slug=slug, project_id=project_id, pk=cycle_id).first()
workspace__slug=slug, project_id=project_id, pk=cycle_id
).first()
current_cycle.progress_snapshot = { current_cycle.progress_snapshot = {
"total_issues": old_cycle.total_issues, "total_issues": old_cycle.total_issues,
@ -461,9 +451,7 @@ def transfer_cycle_issues(
) )
# Bulk update cycle issues # Bulk update cycle issues
cycle_issues = CycleIssue.objects.bulk_update( cycle_issues = CycleIssue.objects.bulk_update(updated_cycles, ["cycle_id"], batch_size=100)
updated_cycles, ["cycle_id"], batch_size=100
)
# Capture Issue Activity # Capture Issue Activity
issue_activity.delay( issue_activity.delay(

View file

@ -263,6 +263,7 @@ def state_docs(**kwargs):
return extend_schema(**_merge_schema_options(defaults, kwargs)) return extend_schema(**_merge_schema_options(defaults, kwargs))
def sticky_docs(**kwargs): def sticky_docs(**kwargs):
"""Decorator for sticky management endpoints""" """Decorator for sticky management endpoints"""
defaults = { defaults = {
@ -276,4 +277,4 @@ def sticky_docs(**kwargs):
}, },
} }
return extend_schema(**_merge_schema_options(defaults, kwargs)) return extend_schema(**_merge_schema_options(defaults, kwargs))