diff --git a/hypha/apply/activity/adapters/base.py b/hypha/apply/activity/adapters/base.py index 42d2701744..480bb6727e 100644 --- a/hypha/apply/activity/adapters/base.py +++ b/hypha/apply/activity/adapters/base.py @@ -37,6 +37,7 @@ MESSAGES.REVIEW_REMINDER: "reminder", MESSAGES.BATCH_UPDATE_INVOICE_STATUS: "invoices", MESSAGES.REMOVE_TASK: "task", + MESSAGES.UPDATED_AUTHOR: "old_author", } diff --git a/hypha/apply/activity/adapters/emails.py b/hypha/apply/activity/adapters/emails.py index c6f58ce450..dd151ceac0 100644 --- a/hypha/apply/activity/adapters/emails.py +++ b/hypha/apply/activity/adapters/emails.py @@ -70,6 +70,7 @@ class EmailAdapter(AdapterBase): MESSAGES.REPORT_NOTIFY: "messages/email/report_notify.html", MESSAGES.REVIEW_REMINDER: "messages/email/ready_to_review.html", MESSAGES.PROJECT_TRANSITION: "handle_project_transition", + MESSAGES.UPDATED_AUTHOR: "messages/email/author_updated.html", } def get_subject(self, message_type, source): diff --git a/hypha/apply/activity/adapters/slack.py b/hypha/apply/activity/adapters/slack.py index d2de9ca18f..eff2f17ed2 100644 --- a/hypha/apply/activity/adapters/slack.py +++ b/hypha/apply/activity/adapters/slack.py @@ -137,6 +137,9 @@ class SlackAdapter(AdapterBase): MESSAGES.UNARCHIVE_SUBMISSION: _( "{user} has unarchived the submission: {source.title_text_display}" ), + MESSAGES.UPDATED_AUTHOR: _( + "{user} has updated author from {old_author} to {source.user} for submission <{link}|{source}>" + ), } def __init__(self): diff --git a/hypha/apply/activity/migrations/0087_alter_event_type.py b/hypha/apply/activity/migrations/0087_alter_event_type.py new file mode 100644 index 0000000000..b258596418 --- /dev/null +++ b/hypha/apply/activity/migrations/0087_alter_event_type.py @@ -0,0 +1,83 @@ +# Generated by Django 4.2.18 on 2025-02-06 04:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("activity", "0086_remove_django_messages_adapter"), + ] + + operations = [ + migrations.AlterField( + model_name="event", + name="type", + field=models.CharField( + choices=[ + ("UPDATE_LEAD", "updated lead"), + ("BATCH_UPDATE_LEAD", "batch updated lead"), + ("EDIT_SUBMISSION", "edited submission"), + ("APPLICANT_EDIT", "edited applicant"), + ("NEW_SUBMISSION", "submitted new submission"), + ("DRAFT_SUBMISSION", "submitted new draft submission"), + ("SCREENING", "screened"), + ("TRANSITION", "transitioned"), + ("BATCH_TRANSITION", "batch transitioned"), + ("DETERMINATION_OUTCOME", "sent determination outcome"), + ("BATCH_DETERMINATION_OUTCOME", "sent batch determination outcome"), + ("INVITED_TO_PROPOSAL", "invited to proposal"), + ("REVIEWERS_UPDATED", "updated reviewers"), + ("BATCH_REVIEWERS_UPDATED", "batch updated reviewers"), + ("PARTNERS_UPDATED", "updated partners"), + ("PARTNERS_UPDATED_PARTNER", "partners updated partner"), + ("READY_FOR_REVIEW", "marked ready for review"), + ("BATCH_READY_FOR_REVIEW", "marked batch ready for review"), + ("NEW_REVIEW", "added new review"), + ("COMMENT", "added comment"), + ("PROPOSAL_SUBMITTED", "submitted proposal"), + ("OPENED_SEALED", "opened sealed submission"), + ("REVIEW_OPINION", "reviewed opinion"), + ("DELETE_SUBMISSION", "deleted submission"), + ("DELETE_REVIEW", "deleted review"), + ("DELETE_REVIEW_OPINION", "deleted review opinion"), + ("CREATED_PROJECT", "created project"), + ("UPDATE_PROJECT_LEAD", "updated project lead"), + ("UPDATE_PROJECT_TITLE", "updated project title"), + ("EDIT_REVIEW", "edited review"), + ("SEND_FOR_APPROVAL", "sent for approval"), + ("APPROVE_PROJECT", "approved project"), + ("ASSIGN_PAF_APPROVER", "assign project form approver"), + ("APPROVE_PAF", "approved project form"), + ("PROJECT_TRANSITION", "transitioned project"), + ("REQUEST_PROJECT_CHANGE", "requested project change"), + ("SUBMIT_CONTRACT_DOCUMENTS", "submitted contract documents"), + ("UPLOAD_DOCUMENT", "uploaded document to project"), + ("UPLOAD_CONTRACT", "uploaded contract to project"), + ("APPROVE_CONTRACT", "approved contract"), + ("CREATE_INVOICE", "created invoice for project"), + ("UPDATE_INVOICE_STATUS", "updated invoice status"), + ("APPROVE_INVOICE", "approve invoice"), + ("DELETE_INVOICE", "deleted invoice"), + ("SENT_TO_COMPLIANCE", "sent project to compliance"), + ("UPDATE_INVOICE", "updated invoice"), + ("SUBMIT_REPORT", "submitted report"), + ("SKIPPED_REPORT", "skipped report"), + ("REPORT_FREQUENCY_CHANGED", "changed report frequency"), + ("DISABLED_REPORTING", "disabled reporting"), + ("REPORT_NOTIFY", "notified report"), + ("REVIEW_REMINDER", "reminder to review"), + ("BATCH_DELETE_SUBMISSION", "batch deleted submissions"), + ("BATCH_ARCHIVE_SUBMISSION", "batch archive submissions"), + ("BATCH_INVOICE_STATUS_UPDATE", "batch update invoice status"), + ("STAFF_ACCOUNT_CREATED", "created new account"), + ("STAFF_ACCOUNT_EDITED", "edited account"), + ("ARCHIVE_SUBMISSION", "archived submission"), + ("UNARCHIVE_SUBMISSION", "unarchived submission"), + ("REMOVE_TASK", "remove task"), + ("UPDATED_AUTHOR", "updated author"), + ], + max_length=50, + verbose_name="verb", + ), + ), + ] diff --git a/hypha/apply/activity/options.py b/hypha/apply/activity/options.py index 36b0bc4edc..9c51ad2048 100644 --- a/hypha/apply/activity/options.py +++ b/hypha/apply/activity/options.py @@ -79,3 +79,4 @@ class MESSAGES(TextChoices): ARCHIVE_SUBMISSION = "ARCHIVE_SUBMISSION", _("archived submission") UNARCHIVE_SUBMISSION = "UNARCHIVE_SUBMISSION", _("unarchived submission") REMOVE_TASK = "REMOVE_TASK", _("remove task") + UPDATED_AUTHOR = "UPDATED_AUTHOR", _("updated author") diff --git a/hypha/apply/activity/templates/messages/email/author_updated.html b/hypha/apply/activity/templates/messages/email/author_updated.html new file mode 100644 index 0000000000..cd091b5f83 --- /dev/null +++ b/hypha/apply/activity/templates/messages/email/author_updated.html @@ -0,0 +1,16 @@ +{% extends "messages/email/applicant_base.html" %} + +{% load i18n %} + +{% block content %}{# fmt:off #} +{% blocktrans with title=source.title %}You have assigned as an Applicant to submission "{{ title }}".{% endblocktrans %} +{% endblock %} + +{% block more_info %} +{% trans "Link to your submission" %}: {{ request.scheme }}://{{ request.get_host }}{{ source.get_absolute_url }} +{% trans "If you have any questions, please submit them here" %}: {{ request.scheme }}://{{ request.get_host }}{{ source.get_absolute_url }}#communications + +{% trans "See our guide for more information" %}: {{ ORG_GUIDE_URL }} + +{% blocktrans %}If you have any issues accessing the submission or other general inquiries, please email us at {{ ORG_EMAIL }}{% endblocktrans %} +{% endblock %}{# fmt:on #} diff --git a/hypha/apply/funds/forms.py b/hypha/apply/funds/forms.py index 3e3bf483cd..518a0f39ce 100644 --- a/hypha/apply/funds/forms.py +++ b/hypha/apply/funds/forms.py @@ -324,6 +324,35 @@ def make_role_reviewer_fields(): return role_fields +class UpdateAuthorForm(ApplicationSubmissionModelForm): + author = forms.ModelChoiceField( + queryset=User.objects.applicants(), + label=_("Applicants"), + required=False, + ) + author.widget.attrs.update({"data-placeholder": "Select..."}) + + class Meta: + model = ApplicationSubmission + fields: list = [] + + def __init__(self, *args, **kwargs): + kwargs.pop("user") + super().__init__(*args, **kwargs) + current_author = self.instance.user + + author_field = self.fields["author"] + + # Removed current author from queryset + author_field.queryset = author_field.queryset.exclude(id=current_author.id) + author_field.initial = current_author + + def save(self, *args, **kwargs): + self.instance.user = self.cleaned_data["author"] + self.instance.save() + return self.instance + + class UpdatePartnersForm(ApplicationSubmissionModelForm): partner_reviewers = forms.ModelMultipleChoiceField( queryset=User.objects.partners(), diff --git a/hypha/apply/funds/permissions.py b/hypha/apply/funds/permissions.py index d4075ff74a..0e59936b9e 100644 --- a/hypha/apply/funds/permissions.py +++ b/hypha/apply/funds/permissions.py @@ -193,9 +193,20 @@ def can_view_submission_screening(user, submission): return True, "" +def can_change_submission_author(user, submission): + if not user.is_authenticated: + return False, "Login Required" + + if user.is_apply_staff: + return True, "Staff can update author" + + return False, "Forbidden Error" + + permissions_map = { "submission_view": is_user_has_access_to_view_submission, "submission_edit": can_edit_submission, + "change_author": can_change_submission_author, "can_view_submission_screening": can_view_submission_screening, "archive_alter": can_alter_archived_submissions, } diff --git a/hypha/apply/funds/templates/funds/includes/admin_primary_actions.html b/hypha/apply/funds/templates/funds/includes/admin_primary_actions.html index 7bcbf79797..01fe45cb2b 100644 --- a/hypha/apply/funds/templates/funds/includes/admin_primary_actions.html +++ b/hypha/apply/funds/templates/funds/includes/admin_primary_actions.html @@ -103,6 +103,12 @@