First off, thanks for the insights into the design philosophy of TG
widgets.  OK, let me see if I can recap what you are saying.  The
current implementation of _is_option_selected assumes that the "value"
in the options list [(value,desc,options)] is a Python value, but the
current implementation of the select field templates do not correctly
render that value into the template using the decorator's from_python
method, making it difficult to impossible to pass complex Python
values in the options list.

In that case, let me suggest that the SelectionField.update_params()
method be updated to actually use the validator's from_python method
to render the values into the template.  I have included below a
version of the method which does just that.  Unfortunately, this alone
does not fix the problem.  Now, when update_params calls
is_option_selected, it is comparing two Python values (one from the
list, one from the value passed into the form).  So in this case, it
seems that is_option_selected should skip the conversion step, since
it is comparing two Python values.

I have included monkey-patched versions of these two methods that seem
to give the desired behavior (passing Python values in the options
parameter, correctly rendering python values to the form using
from_python, and correctly determining which one is selected when
displaying/rendering the template).  I've also included a fixed
version of my test controller method that works with these patches
fine.  Do you see any problem with applying patches (real ones,
obviously, not these monkey-patches) to turbogears/widgets/forms.py?

Thanks,
Rick Copeland

=== Test case begin ===
@expose(template='foo')
def test(self):

        class TestValue(object):
            def __init__(self, value):
                self.value = value
            def __eq__(self, other):
                # necessary to allow the SelectionField to determine
which value is selected
                return self.value == other.value

        class TestObj(object):
            def __init__(self):
                self.testval=TestValue(3)

        class TestValidator:
            def to_python(self, value, state=None):
                log.info('Call to_python on value "%s"', value)
                result = {'1':TestValue(1),
                          '2':TestValue(2),
                          '3':TestValue(3),
                          '4':TestValue(4),
                          '5':TestValue(5) }.get(value, None)
                if result is None: raise Invalid('Bad Value', value,
state)
                return result
            def from_python(self, value, state=None):
                return str(value.value)

        class TestFields(WidgetsList):
            testval=SingleSelectField(
                'testval', label='Test Value',
                options=[(TestValue(1),'one'), (TestValue(2),'two'),
                         (TestValue(3),'three'), (TestValue(4),
'four')],
                validator=TestValidator())

        test_form = TableForm('test', fields=TestFields())

        return dict(form=test_form,
                    item=TestObj())
=== Test case end ===

=== Monkey Patches begin ===
def SF_update_params(self, d):
    super(SelectionField, self).update_params(d)
    grouped_options = []
    options = []
    d['options'] = self._extend_options(d['options'])
    for optgroup in d["options"]:
        if isinstance(optgroup[1], list):
            group = True
            optlist = optgroup[1]
        else:
            group = False
            optlist = [optgroup]
        for i, option in enumerate(optlist):
            if len(option) is 2:
                option_attrs = {}
            elif len(option) is 3:
                option_attrs = option[2]
            if self._is_option_selected(option[0], d['value']):
                option_attrs[self._selected_verb] =
self._selected_verb
            optlist[i] = (self.validator.from_python(option[0]),  #
This is the only line I changed
                          option[1], option_attrs)
        options.extend(optlist)
        if group:
            grouped_options.append((optgroup[0], optlist))
    # options provides a list of *flat* options leaving out any
eventual
    # group, useful for backward compatibility and simpler widgets
    d["options"] = options
    if grouped_options:
        d["grouped_options"] = grouped_options
    else:
        d["grouped_options"] = [(None, options)]
SelectionField.update_params = SF_update_params

def SF_is_option_selected(self, option_value, value):
    # Removed validation logic here
    if value is not None:
        if self._multiple_selection:
            if option_value in value:
                return True
        else:
            if option_value == value:
                return True
    return False
SelectionField._is_option_selected = SF_is_option_selected
=== Monkey Patches end ===

On Mar 4, 6:06 pm, "Diez B. Roggisch" <[EMAIL PROTECTED]>
wrote:
> > No, it means that '1', '2', ... are the values that will be inserted
> > *without conversion* into the "value" attribute of the <option> tag in
> > the template.
>
> This is your misconception. These are the values that are used to be
> compared with the _to_python-converted request parameter value. You are
> right though that they aren't converted during rendering, except from
> being stringified.
>
> See the following example for a working pre-selection of a value in a
> SingleSelectField:
>
> ---- controller -----
> from turbogears import controllers, expose
> import turbogears.widgets as w
>
> import turbogears.validators as v
>
> def calc_options():
>      return [(i, "value: %i" % i) for i in xrange(1000)]
>
> form = w.ListForm(fields=[w.SingleSelectField('test', options=calc_options,
>
> validator=v.Int(not_empty=True)),
>                            w.TextField('foo')])
>
> class Root(controllers.RootController):
>      @expose(template="testproject.templates.welcome")
>      def index(self):
>          import time
>          # log.debug("Happy TurboGears Controller Responding For Duty")
>          return dict(now=time.ctime(), form=form, selected=100, foo='bar')
>
> ----- template ----
>
> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd";>
> <html xmlns="http://www.w3.org/1999/xhtml";
> xmlns:py="http://purl.org/kid/ns#";
>      py:extends="'master.kid'">
> <head>
> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"
> py:replace="''"/>
> <title>Welcome to TurboGears</title>
> </head>
> <body>
>    ${selected}
>    ${form.display(dict(test='20', foo='baz'))}
> </body>
> </html>
>
> So, again: the values in the option-list are compared to whatever the
> validators _to_python-method yields.
>
>  From the formencode docs:
>
> """
> The basic metaphor for validation is to_python and from_python. In this
> context "Python" is meant to refer to "here" -- the trusted application,
> your own Python objects. The "other" may be a web form, an external
> database, an XML-RPC request, or any data source that is not completely
> trusted or does not map directly to Python's object model. to_python is
> the process of taking external data and preparing it for internal use,
> from_python generally reverses this process (from_python is usually the
> less interesting of the pair, but provides some important features).
> """
>
> So - from that POV, it's the right way TG uses the validation stuff.
> OTH, it is sort of an omission that the current implementation doesn't
> use to from_python-method to convert e.g. model objects to id to render
> them as part of a select.
>
> Diez


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"TurboGears" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/turbogears?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to