diff --git a/hypha/apply/determinations/admin.py b/hypha/apply/determinations/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ec7dfb98c7ec6079e65c78393c6bb8feac85590
--- /dev/null
+++ b/hypha/apply/determinations/admin.py
@@ -0,0 +1,45 @@
+from django.conf.urls import url
+from wagtail.contrib.modeladmin.options import ModelAdmin
+from wagtail.contrib.modeladmin.views import CreateView, InstanceSpecificView
+
+from hypha.apply.determinations.models import DeterminationForm
+from hypha.apply.review.admin_helpers import ButtonsWithClone
+from hypha.apply.utils.admin import ListRelatedMixin
+
+
+class CloneView(CreateView, InstanceSpecificView):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.instance.pk = None
+
+
+class DeterminationFormAdmin(ListRelatedMixin, ModelAdmin):
+    model = DeterminationForm
+    menu_icon = 'form'
+    list_display = ('name', 'used_by')
+    button_helper_class = ButtonsWithClone
+    clone_view_class = CloneView
+
+    related_models = [
+        ('applicationbasedeterminationform', 'application'),
+        ('roundbasedeterminationform', 'round'),
+        ('labbasedeterminationform', 'lab'),
+    ]
+
+    def get_admin_urls_for_registration(self):
+        urls = super().get_admin_urls_for_registration()
+
+        urls += (
+            url(
+                self.url_helper.get_action_url_pattern('clone'),
+                self.clone_view,
+                name=self.url_helper.get_action_url_name('clone')
+            ),
+        )
+
+        return urls
+
+    def clone_view(self, request, **kwargs):
+        kwargs.update(**{'model_admin': self})
+        view_class = self.clone_view_class
+        return view_class.as_view(**kwargs)(request)
diff --git a/hypha/apply/determinations/blocks.py b/hypha/apply/determinations/blocks.py
new file mode 100644
index 0000000000000000000000000000000000000000..0eb76027269a0860e33fed03334273c6b393e14f
--- /dev/null
+++ b/hypha/apply/determinations/blocks.py
@@ -0,0 +1,69 @@
+from django import forms
+from django.utils.translation import gettext_lazy as _
+from wagtail.core.blocks import RichTextBlock
+
+from hypha.apply.stream_forms.blocks import (
+    CharFieldBlock,
+    CheckboxFieldBlock,
+    DropdownFieldBlock,
+    TextFieldBlock,
+)
+from hypha.apply.utils.blocks import CustomFormFieldsBlock, MustIncludeFieldBlock
+from hypha.apply.utils.options import RICH_TEXT_WIDGET
+
+from .options import DETERMINATION_CHOICES
+
+
+class DeterminationMustIncludeFieldBlock(MustIncludeFieldBlock):
+    pass
+
+
+class DeterminationBlock(DeterminationMustIncludeFieldBlock):
+    name = 'determination'
+    description = 'Overall determination'
+    field_class = forms.TypedChoiceField
+
+    class Meta:
+        icon = 'pick'
+
+    def get_field_kwargs(self, struct_value):
+        kwargs = super().get_field_kwargs(struct_value)
+        kwargs['choices'] = DETERMINATION_CHOICES
+        return kwargs
+
+    def render(self, value, context=None):
+        data = int(context['data'])
+        choices = dict(DETERMINATION_CHOICES)
+        context['data'] = choices[data]
+        return super().render(value, context)
+
+
+class DeterminationMessageBlock(DeterminationMustIncludeFieldBlock):
+    name = 'message'
+    description = 'Determination message'
+    widget = RICH_TEXT_WIDGET
+
+    class Meta:
+        icon = 'openquote'
+        template = 'stream_forms/render_unsafe_field.html'
+
+    def get_field_kwargs(self, struct_value):
+        kwargs = super().get_field_kwargs(struct_value)
+        kwargs['required'] = False
+        return kwargs
+
+
+class SendNoticeBlock(CheckboxFieldBlock):
+
+    class Meta:
+        label = _('Send Notice')
+
+
+class DeterminationCustomFormFieldsBlock(CustomFormFieldsBlock):
+    char = CharFieldBlock(group=_('Fields'))
+    text = TextFieldBlock(group=_('Fields'))
+    text_markup = RichTextBlock(group=_('Fields'), label=_('Section text/header'))
+    checkbox = CheckboxFieldBlock(group=_('Fields'))
+    dropdown = DropdownFieldBlock(group=_('Fields'))
+    send_notice = SendNoticeBlock(group=_('Fields'))
+    required_blocks = DeterminationMustIncludeFieldBlock.__subclasses__()
diff --git a/hypha/apply/determinations/forms.py b/hypha/apply/determinations/forms.py
index 45d7b4eb3887e27adad45a9ab6b3c6b69813fa63..19c265fdc644deeef26e1dd1bdff45771d5867b6 100644
--- a/hypha/apply/determinations/forms.py
+++ b/hypha/apply/determinations/forms.py
@@ -3,14 +3,11 @@ from django.contrib.auth import get_user_model
 from django.core.exceptions import NON_FIELD_ERRORS
 
 from hypha.apply.funds.models import ApplicationSubmission
+from hypha.apply.stream_forms.forms import StreamBaseForm
 from hypha.apply.utils.fields import RichTextField
 
-from .models import (
-    DETERMINATION_CHOICES,
-    TRANSITION_DETERMINATION,
-    Determination,
-    DeterminationFormSettings,
-)
+from .models import Determination, DeterminationFormSettings
+from .options import DETERMINATION_CHOICES, TRANSITION_DETERMINATION
 from .utils import determination_actions
 
 User = get_user_model()
