From b6005772f7eda42071e00b799fe6ab5fdec0d000 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Tue, 9 Jun 2026 11:19:20 +0800 Subject: [PATCH 1/2] feat(registry): add Registry Settings section and consolidate registry config (#1009) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New 'Registry Settings' section in the Settings app (spp_registry), mirroring 'Programs Settings', carrying the 'Restrict Registry Edits to Admin Only' toggle. - The toggle keeps both legacy config-parameter keys (spp_farmer_registry.registry_admin_only_crud and spp_starter.registry_admin_only_crud) in sync via get_values/set_values, so the spp_farmer_registry and spp_starter_sp_mis controllers keep reading their own key — no migration, no enforcement change. Removed the now-duplicate 'Farmer Registry Settings' and 'SP-MIS Settings' sections and their field definitions. - Relocated the API V2 and Import Match menus out of Registry > Configuration into a new 'Registry' group under the Settings app (Settings > Registry), and updated the API V2 security compliance manifest to match. --- spp_api_v2/security/compliance.yaml | 4 +- spp_api_v2/views/menu.xml | 4 +- spp_farmer_registry/__manifest__.py | 1 - spp_farmer_registry/models/__init__.py | 1 - .../models/res_config_settings.py | 17 ------ .../views/res_config_settings_views.xml | 23 -------- spp_import_match/views/import_match_view.xml | 2 +- spp_registry/__manifest__.py | 1 + spp_registry/models/__init__.py | 1 + spp_registry/models/res_config_settings.py | 51 ++++++++++++++++ spp_registry/tests/__init__.py | 1 + .../tests/test_res_config_settings.py | 53 +++++++++++++++++ .../views/res_config_settings_views.xml | 59 +++++++++++++++++++ spp_starter_sp_mis/__manifest__.py | 1 - spp_starter_sp_mis/models/__init__.py | 1 - .../models/res_config_settings.py | 17 ------ .../views/res_config_settings_views.xml | 37 ------------ 17 files changed, 171 insertions(+), 103 deletions(-) delete mode 100644 spp_farmer_registry/models/res_config_settings.py delete mode 100644 spp_farmer_registry/views/res_config_settings_views.xml create mode 100644 spp_registry/models/res_config_settings.py create mode 100644 spp_registry/tests/test_res_config_settings.py create mode 100644 spp_registry/views/res_config_settings_views.xml delete mode 100644 spp_starter_sp_mis/models/res_config_settings.py delete mode 100644 spp_starter_sp_mis/views/res_config_settings_views.xml 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 e17bfd40b..2a360b87c 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 c5ad486f1..1b5339a65 100644 --- a/spp_registry/__manifest__.py +++ b/spp_registry/__manifest__.py @@ -42,6 +42,7 @@ "wizard/disable_registrant_view.xml", # Views - Base "views/main_view.xml", + "views/res_config_settings_views.xml", "views/reg_relationship_view.xml", "views/reg_id_view.xml", "views/id_types_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/res_config_settings_views.xml b/spp_registry/views/res_config_settings_views.xml new file mode 100644 index 000000000..704be05c2 --- /dev/null +++ b/spp_registry/views/res_config_settings_views.xml @@ -0,0 +1,59 @@ + + + + + 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'} - - From d6e6d2fa18fa280c57c96a61cd4a655e432f06bc Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Fri, 17 Jul 2026 11:10:15 +0800 Subject: [PATCH 2/2] feat(registry): relocate Configuration menu to Settings > Registry (#1009) --- spp_registry/__manifest__.py | 5 ++++- spp_registry/views/main_view.xml | 9 ++++++++- spp_registry/views/res_config_settings_views.xml | 16 ++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/spp_registry/__manifest__.py b/spp_registry/__manifest__.py index 1b5339a65..d13dc6190 100644 --- a/spp_registry/__manifest__.py +++ b/spp_registry/__manifest__.py @@ -41,8 +41,11 @@ # Wizards "wizard/disable_registrant_view.xml", # Views - Base - "views/main_view.xml", + # 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", "views/id_types_view.xml", 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 index 704be05c2..25372c25c 100644 --- a/spp_registry/views/res_config_settings_views.xml +++ b/spp_registry/views/res_config_settings_views.xml @@ -56,4 +56,20 @@ action="res_config_settings_menu_registry_action" groups="spp_security.group_spp_admin" /> + + + + +