diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e50e1b..3eaea4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,6 @@ All notable changes to wuttaweb will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## v0.30.2 (2026-03-22) - -### Fix - -- add support for merging 2 roles - ## v0.30.1 (2026-03-21) ### Fix diff --git a/pyproject.toml b/pyproject.toml index 7483aaf..7c5ec32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "WuttaWeb" -version = "0.30.2" +version = "0.30.1" description = "Web App for Wutta Framework" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}] diff --git a/src/wuttaweb/views/master.py b/src/wuttaweb/views/master.py index b4cc829..170afb6 100644 --- a/src/wuttaweb/views/master.py +++ b/src/wuttaweb/views/master.py @@ -3300,7 +3300,6 @@ class MasterView(View): # pylint: disable=too-many-public-methods removing_data, keeping_data, self.merge_get_final_data(removing_data, keeping_data), - fields=self.merge_get_all_fields(), ) context = {"removing": removing, "keeping": keeping, "diff": diff} diff --git a/src/wuttaweb/views/roles.py b/src/wuttaweb/views/roles.py index 9d63920..f1f9b1e 100644 --- a/src/wuttaweb/views/roles.py +++ b/src/wuttaweb/views/roles.py @@ -2,7 +2,7 @@ ################################################################################ # # wuttaweb -- Web App for Wutta Framework -# Copyright © 2024-2026 Lance Edgar +# Copyright © 2024-2025 Lance Edgar # # This file is part of Wutta Framework. # @@ -59,9 +59,6 @@ class RoleView(MasterView): # pylint: disable=abstract-method } sort_defaults = "name" - mergeable = True - merge_additive_fields = ["permission_count", "user_count"] - wutta_permissions = None # TODO: master should handle this, possibly via configure_form() @@ -287,83 +284,6 @@ class RoleView(MasterView): # pylint: disable=abstract-method else: auth.revoke_permission(role, pkey) - def merge_get_data(self, obj): # pylint: disable=empty-docstring - """ """ - data = super().merge_get_data(obj) - role = obj - - data["permissions"] = role.permissions - data["permission_count"] = len(data["permissions"]) - - data["usernames"] = [user.username for user in role.users] - data["user_count"] = len(data["usernames"]) - - return data - - def merge_get_final_data( - self, removing, keeping - ): # pylint: disable=empty-docstring - """ """ - final = super().merge_get_final_data(removing, keeping) - - permissions = set(removing["permissions"] + keeping["permissions"]) - final["permission_count"] = len(permissions) - - usernames = set(removing["usernames"] + keeping["usernames"]) - final["user_count"] = len(usernames) - - return final - - def merge_why_not(self, removing, keeping): - """ - This checks to ensure the "removing" role is not one of the - special built-in roles (Administrator, Authenticated, - Anonymous). - - See also parent method: - :meth:`~wuttaweb.views.master.MasterView.merge_why_not()` - """ - auth = self.app.get_auth_handler() - session = self.Session() - - if removing is auth.get_role_administrator(session): - return "Cannot remove the Administrator role." - - if removing is auth.get_role_anonymous(session): - return "Cannot remove the Anonymous role." - - if removing is auth.get_role_authenticated(session): - return "Cannot remove the Authenticated role." - - return None - - def merge_execute(self, removing, keeping): - """ - The logic to merge 2 roles is extended as follows: - - Any users belonging to the "removing" role will be added to - the "keeping" role (if not already present). - - Any permissions belonging to the "removing" role will be added - to the "keeping" role (if not already present). - - See also parent method: - :meth:`~wuttaweb.views.master.MasterView.merge_execute()` - """ - - # transfer permissions - for perm in list(removing.permissions): - if perm not in keeping.permissions: - keeping.permissions.append(perm) - - # transfer users - for user in list(removing.users): - if user not in keeping.users: - keeping.users.append(user) - - # continue default merge - super().merge_execute(removing, keeping) - @classmethod def defaults(cls, config): # pylint: disable=empty-docstring """ """ diff --git a/tests/views/test_roles.py b/tests/views/test_roles.py index eed5cfd..0eef4d2 100644 --- a/tests/views/test_roles.py +++ b/tests/views/test_roles.py @@ -284,153 +284,6 @@ class TestRoleView(WebTestCase): self.assertIs(role, blokes) self.assertEqual(blokes.permissions, ["widgets.polish", "widgets.view"]) - def test_merge_get_data(self): - model = self.app.model - auth = self.app.get_auth_handler() - - role = model.Role(name="whatever") - auth.grant_permission(role, "people.list") - auth.grant_permission(role, "people.view") - auth.grant_permission(role, "people.edit") - self.session.add(role) - - user1 = model.User(username="user1") - user1.roles.append(role) - self.session.add(user1) - - user2 = model.User(username="user2") - user2.roles.append(role) - self.session.add(user2) - - self.session.commit() - self.assertEqual(len(role.permissions), 3) - self.assertEqual(len(role.users), 2) - - view = self.make_view() - with patch.object(view, "Session", return_value=self.session): - data = view.merge_get_data(role) - self.assertEqual( - sorted(data["permissions"]), - ["people.edit", "people.list", "people.view"], - ) - self.assertEqual(data["permission_count"], 3) - self.assertEqual(data["usernames"], ["user1", "user2"]) - self.assertEqual(data["user_count"], 2) - - def test_merge_get_final_data(self): - model = self.app.model - auth = self.app.get_auth_handler() - - role1 = model.Role(name="whatever1") - auth.grant_permission(role1, "people.list") - auth.grant_permission(role1, "people.view") - auth.grant_permission(role1, "people.edit") - self.session.add(role1) - - role2 = model.Role(name="whatever2") - auth.grant_permission(role2, "people.list") - auth.grant_permission(role2, "people.view") - self.session.add(role2) - - user1 = model.User(username="user1") - user1.roles.append(role1) - self.session.add(user1) - - user2 = model.User(username="user2") - user2.roles.append(role1) - user2.roles.append(role2) - self.session.add(user2) - - self.session.commit() - self.assertEqual(len(role1.permissions), 3) - self.assertEqual(len(role1.users), 2) - self.assertEqual(len(role2.permissions), 2) - self.assertEqual(len(role2.users), 1) - - view = self.make_view() - with patch.object(view, "Session", return_value=self.session): - removing = view.merge_get_data(role1) - keeping = view.merge_get_data(role2) - final = view.merge_get_final_data(removing, keeping) - self.assertEqual(final["permission_count"], 3) - self.assertEqual(final["user_count"], 2) - - def test_merge_why_not(self): - model = self.app.model - auth = self.app.get_auth_handler() - - role1 = model.Role(name="whatever1") - self.session.add(role1) - role2 = model.Role(name="whatever2") - self.session.add(role2) - self.session.commit() - - view = self.make_view() - with patch.object(view, "Session", return_value=self.session): - - # normal merge is allowed - self.assertIsNone(view.merge_why_not(role1, role2)) - - # special roles can be part of a merge if they are being "kept" - # but not if being "removed" - - admin = auth.get_role_administrator(self.session) - self.assertIsNone(view.merge_why_not(role1, admin)) - reason = view.merge_why_not(admin, role1) - self.assertEqual(reason, "Cannot remove the Administrator role.") - - authed = auth.get_role_authenticated(self.session) - self.assertIsNone(view.merge_why_not(role1, authed)) - reason = view.merge_why_not(authed, role1) - self.assertEqual(reason, "Cannot remove the Authenticated role.") - - anon = auth.get_role_anonymous(self.session) - self.assertIsNone(view.merge_why_not(role1, anon)) - reason = view.merge_why_not(anon, role1) - self.assertEqual(reason, "Cannot remove the Anonymous role.") - - def test_merge_execute(self): - model = self.app.model - auth = self.app.get_auth_handler() - - role1 = model.Role(name="whatever1") - auth.grant_permission(role1, "people.list") - auth.grant_permission(role1, "people.view") - auth.grant_permission(role1, "people.edit") - self.session.add(role1) - - role2 = model.Role(name="whatever2") - auth.grant_permission(role2, "people.list") - auth.grant_permission(role2, "people.view") - self.session.add(role2) - - user1 = model.User(username="user1") - user1.roles.append(role1) - self.session.add(user1) - - user2 = model.User(username="user2") - user2.roles.append(role1) - user2.roles.append(role2) - self.session.add(user2) - - self.session.commit() - self.assertEqual(self.session.query(model.Role).count(), 2) - self.assertEqual(len(role1.permissions), 3) - self.assertEqual(len(role1.users), 2) - self.assertEqual(len(role2.permissions), 2) - self.assertEqual(len(role2.users), 1) - - view = self.make_view() - with patch.object(view, "Session", return_value=self.session): - view.merge_execute(role1, role2) - self.session.commit() - self.assertEqual(self.session.query(model.Role).count(), 1) - self.assertNotIn(role1, self.session) - self.assertIn(role2, self.session) - self.assertIs(role2, self.session.query(model.Role).one()) - self.assertEqual(len(role2.permissions), 3) - self.assertEqual(len(role2.users), 2) - class TestPermissionView(WebTestCase):