diff --git a/requirements.txt b/requirements.txt index 9732a525804ca350a44d28a76c17d3a0fd8ce986..4edebd6b0646bd3afbc9d5f613964dea13a26e24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ Django==5.0.11 +django-rest-framework ruff mypy tox diff --git a/sage_validation/file_validator/templates/upload.html b/sage_validation/file_validator/templates/upload.html index 4a595da4794ec9281508073a490bc54daa4549f5..ec8619dd40130d1555c59dc7a549c6c359c57edc 100644 --- a/sage_validation/file_validator/templates/upload.html +++ b/sage_validation/file_validator/templates/upload.html @@ -40,21 +40,18 @@ </div> </div> - <script> - const form = document.getElementById('uploadForm'); - const fileInput = document.getElementById('fileInput'); - const errorSection = document.getElementById('errorSection'); - const errorList = document.getElementById('errorList'); - const successSection = document.getElementById('successSection'); - const successMessage = document.getElementById('successMessage'); - const downloadSection = document.getElementById('downloadSection'); - const downloadLink = document.getElementById('downloadLink'); - - form.addEventListener('submit', async function (e) { + document.getElementById('uploadForm').addEventListener('submit', async function (e) { e.preventDefault(); - // Clear previous messages + const fileInput = document.getElementById('fileInput'); + const errorSection = document.getElementById('errorSection'); + const errorList = document.getElementById('errorList'); + const successSection = document.getElementById('successSection'); + const successMessage = document.getElementById('successMessage'); + const downloadSection = document.getElementById('downloadSection'); + const downloadLink = document.getElementById('downloadLink'); + errorList.innerHTML = ''; successMessage.innerHTML = ''; errorSection.classList.add('hidden'); @@ -65,7 +62,7 @@ formData.append('file', fileInput.files[0]); try { - const response = await fetch('', { + const response = await fetch("{% url 'upload-file' %}", { method: 'POST', body: formData, headers: { @@ -81,38 +78,13 @@ downloadLink.href = result.download_url; downloadSection.classList.remove('hidden'); } else if (response.status === 400 && result.status === 'error') { - errorList.innerHTML = ''; - - if (Array.isArray(result.errors)) { - result.errors.forEach(errorObj => { - if (typeof errorObj === 'string') { - const li = document.createElement('li'); - li.textContent = errorObj; - errorList.appendChild(li); - } else { - for (const [field, messages] of Object.entries(errorObj)) { - messages.forEach(message => { - const li = document.createElement('li'); - li.textContent = `${field}: ${message}`; - errorList.appendChild(li); - }); - } - } + for (const [field, messages] of Object.entries(result.errors)) { + messages.forEach(message => { + const li = document.createElement('li'); + li.textContent = `${field}: ${message}`; + errorList.appendChild(li); }); - } else if (typeof result.errors === 'object') { - for (const [field, messages] of Object.entries(result.errors)) { - messages.forEach(message => { - const li = document.createElement('li'); - li.textContent = `${field}: ${message}`; - errorList.appendChild(li); - }); - } - } else { - const li = document.createElement('li'); - li.textContent = result.errors; - errorList.appendChild(li); } - errorSection.classList.remove('hidden'); } } catch (error) { diff --git a/sage_validation/file_validator/urls.py b/sage_validation/file_validator/urls.py index a5c0d9fd7840097233aabed1184a392037d7bcc1..ee5ae78157bbb92dcb77a93df310e41755f46739 100644 --- a/sage_validation/file_validator/urls.py +++ b/sage_validation/file_validator/urls.py @@ -2,9 +2,10 @@ from django.urls import path -from sage_validation.file_validator.views import CSVExportView, CSVUploadView +from sage_validation.file_validator.views import CSVExportAPIView, CSVUploadAPIView, upload_page_view urlpatterns = [ - path("upload/", CSVUploadView.as_view(), name="upload-file"), - path("export/", CSVExportView.as_view(), name="export-file"), + path("upload-page/", upload_page_view, name="upload-page"), + path("api/upload/", CSVUploadAPIView.as_view(), name="upload-file"), + path("api/export/", CSVExportAPIView.as_view(), name="export-file"), ] diff --git a/sage_validation/file_validator/views.py b/sage_validation/file_validator/views.py index 99efd175b0fa9498eb15127578cd85e272e21ac1..bfa80c2cde6e71b61c4849f7183a669d74b739c2 100644 --- a/sage_validation/file_validator/views.py +++ b/sage_validation/file_validator/views.py @@ -1,14 +1,15 @@ """Views for the file_validator app.""" import csv import io -from typing import Any -from django.http import HttpRequest, HttpResponse, JsonResponse +from django.http import HttpRequest, HttpResponse from django.shortcuts import render from django.urls import reverse_lazy from django.utils import timezone -from django.views.generic.base import View -from django.views.generic.edit import FormView +from rest_framework import status +from rest_framework.request import Request +from rest_framework.response import Response +from rest_framework.views import APIView from sage_validation.file_validator.forms import CSVUploadForm from sage_validation.file_validator.models import MeoCostCentres, XxData @@ -19,46 +20,41 @@ def index_view(request: HttpRequest) -> HttpResponse: return render(request, "index.html") -class CSVUploadView(FormView): - """View for uploading a CSV file.""" +def upload_page_view(request: HttpRequest) -> HttpResponse: + """Render the file upload page.""" + return render(request, "upload.html") - template_name = "upload.html" - form_class = CSVUploadForm - success_url = reverse_lazy("upload-file") - def get_context_data(self, **kwargs: dict[str, Any]) -> dict[str, Any]: - """Render the form with no error message on GET request.""" - context = super().get_context_data(**kwargs) - context["error"] = None - context["message"] = None - return context +class CSVUploadAPIView(APIView): + """API view for uploading a CSV file.""" + + def post(self, request: Request) -> Response: + """Handle CSV upload and validation.""" + form = CSVUploadForm(data=request.data, files=request.FILES) + + if not form.is_valid(): + return Response({"status": "error", "errors": form.errors}, status=status.HTTP_400_BAD_REQUEST) - def form_valid(self, form: CSVUploadForm) -> JsonResponse: - """Handle the CSV validation, store valid data, and prepare for export.""" csv_file = form.cleaned_data["file"] csv_file.seek(0) decoded_file = csv_file.read().decode("utf-8").strip() if not decoded_file: - return JsonResponse({"status": "error", "message": "Uploaded file is empty."}, status=400) + return Response({"status": "error", "message": "Uploaded file is empty."}, + status=status.HTTP_400_BAD_REQUEST) reader = csv.DictReader(io.StringIO(decoded_file)) csv_data: list[dict[str, str]] = list(reader) updated_data = self.update_fields(csv_data) + request.session["validated_csv"] = updated_data + request.session.modified = True - self.request.session["validated_csv"] = updated_data - self.request.session.modified = True - - return JsonResponse({ + return Response({ "status": "success", "message": "File successfully uploaded and processed.", "download_url": reverse_lazy("export-file") - }) - - def form_invalid(self, form: CSVUploadForm) -> JsonResponse: - """Handle the form when it is invalid.""" - return JsonResponse({"status": "error", "errors": form.errors}, status=400) + }, status=status.HTTP_200_OK) @staticmethod def update_fields(csv_data: list[dict[str, str]]) -> list[dict[str, str]]: @@ -91,21 +87,21 @@ class CSVUploadView(FormView): row[f"NominalAnalysisNominalAccountNumber/{repeat}"] = ( xx_data[0] if cc_type == "Project" else xx_data[1] ) - repeat += 1 return csv_data -class CSVExportView(View): - """View for exporting the updated CSV file.""" +class CSVExportAPIView(APIView): + """API view for exporting the updated CSV file.""" - def get(self, request: HttpRequest) -> HttpResponse: - """Generate a downloadable CSV file with updated values.""" + def get(self, request: Request) -> Response: + """Return processed CSV as a downloadable response.""" csv_data: list[dict[str, str]] = request.session.get("validated_csv", []) if not csv_data: - return HttpResponse("No data available for export.", status=400) + return Response({"status": "error", "message": "No data available for export."}, + status=status.HTTP_400_BAD_REQUEST) response = HttpResponse(content_type="text/csv") response["Content-Disposition"] = 'attachment; filename="updated_file.csv"' diff --git a/sage_validation/templates/index.html b/sage_validation/templates/index.html index 7d7376ea866409f6da728b1c6a343fa98b8fb7a4..72db6b8b5598643485a9e64dcfd7d9b10aa1f65b 100644 --- a/sage_validation/templates/index.html +++ b/sage_validation/templates/index.html @@ -8,7 +8,7 @@ <h1 class="text-5xl md:text-6xl font-bold mb-12 text-blue-900">Welcome to Sage Validation</h1> <p class="text-xl md:text-2xl mb-16 text-gray-700">Click the button below to upload your file for validation.</p> - <a href="{% url "upload-file" %}" + <a href="{% url "upload-page" %}" class="inline-flex py-4 px-16 bg-blue-600 text-white text-lg md:text-xl font-bold rounded-full shadow-lg transition-transform transform hover:scale-105 focus:outline-none focus:ring-4 focus:ring-blue-300 focus:ring-opacity-50"> Upload File </a>