diff --git a/opentech/apply/dashboard/__init__.py b/opentech/apply/dashboard/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/opentech/apply/dashboard/apps.py b/opentech/apply/dashboard/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..50878e766268481aa6732f075857b0f1736fbd2d --- /dev/null +++ b/opentech/apply/dashboard/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class DashboardConfig(AppConfig): + name = 'dashboard' diff --git a/opentech/apply/dashboard/migrations/__init__.py b/opentech/apply/dashboard/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/opentech/apply/dashboard/models.py b/opentech/apply/dashboard/models.py new file mode 100644 index 0000000000000000000000000000000000000000..0b4331b362b98a4e5947c491612f95a3c2e56686 --- /dev/null +++ b/opentech/apply/dashboard/models.py @@ -0,0 +1,3 @@ +# from django.db import models + +# Create your models here. diff --git a/opentech/apply/dashboard/tables.py b/opentech/apply/dashboard/tables.py new file mode 100644 index 0000000000000000000000000000000000000000..4d3b529af3ecc1924ae3b1bc01a8f15ec40d64ba --- /dev/null +++ b/opentech/apply/dashboard/tables.py @@ -0,0 +1,12 @@ +import django_tables2 as tables +from opentech.apply.funds.models import ApplicationSubmission + + +class DashboardTable(tables.Table): + submit_time = tables.DateColumn(verbose_name="Submitted") + page = tables.Column(verbose_name="Fund") + + class Meta: + model = ApplicationSubmission + fields = ('title', 'page', 'submit_time') + template = "dashboard/tables/table.html" diff --git a/opentech/apply/dashboard/templates/dashboard/dashboard.html b/opentech/apply/dashboard/templates/dashboard/dashboard.html new file mode 100644 index 0000000000000000000000000000000000000000..fbe73a38453efe082e7fd9a60c0d1d56e1f536c5 --- /dev/null +++ b/opentech/apply/dashboard/templates/dashboard/dashboard.html @@ -0,0 +1,13 @@ +{% extends "base.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 content %} +<h1>Received Content</h1> +<h3>Track and explore recent submissions</h3> +{% render_table object_list %} +{% endblock %} diff --git a/opentech/apply/dashboard/templates/dashboard/tables/table.html b/opentech/apply/dashboard/templates/dashboard/tables/table.html new file mode 100644 index 0000000000000000000000000000000000000000..6ff5e60321ee8084988b7726392a5c40e4ab3dcf --- /dev/null +++ b/opentech/apply/dashboard/templates/dashboard/tables/table.html @@ -0,0 +1,7 @@ +{% extends 'django_tables2/semantic.html' %} +{# Change back to after demo: extends 'django_tables2/table.html' #} + +{# example of how to extend the table template #} +{% block table.tbody.row %} + {{ block.super }} +{% endblock %} diff --git a/opentech/apply/dashboard/urls.py b/opentech/apply/dashboard/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..b5bfdcafa3957fc4753d43a4d1055be469ff971c --- /dev/null +++ b/opentech/apply/dashboard/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls import url + +from .views import DashboardView + + +urlpatterns = [ + url(r'^$', DashboardView.as_view(), name="dashboard") +] diff --git a/opentech/apply/dashboard/views.py b/opentech/apply/dashboard/views.py new file mode 100644 index 0000000000000000000000000000000000000000..b86e281c466be79136ff303496990da608fd46c3 --- /dev/null +++ b/opentech/apply/dashboard/views.py @@ -0,0 +1,17 @@ +from django.views.generic import ListView +from django_tables2 import RequestConfig + +from opentech.apply.funds.models import ApplicationSubmission + +from .tables import DashboardTable + + +class DashboardView(ListView): + model = ApplicationSubmission + template_name = 'dashboard/dashboard.html' + + 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 diff --git a/opentech/apply/funds/blocks.py b/opentech/apply/funds/blocks.py index 06513827f13da391874045d3b3f3e0b8f6dc7815..d89b2c3187ca1632b87e37737bb70ddda45be479 100644 --- a/opentech/apply/funds/blocks.py +++ b/opentech/apply/funds/blocks.py @@ -1,9 +1,129 @@ +from collections import Counter + +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 opentech.apply.stream_forms.blocks import FormFieldsBlock +from wagtail.wagtailcore.blocks import StaticBlock +from opentech.apply.stream_forms.blocks import FormFieldsBlock, FormFieldBlock from opentech.apply.categories.blocks import CategoryQuestionBlock +def find_duplicates(items): + counted = Counter(items) + duplicates = [ + name for name, count in counted.items() if count > 1 + ] + return duplicates + + class CustomFormFieldsBlock(FormFieldsBlock): category = CategoryQuestionBlock(group=_('Custom')) + + def __init__(self, *args, **kwargs): + child_blocks = [(block.name, block(group=_('Required'))) for block in MustIncludeFieldBlock.__subclasses__()] + super().__init__(child_blocks, *args, **kwargs) + + def clean(self, value): + try: + value = super().clean(value) + except ValidationError as e: + error_dict = e.params + else: + error_dict = dict() + + block_types = [block.block_type for block in value] + missing = set(REQUIRED_BLOCK_NAMES) - set(block_types) + + duplicates = [ + name for name in find_duplicates(block_types) + if name in REQUIRED_BLOCK_NAMES + ] + + all_errors = list() + if missing: + all_errors.append( + 'You are missing the following required fields: {}'.format(', '.join(missing).title()) + ) + + if duplicates: + all_errors.append( + 'You have duplicates of the following required fields: {}'.format(', '.join(duplicates).title()) + ) + for i, block_name in enumerate(block_types): + if block_name in duplicates: + self.add_error_to_child(error_dict, i, 'info', 'Duplicate field') + + if all_errors or error_dict: + error_dict['__all__'] = all_errors + raise ValidationError('Error', params=error_dict) + + return value + + def add_error_to_child(self, errors, child_number, field, message): + new_error = ErrorList([message]) + try: + errors[child_number].data[0].params[field] = new_error + except KeyError: + errors[child_number] = ErrorList( + [ValidationError('Error', params={field: new_error})] + ) + + +class MustIncludeStatic(StaticBlock): + """Helper block which displays additional information about the must include block and + helps display the error in a noticeable way. + """ + def __init__(self, *args, description='', **kwargs): + self.description = description + super().__init__(*args, **kwargs) + + class Meta: + admin_text = 'Much be included in the form only once.' + + def render_form(self, *args, **kwargs): + errors = kwargs.pop('errors') + if errors: + # Pretend the error is a readonly input so that we get nice formatting + # Issue discussed here: https://github.com/wagtail/wagtail/issues/4122 + error_message = '<div class="error"><input readonly placeholder="{}"></div>'.format(errors[0]) + else: + error_message = '' + form = super().render_form(*args, **kwargs) + form = '<br>'.join([self.description, form]) + error_message + return mark_safe(form) + + def deconstruct(self): + return ('wagtail.wagtailcore.blocks.static_block.StaticBlock', (), {}) + + +class MustIncludeFieldBlock(FormFieldBlock): + """Any block inheriting from this will need to be included in the application forms + This data will also be available to query on the submission object + """ + def __init__(self, *args, **kwargs): + info_name = f'{self.name.title()} Field' + child_blocks = [('info', MustIncludeStatic(label=info_name, description=self.description))] + super().__init__(child_blocks, *args, **kwargs) + + def get_field_kwargs(self, struct_value): + kwargs = super().get_field_kwargs(struct_value) + kwargs['required'] = True + return kwargs + + +class TitleBlock(MustIncludeFieldBlock): + name = 'title' + description = 'The title of the project' + + +class ValueBlock(MustIncludeFieldBlock): + name = 'value' + description = 'The value of the project' + widget = forms.NumberInput + + +REQUIRED_BLOCK_NAMES = [block.name for block in MustIncludeFieldBlock.__subclasses__()] diff --git a/opentech/apply/funds/migrations/0005_applicationsubmission.py b/opentech/apply/funds/migrations/0005_applicationsubmission.py new file mode 100644 index 0000000000000000000000000000000000000000..ac820aae9eaf15fbd2148a022f554c4c8de8ff4c --- /dev/null +++ b/opentech/apply/funds/migrations/0005_applicationsubmission.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2018-01-22 09:40 +from __future__ import unicode_literals + +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0040_page_draft_title'), + ('funds', '0004_categoryblock_add_required_option'), + ] + + operations = [ + migrations.CreateModel( + name='ApplicationSubmission', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('form_data', django.contrib.postgres.fields.jsonb.JSONField()), + ('submit_time', models.DateTimeField(auto_now_add=True, verbose_name='submit time')), + ('page', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.Page')), + ], + options={ + 'verbose_name': 'form submission', + 'abstract': False, + }, + ), + ] diff --git a/opentech/apply/funds/migrations/0006_update_block_definitions.py b/opentech/apply/funds/migrations/0006_update_block_definitions.py new file mode 100644 index 0000000000000000000000000000000000000000..92d9ef8540a8270e1146556571a9a3dcc7dd0bec --- /dev/null +++ b/opentech/apply/funds/migrations/0006_update_block_definitions.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2018-01-23 15:18 +from __future__ import unicode_literals + +from django.db import migrations +import opentech.apply.categories.blocks +import wagtail.wagtailcore.blocks +import wagtail.wagtailcore.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('funds', '0005_applicationsubmission'), + ] + + operations = [ + migrations.AlterField( + model_name='applicationform', + name='form_fields', + field=wagtail.wagtailcore.fields.StreamField((('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')), ('text_markup', wagtail.wagtailcore.blocks.RichTextBlock(group='Other', label='Paragraph')), ('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.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.StaticBlock())), group='Required')))), + ), + ] diff --git a/opentech/apply/funds/migrations/0005_round.py b/opentech/apply/funds/migrations/0007_round.py similarity index 92% rename from opentech/apply/funds/migrations/0005_round.py rename to opentech/apply/funds/migrations/0007_round.py index e839c88745c36b286bf86a7ab58809fe350d5fdc..14ed8c2dbdf4b303871d1c486fc0d25cc29404de 100644 --- a/opentech/apply/funds/migrations/0005_round.py +++ b/opentech/apply/funds/migrations/0007_round.py @@ -10,7 +10,7 @@ class Migration(migrations.Migration): dependencies = [ ('wagtailcore', '0040_page_draft_title'), - ('funds', '0004_categoryblock_add_required_option'), + ('funds', '0006_update_block_definitions'), ] operations = [ diff --git a/opentech/apply/funds/migrations/0006_add_dates_to_round.py b/opentech/apply/funds/migrations/0008_add_date_to_round.py similarity index 94% rename from opentech/apply/funds/migrations/0006_add_dates_to_round.py rename to opentech/apply/funds/migrations/0008_add_date_to_round.py index 747ef134788e1689cc12dc9d1e47105361b116c4..b57db907c91167001264c42c3c74a2e479d885c3 100644 --- a/opentech/apply/funds/migrations/0006_add_dates_to_round.py +++ b/opentech/apply/funds/migrations/0008_add_date_to_round.py @@ -9,7 +9,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('funds', '0005_round'), + ('funds', '0007_round'), ] operations = [ diff --git a/opentech/apply/funds/migrations/0007_update_date_fields.py b/opentech/apply/funds/migrations/0009_update_date_fields.py similarity index 93% rename from opentech/apply/funds/migrations/0007_update_date_fields.py rename to opentech/apply/funds/migrations/0009_update_date_fields.py index 7b6b2829535b0139d491a0be0589e4acad48430e..ed7261b0f1e6dc064eb9d4f2640e19d633b05365 100644 --- a/opentech/apply/funds/migrations/0007_update_date_fields.py +++ b/opentech/apply/funds/migrations/0009_update_date_fields.py @@ -9,7 +9,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('funds', '0006_add_dates_to_round'), + ('funds', '0008_add_date_to_round'), ] operations = [ diff --git a/opentech/apply/funds/models.py b/opentech/apply/funds/models.py index 9f575ea6a3e22748e9fd76da3268daa293cbf0c0..347470855cfbe410e192d6ecc26aae50e3ccf352 100644 --- a/opentech/apply/funds/models.py +++ b/opentech/apply/funds/models.py @@ -1,8 +1,10 @@ from datetime import date +from django.contrib.postgres.fields import JSONField from django.core.exceptions import ValidationError from django.db import models from django.db.models import Q +from django.db.models.expressions import RawSQL, OrderBy from django.http import Http404 from django.urls import reverse from django.utils.text import mark_safe @@ -17,10 +19,11 @@ from wagtail.wagtailadmin.edit_handlers import ( ) from wagtail.wagtailcore.fields import StreamField from wagtail.wagtailcore.models import Orderable +from wagtail.wagtailforms.models import AbstractFormSubmission from opentech.apply.stream_forms.models import AbstractStreamForm -from .blocks import CustomFormFieldsBlock +from .blocks import CustomFormFieldsBlock, MustIncludeFieldBlock, REQUIRED_BLOCK_NAMES from .forms import WorkflowFormAdminForm from .workflow import SingleStage, DoubleStage @@ -52,6 +55,22 @@ class FundType(AbstractStreamForm): # Only return the first form, will need updating for when working with 2 stage WF return self.forms.all()[0].fields + def get_submission_class(self): + 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 + + return self.get_submission_class().objects.create( + form_data=cleaned_data, + page=self, + ) + @property def workflow_class(self): return WORKFLOW_CLASS[self.get_workflow_display()] @@ -182,3 +201,44 @@ class Round(AbstractStreamForm): # We hide the round as only the open round is used which is displayed through the # fund page raise Http404() + + +class JSONOrderable(models.QuerySet): + def order_by(self, *field_names): + def build_json_order_by(field): + if field.replace('-', '') not in REQUIRED_BLOCK_NAMES: + return field + + if field[0] == '-': + descending = True + field = field[1:] + else: + descending = False + return OrderBy(RawSQL("LOWER(form_data->>%s)", (field,)), descending=descending) + + field_ordering = [build_json_order_by(field) for field in field_names] + return super().order_by(*field_ordering) + + +class ApplicationSubmission(AbstractFormSubmission): + form_data = JSONField() + + objects = JSONOrderable.as_manager() + + def get_data(self): + # Updated for JSONField + form_data = self.form_data + form_data.update({ + 'submit_time': self.submit_time, + }) + + return form_data + + def __getattr__(self, item): + # fall back to values defined on the data + if item in REQUIRED_BLOCK_NAMES: + return self.get_data()[item] + return super().__getattr__(item) + + def __str__(self): + return str(super().__str__()) diff --git a/opentech/apply/stream_forms/blocks.py b/opentech/apply/stream_forms/blocks.py index 8340e7433d801929dbc5e590294968ce5124725d..f3b0906e6770e5246c6487cdcb9ac760c6ae7e43 100644 --- a/opentech/apply/stream_forms/blocks.py +++ b/opentech/apply/stream_forms/blocks.py @@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _ from unidecode import unidecode from wagtail.wagtailcore.blocks import ( StructBlock, TextBlock, CharBlock, BooleanBlock, ListBlock, StreamBlock, - DateBlock, TimeBlock, DateTimeBlock, ChoiceBlock, RichTextBlock, + DateBlock, TimeBlock, DateTimeBlock, ChoiceBlock, RichTextBlock ) diff --git a/opentech/apply/stream_forms/models.py b/opentech/apply/stream_forms/models.py index 3c34df543635e91fc8bb07d586038a85cb4edf24..699714c23c685ef8cece9c03534b82fac7ad98fb 100644 --- a/opentech/apply/stream_forms/models.py +++ b/opentech/apply/stream_forms/models.py @@ -8,6 +8,8 @@ from .forms import BlockFieldWrapper, StreamBaseForm class AbstractStreamForm(AbstractForm): + page_form_class = StreamBaseForm + class Meta: abstract = True @@ -21,11 +23,10 @@ class AbstractStreamForm(AbstractForm): block = struct_child.block struct_value = struct_child.value if isinstance(block, FormFieldBlock): - field_name = block.get_slug(struct_value) - form_fields[field_name] = block.get_field(struct_value) + form_fields[struct_child.id] = block.get_field(struct_value) else: form_fields[struct_child.id] = BlockFieldWrapper(struct_child) return form_fields def get_form_class(self): - return type('WagtailStreamForm', (StreamBaseForm,), self.get_form_fields()) + return type('WagtailStreamForm', (self.page_form_class,), self.get_form_fields()) diff --git a/opentech/apply/urls.py b/opentech/apply/urls.py index 4624990d07ee3a1c64946e9dc6ab25ea614d4217..802e5c0fcfd9eae92a8b82d091a7070abe82e867 100644 --- a/opentech/apply/urls.py +++ b/opentech/apply/urls.py @@ -2,8 +2,11 @@ from django.conf.urls import include, url from .funds import urls as funds_urls from .users import urls as users_urls +from .dashboard import urls as dashboard_urls + urlpatterns = [ url(r'^apply/', include(funds_urls)), url(r'^account/', include(users_urls, namespace='users')), + url(r'^dashboard/', include(dashboard_urls, namespace='dashboard')), ] diff --git a/opentech/settings/base.py b/opentech/settings/base.py index 38c958902a2f43a3babbbe31749555e0dba3f657..7405abda9515029499f8bded1239684cda298dbe 100644 --- a/opentech/settings/base.py +++ b/opentech/settings/base.py @@ -16,6 +16,7 @@ INSTALLED_APPS = [ 'opentech.apply.categories', 'opentech.apply.funds', + 'opentech.apply.dashboard', 'opentech.apply.home', 'opentech.apply.users', 'opentech.apply.stream_forms', @@ -55,6 +56,7 @@ INSTALLED_APPS = [ 'django_extensions', 'captcha', 'wagtailcaptcha', + 'django_tables2', 'django.contrib.admin', 'django.contrib.auth', diff --git a/requirements.txt b/requirements.txt index 83d06ba2b9b890ff1af1af148881eadcb7987443..5399a07ac8b3ca01941552afeb116640eba3cff2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ wagtail_factories==0.3.0 flake8 social_auth_app_django==2.1.0 +django-tables2==1.17.1 # Production dependencies dj-database-url==0.4.1