From e6d931db25c7c9c4a83413651f15273134420661 Mon Sep 17 00:00:00 2001 From: sandeepsajan0 Date: Thu, 6 Feb 2025 10:01:40 +0530 Subject: [PATCH 1/2] Add author updated feature to submission --- hypha/apply/activity/adapters/base.py | 1 + hypha/apply/activity/adapters/emails.py | 1 + hypha/apply/activity/adapters/slack.py | 3 + hypha/apply/activity/options.py | 1 + .../messages/email/author_updated.html | 16 +++++ hypha/apply/funds/forms.py | 29 +++++++++ hypha/apply/funds/permissions.py | 11 ++++ .../funds/includes/admin_primary_actions.html | 6 ++ .../funds/modals/update_author_form.html | 37 ++++++++++++ hypha/apply/funds/urls.py | 6 ++ hypha/apply/funds/views/submission_edit.py | 60 +++++++++++++++++++ 11 files changed, 171 insertions(+) create mode 100644 hypha/apply/activity/templates/messages/email/author_updated.html create mode 100644 hypha/apply/funds/templates/funds/modals/update_author_form.html 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/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 @@
{% trans "Actions to take" %}
hx-target="#htmx-modal" >{% trans "Create Reminder" %} + + diff --git a/hypha/apply/funds/templates/funds/modals/update_author_form.html b/hypha/apply/funds/templates/funds/modals/update_author_form.html new file mode 100644 index 0000000000..2ab677c012 --- /dev/null +++ b/hypha/apply/funds/templates/funds/modals/update_author_form.html @@ -0,0 +1,37 @@ +{% load i18n static %} +{% modal_title %}{% trans "Update Author" %}{% endmodal_title %} + +
+
+
+
{% trans "Current Author" %}
+
{{ object.user }} <{{object.user.email}}>
+
+
+ + {% url 'funds:submissions:change_author' pk=object.pk as author_update_url %} + {% include 'funds/includes/dialog_form_base.html' with form=form value=value %} +
+ + + diff --git a/hypha/apply/funds/urls.py b/hypha/apply/funds/urls.py index 7b3bc9344b..14492c43f7 100644 --- a/hypha/apply/funds/urls.py +++ b/hypha/apply/funds/urls.py @@ -50,6 +50,7 @@ CreateProjectView, ProgressSubmissionView, SubmissionEditView, + UpdateAuthorView, UpdateLeadView, UpdateMetaTermsView, UpdatePartnersView, @@ -209,6 +210,11 @@ ReminderCreateView.as_view(), name="create_reminder", ), + path( + "author/change/", + UpdateAuthorView.as_view(), + name="change_author", + ), path( "translate/", TranslateSubmissionView.as_view(), diff --git a/hypha/apply/funds/views/submission_edit.py b/hypha/apply/funds/views/submission_edit.py index ccffe5381e..cabd5c14b4 100644 --- a/hypha/apply/funds/views/submission_edit.py +++ b/hypha/apply/funds/views/submission_edit.py @@ -51,6 +51,7 @@ from .. import services from ..forms import ( ProgressSubmissionForm, + UpdateAuthorForm, UpdateMetaTermsForm, UpdatePartnersForm, UpdateReviewersForm, @@ -641,6 +642,65 @@ def post(self, *args, **kwargs): ) +@method_decorator(staff_required, name="dispatch") +class UpdateAuthorView(View): + model = ApplicationSubmission + form_class = UpdateAuthorForm + context_name = "author_form" + template = "funds/modals/update_author_form.html" + + def dispatch(self, request, *args, **kwargs): + self.submission = get_object_or_404(ApplicationSubmission, id=kwargs.get("pk")) + permission, reason = has_permission( + "change_author", + request.user, + object=self.submission, + raise_exception=False, + ) + if not permission: + messages.warning(self.request, reason) + return HttpResponseRedirect(self.submission.get_absolute_url()) + return super(UpdateAuthorView, self).dispatch(request, *args, **kwargs) + + def get(self, *args, **kwargs): + author_form = self.form_class(user=self.request.user, instance=self.submission) + return render( + self.request, + self.template, + context={ + "form": author_form, + "value": _("Update"), + "object": self.submission, + }, + ) + + def post(self, *args, **kwargs): + form = self.form_class( + self.request.POST, user=self.request.user, instance=self.submission + ) + old_author = self.submission.user + if form.is_valid(): + form.save() + + messenger( + MESSAGES.UPDATED_AUTHOR, + request=self.request, + user=self.request.user, + source=self.submission, + related=old_author, + ) + + messages.success(self.request, "Author updated successfully") + return HttpResponseClientRefresh() + + return render( + self.request, + self.template, + context={"form": form, "value": _("Update"), "object": self.submission}, + status=400, + ) + + @method_decorator(staff_required, name="dispatch") class UpdatePartnersView(View): model = ApplicationSubmission From 319770dda2084b9d87dbedef30a400fd8182ede9 Mon Sep 17 00:00:00 2001 From: sandeepsajan0 Date: Thu, 6 Feb 2025 10:08:51 +0530 Subject: [PATCH 2/2] Add migration for Updated author notification --- .../migrations/0087_alter_event_type.py | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 hypha/apply/activity/migrations/0087_alter_event_type.py 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", + ), + ), + ]