From 3463cc103823473aab609d7f10aa56bf7986cfeb Mon Sep 17 00:00:00 2001
From: Fredrik Jonsson <frjo@xdeb.org>
Date: Wed, 1 May 2024 08:12:33 +0200
Subject: [PATCH] Project Reports as stream field forms (#3611)

With `PROJECTS_ENABLED = True`, Project Report Forms may now be
created in Wagtail Admin and associated with a Fund.

Project reports use the form configured in Wagtail admin. To add or
edit a report form, use `Projects -> Report Forms`. To bind a report
form to a given Fund, use `Apply -> Funds`, set `Project Report Form`.

When projects are enabled, a project shows the Reports in various
project interfaces. A new project report will use the current fields
from the Project Report Form associated with the Fund from which the
Submission/Application was granted. An existing report, when edited,
will use the original fields at the time that report was first filed.
This behavior appears consistent with at least one other similar form.

Thanks to Frank Duncan for the "View" functionality in
hypha/apply/projects/templates/application_projects/report_detail.html
and NonFileFormFieldsBlockFactory for tests that don't need files
as well as re-adding the url for project file lookup by id.

Co-authored-by: Frank Duncan <frankduncan@opentechstrategies.com>

---------

Co-authored-by: Jesse Bickel <bickelj@gmail.com>
Co-authored-by: Frank Duncan <frankduncan@opentechstrategies.com>
---
 hypha/apply/funds/files.py                    |   55 +-
 .../0118_labbaseprojectreportform_and_more.py |   89 ++
 hypha/apply/funds/models/forms.py             |   38 +
 hypha/apply/funds/models/mixins.py            |    4 +-
 hypha/apply/funds/models/utils.py             |    2 +
 hypha/apply/funds/tests/test_admin_form.py    |   15 +-
 hypha/apply/funds/views.py                    |    4 +-
 hypha/apply/projects/admin.py                 |   21 +
 hypha/apply/projects/admin_views.py           |    8 +
 hypha/apply/projects/blocks.py                |    4 +-
 hypha/apply/projects/forms/report.py          |  104 +-
 .../0082_projectreportform_and_more.py        | 1423 +++++++++++++++++
 hypha/apply/projects/models/__init__.py       |    2 +
 hypha/apply/projects/models/project.py        |   22 +-
 hypha/apply/projects/models/report.py         |   15 +-
 .../application_projects/report_detail.html   |   12 +-
 .../application_projects/report_form.html     |   16 +-
 hypha/apply/projects/tests/factories.py       |   22 +-
 hypha/apply/projects/tests/test_views.py      |  244 ++-
 hypha/apply/projects/urls.py                  |    7 +-
 hypha/apply/projects/utils.py                 |   12 +
 hypha/apply/projects/views/project.py         |   44 +-
 hypha/apply/projects/views/report.py          |   96 +-
 hypha/apply/stream_forms/testing/factories.py |   11 +-
 24 files changed, 2094 insertions(+), 176 deletions(-)
 create mode 100644 hypha/apply/funds/migrations/0118_labbaseprojectreportform_and_more.py
 create mode 100644 hypha/apply/projects/migrations/0082_projectreportform_and_more.py

diff --git a/hypha/apply/funds/files.py b/hypha/apply/funds/files.py
index 27b8fcba7..0c4a263c3 100644
--- a/hypha/apply/funds/files.py
+++ b/hypha/apply/funds/files.py
@@ -5,36 +5,57 @@ from django.urls import reverse
 from hypha.apply.stream_forms.files import StreamFieldFile
 
 
-def generate_submission_file_path(submission_id, field_id, file_name):
-    path = os.path.join("submission", str(submission_id), str(field_id))
+def generate_private_file_path(entity_id, field_id, file_name, path_start="submission"):
+    path = os.path.join(path_start, str(entity_id), str(field_id))
     if file_name.startswith(path):
         return file_name
 
     return os.path.join(path, file_name)
 
 
-class SubmissionStreamFieldFile(StreamFieldFile):
-    def get_submission_id(self):
+class PrivateStreamFieldFile(StreamFieldFile):
+    """
+    Represents a file from a Wagtail/Hypha Stream Form block.
+    Aside: with imports in their usual place, there could be circular imports. Deferring or delaying import to methods
+    resolves the issue.
+    """
+
+    def get_entity_id(self):
         from hypha.apply.funds.models import ApplicationRevision
+        from hypha.apply.projects.models import ReportVersion
 
-        submission_id = self.instance.pk
+        entity_id = self.instance.pk
 
         if isinstance(self.instance, ApplicationRevision):
-            submission_id = self.instance.submission.pk
-        return submission_id
+            entity_id = self.instance.submission.pk
+        elif isinstance(self.instance, ReportVersion):
+            # Reports are project documents.
+            entity_id = self.instance.report.project.pk
+        return entity_id
 
     def generate_filename(self):
-        return generate_submission_file_path(
-            self.get_submission_id(), self.field.id, self.name
+        from hypha.apply.projects.models import ReportVersion
+
+        path_start = "submission"
+        if isinstance(self.instance, ReportVersion):
+            path_start = "project"
+        return generate_private_file_path(
+            self.get_entity_id(),
+            self.field.id,
+            self.name,
+            path_start=path_start,
         )
 
     @property
     def url(self):
-        return reverse(
-            "apply:submissions:serve_private_media",
-            kwargs={
-                "pk": self.get_submission_id(),
-                "field_id": self.field.id,
-                "file_name": self.basename,
-            },
-        )
+        from hypha.apply.projects.models import ReportVersion
+
+        view_name = "apply:submissions:serve_private_media"
+        kwargs = {
+            "pk": self.get_entity_id(),
+            "field_id": self.field.id,
+            "file_name": self.basename,
+        }
+        if isinstance(self.instance, ReportVersion):
+            view_name = "apply:projects:document"
+        return reverse(viewname=view_name, kwargs=kwargs)
diff --git a/hypha/apply/funds/migrations/0118_labbaseprojectreportform_and_more.py b/hypha/apply/funds/migrations/0118_labbaseprojectreportform_and_more.py
new file mode 100644
index 000000000..802db4a88
--- /dev/null
+++ b/hypha/apply/funds/migrations/0118_labbaseprojectreportform_and_more.py
@@ -0,0 +1,89 @@
+# Generated by Django 4.2.11 on 2024-04-17 20:38
+
+from django.db import migrations, models
+import django.db.models.deletion
+import modelcluster.fields
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("application_projects", "0082_projectreportform_and_more"),
+        ("funds", "0117_applicationrevision_is_draft"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="LabBaseProjectReportForm",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "sort_order",
+                    models.IntegerField(blank=True, editable=False, null=True),
+                ),
+                (
+                    "form",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.PROTECT,
+                        to="application_projects.projectreportform",
+                    ),
+                ),
+                (
+                    "lab",
+                    modelcluster.fields.ParentalKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="report_forms",
+                        to="funds.labbase",
+                    ),
+                ),
+            ],
+            options={
+                "ordering": ["sort_order"],
+                "abstract": False,
+            },
+        ),
+        migrations.CreateModel(
+            name="ApplicationBaseProjectReportForm",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "sort_order",
+                    models.IntegerField(blank=True, editable=False, null=True),
+                ),
+                (
+                    "application",
+                    modelcluster.fields.ParentalKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="report_forms",
+                        to="funds.applicationbase",
+                    ),
+                ),
+                (
+                    "form",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.PROTECT,
+                        to="application_projects.projectreportform",
+                    ),
+                ),
+            ],
+            options={
+                "ordering": ["sort_order"],
+                "abstract": False,
+            },
+        ),
+    ]
diff --git a/hypha/apply/funds/models/forms.py b/hypha/apply/funds/models/forms.py
index 39ad01c0d..92a5f9e7f 100644
--- a/hypha/apply/funds/models/forms.py
+++ b/hypha/apply/funds/models/forms.py
@@ -4,6 +4,7 @@ from wagtail.admin.panels import FieldPanel
 from wagtail.fields import StreamField
 from wagtail.models import Orderable
 
+from ...projects.models.project import ProjectReportForm
 from ..blocks import ApplicationCustomFormFieldsBlock
 from ..edit_handlers import FilteredFieldPanel
 
@@ -268,3 +269,40 @@ class LabBaseProjectApprovalForm(AbstractRelatedProjectApprovalForm):
 
 class LabBaseProjectSOWForm(AbstractRelatedProjectSOWForm):
     lab = ParentalKey("LabBase", related_name="sow_forms")
