From 08f62c797a530f8e1e7ca4e1aa788718e920b403 Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Sun, 12 Feb 2017 21:20:35 +0100 Subject: [PATCH 01/27] refs #170, Add field builder from schema --- formidable/forms/__init__.py | 19 +++++++- formidable/forms/field_builder.py | 6 ++- formidable/forms/field_builder_from_schema.py | 48 +++++++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 formidable/forms/field_builder_from_schema.py diff --git a/formidable/forms/__init__.py b/formidable/forms/__init__.py index 289e69a8..ac5d635b 100644 --- a/formidable/forms/__init__.py +++ b/formidable/forms/__init__.py @@ -13,10 +13,9 @@ from django import forms from django.db.models import Prefetch - import six -from formidable.forms import field_builder +from formidable.forms import field_builder, field_builder_from_schema from formidable.forms.validations.presets import presets_register from formidable.models import Access, Formidable, Item @@ -57,6 +56,22 @@ def clean(self): return cleaned_data +def get_dynamic_form_class_from_schema(schema, field_factory=None): + """ + Return a dynamically generated and contextualized form class + + """ + attrs = OrderedDict() + field_factory = field_factory or field_builder_from_schema.FormFieldFactory() # noqa + doc = schema['description'] + for field in schema['fields']: + attrs[field['slug']] = field_factory.produce(field) + + klass = type(str('DynamicForm'), (BaseDynamicForm,), attrs) + klass.__doc__ = doc + return klass + + def get_dynamic_form_class(formidable, role=None, field_factory=None): """ This is the main method for getting a django form class from a formidable diff --git a/formidable/forms/field_builder.py b/formidable/forms/field_builder.py index 41d26b2c..85e5c782 100644 --- a/formidable/forms/field_builder.py +++ b/formidable/forms/field_builder.py @@ -220,8 +220,12 @@ def produce(self, field, role=None): :class:`django.forms.Field` instance according to the type_id, validations and rules. """ - builder = self.map[field.type_id](field) + type_id = self.get_type_id(field) + builder = self.map[type_id](field) return builder.build(role) + def get_type_id(self, field): + return field.type_id + form_field_factory = FormFieldFactory() diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py new file mode 100644 index 00000000..24abb806 --- /dev/null +++ b/formidable/forms/field_builder_from_schema.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +from django import forms + +from formidable.forms.field_builder import ( + FieldBuilder as FB, FormFieldFactory as FF +) + + +class FieldBuilder(FB): + + field_class = forms.CharField + widget_class = None + + def get_required(self): + return self.field['required'] + + def get_disabled(self): + return self.field['disabled'] + + def get_label(self): + return self.field['label'] + + def get_help_text(self): + return self.field['description'] + + def get_validators(self): + return [] + + +class TextFieldBuilder(FieldBuilder): + + widget_class = forms.TextInput + + +class ParagraphFieldBuilder(FieldBuilder): + + widget_class = forms.Textarea + + +class FormFieldFactory(FF): + + field_map = { + 'text': TextFieldBuilder, + 'paragraph': ParagraphFieldBuilder, + } + + def get_type_id(self, field): + return field['type_id'] From 51caf6f2c7bb37c8537db944a08ad9c0e4b6dc4b Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Sun, 12 Feb 2017 21:21:03 +0100 Subject: [PATCH 02/27] refs #170, Add related tests to paragraph and text field --- demo/tests/test_form_from_schema.py | 59 +++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 demo/tests/test_form_from_schema.py diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py new file mode 100644 index 00000000..3f8d8a52 --- /dev/null +++ b/demo/tests/test_form_from_schema.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +from django.test import TestCase +from django import forms + +from formidable.constants import REQUIRED +from formidable.forms import ( + FormidableForm, fields, get_dynamic_form_class_from_schema +) +from formidable.forms import widgets +from formidable.serializers.forms import ContextFormSerializer + + +class TestFormFromSchema(TestCase): + + def test_charfield(self): + class TestCharField(FormidableForm): + """ Test charfield """ + charfield = fields.CharField() + + formidable = TestCharField.to_formidable(label='label') + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + form = get_dynamic_form_class_from_schema(schema)() + self.assertIn('charfield', form.fields) + charfield = form.fields['charfield'] + self.assertEqual(type(charfield), forms.CharField) + self.assertFalse(charfield.required) + + def test_required_charfield(self): + class TestCharField(FormidableForm): + """ Test charfield """ + charfield = fields.CharField(accesses={'jedi': REQUIRED}) + + formidable = TestCharField.to_formidable(label='label') + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + form = get_dynamic_form_class_from_schema(schema)() + self.assertIn('charfield', form.fields) + charfield = form.fields['charfield'] + self.assertEqual(type(charfield), forms.CharField) + self.assertTrue(charfield.required) + + def test_paragraph_field(self): + class TestParagraphField(FormidableForm): + """ Test Paraprah """ + paragraph = fields.CharField(widget=widgets.Textarea) + + formidable = TestParagraphField.to_formidable(label='label') + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + form = get_dynamic_form_class_from_schema(schema)() + self.assertIn('paragraph', form.fields) + charfield = form.fields['paragraph'] + self.assertEqual(type(charfield), forms.CharField) + self.assertEqual(type(charfield.widget), forms.Textarea) + self.assertFalse(charfield.required) From ba34ef135bd0d28e75d5ec465a459038066c4947 Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Sun, 12 Feb 2017 21:29:00 +0100 Subject: [PATCH 03/27] refs #170, Remove accesses computation from contextualized schema --- formidable/forms/field_builder.py | 9 ++++++++- formidable/forms/field_builder_from_schema.py | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/formidable/forms/field_builder.py b/formidable/forms/field_builder.py index 85e5c782..85db50e7 100644 --- a/formidable/forms/field_builder.py +++ b/formidable/forms/field_builder.py @@ -23,10 +23,17 @@ def __init__(self, field): self.validator_factory = self.validator_factory_class() def build(self, role=None): - self.access = self.field.accesses.all()[0] if role else None + self.access = self.get_accesses(role) field_class = self.get_field_class() return field_class(**self.get_field_kwargs()) + def get_accesses(self, role): + if role: + # The role is previously "prefetch" in order to avoid database + # hit, we don't use a get() method in queryset. + return self.field.accesses.all()[0] + return None + def get_field_class(self): return self.field_class diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index 24abb806..aa2223ff 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -26,6 +26,10 @@ def get_help_text(self): def get_validators(self): return [] + def get_accesses(self, role): + # No need to compute accesses, the schema is already contextualized. + return None + class TextFieldBuilder(FieldBuilder): From ed5792cc05ea1568f0170acdbc87b044bb2da9ac Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Sun, 12 Feb 2017 21:38:09 +0100 Subject: [PATCH 04/27] refs #170, Add checkbox --- demo/tests/test_form_from_schema.py | 15 +++++++++++++++ formidable/forms/field_builder_from_schema.py | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index 3f8d8a52..c555c821 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -57,3 +57,18 @@ class TestParagraphField(FormidableForm): self.assertEqual(type(charfield), forms.CharField) self.assertEqual(type(charfield.widget), forms.Textarea) self.assertFalse(charfield.required) + + def test_checkbox_field(self): + class TestCheckBoxField(FormidableForm): + """ Test checkbox """ + checkbox = fields.BooleanField() + + formidable = TestCheckBoxField.to_formidable(label='label') + + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + form = get_dynamic_form_class_from_schema(schema)() + self.assertIn('checkbox', form.fields) + checkbox = form.fields['checkbox'] + self.assertEqual(type(checkbox), forms.BooleanField) diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index aa2223ff..5ad63651 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -41,11 +41,16 @@ class ParagraphFieldBuilder(FieldBuilder): widget_class = forms.Textarea +class CheckboxFieldBuilder(FieldBuilder): + field_class = forms.BooleanField + + class FormFieldFactory(FF): field_map = { 'text': TextFieldBuilder, 'paragraph': ParagraphFieldBuilder, + 'checkbox': CheckboxFieldBuilder, } def get_type_id(self, field): From 9d65403dd4cf2a9ff461e8a47d025cbf077d9c3b Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Mon, 13 Feb 2017 08:54:56 +0100 Subject: [PATCH 05/27] refs #170, Add email field --- demo/tests/test_form_from_schema.py | 15 +++++++++++++++ formidable/forms/field_builder_from_schema.py | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index c555c821..fc1aa71e 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -72,3 +72,18 @@ class TestCheckBoxField(FormidableForm): self.assertIn('checkbox', form.fields) checkbox = form.fields['checkbox'] self.assertEqual(type(checkbox), forms.BooleanField) + + def test_email_field(self): + class TestemailField(FormidableForm): + """ Test email """ + email = fields.EmailField() + + formidable = TestemailField.to_formidable(label='label') + + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + form = get_dynamic_form_class_from_schema(schema)() + self.assertIn('email', form.fields) + email = form.fields['email'] + self.assertEqual(type(email), forms.EmailField) diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index 5ad63651..a9192936 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -45,12 +45,17 @@ class CheckboxFieldBuilder(FieldBuilder): field_class = forms.BooleanField +class EmailFieldBuilder(FieldBuilder): + field_class = forms.EmailField + + class FormFieldFactory(FF): field_map = { 'text': TextFieldBuilder, 'paragraph': ParagraphFieldBuilder, 'checkbox': CheckboxFieldBuilder, + 'email': EmailFieldBuilder, } def get_type_id(self, field): From cf61e29f3b715a2df485255c04cdbe4919ace732 Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Mon, 13 Feb 2017 08:57:36 +0100 Subject: [PATCH 06/27] refs #170, Add integer field --- demo/tests/test_form_from_schema.py | 15 +++++++++++++++ formidable/forms/field_builder_from_schema.py | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index fc1aa71e..df685c9c 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -87,3 +87,18 @@ class TestemailField(FormidableForm): self.assertIn('email', form.fields) email = form.fields['email'] self.assertEqual(type(email), forms.EmailField) + + def test_integer_field(self): + class TestintegerField(FormidableForm): + """ Test integer """ + integer = fields.IntegerField() + + formidable = TestintegerField.to_formidable(label='label') + + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + form = get_dynamic_form_class_from_schema(schema)() + self.assertIn('integer', form.fields) + integer = form.fields['integer'] + self.assertEqual(type(integer), forms.IntegerField) diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index a9192936..3507ee29 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -49,6 +49,10 @@ class EmailFieldBuilder(FieldBuilder): field_class = forms.EmailField +class IntegerFieldBuilder(FieldBuilder): + field_class = forms.IntegerField + + class FormFieldFactory(FF): field_map = { @@ -56,6 +60,7 @@ class FormFieldFactory(FF): 'paragraph': ParagraphFieldBuilder, 'checkbox': CheckboxFieldBuilder, 'email': EmailFieldBuilder, + 'number': IntegerFieldBuilder, } def get_type_id(self, field): From f6dfd9bc9b82c7c7472dc2b3bd3d46083543793f Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Mon, 13 Feb 2017 08:59:26 +0100 Subject: [PATCH 07/27] refs #170, Add filefield support --- demo/tests/test_form_from_schema.py | 15 +++++++++++++++ formidable/forms/field_builder_from_schema.py | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index df685c9c..e0a49031 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -102,3 +102,18 @@ class TestintegerField(FormidableForm): self.assertIn('integer', form.fields) integer = form.fields['integer'] self.assertEqual(type(integer), forms.IntegerField) + + def test_file_field(self): + class TestfileField(FormidableForm): + """ Test file """ + file_ = fields.FileField() + + formidable = TestfileField.to_formidable(label='label') + + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + form = get_dynamic_form_class_from_schema(schema)() + self.assertIn('file_', form.fields) + file_ = form.fields['file_'] + self.assertEqual(type(file_), forms.FileField) diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index 3507ee29..bf84ddd7 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -53,6 +53,10 @@ class IntegerFieldBuilder(FieldBuilder): field_class = forms.IntegerField +class FileFieldBuilder(FieldBuilder): + field_class = forms.FileField + + class FormFieldFactory(FF): field_map = { @@ -61,6 +65,7 @@ class FormFieldFactory(FF): 'checkbox': CheckboxFieldBuilder, 'email': EmailFieldBuilder, 'number': IntegerFieldBuilder, + 'file': FileFieldBuilder, } def get_type_id(self, field): From a2449cc5edb69c0a07c23d11279da1f4fa6a9045 Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Mon, 13 Feb 2017 09:06:40 +0100 Subject: [PATCH 08/27] refs #170, Add date field support --- demo/tests/test_form_from_schema.py | 15 +++++++++++++++ formidable/forms/field_builder_from_schema.py | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index e0a49031..3e91b63b 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -117,3 +117,18 @@ class TestfileField(FormidableForm): self.assertIn('file_', form.fields) file_ = form.fields['file_'] self.assertEqual(type(file_), forms.FileField) + + def test_date_field(self): + class TestdateField(FormidableForm): + """ Test date """ + date = fields.DateField() + + formidable = TestdateField.to_formidable(label='label') + + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + form = get_dynamic_form_class_from_schema(schema)() + self.assertIn('date', form.fields) + date = form.fields['date'] + self.assertEqual(type(date), forms.DateField) diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index bf84ddd7..680633b6 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -57,6 +57,10 @@ class FileFieldBuilder(FieldBuilder): field_class = forms.FileField +class DateFieldBuilder(FieldBuilder): + field_class = forms.DateField + + class FormFieldFactory(FF): field_map = { @@ -66,6 +70,7 @@ class FormFieldFactory(FF): 'email': EmailFieldBuilder, 'number': IntegerFieldBuilder, 'file': FileFieldBuilder, + 'date': DateFieldBuilder, } def get_type_id(self, field): From 79ccf25d80974815614c8b4eb441f750cc502ad3 Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Mon, 13 Feb 2017 09:45:33 +0100 Subject: [PATCH 09/27] refs #170, Mock the global rules generation in form --- formidable/forms/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/formidable/forms/__init__.py b/formidable/forms/__init__.py index ac5d635b..34a6be57 100644 --- a/formidable/forms/__init__.py +++ b/formidable/forms/__init__.py @@ -67,6 +67,7 @@ def get_dynamic_form_class_from_schema(schema, field_factory=None): for field in schema['fields']: attrs[field['slug']] = field_factory.produce(field) + attrs['rules'] = [] klass = type(str('DynamicForm'), (BaseDynamicForm,), attrs) klass.__doc__ = doc return klass From e46597fafc58c6fba0c8d49e399ad331b0018345 Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Mon, 13 Feb 2017 09:46:02 +0100 Subject: [PATCH 10/27] refs #170, Refactorize ValidatorFactory --- formidable/validators.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/formidable/validators.py b/formidable/validators.py index 2ac48b58..6f415c85 100644 --- a/formidable/validators.py +++ b/formidable/validators.py @@ -235,9 +235,15 @@ def neq(self, value, message): return NEQValidator(value, message) def produce(self, validation): - meth = self.maps[validation.type] - msg = validation.message or None - return meth(self, validation.value, msg) + type_, msg, value = self.extract_validation_attribute(validation) + meth = self.maps[type_] + return meth(self, value, msg) + + def extract_validation_attribute(self, validation): + """ + Return the tuple of validation type, validation msg, validation value + """ + return validation.type, validation.message or None, validation.value class DateValidatorFactory(ValidatorFactory): From 241057923de324c71b058bf1dfc8b75f5888b4db Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Mon, 13 Feb 2017 09:46:32 +0100 Subject: [PATCH 11/27] refs #170, Add method to subclass in field builder --- formidable/forms/field_builder.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/formidable/forms/field_builder.py b/formidable/forms/field_builder.py index 85db50e7..3cf976be 100644 --- a/formidable/forms/field_builder.py +++ b/formidable/forms/field_builder.py @@ -99,9 +99,15 @@ def get_validators(self): return list(self.gen_validators()) def gen_validators(self): - for validation in self.field.validations.all(): + for validation in self.get_validations(): yield self.validator_factory.produce(validation) + def get_validations(self): + """ + return iterator over field validation + """ + return self.field.validations.all() + class FileFieldBuilder(FieldBuilder): From ce2db34692bcced4df27892cb8f90049ee3ec872 Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Mon, 13 Feb 2017 09:51:00 +0100 Subject: [PATCH 12/27] refs #170, Generate validator --- formidable/forms/field_builder_from_schema.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index 680633b6..0d1b51e7 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -4,12 +4,20 @@ from formidable.forms.field_builder import ( FieldBuilder as FB, FormFieldFactory as FF ) +from formidable.validators import ValidatorFactory as VF + + +class ValidatorFactory(VF): + + def extract_validation_attribute(self, validation): + return validation['type'], validation['message'], validation['value'] class FieldBuilder(FB): field_class = forms.CharField widget_class = None + validator_factory_class = ValidatorFactory def get_required(self): return self.field['required'] @@ -23,8 +31,8 @@ def get_label(self): def get_help_text(self): return self.field['description'] - def get_validators(self): - return [] + def get_validations(self): + return self.field['validations'] def get_accesses(self, role): # No need to compute accesses, the schema is already contextualized. From 546d3d5974ddf75820c3f22bf9dcef21c7ae2456 Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Mon, 13 Feb 2017 09:51:15 +0100 Subject: [PATCH 13/27] refs #170, Add test for validations --- demo/tests/test_form_from_schema.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index 3e91b63b..7f756839 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -7,6 +7,7 @@ FormidableForm, fields, get_dynamic_form_class_from_schema ) from formidable.forms import widgets +from formidable.validators import (GTEValidator, MinLengthValidator) from formidable.serializers.forms import ContextFormSerializer @@ -132,3 +133,20 @@ class TestdateField(FormidableForm): self.assertIn('date', form.fields) date = form.fields['date'] self.assertEqual(type(date), forms.DateField) + + def test_with_validations(self): + class FormWithValidations(FormidableForm): + text = fields.CharField(validators=[MinLengthValidator(4)]) + integer = fields.IntegerField(validators=[GTEValidator(42)]) + + formidable = FormWithValidations.to_formidable(label='validation') + + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class(data={'text': 'tut', 'integer': 21}) + self.assertFalse(form.is_valid()) + self.assertEqual(len(form.errors), 2) + form = form_class(data={'text': 'tutu', 'integer': 43}) + self.assertTrue(form.is_valid()) From 8a07f13f95776b7e8fc84b1179017d75249cdebe Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Tue, 14 Feb 2017 11:06:55 +0100 Subject: [PATCH 14/27] refs #170, Add date validator factory --- demo/tests/test_form_from_schema.py | 26 ++++++++++++++++++- formidable/forms/field_builder_from_schema.py | 11 +++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index 7f756839..19903543 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -1,13 +1,16 @@ # -*- coding: utf-8 -*- from django.test import TestCase from django import forms +from freezegun import freeze_time from formidable.constants import REQUIRED from formidable.forms import ( FormidableForm, fields, get_dynamic_form_class_from_schema ) from formidable.forms import widgets -from formidable.validators import (GTEValidator, MinLengthValidator) +from formidable.validators import ( + GTEValidator, MinLengthValidator, AgeAboveValidator +) from formidable.serializers.forms import ContextFormSerializer @@ -134,6 +137,27 @@ class TestdateField(FormidableForm): date = form.fields['date'] self.assertEqual(type(date), forms.DateField) + @freeze_time('2021-01-01') + def test_date_field_with_validation(self): + class TestdateField(FormidableForm): + """ Test date """ + date = fields.DateField(validators=[AgeAboveValidator(21)]) + + formidable = TestdateField.to_formidable(label='label') + + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class(data={'date': '1990-01-01'}) + self.assertIn('date', form.fields) + date = form.fields['date'] + self.assertEqual(type(date), forms.DateField) + self.assertTrue(form.is_valid()) + + form = form_class(data={'date': '2015-01-01'}) + self.assertFalse(form.is_valid()) + def test_with_validations(self): class FormWithValidations(FormidableForm): text = fields.CharField(validators=[MinLengthValidator(4)]) diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index 0d1b51e7..29be7fbf 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -4,7 +4,9 @@ from formidable.forms.field_builder import ( FieldBuilder as FB, FormFieldFactory as FF ) -from formidable.validators import ValidatorFactory as VF +from formidable.validators import ( + DateValidatorFactory as DVF, ValidatorFactory as VF +) class ValidatorFactory(VF): @@ -13,6 +15,12 @@ def extract_validation_attribute(self, validation): return validation['type'], validation['message'], validation['value'] +class DateValidatorFactory(DVF): + + def extract_validation_attribute(self, validation): + return validation['type'], validation['message'], validation['value'] + + class FieldBuilder(FB): field_class = forms.CharField @@ -67,6 +75,7 @@ class FileFieldBuilder(FieldBuilder): class DateFieldBuilder(FieldBuilder): field_class = forms.DateField + validator_factory_class = DateValidatorFactory class FormFieldFactory(FF): From 6bbb04fc3223417ead6b624bf637c7425e4563c7 Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Sun, 26 Feb 2017 18:30:26 +0100 Subject: [PATCH 15/27] refs #170, Use the correct method to build rules from schema --- formidable/forms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formidable/forms/__init__.py b/formidable/forms/__init__.py index 34a6be57..ef13fe34 100644 --- a/formidable/forms/__init__.py +++ b/formidable/forms/__init__.py @@ -67,7 +67,7 @@ def get_dynamic_form_class_from_schema(schema, field_factory=None): for field in schema['fields']: attrs[field['slug']] = field_factory.produce(field) - attrs['rules'] = [] + attrs['rules'] = presets_register.build_rules_from_schema(schema) klass = type(str('DynamicForm'), (BaseDynamicForm,), attrs) klass.__doc__ = doc return klass From a002f80704c77aefd39cc8675af2108530e49d61 Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Sun, 26 Feb 2017 18:30:46 +0100 Subject: [PATCH 16/27] refs #170, Add a method to build rules from schema --- formidable/forms/validations/presets.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/formidable/forms/validations/presets.py b/formidable/forms/validations/presets.py index 73bb94c8..18fce398 100644 --- a/formidable/forms/validations/presets.py +++ b/formidable/forms/validations/presets.py @@ -11,7 +11,7 @@ import six -from formidable.models import Preset +from formidable.models import Preset, PresetArg class PresetsRegister(dict): @@ -30,6 +30,23 @@ def gen_rules(self, form, fields): # does not match the accesses) pass + def build_rules_from_schema(self, schema): + rules = [] + for preset in schema.get('presets', []): + klass = self[preset['preset_id']] + arguments = self.get_arguments_from_schema(preset.get( + 'arguments', [] + )) + rules.append(klass(arguments, message=preset['message'])) + + return rules + + def get_arguments_from_schema(self, arguments): + args = [] + for argument in arguments: + args.append(PresetArg(**argument)) + return args + presets_register = PresetsRegister() From e6a5c76ef3b9c3e5815ab6eeffc2e0747b5c22cd Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Sun, 26 Feb 2017 18:31:01 +0100 Subject: [PATCH 17/27] refs #170, Add test on presets generated from schema --- demo/tests/test_form_from_schema.py | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index 19903543..c53393d0 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -8,6 +8,10 @@ FormidableForm, fields, get_dynamic_form_class_from_schema ) from formidable.forms import widgets +from formidable.forms.validations.presets import ( + ConfirmationPresets +) +from formidable.models import PresetArg from formidable.validators import ( GTEValidator, MinLengthValidator, AgeAboveValidator ) @@ -174,3 +178,33 @@ class FormWithValidations(FormidableForm): self.assertEqual(len(form.errors), 2) form = form_class(data={'text': 'tutu', 'integer': 43}) self.assertTrue(form.is_valid()) + + def test_with_presets(self): + + class FormWithPresets(FormidableForm): + + class Meta: + presets = [ + ConfirmationPresets([ + PresetArg(slug='left', field_id='email'), + PresetArg(slug='right', field_id='email_confirm') + ]) + ] + + email = fields.EmailField() + email_confirm = fields.EmailField() + + formidable = FormWithPresets.to_formidable(label='validation') + + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class(data={ + 'email': 'test@test.tld', 'email_confirm': 'toto@test.tld' + }) + self.assertFalse(form.is_valid()) + form = form_class(data={ + 'email': 'test@test.tld', 'email_confirm': 'test@test.tld' + }) + self.assertTrue(form.is_valid()) From 9264f6dd2a7714a2d7ecc4d3f751ab8497c3ad6a Mon Sep 17 00:00:00 2001 From: Guillaume Camera Date: Sun, 26 Feb 2017 19:20:10 +0100 Subject: [PATCH 18/27] refs #170, Add dropdown builder --- demo/tests/test_form_from_schema.py | 40 +++++++++++++++++++ formidable/forms/field_builder_from_schema.py | 33 +++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index c53393d0..924aa223 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -141,6 +141,46 @@ class TestdateField(FormidableForm): date = form.fields['date'] self.assertEqual(type(date), forms.DateField) + def test_dropdown_field(self): + class WithDropdown(FormidableForm): + weapon = fields.ChoiceField( + widget=widgets.Select, + choices=(('gun', 'Eagles'), ('sword', 'Excalibur')) + ) + + formidable = WithDropdown.to_formidable(label='dropdown') + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + form = get_dynamic_form_class_from_schema(schema)() + self.assertIn('weapon', form.fields) + self.assertEqual(type(form.fields['weapon']), forms.ChoiceField) + self.assertEqual(type(form.fields['weapon'].widget), forms.Select) + self.assertIn(('gun', 'Eagles'), form.fields['weapon'].choices) + self.assertIn(('sword', 'Excalibur'), form.fields['weapon'].choices) + + def test_dropdown_multiple(self): + class WithDropdown(FormidableForm): + weapon = fields.ChoiceField( + widget=widgets.SelectMultiple, + choices=(('gun', 'Eagles'), ('sword', 'Excalibur')) + ) + + formidable = WithDropdown.to_formidable(label='dropdown') + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + form = get_dynamic_form_class_from_schema(schema)() + self.assertIn('weapon', form.fields) + self.assertEqual( + type(form.fields['weapon']), forms.MultipleChoiceField + ) + self.assertEqual( + type(form.fields['weapon'].widget), forms.SelectMultiple + ) + self.assertIn(('gun', 'Eagles'), form.fields['weapon'].choices) + self.assertIn(('sword', 'Excalibur'), form.fields['weapon'].choices) + @freeze_time('2021-01-01') def test_date_field_with_validation(self): class TestdateField(FormidableForm): diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index 29be7fbf..ff515523 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -78,6 +78,37 @@ class DateFieldBuilder(FieldBuilder): validator_factory_class = DateValidatorFactory +class ChoiceFieldBuilder(FieldBuilder): + + field_class = forms.ChoiceField + + def get_field_kwargs(self): + kwargs = super(ChoiceFieldBuilder, self).get_field_kwargs() + kwargs['choices'] = self.get_choices() + return kwargs + + def get_choices(self): + choices = [] + for item in self.field.get('items', []): + choices.append((item['value'], item['label'])) + return choices + + +class DropdownFieldBuilder(ChoiceFieldBuilder): + + widget_class = forms.Select + + def get_field_class(self): + if self.field['multiple']: + return forms.MultipleChoiceField + return super(DropdownFieldBuilder, self).get_field_class() + + def get_widget_class(self): + if self.field['multiple']: + return forms.SelectMultiple + return super(DropdownFieldBuilder, self).get_widget_class() + + class FormFieldFactory(FF): field_map = { @@ -88,6 +119,8 @@ class FormFieldFactory(FF): 'number': IntegerFieldBuilder, 'file': FileFieldBuilder, 'date': DateFieldBuilder, + 'dropdown': DropdownFieldBuilder, + } def get_type_id(self, field): From e388ab11755b0b4deb5e0401dbb53f790fa3056d Mon Sep 17 00:00:00 2001 From: Nicolas Dubois Date: Wed, 22 Mar 2017 09:34:44 +0100 Subject: [PATCH 19/27] refs #170, Use intermediate variable for instancing form class --- demo/tests/test_form_from_schema.py | 30 +++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index 924aa223..7a451fb8 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -29,7 +29,8 @@ class TestCharField(FormidableForm): schema = ContextFormSerializer(instance=formidable, context={ 'role': 'jedi' }).data - form = get_dynamic_form_class_from_schema(schema)() + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() self.assertIn('charfield', form.fields) charfield = form.fields['charfield'] self.assertEqual(type(charfield), forms.CharField) @@ -44,7 +45,8 @@ class TestCharField(FormidableForm): schema = ContextFormSerializer(instance=formidable, context={ 'role': 'jedi' }).data - form = get_dynamic_form_class_from_schema(schema)() + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() self.assertIn('charfield', form.fields) charfield = form.fields['charfield'] self.assertEqual(type(charfield), forms.CharField) @@ -59,7 +61,8 @@ class TestParagraphField(FormidableForm): schema = ContextFormSerializer(instance=formidable, context={ 'role': 'jedi' }).data - form = get_dynamic_form_class_from_schema(schema)() + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() self.assertIn('paragraph', form.fields) charfield = form.fields['paragraph'] self.assertEqual(type(charfield), forms.CharField) @@ -76,7 +79,8 @@ class TestCheckBoxField(FormidableForm): schema = ContextFormSerializer(instance=formidable, context={ 'role': 'jedi' }).data - form = get_dynamic_form_class_from_schema(schema)() + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() self.assertIn('checkbox', form.fields) checkbox = form.fields['checkbox'] self.assertEqual(type(checkbox), forms.BooleanField) @@ -91,7 +95,8 @@ class TestemailField(FormidableForm): schema = ContextFormSerializer(instance=formidable, context={ 'role': 'jedi' }).data - form = get_dynamic_form_class_from_schema(schema)() + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() self.assertIn('email', form.fields) email = form.fields['email'] self.assertEqual(type(email), forms.EmailField) @@ -106,7 +111,8 @@ class TestintegerField(FormidableForm): schema = ContextFormSerializer(instance=formidable, context={ 'role': 'jedi' }).data - form = get_dynamic_form_class_from_schema(schema)() + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() self.assertIn('integer', form.fields) integer = form.fields['integer'] self.assertEqual(type(integer), forms.IntegerField) @@ -121,7 +127,8 @@ class TestfileField(FormidableForm): schema = ContextFormSerializer(instance=formidable, context={ 'role': 'jedi' }).data - form = get_dynamic_form_class_from_schema(schema)() + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() self.assertIn('file_', form.fields) file_ = form.fields['file_'] self.assertEqual(type(file_), forms.FileField) @@ -136,7 +143,8 @@ class TestdateField(FormidableForm): schema = ContextFormSerializer(instance=formidable, context={ 'role': 'jedi' }).data - form = get_dynamic_form_class_from_schema(schema)() + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() self.assertIn('date', form.fields) date = form.fields['date'] self.assertEqual(type(date), forms.DateField) @@ -152,7 +160,8 @@ class WithDropdown(FormidableForm): schema = ContextFormSerializer(instance=formidable, context={ 'role': 'jedi' }).data - form = get_dynamic_form_class_from_schema(schema)() + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() self.assertIn('weapon', form.fields) self.assertEqual(type(form.fields['weapon']), forms.ChoiceField) self.assertEqual(type(form.fields['weapon'].widget), forms.Select) @@ -170,7 +179,8 @@ class WithDropdown(FormidableForm): schema = ContextFormSerializer(instance=formidable, context={ 'role': 'jedi' }).data - form = get_dynamic_form_class_from_schema(schema)() + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() self.assertIn('weapon', form.fields) self.assertEqual( type(form.fields['weapon']), forms.MultipleChoiceField From a995c9d7d1eb0ec35a657edfa0fc95cb3cbd2da8 Mon Sep 17 00:00:00 2001 From: Nicolas Dubois Date: Fri, 24 Mar 2017 01:56:09 +0100 Subject: [PATCH 20/27] refs #170, Use generator for get_choices() --- formidable/forms/field_builder_from_schema.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index ff515523..b0c595e9 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -88,10 +88,8 @@ def get_field_kwargs(self): return kwargs def get_choices(self): - choices = [] for item in self.field.get('items', []): - choices.append((item['value'], item['label'])) - return choices + yield (item['value'], item['label']) class DropdownFieldBuilder(ChoiceFieldBuilder): From 228dae8fcc176904cd2147f51707cc3fabb498ae Mon Sep 17 00:00:00 2001 From: Nicolas Dubois Date: Fri, 24 Mar 2017 01:56:34 +0100 Subject: [PATCH 21/27] refs #170, Add radios field --- demo/tests/test_form_from_schema.py | 58 +++++++++++++++++++ demo/tests/test_from_form.py | 1 + formidable/forms/field_builder_from_schema.py | 14 ++++- 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index 7a451fb8..3bf1312b 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -179,6 +179,7 @@ class WithDropdown(FormidableForm): schema = ContextFormSerializer(instance=formidable, context={ 'role': 'jedi' }).data + form_class = get_dynamic_form_class_from_schema(schema) form = form_class() self.assertIn('weapon', form.fields) @@ -191,6 +192,63 @@ class WithDropdown(FormidableForm): self.assertIn(('gun', 'Eagles'), form.fields['weapon'].choices) self.assertIn(('sword', 'Excalibur'), form.fields['weapon'].choices) + def test_radio_field(self): + class TestRadioField(FormidableForm): + radioinput = fields.ChoiceField( + widget=widgets.RadioSelect, + choices=(('yes', 'Yes'), ('no', 'No')), + ) + + formidable = TestRadioField.to_formidable(label='form-with-radio') + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() + + self.assertIn('radioinput', form.fields) + self.assertEqual( + type(form.fields['radioinput']), forms.ChoiceField + ) + self.assertEqual( + type(form.fields['radioinput'].widget), forms.RadioSelect + ) + self.assertIn(('yes', 'Yes'), form.fields['radioinput'].choices) + self.assertIn(('no', 'No'), form.fields['radioinput'].choices) + + def test_checkbox_multiple_field(self): + + choices = ( + ('BELGIUM', 'Chouffe'), ('GERMANY', 'Paulaner'), + ('FRANCE', 'Antidote') + ) + + class TestCheckboxesField(FormidableForm): + + checkboxesinput = fields.MultipleChoiceField( + widget=widgets.CheckboxSelectMultiple, + choices=choices, + ) + + formidable = TestCheckboxesField.to_formidable(label='checkboxes') + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() + + self.assertIn('checkboxesinput', form.fields) + + checkboxes = form.fields['checkboxesinput'] + + self.assertEqual(type(checkboxes), forms.MultipleChoiceField) + self.assertEqual(type(checkboxes.widget), forms.CheckboxSelectMultiple) + self.assertIn(('BELGIUM', 'Chouffe'), checkboxes.choices) + self.assertIn(('FRANCE', 'Antidote'), checkboxes.choices) + self.assertIn(('GERMANY', 'Paulaner'), checkboxes.choices) + @freeze_time('2021-01-01') def test_date_field_with_validation(self): class TestdateField(FormidableForm): diff --git a/demo/tests/test_from_form.py b/demo/tests/test_from_form.py index 2828b1a6..bf3b055c 100644 --- a/demo/tests/test_from_form.py +++ b/demo/tests/test_from_form.py @@ -417,6 +417,7 @@ class MyForm(FormidableForm): initial_count = Formidable.objects.count() form = MyForm.to_formidable(label='form-with-radios') + self.assertEquals(initial_count + 1, Formidable.objects.count()) self.assertTrue(form.pk) self.assertEquals(form.fields.count(), 1) diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index b0c595e9..36dd8c5c 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -107,18 +107,30 @@ def get_widget_class(self): return super(DropdownFieldBuilder, self).get_widget_class() +class RadioFieldBuilder(ChoiceFieldBuilder): + + widget_class = forms.RadioSelect + + +class CheckboxesFieldBuilder(ChoiceFieldBuilder): + + field_class = forms.MultipleChoiceField + widget_class = forms.CheckboxSelectMultiple + + class FormFieldFactory(FF): field_map = { 'text': TextFieldBuilder, 'paragraph': ParagraphFieldBuilder, 'checkbox': CheckboxFieldBuilder, + 'checkboxes': CheckboxesFieldBuilder, 'email': EmailFieldBuilder, 'number': IntegerFieldBuilder, 'file': FileFieldBuilder, 'date': DateFieldBuilder, 'dropdown': DropdownFieldBuilder, - + 'radios': RadioFieldBuilder, } def get_type_id(self, field): From c492043d0bf08e0a44034dba65d4d7d3bacfa8b6 Mon Sep 17 00:00:00 2001 From: Nicolas Dubois Date: Fri, 24 Mar 2017 02:13:33 +0100 Subject: [PATCH 22/27] refs #170, Add help-text field --- demo/tests/test_form_from_schema.py | 22 +++++++++++++++++++ formidable/forms/field_builder_from_schema.py | 12 ++++++++++ 2 files changed, 34 insertions(+) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index 3bf1312b..9ee908a1 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -69,6 +69,28 @@ class TestParagraphField(FormidableForm): self.assertEqual(type(charfield.widget), forms.Textarea) self.assertFalse(charfield.required) + def test_help_text(self): + class TestHelpTextField(FormidableForm): + """ + Test help text + + """ + helptext = fields.HelpTextField(text='My Help Text') + + formidable = TestHelpTextField.to_formidable(label='label') + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() + + self.assertIn('helptext', form.fields) + helptext = form.fields['helptext'] + self.assertEqual(type(helptext), fields.HelpTextField) + self.assertEqual(type(helptext.widget), widgets.HelpTextWidget) + self.assertFalse(helptext.required) + def test_checkbox_field(self): class TestCheckBoxField(FormidableForm): """ Test checkbox """ diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index 36dd8c5c..076ec10e 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from django import forms +from formidable.forms import fields from formidable.forms.field_builder import ( FieldBuilder as FB, FormFieldFactory as FF ) @@ -47,6 +48,16 @@ def get_accesses(self, role): return None +class HelpTextBuilder(FieldBuilder): + + field_class = fields.HelpTextField + + def get_field_kwargs(self): + kwargs = super(HelpTextBuilder, self).get_field_kwargs() + kwargs['text'] = kwargs.pop('help_text') + return kwargs + + class TextFieldBuilder(FieldBuilder): widget_class = forms.TextInput @@ -131,6 +142,7 @@ class FormFieldFactory(FF): 'date': DateFieldBuilder, 'dropdown': DropdownFieldBuilder, 'radios': RadioFieldBuilder, + 'help_text': HelpTextBuilder, } def get_type_id(self, field): From afdac3dcd622667d32094f91f18fe2227774326e Mon Sep 17 00:00:00 2001 From: Nicolas Dubois Date: Fri, 24 Mar 2017 15:07:20 +0100 Subject: [PATCH 23/27] refs #170, Add separator field --- demo/tests/test_form_from_schema.py | 44 +++++++++++++++++++ formidable/forms/field_builder_from_schema.py | 12 +++++ 2 files changed, 56 insertions(+) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index 9ee908a1..3eb07c0d 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -91,6 +91,50 @@ class TestHelpTextField(FormidableForm): self.assertEqual(type(helptext.widget), widgets.HelpTextWidget) self.assertFalse(helptext.required) + def test_separator(self): + class TestSeparatorField(FormidableForm): + """ + Test for separator + + """ + separator = fields.SeparatorField() + + formidable = TestSeparatorField.to_formidable(label='label') + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() + + self.assertIn('separator', form.fields) + separator = form.fields['separator'] + self.assertEqual(type(separator), fields.SeparatorField) + self.assertEqual(type(separator.widget), widgets.SeparatorWidget) + self.assertFalse(separator.required) + + def test_title(self): + class TestTitleField(FormidableForm): + """ + Test for separator + + """ + title = fields.TitleField() + + formidable = TestTitleField.to_formidable(label='label') + schema = ContextFormSerializer(instance=formidable, context={ + 'role': 'jedi' + }).data + + form_class = get_dynamic_form_class_from_schema(schema) + form = form_class() + + self.assertIn('title', form.fields) + title = form.fields['title'] + self.assertEqual(type(title), fields.TitleField) + self.assertEqual(type(title.widget), widgets.TitleWidget) + self.assertFalse(title.required) + def test_checkbox_field(self): class TestCheckBoxField(FormidableForm): """ Test checkbox """ diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index 076ec10e..8e9f70c5 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -58,6 +58,16 @@ def get_field_kwargs(self): return kwargs +class SeparatorBuilder(FieldBuilder): + + field_class = fields.SeparatorField + + +class TitleFielBuilder(FieldBuilder): + + field_class = fields.TitleField + + class TextFieldBuilder(FieldBuilder): widget_class = forms.TextInput @@ -143,6 +153,8 @@ class FormFieldFactory(FF): 'dropdown': DropdownFieldBuilder, 'radios': RadioFieldBuilder, 'help_text': HelpTextBuilder, + 'separator': SeparatorBuilder, + 'title': TitleFielBuilder, } def get_type_id(self, field): From b12f92c3b98fbf57fe3cfd8ca955a5ffb2862183 Mon Sep 17 00:00:00 2001 From: Nicolas Dubois Date: Mon, 27 Mar 2017 10:01:35 +0200 Subject: [PATCH 24/27] refs #170, Use verbose aliases for imports Instead of using acronyms, we use verbose name prefixed with `Base`. --- formidable/forms/field_builder_from_schema.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index 8e9f70c5..65e649ef 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -3,26 +3,28 @@ from formidable.forms import fields from formidable.forms.field_builder import ( - FieldBuilder as FB, FormFieldFactory as FF + FieldBuilder as BaseFieldBuilder, + FormFieldFactory as BaseFormFieldFactory ) from formidable.validators import ( - DateValidatorFactory as DVF, ValidatorFactory as VF + DateValidatorFactory as BaseDateValidatorFactory, + ValidatorFactory as BaseValidatorFactory ) -class ValidatorFactory(VF): +class ValidatorFactory(BaseValidatorFactory): def extract_validation_attribute(self, validation): return validation['type'], validation['message'], validation['value'] -class DateValidatorFactory(DVF): +class DateValidatorFactory(BaseDateValidatorFactory): def extract_validation_attribute(self, validation): return validation['type'], validation['message'], validation['value'] -class FieldBuilder(FB): +class FieldBuilder(BaseFieldBuilder): field_class = forms.CharField widget_class = None @@ -79,22 +81,27 @@ class ParagraphFieldBuilder(FieldBuilder): class CheckboxFieldBuilder(FieldBuilder): + field_class = forms.BooleanField class EmailFieldBuilder(FieldBuilder): + field_class = forms.EmailField class IntegerFieldBuilder(FieldBuilder): + field_class = forms.IntegerField class FileFieldBuilder(FieldBuilder): + field_class = forms.FileField class DateFieldBuilder(FieldBuilder): + field_class = forms.DateField validator_factory_class = DateValidatorFactory @@ -139,7 +146,7 @@ class CheckboxesFieldBuilder(ChoiceFieldBuilder): widget_class = forms.CheckboxSelectMultiple -class FormFieldFactory(FF): +class FormFieldFactory(BaseFormFieldFactory): field_map = { 'text': TextFieldBuilder, From 80be6d4816cb48b803cd6c66cf02f84130c4502d Mon Sep 17 00:00:00 2001 From: Nicolas Dubois Date: Tue, 11 Apr 2017 05:53:19 +0200 Subject: [PATCH 25/27] refs #170, Add button radios field --- formidable/forms/field_builder_from_schema.py | 1 + 1 file changed, 1 insertion(+) diff --git a/formidable/forms/field_builder_from_schema.py b/formidable/forms/field_builder_from_schema.py index 65e649ef..a7750840 100644 --- a/formidable/forms/field_builder_from_schema.py +++ b/formidable/forms/field_builder_from_schema.py @@ -159,6 +159,7 @@ class FormFieldFactory(BaseFormFieldFactory): 'date': DateFieldBuilder, 'dropdown': DropdownFieldBuilder, 'radios': RadioFieldBuilder, + 'radios_buttons': RadioFieldBuilder, 'help_text': HelpTextBuilder, 'separator': SeparatorBuilder, 'title': TitleFielBuilder, From 87d1cac555df58803937cc5888c9644ee2b72fba Mon Sep 17 00:00:00 2001 From: Nicolas Dubois Date: Tue, 11 Apr 2017 05:53:28 +0200 Subject: [PATCH 26/27] refs #170, Add mapping test --- demo/tests/test_form_from_schema.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/demo/tests/test_form_from_schema.py b/demo/tests/test_form_from_schema.py index 3eb07c0d..f6aaf42f 100644 --- a/demo/tests/test_form_from_schema.py +++ b/demo/tests/test_form_from_schema.py @@ -7,7 +7,9 @@ from formidable.forms import ( FormidableForm, fields, get_dynamic_form_class_from_schema ) -from formidable.forms import widgets +from formidable.forms import ( + field_builder, field_builder_from_schema, widgets +) from formidable.forms.validations.presets import ( ConfirmationPresets ) @@ -15,6 +17,7 @@ from formidable.validators import ( GTEValidator, MinLengthValidator, AgeAboveValidator ) + from formidable.serializers.forms import ContextFormSerializer @@ -382,3 +385,13 @@ class Meta: 'email': 'test@test.tld', 'email_confirm': 'test@test.tld' }) self.assertTrue(form.is_valid()) + + def test_mapping(self): + """ + Simple test to make sure that every widget is implemented. + + """ + self.assertEqual( + set(field_builder.FormFieldFactory.field_map), + set(field_builder_from_schema.FormFieldFactory.field_map) + ) From 86fe8a9b5feb98dd9c2d4e782ac241b64deb0a57 Mon Sep 17 00:00:00 2001 From: Nicolas Dubois Date: Tue, 11 Apr 2017 05:58:30 +0200 Subject: [PATCH 27/27] refs #170, Update ChangeLog --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aff3b062..f280ea8b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,8 @@ master (unreleased) * Fixed the swagger doc generation and rendering (#210). * Fix wrong field type for Checkbox (#208). * Don't rely on database ordering in `NestedListSerializer` (#215) +* Provide a tools in order to generate django-form class from json + contextualized definition (#171) Release 0.8.2 (2017-03-28) ==========================