:mod:`formalchemy.tables` -- `Grid`: Rendering collections
==========================================================

.. automodule:: formalchemy.tables

Besides :class:`~formalchemy.forms.FieldSet`, `FormAlchemy` provides `Grid` for editing and
rendering multiple instances at once.  Most of what you know about
:class:`~formalchemy.forms.FieldSet` applies to `Grid`, with the following differences to
accomodate being bound to multiple objects:

The Grid class
--------------

.. autoclass:: Grid
   :members:

Creating
--------

The `Grid` constructor takes parameters (`cls`, `instances=[]`, `session=None`, `data=None`).
A significant difference from :class:`~formalchemy.forms.FieldSet` is that the first argument
must _always_ be a mapped class, e.g., `User`.  `instances` is the objects to render,
which must all be of the given type.  The other parameters are the same as in 
:class:`~formalchemy.forms.FieldSet`.

Binding
-------

`Grid` `bind` and `rebind` methods are similar to those methods in
:class:`~formalchemy.forms.FieldSet`, except they take an iterable `instances`
instead of an instance `model`.  Thus, the full signature is
(`instances`, `session=None`, `data=None`).

Configuration
-------------

The `Grid` `configure` method takes the same arguments as :class:`~formalchemy.forms.FieldSet`
(`pk`, `exclude`, `include`, `options`, `readonly`), except there is
no `focus` argument.

Validation and Sync
-------------------

These are the same as in :class:`~formalchemy.forms.FieldSet`, except that you can also call
`sync_one(instance)` to sync a single one of the instances that are
bound to the `Grid`.

The `Grid` `errors` attribute is a dictionary keyed by bound instance,
whose value is similar to the `errors` from a :class:`~formalchemy.forms.FieldSet`, that is, a
dictionary whose keys are `Field`s, and whose values are
`ValidationError` instances.

Customizing Grid
----------------

Overriding `Grid` rendering is similar to :class:`~formalchemy.forms.FieldSet`.  The differences are:

  * The default templates take a `collection` parameter instead of `fieldset`, which is the instance of `Grid` to render
  * The instances given to the collection are available in `collection.rows`; to access the fields of each single row, call `_set_active(row)`, then access `render_fields`.

The default templates are `formalchemy.tables.template_grid_readonly` and `formalchemy.tables.template_grid`.

Usage
-----

You need some imports::

  >>> from formalchemy.tables import *

.. Hidden code needed for testing

  >>> from formalchemy.tests import *

Then you can initialize a `Grid` and bind it to a list of row instance::  

  >>> tc = Grid(User, [bill])
  >>> tc.configure(readonly=True)

This will render instances as an html table::

  >>> print tc.render()
  <thead>
   <tr>
    <th>
     Email
    </th>
    <th>
     Password
    </th>
    <th>
     Name
    </th>
    <th>
     Orders
    </th>
   </tr>
  </thead>
  <tbody>
   <tr class="even">
    <td>
     bill@example.com
    </td>
    <td>
     1234
    </td>
    <td>
     Bill
    </td>
    <td>
     Quantity: 10
    </td>
   </tr>
  </tbody>

You can also add a field to the `Grid` manually::

  >>> tc2 = Grid(User, [bill, john])
  >>> tc2.append(Field('link', type=types.String, value=lambda item: '<a href=%d>link</a>' % item.id))
  >>> tc2.configure(readonly=True)
  >>> print tc2.render()
  <thead>
   <tr>
    <th>
     Email
    </th>
    <th>
     Password
    </th>
    <th>
     Name
    </th>
    <th>
     Orders
    </th>
    <th>
     Link
    </th>
   </tr>
  </thead>
  <tbody>
   <tr class="even">
    <td>
     bill@example.com
    </td>
    <td>
     1234
    </td>
    <td>
     Bill
    </td>
    <td>
     Quantity: 10
    </td>
    <td>
     <a href="1">
      link
     </a>
    </td>
   </tr>
   <tr class="odd">
    <td>
     john@example.com
    </td>
    <td>
     5678
    </td>
    <td>
     John
    </td>
    <td>
     Quantity: 5, Quantity: 6
    </td>
    <td>
     <a href="2">
      link
     </a>
    </td>
   </tr>
  </tbody>

You can provide a dict as new values::  

  >>> g = Grid(User, [bill, john], data={'User-1-email': 'bill_@example.com', 'User-1-password': '1234_', 'User-1-name': 'Bill_', 'User-1-orders': '1', 'User-2-email': 'john_@example.com', 'User-2-password': '5678_', 'User-2-name': 'John_', 'User-2-orders': ['2', '3'], })

Validation work like `Fieldset`::

  >>> g.validate()
  True

Sync too:

  >>> g.sync()
  >>> session.flush()
  >>> session.refresh(john)
  >>> john.email == 'john_@example.com'
  True
  >>> session.rollback()
