From 69754e2d73912751fdfc838f0ee89edbd6c485da Mon Sep 17 00:00:00 2001 From: Fredrik Jonsson Date: Mon, 27 Jul 2026 08:14:46 +0200 Subject: [PATCH 1/7] Set correct timestamps on co applicant invites. --- hypha/apply/funds/views/co_applicants.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hypha/apply/funds/views/co_applicants.py b/hypha/apply/funds/views/co_applicants.py index cbd77c6641..2f94af37a2 100644 --- a/hypha/apply/funds/views/co_applicants.py +++ b/hypha/apply/funds/views/co_applicants.py @@ -1,4 +1,3 @@ -import datetime import json from django.conf import settings @@ -154,7 +153,7 @@ def post(self, args, **kwargs): action = self.request.POST.get("action") if action == "accept": self.invite.status = CoApplicantInviteStatus.ACCEPTED - self.invite.responded_on = datetime.datetime.now() + self.invite.responded_on = timezone.now() self.invite.save(update_fields=["status", "responded_on"]) # handle auto login/signup @@ -195,7 +194,7 @@ def post(self, args, **kwargs): login(self.request, user) return HttpResponseClientRedirect(self.get_success_url()) self.invite.status = CoApplicantInviteStatus.REJECTED - self.invite.responded_on = datetime.datetime.now() + self.invite.responded_on = timezone.now() self.invite.save(update_fields=["status", "responded_on"]) if self.request.user.is_authenticated: return HttpResponseClientRedirect(reverse_lazy("dashboard:dashboard")) From 8d926b53bf512a71b1397642108313d12171b078 Mon Sep 17 00:00:00 2001 From: Fredrik Jonsson Date: Mon, 27 Jul 2026 08:15:09 +0200 Subject: [PATCH 2/7] Submission diff cleanup fix. --- hypha/apply/funds/views/revisions.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hypha/apply/funds/views/revisions.py b/hypha/apply/funds/views/revisions.py index 0c316d9524..785349eb5e 100644 --- a/hypha/apply/funds/views/revisions.py +++ b/hypha/apply/funds/views/revisions.py @@ -174,8 +174,7 @@ def cleanse_stream_fields(self, a_field, b_field) -> List[str]: sanitized_answers.append(f"{heading}{answer}") except AttributeError: - # If it fails to match for some reason just cleanse the fields but leave h2s - answer = nh3.clean(answer, attributes={}, tags={"h2"}) - sanitized_answers.append(field) + # If it fails to match for some reason just cleanse the field but leave h2s + sanitized_answers.append(nh3.clean(field, attributes={}, tags={"h2"})) return sanitized_answers From c4255f6f48398de7c43a7cd4a070be9a4b6d38ea Mon Sep 17 00:00:00 2001 From: Fredrik Jonsson Date: Mon, 27 Jul 2026 08:51:37 +0200 Subject: [PATCH 3/7] Fix timestamp in get_copied_form_name. --- hypha/apply/funds/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hypha/apply/funds/utils.py b/hypha/apply/funds/utils.py index a154b4421d..2a07213263 100644 --- a/hypha/apply/funds/utils.py +++ b/hypha/apply/funds/utils.py @@ -1,13 +1,13 @@ import csv import os import re -from datetime import datetime from io import StringIO from itertools import chain from typing import Iterable from django.core.files.storage import default_storage from django.urls import reverse +from django.utils import timezone from django.utils.encoding import force_bytes from django.utils.html import strip_tags from django.utils.http import urlsafe_base64_encode @@ -162,7 +162,7 @@ def get_copied_form_name(original_form_name: str) -> str: str: name of the copied form """ copy_str = _("Copied on {copy_time}") - copy_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-4] + copy_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-4] date_reg = r"(\d{2,4}-?){3} (\d{2}(:|.)?){4}" # match the strftime pattern of %Y-%m-%d %H:%M:%S.%f # Escape the `copy_str` to allow for translations to be matched & replace the From 4932fc967edbf27d0e8170de8a26164934e64ef1 Mon Sep 17 00:00:00 2001 From: Fredrik Jonsson Date: Mon, 27 Jul 2026 08:57:16 +0200 Subject: [PATCH 4/7] Add co-applicant respond function to dedupe code. --- hypha/apply/funds/models/co_applicants.py | 6 ++++++ hypha/apply/funds/views/co_applicants.py | 8 ++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/hypha/apply/funds/models/co_applicants.py b/hypha/apply/funds/models/co_applicants.py index c0cd94575c..504e2eac81 100644 --- a/hypha/apply/funds/models/co_applicants.py +++ b/hypha/apply/funds/models/co_applicants.py @@ -1,4 +1,5 @@ from django.db import models +from django.utils import timezone from django.utils.translation import gettext_lazy as _ from hypha.apply.users.models import User @@ -60,6 +61,11 @@ class Meta: def __str__(self): return f"{self.invited_user_email} invited to {self.submission})" + def respond(self, status): + self.status = status + self.responded_on = timezone.now() + self.save(update_fields=["status", "responded_on"]) + class CoApplicant(models.Model): submission = models.ForeignKey( diff --git a/hypha/apply/funds/views/co_applicants.py b/hypha/apply/funds/views/co_applicants.py index 2f94af37a2..960464f8eb 100644 --- a/hypha/apply/funds/views/co_applicants.py +++ b/hypha/apply/funds/views/co_applicants.py @@ -152,9 +152,7 @@ def get(self, *args, **kwargs): def post(self, args, **kwargs): action = self.request.POST.get("action") if action == "accept": - self.invite.status = CoApplicantInviteStatus.ACCEPTED - self.invite.responded_on = timezone.now() - self.invite.save(update_fields=["status", "responded_on"]) + self.invite.respond(CoApplicantInviteStatus.ACCEPTED) # handle auto login/signup user, created = User.objects.get_or_create( @@ -193,9 +191,7 @@ def post(self, args, **kwargs): login(self.request, user) return HttpResponseClientRedirect(self.get_success_url()) - self.invite.status = CoApplicantInviteStatus.REJECTED - self.invite.responded_on = timezone.now() - self.invite.save(update_fields=["status", "responded_on"]) + self.invite.respond(CoApplicantInviteStatus.REJECTED) if self.request.user.is_authenticated: return HttpResponseClientRedirect(reverse_lazy("dashboard:dashboard")) return HttpResponseClientRedirect("/") From bf615b968c46bce447a9c3940e139764b5e871c3 Mon Sep 17 00:00:00 2001 From: Fredrik Jonsson Date: Mon, 27 Jul 2026 09:12:53 +0200 Subject: [PATCH 5/7] Check that project has report_config in notify_report_due. --- .../projects/reports/management/commands/notify_report_due.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hypha/apply/projects/reports/management/commands/notify_report_due.py b/hypha/apply/projects/reports/management/commands/notify_report_due.py index 41b3786580..8ce99bb7fe 100644 --- a/hypha/apply/projects/reports/management/commands/notify_report_due.py +++ b/hypha/apply/projects/reports/management/commands/notify_report_due.py @@ -46,6 +46,9 @@ def handle(self, *args, **options): due_date = today + relativedelta(days=delta) for project in Project.objects.in_progress(): + # Check that project has report_config. + if not hasattr(project, "report_config"): + continue next_report = project.report_config.ensure_due_report() if not next_report: continue From 1fa8a202102954b2ad8dcd103c4e7180ff182079 Mon Sep 17 00:00:00 2001 From: Fredrik Jonsson Date: Mon, 27 Jul 2026 09:17:06 +0200 Subject: [PATCH 6/7] Fix check in can_user_delete, string not tuple. --- hypha/apply/projects/models/payment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hypha/apply/projects/models/payment.py b/hypha/apply/projects/models/payment.py index 738ee886b8..949ce36f8c 100644 --- a/hypha/apply/projects/models/payment.py +++ b/hypha/apply/projects/models/payment.py @@ -201,7 +201,7 @@ def can_user_delete(self, user): CoApplicantRole, ) - if self.status in (SUBMITTED): + if self.status == SUBMITTED: if user.is_apply_staff: return True if user.is_applicant: From 5f22997aca87894f361c1fe46f0833ac65b34eb5 Mon Sep 17 00:00:00 2001 From: Fredrik Jonsson Date: Wed, 29 Jul 2026 21:54:00 +0200 Subject: [PATCH 7/7] Optimise check that project has report_config in notify_report_due. --- .../reports/management/commands/notify_report_due.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hypha/apply/projects/reports/management/commands/notify_report_due.py b/hypha/apply/projects/reports/management/commands/notify_report_due.py index 8ce99bb7fe..80bac8c06f 100644 --- a/hypha/apply/projects/reports/management/commands/notify_report_due.py +++ b/hypha/apply/projects/reports/management/commands/notify_report_due.py @@ -45,10 +45,10 @@ def handle(self, *args, **options): delta = frequency.reminder_days * multiplier due_date = today + relativedelta(days=delta) - for project in Project.objects.in_progress(): - # Check that project has report_config. - if not hasattr(project, "report_config"): - continue + # Make sure that project has report_config. + for project in Project.objects.in_progress().filter( + report_config__isnull=False + ): next_report = project.report_config.ensure_due_report() if not next_report: continue