1
0
Fork 0

fix: make some tweaks for better tailbone compatibility

this is the result of minimally testing the PersonView from wutta,
configured via a tailbone app.

had to add the `view_profile()` stub, pretty sure we want that..?
This commit is contained in:
Lance Edgar 2024-08-15 02:10:08 -05:00
parent 058632ebeb
commit be8a45e543
11 changed files with 137 additions and 33 deletions

View file

@ -228,9 +228,9 @@ def add_permission(pyramid_config, groupkey, key, label=None):
See also :func:`add_permission_group()`. See also :func:`add_permission_group()`.
""" """
def action():
config = pyramid_config.get_settings()['wutta_config'] config = pyramid_config.get_settings()['wutta_config']
app = config.get_app() app = config.get_app()
def action():
perms = pyramid_config.get_settings().get('wutta_permissions', {}) perms = pyramid_config.get_settings().get('wutta_permissions', {})
group = perms.setdefault(groupkey, {'key': groupkey}) group = perms.setdefault(groupkey, {'key': groupkey})
group.setdefault('label', app.make_title(groupkey)) group.setdefault('label', app.make_title(groupkey))

View file

@ -939,6 +939,20 @@ class Form:
return model_data return model_data
# TODO: for tailbone compat, should document?
# (ideally should remove this and find a better way)
def get_vue_field_value(self, key):
""" """
if key not in self.fields:
return
dform = self.get_deform()
if key not in dform:
return
field = dform[key]
return make_json_safe(field.cstruct)
def validate(self): def validate(self):
""" """
Try to validate the form, using data from the :attr:`request`. Try to validate the form, using data from the :attr:`request`.

View file

@ -552,7 +552,7 @@ class GridAction:
See also :meth:`render_icon_and_label()`. See also :meth:`render_icon_and_label()`.
""" """
if self.request.use_oruga: if self.request.use_oruga:
raise NotImplementedError return HTML.tag('o-icon', icon=self.icon)
return HTML.tag('i', class_=f'fas fa-{self.icon}') return HTML.tag('i', class_=f'fas fa-{self.icon}')

View file

@ -9,15 +9,19 @@
<%def name="render_this_page_template()"> <%def name="render_this_page_template()">
${parent.render_this_page_template()} ${parent.render_this_page_template()}
% if form is not Undefined:
${form.render_vue_template()} ${form.render_vue_template()}
% endif
</%def> </%def>
<%def name="finalize_this_page_vars()"> <%def name="finalize_this_page_vars()">
${parent.finalize_this_page_vars()} ${parent.finalize_this_page_vars()}
% if form is not Undefined:
<script> <script>
${form.vue_component}.data = function() { return ${form.vue_component}Data } ${form.vue_component}.data = function() { return ${form.vue_component}Data }
Vue.component('${form.vue_tagname}', ${form.vue_component}) Vue.component('${form.vue_tagname}', ${form.vue_component})
</script> </script>
% endif
</%def> </%def>

View file

@ -0,0 +1,12 @@
## -*- coding: utf-8; -*-
<%inherit file="/master/view.mako" />
<%def name="page_content()">
<p class="block is-size-5">
TODO: view profile page content
</p>
</%def>
<%def name="title()">TODO: title</%def>
${parent.body()}

View file

