From e02c5cff9acb4a8fe2f6684e6baebd4344849b15 Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Date: Fri, 27 Dec 2024 19:48:46 +0530 Subject: [PATCH] Refractor fund views --- hypha/apply/funds/services.py | 30 - hypha/apply/funds/tests/test_views.py | 2 +- hypha/apply/funds/urls.py | 49 +- hypha/apply/funds/views.py | 1665 ----------------- hypha/apply/funds/views/__init__.py | 75 + .../funds/{views_all.py => views/all.py} | 6 +- .../{views_partials.py => views/partials.py} | 112 +- hypha/apply/funds/views/reminders.py | 120 ++ hypha/apply/funds/views/results.py | 123 ++ .../apply/funds/views/reviewer_leaderboard.py | 92 + hypha/apply/funds/views/revisions.py | 104 + hypha/apply/funds/views/staff_assignments.py | 56 + hypha/apply/funds/views/submission_delete.py | 54 + hypha/apply/funds/views/submission_detail.py | 319 ++++ hypha/apply/funds/views/submission_edit.py | 775 ++++++++ hypha/apply/funds/views/translate.py | 199 ++ 16 files changed, 1951 insertions(+), 1830 deletions(-) delete mode 100644 hypha/apply/funds/views.py create mode 100644 hypha/apply/funds/views/__init__.py rename hypha/apply/funds/{views_all.py => views/all.py} (99%) rename hypha/apply/funds/{views_partials.py => views/partials.py} (82%) create mode 100644 hypha/apply/funds/views/reminders.py create mode 100644 hypha/apply/funds/views/results.py create mode 100644 hypha/apply/funds/views/reviewer_leaderboard.py create mode 100644 hypha/apply/funds/views/revisions.py create mode 100644 hypha/apply/funds/views/staff_assignments.py create mode 100644 hypha/apply/funds/views/submission_delete.py create mode 100644 hypha/apply/funds/views/submission_detail.py create mode 100644 hypha/apply/funds/views/submission_edit.py create mode 100644 hypha/apply/funds/views/translate.py diff --git a/hypha/apply/funds/services.py b/hypha/apply/funds/services.py index ce82e05540..8d372e28b2 100644 --- a/hypha/apply/funds/services.py +++ b/hypha/apply/funds/services.py @@ -1,4 +1,3 @@ -from bs4 import element from django.apps import apps from django.conf import settings from django.core.exceptions import PermissionDenied @@ -261,32 +260,3 @@ def annotate_review_recommendation_and_count(submissions: QuerySet) -> QuerySet: ), ) return submissions - - -def has_valid_str(tag: element.Tag) -> bool: - """Checks that an Tag contains a valid text element and/or string. - - Args: - tag: a `bs4.element.Tag` - Returns: - bool: True if has a valid string that isn't whitespace or `-` - """ - text_elem = tag.name in ["span", "p", "strong", "em", "td", "a"] - - try: - # try block logic handles elements that have text directly in them - # ie. `

test

` or `yeet!` would return true as string values would be contained in tag.string - ret = bool( - text_elem - and tag.find(string=True, recursive=False) - and tag.string.strip(" -\n") - ) - return ret - except AttributeError: - # except block logic handles embedded tag strings where tag.string == None but the specified tag DOES contain a string - # ie. `

Hypha is cool

` contains the string "Hypha is" but due to the strong tag being mixed in will - # have None for the tag.string value. - # tags like `

Hypha rocks