@@ -138,14 +135,14 @@ class BaseBatchDeterminationForm(BaseDeterminationForm, forms.Form):
     def data_fields(self):
         return [
             field for field in self.fields
-            if field not in ['submissions', 'outcome', 'author', 'send_notice']
+            if field not in ['submissions', 'outcome', 'author', 'send_notice', 'message']
         ]
 
     def _post_clean(self):
         submissions = self.cleaned_data['submissions'].undetermined()
         data = {
             field: self.cleaned_data[field]
-            for field in ['author', 'data', 'outcome']
+            for field in ['author', 'data', 'outcome', 'message']
         }
 
         self.instances = [
@@ -411,3 +408,158 @@ class BatchProposalDeterminationForm(BaseProposalDeterminationForm, BaseBatchDet
         self.fields['outcome'].widget = forms.HiddenInput()
 
         self.fields = self.apply_form_settings('proposal', self.fields)
+
+
+class MixedMetaClass(type(StreamBaseForm), type(forms.ModelForm)):
+    pass
+
+
+class DeterminationModelForm(StreamBaseForm, forms.ModelForm, metaclass=MixedMetaClass):
+    draft_button_name = "save draft"
+
+    class Meta:
+        model = Determination
+        fields = ['outcome', 'message', 'submission', 'author', 'send_notice']
+
+        widgets = {
+            'outcome': forms.HiddenInput(),
+            'message': forms.HiddenInput(),
+            'submission': forms.HiddenInput(),
+            'author': forms.HiddenInput(),
+            'send_notice': forms.HiddenInput(),
+        }
+
+        error_messages = {
+            NON_FIELD_ERRORS: {
+                'unique_together': "You have already created a determination for this submission",
+            }
+        }
+
+    def __init__(
+        self, *args, submission, action, user=None,
+        edit=False, initial={}, instance=None, site=None,
+        **kwargs
+    ):
+        initial.update(submission=submission.id)
+        initial.update(author=user.id)
+        if instance:
+            for key, value in instance.form_data.items():
+                if key not in self._meta.fields:
+                    initial[key] = value
+        super().__init__(*args, initial=initial, instance=instance, **kwargs)
+
+        for field in self._meta.widgets:
+            # Need to disable the model form fields as these fields would be
+            # rendered via streamfield form.
+            self.fields[field].disabled = True
+
+        if self.draft_button_name in self.data:
+            for field in self.fields.values():
+                field.required = False
+
+        if edit:
+            self.fields.pop('outcome')
+            self.draft_button_name = None
+
+    def clean(self):
+        cleaned_data = super().clean()
+        cleaned_data['form_data'] = {
+            key: value
+            for key, value in cleaned_data.items()
+            if key not in self._meta.fields
+        }
+
+        return cleaned_data
+
+    def save(self, commit=True):
+        self.instance.send_notice = (
+            self.cleaned_data[self.instance.send_notice_field.id]
+            if self.instance.send_notice_field else True
+        )
+        self.instance.message = self.cleaned_data[self.instance.message_field.id]
+        try:
+            self.instance.outcome = int(self.cleaned_data[self.instance.determination_field.id])
+            # Need to catch KeyError as outcome field would not exist in case of edit.
+        except KeyError:
+            pass
+        self.instance.is_draft = self.draft_button_name in self.data
+        self.instance.form_data = self.cleaned_data['form_data']
+        return super().save(commit)
+
+
+class FormMixedMetaClass(type(StreamBaseForm), type(forms.Form)):
+    pass
+
+
+class BatchDeterminationForm(StreamBaseForm, forms.Form, metaclass=FormMixedMetaClass):
+    submissions = forms.ModelMultipleChoiceField(
+        queryset=ApplicationSubmission.objects.all(),
+        widget=forms.ModelMultipleChoiceField.hidden_widget,
+    )
+    author = forms.ModelChoiceField(
+        queryset=User.objects.staff(),
+        widget=forms.ModelChoiceField.hidden_widget,
+        required=True,
+    )
+    outcome = forms.ChoiceField(
+        choices=DETERMINATION_CHOICES,
+        label='Determination',
+        help_text='Do you recommend requesting a proposal based on this concept note?',
+        widget=forms.HiddenInput()
+    )
+
+    def __init__(
+        self, *args, user, submissions, action, initial={},
+        edit=False, site=None, **kwargs
+    ):
+        initial.update(submissions=submissions.values_list('id', flat=True))
+        try:
+            initial.update(outcome=TRANSITION_DETERMINATION[action])
+        except KeyError:
+            pass
+        initial.update(author=user.id)
+        super().__init__(*args, initial=initial, **kwargs)
+        self.fields['submissions'].disabled = True
+        self.fields['author'].disabled = True
+        self.fields['outcome'].disabled = True
+
+    def data_fields(self):
+        return [
+            field for field in self.fields
+            if field not in ['submissions', 'outcome', 'author', 'send_notice']
+        ]
+
+    def clean(self):
+        cleaned_data = super().clean()
+        cleaned_data['form_data'] = {
+            key: value
+            for key, value in cleaned_data.items()
+            if key in self.data_fields()
+        }
+        return cleaned_data
+
+    def clean_outcome(self):
+        # Enforce outcome as an int
+        return int(self.cleaned_data['outcome'])
+
+    def _post_clean(self):
+        submissions = self.cleaned_data['submissions'].undetermined()
+        data = {
+            field: self.cleaned_data[field]
+            for field in ['author', 'form_data', 'outcome']
+        }
+
+        self.instances = [
+            Determination(
+                submission=submission,
+                **data,
+            )
+            for submission in submissions
+        ]
+
+        return super()._post_clean()
+
+    def save(self):
+        determinations = Determination.objects.bulk_create(self.instances)
+        self.instances = determinations
+        return determinations
diff --git a/hypha/apply/determinations/migrations/0010_add_determination_stream_field_forms.py b/hypha/apply/determinations/migrations/0010_add_determination_stream_field_forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..195a5f683f2c34784878d7bb4b7756c8ddc0e8d8
--- /dev/null
+++ b/hypha/apply/determinations/migrations/0010_add_determination_stream_field_forms.py
@@ -0,0 +1,44 @@
+# Generated by Django 2.2.13 on 2020-07-01 10:19
+
+import django.contrib.postgres.fields.jsonb
+import django.core.serializers.json
+from django.db import migrations, models
+import wagtail.core.blocks
+import wagtail.core.blocks.static_block
+import wagtail.core.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('determinations', '0009_add_send_notice_field'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='DeterminationForm',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('form_fields', wagtail.core.fields.StreamField([('rich_text', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TextBlock(label='Default value', required=False)), ('word_limit', wagtail.core.blocks.IntegerBlock(default=1000, label='Word limit'))], group='Fields')), ('markdown_text', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TextBlock(label='Default value', required=False)), ('word_limit', wagtail.core.blocks.IntegerBlock(default=1000, label='Word limit'))], group='Fields')), ('char', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('format', wagtail.core.blocks.ChoiceBlock(choices=[('email', 'Email'), ('url', 'URL')], label='Format', required=False)), ('default_value', wagtail.core.blocks.CharBlock(label='Default value', required=False))], group='Fields')), ('text', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TextBlock(label='Default value', required=False)), ('word_limit', wagtail.core.blocks.IntegerBlock(default=1000, label='Word limit'))], group='Fields')), ('text_markup', wagtail.core.blocks.RichTextBlock(group='Fields', label='Section text/header')), ('checkbox', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.BooleanBlock(required=False))], group='Fields')), ('dropdown', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.core.blocks.ListBlock(wagtail.core.blocks.CharBlock(label='Choice')))], group='Fields')), ('send_notice', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.BooleanBlock(required=False))], group='Fields')), ('determination', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group=' Required')), ('message', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group=' Required'))], default=[])),
+                ('name', models.CharField(max_length=255)),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+        migrations.AddField(
+            model_name='determination',
+            name='form_data',
+            field=django.contrib.postgres.fields.jsonb.JSONField(default=dict, encoder=django.core.serializers.json.DjangoJSONEncoder),
+        ),
+        migrations.AddField(
+            model_name='determination',
+            name='form_fields',
+            field=wagtail.core.fields.StreamField([('rich_text', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TextBlock(label='Default value', required=False)), ('word_limit', wagtail.core.blocks.IntegerBlock(default=1000, label='Word limit'))], group='Fields')), ('markdown_text', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TextBlock(label='Default value', required=False)), ('word_limit', wagtail.core.blocks.IntegerBlock(default=1000, label='Word limit'))], group='Fields')), ('char', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('format', wagtail.core.blocks.ChoiceBlock(choices=[('email', 'Email'), ('url', 'URL')], label='Format', required=False)), ('default_value', wagtail.core.blocks.CharBlock(label='Default value', required=False))], group='Fields')), ('text', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TextBlock(label='Default value', required=False)), ('word_limit', wagtail.core.blocks.IntegerBlock(default=1000, label='Word limit'))], group='Fields')), ('text_markup', wagtail.core.blocks.RichTextBlock(group='Fields', label='Section text/header')), ('checkbox', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.BooleanBlock(required=False))], group='Fields')), ('dropdown', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.core.blocks.ListBlock(wagtail.core.blocks.CharBlock(label='Choice')))], group='Fields')), ('send_notice', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.BooleanBlock(required=False))], group='Fields')), ('determination', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group=' Required')), ('message', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('help_link', wagtail.core.blocks.URLBlock(label='Help link', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group=' Required'))], default=[]),
+        ),
+        migrations.AlterField(
+            model_name='determination',
+            name='data',
+            field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True),
+        ),
+    ]
diff --git a/hypha/apply/determinations/models.py b/hypha/apply/determinations/models.py
index 28e233ba18c6ed57c98d6763ed50ce8c2ceac22c..4b72205b31d620685f81b43a0add72709f63ffd3 100644
--- a/hypha/apply/determinations/models.py
+++ b/hypha/apply/determinations/models.py
@@ -1,6 +1,7 @@
 import bleach
 from django.conf import settings
 from django.contrib.postgres.fields import JSONField
