diff --git a/spp_api_v2/security/compliance.yaml b/spp_api_v2/security/compliance.yaml index fff4e0e36..e90abf127 100644 --- a/spp_api_v2/security/compliance.yaml +++ b/spp_api_v2/security/compliance.yaml @@ -162,10 +162,10 @@ record_rules: [] menus: # Root menu - visible to all API V2 users - # Located under Registry > Configuration + # Located under Settings > Registry (OP#1009) - id: menu_api_v2_root name: "API V2" - parent: spp_registry.spp_configuration_menu_root + parent: spp_registry.menu_registry_settings_root groups: [group_api_v2_viewer] # API Clients submenu diff --git a/spp_api_v2/views/menu.xml b/spp_api_v2/views/menu.xml index 5d998d23b..34f619f40 100644 --- a/spp_api_v2/views/menu.xml +++ b/spp_api_v2/views/menu.xml @@ -1,10 +1,10 @@ - + diff --git a/spp_farmer_registry/__manifest__.py b/spp_farmer_registry/__manifest__.py index 92143e178..1e2b110d9 100644 --- a/spp_farmer_registry/__manifest__.py +++ b/spp_farmer_registry/__manifest__.py @@ -43,7 +43,6 @@ "data/cel_constants.xml", "data/config_parameters.xml", "data/user_roles.xml", - "views/res_config_settings_views.xml", "views/farm_season_views.xml", "views/farm_details_views.xml", "views/farm_activity_views.xml", diff --git a/spp_farmer_registry/models/__init__.py b/spp_farmer_registry/models/__init__.py index a93481592..9db98934c 100644 --- a/spp_farmer_registry/models/__init__.py +++ b/spp_farmer_registry/models/__init__.py @@ -5,4 +5,3 @@ from . import farm_activity from . import farm_asset from . import farm -from . import res_config_settings diff --git a/spp_farmer_registry/models/res_config_settings.py b/spp_farmer_registry/models/res_config_settings.py deleted file mode 100644 index 55eda5d74..000000000 --- a/spp_farmer_registry/models/res_config_settings.py +++ /dev/null @@ -1,17 +0,0 @@ -# Part of OpenSPP. See LICENSE file for full copyright and licensing details. - -from odoo import fields, models - - -class ResConfigSettings(models.TransientModel): - _inherit = "res.config.settings" - - is_registry_admin_only_crud = fields.Boolean( - "Restrict Registry Create/Edit/Delete to Admin Only", - help=( - "Only administrators can add, modify, or remove registrants. " - "Other users can still view all registry data but cannot make changes." - ), - default=True, - config_parameter="spp_farmer_registry.registry_admin_only_crud", - ) diff --git a/spp_farmer_registry/views/res_config_settings_views.xml b/spp_farmer_registry/views/res_config_settings_views.xml deleted file mode 100644 index ceb765c21..000000000 --- a/spp_farmer_registry/views/res_config_settings_views.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - res.config.settings.view.form.inherit.farmer_registry - res.config.settings - - - - - - - - - - - - - - diff --git a/spp_import_match/views/import_match_view.xml b/spp_import_match/views/import_match_view.xml index 32488a9ce..87c181641 100644 --- a/spp_import_match/views/import_match_view.xml +++ b/spp_import_match/views/import_match_view.xml @@ -138,7 +138,7 @@ id="menu_spp_import_match" name="Import Match" action="action_spp_import_match" - parent="spp_registry.spp_configuration_menu_root" + parent="spp_registry.menu_registry_settings_root" sequence="1000" groups="spp_security.group_spp_admin" /> diff --git a/spp_registry/__manifest__.py b/spp_registry/__manifest__.py index 4b2a0948d..9241c0376 100644 --- a/spp_registry/__manifest__.py +++ b/spp_registry/__manifest__.py @@ -41,6 +41,10 @@ # Wizards "wizard/disable_registrant_view.xml", # Views - Base + # res_config_settings_views.xml defines menu_registry_settings_root, which + # main_view.xml reparents the Configuration menu under (OP#1009), so it + # must load first. + "views/res_config_settings_views.xml", "views/main_view.xml", "views/reg_relationship_view.xml", "views/reg_id_view.xml", diff --git a/spp_registry/models/__init__.py b/spp_registry/models/__init__.py index ca222d231..46184ab09 100644 --- a/spp_registry/models/__init__.py +++ b/spp_registry/models/__init__.py @@ -9,4 +9,5 @@ from . import reg_relationship from . import registrant from . import registry_config +from . import res_config_settings from . import res_users diff --git a/spp_registry/models/res_config_settings.py b/spp_registry/models/res_config_settings.py new file mode 100644 index 000000000..71d8e96a5 --- /dev/null +++ b/spp_registry/models/res_config_settings.py @@ -0,0 +1,51 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + +# Legacy config-parameter keys this toggle keeps in sync. The starter modules' +# controllers still read their own key (spp_farmer_registry reads the first, +# spp_starter_sp_mis reads the second), so the central "Registry Settings" +# toggle writes both — no migration or controller change needed, and neither +# deployment's enforcement breaks. +_LEGACY_KEYS = ( + "spp_farmer_registry.registry_admin_only_crud", + "spp_starter.registry_admin_only_crud", +) + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + is_registry_admin_only_crud = fields.Boolean( + "Restrict Registry Create/Edit/Delete to Admin Only", + default=True, + help=( + "Only administrators can add, modify, or remove registrants. " + "Other users can still view all registry data but cannot make changes." + ), + ) + + def get_values(self): + res = super().get_values() + # ir.config_parameter is a global system setting; sudo is the standard + # access pattern for reading it. + icp = self.env["ir.config_parameter"].sudo() # nosemgrep: odoo-sudo-without-context + # A deployment ships only one of the two starter controllers, so at most + # one key is set. Reflect an explicit value if present; otherwise fall + # back to the secure default (True), matching the legacy + # config_parameter default the starters used. + explicit = [v for v in (icp.get_param(key) for key in _LEGACY_KEYS) if v is not False] + res["is_registry_admin_only_crud"] = any(v == "True" for v in explicit) if explicit else True + return res + + def set_values(self): + res = super().set_values() + # ir.config_parameter is a global system setting; sudo is the standard + # access pattern for writing it. + icp = self.env["ir.config_parameter"].sudo() # nosemgrep: odoo-sudo-without-context + value = "True" if self.is_registry_admin_only_crud else "False" + # Keep both legacy keys in sync so whichever starter controller is + # installed reads the value the operator set here. + for key in _LEGACY_KEYS: + icp.set_param(key, value) + return res diff --git a/spp_registry/tests/__init__.py b/spp_registry/tests/__init__.py index c76f4067f..2177f1fd9 100644 --- a/spp_registry/tests/__init__.py +++ b/spp_registry/tests/__init__.py @@ -12,3 +12,4 @@ from . import test_membership_constraints from . import test_registrant_misc from . import test_group_aggregation +from . import test_res_config_settings diff --git a/spp_registry/tests/test_res_config_settings.py b/spp_registry/tests/test_res_config_settings.py new file mode 100644 index 000000000..fc5d9d03d --- /dev/null +++ b/spp_registry/tests/test_res_config_settings.py @@ -0,0 +1,53 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +"""Tests for the central Registry Settings (OP#1009). + +The "Restrict Registry Edits to Admin Only" toggle lives in spp_registry and +keeps both legacy config-parameter keys in sync so whichever starter +controller is installed reads the operator's choice. +""" + +from odoo.tests import TransactionCase, tagged + +FARMER_KEY = "spp_farmer_registry.registry_admin_only_crud" +SPMIS_KEY = "spp_starter.registry_admin_only_crud" + + +@tagged("post_install", "-at_install") +class TestRegistryResConfigSettings(TransactionCase): + """Registry admin-only-CRUD toggle: default + dual-key sync.""" + + def _icp(self): + return self.env["ir.config_parameter"].sudo() + + def _clear_keys(self): + self._icp().search([("key", "in", [FARMER_KEY, SPMIS_KEY])]).unlink() + + def test_defaults_true_when_unset(self): + """With neither legacy key set, the toggle defaults to True (secure).""" + self._clear_keys() + settings = self.env["res.config.settings"].create({}) + self.assertTrue(settings.is_registry_admin_only_crud) + + def test_set_values_writes_both_keys(self): + """Saving the toggle writes BOTH legacy keys so either controller reads it.""" + settings = self.env["res.config.settings"].create({"is_registry_admin_only_crud": True}) + settings.execute() + self.assertEqual(self._icp().get_param(FARMER_KEY), "True") + self.assertEqual(self._icp().get_param(SPMIS_KEY), "True") + + settings = self.env["res.config.settings"].create({"is_registry_admin_only_crud": False}) + settings.execute() + self.assertEqual(self._icp().get_param(FARMER_KEY), "False") + self.assertEqual(self._icp().get_param(SPMIS_KEY), "False") + + def test_get_values_reflects_an_explicit_key(self): + """An explicit value on either legacy key is reflected in the toggle.""" + self._clear_keys() + self._icp().set_param(SPMIS_KEY, "False") + settings = self.env["res.config.settings"].create({}) + self.assertFalse(settings.is_registry_admin_only_crud) + + self._clear_keys() + self._icp().set_param(FARMER_KEY, "True") + settings = self.env["res.config.settings"].create({}) + self.assertTrue(settings.is_registry_admin_only_crud) diff --git a/spp_registry/views/main_view.xml b/spp_registry/views/main_view.xml index 7f84415d3..e8b46863a 100644 --- a/spp_registry/views/main_view.xml +++ b/spp_registry/views/main_view.xml @@ -12,10 +12,17 @@ groups="spp_security.group_spp_admin,spp_registry.group_registry_manager,spp_registry.group_registry_officer,spp_registry.group_registry_viewer" /> + diff --git a/spp_registry/views/res_config_settings_views.xml b/spp_registry/views/res_config_settings_views.xml new file mode 100644 index 000000000..25372c25c --- /dev/null +++ b/spp_registry/views/res_config_settings_views.xml @@ -0,0 +1,75 @@ + + + + + res.config.settings.view.form.inherit.registry + res.config.settings + + + + + + + + + + + + + + + + + + Settings + ir.actions.act_window + res.config.settings + + form + current + {'module': 'registry_settings'} + + + + + + + + + + + + diff --git a/spp_starter_sp_mis/__manifest__.py b/spp_starter_sp_mis/__manifest__.py index 254a020a8..6cf87174d 100644 --- a/spp_starter_sp_mis/__manifest__.py +++ b/spp_starter_sp_mis/__manifest__.py @@ -22,7 +22,6 @@ ], "data": [ "data/config_parameters.xml", - "views/res_config_settings_views.xml", ], "assets": { "web.assets_backend": [ diff --git a/spp_starter_sp_mis/models/__init__.py b/spp_starter_sp_mis/models/__init__.py index cdb421fac..441611e10 100644 --- a/spp_starter_sp_mis/models/__init__.py +++ b/spp_starter_sp_mis/models/__init__.py @@ -1,2 +1 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. -from . import res_config_settings diff --git a/spp_starter_sp_mis/models/res_config_settings.py b/spp_starter_sp_mis/models/res_config_settings.py deleted file mode 100644 index 86a6520d8..000000000 --- a/spp_starter_sp_mis/models/res_config_settings.py +++ /dev/null @@ -1,17 +0,0 @@ -# Part of OpenSPP. See LICENSE file for full copyright and licensing details. - -from odoo import fields, models - - -class ResConfigSettings(models.TransientModel): - _inherit = "res.config.settings" - - is_registry_admin_only_crud = fields.Boolean( - "Restrict Registry Create/Edit/Delete to Admin Only", - help=( - "Only administrators can add, modify, or remove registrants. " - "Other users can still view all registry data but cannot make changes." - ), - default=True, - config_parameter="spp_starter.registry_admin_only_crud", - ) diff --git a/spp_starter_sp_mis/views/res_config_settings_views.xml b/spp_starter_sp_mis/views/res_config_settings_views.xml deleted file mode 100644 index 330f4241b..000000000 --- a/spp_starter_sp_mis/views/res_config_settings_views.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - res.config.settings.view.form.inherit.sp_mis - res.config.settings - - - - - - - - - - - - - - - - SP-MIS Settings - ir.actions.act_window - res.config.settings - - form - current - {'module': 'spp_starter_sp_mis'} - -