Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cetmix_tower_jet_isolation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import wizards
17 changes: 17 additions & 0 deletions cetmix_tower_jet_isolation/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "Cetmix Tower Jet Isolation",
"version": "18.0.1.0.0",
"category": "Tower",
"summary": "Adds strict isolation mode for Jets, preventing users from altering command visibility.",
"author": "Antigravity",
"depends": [
"cetmix_tower_server",
],
"data": [
"views/cx_tower_jet_template_views.xml",
"views/cx_tower_command_run_wizard_views.xml",
"views/cx_tower_plan_run_wizard_views.xml",
],
"installable": True,
"license": "AGPL-3",
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
1 change: 1 addition & 0 deletions cetmix_tower_jet_isolation/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import cx_tower_jet_template
25 changes: 25 additions & 0 deletions cetmix_tower_jet_isolation/models/cx_tower_jet_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from odoo import models, fields

class CxTowerJetTemplate(models.Model):
_inherit = 'cx.tower.jet.template'

isolation_mode = fields.Boolean(
string='Isolation Mode',
help='When active, prevents users from changing applicability or tags when running commands/plans.'
)
forced_applicability = fields.Selection([
('this', 'For selected server(s)'),
('shared', 'Non server restricted')
], string='Forced Applicability')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Enforce forced_applicability presence when isolation_mode=True on the server.

The XML view declares required="isolation_mode", but this is only a UI constraint — RPC writes can leave forced_applicability empty while isolation_mode=True, which makes the wizard's if template.forced_applicability: silently skip enforcement. Add a Python @api.constrains('isolation_mode', 'forced_applicability') (or a SQL/DB-level check) to make the rule authoritative.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/models/cx_tower_jet_template.py` around lines 10 -
13, Add a server-side constraint to enforce that forced_applicability is set
when isolation_mode is True: in the model class (e.g., CxTowerJetTemplate) add
an `@api.constrains`('isolation_mode', 'forced_applicability') method that checks
if record.isolation_mode and not record.forced_applicability then raise
ValidationError with a clear message; ensure you import from odoo import api and
from odoo.exceptions import ValidationError so RPC writes trigger the same
validation as the UI.

🧹 Nitpick | 🔵 Trivial

Avoid duplicating the applicability selection values.

forced_applicability hardcodes [('this', ...), ('shared', ...)], duplicating the selection on cx.tower.plan.run.wizard.applicability. If the base selection is ever extended (e.g., a new option), these will drift. Prefer deriving the selection from the base field, e.g.:

