diff --git a/docs/developer_guide/customization/custom_areas/1.png b/docs/developer_guide/customization/custom_areas/1.png deleted file mode 100644 index 827e0a7f..00000000 Binary files a/docs/developer_guide/customization/custom_areas/1.png and /dev/null differ diff --git a/docs/developer_guide/customization/custom_areas/2.png b/docs/developer_guide/customization/custom_areas/2.png deleted file mode 100644 index 1cf0be06..00000000 Binary files a/docs/developer_guide/customization/custom_areas/2.png and /dev/null differ diff --git a/docs/developer_guide/customization/custom_areas/apps.png b/docs/developer_guide/customization/custom_areas/apps.png new file mode 100644 index 00000000..4e7ac899 Binary files /dev/null and b/docs/developer_guide/customization/custom_areas/apps.png differ diff --git a/docs/developer_guide/customization/custom_areas/updates.png b/docs/developer_guide/customization/custom_areas/updates.png new file mode 100644 index 00000000..7bb92d1f Binary files /dev/null and b/docs/developer_guide/customization/custom_areas/updates.png differ diff --git a/docs/developer_guide/customization/customizing_areas.md b/docs/developer_guide/customization/customizing_areas.md index 27d70d37..2db7fa6d 100644 --- a/docs/developer_guide/customization/customizing_areas.md +++ b/docs/developer_guide/customization/customizing_areas.md @@ -7,7 +7,7 @@ migration-notes: "Added during 2025 documentation reorganization" # Customize Areas -The following article guides the reader in understanding how the area module will work in OpenSPP and how it can be customized by providing a sample scenario and a working example. The area module is used to set up the administration areas in OpenSPP, which can be used in programs and other modules. +The following article guides the reader in understanding how the area module works in OpenSPP and how it can be customized by providing a sample scenario and a working example. The `spp_area_base` module provides the foundation for managing geographical areas in OpenSPP, which can be used in programs and other modules. ## Prerequisites @@ -18,79 +18,272 @@ The following article guides the reader in understanding how the area module wil 1. Log into OpenSPP with administrative rights. -2. Access the “Apps” menu from the dashboard to manage OpenSPP modules. +2. Access the "Apps" menu from the dashboard to manage OpenSPP modules. -3. Choose “Update Apps List” to refresh the module list. +3. Choose "Update Apps List" to refresh the module list. -4. Search for “Area” and initiate installation. This will also install the other modules required. +4. Search for "Area Management (Base)" or "spp_area_base" and initiate installation. This will also install the other modules required. -![](custom_areas/1.png) +![](custom_areas/apps.png) -## Utilizing the Area Module +## Understanding the Area Module Structure -For more detailed guidance on utilizing the Area module in OpenSPP, please refer to the information available at the provided link which will be publish soon. +The `spp_area_base` module provides the core area management functionality with the following key components: -## Customize Area +### Core Models +- **`spp.area`**: The main area model that manages geographical areas with hierarchical relationships +- **`spp.area.kind`**: Defines different types of areas (administrative regions, ecological zones, etc.) +- **`spp.area.import`**: Handles bulk import of area data from external sources -In a hypothetical scenario, customizing the areas module to include the population of the created areas serves as a practical example. This could involve recording the population of an area such as province or district, providing valuable insights for reporting and dashboard analysis. +### Key Features +- Hierarchical area structure with parent-child relationships +- Area codes for unique identification +- Area types for categorization +- Complete name computation showing full hierarchy path +- Area level management (up to 10 levels) +- Bulk import capabilities with queue job processing + +## Customizing the Area Module + +In a hypothetical scenario, customizing the areas module to include population data serves as a practical example. This could involve recording the population of an area such as province or district, providing valuable insights for reporting and dashboard analysis. A working sample module for the described scenario can be accessed at the provided [link](https://github.com/OpenSPP/documentation_code/tree/main/howto/developer_guides/customizations/spp_custom_area). The key steps in module development are as follows: -1. To customize areas, a new module can be developed. -2. To initiate the development of a custom module for area customization, begin by creating a manifest file. This file should include fields like name, category, and version. Additionally, it's crucial to define the dependencies of the new module as outlined below. +### 1. Create Module Structure + +To customize areas, create a new module following the OpenSPP module structure: + +``` +spp_custom_area/ +├── __init__.py +├── __manifest__.py +├── models/ +│ ├── __init__.py +│ └── area.py +├── views/ +│ └── area_views.xml +├── security/ +│ └── ir.model.access.csv +└── data/ + └── area_kind_data.xml +``` + +### 2. Define Module Manifest + +Create a manifest file that includes the proper dependencies and data files: ```python - "depends": [ - "spp_area", - ], -") +{ + "name": "OpenSPP Custom Area Extensions", + "summary": "Custom extensions for OpenSPP Area Management", + "category": "OpenSPP", + "version": "17.0.1.0.0", + "author": "Your Organization", + "website": "https://your-website.com", + "license": "LGPL-3", + "depends": [ + "spp_area_base", + ], + "data": [ + "security/ir.model.access.csv", + "views/area_views.xml", + "data/area_kind_data.xml", + ], + "application": False, + "installable": True, + "auto_install": False, +} ``` -3. To add the new field in the new module, develop a Python file named `area.py` that extends `spp.area` and incorporate this file into `models/init.py`. The definition of the population fields should be implemented as demonstrated below. +### 3. Extend the Area Model + +Create a Python file named `area.py` that extends the `spp.area` model and add it to `models/__init__.py`: ```python +from odoo import fields, models + class SPPArea(models.Model): - _inherit = "spp.area" + _inherit = "spp.area" + + # Population field following OpenSPP naming conventions + z_cst_area_population = fields.Integer( + string="Population", + help="Total population of this area" + ) + + # Additional custom fields + z_cst_area_population_year = fields.Integer( + string="Population Year", + help="Year when population data was collected" + ) + + z_cst_area_population_source = fields.Selection([ + ("census", "Census"), + ("survey", "Survey"), + ("estimate", "Estimate"), + ("other", "Other") + ], string="Population Source", default="census") +``` - population = fields.Integer() +### 4. Create View Extensions + +Create a new file called `views/area_views.xml` in the module and add it to the manifest file: + +```xml + + + view_spparea_form_custom + spp.area + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + view_spparea_tree_custom + spp.area + + + + + + + +
+``` +### 5. Add Security Access + +Create `security/ir.model.access.csv` to ensure proper access rights: + +```csv +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_spp_area_user,spp.area.user,spp_area_base.model_spp_area,base.group_user,1,1,1,0 +access_spp_area_manager,spp.area.manager,spp_area_base.model_spp_area,base.group_system,1,1,1,1 ``` -The code mentioned above will introduce a new field to the spp_area table for storing the province name of an area. To understand further, refer to the following documentation [Link 1](https://www.odoo.com/documentation/17.0/developer/tutorials/server_framework_101/03_basicmodel.html), [Link 2](https://www.odoo.com/documentation/17.0/developer/tutorials/server_framework_101/13_other_module.html), [Link 3](https://www.odoo.com/documentation/17.0/developer/tutorials/server_framework_101/12_inheritance.html) +### 6. Add Custom Area Types (Optional) -4. To integrate new fields into the UI, the following steps should be followed. +Create `data/area_kind_data.xml` to add custom area types: - - Create a new file called `views/custom_area_view.xml` in the module - - Add the below code to the manifest file. +```xml + + + + Population Zone + + + +``` - ```python - "data": [ - "views/custom_area_view.xml", - ], +### 7. Install and Test - ``` +1. Install the module through the Apps menu +2. Update the Area module to apply changes +3. Test the new fields in the area forms and lists - - The following code can be added to the custom_area_view.xml file to show the province name in the UI. +The following screenshot shows the added population fields in the newly developed module. - ```xml - - spp.area.view.form.inherit - spp.area - - - - - - - - ``` +![](custom_areas/updates.png) + +## Advanced Customization Examples + +### Adding Computed Fields + +You can add computed fields that calculate values based on other area data: + +```python +from odoo import fields, models, api + +class SPPArea(models.Model): + _inherit = "spp.area" + + z_ind_area_population_density = fields.Float( + string="Population Density", + compute="_compute_population_density", + store=True, + help="Population per square kilometer" + ) + + @api.depends("z_cst_area_population", "area_sqkm") + def _compute_population_density(self): + for record in self: + if record.area_sqkm and record.area_sqkm > 0: + record.z_ind_area_population_density = record.z_cst_area_population / record.area_sqkm + else: + record.z_ind_area_population_density = 0.0 +``` + +### Adding Constraints and Validations + +```python +from odoo import fields, models, api +from odoo.exceptions import ValidationError + +class SPPArea(models.Model): + _inherit = "spp.area" + + @api.constrains("z_cst_area_population") + def _check_population_positive(self): + for record in self: + if record.z_cst_area_population and record.z_cst_area_population < 0: + raise ValidationError("Population cannot be negative.") + + @api.constrains("z_cst_area_population_year") + def _check_population_year(self): + current_year = fields.Date.today().year + for record in self: + if record.z_cst_area_population_year: + if record.z_cst_area_population_year > current_year: + raise ValidationError("Population year cannot be in the future.") +``` - Further references [Link 1](https://www.odoo.com/documentation/17.0/developer/tutorials/server_framework_101/06_basicviews.html), [Link 2](https://www.odoo.com/documentation/17.0/developer/reference/user_interface/view_records.html) +## Best Practices -5. Install the module to include the new changes, then apply these updates through the backend App by selecting 'Update' in the Area module. +1. **Follow OpenSPP Naming Conventions**: Use the `z_cst_` prefix for custom fields +2. **Extend Existing Views**: Always inherit from existing views rather than creating new ones +3. **Add Proper Security**: Include appropriate access rights in your module +4. **Test Thoroughly**: Verify that your customizations work with the existing area hierarchy +5. **Document Changes**: Update your module's README with usage instructions -The following screenshot shows the added field population in the newly developed module. +## References -![](custom_areas/2.png) +For more information on extending Odoo models and views, refer to: +- [Odoo 17 Developer Documentation](https://www.odoo.com/documentation/17.0/developer/) +- [OpenSPP Development Guidelines](https://docs.openspp.org/) +- [Area Management Module Source](https://github.com/OpenSPP/openspp-modules/tree/17.0/spp_area_base) diff --git a/docs/developer_guide/customization/customizing_audit.md b/docs/developer_guide/customization/customizing_audit.md index 4d2ecd2d..151cf45e 100644 --- a/docs/developer_guide/customization/customizing_audit.md +++ b/docs/developer_guide/customization/customizing_audit.md @@ -7,7 +7,7 @@ migration-notes: "Added during 2025 documentation reorganization" # Customize Audit Logs -The following article guides the reader in understanding how the existing audit module can be customized by providing a sample scenario and a working example. +The following article guides the reader in understanding how the audit modules work in OpenSPP and how they can be customized by providing a sample scenario and a working example. The audit stack consists of `spp_audit_log` (core models and UI), `spp_audit_post` (optional chatter posting), and `spp_audit_config` (preconfigured rules). ## Prerequisites @@ -18,11 +18,11 @@ The following article guides the reader in understanding how the existing audit 1. Log into OpenSPP with administrative rights. -2. Access the “Apps” menu from the dashboard to manage OpenSPP modules. +2. Access the "Apps" menu from the dashboard to manage OpenSPP modules. -3. Choose “Update Apps List” to refresh the module list. +3. Choose "Update Apps List" to refresh the module list. -4. Search and initiate installation of the following modules, this process will also install all of their associated modules: +4. Search and initiate installation of the following modules, this will also install the other required modules: - SPP Audit Config - SPP Audit Log @@ -30,86 +30,171 @@ The following article guides the reader in understanding how the existing audit ![](custom_audit/0.png) -## Utilising the Audit Log Module +## Understanding the Audit Module Structure -1. Click on the “Audit Log” main menu to see the audit rules. You will see that the audit rules are already in place. They are created when installing the module “SPP Audit Config”. +The audit stack provides core logging and optional posting with the following key components: -2. If you want to add a new rule, you can click the "Create" button to add new rules. +### Core Models +- `spp.audit.rule`: Configures what to log per model. Fields include `model_id`, `field_to_log_ids`, and toggles for `log_create`, `log_write`, `log_unlink`, plus `view_logs` to add a context action. +- `spp.audit.log`: Stores individual change entries including `audit_rule_id`, `user_id`, `model_id`, `res_id`, `method`, and formatted `data_html`. +- (From `spp_audit_post`) `spp.audit.rule` adds parent linking (`parent_id`, `child_ids`, `field_id`) and `spp.audit.log` adds `parent_model_id`, `parent_res_ids_str`, and `parent_data_html` for posting to parent records. -3. To test the audit rule, go to Individual or Group Registry. +### Key Features +- Dynamic method decoration on target models (`create`, `write`, `_write`, `unlink`) when a rule is created or updated +- Field-level selection via `field_to_log_ids` and automatic formatting for selections, relational fields, and datetime values +- Optional action menu "View logs" bound to the target model when `view_logs` is enabled +- Optional posting of audit messages to the target record or its parent model chatter (`spp_audit_post`) +- Preconfigured default rules installed via `spp_audit_config` (`data/audit_rule_data.xml`) -4. Create or Update a record from the Registry. +## Customizing the Audit Module -5. You will see that an audit log will show in the bottom part of the page indicating the fields that were changed and their corresponding old and new values as shown below. +In a hypothetical scenario, customizing the audit rule to include an `active` flag serves as a practical example. This allows enabling/disabling rules without deletion. - ![](./custom_audit/1.png) +A working sample module for the described scenario can be accessed at the provided [link](https://github.com/OpenSPP/documentation_code/tree/main/howto/developer_guides/customizations/spp_audit_log_custom). -6. Further, all audit logs can be viewed by accessing the "Audit Log" main menu, followed by the Audit -> Log menu item. This page displays audit logs from every model indicated in the audit rules. +The key steps in module development are as follows: - ![](custom_audit/2.png) +### 1. Create Module Structure -7. Audit logs for specific records can be accessed by selecting a record from the Individual or Group Registry, clicking "Action," and then choosing "View Logs." +To customize audit logs, create a new module following the OpenSPP module structure: - ![](./custom_audit/3.png) +``` +spp_audit_log_custom/ +├── __init__.py +├── __manifest__.py +├── models/ +│ ├── __init__.py +│ └── spp_audit_rule.py +├── views/ +│ └── spp_audit_rule_views.xml +├── security/ +│ └── ir.model.access.csv +└── data/ + └── audit_rule_data.xml +``` -Note that when you add an audit rule to a model, it effectively patches a specialized function into the model's key events - create, write, and unlink. This function operates as a gatekeeper, first verifying if an audit rule is set for the specific model, then determining which events to log and fields to display in the audit log. +### 2. Define Module Manifest -## Customise Audit Log Module +Create a manifest file that includes the proper dependencies and data files: -In this hypothetical scenario, we are customizing the audit module to include the active field of a rule serves as a practical example. +```python +{ + "name": "OpenSPP Audit Customizations", + "summary": "Custom extensions for OpenSPP Audit", + "category": "OpenSPP", + "version": "17.0.1.0.0", + "author": "Your Organization", + "website": "https://your-website.com", + "license": "LGPL-3", + "depends": [ + "spp_audit_log", + # "spp_audit_post", # include if you extend or rely on chatter posting + ], + "data": [ + "views/spp_audit_rule_views.xml", + # "security/ir.model.access.csv", # not needed if you do not add new models + # "data/audit_rule_data.xml", # optional preconfigured rules + ], + "application": False, + "installable": True, + "auto_install": False, +} +``` -A working sample module for the described scenario can be accessed at the provided [link](https://github.com/OpenSPP/documentation_code/tree/main/howto/developer_guides/customizations/spp_audit_log_custom). +### 3. Extend the Audit Rule Model -The key steps in module development are as follows: +Create a Python file named `spp_audit_rule.py` that extends the `spp.audit.rule` model and add it to `models/__init__.py`: -1. To customize audit module, a new module can be developed. +```python +from odoo import fields, models -2. To initiate the development of a custom module for audit module customization, begin by creating a manifest file. This file should include fields like name, category, and version. Additionally, it's crucial to define the dependencies of the new module as outlined below. +class CustomAuditRule(models.Model): + _inherit = "spp.audit.rule" -```python -"depends": [ - "spp_audit_log", -], + active = fields.Boolean( + default=True, + help="If unchecked, the rule can be treated as inactive in your custom logic", + ) ``` -3. To add the new field in the new module, develop a Python file named `spp_audit_rule.py` that extends `spp.audit.rule` and incorporate this file into `models/init.py`. The definition of the active field should be implemented as demonstrated below. - -```python -from odoo import api, fields, models +### 4. Create View Extensions -class CustomAuditRule(models.Model): - _inherit = "spp.audit.rule" +Create a new file called `views/spp_audit_rule_views.xml` in the module and add it to the manifest file: - active = fields.Boolean(default=True) +```xml + + + view_custom_audit_log_form + spp.audit.rule + + + + + + + + ``` -The code mentioned above will introduce a new field to the `spp_audit_rule` table for storing the state of a rule. To understand further, refer to the following documentation [Link 1](https://www.odoo.com/documentation/17.0/developer/tutorials/server_framework_101/03_basicmodel.html), [Link 2](https://www.odoo.com/documentation/17.0/developer/tutorials/server_framework_101/13_other_module.html), [Link 3](https://www.odoo.com/documentation/17.0/developer/tutorials/server_framework_101/12_inheritance.html) +### 5. Add Security Access (Optional) -4. To integrate new fields into the UI, the following steps should be followed. Create a new file called `views/spp_audit_rule_views.xml` in the module. Add the below code to the manifest file. +If you introduce new models, include access rights. For simple field additions to existing models this is not required. A minimal example (optional): -```python -"data": [ - "views/spp_audit_rule_views.xml", -], +```csv +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_custom_spp_audit_rule,custom.spp.audit.rule,spp_audit_log.model_spp_audit_rule,base.group_system,1,1,1,0 ``` -5. The following code can be added to the `spp_audit_rule_views.xml` file to show the active checkbox in the UI. +### 6. Add Preconfigured Rules (Optional) + +You can seed rules using `data` with the `spp.audit.rule.create_rules` helper provided by `spp_audit_config`: ```xml - - view_custom_audit_log_form - spp.audit.rule - - - - - - - + + + + My Registry Rule + res.partner + + + + + ``` -6. Install the module to include the new changes. +For parent/child rules (requires `spp_audit_post`), also pass `parent_rule_name` and `connecting_field_name` to tie child rules to a parent model. + +### 7. Install and Test + +1. Install the module through the Apps menu +2. Create or update records in the configured models (e.g., Individual or Group Registry) +3. Open Audit Log → Audit → Log to review entries +4. On a specific record, use Action → View logs (if enabled) to see related entries + +The following screenshots show logs and the added field in the UI: -The following screenshot shows the added field population in the newly developed module. +![](./custom_audit/1.png) + +![](custom_audit/2.png) + +![](./custom_audit/3.png) ![](custom_audit/4.png) + +## Best Practices + +1. **Extend Existing Views**: Always inherit from existing views (`spp_audit_log.spp_audit_rule_form`) rather than creating new ones +2. **Use Targeted Field Lists**: Keep `field_to_log_ids` focused on business-critical fields +3. **Control Visibility**: Use `view_logs` when you want a context action; avoid clutter otherwise +4. **Test Thoroughly**: Verify create/write/unlink scenarios and method coverage +5. **Document Changes**: Update your module's README with usage instructions + +## References + +For more information on extending Odoo models and views, refer to: +- [Odoo 17 Developer Documentation](https://www.odoo.com/documentation/17.0/developer/) +- [OpenSPP Development Guidelines](https://docs.openspp.org/) +- Audit modules source: + - `spp_audit_log`: `https://github.com/OpenSPP/openspp-modules/tree/17.0/spp_audit_log` + - `spp_audit_post`: `https://github.com/OpenSPP/openspp-modules/tree/17.0/spp_audit_post` + - `spp_audit_config`: `https://github.com/OpenSPP/openspp-modules/tree/17.0/spp_audit_config` diff --git a/docs/developer_guide/customization/customizing_fields.md b/docs/developer_guide/customization/customizing_fields.md index 594d25e2..bb009c64 100644 --- a/docs/developer_guide/customization/customizing_fields.md +++ b/docs/developer_guide/customization/customizing_fields.md @@ -7,196 +7,264 @@ migration-notes: "Added during 2025 documentation reorganization" # Adding New Fields and Indicators -Custom fields allow the system to capture additional information that may be crucial for various social protection programs. Indicator fields are predefined fields within the system used to capture specific, standard metrics or key performance indicators (KPIs) that are essential for monitoring and evaluating the performance of social protection programs. Customizing groups and individuals with new fields and indicators enhances the system's ability to manage and analyze registrant data effectively. +The following article guides the reader in understanding how the registry modules work in OpenSPP and how to add a simple custom field to registrants by providing a sample scenario and a working-style example. In OpenSPP, registrants (both groups and individuals) are implemented on the `res.partner` model and surfaced through the `g2p_registry_group` and `g2p_registry_individual` modules. ## Prerequisites -- Knowledge of Python, Odoo, XML. +- Knowledge of Python, Odoo, XML, Xpaths. - To set up OpenSPP for development, please refer to the [Developer Guide](https://docs.openspp.org/howto/developer_guides/development_setup.html). -## Objective +## If the Registry modules are not installed -After following this guide, developers will be able to customize indicators in OpenSPP. +1. Log into OpenSPP with administrative rights. -## Step-by-step +2. Access the "Apps" menu from the dashboard to manage OpenSPP modules. -In OpenSPP, both groups and individuals are based on the **res.partner** model. They are differentiated by the is_group field: +3. Choose "Update Apps List" to refresh the module list. -- is_group = True for groups. -- is_group = False for individuals. +4. Search and initiate installation of the following modules, this will also install the other required modules: -These entities are linked through the **G2PGroupMembership** model, which connects individuals to their respective groups. + - G2P Registry: Group (`g2p_registry_group`) + - G2P Registry: Individual (`g2p_registry_individual`) -### Naming conventions for custom fields +## Understanding the Registry Structure -Custom fields in OpenSPP follow a specific naming convention: +The registry separates groups and individuals while sharing the same underlying model: -- **z\_** prefix for custom fields (x\_ when created from the UI). -- **ind** for indicator fields. -- **grp** for group-related fields. -- **cst** for custom fields. -- **indv** for individual-related fields. +### Core Models +- `res.partner`: Base model used for both groups and individuals. Differentiated by `is_group`. +- `g2p.group.membership` (referenced in OpenSPP): Connects individuals to their groups. -#### Example naming conventions +### Key Features +- A unified `res.partner` record represents either a group (`is_group = True`) or an individual (`is_group = False`). +- Rich registry user interfaces provided by `g2p_registry_group` and `g2p_registry_individual`. +- Existing views that you can extend to expose your custom fields: + - Individuals + - Tree: `g2p_registry_individual.view_individuals_list_tree` + - Form: `g2p_registry_individual.view_individuals_form` + - Groups + - Tree: `g2p_registry_group.view_groups_list_tree` + - Form: `g2p_registry_group.view_groups_form` -1. Indicator field for group +## Customizing the Registry (Add one field on res.partner) -```python -z_ind_grp_num_children -``` +In this scenario, we add a single custom field to `res.partner` that can be shown on both the Individual and Group registry interfaces. This keeps the example simple and focused. -**z\_**: prefix. -**ind**: Indicator. -**grp**: Group-related. -**num_children**: Descriptive name indicating the number of children +The key steps in module development are as follows: -2. Custom field for individual +### 1. Create Module Structure + +Create a new module following the OpenSPP module structure: -```python -z_cst_indv_disability_level +``` +spp_custom_registry_field/ +├── __init__.py +├── __manifest__.py +├── models/ +│ ├── __init__.py +│ └── res_partner.py +├── views/ +│ ├── individual_views.xml +│ └── group_views.xml +└── security/ + └── ir.model.access.csv ``` -**z\_**: prefix. -**cst**: Custom field. -**indv**: Individual-related. -**disability_level**: Descriptive name indicating the disability level. +### 2. Define Module Manifest -### Adding custom fields to groups +Create a manifest file that includes the proper dependencies and data files: -#### Example 1: Adding an indicator to track the number of children +```python +{ + "name": "OpenSPP Custom Registry Field", + "summary": "Adds a simple custom field to registrants (res.partner)", + "category": "OpenSPP", + "version": "17.0.1.0.0", + "author": "Your Organization", + "website": "https://your-website.com", + "license": "LGPL-3", + "depends": [ + "g2p_registry_group", + "g2p_registry_individual", + ], + "data": [ + "views/individual_views.xml", + "views/group_views.xml", + # "security/ir.model.access.csv", # not needed if you do not add new models + ], + "application": False, + "installable": True, + "auto_install": False, +} +``` -To add a custom field to a group, follow these steps: +### 3. Extend the res.partner Model -1. Define the field: +Create `models/res_partner.py` to add your custom field and import it in `models/__init__.py`. ```python -class G2PGroup(models.Model): +from odoo import fields, models + +class G2PRegistrant(models.Model): _inherit = "res.partner" - z_ind_grp_num_children = fields.Integer( - "Number of Children", - compute="_compute_ind_grp_num_children", - help="Number of children in the group", - store=True, - allow_filter=True, + # A simple custom field shared by both individuals and groups + z_cst_reg_note = fields.Char( + string="Registrant Note", + help="Free-form note for this registrant", ) ``` -2. Implement the compute method: - -```python -def _compute_ind_grp_num_children(self): - now = datetime.datetime.now() - children = now - relativedelta(years=CHILDREN_AGE_LIMIT) - domain = [("birthdate", ">=", children)] - self.compute_count_and_set_indicator("z_ind_grp_num_children", None, domain) -``` - -**compute_count_and_set_indicator** is a helper function in the OpenSPP API that simplifies computing indicators. You provide the field name, any specific relationship kinds, and the domain criteria to filter the relevant records. - -#### Example 2: Without compute_count_and_set_indicator +### 4. Create View Extensions -To compute an indicator without using compute_count_and_set_indicator, directly implement the compute logic within the method. +Expose the field on both Individuals and Groups UI by extending the existing views. -1. Define the field: - -```python -class G2PGroup(models.Model): - _inherit = "res.partner" +```xml + + + + view_individuals_form_custom_reg_note + res.partner + + + + + + + + + - z_ind_grp_is_single_head_hh = fields.Boolean( - "Is Single-Headed Household", - compute="_compute_ind_grp_is_single_head_hh", - help="Indicates if the household is single-headed", - store=True, - allow_filter=True, - ) -``` + + + view_individuals_list_tree_custom_reg_note + res.partner + + + + + + + -2. Implement the compute method: + + + view_groups_form_custom_reg_note + res.partner + + + + + + + + + -```python -def _compute_ind_grp_is_single_head_hh(self): - for record in self: - adult_count = record.group_membership_ids.filtered( - lambda member: member.individual.birthdate <= (datetime.datetime.now() - relativedelta(years=18)) - ).mapped('individual') - record.z_ind_grp_is_single_head_hh = len(adult_count) == 1 + + + view_groups_list_tree_custom_reg_note + res.partner + + + + + + + + ``` -Do not use **@api.depends** when you want to compute indicators based on individual information, it will be very inefficient. we have another mechanism to recompute when needed. +### 5. Add Security Access (Optional) -### Adding custom fields to individuals +If you introduce new models, include access rights. For a simple field addition to `res.partner`, this is not required. A minimal example (optional): -#### Example: Adding a custom field for disability level +```csv +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_custom_partner_note,custom.partner.note,base.model_res_partner,base.group_user,1,1,0,0 +``` -To add a custom field to an individual it is only necessary to define the field: +### 6. Install and Test -```python -class G2PIndividual(models.Model): - _inherit = "res.partner" - z_cst_indv_disability_level = fields.Integer("Disability Level") -``` +1. Install the module through the Apps menu. +2. Open the Individual and Group registries and verify the new field displays in both list and form views. +3. Create or update records and ensure the new field can be edited and saved. -### Integrating custom fields into the UI +## Advanced Customization Examples -To ensure custom fields are accessible in the user interface for filtering and reporting, modify the XML views in OpenSPP. Below is a list of views that can be extended, followed by examples of how to add custom fields to these views. +### Adding Computed Fields -#### OpenSPP views that can be extended +You can add computed fields on `res.partner` for both Individuals and Groups. Below are simple examples: -**Individuals views** +```python +from odoo import fields, models, api -- Tree view - -- Identifier: g2p_registry_individual.view_individuals_list_tree -- Form view - -- Identifier: g2p_registry_individual.view_individuals_form +class G2PRegistrant(models.Model): + _inherit = "res.partner" -**Groups views** + # Individual indicator: age in years (0 for groups or missing birthdate) + z_ind_indv_age_years = fields.Integer( + string="Age (years)", + compute="_compute_indv_age_years", + store=True, + help="Computed age in years for individuals", + ) -- Tree view - -- Identifier: g2p_registry_group.view_groups_list_tree -- Form view - -- Identifier: g2p_registry_group.view_groups_form + # Group indicator: number of members (0 for individuals) + z_ind_grp_member_count = fields.Integer( + string="Group Member Count", + compute="_compute_grp_member_count", + store=True, + help="Computed number of members for groups", + ) -By extending these views, you can ensure that custom fields are visible and manageable within the OpenSPP interface. Here are examples of how to extend these views to add your custom fields. + @api.depends("birthdate") + def _compute_indv_age_years(self): + today = fields.Date.context_today(self) + for partner in self: + if partner.is_group or not partner.birthdate: + partner.z_ind_indv_age_years = 0 + continue + age = today.year - partner.birthdate.year - ( + (today.month, today.day) < (partner.birthdate.month, partner.birthdate.day) + ) + partner.z_ind_indv_age_years = max(age, 0) -#### Adding custom fields to individual views + @api.depends("group_membership_ids.individual") + def _compute_grp_member_count(self): + for partner in self: + partner.z_ind_grp_member_count = ( + len(partner.group_membership_ids) if partner.is_group else 0 + ) +``` -**Tree view** +Optionally expose the computed fields in the UI: ```xml - - view_individuals_list_tree_custom + + + view_individuals_form_custom_indicators res.partner - + - - 1 - - - + + + + - -``` - -**Form view** -```xml - - - view_individuals_form_custom + + + view_groups_form_custom_indicators res.partner - + - - 1 - - + @@ -204,155 +272,141 @@ By extending these views, you can ensure that custom fields are visible and mana ``` -#### Adding custom fields to group views +Performance note: for high-volume indicators, consider batch recomputation strategies similar to those used in OpenSPP indicator modules instead of heavy `@api.depends` chains. + +## Adding a One2many Field (creates a new model) + +To relate multiple records to a registrant, add a One2many on `res.partner` and define its comodel. Because this introduces a new model, include proper access rights and reference the G2P admin group from `g2p_registry_base`. + +### 7. Define the New Model and One2many + +Create `models/partner_note.py`: + +```python +from odoo import fields, models + +class PartnerNote(models.Model): + _name = "spp.partner.note" + _description = "Partner Note" + + partner_id = fields.Many2one( + "res.partner", + string="Registrant", + required=True, + ondelete="cascade", + ) + name = fields.Char(required=True) + description = fields.Text() +``` + +Extend `res.partner` in `models/res_partner.py`: + +```python +from odoo import fields, models + +class G2PRegistrant(models.Model): + _inherit = "res.partner" + + z_cst_reg_notes = fields.One2many( + "spp.partner.note", + "partner_id", + string="Notes", + ) +``` + +Update `models/__init__.py`: + +```python +from . import res_partner +from . import partner_note +``` -**Tree view** +### 8. Expose in the UI ```xml - - view_individuals_list_tree_custom + + + view_individuals_form_custom_reg_notes res.partner - + - - 1 - - - + + + + + + + +
+ + + + +
+
+
-
-``` - -**Form view** -```xml - - - view_individuals_form_custom + + + view_groups_form_custom_reg_notes res.partner - + - - 1 - - - - - + + + + + + + +
+ + + + +
+
+
``` -By integrating these fields into the appropriate views, you ensure that the custom data is visible and manageable within the OpenSPP interface, enhancing the system's usability and functionality. - -### Testing and validation +### 9. Manifest and Security -Testing customizations is essential to ensure they work as expected. Validate your changes by writing tests with sample data and scenarios. +Update the manifest to include security: -#### Example test case +```python +"data": [ + "views/individual_views.xml", + "views/group_views.xml", + "security/ir.model.access.csv", +], +``` -To validate your custom fields and computed indicators, create a test class. +Create `security/ir.model.access.csv` with access for the G2P Admin group (from `g2p_registry_base.g2p_security`, XML ID `g2p_registry_base.group_g2p_admin`): -```python -import datetime -import logging - -from dateutil.relativedelta import relativedelta -from odoo.tests import tagged -from odoo.tests.common import TransactionCase - -_logger = logging.getLogger(__name__) - -@tagged("post_install", "-at_install") -class ComputeIndicatorFieldsTest(TransactionCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.env = cls.env( - context=dict( - cls.env.context, - test_queue_job_no_delay=True, - ) - ) - - # Initial Setup of Variables - cls.registrant_1 = cls.env["res.partner"].create( - { - "name": "Heidi Jaddranka", - "is_group": False, - "is_registrant": True, - "gender": "Female", - "birthdate": datetime.datetime.now(), - "z_cst_indv_disability_level": 5, - } - ) - cls.registrant_2 = cls.env["res.partner"].create - - ( - { - "name": "Angus Kleitos", - "is_group": False, - "is_registrant": True, - "gender": "Male", - "birthdate": datetime.datetime.now(), - "z_cst_indv_disability_level": 10, - } - ) - cls.group_1 = cls.env["res.partner"].create( - { - "name": "Group 1", - "is_group": True, - "is_registrant": True, - } - ) - members1 = [ - {"individual": cls.registrant_1.id}, - {"individual": cls.registrant_2.id} - ] - group1_members = [[0, 0, val] for val in members1] - cls.group_1.write({"group_membership_ids": group1_members}) - - def test_01_num_children(self): - self.group_1._compute_ind_grp_num_children() - self.assertEqual( - self.group_1.z_ind_grp_num_children, - 2, - ) - - def test_02_num_elderly(self): - now = datetime.datetime.now() - birthdate = now - relativedelta(years=66) - self.registrant_1.write({"birthdate": birthdate}) - self.group_1._compute_ind_grp_num_elderly() - self.assertEqual( - self.group_1.z_ind_grp_num_elderly, - 1, - ) - - def test_03_num_adults_female_not_elderly(self): - now = datetime.datetime.now() - birthdate = now - relativedelta(years=30) - self.registrant_1.write({"birthdate": birthdate}) - self.group_1._compute_ind_grp_num_adults_female_not_elderly() - self.assertEqual( - self.group_1.z_ind_grp_num_adults_female_not_elderly, - 1, - ) - - def test_04_num_adults_male_not_elderly(self): - now = datetime.datetime.now() - birthdate = now - relativedelta(years=30) - self.registrant_2.write({"birthdate": birthdate}) - self.group_1._compute_ind_grp_num_adults_male_not_elderly() - self.assertEqual( - self.group_1.z_ind_grp_num_adults_male_not_elderly, - 1, - ) +```csv +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_spp_partner_note_admin,spp.partner.note.admin,model_spp_partner_note,g2p_registry_base.group_g2p_admin,1,1,1,1 ``` -By writing and running these tests, you can ensure that your custom fields and indicators work correctly within OpenSPP. This approach helps maintain the system's integrity and reliability as you implement customizations. +## Best Practices + +1. **Follow OpenSPP Naming Conventions**: Use the `z_cst_` prefix for custom fields. Add `indv`/`grp` when a field is specific to one type. +2. **Extend Existing Views**: Always inherit from existing views rather than creating new ones. +3. **Be Selective**: Only surface fields that add value to users; place them logically in the form/tree. +4. **Test Thoroughly**: Verify both Individual and Group flows where the field appears. +5. **Document Changes**: Update your module README with usage instructions. + +## References + +For more information on extending Odoo models and views, refer to: +- [Odoo 17 Developer Documentation](https://www.odoo.com/documentation/17.0/developer/) +- [OpenSPP Development Guidelines](https://docs.openspp.org/) +- Registry modules referenced: `g2p_registry_group`, `g2p_registry_individual` diff --git a/docs/getting_started/installation_deb.md b/docs/getting_started/installation_deb.md index cea2b1a9..6d5419e1 100644 --- a/docs/getting_started/installation_deb.md +++ b/docs/getting_started/installation_deb.md @@ -14,6 +14,7 @@ This guide walks you through installing OpenSPP on Ubuntu 24.04 or Debian 12 (Bo Before installing OpenSPP, ensure you have: - Ubuntu 24.04 LTS (Noble Numbat) or Debian 12 (Bookworm) +- 64-bit Intel or AMD CPU (amd64) - Minimum 4GB RAM (8GB recommended for production) - Minimum 10GB disk space - Root or sudo access @@ -29,15 +30,22 @@ sudo apt-get update sudo apt-get upgrade -y ``` +Install wget, gnupg2 +```bash +sudo apt-get install -y wget gnupg2 +``` + ## Step 2: Install PostgreSQL OpenSPP requires PostgreSQL as its database backend. +### Install PostgreSQL ```bash -# Install PostgreSQL sudo apt-get install -y postgresql postgresql-client +``` -# Verify PostgreSQL is running +### Verify PostgreSQL is running +```bash sudo systemctl status postgresql ``` @@ -45,15 +53,17 @@ sudo systemctl status postgresql Add the OpenSPP APT repository to your system: +### Add the OpenSPP public key ```bash -# Add the OpenSPP public key wget -qO - https://builds.acn.fr/repository/apt-keys/openspp/public.key | sudo apt-key add - - -# Add the OpenSPP repository +``` +### Add the OpenSPP repository +```bash echo "deb https://builds.acn.fr/repository/apt-openspp-daily bookworm main" | \ sudo tee /etc/apt/sources.list.d/openspp.list - -# Update package list +``` +### Update package list +```bash sudo apt-get update ``` @@ -61,8 +71,8 @@ sudo apt-get update Install OpenSPP directly from the repository: +### Install OpenSPP package ```bash -# Install OpenSPP package sudo apt-get install -y openspp-17-daily ``` @@ -70,18 +80,21 @@ sudo apt-get install -y openspp-17-daily If you prefer to download the package manually or the repository is not accessible: +### Create a temporary directory ```bash -# Create a temporary directory mkdir -p ~/openspp-install cd ~/openspp-install - -# Download directly from Nexus repository +``` +### Download directly from Nexus repository +```bash wget https://builds.acn.fr/repository/apt-openspp/pool/main/o/openspp/openspp_17.0.1+odoo17.0-1_amd64.deb - -# Install the package +``` +### Install the package +```bash sudo dpkg -i openspp_17.0.1+odoo17.0-1_amd64.deb - -# Fix any dependency issues if they occur +``` +### Fix any dependency issues if they occur +```bash sudo apt-get install -f ``` @@ -97,12 +110,14 @@ The installation will: Create a PostgreSQL user for OpenSPP: +### Create the openspp PostgreSQL user ```bash -# Create the openspp PostgreSQL user sudo -u postgres createuser -s openspp - -# Optional: If you want to use password authentication instead of peer authentication -# sudo -u postgres psql -c "ALTER USER openspp WITH PASSWORD 'your_secure_password';" +``` +### Set password (Optional) +If you want to use password authentication instead of peer authentication +```bash +sudo -u postgres psql -c "ALTER USER openspp WITH PASSWORD 'your_secure_password';" ``` ## Step 6: Configure OpenSPP @@ -113,15 +128,18 @@ The main configuration file is located at `/etc/openspp/odoo.conf`. 1. **Set the admin password** (IMPORTANT for security): +### Generate a strong password ```bash -# Generate a strong password openssl rand -base64 32 +``` +Copy the generated password -# Edit the configuration +### Edit the configuration +```bash sudo nano /etc/openspp/odoo.conf ``` -Find and update these lines: +Find and update these lines (paste the generated password): ```ini ; Security admin_passwd = YOUR_STRONG_PASSWORD_HERE @@ -190,14 +208,16 @@ db_password = your_postgresql_password ## Step 7: Start OpenSPP Service +### Enable the service to start on boot ```bash -# Enable the service to start on boot sudo systemctl enable openspp - -# Start the service +``` +### Start the service +```bash sudo systemctl start openspp - -# Check service status +``` +### Check service status +```bash sudo systemctl status openspp ```