diff --git a/opentech/apply/funds/blocks.py b/opentech/apply/funds/blocks.py index 614bf4918a1e2fde4da36d2c5f8b6afa2ef51545..6069c1390ba8a2813b8236d793602d664fa49156 100644 --- a/opentech/apply/funds/blocks.py +++ b/opentech/apply/funds/blocks.py @@ -6,7 +6,16 @@ from django.utils.translation import ugettext_lazy as _ from addressfield.fields import AddressField from opentech.apply.categories.blocks import CategoryQuestionBlock from opentech.apply.stream_forms.blocks import FormFieldsBlock -from opentech.apply.utils.blocks import MustIncludeFieldBlock, CustomFormFieldsBlock, RichTextFieldBlock +from opentech.apply.utils.blocks import ( + CustomFormFieldsBlock, + MustIncludeFieldBlock, + RichTextFieldBlock, + SingleIncludeBlock, +) + + +class ApplicationSingleIncludeFieldBlock(SingleIncludeBlock): + pass class ApplicationMustIncludeFieldBlock(MustIncludeFieldBlock): @@ -21,7 +30,7 @@ class TitleBlock(ApplicationMustIncludeFieldBlock): icon = 'tag' -class ValueBlock(ApplicationMustIncludeFieldBlock): +class ValueBlock(ApplicationSingleIncludeFieldBlock): name = 'value' description = 'The value of the project' widget = forms.NumberInput @@ -36,7 +45,7 @@ class EmailBlock(ApplicationMustIncludeFieldBlock): icon = 'mail' -class AddressFieldBlock(ApplicationMustIncludeFieldBlock): +class AddressFieldBlock(ApplicationSingleIncludeFieldBlock): name = 'address' description = 'The postal address of the user' @@ -105,6 +114,11 @@ class ApplicationCustomFormFieldsBlock(CustomFormFieldsBlock, FormFieldsBlock): category = CategoryQuestionBlock(group=_('Custom')) rich_text = RichTextFieldBlock(group=_('Fields')) required_blocks = ApplicationMustIncludeFieldBlock.__subclasses__() + single_blocks = ApplicationSingleIncludeFieldBlock.__subclasses__() REQUIRED_BLOCK_NAMES = [block.name for block in ApplicationMustIncludeFieldBlock.__subclasses__()] + +SINGLE_BLOCK_NAMES = [block.name for block in ApplicationSingleIncludeFieldBlock.__subclasses__()] + +NAMED_BLOCKS = REQUIRED_BLOCK_NAMES + SINGLE_BLOCK_NAMES diff --git a/opentech/apply/funds/migrations/0044_add_named_blocks.py b/opentech/apply/funds/migrations/0044_add_named_blocks.py new file mode 100644 index 0000000000000000000000000000000000000000..21707e82e4fb9560e05fcbf0dc1f91d048750d1b --- /dev/null +++ b/opentech/apply/funds/migrations/0044_add_named_blocks.py @@ -0,0 +1,27 @@ +# Generated by Django 2.0.2 on 2018-09-26 16:15 + +from django.db import migrations +import opentech.apply.categories.blocks +import wagtail.core.blocks +import wagtail.core.blocks.static_block +import wagtail.core.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('funds', '0043_auto_20180926_0948'), + ] + + operations = [ + migrations.AlterField( + model_name='applicationform', + name='form_fields', + field=wagtail.core.fields.StreamField([('text_markup', wagtail.core.blocks.RichTextBlock(group='Other', label='Paragraph')), ('char', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('format', wagtail.core.blocks.ChoiceBlock(choices=[('email', 'Email'), ('url', 'URL')], label='Format', required=False)), ('default_value', wagtail.core.blocks.CharBlock(label='Default value', required=False))], group='Fields')), ('text', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TextBlock(label='Default value', required=False))], group='Fields')), ('number', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.CharBlock(label='Default value', required=False))], group='Fields')), ('checkbox', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.BooleanBlock(required=False))], group='Fields')), ('radios', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.core.blocks.ListBlock(wagtail.core.blocks.CharBlock(label='Choice')))], group='Fields')), ('dropdown', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.core.blocks.ListBlock(wagtail.core.blocks.CharBlock(label='Choice')))], group='Fields')), ('checkboxes', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('checkboxes', wagtail.core.blocks.ListBlock(wagtail.core.blocks.CharBlock(label='Checkbox')))], group='Fields')), ('date', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.DateBlock(required=False))], group='Fields')), ('time', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TimeBlock(required=False))], group='Fields')), ('datetime', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.DateTimeBlock(required=False))], group='Fields')), ('image', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False))], group='Fields')), ('file', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False))], group='Fields')), ('multi_file', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False))], group='Fields')), ('rich_text', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TextBlock(label='Default value', required=False))], group='Fields')), ('category', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(help_text='Leave blank to use the default Category label', label='Label', required=False)), ('help_text', wagtail.core.blocks.TextBlock(label='Leave blank to use the default Category help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('category', opentech.apply.categories.blocks.ModelChooserBlock('categories.Category')), ('multi', wagtail.core.blocks.BooleanBlock(label='Multi select', required=False))], group='Custom')), ('title', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group='Required')), ('email', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group='Required')), ('full_name', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group='Required')), ('duration', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group='Required')), ('value', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group='Custom')), ('address', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group='Custom'))]), + ), + migrations.AlterField( + model_name='applicationsubmission', + name='form_fields', + field=wagtail.core.fields.StreamField([('text_markup', wagtail.core.blocks.RichTextBlock(group='Other', label='Paragraph')), ('char', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('format', wagtail.core.blocks.ChoiceBlock(choices=[('email', 'Email'), ('url', 'URL')], label='Format', required=False)), ('default_value', wagtail.core.blocks.CharBlock(label='Default value', required=False))], group='Fields')), ('text', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TextBlock(label='Default value', required=False))], group='Fields')), ('number', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.CharBlock(label='Default value', required=False))], group='Fields')), ('checkbox', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.BooleanBlock(required=False))], group='Fields')), ('radios', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.core.blocks.ListBlock(wagtail.core.blocks.CharBlock(label='Choice')))], group='Fields')), ('dropdown', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('choices', wagtail.core.blocks.ListBlock(wagtail.core.blocks.CharBlock(label='Choice')))], group='Fields')), ('checkboxes', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('checkboxes', wagtail.core.blocks.ListBlock(wagtail.core.blocks.CharBlock(label='Checkbox')))], group='Fields')), ('date', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.DateBlock(required=False))], group='Fields')), ('time', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TimeBlock(required=False))], group='Fields')), ('datetime', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.DateTimeBlock(required=False))], group='Fields')), ('image', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False))], group='Fields')), ('file', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False))], group='Fields')), ('multi_file', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False))], group='Fields')), ('rich_text', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('default_value', wagtail.core.blocks.TextBlock(label='Default value', required=False))], group='Fields')), ('category', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(help_text='Leave blank to use the default Category label', label='Label', required=False)), ('help_text', wagtail.core.blocks.TextBlock(label='Leave blank to use the default Category help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('category', opentech.apply.categories.blocks.ModelChooserBlock('categories.Category')), ('multi', wagtail.core.blocks.BooleanBlock(label='Multi select', required=False))], group='Custom')), ('title', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group='Required')), ('email', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group='Required')), ('full_name', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group='Required')), ('duration', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group='Required')), ('value', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group='Custom')), ('address', wagtail.core.blocks.StructBlock([('field_label', wagtail.core.blocks.CharBlock(label='Label')), ('help_text', wagtail.core.blocks.TextBlock(label='Help text', required=False)), ('required', wagtail.core.blocks.BooleanBlock(label='Required', required=False)), ('info', wagtail.core.blocks.static_block.StaticBlock())], group='Custom'))]), + ), + ] diff --git a/opentech/apply/funds/models/mixins.py b/opentech/apply/funds/models/mixins.py index d13c71f99ce9d37cd8a02e5345e0620b62610783..c51f64fecda4c7c49942b17db5a54e5b3f11766b 100644 --- a/opentech/apply/funds/models/mixins.py +++ b/opentech/apply/funds/models/mixins.py @@ -4,7 +4,7 @@ from django.core.files import File from django.core.files.storage import get_storage_class from opentech.apply.stream_forms.blocks import FormFieldBlock -from opentech.apply.utils.blocks import MustIncludeFieldBlock +from opentech.apply.utils.blocks import SingleIncludeMixin from opentech.apply.stream_forms.blocks import UploadableMediaBlock from opentech.apply.stream_forms.files import StreamFieldFile @@ -16,6 +16,10 @@ __all__ = ['AccessFormData'] submission_storage = get_storage_class(getattr(settings, 'PRIVATE_FILE_STORAGE', None))() +class UnusedFieldException(Exception): + pass + + class AccessFormData: """Mixin for interacting with form data from streamfields @@ -30,7 +34,7 @@ class AccessFormData: # Returns the data mapped by field id instead of the data stored using the must include # values data = self.form_data.copy() - for field_name, field_id in self.must_include.items(): + for field_name, field_id in self.named_blocks.items(): if field_id not in data: response = data[field_name] data[field_id] = response @@ -78,13 +82,16 @@ class AccessFormData: return data def get_definitive_id(self, id): - if id in self.must_include: - return self.must_include[id] + if id in self.named_blocks: + return self.named_blocks[id] return id def field(self, id): definitive_id = self.get_definitive_id(id) - return self.raw_fields[definitive_id] + try: + return self.raw_fields[definitive_id] + except KeyError: + raise UnusedFieldException(id) from None def data(self, id): definitive_id = self.get_definitive_id(id) @@ -112,21 +119,24 @@ class AccessFormData: def fields(self): # ALl fields on the application fields = self.raw_fields.copy() - for field_name, field_id in self.must_include.items(): + for field_name, field_id in self.named_blocks.items(): response = fields.pop(field_id) fields[field_name] = response return fields @property - def must_include(self): + def named_blocks(self): return { field.block.name: field.id for field in self.form_fields - if isinstance(field.block, MustIncludeFieldBlock) + if isinstance(field.block, SingleIncludeMixin) } def render_answer(self, field_id, include_question=False): - field = self.field(field_id) + try: + field = self.field(field_id) + except UnusedFieldException: + return '-' data = self.data(field_id) return field.render(context={'data': data, 'include_question': include_question}) @@ -135,7 +145,7 @@ class AccessFormData: return [ self.render_answer(field_id, include_question=True) for field_id in self.question_field_ids - if field_id not in self.must_include + if field_id not in self.named_blocks ] def output_answers(self): diff --git a/opentech/apply/funds/models/submissions.py b/opentech/apply/funds/models/submissions.py index 646422bc08ced60de8d222412a6e76843a3bf54f..d5029ea5047eecb3f98f762d83c906424a009d6f 100644 --- a/opentech/apply/funds/models/submissions.py +++ b/opentech/apply/funds/models/submissions.py @@ -26,7 +26,7 @@ from opentech.apply.stream_forms.models import BaseStreamForm from .mixins import AccessFormData from .utils import LIMIT_TO_STAFF, LIMIT_TO_STAFF_AND_REVIEWERS, WorkflowHelpers -from ..blocks import ApplicationCustomFormFieldsBlock, REQUIRED_BLOCK_NAMES +from ..blocks import ApplicationCustomFormFieldsBlock, NAMED_BLOCKS from ..workflow import ( active_statuses, DETERMINATION_RESPONSE_PHASES, @@ -51,7 +51,7 @@ class JSONOrderable(models.QuerySet): def build_json_order_by(field): try: - if field.replace('-', '') not in REQUIRED_BLOCK_NAMES: + if field.replace('-', '') not in NAMED_BLOCKS: return field except AttributeError: return field @@ -61,7 +61,7 @@ class JSONOrderable(models.QuerySet): field = field[1:] else: descending = False - return OrderBy(RawSQL(f'LOWER({self.json_field}->>%s)', (field,)), descending=descending) + return OrderBy(RawSQL(f'LOWER({self.json_field}->>%s)', (field,)), descending=descending, nulls_last=True) field_ordering = [build_json_order_by(field) for field in field_names] return super().order_by(*field_ordering) @@ -251,8 +251,8 @@ class ApplicationSubmissionMetaclass(AddTransitions): # We want to access the redered display of the required fields. # Treat in similar way to django's get_FIELD_display - for required_name in REQUIRED_BLOCK_NAMES: - partial_method_name = f'_{required_name}_method' + for block_name in NAMED_BLOCKS: + partial_method_name = f'_{block_name}_method' # We need to generate the partial method and the wrap it in property so # we can access the required fields like normal fields. e.g. self.title # Partial method requires __get__ to be called in order to bind it to the @@ -262,17 +262,17 @@ class ApplicationSubmissionMetaclass(AddTransitions): setattr( cls, partial_method_name, - partialmethod(cls._get_REQUIRED_value, name=required_name), + partialmethod(cls._get_REQUIRED_value, name=block_name), ) setattr( cls, - f'{required_name}', + f'{block_name}', property(getattr(cls, partial_method_name)), ) setattr( cls, - f'get_{required_name}_display', - partialmethod(cls._get_REQUIRED_display, name=required_name), + f'get_{block_name}_display', + partialmethod(cls._get_REQUIRED_display, name=block_name), ) return cls @@ -460,7 +460,7 @@ class ApplicationSubmission( self.process_file_data(self.form_data) def process_form_data(self): - for field_name, field_id in self.must_include.items(): + for field_name, field_id in self.named_blocks.items(): response = self.form_data.pop(field_id, None) if response: self.form_data[field_name] = response diff --git a/opentech/apply/funds/tests/test_models.py b/opentech/apply/funds/tests/test_models.py index 012127318004a6c19f08ec575060cc3652a7242e..0745ef0280d4c152d7f9147fe5c6f6f8a3e22a87 100644 --- a/opentech/apply/funds/tests/test_models.py +++ b/opentech/apply/funds/tests/test_models.py @@ -445,10 +445,10 @@ class TestApplicationSubmission(TestCase): class TestSubmissionRenderMethods(TestCase): - def test_must_include_not_included_in_answers(self): + def test_named_blocks_not_included_in_answers(self): submission = ApplicationSubmissionFactory() answers = submission.render_answers() - for name in submission.must_include: + for name in submission.named_blocks: field = submission.field(name) self.assertNotIn(field.value['field_label'], answers) @@ -456,7 +456,7 @@ class TestSubmissionRenderMethods(TestCase): submission = ApplicationSubmissionFactory() answers = submission.output_answers() for field_name in submission.question_field_ids: - if field_name not in submission.must_include: + if field_name not in submission.named_blocks: field = submission.field(field_name) self.assertIn(field.value['field_label'], answers) diff --git a/opentech/apply/funds/tests/test_views.py b/opentech/apply/funds/tests/test_views.py index 6888d4d7475d0fcb86dda61b1b2957b772aefb0d..62c1c7928e19ee032c3145562016a34399935c98 100644 --- a/opentech/apply/funds/tests/test_views.py +++ b/opentech/apply/funds/tests/test_views.py @@ -32,7 +32,7 @@ def prepare_form_data(submission, **kwargs): field_id = submission.field(field).id data[field_id] = value - address_field = submission.must_include['address'] + address_field = submission.named_blocks['address'] address = data.pop(address_field) data.update(**prepare_address(address, address_field)) @@ -175,6 +175,11 @@ class TestStaffSubmissionView(BaseSubmissionViewTestCase): self.assertEqual(old_status, submission.status) self.assertEqual(new_title, submission.title) + def test_not_included_fields_render(self): + submission = ApplicationSubmissionFactory(form_fields__exclude__value=True) + response = self.get_page(submission) + self.assertNotContains(response, 'Value') + class TestReviewersUpdateView(BaseSubmissionViewTestCase): user_factory = StaffFactory diff --git a/opentech/apply/funds/views.py b/opentech/apply/funds/views.py index 39120dd227f1c11b2df649bc27a71feed15af5bb..c72ceb10a04ef159b5d7d01b6efe4be03ea3a39d 100644 --- a/opentech/apply/funds/views.py +++ b/opentech/apply/funds/views.py @@ -393,7 +393,7 @@ class RevisionCompareView(DetailView): compare(*fields, should_bleach=False) for fields in zip(from_required, to_required) ] - for field, diff in zip(self.object.must_include, diffed_required): + for field, diff in zip(self.object.named_blocks, diffed_required): setattr(self.object, 'get_{}_display'.format(field), diff) # Compare all the answers @@ -407,7 +407,7 @@ class RevisionCompareView(DetailView): def render_required(self): return [ getattr(self.object, 'get_{}_display'.format(field))() - for field in self.object.must_include + for field in self.object.named_blocks ] def get_context_data(self, **kwargs): diff --git a/opentech/apply/stream_forms/blocks.py b/opentech/apply/stream_forms/blocks.py index 72ca5028140a6859712f63b90b8038110bc4a0e8..707f480ca598efd6794178635243a2e5332335b6 100644 --- a/opentech/apply/stream_forms/blocks.py +++ b/opentech/apply/stream_forms/blocks.py @@ -68,9 +68,6 @@ class FormFieldBlock(StructBlock): class OptionalFormFieldBlock(FormFieldBlock): required = BooleanBlock(label=_('Required'), required=False) - def get_searchable_content(self, value, data): - return data - CHARFIELD_FORMATS = [ ('email', _('Email')), diff --git a/opentech/apply/stream_forms/testing/factories.py b/opentech/apply/stream_forms/testing/factories.py index b60588aef0a00318fc67b126b3e50cedbb2ae843..902ba4ce90239e439023ae722f12e5ca5079e6d1 100644 --- a/opentech/apply/stream_forms/testing/factories.py +++ b/opentech/apply/stream_forms/testing/factories.py @@ -56,7 +56,13 @@ class FormDataFactory(factory.Factory, metaclass=AddFormFieldsMetaclass): form_data = {} for name, answer in kwargs.items(): - form_data[form_definition[name]] = answer + try: + key = form_definition[name] + except KeyError: + # We are not using that field - don't add the submission data + pass + else: + form_data[key] = answer if clean: clean_object = for_factory() diff --git a/opentech/apply/utils/blocks.py b/opentech/apply/utils/blocks.py index 898e0487a2726a079050f92b3c78e9fd3f1df9ac..1cd8182090a2f4709317eb7f5b4aeb487785a652 100644 --- a/opentech/apply/utils/blocks.py +++ b/opentech/apply/utils/blocks.py @@ -8,7 +8,7 @@ from django.utils.text import mark_safe from wagtail.core.blocks import StaticBlock, StreamValue, StreamBlock -from opentech.apply.stream_forms.blocks import FormFieldBlock, TextFieldBlock +from opentech.apply.stream_forms.blocks import FormFieldBlock, OptionalFormFieldBlock, TextFieldBlock from opentech.apply.utils.options import RICH_TEXT_WIDGET @@ -45,10 +45,13 @@ class RichTextFieldBlock(TextFieldBlock): class CustomFormFieldsBlock(StreamBlock): rich_text = RichTextFieldBlock(group=_('Fields')) required_blocks = [] + single_blocks = [] def __init__(self, *args, **kwargs): child_blocks = [(block.name, block(group=_('Required'))) for block in self.required_blocks] + child_blocks += [(block.name, block(group=_('Custom'))) for block in self.single_blocks] self.required_block_names = [block.name for block in self.required_blocks] + self.single_block_names = [block.name for block in self.single_blocks] + self.required_block_names super().__init__(child_blocks, *args, **kwargs) @@ -65,7 +68,7 @@ class CustomFormFieldsBlock(StreamBlock): duplicates = [ name for name in find_duplicates(block_types) - if name in self.required_block_names + if name in self.single_block_names ] all_errors = list() @@ -76,7 +79,7 @@ class CustomFormFieldsBlock(StreamBlock): if duplicates: all_errors.append( - 'You have duplicates of the following required fields: {}'.format(', '.join(prettify_names(duplicates))) + 'The following fields must be included only once: {}'.format(', '.join(prettify_names(duplicates))) ) for i, block_name in enumerate(block_types): if block_name in duplicates: @@ -110,10 +113,11 @@ class CustomFormFieldsBlock(StreamBlock): return StreamValue(self, value, is_lazy=True) -class MustIncludeStatic(StaticBlock): +class SingleIncludeStatic(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) @@ -137,15 +141,22 @@ class MustIncludeStatic(StaticBlock): return ('wagtail.core.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 - """ +class SingleIncludeMixin: def __init__(self, *args, **kwargs): info_name = f'{self.name.title()} Field' - child_blocks = [('info', MustIncludeStatic(label=info_name, description=self.description))] + child_blocks = [('info', SingleIncludeStatic(label=info_name, description=self.description))] super().__init__(child_blocks, *args, **kwargs) + +class SingleIncludeBlock(SingleIncludeMixin, OptionalFormFieldBlock): + """A block that is only allowed to be included once in the form, but is optional""" + + +class MustIncludeFieldBlock(SingleIncludeMixin, 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 get_field_kwargs(self, struct_value): kwargs = super().get_field_kwargs(struct_value) kwargs['required'] = True