jira/apps/api/plane/app/views/issue/bulk_import.py
vickytechkey a54ef78a3b
All checks were successful
Deploy Plane Project Tracker / Deploy to cisystem (push) Successful in 1m3s
fix(api): provide missing context keys for IssueCreateSerializer in bulk import
2026-09-19 21:48:20 +05:30

106 lines
4.2 KiB
Python

import csv
import io
from django.http import HttpResponse
from rest_framework import status
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser
from plane.app.permissions import ROLE, allow_permission
from plane.app.serializers import IssueCreateSerializer
from plane.db.models import State, Project, Issue
from .. import BaseAPIView
from plane.utils.csv_utils import sanitize_csv_row
class DownloadIssueTemplateEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def get(self, request, slug, project_id):
csv_buffer = io.StringIO()
writer = csv.writer(csv_buffer, delimiter=",", quoting=csv.QUOTE_ALL)
headers = ["name", "description", "state", "priority"]
sample_row = ["Sample Task", "This is a sample task description.", "Todo", "high"]
writer.writerow(sanitize_csv_row(headers))
writer.writerow(sanitize_csv_row(sample_row))
response = HttpResponse(csv_buffer.getvalue(), content_type="text/csv")
response["Content-Disposition"] = 'attachment; filename="issue_template.csv"'
return response
class BulkImportIssuesEndpoint(BaseAPIView):
parser_classes = (MultiPartParser,)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def post(self, request, slug, project_id):
file = request.FILES.get("file")
if not file:
return Response({"error": "File is required"}, status=status.HTTP_400_BAD_REQUEST)
if not file.name.endswith(".csv"):
return Response({"error": "Invalid file format. Please upload a CSV file."}, status=status.HTTP_400_BAD_REQUEST)
try:
decoded_file = file.read().decode("utf-8")
reader = csv.DictReader(io.StringIO(decoded_file))
except Exception as e:
return Response({"error": "Failed to read CSV file. Please ensure it is correctly formatted."}, status=status.HTTP_400_BAD_REQUEST)
project = Project.objects.filter(pk=project_id).first()
if not project:
return Response({"error": "Project not found."}, status=status.HTTP_404_NOT_FOUND)
default_state = State.objects.filter(project_id=project_id, default=True).first()
if not default_state:
default_state = State.objects.filter(project_id=project_id).order_by("sequence").first()
created_issues = []
errors = []
for row_index, row in enumerate(reader, start=2):
name = row.get("name")
if not name:
errors.append(f"Row {row_index}: Name is required")
continue
state_name = row.get("state")
state_id = default_state.id if default_state else None
if state_name:
state = State.objects.filter(project_id=project_id, name__iexact=state_name.strip()).first()
if state:
state_id = state.id
priority = row.get("priority", "none").lower()
if priority not in ["urgent", "high", "medium", "low", "none"]:
priority = "none"
issue_data = {
"name": name.strip(),
"description_html": f"<p>{row.get('description', '')}</p>",
"state_id": state_id,
"priority": priority,
}
serializer = IssueCreateSerializer(
data=issue_data,
context={
"project_id": project_id,
"workspace_id": project.workspace_id,
"default_assignee_id": project.default_assignee_id,
}
)
if serializer.is_valid():
issue = serializer.save(created_by=request.user, updated_by=request.user)
created_issues.append(issue.id)
else:
errors.append(f"Row {row_index}: {serializer.errors}")
return Response(
{
"message": f"Successfully imported {len(created_issues)} issues.",
"created_issues": created_issues,
"errors": errors,
},
status=status.HTTP_201_CREATED if len(created_issues) > 0 else status.HTTP_400_BAD_REQUEST,
)