fix: show installed python packages on appinfo page
This commit is contained in:
parent
3665d69e0c
commit
d15ac46184
|
@ -46,12 +46,53 @@
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<${b}-collapse class="panel"
|
||||||
|
:open="false"
|
||||||
|
@open="openInstalledPackages">
|
||||||
|
|
||||||
|
<template #trigger="props">
|
||||||
|
<div class="panel-heading"
|
||||||
|
style="cursor: pointer;"
|
||||||
|
role="button">
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
<b-icon v-if="props.open"
|
||||||
|
pack="fas"
|
||||||
|
icon="angle-down" />
|
||||||
|
|
||||||
|
<span v-if="!props.open">
|
||||||
|
<b-icon pack="fas"
|
||||||
|
icon="angle-right" />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<strong>Installed Packages</strong>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="panel-block">
|
||||||
|
<div style="width: 100%;">
|
||||||
|
${grid.render_vue_tag(ref='packagesGrid')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</${b}-collapse>
|
||||||
|
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="modify_vue_vars()">
|
<%def name="modify_vue_vars()">
|
||||||
${parent.modify_vue_vars()}
|
${parent.modify_vue_vars()}
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
ThisPageData.configFiles = ${json.dumps([dict(path=p, priority=i) for i, p in enumerate(config.get_prioritized_files(), 1)])|n}
|
ThisPageData.configFiles = ${json.dumps([dict(path=p, priority=i) for i, p in enumerate(config.get_prioritized_files(), 1)])|n}
|
||||||
|
|
||||||
|
ThisPage.methods.openInstalledPackages = function() {
|
||||||
|
this.$refs.packagesGrid.fetchFirstData()
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
|
|
@ -134,6 +134,9 @@
|
||||||
data: ${grid.vue_component}CurrentData,
|
data: ${grid.vue_component}CurrentData,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
|
||||||
|
## nb. this tracks whether grid.fetchFirstData() happened
|
||||||
|
fetchedFirstData: false,
|
||||||
|
|
||||||
## sorting
|
## sorting
|
||||||
% if grid.sortable:
|
% if grid.sortable:
|
||||||
sorters: ${json.dumps(grid.get_vue_active_sorters())|n},
|
sorters: ${json.dumps(grid.get_vue_active_sorters())|n},
|
||||||
|
@ -230,6 +233,17 @@
|
||||||
return params
|
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() {
|
async fetchData() {
|
||||||
|
|
||||||
let params = new URLSearchParams(this.getBasicParams())
|
let params = new URLSearchParams(this.getBasicParams())
|
||||||
|
|
|
@ -1208,8 +1208,10 @@ class MasterView(View):
|
||||||
|
|
||||||
self.set_labels(grid)
|
self.set_labels(grid)
|
||||||
|
|
||||||
for key in self.get_model_key():
|
# TODO: i thought this was a good idea but if so it
|
||||||
grid.set_link(key)
|
# 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):
|
def grid_render_notes(self, record, key, value, maxlen=100):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -24,6 +24,11 @@
|
||||||
Views for app settings
|
Views for app settings
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from wuttjamaican.db.model import Setting
|
from wuttjamaican.db.model import Setting
|
||||||
|
@ -47,13 +52,47 @@ class AppInfoView(MasterView):
|
||||||
model_name = 'AppInfo'
|
model_name = 'AppInfo'
|
||||||
model_title_plural = "App Info"
|
model_title_plural = "App Info"
|
||||||
route_prefix = 'appinfo'
|
route_prefix = 'appinfo'
|
||||||
has_grid = False
|
sort_on_backend = False
|
||||||
|
sort_defaults = 'name'
|
||||||
|
paginated = False
|
||||||
creatable = False
|
creatable = False
|
||||||
viewable = False
|
viewable = False
|
||||||
editable = False
|
editable = False
|
||||||
deletable = False
|
deletable = False
|
||||||
configurable = True
|
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):
|
def configure_get_simple_settings(self):
|
||||||
""" """
|
""" """
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -424,7 +424,10 @@ class TestMasterView(WebTestCase):
|
||||||
url_prefix='/appinfo',
|
url_prefix='/appinfo',
|
||||||
creatable=False):
|
creatable=False):
|
||||||
view = master.MasterView(self.request)
|
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)
|
self.assertIsInstance(response, Response)
|
||||||
|
|
||||||
# bad template name causes error
|
# bad template name causes error
|
||||||
|
|
|
@ -18,6 +18,19 @@ class TestAppInfoView(WebTestCase):
|
||||||
def make_view(self):
|
def make_view(self):
|
||||||
return mod.AppInfoView(self.request)
|
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):
|
def test_index(self):
|
||||||
# sanity/coverage check
|
# sanity/coverage check
|
||||||
view = self.make_view()
|
view = self.make_view()
|
||||||
|
|
Loading…
Reference in a new issue