Tower: upload cetmix_tower_yaml 16.0.2.0.3 (via marketplace)
This commit is contained in:
577
addons/cetmix_tower_yaml/models/cx_tower_yaml_mixin.py
Normal file
577
addons/cetmix_tower_yaml/models/cx_tower_yaml_mixin.py
Normal file
@@ -0,0 +1,577 @@
|
|||||||
|
# Copyright (C) 2024 Cetmix OÜ
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from odoo import _, api, fields, models
|
||||||
|
from odoo.exceptions import AccessError, ValidationError
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomDumper(yaml.Dumper):
|
||||||
|
"""Custom dumper to ensures code
|
||||||
|
is properly dumped in YAML
|
||||||
|
"""
|
||||||
|
|
||||||
|
def represent_scalar(self, tag, value, style=None):
|
||||||
|
if isinstance(value, str) and "\n" in value:
|
||||||
|
style = "|"
|
||||||
|
return super().represent_scalar(tag, value, style)
|
||||||
|
|
||||||
|
|
||||||
|
class YamlExportCollector:
|
||||||
|
"""
|
||||||
|
Collector for YAML export.
|
||||||
|
Tracks unique records by their (model_name, reference) tuple to avoid duplicates.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""
|
||||||
|
Initialize the collector.
|
||||||
|
"""
|
||||||
|
self.added_references = set()
|
||||||
|
|
||||||
|
def add(self, key):
|
||||||
|
"""
|
||||||
|
Add a record to the collector if its reference is unique.
|
||||||
|
:param key: tuple, key of the record
|
||||||
|
"""
|
||||||
|
if key and key not in self.added_references:
|
||||||
|
self.added_references.add(key)
|
||||||
|
|
||||||
|
def is_added(self, key):
|
||||||
|
"""
|
||||||
|
Check by (model, reference) tuple.
|
||||||
|
:param key: tuple, key of the record
|
||||||
|
:return: bool
|
||||||
|
"""
|
||||||
|
return key in self.added_references
|
||||||
|
|
||||||
|
|
||||||
|
class CxTowerYamlMixin(models.AbstractModel):
|
||||||
|
"""Used to implement YAML rendering functions.
|
||||||
|
Inherit in your model in case you want to YAML instance of the records.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_name = "cx.tower.yaml.mixin"
|
||||||
|
_description = "Cetmix Tower YAML rendering mixin"
|
||||||
|
|
||||||
|
# File format version in order to track compatibility
|
||||||
|
CETMIX_TOWER_YAML_VERSION = 1
|
||||||
|
|
||||||
|
# TO_YAML_* used to convert from Odoo field values to YAML
|
||||||
|
TO_YAML_ACCESS_LEVEL = {"1": "user", "2": "manager", "3": "root"}
|
||||||
|
|
||||||
|
# TO_TOWER_* used to convert from YAML field values to Tower ones
|
||||||
|
TO_TOWER_ACCESS_LEVEL = {"user": "1", "manager": "2", "root": "3"}
|
||||||
|
|
||||||
|
yaml_code = fields.Text(
|
||||||
|
compute="_compute_yaml_code",
|
||||||
|
inverse="_inverse_yaml_code",
|
||||||
|
groups="cetmix_tower_yaml.group_export,cetmix_tower_yaml.group_import",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _compute_yaml_code(self):
|
||||||
|
"""Compute YAML code based on model record data"""
|
||||||
|
|
||||||
|
# This is used for the file name.
|
||||||
|
# Eg cx.tower.command record will have 'command_' prefix.
|
||||||
|
for record in self:
|
||||||
|
# We are reading field list for each record
|
||||||
|
# because list of fields can differ from record to record
|
||||||
|
record.yaml_code = self._convert_dict_to_yaml(
|
||||||
|
record._prepare_record_for_yaml()
|
||||||
|
)
|
||||||
|
|
||||||
|
def _inverse_yaml_code(self):
|
||||||
|
"""Compose record based on provided YAML"""
|
||||||
|
for record in self:
|
||||||
|
if record.yaml_code:
|
||||||
|
record_yaml_dict = yaml.safe_load(record.yaml_code)
|
||||||
|
record_vals = record._post_process_yaml_dict_values(record_yaml_dict)
|
||||||
|
record.update(record_vals)
|
||||||
|
|
||||||
|
@api.constrains("yaml_code")
|
||||||
|
def _check_yaml_code_write_access(self):
|
||||||
|
"""
|
||||||
|
Check if user has access to create records from YAML.
|
||||||
|
This is checked only when user already has access to export YAML.
|
||||||
|
Otherwise, the field is not accessible due to security group.
|
||||||
|
"""
|
||||||
|
if self.env.user.has_group("cetmix_tower_yaml.group_export") and (
|
||||||
|
not self.env.user.has_group("cetmix_tower_yaml.group_import")
|
||||||
|
and not self.env.user._is_superuser()
|
||||||
|
):
|
||||||
|
raise AccessError(_("You are not allowed to create records from YAML"))
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
# Handle validation error when field values are not valid
|
||||||
|
try:
|
||||||
|
return super().create(vals_list)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValidationError(str(e)) from e
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
# Handle validation error when field values are not valid
|
||||||
|
try:
|
||||||
|
return super().write(vals)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValidationError(str(e)) from e
|
||||||
|
|
||||||
|
def action_open_yaml_export_wizard(self):
|
||||||
|
"""Open YAML export wizard"""
|
||||||
|
|
||||||
|
return {
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"res_model": "cx.tower.yaml.export.wiz",
|
||||||
|
"view_mode": "form",
|
||||||
|
"target": "new",
|
||||||
|
}
|
||||||
|
|
||||||
|
def _convert_dict_to_yaml(self, values):
|
||||||
|
"""Converts Python dictionary to YAML string.
|
||||||
|
|
||||||
|
This is a helper function that is designed to be used
|
||||||
|
by any models that need to convert a dictionary to YAML.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
values (Dict): Dictionary containing data
|
||||||
|
to be converted to YAML format
|
||||||
|
Returns:
|
||||||
|
Text: YAML string
|
||||||
|
Raises:
|
||||||
|
ValidationError: If values is not a dictionary
|
||||||
|
or YAML conversion fails
|
||||||
|
"""
|
||||||
|
if not isinstance(values, dict):
|
||||||
|
raise ValidationError(_("Values must be a dictionary"))
|
||||||
|
try:
|
||||||
|
yaml_code = yaml.dump(
|
||||||
|
values,
|
||||||
|
Dumper=CustomDumper,
|
||||||
|
default_flow_style=False,
|
||||||
|
sort_keys=False,
|
||||||
|
)
|
||||||
|
return yaml_code
|
||||||
|
except (yaml.YAMLError, UnicodeEncodeError) as e:
|
||||||
|
raise ValidationError(
|
||||||
|
_(
|
||||||
|
"Failed to convert dictionary" " to YAML: %(error)s",
|
||||||
|
error=str(e),
|
||||||
|
)
|
||||||
|
) from e
|
||||||
|
|
||||||
|
def _prepare_record_for_yaml(self):
|
||||||
|
"""Reads and processes current record before converting it to YAML
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: values ready for YAML conversion
|
||||||
|
"""
|
||||||
|
self.ensure_one()
|
||||||
|
yaml_keys = self._get_fields_for_yaml()
|
||||||
|
record_dict = self.read(fields=yaml_keys)[0]
|
||||||
|
return self._post_process_record_values(record_dict)
|
||||||
|
|
||||||
|
def _get_fields_for_yaml(self):
|
||||||
|
"""Get ist of field to be present in YAML
|
||||||
|
|
||||||
|
Set 'no_yaml_service_fields' context key to skip
|
||||||
|
service fields creation (cetmix_tower_yaml_version, cetmix_tower_model)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list(): list of fields to be used as YAML keys
|
||||||
|
"""
|
||||||
|
return ["reference"]
|
||||||
|
|
||||||
|
def _get_force_x2m_resolve_models(self):
|
||||||
|
"""List of models that will always try to be resolved
|
||||||
|
when referenced in x2m related fields.
|
||||||
|
|
||||||
|
This is useful for models that should always use existing records
|
||||||
|
instead of creating new ones when referenced in x2m related fields.
|
||||||
|
Such as variables or tags.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List: list of models that will always try to be resolved
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
"cx.tower.variable",
|
||||||
|
"cx.tower.variable.option",
|
||||||
|
"cx.tower.tag",
|
||||||
|
"cx.tower.os",
|
||||||
|
"cx.tower.key",
|
||||||
|
]
|
||||||
|
|
||||||
|
def _post_process_record_values(self, values):
|
||||||
|
"""Post process record values
|
||||||
|
before converting them to YAML
|
||||||
|
|
||||||
|
Args:
|
||||||
|
values (dict): values returned by 'read' method
|
||||||
|
|
||||||
|
Context:
|
||||||
|
explode_related_record: if set will return entire record dictionary
|
||||||
|
not just a reference
|
||||||
|
remove_empty_values: if set will remove empty values from the record
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict(): processed values
|
||||||
|
"""
|
||||||
|
collector = self._context.get("yaml_collector")
|
||||||
|
ref = values.get("reference")
|
||||||
|
collector_key = (self._name, ref) if ref else None
|
||||||
|
|
||||||
|
if collector and collector_key and collector.is_added(collector_key):
|
||||||
|
return {"reference": ref}
|
||||||
|
|
||||||
|
# We don't need id because we are not using it
|
||||||
|
values.pop("id", None)
|
||||||
|
|
||||||
|
# Add YAML format version and model
|
||||||
|
if not self._context.get("no_yaml_service_fields"):
|
||||||
|
model_name = self._name.replace("cx.tower.", "").replace(".", "_")
|
||||||
|
model_values = {
|
||||||
|
"cetmix_tower_model": model_name,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
model_values = {}
|
||||||
|
|
||||||
|
# Parse access level
|
||||||
|
access_level = values.pop("access_level", None)
|
||||||
|
if access_level:
|
||||||
|
model_values.update(
|
||||||
|
{"access_level": self.TO_YAML_ACCESS_LEVEL[access_level]}
|
||||||
|
)
|
||||||
|
|
||||||
|
values = {**model_values, **values}
|
||||||
|
# Copy values to avoid modifying the original values
|
||||||
|
new_values = values.copy()
|
||||||
|
|
||||||
|
# Check if we need to return a record dict or just a reference
|
||||||
|
# Use context value first, revert to the record setting if not defined
|
||||||
|
explode_related_record = self._context.get("explode_related_record")
|
||||||
|
|
||||||
|
# Check if we need to remove empty values
|
||||||
|
# Currently only x2m fields are supported
|
||||||
|
remove_empty_values = self._context.get("remove_empty_values")
|
||||||
|
|
||||||
|
# Post process m2o and x2m fields
|
||||||
|
for key, value in values.items():
|
||||||
|
# IMPORTANT: Odoo naming patterns must be followed for related fields.
|
||||||
|
# This is why we are checking for the field name ending here.
|
||||||
|
# Further checks for the field type are done
|
||||||
|
# in _process_relation_field_value()
|
||||||
|
if key.endswith("_id") or key.endswith("_ids"):
|
||||||
|
if not value and remove_empty_values:
|
||||||
|
del new_values[key]
|
||||||
|
else:
|
||||||
|
processed_value = self.with_context(
|
||||||
|
explode_related_record=explode_related_record
|
||||||
|
)._process_relation_field_value(key, value, record_mode=True)
|
||||||
|
new_values.update({key: processed_value})
|
||||||
|
|
||||||
|
if collector and collector_key:
|
||||||
|
collector.add(collector_key)
|
||||||
|
|
||||||
|
return new_values
|
||||||
|
|
||||||
|
def _post_process_yaml_dict_values(self, values):
|
||||||
|
"""Post process dictionary values generated from YAML code
|
||||||
|
|
||||||
|
Args:
|
||||||
|
values (dict): Dictionary generated from YAML
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict(): Post-processed values
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Remove model data because it is not a field
|
||||||
|
if "cetmix_tower_model" in values:
|
||||||
|
values.pop("cetmix_tower_model")
|
||||||
|
|
||||||
|
# Parse access level
|
||||||
|
if "access_level" in values:
|
||||||
|
values_access_level = values["access_level"]
|
||||||
|
access_level = self.TO_TOWER_ACCESS_LEVEL.get(values_access_level)
|
||||||
|
if access_level:
|
||||||
|
values.update({"access_level": access_level})
|
||||||
|
else:
|
||||||
|
raise ValidationError(
|
||||||
|
_(
|
||||||
|
"Wrong value for 'access_level' key: %(acv)s",
|
||||||
|
acv=values_access_level,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Leave supported keys only
|
||||||
|
supported_keys = self._get_fields_for_yaml()
|
||||||
|
filtered_values = {k: v for k, v in values.items() if k in supported_keys}
|
||||||
|
|
||||||
|
# Post process m2o fields
|
||||||
|
for key, value in filtered_values.items():
|
||||||
|
# IMPORTANT: Odoo naming patterns must be followed for related fields.
|
||||||
|
# This is why we are checking for the field name ending here.
|
||||||
|
# Further checks for the field type are done
|
||||||
|
# in _process_relation_field_value()
|
||||||
|
if key.endswith("_id") or key.endswith("_ids"):
|
||||||
|
processed_value = self.with_context(
|
||||||
|
explode_related_record=True
|
||||||
|
)._process_relation_field_value(key, value, record_mode=False)
|
||||||
|
filtered_values.update({key: processed_value})
|
||||||
|
|
||||||
|
return filtered_values
|
||||||
|
|
||||||
|
def _process_relation_field_value(self, field, value, record_mode=False):
|
||||||
|
"""Post process One2many, Many2many or Many2one value
|
||||||
|
|
||||||
|
Args:
|
||||||
|
field (Char): Field the value belongs to
|
||||||
|
value (Char): Value to process
|
||||||
|
record_mode (Bool): If True process value as a record value
|
||||||
|
else process value as a YAML value
|
||||||
|
Context:
|
||||||
|
explode_related_record: if set will return entire record dictionary
|
||||||
|
not just a reference
|
||||||
|
Returns:
|
||||||
|
dict() or Char: record dictionary if fetch_record else reference
|
||||||
|
"""
|
||||||
|
# Step 1: Return False if the value is not set or the field is not found
|
||||||
|
if not value:
|
||||||
|
return False
|
||||||
|
|
||||||
|
field_obj = self._fields.get(field)
|
||||||
|
if not field_obj:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Step 2: Return False if the field type doesn't match
|
||||||
|
# or comodel is not defined
|
||||||
|
field_type = field_obj.type
|
||||||
|
if (
|
||||||
|
field_type not in ["one2many", "many2many", "many2one"]
|
||||||
|
or not field_obj.comodel_name
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
comodel = self.env[field_obj.comodel_name]
|
||||||
|
explode_related_record = self._context.get("explode_related_record")
|
||||||
|
|
||||||
|
# Step 3: process value based on the field type
|
||||||
|
if field_type == "many2one":
|
||||||
|
return self._process_m2o_value(
|
||||||
|
comodel, value, explode_related_record, record_mode
|
||||||
|
)
|
||||||
|
if field_type in ["one2many", "many2many"]:
|
||||||
|
return self._process_x2m_values(
|
||||||
|
comodel, field_type, value, explode_related_record, record_mode
|
||||||
|
)
|
||||||
|
|
||||||
|
# Step 4: fall back if field type is not supported
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _process_m2o_value(
|
||||||
|
self, comodel, value, explode_related_record, record_mode=False
|
||||||
|
):
|
||||||
|
"""Post process many2one value
|
||||||
|
Args:
|
||||||
|
comodel (BaseClass): Model the value belongs to
|
||||||
|
value (Char): Value to process
|
||||||
|
explode_related_record (Bool): If True return entire record dict
|
||||||
|
instead of a reference
|
||||||
|
record_mode (Bool): If True process value as a record value
|
||||||
|
else process value as a YAML value
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict() or Char: record dictionary if fetch_record else reference
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -- (Record -> YAML)
|
||||||
|
if record_mode:
|
||||||
|
# Retrieve the record based on the ID provided in the value
|
||||||
|
record = comodel.browse(value[0])
|
||||||
|
|
||||||
|
# If the context specifies to explode the related record,
|
||||||
|
# return its dictionary representation
|
||||||
|
if explode_related_record:
|
||||||
|
return (
|
||||||
|
record.with_context(
|
||||||
|
no_yaml_service_fields=True
|
||||||
|
)._prepare_record_for_yaml()
|
||||||
|
if record
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Otherwise, return just the reference (or False if record does not exist)
|
||||||
|
return record.reference if record else False
|
||||||
|
|
||||||
|
# -- (YAML -> Record)
|
||||||
|
# Step 1: Process value in normal mode
|
||||||
|
record = False
|
||||||
|
|
||||||
|
# If the value is a string, it is treated as a reference
|
||||||
|
if isinstance(value, str):
|
||||||
|
reference = value
|
||||||
|
|
||||||
|
# If the value is a dictionary, extract the reference from it
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
reference = value.get("reference")
|
||||||
|
|
||||||
|
record = self._update_or_create_related_record(
|
||||||
|
comodel, reference, value, create_immediately=True
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Step 2: Final fallback: attempt to retrieve the record by reference if set,
|
||||||
|
# return its ID or False
|
||||||
|
if not record and reference:
|
||||||
|
record = comodel.get_by_reference(reference)
|
||||||
|
return record.id if record else False
|
||||||
|
|
||||||
|
def _process_x2m_values(
|
||||||
|
self, comodel, field_type, values, explode_related_record, record_mode=False
|
||||||
|
):
|
||||||
|
"""Post process many2many value
|
||||||
|
Args:
|
||||||
|
comodel (BaseClass): Model the value belongs to
|
||||||
|
field_type (Char): Field type
|
||||||
|
values (list()): Values to process
|
||||||
|
explode_related_record (Bool): If True return entire record dict
|
||||||
|
instead of a reference
|
||||||
|
record_mode (Bool): If True process value as a record value
|
||||||
|
else process value as a YAML value
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict() or Char: record dictionary if fetch_record else reference
|
||||||
|
"""
|
||||||
|
|
||||||
|
# -- (Record -> YAML)
|
||||||
|
if record_mode:
|
||||||
|
record_list = []
|
||||||
|
for value in values:
|
||||||
|
# Retrieve the record based on the ID provided in the value
|
||||||
|
record = comodel.browse(value)
|
||||||
|
|
||||||
|
# If the context specifies to explode the related record,
|
||||||
|
# return its dictionary representation
|
||||||
|
if explode_related_record:
|
||||||
|
record_list.append(
|
||||||
|
record.with_context(
|
||||||
|
no_yaml_service_fields=True
|
||||||
|
)._prepare_record_for_yaml()
|
||||||
|
if record
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Otherwise, return just the reference
|
||||||
|
# (or False if record does not exist)
|
||||||
|
else:
|
||||||
|
record_list.append(record.reference if record else False)
|
||||||
|
|
||||||
|
return record_list
|
||||||
|
|
||||||
|
# -- (YAML -> Record)
|
||||||
|
# Step 1: Process value in normal mode
|
||||||
|
record_ids = []
|
||||||
|
|
||||||
|
for value in values:
|
||||||
|
record = False
|
||||||
|
# If the value is a string, it is treated as a reference
|
||||||
|
if isinstance(value, str):
|
||||||
|
reference = value
|
||||||
|
|
||||||
|
# If the value is a dictionary, extract the reference from it
|
||||||
|
elif isinstance(value, dict):
|
||||||
|
reference = value.get("reference")
|
||||||
|
record = self._update_or_create_related_record(
|
||||||
|
comodel,
|
||||||
|
reference,
|
||||||
|
value,
|
||||||
|
create_immediately=field_type == "many2many",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Step 2: Final fallback: attempt to retrieve the record by reference
|
||||||
|
# Return record ID or False if reference is not defined
|
||||||
|
if not record and reference:
|
||||||
|
record = comodel.get_by_reference(reference)
|
||||||
|
|
||||||
|
# Save record data
|
||||||
|
if record:
|
||||||
|
record_ids.append(
|
||||||
|
record if isinstance(record, tuple) else (4, record.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
return record_ids
|
||||||
|
|
||||||
|
def _update_or_create_related_record(
|
||||||
|
self, model, reference, values, create_immediately=False
|
||||||
|
):
|
||||||
|
"""Update related record with provided values or create a new one
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model (BaseModel): Related record model
|
||||||
|
values (dict()): Values to update existing/create new record
|
||||||
|
reference (Char): Record reference
|
||||||
|
create_immediately (Bool): If True create a new record immediately.
|
||||||
|
Used for Many2one fields.
|
||||||
|
|
||||||
|
Context:
|
||||||
|
force_create_related_record (Bool): If True, create a new record
|
||||||
|
even if reference is provided.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
record: Existing record or new record tuple
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If reference is found, retrieve the corresponding record
|
||||||
|
if reference and (
|
||||||
|
model._name in self._get_force_x2m_resolve_models()
|
||||||
|
or not self._context.get("force_create_related_record")
|
||||||
|
):
|
||||||
|
record = model.get_by_reference(reference)
|
||||||
|
# If the record exists, update it with the values from the dictionary
|
||||||
|
if record:
|
||||||
|
# Remove reference from values to avoid possible consequences
|
||||||
|
values.pop("reference", None)
|
||||||
|
record.with_context(from_yaml=True).write(
|
||||||
|
record._post_process_yaml_dict_values(values)
|
||||||
|
)
|
||||||
|
|
||||||
|
# If the record does not exist, create a new one
|
||||||
|
else:
|
||||||
|
if create_immediately:
|
||||||
|
record = model.with_context(from_yaml=True).create(
|
||||||
|
model._post_process_yaml_dict_values(values)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Use "Create" service command tuple
|
||||||
|
record = (0, 0, model._post_process_yaml_dict_values(values))
|
||||||
|
|
||||||
|
# If there's no reference but value is a dict, create a new record
|
||||||
|
else:
|
||||||
|
if create_immediately:
|
||||||
|
# Only 'reference' provided, no other data: do not create,
|
||||||
|
# just log warning
|
||||||
|
if set(values.keys()) == {"reference"}:
|
||||||
|
_logger.warning(
|
||||||
|
"Attempted to import a record for model '%s' with reference "
|
||||||
|
"'%s', but only the 'reference' field was provided. "
|
||||||
|
"It is possible that this record has already been imported. "
|
||||||
|
"Creation will be skipped.",
|
||||||
|
model._name,
|
||||||
|
reference,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
record = model.with_context(from_yaml=True).create(
|
||||||
|
model._post_process_yaml_dict_values(values)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Use "Create" service command tuple
|
||||||
|
record = (0, 0, model._post_process_yaml_dict_values(values))
|
||||||
|
|
||||||
|
# Return the record's ID if it exists, otherwise return False
|
||||||
|
return record or False
|
||||||
Reference in New Issue
Block a user