Tabler UI#2892
Conversation
|
This is huge! Great work, really appreciate the effort, thanks! |
|
@ElLorans What are the steps that we need to do to push this to the finish line? |
|
Replying to my 2 comments and fixing the tests (but this I can do myself) |
@ElLorans Fixed the tests in mariofix#1 cc @mariofix |
|
I see you added some static files in the end. What is missing? |
Yes for Tabler version 1.4.0, the switch stays to allow the use of CDN if you prefer that way. I'm already working with the nigthly version of tabler so i should have a fast update when they release a new version. If you have no more comments, this pr is ready. -- Edit -- <script>
window.addEventListener('error', function(e) {
var suppress = [
'helpers',
'form',
'actions'
];
if (e.filename && suppress.some(function(f) { return e.filename.includes(f); })) {
e.preventDefault();
console.warn('Suppressed error in ' + e.filename + ':', e.message);
}
}, true);
</script>this code silences errors from the files loaded by flask-admin that area made for bootstrap4, this is a dirty fix to allow the js for tabler to work |
|
If you already added all static files, maybe we can remove the option of using the CDN? What is the advantage? I was proposing to have the first PR without static assets to simplify the review, but if you add them, I don't think we need the CDN anymore. |
remove that switch then? |
|
| TablerLayout = t.Literal["vertical", "fluid", "condensed"] | ||
|
|
||
|
|
||
| def _validate_choice(value: str, choices: tuple[str, ...]) -> None: |
There was a problem hiding this comment.
Asking for your honest opinion here: after the t.Literal, do we still need this or is the type hint enough?
Merged from mariofix/flask-admin-tablerui Tabler version 1.4.0
…rts --disable-error-code name-defined --no-warn-unused-ignores --allow-subclassing-any examples
examples/tabler/main.py:73: error: Missing type parameters for generic type "dict" [type-arg]
meta_data: Mapped[dict] = mapped_column(JSON, default=dict, server_default=text("'{}'"))
^
Found 1 error in 1 file (checked 55 source files)
typing: exit 1 (23.45 seconds) /home/umpirsky/Projects/flask-admin> mypy --python-version 3.14 --ignore-missing-imports --disable-error-code name-defined --no-warn-unused-ignores --allow-subclassing-any examples pid=4036247
typing: FAIL code 1 (72.73=setup[2.26]+cmd[23.15,23.88,23.45] seconds)
evaluation failed :( (72.82 seconds)
style: venv> .venv/bin/uv venv -p /home/umpirsky/Projects/flask-admin/.venv/bin/python --allow-existing --python-preference system /home/umpirsky/Projects/flask-admin/.tox/style style: uv-sync> uv sync --locked --python-preference system --extra all --no-default-groups --no-editable --reinstall-package Flask-Admin --group pre-commit -p /home/umpirsky/Projects/flask-admin/.venv/bin/python style: commands[0]> pre-commit run --all-files [INFO] Initializing environment for https://github.com/astral-sh/ruff-pre-commit. [INFO] Initializing environment for https://github.com/pre-commit/pre-commit-hooks. [INFO] Installing environment for https://github.com/astral-sh/ruff-pre-commit. [INFO] Once installed this environment will be reused. [INFO] This may take a few minutes... [INFO] Using pre-commit with uv 0.11.6 via pre-commit-uv 4.2.0 [INFO] Installing environment for https://github.com/pre-commit/pre-commit-hooks. [INFO] Once installed this environment will be reused. [INFO] This may take a few minutes... [INFO] Using pre-commit with uv 0.11.6 via pre-commit-uv 4.2.0 ruff.....................................................................Failed - hook id: ruff - exit code: 1 - files were modified by this hook examples/tabler/main.py:69:89: E501 Line too long (102 > 88) | 67 | title: Mapped[str] = mapped_column(String(64)) 68 | content: Mapped[Text] = mapped_column(Text) 69 | meta_data: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, server_default=text("'{}'")) | ^^^^^^^^^^^^^^ E501 70 | 71 | def __repr__(self): | examples/tabler/main.py:178:89: E501 Line too long (89 > 88) | 176 | # admin.add_menu_item(MenuDivider(), target_category="Links") 177 | # admin.add_link( 178 | # MenuLink(name="External link", url="http://www.example.com/", category="Links") | ^ E501 179 | # ) | flask_admin/tests/tabler/test_renders.py:64:89: E501 Line too long (92 > 88) | 62 | ], 63 | ), 64 | ("condensed", ["data-tabler-layout=\"condensed\"", "tabler-admin-navbar-collapse"]), | ^^^^ E501 65 | ], 66 | ) | Fixed 7 errors: - examples/tabler/main.py: 5 × F401 (unused-import) 1 × I001 (unsorted-imports) - flask_admin/tests/tabler/test_renders.py: 1 × I001 (unsorted-imports) Found 10 errors (7 fixed, 3 remaining). ruff-format..............................................................Failed - hook id: ruff-format - files were modified by this hook 4 files reformatted, 167 files left unchanged check for merge conflicts................................................Passed debug statements (python)................................................Passed fix utf-8 byte order marker..............................................Passed trim trailing whitespace.................................................Passed fix end of files.........................................................Failed - hook id: end-of-file-fixer - exit code: 1 - files were modified by this hook Fixing flask_admin/tests/tabler/__init__.py style: exit 1 (10.35 seconds) /home/umpirsky/Projects/flask-admin> pre-commit run --all-files pid=4052435 style: FAIL code 1 (12.66=setup[2.31]+cmd[10.35] seconds) evaluation failed :( (12.72 seconds)
|
Remaining issues:
3. `column_editable_list` not working. #2847 could help? I would appreciate @hasansezertasan opinion on how to proceed.
|
I prefer CDN for my use case. I use this on embedded hardware and want to minimize traffic. |
|
Column editable list works in #2910, so we only need to fix fileadmin. |
I haven't had much time this week, i commented out to try to fix it within flask-admin, i assume filters don't work in this PR, and there should be errors in the javascript console. |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds an experimental Tabler-based theme to Flask-Admin, including templates/assets, icon support, tests, and an example app to demonstrate configuration.
Changes:
- Introduce
TablerThemeinflask_admin/theme.pywith layout + theme attribute options. - Add Tabler template set + supporting JS/CSS for modals, filters, actions, and pages.
- Add Tabler icon type constant/docs, update boolean formatter styling, add tests + a runnable example.
Reviewed changes
Copilot reviewed 42 out of 51 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| flask_admin/theme.py | Adds TablerTheme dataclass + layout validation and theme option fields. |
| flask_admin/tests/tabler/test_renders.py | Adds render tests asserting layout markers and table CSS for Tabler templates. |
| flask_admin/templates/tabler/admin/static.html | Adds a theme-local static URL macro for Tabler templates. |
| flask_admin/templates/tabler/admin/rediscli/response.html | Adds Tabler Redis CLI response rendering template. |
| flask_admin/templates/tabler/admin/rediscli/console.html | Adds Tabler Redis CLI console page template. |
| flask_admin/templates/tabler/admin/model/row_actions.html | Adds Tabler row action macros (view/edit/delete). |
| flask_admin/templates/tabler/admin/model/modals/edit.html | Adds Tabler modal edit template. |
| flask_admin/templates/tabler/admin/model/modals/details.html | Adds Tabler modal details template. |
| flask_admin/templates/tabler/admin/model/modals/create.html | Adds Tabler modal create template. |
| flask_admin/templates/tabler/admin/model/list.html | Adds Tabler model list template with filters/actions/modals. |
| flask_admin/templates/tabler/admin/model/layout.html | Adds Tabler model helper macros (filters/search/page size/export). |
| flask_admin/templates/tabler/admin/model/inline_list_base.html | Adds Tabler inline form list rendering base. |
| flask_admin/templates/tabler/admin/model/inline_form.html | Adds Tabler inline form field renderer. |
| flask_admin/templates/tabler/admin/model/inline_field_list.html | Adds Tabler inline field list rendering with errors. |
| flask_admin/templates/tabler/admin/model/edit.html | Adds Tabler model edit page template. |
| flask_admin/templates/tabler/admin/model/details.html | Adds Tabler model details page template. |
| flask_admin/templates/tabler/admin/model/create.html | Adds Tabler model create page template. |
| flask_admin/templates/tabler/admin/master.html | Tabler master template extending admin_base_template. |
| flask_admin/templates/tabler/admin/lib.html | Adds Tabler-flavored shared macros (forms, pager, modal helpers). |
| flask_admin/templates/tabler/admin/layout.html | Adds Tabler menu + message macros including Tabler icon rendering. |
| flask_admin/templates/tabler/admin/index.html | Adds Tabler index template scaffold. |
| flask_admin/templates/tabler/admin/file/modals/form.html | Adds Tabler FileAdmin modal form template. |
| flask_admin/templates/tabler/admin/file/list.html | Adds Tabler FileAdmin list view template with actions + modals. |
| flask_admin/templates/tabler/admin/file/form.html | Adds Tabler FileAdmin upload/mkdir/rename form template. |
| flask_admin/templates/tabler/admin/base.html | Adds full Tabler base layout, assets, and theme toggle logic. |
| flask_admin/templates/tabler/admin/actions.html | Adds Tabler actions dropdown/form/script macros. |
| flask_admin/templates/tabler/admin/_theme_toggle.html | Adds dark/light toggle markup for header/sidebar. |
| flask_admin/static/admin/js/tabler_modal.js | Adds JS to load modal content via fetch when opened. |
| flask_admin/static/admin/js/tabler_filters.js | Adds JS to manage filter UI for Tabler model list. |
| flask_admin/static/admin/css/tabler/rediscli.css | Adds Tabler Redis CLI styling. |
| flask_admin/static/admin/css/tabler/admin.css | Adds Tabler admin UI styling helpers. |
| flask_admin/model/typefmt.py | Extends bool formatter output to include Tabler icon classes + color classes. |
| flask_admin/model/base.py | Documents new ICON_TYPE_TABLER option for model menu icons. |
| flask_admin/consts.py | Adds ICON_TYPE_TABLER = "ti". |
| flask_admin/base.py | Documents new ICON_TYPE_TABLER option for menu icons. |
| examples/tabler/static/js/active-layout.js | Adds example-only JS to highlight active theme options in menu. |
| examples/tabler/pyproject.toml | Adds example project metadata/deps for Tabler demo app. |
| examples/tabler/main.py | Adds runnable Tabler example app demonstrating layouts/theme switching. |
| examples/tabler/data.py | Adds sample DB generator for the Tabler example. |
| examples/tabler/README.md | Adds docs for running and using the Tabler example. |
| examples/tabler/.python-version | Pins Python version for the example. |
| doc/changelog.rst | Notes experimental TablerTheme in changelog. |
Files not reviewed (1)
- flask_admin/static/tabler/css/tabler-themes.min.css: Language not supported
Comments suppressed due to low confidence (5)
flask_admin/templates/tabler/admin/model/details.html:1
- This template references
admin_static.url(...)but never importsadmin/static.htmlasadmin_static(unlike other Tabler templates). This will raise a JinjaUndefinedErrorat render time. Add{% import 'admin/static.html' as admin_static with context %}near the top of the file.
flask_admin/templates/tabler/admin/model/details.html:1 - This template references
admin_static.url(...)but never importsadmin/static.htmlasadmin_static(unlike other Tabler templates). This will raise a JinjaUndefinedErrorat render time. Add{% import 'admin/static.html' as admin_static with context %}near the top of the file.
flask_admin/templates/tabler/admin/model/row_actions.html:1 - This embeds translated text inside a single-quoted JS string in an
onclickattribute. If the translation contains an apostrophe (or other characters that become'after HTML entity decoding), it can break the JS string and potentially enable injection. Prefer passing a JSON-encoded string, e.g.faHelpers.safeConfirm({{ _gettext('...')|tojson }});, or move the handler into JS and read text from adata-*attribute encoded with|tojson.
flask_admin/tests/tabler/test_renders.py:1 TablerTheme.__post_init__now validateslayoutand raisesValueErrorfor unsupported values, but this new behavior isn’t exercised here. Add a small test asserting invalid layouts raise (e.g.,TablerTheme(layout='bad')) so the validation and error message remain stable.
flask_admin/templates/tabler/admin/rediscli/console.html:1<input>is a void element in HTML and should not be closed with</input>. Use<input type=\"text\">(or<input type=\"text\" />) to avoid invalid markup and improve compatibility with strict HTML tooling.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| {% if _t.use_cdn %} | ||
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler.min.css" /> | ||
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/css/tabler-themes.min.css" /> | ||
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tabler-icons/3.35.0/tabler-icons.min.css" /> | ||
| {% else %} | ||
| <link href="{{ url_for('admin.static', filename='tabler/css/tabler.min.css', v='1.4.0') }}" rel="stylesheet" {{ admin_csp_nonce_attribute }}> | ||
| <link href="{{ url_for('admin.static', filename='tabler/css/tabler-themes.min.css', v='1.4.0') }}" rel="stylesheet" {{ admin_csp_nonce_attribute }}> | ||
| <link href="{{ url_for('admin.static', filename='tabler/icons/tabler-icons.min.css', v='3.40.0') }}" rel="stylesheet" {{ admin_csp_nonce_attribute }}> | ||
| {% endif %} | ||
| <link href="{{ url_for('admin.static', filename='admin/css/tabler/admin.css') }}" rel="stylesheet" {{ admin_csp_nonce_attribute }}> |
| db.drop_all() | ||
| db.create_all() | ||
|
|
||
| first_names = [ |
| ] | ||
| last_names = [ |
| ] | ||
|
|
||
| for i in range(len(first_names)): | ||
| user = User() | ||
| user.name = first_names[i] + " " + last_names[i] | ||
| user.email = first_names[i].lower() + "@example.com" |
| {% endif %} | ||
| <button type="submit" class="btn btn-sm btn-ghost-danger" | ||
| title="{{ _gettext('Delete Directory') }}" | ||
| onclick="return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\' recursively?', name=name) }}')"> |
| {% endif %} | ||
| <button type="submit" class="btn btn-sm btn-ghost-danger" | ||
| title="{{ _gettext('Delete File') }}" | ||
| onclick="return confirm('{{ _gettext('Are you sure you want to delete \\\'%(name)s\\\'?', name=name) }}')"> |
| fetch(href) | ||
| .then(function (response) { return response.text(); }) | ||
| .then(function (html) { modalContent.innerHTML = html; }); |
| function getCount(name) { | ||
| const idx = name.indexOf('_'); | ||
| if (idx === -1) return 0; | ||
| return parseInt(name.substr(3, idx - 3), 10); |











This PR adds Tabler UI into Flask-Admin, an early version.
fixes #2443
See
tablerfolder included in examplesoptions allowed
you can go to https://preview.tabler.io to see combinations
let me know what you think