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

Changes between Version 5 and Version 8 of PassingArgumentsToCallables


Ignore:
Timestamp:
07/13/06 09:44:38 (13 years ago)
Author:
ksenia
Comment:

remove spam

Legend:

Unmodified
Added
Removed
Modified
  • PassingArgumentsToCallables

    v5 v8  
     1{{{ 
     2#!html  
     3}}} 
    14== Problem == 
    25 
     
    912== Solution == 
    1013 
    11 We are going to create two things: a generic method which returns SQLObject records in the correct format (a list of tuples), and a function which modifies that list by prepending optional "empty" item, while passing additional arguments to the generic method. 
     14We are going to create two things: a generic method which returns SQLObject records in the correct format (a list of tuples), and a function which modifies that list by prepending optional "empty" item, while passing additional arguments to the generic method. 
    1215 
    1316 
     
    1619This function should be defined (through copy-and-paste or assignment) within each SQLObject class we wish to use as a source of select items.  (Note: in Python 2.3 replace `@classmethod` before the method with `build_list = classmethod(build_list)` after.) 
    1720 
    18 {{{ 
    19 @classmethod 
    20 def build_list(self, field, *args, **kwargs): 
    21     return [(a.id, getattr(a, field)) for a in self.select(*args, **kwargs)] 
    22 }}} 
    2321 
    24 An alternative to copying and pasting is adding it to SQL Object dinamically.  Define it in the beginning of your model and add this line after this definition: 
    2522 
    26 {{{ 
    27 SQLObject.build_list = build_list 
    28 }}} 
    29  
    30 This way, when your classes inherit from SQL Object they will already have the `build_list` method defined within them through inheritance. 
    31  
    32 You can test this straight away by running Python over the following application: 
    33  
    34 {{{ 
    35 from sqlobject import * 
    36  
    37 __connection__ = connectionForURI("sqlite:///:memory:") 
    38 hub = __connection__ 
    39  
    40 class Role(SQLObject): 
    41     name = StringCol(length=20) 
    42  
    43     @classmethod 
    44     def build_list(self, field, *args, **kwargs): 
    45         return [(a.id, getattr(a, field)) for a in self.select(*args, **kwargs)] 
    46  
    47 Role.createTable(ifNotExists=True) 
    48 }}} 
    49  
    50 If you use the inheritance approach you'll end up with: 
    51  
    52 {{{ 
    53 from sqlobject import * 
    54  
    55 __connection__ = connectionForURI("sqlite:///:memory:") 
    56 hub = __connection__ 
    57  
    58 @classmethod 
    59 def build_list(self, field, *args, **kwargs): 
    60     return [(a.id, getattr(a, field)) for a in self.select(*args, **kwargs)] 
    61  
    62 SQLObject.build_list = build_list 
    63  
    64 class Role(SQLObject): 
    65     name = StringCol(length=20) 
    66  
    67 Role.createTable(ifNotExists=True) 
    68 }}} 
    69  
    70 Then, we populate our database for testing purposes: 
    71  
    72 {{{ 
    73 Role(name="Administrator") 
    74 Role(name="Anonymous") 
    75 Role(name="Member") 
    76 Role(name="Author") 
    77 Role(name="Editor") 
    78 Role(name="Manager") 
    79  
    80 print repr(Role.build_list('name')) 
    81 }}} 
    82  
    83 Output should be: 
    84 {{{ 
    85 [(1, 'Administrator'), (2, 'Anonymous'), (3, 'Member'), (4, 'Author'), (5, 'Editor'), (6, 'Manager')] 
    86 }}} 
    87  
    88 We can also change the last line to add any SQLBuilder query or other SQLObject `select()` argument you wish.  For example: 
    89 {{{ 
    90 print repr(Role.build_list('name', orderBy=Role.q.name)) 
    91 }}} 
    92  
    93 Produces: 
    94 {{{ 
    95 [(1, 'Administrator'), (2, 'Anonymous'), (4, 'Author'), (5, 'Editor'), (6, 'Manager'), (3, 'Member')] 
    96 }}} 
    97  
    98 === Supplying Options to Select-Style Widgets === 
    99  
    100 Now that we have nice, pretty, usable lists of tuples (representing the value and caption of the options) we can play with TurboGears' Widgets system.  Normally we would have something like the following, where I use differing quotes to help identify parts: 
    101  
    102 {{{ 
    103 options = [('red', "Red"), ('green', "Green"), ('blue', "Blue")] 
    104 colors = SingleSelectField('colors', options=options) 
    105 }}} 
    106  
    107 If we were to run `colors.render()` we would get: 
    108 {{{ 
    109 <SELECT CLASS="singleselectfield" NAME="bob" ID="bob"> 
    110  
    111             <OPTION VALUE="red">Red</OPTION><OPTION VALUE="green">Green</OPTION><OPTION VALUE="blue">Blue</OPTION> 
    112  
    113     </SELECT> 
    114 }}} 
    115  
    116 Not pretty, but it's what we're looking for; a widget.  Now, we want this to be driven via a database of available colors.  The easy way is the demonstrated in the following application: 
    117  
    118 {{{ 
    119 from turbogears.widgets import * 
    120 from sqlobject import * 
    121  
    122 __connection__ = connectionForURI("sqlite:///:memory:") 
    123 hub = __connection__ 
    124  
    125 class Color(SQLObject): 
    126     name = StringCol(length=20) 
    127     r = FloatCol() 
    128     g = FloatCol() 
    129     b = FloatCol() 
    130  
    131 Color.createTable(ifNotExists=True) 
    132  
    133 Color(name="Red", r=1.0, g=0.0, b=0.0) 
    134 Color(name="Green", r=0.0, g=1.0, b=0.0) 
    135 Color(name="Blue", r=0.0, g=0.0, b=1.0) 
    136 Color(name="Purple", r=1.0, g=0.0, b=1.0) 
    137 Color(name="Yellow", r=1.0, g=1.0, b=0.0) 
    138 Color(name="Teal", r=0.0, g=1.0, b=1.0) 
    139  
    140 colors = [(a.id, a.name) for a in Color.select()] 
    141 widget = SingleSelectField('colors', options=colors) 
    142 print widget.render() 
    143 }}} 
    144  
    145 The output is: 
    146 {{{ 
    147 <SELECT CLASS="singleselectfield" NAME="colors" ID="colors"> 
    148  
    149             <OPTION VALUE="1">Red</OPTION><OPTION VALUE="2">Green</OPTION><OPTION VALUE="3">Blue</OPTION><OPTION VALUE="4">Purple</OPTION><OPTION VALUE="5">Yellow</OPTION><OPTION VALUE="6">Teal</OPTION> 
    150  
    151     </SELECT> 
    152 }}} 
    153  
    154 This, however, is a static list.  Making this dynamic is easy, though; simply add the `build_list` class method to the `Color` class, remembering to default the field name to `'name'`, remove the `colors` assignment third from the bottom, and set `options` in the widget equal to `Color.build_list`.  Now the widget gets its values from the DB on every call.  Now how do we sort them?  We need our second magical function, `make_list`: 
    155  
    156 {{{ 
    157 def make_list(c, none=None, *args, **kwargs): 
    158     def _call(): 
    159         return [ [], [('', none)] ][none is not None] + \ 
    160                 c.build_list(*args, **kwargs) 
    161     return _call 
    162 }}} 
    163  
    164 We can now pass arguments, like `sortOrder`, to the `build_list` method as follows: 
    165  
    166 {{{ 
    167 # Only list colors that have some red in them, and sort alphabetically. 
    168 widget = SingleSelectField('colors', options=make_list(Color, None, 'name', 
    169         Color.q.r > 0, orderBy=Color.q.name) 
    170 }}} 
    171  
    172 You can even create these lists ahead of time by setting properties of the SQLObject class (Color in this case) to the appropriate `make_list` call.  The final test application, below, does this. 
    173  
    174 == Final Test Application == 
    175  
    176 {{{ 
    177 from turbogears.widgets import * 
    178 from sqlobject import * 
    179  
    180 __connection__ = connectionForURI("sqlite:///:memory:") 
    181 hub = __connection__ 
    182  
    183 def make_list(c, none=None, *args, **kwargs): 
    184     def _call(): 
    185         return [ [], [('', none)] ][none is not None] + \ 
    186                 c.build_list(*args, **kwargs) 
    187     return _call 
    188  
    189 class Color(SQLObject): 
    190     name = StringCol(length=20) 
    191     r = FloatCol() 
    192     g = FloatCol() 
    193     b = FloatCol() 
    194      
    195     @classmethod 
    196     def build_list(self, field, *args, **kwargs): 
    197         return [(a.id, getattr(a, field)) for a in self.select(*args, **kwargs)] 
    198  
    199 Color.list_bright = staticmethod(make_list(Color, None, 'name', 
    200             OR(OR(Color.q.r > 0.8, Color.q.g > 0.8, Color.q.b > 0.8), 
    201                     (Color.q.r + Color.q.g + Color.q.b) > 2), 
    202                     orderBy=Color.q.name)) 
    203 Color.list_shades = staticmethod(make_list(Color, None, 'name', 
    204             AND(Color.q.r == Color.q.g, Color.q.g == Color.q.b), 
    205                     orderBy=Color.q.r)) 
    206  
    207 Color.createTable(ifNotExists=True) 
    208  
    209 Color(name="Red", r=1.0, g=0.0, b=0.0) 
    210 Color(name="Green", r=0.0, g=1.0, b=0.0) 
    211 Color(name="Blue", r=0.0, g=0.0, b=1.0) 
    212 Color(name="Purple", r=1.0, g=0.0, b=1.0) 
    213 Color(name="Yellow", r=1.0, g=1.0, b=0.0) 
    214 Color(name="Teal", r=0.0, g=1.0, b=1.0) 
    215 Color(name="Orange", r=1.0, g=0.5, b=0.0) 
    216 Color(name="Black", r=0.0, g=0.0, b=0.0) 
    217 Color(name="25% Grey", r=0.25, g=0.25, b=0.25) 
    218 Color(name="50% Grey", r=0.5, g=0.5, b=0.5) 
    219 Color(name="75% Grey", r=0.75, g=0.75, b=0.75) 
    220 Color(name="White", r=1.0, g=1.0, b=1.0) 
    221  
    222 colors = [(a.id, a.name) for a in Color.select()] 
    223 widget = SingleSelectField('bright_colors', options=Color.list_shades) 
    224 print widget.render() 
    225 }}} 
    226  
    227 {{{ 
    228 <SELECT CLASS="singleselectfield" NAME="bright_colors" ID="bright_colors"> 
    229  
    230             <OPTION VALUE="11">75% Grey</OPTION><OPTION VALUE="3">Blue</OPTION><OPTION VALUE="2">Green</OPTION><OPTION VALUE="7">Orange</OPTION><OPTION VALUE="4">Purple</OPTION><OPTION VALUE="1">Red</OPTION><OPTION VALUE="6">Teal</OPTION><OPTION VALUE="12">White</OPTION><OPTION VALUE="5">Yellow</OPTION> 
    231  
    232     </SELECT> 
    233 }}} 
    234  
    235 == Discussion == 
    236  
    237 Currying is described throughly in the Python Cookbook (c. 16.4, p. 594-597) and is also [http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549 available on-line], although not in as pretty a package, nor as in-depth. 
    238  
    239 = Add-Ons = 
    240  
    241 Sometimes one wants to use a column other than "id" as the ID, i.e., have something else as the first value while using build_list.  These two alternative methods allows that.  Extending this for other needs should be trivial. 
    242  
    243 The new build_list is named build_list_with_key and its code is: 
    244  
    245 {{{ 
    246 @classmethod 
    247 def build_list_with_key(self, key, field, *args, **kwargs): 
    248     return [(getattr(a, key), getattr(a, field)) for a in self.select(*args, **kwargs)] 
    249  
    250 SQLObject.build_list_with_key = build_list_with_key 
    251 }}} 
    252  
    253 The new make_list function now can pass a key to build_list_with_key, making it usable with this different value.  The code is: 
    254  
    255 {{{ 
    256 def make_list_with_key(c, none=None, key = 'id', *args, **kwargs): 
    257     def _call(): 
    258         return [ [], [('', none)] ][none is not None] + \ 
    259                c.build_list_with_key(key, *args, **kwargs) 
    260     return _call 
    261 }}} 
    262