diff --git a/backend/audit/forms.py b/backend/audit/forms.py index bfdd6073f4..5967f5feac 100644 --- a/backend/audit/forms.py +++ b/backend/audit/forms.py @@ -22,6 +22,35 @@ class UploadReportForm(forms.Form): schedule_prior_findings = forms.IntegerField(initial=0, required=False, min_value=1) CAP_page = forms.IntegerField(initial=0, required=False, min_value=1) upload_report = forms.FileField() + keep_previous_report = forms.BooleanField(required=False) + + def clean(self): + """ + For 'Original' submissions, these fields do not really need cleaning - they're integers, so the default errors will do. + The view will handle erroneous report uploads. + + For resubmissions, a user might upload an erroneous report and then indicate to keep their previous report. + Then, we want to clear all errors. + """ + cleaned_data = super().clean() + if cleaned_data.get("keep_previous_report"): + for field in [ + "financial_statements", + "financial_statements_opinion", + "schedule_expenditures", + "schedule_expenditures_opinion", + "uniform_guidance_control", + "uniform_guidance_compliance", + "GAS_control", + "GAS_compliance", + "schedule_findings", + "schedule_prior_findings", + "CAP_page", + "upload_report", + ]: + if field in self.errors: + del self.errors[field] + return cleaned_data def _kvpair(info): diff --git a/backend/audit/templates/audit/upload-report.html b/backend/audit/templates/audit/upload-report.html index 6d7e11d50e..843ed3540b 100644 --- a/backend/audit/templates/audit/upload-report.html +++ b/backend/audit/templates/audit/upload-report.html @@ -96,6 +96,22 @@

