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 8 and Version 9 of PassingArgumentsToCallables


Ignore:
Timestamp:
07/13/06 13:03:41 (13 years ago)
Author:
plewis
Comment:

revert to v6 (despam)

Legend:

Unmodified
Added
Removed
Modified
  • PassingArgumentsToCallables

    v8 v9  
    1 {{{ 
    2 #!html  
    3 }}} 
    41== Problem == 
    52 
     
    129== Solution == 
    1310 
    14 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. 
     11We 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. 
    1512 
    1613 
     
    1916This 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.) 
    2017 
    21  
    22  
     18{{{ 
     19@classmethod 
     20def build_list(self, field, *args, **kwargs): 
     21    return [(a.id, getattr(a, field)) for a in self.select(*args, **kwargs)] 
     22}}} 
     23 
     24An alternative to copying and pasting is adding it to SQL Object dynamically.  Define it in the beginning of your model and add this line after this definition: 
     25 
     26{{{ 
     27SQLObject.build_list = build_list 
     28}}} 
     29 
     30This way, when your classes inherit from SQL Object they will already have the `build_list` method defined within them through inheritance. 
     31 
     32You can test this straight away by running Python over the following application: 
     33 
     34{{{ 
     35from sqlobject import * 
     36 
     37__connection__ = connectionForURI("sqlite:///:memory:") 
     38hub = __connection__ 
     39 
     40class 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 
     47Role.createTable(ifNotExists=True) 
     48}}} 
     49 
     50If you use the inheritance approach you'll end up with: 
     51 
     52{{{ 
     53from sqlobject import * 
     54 
     55__connection__ = connectionForURI("sqlite:///:memory:") 
     56hub = __connection__ 
     57 
     58@classmethod 
     59def build_list(self, field, *args, **kwargs): 
     60    return [(a.id, getattr(a, field)) for a in self.select(*args, **kwargs)] 
     61 
     62SQLObject.build_list = build_list 
     63 
     64class Role(SQLObject): 
     65    name = StringCol(length=20) 
     66 
     67Role.createTable(ifNotExists=True) 
     68}}} 
     69 
     70Then, we populate our database for testing purposes: 
     71 
     72{{{ 
     73Role(name="Administrator") 
     74Role(name="Anonymous") 
     75Role(name="Member") 
     76Role(name="Author") 
     77Role(name="Editor") 
     78Role(name="Manager") 
     79 
     80print repr(Role.build_list('name')) 
     81}}} 
     82 
     83Output should be: 
     84{{{ 
     85[(1, 'Administrator'), (2, 'Anonymous'), (3, 'Member'), (4, 'Author'), (5, 'Editor'), (6, 'Manager')] 
     86}}} 
     87 
     88We can also change the last line to add any SQLBuilder query or other SQLObject `select()` argument you wish.  For example: 
     89{{{ 
     90print repr(Role.build_list('name', orderBy=Role.q.name)) 
     91}}} 
     92 
     93Produces: 
     94{{{ 
     95[(1, 'Administrator'), (2, 'Anonymous'), (4, 'Author'), (5, 'Editor'), (6, 'Manager'), (3, 'Member')] 
     96}}} 
     97 
     98=== Supplying Options to Select-Style Widgets === 
     99 
     100Now 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{{{ 
     103options = [('red', "Red"), ('green', "Green"), ('blue', "Blue")] 
     104colors = SingleSelectField('colors', options=options) 
     105}}} 
     106 
     107If 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 
     116Not 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{{{ 
     119from turbogears.widgets import * 
     120from sqlobject import * 
     121 
     122__connection__ = connectionForURI("sqlite:///:memory:") 
     123hub = __connection__ 
     124 
     125class Color(SQLObject): 
     126    name = StringCol(length=20) 
     127    r = FloatCol() 
     128    g = FloatCol() 
     129    b = FloatCol() 
     130 
     131Color.createTable(ifNotExists=True) 
     132 
     133Color(name="Red", r=1.0, g=0.0, b=0.0) 
     134Color(name="Green", r=0.0, g=1.0, b=0.0) 
     135Color(name="Blue", r=0.0, g=0.0, b=1.0) 
     136Color(name="Purple", r=1.0, g=0.0, b=1.0) 
     137Color(name="Yellow", r=1.0, g=1.0, b=0.0) 
     138Color(name="Teal", r=0.0, g=1.0, b=1.0) 
     139 
     140colors = [(a.id, a.name) for a in Color.select()] 
     141widget = SingleSelectField('colors', options=colors) 
     142print widget.render() 
     143}}} 
     144 
     145The 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 
     154This, 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{{{ 
     157def 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 
     164We 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. 
     168widget = SingleSelectField('colors', options=make_list(Color, None, 'name', 
     169        Color.q.r > 0, orderBy=Color.q.name) 
     170}}} 
     171 
     172You 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{{{ 
     177from turbogears.widgets import * 
     178from sqlobject import * 
     179 
     180__connection__ = connectionForURI("sqlite:///:memory:") 
     181hub = __connection__ 
     182 
     183def 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 
     189class 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 
     199Color.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)) 
     203Color.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 
     207Color.createTable(ifNotExists=True) 
     208 
     209Color(name="Red", r=1.0, g=0.0, b=0.0) 
     210Color(name="Green", r=0.0, g=1.0, b=0.0) 
     211Color(name="Blue", r=0.0, g=0.0, b=1.0) 
     212Color(name="Purple", r=1.0, g=0.0, b=1.0) 
     213Color(name="Yellow", r=1.0, g=1.0, b=0.0) 
     214Color(name="Teal", r=0.0, g=1.0, b=1.0) 
     215Color(name="Orange", r=1.0, g=0.5, b=0.0) 
     216Color(name="Black", r=0.0, g=0.0, b=0.0) 
     217Color(name="25% Grey", r=0.25, g=0.25, b=0.25) 
     218Color(name="50% Grey", r=0.5, g=0.5, b=0.5) 
     219Color(name="75% Grey", r=0.75, g=0.75, b=0.75) 
     220Color(name="White", r=1.0, g=1.0, b=1.0) 
     221 
     222colors = [(a.id, a.name) for a in Color.select()] 
     223widget = SingleSelectField('bright_colors', options=Color.list_shades) 
     224print 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 
     237Currying 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 
     241Sometimes 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 
     243The new build_list is named build_list_with_key and its code is: 
     244 
     245{{{ 
     246@classmethod 
     247def 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 
     250SQLObject.build_list_with_key = build_list_with_key 
     251}}} 
     252 
     253The 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{{{ 
     256def 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