@ -242,6 +242,9 @@ class MasterView(View):
deleting = False deleting = False
configuring = False configuring = False
# default DB session
Session = Session
############################## ##############################
# index methods # index methods
############################## ##############################
@ -305,7 +308,7 @@ class MasterView(View):
if form.validate(): if form.validate():
obj = self.create_save_form(form) obj = self.create_save_form(form)
Session.flush() self.Session.flush()
return self.redirect(self.get_action_url('view', obj)) return self.redirect(self.get_action_url('view', obj))
context = { context = {
@ -362,7 +365,6 @@ class MasterView(View):
context = { context = {
'instance': instance, 'instance': instance,
'instance_title': self.get_instance_title(instance),
'form': form, 'form': form,
} }
return self.render_to_response('view', context) return self.render_to_response('view', context)
@ -396,7 +398,6 @@ class MasterView(View):
""" """
self.editing = True self.editing = True
instance = self.get_instance() instance = self.get_instance()
instance_title = self.get_instance_title(instance)
form = self.make_model_form(instance, form = self.make_model_form(instance,
cancel_url_fallback=self.get_action_url('view', instance)) cancel_url_fallback=self.get_action_url('view', instance))
@ -407,7 +408,6 @@ class MasterView(View):
context = { context = {
'instance': instance, 'instance': instance,
'instance_title': instance_title,
'form': form, 'form': form,
} }
return self.render_to_response('edit', context) return self.render_to_response('edit', context)
@ -460,7 +460,6 @@ class MasterView(View):
""" """
self.deleting = True self.deleting = True
instance = self.get_instance() instance = self.get_instance()
instance_title = self.get_instance_title(instance)
if not self.is_deletable(instance): if not self.is_deletable(instance):
return self.redirect(self.get_action_url('view', instance)) return self.redirect(self.get_action_url('view', instance))
@ -481,7 +480,6 @@ class MasterView(View):
context = { context = {
'instance': instance, 'instance': instance,
'instance_title': instance_title,
'form': form, 'form': form,
} }
return self.render_to_response('delete', context) return self.render_to_response('delete', context)
@ -516,7 +514,7 @@ class MasterView(View):
# configure methods # configure methods
############################## ##############################
def configure(self): def configure(self, session=None):
""" """
View for configuring aspects of the app which are pertinent to View for configuring aspects of the app which are pertinent to
this master view and/or model. this master view and/or model.
@ -562,7 +560,7 @@ class MasterView(View):
# maybe just remove settings # maybe just remove settings
if self.request.POST.get('remove_settings'): if self.request.POST.get('remove_settings'):
self.configure_remove_settings() self.configure_remove_settings(session=session)
self.request.session.flash(f"All settings for {config_title} have been removed.", self.request.session.flash(f"All settings for {config_title} have been removed.",
'warning') 'warning')
@ -572,8 +570,8 @@ class MasterView(View):
# gather/save settings # gather/save settings
data = get_form_data(self.request) data = get_form_data(self.request)
settings = self.configure_gather_settings(data) settings = self.configure_gather_settings(data)
self.configure_remove_settings() self.configure_remove_settings(session=session)
self.configure_save_settings(settings) self.configure_save_settings(settings, session=session)
self.request.session.flash("Settings have been saved.") self.request.session.flash("Settings have been saved.")
# reload configure page # reload configure page
@ -751,6 +749,7 @@ class MasterView(View):
def configure_remove_settings( def configure_remove_settings(
self, self,
simple_settings=None, simple_settings=None,
session=None,
): ):
""" """
Remove all "known" settings from the DB; this is called by Remove all "known" settings from the DB; this is called by
@ -778,11 +777,11 @@ class MasterView(View):
if names: if names:
# nb. must avoid self.Session here in case that does not # nb. must avoid self.Session here in case that does not
# point to our primary app DB # point to our primary app DB
session = Session() session = session or self.Session()
for name in names: for name in names:
self.app.delete_setting(session, name) self.app.delete_setting(session, name)
def configure_save_settings(self, settings): def configure_save_settings(self, settings, session=None):
""" """
Save the given settings to the DB; this is called by Save the given settings to the DB; this is called by
:meth:`configure()`. :meth:`configure()`.
@ -795,7 +794,7 @@ class MasterView(View):
""" """
# nb. must avoid self.Session here in case that does not point # nb. must avoid self.Session here in case that does not point
# to our primary app DB # to our primary app DB
session = Session() session = session or self.Session()
for setting in settings: for setting in settings:
self.app.save_setting(session, setting['name'], setting['value'], self.app.save_setting(session, setting['name'], setting['value'],
force_create=True) force_create=True)
@ -885,7 +884,13 @@ class MasterView(View):
# add crud flags if we have an instance # add crud flags if we have an instance
if 'instance' in context: if 'instance' in context:
context['instance_deletable'] = self.is_deletable(context['instance']) instance = context['instance']
if 'instance_title' not in context:
context['instance_title'] = self.get_instance_title(instance)
if 'instance_editable' not in context:
context['instance_editable'] = self.is_editable(instance)
if 'instance_deletable' not in context:
context['instance_deletable'] = self.is_deletable(instance)
# first try the template path most specific to this view # first try the template path most specific to this view
template_prefix = self.get_template_prefix() template_prefix = self.get_template_prefix()
@ -1050,7 +1055,7 @@ class MasterView(View):
model = self.app.model model = self.app.model
model_class = self.get_model_class() model_class = self.get_model_class()
if model_class and issubclass(model_class, model.Base): if model_class and issubclass(model_class, model.Base):
session = session or Session() session = session or self.Session()
return session.query(model_class) return session.query(model_class)
def configure_grid(self, grid): def configure_grid(self, grid):
@ -1110,7 +1115,7 @@ class MasterView(View):
""" """
model_class = self.get_model_class() model_class = self.get_model_class()
if model_class: if model_class:
session = session or Session() session = session or self.Session()
def filtr(query, model_key): def filtr(query, model_key):
key = self.request.matchdict[model_key] key = self.request.matchdict[model_key]
@ -1361,7 +1366,7 @@ class MasterView(View):
if model_class and issubclass(model_class, model.Base): if model_class and issubclass(model_class, model.Base):
# add sqlalchemy model to session # add sqlalchemy model to session
session = session or Session() session = session or self.Session()
session.add(obj) session.add(obj)
############################## ##############################

View file

@ -85,6 +85,33 @@ class PersonView(MasterView):
if 'users' in f: if 'users' in f:
f.fields.remove('users') f.fields.remove('users')
def view_profile(self, session=None):
""" """
instance = self.get_instance(session=session)
context = {
'instance': instance,
}
return self.render_to_response('view_profile', context)
@classmethod
def defaults(cls, config):
cls._defaults(config)
cls._people_defaults(config)
@classmethod
def _people_defaults(cls, config):
route_prefix = cls.get_route_prefix()
instance_url_prefix = cls.get_instance_url_prefix()
permission_prefix = cls.get_permission_prefix()
# view profile
config.add_route(f'{route_prefix}.view_profile',
f'{instance_url_prefix}/profile',
request_method='GET')
config.add_view(cls, attr='view_profile',
route_name=f'{route_prefix}.view_profile',
permission=f'{permission_prefix}.view_profile')
def defaults(config, **kwargs): def defaults(config, **kwargs):
base = globals() base = globals()

View file

@ -454,6 +454,28 @@ class TestForm(TestCase):
# nb. no error message # nb. no error message
self.assertNotIn('message', html) self.assertNotIn('message', html)
def test_get_vue_field_value(self):
schema = self.make_schema()
form = self.make_form(schema=schema)
# TODO: yikes what a hack (?)
dform = form.get_deform()
dform.set_appstruct({'foo': 'one', 'bar': 'two'})
# null for missing field
value = form.get_vue_field_value('doesnotexist')
self.assertIsNone(value)
# normal value is returned
value = form.get_vue_field_value('foo')
self.assertEqual(value, 'one')
# but not if we remove field from deform
# TODO: what is the use case here again?
dform.children.remove(dform['foo'])
value = form.get_vue_field_value('foo')
self.assertIsNone(value)
def test_get_vue_model_data(self): def test_get_vue_model_data(self):
schema = self.make_schema() schema = self.make_schema()
form = self.make_form(schema=schema) form = self.make_form(schema=schema)

View file

@ -190,9 +190,10 @@ class TestGridAction(TestCase):
html = action.render_icon() html = action.render_icon()
self.assertIn('<i class="fas fa-blarg">', html) self.assertIn('<i class="fas fa-blarg">', html)
# oruga not yet supported # oruga has different output
self.request.use_oruga = True self.request.use_oruga = True
self.assertRaises(NotImplementedError, action.render_icon) html = action.render_icon()
self.assertIn('<o-icon icon="blarg">', html)
def test_render_label(self): def test_render_label(self):

View file

@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch
from pyramid import testing from pyramid import testing
from pyramid.response import Response from pyramid.response import Response
from pyramid.httpexceptions import HTTPFound, HTTPNotFound from pyramid.httpexceptions import HTTPNotFound
from wuttjamaican.conf import WuttaConfig from wuttjamaican.conf import WuttaConfig
from wuttaweb.views import master from wuttaweb.views import master
@ -942,7 +942,7 @@ class TestMasterView(WebTestCase):
configure_get_simple_settings=MagicMock(return_value=settings)): configure_get_simple_settings=MagicMock(return_value=settings)):
# get the form page # get the form page
response = view.configure() response = view.configure(session=self.session)
self.assertIsInstance(response, Response) self.assertIsInstance(response, Response)
# post request to save settings # post request to save settings
@ -952,9 +952,9 @@ class TestMasterView(WebTestCase):
'wutta.foo': 'bar', 'wutta.foo': 'bar',
'wutta.flag': 'true', 'wutta.flag': 'true',
} }
response = view.configure() response = view.configure(session=self.session)
# nb. should get redirect back to configure page # nb. should get redirect back to configure page
self.assertIsInstance(response, HTTPFound) self.assertEqual(response.status_code, 302)
# should now have 5 settings # should now have 5 settings
count = self.session.query(model.Setting).count() count = self.session.query(model.Setting).count()
@ -970,9 +970,9 @@ class TestMasterView(WebTestCase):
# post request to remove settings # post request to remove settings
self.request.method = 'POST' self.request.method = 'POST'
self.request.POST = {'remove_settings': '1'} self.request.POST = {'remove_settings': '1'}
response = view.configure() response = view.configure(session=self.session)
# nb. should get redirect back to configure page # nb. should get redirect back to configure page
self.assertIsInstance(response, HTTPFound) self.assertEqual(response.status_code, 302)
# should now have 0 settings # should now have 0 settings
count = self.session.query(model.Setting).count() count = self.session.query(model.Setting).count()

View file

@ -15,6 +15,9 @@ class TestPersonView(WebTestCase):
def make_view(self): def make_view(self):
return people.PersonView(self.request) return people.PersonView(self.request)
def test_includeme(self):
self.pyramid_config.include('wuttaweb.views.people')
def test_get_query(self): def test_get_query(self):
view = self.make_view() view = self.make_view()
query = view.get_query(session=self.session) query = view.get_query(session=self.session)
@ -37,3 +40,19 @@ class TestPersonView(WebTestCase):
view.configure_form(form) view.configure_form(form)
self.assertTrue(form.required_fields) self.assertTrue(form.required_fields)
self.assertFalse(form.required_fields['middle_name']) self.assertFalse(form.required_fields['middle_name'])
def test_view_profile(self):
self.pyramid_config.include('wuttaweb.views.common')
self.pyramid_config.include('wuttaweb.views.auth')
self.pyramid_config.add_route('people', '/people/')
model = self.app.model
person = model.Person(full_name="Barney Rubble")
self.session.add(person)
self.session.commit()
# sanity check
view = self.make_view()
self.request.matchdict = {'uuid': person.uuid}
response = view.view_profile(session=self.session)
self.assertEqual(response.status_code, 200)