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 #326 (closed enhancement: fixed)

Opened 11 years ago

Last modified 10 years ago

Containers/Compound Widget

Reported by: KarlGuertin Owned by: Karl Guertin <grayrest@…>
Priority: normal Milestone: 0.9a1
Component: TG Widgets Version:
Severity: normal Keywords:


The main reason I had to implement declarative style forms (#114) was to make compound widgets straightforward to declare.

The purpose of compound widgets is to enable SQLObject joins to be shown on the same page. If, for example we have the following SQLObject class setup (taken from the SQLObject documentation:

class Person(SQLObject):
    firstName = StringCol()
    middleInitial = StringCol(length=1, default=None)
    lastName = StringCol()
    addresses = MultipleJoin('Address')

class Address(SQLObject):
    street = StringCol()
    city = StringCol()
    state = StringCol(length=2)
    zip = StringCol(length=9)
    person = ForeignKey('Person')

And we want to display the person on a single page including all his addresses. This is not doable under the current widgets system (or if it is I missed it). It is possible, however, if you have containers:

class AddressForm(W.ListContainer):
    street = TextField(labeltext="Street Address")
    city = TextField()
    state = TextField(validator=V.StateProvince())
    zip = StringCol(validator=V.PostalCode())

class PersonForm(W.ListForm):
    firstName = TextField(labeltext="First Name")
    middleInitial = TextField(labeltext="Middle Initial")
    lastName = TextField(labeltext="Last Name")
    addresses = AddressForm()

And and the form will come out with all addresses (this example uses the syntax for the preview patch attached and depends on the ListForm? path that's currently pending). Presumably the same thing could be accomplished using a datacontroller and work automagically.


containers-preview.patch Download (24.5 KB) - added by KarlGuertin 11 years ago.
Preview, this thing doesn't completely work

Change History

Changed 11 years ago by KarlGuertin

Preview, this thing doesn't completely work

comment:1 Changed 11 years ago by KarlGuertin

So here's the problems with the above patch and why this ticket isn't marked [PATCH].

The above patch only does output. It name mangles the widgets using a path attribute into a form that is understandable by the FormEncode NestedVariables? validator. The reason it doesn't do input and validation is because I'm not entirely sure the best way to go about doing it.

My idea was to create a  FormEncode Schema from the widget validators, set the pre_validators to NestedVariables? and stick the container validators into chained_validators. The problem is that I haven't figured out how to incorporate this into the TG validation framework. I'm hoping for some input from people who better understand that area of TG.

The other problem is that I want to do output based on the values passed in. I've added, for example, a summary attribute to these classes. The idea is that the contents be used to provide a title and/or a useful standin. For example, if the PersonForm? class above was included in another form, it'd make sense for the summary to be: '%(firstName) %s(lastName)', which would get filled in with the appropriate values so you'd get:

+ Jim Jones
    * First Name: Jim
    * Middle Initial: J
    * First Name: Jones
+ Guido VanRossum
    * First Name: Guido
    * Middle Initial: Z
    * First Name: VanRossum

Instead of having the summary empty and getting:

+   * First Name: Jim
    * Middle Initial: J
    * First Name: Jones
+   * First Name: Guido
    * Middle Initial: Z
    * First Name: VanRossum

Or having the summary static and getting:

+ Person
    * First Name: Jim
    * Middle Initial: J
    * First Name: Jones
+ Person
    * First Name: Guido
    * Middle Initial: Z
    * First Name: VanRossum

I do the above using a very evil hack that looks at the sqlobject class' dictionary for _SO_val_ attributes and copies them into a dictionary without the _SO_val_. Bad, evil, wrong, etc.

The other issue for output is options widgets. If the values you can select from vary among the outputs, you need to have a way to set the options attribute on every pass. I currently do this through a magic attribute on the appropriate sqlobject called 'TG_Options', which is (also) wrong and evil, but works.

If people have ideas on how to solve these, I'm open to suggestions. Oh, but the above patch does pass all the tests in the suite. :P

comment:2 Changed 11 years ago by anonymous

class AddressSchema(formencode.Schema):
    street = validators.String(notEmpty = True)
    city = validators.String(notEmpty = True)
    state = validators.string(notEmpty = True, length=2)
    zip = validators.PostalCode()

class AddressForm(W.ListContainer, AddressSchema):
    street = TextField()
    city = TextField()
    state = TextField()
    zip = StringCol()

class PersonSchema(formencode.Schema):
    firstName = validators.StringCol(notEmpty = True)
    middleInitial = validators.StringCol(notEmpty = True)
    lastName = validators.StringCol(notEmpty = True)
    addresses = AddressSchema()

class PersonForm(W.ListForm, PersonSchema):
    firstName = TextField(labeltext="First Name")
    middleInitial = TextField(labeltext="Middle Initial")
    lastName = TextField(labeltext="Last Name")
    addresses = AddressForm()

Would something like that be a better solution to your troubles? The *Form attributes are the same as the FormEncode attributes, so I don't see why something like that couldn't be done instead. Or maybe PersonForm? could have a formmeta.validators attribute specifying PersonSchema?.

comment:3 Changed 11 years ago by kevin

Yeah, the widget itself should be responsible for the conversion to/from web format (using the validator). You wouldn't put the validator on the controller method, you would just specify the inputform and everything else should follow from that.

comment:4 Changed 11 years ago by KarlGuertin

  • Status changed from new to assigned
  • Milestone changed from 0.9 to 1.0

I don't plan on landing this before 0.9 so I'm setting the target to 1.0.

comment:5 Changed 11 years ago by Karl Guertin <grayrest@…>

  • Status changed from assigned to new
  • Owner changed from anonymous to Karl Guertin <grayrest@…>

comment:6 Changed 11 years ago by Dan Jacob(dan.jacob@…

Instead of containers, think of "layouts" (as in, for example, Java Swing). A form consists of a default layout, which may in turn contain other layouts and so on. Validation and value setting is handled purely by the form, as it is now.

The basic Layout class can be extended, as with the Form class, by providing a template, so you can have ListLayout?, TableLayout?, GridLayout? or whatever custom layout you want. The Form class itself has only a template for handling the start and end form tags and the hidden widgets (maybe the submit button(s) too).

When you create your Form, you pass in the widgets parameter as before, and you also pass in the layout parameter. If no layout parameter is passed the default layout is used (this can be an attribute of the Form class). The layout constructor may take a list of widget names.

For example:

my_form = Form( widgets = [TextField?("name"), CheckBox?("mail_ok"), TextField?("email", validator=EmailValidator?())], layout = TableLayout?()) # this would normally be default.

A more complex layout, using a CompositeLayout?:

my_form = Form( widgets = [TextField?("name"), CheckBox?("mail_ok"), TextField?("street"), TextField?("postcode"), TextField?("city")], layout=CompositeLayout?( layouts = [

TableLayout?(widgets = ["name", "mail_ok"]), FieldSetLayout?(widgets = ["street", "postcode", "city"], legend="Address")

] )

comment:7 Changed 11 years ago by michele

Dan, the right approch IMHO is using the brand new py:layour kid function.

comment:8 Changed 11 years ago by Dan Jacob

I'll have to take a look at that. Thanks for the tip !

comment:9 Changed 11 years ago by alberto

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

I think the changes at r804 address this issue.

comment:10 Changed 11 years ago by kevin

  • Milestone changed from 1.0 to 0.9a1
Note: See TracTickets for help on using tickets.