Modify or clean form input in zope or plone

132 Views Asked by At

So basically I'd like to try to clean/filter or sanitize an input form similar to other python web frameworks. Specifically I am trying to strip whitespace from the email address. I can do it in javascript but it seems like there should be a way to do it using the zope libraries.

The documentation goes into detail on setting up the constraints but not actually on modifying form values. Of course the issue is the constraint exception gets triggered before I can do anything to the whitespace.

class IEmailAddress(zope.schema.interfaces.ITextLine):
    pass

class InvalidEmailAddress(zope.schema.ValidationError):
    def __init__(self, email):
        super(InvalidEmailAddress, self).__init__(
            "'%s' is not a valid e-mail address." % email)


class EmailAddress(zope.schema.TextLine):

    zope.interface.implements(IEmailAddress)

    def constraint(self, value):
        return '\n' not in value and '\r' not in value

    def _validate(self, value):
        super(EmailAddress, self)._validate(value)
        if not is_email_address(value):
            raise InvalidEmailAddress(value)


class InviteForm(FormProvider):

    dropdown_css_class = "dropdown-spacer-left"

    def fields(self):
        """Build form fields dynamically"""
        fields = Fields(IPerson).select("name", "email")
        person = self.request.principal.person

        field['email'].field.strip() # Does not work

    @action("Invite", primary=True)
    def add_administrator(self, action, data):
        name, email = data["name"], data["email"]

        email = email.strip() # Does not work
3

There are 3 best solutions below

0
On BEST ANSWER

I actually ended up doing it this way because it was more general than the original way I was doing it, which involved setting each field individually instead of a global option.

class StripTextWidget(zope.formlib.textwidgets.TextWidget):
    def _getFormInput(self):
        value = super(StripTextWidget, self)._getFormInput()
        return value.strip()

class EmailAddress(zope.schema.TextLine):

    zope.interface.implements(IEmailAddress)
    custom_widget = StripTextWidget

    def constraint(self, value):
        return '\n' not in value and '\r' not in value

    def _validate(self, value):
        super(EmailAddress, self)._validate(value)
        if not is_email_address(value):
            raise InvalidEmailAddress(value)
0
On

What I ended up doing is the following using the custom widget functionality:

class StripTextWidget(zope.formlib.textwidgets.TextWidget):
    def _getFormInput(self):
        value = super(StripTextWidget, self)._getFormInput()
        return value.strip()

...
def fields(self):
   """Build form fields dynamically"""
   fields = Fields(IPerson).select("name", "email")
   fields["email"].custom_widget = StripTextWidget

This way is also described in the book Web Component Development With Zope 3 on page 130

1
On

There are a few different possible approaches, depending on where you want to put the abstraction layer.

If you wanted to put it relatively close to the data model, you could define a set method on your custom field, which would look like this:

    def set(self, obj, value):
        if value is not None:
            value = value.strip()
        super().set(obj, value)

That would mean anything that set the field via the schema (perhaps some kind of API?) would strip whitespace. This approach is usually used for type conversions, but it doesn't seem too unreasonable to use it for this kind of simple normalization as well.

If you wanted the whitespace-stripping to be a property of your form views, but still allow for a reasonable amount of code reuse, then you could use a custom widget as you suggest in your own answer.

Finally, if you just wanted to have some custom behaviour specific to a particular form, then you can always just have the form process the data manually between reading it from widgets and applying it to the underlying models, perhaps using an action validator, or even something more explicit if you're using zope.formlib's Edit Forms approach in which you call form.getWidgetsData and form.applyChanges directly.