` will return false as the

tag contains no valid strings, it's child does. - tag_contents = "".join(tag.find_all(string=True, recursive=False)) - ret = bool(tag.text and tag.text.strip() and tag_contents.strip()) - return ret diff --git a/hypha/apply/funds/tests/test_views.py b/hypha/apply/funds/tests/test_views.py index 63a13591c7..6b9316dbc2 100644 --- a/hypha/apply/funds/tests/test_views.py +++ b/hypha/apply/funds/tests/test_views.py @@ -25,6 +25,7 @@ SealedRoundFactory, SealedSubmissionFactory, ) +from hypha.apply.funds.views.submission_detail import SubmissionDetailView from hypha.apply.funds.workflow import INITIAL_STATE from hypha.apply.projects.models import Project from hypha.apply.projects.tests.factories import ProjectFactory @@ -47,7 +48,6 @@ ReviewerSettings, ScreeningStatus, ) -from ..views import SubmissionDetailView from .factories import CustomFormFieldsFactory diff --git a/hypha/apply/funds/urls.py b/hypha/apply/funds/urls.py index 121dc6887d..7b3bc9344b 100644 --- a/hypha/apply/funds/urls.py +++ b/hypha/apply/funds/urls.py @@ -5,40 +5,18 @@ from hypha.apply.projects import urls as projects_urls from .views import ( - CreateProjectView, GroupingApplicationsListView, - ProgressSubmissionView, - ReminderCreateView, - ReminderDeleteView, - ReviewerLeaderboard, - ReviewerLeaderboardDetail, - RevisionCompareView, - RevisionListView, RoundListView, - StaffAssignments, - SubmissionDeleteView, - SubmissionDetailPDFView, - SubmissionDetailView, - SubmissionEditView, SubmissionPrivateMediaView, - SubmissionResultView, - SubmissionSealedView, - TranslateSubmissionView, - UpdateLeadView, - UpdateMetaTermsView, - UpdatePartnersView, - UpdateReviewersView, - htmx_archive_unarchive_submission, - reminder_list, submission_success, ) -from .views_all import ( +from .views.all import ( bulk_archive_submissions, bulk_delete_submissions, bulk_update_submissions_status, submissions_all, ) -from .views_partials import ( +from .views.partials import ( get_applications_status_counts, partial_meta_terms_card, partial_reviews_card, @@ -47,7 +25,6 @@ partial_submission_activities, partial_submission_answers, partial_submission_lead, - partial_translate_answers, sub_menu_bulk_update_lead, sub_menu_bulk_update_reviewers, sub_menu_category_options, @@ -58,6 +35,28 @@ sub_menu_rounds, sub_menu_update_status, ) +from .views.reminders import ReminderCreateView, ReminderDeleteView, reminder_list +from .views.results import SubmissionResultView +from .views.reviewer_leaderboard import ReviewerLeaderboard, ReviewerLeaderboardDetail +from .views.revisions import RevisionCompareView, RevisionListView +from .views.staff_assignments import StaffAssignments +from .views.submission_delete import SubmissionDeleteView +from .views.submission_detail import ( + SubmissionDetailPDFView, + SubmissionDetailView, + SubmissionSealedView, +) +from .views.submission_edit import ( + CreateProjectView, + ProgressSubmissionView, + SubmissionEditView, + UpdateLeadView, + UpdateMetaTermsView, + UpdatePartnersView, + UpdateReviewersView, + htmx_archive_unarchive_submission, +) +from .views.translate import TranslateSubmissionView, partial_translate_answers revision_urls = ( [ diff --git a/hypha/apply/funds/views.py b/hypha/apply/funds/views.py deleted file mode 100644 index 5045e190ca..0000000000 --- a/hypha/apply/funds/views.py +++ /dev/null @@ -1,1665 +0,0 @@ -import json -from copy import copy -from datetime import timedelta -from typing import Generator, Tuple - -import django_tables2 as tables -from django.conf import settings -from django.contrib import messages -from django.contrib.auth import get_user_model -from django.contrib.auth.decorators import ( - login_required, - user_passes_test, -) -from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin -from django.core.exceptions import PermissionDenied -from django.db.models import Count, Q -from django.forms import BaseModelForm -from django.http import ( - FileResponse, - Http404, - HttpRequest, - HttpResponse, - HttpResponseRedirect, -) -from django.shortcuts import get_object_or_404, redirect, render -from django.urls import reverse_lazy -from django.utils import timezone -from django.utils.decorators import method_decorator -from django.utils.translation import gettext as _ -from django.views import View -from django.views.decorators.cache import cache_page -from django.views.decorators.http import require_http_methods -from django.views.generic import ( - DeleteView, - DetailView, - ListView, - UpdateView, -) -from django.views.generic.base import TemplateView -from django.views.generic.detail import SingleObjectMixin -from django_file_form.models import PlaceholderUploadedFile -from django_filters.views import FilterView -from django_htmx.http import ( - HttpResponseClientRedirect, - HttpResponseClientRefresh, -) -from django_tables2.paginators import LazyPaginator -from django_tables2.views import SingleTableMixin -from rolepermissions.checkers import has_object_permission - -from hypha.apply.activity.messaging import MESSAGES, messenger -from hypha.apply.activity.models import Event -from hypha.apply.activity.views import ( - ActivityContextMixin, - CommentFormView, -) -from hypha.apply.determinations.views import ( - DeterminationCreateOrUpdateView, -) -from hypha.apply.projects.forms import ProjectCreateForm -from hypha.apply.review.models import Review -from hypha.apply.stream_forms.blocks import GroupToggleBlock -from hypha.apply.todo.options import PROJECT_WAITING_PF, PROJECT_WAITING_SOW -from hypha.apply.todo.views import add_task_to_user -from hypha.apply.users.decorators import ( - is_apply_staff, - staff_or_finance_required, - staff_required, -) -from hypha.apply.utils.models import PDFPageSettings -from hypha.apply.utils.pdfs import draw_submission_content, make_pdf -from hypha.apply.utils.storage import PrivateMediaView -from hypha.apply.utils.views import ( - DelegateableView, - ViewDispatcher, -) - -from . import services -from .differ import compare -from .files import generate_private_file_path -from .forms import ( - CreateReminderForm, - ProgressSubmissionForm, - UpdateMetaTermsForm, - UpdatePartnersForm, - UpdateReviewersForm, - UpdateSubmissionLeadForm, -) -from .models import ( - ApplicationRevision, - ApplicationSubmission, - AssignedReviewers, - Reminder, - ReviewerRole, - ReviewerSettings, - RoundsAndLabs, -) -from .permissions import ( - can_alter_archived_submissions, - get_archive_view_groups, - has_permission, -) -from .tables import ( - ReviewerLeaderboardDetailTable, - ReviewerLeaderboardFilter, - ReviewerLeaderboardTable, - RoundsFilter, - RoundsTable, - StaffAssignmentsTable, - SubmissionFilterAndSearch, -) -from .utils import ( - format_submission_sum_value, - is_filter_empty, -) -from .workflow import ( - DRAFT_STATE, - STAGE_CHANGE_ACTIONS, - active_statuses, -) - -if settings.APPLICATION_TRANSLATIONS_ENABLED: - from hypha.apply.translate.forms import TranslateSubmissionForm - from hypha.apply.translate.utils import ( - get_lang_name, - get_language_choices_json, - get_translation_params, - translate_application_form_data, - ) - -User = get_user_model() - - -def submission_success(request, pk): - submission = get_object_or_404(ApplicationSubmission, pk=pk) - return render( - request, - "funds/submission-success.html", - { - "form_submission": submission, - }, - ) - - -class SubmissionStatsMixin: - def get_context_data(self, **kwargs): - submissions = ApplicationSubmission.objects.exclude_draft() - # Getting values is an expensive operation. If there's no valid filters - # then `count_values` & `total_value` will be encapsulating all submissions - # and should be used rather than recaluclating these values. - if not (filter := kwargs.get("filter")) or not is_filter_empty(filter): - submission_count = kwargs.get("count_values") - submission_sum = kwargs.get("total_value") - else: - submission_count = submissions.count() - submission_value = submissions.current().value() - submission_sum = format_submission_sum_value(submission_value) - - submission_undetermined_count = submissions.undetermined().count() - review_my_count = submissions.reviewed_by(self.request.user).count() - - submission_accepted = submissions.current_accepted() - submission_accepted_value = submission_accepted.value() - submission_accepted_sum = format_submission_sum_value(submission_accepted_value) - submission_accepted_count = submission_accepted.count() - - reviews = Review.objects.submitted() - review_count = reviews.count() - review_my_score = reviews.by_user(self.request.user).score() - - return super().get_context_data( - submission_undetermined_count=submission_undetermined_count, - review_my_count=review_my_count, - submission_sum=submission_sum, - submission_count=submission_count, - submission_accepted_count=submission_accepted_count, - submission_accepted_sum=submission_accepted_sum, - review_count=review_count, - review_my_score=review_my_score, - **kwargs, - ) - - -@method_decorator(staff_required, name="dispatch") -class GroupingApplicationsListView(TemplateView): - """ - Template view for grouped submissions - """ - - template_name = "funds/grouped_application_list.html" - - -@method_decorator(staff_required, name="dispatch") -class ProgressSubmissionView(View): - def dispatch(self, request, *args, **kwargs): - self.submission = get_object_or_404(ApplicationSubmission, id=kwargs.get("pk")) - permission, reason = has_permission( - "submission_edit", - 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(ProgressSubmissionView, self).dispatch(request, *args, **kwargs) - - def get(self, *args, **kwargs): - project_creation_form = ProgressSubmissionForm( - instance=self.submission, user=self.request.user - ) - return render( - self.request, - "funds/includes/progress_form.html", - context={ - "form": project_creation_form, - "value": _("Progress"), - "object": self.submission, - }, - ) - - def post(self, *args, **kwargs): - form = ProgressSubmissionForm( - self.request.POST, instance=self.submission, user=self.request.user - ) - if form.is_valid(): - action = form.cleaned_data.get("action") - redirect = DeterminationCreateOrUpdateView.should_redirect( - self.request, self.submission, action - ) - message_storage = messages.get_messages(self.request) - if redirect: - return HttpResponseClientRedirect(redirect.url, content=message_storage) - - self.submission.perform_transition( - action, self.request.user, request=self.request - ) - form.save() - return HttpResponseClientRefresh() - return render( - self.request, - "funds/includes/progress_form.html", - context={"form": form, "value": _("Progress"), "object": self.submission}, - status=400, - ) - - -@method_decorator(staff_required, name="dispatch") -class CreateProjectView(View): - def dispatch(self, request, *args, **kwargs): - self.submission = get_object_or_404(ApplicationSubmission, id=kwargs.get("pk")) - permission, reason = has_permission( - "submission_edit", - 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(CreateProjectView, self).dispatch(request, *args, **kwargs) - - def get(self, *args, **kwargs): - project_creation_form = ProjectCreateForm(instance=self.submission) - return render( - self.request, - "funds/includes/create_project_form.html", - context={ - "form": project_creation_form, - "value": _("Confirm"), - "object": self.submission, - }, - ) - - def post(self, *args, **kwargs): - form = ProjectCreateForm(self.request.POST, instance=self.submission) - if form.is_valid(): - project = form.save() - # Record activity - messenger( - MESSAGES.CREATED_PROJECT, - request=self.request, - user=self.request.user, - source=project, - related=project.submission, - ) - # add task for staff to add PAF to the project - add_task_to_user( - code=PROJECT_WAITING_PF, - user=project.lead, - related_obj=project, - ) - if self.submission.page.specific.sow_forms.first(): - # Add SOW task if one exists on the parent - add_task_to_user( - code=PROJECT_WAITING_SOW, - user=project.lead, - related_obj=project, - ) - return HttpResponseClientRedirect(project.get_absolute_url()) - return render( - self.request, - "funds/includes/create_project_form.html", - context={"form": form, "value": _("Confirm"), "object": self.object}, - status=400, - ) - - -@login_required -@user_passes_test(is_apply_staff) -@require_http_methods(["GET", "POST"]) -def htmx_archive_unarchive_submission(request, pk): - submission = get_object_or_404(ApplicationSubmission, id=pk) - permission, reason = has_permission( - "archive_alter", request.user, object=submission, raise_exception=False - ) - if not permission: - return HttpResponse(reason) - - if submission.is_archive: - template = "funds/includes/modal_unarchive_submission_confirm.html" - else: - template = "funds/includes/modal_archive_submission_confirm.html" - - if request.method == "POST": - if submission.is_archive: - submission.is_archive = False - submission.save() - messenger( - MESSAGES.UNARCHIVE_SUBMISSION, - request=request, - user=request.user, - source=submission, - ) - else: - submission.is_archive = True - submission.save() - messenger( - MESSAGES.ARCHIVE_SUBMISSION, - request=request, - user=request.user, - source=submission, - ) - - return redirect(submission.get_absolute_url()) - - return render( - request, - template, - context={"submission": submission}, - ) - - -@method_decorator(staff_required, name="dispatch") -class UpdateLeadView(View): - model = ApplicationSubmission - form_class = UpdateSubmissionLeadForm - context_name = "lead_form" - template = "funds/modals/update_lead_form.html" - - def dispatch(self, request, *args, **kwargs): - self.object = get_object_or_404(ApplicationSubmission, id=kwargs.get("pk")) - permission, reason = has_permission( - "submission_edit", request.user, object=self.object, raise_exception=False - ) - if not permission: - messages.warning(self.request, reason) - return HttpResponseRedirect(self.object.get_absolute_url()) - return super().dispatch(request, *args, **kwargs) - - def get(self, *args, **kwargs): - lead_form = UpdateSubmissionLeadForm(instance=self.object) - return render( - self.request, - self.template, - context={ - "form": lead_form, - "value": _("Submit"), - "object": self.object, - }, - ) - - def post(self, *args, **kwargs): - form = UpdateSubmissionLeadForm(self.request.POST, instance=self.object) - old_lead = copy(self.object.lead) - if form.is_valid(): - form.save() - messenger( - MESSAGES.UPDATE_LEAD, - request=self.request, - user=self.request.user, - source=form.instance, - related=old_lead, - ) - return HttpResponse( - status=204, - headers={ - "HX-Trigger": json.dumps( - {"leadUpdated": None, "showMessage": "Submission Lead updated."} - ), - }, - ) - return render( - self.request, - self.template, - context={"form": form, "value": _("Update"), "object": self.object}, - status=400, - ) - - -@method_decorator(staff_required, name="dispatch") -class UpdateReviewersView(View): - def dispatch(self, request, *args, **kwargs): - self.submission = get_object_or_404(ApplicationSubmission, id=kwargs.get("pk")) - permission, reason = has_permission( - "submission_edit", - 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(UpdateReviewersView, self).dispatch(request, *args, **kwargs) - - def get(self, *args, **kwargs): - reviewer_form = UpdateReviewersForm( - user=self.request.user, instance=self.submission - ) - return render( - self.request, - "funds/includes/update_reviewer_form.html", - context={ - "form": reviewer_form, - "value": _("Update"), - "object": self.submission, - }, - ) - - def post(self, *args, **kwargs): - form = UpdateReviewersForm( - self.request.POST, user=self.request.user, instance=self.submission - ) - old_reviewers = {copy(reviewer) for reviewer in form.instance.assigned.all()} - if form.is_valid(): - form.save() - new_reviewers = set(form.instance.assigned.all()) - added = new_reviewers - old_reviewers - removed = old_reviewers - new_reviewers - messenger( - MESSAGES.REVIEWERS_UPDATED, - request=self.request, - user=self.request.user, - source=self.submission, - added=added, - removed=removed, - ) - # Update submission status if needed. - services.set_status_after_reviewers_assigned( - submission=form.instance, - updated_by=self.request.user, - request=self.request, - ) - return HttpResponse( - status=204, - headers={ - "HX-Trigger": json.dumps( - {"reviewerUpdated": None, "showMessage": "Reviewers updated."} - ), - }, - ) - - return render( - self.request, - "funds/includes/update_reviewer_form.html", - context={"form": form, "value": _("Update"), "object": self.submission}, - ) - - -@method_decorator(staff_required, name="dispatch") -class UpdatePartnersView(View): - model = ApplicationSubmission - form_class = UpdatePartnersForm - context_name = "partner_form" - template = "funds/modals/update_partner_form.html" - - def dispatch(self, request, *args, **kwargs): - self.submission = get_object_or_404(ApplicationSubmission, id=kwargs.get("pk")) - permission, reason = has_permission( - "submission_edit", - 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(UpdatePartnersView, self).dispatch(request, *args, **kwargs) - - def get(self, *args, **kwargs): - partner_form = UpdatePartnersForm( - user=self.request.user, instance=self.submission - ) - return render( - self.request, - self.template, - context={ - "form": partner_form, - "value": _("Update"), - "object": self.submission, - }, - ) - - def post(self, *args, **kwargs): - form = UpdatePartnersForm( - self.request.POST, user=self.request.user, instance=self.submission - ) - old_partners = set(self.submission.partners.all()) - if form.is_valid(): - form.save() - new_partners = set(form.instance.partners.all()) - - added = new_partners - old_partners - removed = old_partners - new_partners - messenger( - MESSAGES.PARTNERS_UPDATED, - request=self.request, - user=self.request.user, - source=self.submission, - added=added, - removed=removed, - ) - - messenger( - MESSAGES.PARTNERS_UPDATED_PARTNER, - request=self.request, - user=self.request.user, - source=self.submission, - added=added, - removed=removed, - ) - - return HttpResponse( - status=204, - headers={ - "HX-Trigger": json.dumps( - { - "partnerUpdated": None, - "showMessage": "Partners updated successfully.", - } - ), - }, - ) - - return render( - self.request, - self.template, - context={"form": form, "value": _("Update"), "object": self.submission}, - status=400, - ) - - -@method_decorator(staff_required, name="dispatch") -class UpdateMetaTermsView(View): - template = "funds/includes/update_meta_terms_form.html" - - def dispatch(self, request, *args, **kwargs): - self.submission = get_object_or_404(ApplicationSubmission, id=kwargs.get("pk")) - permission, reason = has_permission( - "submission_edit", - 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(UpdateMetaTermsView, self).dispatch(request, *args, **kwargs) - - def get(self, *args, **kwargs): - metaterms_form = UpdateMetaTermsForm( - user=self.request.user, instance=self.submission - ) - return render( - self.request, - self.template, - context={ - "form": metaterms_form, - "value": _("Update"), - "object": self.submission, - }, - ) - - def post(self, *args, **kwargs): - form = UpdateMetaTermsForm( - self.request.POST, instance=self.submission, user=self.request.user - ) - if form.is_valid(): - form.save() - - return HttpResponse( - status=204, - headers={ - "HX-Trigger": json.dumps( - { - "metaTermsUpdated": None, - "showMessage": "Meta terms updated successfully.", - } - ), - }, - ) - return render( - self.request, - self.template, - context={"form": form, "value": _("Update"), "object": self.submission}, - status=400, - ) - - -@method_decorator(staff_required, name="dispatch") -class TranslateSubmissionView(View): - template = "funds/includes/translate_application_form.html" - - if settings.APPLICATION_TRANSLATIONS_ENABLED: - - def dispatch(self, request, *args, **kwargs): - self.submission = get_object_or_404( - ApplicationSubmission, id=kwargs.get("pk") - ) - if not request.user.is_org_faculty: - messages.warning( - self.request, - "User attempted to translate submission but is not org faculty", - ) - return HttpResponseRedirect(self.submission.get_absolute_url()) - return super(TranslateSubmissionView, self).dispatch( - request, *args, **kwargs - ) - - def get(self, *args, **kwargs): - translate_form = TranslateSubmissionForm() - return render( - self.request, - self.template, - context={ - "form": translate_form, - "value": _("Update"), - "object": self.submission, - "json_choices": get_language_choices_json(self.request), - }, - ) - - def post(self, request, *args, **kwargs): - form = TranslateSubmissionForm(self.request.POST) - - if form.is_valid(): - FROM_LANG_KEY = "from_lang" - TO_LANG_KEY = "to_lang" - - from_lang = form.cleaned_data[FROM_LANG_KEY] - to_lang = form.cleaned_data[TO_LANG_KEY] - - return HttpResponse( - status=204, - headers={ - "HX-Trigger": json.dumps( - { - "translateSubmission": { - FROM_LANG_KEY: from_lang, - TO_LANG_KEY: to_lang, - } - } - ), - }, - ) - - return render( - self.request, - self.template, - context={ - "form": form, - "value": _("Update"), - "object": self.submission, - "json_choices": get_language_choices_json(self.request), - }, - status=400, - ) - else: - - def get(self, *args, **kwargs): - raise Http404 - - def post(self, *args, **kwargs): - raise Http404 - - -@login_required -@user_passes_test(is_apply_staff) -@require_http_methods(["GET"]) -def reminder_list(request, pk): - submission = get_object_or_404(ApplicationSubmission, id=pk) - reminders = Reminder.objects.filter(submission=submission) - return render( - request, - "funds/includes/reminders_block.html", - context={"reminders": reminders, "object": submission}, - ) - - -@method_decorator(staff_required, name="dispatch") -class ReminderCreateView(View): - def dispatch(self, request, *args, **kwargs): - self.submission = get_object_or_404(ApplicationSubmission, id=kwargs.get("pk")) - permission, reason = has_permission( - "submission_edit", - 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(ReminderCreateView, self).dispatch(request, *args, **kwargs) - - def get(self, *args, **kwargs): - reminder_form = CreateReminderForm(instance=self.submission) - return render( - self.request, - "funds/includes/create_reminder_form.html", - context={ - "form": reminder_form, - "value": _("Create Reminder"), - "object": self.submission, - }, - ) - - def post(self, *args, **kwargs): - form = CreateReminderForm( - self.request.POST, instance=self.submission, user=self.request.user - ) - if form.is_valid(): - reminder = form.save() - messenger( - MESSAGES.CREATE_REMINDER, - request=self.request, - user=self.request.user, - source=self.submission, - related=reminder, - ) - return HttpResponse( - status=204, - headers={ - "HX-Trigger": json.dumps({"remindersUpdated": None}), - }, - ) - return render( - self.request, - "funds/includes/create_reminder_form.html", - context={"form": form, "value": _("Create"), "object": self.submission}, - status=400, - ) - - -@method_decorator(staff_required, name="dispatch") -class ReminderDeleteView(DeleteView): - model = Reminder - - def get_success_url(self): - submission = get_object_or_404( - ApplicationSubmission, id=self.kwargs["submission_pk"] - ) - return reverse_lazy("funds:submissions:detail", args=(submission.id,)) - - def form_valid(self, form): - reminder = self.get_object() - messenger( - MESSAGES.DELETE_REMINDER, - user=self.request.user, - request=self.request, - source=reminder.submission, - related=reminder, - ) - return super().form_valid(form) - - -class AdminSubmissionDetailView(ActivityContextMixin, DelegateableView, DetailView): - template_name_suffix = "_admin_detail" - model = ApplicationSubmission - form_views = [ - CommentFormView, - ] - - def dispatch(self, request, *args, **kwargs): - submission = self.get_object() - if submission.status == DRAFT_STATE and not submission.can_view_draft( - request.user - ): - raise Http404 - permission, _ = has_permission( - "submission_view", request.user, object=submission, raise_exception=True - ) - redirect = SubmissionSealedView.should_redirect(request, submission) - return redirect or super().dispatch(request, *args, **kwargs) - - if settings.APPLICATION_TRANSLATIONS_ENABLED: - - def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: - self.object = self.get_object() - - extra_context = {} - - # Check for language params - if they exist and are valid then update the context - if lang_params := get_translation_params(request=request): - from_lang, to_lang = lang_params - try: - self.object.form_data = translate_application_form_data( - self.object, from_lang, to_lang - ) - extra_context.update( - { - "from_lang_name": get_lang_name(from_lang), - "to_lang_name": get_lang_name(to_lang), - } - ) - except ValueError: - # Language package isn't valid or installed, redirect to the submission w/o params - return redirect(self.object.get_absolute_url()) - - context = self.get_context_data(object=self.object, **extra_context) - return self.render_to_response(context) - - def get_context_data(self, **kwargs): - other_submissions = ( - self.model.objects.filter(user=self.object.user) - .current() - .exclude(id=self.object.id) - .order_by("-submit_time") - ) - if self.object.next: - other_submissions = other_submissions.exclude(id=self.object.next.id) - - return super().get_context_data( - other_submissions=other_submissions, - archive_access_groups=get_archive_view_groups(), - can_archive=can_alter_archived_submissions(self.request.user), - **kwargs, - ) - - -class ReviewerSubmissionDetailView(ActivityContextMixin, DelegateableView, DetailView): - template_name_suffix = "_reviewer_detail" - model = ApplicationSubmission - form_views = [CommentFormView] - - def dispatch(self, request, *args, **kwargs): - submission = self.get_object() - # If the requesting user submitted the application, return the Applicant view. - # Reviewers may sometimes be applicants as well. - if submission.user == request.user: - return ApplicantSubmissionDetailView.as_view()(request, *args, **kwargs) - if submission.status == DRAFT_STATE: - raise Http404 - - permission, _ = has_permission( - "submission_view", request.user, object=submission, raise_exception=True - ) - - reviewer_settings = ReviewerSettings.for_request(request) - if reviewer_settings.use_settings: - queryset = ApplicationSubmission.objects.for_reviewer_settings( - request.user, reviewer_settings - ) - # Reviewer can't view submission which is not listed in ReviewerSubmissionsTable - if not queryset.filter(id=submission.id).exists(): - raise PermissionDenied - - return super().dispatch(request, *args, **kwargs) - - -class PartnerSubmissionDetailView(ActivityContextMixin, DelegateableView, DetailView): - model = ApplicationSubmission - form_views = [CommentFormView] - - def get_object(self): - return super().get_object().from_draft() - - def dispatch(self, request, *args, **kwargs): - submission = self.get_object() - permission, _ = has_permission( - "submission_view", request.user, object=submission, raise_exception=True - ) - # If the requesting user submitted the application, return the Applicant view. - # Partners may sometimes be applicants as well. - if submission.user == request.user: - return ApplicantSubmissionDetailView.as_view()(request, *args, **kwargs) - # Only allow partners in the submission they are added as partners - partner_has_access = submission.partners.filter(pk=request.user.pk).exists() - if not partner_has_access: - raise PermissionDenied - if submission.status == DRAFT_STATE: - raise Http404 - return super().dispatch(request, *args, **kwargs) - - -class CommunitySubmissionDetailView(ActivityContextMixin, DelegateableView, DetailView): - template_name_suffix = "_community_detail" - model = ApplicationSubmission - form_views = [CommentFormView] - - def dispatch(self, request, *args, **kwargs): - submission = self.get_object() - permission, _ = has_permission( - "submission_view", request.user, object=submission, raise_exception=True - ) - # If the requesting user submitted the application, return the Applicant view. - # Reviewers may sometimes be applicants as well. - if submission.user == request.user: - return ApplicantSubmissionDetailView.as_view()(request, *args, **kwargs) - # Only allow community reviewers in submission with a community review state. - if not submission.community_review: - raise PermissionDenied - if submission.status == DRAFT_STATE: - raise Http404 - return super().dispatch(request, *args, **kwargs) - - -class ApplicantSubmissionDetailView(ActivityContextMixin, DelegateableView, DetailView): - model = ApplicationSubmission - form_views = [CommentFormView] - - def get_object(self): - return super().get_object().from_draft() - - def dispatch(self, request, *args, **kwargs): - submission = self.get_object() - permission, _ = has_permission( - "submission_view", request.user, object=submission, raise_exception=True - ) - # This view is only for applicants. - if submission.user != request.user: - raise PermissionDenied - return super().dispatch(request, *args, **kwargs) - - -class SubmissionDetailView(ViewDispatcher): - admin_view = AdminSubmissionDetailView - reviewer_view = ReviewerSubmissionDetailView - partner_view = PartnerSubmissionDetailView - community_view = CommunitySubmissionDetailView - applicant_view = ApplicantSubmissionDetailView - - -@method_decorator(staff_required, "dispatch") -class SubmissionSealedView(DetailView): - template_name = "funds/submission_sealed.html" - model = ApplicationSubmission - - def get(self, request, *args, **kwargs): - submission = self.get_object() - if not self.round_is_sealed(submission): - return self.redirect_detail(submission) - return super().get(request, *args, **kwargs) - - def post(self, request, *args, **kwargs): - submission = self.get_object() - if self.can_view_sealed(request.user): - self.peeked(submission) - return self.redirect_detail(submission) - - def redirect_detail(self, submission): - return HttpResponseRedirect( - reverse_lazy("funds:submissions:detail", args=(submission.id,)) - ) - - def peeked(self, submission): - messenger( - MESSAGES.OPENED_SEALED, - request=self.request, - user=self.request.user, - source=submission, - ) - self.request.session.setdefault("peeked", {})[str(submission.id)] = True - # Dictionary updates do not trigger session saves. Force update - self.request.session.modified = True - - def can_view_sealed(self, user): - return user.is_superuser - - def get_context_data(self, **kwargs): - return super().get_context_data( - can_view_sealed=self.can_view_sealed(self.request.user), - **kwargs, - ) - - @classmethod - def round_is_sealed(cls, submission): - try: - return submission.round.specific.is_sealed - except AttributeError: - # Its a lab - cant be sealed - return False - - @classmethod - def has_peeked(cls, request, submission): - return str(submission.id) in request.session.get("peeked", {}) - - @classmethod - def should_redirect(cls, request, submission): - if cls.round_is_sealed(submission) and not cls.has_peeked(request, submission): - return HttpResponseRedirect( - reverse_lazy("funds:submissions:sealed", args=(submission.id,)) - ) - - -class BaseSubmissionEditView(UpdateView): - """ - Converts the data held on the submission into an editable format and knows how to save - that back to the object. Shortcuts the normal update view save approach - """ - - model = ApplicationSubmission - - def render_preview(self, request: HttpRequest, form: BaseModelForm) -> HttpResponse: - """Gets a rendered preview of a form - - Creates a new revision on the `ApplicationSubmission`, removes the - forms temporary files - - Args: - request: - Request used to trigger the preview to be used in the render - form: - Form to be rendered - - Returns: - An `HttpResponse` containing a preview of the given form - """ - - self.object.create_revision(draft=True, by=request.user) - messages.success(self.request, _("Draft saved")) - - # Required for django-file-form: delete temporary files for the new files - # uploaded while edit. - form.delete_temporary_files() - - context = self.get_context_data() - return render(request, "funds/application_preview.html", context) - - def dispatch(self, request, *args, **kwargs): - permission, _ = has_permission( - "submission_edit", - request.user, - object=self.get_object(), - raise_exception=True, - ) - if not self.get_object().phase.permissions.can_edit(request.user): - raise PermissionDenied - return super().dispatch(request, *args, **kwargs) - - def buttons( - self, - ) -> Generator[Tuple[str, str, str], Tuple[str, str, str], Tuple[str, str, str]]: - """The buttons to be presented to the in the EditView - - Returns: - A generator returning a tuple strings in the format of: - (