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()`.
"""
config = pyramid_config.get_settings()['wutta_config']
app = config.get_app()
def action():
config = pyramid_config.get_settings()['wutta_config']
app = config.get_app()
perms = pyramid_config.get_settings().get('wutta_permissions', {})
group = perms.setdefault(groupkey, {'key': groupkey})
group.setdefault('label', app.make_title(groupkey))

View file

@ -939,6 +939,20 @@ class Form:
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):
"""
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()`.
"""
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}')

View file

@ -9,15 +9,19 @@
<%def name="render_this_page_template()">
${parent.render_this_page_template()}
${form.render_vue_template()}
% if form is not Undefined:
${form.render_vue_template()}
% endif
</%def>
<%def name="finalize_this_page_vars()">
${parent.finalize_this_page_vars()}
<script>
${form.vue_component}.data = function() { return ${form.vue_component}Data }
Vue.component('${form.vue_tagname}', ${form.vue_component})
</script>
% if form is not Undefined:
<script>
${form.vue_component}.data = function() { return ${form.vue_component}Data }
Vue.component('${form.vue_tagname}', ${form.vue_component})
</script>
% endif
</%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
configuring = False
# default DB session
Session = Session
##############################
# index methods
##############################
@ -305,7 +308,7 @@ class MasterView(View):
if form.validate():
obj = self.create_save_form(form)
Session.flush()
self.Session.flush()
return self.redirect(self.get_action_url('view', obj))
context = {
@ -362,7 +365,6 @@ class MasterView(View):
context = {
'instance': instance,
'instance_title': self.get_instance_title(instance),
'form': form,
}
return self.render_to_response('view', context)
@ -396,7 +398,6 @@ class MasterView(View):
"""
self.editing = True
instance = self.get_instance()
instance_title = self.get_instance_title(instance)
form = self.make_model_form(instance,
cancel_url_fallback=self.get_action_url('view', instance))
@ -407,7 +408,6 @@ class MasterView(View):
context = {
'instance': instance,
'instance_title': instance_title,
'form': form,
}
return self.render_to_response('edit', context)
@ -460,7 +460,6 @@ class MasterView(View):
"""
self.deleting = True
instance = self.get_instance()
instance_title = self.get_instance_title(instance)
if not self.is_deletable(instance):
return self.redirect(self.get_action_url('view', instance))
@ -481,7 +480,6 @@ class MasterView(View):
context = {
'instance': instance,
'instance_title': instance_title,
'form': form,
}
return self.render_to_response('delete', context)
@ -516,7 +514,7 @@ class MasterView(View):
# configure methods
##############################
def configure(self):
def configure(self, session=None):
"""
View for configuring aspects of the app which are pertinent to
this master view and/or model.
@ -562,7 +560,7 @@ class MasterView(View):
# maybe just 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.",
'warning')
@ -572,8 +570,8 @@ class MasterView(View):
# gather/save settings
data = get_form_data(self.request)
settings = self.configure_gather_settings(data)
self.configure_remove_settings()
self.configure_save_settings(settings)
self.configure_remove_settings(session=session)
self.configure_save_settings(settings, session=session)
self.request.session.flash("Settings have been saved.")
# reload configure page
@ -751,6 +749,7 @@ class MasterView(View):
def configure_remove_settings(
self,
simple_settings=None,
session=None,
):
"""
Remove all "known" settings from the DB; this is called by
@ -778,11 +777,11 @@ class MasterView(View):
if names:
# nb. must avoid self.Session here in case that does not
# point to our primary app DB
session = Session()
session = session or self.Session()
for name in names:
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
:meth:`configure()`.
@ -795,7 +794,7 @@ class MasterView(View):
"""
# nb. must avoid self.Session here in case that does not point
# to our primary app DB
session = Session()
session = session or self.Session()
for setting in settings:
self.app.save_setting(session, setting['name'], setting['value'],
force_create=True)
@ -885,7 +884,13 @@ class MasterView(View):
# add crud flags if we have an instance
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
template_prefix = self.get_template_prefix()
@ -1050,7 +1055,7 @@ class MasterView(View):
model = self.app.model
model_class = self.get_model_class()
if model_class and issubclass(model_class, model.Base):
session = session or Session()
session = session or self.Session()
return session.query(model_class)
def configure_grid(self, grid):
@ -1110,7 +1115,7 @@ class MasterView(View):
"""
model_class = self.get_model_class()
if model_class:
session = session or Session()
session = session or self.Session()
def filtr(query, model_key):
key = self.request.matchdict[model_key]
@ -1361,7 +1366,7 @@ class MasterView(View):
if model_class and issubclass(model_class, model.Base):
# add sqlalchemy model to session
session = session or Session()
session = session or self.Session()
session.add(obj)
##############################

View file

@ -85,6 +85,33 @@ class PersonView(MasterView):
if 'users' in f:
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):
base = globals()

View file

@ -454,6 +454,28 @@ class TestForm(TestCase):
# nb. no error message
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):
schema = self.make_schema()
form = self.make_form(schema=schema)

View file

@ -190,9 +190,10 @@ class TestGridAction(TestCase):
html = action.render_icon()
self.assertIn('<i class="fas fa-blarg">', html)
# oruga not yet supported
# oruga has different output
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):

View file

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

View file

@ -15,6 +15,9 @@ class TestPersonView(WebTestCase):
def make_view(self):
return people.PersonView(self.request)
def test_includeme(self):
self.pyramid_config.include('wuttaweb.views.people')
def test_get_query(self):
view = self.make_view()
query = view.get_query(session=self.session)
@ -37,3 +40,19 @@ class TestPersonView(WebTestCase):
view.configure_form(form)
self.assertTrue(form.required_fields)
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)