Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ def precommit(session: nox.Session) -> None:
"lint",
external=True,
)
session.install("pydoclint")
session.run("pre-commit", *args, external=True)
if args and args[0] == "install":
activate_virtualenv_in_precommit_hooks(session)
Expand Down
5 changes: 5 additions & 0 deletions src/odoo_data_flow/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,11 @@ def import_cmd(connection_file: str, **kwargs: Any) -> None:
except (ValueError, SyntaxError) as e:
log.error(f"Invalid --context dictionary provided: {e}")
return

groupby = kwargs.get("groupby")
if groupby:
kwargs["groupby"] = [col.strip() for col in groupby.split(",")]

run_import(**kwargs)


Expand Down
17 changes: 17 additions & 0 deletions src/odoo_data_flow/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,23 @@ def run_import( # noqa: C901
# Disable deferred fields for this strategy
deferred_fields = []

# Filter user-deferred fields to exclude required ones
if deferred_fields and model:
odoo_fields = preflight._get_odoo_fields(config, model)
if odoo_fields:
safe_deferred_fields = []
for field_name in deferred_fields:
clean_field_name = field_name.replace("/id", "")
field_info = odoo_fields.get(clean_field_name)
if field_info and field_info.get("required", False):
log.warning(
f"Field '{field_name}' is required and cannot be deferred. "
"It will be imported in the first pass."
)
else:
safe_deferred_fields.append(field_name)
deferred_fields = safe_deferred_fields

final_deferred = deferred_fields or import_plan.get("deferred_fields", [])
final_uid_field = unique_id_field or import_plan.get("unique_id_field") or "id"
fail_output_file = str(Path(filename).parent / _get_fail_filename(model, fail))
Expand Down
35 changes: 35 additions & 0 deletions tests/test_import_threaded.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,3 +661,38 @@ def test_recursive_batching_group_col_not_found(self) -> None:
mock_log.error.assert_called_once_with(
"Grouping column 'non_existent' not found. Cannot use --groupby."
)

def test_recursive_batching_with_special_chars_in_col_name(self) -> None:
"""Test batching with special characters in column names."""
from odoo_data_flow.import_threaded import _recursive_create_batches

header = ["id", "name", "partner_id/id"]
data = [
["1", "A", "p1"],
["2", "B", "p1"],
["3", "C", "p2"],
]
batches = list(
_recursive_create_batches(data, ["partner_id/id"], header, 10, False)
)
assert len(batches) == 2
assert batches[0][1][0][2] == "p1"
assert batches[1][1][0][2] == "p2"

def test_recursive_batching_multiple_cols_with_special_chars(self) -> None:
"""Test batching with multiple columns, one with special characters."""
from odoo_data_flow.import_threaded import _recursive_create_batches

header = ["id", "name", "partner_id/id", "company_id"]
data = [
["1", "A", "p1", "c1"],
["2", "B", "p1", "c2"],
["3", "C", "p2", "c1"],
["4", "D", "p1", "c1"],
]
batches = list(
_recursive_create_batches(
data, ["partner_id/id", "company_id"], header, 10, False
)
)
assert len(batches) == 3
51 changes: 51 additions & 0 deletions tests/test_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,54 @@ def preflight_side_effect(*_args: Any, **kwargs: Any) -> bool:
)
mock_import_data.assert_called_once()
mock_relational_import.assert_not_called()


@patch("odoo_data_flow.importer.log")
@patch("odoo_data_flow.importer.preflight._get_odoo_fields")
@patch("odoo_data_flow.importer.import_threaded.import_data")
@patch("odoo_data_flow.importer._run_preflight_checks")
def test_run_import_does_not_defer_required_fields(
mock_preflight: MagicMock,
mock_import_data: MagicMock,
mock_get_odoo_fields: MagicMock,
mock_log: MagicMock,
tmp_path: Path,
) -> None:
"""Test that a required field is not deferred even if specified by the user."""
# Arrange
source_file = tmp_path / "source.csv"
source_file.touch()
mock_preflight.return_value = True
mock_import_data.return_value = (True, {"total_records": 1})
mock_get_odoo_fields.return_value = {
"partner_id": {"type": "many2one", "required": True}
}

# Act
run_import(
config="dummy.conf",
filename=str(source_file),
model="res.partner.bank",
deferred_fields=["partner_id"],
unique_id_field=None,
no_preflight_checks=False,
headless=True,
worker=1,
batch_size=100,
skip=0,
fail=False,
separator=";",
ignore=None,
context={},
encoding="utf-8",
o2m=False,
groupby=None,
)

# Assert
mock_import_data.assert_called_once()
assert mock_import_data.call_args.kwargs["deferred_fields"] == []
mock_log.warning.assert_called_once_with(
"Field 'partner_id' is required and cannot be deferred. "
"It will be imported in the first pass."
)