3
0
Fork 0

feat: add wizard for generating new master view code

This commit is contained in:
Lance Edgar 2025-12-31 18:52:37 -06:00
parent 92d4ce43b1
commit a62827d2c3
8 changed files with 1690 additions and 4 deletions

View file

@ -90,20 +90,33 @@ version_locations = wuttjamaican.db:alembic/versions
def test_get_xref_buttons(self):
self.pyramid_config.add_route("users", "/users/")
self.pyramid_config.add_route("master_views.create", "/views/master/new")
model = self.app.model
view = self.make_view()
# nb. must add this first
self.pyramid_config.add_wutta_master_view(UserView)
# now xref button should work
table = {"name": "person", "model_class": model.User}
# should be just one xref button by default
table = {"name": "user", "model_class": model.User}
buttons = view.get_xref_buttons(table)
self.assertEqual(len(buttons), 1)
button = buttons[0]
self.assertIn("Users", button)
self.assertIn("http://example.com/users/", button)
# unless we have perm to make new master view
with patch.object(self.request, "is_root", new=True):
table = {"name": "user", "model_class": model.User}
buttons = view.get_xref_buttons(table)
self.assertEqual(len(buttons), 3)
first, second, third = buttons
self.assertIn("Users", first)
self.assertIn("http://example.com/users/", first)
self.assertEqual(second, "<br />")
self.assertIn("New Master View", third)
self.assertIn("http://example.com/views/master/new", third)
def test_get_row_grid_data(self):
model = self.app.model
view = self.make_view()
@ -232,6 +245,7 @@ version_locations = wuttjamaican.db:alembic/versions
result = view.wizard_action()
self.assertIn("error", result)
self.assertEqual(result["error"], "File already exists")
self.assertEqual(os.path.getsize(module_path), 0)
# but it can overwrite if requested
with patch.dict(sample, {"overwrite": True}):

View file

