data table¶
core4os ships a request handler
core4.api.v1.tool.datatable.CoreDataTableRequest to easily render
tabular data.
This document demonstrates the usage of this handler. Before we start implementing an API service with an appropriate handler we have to create some sample data.
data preparation¶
Let’s create a MongoDB collection with some data.
mongo = pymongo.MongoClient("mongodb://core:654321@localhost:27017")
coll = mongo.core4test.data1
segment = ["segment A", "segment B", "segment C", "segment D",
"segment E"]
coll.delete_many({})
t0 = datetime.datetime(2014, 1, 1)
for i in range(1000):
t0 += datetime.timedelta(hours=4)
coll.insert_one({
"timestamp": t0,
"idx": i + 1,
"real": random.random() * 100.,
"value": random.randint(1, 20),
"segment": segment[random.randint(0, 4)]
})
This creates 10000 records in MongoDB database core4test, collection
data1.
We will now implement a request handler delivering this data.
column specification¶
The following list defines the columns. We will use it in the next step.
COLS = [
{"name": "_id", "key": True, "hide": True, "editable": False},
{"name": "idx", "label": "#", "format": "{:05d}"},
{"name": "segment", "label": "Segment", "format": "{:s}" ,
{"name": "real", "label": "Realzahl", "format": "{:+1.3f}" ,
{"name": "value", "label": "Ganzzahl", "format": "{:d}" ,
{"name": "timestamp", "label": "Zeitstempel", "format": "{:%d.%m.%Y at %H:%M:%S}"}
]
With one exception of column _id all columns have a label and a format.
Column _id is defined as hidden and not editable but as the key
column. This is because the _id acts as the mere identifier of the data row.
No need to display it.
We will now assign this column definition to our request handler.
request handler¶
Inheriting from core4.api.v1.tool.datatable.CoreDataTableRequest we
will implement a request handler delivering the data from MongoDB collection
core4test.data1.
class TableHandler(CoreDataTableRequest):
author = "mra"
title = "datatable example"
column = COLS
async def length(self, filter):
pass
async def query(self, skip, limit, filter, sort_by):
pass
The three elements column, .length and .query are the minimum
requirements of a data table request handler.
The .length method must return the number of records after applying a
filter argument. The .query method must return the records. This method
is supposed to process the filter argument, too. Furthermore it must respect
the number of records to skip, the number of records to retrieve (limit) and
the expected sort order (sort_by).
The following snippet implements the final version of the request handler.
class TableHandler(CoreDataTableRequest):
author = "mra"
title = "datatable example"
column = COLS
@property
def collection(self):
return self.config.tests.data1_collection
def myfilter(self, filter):
query = self.convert_filter(filter)
if not isinstance(query, dict):
query = {}
return query
async def length(self, filter):
return await self.collection.count_documents(self.myfilter(filter))
async def query(self, skip, limit, filter, sort_by):
return await self.collection.find(
self.myfilter(filter)).sort(
sort_by).skip(
skip).limit(
limit).to_list(
None)
The implementation wraps collection access into a property method
.collection and uses .myfilter method to to preprocess the passed
filter attribute. This attribute is always passed as a plain string and
requires further processing before it can be passed to the query.
Before we turn to the usage of this handler, we conclude this section with the
api container and the usual Python __main__ section to use the web service
with python.
class TableServer(CoreApiContainer):
rules = [
("/table", TableHandler)
]
if __name__ == '__main__':
from core4.api.v1.tool.functool import serve
serve(TableServer)
The full Python code can be found in the core4os repository, directory
core4/tests/other/datatable.py.
using the handler¶
The following example session requires a running API container and uses
requests module to work with the data.
from requests import get, post
url = "http://localhost:5001/tests/table"
username = "admin"
password = "hans"
login = get(
"http://localhost:5001/core4/api/v1/login?username=%s&password=%s" %(
username, password))
# get results with default settings, display available keys
get(url, cookies=login.cookies).json()["data"].keys()
# show pagination
get(url, cookies=login.cookies).json()["data"]["paging"]
# show columns
get(url, cookies=login.cookies).json()["data"]["column"]
# show page 2
post(url, json={"page": 1}, cookies=login.cookies).json()["data"]["paging"]
# show page 2 with 25 per page and save settings with POST
post(url, json={"page": 1, "per_page" :25},
cookies=login.cookies).json()["data"]["paging"]
# show page 3 with saved settings (25 records per page)
post(url, json={"page": 2}, cookies=login.cookies).json()["data"]["paging"]
# show only 2 columns, change column order and save settings again
selected_column = [
{'hide': False, 'name': 'segment'},
{'hide': False, 'name': 'real'},
{'hide': True, 'name': '_id'},
{'hide': True, 'name': 'idx'},
{'hide': True, 'name': 'value'},
{'hide': True, 'name': 'timestamp'}]
post(url, json={"column": selected_column},
cookies=login.cookies).json()["data"]["paging"]
# show page 4 with saved settings
get(url + "?page=3", cookies=login.cookies).json()["data"]["column"]
post(url, json={"page": 3}, cookies=login.cookies).json()["data"]["column"]
# download data with saved settings
for line in get(url + "?download=1", cookies=login.cookies, stream=True):
print(line.decode("utf-8"), end="")
# download all columns but do not touch saved settings
for line in get(url + "?download=1&reset=True", cookies=login.cookies, stream=True):
print(line.decode("utf-8"), end="")
# go back to saved settings
for line in post(url, json={"download": True}, cookies=login.cookies, stream=True):
print(line.decode("utf-8"), end="")
Before we turn to the frontend side, please have a look at all available options at datatable request handler.