Datatables tools#
Overview#
The datatables module provides seamless server-side processing for the DataTables jQuery plugin within CherryPy applications. It bridges SQLAlchemy queries with the DataTables server-side protocol, handling sorting, global search, per-column filtering, pagination, and JSON serialization automatically.
How It Works#
The module operates through two complementary layers:
1. DataTableQuery — Core Processing Class#
DataTableQuery wraps a SQLAlchemy Query object and processes it according to DataTables request parameters. When instantiated, it immediately:
Counts total records from the original unfiltered query.
Applies sorting based on
order[0][column]andorder[0][dir]parameters.Applies global search across all configured
search_columnsusingILIKE(case-insensitive partial match).Applies per-column search for each
columns[N][search][value]parameter, supporting both plainILIKEmatching and regex patterns.Counts filtered records after all search/filter conditions are applied.
Calling .paginate() then slices the result set with OFFSET/LIMIT and serializes each row into a plain Python dict, returning the standard DataTables response envelope:
{
"draw": 1,
"recordsTotal": 100,
"recordsFiltered": 42,
"data": []
}
2. datatables_out — CherryPy Tool#
datatables_out is a CherryPy tool registered at the before_handler hook (priority 29). It wraps the original page handler using a handler replacement pattern (similar to json_out):
The original handler is called first and must return a SQLAlchemy
Queryobject.The wrapper intercepts this
Query, extracts DataTables parameters from the request, runsDataTableQuery, and returns the response dict.The
json_outtool (applied alongside) converts the dict to a JSON HTTP response.
This design keeps your handler clean — it only builds and returns a query, with zero boilerplate for DataTables protocol handling.
Setup & Usage#
Registration#
The tool is registered automatically when the module is imported:
import cherrypy_foundation.tools.datatables # noqa
After import, cherrypy.tools.datatables_out is available globally.
@cherrypy.tools.datatables_out() Decorator#
Wraps a handler so that its returned Query is processed by DataTableQuery.
Must be paired with @cherrypy.tools.json_out().
Parameters#
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
|
|
SQLAlchemy column expressions to include in global search. If |
|
|
|
Maximum number of rows returned per page, regardless of the |
|
|
|
Default sort direction ( |
|
|
|
Default column index for sorting when no valid |
|
|
|
Emit a log message when the handler is replaced. |
DataTables Request Parameters (Client → Server)#
The tool reads standard DataTables server-side parameters from the request:
Parameter |
Description |
|---|---|
|
DataTables draw counter (echoed back). |
|
Row offset for pagination. |
|
Number of rows to return (capped at |
|
Column index to sort by. |
|
Sort direction: |
|
Global search string. |
|
Per-column search string for column |
|
|
DataTableQuery — Direct Usage#
You can also use DataTableQuery directly, outside of the CherryPy tool, for example in non-standard handlers or tests.
result = DataTableQuery(
query=db.session.query(Message, UserObject.username).join(UserObject),
params=request.params,
search_columns=[Message.body, UserObject.username],
max_length=50,
default_dir="asc",
default_col=0,
).paginate(draw=1, start=0, length=25)
Query Requirements#
Use explicit columns in your query (via
.with_entities()or direct attribute selection). Queries returning full model instances (e.g.,session.query(MyModel)) are supported for serialization but not for index-based column sorting or per-column search.When using multi-column queries (named tuples), all selected expressions must be resolvable column attributes for sorting and filtering to work correctly.
Complete Example#
app.py:
from sqlalchemy import Column, Integer, String
import cherrypy
import cherrypy_foundation.tools.datatables # noqa — registers cherrypy.tools.datatables_out
env = cherrypy.tools.jinja2.create_env(
package_name=__package__,
)
Base = cherrypy.db.base
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String)
email = Column(String)
@cherrypy.tools.jinja2(on=False, env=env)
class Root:
@cherrypy.expose
@cherrypy.tools.jinja2(template='index.html')
def index(self):
# Serve your HTML page that initialises DataTables with serverSide: true
return {}
@cherrypy.expose
@cherrypy.tools.json_out()
@cherrypy.tools.datatables_out(
search_columns=[User.username, User.email],
max_length=50,
default_dir="asc",
default_col=0,
)
def data_json(self, **kwargs):
# Return a SQLAlchemy Query — the tool does the rest
return User.query.with_entitied(User.id, User.username, User.email)
cherrypy.tree.mount(Root(), '/')
cherrypy.engine.start()
cherrypy.engine.block()
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<title>test-datatables</title>
{{ catalog.render_assets() }}
</head>
<body>
{% set columns = [
{'name':'id', 'data':'id', 'title':_('ID')},
{'name':'username', 'data':'username', 'title':_('Username')},
{'name':'email', 'data':'email', 'title':_('Email')},
] %}
<cf:Datatable data="/data.json" :columns="columns" :server-side="True" :page-length="10"/>
</body>
</html>