From be8a45e5434a52859b059ad0b7b20461b9a1de83 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 15 Aug 2024 02:10:08 -0500 Subject: [PATCH] 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..? --- src/wuttaweb/auth.py | 4 +- src/wuttaweb/forms/base.py | 14 +++++++ src/wuttaweb/grids/base.py | 2 +- src/wuttaweb/templates/form.mako | 14 ++++--- .../templates/people/view_profile.mako | 12 ++++++ src/wuttaweb/views/master.py | 39 +++++++++++-------- src/wuttaweb/views/people.py | 27 +++++++++++++ tests/forms/test_base.py | 22 +++++++++++ tests/grids/test_base.py | 5 ++- tests/views/test_master.py | 12 +++--- tests/views/test_people.py | 19 +++++++++ 11 files changed, 137 insertions(+), 33 deletions(-) create mode 100644 src/wuttaweb/templates/people/view_profile.mako diff --git a/src/wuttaweb/auth.py b/src/wuttaweb/auth.py index fb0519b..88b1fea 100644 --- a/src/wuttaweb/auth.py +++ b/src/wuttaweb/auth.py @@ -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)) diff --git a/src/wuttaweb/forms/base.py b/src/wuttaweb/forms/base.py index 80eec2d..f9e468b 100644 --- a/src/wuttaweb/forms/base.py +++ b/src/wuttaweb/forms/base.py @@ -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`. diff --git a/src/wuttaweb/grids/base.py b/src/wuttaweb/grids/base.py index add7517..1e793bd 100644 --- a/src/wuttaweb/grids/base.py +++ b/src/wuttaweb/grids/base.py @@ -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}') diff --git a/src/wuttaweb/templates/form.mako b/src/wuttaweb/templates/form.mako index 81b6ece..fc6d3c0 100644 --- a/src/wuttaweb/templates/form.mako +++ b/src/wuttaweb/templates/form.mako @@ -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 name="finalize_this_page_vars()"> ${parent.finalize_this_page_vars()} - + % if form is not Undefined: + + % endif diff --git a/src/wuttaweb/templates/people/view_profile.mako b/src/wuttaweb/templates/people/view_profile.mako new file mode 100644 index 0000000..1457074 --- /dev/null +++ b/src/wuttaweb/templates/people/view_profile.mako @@ -0,0 +1,12 @@ +## -*- coding: utf-8; -*- +<%inherit file="/master/view.mako" /> + +<%def name="page_content()"> +

+ TODO: view profile page content +

+ + +<%def name="title()">TODO: title + +${parent.body()} diff --git a/src/wuttaweb/views/master.py b/src/wuttaweb/views/master.py index 2fac5ae..fe4448f 100644 --- a/src/wuttaweb/views/master.py +++ b/src/wuttaweb/views/master.py @@ -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) ############################## diff --git a/src/wuttaweb/views/people.py b/src/wuttaweb/views/people.py index 249aa3f..372673f 100644 --- a/src/wuttaweb/views/people.py +++ b/src/wuttaweb/views/people.py @@ -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() diff --git a/tests/forms/test_base.py b/tests/forms/test_base.py index f75f1b7..399ecaf 100644 --- a/tests/forms/test_base.py +++ b/tests/forms/test_base.py @@ -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) diff --git a/tests/grids/test_base.py b/tests/grids/test_base.py index 79f37ae..c3e8f7b 100644 --- a/tests/grids/test_base.py +++ b/tests/grids/test_base.py @@ -190,9 +190,10 @@ class TestGridAction(TestCase): html = action.render_icon() self.assertIn('', 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('', html) def test_render_label(self): diff --git a/tests/views/test_master.py b/tests/views/test_master.py index 2e87687..1f00d28 100644 --- a/tests/views/test_master.py +++ b/tests/views/test_master.py @@ -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() diff --git a/tests/views/test_people.py b/tests/views/test_people.py index 4bd748d..cd6e1ae 100644 --- a/tests/views/test_people.py +++ b/tests/views/test_people.py @@ -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)