+
+
+class AbstractRelatedProjectReportForm(Orderable):
+    class Meta(Orderable.Meta):
+        abstract = True
+
+    form = models.ForeignKey(to=ProjectReportForm, on_delete=models.PROTECT)
+
+    @property
+    def fields(self):
+        return self.form.form_fields
+
+    def __eq__(self, other):
+        try:
+            if self.fields == other.fields and self.sort_order == other.sort_order:
+                # If the objects are saved to db. pk should also be compared
+                if hasattr(other, "pk") and hasattr(self, "pk"):
+                    return self.pk == other.pk
+                return True
+            return False
+        except AttributeError:
+            return False
+
+    def __hash__(self):
+        fields = [field.id for field in self.fields]
+        return hash((tuple(fields), self.sort_order, self.pk))
+
+    def __str__(self):
+        return self.form.name
+
+
+class ApplicationBaseProjectReportForm(AbstractRelatedProjectReportForm):
+    application = ParentalKey("ApplicationBase", related_name="report_forms")
+
+
+class LabBaseProjectReportForm(AbstractRelatedProjectReportForm):
+    lab = ParentalKey("LabBase", related_name="report_forms")
diff --git a/hypha/apply/funds/models/mixins.py b/hypha/apply/funds/models/mixins.py
index 1d7aae6fe..b6479d3fd 100644
--- a/hypha/apply/funds/models/mixins.py
+++ b/hypha/apply/funds/models/mixins.py
@@ -14,7 +14,7 @@ from hypha.apply.stream_forms.blocks import (
 from hypha.apply.utils.blocks import SingleIncludeMixin
 from hypha.apply.utils.storage import PrivateStorage
 
-from ..files import SubmissionStreamFieldFile
+from ..files import PrivateStreamFieldFile
 
 __all__ = ["AccessFormData"]
 
@@ -31,7 +31,7 @@ class AccessFormData:
          - form_fields > streamfield containing the original form fields
     """
 
-    stream_file_class = SubmissionStreamFieldFile
+    stream_file_class = PrivateStreamFieldFile
     storage_class = PrivateStorage
 
     @property
diff --git a/hypha/apply/funds/models/utils.py b/hypha/apply/funds/models/utils.py
index dd52fbe33..b8ead6652 100644
--- a/hypha/apply/funds/models/utils.py
+++ b/hypha/apply/funds/models/utils.py
@@ -146,6 +146,8 @@ class WorkflowStreamForm(WorkflowHelpers, AbstractStreamForm):  # type: ignore
         InlinePanel("determination_forms", label=_("Determination Forms")),
         InlinePanel("approval_forms", label=_("Project Approval Form"), max_num=1),
         InlinePanel("sow_forms", label=_("Project SOW Form"), max_num=1),
+        # The models technically allow for multiple Report forms but to start we permit only one in the UIs.
+        InlinePanel("report_forms", label=_("Project Report Form"), max_num=1),
     ]
 
 
diff --git a/hypha/apply/funds/tests/test_admin_form.py b/hypha/apply/funds/tests/test_admin_form.py
index f2f147a9d..f484492c7 100644
--- a/hypha/apply/funds/tests/test_admin_form.py
+++ b/hypha/apply/funds/tests/test_admin_form.py
@@ -5,6 +5,7 @@ from hypha.apply.determinations.tests.factories import DeterminationFormFactory
 from hypha.apply.funds.models import FundType
 from hypha.apply.projects.tests.factories import (
     ProjectApprovalFormFactory,
+    ProjectReportFormFactory,
     ProjectSOWFormFactory,
 )
 from hypha.apply.review.tests.factories import ReviewFormFactory
@@ -55,6 +56,7 @@ def form_data(
     stages=1,
     same_forms=False,
     form_stage_info=None,
+    num_project_report_forms=0,
 ):
     if form_stage_info is None:
         form_stage_info = [1]
@@ -101,12 +103,19 @@ def form_data(
         same=same_forms,
         factory=ProjectSOWFormFactory,
     )
-
+    project_report_form_data = formset_base(
+        "report_forms",
+        num_project_report_forms,
+        False,
+        same=same_forms,
+        factory=ProjectReportFormFactory,
+    )
     form_data.update(review_form_data)
     form_data.update(external_review_form_data)
     form_data.update(determination_form_data)
     form_data.update(project_approval_form_data)
     form_data.update(project_sow_form_data)
+    form_data.update(project_report_form_data)
 
     fund_data = factory.build(dict, FACTORY_CLASS=FundTypeFactory)
     fund_data["workflow_name"] = workflow_for_stages(stages)
@@ -232,3 +241,7 @@ class TestWorkflowFormAdminForm(TestCase):
             form_data(1, 1, 1, 0, num_project_approval_form=2, stages=2)
         )
         self.assertFalse(form.is_valid(), form.errors.as_text())
+
+    def test_validates_with_project_report_form(self):
+        form = self.submit_data(form_data(1, 1, 1, 0, 1, 0, 0, 1, False, None, 1))
+        self.assertTrue(form.is_valid(), form.errors.as_text())
diff --git a/hypha/apply/funds/views.py b/hypha/apply/funds/views.py
index 0b60e99d2..51eff4583 100644
--- a/hypha/apply/funds/views.py
+++ b/hypha/apply/funds/views.py
@@ -76,7 +76,7 @@ from hypha.apply.utils.views import (
 
 from . import services
 from .differ import compare
-from .files import generate_submission_file_path
+from .files import generate_private_file_path
 from .forms import (
     ArchiveSubmissionForm,
     BatchArchiveSubmissionForm,
@@ -1701,7 +1701,7 @@ class SubmissionPrivateMediaView(UserPassesTestMixin, PrivateMediaView):
     def get_media(self, *args, **kwargs):
         field_id = kwargs["field_id"]
         file_name = kwargs["file_name"]
-        path_to_file = generate_submission_file_path(
+        path_to_file = generate_private_file_path(
             self.submission.pk, field_id, file_name
         )
         return self.storage.open(path_to_file)
diff --git a/hypha/apply/projects/admin.py b/hypha/apply/projects/admin.py
index 6a3b58fed..0a46204ae 100644
--- a/hypha/apply/projects/admin.py
+++ b/hypha/apply/projects/admin.py
@@ -5,14 +5,17 @@ from hypha.core.wagtail.admin import SettingModelAdmin
 
 from .admin_views import (
     CreateProjectApprovalFormView,
+    CreateProjectReportFormView,
     CreateProjectSOWFormView,
     EditProjectApprovalFormView,
+    EditProjectReportFormView,
     EditProjectSOWFormView,
 )
 from .models import (
     ContractDocumentCategory,
     DocumentCategory,
     ProjectApprovalForm,
+    ProjectReportForm,
     ProjectSettings,
     ProjectSOWForm,
     VendorFormSettings,
@@ -71,6 +74,23 @@ class ProjectSOWFormAdmin(ListRelatedMixin, ModelAdmin):
     ]
 
 
+class ProjectReportFormAdmin(ListRelatedMixin, ModelAdmin):
+    model = ProjectReportForm
+    menu_label = "Report Forms"
+    menu_icon = "form"
+    list_display = (
+        "name",
+        "used_by",
+    )
+    create_view_class = CreateProjectReportFormView
+    edit_view_class = EditProjectReportFormView
+
+    related_models = [
+        ("applicationbaseprojectreportform", "application"),
+        ("labbaseprojectreportform", "lab"),
+    ]
+
+
 class ProjectSettingsAdmin(SettingModelAdmin):
     model = ProjectSettings
 
@@ -86,6 +106,7 @@ class ProjectAdminGroup(ModelAdminGroup):
         ContractDocumentCategoryAdmin,
         DocumentCategoryAdmin,
         ProjectApprovalFormAdmin,
+        ProjectReportFormAdmin,
         ProjectSOWFormAdmin,
         VendorFormSettingsAdmin,
         ProjectSettingsAdmin,
diff --git a/hypha/apply/projects/admin_views.py b/hypha/apply/projects/admin_views.py
index 5dcc4c04d..4bdaa7a80 100644
--- a/hypha/apply/projects/admin_views.py
+++ b/hypha/apply/projects/admin_views.py
@@ -21,3 +21,11 @@ class CreateProjectSOWFormView(CreateProjectApprovalFormView):
 
 class EditProjectSOWFormView(EditProjectApprovalFormView):
     pass
+
+
+class CreateProjectReportFormView(CreateProjectApprovalFormView):
+    pass
+
+
+class EditProjectReportFormView(EditProjectApprovalFormView):
+    pass
diff --git a/hypha/apply/projects/blocks.py b/hypha/apply/projects/blocks.py
index c5378ff36..a8207a333 100644
--- a/hypha/apply/projects/blocks.py
+++ b/hypha/apply/projects/blocks.py
@@ -2,5 +2,7 @@ from hypha.apply.stream_forms.blocks import FormFieldsBlock
 from hypha.apply.utils.blocks import CustomFormFieldsBlock
 
 
-class ProjectApprovalFormCustomFormFieldsBlock(CustomFormFieldsBlock, FormFieldsBlock):
+class ProjectFormCustomFormFieldsBlock(CustomFormFieldsBlock, FormFieldsBlock):
+    """A block that can be used for customizable Project-related forms: PAF, SOW, and Report."""
+
     pass
diff --git a/hypha/apply/projects/forms/report.py b/hypha/apply/projects/forms/report.py
index 917ae5824..f63ad8b91 100644
--- a/hypha/apply/projects/forms/report.py
+++ b/hypha/apply/projects/forms/report.py
@@ -1,76 +1,73 @@
 from django import forms
 from django.db import transaction
 from django.utils import timezone
-from django.utils.translation import gettext_lazy as _
-from django_file_form.forms import FileFormMixin
 
-from hypha.apply.stream_forms.fields import MultiFileField
-from hypha.apply.utils.fields import RichTextField
+from ...review.forms import MixedMetaClass
+from ...stream_forms.forms import StreamBaseForm
+from ..models.report import Report, ReportConfig, ReportVersion
 
-from ..models.report import Report, ReportConfig, ReportPrivateFiles, ReportVersion
-
-
-class ReportEditForm(FileFormMixin, forms.ModelForm):
-    public_content = RichTextField(
-        help_text=_(
-            "This section of the report will be shared with the broader community."
-        )
-    )
-    private_content = RichTextField(
-        help_text=_("This section of the report will be shared with staff only.")
-    )
-    file_list = forms.ModelMultipleChoiceField(
-        widget=forms.CheckboxSelectMultiple(attrs={"class": "delete"}),
-        queryset=ReportPrivateFiles.objects.all(),
-        required=False,
-        label=_("Files"),
-    )
-    files = MultiFileField(required=False, label="")
 
+class ReportEditForm(StreamBaseForm, forms.ModelForm, metaclass=MixedMetaClass):
     class Meta:
         model = Report
-        fields = (
-            "public_content",
-            "private_content",
-            "file_list",
-            "files",
-        )
+        fields: list = []
 
     def __init__(self, *args, user=None, initial=None, **kwargs):
         if initial is None:
             initial = {}
-        self.report_files = initial.pop(
-            "file_list",
-            ReportPrivateFiles.objects.none(),
-        )
+        # Need to populate form_fields, right?
+        # No: The form_fields got populated from the view which instantiated this Form.
+        # Yes: they don't seem to be here.
+        # No: this is not where the magic happens.
+        # self.form_fields = kwargs.pop("form_fields", {})
+        # Need to populate form_data, right? Yes. No. IDK. Appears no.
+        # self.form_data = kwargs.pop("form_data", {})
+        # OK, both yes and no. If there is an existing value it will come via "initial", so if present there, use them.
+        # if initial["form_fields"] is not None:
+        #     self.form_fields = initial["form_fields"]
+        # if initial["form_data"] is not None:
+        #     self.form_data = initial["form_data"]
+        # But this should not be needed because super().__init__ will already take these initial values and use them.
         super().__init__(*args, initial=initial, **kwargs)
-        self.fields["file_list"].queryset = self.report_files
         self.user = user
 
     def clean(self):
         cleaned_data = super().clean()
-        public = cleaned_data["public_content"]
-        private = cleaned_data["private_content"]
-        if not private and not public:
-            missing_content = _(
-                "Must include either public or private content when submitting a report."
-            )
-            self.add_error("public_content", missing_content)
-            self.add_error("private_content", missing_content)
+        cleaned_data["form_data"] = {
+            key: value
+            for key, value in cleaned_data.items()
+            if key not in self._meta.fields
+        }
         return cleaned_data
 
     @transaction.atomic
-    def save(self, commit=True):
+    def save(self, commit=True, form_fields=dict):
         is_draft = "save" in self.data
-
         version = ReportVersion.objects.create(
             report=self.instance,
-            public_content=self.cleaned_data["public_content"],
-            private_content=self.cleaned_data["private_content"],
+            form_fields=form_fields,
+            # Save a ReportVersion first then edit/update the form_data below.
+            form_data={},
             submitted=timezone.now(),
             draft=is_draft,
             author=self.user,
         )
+        # We need to save the fields first, not attempt to save form_data on first save, then update the form_data next.
+        # Otherwise, we don't get access to the generator method "question_field_ids" which we use to prevent temp file
+        # fields from getting into the saved form_data.
+        # Inspired by ProjectApprovalForm.save and ProjectSOWForm.save but enhanced to support multi-answer fields.
+        version.form_data = {
+            field: self.cleaned_data["form_data"][field]
+            for field in self.cleaned_data["form_data"]
+            # Where do we get question_field_ids? On the version, but only when it exists, thus the create-then-update.
+            # The split-on-underscore supports the use of multi-answer fields such as MultiInputCharFieldBlock.
+            if field.split("_")[0] in version.question_field_ids
+        }
+
+        # In case there are stream form file fields, process those here.
+        version.process_file_data(self.cleaned_data["form_data"])
+        # Because ReportVersion is a separate entity from Project, super().save will not save ReportVersion: save here.
+        version.save()
 
         if is_draft:
             self.instance.draft = version
@@ -83,21 +80,6 @@ class ReportEditForm(FileFormMixin, forms.ModelForm):
             self.instance.draft = None
 
         instance = super().save(commit)
-
-        removed_files = self.cleaned_data["file_list"]
-        ReportPrivateFiles.objects.bulk_create(
-            ReportPrivateFiles(report=version, document=file.document)
-            for file in self.report_files
-            if file not in removed_files
-        )
-
-        added_files = self.cleaned_data["files"]
-        if added_files:
-            ReportPrivateFiles.objects.bulk_create(
-                ReportPrivateFiles(report=version, document=file)
-                for file in added_files
-            )
-
         return instance
 
 
diff --git a/hypha/apply/projects/migrations/0082_projectreportform_and_more.py b/hypha/apply/projects/migrations/0082_projectreportform_and_more.py
new file mode 100644
index 000000000..37535ccaf
--- /dev/null
+++ b/hypha/apply/projects/migrations/0082_projectreportform_and_more.py
@@ -0,0 +1,1423 @@
+# Generated by Django 4.2.11 on 2024-04-05 16:30
+
+from django.db import migrations, models
+import hypha.apply.stream_forms.blocks
+import hypha.apply.stream_forms.files
+import hypha.apply.stream_forms.models
+import wagtail.blocks
+import wagtail.fields
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("application_projects", "0081_alter_project_value"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="ProjectReportForm",
+            fields=[
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=255)),
+                (
+                    "form_fields",
+                    wagtail.fields.StreamField(
+                        [
+                            (
+                                "text_markup",
+                                wagtail.blocks.RichTextBlock(
+                                    group="Custom", label="Paragraph"
+                                ),
+                            ),
+                            (
+                                "header_markup",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "heading_text",
+                                            wagtail.blocks.CharBlock(
+                                                form_classname="title", required=True
+                                            ),
+                                        ),
+                                        (
+                                            "size",
+                                            wagtail.blocks.ChoiceBlock(
+                                                choices=[
+                                                    ("h2", "H2"),
+                                                    ("h3", "H3"),
+                                                    ("h4", "H4"),
+                                                ]
+                                            ),
+                                        ),
+                                    ],
+                                    group="Custom",
+                                    label="Section header",
+                                ),
+                            ),
+                            (
+                                "char",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "format",
+                                            wagtail.blocks.ChoiceBlock(
+                                                choices=[
+                                                    ("email", "Email"),
+                                                    ("url", "URL"),
+                                                ],
+                                                label="Format",
+                                                required=False,
+                                            ),
+                                        ),
+                                        (
+                                            "default_value",
+                                            wagtail.blocks.CharBlock(
+                                                label="Default value", required=False
+                                            ),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "multi_inputs_char",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "format",
+                                            wagtail.blocks.ChoiceBlock(
+                                                choices=[
+                                                    ("email", "Email"),
+                                                    ("url", "URL"),
+                                                ],
+                                                label="Format",
+                                                required=False,
+                                            ),
+                                        ),
+                                        (
+                                            "default_value",
+                                            wagtail.blocks.CharBlock(
+                                                label="Default value", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "number_of_inputs",
+                                            wagtail.blocks.IntegerBlock(
+                                                default=2, label="Max number of inputs"
+                                            ),
+                                        ),
+                                        (
+                                            "add_button_text",
+                                            wagtail.blocks.CharBlock(
+                                                default="Add new item", required=False
+                                            ),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "text",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "default_value",
+                                            wagtail.blocks.TextBlock(
+                                                label="Default value", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "word_limit",
+                                            wagtail.blocks.IntegerBlock(
+                                                default=1000, label="Word limit"
+                                            ),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "number",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "default_value",
+                                            wagtail.blocks.CharBlock(
+                                                label="Default value", required=False
+                                            ),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "checkbox",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "default_value",
+                                            wagtail.blocks.BooleanBlock(required=False),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "radios",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "choices",
+                                            wagtail.blocks.ListBlock(
+                                                wagtail.blocks.CharBlock(label="Choice")
+                                            ),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "dropdown",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "choices",
+                                            wagtail.blocks.ListBlock(
+                                                wagtail.blocks.CharBlock(label="Choice")
+                                            ),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "checkboxes",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "checkboxes",
+                                            wagtail.blocks.ListBlock(
+                                                wagtail.blocks.CharBlock(
+                                                    label="Checkbox"
+                                                )
+                                            ),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "date",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "default_value",
+                                            wagtail.blocks.DateBlock(required=False),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "time",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "default_value",
+                                            wagtail.blocks.TimeBlock(required=False),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "datetime",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "default_value",
+                                            wagtail.blocks.DateTimeBlock(
+                                                required=False
+                                            ),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "image",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "file",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "multi_file",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "group_toggle",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                default=True,
+                                                label="Required",
+                                                required=False,
+                                            ),
+                                        ),
+                                        (
+                                            "choices",
+                                            wagtail.blocks.ListBlock(
+                                                wagtail.blocks.CharBlock(
+                                                    label="Choice"
+                                                ),
+                                                help_text="Please create only two choices for toggle. First choice will revel the group and the second hide it. Additional choices will be ignored.",
+                                            ),
+                                        ),
+                                    ],
+                                    group="Custom",
+                                ),
+                            ),
+                            (
+                                "group_toggle_end",
+                                hypha.apply.stream_forms.blocks.GroupToggleEndBlock(
+                                    group="Custom"
+                                ),
+                            ),
+                            (
+                                "rich_text",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "default_value",
+                                            wagtail.blocks.TextBlock(
+                                                label="Default value", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "word_limit",
+                                            wagtail.blocks.IntegerBlock(
+                                                default=1000, label="Word limit"
+                                            ),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                            (
+                                "markdown_text",
+                                wagtail.blocks.StructBlock(
+                                    [
+                                        (
+                                            "field_label",
+                                            wagtail.blocks.CharBlock(label="Label"),
+                                        ),
+                                        (
+                                            "help_text",
+                                            wagtail.blocks.TextBlock(
+                                                label="Help text", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "help_link",
+                                            wagtail.blocks.URLBlock(
+                                                label="Help link", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "required",
+                                            wagtail.blocks.BooleanBlock(
+                                                label="Required", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "default_value",
+                                            wagtail.blocks.TextBlock(
+                                                label="Default value", required=False
+                                            ),
+                                        ),
+                                        (
+                                            "word_limit",
+                                            wagtail.blocks.IntegerBlock(
+                                                default=1000, label="Word limit"
+                                            ),
+                                        ),
+                                    ],
+                                    group="Fields",
+                                ),
+                            ),
+                        ],
+                        use_json_field=True,
+                    ),
+                ),
+            ],
+            options={
+                "abstract": False,
+            },
+            bases=(hypha.apply.stream_forms.models.BaseStreamForm, models.Model),
+        ),
+        migrations.RemoveField(
+            model_name="reportversion",
+            name="private_content",
+        ),
+        migrations.RemoveField(
+            model_name="reportversion",
+            name="public_content",
+        ),
+        migrations.AddField(
+            model_name="reportversion",
+            name="form_data",
+            field=models.JSONField(
+                default=dict,
+                encoder=hypha.apply.stream_forms.files.StreamFieldDataEncoder,
+            ),
+        ),
+        migrations.AddField(
+            model_name="reportversion",
+            name="form_fields",
+            field=wagtail.fields.StreamField(
+                [
+                    (
+                        "text_markup",
+                        wagtail.blocks.RichTextBlock(group="Custom", label="Paragraph"),
+                    ),
+                    (
+                        "header_markup",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "heading_text",
+                                    wagtail.blocks.CharBlock(
+                                        form_classname="title", required=True
+                                    ),
+                                ),
+                                (
+                                    "size",
+                                    wagtail.blocks.ChoiceBlock(
+                                        choices=[
+                                            ("h2", "H2"),
+                                            ("h3", "H3"),
+                                            ("h4", "H4"),
+                                        ]
+                                    ),
+                                ),
+                            ],
+                            group="Custom",
+                            label="Section header",
+                        ),
+                    ),
+                    (
+                        "char",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "format",
+                                    wagtail.blocks.ChoiceBlock(
+                                        choices=[("email", "Email"), ("url", "URL")],
+                                        label="Format",
+                                        required=False,
+                                    ),
+                                ),
+                                (
+                                    "default_value",
+                                    wagtail.blocks.CharBlock(
+                                        label="Default value", required=False
+                                    ),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "multi_inputs_char",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "format",
+                                    wagtail.blocks.ChoiceBlock(
+                                        choices=[("email", "Email"), ("url", "URL")],
+                                        label="Format",
+                                        required=False,
+                                    ),
+                                ),
+                                (
+                                    "default_value",
+                                    wagtail.blocks.CharBlock(
+                                        label="Default value", required=False
+                                    ),
+                                ),
+                                (
+                                    "number_of_inputs",
+                                    wagtail.blocks.IntegerBlock(
+                                        default=2, label="Max number of inputs"
+                                    ),
+                                ),
+                                (
+                                    "add_button_text",
+                                    wagtail.blocks.CharBlock(
+                                        default="Add new item", required=False
+                                    ),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "text",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "default_value",
+                                    wagtail.blocks.TextBlock(
+                                        label="Default value", required=False
+                                    ),
+                                ),
+                                (
+                                    "word_limit",
+                                    wagtail.blocks.IntegerBlock(
+                                        default=1000, label="Word limit"
+                                    ),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "number",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "default_value",
+                                    wagtail.blocks.CharBlock(
+                                        label="Default value", required=False
+                                    ),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "checkbox",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "default_value",
+                                    wagtail.blocks.BooleanBlock(required=False),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "radios",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "choices",
+                                    wagtail.blocks.ListBlock(
+                                        wagtail.blocks.CharBlock(label="Choice")
+                                    ),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "dropdown",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "choices",
+                                    wagtail.blocks.ListBlock(
+                                        wagtail.blocks.CharBlock(label="Choice")
+                                    ),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "checkboxes",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "checkboxes",
+                                    wagtail.blocks.ListBlock(
+                                        wagtail.blocks.CharBlock(label="Checkbox")
+                                    ),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "date",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "default_value",
+                                    wagtail.blocks.DateBlock(required=False),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "time",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "default_value",
+                                    wagtail.blocks.TimeBlock(required=False),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "datetime",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "default_value",
+                                    wagtail.blocks.DateTimeBlock(required=False),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "image",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "file",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "multi_file",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "group_toggle",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        default=True, label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "choices",
+                                    wagtail.blocks.ListBlock(
+                                        wagtail.blocks.CharBlock(label="Choice"),
+                                        help_text="Please create only two choices for toggle. First choice will revel the group and the second hide it. Additional choices will be ignored.",
+                                    ),
+                                ),
+                            ],
+                            group="Custom",
+                        ),
+                    ),
+                    (
+                        "group_toggle_end",
+                        hypha.apply.stream_forms.blocks.GroupToggleEndBlock(
+                            group="Custom"
+                        ),
+                    ),
+                    (
+                        "rich_text",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "default_value",
+                                    wagtail.blocks.TextBlock(
+                                        label="Default value", required=False
+                                    ),
+                                ),
+                                (
+                                    "word_limit",
+                                    wagtail.blocks.IntegerBlock(
+                                        default=1000, label="Word limit"
+                                    ),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                    (
+                        "markdown_text",
+                        wagtail.blocks.StructBlock(
+                            [
+                                (
+                                    "field_label",
+                                    wagtail.blocks.CharBlock(label="Label"),
+                                ),
+                                (
+                                    "help_text",
+                                    wagtail.blocks.TextBlock(
+                                        label="Help text", required=False
+                                    ),
+                                ),
+                                (
+                                    "help_link",
+                                    wagtail.blocks.URLBlock(
+                                        label="Help link", required=False
+                                    ),
+                                ),
+                                (
+                                    "required",
+                                    wagtail.blocks.BooleanBlock(
+                                        label="Required", required=False
+                                    ),
+                                ),
+                                (
+                                    "default_value",
+                                    wagtail.blocks.TextBlock(
+                                        label="Default value", required=False
+                                    ),
+                                ),
+                                (
+                                    "word_limit",
+                                    wagtail.blocks.IntegerBlock(
+                                        default=1000, label="Word limit"
+                                    ),
+                                ),
+                            ],
+                            group="Fields",
+                        ),
+                    ),
+                ],
+                default={},
+                use_json_field=True,
+            ),
+            preserve_default=False,
+        ),
+    ]
diff --git a/hypha/apply/projects/models/__init__.py b/hypha/apply/projects/models/__init__.py
index 5e289c203..871444e46 100644
--- a/hypha/apply/projects/models/__init__.py
+++ b/hypha/apply/projects/models/__init__.py
@@ -9,6 +9,7 @@ from .project import (
     PAFApprovals,
     Project,
     ProjectApprovalForm,
+    ProjectReportForm,
     ProjectSettings,
     ProjectSOWForm,
 )
@@ -18,6 +19,7 @@ from .vendor import BankInformation, DueDiligenceDocument, Vendor, VendorFormSet
 __all__ = [
     "Project",
     "ProjectApprovalForm",
+    "ProjectReportForm",
     "ProjectSOWForm",
     "ProjectSettings",
     "PAFApprovals",
diff --git a/hypha/apply/projects/models/project.py b/hypha/apply/projects/models/project.py
index 14aae7c17..e7a162b29 100644
--- a/hypha/apply/projects/models/project.py
+++ b/hypha/apply/projects/models/project.py
@@ -30,7 +30,7 @@ from hypha.apply.stream_forms.files import StreamFieldDataEncoder
 from hypha.apply.stream_forms.models import BaseStreamForm
 from hypha.apply.utils.storage import PrivateStorage
 
-from ..blocks import ProjectApprovalFormCustomFormFieldsBlock
+from ..blocks import ProjectFormCustomFormFieldsBlock
 from .vendor import Vendor
 
 logger = logging.getLogger(__name__)
@@ -219,7 +219,7 @@ class Project(BaseStreamForm, AccessFormData, models.Model):
 
     form_data = models.JSONField(encoder=StreamFieldDataEncoder, default=dict)
     form_fields = StreamField(
-        ProjectApprovalFormCustomFormFieldsBlock(), null=True, use_json_field=True
+        ProjectFormCustomFormFieldsBlock(), null=True, use_json_field=True
     )
 
     # tracks read/write state of the Project
@@ -321,7 +321,6 @@ class Project(BaseStreamForm, AccessFormData, models.Model):
         )
         if not first_approved_contract:
             return None
-
         return first_approved_contract.approved_at.date()
 
     @property
@@ -455,15 +454,13 @@ class ProjectSOW(BaseStreamForm, AccessFormData, models.Model):
     )
     form_data = models.JSONField(encoder=StreamFieldDataEncoder, default=dict)
     form_fields = StreamField(
-        ProjectApprovalFormCustomFormFieldsBlock(), null=True, use_json_field=True
+        ProjectFormCustomFormFieldsBlock(), null=True, use_json_field=True
     )
 
 
 class ProjectBaseStreamForm(BaseStreamForm, models.Model):
     name = models.CharField(max_length=255)
-    form_fields = StreamField(
-        ProjectApprovalFormCustomFormFieldsBlock(), use_json_field=True
-    )
+    form_fields = StreamField(ProjectFormCustomFormFieldsBlock(), use_json_field=True)
 
     panels = [
         FieldPanel("name"),
@@ -485,6 +482,17 @@ class ProjectSOWForm(ProjectBaseStreamForm):
     pass
 
 
+class ProjectReportForm(ProjectBaseStreamForm):
+    """
+    An Applicant Report Form can be attached to a Fund to collect reports from Applicants aka Grantees during the
+    Project. It is only relevant for accepted or granted Submissions which is why it is attached to Project. It is
+    similar to the other Forms (PAF, SOW) in that it uses StreamForm to allow maximum flexibility in form creation.
+    See Also ReportVersion where the fields from the form get copied and the response data gets filled in.
+    """
+
+    pass
+
+
 class PAFReviewersRole(Orderable, ClusterableModel):
     label = models.CharField(max_length=200)
     user_roles = ParentalManyToManyField(
diff --git a/hypha/apply/projects/models/report.py b/hypha/apply/projects/models/report.py
index 9467b2f03..ee6a2d12c 100644
--- a/hypha/apply/projects/models/report.py
+++ b/hypha/apply/projects/models/report.py
@@ -12,7 +12,12 @@ from django.urls import reverse
 from django.utils import timezone
 from django.utils.functional import cached_property
 from django.utils.translation import gettext_lazy as _
+from wagtail.fields import StreamField
 
+from hypha.apply.funds.models.mixins import AccessFormData
+from hypha.apply.projects.blocks import ProjectFormCustomFormFieldsBlock
+from hypha.apply.stream_forms.files import StreamFieldDataEncoder
+from hypha.apply.stream_forms.models import BaseStreamForm
 from hypha.apply.utils.storage import PrivateStorage
 
 
@@ -166,13 +171,17 @@ class Report(models.Model):
         return self.project.start_date
 
 
-class ReportVersion(models.Model):
+class ReportVersion(BaseStreamForm, AccessFormData, models.Model):
     report = models.ForeignKey(
         "Report", on_delete=models.CASCADE, related_name="versions"
     )
     submitted = models.DateTimeField()
-    public_content = models.TextField()
-    private_content = models.TextField()
+    form_fields = StreamField(
+        # Re-use the Project Custom Form class. The original fields (used at the time of response) should be required.
+        ProjectFormCustomFormFieldsBlock(),
+        use_json_field=True,
+    )
+    form_data = models.JSONField(encoder=StreamFieldDataEncoder, default=dict)
     draft = models.BooleanField()
     author = models.ForeignKey(
         settings.AUTH_USER_MODEL,
diff --git a/hypha/apply/projects/templates/application_projects/report_detail.html b/hypha/apply/projects/templates/application_projects/report_detail.html
index b0209e7be..10ecebe2c 100644
--- a/hypha/apply/projects/templates/application_projects/report_detail.html
+++ b/hypha/apply/projects/templates/application_projects/report_detail.html
@@ -27,14 +27,14 @@
                     <h2>{% trans "Report Skipped" %}</h2>
                 {% else %}
                     <h4>{% trans "Public Report" %}</h4>
-                    <div class="rich-text">
-                        {{ object.current.public_content|nh3|safe }}
+                    <div class="card card--solid">
+                        {% if object.current %}
+                            <div class="simplified__paf_answers">
+                                {{ object.current.output_answers }}
+                            </div>
+                        {% endif %}
                     </div>
 
-                    <h4>{% trans "Private Report" %}</h4>
-                    <div class="rich-text">
-                        {{ object.current.private_content|nh3|safe }}
-                    </div>
                     {% for file in object.current.files.all %}
                         {% if forloop.first %}
                             <h4>{% trans "Attachements" %}</h4>
diff --git a/hypha/apply/projects/templates/application_projects/report_form.html b/hypha/apply/projects/templates/application_projects/report_form.html
index da0de3f82..d7669e102 100644
--- a/hypha/apply/projects/templates/application_projects/report_form.html
+++ b/hypha/apply/projects/templates/application_projects/report_form.html
@@ -35,16 +35,23 @@
 
                 {% for field in form %}
                     {% if field.field %}
-                        {% include "forms/includes/field.html" %}
+                        {% if field.field.multi_input_field %}
+                            {% include "forms/includes/multi_input_field.html" %}
+                        {% else %}
+                            {% include "forms/includes/field.html" %}
+                        {% endif %}
                     {% else %}
-                        {{ field }}
+                        {{ field.block }}
                     {% endif %}
                 {% endfor %}
 
+                {% for hidden_field in form.hidden_fields %}
+                    {{ hidden_field }}
+                {% endfor %}
                 <input type="submit" id="submit-report-form-submit" name="submit" class="is-hidden" />
                 <input type="submit" id="submit-report-form-save" name="save" class="is-hidden" />
-                <button data-fancybox data-src="#save-report" class="button button--submit button--top-space button--white" type="button">{% trans "Save" %}</button>
-                <button data-fancybox data-src="#submit-report" class="button button--primary" type="button">{% trans "Submit" %}</button>
+                <button data-fancybox data-src="#save-report" class="button button--submit button--top-space button--white" type="button">{% trans "Save draft" %}</button>
+                <button data-fancybox data-src="#submit-report" class="button  button--submit button--top-space button--primary" type="button">{% trans "Submit" %}</button>
 
             <!-- Save report modal -->
                 <div class="modal" id="save-report">
@@ -75,4 +82,5 @@
 {% block extra_js %}
     <script src="{% static 'js/jquery.fancybox.min.js' %}"></script>
     <script src="{% static 'js/fancybox-global.js' %}"></script>
+    <script src="{% static 'js/multi-input-fields.js' %}"></script>
 {% endblock %}
diff --git a/hypha/apply/projects/tests/factories.py b/hypha/apply/projects/tests/factories.py
index f65f243f9..8c40c41bf 100644
--- a/hypha/apply/projects/tests/factories.py
+++ b/hypha/apply/projects/tests/factories.py
@@ -8,6 +8,7 @@ from hypha.apply.funds.tests.factories import ApplicationSubmissionFactory
 from hypha.apply.stream_forms.testing.factories import (
     FormDataFactory,
     FormFieldsBlockFactory,
+    NonFileFormFieldsBlockFactory,
 )
 from hypha.apply.users.groups import APPROVER_GROUP_NAME, STAFF_GROUP_NAME
 from hypha.apply.users.tests.factories import GroupFactory, StaffFactory, UserFactory
@@ -24,6 +25,7 @@ from ..models.project import (
     PAFReviewersRole,
     Project,
     ProjectApprovalForm,
+    ProjectReportForm,
     ProjectSOWForm,
 )
 from ..models.report import Report, ReportConfig, ReportVersion
@@ -84,6 +86,14 @@ class ProjectApprovalFormDataFactory(FormDataFactory):
     field_factory = FormFieldsBlockFactory
 
 
+class ProjectReportFormFactory(factory.django.DjangoModelFactory):
+    class Meta:
+        model = ProjectReportForm
+
+    name = factory.Faker("word")
+    form_fields = FormFieldsBlockFactory
+
+
 class ProjectFactory(factory.django.DjangoModelFactory):
     submission = factory.SubFactory(ApplicationSubmissionFactory)
     user = factory.SubFactory(UserFactory)
@@ -200,11 +210,19 @@ class ReportConfigFactory(factory.django.DjangoModelFactory):
         )
 
 
+class ReportVersionDataFactory(FormDataFactory):
+    field_factory = NonFileFormFieldsBlockFactory
+
+
 class ReportVersionFactory(factory.django.DjangoModelFactory):
     report = factory.SubFactory("hypha.apply.projects.tests.factories.ReportFactory")
     submitted = factory.LazyFunction(timezone.now)
-    public_content = factory.Faker("paragraph")
-    private_content = factory.Faker("paragraph")
+    form_fields = NonFileFormFieldsBlockFactory
+    # TODO: is it better to keep the following link between form_data and form_fields or to remove it?
+    form_data = factory.SubFactory(
+        ReportVersionDataFactory,
+        form_fields=factory.SelfAttribute("..form_fields"),
+    )
     draft = True
 
     class Meta:
diff --git a/hypha/apply/projects/tests/test_views.py b/hypha/apply/projects/tests/test_views.py
index 7e2b462c7..429e71e19 100644
--- a/hypha/apply/projects/tests/test_views.py
+++ b/hypha/apply/projects/tests/test_views.py
@@ -1,6 +1,7 @@
 import json
 from io import BytesIO
 
+import factory
 from dateutil.relativedelta import relativedelta
 from django.conf import settings
 from django.contrib.auth.models import AnonymousUser
@@ -23,6 +24,7 @@ from hypha.apply.users.tests.factories import (
 from hypha.apply.utils.testing.tests import BaseViewTestCase
 from hypha.home.factories import ApplySiteFactory
 
+from ...funds.models.forms import ApplicationBaseProjectReportForm
 from ..files import get_files
 from ..forms import SetPendingForm
 from ..models.payment import CHANGES_REQUESTED_BY_STAFF, SUBMITTED
@@ -35,6 +37,7 @@ from ..models.project import (
     INTERNAL_APPROVAL,
     INVOICING_AND_REPORTING,
     REQUEST_CHANGE,
+    ProjectReportForm,
     ProjectSettings,
 )
 from ..views.project import ContractsMixin, ProjectDetailApprovalView
@@ -51,6 +54,20 @@ from .factories import (
     SupportingDocumentFactory,
 )
 
+# A boilerplate stream form for Project Report tests below.
+FORM_FIELDS = [
+    {
+        "id": "012a4f29-0882-4b1c-b567-aede1b601d4a",
+        "type": "number",
+        "value": {
+            "required": True,
+            "help_text": "",
+            "field_label": "How many folks did you reach?",
+            "default_value": "",
+        },
+    }
+]
+
 
 class TestUpdateLeadView(BaseViewTestCase):
     base_view_name = "detail"
@@ -1189,6 +1206,15 @@ class TestStaffSubmitReport(BaseViewTestCase):
     base_view_name = "edit"
     url_name = "funds:projects:reports:{}"
     user_factory = StaffFactory
+    report_form_id = None
+
+    def setUp(self):
+        super().setUp()
+        report_form, _ = ProjectReportForm.objects.get_or_create(
+            name=factory.Faker("word"),
+            form_fields=FORM_FIELDS,
+        )
+        self.report_form_id = report_form.id
 
     def get_kwargs(self, instance):
         return {
@@ -1197,11 +1223,19 @@ class TestStaffSubmitReport(BaseViewTestCase):
 
     def test_get_page_for_inprogress_project(self):
         report = ReportFactory(project__status=INVOICING_AND_REPORTING)
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         response = self.get_page(report)
         self.assertContains(response, report.project.title)
 
     def test_cant_get_page_for_closing_and_complete_project(self):
         report = ReportFactory(project__status=CLOSING)
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         response = self.get_page(report)
         self.assertEqual(response.status_code, 403)
 
@@ -1211,39 +1245,72 @@ class TestStaffSubmitReport(BaseViewTestCase):
 
     def test_submit_report(self):
         report = ReportFactory(project__status=INVOICING_AND_REPORTING)
-        response = self.post_page(report, {"public_content": "Some text"})
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
+        response = self.post_page(
+            report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "11"}
+        )
         report.refresh_from_db()
         self.assertRedirects(
             response, self.absolute_url(report.project.get_absolute_url())
         )
-        self.assertEqual(report.versions.first().public_content, "Some text")
+        self.assertEqual(
+            report.versions.first().form_data,
+            {"012a4f29-0882-4b1c-b567-aede1b601d4a": "11"},
+        )
         self.assertEqual(report.versions.first(), report.current)
         self.assertEqual(report.current.author, self.user)
         self.assertIsNone(report.draft)
 
     def test_cant_submit_report_for_closing_and_complete_project(self):
         report = ReportFactory(project__status=CLOSING)
-        response = self.post_page(report, {"public_content": "Some text"})
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
+        response = self.post_page(
+            report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "13"}
+        )
         self.assertEqual(response.status_code, 403)
 
         report = ReportFactory(project__status=COMPLETE)
-        response = self.post_page(report, {"public_content": "Some text"})
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
+        response = self.post_page(
+            report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "13"}
+        )
         self.assertEqual(response.status_code, 403)
 
     def test_submit_private_report(self):
         report = ReportFactory(project__status=INVOICING_AND_REPORTING)
-        response = self.post_page(report, {"private_content": "Some text"})
+        # Link the single-field report_form to the Fund associated with this Submission.
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
+        response = self.post_page(
+            report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "17"}
+        )
         report.refresh_from_db()
-        self.assertRedirects(
-            response, self.absolute_url(report.project.get_absolute_url())
+        self.assertEquals(response.status_code, 200)
+        self.assertEqual(
+            report.versions.first().form_data,
+            {"012a4f29-0882-4b1c-b567-aede1b601d4a": "17"},
         )
-        self.assertEqual(report.versions.first().private_content, "Some text")
         self.assertEqual(report.versions.first(), report.current)
         self.assertEqual(report.current.author, self.user)
         self.assertIsNone(report.draft)
 
     def test_cant_submit_blank_report(self):
         report = ReportFactory(project__status=INVOICING_AND_REPORTING)
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         response = self.post_page(report, {})
         report.refresh_from_db()
         self.assertEqual(response.status_code, 200)
@@ -1251,62 +1318,94 @@ class TestStaffSubmitReport(BaseViewTestCase):
 
     def test_save_report_draft(self):
         report = ReportFactory(project__status=INVOICING_AND_REPORTING)
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         response = self.post_page(
-            report, {"public_content": "Some text", "save": "Save"}
+            report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "19", "save": "Save"}
         )
         report.refresh_from_db()
-        self.assertRedirects(
-            response, self.absolute_url(report.project.get_absolute_url())
+        self.assertEquals(response.status_code, 200)
+        self.assertEqual(
+            report.versions.first().form_data,
+            {"012a4f29-0882-4b1c-b567-aede1b601d4a": "19"},
         )
-        self.assertEqual(report.versions.first().public_content, "Some text")
         self.assertEqual(report.versions.first(), report.draft)
         self.assertIsNone(report.current)
 
     def test_save_report_with_draft(self):
         report = ReportFactory(is_draft=True, project__status=INVOICING_AND_REPORTING)
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         self.assertEqual(report.versions.first(), report.draft)
-        response = self.post_page(report, {"public_content": "Some text"})
+        response = self.post_page(
+            report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "23"}
+        )
         report.refresh_from_db()
-        self.assertRedirects(
-            response, self.absolute_url(report.project.get_absolute_url())
+        self.assertEquals(response.status_code, 200)
+        self.assertEqual(
+            report.versions.last().form_data,
+            {"012a4f29-0882-4b1c-b567-aede1b601d4a": "23"},
         )
-        self.assertEqual(report.versions.last().public_content, "Some text")
         self.assertEqual(report.versions.last(), report.current)
         self.assertIsNone(report.draft)
 
     def test_edit_submitted_report(self):
         report = ReportFactory(
-            is_submitted=True, project__status=INVOICING_AND_REPORTING
+            is_submitted=True,
+            project__status=INVOICING_AND_REPORTING,
+            version__form_fields=json.dumps(FORM_FIELDS),
+        )
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
         )
         self.assertEqual(report.versions.first(), report.current)
         response = self.post_page(
-            report, {"public_content": "Some text", "save": " Save"}
+            report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "29", "save": " Save"}
         )
         report.refresh_from_db()
         self.assertRedirects(
             response, self.absolute_url(report.project.get_absolute_url())
         )
-        self.assertEqual(report.versions.last().public_content, "Some text")
+        self.assertEqual(
+            report.versions.last().form_data["012a4f29-0882-4b1c-b567-aede1b601d4a"],
+            "29",
+        )
         self.assertEqual(report.versions.last(), report.draft)
         self.assertEqual(report.versions.first(), report.current)
 
     def test_resubmit_submitted_report(self):
         yesterday = timezone.now() - relativedelta(days=1)
         version = ReportVersionFactory(
-            report__project__status=INVOICING_AND_REPORTING, submitted=yesterday
+            report__project__status=INVOICING_AND_REPORTING,
+            submitted=yesterday,
+            form_fields=json.dumps(FORM_FIELDS),
         )
         report = version.report
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         report.current = version
         report.submitted = version.submitted
         report.save()
         self.assertEqual(report.submitted, yesterday)
         self.assertEqual(report.versions.first(), report.current)
-        response = self.post_page(report, {"public_content": "Some text"})
+        response = self.post_page(
+            report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "31"}
+        )
         report.refresh_from_db()
         self.assertRedirects(
             response, self.absolute_url(report.project.get_absolute_url())
         )
-        self.assertEqual(report.versions.last().public_content, "Some text")
+        self.assertEqual(
+            report.versions.last().form_data,
+            {"012a4f29-0882-4b1c-b567-aede1b601d4a": "31"},
+        )
         self.assertEqual(report.versions.last(), report.current)
         self.assertIsNone(report.draft)
         self.assertEqual(report.submitted.date(), yesterday.date())
@@ -1318,11 +1417,17 @@ class TestStaffSubmitReport(BaseViewTestCase):
             is_submitted=True,
             project__status=INVOICING_AND_REPORTING,
         )
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=submitted_report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         future_report = ReportFactory(
             end_date=timezone.now() + relativedelta(days=3),
             project=submitted_report.project,
         )
-        response = self.post_page(future_report, {"public_content": "Some text"})
+        response = self.post_page(
+            future_report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "37"}
+        )
         self.assertEqual(response.status_code, 403)
 
 
@@ -1330,6 +1435,15 @@ class TestApplicantSubmitReport(BaseViewTestCase):
     base_view_name = "edit"
     url_name = "funds:projects:reports:{}"
     user_factory = ApplicantFactory
+    report_form_id = None
+
+    def setUp(self):
+        super().setUp()
+        report_form, _ = ProjectReportForm.objects.get_or_create(
+            name=factory.Faker("word"),
+            form_fields=FORM_FIELDS,
+        )
+        self.report_form_id = report_form.id
 
     def get_kwargs(self, instance):
         return {
@@ -1340,20 +1454,36 @@ class TestApplicantSubmitReport(BaseViewTestCase):
         report = ReportFactory(
             project__status=INVOICING_AND_REPORTING, project__user=self.user
         )
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         response = self.get_page(report)
         self.assertContains(response, report.project.title)
 
     def test_cant_get_own_report_for_closing_and_complete_project(self):
         report = ReportFactory(project__status=CLOSING, project__user=self.user)
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         response = self.get_page(report)
         self.assertEqual(response.status_code, 403)
 
         report = ReportFactory(project__status=COMPLETE, project__user=self.user)
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         response = self.get_page(report)
         self.assertEqual(response.status_code, 403)
 
     def test_cant_get_other_report(self):
         report = ReportFactory(project__status=INVOICING_AND_REPORTING)
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         response = self.get_page(report)
         self.assertEqual(response.status_code, 403)
 
@@ -1361,12 +1491,21 @@ class TestApplicantSubmitReport(BaseViewTestCase):
         report = ReportFactory(
             project__status=INVOICING_AND_REPORTING, project__user=self.user
         )
-        response = self.post_page(report, {"public_content": "Some text"})
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
+        response = self.post_page(
+            report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "37"}
+        )
         report.refresh_from_db()
         self.assertRedirects(
             response, self.absolute_url(report.project.get_absolute_url())
         )
-        self.assertEqual(report.versions.first().public_content, "Some text")
+        self.assertEqual(
+            report.versions.first().form_data,
+            {"012a4f29-0882-4b1c-b567-aede1b601d4a": "37"},
+        )
         self.assertEqual(report.versions.first(), report.current)
         self.assertEqual(report.current.author, self.user)
 
@@ -1374,12 +1513,19 @@ class TestApplicantSubmitReport(BaseViewTestCase):
         report = ReportFactory(
             project__status=INVOICING_AND_REPORTING, project__user=self.user
         )
-        response = self.post_page(report, {"private_content": "Some text"})
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
+        response = self.post_page(
+            report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "41"}
+        )
         report.refresh_from_db()
-        self.assertRedirects(
-            response, self.absolute_url(report.project.get_absolute_url())
+        self.assertEquals(response.status_code, 200)
+        self.assertEqual(
+            report.versions.first().form_data,
+            {"012a4f29-0882-4b1c-b567-aede1b601d4a": "41"},
         )
-        self.assertEqual(report.versions.first().private_content, "Some text")
         self.assertEqual(report.versions.first(), report.current)
         self.assertEqual(report.current.author, self.user)
         self.assertIsNone(report.draft)
@@ -1388,6 +1534,10 @@ class TestApplicantSubmitReport(BaseViewTestCase):
         report = ReportFactory(
             project__status=INVOICING_AND_REPORTING, project__user=self.user
         )
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         response = self.post_page(report, {})
         report.refresh_from_db()
         self.assertEqual(response.status_code, 200)
@@ -1397,14 +1547,21 @@ class TestApplicantSubmitReport(BaseViewTestCase):
         report = ReportFactory(
             project__status=INVOICING_AND_REPORTING, project__user=self.user
         )
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         response = self.post_page(
-            report, {"public_content": "Some text", "save": "Save"}
+            report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "43", "save": "Save"}
         )
         report.refresh_from_db()
         self.assertRedirects(
             response, self.absolute_url(report.project.get_absolute_url())
         )
-        self.assertEqual(report.versions.first().public_content, "Some text")
+        self.assertEqual(
+            report.versions.first().form_data,
+            {"012a4f29-0882-4b1c-b567-aede1b601d4a": "43"},
+        )
         self.assertEqual(report.versions.first(), report.draft)
         self.assertIsNone(report.current)
 
@@ -1414,13 +1571,22 @@ class TestApplicantSubmitReport(BaseViewTestCase):
             is_draft=True,
             project__user=self.user,
         )
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         self.assertEqual(report.versions.first(), report.draft)
-        response = self.post_page(report, {"public_content": "Some text"})
+        response = self.post_page(
+            report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "47"}
+        )
         report.refresh_from_db()
         self.assertRedirects(
             response, self.absolute_url(report.project.get_absolute_url())
         )
-        self.assertEqual(report.versions.last().public_content, "Some text")
+        self.assertEqual(
+            report.versions.last().form_data,
+            {"012a4f29-0882-4b1c-b567-aede1b601d4a": "47"},
+        )
         self.assertEqual(report.versions.last(), report.current)
         self.assertIsNone(report.draft)
 
@@ -1431,6 +1597,10 @@ class TestApplicantSubmitReport(BaseViewTestCase):
             is_submitted=True,
             project__user=self.user,
         )
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
         self.assertEqual(report.versions.first(), report.current)
         response = self.post_page(
             report, {"public_content": "Some text", "save": " Save"}
@@ -1439,7 +1609,13 @@ class TestApplicantSubmitReport(BaseViewTestCase):
 
     def test_cant_submit_other_report(self):
         report = ReportFactory(project__status=INVOICING_AND_REPORTING)
-        response = self.post_page(report, {"public_content": "Some text"})
+        ApplicationBaseProjectReportForm.objects.get_or_create(
+            application_id=report.project.submission.page.specific.id,
+            form_id=self.report_form_id,
+        )
+        response = self.post_page(
+            report, {"012a4f29-0882-4b1c-b567-aede1b601d4a": "53"}
+        )
         self.assertEqual(response.status_code, 403)
 
 
diff --git a/hypha/apply/projects/urls.py b/hypha/apply/projects/urls.py
index a3d80e397..88601a80c 100644
--- a/hypha/apply/projects/urls.py
+++ b/hypha/apply/projects/urls.py
@@ -54,7 +54,12 @@ urlpatterns = [
                 ),
                 path("edit/", ProjectApprovalFormEditView.as_view(), name="edit"),
                 path(
-                    "documents/<int:file_pk>/",
+                    "documents/<int:file_pk>",
+                    ProjectPrivateMediaView.as_view(),
+                    name="document",
+                ),
+                path(
+                    "documents/<uuid:field_id>/<str:file_name>",
                     ProjectPrivateMediaView.as_view(),
                     name="document",
                 ),
diff --git a/hypha/apply/projects/utils.py b/hypha/apply/projects/utils.py
index 5a07ecb82..ea06cc427 100644
--- a/hypha/apply/projects/utils.py
+++ b/hypha/apply/projects/utils.py
@@ -1,5 +1,6 @@
 from django.conf import settings
 from django.utils.translation import gettext_lazy as _
+from django_file_form.uploaded_file import PlaceholderUploadedFile
 
 from .constants import (
     INT_DECLINED,
@@ -190,3 +191,14 @@ def get_invoice_table_status(invoice_status, is_applicant=False):
         return INT_DECLINED
     if invoice_status == PAYMENT_FAILED:
         return INT_PAYMENT_FAILED
+
+
+def get_placeholder_file(initial_file):
+    if not isinstance(initial_file, list):
+        return PlaceholderUploadedFile(
+            initial_file.filename, size=initial_file.size, file_id=initial_file.name
+        )
+    return [
+        PlaceholderUploadedFile(f.filename, size=f.size, file_id=f.name)
+        for f in initial_file
+    ]
diff --git a/hypha/apply/projects/views/project.py b/hypha/apply/projects/views/project.py
index 2ab814bae..f65848aa1 100644
--- a/hypha/apply/projects/views/project.py
+++ b/hypha/apply/projects/views/project.py
@@ -1,6 +1,6 @@
+import copy
 import datetime
 import io
-from copy import copy
 
 from django.conf import settings
 from django.contrib import messages
@@ -29,7 +29,6 @@ from django.views.generic import (
     UpdateView,
 )
 from django.views.generic.detail import SingleObjectMixin
-from django_file_form.models import PlaceholderUploadedFile
 from django_filters.views import FilterView
 from django_tables2 import SingleTableMixin
 from docx import Document
@@ -67,6 +66,7 @@ from hypha.apply.utils.models import PDFPageSettings
 from hypha.apply.utils.storage import PrivateMediaView
 from hypha.apply.utils.views import DelegateableView, DelegatedViewMixin, ViewDispatcher
 
+from ...funds.files import generate_private_file_path
 from ..files import get_files
 from ..filters import InvoiceListFilter, ProjectListFilter, ReportListFilter
 from ..forms import (
@@ -110,7 +110,7 @@ from ..models.project import (
 from ..models.report import Report
 from ..permissions import has_permission
 from ..tables import InvoiceListTable, ProjectsListTable, ReportListTable
-from ..utils import get_paf_status_display
+from ..utils import get_paf_status_display, get_placeholder_file
 from ..views.payment import ChangeInvoiceStatusView
 from .report import ReportFrequencyUpdate, ReportingMixin
 
@@ -371,7 +371,7 @@ class UpdateLeadView(DelegatedViewMixin, UpdateView):
 
     def form_valid(self, form):
         # Fetch the old lead from the database
-        old_lead = copy(self.get_object().lead)
+        old_lead = copy.copy(self.get_object().lead)
 
         response = super().form_valid(form)
         project = form.instance
@@ -1315,6 +1315,10 @@ class ProjectDetailView(ViewDispatcher):
 
 @method_decorator(login_required, name="dispatch")
 class ProjectPrivateMediaView(UserPassesTestMixin, PrivateMediaView):
+    """
+    See also hypha/apply/funds/files.py
+    """
+
     raise_exception = True
 
     def dispatch(self, *args, **kwargs):
@@ -1323,10 +1327,18 @@ class ProjectPrivateMediaView(UserPassesTestMixin, PrivateMediaView):
         return super().dispatch(*args, **kwargs)
 
     def get_media(self, *args, **kwargs):
-        document = PacketFile.objects.get(pk=kwargs["file_pk"])
-        if document.project != self.project:
-            raise Http404
-        return document.document
+        if "file_pk" in kwargs:
+            document = PacketFile.objects.get(pk=kwargs["file_pk"])
+            if document.project != self.project:
+                raise Http404
+            return document.document
+        else:
+            field_id = kwargs["field_id"]
+            file_name = kwargs["file_name"]
+            path_to_file = generate_private_file_path(
+                self.project.pk, field_id, file_name, path_start="project"
+            )
+            return self.storage.open(path_to_file)
 
     def test_func(self):
         if self.request.user.is_apply_staff:
@@ -1765,22 +1777,10 @@ class ProjectApprovalFormEditView(BaseStreamForm, UpdateView):
         initial = self.object.raw_data
         for field_id in self.object.file_field_ids:
             initial.pop(field_id + "-uploads", False)
-            initial[field_id] = self.get_placeholder_file(
-                self.object.raw_data.get(field_id)
-            )
+            initial[field_id] = get_placeholder_file(self.object.raw_data.get(field_id))
         kwargs["initial"].update(initial)
         return kwargs
 
-    def get_placeholder_file(self, initial_file):
-        if not isinstance(initial_file, list):
-            return PlaceholderUploadedFile(
-                initial_file.filename, size=initial_file.size, file_id=initial_file.name
-            )
-        return [
-            PlaceholderUploadedFile(f.filename, size=f.size, file_id=f.name)
-            for f in initial_file
-        ]
-
     def get_sow_form_kwargs(self):
         kwargs = super().get_form_kwargs()
         if self.approval_sow_form:
@@ -1792,7 +1792,7 @@ class ProjectApprovalFormEditView(BaseStreamForm, UpdateView):
                 initial = sow_instance.raw_data
                 for field_id in sow_instance.file_field_ids:
                     initial.pop(field_id + "-uploads", False)
-                    initial[field_id] = self.get_placeholder_file(
+                    initial[field_id] = get_placeholder_file(
                         sow_instance.raw_data.get(field_id)
                     )
                 initial["project"] = self.object
diff --git a/hypha/apply/projects/views/report.py b/hypha/apply/projects/views/report.py
index f34341b1e..71d0f4757 100644
--- a/hypha/apply/projects/views/report.py
+++ b/hypha/apply/projects/views/report.py
@@ -1,5 +1,6 @@
 from django.contrib.auth.decorators import login_required
 from django.contrib.auth.mixins import UserPassesTestMixin
+from django.http import HttpResponseRedirect
 from django.shortcuts import get_object_or_404, redirect
 from django.utils.decorators import method_decorator
 from django.views import View
@@ -13,11 +14,13 @@ from hypha.apply.users.decorators import staff_or_finance_required, staff_requir
 from hypha.apply.utils.storage import PrivateMediaView
 from hypha.apply.utils.views import DelegatedViewMixin
 
+from ...stream_forms.models import BaseStreamForm
 from ..filters import ReportListFilter
 from ..forms import ReportEditForm, ReportFrequencyForm
 from ..models import Report, ReportConfig, ReportPrivateFiles
 from ..permissions import has_permission
 from ..tables import ReportListTable
+from ..utils import get_placeholder_file
 
 
 class ReportingMixin:
@@ -60,40 +63,109 @@ class ReportDetailView(DetailView):
 
 
 @method_decorator(login_required, name="dispatch")
-class ReportUpdateView(UpdateView):
-    form_class = ReportEditForm
+class ReportUpdateView(BaseStreamForm, UpdateView):
     model = Report
+    # Values for `object`, `form_class`, and `form_fields` are set during `dispatch` and functions it calls.
+    object = None
+    form_class = None
+    form_fields = None
+
+    def get_form_class(self, draft=False, form_data=None, user=None):
+        """
+        Expects self.form_fields to have already been set.
+        """
+        if not self.form_fields:
+            raise RuntimeError("Expected self.form_fields to be set")
+        # This is where the magic happens.
+        fields = self.get_form_fields(draft, form_data, user)
+        the_class = type(
+            "WagtailStreamForm",
+            (ReportEditForm,),
+            fields,
+        )
+        return the_class
 
-    def dispatch(self, *args, **kwargs):
+    def dispatch(self, request, *args, **kwargs):
         report = self.get_object()
         permission, _ = has_permission(
             "report_update", self.request.user, object=report, raise_exception=True
         )
-        return super().dispatch(*args, **kwargs)
+        self.object = report
+        # super().dispatch calls get_context_data() which calls the rest to get the form fully ready for use.
+        return super().dispatch(request, *args, **kwargs)
+
+    def get_context_data(self, *args, **kwargs):
+        """
+        Django note: super().dispatch calls get_context_data.
+        """
+        # Is this where we need to get the associated form fields? Not in the form itself but up here? Yes. But in a
+        # roundabout way: get_form (here) gets fields and calls get_form_class (here) which calls get_form_fields
+        # (super) which sets up the fields in the returned form.
+        form = self.get_form()
+        context_data = {
+            "form": form,
+            "object": self.object,
+            **kwargs,
+        }
+        return context_data
+
+    def get_form(self, form_class=None):
+        if self.object.current is None or self.object.current.form_fields is None:
+            # Here is where we get the form_fields, the ProjectReportForm associated with the Fund:
+            report_form = (
+                self.object.project.submission.page.specific.report_forms.first()
+            )
+            if report_form:
+                self.form_fields = report_form.form.form_fields
+            else:
+                self.form_fields = {}
+        else:
+            self.form_fields = self.object.current.form_fields
+
+        if form_class is None:
+            form_class = self.get_form_class()
+        report_instance = form_class(**self.get_form_kwargs())
+        return report_instance
 
     def get_initial(self):
+        initial = {}
         if self.object.draft:
             current = self.object.draft
         else:
             current = self.object.current
 
+        # current here is a ReportVersion which should already have the data associated.
         if current:
-            return {
-                "public_content": current.public_content,
-                "private_content": current.private_content,
-                "file_list": current.files.all(),
-            }
+            # The following allows existing data to populate the form. This code was inspired by (aka copied from)
+            # ProjectApprovalFormEditView.get_paf_form_kwargs().
+            initial = current.raw_data
+            # Is the following needed to see the file in a friendly URL? Does not appear so. But needed to not blow up.
+            for field_id in current.file_field_ids:
+                initial.pop(field_id + "-uploads", False)
+                initial[field_id] = get_placeholder_file(current.raw_data.get(field_id))
 
-        return {}
+        return initial
 
     def get_form_kwargs(self):
-        return {
+        form_kwargs = {
             "user": self.request.user,
             **super().get_form_kwargs(),
         }
+        return form_kwargs
+
+    def post(self, request, *args, **kwargs):
+        form = self.get_form()
+        if form.is_valid():
+            form.save(form_fields=self.form_fields)
+            form.delete_temporary_files()
+            response = HttpResponseRedirect(self.get_success_url())
+        else:
+            response = self.form_invalid(form)
+        return response
 
     def get_success_url(self):
-        return self.object.project.get_absolute_url()
+        success_url = self.object.project.get_absolute_url()
+        return success_url
 
     def form_valid(self, form):
         response = super().form_valid(form)
diff --git a/hypha/apply/stream_forms/testing/factories.py b/hypha/apply/stream_forms/testing/factories.py
index 3c23d0bcc..9b30e4f1f 100644
--- a/hypha/apply/stream_forms/testing/factories.py
+++ b/hypha/apply/stream_forms/testing/factories.py
@@ -318,7 +318,7 @@ class StreamFieldUUIDFactory(wagtail_factories.StreamFieldFactory):
         return flatten_for_form(data)
 
 
-BLOCK_FACTORY_DEFINITION = {
+NON_FILE_BLOCK_FACTORY_DEFINITION = {
     "text_markup": ParagraphBlockFactory,
     "char": CharFieldBlockFactory,
     "text": TextFieldBlockFactory,
@@ -330,11 +330,20 @@ BLOCK_FACTORY_DEFINITION = {
     "date": DateFieldBlockFactory,
     "time": TimeFieldBlockFactory,
     "datetime": DateTimeFieldBlockFactory,
+}
+
+BLOCK_FACTORY_DEFINITION = {
+    **NON_FILE_BLOCK_FACTORY_DEFINITION,
     "image": ImageFieldBlockFactory,
     "file": FileFieldBlockFactory,
     "multi_file": MultiFileFieldBlockFactory,
 }
 
+# There are two here, because some tests will fail due to JSON serialization errors
+# if SimpleUploadedFile is included in the factory (most notably Project ReportVersion tests)
+NonFileFormFieldsBlockFactory = StreamFieldUUIDFactory(
+    NON_FILE_BLOCK_FACTORY_DEFINITION
+)
 FormFieldsBlockFactory = StreamFieldUUIDFactory(BLOCK_FACTORY_DEFINITION)
 
 
-- 
GitLab