feat: improve page linkage between role/user/person
- show Users grid when viewing a Role - add hyperlinks between things
This commit is contained in:
parent
9d261de45a
commit
770c4612d5
|
@ -25,6 +25,7 @@ Base form classes
|
|||
"""
|
||||
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
import colander
|
||||
import deform
|
||||
|
@ -311,6 +312,9 @@ class Form:
|
|||
|
||||
self.set_fields(fields or self.get_fields())
|
||||
|
||||
# nb. this tracks grid JSON data for inclusion in page template
|
||||
self.grid_vue_data = OrderedDict()
|
||||
|
||||
def __contains__(self, name):
|
||||
"""
|
||||
Custom logic for the ``in`` operator, to allow easily checking
|
||||
|
@ -750,6 +754,10 @@ class Form:
|
|||
kwargs['appstruct'] = self.model_instance
|
||||
|
||||
form = deform.Form(schema, **kwargs)
|
||||
# nb. must give a reference back to wutta form; this is
|
||||
# for sake of field schema nodes and widgets, e.g. to
|
||||
# access the main model instance
|
||||
form.wutta_form = self
|
||||
self.deform_form = form
|
||||
|
||||
return self.deform_form
|
||||
|
@ -818,6 +826,17 @@ class Form:
|
|||
output = render(template, context)
|
||||
return HTML.literal(output)
|
||||
|
||||
def add_grid_vue_data(self, grid):
|
||||
""" """
|
||||
if not grid.key:
|
||||
raise ValueError("grid must have a key!")
|
||||
|
||||
if grid.key in self.grid_vue_data:
|
||||
log.warning("grid data with key '%s' already registered, "
|
||||
"but will be replaced", grid.key)
|
||||
|
||||
self.grid_vue_data[grid.key] = grid.get_vue_data()
|
||||
|
||||
def render_vue_field(
|
||||
self,
|
||||
fieldname,
|
||||
|
|
|
@ -246,6 +246,9 @@ class ObjectRef(colander.SchemaType):
|
|||
values.insert(0, self.empty_option)
|
||||
kwargs['values'] = values
|
||||
|
||||
if 'url' not in kwargs:
|
||||
kwargs['url'] = lambda person: self.request.route_url('people.view', uuid=person.uuid)
|
||||
|
||||
return widgets.ObjectRefWidget(self.request, **kwargs)
|
||||
|
||||
|
||||
|
@ -321,6 +324,28 @@ class RoleRefs(WuttaSet):
|
|||
return widgets.RoleRefsWidget(self.request, **kwargs)
|
||||
|
||||
|
||||
class UserRefs(WuttaSet):
|
||||
"""
|
||||
Form schema type for the Role
|
||||
:attr:`~wuttjamaican:wuttjamaican.db.model.auth.Role.users`
|
||||
association proxy field.
|
||||
|
||||
This is a subclass of :class:`WuttaSet`. It uses a ``set`` of
|
||||
:class:`~wuttjamaican:wuttjamaican.db.model.auth.User` ``uuid``
|
||||
values for underlying data format.
|
||||
"""
|
||||
|
||||
def widget_maker(self, **kwargs):
|
||||
"""
|
||||
Constructs a default widget for the field.
|
||||
|
||||
:returns: Instance of
|
||||
:class:`~wuttaweb.forms.widgets.UserRefsWidget`.
|
||||
"""
|
||||
kwargs.setdefault('session', self.session)
|
||||
return widgets.UserRefsWidget(self.request, **kwargs)
|
||||
|
||||
|
||||
class Permissions(WuttaSet):
|
||||
"""
|
||||
Form schema type for the Role
|
||||
|
|
|
@ -44,6 +44,7 @@ from deform.widget import (Widget, TextInputWidget, TextAreaWidget,
|
|||
from webhelpers2.html import HTML
|
||||
|
||||
from wuttaweb.db import Session
|
||||
from wuttaweb.grids import Grid
|
||||
|
||||
|
||||
class ObjectRefWidget(SelectWidget):
|
||||
|
@ -83,9 +84,19 @@ class ObjectRefWidget(SelectWidget):
|
|||
"""
|
||||
readonly_template = 'readonly/objectref'
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
def __init__(self, request, url=None, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.request = request
|
||||
self.url = url
|
||||
|
||||
def get_template_values(self, field, cstruct, kw):
|
||||
""" """
|
||||
values = super().get_template_values(field, cstruct, kw)
|
||||
|
||||
if 'url' not in values and self.url and field.schema.model_instance:
|
||||
values['url'] = self.url(field.schema.model_instance)
|
||||
|
||||
return values
|
||||
|
||||
|
||||
class NotesWidget(TextAreaWidget):
|
||||
|
@ -137,12 +148,17 @@ class RoleRefsWidget(WuttaCheckboxChoiceWidget):
|
|||
"""
|
||||
Widget for use with User
|
||||
:attr:`~wuttjamaican:wuttjamaican.db.model.auth.User.roles` field.
|
||||
This is the default widget for the
|
||||
:class:`~wuttaweb.forms.schema.RoleRefs` type.
|
||||
|
||||
This is a subclass of :class:`WuttaCheckboxChoiceWidget`.
|
||||
"""
|
||||
readonly_template = 'readonly/rolerefs'
|
||||
|
||||
def serialize(self, field, cstruct, **kw):
|
||||
""" """
|
||||
model = self.app.model
|
||||
|
||||
# special logic when field is editable
|
||||
readonly = kw.get('readonly', self.readonly)
|
||||
if not readonly:
|
||||
|
@ -159,10 +175,78 @@ class RoleRefsWidget(WuttaCheckboxChoiceWidget):
|
|||
if val[0] != admin.uuid]
|
||||
kw['values'] = values
|
||||
|
||||
else: # readonly
|
||||
|
||||
# roles
|
||||
roles = []
|
||||
if cstruct:
|
||||
for uuid in cstruct:
|
||||
role = self.session.query(model.Role).get(uuid)
|
||||
if role:
|
||||
roles.append(role)
|
||||
kw['roles'] = roles
|
||||
|
||||
# url
|
||||
url = lambda role: self.request.route_url('roles.view', uuid=role.uuid)
|
||||
kw['url'] = url
|
||||
|
||||
# default logic from here
|
||||
return super().serialize(field, cstruct, **kw)
|
||||
|
||||
|
||||
class UserRefsWidget(WuttaCheckboxChoiceWidget):
|
||||
"""
|
||||
Widget for use with Role
|
||||
:attr:`~wuttjamaican:wuttjamaican.db.model.auth.Role.users` field.
|
||||
This is the default widget for the
|
||||
:class:`~wuttaweb.forms.schema.UserRefs` type.
|
||||
|
||||
This is a subclass of :class:`WuttaCheckboxChoiceWidget`; however
|
||||
it only supports readonly mode and does not use a template.
|
||||
Rather, it generates and renders a
|
||||
:class:`~wuttaweb.grids.base.Grid` showing the users list.
|
||||
"""
|
||||
|
||||
def serialize(self, field, cstruct, **kw):
|
||||
""" """
|
||||
readonly = kw.get('readonly', self.readonly)
|
||||
if not readonly:
|
||||
raise NotImplementedError("edit not allowed for this widget")
|
||||
|
||||
model = self.app.model
|
||||
columns = ['person', 'username', 'active']
|
||||
|
||||
# generate data set for users
|
||||
users = []
|
||||
if cstruct:
|
||||
for uuid in cstruct:
|
||||
user = self.session.query(model.User).get(uuid)
|
||||
if user:
|
||||
users.append(dict([(key, getattr(user, key))
|
||||
for key in columns + ['uuid']]))
|
||||
|
||||
# grid
|
||||
grid = Grid(self.request, key='roles.view.users',
|
||||
columns=columns, data=users)
|
||||
|
||||
# view action
|
||||
if self.request.has_perm('users.view'):
|
||||
url = lambda user, i: self.request.route_url('users.view', uuid=user['uuid'])
|
||||
grid.add_action('view', icon='eye', url=url)
|
||||
grid.set_link('person')
|
||||
grid.set_link('username')
|
||||
|
||||
# edit action
|
||||
if self.request.has_perm('users.edit'):
|
||||
url = lambda user, i: self.request.route_url('users.edit', uuid=user['uuid'])
|
||||
grid.add_action('edit', url=url)
|
||||
|
||||
# render as simple <b-table>
|
||||
# nb. must indicate we are a part of this form
|
||||
form = getattr(field.parent, 'wutta_form', None)
|
||||
return grid.render_table_element(form)
|
||||
|
||||
|
||||
class PermissionsWidget(WuttaCheckboxChoiceWidget):
|
||||
"""
|
||||
Widget for use with Role
|
||||
|
|
|
@ -543,6 +543,13 @@ class Grid:
|
|||
return True
|
||||
return False
|
||||
|
||||
def add_action(self, key, **kwargs):
|
||||
"""
|
||||
Convenience to add a new :class:`GridAction` instance to the
|
||||
grid's :attr:`actions` list.
|
||||
"""
|
||||
self.actions.append(GridAction(self.request, key, **kwargs))
|
||||
|
||||
##############################
|
||||
# sorting methods
|
||||
##############################
|
||||
|
@ -1251,6 +1258,9 @@ class Grid:
|
|||
"""
|
||||
Render the Vue template block for the grid.
|
||||
|
||||
This is what you want for a "full-featured" grid which will
|
||||
exist as its own unique Vue component on the frontend.
|
||||
|
||||
This returns something like:
|
||||
|
||||
.. code-block:: none
|
||||
|
@ -1261,12 +1271,21 @@ class Grid:
|
|||
</b-table>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
WuttaGridData = {}
|
||||
WuttaGrid = {
|
||||
template: 'wutta-grid-template',
|
||||
}
|
||||
</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?
|
||||
|
||||
See :meth:`render_table_element()` for a simpler variant.
|
||||
|
||||
Actual output will of course depend on grid attributes,
|
||||
:attr:`vue_tagname` and :attr:`columns` etc.
|
||||
|
||||
|
@ -1278,6 +1297,58 @@ class Grid:
|
|||
output = render(template, context)
|
||||
return HTML.literal(output)
|
||||
|
||||
def render_table_element(
|
||||
self,
|
||||
form=None,
|
||||
template='/grids/element.mako',
|
||||
**context):
|
||||
"""
|
||||
Render a simple Vue table element for the grid.
|
||||
|
||||
This is what you want for a "simple" grid which does require a
|
||||
unique Vue component, but can instead use the standard table
|
||||
component.
|
||||
|
||||
This returns something like:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<b-table :data="gridData['mykey']">
|
||||
<!-- columns etc. -->
|
||||
</b-table>
|
||||
|
||||
See :meth:`render_vue_template()` for a more complete variant.
|
||||
|
||||
Actual output will of course depend on grid attributes,
|
||||
:attr:`key`, :attr:`columns` etc.
|
||||
|
||||
:param form: Reference to the
|
||||
:class:`~wuttaweb.forms.base.Form` instance which
|
||||
"contains" this grid. This is needed in order to ensure
|
||||
the grid data is available to the form Vue component.
|
||||
|
||||
:param template: Path to Mako template which is used to render
|
||||
the output.
|
||||
|
||||
.. note::
|
||||
|
||||
The above example shows ``gridData['mykey']`` as the Vue
|
||||
data reference. This should "just work" if you provide the
|
||||
correct ``form`` arg and the grid is contained directly by
|
||||
that form's Vue component.
|
||||
|
||||
However, this may not account for all use cases. For now
|
||||
we wait and see what comes up, but know the dust may not
|
||||
yet be settled here.
|
||||
"""
|
||||
|
||||
# nb. must register data for inclusion on page template
|
||||
if form:
|
||||
form.add_grid_vue_data(self)
|
||||
|
||||
# otherwise logic is the same, just different template
|
||||
return self.render_vue_template(template=template, **context)
|
||||
|
||||
def render_vue_finalize(self):
|
||||
"""
|
||||
Render the Vue "finalize" script for the grid.
|
||||
|
|
|
@ -501,7 +501,7 @@
|
|||
label="Delete This" />
|
||||
% endif
|
||||
% elif master.editing:
|
||||
% if instance_viewable and master.has_perm('view'):
|
||||
% if master.has_perm('view'):
|
||||
<wutta-button once
|
||||
tag="a" href="${master.get_action_url('view', instance)}"
|
||||
icon-left="eye"
|
||||
|
@ -514,7 +514,7 @@
|
|||
label="Delete This" />
|
||||
% endif
|
||||
% elif master.deleting:
|
||||
% if instance_viewable and master.has_perm('view'):
|
||||
% if master.has_perm('view'):
|
||||
<wutta-button once
|
||||
tag="a" href="${master.get_action_url('view', instance)}"
|
||||
icon-left="eye"
|
||||
|
|
|
@ -1 +1,9 @@
|
|||
<span>${str(field.schema.model_instance or '')}</span>
|
||||
<tal:omit tal:define="url url|None;">
|
||||
<a tal:condition="url"
|
||||
href="${url}">
|
||||
${str(field.schema.model_instance or '')}
|
||||
</a>
|
||||
<span tal:condition="not url">
|
||||
${str(field.schema.model_instance or '')}
|
||||
</span>
|
||||
</tal:omit>
|
||||
|
|
7
src/wuttaweb/templates/deform/readonly/rolerefs.pt
Normal file
7
src/wuttaweb/templates/deform/readonly/rolerefs.pt
Normal file
|
@ -0,0 +1,7 @@
|
|||
<ul class="list-group">
|
||||
<tal:loop tal:repeat="role roles">
|
||||
<li class="list-group-item">
|
||||
<a href="${url(role)}">${role}</a>
|
||||
</li>
|
||||
</tal:loop>
|
||||
</ul>
|
|
@ -68,6 +68,14 @@
|
|||
% endif
|
||||
|
||||
% endif
|
||||
|
||||
% if form.grid_vue_data:
|
||||
gridData: {
|
||||
% for key, data in form.grid_vue_data.items():
|
||||
'${key}': ${json.dumps(data)|n},
|
||||
% endfor
|
||||
},
|
||||
% endif
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
49
src/wuttaweb/templates/grids/element.mako
Normal file
49
src/wuttaweb/templates/grids/element.mako
Normal file
|
@ -0,0 +1,49 @@
|
|||
## -*- coding: utf-8; -*-
|
||||
<${b}-table :data="gridData['${grid.key}']">
|
||||
|
||||
% for column in grid.get_vue_columns():
|
||||
<${b}-table-column field="${column['field']}"
|
||||
label="${column['label']}"
|
||||
v-slot="props"
|
||||
:sortable="${json.dumps(column.get('sortable', False))|n}"
|
||||
cell-class="c_${column['field']}">
|
||||
% if grid.is_linked(column['field']):
|
||||
<a :href="props.row._action_url_view"
|
||||
v-html="props.row.${column['field']}" />
|
||||
% else:
|
||||
<span v-html="props.row.${column['field']}"></span>
|
||||
% endif
|
||||
</${b}-table-column>
|
||||
% endfor
|
||||
|
||||
% if grid.actions:
|
||||
<${b}-table-column field="actions"
|
||||
label="Actions"
|
||||
v-slot="props">
|
||||
% for action in grid.actions:
|
||||
<a v-if="props.row._action_url_${action.key}"
|
||||
:href="props.row._action_url_${action.key}"
|
||||
class="${action.link_class}">
|
||||
${action.render_icon_and_label()}
|
||||
</a>
|
||||
|
||||
% endfor
|
||||
</${b}-table-column>
|
||||
% endif
|
||||
|
||||
<template #empty>
|
||||
<section class="section">
|
||||
<div class="content has-text-grey has-text-centered">
|
||||
<p>
|
||||
<b-icon
|
||||
pack="fas"
|
||||
icon="sad-tear"
|
||||
size="is-large">
|
||||
</b-icon>
|
||||
</p>
|
||||
<p>Nothing here.</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
</${b}-table>
|
|
@ -28,6 +28,7 @@ import sqlalchemy as sa
|
|||
|
||||
from wuttjamaican.db.model import Person
|
||||
from wuttaweb.views import MasterView
|
||||
from wuttaweb.forms.schema import UserRefs
|
||||
|
||||
|
||||
class PersonView(MasterView):
|
||||
|
@ -70,23 +71,25 @@ class PersonView(MasterView):
|
|||
# last_name
|
||||
g.set_link('last_name')
|
||||
|
||||
# TODO: master should handle this?
|
||||
def configure_form(self, f):
|
||||
""" """
|
||||
super().configure_form(f)
|
||||
person = f.model_instance
|
||||
|
||||
# first_name
|
||||
# TODO: master should handle these? (nullable column)
|
||||
f.set_required('first_name', False)
|
||||
|
||||
# middle_name
|
||||
f.set_required('middle_name', False)
|
||||
|
||||
# last_name
|
||||
f.set_required('last_name', False)
|
||||
|
||||
# users
|
||||
if 'users' in f:
|
||||
f.fields.remove('users')
|
||||
# nb. colanderalchemy wants to do some magic for the true
|
||||
# 'users' relationship, so we use a different field name
|
||||
f.remove('users')
|
||||
if not (self.creating or self.editing):
|
||||
f.append('_users')
|
||||
f.set_readonly('_users')
|
||||
f.set_node('_users', UserRefs(self.request))
|
||||
f.set_default('_users', [u.uuid for u in person.users])
|
||||
|
||||
def autocomplete_query(self, term):
|
||||
""" """
|
||||
|
|
|
@ -28,7 +28,7 @@ from wuttjamaican.db.model import Role
|
|||
from wuttaweb.views import MasterView
|
||||
from wuttaweb.db import Session
|
||||
from wuttaweb.forms import widgets
|
||||
from wuttaweb.forms.schema import Permissions
|
||||
from wuttaweb.forms.schema import UserRefs, Permissions
|
||||
|
||||
|
||||
class RoleView(MasterView):
|
||||
|
@ -115,6 +115,13 @@ class RoleView(MasterView):
|
|||
# notes
|
||||
f.set_widget('notes', widgets.NotesWidget())
|
||||
|
||||
# users
|
||||
if not (self.creating or self.editing):
|
||||
f.append('users')
|
||||
f.set_readonly('users')
|
||||
f.set_node('users', UserRefs(self.request))
|
||||
f.set_default('users', [u.uuid for u in role.users])
|
||||
|
||||
# permissions
|
||||
f.append('permissions')
|
||||
self.wutta_permissions = self.get_available_permissions()
|
||||
|
|
|
@ -10,6 +10,7 @@ from pyramid import testing
|
|||
from wuttjamaican.conf import WuttaConfig
|
||||
from wuttaweb.forms import base, widgets
|
||||
from wuttaweb import helpers
|
||||
from wuttaweb.grids import Grid
|
||||
|
||||
|
||||
class TestForm(TestCase):
|
||||
|
@ -405,6 +406,29 @@ class TestForm(TestCase):
|
|||
self.assertIn('<script type="text/x-template" id="wutta-form-template">', html)
|
||||
self.assertNotIn('@submit', html)
|
||||
|
||||
def test_add_grid_vue_data(self):
|
||||
form = self.make_form()
|
||||
|
||||
# grid must have key
|
||||
grid = Grid(self.request)
|
||||
self.assertRaises(ValueError, form.add_grid_vue_data, grid)
|
||||
|
||||
# otherwise it works
|
||||
grid = Grid(self.request, key='foo')
|
||||
self.assertEqual(len(form.grid_vue_data), 0)
|
||||
form.add_grid_vue_data(grid)
|
||||
self.assertEqual(len(form.grid_vue_data), 1)
|
||||
self.assertIn('foo', form.grid_vue_data)
|
||||
self.assertEqual(form.grid_vue_data['foo'], [])
|
||||
|
||||
# calling again with same key will replace data
|
||||
records = [{'foo': 1}, {'foo': 2}]
|
||||
grid = Grid(self.request, key='foo', columns=['foo'], data=records)
|
||||
form.add_grid_vue_data(grid)
|
||||
self.assertEqual(len(form.grid_vue_data), 1)
|
||||
self.assertIn('foo', form.grid_vue_data)
|
||||
self.assertEqual(form.grid_vue_data['foo'], records)
|
||||
|
||||
def test_render_vue_finalize(self):
|
||||
form = self.make_form()
|
||||
html = form.render_vue_finalize()
|
||||
|
|
|
@ -9,6 +9,7 @@ from sqlalchemy import orm
|
|||
|
||||
from wuttjamaican.conf import WuttaConfig
|
||||
from wuttaweb.forms import schema as mod
|
||||
from wuttaweb.forms import widgets
|
||||
from tests.util import DataTestCase
|
||||
|
||||
|
||||
|
@ -200,6 +201,19 @@ class TestPersonRef(DataTestCase):
|
|||
self.assertIsNot(sorted_query, query)
|
||||
|
||||
|
||||
class TestUserRefs(DataTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.setup_db()
|
||||
self.request = testing.DummyRequest(wutta_config=self.config)
|
||||
|
||||
def test_widget_maker(self):
|
||||
model = self.app.model
|
||||
typ = mod.UserRefs(self.request, session=self.session)
|
||||
widget = typ.widget_maker()
|
||||
self.assertIsInstance(widget, widgets.UserRefsWidget)
|
||||
|
||||
|
||||
class TestRoleRefs(DataTestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import colander
|
||||
import deform
|
||||
from pyramid import testing
|
||||
|
||||
from wuttaweb.forms import widgets as mod
|
||||
from wuttaweb.forms.schema import PersonRef, RoleRefs, Permissions
|
||||
from wuttaweb.forms.schema import PersonRef, RoleRefs, UserRefs, Permissions
|
||||
from tests.util import WebTestCase
|
||||
|
||||
|
||||
|
@ -36,7 +38,18 @@ class TestObjectRefWidget(WebTestCase):
|
|||
widget = mod.ObjectRefWidget(self.request)
|
||||
field = self.make_field(node)
|
||||
html = widget.serialize(field, person.uuid, readonly=True)
|
||||
self.assertEqual(html.strip(), '<span>Betty Boop</span>')
|
||||
self.assertIn('Betty Boop', html)
|
||||
self.assertNotIn('<a', html)
|
||||
|
||||
# with hyperlink
|
||||
node = colander.SchemaNode(PersonRef(self.request, session=self.session))
|
||||
node.model_instance = person
|
||||
widget = mod.ObjectRefWidget(self.request, url=lambda p: '/foo')
|
||||
field = self.make_field(node)
|
||||
html = widget.serialize(field, person.uuid, readonly=True)
|
||||
self.assertIn('Betty Boop', html)
|
||||
self.assertIn('<a', html)
|
||||
self.assertIn('href="/foo"', html)
|
||||
|
||||
|
||||
class TestRoleRefsWidget(WebTestCase):
|
||||
|
@ -48,6 +61,7 @@ class TestRoleRefsWidget(WebTestCase):
|
|||
return deform.Field(node, **kwargs)
|
||||
|
||||
def test_serialize(self):
|
||||
self.pyramid_config.add_route('roles.view', '/roles/{uuid}')
|
||||
model = self.app.model
|
||||
auth = self.app.get_auth_handler()
|
||||
admin = auth.get_role_administrator(self.session)
|
||||
|
@ -77,6 +91,49 @@ class TestRoleRefsWidget(WebTestCase):
|
|||
self.assertIn(blokes.uuid, html)
|
||||
|
||||
|
||||
class TestUserRefsWidget(WebTestCase):
|
||||
|
||||
def make_field(self, node, **kwargs):
|
||||
# TODO: not sure why default renderer is in use even though
|
||||
# pyramid_deform was included in setup? but this works..
|
||||
kwargs.setdefault('renderer', deform.Form.default_renderer)
|
||||
return deform.Field(node, **kwargs)
|
||||
|
||||
def test_serialize(self):
|
||||
model = self.app.model
|
||||
|
||||
# nb. we let the field construct the widget via our type
|
||||
node = colander.SchemaNode(UserRefs(self.request, session=self.session))
|
||||
field = self.make_field(node)
|
||||
widget = field.widget
|
||||
|
||||
# readonly is required
|
||||
self.assertRaises(NotImplementedError, widget.serialize, field, set())
|
||||
self.assertRaises(NotImplementedError, widget.serialize, field, set(), readonly=False)
|
||||
|
||||
# empty
|
||||
html = widget.serialize(field, set(), readonly=True)
|
||||
self.assertIn('<b-table ', html)
|
||||
|
||||
# with data, no actions
|
||||
user = model.User(username='barney')
|
||||
self.session.add(user)
|
||||
self.session.commit()
|
||||
html = widget.serialize(field, {user.uuid}, readonly=True)
|
||||
self.assertIn('<b-table ', html)
|
||||
self.assertNotIn('Actions', html)
|
||||
self.assertNotIn('View', html)
|
||||
self.assertNotIn('Edit', html)
|
||||
|
||||
# with view/edit actions
|
||||
with patch.object(self.request, 'is_root', new=True):
|
||||
html = widget.serialize(field, {user.uuid}, readonly=True)
|
||||
self.assertIn('<b-table ', html)
|
||||
self.assertIn('Actions', html)
|
||||
self.assertIn('View', html)
|
||||
self.assertIn('Edit', html)
|
||||
|
||||
|
||||
class TestPermissionsWidget(WebTestCase):
|
||||
|
||||
def make_field(self, node, **kwargs):
|
||||
|
|
|
@ -10,7 +10,8 @@ from pyramid import testing
|
|||
|
||||
from wuttjamaican.conf import WuttaConfig
|
||||
from wuttaweb.grids import base as mod
|
||||
from wuttaweb.forms import FieldList
|
||||
from wuttaweb.util import FieldList
|
||||
from wuttaweb.forms import Form
|
||||
from tests.util import WebTestCase
|
||||
|
||||
|
||||
|
@ -186,6 +187,14 @@ class TestGrid(WebTestCase):
|
|||
self.assertFalse(grid.is_linked('foo'))
|
||||
self.assertTrue(grid.is_linked('bar'))
|
||||
|
||||
def test_add_action(self):
|
||||
grid = self.make_grid()
|
||||
self.assertEqual(len(grid.actions), 0)
|
||||
|
||||
grid.add_action('view')
|
||||
self.assertEqual(len(grid.actions), 1)
|
||||
self.assertIsInstance(grid.actions[0], mod.GridAction)
|
||||
|
||||
def test_get_pagesize_options(self):
|
||||
grid = self.make_grid()
|
||||
|
||||
|
@ -855,6 +864,25 @@ class TestGrid(WebTestCase):
|
|||
html = grid.render_vue_template()
|
||||
self.assertIn('<script type="text/x-template" id="wutta-grid-template">', html)
|
||||
|
||||
def test_render_table_element(self):
|
||||
self.pyramid_config.include('pyramid_mako')
|
||||
self.pyramid_config.add_subscriber('wuttaweb.subscribers.before_render',
|
||||
'pyramid.events.BeforeRender')
|
||||
|
||||
grid = self.make_grid(key='foobar', columns=['foo', 'bar'])
|
||||
|
||||
# form not required
|
||||
html = grid.render_table_element()
|
||||
self.assertNotIn('<script ', html)
|
||||
self.assertIn('<b-table ', html)
|
||||
|
||||
# form will register grid data
|
||||
form = Form(self.request)
|
||||
self.assertEqual(len(form.grid_vue_data), 0)
|
||||
html = grid.render_table_element(form)
|
||||
self.assertEqual(len(form.grid_vue_data), 1)
|
||||
self.assertIn('foobar', form.grid_vue_data)
|
||||
|
||||
def test_render_vue_finalize(self):
|
||||
grid = self.make_grid()
|
||||
html = grid.render_vue_finalize()
|
||||
|
|
|
@ -35,12 +35,27 @@ class TestPersonView(WebTestCase):
|
|||
model = self.app.model
|
||||
view = self.make_view()
|
||||
form = view.make_form(model_class=model.Person)
|
||||
|
||||
# required fields
|
||||
with patch.object(view, 'creating', new=True):
|
||||
form.set_fields(form.get_model_fields())
|
||||
self.assertEqual(form.required_fields, {})
|
||||
view.configure_form(form)
|
||||
self.assertTrue(form.required_fields)
|
||||
self.assertFalse(form.required_fields['middle_name'])
|
||||
|
||||
person = model.Person(full_name="Barney Rubble")
|
||||
user = model.User(username='barney', person=person)
|
||||
self.session.add(user)
|
||||
self.session.commit()
|
||||
|
||||
# users field
|
||||
with patch.object(view, 'viewing', new=True):
|
||||
form = view.make_form(model_instance=person)
|
||||
self.assertEqual(form.defaults, {})
|
||||
view.configure_form(form)
|
||||
self.assertIn('_users', form.defaults)
|
||||
|
||||
def test_autocomplete_query(self):
|
||||
model = self.app.model
|
||||
|
||||
|
|
Loading…
Reference in a new issue