+from django.core.serializers.json import DjangoJSONEncoder
 from django.db import models
 from django.urls import reverse
 from django.utils.translation import gettext_lazy as _
@@ -8,33 +9,22 @@ from wagtail.admin.edit_handlers import (
     FieldPanel,
     MultiFieldPanel,
     ObjectList,
+    StreamFieldPanel,
     TabbedInterface,
 )
 from wagtail.contrib.settings.models import BaseSetting, register_setting
-from wagtail.core.fields import RichTextField
+from wagtail.core.fields import RichTextField, StreamField
 
-from hypha.apply.funds.workflow import DETERMINATION_OUTCOMES
+from hypha.apply.funds.models.mixins import AccessFormData
 
-REJECTED = 0
-NEEDS_MORE_INFO = 1
-ACCEPTED = 2
-
-DETERMINATION_CHOICES = (
-    (REJECTED, _('Dismissed')),
-    (NEEDS_MORE_INFO, _('More information requested')),
-    (ACCEPTED, _('Approved')),
+from .blocks import (
+    DeterminationBlock,
+    DeterminationCustomFormFieldsBlock,
+    DeterminationMessageBlock,
+    DeterminationMustIncludeFieldBlock,
+    SendNoticeBlock,
 )
-
-DETERMINATION_TO_OUTCOME = {
-    'rejected': REJECTED,
-    'accepted': ACCEPTED,
-    'more_info': NEEDS_MORE_INFO,
-}
-
-TRANSITION_DETERMINATION = {
-    name: DETERMINATION_TO_OUTCOME[type]
-    for name, type in DETERMINATION_OUTCOMES.items()
-}
+from .options import ACCEPTED, DETERMINATION_CHOICES, REJECTED
 
 
 class DeterminationQuerySet(models.QuerySet):
@@ -49,7 +39,52 @@ class DeterminationQuerySet(models.QuerySet):
         return self.submitted().filter(outcome__in=[ACCEPTED, REJECTED])
 
 
-class Determination(models.Model):
+class DeterminationFormFieldsMixin(models.Model):
+    class Meta:
+        abstract = True
+
+    form_fields = StreamField(DeterminationCustomFormFieldsBlock(), default=[])
+
+    @property
+    def determination_field(self):
+        return self._get_field_type(DeterminationBlock)
+
+    @property
+    def message_field(self):
+        return self._get_field_type(DeterminationMessageBlock)
+
+    @property
+    def send_notice_field(self):
+        return self._get_field_type(SendNoticeBlock)
+
+    def _get_field_type(self, block_type, many=False):
+        fields = list()
+        for field in self.form_fields:
+            try:
+                if isinstance(field.block, block_type):
+                    if many:
+                        fields.append(field)
+                    else:
+                        return field
+            except AttributeError:
+                pass
+        if many:
+            return fields
+
+
+class DeterminationForm(DeterminationFormFieldsMixin, models.Model):
+    name = models.CharField(max_length=255)
+
+    panels = [
+        FieldPanel('name'),
+        StreamFieldPanel('form_fields'),
+    ]
+
+    def __str__(self):
+        return self.name
+
+
+class Determination(DeterminationFormFieldsMixin, AccessFormData, models.Model):
     submission = models.ForeignKey(
         'funds.ApplicationSubmission',
         on_delete=models.CASCADE,
@@ -62,7 +97,12 @@ class Determination(models.Model):
 
     outcome = models.IntegerField(verbose_name=_("Determination"), choices=DETERMINATION_CHOICES, default=1)
     message = models.TextField(verbose_name=_("Determination message"), blank=True)
-    data = JSONField(blank=True)
+
+    # Stores old determination forms data
+    data = JSONField(blank=True, null=True)
+
+    # Stores data submitted via streamfield determination forms
+    form_data = JSONField(default=dict, encoder=DjangoJSONEncoder)
     is_draft = models.BooleanField(default=False, verbose_name=_("Draft"))
     created_at = models.DateTimeField(verbose_name=_('Creation time'), auto_now_add=True)
     updated_at = models.DateTimeField(verbose_name=_('Update time'), auto_now=True)
@@ -92,12 +132,48 @@ class Determination(models.Model):
         return f'Determination for {self.submission.title} by {self.author!s}'
 
     def __repr__(self):
-        return f'<{self.__class__.__name__}: {str(self.data)}>'
+        return f'<{self.__class__.__name__}: {str(self.form_data)}>'
+
+    @property
+    def use_new_determination_form(self):
+        """
+        Checks if a submission has the new streamfield determination form
+        attached to it and along with that it also verify that if self.data is None.
+
+        self.data would be set as None for the determination which are created using
+        streamfield determination forms.
+
+        But old lab forms can be edited to add new determination forms
+        so we need to use old determination forms for already submitted determination.
+        """
+        return self.submission.is_determination_form_attached and self.data is None
 
     @property
     def detailed_data(self):
-        from .views import get_form_for_stage
-        return get_form_for_stage(self.submission).get_detailed_response(self.data)
+        if not self.use_new_determination_form:
+            from .views import get_form_for_stage
+            return get_form_for_stage(self.submission).get_detailed_response(self.data)
+        return self.get_detailed_response()
+
+    def get_detailed_response(self):
+        data = {}
+        group = 0
+        data.setdefault(group, {'title': None, 'questions': list()})
+        for field in self.form_fields:
+            if issubclass(
+                field.block.__class__, DeterminationMustIncludeFieldBlock
+            ) or isinstance(field.block, SendNoticeBlock):
+                continue
+            try:
+                value = self.form_data[field.id]
+            except KeyError:
+                group = group + 1
+                data.setdefault(group, {'title': field.value.source, 'questions': list()})
+            else:
+                data[group]['questions'].append(
+                    (field.value.get('field_label'), value)
+                )
+        return data
 
 
 @register_setting
diff --git a/hypha/apply/determinations/options.py b/hypha/apply/determinations/options.py
new file mode 100644
index 0000000000000000000000000000000000000000..1da2980b18ac439b72483f50a62e163cfbb90d17
--- /dev/null
+++ b/hypha/apply/determinations/options.py
@@ -0,0 +1,24 @@
+from django.utils.translation import gettext_lazy as _
+
+from hypha.apply.funds.workflow import DETERMINATION_OUTCOMES
+
+REJECTED = 0
+NEEDS_MORE_INFO = 1
+ACCEPTED = 2
+
+DETERMINATION_CHOICES = (
+    (REJECTED, _('Dismissed')),
+    (NEEDS_MORE_INFO, _('More information requested')),
+    (ACCEPTED, _('Approved')),
+)
+
+DETERMINATION_TO_OUTCOME = {
+    'rejected': REJECTED,
+    'accepted': ACCEPTED,
+    'more_info': NEEDS_MORE_INFO,
+}
+
+TRANSITION_DETERMINATION = {
+    name: DETERMINATION_TO_OUTCOME[type]
+    for name, type in DETERMINATION_OUTCOMES.items()
+}
diff --git a/hypha/apply/determinations/templates/determinations/base_determination_form.html b/hypha/apply/determinations/templates/determinations/base_determination_form.html
index fddfcb2d4d16fecb13c37b6f5c80ad366052a7a1..e5c6f53a5302111fb16c4e851607e458a5153584 100644
--- a/hypha/apply/determinations/templates/determinations/base_determination_form.html
+++ b/hypha/apply/determinations/templates/determinations/base_determination_form.html
@@ -52,6 +52,7 @@
     </form>
     {% for type, message in message_templates.items %}
         <div class="is-hidden" data-type="{{ type }}" id="determination-message-{{ type }}">
+            <h1>message</h1>
             {{ message|bleach }}
         </div>
     {% endfor %}
diff --git a/hypha/apply/determinations/templates/determinations/determination_detail.html b/hypha/apply/determinations/templates/determinations/determination_detail.html
index 64a465cd4d0bfe8c6c40a8b87205cedbb69dcd80..06f562be2f71e82b31c0a2427a6ab6381bfa34a1 100644
--- a/hypha/apply/determinations/templates/determinations/determination_detail.html
+++ b/hypha/apply/determinations/templates/determinations/determination_detail.html
@@ -30,7 +30,9 @@
     <h4>Determination message</h4>
     {{ determination.message|bleach }}
     {% for group in determination.detailed_data.values %}
-        <h4>{{ group.title }}</h4>
+        {% if group.title %}
+            <h4>{{ group.title|bleach }}</h4>
+        {% endif %}
         {% for question, answer in group.questions %}
             <h5>{{ question }}</h5>
             {% if answer %}{{ answer|bleach }}{% else %}-{% endif %}
diff --git a/hypha/apply/determinations/tests/factories.py b/hypha/apply/determinations/tests/factories.py
index 53708005d129597526db4b7e10b5cceba4fa56f6..2d4d791cf30a4c089e40f4e818c298135ee0b331 100644
--- a/hypha/apply/determinations/tests/factories.py
+++ b/hypha/apply/determinations/tests/factories.py
@@ -1,8 +1,18 @@
+import random
+
 import factory
 
 from hypha.apply.funds.tests.factories import ApplicationSubmissionFactory
+from hypha.apply.stream_forms.testing.factories import (
+    CharFieldBlockFactory,
+    FormFieldBlockFactory,
+    StreamFieldUUIDFactory,
+)
+from hypha.apply.utils.testing.factories import RichTextFieldBlockFactory
 
-from ..models import ACCEPTED, NEEDS_MORE_INFO, REJECTED, Determination
+from ..blocks import DeterminationBlock, DeterminationMessageBlock, SendNoticeBlock
+from ..models import Determination, DeterminationForm
+from ..options import ACCEPTED, NEEDS_MORE_INFO, REJECTED
 from ..views import get_form_for_stage
 
 
@@ -44,3 +54,39 @@ class DeterminationFactory(factory.DjangoModelFactory):
     }, dict_factory=DeterminationDataFactory)
 
     is_draft = True
