feat: add basic Grid
class, and /settings master view
This commit is contained in:
parent
2ad1ae9c49
commit
754e0989e4
6
docs/api/wuttaweb/grids.base.rst
Normal file
6
docs/api/wuttaweb/grids.base.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
``wuttaweb.grids.base``
|
||||
=======================
|
||||
|
||||
.. automodule:: wuttaweb.grids.base
|
||||
:members:
|
6
docs/api/wuttaweb/grids.rst
Normal file
6
docs/api/wuttaweb/grids.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
``wuttaweb.grids``
|
||||
==================
|
||||
|
||||
.. automodule:: wuttaweb.grids
|
||||
:members:
|
|
@ -12,6 +12,8 @@
|
|||
db
|
||||
forms
|
||||
forms.base
|
||||
grids
|
||||
grids.base
|
||||
handler
|
||||
helpers
|
||||
menus
|
||||
|
|
|
@ -26,6 +26,7 @@ Forms Library
|
|||
The ``wuttaweb.forms`` namespace contains the following:
|
||||
|
||||
* :class:`~wuttaweb.forms.base.Form`
|
||||
* :class:`~wuttaweb.forms.base.FieldList`
|
||||
"""
|
||||
|
||||
from .base import Form
|
||||
from .base import Form, FieldList
|
||||
|
|
|
@ -44,7 +44,8 @@ class FieldList(list):
|
|||
of :class:`python:list`.
|
||||
|
||||
You normally would not need to instantiate this yourself, but it
|
||||
is used under the hood for e.g. :attr:`Form.fields`.
|
||||
is used under the hood for :attr:`Form.fields` as well as
|
||||
:attr:`~wuttaweb.grids.base.Grid.columns`.
|
||||
"""
|
||||
|
||||
def insert_before(self, field, newfield):
|
||||
|
|
31
src/wuttaweb/grids/__init__.py
Normal file
31
src/wuttaweb/grids/__init__.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# wuttaweb -- Web App for Wutta Framework
|
||||
# Copyright © 2024 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/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Grids Library
|
||||
|
||||
The ``wuttaweb.grids`` namespace contains the following:
|
||||
|
||||
* :class:`~wuttaweb.grids.base.Grid`
|
||||
"""
|
||||
|
||||
from .base import Grid
|
203
src/wuttaweb/grids/base.py
Normal file
203
src/wuttaweb/grids/base.py
Normal file
|
@ -0,0 +1,203 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# wuttaweb -- Web App for Wutta Framework
|
||||
# Copyright © 2024 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/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Base grid classes
|
||||
"""
|
||||
|
||||
from pyramid.renderers import render
|
||||
from webhelpers2.html import HTML
|
||||
|
||||
from wuttaweb.forms import FieldList
|
||||
|
||||
|
||||
class Grid:
|
||||
"""
|
||||
Base class for all grids.
|
||||
|
||||
:param request: Reference to current :term:`request` object.
|
||||
|
||||
:param columns: List of column names for the grid. This is
|
||||
optional; if not specified an attempt will be made to deduce
|
||||
the list automatically. See also :attr:`columns`.
|
||||
|
||||
.. note::
|
||||
|
||||
Some parameters are not explicitly described above. However
|
||||
their corresponding attributes are described below.
|
||||
|
||||
Grid instances contain the following attributes:
|
||||
|
||||
.. attribute:: key
|
||||
|
||||
Presumably unique key for the grid; used to track per-grid
|
||||
sort/filter settings etc.
|
||||
|
||||
.. attribute:: columns
|
||||
|
||||
:class:`~wuttaweb.forms.base.FieldList` instance containing
|
||||
string column names for the grid. Columns will appear in the
|
||||
same order as they are in this list.
|
||||
|
||||
See also :meth:`set_columns()`.
|
||||
|
||||
.. attribute:: data
|
||||
|
||||
Data set for the grid. This should either be a list of dicts
|
||||
(or objects with dict-like access to fields, corresponding to
|
||||
model records) or else an object capable of producing such a
|
||||
list, e.g. SQLAlchemy query.
|
||||
|
||||
.. attribute:: vue_tagname
|
||||
|
||||
String name for Vue component tag. By default this is
|
||||
``'wutta-grid'``. See also :meth:`render_vue_tag()`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
request,
|
||||
key=None,
|
||||
columns=None,
|
||||
data=None,
|
||||
vue_tagname='wutta-grid',
|
||||
):
|
||||
self.request = request
|
||||
self.key = key
|
||||
self.data = data
|
||||
self.vue_tagname = vue_tagname
|
||||
|
||||
self.config = self.request.wutta_config
|
||||
self.app = self.config.get_app()
|
||||
|
||||
if columns is not None:
|
||||
self.set_columns(columns)
|
||||
else:
|
||||
self.columns = None
|
||||
|
||||
@property
|
||||
def vue_component(self):
|
||||
"""
|
||||
String name for the Vue component, e.g. ``'WuttaGrid'``.
|
||||
|
||||
This is a generated value based on :attr:`vue_tagname`.
|
||||
"""
|
||||
words = self.vue_tagname.split('-')
|
||||
return ''.join([word.capitalize() for word in words])
|
||||
|
||||
def set_columns(self, columns):
|
||||
"""
|
||||
Explicitly set the list of grid columns.
|
||||
|
||||
This will overwrite :attr:`columns` with a new
|
||||
:class:`~wuttaweb.forms.base.FieldList` instance.
|
||||
|
||||
:param columns: List of string column names.
|
||||
"""
|
||||
self.columns = FieldList(columns)
|
||||
|
||||
def render_vue_tag(self, **kwargs):
|
||||
"""
|
||||
Render the Vue component tag for the grid.
|
||||
|
||||
By default this simply returns:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<wutta-grid></wutta-grid>
|
||||
|
||||
The actual output will depend on various grid attributes, in
|
||||
particular :attr:`vue_tagname`.
|
||||
"""
|
||||
return HTML.tag(self.vue_tagname, **kwargs)
|
||||
|
||||
def render_vue_template(
|
||||
self,
|
||||
template='/grids/vue_template.mako',
|
||||
**context):
|
||||
"""
|
||||
Render the Vue template block for the grid.
|
||||
|
||||
This returns something like:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
<script type="text/x-template" id="wutta-grid-template">
|
||||
<b-table>
|
||||
<!-- columns etc. -->
|
||||
</b-table>
|
||||
</script>
|
||||
|
||||
.. todo::
|
||||
|
||||
Why can't Sphinx render the above code block as 'html' ?
|
||||
|
||||
It acts like it can't handle a ``<script>`` tag at all?
|
||||
|
||||
Actual output will of course depend on grid attributes,
|
||||
:attr:`vue_tagname` and :attr:`columns` etc.
|
||||
|
||||
:param template: Path to Mako template which is used to render
|
||||
the output.
|
||||
"""
|
||||
context['grid'] = self
|
||||
context.setdefault('request', self.request)
|
||||
output = render(template, context)
|
||||
return HTML.literal(output)
|
||||
|
||||
def get_vue_columns(self):
|
||||
"""
|
||||
Returns a list of Vue-compatible column definitions.
|
||||
|
||||
This uses :attr:`columns` as the basis; each definition
|
||||
returned will be a dict in this format::
|
||||
|
||||
{
|
||||
'field': 'foo',
|
||||
'label': "Foo",
|
||||
}
|
||||
|
||||
See also :meth:`get_vue_data()`.
|
||||
"""
|
||||
if not self.columns:
|
||||
raise ValueError(f"you must define columns for the grid! key = {self.key}")
|
||||
|
||||
columns = []
|
||||
for name in self.columns:
|
||||
columns.append({
|
||||
'field': name,
|
||||
'label': self.app.make_title(name),
|
||||
})
|
||||
return columns
|
||||
|
||||
def get_vue_data(self):
|
||||
"""
|
||||
Returns a list of Vue-compatible data records.
|
||||
|
||||
This uses :attr:`data` as the basis.
|
||||
|
||||
TODO: not clear yet how/where "non-simple" data should be
|
||||
converted?
|
||||
|
||||
See also :meth:`get_vue_columns()`.
|
||||
"""
|
||||
return self.data # TODO
|
|
@ -122,6 +122,11 @@ class MenuHandler(GenericHandler):
|
|||
'route': 'appinfo',
|
||||
'perm': 'appinfo.list',
|
||||
},
|
||||
{
|
||||
'title': "Raw Settings",
|
||||
'route': 'settings',
|
||||
'perm': 'settings.list',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
|
33
src/wuttaweb/templates/grids/vue_template.mako
Normal file
33
src/wuttaweb/templates/grids/vue_template.mako
Normal file
|
@ -0,0 +1,33 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
|
||||
<script type="text/x-template" id="${grid.vue_tagname}-template">
|
||||
<${b}-table :data="data"
|
||||
:loading="loading">
|
||||
|
||||
% for column in grid.get_vue_columns():
|
||||
<${b}-table-column field="${column['field']}"
|
||||
label="${column['label']}"
|
||||
v-slot="props"
|
||||
cell-class="c_${column['field']}">
|
||||
<span v-html="props.row.${column['field']}"></span>
|
||||
</${b}-table-column>
|
||||
% endfor
|
||||
|
||||
</${b}-table>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
let ${grid.vue_component} = {
|
||||
template: '#${grid.vue_tagname}-template',
|
||||
methods: {},
|
||||
}
|
||||
|
||||
let ${grid.vue_component}CurrentData = ${json.dumps(grid.get_vue_data())|n}
|
||||
|
||||
let ${grid.vue_component}Data = {
|
||||
data: ${grid.vue_component}CurrentData,
|
||||
loading: false,
|
||||
}
|
||||
|
||||
</script>
|
|
@ -3,11 +3,30 @@
|
|||
|
||||
<%def name="title()">${index_title}</%def>
|
||||
|
||||
## nb. avoid hero bar for index page
|
||||
<%def name="content_title()"></%def>
|
||||
|
||||
<%def name="page_content()">
|
||||
<p>TODO: index page content</p>
|
||||
% if grid is not Undefined:
|
||||
${grid.render_vue_tag()}
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="render_this_page_template()">
|
||||
${parent.render_this_page_template()}
|
||||
% if grid is not Undefined:
|
||||
${grid.render_vue_template()}
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="finalize_this_page_vars()">
|
||||
${parent.finalize_this_page_vars()}
|
||||
% if grid is not Undefined:
|
||||
<script>
|
||||
${grid.vue_component}.data = function() { return ${grid.vue_component}Data }
|
||||
Vue.component('${grid.vue_tagname}', ${grid.vue_component})
|
||||
</script>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
${parent.body()}
|
||||
|
|
|
@ -26,7 +26,7 @@ Base Logic for Views
|
|||
|
||||
from pyramid import httpexceptions
|
||||
|
||||
from wuttaweb import forms
|
||||
from wuttaweb import forms, grids
|
||||
|
||||
|
||||
class View:
|
||||
|
@ -68,11 +68,21 @@ class View:
|
|||
Make and return a new :class:`~wuttaweb.forms.base.Form`
|
||||
instance, per the given ``kwargs``.
|
||||
|
||||
This is the "default" form factory which merely invokes
|
||||
the constructor.
|
||||
This is the "base" form factory which merely invokes the
|
||||
constructor.
|
||||
"""
|
||||
return forms.Form(self.request, **kwargs)
|
||||
|
||||
def make_grid(self, **kwargs):
|
||||
"""
|
||||
Make and return a new :class:`~wuttaweb.grids.base.Grid`
|
||||
instance, per the given ``kwargs``.
|
||||
|
||||
This is the "base" grid factory which merely invokes the
|
||||
constructor.
|
||||
"""
|
||||
return grids.Grid(self.request, **kwargs)
|
||||
|
||||
def notfound(self):
|
||||
"""
|
||||
Convenience method, to raise a HTTP 404 Not Found exception::
|
||||
|
|
|
@ -100,6 +100,13 @@ class MasterView(View):
|
|||
Code should not access this directly but instead call
|
||||
:meth:`get_model_title_plural()`.
|
||||
|
||||
.. attribute:: grid_key
|
||||
|
||||
Optional override for the view's grid key, e.g. ``'widgets'``.
|
||||
|
||||
Code should not access this directly but instead call
|
||||
:meth:`get_grid_key()`.
|
||||
|
||||
.. attribute:: config_title
|
||||
|
||||
Optional override for the view's "config" title, e.g. ``"Wutta
|
||||
|
@ -138,6 +145,17 @@ class MasterView(View):
|
|||
i.e. it should have an :meth:`index()` view. Default value is
|
||||
``True``.
|
||||
|
||||
.. attribute:: has_grid
|
||||
|
||||
Boolean indicating whether the :meth:`index()` view should
|
||||
include a grid. Default value is ``True``.
|
||||
|
||||
.. attribute:: grid_columns
|
||||
|
||||
List of columns for the :meth:`index()` view grid.
|
||||
|
||||
This is optional; see also :meth:`index_get_grid_columns()`.
|
||||
|
||||
.. attribute:: configurable
|
||||
|
||||
Boolean indicating whether the master view supports
|
||||
|
@ -151,9 +169,11 @@ class MasterView(View):
|
|||
|
||||
# features
|
||||
listable = True
|
||||
has_grid = True
|
||||
configurable = False
|
||||
|
||||
# current action
|
||||
listing = False
|
||||
configuring = False
|
||||
|
||||
##############################
|
||||
|
@ -170,12 +190,101 @@ class MasterView(View):
|
|||
|
||||
By default, this view is included only if :attr:`listable` is
|
||||
true.
|
||||
|
||||
The default view logic will show a "grid" (table) with the
|
||||
model data (unless :attr:`has_grid` is false).
|
||||
|
||||
See also related methods, which are called by this one:
|
||||
|
||||
* :meth:`index_make_grid()`
|
||||
"""
|
||||
self.listing = True
|
||||
|
||||
context = {
|
||||
'index_url': None, # avoid title link since this *is* the index
|
||||
'index_url': None, # nb. avoid title link since this *is* the index
|
||||
}
|
||||
|
||||
if self.has_grid:
|
||||
context['grid'] = self.index_make_grid()
|
||||
|
||||
return self.render_to_response('index', context)
|
||||
|
||||
def index_make_grid(self, **kwargs):
|
||||
"""
|
||||
Create and return a :class:`~wuttaweb.grids.base.Grid`
|
||||
instance for use with the :meth:`index()` view.
|
||||
|
||||
See also related methods, which are called by this one:
|
||||
|
||||
* :meth:`get_grid_key()`
|
||||
* :meth:`index_get_grid_columns()`
|
||||
* :meth:`index_get_grid_data()`
|
||||
* :meth:`index_configure_grid()`
|
||||
"""
|
||||
if 'key' not in kwargs:
|
||||
kwargs['key'] = self.get_grid_key()
|
||||
|
||||
if 'columns' not in kwargs:
|
||||
kwargs['columns'] = self.index_get_grid_columns()
|
||||
|
||||
if 'data' not in kwargs:
|
||||
kwargs['data'] = self.index_get_grid_data()
|
||||
|
||||
grid = self.make_grid(**kwargs)
|
||||
self.index_configure_grid(grid)
|
||||
return grid
|
||||
|
||||
def index_get_grid_columns(self):
|
||||
"""
|
||||
Returns the default list of grid column names, for the
|
||||
:meth:`index()` view.
|
||||
|
||||
This is called by :meth:`index_make_grid()`; in the resulting
|
||||
:class:`~wuttaweb.grids.base.Grid` instance, this becomes
|
||||
:attr:`~wuttaweb.grids.base.Grid.columns`.
|
||||
|
||||
This method may return ``None``, in which case the grid may
|
||||
(try to) generate its own default list.
|
||||
|
||||
Subclass may define :attr:`grid_columns` for simple cases, or
|
||||
can override this method if needed.
|
||||
|
||||
Also note that :meth:`index_configure_grid()` may be used to
|
||||
further modify the final column set, regardless of what this
|
||||
method returns. So a common pattern is to declare all
|
||||
"supported" columns by setting :attr:`grid_columns` but then
|
||||
optionally remove or replace some of those within
|
||||
:meth:`index_configure_grid()`.
|
||||
"""
|
||||
if hasattr(self, 'grid_columns'):
|
||||
return self.grid_columns
|
||||
|
||||
def index_get_grid_data(self):
|
||||
"""
|
||||
Returns the grid data for the :meth:`index()` view.
|
||||
|
||||
This is called by :meth:`index_make_grid()`; in the resulting
|
||||
:class:`~wuttaweb.grids.base.Grid` instance, this becomes
|
||||
:attr:`~wuttaweb.grids.base.Grid.data`.
|
||||
|
||||
As of now there is not yet a "sane" default for this method;
|
||||
it simply returns an empty list. Subclass should override as
|
||||
needed.
|
||||
"""
|
||||
return []
|
||||
|
||||
def index_configure_grid(self, grid):
|
||||
"""
|
||||
Configure the grid for the :meth:`index()` view.
|
||||
|
||||
This is called by :meth:`index_make_grid()`.
|
||||
|
||||
There is no default logic here; subclass should override as
|
||||
needed. The ``grid`` param will already be "complete" and
|
||||
ready to use as-is, but this method can further modify it
|
||||
based on request details etc.
|
||||
"""
|
||||
|
||||
##############################
|
||||
# configure methods
|
||||
##############################
|
||||
|
@ -738,6 +847,26 @@ class MasterView(View):
|
|||
|
||||
return cls.get_url_prefix()
|
||||
|
||||
@classmethod
|
||||
def get_grid_key(cls):
|
||||
"""
|
||||
Returns the (presumably) unique key to be used for the primary
|
||||
grid in the :meth:`index()` view. This key may also be used
|
||||
as the basis (key prefix) for secondary grids.
|
||||
|
||||
This is called from :meth:`index_make_grid()`; in the
|
||||
resulting :class:`~wuttaweb.grids.base.Grid` instance, this
|
||||
becomes :attr:`~wuttaweb.grids.base.Grid.key`.
|
||||
|
||||
The default logic for this method will call
|
||||
:meth:`get_route_prefix()` and return that value as-is. A
|
||||
subclass may override by assigning :attr:`grid_key`.
|
||||
"""
|
||||
if hasattr(cls, 'grid_key'):
|
||||
return cls.grid_key
|
||||
|
||||
return cls.get_route_prefix()
|
||||
|
||||
@classmethod
|
||||
def get_config_title(cls):
|
||||
"""
|
||||
|
|
|
@ -26,8 +26,11 @@ Views for app settings
|
|||
|
||||
from collections import OrderedDict
|
||||
|
||||
from wuttjamaican.db.model import Setting
|
||||
|
||||
from wuttaweb.views import MasterView
|
||||
from wuttaweb.util import get_libver, get_liburl
|
||||
from wuttaweb.db import Session
|
||||
|
||||
|
||||
class AppInfoView(MasterView):
|
||||
|
@ -38,10 +41,13 @@ class AppInfoView(MasterView):
|
|||
|
||||
* ``/appinfo/``
|
||||
* ``/appinfo/configure``
|
||||
|
||||
See also :class:`SettingView`.
|
||||
"""
|
||||
model_name = 'AppInfo'
|
||||
model_title_plural = "App Info"
|
||||
route_prefix = 'appinfo'
|
||||
has_grid = False
|
||||
configurable = True
|
||||
|
||||
def configure_get_simple_settings(self):
|
||||
|
@ -103,8 +109,6 @@ class AppInfoView(MasterView):
|
|||
('bb_vue_fontawesome', "(BB) @fortawesome/vue-fontawesome"),
|
||||
])
|
||||
|
||||
# import ipdb; ipdb.set_trace()
|
||||
|
||||
for key in weblibs:
|
||||
title = weblibs[key]
|
||||
weblibs[key] = {
|
||||
|
@ -127,12 +131,53 @@ class AppInfoView(MasterView):
|
|||
return context
|
||||
|
||||
|
||||
class SettingView(MasterView):
|
||||
"""
|
||||
Master view for the "raw" settings table.
|
||||
|
||||
Notable URLs provided by this class:
|
||||
|
||||
* ``/settings/``
|
||||
|
||||
See also :class:`AppInfoView`.
|
||||
"""
|
||||
model_class = Setting
|
||||
model_title = "Raw Setting"
|
||||
|
||||
# TODO: try removing these
|
||||
grid_columns = [
|
||||
'name',
|
||||
'value',
|
||||
]
|
||||
|
||||
# TODO: should define query, let master handle the rest
|
||||
def index_get_grid_data(self, session=None):
|
||||
""" """
|
||||
model = self.app.model
|
||||
|
||||
session = session or Session()
|
||||
query = session.query(model.Setting)\
|
||||
.order_by(model.Setting.name)
|
||||
|
||||
settings = []
|
||||
for setting in query:
|
||||
settings.append({
|
||||
'name': setting.name,
|
||||
'value': setting.value,
|
||||
})
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
AppInfoView = kwargs.get('AppInfoView', base['AppInfoView'])
|
||||
AppInfoView.defaults(config)
|
||||
|
||||
SettingView = kwargs.get('SettingView', base['SettingView'])
|
||||
SettingView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
defaults(config)
|
||||
|
|
0
tests/grids/__init__.py
Normal file
0
tests/grids/__init__.py
Normal file
77
tests/grids/test_base.py
Normal file
77
tests/grids/test_base.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from pyramid import testing
|
||||
|
||||
from wuttjamaican.conf import WuttaConfig
|
||||
from wuttaweb.grids import base
|
||||
from wuttaweb.forms import FieldList
|
||||
|
||||
|
||||
class TestGrid(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.config = WuttaConfig(defaults={
|
||||
'wutta.web.menus.handler_spec': 'tests.utils:NullMenuHandler',
|
||||
})
|
||||
|
||||
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
|
||||
|
||||
self.pyramid_config = testing.setUp(request=self.request, settings={
|
||||
'mako.directories': ['wuttaweb:templates'],
|
||||
})
|
||||
|
||||
def tearDown(self):
|
||||
testing.tearDown()
|
||||
|
||||
def make_grid(self, request=None, **kwargs):
|
||||
return base.Grid(request or self.request, **kwargs)
|
||||
|
||||
def test_constructor(self):
|
||||
|
||||
# empty
|
||||
grid = self.make_grid()
|
||||
self.assertIsNone(grid.key)
|
||||
self.assertIsNone(grid.columns)
|
||||
self.assertIsNone(grid.data)
|
||||
|
||||
# now with columns
|
||||
grid = self.make_grid(columns=['foo', 'bar'])
|
||||
self.assertIsInstance(grid.columns, FieldList)
|
||||
self.assertEqual(grid.columns, ['foo', 'bar'])
|
||||
|
||||
def test_vue_tagname(self):
|
||||
grid = self.make_grid()
|
||||
self.assertEqual(grid.vue_tagname, 'wutta-grid')
|
||||
|
||||
def test_vue_component(self):
|
||||
grid = self.make_grid()
|
||||
self.assertEqual(grid.vue_component, 'WuttaGrid')
|
||||
|
||||
def test_render_vue_tag(self):
|
||||
grid = self.make_grid(columns=['foo', 'bar'])
|
||||
html = grid.render_vue_tag()
|
||||
self.assertEqual(html, '<wutta-grid></wutta-grid>')
|
||||
|
||||
def test_render_vue_template(self):
|
||||
self.pyramid_config.include('pyramid_mako')
|
||||
self.pyramid_config.add_subscriber('wuttaweb.subscribers.before_render',
|
||||
'pyramid.events.BeforeRender')
|
||||
|
||||
grid = self.make_grid(columns=['foo', 'bar'])
|
||||
html = grid.render_vue_template()
|
||||
self.assertIn('<script type="text/x-template" id="wutta-grid-template">', html)
|
||||
|
||||
def test_get_vue_columns(self):
|
||||
|
||||
# error if no columns are set
|
||||
grid = self.make_grid()
|
||||
self.assertRaises(ValueError, grid.get_vue_columns)
|
||||
|
||||
# otherwise get back field/label dicts
|
||||
grid = self.make_grid(columns=['foo', 'bar'])
|
||||
columns = grid.get_vue_columns()
|
||||
first = columns[0]
|
||||
self.assertEqual(first['field'], 'foo')
|
||||
self.assertEqual(first['label'], 'Foo')
|
|
@ -8,6 +8,7 @@ from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound
|
|||
from wuttjamaican.conf import WuttaConfig
|
||||
from wuttaweb.views import base
|
||||
from wuttaweb.forms import Form
|
||||
from wuttaweb.grids import Grid
|
||||
|
||||
|
||||
class TestView(TestCase):
|
||||
|
@ -31,6 +32,10 @@ class TestView(TestCase):
|
|||
form = self.view.make_form()
|
||||
self.assertIsInstance(form, Form)
|
||||
|
||||
def test_make_grid(self):
|
||||
grid = self.view.make_grid()
|
||||
self.assertIsInstance(grid, Grid)
|
||||
|
||||
def test_notfound(self):
|
||||
error = self.view.notfound()
|
||||
self.assertIsInstance(error, HTTPNotFound)
|
||||
|
|
|
@ -215,6 +215,37 @@ class TestMasterView(WebTestCase):
|
|||
self.assertEqual(master.MasterView.get_template_prefix(), '/machines')
|
||||
del master.MasterView.model_class
|
||||
|
||||
def test_get_grid_key(self):
|
||||
|
||||
# error by default (since no model class)
|
||||
self.assertRaises(AttributeError, master.MasterView.get_grid_key)
|
||||
|
||||
# subclass may specify grid key
|
||||
master.MasterView.grid_key = 'widgets'
|
||||
self.assertEqual(master.MasterView.get_grid_key(), 'widgets')
|
||||
del master.MasterView.grid_key
|
||||
|
||||
# or it may specify route prefix
|
||||
master.MasterView.route_prefix = 'trucks'
|
||||
self.assertEqual(master.MasterView.get_grid_key(), 'trucks')
|
||||
del master.MasterView.route_prefix
|
||||
|
||||
# or it may specify *normalized* model name
|
||||
master.MasterView.model_name_normalized = 'blaster'
|
||||
self.assertEqual(master.MasterView.get_grid_key(), 'blasters')
|
||||
del master.MasterView.model_name_normalized
|
||||
|
||||
# or it may specify *standard* model name
|
||||
master.MasterView.model_name = 'Dinosaur'
|
||||
self.assertEqual(master.MasterView.get_grid_key(), 'dinosaurs')
|
||||
del master.MasterView.model_name
|
||||
|
||||
# or it may specify model class
|
||||
MyModel = MagicMock(__name__='Machine')
|
||||
master.MasterView.model_class = MyModel
|
||||
self.assertEqual(master.MasterView.get_grid_key(), 'machines')
|
||||
del master.MasterView.model_class
|
||||
|
||||
def test_get_config_title(self):
|
||||
|
||||
# error by default (since no model class)
|
||||
|
@ -296,11 +327,13 @@ class TestMasterView(WebTestCase):
|
|||
master.MasterView.model_name = 'AppInfo'
|
||||
master.MasterView.route_prefix = 'appinfo'
|
||||
master.MasterView.template_prefix = '/appinfo'
|
||||
master.MasterView.grid_columns = ['foo', 'bar']
|
||||
view = master.MasterView(self.request)
|
||||
response = view.index()
|
||||
del master.MasterView.model_name
|
||||
del master.MasterView.route_prefix
|
||||
del master.MasterView.template_prefix
|
||||
del master.MasterView.grid_columns
|
||||
|
||||
def test_configure(self):
|
||||
model = self.app.model
|
||||
|
|
|
@ -7,17 +7,39 @@ from wuttaweb.views import settings
|
|||
|
||||
class TestAppInfoView(WebTestCase):
|
||||
|
||||
def make_view(self):
|
||||
return settings.AppInfoView(self.request)
|
||||
|
||||
def test_index(self):
|
||||
# sanity/coverage check
|
||||
view = settings.AppInfoView(self.request)
|
||||
view = self.make_view()
|
||||
response = view.index()
|
||||
|
||||
def test_configure_get_simple_settings(self):
|
||||
# sanity/coverage check
|
||||
view = settings.AppInfoView(self.request)
|
||||
view = self.make_view()
|
||||
simple = view.configure_get_simple_settings()
|
||||
|
||||
def test_configure_get_context(self):
|
||||
# sanity/coverage check
|
||||
view = settings.AppInfoView(self.request)
|
||||
view = self.make_view()
|
||||
context = view.configure_get_context()
|
||||
|
||||
|
||||
class TestSettingView(WebTestCase):
|
||||
|
||||
def make_view(self):
|
||||
return settings.SettingView(self.request)
|
||||
|
||||
def test_index_get_grid_data(self):
|
||||
|
||||
# empty data by default
|
||||
view = self.make_view()
|
||||
data = view.index_get_grid_data(session=self.session)
|
||||
self.assertEqual(len(data), 0)
|
||||
|
||||
# unless we save some settings
|
||||
self.app.save_setting(self.session, 'foo', 'bar')
|
||||
self.session.commit()
|
||||
data = view.index_get_grid_data(session=self.session)
|
||||
self.assertEqual(len(data), 1)
|
||||
|
|
Loading…
Reference in a new issue