Warning: Can't synchronize with repository "(default)" (Unsupported version control system "svn": No module named svn). Look in the Trac log for more information.

Ticket #2393 (closed defect: fixed)

Opened 7 years ago

Last modified 7 years ago

Forms with nested schemas

Reported by: guest Owned by: Chris Arndt
Priority: normal Milestone: 1.0.x bugfix
Component: TG Widgets Version: 1.1
Severity: normal Keywords: needs review, forms, FormEncode
Cc:

Description

Given a form with schemas (stripped down excerpt below):

class RegistrationForm(ListForm): 
    class RegistrationFields(WidgetsList): 
        class PersonalDetailsFields(WidgetsList): 
            lastname = TextField(label=_(u"Last name")) 
            firstname = TextField(label=_(u"First name")) 
        personal = FieldSet( 
            legend = _(u"Personal information"), 
            fields = PersonalDetailsFields()) 
    class RegistrationSchema(Schema): 
        class PersonalDetailsSchema(Schema): 
            lastname = UnicodeString(strip=True, not_empty=True,  max=20) 
            firstname = UnicodeString(strip=True, not_empty=True,  max=20) 
        personal = PersonalDetailsSchema() 
    fields = RegistrationFields() 
    validator = RegistrationSchema() 

This worked pretty fine with the validate-decorator in TG 1.0.8 using FormEncode 0.7.1. Now with 1.1 (and FormEncode 1.2.2) I always get validate-errors for the schema named PersonalDetailsSchema as there is no such sub-form (the only given sub-form is given by the key 'personal'). Actually the real name would be "personal", like the name of the form above. The @validate-decorator seems to validate the instance of the PersonalDetailsSchema-class as well as the class itself. If I clear out the schema-class by setting PersonalDetailsSchema=None after the instantiation of 'personal' like this:

class RegistrationSchema(Schema): 
    class PersonalDetailsSchema(Schema): 
        lastname = UnicodeString(strip=True, not_empty=True, max=20) 
        firstname = UnicodeString(strip=True, not_empty=True, max=20) 
    personal = PersonalDetailsSchema() 
    PersonalDetailsSchema = None 

then validation works well. I can as well move the RegistrationSchema-class out of the RegistrationForm-class to module level, which also works.

I attached a minimal testcase below.

Discussion:  http://groups.google.com/group/turbogears/browse_thread/thread/79d703a064f7f69f

Attachments

NestedForms-1.0.tar.gz Download (8.7 KB) - added by guest 7 years ago.
Testcase
test_nested_validator.py Download (2.5 KB) - added by Chris Arndt 7 years ago.
FormEncode nested schemas tests
widget-nested-validators.diff Download (6.4 KB) - added by Chris Arndt 7 years ago.

Change History

Changed 7 years ago by guest

Testcase

comment:1 Changed 7 years ago by Chris Arndt

  • Owner set to Chris Arndt
  • Component changed from TurboGears to TG Widgets

The issue is not specific to TG 1.1. TG 1.0 with FormEncode 1.2.1 exhibits the same behaviour. Can you say which FE version breaks your testcase?

comment:2 Changed 7 years ago by guest

It does work with FormEncode 0.7.1. The lowest release that breaks it is 0.9. I guess there is no 0.8 release, right?

Changed 7 years ago by Chris Arndt

FormEncode nested schemas tests

comment:3 follow-up: ↓ 7 Changed 7 years ago by Chris Arndt

  • Status changed from new to assigned

I did some research on this issue. It seems that the way you define your nested validators was never supported by FormEncode directly. If you define a validator class inside of another, the class will be added to the list of nested validators under the class name.

It was only possible in TurboGears to work around this because turbogears.validators.Schema sets the if_key_missing attribute to None, so when FE validates the data, it will just assign a None value to those fields, i.e. you get PersonalDetailsFields=None in the data with your example above.

Why this isn't working anymore in FE > 0.7.1 I don't know. I think you should raise this issue on the FormEncode mailing list.

It seems to me that you were relying on an undocumented behaviour of TurboGears/FormEncode, which is long gone. Since it works if you define the nested validators outside of the parent validator class, I guess you just have to change your form definitions.

Alternatively, you should be able to just name your nested validator classes so that they match the names of the nested forms, e.g.:

class PersonSchema(Schema):
    class namefields(Schema):
        firstname = UnicodeString(...)
        ...
    ...

BUT unfortunately, turbogears.widgets.meta.add_field_to_schema chokes on this, because it uses isinstance to check the nested validators. This should be fixed to also test using issubclass (needs fixes to copy_schema as well).

I attached my test script so you can experiment yourself.

To sum it up:

  • You fix your nested validator definitions.
  • We fix add_field_to_schema.

comment:4 Changed 7 years ago by Chris Arndt

  • Milestone changed from __unclassified__ to 1.0.x bugfix

comment:5 Changed 7 years ago by Chris Arndt

  • Keywords needs review, added

I added a patch, which allows to define nested schemas as shown in my previous comment and use them with nested widget forms.

The file forms.py in your example app should be fixed to look like the following to make it work with this patch:

from turbogears.widgets import *
from turbogears.validators import *


class PersonForm(ListForm):
    class PersonFields(WidgetsList):
        class NameFields(WidgetsList):
            firstname = TextField(label=_(u'Firstname'))
            lastname = TextField(label=_(u'Lastname'))

        namefields = FieldSet(
            legend = _(u"Name"),
            fields = NameFields()
        )

        class ContactFields(WidgetsList):
            email = TextField(label=_(u"Email address"))

        contact = FieldSet(
            legend = _(u"Contact"),
            fields = ContactFields()
        )

    class PersonSchema(Schema):
        class namefields(Schema):
            firstname = UnicodeString(strip=True, not_empty=True, max=20)
            lastname = UnicodeString(strip=True, not_empty=True, max=20)

        class contact(Schema):
            email = Email(not_empty=True)

    fields = PersonFields()
    validator = PersonSchema()

Can you test this with your real application? I'm unsure whether I have considered all the implications of this change.

Changed 7 years ago by Chris Arndt

comment:6 Changed 7 years ago by guest

Thanks, Chris, your patch works fine. Great!

comment:7 in reply to: ↑ 3 Changed 7 years ago by chrisz

Replying to Chris Arndt:

It was only possible in TurboGears to work around this because turbogears.validators.Schema sets the if_key_missing attribute to None, so when FE validates the data, it will just assign a None value to those fields, i.e. you get PersonalDetailsFields=None in the data with your example above.

Why this isn't working anymore in FE > 0.7.1 I don't know. I think you should raise this issue on the FormEncode mailing list.

Actually this is due to the formencode bugfix in changeset 3104 after which is_empty() returns False by default. So it seems it worked only because of a bug in formencode.

The patch looks good to me, but I think we don't need the change in generate_schema(); the change in copy_schema() should be sufficient.

comment:8 Changed 7 years ago by Chris Arndt

I added that as an afterthought. I basically allows you to attach a validator schema class (not an instance) to a form.

comment:9 Changed 7 years ago by Chris Arndt

  • Status changed from assigned to closed
  • Resolution set to fixed

Applied to all 1.x branches in r6887.

Note: See TracTickets for help on using tickets.