+
+
+class DeterminationBlockFactory(FormFieldBlockFactory):
+    class Meta:
+        model = DeterminationBlock
+
+    @classmethod
+    def make_answer(cls, params=dict()):
+        return random.choices([ACCEPTED, NEEDS_MORE_INFO, REJECTED])
+
+
+class DeterminationMessageBlockFactory(FormFieldBlockFactory):
+    class Meta:
+        model = DeterminationMessageBlock
+
+
+class SendNoticeBlockFactory(FormFieldBlockFactory):
+    class Meta:
+        model = SendNoticeBlock
+
+
+DeterminationFormFieldsFactory = StreamFieldUUIDFactory({
+    'char': CharFieldBlockFactory,
+    'text': RichTextFieldBlockFactory,
+    'send_notice': SendNoticeBlockFactory,
+    'determination': DeterminationBlockFactory,
+    'message': DeterminationMessageBlockFactory,
+})
+
+
+class DeterminationFormFactory(factory.DjangoModelFactory):
+    class Meta:
+        model = DeterminationForm
+
+    name = factory.Faker('word')
+    form_fields = DeterminationFormFieldsFactory
diff --git a/hypha/apply/determinations/tests/test_views.py b/hypha/apply/determinations/tests/test_views.py
index 3c773fddb796829e6ca065b41c9aca6f4fd9480f..34990a9f528c2e0d29995b84a63e46315c2613c8 100644
--- a/hypha/apply/determinations/tests/test_views.py
+++ b/hypha/apply/determinations/tests/test_views.py
@@ -6,7 +6,7 @@ from django.test import RequestFactory, override_settings
 from django.urls import reverse_lazy
 
 from hypha.apply.activity.models import Activity
-from hypha.apply.determinations.models import ACCEPTED, NEEDS_MORE_INFO, REJECTED
+from hypha.apply.determinations.options import ACCEPTED, NEEDS_MORE_INFO, REJECTED
 from hypha.apply.determinations.views import BatchDeterminationCreateView
 from hypha.apply.funds.models import ApplicationSubmission
 from hypha.apply.funds.tests.factories import ApplicationSubmissionFactory
diff --git a/hypha/apply/determinations/utils.py b/hypha/apply/determinations/utils.py
index 6fe8381cb35c57e210e225f6bcdce27dec77b7b6..40210657a364577953b0871195d2b2e8ecb4d3b2 100644
--- a/hypha/apply/determinations/utils.py
+++ b/hypha/apply/determinations/utils.py
@@ -1,6 +1,6 @@
 from hypha.apply.funds.workflow import DETERMINATION_OUTCOMES
 
