From cc932a30b9fe983073d68db32948cbf2fc6bc32f Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Wed, 25 Sep 2019 14:42:12 +0700 Subject: [PATCH 01/21] Put in tests to show recaptcha validation prevents submissions --- CHANGES.rst | 2 + buildout.cfg | 2 +- src/collective/easyform/tests/base.py | 4 ++ .../easyform/tests/fixtures/recaptcha.xml | 9 ++++ .../easyform/tests/testValidators.py | 53 +++++++++++++++++++ 5 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/collective/easyform/tests/fixtures/recaptcha.xml diff --git a/CHANGES.rst b/CHANGES.rst index b343e68a..2fe38b18 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -26,6 +26,8 @@ issues if those validators, or default values, were misconfigured in the first p - Add collective.z3cform.norobots integration #145 [1letter/gomez] +- Put in tests to show recaptcha validation prevents submissions + [djay] 2.1.0 (2019-04-25) ------------------ diff --git a/buildout.cfg b/buildout.cfg index 8039ab69..73fcd037 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -4,7 +4,7 @@ extends = tests-5.2.x.cfg parts += releaser i18ndude - omelette +# omelette [instance] eggs += diff --git a/src/collective/easyform/tests/base.py b/src/collective/easyform/tests/base.py index c2e5c6ae..ecc55ba1 100644 --- a/src/collective/easyform/tests/base.py +++ b/src/collective/easyform/tests/base.py @@ -58,6 +58,10 @@ def setUpZope(self, app, configurationContext): def setUpPloneSite(self, portal): # Install the collective.easyform product self.applyProfile(portal, "collective.easyform:default") + try: + self.applyProfile(portal, "plone.formwidget.recaptcha:default") + except KeyError: + pass setRoles(portal, TEST_USER_ID, ["Manager"]) portal.manage_changeProperties(email_from_address="mdummy@address.com") portal.MailHost = mailhost = MailHostMock() diff --git a/src/collective/easyform/tests/fixtures/recaptcha.xml b/src/collective/easyform/tests/fixtures/recaptcha.xml new file mode 100644 index 00000000..b9bbc724 --- /dev/null +++ b/src/collective/easyform/tests/fixtures/recaptcha.xml @@ -0,0 +1,9 @@ + + + + + False + Verification + + + diff --git a/src/collective/easyform/tests/testValidators.py b/src/collective/easyform/tests/testValidators.py index dbda6162..43ca6003 100644 --- a/src/collective/easyform/tests/testValidators.py +++ b/src/collective/easyform/tests/testValidators.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +from plone.formwidget.recaptcha.interfaces import IReCaptchaSettings +from plone.registry.interfaces import IRegistry + from collective.easyform import validators from collective.easyform.api import get_schema from collective.easyform.api import set_fields @@ -337,3 +340,53 @@ def test_allowed_type_no_ext(self): DummyFile(filename="foo"), allowed_types=("txt",) ) self.assertEqual(translate(validation), u'File type "" is not allowed!') + + +class TestSingleRecaptchaValidator(base.EasyFormTestCase): + + """ Can't test captcha passes but we can test it fails + """ + schema_fixture = "recaptcha.xml" + + def afterSetUp(self): + self.folder.invokeFactory("EasyForm", "ff1") + self.ff1 = getattr(self.folder, "ff1") + self.ff1.CSRFProtection = False # no csrf protection + self.ff1.showAll = True + field_template = api.content.create( + self.layer["portal"], "File", id="easyform_default_fields.xml" + ) + with open(join(dirname(__file__), "fixtures", self.schema_fixture)) as f: + filecontent = NamedFile(f.read(), contentType="application/xml") + field_template.file = filecontent + classImplements(BaseRequest, IFormLayer) + validators.update_validators() + + # Put some dummy values for recaptcha + registry = getUtility(IRegistry) + proxy = registry.forInterface(IReCaptchaSettings) + proxy.public_key = u"foo" + proxy.private_key = u"bar" + + + def LoadRequestForm(self, **kwargs): + request = self.layer["request"] + request.form.clear() + prefix = "form.widgets." + for key in kwargs.keys(): + request.form[prefix + key] = kwargs[key] + return request + + def test_no_answer(self): + data = {"verification": ""} + request = self.LoadRequestForm(**data) + request.method = "POST" + form = EasyFormForm(self.ff1, request)() + self.assertIn('The code you entered was wrong, please enter the new one.', form) + + def test_wrong(self): + data = {"verification": "123"} + request = self.LoadRequestForm(**data) + request.method = "POST" + form = EasyFormForm(self.ff1, request)() + self.assertIn('The code you entered was wrong, please enter the new one.', form) From 36951452477df6d6ea5b283bba99d06a216baf0c Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Wed, 25 Sep 2019 17:29:48 +0700 Subject: [PATCH 02/21] add robot tests to coverage --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1d1aea37..1edbaac0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,7 +54,7 @@ script: # Run code-analysis, except on Python 3.6, which mysteriously fails to find zc.buildout. - python --version 2> /dev/stdout | grep 3.6 || bin/code-analysis - bin/test --all $TEST_OPTIONS - - bin/createcoverage + - bin/createcoverage -t '--all $TEST_OPTIONS' after_success: - pip install -q coveralls - coveralls From c0e17c8b7d3df6bd9f7b95b836d676e4d296169e Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Wed, 25 Sep 2019 17:33:00 +0700 Subject: [PATCH 03/21] fix flake8 --- src/collective/easyform/tests/testValidators.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/collective/easyform/tests/testValidators.py b/src/collective/easyform/tests/testValidators.py index 43ca6003..6ef04c4a 100644 --- a/src/collective/easyform/tests/testValidators.py +++ b/src/collective/easyform/tests/testValidators.py @@ -368,7 +368,6 @@ def afterSetUp(self): proxy.public_key = u"foo" proxy.private_key = u"bar" - def LoadRequestForm(self, **kwargs): request = self.layer["request"] request.form.clear() From d30dfdfb78544717c69c806e2420176dfe0754a0 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Wed, 25 Sep 2019 21:12:13 +0700 Subject: [PATCH 04/21] try improve caching --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1edbaac0..dfdc7e9a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,7 @@ cache: - eggs - downloads - buildout-cache + - $HOME/buildout-cache before_install: # install chrome webdriver - mkdir webdriver; From e048b59185b6855f006682bee877294031bd2b01 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 26 Sep 2019 10:55:38 +0700 Subject: [PATCH 05/21] show failing test recaptcha in a fieldset --- .../easyform/tests/fixtures/fieldset_recaptcha.xml | 11 +++++++++++ src/collective/easyform/tests/testValidators.py | 8 ++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/collective/easyform/tests/fixtures/fieldset_recaptcha.xml diff --git a/src/collective/easyform/tests/fixtures/fieldset_recaptcha.xml b/src/collective/easyform/tests/fixtures/fieldset_recaptcha.xml new file mode 100644 index 00000000..7d61481f --- /dev/null +++ b/src/collective/easyform/tests/fixtures/fieldset_recaptcha.xml @@ -0,0 +1,11 @@ + + +
+ + + False + Verification + +
+
+
diff --git a/src/collective/easyform/tests/testValidators.py b/src/collective/easyform/tests/testValidators.py index 6ef04c4a..46ffc586 100644 --- a/src/collective/easyform/tests/testValidators.py +++ b/src/collective/easyform/tests/testValidators.py @@ -382,6 +382,7 @@ def test_no_answer(self): request.method = "POST" form = EasyFormForm(self.ff1, request)() self.assertIn('The code you entered was wrong, please enter the new one.', form) + self.assertNotIn('Thanks for your input.', form) def test_wrong(self): data = {"verification": "123"} @@ -389,3 +390,10 @@ def test_wrong(self): request.method = "POST" form = EasyFormForm(self.ff1, request)() self.assertIn('The code you entered was wrong, please enter the new one.', form) + self.assertNotIn('Thanks for your input.', form) + +class TestFieldsetRecaptchaValidator(TestSingleRecaptchaValidator): + + """ make sure it works inside a fieldset too + """ + schema_fixture = "fieldset_recaptcha.xml" From 0f185321b7df272324604d76927b13d92636f8e2 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 26 Sep 2019 10:55:38 +0700 Subject: [PATCH 06/21] show failing test recaptcha in a fieldset --- .../easyform/tests/fixtures/fieldset_recaptcha.xml | 11 +++++++++++ src/collective/easyform/tests/testValidators.py | 8 ++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/collective/easyform/tests/fixtures/fieldset_recaptcha.xml diff --git a/src/collective/easyform/tests/fixtures/fieldset_recaptcha.xml b/src/collective/easyform/tests/fixtures/fieldset_recaptcha.xml new file mode 100644 index 00000000..7d61481f --- /dev/null +++ b/src/collective/easyform/tests/fixtures/fieldset_recaptcha.xml @@ -0,0 +1,11 @@ + + +
+ + + False + Verification + +
+
+
diff --git a/src/collective/easyform/tests/testValidators.py b/src/collective/easyform/tests/testValidators.py index 6ef04c4a..46ffc586 100644 --- a/src/collective/easyform/tests/testValidators.py +++ b/src/collective/easyform/tests/testValidators.py @@ -382,6 +382,7 @@ def test_no_answer(self): request.method = "POST" form = EasyFormForm(self.ff1, request)() self.assertIn('The code you entered was wrong, please enter the new one.', form) + self.assertNotIn('Thanks for your input.', form) def test_wrong(self): data = {"verification": "123"} @@ -389,3 +390,10 @@ def test_wrong(self): request.method = "POST" form = EasyFormForm(self.ff1, request)() self.assertIn('The code you entered was wrong, please enter the new one.', form) + self.assertNotIn('Thanks for your input.', form) + +class TestFieldsetRecaptchaValidator(TestSingleRecaptchaValidator): + + """ make sure it works inside a fieldset too + """ + schema_fixture = "fieldset_recaptcha.xml" From 63888c666154ac7b3522c9af5c980175d2117cd1 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 26 Sep 2019 12:20:53 +0700 Subject: [PATCH 07/21] fix fieldsets with recaptcha usecase --- src/collective/easyform/fields.zcml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/collective/easyform/fields.zcml b/src/collective/easyform/fields.zcml index 942d5d28..eba50d57 100644 --- a/src/collective/easyform/fields.zcml +++ b/src/collective/easyform/fields.zcml @@ -51,5 +51,15 @@ provides="z3c.form.interfaces.IValidator" factory="plone.formwidget.recaptcha.ReCaptchaValidator" /> + + From 3727b32811285dded436eada1760b111f06aa5c2 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 26 Sep 2019 14:24:33 +0700 Subject: [PATCH 08/21] flake 8 --- src/collective/easyform/tests/testValidators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/collective/easyform/tests/testValidators.py b/src/collective/easyform/tests/testValidators.py index 46ffc586..f3233d54 100644 --- a/src/collective/easyform/tests/testValidators.py +++ b/src/collective/easyform/tests/testValidators.py @@ -393,7 +393,7 @@ def test_wrong(self): self.assertNotIn('Thanks for your input.', form) class TestFieldsetRecaptchaValidator(TestSingleRecaptchaValidator): - """ make sure it works inside a fieldset too """ - schema_fixture = "fieldset_recaptcha.xml" + + schema_fixture = "fieldset_recaptcha.xml" \ No newline at end of file From 818c43d8b5f232c79f95f0c1d57e61c315988928 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 26 Sep 2019 15:46:05 +0700 Subject: [PATCH 09/21] more detailed comment --- src/collective/easyform/fields.zcml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/collective/easyform/fields.zcml b/src/collective/easyform/fields.zcml index eba50d57..4148d3f6 100644 --- a/src/collective/easyform/fields.zcml +++ b/src/collective/easyform/fields.zcml @@ -51,7 +51,8 @@ provides="z3c.form.interfaces.IValidator" factory="plone.formwidget.recaptcha.ReCaptchaValidator" /> - + Date: Thu, 26 Sep 2019 21:34:18 +0700 Subject: [PATCH 11/21] simply test --- .../easyform/tests/testValidators.py | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/collective/easyform/tests/testValidators.py b/src/collective/easyform/tests/testValidators.py index 64bdb4d7..5fad7faa 100644 --- a/src/collective/easyform/tests/testValidators.py +++ b/src/collective/easyform/tests/testValidators.py @@ -104,7 +104,8 @@ def test_talvalidator2(self): self.assertEqual(len(errors), 1) -class TestSingleFieldValidator(base.EasyFormTestCase): + +class LoadFixtureBase(base.EasyFormTestCase): """ test validator in form outside of fieldset @@ -135,6 +136,16 @@ def LoadRequestForm(self, **kwargs): request.form[prefix + key] = kwargs[key] return request + +class TestSingleFieldValidator(LoadFixtureBase): + + """ test validator in form outside of fieldset + + The test methods are reused in TestFieldsetValidator. + They use the same field, except that one has it in a fieldset. + """ + schema_fixture = "single_field.xml" + def test_get_default(self): # With a GET, we should see the default value in the form. request = self.LoadRequestForm() @@ -342,25 +353,14 @@ def test_allowed_type_no_ext(self): self.assertEqual(translate(validation), u'File type "" is not allowed!') -class TestSingleRecaptchaValidator(base.EasyFormTestCase): +class TestSingleRecaptchaValidator(LoadFixtureBase): """ Can't test captcha passes but we can test it fails """ schema_fixture = "recaptcha.xml" def afterSetUp(self): - self.folder.invokeFactory("EasyForm", "ff1") - self.ff1 = getattr(self.folder, "ff1") - self.ff1.CSRFProtection = False # no csrf protection - self.ff1.showAll = True - field_template = api.content.create( - self.layer["portal"], "File", id="easyform_default_fields.xml" - ) - with open(join(dirname(__file__), "fixtures", self.schema_fixture)) as f: - filecontent = NamedFile(f.read(), contentType="application/xml") - field_template.file = filecontent - classImplements(BaseRequest, IFormLayer) - validators.update_validators() + super(TestSingleRecaptchaValidator, self).afterSetUp() # Put some dummy values for recaptcha registry = getUtility(IRegistry) @@ -368,13 +368,6 @@ def afterSetUp(self): proxy.public_key = u"foo" proxy.private_key = u"bar" - def LoadRequestForm(self, **kwargs): - request = self.layer["request"] - request.form.clear() - prefix = "form.widgets." - for key in kwargs.keys(): - request.form[prefix + key] = kwargs[key] - return request def test_no_answer(self): data = {"verification": ""} From f6cf94219abc70c5b710ce7bba23bbfe45b273e4 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 26 Sep 2019 21:37:04 +0700 Subject: [PATCH 12/21] more generic solution so IValidator adapters still work on fields --- src/collective/easyform/fields.py | 36 ++++++++++++++++++++++++----- src/collective/easyform/fields.zcml | 11 --------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/collective/easyform/fields.py b/src/collective/easyform/fields.py index 71f6063e..b686f970 100644 --- a/src/collective/easyform/fields.py +++ b/src/collective/easyform/fields.py @@ -12,12 +12,12 @@ from plone.schemaeditor.fields import FieldFactory from plone.supermodel.exportimport import BaseHandler from z3c.form import validator as z3c_validator -from z3c.form.interfaces import IGroup +from z3c.form.interfaces import IGroup, IForm from z3c.form.interfaces import IValidator from z3c.form.interfaces import IValue -from zope.component import adapter +from zope.component import adapter, queryMultiAdapter from zope.component import queryUtility -from zope.interface import implementer +from zope.interface import implementer, providedBy from zope.interface import Interface from zope.interface import Invalid from zope.schema import Field @@ -26,15 +26,39 @@ from zope.schema.interfaces import IField +@implementer(IForm) +class GenericFormWrapper(object): + def __init__(self, view): + self.__view__ = view + def __getattr__(self, item): + return getattr(self.__view__, item) + + + @implementer(IValidator) @adapter(IEasyForm, Interface, IEasyFormForm, IField, Interface) -class FieldExtenderValidator(z3c_validator.SimpleFieldValidator): - +class FieldExtenderValidator(object): """ z3c.form validator class for easyform fields in the default fieldset""" + def __init__(self, context, request, view, field, widget): + self.context = context + self.request = request + self.view = view + self.field = field + self.widget = widget + + def validate(self, value): """ Validate field by TValidator """ - super(FieldExtenderValidator, self).validate(value) + + # First see if there is a another validator adapter for the field which isn't us + view = GenericFormWrapper(self.view) + assert providedBy(view)(IForm) + assert not providedBy(view)(IEasyFormForm) + validator = queryMultiAdapter((self.context, self.request, view, self.field, self.widget), IValidator) + if validator is not None: + validator.validate(value) + efield = IFieldExtender(self.field) validators = getattr(efield, "validators", []) if validators: diff --git a/src/collective/easyform/fields.zcml b/src/collective/easyform/fields.zcml index 4148d3f6..49c7ba5a 100644 --- a/src/collective/easyform/fields.zcml +++ b/src/collective/easyform/fields.zcml @@ -45,18 +45,7 @@ - - Date: Thu, 26 Sep 2019 23:25:52 +0700 Subject: [PATCH 13/21] flake8 --- src/collective/easyform/fields.py | 4 +--- src/collective/easyform/tests/testValidators.py | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/collective/easyform/fields.py b/src/collective/easyform/fields.py index b686f970..8ea5e3f0 100644 --- a/src/collective/easyform/fields.py +++ b/src/collective/easyform/fields.py @@ -11,7 +11,6 @@ from collective.easyform.validators import IFieldValidator from plone.schemaeditor.fields import FieldFactory from plone.supermodel.exportimport import BaseHandler -from z3c.form import validator as z3c_validator from z3c.form.interfaces import IGroup, IForm from z3c.form.interfaces import IValidator from z3c.form.interfaces import IValue @@ -30,11 +29,11 @@ class GenericFormWrapper(object): def __init__(self, view): self.__view__ = view + def __getattr__(self, item): return getattr(self.__view__, item) - @implementer(IValidator) @adapter(IEasyForm, Interface, IEasyFormForm, IField, Interface) class FieldExtenderValidator(object): @@ -47,7 +46,6 @@ def __init__(self, context, request, view, field, widget): self.field = field self.widget = widget - def validate(self, value): """ Validate field by TValidator """ diff --git a/src/collective/easyform/tests/testValidators.py b/src/collective/easyform/tests/testValidators.py index 5fad7faa..80fd8810 100644 --- a/src/collective/easyform/tests/testValidators.py +++ b/src/collective/easyform/tests/testValidators.py @@ -183,7 +183,6 @@ class TestFieldsetValidator(TestSingleFieldValidator): class TestCustomValidators(base.EasyFormTestCase): - """ test our validators """ def ttest_inExNumericRange(self): @@ -368,7 +367,6 @@ def afterSetUp(self): proxy.public_key = u"foo" proxy.private_key = u"bar" - def test_no_answer(self): data = {"verification": ""} request = self.LoadRequestForm(**data) From 0be8878c9451768f7a2a5070f7cf990a4a3cc2fb Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Thu, 26 Sep 2019 23:44:16 +0700 Subject: [PATCH 14/21] flake8 --- src/collective/easyform/tests/testValidators.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/collective/easyform/tests/testValidators.py b/src/collective/easyform/tests/testValidators.py index 80fd8810..44208ccc 100644 --- a/src/collective/easyform/tests/testValidators.py +++ b/src/collective/easyform/tests/testValidators.py @@ -104,7 +104,6 @@ def test_talvalidator2(self): self.assertEqual(len(errors), 1) - class LoadFixtureBase(base.EasyFormTestCase): """ test validator in form outside of fieldset From c5cd829529925b9c1b4d236be23f3994bc1bf17e Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 30 Sep 2019 23:27:54 +0700 Subject: [PATCH 15/21] put in comments and make clearer --- src/collective/easyform/fields.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/collective/easyform/fields.py b/src/collective/easyform/fields.py index 826d01fd..617c5c3b 100644 --- a/src/collective/easyform/fields.py +++ b/src/collective/easyform/fields.py @@ -16,7 +16,7 @@ from z3c.form.interfaces import IValue from zope.component import adapter, queryMultiAdapter from zope.component import queryUtility -from zope.interface import implementer, providedBy +from zope.interface import implementer from zope.interface import Interface from zope.interface import Invalid from zope.schema import Field @@ -24,14 +24,18 @@ from zope.schema._bootstrapinterfaces import IFromUnicode from zope.schema.interfaces import IField +def LessSpecificInterfaceWrapper(view, interface): + """ rewrap an adapter so it its class implements a different interface """ -@implementer(IForm) -class GenericFormWrapper(object): - def __init__(self, view): - self.__view__ = view + @implementer(interface) + class Wrapper(object): + def __init__(self, view): + self.__view__ = view - def __getattr__(self, item): - return getattr(self.__view__, item) + def __getattr__(self, item): + return getattr(self.__view__, item) + + return Wrapper(view) @implementer(IValidator) @@ -48,10 +52,12 @@ def __init__(self, context, request, view, field, widget): def validate(self, value): """ Validate field by TValidator """ - # First see if there is a another validator adapter for the field which isn't us - view = GenericFormWrapper(self.view) - assert providedBy(view)(IForm) - assert not providedBy(view)(IEasyFormForm) + view = LessSpecificInterfaceWrapper(self.view, IForm) + # view now doesn't implement IEasyFormForm so we can call another less specific validation adapter + # that might exist for this field. The above line prevents a loop. + # By default this will call SimpleFieldValidator.validator but allows for special fields + # custom validation to also be called + validator = queryMultiAdapter((self.context, self.request, view, self.field, self.widget), IValidator) if validator is not None: validator.validate(value) @@ -79,7 +85,6 @@ def validate(self, value): @implementer(IValidator) @adapter(IEasyForm, Interface, IGroup, IField, Interface) class GroupFieldExtenderValidator(FieldExtenderValidator): - """ z3c.form validator class for easyform fields in fieldset groups """ pass From 9e1bb0ed276c8fc99b42f3f3c4454941bd754833 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 30 Sep 2019 23:50:55 +0700 Subject: [PATCH 16/21] add in less specific adapter call for defaults also to pick up any fields with default adapters. --- src/collective/easyform/fields.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/collective/easyform/fields.py b/src/collective/easyform/fields.py index 617c5c3b..fe1b7d9c 100644 --- a/src/collective/easyform/fields.py +++ b/src/collective/easyform/fields.py @@ -108,7 +108,17 @@ def get(self): fdefault = self.field.default efield = IFieldExtender(self.field) TDefault = getattr(efield, "TDefault", None) - return get_expression(self.context, TDefault) if TDefault else fdefault + if TDefault: + return get_expression(self.context, TDefault) + + # see if there is another default adapter for this field instead + view = LessSpecificInterfaceWrapper(self.view, IForm) + adapter = queryMultiAdapter((self.context, self.request, view, self.field, self.widget), IValue, name='default') + if adapter is not None: + return adapter.get() + else: + # TODO: this should have already been done by z3c.form.widget.update() so shouldn't be needed + return self.field.default @implementer(IValue) From 53a98749f7454e5bfde8e33e5bb1d49fee1cdd08 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Mon, 30 Sep 2019 23:53:22 +0700 Subject: [PATCH 17/21] flake8 --- src/collective/easyform/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/collective/easyform/fields.py b/src/collective/easyform/fields.py index fe1b7d9c..cc527281 100644 --- a/src/collective/easyform/fields.py +++ b/src/collective/easyform/fields.py @@ -24,6 +24,7 @@ from zope.schema._bootstrapinterfaces import IFromUnicode from zope.schema.interfaces import IField + def LessSpecificInterfaceWrapper(view, interface): """ rewrap an adapter so it its class implements a different interface """ @@ -105,7 +106,6 @@ def __init__(self, context, request, view, field, widget): def get(self): """ get default value of field from TDefault """ - fdefault = self.field.default efield = IFieldExtender(self.field) TDefault = getattr(efield, "TDefault", None) if TDefault: From fe0c69aa639c243b5d6bae37e3dfe526ba5a36c0 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Tue, 1 Oct 2019 00:12:57 +0700 Subject: [PATCH 18/21] show test results quicker --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dfdc7e9a..b8d16adb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,8 +55,8 @@ script: # Run code-analysis, except on Python 3.6, which mysteriously fails to find zc.buildout. - python --version 2> /dev/stdout | grep 3.6 || bin/code-analysis - bin/test --all $TEST_OPTIONS - - bin/createcoverage -t '--all $TEST_OPTIONS' after_success: + - bin/createcoverage -t '--all $TEST_OPTIONS' - pip install -q coveralls - coveralls notifications: From 8b1772a348da2017014322432c89ee05126eb650 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Tue, 1 Oct 2019 09:56:16 +0700 Subject: [PATCH 19/21] try to make the code clearer --- src/collective/easyform/fields.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/collective/easyform/fields.py b/src/collective/easyform/fields.py index cc527281..77131e85 100644 --- a/src/collective/easyform/fields.py +++ b/src/collective/easyform/fields.py @@ -25,10 +25,14 @@ from zope.schema.interfaces import IField -def LessSpecificInterfaceWrapper(view, interface): - """ rewrap an adapter so it its class implements a different interface """ - - @implementer(interface) +def nextAdapter(objects, provides, name=u''): + """ find the next most specific adapter """ + + # HACK: We are hard coding adjusting view object class to provide IForm rather than IEasyFormForm or IGroup to make + # one of the objects less specific. This allows us to find anotehr adapter other than our one. This allows us to + # find any custom adapters for any fields that we have overridden + # TODO: It would be better to to find an more automatic way to find the next most specific adapter + @implementer(IForm) class Wrapper(object): def __init__(self, view): self.__view__ = view @@ -36,7 +40,11 @@ def __init__(self, view): def __getattr__(self, item): return getattr(self.__view__, item) - return Wrapper(view) + context, request, view, field, widget = objects + view = Wrapper(view) # Make one class less specific + objects = (context, request, view, field, widget) + + return queryMultiAdapter(objects, provides, name=name) @implementer(IValidator) @@ -53,13 +61,9 @@ def __init__(self, context, request, view, field, widget): def validate(self, value): """ Validate field by TValidator """ - view = LessSpecificInterfaceWrapper(self.view, IForm) - # view now doesn't implement IEasyFormForm so we can call another less specific validation adapter - # that might exist for this field. The above line prevents a loop. - # By default this will call SimpleFieldValidator.validator but allows for special fields - # custom validation to also be called - - validator = queryMultiAdapter((self.context, self.request, view, self.field, self.widget), IValidator) + # By default this will call SimpleFieldValidator.validator but allows for a fields + # custom validation adaptor to also be called such as recaptcha + validator = nextAdapter((self.context, self.request, self.view, self.field, self.widget), IValidator) if validator is not None: validator.validate(value) @@ -112,8 +116,7 @@ def get(self): return get_expression(self.context, TDefault) # see if there is another default adapter for this field instead - view = LessSpecificInterfaceWrapper(self.view, IForm) - adapter = queryMultiAdapter((self.context, self.request, view, self.field, self.widget), IValue, name='default') + adapter = nextAdapter((self.context, self.request, self.view, self.field, self.widget), IValue, name='default') if adapter is not None: return adapter.get() else: From 749eb064356ef8c80551ca58dd286e6343987b09 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Tue, 1 Oct 2019 18:40:24 +0700 Subject: [PATCH 20/21] more reusable way of getting the superAdapter --- src/collective/easyform/fields.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/collective/easyform/fields.py b/src/collective/easyform/fields.py index 77131e85..31b23384 100644 --- a/src/collective/easyform/fields.py +++ b/src/collective/easyform/fields.py @@ -16,7 +16,7 @@ from z3c.form.interfaces import IValue from zope.component import adapter, queryMultiAdapter from zope.component import queryUtility -from zope.interface import implementer +from zope.interface import implementer, classProvides, providedBy from zope.interface import Interface from zope.interface import Invalid from zope.schema import Field @@ -25,14 +25,19 @@ from zope.schema.interfaces import IField -def nextAdapter(objects, provides, name=u''): +def superAdapter(adapter, objects, name=u''): """ find the next most specific adapter """ # HACK: We are hard coding adjusting view object class to provide IForm rather than IEasyFormForm or IGroup to make # one of the objects less specific. This allows us to find anotehr adapter other than our one. This allows us to # find any custom adapters for any fields that we have overridden - # TODO: It would be better to to find an more automatic way to find the next most specific adapter - @implementer(IForm) + context, request, view, field, widget = objects + # TODO: it would be better if we didn't have hard code the view_interface + _, _, view_interface, _, _ = adapter.__class__.__component_adapts__ + interfaces = list(providedBy(view).interfaces()) + super_inferface = interfaces[interfaces.index(view_interface)+1] + + @implementer(super_inferface) class Wrapper(object): def __init__(self, view): self.__view__ = view @@ -40,9 +45,9 @@ def __init__(self, view): def __getattr__(self, item): return getattr(self.__view__, item) - context, request, view, field, widget = objects view = Wrapper(view) # Make one class less specific objects = (context, request, view, field, widget) + provides = providedBy(adapter).declared[0] return queryMultiAdapter(objects, provides, name=name) @@ -63,7 +68,7 @@ def validate(self, value): """ Validate field by TValidator """ # By default this will call SimpleFieldValidator.validator but allows for a fields # custom validation adaptor to also be called such as recaptcha - validator = nextAdapter((self.context, self.request, self.view, self.field, self.widget), IValidator) + validator = superAdapter(self, (self.context, self.request, self.view, self.field, self.widget)) if validator is not None: validator.validate(value) @@ -116,7 +121,7 @@ def get(self): return get_expression(self.context, TDefault) # see if there is another default adapter for this field instead - adapter = nextAdapter((self.context, self.request, self.view, self.field, self.widget), IValue, name='default') + adapter = superAdapter(self, (self.context, self.request, self.view, self.field, self.widget), name='default') if adapter is not None: return adapter.get() else: From d0efb972faa44b1fcf3a714aba02e71080cf5068 Mon Sep 17 00:00:00 2001 From: Dylan Jay Date: Tue, 1 Oct 2019 22:46:13 +0700 Subject: [PATCH 21/21] make more generic --- src/collective/easyform/fields.py | 57 ++++++++++++++++++------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/collective/easyform/fields.py b/src/collective/easyform/fields.py index 31b23384..947a0e6e 100644 --- a/src/collective/easyform/fields.py +++ b/src/collective/easyform/fields.py @@ -11,12 +11,12 @@ from collective.easyform.validators import IFieldValidator from plone.schemaeditor.fields import FieldFactory from plone.supermodel.exportimport import BaseHandler -from z3c.form.interfaces import IGroup, IForm +from z3c.form.interfaces import IGroup from z3c.form.interfaces import IValidator from z3c.form.interfaces import IValue from zope.component import adapter, queryMultiAdapter from zope.component import queryUtility -from zope.interface import implementer, classProvides, providedBy +from zope.interface import implementer, providedBy from zope.interface import Interface from zope.interface import Invalid from zope.schema import Field @@ -25,31 +25,40 @@ from zope.schema.interfaces import IField -def superAdapter(adapter, objects, name=u''): +def superAdapter(specific_interface, adapter, objects, name=u''): """ find the next most specific adapter """ - # HACK: We are hard coding adjusting view object class to provide IForm rather than IEasyFormForm or IGroup to make + # We are adjusting view object class to provide IForm rather than IEasyFormForm or IGroup to make # one of the objects less specific. This allows us to find anotehr adapter other than our one. This allows us to # find any custom adapters for any fields that we have overridden - context, request, view, field, widget = objects - # TODO: it would be better if we didn't have hard code the view_interface - _, _, view_interface, _, _ = adapter.__class__.__component_adapts__ - interfaces = list(providedBy(view).interfaces()) - super_inferface = interfaces[interfaces.index(view_interface)+1] - - @implementer(super_inferface) - class Wrapper(object): - def __init__(self, view): - self.__view__ = view - - def __getattr__(self, item): - return getattr(self.__view__, item) - - view = Wrapper(view) # Make one class less specific - objects = (context, request, view, field, widget) + new_obj = [] + found = False + for obj in objects: + interfaces = list(providedBy(obj).interfaces()) + try: + index = interfaces.index(specific_interface) + found = True + except ValueError: + pass + else: + super_inferface = interfaces[index + 1] + + @implementer(super_inferface) + class Wrapper(object): + def __init__(self, view): + self.__view__ = view + + def __getattr__(self, item): + return getattr(self.__view__, item) + + obj = Wrapper(obj) # Make one class less specific + new_obj.append(obj) + if not found: + return None + provides = providedBy(adapter).declared[0] - return queryMultiAdapter(objects, provides, name=name) + return queryMultiAdapter(new_obj, provides, name=name) @implementer(IValidator) @@ -68,7 +77,8 @@ def validate(self, value): """ Validate field by TValidator """ # By default this will call SimpleFieldValidator.validator but allows for a fields # custom validation adaptor to also be called such as recaptcha - validator = superAdapter(self, (self.context, self.request, self.view, self.field, self.widget)) + _, _, view_interface, _, _ = self.__class__.__component_adapts__ + validator = superAdapter(view_interface, self, (self.context, self.request, self.view, self.field, self.widget)) if validator is not None: validator.validate(value) @@ -121,7 +131,8 @@ def get(self): return get_expression(self.context, TDefault) # see if there is another default adapter for this field instead - adapter = superAdapter(self, (self.context, self.request, self.view, self.field, self.widget), name='default') + _, _, view_interface, _, _ = self.__class__.__component_adapts__ + adapter = superAdapter(view_interface, self, (self.context, self.request, self.view, self.field, self.widget), name='default') if adapter is not None: return adapter.get() else: