{"id":15067202,"url":"https://github.com/plumdog/flask_table","last_synced_at":"2025-04-04T11:13:59.298Z","repository":{"id":16652507,"uuid":"19407941","full_name":"plumdog/flask_table","owner":"plumdog","description":"Because writing HTML is fiddly and all of your tables are basically the same","archived":false,"fork":false,"pushed_at":"2024-04-17T20:47:18.000Z","size":229,"stargazers_count":210,"open_issues_count":21,"forks_count":45,"subscribers_count":13,"default_branch":"master","last_synced_at":"2024-10-30T03:42:08.319Z","etag":null,"topics":["flask","html","python","table"],"latest_commit_sha":null,"homepage":"http://flask-table.readthedocs.org/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/plumdog.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-05-03T17:10:03.000Z","updated_at":"2024-09-20T07:59:54.000Z","dependencies_parsed_at":"2024-06-18T17:22:01.719Z","dependency_job_id":null,"html_url":"https://github.com/plumdog/flask_table","commit_stats":{"total_commits":208,"total_committers":7,"mean_commits":"29.714285714285715","dds":0.09134615384615385,"last_synced_commit":"0ef1e6f195f83af9df45e76de77dd3207f30f679"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumdog%2Fflask_table","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumdog%2Fflask_table/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumdog%2Fflask_table/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumdog%2Fflask_table/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/plumdog","download_url":"https://codeload.github.com/plumdog/flask_table/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247166168,"owners_count":20894654,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flask","html","python","table"],"created_at":"2024-09-25T01:18:11.544Z","updated_at":"2025-04-04T11:13:59.280Z","avatar_url":"https://github.com/plumdog.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Flask Table\n===========\n\nBecause writing HTML is fiddly and all of your tables are basically\nthe same.\n\n[![Build Status](https://travis-ci.org/plumdog/flask_table.svg?branch=master)](https://travis-ci.org/plumdog/flask_table)\n[![Coverage Status](https://coveralls.io/repos/plumdog/flask_table/badge.png?branch=master)](https://coveralls.io/r/plumdog/flask_table?branch=master)\n[![PyPI version](https://badge.fury.io/py/Flask-Table.svg)](https://badge.fury.io/py/Flask-Table)\n\nInstallation\n============\n```\npip install flask-table\n```\n\nQuick Start\n===========\n\n```python\n# import things\nfrom flask_table import Table, Col\n\n# Declare your table\nclass ItemTable(Table):\n    name = Col('Name')\n    description = Col('Description')\n\n# Get some objects\nclass Item(object):\n    def __init__(self, name, description):\n        self.name = name\n        self.description = description\nitems = [Item('Name1', 'Description1'),\n         Item('Name2', 'Description2'),\n         Item('Name3', 'Description3')]\n# Or, equivalently, some dicts\nitems = [dict(name='Name1', description='Description1'),\n         dict(name='Name2', description='Description2'),\n         dict(name='Name3', description='Description3')]\n\n# Or, more likely, load items from your database with something like\nitems = ItemModel.query.all()\n\n# Populate the table\ntable = ItemTable(items)\n\n# Print the html\nprint(table.__html__())\n# or just {{ table }} from within a Jinja template\n```\n\nWhich gives something like:\n\n```html\n\u003ctable\u003e\n\u003cthead\u003e\u003ctr\u003e\u003cth\u003eName\u003c/th\u003e\u003cth\u003eDescription\u003c/th\u003e\u003c/tr\u003e\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\u003ctd\u003eName1\u003c/td\u003e\u003ctd\u003eDescription1\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003eName2\u003c/td\u003e\u003ctd\u003eDescription2\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003eName3\u003c/td\u003e\u003ctd\u003eDescription3\u003c/td\u003e\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n```\n\nOr as HTML:\n\n\u003ctable\u003e\n\u003cthead\u003e\u003ctr\u003e\u003cth\u003eName\u003c/th\u003e\u003cth\u003eDescription\u003c/th\u003e\u003c/tr\u003e\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\u003ctd\u003eName1\u003c/td\u003e\u003ctd\u003eDescription1\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003eName2\u003c/td\u003e\u003ctd\u003eDescription2\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003eName3\u003c/td\u003e\u003ctd\u003eDescription3\u003c/td\u003e\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\nFor more, see [the examples](#the-examples) for some complete,\nrunnable demonstrations.\n\nExtra things:\n-------------\n\n* The attribute used for each column in the declaration of the column\n  is used as the default thing to lookup in each item.\n\n* The thing that you pass when you populate the table must:\n  * be iterable\n  * contain dicts or objects - there's nothing saying it can't contain\n    some of each. See `examples/simple_sqlalchemy.py` for a database\n    example.\n\n* You can pass attributes to the `td` and `th` elements by passing a\n  dict of attributes as `td_html_attrs` or `th_html_attrs` when creating a\n  Col. Or as `column_html_attrs` to apply the attributes to both the `th`s\n  and the `td`s. (Any that you pass in `th_html_attrs` or `td_html_attrs` will\n  overwrite any that you also pass with `column_html_attrs`.) See\n  examples/column_html_attrs.py for more.\n\n* There are also LinkCol and ButtonCol that allow links and buttons,\n  which is where the Flask-specific-ness comes in.\n\n* There are also DateCol and DatetimeCol that format dates and\n  datetimes.\n\n* Oh, and BoolCol, which does Yes/No.\n\n* But most importantly, Col is easy to subclass.\n\nTable configuration and options\n===============================\n\nThe following options configure table-level options:\n\n* `thead_classes` - a list of classes to set on the `\u003cthead\u003e` element.\n\n* `no_items` - a string to display if no items are passed, defaults to\n  `'No Items'`.\n\n* `html_attrs` - a dictionary of attributes to set on the `\u003ctable\u003e` element.\n\n* `classes` - a list of strings to be set as the `class` attribute on\n  the `\u003ctable\u003e` element.\n\n* `table_id` - a string to set as the `id` attribute on the `\u003ctable\u003e` element.\n\n* `border` - whether the `border` should be set on the `\u003ctable\u003e` element.\n\nThese can be set in a few different ways:\n\na) set when defining the table class\n```python\nclass MyTable\n    classes = ['class1', 'class2']\n```\n\nb) passed in the `options` argument to `create_table`.\n```python\nMyTable = create_table(options={'table_id': 'my-table-id'})\n```\n\nc) passed to the table's `__init__`\n```python\ntable = MyTable(items, no_items='There is nothing', ...)\n```\n\nNote that a) and b) define an attribute on the table class, but c)\ndefines an attribute on the instance, so anything set like in c) will\noverride anything set in a) or b).\n\nEg:\n```python\nclass ItemTable(Table):\n    classes = ['myclass']\n    name = Col('Name')\ntable = ItemTable(items, classes=['otherclass'])\n```\nwould create a table with `class=\"otherclass\"`.\n\nIncluded Col Types\n==================\n\n* [`OptCol`](#more-about-optcol) - converts values according to a\n  dictionary of choices. Eg for turning stored codes into human\n  readable text.\n\n* [`BoolCol`](#more-about-boolcol) (subclass of OptCol) - converts\n  values to yes/no.\n\n* [`BoolNaCol`](#more-about-boolnacol) (subclass of BoolCol) - converts\n  values to yes/no/na.\n\n* [`DateCol`](#more-about-datecol) - for dates (uses `format_date`\n  from `babel.dates`).\n\n* [`DatetimeCol`](#more-about-datetimecol) - for date-times (uses\n  `format_datetime` from `babel.dates`).\n\n* [`LinkCol`](#more-about-linkcol) - creates a link by specifying an\n  endpoint and url_kwargs.\n\n* [`ButtonCol`](#more-about-buttoncol) (subclass of LinkCol) creates a\n  button that posts the the given address.\n\n* [`NestedTableCol`](#more-about-nestedtablecol) - allows nesting of\n  tables inside columns\n\nMore about `OptCol`\n-------------------\n\nWhen creating the column, you pass some `choices`. This should be a\ndict with the keys being the values that will be found on the item's\nattribute, and the values will be the text to be displayed.\n\nYou can also set a `default_key`, or a `default_value`. The default\nvalue will be used if the value found from the item isn't in the\nchoices dict. The default key works in much the same way, but means\nthat if your default is already in your choices, you can just point to\nit rather than repeat it.\n\nAnd you can use `coerce_fn` if you need to alter the value from the\nitem before looking it up in the dict.\n\nMore about `BoolCol`\n--------------------\n\nA subclass of `OptCol` where the `choices` are:\n\n```python\n{True: 'Yes', False: 'No'}\n```\n\nand the `coerce_fn` is `bool`. So the value from the item is coerced\nto a `bool` and then looked up in the choices to get the text to\ndisplay.\n\nIf you want to specify something other than \"Yes\" and \"No\", you can\npass `yes_display` and/or `no_display` when creating the column. Eg:\n\n```python\nclass MyTable(Table):\n    mybool = BoolCol('myboolcol', yes_display='Affirmative', no_display='Negatory')\n```\n\nMore about `BoolNaCol`\n----------------------\n\nJust like `BoolCol`, except displays `None` as \"N/A\". Can override\nwith the `na_display` argument.\n\nMore about `DateCol`\n--------------------\n\n[Requires Babel configuration](#babel-configuration)\n\nFormats a date from the item. Can specify a `date_format` to use,\nwhich defaults to `'short'`, which is passed to\n`babel.dates.format_date`.\n\nMore about `DatetimeCol`\n------------------------\n\n[Requires Babel configuration](#babel-configuration)\n\nFormats a datetime from the item. Can specify a `datetime_format` to\nuse, which defaults to `'short'`, which is passed to\n`babel.dates.format_datetime`.\n\nBabel configuration\n-------------------\n\nBabel uses a locale to determine how to format dates. It falls back to\nusing environment variables (`LC_TIME`, `LANGUAGE`, `LC_ALL`,\n`LC_CTYPE`, `LANG`), or can be configured\n[within Flask](https://pythonhosted.org/Flask-Babel/#configuration),\nallowing dynamic selection of locale.\n\nMake sure that one of the following is true:\n\n- at least one of the above environment variables is set to a valid locale\n- `BABEL_DEFAULT_LOCALE` is set as config on the Flask app to a valid locale\n- a `@babel.localeselector` function is configured\n\nNote that Babel reads the environment variables at import time, so if\nyou set these within Python, make sure it happens before you import\nFlask Table. The other two options would be considered \"better\",\nlargely for this reason.\n\nMore about `LinkCol`\n--------------------\n\nGives a way of putting a link into a `td`. You must specify an\n`endpoint` for the url. You should also specify some\n`url_kwargs`. This should be a dict which will be passed as the second\nargument of `url_for`, except the values will be treated as attributes\nto be looked up on the item. These keys obey the same rules as\nelsewhere, so can be things like `'category.name'` or `('category',\n'name')`.\n\nThe kwarg `url_kwargs_extra` allows passing of contants to the\nurl. This can be useful for adding constant GET params to a url.\n\nThe text for the link is acquired in *almost* the same way as with\nother columns. However, other columns can be given no `attr` or\n`attr_list` and will use the attribute that the column was given in\nthe table class, but `LinkCol` does not, and instead falls back to the\nheading of the column. This make more sense for things like an \"Edit\"\nlink. You can override this fallback with the `text_fallback` kwarg.\n\nSet attributes for anchor tag by passing `anchor_attrs`:\n```python\nname = LinkCol('Name', 'single_item', url_kwargs=dict(id='id'), anchor_attrs={'class': 'myclass'})\n```\n\nMore about `ButtonCol`\n----------------------\n\nHas all the same options as `LinkCol` but instead adds a form and a\nbutton that gets posted to the url.\n\nYou can pass a dict of attributes to add to the button element with\nthe `button_attrs` kwarg.\n\nYou can pass a dict of attributes to add to the form element with\nthe `form_attrs` kwarg.\n\nYou can pass a dict of hidden fields to add into the form element with\nthe `form_hidden_fields` kwargs. The keys will be used as the `name`\nattributes and the values as the `value` attributes.\n\nMore about `NestedTableCol`\n---------------------------\n\nThis column type makes it possible to nest tables in columns. For each\nnested table column you need to define a subclass of Table as you\nnormally would when defining a table. The name of that Table sub-class\nis the second argument to NestedTableCol.\n\nEg:\n\n```python\nclass MySubTable(Table):\n    a = Col('1st nested table col')\n    b = Col('2nd nested table col')\n\nclass MainTable(Table):\n    id = Col('id')\n    objects = NestedTableCol('objects', MySubTable)\n```\n\nSubclassing Col\n===============\n\n(Look in examples/subclassing.py for a more concrete example)\n\nSuppose our item has an attribute, but we don't want to output the\nvalue directly, we need to alter it first. If the value that we get\nfrom the item gives us all the information we need, then we can just\noverride the td_format method:\n\n```python\nclass LangCol(Col):\n    def td_format(self, content):\n        if content == 'en_GB':\n            return 'British English'\n        elif content == 'de_DE':\n            return 'German'\n        elif content == 'fr_FR':\n            return 'French'\n        else:\n            return 'Not Specified'\n```\n\nIf you need access to all of information in the item, then we can go a\nstage earlier in the process and override the td_contents method:\n\n```python\nfrom flask import Markup\n\ndef td_contents(self, i, attr_list):\n    # by default this does\n    # return self.td_format(self.from_attr_list(i, attr_list))\n    return Markup.escape(self.from_attr_list(i, attr_list) + ' for ' + item.name)\n```\n\nAt present, you do still need to be careful about escaping things as\nyou override these methods. Also, because of the way that the Markup\nclass works, you need to be careful about how you concatenate these\nwith other strings.\n\nManipulating `\u003ctr\u003e`s\n====================\n\n(Look in examples/rows.py for a more concrete example)\n\nSuppose you want to change something about the tr element for some or\nall items. You can do this by overriding your table's `get_tr_attrs`\nmethod. By default, this method returns an empty dict.\n\nSo, we might want to use something like:\n\n```python\nclass ItemTable(Table):\n    name = Col('Name')\n    description = Col('Description')\n\n    def get_tr_attrs(self, item):\n        if item.important():\n            return {'class': 'important'}\n        else:\n            return {}\n```\n\nwhich would give all trs for items that returned a true value for the\n`important()` method, a class of \"important\".\n\nDynamically Creating Tables\n===========================\n\n(Look in examples/dynamic.py for a more concrete example)\n\nYou can define a table dynamically too.\n\n```python\nTableCls = create_table('TableCls')\\\n    .add_column('name', Col('Name'))\\\n    .add_column('description', Col('Description'))\n```\n\nwhich is equivalent to\n\n```python\nclass TableCls(Table):\n    name = Col('Name')\n    description = Col('Description')\n```\n\nbut makes it easier to add columns dynamically.\n\nFor example, you may wish to only add a column based on a condition.\n\n```python\nTableCls = create_table('TableCls')\\\n    .add_column('name', Col('Name'))\n\nif condition:\n    TableCls.add_column('description', Col('Description'))\n```\n\nwhich is equivalent to\n\n```python\nclass TableCls(Table):\n    name = Col('Name')\n    description = Col('Description', show=condition)\n```\n\nthanks to the `show` option. Use whichever you think makes your code\nmore readable. Though you may still need the dynamic option for\nsomething like\n\n```python\nTableCls = create_table('TableCls')\nfor i in range(num):\n    TableCls.add_column(str(i), Col(str(i)))\n```\n\nWe can also set some extra options to the table class by passing `options` parameter to `create_table()`:\n```python\ntbl_options = dict(\n    classes=['cls1', 'cls2'],\n    thead_classes=['cls_head1', 'cls_head2'],\n    no_items='Empty')\nTableCls = create_table(options=tbl_options)\n\n# equals to\n\nclass TableCls(Table):\n    classes = ['cls1', 'cls2']\n    thead_classes = ['cls_head1', 'cls_head2']\n    no_items = 'Empty'\n```\n\nSortable Tables\n===============\n\n(Look in examples/sortable.py for a more concrete example)\n\nDefine a table and set its allow_sort attribute to True. Now all\ncolumns will be default try to turn their header into a link for\nsorting, unless you set allow_sort to False for a column.\n\nYou also must declare a sort_url method for that table. Given a\ncol_key, this determines the url for link in the header. If reverse is\nTrue, then that means that the table has just been sorted by that\ncolumn and the url can adjust accordingly, ie to now give the address\nfor the table sorted in the reverse direction. It is, however,\nentirely up to your flask view method to interpret the values given to\nit from this url and to order the results before giving the to the\ntable. The table itself will not do any reordering of the items it is\ngiven.\n\n```python\nclass SortableTable(Table):\n    name = Col('Name')\n    allow_sort = True\n\n    def sort_url(self, col_key, reverse=False):\n        if reverse:\n            direction =  'desc'\n        else:\n            direction = 'asc'\n        return url_for('index', sort=col_key, direction=direction)\n```\n\nThe Examples\n============\n\nThe [`examples`](/examples) directory contains a few pieces of sample code to show\nsome of the concepts and features. They are all intended to be\nrunnable. Some of them just output the code they generate, but some\n(just one, `sortable.py`, at present) actually creates a Flask app\nthat you can access.\n\nYou should be able to just run them directly with `python`, but if you\nhave cloned the repository for the sake of dev, and created a\nvirtualenv, you may find that they generate an import error for\n`flask_table`. This is because `flask_table` hasn't been installed,\nand can be rectified by running something like\n`PYTHONPATH=.:./lib/python3.3/site-packages python examples/simple.py`,\nwhich will use the local version of `flask_table`\nincluding any changes.\n\nAlso, if there is anything that you think is not clear and would be\nhelped by an example, please just ask and I'll happily write one. Only\nyou can help me realise which bits are tricky or non-obvious and help\nme to work on explaining the bits that need explaining.\n\nOther Things\n============\n\nAt the time of first writing, I was not aware of the work of\nDjango-Tables. However, I have now found it and started adapting ideas\nfrom it, where appropriate. For example, allowing items to be dicts as\nwell as objects.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplumdog%2Fflask_table","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplumdog%2Fflask_table","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplumdog%2Fflask_table/lists"}