forced_applicability = fields.Selection(
    selection=lambda self: self.env["cx.tower.plan.run.wizard"]._fields["applicability"].selection,
    string="Forced Applicability",
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/models/cx_tower_jet_template.py` around lines 10 -
13, Replace the hardcoded selection on forced_applicability with a dynamic
selection that reads the existing applicability selection from the
cx.tower.plan.run.wizard field to avoid duplication; update the
forced_applicability field definition (in the cx_tower_jet_template model) so
its selection is provided as a lambda that returns
self.env["cx.tower.plan.run.wizard"]._fields["applicability"].selection and keep
the string parameter (e.g., "Forced Applicability") unchanged.


forced_command_tag_ids = fields.Many2many(
comodel_name='cx.tower.tag',
relation='cx_tower_template_forced_command_tag_rel',
string='Forced Command Tags',
)

forced_plan_tag_ids = fields.Many2many(
comodel_name='cx.tower.tag',
relation='cx_tower_template_forced_plan_tag_rel',
string='Forced Flight Plan Tags',
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Missing module/class docstring and style inconsistencies.

This file uses single quotes and omits a class docstring, diverging from the _description/docstring conventions in cetmix_tower_server/models/cx_tower_jet_template.py. Please align with repo style (double quotes, docstring, trailing commas on multiline literals).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/models/cx_tower_jet_template.py` around lines 1 -
25, Update the model to follow repo style: change all single quotes to double
quotes, add a class docstring and the _description attribute on class
CxTowerJetTemplate describing the purpose, and ensure trailing commas on
multiline literals (e.g., the forced_applicability selection list and the
Many2many field definitions forced_command_tag_ids and forced_plan_tag_ids).
Specifically, add a top-of-class docstring string, set _description = "..."
under the class, replace string='...' occurrences on fields (isolation_mode,
forced_applicability, forced_command_tag_ids, forced_plan_tag_ids) with
double-quoted values, and ensure trailing commas remain on multi-line arguments
and lists.

Comment on lines +14 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Specify column1/column2 for the Many2many relations and follow repo naming.

Other cx.tower.tag Many2many relations in cetmix_tower_server/models/cx_tower_tag.py declare explicit column1/column2 (e.g., tag_id / <target>_id). Omitting them lets Odoo auto-generate columns from field names, which is fragile across renames. Also, the relation table name cx_tower_template_forced_command_tag_rel elides the "jet" segment — consider cx_tower_jet_template_forced_command_tag_rel for clarity and to avoid collisions with other template models.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/models/cx_tower_jet_template.py` around lines 15 -
25, Update the Many2many definitions for forced_command_tag_ids and
forced_plan_tag_ids to explicitly declare column1 and column2 and rename the
relation tables to include "jet" to match repo conventions; specifically, for
the field forced_command_tag_ids (comodel_name='cx.tower.tag', relation
currently 'cx_tower_template_forced_command_tag_rel') add
column1='jet_template_id' and column2='tag_id' and rename the relation to
'cx_tower_jet_template_forced_command_tag_rel', and for forced_plan_tag_ids add
column1='jet_template_id' and column2='tag_id' and rename its relation to
'cx_tower_jet_template_forced_plan_tag_rel' so the DB columns and table name
match other cx.tower.tag Many2manys and avoid auto-generated fragile names.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="cx_tower_command_run_wizard_view_form_isolation" model="ir.ui.view">
<field name="name">cx.tower.command.run.wizard.view.form.isolation</field>
<field name="model">cx.tower.command.run.wizard</field>
<field name="inherit_id" ref="cetmix_tower_server.cx_tower_command_run_wizard_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='tag_ids']" position="before">
<field name="is_isolated_context" invisible="1"/>
</xpath>

<xpath expr="//field[@name='applicability']" position="attributes">
<attribute name="readonly">is_isolated_context</attribute>
<attribute name="force_save">1</attribute>
</xpath>
Comment on lines +19 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Overriding readonly drops existing show_servers condition on applicability.

The parent view sets readonly="show_servers" on applicability. Replacing the attribute with readonly="is_isolated_context" silently removes the original condition, so applicability will become editable again in contexts where it previously wasn't (e.g., after servers are computed). Combine both conditions.

🔧 Proposed fix
             <xpath expr="//field[`@name`='applicability']" position="attributes">
-                <attribute name="readonly">is_isolated_context</attribute>
+                <attribute name="readonly">is_isolated_context or show_servers</attribute>
                 <attribute name="force_save">1</attribute>
             </xpath>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<xpath expr="//field[@name='applicability']" position="attributes">
<attribute name="readonly">is_isolated_context</attribute>
<attribute name="force_save">1</attribute>
</xpath>
<xpath expr="//field[`@name`='applicability']" position="attributes">
<attribute name="readonly">is_isolated_context or show_servers</attribute>
<attribute name="force_save">1</attribute>
</xpath>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/views/cx_tower_command_run_wizard_views.xml`
around lines 15 - 18, The override of the applicability field replaced the
original readonly condition and removed the parent condition show_servers;
update the readonly attribute on the applicability field (the xpath targeting
//field[`@name`='applicability']) to combine both conditions instead of replacing
them—e.g. set readonly to an expression that ORs the two flags like
"show_servers or is_isolated_context" so the field remains readonly when either
condition applies, and keep the existing force_save="1" attribute.


<xpath expr="//field[@name='tag_ids']" position="attributes">
<attribute name="readonly">is_isolated_context</attribute>
<attribute name="force_save">1</attribute>
</xpath>
Comment on lines +24 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify static group narrowing on tag_ids versus dynamic restriction logic.
rg -n -C4 'name="tag_ids"|is_restricted_context|manager_ids|has_group\("cetmix_tower_server.group_manager"\)' \
  cetmix_tower_jet_isolation/views/cx_tower_command_run_wizard_views.xml \
  cetmix_tower_jet_isolation/wizards/cx_tower_command_run_wizard.py

Repository: cetmix/cetmix-tower

Length of output: 7612


Remove static group restriction on tag_ids field.

Line 25 unnecessarily restricts tag_ids visibility to global managers only using groups="cetmix_tower_server.group_manager". This contradicts the dynamic is_restricted_context logic, which allows jet managers (non-global) to see the field when managing their own jets. The existing invisible="is_restricted_context or result" attribute already provides the correct visibility control.

Proposed fix
             <xpath expr="//field[`@name`='tag_ids']" position="attributes">
-                <attribute name="groups">cetmix_tower_server.group_manager</attribute>
                 <attribute name="invisible">is_restricted_context or result</attribute>
                 <attribute name="force_save">1</attribute>
             </xpath>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<xpath expr="//field[@name='tag_ids']" position="attributes">
<attribute name="groups">cetmix_tower_server.group_manager</attribute>
<attribute name="invisible">is_restricted_context or result</attribute>
<attribute name="force_save">1</attribute>
</xpath>
<xpath expr="//field[`@name`='tag_ids']" position="attributes">
<attribute name="invisible">is_restricted_context or result</attribute>
<attribute name="force_save">1</attribute>
</xpath>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/views/cx_tower_command_run_wizard_views.xml`
around lines 24 - 28, Remove the static groups restriction on the tag_ids field
by deleting the attribute line that sets
groups="cetmix_tower_server.group_manager" inside the xpath targeting
//field[`@name`='tag_ids']; keep the existing invisible="is_restricted_context or
result" and force_save attributes so visibility remains dynamically controlled
by is_restricted_context and result logic (i.e., remove the <attribute
name="groups"> entry in the xpath for tag_ids).

</field>
</record>
</odoo>
18 changes: 18 additions & 0 deletions cetmix_tower_jet_isolation/views/cx_tower_jet_template_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="cx_tower_jet_template_view_form_isolation" model="ir.ui.view">
<field name="name">cx.tower.jet.template.view.form.isolation</field>
<field name="model">cx.tower.jet.template</field>
<field name="inherit_id" ref="cetmix_tower_server.cx_tower_jet_template_view_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='general']" position="after">
<group name="isolation" string="Execution Security (Isolation Mode)">
<field name="isolation_mode" widget="boolean_toggle"/>
<field name="forced_applicability" invisible="not isolation_mode" required="isolation_mode"/>
<field name="forced_command_tag_ids" widget="many2many_tags" invisible="not isolation_mode"/>
<field name="forced_plan_tag_ids" widget="many2many_tags" invisible="not isolation_mode"/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Clear forced values when isolation is disabled.

forced_applicability, forced_command_tag_ids, and forced_plan_tag_ids are only hidden when isolation_mode is false, but stale values persist in the database. Consider either clearing them in a Python write/onchange override or relying on them only while isolation_mode=True in the wizards (the current wizards already do the latter, so this is mainly UI hygiene).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/views/cx_tower_jet_template_views.xml` around
lines 11 - 13, The fields forced_applicability, forced_command_tag_ids and
forced_plan_tag_ids remain populated when isolation_mode is turned off; add
logic to clear them when isolation_mode becomes False by implementing either an
onchange_isolation_mode handler or overriding write/create to set those fields
to False/empty when isolation_mode is False. Specifically, in the model
containing these fields implement an `@api.onchange`('isolation_mode') that clears
forced_applicability and empties forced_command_tag_ids / forced_plan_tag_ids
when isolation_mode is falsy, and/or add a write method that detects
isolation_mode being set to False and updates those same fields to
False/[(6,0,[])] (or empty) before calling super so stale values are removed
from the DB.

</group>
</xpath>
</field>
</record>
</odoo>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="cx_tower_plan_run_wizard_view_form_isolation" model="ir.ui.view">
<field name="name">cx.tower.plan.run.wizard.view.form.isolation</field>
<field name="model">cx.tower.plan.run.wizard</field>
<field name="inherit_id" ref="cetmix_tower_server.cx_tower_plan_run_wizard_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='tag_ids']" position="before">
<field name="is_isolated_context" invisible="1"/>
</xpath>

<xpath expr="//field[@name='applicability']" position="attributes">
<attribute name="readonly">is_isolated_context</attribute>
<attribute name="force_save">1</attribute>
</xpath>
Comment on lines +19 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t f 'cx_tower_plan_run_wizard_view.xml' | xargs -I{} rg -n -C2 "name=['\"]applicability['\"]" {}

Repository: cetmix/cetmix-tower

Length of output: 299


🏁 Script executed:

# Find the parent view by examining the file and locating the parent reference
fd -t f '*.xml' -p 'cetmix_tower_jet_isolation/views/*' | head -20

Repository: cetmix/cetmix-tower

Length of output: 184


🏁 Script executed:

# Search for the parent view name referenced in the file being reviewed
rg -A5 -B5 "cx_tower_plan_run_wizard_view" cetmix_tower_jet_isolation/views/cx_tower_plan_run_wizard_views.xml | head -40

Repository: cetmix/cetmix-tower

Length of output: 658


🏁 Script executed:

# Look for any parent view definition files that might contain applicability field
fd -t f '*.xml' cetmix_tower_jet_isolation/views/ | xargs grep -l "applicability" | head -10

Repository: cetmix/cetmix-tower

Length of output: 347


🏁 Script executed:

# List all XML files to understand the structure
find . -name "*.xml" -type f | grep -E "(cetmix_tower_server|cetmix_tower_jet)" | head -20

Repository: cetmix/cetmix-tower

Length of output: 1292


🏁 Script executed:

# Search for the parent view definition across the entire repo
rg -l "cx_tower_plan_run_wizard_view_form" --type xml

Repository: cetmix/cetmix-tower

Length of output: 192


🏁 Script executed:

# Search for applicability field with readonly attribute
rg "readonly.*applicability|applicability.*readonly" --type xml -A2 -B2

Repository: cetmix/cetmix-tower

Length of output: 45


🏁 Script executed:

# Read the parent view file to find applicability field definition
cat -n cetmix_tower_server/wizards/cx_tower_plan_run_wizard_view.xml | grep -A10 -B10 "applicability"

Repository: cetmix/cetmix-tower

Length of output: 1462


Combine readonly conditions instead of replacing: The parent view already sets readonly="show_servers" on the applicability field (line 31 of cetmix_tower_server/wizards/cx_tower_plan_run_wizard_view.xml). The xpath with position="attributes" will override this, causing the field to ignore the show_servers guard. Change the attribute to readonly="show_servers or is_isolated_context" to preserve both conditions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/views/cx_tower_plan_run_wizard_views.xml` around
lines 15 - 18, The xpath that sets attributes on the field applicability
currently replaces the parent readonly condition; update the readonly attribute
to combine both guards by setting readonly="show_servers or is_isolated_context"
(keep the existing force_save attribute unchanged) so the field honors both the
parent show_servers and the local is_isolated_context conditions.


<xpath expr="//field[@name='tag_ids']" position="attributes">
<attribute name="readonly">is_isolated_context</attribute>
<attribute name="force_save">1</attribute>
</xpath>
</field>
</record>
</odoo>
2 changes: 2 additions & 0 deletions cetmix_tower_jet_isolation/wizards/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import cx_tower_command_run_wizard
from . import cx_tower_plan_run_wizard
29 changes: 29 additions & 0 deletions cetmix_tower_jet_isolation/wizards/cx_tower_command_run_wizard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from odoo import models, fields, api

class CxTowerCommandRunWizardFilter(models.TransientModel):
_inherit = 'cx.tower.command.run.wizard'

is_isolated_context = fields.Boolean(compute='_compute_is_isolated_context')

@api.depends('jet_ids')
def _compute_is_isolated_context(self):
for record in self:
if record.jet_ids and any(j.jet_template_id.isolation_mode for j in record.jet_ids):
record.is_isolated_context = True
else:
record.is_isolated_context = False
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Simplify compute and guard against NewId/unsaved templates.

Minor cleanup: the if/else can collapse to a boolean expression, and any() will already be False for an empty recordset so the record.jet_ids and guard is redundant.

♻️ Proposed refactor
-    `@api.depends`('jet_ids')
-    def _compute_is_isolated_context(self):
-        for record in self:
-            if record.jet_ids and any(j.jet_template_id.isolation_mode for j in record.jet_ids):
-                record.is_isolated_context = True
-            else:
-                record.is_isolated_context = False
+    `@api.depends`('jet_ids.jet_template_id.isolation_mode')
+    def _compute_is_isolated_context(self):
+        for record in self:
+            record.is_isolated_context = any(
+                record.jet_ids.jet_template_id.mapped('isolation_mode')
+            )

Note the added dependency on jet_template_id.isolation_mode so the flag recomputes if the underlying template toggles while the wizard is open.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@api.depends('jet_ids')
def _compute_is_isolated_context(self):
for record in self:
if record.jet_ids and any(j.jet_template_id.isolation_mode for j in record.jet_ids):
record.is_isolated_context = True
else:
record.is_isolated_context = False
`@api.depends`('jet_ids.jet_template_id.isolation_mode')
def _compute_is_isolated_context(self):
for record in self:
record.is_isolated_context = any(
record.jet_ids.jet_template_id.mapped('isolation_mode')
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/wizards/cx_tower_command_run_wizard.py` around
lines 8 - 14, Replace the loop in _compute_is_isolated_context by assigning the
boolean expression directly to is_isolated_context and add a dependency on the
template field; specifically, change the compute to set
record.is_isolated_context = any(j.jet_template_id.id and
j.jet_template_id.isolation_mode for j in record.jet_ids) (this guards against
NewId/unsaved jet_template_id by checking its id) and update the `@api.depends` to
include 'jet_ids.jet_template_id.isolation_mode' so the flag recomputes when the
underlying template toggles.


@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
if 'default_jet_ids' in self.env.context:
jet_ids = self.env['cx.tower.jet'].browse(self.env.context['default_jet_ids'])
if jet_ids:
template = jet_ids[0].jet_template_id

if template.isolation_mode:
if template.forced_applicability:
res['applicability'] = template.forced_applicability
if template.forced_command_tag_ids:
res['tag_ids'] = [(6, 0, template.forced_command_tag_ids.ids)]
return res
Comment on lines +40 to +55
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Inconsistency between _compute_is_isolated_context and default_get — isolation can be bypassed.

_compute_is_isolated_context activates when any selected jet's template has isolation_mode enabled (Line 11), but default_get only inspects jet_ids[0].jet_template_id (Line 22). If the first selected jet's template has no isolation, but a later one does, the wizard enters isolated (read-only) context without the forced applicability/tag_ids being applied — defeating the isolation guarantees described in the PR objectives.

Iterate over all jets and apply the strictest forced values, or otherwise define a deterministic selection rule (e.g., the first isolated template).

🛠️ Proposed fix
-        if 'default_jet_ids' in self.env.context:
-            jet_ids = self.env['cx.tower.jet'].browse(self.env.context['default_jet_ids'])
-            if jet_ids:
-                template = jet_ids[0].jet_template_id
-                
-                if template.isolation_mode:
-                    if template.forced_applicability:
-                        res['applicability'] = template.forced_applicability
-                    if template.forced_command_tag_ids:
-                        res['tag_ids'] = [(6, 0, template.forced_command_tag_ids.ids)]
+        jet_ids_ctx = self.env.context.get('default_jet_ids')
+        if jet_ids_ctx:
+            jets = self.env['cx.tower.jet'].browse(jet_ids_ctx)
+            isolated_templates = jets.jet_template_id.filtered('isolation_mode')
+            if isolated_templates:
+                template = isolated_templates[0]
+                if template.forced_applicability:
+                    res['applicability'] = template.forced_applicability
+                forced_tags = isolated_templates.mapped('forced_command_tag_ids')
+                if forced_tags:
+                    res['tag_ids'] = [(6, 0, forced_tags.ids)]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/wizards/cx_tower_command_run_wizard.py` around
lines 16 - 29, default_get currently only inspects jet_ids[0].jet_template_id
which can miss isolation info used by _compute_is_isolated_context; update
default_get to iterate all jets in jet_ids, collect templates where
template.isolation_mode is True, then apply a deterministic rule (e.g., pick the
first isolated template) or merge strictest values: set res['applicability']
from the chosen template.forced_applicability (if present) and res['tag_ids'] to
the union of all chosen templates' forced_command_tag_ids.ids (if any) so
forced_applicability and forced_command_tag_ids are applied whenever any
selected jet enforces isolation; reference variables/functions: default_get,
jet_ids, template, template.isolation_mode, template.forced_applicability,
template.forced_command_tag_ids, res['applicability'], res['tag_ids'].

29 changes: 29 additions & 0 deletions cetmix_tower_jet_isolation/wizards/cx_tower_plan_run_wizard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from odoo import models, fields, api

class CxTowerPlanRunWizardFilter(models.TransientModel):
_inherit = 'cx.tower.plan.run.wizard'

is_isolated_context = fields.Boolean(compute='_compute_is_isolated_context')

@api.depends('jet_ids')
def _compute_is_isolated_context(self):
for record in self:
if record.jet_ids and any(j.jet_template_id.isolation_mode for j in record.jet_ids):
record.is_isolated_context = True
else:
record.is_isolated_context = False
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Style/consistency nits.

  • The rest of the repo uses double quotes and from odoo import api, fields, models ordering; this file uses single quotes and a different import order.
  • Class name CxTowerPlanRunWizardFilter is misleading since it is not a filter but an extension — consider CxTowerPlanRunWizard (matching the base class name) for consistency with other _inherit extensions in the codebase.
  • The compute can be simplified:
Proposed simplification
-    `@api.depends`('jet_ids')
-    def _compute_is_isolated_context(self):
-        for record in self:
-            if record.jet_ids and any(j.jet_template_id.isolation_mode for j in record.jet_ids):
-                record.is_isolated_context = True
-            else:
-                record.is_isolated_context = False
+    `@api.depends`("jet_ids.jet_template_id.isolation_mode")
+    def _compute_is_isolated_context(self):
+        for record in self:
+            record.is_isolated_context = any(
+                record.jet_ids.mapped("jet_template_id.isolation_mode")
+            )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from odoo import models, fields, api
class CxTowerPlanRunWizardFilter(models.TransientModel):
_inherit = 'cx.tower.plan.run.wizard'
is_isolated_context = fields.Boolean(compute='_compute_is_isolated_context')
@api.depends('jet_ids')
def _compute_is_isolated_context(self):
for record in self:
if record.jet_ids and any(j.jet_template_id.isolation_mode for j in record.jet_ids):
record.is_isolated_context = True
else:
record.is_isolated_context = False
from odoo import models, fields, api
class CxTowerPlanRunWizardFilter(models.TransientModel):
_inherit = 'cx.tower.plan.run.wizard'
is_isolated_context = fields.Boolean(compute='_compute_is_isolated_context')
`@api.depends`("jet_ids.jet_template_id.isolation_mode")
def _compute_is_isolated_context(self):
for record in self:
record.is_isolated_context = any(
record.jet_ids.mapped("jet_template_id.isolation_mode")
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/wizards/cx_tower_plan_run_wizard.py` around lines
1 - 14, Update style and naming: reorder and use imports as "from odoo import
api, fields, models" and switch string quotes to double quotes; rename class
CxTowerPlanRunWizardFilter to CxTowerPlanRunWizard to reflect that it extends
the existing model (keep _inherit='cx.tower.plan.run.wizard'); and simplify the
compute method _compute_is_isolated_context to assign the boolean directly
(e.g., record.is_isolated_context = bool(record.jet_ids and
any(j.jet_template_id.isolation_mode for j in record.jet_ids))) while keeping
the field name is_isolated_context and the `@api.depends`('jet_ids') decorator.


@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
if 'default_jet_ids' in self.env.context:
jet_ids = self.env['cx.tower.jet'].browse(self.env.context['default_jet_ids'])
if jet_ids:
template = jet_ids[0].jet_template_id
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Using only the first jet's template is fragile.

jet_ids[0].jet_template_id silently ignores other jets. If the selection mixes jets from different templates (some with isolation, some without), the user either gets partial enforcement or none. Either enforce that all selected jets share the same isolation template (raise UserError otherwise) or iterate and apply the strictest union of forced tags/applicability.

Also, _compute_is_isolated_context uses any(...) across jets while default_get uses only the first — inconsistent semantics between the flag and the applied values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/wizards/cx_tower_plan_run_wizard.py` around lines
19 - 22, The current default_get uses only the first selected jet's template
(jet_ids[0].jet_template_id), which is inconsistent with
_compute_is_isolated_context (which aggregates across jets) and silently ignores
mixed templates; update default_get to either (a) enforce that all selected jets
(self.env.context['default_jet_ids']) share the same jet_template_id and raise a
UserError if they do not, or (b) compute the union/strictest combination of
forced tags/applicability across all jet_ids and derive defaults from that
union; ensure you reference and align behavior with _compute_is_isolated_context
so the default flag semantics match the applied values (use symbols:
default_jet_ids, jet_ids, jet_template_id, _compute_is_isolated_context,
default_get) and raise UserError when choosing the enforcement option.


if template.isolation_mode:
if template.forced_applicability:
res['applicability'] = template.forced_applicability
if template.forced_plan_tag_ids:
res['tag_ids'] = [(6, 0, template.forced_plan_tag_ids.ids)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Privilege-escalation risk: isolation may override the base wizard's non-privileged applicability='this' restriction.

The base cx.tower.plan.run.wizard.default_get forces applicability='this' for non-privileged users. This override runs after super() and unconditionally writes res['applicability'] = template.forced_applicability, so a template configured with forced_applicability='shared' effectively grants non-privileged users the ability to run non-server-restricted plans — the opposite of the isolation intent.

Consider respecting the base restriction (e.g., only widen to 'shared' when the user is privileged, or intersect with the base result).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/wizards/cx_tower_plan_run_wizard.py` around lines
18 - 28, The override in default_get unconditionally applies
template.forced_applicability and can widen the base wizard's non-privileged
restriction (res['applicability']) which is a privilege-escalation risk; modify
default_get so that before assigning res['applicability'] =
template.forced_applicability you compare the existing res.get('applicability')
and only change it when the new value does not relax access for the current user
(e.g., only set to a broader scope like 'shared' if the current user is
privileged via an explicit check such as self.env.user.has_group(...) or another
project-specific privilege helper), otherwise leave res['applicability'] as
returned by super(); keep the existing logic for tag population
(template.forced_plan_tag_ids -> res['tag_ids']) unchanged.

return res
Comment on lines +40 to +55
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Server-side enforcement is missing — isolation is bypassable.

default_get only prefills applicability/tag_ids on form open; the view's readonly + force_save="1" is a client-side restriction. A user can still submit arbitrary values via RPC (e.g., create/write/direct button call) and bypass the isolation filter entirely, defeating the stated security goal of this module.

Enforce the isolation constraints on the server side by overriding create/write (or the action that launches the plan) to re-apply forced_applicability and forced_plan_tag_ids whenever is_isolated_context is true, or validate them in a @api.constrains. The same concern applies symmetrically in cetmix_tower_jet_isolation/wizards/cx_tower_command_run_wizard.py.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cetmix_tower_jet_isolation/wizards/cx_tower_plan_run_wizard.py` around lines
16 - 29, default_get currently only pre-fills applicability/tag_ids but does not
enforce them server-side, so override create and write on the wizard model (the
same pattern should be applied to cx_tower_command_run_wizard.py) to enforce the
template's forced values: when is_isolated_context is true (use the wizard
model's field), fetch the related cx.tower.jet -> jet_template_id and, if
template.isolation_mode, set/replace record values for applicability with
template.forced_applicability and tag_ids with template.forced_plan_tag_ids.ids
(or refuse the operation) before saving; alternatively add an `@api.constrains`
that validates incoming applicability/tag_ids against template.forced_* and
raises an error if they diverge.

Loading