list:<type> and contains

web2py provides the following special field types:

  1. list:string
  2. list:integer
  3. list:reference <table>

They can contain lists of strings, of integers and of references respectively.

On Google App Engine NoSQL list:string is mapped into StringListProperty, the other two are mapped into ListProperty(int). On relational databases they are mapped into text fields which contain the list of items separated by |. For example [1, 2, 3] is mapped into |1|2|3|.

For lists of string the items are escaped so that any | in the item is replaced by a ||. Anyway this is an internal representation and it is transparent to the user.

You can use list:string, for example, in the following way:

  1. >>> db.define_table('product',
  2. ... Field('name'),
  3. ... Field('colors', 'list:string'))
  4. <Table product (id, name, colors)>
  5. >>> db.product.colors.requires = IS_IN_SET(('red', 'blue', 'green'))
  6. >>> db.product.insert(name='Toy Car', colors=['red', 'green'])
  7. 1
  8. >>> products = db(db.product.colors.contains('red')).select()
  9. >>> for item in products:
  10. ... print item.name, item.colors
  11. ...
  12. Toy Car ['red', 'green']

list:integer works in the same way but the items must be integers.

As usual the requirements are enforced at the level of forms, not at the level of insert.

For list:<type> fields the contains(value) operator maps into a non trivial query that checks for lists containing the value. The contains operator also works for regular string and text fields and it maps into a LIKE '%value%'.

The list:reference and the contains(value) operator are particularly useful to de-normalize many-to-many relations. Here is an example:

  1. >>> db.define_table('tag',
  2. ... Field('name'),
  3. ... format='%(name)s')
  4. <Table tag (id, name)>
  5. >>> db.define_table('product',
  6. ... Field('name'),
  7. ... Field('tags', 'list:reference tag'))
  8. <Table product (id, name, tags)>
  9. >>> a = db.tag.insert(name='red')
  10. >>> b = db.tag.insert(name='green')
  11. >>> c = db.tag.insert(name='blue')
  12. >>> db.product.insert(name='Toy Car', tags=[a, b, c])
  13. 1
  14. >>> products = db(db.product.tags.contains(b)).select()
  15. >>> for item in products:
  16. ... print item.name, item.tags
  17. ...
  18. Toy Car [1, 2, 3]
  19. >>> for item in products:
  20. ... print item.name, db.product.tags.represent(item.tags)
  21. ...
  22. Toy Car red, green, blue

Notice that a list:reference tag field get a default constraint

  1. requires = IS_IN_DB(db, db.tag._id, db.tag._format, multiple=True)

that produces a SELECT/OPTION multiple drop-box in forms.

Also notice that this field gets a default represent attribute which represents the list of references as a comma-separated list of formatted references. This is used in read forms and SQLTABLEs.

While list:reference has a default validator and a default representation, list:integer and list:string do not. So these two need an IS_IN_SET or an IS_IN_DB validator if you want to use them in forms.