feat: add basic Table views
very minimal so far, just laying foundations
This commit is contained in:
parent
d6a4c5e657
commit
6791abe96f
6 changed files with 212 additions and 0 deletions
6
docs/api/wuttaweb.views.tables.rst
Normal file
6
docs/api/wuttaweb.views.tables.rst
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
``wuttaweb.views.tables``
|
||||||
|
=========================
|
||||||
|
|
||||||
|
.. automodule:: wuttaweb.views.tables
|
||||||
|
:members:
|
||||||
|
|
@ -70,6 +70,7 @@ the narrative docs are pretty scant. That will eventually change.
|
||||||
api/wuttaweb.views.reports
|
api/wuttaweb.views.reports
|
||||||
api/wuttaweb.views.roles
|
api/wuttaweb.views.roles
|
||||||
api/wuttaweb.views.settings
|
api/wuttaweb.views.settings
|
||||||
|
api/wuttaweb.views.tables
|
||||||
api/wuttaweb.views.upgrades
|
api/wuttaweb.views.upgrades
|
||||||
api/wuttaweb.views.users
|
api/wuttaweb.views.users
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,18 @@
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
|
||||||
|
% if request.has_perm("tables.list"):
|
||||||
|
<wutta-button type="is-primary"
|
||||||
|
tag="a" href="${url('tables')}"
|
||||||
|
icon-left="table"
|
||||||
|
label="Database Tables"
|
||||||
|
once />
|
||||||
|
% endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
<nav class="panel">
|
<nav class="panel">
|
||||||
<p class="panel-heading">Configuration Files</p>
|
<p class="panel-heading">Configuration Files</p>
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ That will in turn include the following modules:
|
||||||
* :mod:`wuttaweb.views.roles`
|
* :mod:`wuttaweb.views.roles`
|
||||||
* :mod:`wuttaweb.views.users`
|
* :mod:`wuttaweb.views.users`
|
||||||
* :mod:`wuttaweb.views.upgrades`
|
* :mod:`wuttaweb.views.upgrades`
|
||||||
|
* :mod:`wuttaweb.views.tables`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -55,6 +56,7 @@ def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||||
config.include(mod("wuttaweb.views.roles"))
|
config.include(mod("wuttaweb.views.roles"))
|
||||||
config.include(mod("wuttaweb.views.users"))
|
config.include(mod("wuttaweb.views.users"))
|
||||||
config.include(mod("wuttaweb.views.upgrades"))
|
config.include(mod("wuttaweb.views.upgrades"))
|
||||||
|
config.include(mod("wuttaweb.views.tables"))
|
||||||
|
|
||||||
|
|
||||||
def includeme(config): # pylint: disable=missing-function-docstring
|
def includeme(config): # pylint: disable=missing-function-docstring
|
||||||
|
|
|
||||||
135
src/wuttaweb/views/tables.py
Normal file
135
src/wuttaweb/views/tables.py
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# wuttaweb -- Web App for Wutta Framework
|
||||||
|
# Copyright © 2024-2025 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Wutta Framework.
|
||||||
|
#
|
||||||
|
# Wutta Framework is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by the Free
|
||||||
|
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
|
# later version.
|
||||||
|
#
|
||||||
|
# Wutta Framework is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
# more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Table Views
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from wuttaweb.views import MasterView
|
||||||
|
|
||||||
|
|
||||||
|
class TableView(MasterView):
|
||||||
|
"""
|
||||||
|
Master view showing all tables in the :term:`app database`.
|
||||||
|
|
||||||
|
Default route prefix is ``tables``.
|
||||||
|
|
||||||
|
Notable URLs provided by this class:
|
||||||
|
|
||||||
|
* ``/tables/``
|
||||||
|
"""
|
||||||
|
|
||||||
|
model_name = "table"
|
||||||
|
model_title = "Database Table"
|
||||||
|
model_key = "name"
|
||||||
|
filterable = False
|
||||||
|
sortable = True
|
||||||
|
sort_on_backend = False
|
||||||
|
paginated = True
|
||||||
|
paginate_on_backend = False
|
||||||
|
creatable = False
|
||||||
|
editable = False
|
||||||
|
deletable = False
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
"name": "Table Name",
|
||||||
|
}
|
||||||
|
|
||||||
|
grid_columns = [
|
||||||
|
"name",
|
||||||
|
"schema",
|
||||||
|
# "row_count",
|
||||||
|
]
|
||||||
|
|
||||||
|
sort_defaults = "name"
|
||||||
|
|
||||||
|
form_fields = [
|
||||||
|
"name",
|
||||||
|
"schema",
|
||||||
|
# "row_count",
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_grid_data( # pylint: disable=empty-docstring
|
||||||
|
self, columns=None, session=None
|
||||||
|
):
|
||||||
|
""" """
|
||||||
|
model = self.app.model
|
||||||
|
data = []
|
||||||
|
|
||||||
|
for table in model.Base.metadata.tables.values():
|
||||||
|
data.append(
|
||||||
|
{
|
||||||
|
"name": table.name,
|
||||||
|
"schema": table.schema or "",
|
||||||
|
# "row_count": 42,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def configure_grid(self, grid): # pylint: disable=empty-docstring
|
||||||
|
""" """
|
||||||
|
g = grid
|
||||||
|
super().configure_grid(g)
|
||||||
|
|
||||||
|
# schema
|
||||||
|
g.set_searchable("schema")
|
||||||
|
|
||||||
|
# name
|
||||||
|
g.set_searchable("name")
|
||||||
|
g.set_link("name")
|
||||||
|
|
||||||
|
def get_instance(self): # pylint: disable=empty-docstring
|
||||||
|
""" """
|
||||||
|
if "_cached_instance" not in self.__dict__:
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
name = self.request.matchdict["name"]
|
||||||
|
table = model.Base.metadata.tables[name]
|
||||||
|
data = {
|
||||||
|
"name": table.name,
|
||||||
|
"schema": table.schema or "",
|
||||||
|
# "row_count": 42,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.__dict__["_cached_instance"] = data
|
||||||
|
|
||||||
|
return self.__dict__["_cached_instance"]
|
||||||
|
|
||||||
|
def get_instance_title(self, instance): # pylint: disable=empty-docstring
|
||||||
|
""" """
|
||||||
|
return instance["name"]
|
||||||
|
|
||||||
|
|
||||||
|
def defaults(config, **kwargs): # pylint: disable=missing-function-docstring
|
||||||
|
base = globals()
|
||||||
|
|
||||||
|
TableView = kwargs.get( # pylint: disable=invalid-name,redefined-outer-name
|
||||||
|
"TableView", base["TableView"]
|
||||||
|
)
|
||||||
|
TableView.defaults(config)
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config): # pylint: disable=missing-function-docstring
|
||||||
|
defaults(config)
|
||||||
56
tests/views/test_tables.py
Normal file
56
tests/views/test_tables.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from wuttaweb.testing import WebTestCase
|
||||||
|
from wuttaweb.views import tables as mod
|
||||||
|
|
||||||
|
|
||||||
|
class TestUpgradeView(WebTestCase):
|
||||||
|
|
||||||
|
def make_view(self):
|
||||||
|
return mod.TableView(self.request)
|
||||||
|
|
||||||
|
def test_includeme(self):
|
||||||
|
self.pyramid_config.include("wuttaweb.views.tables")
|
||||||
|
|
||||||
|
def test_get_grid_data(self):
|
||||||
|
view = self.make_view()
|
||||||
|
data = view.get_grid_data()
|
||||||
|
self.assertIsInstance(data, list)
|
||||||
|
self.assertGreater(len(data), 0)
|
||||||
|
table = data[0]
|
||||||
|
self.assertIsInstance(table, dict)
|
||||||
|
self.assertIn("name", table)
|
||||||
|
self.assertIn("schema", table)
|
||||||
|
|
||||||
|
def test_configure_grid(self):
|
||||||
|
model = self.app.model
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
# sanity / coverage check
|
||||||
|
grid = view.make_grid(columns=["name", "schema"])
|
||||||
|
view.configure_grid(grid)
|
||||||
|
|
||||||
|
def test_get_instance(self):
|
||||||
|
model = self.app.model
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
with patch.object(self.request, "matchdict", new={"name": "person"}):
|
||||||
|
|
||||||
|
table1 = view.get_instance()
|
||||||
|
self.assertIsInstance(table1, dict)
|
||||||
|
self.assertIn("name", table1)
|
||||||
|
self.assertEqual(table1["name"], "person")
|
||||||
|
self.assertIn("schema", table1)
|
||||||
|
|
||||||
|
table2 = view.get_instance()
|
||||||
|
self.assertIs(table2, table1)
|
||||||
|
self.assertEqual(table2["name"], "person")
|
||||||
|
|
||||||
|
def test_get_instance_title(self):
|
||||||
|
model = self.app.model
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
table = {"name": "poser_foo"}
|
||||||
|
self.assertEqual(view.get_instance_title(table), "poser_foo")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue