diff --git a/src/wuttaweb/templates/appinfo/index.mako b/src/wuttaweb/templates/appinfo/index.mako
index 342617e..0997544 100644
--- a/src/wuttaweb/templates/appinfo/index.mako
+++ b/src/wuttaweb/templates/appinfo/index.mako
@@ -46,12 +46,53 @@
+ <${b}-collapse class="panel"
+ :open="false"
+ @open="openInstalledPackages">
+
+
+
+
+ ## TODO: for some reason buefy will "reuse" the icon
+ ## element in such a way that its display does not
+ ## refresh. so to work around that, we use different
+ ## structure for the two icons, so buefy is forced to
+ ## re-draw
+
+
+
+
+
+
+
+ Installed Packages
+
+
+
+
+
+ ${grid.render_vue_tag(ref='packagesGrid')}
+
+
+ ${b}-collapse>
+
%def>
<%def name="modify_vue_vars()">
${parent.modify_vue_vars()}
%def>
diff --git a/src/wuttaweb/templates/grids/vue_template.mako b/src/wuttaweb/templates/grids/vue_template.mako
index b161d9f..cb4f7a8 100644
--- a/src/wuttaweb/templates/grids/vue_template.mako
+++ b/src/wuttaweb/templates/grids/vue_template.mako
@@ -134,6 +134,9 @@
data: ${grid.vue_component}CurrentData,
loading: false,
+ ## nb. this tracks whether grid.fetchFirstData() happened
+ fetchedFirstData: false,
+
## sorting
% if grid.sortable:
sorters: ${json.dumps(grid.get_vue_active_sorters())|n},
@@ -230,6 +233,17 @@
return params
},
+ ## nb. this is meant to call for a grid which is hidden at
+ ## first, when it is first being shown to the user. and if
+ ## it was initialized with empty data set.
+ async fetchFirstData() {
+ if (this.fetchedFirstData) {
+ return
+ }
+ await this.fetchData()
+ this.fetchedFirstData = true
+ },
+
async fetchData() {
let params = new URLSearchParams(this.getBasicParams())
diff --git a/src/wuttaweb/views/master.py b/src/wuttaweb/views/master.py
index d43de9e..3422fe1 100644
--- a/src/wuttaweb/views/master.py
+++ b/src/wuttaweb/views/master.py
@@ -1208,8 +1208,10 @@ class MasterView(View):
self.set_labels(grid)
- for key in self.get_model_key():
- grid.set_link(key)
+ # TODO: i thought this was a good idea but if so it
+ # needs a try/catch in case of no model class
+ # for key in self.get_model_key():
+ # grid.set_link(key)
def grid_render_notes(self, record, key, value, maxlen=100):
"""
diff --git a/src/wuttaweb/views/settings.py b/src/wuttaweb/views/settings.py
index 087a7df..0cea55b 100644
--- a/src/wuttaweb/views/settings.py
+++ b/src/wuttaweb/views/settings.py
@@ -24,6 +24,11 @@
Views for app settings
"""
+import json
+import os
+import sys
+import subprocess
+
from collections import OrderedDict
from wuttjamaican.db.model import Setting
@@ -47,13 +52,47 @@ class AppInfoView(MasterView):
model_name = 'AppInfo'
model_title_plural = "App Info"
route_prefix = 'appinfo'
- has_grid = False
+ sort_on_backend = False
+ sort_defaults = 'name'
+ paginated = False
creatable = False
viewable = False
editable = False
deletable = False
configurable = True
+ grid_columns = [
+ 'name',
+ 'version',
+ 'editable_project_location',
+ ]
+
+ def get_grid_data(self, columns=None, session=None):
+ """ """
+
+ # nb. init with empty data, only load it upon user request
+ if not self.request.GET.get('partial'):
+ return []
+
+ # TODO: pretty sure this is not cross-platform. probably some
+ # sort of pip methods belong on the app handler? or it should
+ # have a pip handler for all that?
+ pip = os.path.join(sys.prefix, 'bin', 'pip')
+ output = subprocess.check_output([pip, 'list', '--format=json'], text=True)
+ data = json.loads(output.strip())
+
+ # must avoid null values for sort to work right
+ for pkg in data:
+ pkg.setdefault('editable_project_location', '')
+
+ return data
+
+ def configure_grid(self, g):
+ """ """
+ super().configure_grid(g)
+
+ g.sort_multiple = False
+
def configure_get_simple_settings(self):
""" """
return [
diff --git a/tests/views/test_master.py b/tests/views/test_master.py
index 5cf60db..21da2e5 100644
--- a/tests/views/test_master.py
+++ b/tests/views/test_master.py
@@ -424,7 +424,10 @@ class TestMasterView(WebTestCase):
url_prefix='/appinfo',
creatable=False):
view = master.MasterView(self.request)
- response = view.render_to_response('index', {})
+ response = view.render_to_response('index', {
+ # nb. grid is required for this template
+ 'grid': MagicMock(),
+ })
self.assertIsInstance(response, Response)
# bad template name causes error
diff --git a/tests/views/test_settings.py b/tests/views/test_settings.py
index f0485c1..208d0f2 100644
--- a/tests/views/test_settings.py
+++ b/tests/views/test_settings.py
@@ -18,6 +18,19 @@ class TestAppInfoView(WebTestCase):
def make_view(self):
return mod.AppInfoView(self.request)
+ def test_get_grid_data(self):
+ view = self.make_view()
+
+ # empty data by default
+ data = view.get_grid_data()
+ self.assertEqual(data, [])
+
+ # 'partial' request returns data
+ self.request.GET = {'partial': '1'}
+ data = view.get_grid_data()
+ self.assertIsInstance(data, list)
+ self.assertTrue(data)
+
def test_index(self):
# sanity/coverage check
view = self.make_view()