{% if already_submitted %}Re-upload{% else

{% endif %} + {% if is_resubmission %} +
+ + +
+ {% endif %} + + {% if form.non_field_errors %} + {{ form.non_field_errors|striptags }} + {% endif %} + diff --git a/backend/audit/test_viewlib/__init__.py b/backend/audit/test_viewlib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/audit/test_viewlib/test_resubmission_start_view.py b/backend/audit/test_viewlib/test_resubmission_start_view.py index f331e19792..0cd3ac1843 100644 --- a/backend/audit/test_viewlib/test_resubmission_start_view.py +++ b/backend/audit/test_viewlib/test_resubmission_start_view.py @@ -6,6 +6,7 @@ from audit.models import SingleAuditChecklist from audit.models.constants import STATUS +from dissemination.models import General User = get_user_model() @@ -16,20 +17,40 @@ class ResubmissionStartViewTests(TestCase): invalid_report_id = "NOT-LONG-ENOUGH" nonexistent_report_id = "LONGENOUGHBUTDOESNOTEXIST" valid_report_id = "0123-01-SOURCE-0123456789" + valid_sibling_report_id = "3210-10-SOURCE-9876543210" + general_information = { + "auditee_uei": "auditee_uei", + "auditee_name": "auditee_name", + "auditee_fiscal_period_end": "2022-01-01", + } # Recreated per test def setUp(self): - """Setup user and client.""" + """Setup prerequisite fake submissions, then add a user and client.""" self.valid_sac = baker.make( SingleAuditChecklist, report_id=self.valid_report_id, submission_status=STATUS.DISSEMINATED, + general_information=self.general_information, ) + self.sibling_sac = baker.make( + SingleAuditChecklist, + report_id=self.valid_sibling_report_id, + submission_status=STATUS.DISSEMINATED, + general_information=self.general_information, + ) + self.sibling_general = baker.make( + General, + report_id=self.valid_sibling_report_id, + audit_year="2022", + auditee_uei="auditee_uei", + ) + self.user = baker.make(User) self.client = Client() def test_redirect_if_not_logged_in(self): - """Test that accessing resubmission start page redirects if the user is not logged in""" + """Test that accessing resubmission start page redirects if the user is not logged in.""" response = self.client.get(self.path_name) self.assertAlmostEqual(response.status_code, 302) @@ -62,4 +83,8 @@ def test_valid_report_id(self): self.client.force_login(user=self.user) response = self.client.post(self.path_name, {"report_id": self.valid_report_id}) - self.assertRedirects(response, reverse("report_submission:eligibility")) + self.assertRedirects( + response, + reverse("report_submission:eligibility"), + fetch_redirect_response=False, + ) diff --git a/backend/audit/test_viewlib/test_upload_report_view.py b/backend/audit/test_viewlib/test_upload_report_view.py new file mode 100644 index 0000000000..b89d86dc41 --- /dev/null +++ b/backend/audit/test_viewlib/test_upload_report_view.py @@ -0,0 +1,221 @@ +from unittest.mock import patch + +from audit.models import ( + Access, + SingleAuditReportFile, + SubmissionEvent, +) +from audit.models.constants import SAC_SEQUENCE_ID +from audit.models.utils import get_next_sequence_id +from audit.test_views import ( + _mock_gen_report_id, + _mock_login_and_scan, + _make_user_and_sac, +) +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.urls import reverse +from model_bakery import baker + +User = get_user_model() + + +class SingleAuditReportFileHandlerViewTests(TestCase): + valid_page_numbers = { + "financial_statements": 1, + "financial_statements_opinion": 2, + "schedule_expenditures": 3, + "schedule_expenditures_opinion": 4, + "uniform_guidance_control": 5, + "uniform_guidance_compliance": 6, + "GAS_control": 7, + "GAS_compliance": 8, + "schedule_findings": 9, + } + + def test_login_required(self): + """When an unauthenticated request is made.""" + + response = self.client.post( + reverse( + "audit:SingleAuditReport", + kwargs={"report_id": "12345"}, + ) + ) + + self.assertTemplateUsed(response, "home.html") + self.assertTrue(response.context["session_expired"]) + + def test_bad_report_id_returns_403(self): + """When a request is made for a malformed or nonexistent report_id, a 403 error should be returned.""" + user = baker.make(User) + + self.client.force_login(user) + + response = self.client.post( + reverse( + "audit:SingleAuditReport", + kwargs={ + "report_id": "this is not a report id", + }, + ) + ) + + self.assertEqual(response.status_code, 403) + + def test_inaccessible_audit_returns_403(self): + """When a request is made for an audit that is inaccessible for this user, a 403 error should be returned.""" + user, sac = _make_user_and_sac() + + self.client.force_login(user) + response = self.client.post( + reverse( + "audit:SingleAuditReport", + kwargs={"report_id": sac.report_id}, + ) + ) + + self.assertEqual(response.status_code, 403) + + def test_no_file_attached_returns_400(self): + """When a request is made with no file attached, a 400 error should be returned.""" + user, sac = _make_user_and_sac() + baker.make(Access, user=user, sac=sac) + + self.client.force_login(user) + + response = self.client.post( + reverse( + "audit:SingleAuditReport", + kwargs={"report_id": sac.report_id}, + ) + ) + + self.assertEqual(response.status_code, 400) + + @patch("audit.validators._scan_file") + def test_valid_file_upload(self, mock_scan_file): + """Test that uploading a valid SAR update the SAC accordingly.""" + sequence = get_next_sequence_id(SAC_SEQUENCE_ID) + sac = _mock_login_and_scan( + self.client, + mock_scan_file, + id=sequence, + report_id=_mock_gen_report_id(sequence), + ) + + with open("audit/fixtures/basic.pdf", "rb") as pdf_file: + response = self.client.post( + reverse( + "audit:SingleAuditReport", + kwargs={ + "report_id": sac.report_id, + }, + ), + data={"FILES": pdf_file}, + ) + + self.assertEqual(response.status_code, 302) + + submission_events = SubmissionEvent.objects.filter(sac=sac) + + # the most recent event should be AUDIT_REPORT_PDF_UPDATED + event_count = len(submission_events) + self.assertGreaterEqual(event_count, 1) + self.assertEqual( + submission_events[event_count - 1].event, + SubmissionEvent.EventType.AUDIT_REPORT_PDF_UPDATED, + ) + + @patch("audit.validators._scan_file") + def test_new_report_upload_via_upload_report_view(self, mock_scan_file): + """ + When a user uploads a new report, `keep_previous_report` is either False or absent. + Ensure the view redirects the user. Then pull down the SAR and spot check the data and file. + """ + sequence = get_next_sequence_id(SAC_SEQUENCE_ID) + sac = _mock_login_and_scan( + self.client, + mock_scan_file, + id=sequence, + report_id=_mock_gen_report_id(sequence), + ) + + with open("audit/fixtures/basic.pdf", "rb") as pdf_file: + response = self.client.post( + reverse( + "audit:UploadReport", + kwargs={"report_id": sac.report_id}, + ), + data={**self.valid_page_numbers, "upload_report": pdf_file}, + ) + + self.assertEqual(response.status_code, 302) + + sar = SingleAuditReportFile.objects.filter(sac=sac).latest("date_created") + self.assertIsNotNone(sar) + self.assertEqual( + sar.component_page_numbers["schedule_findings"], + self.valid_page_numbers["schedule_findings"], + ) + + @patch("audit.views.upload_report_view.copy_file") + @patch("audit.validators._scan_file") + def test_keep_previous_report_copies_data(self, mock_scan_file, mock_copy_file): + """ + When a user opts to copy their report during a resubmission, `keep_previous_report` is either True. + Create a previous report. Ensure the view redirects the user. Ensure `copy_file` was run with the right params. Spot check the data and file. + """ + # Create the necessary "previous" report data. + prev_sequence = get_next_sequence_id(SAC_SEQUENCE_ID) + prev_sac = _mock_login_and_scan( + self.client, + mock_scan_file, + id=prev_sequence, + report_id=_mock_gen_report_id(prev_sequence), + ) + + baker.make( + SingleAuditReportFile, + sac=prev_sac, + file=f"singleauditreport/{prev_sac.report_id}.pdf", + filename=f"{prev_sac.report_id}.pdf", + component_page_numbers=self.valid_page_numbers, + ) + + # Create the necessary "current" report data, link it to the "previous". + current_sequence = get_next_sequence_id(SAC_SEQUENCE_ID) + current_sac = _mock_login_and_scan( + self.client, + mock_scan_file, + id=current_sequence, + report_id=_mock_gen_report_id(current_sequence), + resubmission_meta={"previous_report_id": prev_sac.report_id}, + ) + + # POST with keep_previous_report checked + response = self.client.post( + reverse( + "audit:UploadReport", + kwargs={"report_id": current_sac.report_id}, + ), + data={"keep_previous_report": True}, + ) + + self.assertEqual(response.status_code, 302) + + # Ensure the copy function was called. + mock_copy_file.assert_called_once_with( + f"singleauditreport/{prev_sac.report_id}.pdf", + f"singleauditreport/{current_sac.report_id}.pdf", + ) + + # Spot check the "new" file. + new_sar = SingleAuditReportFile.objects.filter(sac=current_sac).latest( + "date_created" + ) + self.assertIsNotNone(new_sar) + self.assertEqual( + new_sar.component_page_numbers["schedule_findings"], + self.valid_page_numbers["schedule_findings"], + ) diff --git a/backend/audit/test_views.py b/backend/audit/test_views.py index 9760388e9c..5bea764380 100644 --- a/backend/audit/test_views.py +++ b/backend/audit/test_views.py @@ -1624,102 +1624,6 @@ def test_excel_file_not_saved_on_validation_failure(self): ) self.assertFalse(ExcelFile.objects.exists()) - -class SingleAuditReportFileHandlerViewTests(TestCase): - def test_login_required(self): - """When an unauthenticated request is made""" - - response = self.client.post( - reverse( - "audit:SingleAuditReport", - kwargs={"report_id": "12345"}, - ) - ) - - self.assertTemplateUsed(response, "home.html") - self.assertTrue(response.context["session_expired"]) - - def test_bad_report_id_returns_403(self): - """When a request is made for a malformed or nonexistent report_id, a 403 error should be returned""" - user = baker.make(User) - - self.client.force_login(user) - - response = self.client.post( - reverse( - "audit:SingleAuditReport", - kwargs={ - "report_id": "this is not a report id", - }, - ) - ) - - self.assertEqual(response.status_code, 403) - - def test_inaccessible_audit_returns_403(self): - """When a request is made for an audit that is inaccessible for this user, a 403 error should be returned""" - user, sac = _make_user_and_sac() - - self.client.force_login(user) - response = self.client.post( - reverse( - "audit:SingleAuditReport", - kwargs={"report_id": sac.report_id}, - ) - ) - - self.assertEqual(response.status_code, 403) - - def test_no_file_attached_returns_400(self): - """When a request is made with no file attached, a 400 error should be returned""" - user, sac = _make_user_and_sac() - baker.make(Access, user=user, sac=sac) - - self.client.force_login(user) - - response = self.client.post( - reverse( - "audit:SingleAuditReport", - kwargs={"report_id": sac.report_id}, - ) - ) - - self.assertEqual(response.status_code, 400) - - @patch("audit.validators._scan_file") - def test_valid_file_upload(self, mock_scan_file): - """Test that uploading a valid SAR update the SAC accordingly""" - sequence = get_next_sequence_id(SAC_SEQUENCE_ID) - sac = _mock_login_and_scan( - self.client, - mock_scan_file, - id=sequence, - report_id=_mock_gen_report_id(sequence), - ) - - with open("audit/fixtures/basic.pdf", "rb") as pdf_file: - response = self.client.post( - reverse( - "audit:SingleAuditReport", - kwargs={ - "report_id": sac.report_id, - }, - ), - data={"FILES": pdf_file}, - ) - - self.assertEqual(response.status_code, 302) - - submission_events = SubmissionEvent.objects.filter(sac=sac) - - # the most recent event should be AUDIT_REPORT_PDF_UPDATED - event_count = len(submission_events) - self.assertGreaterEqual(event_count, 1) - self.assertEqual( - submission_events[event_count - 1].event, - SubmissionEvent.EventType.AUDIT_REPORT_PDF_UPDATED, - ) - @patch("audit.validators._scan_file") def test_valid_file_upload_for_additional_ueis(self, mock_scan_file): """When a valid Excel file is uploaded, the file should be stored and the SingleAuditChecklist should be updated to include the uploaded Additional UEIs data""" diff --git a/backend/audit/views/upload_report_view.py b/backend/audit/views/upload_report_view.py index 5e29276f23..8dd38adb2e 100644 --- a/backend/audit/views/upload_report_view.py +++ b/backend/audit/views/upload_report_view.py @@ -1,10 +1,13 @@ import logging from django.core.exceptions import BadRequest, PermissionDenied, ValidationError +from django.core.files.uploadedfile import UploadedFile +from django.http import HttpRequest, HttpResponse from django.shortcuts import render, redirect from django.views import generic from django.urls import reverse +from audit.forms import UploadReportForm from audit.mixins import ( SingleAuditChecklistAccessRequiredMixin, ) @@ -14,8 +17,8 @@ SingleAuditReportFile, Audit, ) -from audit.forms import UploadReportForm from audit.models.constants import EventType +from dissemination.file_downloads import copy_file logging.basicConfig( format="%(asctime)s %(levelname)-8s %(module)s:%(lineno)d %(message)s" @@ -39,7 +42,7 @@ def __init__(self, text="", id="", required=True, hint=None): class UploadReportView(SingleAuditChecklistAccessRequiredMixin, generic.View): - def page_number_inputs(self): + def page_number_inputs(self) -> list[PageInput]: """ Build the input elements to be passed to the context for use in audit/templates/audit/upload-report.html @@ -97,11 +100,16 @@ def get(self, request, *args, **kwargs): sar = SingleAuditReportFile.objects.filter(sac_id=sac.id) if sar.exists(): sar = sar.latest("date_created") - current_info = { "cleaned_data": getattr(sar, "component_page_numbers", {}), } + previous_report_id = ( + sac.resubmission_meta.get("previous_report_id") + if sac.resubmission_meta + else None + ) + context = { "auditee_name": sac.auditee_name, "report_id": report_id, @@ -109,6 +117,7 @@ def get(self, request, *args, **kwargs): "user_provided_organization_type": sac.user_provided_organization_type, "page_number_inputs": self.page_number_inputs(), "already_submitted": True if sar else False, + "is_resubmission": True if previous_report_id else False, "form": current_info, } @@ -125,11 +134,14 @@ def post(self, request, *args, **kwargs): try: # TODO SOT: Switch to `audit` sac = SingleAuditChecklist.objects.get(report_id=report_id) - # In the transition (SOT1.5), the audit MUST exist at this point, - # becuase it was created with the SAC. So, we should at least get *something*. - audit = Audit.objects.find_audit_or_none(report_id) form = UploadReportForm(request.POST, request.FILES) + previous_report_id = ( + sac.resubmission_meta.get("previous_report_id") + if sac.resubmission_meta + else None + ) + # Standard context always needed on this page context = { "auditee_name": sac.auditee_name, @@ -137,59 +149,103 @@ def post(self, request, *args, **kwargs): "auditee_uei": sac.auditee_uei, "user_provided_organization_type": sac.user_provided_organization_type, "page_number_inputs": self.page_number_inputs(), + "is_resubmission": bool(previous_report_id), } - if form.is_valid(): - file = request.FILES["upload_report"] - # SOT TODO: The audit.id, per comment above, MUST exist at this point. - # Pass the audit ID if we have one. Otherwise, None is valid. Revert to just `audit.id` after TODO. - sar_file = self.reformat_form_data( - file, form, sac.id, audit.id if audit else None + # Find form errors and return if any exist, then EITHER: + # 1. For resubmissions that opt in, copy the previous report. + # 2. For original or updated resubmissions, validate and store as normal. + if not form.is_valid(): + return render( + request, "audit/upload-report.html", context | {"form": form} ) - # Try to save the formatted form data. If it fails on the file - # (encryption issues, file size issues), add and pass back the file errors. - # If it fails due to something else, re-raise it to be handled further below. - try: - sar_file.full_clean() - sar_file.save( - event_user=request.user, - event_type=EventType.AUDIT_REPORT_PDF_UPDATED, - ) - - self._save_audit( - report_id=report_id, sar_file=sar_file, request=request - ) - except ValidationError as err: - for issue in err.error_dict.get("file"): - form.add_error("upload_report", issue) - return render( - request, "audit/upload-report.html", context | {"form": form} - ) - except Exception as err: - raise err - - # Form data saved, redirect to checklist. - return redirect(reverse("audit:SubmissionProgress", args=[report_id])) - - # form.is_valid() failed (standard Django issues). Show the errors. - return render(request, "audit/upload-report.html", context | {"form": form}) + if form.cleaned_data.get("keep_previous_report") and previous_report_id: + return self._handle_keep_previous_report( + request, report_id, previous_report_id, form, context + ) + + return self._handle_new_report(request, report_id, form, context) except SingleAuditChecklist.DoesNotExist as err: raise PermissionDenied("You do not have access to this audit.") from err except LateChangeError: return render(request, "audit/no-late-changes.html") - except Exception as err: logger.error("Unexpected error in UploadReportView post:\n %s", err) raise BadRequest() from err - def reformat_form_data(self, file, form, sac_id, audit_id): + def _handle_keep_previous_report( + self, + request: HttpRequest, + report_id: str, + previous_report_id: str, + form: UploadReportForm, + context: dict, + ) -> HttpResponse: + """ + Copy the previous submission's SingleAuditReportFile and PDF to the current resubmission. + """ + sac = SingleAuditChecklist.objects.get(report_id=report_id) + audit = Audit.objects.find_audit_or_none(report_id) + + try: + self.copy_previous_report_data( + previous_report_id=previous_report_id, + current_sac=sac, + current_audit=audit, + request=request, + ) + except Exception as err: + logger.error("Unexpected error copying a SingleAuditReportFile: {err}") + form.add_error(None, f"Unable to copy the previous report: {err}") + return render(request, "audit/upload-report.html", context | {"form": form}) + + return redirect(reverse("audit:SubmissionProgress", args=[report_id])) + + def _handle_new_report( + self, + request: HttpRequest, + report_id: str, + form: UploadReportForm, + context: dict, + ) -> HttpResponse: + """ + Validate and store a newly uploaded report. + """ + sac = SingleAuditChecklist.objects.get(report_id=report_id) + audit = Audit.objects.find_audit_or_none(report_id) + + file = request.FILES["upload_report"] + sar_file = self.reformat_form_data( + file, form, sac.id, audit.id if audit else None + ) + + try: + sar_file.full_clean() + sar_file.save( + event_user=request.user, + event_type=EventType.AUDIT_REPORT_PDF_UPDATED, + ) + self._save_audit(report_id=report_id, sar_file=sar_file, request=request) + except ValidationError as err: + for issue in err.error_dict.get("file"): + form.add_error("upload_report", issue) + return render(request, "audit/upload-report.html", context | {"form": form}) + + return redirect(reverse("audit:SubmissionProgress", args=[report_id])) + + def reformat_form_data( + self, + file: UploadedFile, + form: UploadReportForm, + sac_id: int, + audit_id: int | None, + ) -> SingleAuditReportFile: """ Given the file, form, and report_id, return the formatted SingleAuditReportFile. Maps cleaned form data into an object to be passed alongside the file, filename, and report id. """ - component_page_numbers = { "financial_statements": form.cleaned_data["financial_statements"], "financial_statements_opinion": form.cleaned_data[ @@ -222,8 +278,52 @@ def reformat_form_data(self, file, form, sac_id, audit_id): ) return sar_file + def copy_previous_report_data( + self, + previous_report_id: str, + current_sac: SingleAuditChecklist, + current_audit: Audit | None, + request: HttpRequest, + ) -> None: + """ + Copy the SingleAuditReportFile and the associated s3 object from the + previous submission to the current resubmission. + """ + previous_sac = SingleAuditChecklist.objects.get(report_id=previous_report_id) + previous_sar = SingleAuditReportFile.objects.filter(sac=previous_sac).latest( + "date_created" + ) + + # Copy the S3 object + source_key = f"singleauditreport/{previous_sac.report_id}.pdf" + dest_key = f"singleauditreport/{current_sac.report_id}.pdf" + copy_file(source_key, dest_key) + + # Copy the SingleAuditReportFile row + new_sar = SingleAuditReportFile( + file=dest_key, + filename=f"{current_sac.report_id}.pdf", + sac=current_sac, + audit=current_audit, + component_page_numbers=previous_sar.component_page_numbers, + ) + new_sar.save( + event_user=request.user, + event_type=EventType.AUDIT_REPORT_PDF_UPDATED, + ) + + # Mirror onto the Audit model if it exists + if current_audit: + self._save_audit( + report_id=current_sac.report_id, sar_file=new_sar, request=request + ) + @staticmethod - def _save_audit(report_id, sar_file, request): + def _save_audit( + report_id: str, + sar_file: SingleAuditReportFile, + request: HttpRequest, + ) -> None: # TODO: Update Post SOC Launch : Delete and move done for linting complexity audit = Audit.objects.find_audit_or_none(report_id=report_id) if audit: diff --git a/backend/dissemination/file_downloads.py b/backend/dissemination/file_downloads.py index 9f2c65028e..d8d33c7c0c 100644 --- a/backend/dissemination/file_downloads.py +++ b/backend/dissemination/file_downloads.py @@ -91,7 +91,7 @@ def file_exists(filename, show_warning=True): return True except ClientError: if show_warning: - logger.warn(f"Unable to locate file {filename} in S3!") + logger.warning(f"Unable to locate file {filename} in S3!") return False @@ -125,3 +125,33 @@ def get_download_url(filename): raise Http404("File not found") except ClientError: raise Http404("File not found") + + +def copy_file(source_key, dest_key): + """ + Copy a file within the private S3 bucket from source_key to dest_key. + Returns True on success, raises Http404 if the source does not exist. + """ + if not file_exists(source_key): + raise Http404(f"Unable to locate file {source_key} in S3!") + + s3_client = boto3_client( + service_name="s3", + region_name=settings.AWS_S3_PRIVATE_REGION_NAME, + aws_access_key_id=settings.AWS_PRIVATE_ACCESS_KEY_ID, + aws_secret_access_key=settings.AWS_PRIVATE_SECRET_ACCESS_KEY, + endpoint_url=settings.AWS_S3_PRIVATE_INTERNAL_ENDPOINT, + config=Config(signature_version="s3v4"), + ) + + source_bucket = settings.AWS_PRIVATE_STORAGE_BUCKET_NAME + try: + s3_client.copy_object( + Bucket=source_bucket, + Key=dest_key, + CopySource={"Bucket": source_bucket, "Key": source_key}, + ) + return True + except ClientError as err: + logger.error(f"Failed to copy S3 object {source_key} -> {dest_key}: {err}") + raise Http404("Failed to copy file") from err diff --git a/backend/dissemination/test_file_downloads.py b/backend/dissemination/test_file_downloads.py index 419dab9654..9451f99736 100644 --- a/backend/dissemination/test_file_downloads.py +++ b/backend/dissemination/test_file_downloads.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timezone from django.conf import settings from django.http import Http404 @@ -23,7 +23,7 @@ def setUp(self): pass def _report_id(self, sequence, source): - today = datetime.utcnow().date().isoformat() + today = datetime.now(timezone.utc).date().isoformat() return generate_sac_report_id(sequence=sequence, end_date=today, source=source) def test_gsafac_no_singleauditchecklist(self): diff --git a/backend/static/js/upload-report.js b/backend/static/js/upload-report.js index 1575485075..fb6dbbac73 100644 --- a/backend/static/js/upload-report.js +++ b/backend/static/js/upload-report.js @@ -1,20 +1,48 @@ var FORM = document.getElementById('upload-report__form'); -const continue_button = document.getElementById(`continue`); //