All checks were successful
Deploy Plane Project Tracker / Deploy to cisystem (push) Successful in 1m3s
106 lines
4.2 KiB
Python
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,
|
|
)
|