-from .models import DETERMINATION_TO_OUTCOME, TRANSITION_DETERMINATION
+from .options import DETERMINATION_TO_OUTCOME, TRANSITION_DETERMINATION
 
 OUTCOME_TO_DETERMINATION = {
     v: k
diff --git a/hypha/apply/determinations/views.py b/hypha/apply/determinations/views.py
index 9e92c9da60c5e182e616c48c3a64a48871937aa6..cb65e3a343060faf686405d2a917b7e59acd412c 100644
--- a/hypha/apply/determinations/views.py
+++ b/hypha/apply/determinations/views.py
@@ -19,24 +19,25 @@ from hypha.apply.activity.models import Activity
 from hypha.apply.funds.models import ApplicationSubmission
 from hypha.apply.funds.workflow import DETERMINATION_OUTCOMES
 from hypha.apply.projects.models import Project
+from hypha.apply.stream_forms.models import BaseStreamForm
 from hypha.apply.users.decorators import staff_required
 from hypha.apply.utils.views import CreateOrUpdateView, ViewDispatcher
 
+from .blocks import DeterminationBlock
 from .forms import (
     BatchConceptDeterminationForm,
+    BatchDeterminationForm,
     BatchProposalDeterminationForm,
     ConceptDeterminationForm,
+    DeterminationModelForm,
     ProposalDeterminationForm,
 )
-from .models import (
-    NEEDS_MORE_INFO,
-    TRANSITION_DETERMINATION,
-    Determination,
-    DeterminationMessageSettings,
-)
+from .models import Determination, DeterminationMessageSettings
+from .options import DETERMINATION_CHOICES, NEEDS_MORE_INFO, TRANSITION_DETERMINATION
 from .utils import (
     can_create_determination,
     can_edit_determination,
+    determination_actions,
     has_final_determination,
     outcome_from_actions,
     transition_from_outcome,
@@ -63,8 +64,47 @@ def get_form_for_stage(submission, batch=False, edit=False):
     return forms[index]
 
 
+def get_fields_for_stages(submissions):
+    forms_fields = [
+        get_fields_for_stage(submission)
+        for submission in submissions
+    ]
+    if not all(i == forms_fields[0] for i in forms_fields):
+        raise ValueError('Submissions expect different forms - please contact admin')
+    return forms_fields[0]
+
+
+def get_fields_for_stage(submission):
+    forms = submission.get_from_parent('determination_forms').all()
+    index = submission.workflow.stages.index(submission.stage)
+    try:
+        return forms[index].form.form_fields
+    except IndexError:
+        return forms[0].form.form_fields
+
+
+def outcome_choices_for_phase(submission, user):
+    """
+    Outcome choices correspond to Phase transitions.
+    We need to filter out non-matching choices.
+    i.e. a transition to In Review is not a determination, while Needs more info or Rejected are.
+    """
+    available_choices = set()
+    choices = dict(DETERMINATION_CHOICES)
+    for transition_name in determination_actions(user, submission):
+        try:
+            determination_type = TRANSITION_DETERMINATION[transition_name]
+        except KeyError:
+            pass
+        else:
+            available_choices.add((determination_type, choices[determination_type]))
+
+    return available_choices
+
+
 @method_decorator(staff_required, name='dispatch')
-class BatchDeterminationCreateView(CreateView):
+class BatchDeterminationCreateView(BaseStreamForm, CreateView):
+    submission_form_class = BatchDeterminationForm
     template_name = 'determinations/batch_determination_form.html'
 
     def dispatch(self, *args, **kwargs):
@@ -100,8 +140,44 @@ class BatchDeterminationCreateView(CreateView):
         kwargs.pop('instance')
         return kwargs
 
+    def check_all_submissions_are_of_same_type(self, submissions):
+        """
+        Checks if all the submission as the new determination form attached to it.
+
+        Or all should be using the old determination forms.
+
+        We can not create batch determination with submissions using two different
+        type of forms.
+        """
+        return len(set(
+            [
+                submission.is_determination_form_attached
+                for submission in submissions
+            ]
+        )) == 1
+
     def get_form_class(self):
-        return get_form_for_stages(self.get_submissions())
+        submissions = self.get_submissions()
+        if not self.check_all_submissions_are_of_same_type(submissions):
+            raise ValueError(
+                "All selected submissions excpects determination forms attached"
+                " - please contact admin"
+            )
+        if not submissions[0].is_determination_form_attached:
+            # If all the submission has same type of forms but they are not the
+            # new streamfield forms then use the old determination forms.
+            return get_form_for_stages(submissions)
+        form_fields = self.get_form_fields()
+        field_blocks = self.get_defined_fields()
+        for field_block in field_blocks:
+            if isinstance(field_block.block, DeterminationBlock):
+                # Outcome is already set in case of batch determinations so we do
+                # not need to render this field.
+                form_fields.pop(field_block.id)
+        return type('WagtailStreamForm', (self.submission_form_class,), form_fields)
+
+    def get_defined_fields(self):
+        return get_fields_for_stages(self.get_submissions())
 
     def get_context_data(self, **kwargs):
         outcome = TRANSITION_DETERMINATION[self.get_action()]
@@ -121,7 +197,6 @@ class BatchDeterminationCreateView(CreateView):
             determination.submission.id: determination
             for determination in form.instances
         }
-
         messenger(
             MESSAGES.BATCH_DETERMINATION_OUTCOME,
             request=self.request,
@@ -139,6 +214,10 @@ class BatchDeterminationCreateView(CreateView):
                     'Unable to determine submission "{title}" as already determined'.format(title=submission.title),
                 )
             else:
+                if submission.is_determination_form_attached:
+                    determination.form_fields = self.get_defined_fields()
+                    determination.message = form.cleaned_data[determination.message_field.id]
+                    determination.save()
                 transition = transition_from_outcome(form.cleaned_data.get('outcome'), submission)
 
                 if determination.outcome == NEEDS_MORE_INFO:
@@ -190,7 +269,8 @@ class BatchDeterminationCreateView(CreateView):
 
 
 @method_decorator(staff_required, name='dispatch')
-class DeterminationCreateOrUpdateView(CreateOrUpdateView):
+class DeterminationCreateOrUpdateView(BaseStreamForm, CreateOrUpdateView):
+    submission_form_class = DeterminationModelForm
     model = Determination
     template_name = 'determinations/determination_form.html'
 
@@ -234,8 +314,8 @@ class DeterminationCreateOrUpdateView(CreateOrUpdateView):
             **kwargs
         )
 
-    def get_form_class(self):
-        return get_form_for_stage(self.submission)
+    def get_defined_fields(self):
+        return get_fields_for_stage(self.submission)
 
     def get_form_kwargs(self):
         kwargs = super().get_form_kwargs()
@@ -245,12 +325,29 @@ class DeterminationCreateOrUpdateView(CreateOrUpdateView):
         kwargs['site'] = Site.find_for_request(self.request)
         return kwargs
 