@ -1,5 +1,9 @@
# -*- coding: utf-8; -*-
import os
import sys
from unittest.mock import patch
from wuttaweb.testing import WebTestCase
from wuttaweb.views import views as mod
from wuttaweb.views.users import UserView
@ -44,3 +48,323 @@ class TestMasterViewView(WebTestCase):
# nb. must invoke this to exercise the url logic
grid.get_vue_context()
def test_get_template_context(self):
view = self.make_view()
with patch.object(view, "Session", return_value=self.session):
# normal view gets no extra context
context = view.get_template_context({})
self.assertIsInstance(context, dict)
self.assertNotIn("app_models", context)
self.assertNotIn("view_module_dirs", context)
self.assertNotIn("view_module_dir", context)
self.assertNotIn("menu_path", context)
self.assertNotIn("roles", context)
self.assertNotIn("listing_roles", context)
self.assertNotIn("creating_roles", context)
self.assertNotIn("viewing_roles", context)
self.assertNotIn("editing_roles", context)
self.assertNotIn("deleting_roles", context)
# but 'create' view gets extra context
with patch.object(view, "creating", new=True):
context = view.get_template_context({})
self.assertIsInstance(context, dict)
self.assertIn("app_models", context)
self.assertIn("view_module_dirs", context)
self.assertIn("view_module_dir", context)
self.assertIn("menu_path", context)
self.assertIn("roles", context)
self.assertIn("listing_roles", context)
self.assertIn("creating_roles", context)
self.assertIn("viewing_roles", context)
self.assertIn("editing_roles", context)
self.assertIn("deleting_roles", context)
# try that again but this time make sure there is only
# one possibility for view module path, which is auto
# selected by default
with patch.object(
view, "get_view_module_options", return_value=["wuttaweb.views"]
):
context = view.get_template_context({})
self.assertEqual(context["view_module_dir"], "wuttaweb.views")
def test_get_view_module_options(self):
view = self.make_view()
# register one master view, which should be reflected in options
self.pyramid_config.add_wutta_master_view(UserView)
options = view.get_view_module_options()
self.assertEqual(len(options), 1)
self.assertEqual(options[0], "wuttaweb.views")
def test_suggest_details(self):
view = self.make_view()
# first test uses model_class
sample = {
"action": "suggest_details",
"model_option": "model_class",
"model_name": "Person",
}
with patch.object(self.request, "json_body", new=sample, create=True):
result = view.wizard_action()
self.assertEqual(result["class_file_name"], "people.py")
self.assertEqual(result["class_name"], "PersonView")
self.assertEqual(result["model_name"], "Person")
self.assertEqual(result["model_title"], "Person")
self.assertEqual(result["model_title_plural"], "People")
self.assertEqual(result["route_prefix"], "people")
self.assertEqual(result["permission_prefix"], "people")
self.assertEqual(result["url_prefix"], "/people")
self.assertEqual(result["template_prefix"], "/people")
self.assertIn("grid_columns", result)
self.assertIsInstance(result["grid_columns"], str)
self.assertIn("form_fields", result)
self.assertIsInstance(result["form_fields"], str)
# second test uses model_name
sample = {
"action": "suggest_details",
"model_option": "model_name",
"model_name": "acme_brick",
}
with patch.object(self.request, "json_body", new=sample, create=True):
result = view.wizard_action()
self.assertEqual(result["class_file_name"], "acme_bricks.py")
self.assertEqual(result["class_name"], "AcmeBrickView")
self.assertEqual(result["model_name"], "acme_brick")
self.assertEqual(result["model_title"], "Acme Brick")
self.assertEqual(result["model_title_plural"], "Acme Bricks")
self.assertEqual(result["route_prefix"], "acme_bricks")
self.assertEqual(result["permission_prefix"], "acme_bricks")
self.assertEqual(result["url_prefix"], "/acme-bricks")
self.assertEqual(result["template_prefix"], "/acme-bricks")
self.assertEqual(result["grid_columns"], "")
self.assertEqual(result["form_fields"], "")
def test_write_view_file(self):
view = self.make_view()
view_file_path = self.write_file("silly_things.py", "")
wutta_file_path = os.path.join(
os.path.dirname(sys.modules["wuttaweb.views"].__file__),
"silly_things.py",
)
self.assertEqual(os.path.getsize(view_file_path), 0)
# first test w/ Upgrade model_class and target file path
sample = {
"action": "write_view_file",
"view_location": None,
"view_file_path": view_file_path,
"overwrite": False,
"class_name": "UpgradeView",
"model_option": "model_class",
"model_name": "Upgrade",
"model_title": "Upgrade",
"model_title_plural": "Upgrades",
"route_prefix": "upgrades",
"permission_prefix": "upgrades",
"url_prefix": "/upgrades",
"template_prefix": "/upgrades",
"listable": True,
"creatable": True,
"viewable": True,
"editable": True,
"deletable": True,
"grid_columns": ["description", "created_by"],
"form_fields": ["description", "created_by"],
}
with patch.object(self.request, "json_body", new=sample, create=True):
# does not overwrite by default
result = view.wizard_action()
self.assertIn("error", result)
self.assertEqual(result["error"], "File already exists")
self.assertEqual(os.path.getsize(view_file_path), 0)
# but can overwrite if requested
with patch.dict(sample, {"overwrite": True}):
result = view.wizard_action()
self.assertNotIn("error", result)
self.assertGreater(os.path.getsize(view_file_path), 1000)
self.assertEqual(result["view_file_path"], view_file_path)
self.assertEqual(
result["view_module_path"], "poser.web.views.silly_things"
)
# reset file
with open(view_file_path, "wb") as f:
pass
self.assertEqual(os.path.getsize(view_file_path), 0)
# second test w/ silly_thing model_name and target module path
sample = {
"action": "write_view_file",
"view_location": "wuttaweb.views",
"view_file_name": "silly_things.py",
"overwrite": False,
"class_name": "SillyThingView",
"model_option": "model_name",
"model_name": "silly_thing",
"model_title": "Silly Thing",
"model_title_plural": "Silly Things",
"route_prefix": "silly_things",
"permission_prefix": "silly_things",
"url_prefix": "/silly-things",
"template_prefix": "/silly-things",
"listable": True,
"creatable": True,
"viewable": True,
"editable": True,
"deletable": True,
"grid_columns": ["id", "name", "description"],
"form_fields": ["id", "name", "description"],
}
with patch.object(self.request, "json_body", new=sample, create=True):
# file does not yet exist, so will be written
result = view.wizard_action()
self.assertNotIn("error", result)
self.assertEqual(result["view_file_path"], wutta_file_path)
self.assertGreater(os.path.getsize(wutta_file_path), 1000)
self.assertEqual(os.path.getsize(view_file_path), 0)
self.assertEqual(result["view_module_path"], "wuttaweb.views.silly_things")
# once file exists, will not overwrite by default
result = view.wizard_action()
self.assertIn("error", result)
self.assertEqual(result["error"], "File already exists")
self.assertEqual(os.path.getsize(view_file_path), 0)
# reset file
with open(wutta_file_path, "wb") as f:
pass
self.assertEqual(os.path.getsize(wutta_file_path), 0)
# can still overrwite explicitly
with patch.dict(sample, {"overwrite": True}):
result = view.wizard_action()
self.assertNotIn("error", result)
self.assertEqual(result["view_file_path"], wutta_file_path)
self.assertGreater(os.path.getsize(wutta_file_path), 1000)
self.assertEqual(os.path.getsize(view_file_path), 0)
self.assertEqual(
result["view_module_path"], "wuttaweb.views.silly_things"
)
# nb. must be sure to deleta that file!
os.remove(wutta_file_path)
def test_check_route(self):
self.pyramid_config.add_route("people", "/people/")
view = self.make_view()
sample = {
"action": "check_route",
"route": "people",
}
with patch.object(self.request, "json_body", new=sample, create=True):
# should get url and path
result = view.wizard_action()
self.assertEqual(result["url"], "http://example.com/people/")
self.assertEqual(result["path"], "/people/")
self.assertNotIn("problem", result)
# unless we check a bad route
with patch.dict(sample, {"route": "invalid_nothing_burger"}):
result = view.wizard_action()
self.assertIn("problem", result)
self.assertNotIn("url", result)
self.assertNotIn("path", result)
def test_apply_permissions(self):
model = self.app.model
auth = self.app.get_auth_handler()
admin = auth.get_role_administrator(self.session)
known = auth.get_role_authenticated(self.session)
manager = model.Role(name="Manager")
self.session.add(manager)
worker = model.Role(name="worker")
self.session.add(worker)
fred = model.User(username="fred")
fred.roles.append(manager)
fred.roles.append(worker)
self.session.add(fred)
self.session.commit()
self.assertFalse(auth.has_permission(self.session, fred, "people.list"))
self.assertFalse(auth.has_permission(self.session, fred, "people.create"))
self.assertFalse(auth.has_permission(self.session, fred, "people.view"))
self.assertFalse(auth.has_permission(self.session, fred, "people.edit"))
self.assertFalse(auth.has_permission(self.session, fred, "people.delete"))
view = self.make_view()
with patch.object(view, "Session", return_value=self.session):
sample = {
"action": "apply_permissions",
"permission_prefix": "people",
"listing_roles": {known.uuid.hex: True},
"creating_roles": {worker.uuid.hex: True},
"viewing_roles": {known.uuid.hex: True},
"editing_roles": {manager.uuid.hex: True},
"deleting_roles": {manager.uuid.hex: True},
}
with patch.object(self.request, "json_body", new=sample, create=True):
# nb. empty result is normal
result = view.wizard_action()
self.assertEqual(result, {})
self.assertTrue(auth.has_permission(self.session, fred, "people.list"))
self.assertTrue(
auth.has_permission(self.session, fred, "people.create")
)
self.assertTrue(auth.has_permission(self.session, fred, "people.view"))
self.assertTrue(auth.has_permission(self.session, fred, "people.edit"))
self.assertTrue(
auth.has_permission(self.session, fred, "people.delete")
)
def test_wizard_action(self):
view = self.make_view()
# missing action
with patch.object(self.request, "json_body", create=True, new={}):
result = view.wizard_action()
self.assertIn("error", result)
self.assertEqual(result["error"], "Must specify the action to perform.")
# unknown action
with patch.object(
self.request, "json_body", create=True, new={"action": "nothing"}
):
result = view.wizard_action()
self.assertIn("error", result)
self.assertEqual(result["error"], "Unknown action requested: nothing")
# error invoking action
with patch.object(
self.request, "json_body", create=True, new={"action": "check_route"}
):
with patch.object(view, "check_route", side_effect=RuntimeError("whoa")):
result = view.wizard_action()
self.assertIn("error", result)
self.assertEqual(result["error"], "Unexpected error occurred: whoa")
def test_configure(self):
self.pyramid_config.add_route("home", "/")
self.pyramid_config.add_route("login", "/auth/login")
self.pyramid_config.add_route("master_views", "/views/master")
view = self.make_view()
# sanity/coverage
view.configure()