From 3ed3b0ad23bed159e2124f07e8d45cd1b57b6d5e Mon Sep 17 00:00:00 2001 From: Wes Appler <145372368+wes-otf@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:57:17 -0400 Subject: [PATCH] Fixes to application previews and draft refactoring (#3795) Fixes #3788 and a few other issues. The main problems being seen around the preview were when it came to the edit view. This was because the use of the `Draft` status was being relied on for the saving of content before previews, when realistically a new application revision had the same effect. Previously, Admins/Staff could submit new applications, but could not submit existing applications. This is why when an Admin had attempted to submit a preview or submit an edited application, they could not do so. This functionality has been moved from `ApplicantSubmissionEditView` to `BaseSubmissionEditView` as it made sense to me that Admins would be able to access the same edit workflow & transitions as Applicants. I believe most OTF staff avoid editing incoming applications though. --- .../0117_applicationrevision_is_draft.py | 17 ++ .../funds/models/application_revisions.py | 3 + hypha/apply/funds/models/applications.py | 27 +- hypha/apply/funds/models/submissions.py | 72 ++++- .../templates/funds/application_base.html | 3 +- .../templates/funds/application_preview.html | 11 +- .../funds/applicationrevision_list.html | 3 + hypha/apply/funds/tests/test_models.py | 4 - hypha/apply/funds/tests/test_views.py | 16 +- hypha/apply/funds/views.py | 263 +++++++++++------- hypha/settings/base.py | 3 +- .../static_src/sass/components/_revision.scss | 3 +- 12 files changed, 286 insertions(+), 139 deletions(-) create mode 100644 hypha/apply/funds/migrations/0117_applicationrevision_is_draft.py diff --git a/hypha/apply/funds/migrations/0117_applicationrevision_is_draft.py b/hypha/apply/funds/migrations/0117_applicationrevision_is_draft.py new file mode 100644 index 000000000..6691630a0 --- /dev/null +++ b/hypha/apply/funds/migrations/0117_applicationrevision_is_draft.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.10 on 2024-03-12 16:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("funds", "0115_list_on_front_page"), + ] + + operations = [ + migrations.AddField( + model_name="applicationrevision", + name="is_draft", + field=models.BooleanField(default=False), + ), + ] diff --git a/hypha/apply/funds/models/application_revisions.py b/hypha/apply/funds/models/application_revisions.py index 14508d6f6..3ee8d3800 100644 --- a/hypha/apply/funds/models/application_revisions.py +++ b/hypha/apply/funds/models/application_revisions.py @@ -22,6 +22,9 @@ class ApplicationRevision(BaseStreamForm, AccessFormData, models.Model): settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True ) + # Is the revision a draft - also used by previews to save before rendering + is_draft = models.BooleanField(default=False) + class Meta: ordering = ["-timestamp"] diff --git a/hypha/apply/funds/models/applications.py b/hypha/apply/funds/models/applications.py index 951efbabf..2fa80e184 100644 --- a/hypha/apply/funds/models/applications.py +++ b/hypha/apply/funds/models/applications.py @@ -484,6 +484,9 @@ class RoundBase(WorkflowStreamForm, SubmittableStreamForm): # type: ignore return form_class(*args, **form_params) def serve(self, request, *args, **kwargs): + # NOTE: `is_preview` is referring to the Wagtail admin preview + # functionality, while `preview` refers to the applicant rendering + # a preview of their application. if hasattr(request, "is_preview") or hasattr(request, "show_round"): # Overriding serve method to pass submission id to get_form method copy_open_submission = request.GET.get("open_call_submission") @@ -500,13 +503,11 @@ class RoundBase(WorkflowStreamForm, SubmittableStreamForm): # type: ignore if form.is_valid(): form_submission = self.process_form_submission(form, draft=draft) - # Required for django-file-form: delete temporary files for the new files - # that are uploaded. - form.delete_temporary_files() - # If a preview is specified in form submission, render the applicant's answers rather than the landing page. - # At the moment ALL previews are drafted first and then shown - if preview and draft: + # If a preview is specified in form submission, render the + # applicant's answers rather than the landing page. + # Previews are drafted first and then shown + if preview: context = self.get_context(request) context["object"] = form_submission context["form"] = form @@ -514,6 +515,10 @@ class RoundBase(WorkflowStreamForm, SubmittableStreamForm): # type: ignore request, "funds/application_preview.html", context ) + # Required for django-file-form: delete temporary files for the new files + # that are uploaded. + form.delete_temporary_files() + return self.render_landing_page( request, form_submission, *args, **kwargs ) @@ -653,14 +658,18 @@ class LabBase(EmailForm, WorkflowStreamForm, SubmittableStreamForm): # type: ig self, form, draft=draft ) - # If a preview is specified in form submission, render the applicant's answers rather than the landing page. - # At the moment ALL previews are drafted first and then shown - if preview and draft: + # If a preview is specified in form submission, render the + # applicant's answers rather than the landing page. + if preview: context = self.get_context(request) context["object"] = form_submission context["form"] = form return render(request, "funds/application_preview.html", context) + # Required for django-file-form: delete temporary files for the new files + # that are uploaded. + form.delete_temporary_files() + return self.render_landing_page( request, form_submission, *args, **kwargs ) diff --git a/hypha/apply/funds/models/submissions.py b/hypha/apply/funds/models/submissions.py index 06cda0d7d..bb31213dd 100644 --- a/hypha/apply/funds/models/submissions.py +++ b/hypha/apply/funds/models/submissions.py @@ -1,11 +1,12 @@ import json import operator from functools import partialmethod, reduce +from typing import Optional, Self from django.apps import apps from django.conf import settings from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group +from django.contrib.auth.models import AbstractBaseUser, AnonymousUser, Group from django.contrib.contenttypes.fields import GenericRelation from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.search import SearchVector, SearchVectorField @@ -472,8 +473,6 @@ class ApplicationSubmission( verbose_name=_("submit time"), auto_now_add=False ) - _is_draft = False - live_revision = models.OneToOneField( "ApplicationRevision", on_delete=models.CASCADE, @@ -626,20 +625,40 @@ class ApplicationSubmission( submission_in_db.next = self submission_in_db.save() - def new_data(self, data): - self._is_draft = False - self.form_data = data - return self + def from_draft(self) -> Self: + """Sets current `form_data` to the `form_data` from the draft revision. - def from_draft(self): - self._is_draft = True + Returns: + Self with the `form_data` attribute updated. + """ self.form_data = self.deserialised_data( self, self.draft_revision.form_data, self.form_fields ) + return self - def create_revision(self, draft=False, force=False, by=None, **kwargs): - # Will return True/False if the revision was created or not + # TODO: It would be nice to extract this to a services.py and potentially break this into smaller, more logical functions. + def create_revision( + self, + draft=False, + force=False, + by: Optional[AnonymousUser | AbstractBaseUser] = None, + **kwargs, + ) -> Optional[models.Model]: + """Create a new revision on the submission + + This is used to save drafts, track changes when an RFI is made and + save changes before rendering a preview + + Args: + draft: if the revision is a draft + force: force a revision even if form data is the same + by: the author of the revision + preview: if the revision is being used to save befor a preview + + Returns: + Returns the [`ApplicationRevision`][hypha.apply.funds.models.ApplicationRevision] if it was created, otherwise returns `None` + """ ApplicationRevision = apps.get_model("funds", "ApplicationRevision") self.clean_submission() current_submission = ApplicationSubmission.objects.get(id=self.id) @@ -647,7 +666,10 @@ class ApplicationSubmission( if current_data != self.form_data or force: if self.live_revision == self.draft_revision: revision = ApplicationRevision.objects.create( - submission=self, form_data=self.form_data, author=by + submission=self, + form_data=self.form_data, + author=by, + is_draft=draft, ) else: revision = self.draft_revision @@ -658,6 +680,10 @@ class ApplicationSubmission( if draft: self.form_data = current_submission.form_data else: + # Move the revision state out of draft as it is being submitted + if revision.is_draft: + revision.is_draft = False + revision.save() self.live_revision = revision self.search_data = " ".join(list(self.prepare_search_values())) self.search_document = self.prepare_search_vector() @@ -665,6 +691,22 @@ class ApplicationSubmission( self.draft_revision = revision self.save(skip_custom=True) return revision + else: + revision = self.draft_revision + + # Utilized when the user has previously saved a draft, + # then doesn't edit the draft but submits it straight + # from the edit view + if not draft and revision.is_draft: + revision.is_draft = False + revision.save() + self.live_revision = revision + self.search_data = " ".join(list(self.prepare_search_values())) + self.search_document = self.prepare_search_vector() + self.save(skip_custom=True) + + return revision + return None def clean_submission(self): @@ -691,9 +733,6 @@ class ApplicationSubmission( elif skip_custom: return super().save(*args, **kwargs) - if self._is_draft: - raise ValueError("Cannot save with draft data") - creating = not self.id if creating: @@ -715,6 +754,7 @@ class ApplicationSubmission( super().save(*args, **kwargs) + # TODO: This functionality should be extracted and moved to a seperate function, too hidden here if creating: AssignedReviewers = apps.get_model("funds", "AssignedReviewers") ApplicationRevision = apps.get_model("funds", "ApplicationRevision") @@ -724,10 +764,12 @@ class ApplicationSubmission( list(self.get_from_parent("reviewers").all()), self, ) + # TODO: This functionality should be implemented into `ApplicationSubmission.create_revision` first_revision = ApplicationRevision.objects.create( submission=self, form_data=self.form_data, author=self.user, + is_draft=self.is_draft, ) self.live_revision = first_revision self.draft_revision = first_revision diff --git a/hypha/apply/funds/templates/funds/application_base.html b/hypha/apply/funds/templates/funds/application_base.html index d158f3698..073666dec 100644 --- a/hypha/apply/funds/templates/funds/application_base.html +++ b/hypha/apply/funds/templates/funds/application_base.html @@ -75,8 +75,7 @@ <button class="button button--submit button--primary" type="submit" disabled>{% trans "Submit for review" %}</button> {% endif %} <button class="button button--submit button--white" type="submit" name="draft" value="Save draft" formnovalidate>{% trans "Save draft" %}</button> - {# TODO Fix preview bugs before reactivating. #} - {% if False and not require_preview and request.user.is_authenticated %} + {% if not require_preview and request.user.is_authenticated %} <button class="button button--submit button--white" type="submit" name="preview">{% trans "Preview" %}</button> {% endif %} </div> diff --git a/hypha/apply/funds/templates/funds/application_preview.html b/hypha/apply/funds/templates/funds/application_preview.html index 932cdac75..00c300630 100644 --- a/hypha/apply/funds/templates/funds/application_preview.html +++ b/hypha/apply/funds/templates/funds/application_preview.html @@ -12,10 +12,11 @@ <div class="wrapper wrapper--medium wrapper--form"> {% include "funds/includes/rendered_answers.html" %} - <form id="preview-form-submit" class="form application-form" action="{% url 'funds:submissions:edit' object.id %}" method="POST" enctype="multipart/form-data"> + <form id="preview-form-submit" class="form application-form" action="{% url 'funds:submissions:edit' object.id %}" method="POST" enctype="multipart/form-data" novalidate> {% csrf_token %} - <div class="preview-hidden-form" hidden> + {# Hidden form fields to allow for POSTing to funds:submissions:edit on submit/edit #} + <div hidden> {% for field in form %} {% if field.field %} {% if field.field.multi_input_field %} @@ -27,8 +28,12 @@ {{ field.block }} {% endif %} {% endfor %} + + {# Hidden fields needed e.g. for django-file-form. See `StreamBaseForm.hidden_fields` #} + {% for hidden_field in form.hidden_fields %} + {{ hidden_field }} + {% endfor %} </div> - <!-- <button class="button button--primary" name="submit" type="submit">{% trans "Submit for review" %}</button> --> </form> <form id="preview-form-edit" class="form application-form" action="{% url 'funds:submissions:edit' object.id %}"> diff --git a/hypha/apply/funds/templates/funds/applicationrevision_list.html b/hypha/apply/funds/templates/funds/applicationrevision_list.html index ca769513d..437b118a4 100644 --- a/hypha/apply/funds/templates/funds/applicationrevision_list.html +++ b/hypha/apply/funds/templates/funds/applicationrevision_list.html @@ -18,6 +18,9 @@ {% if forloop.first %} <span class="revision__current">- {% trans "current" %}</span> {% endif %} + {% if revision.is_draft %} + <span class="revision__draft">(<span class="text-red-600">{% trans "draft" %}</span>)</span + {% endif %} </p> {% if not forloop.first %} <a class="button button--compare" href="{{ revision.get_compare_url_to_latest }}">{% trans "Compare" %}</a> diff --git a/hypha/apply/funds/tests/test_models.py b/hypha/apply/funds/tests/test_models.py index f1a518902..e859bcb28 100644 --- a/hypha/apply/funds/tests/test_models.py +++ b/hypha/apply/funds/tests/test_models.py @@ -505,10 +505,6 @@ class TestApplicationSubmission(TestCase): draft_submission = submission.from_draft() self.assertDictEqual(draft_submission.form_data, submission.form_data) self.assertEqual(draft_submission.title, title) - self.assertTrue(draft_submission._is_draft, True) - - with self.assertRaises(ValueError): - draft_submission.save() submission = self.refresh(submission) self.assertNotEqual(submission.title, title) diff --git a/hypha/apply/funds/tests/test_views.py b/hypha/apply/funds/tests/test_views.py index 75a397815..d5ac5d778 100644 --- a/hypha/apply/funds/tests/test_views.py +++ b/hypha/apply/funds/tests/test_views.py @@ -6,7 +6,7 @@ from django.conf import settings from django.contrib.auth.models import AnonymousUser from django.core.exceptions import PermissionDenied from django.http import Http404 -from django.test import RequestFactory, TestCase +from django.test import RequestFactory, TestCase, override_settings from django.urls import reverse from django.utils import timezone @@ -581,7 +581,7 @@ class TestStaffSubmissionView(BaseSubmissionViewTestCase): ) assert_view_determination_not_displayed(submission) - def test_cant_see_application_draft_status(self): + def test_staff_cant_see_application_draft_status(self): factory = RequestFactory() submission = ApplicationSubmissionFactory(status="draft") ProjectFactory(submission=submission) @@ -592,6 +592,18 @@ class TestStaffSubmissionView(BaseSubmissionViewTestCase): with self.assertRaises(Http404): SubmissionDetailView.as_view()(request, pk=submission.pk) + @override_settings(SUBMISSIONS_DRAFT_ACCESS_STAFF=True) + def test_staff_can_see_application_draft_status(self): + factory = RequestFactory() + submission = ApplicationSubmissionFactory(status="draft") + ProjectFactory(submission=submission) + + request = factory.get(f"/submission/{submission.pk}") + request.user = StaffFactory() + + response = SubmissionDetailView.as_view()(request, pk=submission.pk) + self.assertEqual(response.status_code, 200) + def test_applicant_can_see_application_draft_status(self): factory = RequestFactory() user = ApplicantFactory() diff --git a/hypha/apply/funds/views.py b/hypha/apply/funds/views.py index 81a2cef5c..c98626664 100644 --- a/hypha/apply/funds/views.py +++ b/hypha/apply/funds/views.py @@ -2,6 +2,7 @@ import csv from copy import copy from datetime import timedelta from io import StringIO +from typing import Generator, Tuple import django_tables2 as tables from django.conf import settings @@ -13,7 +14,14 @@ from django.contrib.auth.models import Group from django.contrib.humanize.templatetags.humanize import intcomma from django.core.exceptions import PermissionDenied from django.db.models import Count, F, Q -from django.http import FileResponse, Http404, HttpResponse, HttpResponseRedirect +from django.forms import BaseModelForm +from django.http import ( + FileResponse, + Http404, + HttpRequest, + HttpResponse, + HttpResponseRedirect, +) from django.shortcuts import get_object_or_404, render from django.urls import reverse_lazy from django.utils import timezone @@ -1297,6 +1305,39 @@ class BaseSubmissionEditView(UpdateView): model = ApplicationSubmission + @property + def transitions(self): + transitions = self.object.get_available_user_status_transitions( + self.request.user + ) + return {transition.name: transition for transition in transitions} + + 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", @@ -1308,15 +1349,99 @@ class BaseSubmissionEditView(UpdateView): raise PermissionDenied return super().dispatch(request, *args, **kwargs) - def buttons(self): + 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: + (<button type>, <button styling>, <button label>) + """ if settings.SUBMISSION_PREVIEW_REQUIRED: yield ("preview", "primary", _("Preview and submit")) yield ("save", "white", _("Save draft")) else: yield ("submit", "primary", _("Submit")) yield ("save", "white", _("Save draft")) - # TODO Fix preview bugs before reactivating. - # yield ("preview", "white", _("Preview")) + yield ("preview", "white", _("Preview")) + + def get_object_fund_current_round(self): + assigned_fund = self.object.round.get_parent().specific + if assigned_fund.open_round: + return assigned_fund.open_round + return False + + def form_valid(self, form: BaseModelForm) -> HttpResponse: + """Handle the form returned from a `SubmissionEditView`. + + Determine whether to return a form preview, draft the new edits, + or submit and transition the `ApplicationSubmission` object + + Args: + form: The valid form + + Returns: + An `HttpResponse` depending on the actions taken in the edit view + """ + + self.object.form_data = form.cleaned_data + + is_draft = self.object.status == DRAFT_STATE + + # Handle a preview or a save (aka a draft) + if "preview" in self.request.POST: + return self.render_preview(self.request, form) + + if "save" in self.request.POST: + return self.save_draft_and_refresh_page(form=form) + + # Handle an application being submitted from a DRAFT_STATE. This includes updating submit_time + if is_draft and "submit" in self.request.POST: + self.object.submit_time = timezone.now() + if self.object.round: + current_round = self.get_object_fund_current_round() + if current_round: + self.object.round = current_round + self.object.save(update_fields=["submit_time", "round"]) + + revision = self.object.create_revision(by=self.request.user) + submitting_proposal = self.object.phase.name in STAGE_CHANGE_ACTIONS + + if submitting_proposal: + messenger( + MESSAGES.PROPOSAL_SUBMITTED, + request=self.request, + user=self.request.user, + source=self.object, + ) + elif revision and not self.object.status == DRAFT_STATE: + messenger( + MESSAGES.APPLICANT_EDIT, + request=self.request, + user=self.request.user, + source=self.object, + related=revision, + ) + + action = set(self.request.POST.keys()) & set(self.transitions.keys()) + try: + transition = self.transitions[action.pop()] + except KeyError: + pass + else: + self.object.perform_transition( + transition.target, + self.request.user, + request=self.request, + notify=not (revision or submitting_proposal) + or self.object.status == DRAFT_STATE, # Use the other notification + ) + + # Required for django-file-form: delete temporary files for the new files + # uploaded while edit. + form.delete_temporary_files() + return HttpResponseRedirect(self.get_success_url()) def get_form_kwargs(self): """ @@ -1393,27 +1518,20 @@ class BaseSubmissionEditView(UpdateView): @method_decorator(staff_required, name="dispatch") class AdminSubmissionEditView(BaseSubmissionEditView): - def form_valid(self, form): - self.object.new_data(form.cleaned_data) + def buttons( + self, + ) -> Generator[Tuple[str, str, str], Tuple[str, str, str], Tuple[str, str, str]]: + """The buttons to be presented in the `AdminSubmissionEditView` - if "save" in self.request.POST: - return self.save_draft_and_refresh_page(form=form) + Admins shouldn't be required to preview, but should have the option. - if "submit" in self.request.POST: - revision = self.object.create_revision(by=self.request.user) - if revision: - messenger( - MESSAGES.EDIT_SUBMISSION, - request=self.request, - user=self.request.user, - source=self.object, - related=revision, - ) - - # Required for django-file-form: delete temporary files for the new files - # uploaded while edit. - form.delete_temporary_files() - return HttpResponseRedirect(self.get_success_url()) + Returns: + A generator returning a tuple strings in the format of: + (<button type>, <button styling>, <button label>) + """ + yield ("submit", "primary", _("Submit")) + yield ("save", "white", _("Save draft")) + yield ("preview", "white", _("Preview")) @method_decorator(login_required, name="dispatch") @@ -1424,79 +1542,6 @@ class ApplicantSubmissionEditView(BaseSubmissionEditView): raise PermissionDenied return super().dispatch(request, *args, **kwargs) - @property - def transitions(self): - transitions = self.object.get_available_user_status_transitions( - self.request.user - ) - return {transition.name: transition for transition in transitions} - - def get_object_fund_current_round(self): - assigned_fund = self.object.round.get_parent().specific - if assigned_fund.open_round: - return assigned_fund.open_round - return False - - def form_valid(self, form): - self.object.new_data(form.cleaned_data) - - # Update submit_time only when application is getting submitted from the Draft State for the first time. - if self.object.status == DRAFT_STATE and "submit" in self.request.POST: - self.object.submit_time = timezone.now() - if self.object.round: - current_round = self.get_object_fund_current_round() - if current_round: - self.object.round = current_round - self.object.save(update_fields=["submit_time", "round"]) - - if self.object.status == DRAFT_STATE and "preview" in self.request.POST: - self.object.create_revision(draft=True, by=self.request.user) - form.delete_temporary_files() - # messages.success(self.request, _("Draft saved")) - context = self.get_context_data() - return render(self.request, "funds/application_preview.html", context) - - if "save" in self.request.POST: - return self.save_draft_and_refresh_page(form=form) - - revision = self.object.create_revision(by=self.request.user) - submitting_proposal = self.object.phase.name in STAGE_CHANGE_ACTIONS - - if submitting_proposal: - messenger( - MESSAGES.PROPOSAL_SUBMITTED, - request=self.request, - user=self.request.user, - source=self.object, - ) - elif revision and not self.object.status == DRAFT_STATE: - messenger( - MESSAGES.APPLICANT_EDIT, - request=self.request, - user=self.request.user, - source=self.object, - related=revision, - ) - - action = set(self.request.POST.keys()) & set(self.transitions.keys()) - try: - transition = self.transitions[action.pop()] - except KeyError: - pass - else: - self.object.perform_transition( - transition.target, - self.request.user, - request=self.request, - notify=not (revision or submitting_proposal) - or self.object.status == DRAFT_STATE, # Use the other notification - ) - - # Required for django-file-form: delete temporary files for the new files - # uploaded while edit. - form.delete_temporary_files() - return HttpResponseRedirect(self.get_success_url()) - @method_decorator(login_required, name="dispatch") class PartnerSubmissionEditView(ApplicantSubmissionEditView): @@ -1524,15 +1569,31 @@ class RevisionListView(ListView): model = ApplicationRevision def get_queryset(self): + """Get a queryset of all valid `ApplicationRevision`s that can be + compared for the current submission + + This excludes draft & preview revisions unless draft(s) are the only + existing revisions, in which the last draft will be returned in a QuerySet + + Returns: + An [`ApplicationRevision`][hypha.apply.funds.models.ApplicationRevision] QuerySet + """ self.submission = get_object_or_404( ApplicationSubmission, id=self.kwargs["submission_pk"] ) - self.queryset = self.model.objects.filter( - submission=self.submission, - ).exclude( - draft__isnull=False, - live__isnull=True, + revisions = self.model.objects.filter(submission=self.submission).exclude( + draft__isnull=False, live__isnull=True ) + + filtered_revisions = revisions.filter(is_draft=False) + + # An edge case for when an instance has `SUBMISSIONS_DRAFT_ACCESS_STAFF=True` + # and a staff member tries to view the revisions of the draft. + if len(filtered_revisions) < 1: + self.queryset = self.model.objects.filter(id=revisions.last().id) + else: + self.queryset = filtered_revisions + return super().get_queryset() def get_context_data(self, **kwargs): diff --git a/hypha/settings/base.py b/hypha/settings/base.py index 908b37b78..ff0c798c3 100644 --- a/hypha/settings/base.py +++ b/hypha/settings/base.py @@ -148,8 +148,7 @@ TRANSITION_AFTER_REVIEWS = env.bool("TRANSITION_AFTER_REVIEWS", False) REVIEW_VISIBILITY_DEFAULT = env.str("REVIEW_VISIBILITY_DEFAULT", "private") # Require an applicant to view their rendered application before submitting -# TODO Fix preview bugs before setting this to True as default. -SUBMISSION_PREVIEW_REQUIRED = env.bool("SUBMISSION_PREVIEW_REQUIRED", False) +SUBMISSION_PREVIEW_REQUIRED = env.bool("SUBMISSION_PREVIEW_REQUIRED", True) # Project settings. diff --git a/hypha/static_src/sass/components/_revision.scss b/hypha/static_src/sass/components/_revision.scss index 9a0682b09..d675828ee 100644 --- a/hypha/static_src/sass/components/_revision.scss +++ b/hypha/static_src/sass/components/_revision.scss @@ -32,7 +32,8 @@ } &__date, - &__current { + &__current, + &__draft { font-weight: $weight--semibold; } } -- GitLab