+    def get_form_class(self):
+        if not self.submission.is_determination_form_attached:
+            # If new determination forms are not attached use the old ones.
+            return get_form_for_stage(self.submission)
+        form_fields = self.get_form_fields()
+        field_blocks = self.get_defined_fields()
+        for field_block in field_blocks:
+            if isinstance(field_block.block, DeterminationBlock):
+                outcome_choices = outcome_choices_for_phase(
+                    self.submission, self.request.user
+                )
+                # Outcome field choices need to be set according to the phase.
+                form_fields[field_block.id].choices = outcome_choices
+        return type('WagtailStreamForm', (self.submission_form_class,), form_fields)
+
     def get_success_url(self):
         return self.submission.get_absolute_url()
 
     def form_valid(self, form):
-        super().form_valid(form)
+        if self.submission.is_determination_form_attached:
+            form.instance.form_fields = self.get_defined_fields()
 
+        super().form_valid(form)
         if self.object.is_draft:
             return HttpResponseRedirect(self.submission.get_absolute_url())
 
@@ -263,8 +360,7 @@ class DeterminationCreateOrUpdateView(CreateOrUpdateView):
                 related=self.object,
             )
             proposal_form = form.cleaned_data.get('proposal_form')
-
-            transition = transition_from_outcome(form.cleaned_data.get('outcome'), self.submission)
+            transition = transition_from_outcome(int(self.object.outcome), self.submission)
 
             if self.object.outcome == NEEDS_MORE_INFO:
                 # We keep a record of the message sent to the user in the comment
@@ -275,7 +371,6 @@ class DeterminationCreateOrUpdateView(CreateOrUpdateView):
                     source=self.submission,
                     related_object=self.object,
                 )
-
             self.submission.perform_transition(
                 transition,
                 self.request.user,
@@ -434,41 +529,57 @@ class DeterminationDetailView(ViewDispatcher):
 
 
 @method_decorator(staff_required, name='dispatch')
-class DeterminationEditView(UpdateView):
+class DeterminationEditView(BaseStreamForm, UpdateView):
+    submission_form_class = DeterminationModelForm
     model = Determination
-
-    def get_object(self, queryset=None):
-        return self.model.objects.get(submission=self.submission, id=self.kwargs['pk'])
-
-    def dispatch(self, request, *args, **kwargs):
-        self.submission = get_object_or_404(ApplicationSubmission, id=self.kwargs['submission_pk'])
-        return super().dispatch(request, *args, **kwargs)
+    template_name = 'determinations/determination_form.html'
+    raise_exception = True
 
     def get_context_data(self, **kwargs):
         site = Site.find_for_request(self.request)
         determination_messages = DeterminationMessageSettings.for_site(site)
 
+        determination = self.get_object()
         return super().get_context_data(
-            submission=self.submission,
-            message_templates=determination_messages.get_for_stage(self.submission.stage.name),
+            submission=determination.submission,
+            message_templates=determination_messages.get_for_stage(
+                determination.submission.stage.name
+            ),
             **kwargs
         )
 
-    def get_form_class(self):
-        return get_form_for_stage(self.submission)
+    def get_defined_fields(self):
+        determination = self.get_object()
+        return get_fields_for_stage(determination.submission)
 
     def get_form_kwargs(self):
+        determiantion = self.get_object()
         kwargs = super().get_form_kwargs()
         kwargs['user'] = self.request.user
-        kwargs['submission'] = self.submission
+        kwargs['submission'] = determiantion.submission
+        kwargs['edit'] = True
         kwargs['action'] = self.request.GET.get('action')
         kwargs['site'] = Site.find_for_request(self.request)
-        kwargs['edit'] = True
+        if self.object:
+            kwargs['initial'] = self.object.form_data
         return kwargs
 
+    def get_form_class(self):
+        determination = self.get_object()
+        if not determination.use_new_determination_form:
+            return get_form_for_stage(determination.submission)
+        form_fields = self.get_form_fields()
+        field_blocks = self.get_defined_fields()
+        for field_block in field_blocks:
+            if isinstance(field_block.block, DeterminationBlock):
+                # Outcome can not be edited after being set once, so we do not
+                # need to render this field.
+                form_fields.pop(field_block.id)
+        return type('WagtailStreamForm', (self.submission_form_class,), form_fields)
+
     def form_valid(self, form):
         super().form_valid(form)
-
+        determination = self.get_object()
         messenger(
             MESSAGES.DETERMINATION_OUTCOME,
             request=self.request,
@@ -477,4 +588,4 @@ class DeterminationEditView(UpdateView):
             related=self.object,
         )
 
-        return HttpResponseRedirect(self.submission.get_absolute_url())
+        return HttpResponseRedirect(determination.submission.get_absolute_url())
diff --git a/hypha/apply/funds/admin.py b/hypha/apply/funds/admin.py
index 91d7bc8b3947eafbaa2023a6b85610c1ede9d223..5a48fa379e1c497aa6126ccdcf483e7d2ec96eed 100644
--- a/hypha/apply/funds/admin.py
+++ b/hypha/apply/funds/admin.py
@@ -5,6 +5,7 @@ from wagtail.contrib.modeladmin.helpers import PermissionHelper
 from wagtail.contrib.modeladmin.options import ModelAdmin, ModelAdminGroup
 
 from hypha.apply.categories.admin import CategoryAdmin, MetaTermAdmin
+from hypha.apply.determinations.admin import DeterminationFormAdmin
 from hypha.apply.funds.models import ReviewerRole, ScreeningStatus
 from hypha.apply.review.admin import ReviewFormAdmin
 from hypha.apply.utils.admin import ListRelatedMixin
@@ -76,6 +77,22 @@ class RoundAdmin(BaseRoundAdmin):
 
         return mark_safe('<br />'.join(urls))
 
+    def determination_forms(self, obj):
+        def build_urls(determinations):
+            for determination in determinations:
+                url = reverse(
+                    'determination_determinationform_modeladmin_edit',
+                    args=[determination.form.id]
+                )
+                yield f'<a href="{url}">{determination}</a>'
+
+        urls = list(build_urls(obj.determination_forms.all()))
+
+        if not urls:
+            return
+
+        return mark_safe('<br />'.join(urls))
+
 
 class ScreeningStatusPermissionHelper(PermissionHelper):
     def user_can_edit_obj(self, user, obj):
@@ -185,6 +202,7 @@ class ApplyAdminGroup(ModelAdminGroup):
         RFPAdmin,
         ApplicationFormAdmin,
         ReviewFormAdmin,
+        DeterminationFormAdmin,
         CategoryAdmin,
         ScreeningStatusAdmin,
         ReviewerRoleAdmin,
diff --git a/hypha/apply/funds/admin_forms.py b/hypha/apply/funds/admin_forms.py
index 4d6c7b0b74526aa1d25a51041ea9245e8cd49626..1cca6f9d2be4d20c9e877f863a8a8fdb9730a63b 100644
--- a/hypha/apply/funds/admin_forms.py
+++ b/hypha/apply/funds/admin_forms.py
@@ -12,12 +12,16 @@ class WorkflowFormAdminForm(WagtailAdminPageForm):
         workflow = WORKFLOWS[cleaned_data['workflow_name']]
         application_forms = self.formsets['forms']
         review_forms = self.formsets['review_forms']
+        determination_forms = self.formsets['determination_forms']
         number_of_stages = len(workflow.stages)
 
         self.validate_application_forms(workflow, application_forms)
         if number_of_stages == 1:
             self.validate_stages_equal_forms(workflow, application_forms)
         self.validate_stages_equal_forms(workflow, review_forms, form_type="Review form")
+        self.validate_stages_equal_forms(
+            workflow, determination_forms, form_type="Determination form"
+        )
 
         return cleaned_data
 
diff --git a/hypha/apply/funds/migrations/0077_add_determination_stream_field_forms.py b/hypha/apply/funds/migrations/0077_add_determination_stream_field_forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c84636f35de28766e593862d99feac8fdc5b1c0
--- /dev/null
+++ b/hypha/apply/funds/migrations/0077_add_determination_stream_field_forms.py
@@ -0,0 +1,55 @@
+# Generated by Django 2.2.13 on 2020-07-01 10:19
+
+from django.db import migrations, models
+import django.db.models.deletion
+import modelcluster.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('determinations', '0010_add_determination_stream_field_forms'),
+        ('funds', '0076_multi_input_char_block'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='RoundBaseDeterminationForm',
+            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='determinations.DeterminationForm')),
+                ('round', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='determination_forms', to='funds.RoundBase')),
+            ],
+            options={
+                'ordering': ['sort_order'],
+                'abstract': False,
+            },
+        ),
+        migrations.CreateModel(
+            name='LabBaseDeterminationForm',
+            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='determinations.DeterminationForm')),
+                ('lab', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='determination_forms', to='funds.LabBase')),
+            ],
+            options={
+                'ordering': ['sort_order'],
+                'abstract': False,
+            },
+        ),
+        migrations.CreateModel(
+            name='ApplicationBaseDeterminationForm',
+            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='determination_forms', to='funds.ApplicationBase')),
+                ('form', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='determinations.DeterminationForm')),
+            ],
+            options={
+                'ordering': ['sort_order'],
+                'abstract': False,
+            },
+        ),
+    ]
diff --git a/hypha/apply/funds/models/applications.py b/hypha/apply/funds/models/applications.py
index 19d1a52289e538580122569cd1bbcda6f3dadc70..abd210a8bf6ca961df72d88bab969c4877df7059 100644
--- a/hypha/apply/funds/models/applications.py
+++ b/hypha/apply/funds/models/applications.py
@@ -187,6 +187,7 @@ class RoundBase(WorkflowStreamForm, SubmittableStreamForm):  # type: ignore
         # Forms comes from parental key in models/forms.py
         ReadOnlyInlinePanel('forms', help_text="Copied from the fund."),
         ReadOnlyInlinePanel('review_forms', help_text="Copied from the fund."),
