diff --git a/opentech/apply/categories/blocks.py b/opentech/apply/categories/blocks.py
index e0c92bb60efdcaa50081e015a7c3c4c35853e9f4..5a0c5cf11c20bea90e7aa9c9e37a1178755059a7 100644
--- a/opentech/apply/categories/blocks.py
+++ b/opentech/apply/categories/blocks.py
@@ -21,6 +21,9 @@ class ModelChooserBlock(ChooserBlock):
 
 
 class CategoryQuestionBlock(OptionalFormFieldBlock):
+    class Meta:
+        template = 'stream_forms/render_list_field.html'
+
     # Overwrite field label and help text so we can defer to the category
     # as required
     field_label = CharBlock(
@@ -63,3 +66,12 @@ class CategoryQuestionBlock(OptionalFormFieldBlock):
             return forms.CheckboxSelectMultiple
         else:
             return forms.RadioSelect
+
+    def render(self, value, context):
+        data = context['data']
+        category = value['category']
+        context['data'] = category.options.filter(id__in=data).values_list('value', flat=True)
+        return super().render(value, context)
+
+    def get_searchable_content(self, value, data):
+        return None
diff --git a/opentech/apply/dashboard/static/js/django_select2-checkboxes.js b/opentech/apply/dashboard/static/js/django_select2-checkboxes.js
new file mode 100644
index 0000000000000000000000000000000000000000..a024d320e07b3437ce0572680ab76b26f97c5dc9
--- /dev/null
+++ b/opentech/apply/dashboard/static/js/django_select2-checkboxes.js
@@ -0,0 +1,23 @@
+(function ($) {
+    var init = function ($element) {
+        options = {
+            placeholder: $element.data('placeholder'),
+            templateSelection: function(selected, total) {
+                let filterType = this.placeholder;
+                if ( !selected.length ) {
+                    return filterType;
+                } else if ( selected.length===total ) {
+                    return 'All ' + filterType + ' selected';
+                }
+                return selected.length + ' of ' + total + ' ' + filterType + ' selected';
+            }
+        };
+        $element.select2MultiCheckboxes(options);
+    };
+    $(function () {
+        $('.django-select2-checkboxes').each(function (i, element) {
+            var $element = $(element);
+            init($element);
+        });
+    });
+}(this.jQuery));
diff --git a/opentech/apply/dashboard/static/js/select2.multi-checkboxes.js b/opentech/apply/dashboard/static/js/select2.multi-checkboxes.js
new file mode 100644
index 0000000000000000000000000000000000000000..ec6182afb5654175c7aeb51643c9329943f315fc
--- /dev/null
+++ b/opentech/apply/dashboard/static/js/select2.multi-checkboxes.js
@@ -0,0 +1,79 @@
+/**
+ * jQuery Select2 Multi checkboxes
+ * - allow to select multi values via normal dropdown control
+ *
+ * author      : wasikuss
+ * repo        : https://github.com/wasikuss/select2-multi-checkboxes
+ * inspired by : https://github.com/select2/select2/issues/411
+ * License     : MIT
+ */
+(function($) {
+  var S2MultiCheckboxes = function(options, element) {
+    var self = this;
+    self.options = options;
+    self.$element = $(element);
+  var values = self.$element.val();
+    self.$element.removeAttr('multiple');
+    self.select2 = self.$element.select2({
+      allowClear: true,
+      minimumResultsForSearch: options.minimumResultsForSearch,
+      placeholder: options.placeholder,
+      closeOnSelect: false,
+      templateSelection: function() {
+        return self.options.templateSelection(self.$element.val() || [], $('option', self.$element).length);
+      },
+      templateResult: function(result) {
+        if (result.loading !== undefined)
+          return result.text;
+        return $('<div>').text(result.text).addClass(self.options.wrapClass);
+      },
+      matcher: function(params, data) {
+        var original_matcher = $.fn.select2.defaults.defaults.matcher;
+        var result = original_matcher(params, data);
+        if (result && self.options.searchMatchOptGroups && data.children && result.children && data.children.length != result.children.length) {
+          result.children = data.children;
+        }
+        return result;
+      }
+    }).data('select2');
+    self.select2.$results.off("mouseup").on("mouseup", ".select2-results__option[aria-selected]", (function(self) {
+      return function(evt) {
+        var $this = $(this);
+
+        var data = $this.data('data');
+
+        if ($this.attr('aria-selected') === 'true') {
+          self.trigger('unselect', {
+            originalEvent: evt,
+            data: data
+          });
+          return;
+        }
+
+        self.trigger('select', {
+          originalEvent: evt,
+          data: data
+        });
+      }
+    })(self.select2));
+    self.$element.attr('multiple', 'multiple').val(values).trigger('change.select2');
+  }
+
+  $.fn.extend({
+    select2MultiCheckboxes: function() {
+      var options = $.extend({
+        placeholder: 'Choose elements',
+        templateSelection: function(selected, total) {
+          return selected.length + ' > ' + total + ' total';
+        },
+        wrapClass: 'wrap',
+        minimumResultsForSearch: -1,
+        searchMatchOptGroups: true
+      }, arguments[0]);
+
+      this.each(function() {
+        new S2MultiCheckboxes(options, this);
+      });
+    }
+  });
+})(jQuery);
diff --git a/opentech/apply/dashboard/tables.py b/opentech/apply/dashboard/tables.py
index 3ae000d46e2ae10636743f3e77b624528b0679cc..4e85da55a0e1612b90c0e19d342ae3dd93724e15 100644
--- a/opentech/apply/dashboard/tables.py
+++ b/opentech/apply/dashboard/tables.py
@@ -1,17 +1,78 @@
+from django import forms
+from django.contrib.auth import get_user_model
+from django.utils.text import mark_safe
+
+import django_filters as filters
 import django_tables2 as tables
-from opentech.apply.funds.models import ApplicationSubmission
+from django_tables2.utils import A
+
+from wagtail.wagtailcore.models import Page
+
+from opentech.apply.funds.models import ApplicationSubmission, Round
+from opentech.apply.funds.workflow import status_options
+from .widgets import Select2MultiCheckboxesWidget
 
 
 class DashboardTable(tables.Table):
+    title = tables.LinkColumn('funds:submission', args=[A('pk')], orderable=True)
     submit_time = tables.DateColumn(verbose_name="Submitted")
     status_name = tables.Column(verbose_name="Status")
     stage = tables.Column(verbose_name="Type")
     page = tables.Column(verbose_name="Fund")
+    lead = tables.Column(accessor='round.specific.lead', verbose_name='Lead')
 
     class Meta:
         model = ApplicationSubmission
-        fields = ('title', 'status_name', 'stage', 'page', 'round', 'submit_time', 'user')
+        fields = ('title', 'status_name', 'stage', 'page', 'round', 'submit_time')
+        sequence = ('title', 'status_name', 'stage', 'page', 'round', 'lead', 'submit_time')
         template = "dashboard/tables/table.html"
 
     def render_user(self, value):
         return value.get_full_name()
+
+    def render_status_name(self, value):
+        return mark_safe(f'<span>{ value }</span>')
+
+
+def get_used_rounds(request):
+    return Round.objects.filter(submissions__isnull=False).distinct()
+
+
+def get_used_funds(request):
+    # Use page to pick up on both Labs and Funds
+    return Page.objects.filter(applicationsubmission__isnull=False).distinct()
+
+
+def get_round_leads(request):
+    User = get_user_model()
+    return User.objects.filter(round__isnull=False).distinct()
+
+
+class Select2CheckboxWidgetMixin:
+    def __init__(self, *args, **kwargs):
+        label = kwargs.get('label')
+        kwargs.setdefault('widget', Select2MultiCheckboxesWidget(attrs={'data-placeholder': label}))
+        super().__init__(*args, **kwargs)
+
+
+class Select2MultipleChoiceFilter(Select2CheckboxWidgetMixin, filters.MultipleChoiceFilter):
+    pass
+
+
+class Select2ModelMultipleChoiceFilter(Select2MultipleChoiceFilter, filters.ModelMultipleChoiceFilter):
+    pass
+
+
+class SubmissionFilter(filters.FilterSet):
+    round = Select2ModelMultipleChoiceFilter(queryset=get_used_rounds, label='Rounds')
+    funds = Select2ModelMultipleChoiceFilter(name='page', queryset=get_used_funds, label='Funds')
+    status = Select2MultipleChoiceFilter(name='status__contains', choices=status_options, label='Statuses')
+    lead = Select2ModelMultipleChoiceFilter(name='round__round__lead', queryset=get_round_leads, label='Leads')
+
+    class Meta:
+        model = ApplicationSubmission
+        fields = ('funds', 'round', 'status')
+
+
+class SubmissionFilterAndSearch(SubmissionFilter):
+    query = filters.CharFilter(name='search_data', lookup_expr="search", widget=forms.HiddenInput)
diff --git a/opentech/apply/dashboard/templates/dashboard/dashboard.html b/opentech/apply/dashboard/templates/dashboard/dashboard.html
index ee2f7fc260f32525cd43019b34d9ff0487df6f5d..1a71bf96fe3b3dd7757793eb59c23fa84f1efbf2 100644
--- a/opentech/apply/dashboard/templates/dashboard/dashboard.html
+++ b/opentech/apply/dashboard/templates/dashboard/dashboard.html
@@ -1,20 +1,29 @@
 {% extends "base-apply.html" %}
 {% load render_table from django_tables2 %}
-
-{% block extra_css %}
-    {# To remove after the demo and we have some style #}
-    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.9/semantic.min.css"/>
-{% endblock %}
-
+{% block title %}OTF Dashboard{% endblock %}
 {% block content %}
 <div class="wrapper wrapper--breakout wrapper--admin">
-    <div class="wrapper wrapper--medium">
-        <h3>Received Content</h3>
-        <h5>Track and explore recent submissions</h5>
+    <div class="wrapper wrapper--large">
+        {% block page_header %}
+            <h3>Received Submissions</h3>
+            <h5>Track and explore recent submissions</h5>
+        {% endblock %}
     </div>
+    {% include "dashboard/includes/search.html" %}
 </div>
 
 <div class="wrapper wrapper--medium wrapper--top-bottom-inner-space">
-    {% render_table object_list %}
+    {% if filter %}
+        <form action="" method="get" class="form form-inline">
+            {{ filter.form.as_p }}
+            <button type="submit" value="Filter">Filter</button>
+        </form>
+    {% endif %}
+    {% render_table table %}
 </div>
+
+{% endblock %}
+
+{% block extra_js %}
+    {{ filter.form.media }}
 {% endblock %}
diff --git a/opentech/apply/dashboard/templates/dashboard/includes/search.html b/opentech/apply/dashboard/templates/dashboard/includes/search.html
new file mode 100644
index 0000000000000000000000000000000000000000..8797eff8dee3b0fb978c7ce731d76f8d2347d0d5
--- /dev/null
+++ b/opentech/apply/dashboard/templates/dashboard/includes/search.html
@@ -0,0 +1,6 @@
+<form action="{% url 'dashboard:search' %}" method="get" role="search" class="form form--header-search-desktop">
+    <button class="button" type="submit" aria-label="Search">
+        <svg class="icon icon--magnifying-glass icon--search"><use xlink:href="#magnifying-glass"></use></svg>
+    </button>
+    <input class="input input--transparent input--secondary" type="text" placeholder="Search…" name="query"{% if search_query %} value="{{ search_query }}{% endif %}" aria-label="Search input">
+</form>
diff --git a/opentech/apply/dashboard/templates/dashboard/search.html b/opentech/apply/dashboard/templates/dashboard/search.html
new file mode 100644
index 0000000000000000000000000000000000000000..bbf644442b04139ff5de5b02756e62d2c3a7915c
--- /dev/null
+++ b/opentech/apply/dashboard/templates/dashboard/search.html
@@ -0,0 +1,10 @@
+{% extends "dashboard/dashboard.html" %}
+
+{% block page_header %}
+    <div class="wrapper wrapper--medium">
+        <h3>Search Results</h3>
+        {% if search_term %}
+            <h5>There are {{ object_list|length }} results for: {{ search_term }}</h5>
+        {% endif %}
+    </div>
+{% endblock %}
diff --git a/opentech/apply/dashboard/templates/dashboard/tables/table.html b/opentech/apply/dashboard/templates/dashboard/tables/table.html
index 6ff5e60321ee8084988b7726392a5c40e4ab3dcf..49e9c41a1f8093cebb452a68e321a11eb78d72aa 100644
--- a/opentech/apply/dashboard/templates/dashboard/tables/table.html
+++ b/opentech/apply/dashboard/templates/dashboard/tables/table.html
@@ -1,5 +1,4 @@
-{% extends 'django_tables2/semantic.html' %}
-{# Change back to after demo: extends 'django_tables2/table.html' #}
+{% extends 'django_tables2/table.html' %}
 
 {# example of how to extend the table template #}
 {% block table.tbody.row %}
diff --git a/opentech/apply/dashboard/urls.py b/opentech/apply/dashboard/urls.py
index b5bfdcafa3957fc4753d43a4d1055be469ff971c..07b7da83c765958c13cf34d29fa614abdfe84ab5 100644
--- a/opentech/apply/dashboard/urls.py
+++ b/opentech/apply/dashboard/urls.py
@@ -1,8 +1,9 @@
 from django.conf.urls import url
 
-from .views import DashboardView
+from .views import DashboardView, SearchView
 
 
 urlpatterns = [
-    url(r'^$', DashboardView.as_view(), name="dashboard")
+    url(r'^$', DashboardView.as_view(), name="dashboard"),
+    url(r'^search$', SearchView.as_view(), name="search"),
 ]
diff --git a/opentech/apply/dashboard/views.py b/opentech/apply/dashboard/views.py
index b86e281c466be79136ff303496990da608fd46c3..34d5deb34e4e54d724470ab27352d1ea3ecebb1b 100644
--- a/opentech/apply/dashboard/views.py
+++ b/opentech/apply/dashboard/views.py
@@ -1,17 +1,25 @@
-from django.views.generic import ListView
-from django_tables2 import RequestConfig
+from django_filters.views import FilterView
+from django_tables2.views import SingleTableMixin
 
 from opentech.apply.funds.models import ApplicationSubmission
 
-from .tables import DashboardTable
+from .tables import DashboardTable, SubmissionFilter, SubmissionFilterAndSearch
 
 
-class DashboardView(ListView):
+class DashboardView(SingleTableMixin, FilterView):
     model = ApplicationSubmission
     template_name = 'dashboard/dashboard.html'
+    table_class = DashboardTable
+
+    filterset_class = SubmissionFilter
+
+
+class SearchView(SingleTableMixin, FilterView):
+    template_name = 'dashboard/search.html'
+    table_class = DashboardTable
+
+    filterset_class = SubmissionFilterAndSearch
 
     def get_context_data(self, **kwargs):
-        context = super().get_context_data(**kwargs)
-        context['object_list'] = DashboardTable(context['object_list'])
-        RequestConfig(self.request).configure(context['object_list'])
-        return context
+        search_term = self.request.GET.get('query')
+        return super().get_context_data(search_term=search_term, **kwargs)
diff --git a/opentech/apply/dashboard/widgets.py b/opentech/apply/dashboard/widgets.py
new file mode 100644
index 0000000000000000000000000000000000000000..a053acfb0b28717708aee1375c0937d64eaf047c
--- /dev/null
+++ b/opentech/apply/dashboard/widgets.py
@@ -0,0 +1,22 @@
+from django.contrib.staticfiles.templatetags.staticfiles import static
+
+from django_select2.forms import Select2MultipleWidget
+
+
+class Select2MultiCheckboxesWidget(Select2MultipleWidget):
+    def __init__(self, *args, **kwargs):
+        attrs = kwargs.get('attrs', {})
+        attrs.setdefault('data-placeholder', 'items')
+        kwargs['attrs'] = attrs
+        super().__init__(*args, **kwargs)
+
+    def build_attrs(self, *args, **kwargs):
+        attrs = super().build_attrs(*args, **kwargs)
+        attrs['class'] = attrs['class'].replace('django-select2', 'django-select2-checkboxes')
+        return attrs
+
+    @property
+    def media(self):
+        media = super().media
+        media.add_js([static('js/select2.multi-checkboxes.js'), static('js/django_select2-checkboxes.js')])
+        return media
diff --git a/opentech/apply/funds/blocks.py b/opentech/apply/funds/blocks.py
index 770c321c26e15c2cea647c5a68b689fd2b68d87f..4a759e0ef7821d9eda48f0a9af9db5ba5fcd4386 100644
--- a/opentech/apply/funds/blocks.py
+++ b/opentech/apply/funds/blocks.py
@@ -1,12 +1,13 @@
 from collections import Counter
 
+import bleach
 from django import forms
 from django.core.exceptions import ValidationError
 from django.forms.utils import ErrorList
 from django.utils.translation import ugettext_lazy as _
 from django.utils.text import mark_safe
 
-from wagtail.wagtailcore.blocks import StaticBlock
+from wagtail.wagtailcore.blocks import StaticBlock, StreamValue
 
 from tinymce.widgets import TinyMCE
 
@@ -39,12 +40,28 @@ class RichTextFieldBlock(TextFieldBlock):
     widget = TinyMCE(mce_attrs={
         'elementpath': False,
         'branding': False,
+        'toolbar1': 'undo redo | styleselect | bold italic | bullist numlist | link',
+        'style_formats': [
+            {'title': 'Headers', 'items': [
+                {'title': 'Header 1', 'format': 'h1'},
+                {'title': 'Header 2', 'format': 'h2'},
+                {'title': 'Header 3', 'format': 'h3'},
+            ]},
+            {'title': 'Inline', 'items': [
+                {'title': 'Bold', 'icon': 'bold', 'format': 'bold'},
+                {'title': 'Italic', 'icon': 'italic', 'format': 'italic'},
+                {'title': 'Underline', 'icon': 'underline', 'format': 'underline'},
+            ]},
+        ],
     })
 
     class Meta:
         label = _('Rich text field')
         icon = 'form'
 
+    def get_searchable_content(self, value, data):
+        return bleach.clean(data, tags=[], strip=True)
+
 
 class CustomFormFieldsBlock(FormFieldsBlock):
     rich_text = RichTextFieldBlock(group=_('Fields'))
@@ -99,6 +116,14 @@ class CustomFormFieldsBlock(FormFieldsBlock):
                 [ValidationError('Error', params={field: new_error})]
             )
 
+    def to_python(self, value):
+        # If the data type is missing, fallback to a CharField
+        for child_data in value:
+            if child_data['type'] not in self.child_blocks:
+                child_data['type'] = 'char'
+
+        return StreamValue(self, value, is_lazy=True)
+
 
 class MustIncludeStatic(StaticBlock):
     """Helper block which displays additional information about the must include block and
diff --git a/opentech/apply/funds/migrations/0022_applicationsubmission_form_fields.py b/opentech/apply/funds/migrations/0022_applicationsubmission_form_fields.py
new file mode 100644
index 0000000000000000000000000000000000000000..be99a13260756412bada68dbf73a2b5a5ab175e5
--- /dev/null
+++ b/opentech/apply/funds/migrations/0022_applicationsubmission_form_fields.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.8 on 2018-02-13 15:10
+from __future__ import unicode_literals
+
+from django.db import migrations
+import opentech.apply.categories.blocks
+import wagtail.wagtailcore.blocks
+import wagtail.wagtailcore.blocks.static_block
+import wagtail.wagtailcore.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('funds', '0021_rename_workflow_field'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='applicationsubmission',
+            name='form_fields',
+            field=wagtail.wagtailcore.fields.StreamField((('text_markup', wagtail.wagtailcore.blocks.RichTextBlock(group='Other', label='Paragraph')), ('char', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('format', wagtail.wagtailcore.blocks.ChoiceBlock(choices=[('email', 'Email'), ('url', 'URL')], label='Format', required=False)), ('default_value', wagtail.wagtailcore.blocks.CharBlock(label='Default value', required=False))), group='Fields')), ('text', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.TextBlock(label='Default value', required=False))), group='Fields')), ('number', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.CharBlock(label='Default value', required=False))), group='Fields')), ('checkbox', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('default_value', wagtail.wagtailcore.blocks.BooleanBlock(required=False))), group='Fields')), ('radios', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailcore.blocks.CharBlock(label='Choice')))), group='Fields')), ('dropdown', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailcore.blocks.CharBlock(label='Choice')))), group='Fields')), ('checkboxes', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('checkboxes', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailcore.blocks.CharBlock(label='Checkbox')))), group='Fields')), ('date', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.DateBlock(required=False))), group='Fields')), ('time', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.TimeBlock(required=False))), group='Fields')), ('datetime', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.DateTimeBlock(required=False))), group='Fields')), ('image', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False))), group='Fields')), ('file', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False))), group='Fields')), ('rich_text', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.TextBlock(label='Default value', required=False))), group='Fields')), ('category', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(help_text='Leave blank to use the default Category label', label='Label', required=False)), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Leave blank to use the default Category help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('category', opentech.apply.categories.blocks.ModelChooserBlock('categories.Category')), ('multi', wagtail.wagtailcore.blocks.BooleanBlock(label='Multi select', required=False))), group='Custom')), ('title', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')), ('value', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')), ('email', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')), ('address', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')), ('full_name', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required'))), default=[]),
+            preserve_default=False,
+        ),
+    ]
diff --git a/opentech/apply/funds/migrations/0023_round_lead.py b/opentech/apply/funds/migrations/0023_round_lead.py
new file mode 100644
index 0000000000000000000000000000000000000000..df3a73882afa0a1f4fa94d1c1d67d8d52af6a839
--- /dev/null
+++ b/opentech/apply/funds/migrations/0023_round_lead.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.8 on 2018-02-14 17:21
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('funds', '0022_applicationsubmission_form_fields'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='round',
+            name='lead',
+            field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+            preserve_default=False,
+        ),
+    ]
diff --git a/opentech/apply/funds/migrations/0024_applicationsubmission_search_data.py b/opentech/apply/funds/migrations/0024_applicationsubmission_search_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..97dc908a30de6f1bb1d71609d4d66dfb6ab262f8
--- /dev/null
+++ b/opentech/apply/funds/migrations/0024_applicationsubmission_search_data.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.8 on 2018-02-16 16:29
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('funds', '0023_round_lead'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='applicationsubmission',
+            name='search_data',
+            field=models.TextField(default=''),
+            preserve_default=False,
+        ),
+    ]
diff --git a/opentech/apply/funds/migrations/0025_update_with_file_blocks.py b/opentech/apply/funds/migrations/0025_update_with_file_blocks.py
new file mode 100644
index 0000000000000000000000000000000000000000..6b836eaa40822d8465dfa54c6c62da8b12424858
--- /dev/null
+++ b/opentech/apply/funds/migrations/0025_update_with_file_blocks.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.8 on 2018-02-21 15:14
+from __future__ import unicode_literals
+
+from django.db import migrations
+import opentech.apply.categories.blocks
+import wagtail.wagtailcore.blocks
+import wagtail.wagtailcore.blocks.static_block
+import wagtail.wagtailcore.fields
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('funds', '0024_applicationsubmission_search_data'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='applicationform',
+            name='form_fields',
+            field=wagtail.wagtailcore.fields.StreamField((('text_markup', wagtail.wagtailcore.blocks.RichTextBlock(group='Other', label='Paragraph')), ('char', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('format', wagtail.wagtailcore.blocks.ChoiceBlock(choices=[('email', 'Email'), ('url', 'URL')], label='Format', required=False)), ('default_value', wagtail.wagtailcore.blocks.CharBlock(label='Default value', required=False))), group='Fields')), ('text', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.TextBlock(label='Default value', required=False))), group='Fields')), ('number', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.CharBlock(label='Default value', required=False))), group='Fields')), ('checkbox', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('default_value', wagtail.wagtailcore.blocks.BooleanBlock(required=False))), group='Fields')), ('radios', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailcore.blocks.CharBlock(label='Choice')))), group='Fields')), ('dropdown', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailcore.blocks.CharBlock(label='Choice')))), group='Fields')), ('checkboxes', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('checkboxes', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailcore.blocks.CharBlock(label='Checkbox')))), group='Fields')), ('date', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.DateBlock(required=False))), group='Fields')), ('time', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.TimeBlock(required=False))), group='Fields')), ('datetime', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.DateTimeBlock(required=False))), group='Fields')), ('image', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False))), group='Fields')), ('file', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False))), group='Fields')), ('multi_file', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False))), group='Fields')), ('rich_text', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.TextBlock(label='Default value', required=False))), group='Fields')), ('category', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(help_text='Leave blank to use the default Category label', label='Label', required=False)), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Leave blank to use the default Category help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('category', opentech.apply.categories.blocks.ModelChooserBlock('categories.Category')), ('multi', wagtail.wagtailcore.blocks.BooleanBlock(label='Multi select', required=False))), group='Custom')), ('title', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')), ('value', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')), ('email', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')), ('address', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')), ('full_name', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')))),
+        ),
+        migrations.AlterField(
+            model_name='applicationsubmission',
+            name='form_fields',
+            field=wagtail.wagtailcore.fields.StreamField((('text_markup', wagtail.wagtailcore.blocks.RichTextBlock(group='Other', label='Paragraph')), ('char', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('format', wagtail.wagtailcore.blocks.ChoiceBlock(choices=[('email', 'Email'), ('url', 'URL')], label='Format', required=False)), ('default_value', wagtail.wagtailcore.blocks.CharBlock(label='Default value', required=False))), group='Fields')), ('text', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.TextBlock(label='Default value', required=False))), group='Fields')), ('number', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.CharBlock(label='Default value', required=False))), group='Fields')), ('checkbox', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('default_value', wagtail.wagtailcore.blocks.BooleanBlock(required=False))), group='Fields')), ('radios', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailcore.blocks.CharBlock(label='Choice')))), group='Fields')), ('dropdown', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailcore.blocks.CharBlock(label='Choice')))), group='Fields')), ('checkboxes', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('checkboxes', wagtail.wagtailcore.blocks.ListBlock(wagtail.wagtailcore.blocks.CharBlock(label='Checkbox')))), group='Fields')), ('date', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.DateBlock(required=False))), group='Fields')), ('time', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.TimeBlock(required=False))), group='Fields')), ('datetime', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.DateTimeBlock(required=False))), group='Fields')), ('image', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False))), group='Fields')), ('file', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False))), group='Fields')), ('multi_file', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False))), group='Fields')), ('rich_text', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.wagtailcore.blocks.TextBlock(label='Default value', required=False))), group='Fields')), ('category', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(help_text='Leave blank to use the default Category label', label='Label', required=False)), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Leave blank to use the default Category help text', required=False)), ('required', wagtail.wagtailcore.blocks.BooleanBlock(label='Required', required=False)), ('category', opentech.apply.categories.blocks.ModelChooserBlock('categories.Category')), ('multi', wagtail.wagtailcore.blocks.BooleanBlock(label='Multi select', required=False))), group='Custom')), ('title', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')), ('value', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')), ('email', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')), ('address', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')), ('full_name', wagtail.wagtailcore.blocks.StructBlock((('field_label', wagtail.wagtailcore.blocks.CharBlock(label='Label')), ('help_text', wagtail.wagtailcore.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.wagtailcore.blocks.static_block.StaticBlock())), group='Required')))),
+        ),
+    ]
diff --git a/opentech/apply/funds/models.py b/opentech/apply/funds/models.py
index 7a1ec0461e3d517d13358d44d33c28a5faf43590..eb486c5799942b755e2dd64aebb933ed978fde66 100644
--- a/opentech/apply/funds/models.py
+++ b/opentech/apply/funds/models.py
@@ -1,9 +1,11 @@
 from datetime import date
+import os
 
 from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.contrib.postgres.fields import JSONField
 from django.core.exceptions import ValidationError
+from django.core.files.storage import default_storage
 from django.core.serializers.json import DjangoJSONEncoder
 from django.db import models
 from django.db.models import Q
@@ -30,7 +32,9 @@ from wagtail.wagtailcore.fields import StreamField
 from wagtail.wagtailcore.models import Orderable
 from wagtail.wagtailforms.models import AbstractEmailForm, AbstractFormSubmission
 
+from opentech.apply.stream_forms.blocks import FileFieldBlock
 from opentech.apply.stream_forms.models import AbstractStreamForm
+from opentech.apply.users.groups import STAFF_GROUP_NAME
 
 from .blocks import CustomFormFieldsBlock, MustIncludeFieldBlock, REQUIRED_BLOCK_NAMES
 from .edit_handlers import FilteredFieldPanel, ReadOnlyPanel, ReadOnlyInlinePanel
@@ -59,32 +63,12 @@ class SubmittableStreamForm(AbstractStreamForm):
         return ApplicationSubmission
 
     def process_form_submission(self, form):
-        cleaned_data = form.cleaned_data
-        for field in self.get_defined_fields():
-            # Update the ids which are unique to use the unique name
-            if isinstance(field.block, MustIncludeFieldBlock):
-                response = cleaned_data.pop(field.id)
-                cleaned_data[field.block.name] = response
-
-        if form.user.is_authenticated():
-            user = form.user
-            cleaned_data['email'] = user.email
-            cleaned_data['full_name'] = user.get_full_name()
-        else:
-            # Rely on the form having the following must include fields (see blocks.py)
-            email = cleaned_data.get('email')
-            full_name = cleaned_data.get('full_name')
-
-            User = get_user_model()
-            user, _ = User.objects.get_or_create_and_notify(
-                email=email,
-                site=self.get_site(),
-                defaults={'full_name': full_name}
-            )
-
+        if not form.user.is_authenticated():
+            form.user = None
         return self.get_submission_class().objects.create(
-            form_data=cleaned_data,
-            **self.get_submit_meta_data(user=user),
+            form_data=form.cleaned_data,
+            form_fields=self.get_defined_fields(),
+            **self.get_submit_meta_data(user=form.user),
         )
 
     def get_submit_meta_data(self, **kwargs):
@@ -150,22 +134,21 @@ class EmailForm(AbstractEmailForm):
 
     def process_form_submission(self, form):
         submission = super().process_form_submission(form)
-        self.send_mail(form)
+        self.send_mail(submission)
         return submission
 
-    def send_mail(self, form):
-        data = form.cleaned_data
-        email = data.get('email')
+    def send_mail(self, submission):
+        user = submission.user
         context = {
-            'name': data.get('full_name'),
-            'email': email,
-            'project_name': data.get('title'),
+            'name': user.get_full_name(),
+            'email': user.email,
+            'project_name': submission.form_data.get('title'),
             'extra_text': self.confirmation_text_extra,
             'fund_type': self.title,
         }
 
         subject = self.subject if self.subject else 'Thank you for your submission to Open Technology Fund'
-        send_mail(subject, render_to_string('funds/email/confirmation.txt', context), (email,), self.from_address, )
+        send_mail(subject, render_to_string('funds/email/confirmation.txt', context), (user.email,), self.from_address, )
 
     email_confirmation_panels = [
         MultiFieldPanel(
@@ -207,7 +190,11 @@ class FundType(EmailForm, WorkflowStreamForm):  # type: ignore
         ).first()
 
     def next_deadline(self):
-        return self.open_round.end_date
+        try:
+            return self.open_round.end_date
+        except AttributeError:
+            # There isn't an open round
+            return None
 
     def serve(self, request):
         if hasattr(request, 'is_preview') or not self.open_round:
@@ -273,6 +260,7 @@ class Round(WorkflowStreamForm, SubmittableStreamForm):  # type: ignore
     parent_page_types = ['funds.FundType']
     subpage_types = []  # type: ignore
 
+    lead = models.ForeignKey(settings.AUTH_USER_MODEL, limit_choices_to={'groups__name': STAFF_GROUP_NAME})
     start_date = models.DateField(default=date.today)
     end_date = models.DateField(
         blank=True,
@@ -282,6 +270,7 @@ class Round(WorkflowStreamForm, SubmittableStreamForm):  # type: ignore
     )
 
     content_panels = SubmittableStreamForm.content_panels + [
+        FieldPanel('lead'),
         MultiFieldPanel([
             FieldRowPanel([
                 FieldPanel('start_date'),
@@ -330,7 +319,7 @@ class Round(WorkflowStreamForm, SubmittableStreamForm):  # type: ignore
 
     def process_form_submission(self, form):
         submission = super().process_form_submission(form)
-        self.get_parent().specific.send_mail(form)
+        self.get_parent().specific.send_mail(submission)
         return submission
 
     def clean(self):
@@ -439,10 +428,14 @@ class JSONOrderable(models.QuerySet):
 
 
 class ApplicationSubmission(WorkflowHelpers, AbstractFormSubmission):
+    field_template = 'funds/includes/submission_field.html'
+
     form_data = JSONField(encoder=DjangoJSONEncoder)
+    form_fields = StreamField(CustomFormFieldsBlock())
     page = models.ForeignKey('wagtailcore.Page', on_delete=models.PROTECT)
     round = models.ForeignKey('wagtailcore.Page', on_delete=models.PROTECT, related_name='submissions', null=True)
     user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True)
+    search_data = models.TextField()
 
     # Workflow inherited from WorkflowHelpers
     status = models.CharField(max_length=254)
@@ -461,7 +454,55 @@ class ApplicationSubmission(WorkflowHelpers, AbstractFormSubmission):
     def phase(self):
         return self.workflow.current(self.status)
 
+    def ensure_user_has_account(self):
+        if self.user and self.user.is_authenticated():
+            self.form_data['email'] = self.user.email
+            self.form_data['full_name'] = self.user.get_full_name()
+        else:
+            # Rely on the form having the following must include fields (see blocks.py)
+            email = self.form_data.get('email')
+            full_name = self.form_data.get('full_name')
+
+            User = get_user_model()
+            self.user, _ = User.objects.get_or_create_and_notify(
+                email=email,
+                site=self.page.get_site(),
+                defaults={'full_name': full_name}
+            )
+
+    def handle_file(self, file):
+        # File is potentially optional
+        if file:
+            file_path = os.path.join('submissions', 'user', str(self.user.id), file.name)
+            filename = default_storage.generate_filename(file_path)
+            saved_name = default_storage.save(filename, file)
+            return {
+                'name': file.name,
+                'path': saved_name,
+                'url': default_storage.url(saved_name)
+            }
+
+    def handle_files(self, files):
+        if isinstance(files, list):
+            return [self.handle_file(file) for file in files]
+
+        return self.handle_file(files)
+
     def save(self, *args, **kwargs):
+        for field in self.form_fields:
+            # Update the ids which are unique to use the unique name
+            if isinstance(field.block, MustIncludeFieldBlock):
+                response = self.form_data.pop(field.id, None)
+                if response:
+                    self.form_data[field.block.name] = response
+
+        self.ensure_user_has_account()
+
+        for field in self.form_fields:
+            if isinstance(field.block, FileFieldBlock):
+                file = self.form_data[field.id]
+                self.form_data[field.id] = self.handle_files(file)
+
         if not self.id:
             # We are creating the object default to first stage
             try:
@@ -471,8 +512,40 @@ class ApplicationSubmission(WorkflowHelpers, AbstractFormSubmission):
                 self.workflow_name = self.page.workflow_name
             self.status = str(self.workflow.first())
 
+        # add a denormed version of the answer for searching
+        self.search_data = ' '.join(self.prepare_search_values())
+
         return super().save(*args, **kwargs)
 
+    def data_and_fields(self):
+        for stream_value in self.form_fields:
+            try:
+                data = self.form_data[stream_value.id]
+            except KeyError:
+                pass  # It was a named field or a paragraph
+            else:
+                yield data, stream_value
+
+    def render_answers(self):
+        fields = [
+            field.render(context={'data': data})
+            for data, field in self.data_and_fields()
+        ]
+        return mark_safe(''.join(fields))
+
+    def prepare_search_values(self):
+        for data, stream in self.data_and_fields():
+            value = stream.block.get_searchable_content(stream.value, data)
+            if value:
+                if isinstance(value, list):
+                    yield ', '.join(value)
+                else:
+                    yield value
+
+        # Add named fields into the search index
+        for field in ['email', 'title']:
+            yield getattr(self, field)
+
     def get_data(self):
         # Updated for JSONField
         form_data = self.form_data
diff --git a/opentech/apply/funds/templates/funds/applicationsubmission_detail.html b/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
new file mode 100644
index 0000000000000000000000000000000000000000..a733b9e6d3b36c9fac9e8bf21ff325cabaca2722
--- /dev/null
+++ b/opentech/apply/funds/templates/funds/applicationsubmission_detail.html
@@ -0,0 +1,35 @@
+{% extends "base-apply.html" %}
+
+{% block content %}
+<div class="wrapper wrapper--breakout wrapper--admin">
+    <div class="wrapper wrapper--medium">
+        <h3>{{ object.title }}</h3>
+        <h5>{{ object.stage }} | {{ object.page }} | {{ object.round }} | Lead: {{ object.round.specific.lead }}</h5>
+    </div>
+
+    {% include "funds/includes/status_bar.html" with workflow=object.workflow status=object.status %}
+
+</div>
+
+<div>Submission Details</div>
+
+<div class="wrapper wrapper--medium wrapper--top-bottom-inner-space">
+    Submitted: {{ object.submit_time.date }} by {{ object.user.get_full_name }}
+    <h2>Proposal Information</h2>
+    <div>
+        Requested Funding {{ object.value }}
+        Project Duration {{ object.value }}
+        Legal Name {{ object.full_name }}
+        Email {{ object.email }}
+    </div>
+    <div>
+        {{ object.render_answers }}
+    </div>
+</div>
+<div>
+    <h3>Other Submissions</h3>
+    {% for submission in other_submissions %}
+        <a href="{% url 'funds:submission' submission.id %}">{{ submission.title }}</a>
+    {% endfor %}
+</div>
+{% endblock %}
diff --git a/opentech/apply/funds/templates/funds/demo_workflow.html b/opentech/apply/funds/templates/funds/demo_workflow.html
index 0187115e51dd9930bb31df9825de4903e4f4d906..f7b514e17b36e4f2fec14260959624d9969b66b0 100644
--- a/opentech/apply/funds/templates/funds/demo_workflow.html
+++ b/opentech/apply/funds/templates/funds/demo_workflow.html
@@ -7,8 +7,8 @@
     <main class="wrapper">
         <nav>
             <section class="container">
-                <a class="button button-clear" href="{% url 'workflow_demo' 1 %}">Single Stage</a>
-                <a class="button button-clear" href="{% url 'workflow_demo' 2 %}">Double Stage</a>
+                <a class="button button-clear" href="{% url 'funds:workflow_demo' 1 %}">Single Stage</a>
+                <a class="button button-clear" href="{% url 'funds:workflow_demo' 2 %}">Double Stage</a>
             </section>
         </nav>
         <section class="container">
diff --git a/opentech/apply/funds/templates/funds/fund_type.html b/opentech/apply/funds/templates/funds/fund_type.html
index 075599e6ce86acc0c96fb2c5daf6953703fac075..1415fd1ebce20830e880cabed094c9168bc0e4e0 100644
--- a/opentech/apply/funds/templates/funds/fund_type.html
+++ b/opentech/apply/funds/templates/funds/fund_type.html
@@ -26,7 +26,7 @@
         {# the fund has no open rounds and we arent on a round page #}
         <h3>{% trans "Sorry this fund is not accepting applications at the moment" %}</h3>
     {% else%}
-        <form class="form" action="" method="POST">
+        <form class="form" action="" method="POST" enctype="multipart/form-data">
             {{ form.media }}
             {% csrf_token %}
 
diff --git a/opentech/apply/funds/templates/funds/includes/field.html b/opentech/apply/funds/templates/funds/includes/field.html
index 935b4cef3efb5ecea41a7c678fbaa1204419527d..c2b2dca8b13a0992889b7498fcaa8c813e4af5c6 100644
--- a/opentech/apply/funds/templates/funds/includes/field.html
+++ b/opentech/apply/funds/templates/funds/includes/field.html
@@ -3,7 +3,7 @@
 {% with widget_type=field|widget_type field_type=field|field_type %}
 
 <div class="form__group {% if field.errors %}form__error{% endif %}">
-    {% if widget_type == 'clearable_file_input' %}
+    {% if widget_type == 'clearable_file_input' or widget_type == 'multi_file_input' %}
         <span class="form__question">{{ field.label }}</span>
         <label for="{{ field.id_for_label }}" class="form__question form__question--{{ field_type }} {{ widget_type }}" {% if field.field.required %}required{% endif %}>
             <span>Upload</span>
diff --git a/opentech/apply/funds/templates/funds/includes/status_bar.html b/opentech/apply/funds/templates/funds/includes/status_bar.html
new file mode 100644
index 0000000000000000000000000000000000000000..04d26c7bfc1cbd02dd643a1054a8fdd8b20e083e
--- /dev/null
+++ b/opentech/apply/funds/templates/funds/includes/status_bar.html
@@ -0,0 +1,5 @@
+{% for phase in workflow %}
+<div class="{% if phase == status %}current{% endif %}" style="{% if phase == status %}font-weight:bold;{% endif %}">
+    {{ phase.name }}
+</div>
+{% endfor %}
diff --git a/opentech/apply/funds/tests/factories/blocks.py b/opentech/apply/funds/tests/factories/blocks.py
index e568c5c3d3958aaa0c6a715745f1c84f86c180a2..4d5863edce829d2b843f68cf2c600f724eaabfbc 100644
--- a/opentech/apply/funds/tests/factories/blocks.py
+++ b/opentech/apply/funds/tests/factories/blocks.py
@@ -1,15 +1,46 @@
+import json
+import uuid
+
+from wagtail.wagtailcore.blocks import CharBlock
 import wagtail_factories
 
-from opentech.apply.stream_forms.blocks import FormFieldBlock
+from opentech.apply.stream_forms import blocks as stream_blocks
 from opentech.apply.funds import blocks
 
 
-__all__ = ['CustomFormFieldsFactory', 'FormFieldBlock', 'FullNameBlockFactory', 'EmailBlockFactory']
+__all__ = ['CustomFormFieldsFactory', 'FormFieldBlockFactory', 'FullNameBlockFactory', 'EmailBlockFactory']
+
+
+class CharBlockFactory(wagtail_factories.blocks.BlockFactory):
+    class Meta:
+        model = CharBlock
 
 
 class FormFieldBlockFactory(wagtail_factories.StructBlockFactory):
     class Meta:
-        model = FormFieldBlock
+        model = stream_blocks.FormFieldBlock
+
+
+class CharFieldBlockFactory(FormFieldBlockFactory):
+    class Meta:
+        model = stream_blocks.CharFieldBlock
+
+
+class NumberFieldBlockFactory(FormFieldBlockFactory):
+    class Meta:
+        model = stream_blocks.NumberFieldBlock
+
+
+class RadioFieldBlockFactory(FormFieldBlockFactory):
+    choices = wagtail_factories.ListBlockFactory(CharBlockFactory)
+
+    class Meta:
+        model = stream_blocks.RadioButtonsFieldBlock
+
+
+class TitleBlockFactory(FormFieldBlockFactory):
+    class Meta:
+        model = blocks.TitleBlock
 
 
 class EmailBlockFactory(FormFieldBlockFactory):
@@ -22,7 +53,29 @@ class FullNameBlockFactory(FormFieldBlockFactory):
         model = blocks.FullNameBlock
 
 
-CustomFormFieldsFactory = wagtail_factories.StreamFieldFactory({
+class RichTextFieldBlockFactory(FormFieldBlockFactory):
+    class Meta:
+        model = blocks.RichTextFieldBlock
+
+
+class StreamFieldUUIDFactory(wagtail_factories.StreamFieldFactory):
+    def generate(self, *args, **kwargs):
+        blocks = super().generate(*args, **kwargs)
+        ret_val = list()
+        # Convert to JSON so we can add id before create
+        for block_name, value in blocks:
+            block = self.factories[block_name]._meta.model()
+            value = block.get_prep_value(value)
+            ret_val.append({'type': block_name, 'value': value, 'id': str(uuid.uuid4())})
+        return json.dumps(ret_val)
+
+
+CustomFormFieldsFactory = StreamFieldUUIDFactory({
+    'title': TitleBlockFactory,
     'email': EmailBlockFactory,
     'full_name': FullNameBlockFactory,
+    'char': CharFieldBlockFactory,
+    'number': NumberFieldBlockFactory,
+    'radios': RadioFieldBlockFactory,
+    'rich_text': RichTextFieldBlockFactory,
 })
diff --git a/opentech/apply/funds/tests/factories/models.py b/opentech/apply/funds/tests/factories/models.py
index 116b07094810dbda0b7bf52cc8cf1aff315337c1..5fada182149ec8ec79849618c28f25efe78bc8ae 100644
--- a/opentech/apply/funds/tests/factories/models.py
+++ b/opentech/apply/funds/tests/factories/models.py
@@ -1,10 +1,13 @@
+from collections import defaultdict
 import datetime
+import json
 
 import factory
 import wagtail_factories
 
 from opentech.apply.funds.models import (
     AbstractRelatedForm,
+    ApplicationSubmission,
     ApplicationForm,
     FundType,
     FundForm,
@@ -13,6 +16,8 @@ from opentech.apply.funds.models import (
     Round,
     RoundForm,
 )
+from opentech.apply.users.tests.factories import UserFactory
+from opentech.apply.users.groups import STAFF_GROUP_NAME
 
 from . import blocks
 
@@ -21,6 +26,7 @@ __all__ = [
     'FundTypeFactory',
     'FundFormFactory',
     'ApplicationFormFactory',
+    'ApplicationSubmissionFactory',
     'RoundFactory',
     'RoundFormFactory',
     'LabFactory',
@@ -28,6 +34,25 @@ __all__ = [
 ]
 
 
+def build_form(data, prefix=''):
+    if prefix:
+        prefix += '__'
+
+    extras = defaultdict(dict)
+    for key, value in data.items():
+        if 'form_fields' in key:
+            _, field, attr = key.split('__')
+            extras[field][attr] = value
+
+    form_fields = {}
+    for i, field in enumerate(blocks.CustomFormFieldsFactory.factories.keys()):
+        form_fields[f'{prefix}form_fields__{i}__{field}__'] = ''
+        for attr, value in extras[field].items():
+            form_fields[f'{prefix}form_fields__{i}__{field}__{attr}'] = value
+
+    return form_fields
+
+
 class FundTypeFactory(wagtail_factories.PageFactory):
     class Meta:
         model = FundType
@@ -41,11 +66,7 @@ class FundTypeFactory(wagtail_factories.PageFactory):
     @factory.post_generation
     def forms(self, create, extracted, **kwargs):
         if create:
-            fields = {
-                f'form__form_fields__{i}__{field}__': ''
-                for i, field in enumerate(blocks.CustomFormFieldsFactory.factories.keys())
-            }
-            fields.update(**kwargs)
+            fields = build_form(kwargs, prefix='form')
             for _ in range(len(self.workflow_class.stage_classes)):
                 # Generate a form based on all defined fields on the model
                 FundFormFactory(
@@ -82,6 +103,7 @@ class RoundFactory(wagtail_factories.PageFactory):
     title = factory.Sequence('Round {}'.format)
     start_date = factory.LazyFunction(datetime.date.today)
     end_date = factory.LazyFunction(lambda: datetime.date.today() + datetime.timedelta(days=7))
+    lead = factory.SubFactory(UserFactory, groups__name=STAFF_GROUP_NAME)
 
 
 class RoundFormFactory(AbstractRelatedFormFactory):
@@ -106,3 +128,33 @@ class LabFormFactory(AbstractRelatedFormFactory):
     class Meta:
         model = LabForm
     lab = factory.SubFactory(LabFactory, parent=None)
+
+
+class FormDataFactory(factory.Factory):
+    def _create(self, *args, form_fields='{}', **kwargs):
+        form_fields = json.loads(form_fields)
+        form_data = {}
+        for field in form_fields:
+            try:
+                answer = kwargs[field['type']]
+            except KeyError:
+                answer = 'the answer'
+            form_data[field['id']] = answer
+
+        return form_data
+
+
+class ApplicationSubmissionFactory(factory.DjangoModelFactory):
+    class Meta:
+        model = ApplicationSubmission
+
+    form_fields = blocks.CustomFormFieldsFactory
+    form_data = factory.SubFactory(FormDataFactory, form_fields=factory.SelfAttribute('..form_fields'))
+    page = factory.SubFactory(FundTypeFactory)
+    round = factory.SubFactory(RoundFactory)
+    user = factory.SubFactory(UserFactory)
+
+    @classmethod
+    def _generate(cls, strat, params):
+        params.update(**build_form(params))
+        return super()._generate(strat, params)
diff --git a/opentech/apply/funds/tests/test_models.py b/opentech/apply/funds/tests/test_models.py
index 86c173891275de97020ffb14b3543f80f4fb73e3..487526f6233e910edbd19e1247884f27eb5d4738 100644
--- a/opentech/apply/funds/tests/test_models.py
+++ b/opentech/apply/funds/tests/test_models.py
@@ -14,6 +14,7 @@ from opentech.apply.funds.workflow import SingleStage
 
 from .factories import (
     ApplicationFormFactory,
+    ApplicationSubmissionFactory,
     CustomFormFieldsFactory,
     FundTypeFactory,
     LabFactory,
@@ -69,6 +70,9 @@ class TestFundModel(TestCase):
         new_round.save()
         self.assertEqual(self.fund.open_round, None)
 
+    def test_no_round_exists(self):
+        self.assertIsNone(self.fund.next_deadline())
+
 
 class TestRoundModelDates(TestCase):
     def setUp(self):
@@ -147,6 +151,7 @@ class TestRoundModelWorkflowAndForms(TestCase):
 
         self.round = RoundFactory.build()
         self.round.parent_page = self.fund
+        self.round.lead = RoundFactory.lead.get_factory()(**RoundFactory.lead.defaults)
 
         self.fund.add_child(instance=self.round)
 
@@ -185,6 +190,7 @@ class TestFormSubmission(TestCase):
         application_form = {
             'form_fields__0__email__': '',
             'form_fields__1__full_name__': '',
+            'form_fields__2__title__': '',
         }
         form = ApplicationFormFactory(**application_form)
         fund = FundTypeFactory()
@@ -205,7 +211,7 @@ class TestFormSubmission(TestCase):
 
         page = page or self.round_page
         fields = page.get_form_fields()
-        data = {k: v for k, v in zip(fields, [email, name])}
+        data = {k: v for k, v in zip(fields, [email, name, 'project'])}
 
         request = self.request_factory.post('', data)
         request.user = user
@@ -235,7 +241,8 @@ class TestFormSubmission(TestCase):
     def test_can_submit_if_new(self):
         self.submit_form()
 
-        self.assertEqual(self.User.objects.count(), 1)
+        # Lead + applicant
+        self.assertEqual(self.User.objects.count(), 2)
         new_user = self.User.objects.get(email=self.email)
         self.assertEqual(new_user.get_full_name(), self.name)
 
@@ -246,7 +253,8 @@ class TestFormSubmission(TestCase):
         self.submit_form()
         self.submit_form()
 
-        self.assertEqual(self.User.objects.count(), 1)
+        # Lead + applicant
+        self.assertEqual(self.User.objects.count(), 2)
 
         user = self.User.objects.get(email=self.email)
         self.assertEqual(ApplicationSubmission.objects.count(), 2)
@@ -257,9 +265,10 @@ class TestFormSubmission(TestCase):
         # Someone else submits a form
         self.submit_form(email='another@email.com')
 
-        self.assertEqual(self.User.objects.count(), 2)
+        # Lead + 2 x applicant
+        self.assertEqual(self.User.objects.count(), 3)
 
-        first_user, second_user = self.User.objects.all()
+        _, first_user, second_user = self.User.objects.all()
         self.assertEqual(ApplicationSubmission.objects.count(), 2)
         self.assertEqual(ApplicationSubmission.objects.first().user, first_user)
         self.assertEqual(ApplicationSubmission.objects.last().user, second_user)
@@ -267,11 +276,13 @@ class TestFormSubmission(TestCase):
     def test_associated_if_logged_in(self):
         user, _ = self.User.objects.get_or_create(email=self.email, defaults={'full_name': self.name})
 
-        self.assertEqual(self.User.objects.count(), 1)
+        # Lead + Applicant
+        self.assertEqual(self.User.objects.count(), 2)
 
         self.submit_form(email=self.email, name=self.name, user=user)
 
-        self.assertEqual(self.User.objects.count(), 1)
+        # Lead + Applicant
+        self.assertEqual(self.User.objects.count(), 2)
 
         self.assertEqual(ApplicationSubmission.objects.count(), 1)
         self.assertEqual(ApplicationSubmission.objects.first().user, user)
@@ -280,12 +291,14 @@ class TestFormSubmission(TestCase):
     def test_errors_if_blank_user_data_even_if_logged_in(self):
         user, _ = self.User.objects.get_or_create(email=self.email, defaults={'full_name': self.name})
 
-        self.assertEqual(self.User.objects.count(), 1)
+        # Lead + applicant
+        self.assertEqual(self.User.objects.count(), 2)
 
         response = self.submit_form(email='', name='', user=user)
         self.assertContains(response, 'This field is required')
 
-        self.assertEqual(self.User.objects.count(), 1)
+        # Lead + applicant
+        self.assertEqual(self.User.objects.count(), 2)
 
         self.assertEqual(ApplicationSubmission.objects.count(), 0)
 
@@ -300,3 +313,56 @@ class TestFormSubmission(TestCase):
         # "Thank you for your submission" and "Account Creation"
         self.assertEqual(len(mail.outbox), 2)
         self.assertEqual(mail.outbox[0].to[0], self.email)
+
+
+class TestApplicationSubmission(TestCase):
+    def make_submission(self, **kwargs):
+        return ApplicationSubmissionFactory(**kwargs)
+
+    def test_can_get_required_block_names(self):
+        email = 'test@test.com'
+        submission = self.make_submission(user__email=email)
+        self.assertEqual(submission.email, email)
+
+    def test_can_get_ordered_qs(self):
+        # Emails are created sequentially
+        submission_a = self.make_submission()
+        submission_b = self.make_submission(round=submission_a.round)
+        submissions = [submission_a, submission_b]
+        self.assertEqual(
+            list(ApplicationSubmission.objects.order_by('email')),
+            submissions,
+        )
+
+    def test_can_get_reverse_ordered_qs(self):
+        submission_a = self.make_submission()
+        submission_b = self.make_submission(round=submission_a.round)
+        submissions = [submission_b, submission_a]
+        self.assertEqual(
+            list(ApplicationSubmission.objects.order_by('-email')),
+            submissions,
+        )
+
+    def test_richtext_in_char_is_removed_for_search(self):
+        text = 'I am text'
+        rich_text = f'<b>{text}</b>'
+        submission = self.make_submission(form_data__char=rich_text)
+        self.assertNotIn(rich_text, submission.search_data)
+        self.assertIn(text, submission.search_data)
+
+    def test_richtext_is_removed_for_search(self):
+        text = 'I am text'
+        rich_text = f'<b>{text}</b>'
+        submission = self.make_submission(form_data__rich_text=rich_text)
+        self.assertNotIn(rich_text, submission.search_data)
+        self.assertIn(text, submission.search_data)
+
+    def test_choices_added_for_search(self):
+        choices = ['blah', 'foo']
+        submission = self.make_submission(form_fields__radios__choices=choices, form_data__radios=['blah'])
+        self.assertIn('blah', submission.search_data)
+
+    def test_number_not_in_search(self):
+        value = 12345
+        submission = self.make_submission(form_data__number=value)
+        self.assertNotIn(str(value), submission.search_data)
diff --git a/opentech/apply/funds/urls.py b/opentech/apply/funds/urls.py
index 9d1bd77962f3ed7588235332bf0f14ffedc8e163..8df134af8e039de324dab47e08f81b181d61e905 100644
--- a/opentech/apply/funds/urls.py
+++ b/opentech/apply/funds/urls.py
@@ -1,7 +1,9 @@
 from django.conf.urls import url
 
-from .views import demo_workflow
+from .views import SubmissionDetailView, demo_workflow
+
 
 urlpatterns = [
-    url(r'^demo/(?P<wf_id>[1-2])/$', demo_workflow, name="workflow_demo")
+    url(r'^demo/(?P<wf_id>[1-2])/$', demo_workflow, name="workflow_demo"),
+    url(r'^submission/(?P<pk>\d+)/$', SubmissionDetailView.as_view(), name="submission"),
 ]
diff --git a/opentech/apply/funds/views.py b/opentech/apply/funds/views.py
index bb3b10454f1c82a47bebcdcc1b0c48ab2f339241..9dbb30a19f7c0a8b5027e09ba3a7431f3a3a8e31 100644
--- a/opentech/apply/funds/views.py
+++ b/opentech/apply/funds/views.py
@@ -1,9 +1,21 @@
 from django import forms
 from django.template.response import TemplateResponse
+from django.views.generic import DetailView
 
+from .models import ApplicationSubmission
 from .workflow import SingleStage, DoubleStage
 
 
+class SubmissionDetailView(DetailView):
+    model = ApplicationSubmission
+
+    def get_context_data(self, **kwargs):
+        return super().get_context_data(
+            other_submissions=self.model.objects.filter(user=self.object.user).exclude(id=self.object.id),
+            **kwargs
+        )
+
+
 workflows = [SingleStage, DoubleStage]
 
 
diff --git a/opentech/apply/funds/workflow.py b/opentech/apply/funds/workflow.py
index 6659d734203f337f4ec92059af4a7a0a5810a934..b356dd158876933ec3ab865c374fea8839389a40 100644
--- a/opentech/apply/funds/workflow.py
+++ b/opentech/apply/funds/workflow.py
@@ -1,6 +1,6 @@
 import copy
 
-from typing import List, Sequence, Type, Union
+from typing import Iterable, Iterator, List, Sequence, Type, Union
 
 from django.forms import Form
 from django.utils.text import slugify
@@ -22,7 +22,7 @@ def phase_name(stage: 'Stage', phase: Union['Phase', str], occurrence: int) -> s
     return '__'.join([stage.name, phase_name, str(occurrence)])
 
 
-class Workflow:
+class Workflow(Iterable):
     """
     A Workflow is a collection of Stages an application goes through. When a Stage is complete,
     it will return the next Stage in the list or `None` if no such Stage exists.
@@ -36,6 +36,10 @@ class Workflow:
 
         self.stages = [stage(form) for stage, form in zip(self.stage_classes, forms)]
 
+    def __iter__(self) -> Iterator['Phase']:
+        for stage in self.stages:
+            yield from stage
+
     def current(self, current_phase: Union[str, 'Phase']) -> Union['Phase', None]:
         if isinstance(current_phase, Phase):
             return current_phase
@@ -94,7 +98,7 @@ class Workflow:
         return self.name
 
 
-class Stage:
+class Stage(Iterable):
     """
     Holds the Phases that are progressed through as part of the workflow process
     """
@@ -120,6 +124,9 @@ class Stage:
             new_phases.append(copy.copy(phase))
         self.phases = new_phases
 
+    def __iter__(self) -> Iterator['Phase']:
+        yield from self.phases
+
     def __str__(self) -> str:
         return self.name
 
@@ -168,7 +175,9 @@ class Phase:
         self._actions = {action.name: action for action in self.actions}
         self.occurrence: int = 0
 
-    def __eq__(self, other: object) -> bool:
+    def __eq__(self, other: Union[object, str]) -> bool:
+        if isinstance(other, str):
+            return str(self) == other
         to_match = ['name', 'occurrence']
         return all(getattr(self, attr) == getattr(other, attr) for attr in to_match)
 
@@ -302,3 +311,7 @@ class SingleStage(Workflow):
 class DoubleStage(Workflow):
     name = 'Two Stage'
     stage_classes = [ConceptStage, ProposalStage]
+
+
+statuses = set(phase.name for phase in Phase.__subclasses__())
+status_options = [(slugify(opt), opt) for opt in statuses]
diff --git a/opentech/apply/home/templates/apply_home/apply_home_page.html b/opentech/apply/home/templates/apply_home/apply_home_page.html
index 455a52f6c236b9b0045267e248b2d2815f0cbb7f..e1a67fcb97c62e0c0bc9edf7f7de4302bc14e70d 100644
--- a/opentech/apply/home/templates/apply_home/apply_home_page.html
+++ b/opentech/apply/home/templates/apply_home/apply_home_page.html
@@ -6,15 +6,23 @@
 {% block header_modifier %}header--light-bg{% endblock %}
 
 {% block content %}
-<div class="wrapper wrapper--small wrapper--top-bottom-inner-space">
+<div class="wrapper wrapper--small">
     {% if page.strapline %}
-        <h4 class="heading heading--listings-introduction">{{ page.strapline }}</h4>
+        <h4 class="heading heading--regular">{{ page.strapline }}</h4>
     {% endif %}
 
+    <div class="wrapper wrapper--breakout">
+        <svg class="icon icon--body-pixels-right"><use xlink:href="#body-pixels-right"></use></svg>
+    </div>
+
     <div class="wrapper wrapper--listings">
     {% for child_page in page.get_children.public.live %}
         {% include "apply_home/includes/apply_listing.html" with page=child_page %}
     {% endfor %}
     </div>
+
+    <div class="wrapper wrapper--breakout">
+        <svg class="icon icon--body-pixels-left"><use xlink:href="#body-pixels-left"></use></svg>
+    </div>
 </div>
 {% endblock %}
diff --git a/opentech/apply/home/templates/apply_home/includes/apply_listing.html b/opentech/apply/home/templates/apply_home/includes/apply_listing.html
index 1b459f7a6c280fa3be761df5b1f53f5307aed3be..dd1083c61b3a4bc5e4a78b2fb64f0f9e9e0a6ef5 100644
--- a/opentech/apply/home/templates/apply_home/includes/apply_listing.html
+++ b/opentech/apply/home/templates/apply_home/includes/apply_listing.html
@@ -1,24 +1,45 @@
 {% load wagtailcore_tags %}
 
 {% with details=page.specific.detail.specific %}
-<div class="listing">
-    <h4 class="listing__title">
-        {# details may be None, so be more verbose in the handling of the title #}
-        {% if page.title %}
-            {{ page.title }}
-        {% else %}
-            {{ details.listing_title|default:details.title }}
-        {% endif %}
-    </h4>
+{% if page.specific.open_round %}
+  <div class="listing listing--not-a-link">
+      <div>
+          <h4 class="listing__title listing__title--link">
+              {% if details.deadline %}
+                  <p class="listing__deadline">
+                      <svg class="icon icon--calendar icon--small"><use xlink:href="#calendar"></use></svg>
+                      <span>Next deadline: {{ details.deadline|date:"M j, Y" }}</span>
+                  </p>
+              {% endif %}
+              {# details may be None, so be more verbose in the handling of the title #}
+              {% if page.title %}
+                  {% if details %}
+                      <a href="{% pageurl details %}">
+                  {% endif %}
 
-    {% if details.listing_summary or details.introduction %}
-        <h6 class="listing__teaser">{{ details.listing_summary|default:details.introduction|truncatechars_html:155 }}</h6>
-    {% endif %}
+                  {{ page.title }}
 
-    {% if details %}
-        <a href="{% pageurl details %}">More info...</a>
-    {% endif %}
+                  {% if details %}
+                      </a>
+                  {% endif %}
+              {% else %}
+                  {% if details %}
+                      <a href="{% pageurl details %}">
+                  {% endif %}
 
-    <a class="" href="{% pageurl page %}">Apply</a>
-</div>
+                  {{ details.listing_title|default:details.title }}
+
+                  {% if details %}
+                      </a>
+                  {% endif %}
+              {% endif %}
+          </h4>
+
+          {% if details.listing_summary or details.introduction %}
+              <h6 class="listing__teaser">{{ details.listing_summary|default:details.introduction|truncatechars_html:120 }}</h6>
+          {% endif %}
+      </div>
+      <a class="listing__button" href="{% pageurl page %}">Apply</a>
+  </div>   
+{% endif %}
 {% endwith %}
diff --git a/opentech/apply/stream_forms/blocks.py b/opentech/apply/stream_forms/blocks.py
index 00ac5026f87581a245ec0fba0982fcd3ed656d24..339f7cab2939c43c426eb810f1464a993747a059 100644
--- a/opentech/apply/stream_forms/blocks.py
+++ b/opentech/apply/stream_forms/blocks.py
@@ -1,4 +1,6 @@
 # Credit to https://github.com/BertrandBordage for initial implementation
+import bleach
+
 from django import forms
 from django.db.models import BLANK_CHOICE_DASH
 from django.utils.dateparse import parse_datetime
@@ -11,6 +13,8 @@ from wagtail.wagtailcore.blocks import (
     DateBlock, TimeBlock, DateTimeBlock, ChoiceBlock, RichTextBlock
 )
 
+from .fields import MultiFileField
+
 
 class FormFieldBlock(StructBlock):
     field_label = CharBlock(label=_('Label'))
@@ -19,6 +23,9 @@ class FormFieldBlock(StructBlock):
     field_class = forms.CharField
     widget = None
 
+    class Meta:
+        template = 'stream_forms/render_field.html'
+
     def get_slug(self, struct_value):
         return force_text(slugify(unidecode(struct_value['field_label'])))
 
@@ -43,10 +50,16 @@ class FormFieldBlock(StructBlock):
         return self.get_field_class(struct_value)(
             **self.get_field_kwargs(struct_value))
 
+    def get_searchable_content(self, value, data):
+        return str(data)
+
 
 class OptionalFormFieldBlock(FormFieldBlock):
     required = BooleanBlock(label=_('Required'), required=False)
 
+    def get_searchable_content(self, value, data):
+        return data
+
 
 CHARFIELD_FORMATS = [
     ('email', _('Email')),
@@ -60,6 +73,7 @@ class CharFieldBlock(OptionalFormFieldBlock):
 
     class Meta:
         label = _('Text field (single line)')
+        template = 'stream_forms/render_unsafe_field.html'
 
     def get_field_class(self, struct_value):
         text_format = struct_value['format']
@@ -69,6 +83,11 @@ class CharFieldBlock(OptionalFormFieldBlock):
             return forms.EmailField
         return super().get_field_class(struct_value)
 
+    def get_searchable_content(self, value, data):
+        # CharField acts as a fallback. Force data to string
+        data = str(data)
+        return bleach.clean(data, tags=[], strip=True)
+
 
 class TextFieldBlock(OptionalFormFieldBlock):
     default_value = TextBlock(required=False, label=_('Default value'))
@@ -77,6 +96,10 @@ class TextFieldBlock(OptionalFormFieldBlock):
 
     class Meta:
         label = _('Text field (multi line)')
+        template = 'stream_forms/render_unsafe_field.html'
+
+    def get_searchable_content(self, value, data):
+        return bleach.clean(data, tags=[], strip=True)
 
 
 class NumberFieldBlock(OptionalFormFieldBlock):
@@ -87,6 +110,9 @@ class NumberFieldBlock(OptionalFormFieldBlock):
     class Meta:
         label = _('Number field')
 
+    def get_searchable_content(self, value, data):
+        return None
+
 
 class CheckboxFieldBlock(FormFieldBlock):
     default_value = BooleanBlock(required=False)
@@ -97,6 +123,9 @@ class CheckboxFieldBlock(FormFieldBlock):
         label = _('Checkbox field')
         icon = 'tick-inverse'
 
+    def get_searchable_content(self, value, data):
+        return None
+
 
 class RadioButtonsFieldBlock(OptionalFormFieldBlock):
     choices = ListBlock(CharBlock(label=_('Choice')))
@@ -139,6 +168,7 @@ class CheckboxesFieldBlock(OptionalFormFieldBlock):
     class Meta:
         label = _('Multiple checkboxes field')
         icon = 'list-ul'
+        template = 'stream_forms/render_list_field.html'
 
     def get_field_kwargs(self, struct_value):
         kwargs = super(CheckboxesFieldBlock,
@@ -147,6 +177,9 @@ class CheckboxesFieldBlock(OptionalFormFieldBlock):
                              for choice in struct_value['checkboxes']]
         return kwargs
 
+    def get_searchable_content(self, value, data):
+        return data
+
 
 class DatePickerInput(forms.DateInput):
     def __init__(self, *args, **kwargs):
@@ -171,6 +204,9 @@ class DateFieldBlock(OptionalFormFieldBlock):
         label = _('Date field')
         icon = 'date'
 
+    def get_searchable_content(self, value, data):
+        return None
+
 
 class HTML5TimeInput(forms.TimeInput):
     input_type = 'time'
@@ -186,6 +222,9 @@ class TimeFieldBlock(OptionalFormFieldBlock):
         label = _('Time field')
         icon = 'time'
 
+    def get_searchable_content(self, value, data):
+        return None
+
 
 class DateTimePickerInput(forms.SplitDateTimeWidget):
     def __init__(self, attrs=None, date_format=None, time_format=None):
@@ -212,6 +251,9 @@ class DateTimeFieldBlock(OptionalFormFieldBlock):
         label = _('Date+time field')
         icon = 'date'
 
+    def get_searchable_content(self, value, data):
+        return None
+
 
 class ImageFieldBlock(OptionalFormFieldBlock):
     field_class = forms.ImageField
@@ -220,13 +262,35 @@ class ImageFieldBlock(OptionalFormFieldBlock):
         label = _('Image field')
         icon = 'image'
 
+    def get_searchable_content(self, value, data):
+        return None
+
 
 class FileFieldBlock(OptionalFormFieldBlock):
+    """This doesn't know how to save the uploaded files
+
+    You must implement this if you want to reuse it.
+    """
     field_class = forms.FileField
 
     class Meta:
         label = _('File field')
         icon = 'download'
+        template = 'stream_forms/render_file_field.html'
+
+    def get_searchable_content(self, value, data):
+        return None
+
+
+class MultiFileFieldBlock(FileFieldBlock):
+    field_class = MultiFileField
+
+    class Meta:
+        label = _('Multiple File field')
+        template = 'stream_forms/render_multi_file_field.html'
+
+    def get_searchable_content(self, value, data):
+        return None
 
 
 class FormFieldsBlock(StreamBlock):
@@ -243,6 +307,7 @@ class FormFieldsBlock(StreamBlock):
     datetime = DateTimeFieldBlock(group=_('Fields'))
     image = ImageFieldBlock(group=_('Fields'))
     file = FileFieldBlock(group=_('Fields'))
+    multi_file = MultiFileFieldBlock(group=_('Fields'))
 
     class Meta:
         label = _('Form fields')
diff --git a/opentech/apply/stream_forms/fields.py b/opentech/apply/stream_forms/fields.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f14002ec1b3ed6a454620b20903ddbf0b191bbd
--- /dev/null
+++ b/opentech/apply/stream_forms/fields.py
@@ -0,0 +1,23 @@
+from django.forms import FileInput, FileField
+
+
+class MultiFileInput(FileInput):
+    """
+    File Input only returns one file from its clean method.
+
+    This passes all files through the clean method and means we have a list of
+    files available for post processing
+    """
+    def __init__(self, *args, attrs={}, **kwargs):
+        attrs['multiple'] = True
+        super().__init__(*args, attrs=attrs, **kwargs)
+
+    def value_from_datadict(self, data, files, name):
+        return files.getlist(name)
+
+
+class MultiFileField(FileField):
+    widget = MultiFileInput
+
+    def clean(self, value, initial):
+        return [FileField().clean(file, initial) for file in value]
diff --git a/opentech/apply/stream_forms/templates/stream_forms/includes/file_field.html b/opentech/apply/stream_forms/templates/stream_forms/includes/file_field.html
new file mode 100644
index 0000000000000000000000000000000000000000..b08191a0e2928cf5253158297716419c9d5ed654
--- /dev/null
+++ b/opentech/apply/stream_forms/templates/stream_forms/includes/file_field.html
@@ -0,0 +1,7 @@
+<a class="link link--download" href="{{ file.url }}">
+    <div>
+        <svg><use xlink:href="#file"></use></svg>
+        <span>{{ file.name }}</span>
+    </div>
+    <svg><use xlink:href="#download"></use></svg>
+</a>
diff --git a/opentech/apply/stream_forms/templates/stream_forms/render_field.html b/opentech/apply/stream_forms/templates/stream_forms/render_field.html
new file mode 100644
index 0000000000000000000000000000000000000000..049375a32c6fc5d22bf2a4516b101452ffbbb17c
--- /dev/null
+++ b/opentech/apply/stream_forms/templates/stream_forms/render_field.html
@@ -0,0 +1,4 @@
+<div>
+    <h5>{{ value.field_label }}</h5>
+    <div>{% block data_display %}{{ data|default:"No response" }}{% endblock %}</div>
+</div>
diff --git a/opentech/apply/stream_forms/templates/stream_forms/render_file_field.html b/opentech/apply/stream_forms/templates/stream_forms/render_file_field.html
new file mode 100644
index 0000000000000000000000000000000000000000..cece5ac71e19e02c2a160caa482fbb1a2a72f445
--- /dev/null
+++ b/opentech/apply/stream_forms/templates/stream_forms/render_file_field.html
@@ -0,0 +1,10 @@
+{% extends "stream_forms/render_field.html" %}
+{% block data_display %}
+    {% if data %}
+        <div class="wrapper wrapper--top-bottom-space">
+            {% include "stream_forms/includes/file_field.html" with file=data %}
+        </div>
+    {% else %}
+        {{ block.super }}
+    {% endif %}
+{% endblock %}
diff --git a/opentech/apply/stream_forms/templates/stream_forms/render_list_field.html b/opentech/apply/stream_forms/templates/stream_forms/render_list_field.html
new file mode 100644
index 0000000000000000000000000000000000000000..a2cb46ef1a13f2e2dc36641eed5e931fc47a762c
--- /dev/null
+++ b/opentech/apply/stream_forms/templates/stream_forms/render_list_field.html
@@ -0,0 +1,12 @@
+{% extends "stream_forms/render_field.html" %}
+{% block data_display %}
+    {% if data %}
+        {% for value in data %}
+            {% if forloop.first %}<ul>{% endif %}
+            <li>{{ value }}</li>
+            {% if forloop.last %}</ul>{% endif %}
+        {% endfor %}
+    {% else %}
+        {{ block.super }}
+    {% endif %}
+{% endblock %}
diff --git a/opentech/apply/stream_forms/templates/stream_forms/render_multi_file_field.html b/opentech/apply/stream_forms/templates/stream_forms/render_multi_file_field.html
new file mode 100644
index 0000000000000000000000000000000000000000..c7997cbfd2407df9ffb17d6ab62466899b328f2c
--- /dev/null
+++ b/opentech/apply/stream_forms/templates/stream_forms/render_multi_file_field.html
@@ -0,0 +1,8 @@
+{% extends "stream_forms/render_field.html" %}
+{% block data_display %}
+    <div class="wrapper wrapper--top-bottom-space">
+    {% for file in data %}
+            {% include "stream_forms/includes/file_field.html" with file=file %}
+    {% endfor %}
+    </div>
+{% endblock %}
diff --git a/opentech/apply/stream_forms/templates/stream_forms/render_unsafe_field.html b/opentech/apply/stream_forms/templates/stream_forms/render_unsafe_field.html
new file mode 100644
index 0000000000000000000000000000000000000000..04ea0ffc4395d43f00b90a1fe36a9ec95e039e65
--- /dev/null
+++ b/opentech/apply/stream_forms/templates/stream_forms/render_unsafe_field.html
@@ -0,0 +1,9 @@
+{% extends "stream_forms/render_field.html" %}
+{% load bleach_tags %}
+{% block data_display %}
+    {% if data %}
+        {{ data|bleach }}
+    {% else %}
+        {{ block.super }}
+    {% endif %}
+{% endblock %}
diff --git a/opentech/apply/urls.py b/opentech/apply/urls.py
index 802e5c0fcfd9eae92a8b82d091a7070abe82e867..6a4d437d8a11358373ae6238415d399796d40b6e 100644
--- a/opentech/apply/urls.py
+++ b/opentech/apply/urls.py
@@ -6,7 +6,7 @@ from .dashboard import urls as dashboard_urls
 
 
 urlpatterns = [
-    url(r'^apply/', include(funds_urls)),
+    url(r'^apply/', include(funds_urls, namespace='funds')),
     url(r'^account/', include(users_urls, namespace='users')),
     url(r'^dashboard/', include(dashboard_urls, namespace='dashboard')),
 ]
diff --git a/opentech/apply/users/groups.py b/opentech/apply/users/groups.py
index 4ec30e03170c669f30fe6f8b8a13e4b6f3ec1cee..ec87a6dc6d8d6fe0ea267a1e686fa691d2251581 100644
--- a/opentech/apply/users/groups.py
+++ b/opentech/apply/users/groups.py
@@ -1,3 +1,5 @@
+STAFF_GROUP_NAME = 'Staff'
+
 GROUPS = [
     {
         'name': 'Applicant',
@@ -12,7 +14,7 @@ GROUPS = [
         'permissions': [],
     },
     {
-        'name': 'Staff',
+        'name': STAFF_GROUP_NAME,
         'permissions': [],
     },
     {
diff --git a/opentech/apply/users/models.py b/opentech/apply/users/models.py
index 5e39dfb2ef57fd63691a26de9140ec21578b0333..26a0c2c355a20b12d08cdc30126ebbd9073ac29c 100644
--- a/opentech/apply/users/models.py
+++ b/opentech/apply/users/models.py
@@ -7,6 +7,9 @@ from .utils import send_activation_email
 
 def convert_full_name_to_parts(defaults):
     full_name = defaults.pop('full_name', ' ')
+    if not full_name:
+        # full_name was None
+        full_name = ' '
     first_name, *last_name = full_name.split(' ')
     if first_name:
         defaults.update(first_name=first_name)
@@ -69,3 +72,6 @@ class User(AbstractUser):
     username = None
 
     objects = UserManager()
+
+    def __str__(self):
+        return self.get_full_name()
diff --git a/opentech/apply/users/pipeline.py b/opentech/apply/users/pipeline.py
new file mode 100644
index 0000000000000000000000000000000000000000..87971ec425d9ab5672b155ee1e09a770ae6349fa
--- /dev/null
+++ b/opentech/apply/users/pipeline.py
@@ -0,0 +1,11 @@
+from django.conf import settings
+from django.contrib.auth.models import Group
+
+from opentech.apply.users.groups import STAFF_GROUP_NAME
+
+
+def make_otf_staff(backend, user, response, *args, **kwargs):
+    _, email_domain = user.email.split('@')
+    if email_domain in settings.STAFF_EMAIL_DOMAINS:
+        staff_group = Group.objects.get(STAFF_GROUP_NAME)
+        user.groups.add(staff_group)
diff --git a/opentech/apply/users/tests/factories.py b/opentech/apply/users/tests/factories.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e1ddfb5152e6e8bb4f9a37dc92e81324a9ef439
--- /dev/null
+++ b/opentech/apply/users/tests/factories.py
@@ -0,0 +1,29 @@
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group
+
+import factory
+
+
+class GroupFactory(factory.DjangoModelFactory):
+    class Meta:
+        model = Group
+        django_get_or_create = ('name',)
+
+    name = factory.Sequence('group name {}'.format)
+
+
+class UserFactory(factory.DjangoModelFactory):
+    class Meta:
+        model = get_user_model()
+
+    email = factory.Sequence('email{}@email.com'.format)
+
+    @factory.PostGeneration
+    def groups(self, create, extracted, **kwargs):
+        if create:
+            if not extracted:
+                groups = GroupFactory(**kwargs)
+            else:
+                groups = extracted
+
+            self.groups.add(groups)
diff --git a/opentech/public/navigation/templatetags/navigation_tags.py b/opentech/public/navigation/templatetags/navigation_tags.py
index 9682f7aa73db6bb8507c549fe7236184c42ccc23..2a692a83d9a9315f3d384d5793c7fcfb4f3cdf4b 100644
--- a/opentech/public/navigation/templatetags/navigation_tags.py
+++ b/opentech/public/navigation/templatetags/navigation_tags.py
@@ -13,8 +13,9 @@ esi_inclusion_tag = register_inclusion_tag(register)
 @esi_inclusion_tag('navigation/primarynav.html')
 def primarynav(context):
     request = context['request']
+    site = context.get('PUBLIC_SITE', request.site)
     return {
-        'primarynav': NavigationSettings.for_site(request.site).primary_navigation,
+        'primarynav': NavigationSettings.for_site(site).primary_navigation,
         'request': request,
     }
 
@@ -23,8 +24,9 @@ def primarynav(context):
 @esi_inclusion_tag('navigation/secondarynav.html')
 def secondarynav(context):
     request = context['request']
+    site = context.get('PUBLIC_SITE', request.site)
     return {
-        'secondarynav': NavigationSettings.for_site(request.site).secondary_navigation,
+        'secondarynav': NavigationSettings.for_site(site).secondary_navigation,
         'request': request,
     }
 
@@ -33,8 +35,9 @@ def secondarynav(context):
 @esi_inclusion_tag('navigation/footernav.html')
 def footernav(context):
     request = context['request']
+    site = context.get('PUBLIC_SITE', request.site)
     return {
-        'footernav': NavigationSettings.for_site(request.site).footer_navigation,
+        'footernav': NavigationSettings.for_site(site).footer_navigation,
         'request': request,
     }
 
@@ -52,7 +55,8 @@ def sidebar(context):
 @esi_inclusion_tag('navigation/footerlinks.html')
 def footerlinks(context):
     request = context['request']
+    site = context.get('PUBLIC_SITE', request.site)
     return {
-        'footerlinks': NavigationSettings.for_site(request.site).footer_links,
+        'footerlinks': NavigationSettings.for_site(site).footer_links,
         'request': request,
     }
diff --git a/opentech/public/search/templates/search/includes/search_result.html b/opentech/public/search/templates/search/includes/search_result.html
index 020a1b907664c5c2b002167f36caa32789e5c296..e7d7b4c519c6e51537b7715b1090b2a7976fd7ef 100644
--- a/opentech/public/search/templates/search/includes/search_result.html
+++ b/opentech/public/search/templates/search/includes/search_result.html
@@ -1,33 +1,33 @@
 {% load static wagtailcore_tags wagtailsearchpromotions_tags wagtailimages_tags %}
 
-{# breadcrumbs #}
-{% if result.get_ancestors|length > 2 %}
-    {% for ancestor in result.get_ancestors %}
-        {% if not ancestor.is_root %}
-            {% if ancestor.depth > 2 %}
-                {{ ancestor.title }}
-                {% if ancestor.depth|add:1 < result.depth %}
-                    &nbsp;/&nbsp;
+<a class="listing" href="{% pageurl result %}">
+    {# breadcrumbs #}
+    {% if result.get_ancestors|length > 2 %}
+        <h6 class="listing__path">
+            {% for ancestor in result.get_ancestors %}
+                {% if not ancestor.is_root %}
+                    {% if ancestor.depth > 2 %}
+                        <span>{{ ancestor.title }}</span>
+                        {% if ancestor.depth|add:1 < result.depth %}
+                            <span class="nav__item--breadcrumb"></span>
+                        {% endif %}
+                    {% else %}<span class="nav__item--breadcrumb"></span>{% endif %} {# the first one #}
                 {% endif %}
-            {% else %}/{% endif %} {# the first one #}
-        {% endif %}
-    {% endfor %}
-{% endif %}
+            {% endfor %}
+        </h6>
+    {% endif %}
 
-{% if result.listing_image %}
-    <a href="{% pageurl result %}">
-        {% image result.listing_image fill-450x300 %}
-    </a>
-{% endif %}
-
-<h4>
-    <a href="{% pageurl result %}">
-        {{ result.listing_title|default:result.title }}
-    </a>
-</h4>
-
-{% if pick.description or result.listing_summary or result.search_description %}
-    <p>{{ pick.description|default:result.listing_summary|default:result.search_description }}</p>
-{% endif %}
+    {% if result.listing_image or result.icon %}
+        {% image result.listing_image|default:result.icon fill-180x180 class="listing__image" %}
+    {% else %}
+        <div class="listing__image listing__image--default">
+            <svg><use xlink:href="#logo-mobile-no-text"></use></svg>
+        </div>
+    {% endif %}
 
+    <h4 class="listing__title">{{ result.listing_title|default:result.title }}</h4>
 
+    {% if pick.description or result.listing_summary or result.search_description or result.listing_summary or result.introduction  %}
+        <h6 class="listing__teaser">{{ pick.description|default:result.listing_summary|default:result.search_description|default:result.listing_summary|default:result.introduction|truncatechars_html:155 }}</h6>
+    {% endif %}
+</a>
diff --git a/opentech/public/search/templates/search/search.html b/opentech/public/search/templates/search/search.html
index d9e84558902033bcb8988ff6572a6908dc6e3dc5..b15ac6ea856529b114d4e9e65da166c93b943242 100644
--- a/opentech/public/search/templates/search/search.html
+++ b/opentech/public/search/templates/search/search.html
@@ -1,48 +1,41 @@
 {% extends "base.html" %}
 {% load static wagtailcore_tags wagtailsearchpromotions_tags %}
-
-{% block body_class %}template-searchresults{% endblock %}
-
+{% block body_class %}template-searchresults light-grey-bg{% endblock %}
+{% block page_title %}Search results{% endblock %}
 {% block title %}{% if search_query %}Search results for &ldquo;{{ search_query }}&rdquo;{% else %}Search{% endif %}{% endblock %}
-
 {% block content %}
-
-    <h1>{% if search_query %}Search results for &ldquo;{{ search_query }}&rdquo;{% else %}Search{% endif %}</h1>
-
-    <form action="{% url 'search' %}" method="get" role="search">
-        <input type="text" placeholder="Search…" name="query"{% if search_query %} value="{{ search_query }}"{% endif %}>
-        <input type="submit" value="Search">
-    </form>
-
-    {% get_search_promotions search_query as search_picks %}
-    {% if search_picks %}
-         <ul>
-            {% for pick in search_picks %}
-                <li>
+    <div class="wrapper wrapper--small wrapper--top-bottom-inner-space">
+        <h2 class="heading heading--no-margin">{% if search_query %}Search results for &ldquo;{{ search_query }}&rdquo;{% else %}Search{% endif %}</h2>
+
+        {% if search_results %}
+            {% with count=search_results.paginator.count %}
+                <p>{{ count }} result{{ count|pluralize }} found.</p>
+            {% endwith %}
+        {% elif search_query and not search_picks %}
+            <p>No results found.</p>
+        {% endif %}
+
+        <form class="form" action="{% url 'search' %}" method="get" role="search" aria-label="Search form">
+            <input class="input input--bottom-space" type="text" placeholder="Search…" name="query"{% if search_query %} value="{{ search_query }}"{% endif %} aria-label="Search input">
+            <input class="link link--button" type="submit" value="Search" aria-label="search">
+        </form>
+
+        {% get_search_promotions search_query as search_picks %}
+        {% if search_picks %}
+            <div class="wrapper wrapper--listings">
+                {% for pick in search_picks %}
                     {% include "search/includes/search_result.html" with result=pick.page.specific %}
-                </li>
-             {% endfor %}
-         </ul>
-    {% endif %}
+                {% endfor %}
+            </div>
+        {% endif %}
 
-    {% if search_results %}
-
-        {% with count=search_results.paginator.count %}
-            {{ count }} result{{ count|pluralize }} found.
-        {% endwith %}
-
-        <ul>
-            {% for result in search_results %}
-                <li>
+        {% if search_results %}
+            <div class="wrapper wrapper--listings">
+                {% for result in search_results %}
                     {% include "search/includes/search_result.html" with result=result.specific %}
-                </li>
-            {% endfor %}
-        </ul>
-
-        {% include "includes/pagination.html" with paginator_page=search_results %}
-
-    {% elif search_query and not search_picks %}
-        No results found.
-    {% endif %}
-
+                {% endfor %}
+            </div>
+            {% include "includes/pagination.html" with paginator_page=search_results %}
+        {% endif %}
+    </div>
 {% endblock %}
diff --git a/opentech/public/utils/context_processors.py b/opentech/public/utils/context_processors.py
index 330053296b778a10ef308e74095f8bf0c2efe668..482ef0e1123321f653f8d821746ac1840a6538f3 100644
--- a/opentech/public/utils/context_processors.py
+++ b/opentech/public/utils/context_processors.py
@@ -1,10 +1,12 @@
 from django.conf import settings
 
 from opentech.apply.home.models import ApplyHomePage
+from opentech.public.home.models import HomePage
 
 
 def global_vars(request):
     return {
         'GOOGLE_TAG_MANAGER_ID': getattr(settings, 'GOOGLE_TAG_MANAGER_ID', None),
-        'APPLY_SITE': ApplyHomePage.objects.first(),
+        'APPLY_SITE': ApplyHomePage.objects.first().get_site(),
+        'PUBLIC_SITE': HomePage.objects.first().get_site(),
     }
diff --git a/opentech/settings/base.py b/opentech/settings/base.py
index 063ce2e71d820323e02d9e943cf5a0d7867bdef4..afcbf80bcd413edbb96719cf3b61a26506721e35 100644
--- a/opentech/settings/base.py
+++ b/opentech/settings/base.py
@@ -57,13 +57,17 @@ INSTALLED_APPS = [
     'tinymce',
     'wagtailcaptcha',
     'django_tables2',
+    'django_filters',
+    'django_select2',
     'addressfield',
+    'django_bleach',
 
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
+    'django.contrib.postgres',
     'django.contrib.staticfiles',
     'django.contrib.sitemaps',
     'django.forms',
@@ -295,7 +299,8 @@ SOCIAL_AUTH_URL_NAMESPACE = 'social'
 # Set the Google OAuth2 credentials in ENV variables or local.py
 # To create a new set of credentials, go to https://console.developers.google.com/apis/credentials
 # Make sure the Google+ API is enabled for your API project
-SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS = ['opentechfund.org']
+STAFF_EMAIL_DOMAINS = ['opentechfund.org']
+SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS = STAFF_EMAIL_DOMAINS
 SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = ''
 SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = ''
 
@@ -315,4 +320,16 @@ SOCIAL_AUTH_PIPELINE = (
     'social_core.pipeline.social_auth.associate_user',
     'social_core.pipeline.social_auth.load_extra_data',
     'social_core.pipeline.user.user_details',
+    'opentech.apply.users.pipeline.make_otf_staff',
 )
+
+# Bleach Settings
+BLEACH_ALLOWED_TAGS = ['h2', 'h3', 'p', 'b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li']
+
+BLEACH_ALLOWED_ATTRIBUTES = ['href', 'title', 'style']
+
+BLEACH_ALLOWED_STYLES = ['font-family', 'font-weight', 'text-decoration', 'font-variant']
+
+BLEACH_STRIP_TAGS = True
+
+BLEACH_STRIP_COMMENTS = True
diff --git a/opentech/static_src/src/sass/apply/abstracts/_functions.scss b/opentech/static_src/src/sass/apply/abstracts/_functions.scss
new file mode 100644
index 0000000000000000000000000000000000000000..33cdc75e5a4e53f7930dc9595cf36d77fd83b903
--- /dev/null
+++ b/opentech/static_src/src/sass/apply/abstracts/_functions.scss
@@ -0,0 +1,26 @@
+// Returns the opposite direction of each direction in a list
+// @param {List} $directions - List of initial directions
+// @return {List} - List of opposite directions
+@function opposite-direction($directions) {
+    $opposite-directions: ();
+    $direction-map: (
+        'top':    'bottom',
+        'right':  'left',
+        'bottom': 'top',
+        'left':   'right',
+        'center': 'center',
+        'ltr':    'rtl',
+        'rtl':    'ltr'
+    );
+
+    @each $direction in $directions {
+        $direction: to-lower-case($direction);
+
+        @if map-has-key($direction-map, $direction) {
+            $opposite-directions: append($opposite-directions, unquote(map-get($direction-map, $direction)));
+        } @else {
+            @warn 'No opposite direction can be found for `#{$direction}`. Direction omitted.';
+        }
+    }
+    @return $opposite-directions;
+}
diff --git a/opentech/static_src/src/sass/apply/abstracts/_mixins.scss b/opentech/static_src/src/sass/apply/abstracts/_mixins.scss
index 077351637d705d5ddaba5719f2080731e8b79c65..8ce5a38b932952fa732ac1bc26f2a5bde95d9c55 100755
--- a/opentech/static_src/src/sass/apply/abstracts/_mixins.scss
+++ b/opentech/static_src/src/sass/apply/abstracts/_mixins.scss
@@ -126,3 +126,29 @@
 
     font-size: $responsive;
 }
+
+// Triangle mixin
+// @param {Direction} $direction - Triangle direction, either `top`, `right`, `bottom` or `left`
+// @param {Color} $color [currentcolor] - Triangle color
+// @param {Length} $size [1em] - Triangle size
+@mixin triangle($direction, $color: currentcolor, $size: 1em) {
+    @if not index(top right bottom left, $direction) {
+        @error 'Direction must be either `top`, `right`, `bottom` or `left`.';
+    }
+
+    width: 0;
+    height: 0;
+    content: '';
+    border-#{opposite-direction($direction)}: ($size * 1.5) solid $color;
+
+    $perpendicular-borders: $size solid transparent;
+
+    @if $direction == top or $direction == bottom {
+        border-right:  $perpendicular-borders;
+        border-left:   $perpendicular-borders;
+    } @else if $direction == right or $direction == left {
+        border-top:    $perpendicular-borders;
+        border-bottom: $perpendicular-borders;
+    }
+  }
+
diff --git a/opentech/static_src/src/sass/apply/abstracts/_variables.scss b/opentech/static_src/src/sass/apply/abstracts/_variables.scss
index 3b9e796a9e93a1627116d394a75f15b272776939..4627dffb84df838b295f8b3b9f301660025ca548 100755
--- a/opentech/static_src/src/sass/apply/abstracts/_variables.scss
+++ b/opentech/static_src/src/sass/apply/abstracts/_variables.scss
@@ -1,9 +1,11 @@
 // Default
 $color--white: #fff;
 $color--black: #141414;
-$color--dark-grey: #404041;
 $color--light-grey: #f7f7f7;
+$color--light-mid-grey: #e8e8e8;
 $color--mid-grey: #cfcfcf;
+$color--mid-dark-grey: #919191;
+$color--dark-grey: #404041;
 
 // Brand
 $color--light-blue: #43bbf1;
@@ -16,12 +18,16 @@ $color--light-pink: #ffe1df;
 $color--tomato: #f05e54;
 $color--mint: #40c2ad;
 
+$color--sky-blue: #e7f2f6;
+$color--marine: #177da8;
+
 // Social
 $color--twitter: #1da6f6;
 $color--linkedin: #137ab8;
 $color--facebook: #396ab5;
 
 // Transparent
+$color--black-50: rgba(0, 0, 0, 0.5);
 $color--black-10: rgba(0, 0, 0, 0.1);
 
 // Assignment
@@ -62,6 +68,7 @@ $wrapper--small: 790px;
 $breakpoints: (
     'mob-portrait'      '(min-width: 320px)',
     'mob-landscape'     '(min-width: 480px)',
+    'small-tablet'      '(min-width: 600px)',
     'tablet-portrait'   '(min-width: 768px)',
     'tablet-landscape'  '(min-width: 1024px)',
     'desktop'           '(min-width: 1280px)',
diff --git a/opentech/static_src/src/sass/apply/base/_typography.scss b/opentech/static_src/src/sass/apply/base/_typography.scss
index e0aeff91b2984e7783f06f93e317b0ff16e0f412..e897ac69d770e9430a5d14a7e8f6bb484ad0b368 100755
--- a/opentech/static_src/src/sass/apply/base/_typography.scss
+++ b/opentech/static_src/src/sass/apply/base/_typography.scss
@@ -34,7 +34,7 @@ h1, h2, h3, h4, h5, h6,
 
 html,
 .body-text {
-    @include responsive-font-sizes(16px, 19px);
+    font-size: 16px;
 }
 
 // Default sizes
diff --git a/opentech/static_src/src/sass/apply/components/_pagination.scss b/opentech/static_src/src/sass/apply/components/_pagination.scss
new file mode 100644
index 0000000000000000000000000000000000000000..67f49b2b5d024e8ed9be3f92e2ba5bdb5af8892d
--- /dev/null
+++ b/opentech/static_src/src/sass/apply/components/_pagination.scss
@@ -0,0 +1,47 @@
+.pagination {
+    @extend %h6;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-top: 30px;
+
+    .cardinality {
+        margin: 0 10px;
+    }
+
+    .previous,
+    .next {
+        a {
+            position: relative;
+            display: block;
+            width: 55px;
+            height: 55px;
+            font-size: 0;
+            color: $color--white;
+            background: $color--white;
+            border: 1px solid $color--mid-grey;
+
+            &::after {
+                position: absolute;;
+                top: 18.5px;
+                left: 22.5px;
+            }
+        }
+    }
+
+    .previous {
+        a {
+            &::after {
+                @include triangle(left, $color--primary, 7px);
+            }
+        }
+    }
+
+    .next {
+        a {
+            &::after {
+                @include triangle(right, $color--primary, 7px);
+            }
+        }
+    }
+}
diff --git a/opentech/static_src/src/sass/apply/components/_table.scss b/opentech/static_src/src/sass/apply/components/_table.scss
new file mode 100644
index 0000000000000000000000000000000000000000..176e03d9af96388fe2c68eb09c7020e7cad50b87
--- /dev/null
+++ b/opentech/static_src/src/sass/apply/components/_table.scss
@@ -0,0 +1,131 @@
+$table-breakpoint: 'small-tablet';
+
+table {
+    width: 100%;
+    background-color: $color--white;
+    border-collapse: collapse;
+    table-layout: fixed;
+
+    thead {
+        display: none;
+
+        @include media-query($table-breakpoint) {
+            display: table-header-group;
+        }
+
+        tr {
+            &:hover {
+                box-shadow: none;
+            }
+        }
+    }
+
+    tr {
+        border: 1px solid $color--light-mid-grey;
+        transition: box-shadow 0.15s ease;
+
+        @include media-query($table-breakpoint) {
+            border-top: 0;
+            border-right: 0;
+            border-bottom: 2px solid $color--light-grey;
+            border-left: 0;
+
+            &:hover {
+                box-shadow: 0 6px 35px -13px $color--black-50;
+            }
+        }
+
+        > td {
+            display: block;
+            width: 100%;
+
+
+            @include media-query($table-breakpoint) {
+                display: table-cell;
+                width: initial;
+                height: 90px;
+            }
+
+            &:first-child {
+                padding-top: 20px;
+            }
+
+            &:last-child {
+                padding-bottom: 20px;
+            }
+
+            &.title {
+                font-weight: $weight--bold;
+
+                a {
+                    color: $color--primary;
+
+                    @include media-query($table-breakpoint) {
+                        color: $color--dark-grey;
+                    }
+                }
+            }
+
+            &.status_name {
+                span {
+                    display: inline-block;
+                    padding: 10px;
+                    font-size: 13px;
+                    font-weight: $weight--bold;
+                    color: $color--marine;
+                    text-align: center;
+                    background-color: $color--sky-blue;
+                }
+            }
+        }
+    }
+
+    td,
+    th {
+        padding: 5px 20px;
+
+        @include media-query($table-breakpoint) {
+            padding: 20px;
+        }
+    }
+
+    th {
+        padding: 20px;
+        font-size: 15px;
+        font-weight: 600;
+        text-align: left;
+
+        a {
+            color: $color--mid-dark-grey;
+            transition: color 0.25s ease-out;
+        }
+
+        &.desc,
+        &.asc {
+            position: relative;
+            color: $color--dark-grey;
+
+            &::after {
+                position: absolute;
+                top: 25px;
+                margin-left: 10px;
+            }
+
+            a {
+                color: inherit;
+            }
+        }
+
+        &.desc {
+            &::after {
+                @include triangle(top, $color--default, 5px);
+            }
+        }
+
+        &.asc {
+            &::after {
+                @include triangle(bottom, $color--default, 5px);
+            }
+        }
+    }
+}
diff --git a/opentech/static_src/src/sass/apply/main.scss b/opentech/static_src/src/sass/apply/main.scss
index 81828c292f62a2bf71255a90b84a87f462f2d932..0e5ecb06508d8335ca72d7a6d45bf9c7ab2f9517 100755
--- a/opentech/static_src/src/sass/apply/main.scss
+++ b/opentech/static_src/src/sass/apply/main.scss
@@ -2,6 +2,7 @@
 @import 'vendor/normalize';
 
 // Abstracts
+@import 'abstracts/functions';
 @import 'abstracts/mixins';
 @import 'abstracts/variables';
 
@@ -12,6 +13,8 @@
 // Components
 @import 'components/button';
 @import 'components/icon';
+@import 'components/pagination';
+@import 'components/table';
 @import 'components/wrapper';
 
 // Layout
diff --git a/opentech/static_src/src/sass/public/abstracts/_variables.scss b/opentech/static_src/src/sass/public/abstracts/_variables.scss
index b97b1352934d38964cd541de446972975391bb4e..9ec021c031ad892359e03739dbb6d0812dc40fd1 100755
--- a/opentech/static_src/src/sass/public/abstracts/_variables.scss
+++ b/opentech/static_src/src/sass/public/abstracts/_variables.scss
@@ -3,6 +3,7 @@ $color--white: #fff;
 $color--black: #141414;
 $color--dark-grey: #404041;
 $color--light-grey: #f7f7f7;
+$color--light-mid-grey: #e8e8e8;
 $color--mid-grey: #cfcfcf;
 
 // Brand
diff --git a/opentech/static_src/src/sass/public/components/_form.scss b/opentech/static_src/src/sass/public/components/_form.scss
index ddee807bb320d7f610114a81874f99d1262d8ade..88cf3d5d02f81bde5ebc00de3cec376c64228db8 100644
--- a/opentech/static_src/src/sass/public/components/_form.scss
+++ b/opentech/static_src/src/sass/public/components/_form.scss
@@ -53,6 +53,7 @@
 
         // sass-lint:disable class-name-format
         &--image_field,
+        &--multi_file_field,
         &--file_field {
             @include button($color--light-blue, $color--dark-blue);
             max-width: 290px;
diff --git a/opentech/static_src/src/sass/public/components/_icon.scss b/opentech/static_src/src/sass/public/components/_icon.scss
index 81c31a98b5754cc21a83226b18743b74e6ffa240..311219b3a15a89ea78143ca381e7e2736b17989e 100644
--- a/opentech/static_src/src/sass/public/components/_icon.scss
+++ b/opentech/static_src/src/sass/public/components/_icon.scss
@@ -130,4 +130,32 @@
         width: 14px;
         height: 14px;
     }
+
+    &--body-pixels-right {
+        position: absolute;
+        right: 0;
+        z-index: -1;
+        display: none;
+        width: 218px;
+        height: 457px;
+        fill: $color--dark-blue;
+
+        @include media-query(desktop) {
+            display: block;
+        }
+    }
+
+    &--body-pixels-left {
+        position: absolute;
+        top: -355px;
+        left: 0;
+        display: none;
+        width: 109px;
+        height: 275px;
+        fill: $color--dark-blue;
+
+        @include media-query(desktop) {
+            display: block;
+        }
+    }
 }
diff --git a/opentech/static_src/src/sass/public/components/_input.scss b/opentech/static_src/src/sass/public/components/_input.scss
index 98d0e903291f2b7ab97f454a34f993d57ebdd089..9af5218697d495fbaecc3d2c650e5bda93c66543 100644
--- a/opentech/static_src/src/sass/public/components/_input.scss
+++ b/opentech/static_src/src/sass/public/components/_input.scss
@@ -4,4 +4,8 @@
         background: transparent;
         border: 0;
     }
+
+    &--bottom-space {
+        margin-bottom: 10px
+    }
 }
diff --git a/opentech/static_src/src/sass/public/components/_listing.scss b/opentech/static_src/src/sass/public/components/_listing.scss
index d9f92f84800b24069b7096dabc49bc4157a69df9..ec908b9b7413f904e6b51dd72c391d5ed73f509f 100644
--- a/opentech/static_src/src/sass/public/components/_listing.scss
+++ b/opentech/static_src/src/sass/public/components/_listing.scss
@@ -15,6 +15,43 @@
         box-shadow: 0 2px 15px 0 $color--black-10;
     }
 
+    &--not-a-link {
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+        padding: 0 0 20px;
+        margin: 0 0 20px;
+        border-bottom: 1px solid $color--light-mid-grey;
+
+        @include media-query(mob-landscape) {
+            align-items: center;
+            flex-direction: row;
+            height: 160px;
+        }
+
+        &:hover {
+            box-shadow: none;
+        }
+
+        &:first-child {
+            padding-top: 20px;
+            border-top: 1px solid $color--light-mid-grey;
+
+            @include media-query(mob-landscape) {
+                padding-top: 0;
+                border-top: 0;
+            }
+        }
+
+        &:last-child {
+            border-bottom: 0;
+        }
+
+        > div {
+            flex-basis: 55%;
+        }
+    }
+
     &__title {
         margin-bottom: 5px;
         line-height: 1;
@@ -23,11 +60,34 @@
         #{$root}:hover & {
             color: $color--dark-blue;
         }
+
+        &--link {
+            margin-bottom: 10px;
+
+            #{$root}:hover & {
+                color: $color--default;
+            }
+
+            a {
+                color: $color--default;
+                transition: color $transition;
+
+                &:hover {
+                    color: $color--primary;
+                }
+            }
+        }
     }
 
     &__teaser {
         font-weight: $weight--normal;
         color: $color--default;
+
+        @include media-query(mob-landscape) {
+            .listing--not-a-link & {
+                margin: 0;
+            }
+        }
     }
 
     &__meta {
@@ -60,6 +120,7 @@
         }
     }
 
+    &__path,
     &__category {
         margin-bottom: 5px;
         color: $color--default;
@@ -77,4 +138,24 @@
             margin-left: 5px;
         }
     }
+
+    &__button {
+        @include button(transparent, $color--purple);
+        color: $color--purple;
+        text-align: center;
+        border-color: $color--purple;
+
+        &:hover {
+            color: $color--white;
+        }
+
+        @include media-query(mob-landscape) {
+            margin-right: 30px;
+            text-align: left;
+        }
+    }
+
+    &__path {
+        display: flex;
+    }
 }
diff --git a/opentech/static_src/src/sass/public/components/_nav.scss b/opentech/static_src/src/sass/public/components/_nav.scss
index 703af0bf45e0a05072046e2363dd583d027a651f..15df1f4b841da1638f638c22493b40e99b74a008 100644
--- a/opentech/static_src/src/sass/public/components/_nav.scss
+++ b/opentech/static_src/src/sass/public/components/_nav.scss
@@ -112,6 +112,10 @@
             font-weight: $weight--normal;
             border-bottom: 0;
 
+            .listing & {
+                margin-left: 10px;
+            }
+
             &::after {
                 width: 0;
                 height: 0;
@@ -120,9 +124,18 @@
                 border-bottom: 5px solid transparent;
                 border-left: 5px solid $color--white;
                 content: '';
+
+                .listing & {
+                    margin-left: 0;
+                    border-left: 5px solid $color--default;
+                }
             }
 
             &:first-child {
+                .listing & {
+                    margin-left: 0;
+                }
+
                 a {
                     margin-left: 0;
                 }
diff --git a/opentech/static_src/src/sass/public/components/_wrapper.scss b/opentech/static_src/src/sass/public/components/_wrapper.scss
index 6317fec16795975edc1a1e5e608f5bf93e25283e..5e6b1c70634b9e8341028f280efc77eacfa3b087 100644
--- a/opentech/static_src/src/sass/public/components/_wrapper.scss
+++ b/opentech/static_src/src/sass/public/components/_wrapper.scss
@@ -217,7 +217,11 @@
     &--listings {
         display: flex;
         flex-direction: column;
-        margin-top: 2rem;
+        margin-top: 20px;
+
+        @include media-query(tablet-portrait) {
+            margin-top: 2rem;
+        }
     }
 
     &--page-title {
diff --git a/opentech/static_src/src/sass/public/layout/_header.scss b/opentech/static_src/src/sass/public/layout/_header.scss
index 61006af4daafb7a47f5cd25ae4c648577cbf5ff2..5066e650fca755b2b942cfe01e2989af125bb2c1 100644
--- a/opentech/static_src/src/sass/public/layout/_header.scss
+++ b/opentech/static_src/src/sass/public/layout/_header.scss
@@ -61,11 +61,16 @@
     }
 
     &__title {
+        padding: 0 10px;
         margin: 0 0 20px;
         line-height: 1;
         color: $color--white;
         text-shadow: 0 2px 15px $color--black-10;
         text-transform: uppercase;
+      
+        @include media-query(tablet-portrait) {
+            padding: 0;
+        }
 
         &--homepage {
             @include responsive-font-sizes(36px, 72px);
@@ -83,8 +88,7 @@
                 content: '';
                 transition: height, width, 10s ease;
             }
-        }
-
+        
         .header--light-bg & {
             color: $color--dark-grey;
             text-shadow: none;
diff --git a/opentech/templates/includes/apply_button.html b/opentech/templates/includes/apply_button.html
index 53c5218e43f4e15d8f2b5b2387eb1ac1b24ef27c..3d1cb5df96bfa13473618c99a791a5fd36382924 100644
--- a/opentech/templates/includes/apply_button.html
+++ b/opentech/templates/includes/apply_button.html
@@ -1,2 +1,2 @@
 {% load wagtailcore_tags %}
-<a href="{% pageurl APPLY_SITE %}" class="link link--fixed-apply">Apply</a>
+<a href="{% pageurl APPLY_SITE.root_page %}" class="link link--fixed-apply">Apply</a>
diff --git a/opentech/templates/includes/sprites.html b/opentech/templates/includes/sprites.html
index f0c554168a11fab8555d6f5f119cc582dd4c4276..33409ac69f957e6731c427844023c0a5f405b210 100644
--- a/opentech/templates/includes/sprites.html
+++ b/opentech/templates/includes/sprites.html
@@ -220,4 +220,26 @@
             <path d="M30.5 40c9.253.037 18.342-6.296 27.264-19C50.451 9 41.364 3 30.5 3 19.637 3 10.52 9.167 3.15 21.5 12.326 33.797 21.443 39.964 30.5 40z" />
         </g>
     </symbol>
+
+    <symbol id="body-pixels-right" viewBox="0 0 218 457">
+        <g fill-rule="nonzero">
+            <path opacity=".45" d="M110 402h55v-55h-55z" />
+            <path opacity=".65" d="M165 457h55v-55h-55z" />
+            <path opacity=".594" d="M165 348h55v-55h-55z" />
+            <path d="M165 236h55v-55h-55z" />
+            <path opacity=".505" d="M110 55h55V0h-55z" />
+            <path opacity=".75" d="M55 112h55V57H55z" />
+            <path opacity=".594" d="M0 167h55v-55H0z" />
+            <path d="M0 55h55V0H0z" />
+            <path opacity=".45" d="M55 347h55v-55H55z" />
+        </g>
+    </symbol>
+
+    <symbol id="body-pixels-left" viewBox="0 0 109 275">
+        <g fill-rule="nonzero">
+            <path opacity=".594" d="M-1 110h55V55H-1z" />
+            <path d="M54 275h55v-55H54zM54 55h55V0H54z" />
+            <path opacity=".75" d="M54 165h55v-55H54z" />
+        </g>
+    </symbol>
 </svg>
diff --git a/opentech/urls.py b/opentech/urls.py
index 77fbcf1c9c77288cb42263b35e7a100a76c694b7..22c3889c8af9eab7aee4610cb49cfd108c1a32fd 100644
--- a/opentech/urls.py
+++ b/opentech/urls.py
@@ -24,6 +24,7 @@ urlpatterns = [
     url('^', include(apply_urls)),
     url('^', include('social_django.urls', namespace='social')),
     url(r'^tinymce/', include('tinymce.urls')),
+    url(r'^select2/', include('django_select2.urls')),
 ]
 
 
diff --git a/requirements.txt b/requirements.txt
index 3289a95041273e351a3da4b6fdb048e02b194aee..9a8e1221bbcdd67d36852ee7c27ba38e1461334c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,6 +2,7 @@ Django==1.11.8
 wagtail==1.13.1
 psycopg2==2.7.3.1
 Pillow==4.3.0
+django-bleach==0.3.0
 django-extensions==1.7.4
 django-countries==5.1
 Werkzeug==0.11.11
@@ -19,6 +20,8 @@ flake8
 
 social_auth_app_django==2.1.0
 django-tables2==1.17.1
+django-filter==1.1.0
+django_select2==6.0.1
 
 # Production dependencies
 dj-database-url==0.4.1
diff --git a/setup.cfg b/setup.cfg
index 4c681954a8aa3443f634e61c372d56e2260b9d74..28cc5bb725f3df9aabb06092a3b443d209a4e9c8 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -10,6 +10,9 @@ ignore_errors = True
 check_untyped_defs = True
 ignore_errors = False
 
+[mypy-opentech.apply.funds.tests.factories*]
+ignore_errors = True
+
 # Enforce writing type definitions within workflow
 [mypy-opentech.apply.funds.workflow*]
 disallow_untyped_defs = True