+        ReadOnlyInlinePanel('determination_forms', help_text="Copied from the fund."),
     ]
 
     edit_handler = TabbedInterface([
@@ -227,6 +228,7 @@ class RoundBase(WorkflowStreamForm, SubmittableStreamForm):  # type: ignore
             # Would be nice to do this using model clusters as part of the __init__
             self._copy_forms('forms')
             self._copy_forms('review_forms')
+            self._copy_forms('determination_forms')
 
     def _copy_forms(self, field):
         for form in getattr(self.get_parent().specific, field).all():
diff --git a/hypha/apply/funds/models/forms.py b/hypha/apply/funds/models/forms.py
index e1868c0bae1b1f8cd5a145e64e7c627351728749..351110457b4aeb86cc31bd0f9f75c3345ca69571 100644
--- a/hypha/apply/funds/models/forms.py
+++ b/hypha/apply/funds/models/forms.py
@@ -65,6 +65,34 @@ class LabBaseForm(AbstractRelatedForm):
     lab = ParentalKey('LabBase', related_name='forms')
 
 
+class AbstractRelatedDeterminationForm(Orderable):
+    class Meta(Orderable.Meta):
+        abstract = True
+
+    form = models.ForeignKey(
+        'determinations.DeterminationForm', on_delete=models.PROTECT
+    )
+
+    panels = [
+        FilteredFieldPanel('form', filter_query={
+            'roundbasedeterminationform__isnull': True,
+        })
+    ]
+
+    @property
+    def fields(self):
+        return self.form.form_fields
+
+    def __eq__(self, other):
+        try:
+            return self.fields == other.fields and self.sort_order == other.sort_order
+        except AttributeError:
+            return False
+
+    def __str__(self):
+        return self.form.name
+
+
 class AbstractRelatedReviewForm(Orderable):
     class Meta(Orderable.Meta):
         abstract = True
@@ -101,3 +129,15 @@ class RoundBaseReviewForm(AbstractRelatedReviewForm):
 
 class LabBaseReviewForm(AbstractRelatedReviewForm):
     lab = ParentalKey('LabBase', related_name='review_forms')
+
+
+class ApplicationBaseDeterminationForm(AbstractRelatedDeterminationForm):
+    application = ParentalKey('ApplicationBase', related_name='determination_forms')
+
+
+class RoundBaseDeterminationForm(AbstractRelatedDeterminationForm):
+    round = ParentalKey('RoundBase', related_name='determination_forms')
+
+
+class LabBaseDeterminationForm(AbstractRelatedDeterminationForm):
+    lab = ParentalKey('LabBase', related_name='determination_forms')
diff --git a/hypha/apply/funds/models/submissions.py b/hypha/apply/funds/models/submissions.py
index f13ae29f76522ca729a8f6f55bbe325f366f61d1..b79a0d1fe1a4acb5aeb2f8e3d35e6727f31227db 100644
--- a/hypha/apply/funds/models/submissions.py
+++ b/hypha/apply/funds/models/submissions.py
@@ -536,6 +536,19 @@ class ApplicationSubmission(
             # We are a lab submission
             return getattr(self.page.specific, attribute)
 
+    @property
+    def is_determination_form_attached(self):
+        """
+        We use old django determination forms but now as we are moving
+        to streamfield determination forms which can be created and attached
+        to funds in admin.
+
+        This method checks if there are new determination forms attached to the
+        submission or we would still use the old determination forms for backward
+        compatibility.
+        """
+        return self.get_from_parent('determination_forms').count() > 0
+
     def progress_application(self, **kwargs):
         target = None
         for phase in STAGE_CHANGE_ACTIONS:
diff --git a/hypha/apply/funds/models/utils.py b/hypha/apply/funds/models/utils.py
index 1c5de3edc8b781d920e6da62a79858a408000ccc..1a171e76e3b59440a48dcf09d8b96066629d2d49 100644
--- a/hypha/apply/funds/models/utils.py
+++ b/hypha/apply/funds/models/utils.py
@@ -115,7 +115,8 @@ class WorkflowStreamForm(WorkflowHelpers, AbstractStreamForm):  # type: ignore
     content_panels = AbstractStreamForm.content_panels + [
         FieldPanel('workflow_name'),
         InlinePanel('forms', label="Forms"),
-        InlinePanel('review_forms', label="Review Forms")
+        InlinePanel('review_forms', label="Review Forms"),
+        InlinePanel('determination_forms', label="Determination Forms")
     ]
 
 
diff --git a/hypha/apply/funds/tests/test_admin_form.py b/hypha/apply/funds/tests/test_admin_form.py
index 0975734e77d6b51ca3161d05ea394e5c26a1d2c2..689c665e0365db485e906a136538654b8678f5f9 100644
--- a/hypha/apply/funds/tests/test_admin_form.py
+++ b/hypha/apply/funds/tests/test_admin_form.py
@@ -1,6 +1,7 @@
 import factory
 from django.test import TestCase
 
+from hypha.apply.determinations.tests.factories import DeterminationFormFactory
 from hypha.apply.funds.models import FundType
 from hypha.apply.review.tests.factories import ReviewFormFactory
 
@@ -37,12 +38,15 @@ def formset_base(field, total, delete, factory, same=False, form_stage_info=None
     return base_data
 
 
-def form_data(num_appl_forms=0, num_review_forms=0, delete=0, stages=1, same_forms=False, form_stage_info=[1]):
+def form_data(num_appl_forms=0, num_review_forms=0, num_determination_forms=0, delete=0, stages=1, same_forms=False, form_stage_info=[1]):
     form_data = formset_base(
         'forms', num_appl_forms, delete, same=same_forms, factory=ApplicationFormFactory,
         form_stage_info=form_stage_info)
     review_form_data = formset_base('review_forms', num_review_forms, False, same=same_forms, factory=ReviewFormFactory)
+    determination_form_data = formset_base('determination_forms', num_determination_forms, False, same=same_forms, factory=DeterminationFormFactory)
+
     form_data.update(review_form_data)
+    form_data.update(determination_form_data)
 
     fund_data = factory.build(dict, FACTORY_CLASS=FundTypeFactory)
     fund_data['workflow_name'] = workflow_for_stages(stages)
@@ -64,15 +68,15 @@ class TestWorkflowFormAdminForm(TestCase):
         self.assertTrue(form.errors['__all__'])
 
     def test_validates_with_one_form_one_stage(self):
-        form = self.submit_data(form_data(1, 1))
+        form = self.submit_data(form_data(1, 1, 1))
         self.assertTrue(form.is_valid(), form.errors.as_text())
 
     def test_validates_with_one_form_one_stage_with_deleted(self):
-        form = self.submit_data(form_data(1, 1, delete=1, form_stage_info=[2, 1]))
+        form = self.submit_data(form_data(1, 1, 1, delete=1, form_stage_info=[2, 1]))
         self.assertTrue(form.is_valid(), form.errors.as_text())
 
     def test_doesnt_validates_with_two_forms_one_stage(self):
-        form = self.submit_data(form_data(2, 2, form_stage_info=[1, 2]))
+        form = self.submit_data(form_data(2, 2, 2, form_stage_info=[1, 2]))
         self.assertFalse(form.is_valid())
         self.assertTrue(form.errors['__all__'])
         formset_errors = form.formsets['forms'].errors
@@ -82,14 +86,14 @@ class TestWorkflowFormAdminForm(TestCase):
         self.assertTrue(formset_errors[1]['form'])
 
     def test_can_save_two_forms(self):
-        form = self.submit_data(form_data(2, 2, stages=2, form_stage_info=[1, 2]))
+        form = self.submit_data(form_data(2, 2, 2, stages=2, form_stage_info=[1, 2]))
         self.assertTrue(form.is_valid())
 
     def test_can_save_multiple_forms_stage_two(self):
-        form = self.submit_data(form_data(3, 2, stages=2, form_stage_info=[1, 2, 2]))
+        form = self.submit_data(form_data(3, 2, 2, stages=2, form_stage_info=[1, 2, 2]))
         self.assertTrue(form.is_valid())
 
     def test_doesnt_validates_with_two_first_stage_forms_in_two_stage(self):
-        form = self.submit_data(form_data(2, 2, stages=2, form_stage_info=[1, 1]))
+        form = self.submit_data(form_data(2, 2, 2, stages=2, form_stage_info=[1, 1]))
         self.assertFalse(form.is_valid())
         self.assertTrue(form.errors['__all__'])
diff --git a/hypha/apply/funds/tests/test_admin_views.py b/hypha/apply/funds/tests/test_admin_views.py
index 0fc27921618046fa1beb1238b26a697fb6ef99f8..a9e2ab0ad9112886f348f8842c168e29a18950af 100644
--- a/hypha/apply/funds/tests/test_admin_views.py
+++ b/hypha/apply/funds/tests/test_admin_views.py
@@ -17,13 +17,14 @@ class TestFundCreationView(TestCase):
         cls.user = SuperUserFactory()
         cls.home = ApplyHomePageFactory()
 
-    def create_page(self, appl_forms=1, review_forms=1, stages=1, same_forms=False, form_stage_info=[1]):
+    def create_page(self, appl_forms=1, review_forms=1, determination_forms=1, stages=1, same_forms=False, form_stage_info=[1]):
         self.client.force_login(self.user)
         url = reverse('wagtailadmin_pages:add', args=('funds', 'fundtype', self.home.id))
 
         data = form_data(
             appl_forms,
             review_forms,
+            determination_forms,
             same_forms=same_forms,
             stages=stages,
             form_stage_info=form_stage_info,
@@ -47,21 +48,25 @@ class TestFundCreationView(TestCase):
         fund = self.create_page()
         self.assertEqual(fund.forms.count(), 1)
         self.assertEqual(fund.review_forms.count(), 1)
+        self.assertEqual(fund.determination_forms.count(), 1)
 
     def test_can_create_multi_phase_fund(self):
-        fund = self.create_page(2, 2, stages=2, form_stage_info=[1, 2])
+        fund = self.create_page(2, 2, 2, stages=2, form_stage_info=[1, 2])
         self.assertEqual(fund.forms.count(), 2)
         self.assertEqual(fund.review_forms.count(), 2)
+        self.assertEqual(fund.determination_forms.count(), 2)
 
     def test_can_create_multiple_forms_second_stage_in_fund(self):
-        fund = self.create_page(4, 2, stages=2, form_stage_info=[1, 2, 2, 2])
+        fund = self.create_page(4, 2, 2, stages=2, form_stage_info=[1, 2, 2, 2])
         self.assertEqual(fund.forms.count(), 4)
         self.assertEqual(fund.review_forms.count(), 2)
+        self.assertEqual(fund.determination_forms.count(), 2)
 
     def test_can_create_multi_phase_fund_reuse_forms(self):
-        fund = self.create_page(2, 2, same_forms=True, stages=2, form_stage_info=[1, 2])
+        fund = self.create_page(2, 2, 2, same_forms=True, stages=2, form_stage_info=[1, 2])
         self.assertEqual(fund.forms.count(), 2)
         self.assertEqual(fund.review_forms.count(), 2)
+        self.assertEqual(fund.determination_forms.count(), 2)
 
 
 class TestRoundIndexView(WagtailTestUtils, TestCase):