Trouble on_change_with_<field> with One2Many in a wizard

I have a situation when shipments comes in, the user have to add some properties to it. This behavior worked in Tryton version 5.0 and 5.2, but not in 5.4 anymore. I think something has changed on the server side, but I cannot get my head around it what actually changed.

I extended the shipment.in workflow with a wizard. This wizard get the shipment.id and checks for certain products. This products are then added to a list and the user can walk through this list to add the properties to that particular product. To make “walking” easier, the user uses a “Next” button.
Below the fields:

products = fields.Many2Many('product.product', None, None, 
    'Products To Complete', required=True, readonly=True)
product = fields.Many2One('product.product', 'Current selected Product')
property_set = fields.Many2One('product.property.set', 'Property Set')
properties = fields.One2Many(
        'shipment.in.addoption.add', 'product', 'Product Properties')

@fields.depends('product')
def on_change_product(self, name=None):
    self.property_set = self.product.property_set.id

This works well, no problems so far. I can start the wizard and everything is correctly filled. The wizard form shows up, no error. When I now want to add a new property I click on the “+” button of the One2Many field properties I get the following error:

[...] ERROR trytond.protocols.dispatcher <class 'trytond.modules.my_extensions.shipment.AddProductPropertiesAdd'>.on_change_with_property_set(*({'product': {'attribute_set': 1, 'product': 1, 'id': -11}, 'id': -12}, {'client': '4e69e28f-3abe-419b-b951-6266b0275b48', 'warehouse': None, 'employee': None, 'company': 1, 'company.rec_name': 'testcompany', 'language': 'en', 'language_direction': 'ltr', 'groups': [4, 5, 1, 7, 8, 2, 26, 23, 20, 3, 6, 13, 15, 14, 18, 19, 16, 17, 10, 9, 11, 12, 25, 24, 21, 22], 'company_work_time': {'h': 3600, 'm': 60, 's': 1, 'Y': 6912000.0, 'M': 576000.0, 'w': 144000.0, 'd': 28800.0}, 'active_id': 1, 'active_ids': [1], 'active_model': 'stock.shipment.in', 'action_id': 306}), **{}) from admin@ipaddress//testone2many1/
Traceback (most recent call last):
  File "/home/tryton54/lib/python3.6/site-packages/trytond/protocols/dispatcher.py", line 181, in _dispatch
    = rpc.convert(obj, *args, **kwargs)
  File "/home/tryton54/lib/python3.6/site-packages/trytond/rpc.py", line 81, in convert
    args[self.instantiate] = instance(data)
  File "/home/tryton54/lib/python3.6/site-packages/trytond/rpc.py", line 69, in instance
    return obj(**data)
  File "/home/tryton54/lib/python3.6/site-packages/trytond/model/model.py", line 255, in __init__
    setattr(self, name, value)
  File "/home/tryton54/lib/python3.6/site-packages/trytond/model/fields/many2one.py", line 108, in __set__
    value = Target(**value)
  File "/home/tryton54/lib/python3.6/site-packages/trytond/model/modelstorage.py", line 1392, in __init__
    super(ModelStorage, self).__init__(id, **kwargs)
  File "/home/tryton54/lib/python3.6/site-packages/trytond/model/model.py", line 255, in __init__
    setattr(self, name, value)
AttributeError: 'product.product' object has no attribute 'product'

The fields of the properties are:

product = fields.Many2One('product.product', 'Current Product')
property_set = fields.Function(
    fields.Many2One('product.property.set', 'Property Set'),
    'on_change_with_property_set')

@fields.depends('product')
def on_change_with_property_set(self, name=None):
    if self.product and self.product.property_set:
        return self.product.property_set.id

I removed the rest of the fields to see where the error comes from.
I also added an extra field to the first set of fields like

current_contact = fields.Many2One('party.party', 'Current Contact')

The error then starts to complain about

AttributeError: 'product.product' object has no attribute 'current_contact'

I have changes basically everything but cannot get it to work. Any help is appreciated.

Could you post the full source code of the field?

Are you are using some One2Many fields to target models that are only of ModelView but not ModelSQL?
If so you should probably use None as target field instead of product. I.e:

properties = fields.One2Many( 'shipment.in.addoption.add', None, 'Product Properties')

Note that I replaced ‘product’ to None.

Below most of the actual code. I made some typos in the start post.

class AddProductPropertiesAdd(sequence_ordered(), ModelView):
    __name__ = 'stock.shipment.in.addoptions.add'
    _order = [('sequence', 'ASC')]

    product = fields.Many2One('product.product', 'Asset', required=True)

    property_set = fields.Function(
        fields.Many2One('product.attribute.set', 'Attribute Set'),
        'on_change_with_property_set'
    )

    @fields.depends('product')
    def on_change_with_property_set(self, name=None):
        if self.product and self.product.property_set:
            return self.product.property_set.id


class AddProductPropertiesStart(ModelView):
    __name__ = 'stock.shipment.in.addoptions.start'

    products = fields.Many2Many('product.product', None, None, 
        'Products To Complete', required=True, readonly=True)
    product = fields.Many2One('product.product', 'Current selected Product')
    property_set = fields.Many2One('product.property.set', 'Property Set')
    properties = fields.One2Many(
            'stock.shipment.in.addoptions.add', 'product', 'Product Properties')

    @fields.depends('product')
    def on_change_product(self, name=None):
        self.property_set = self.product.property_set

class AddAssetsOptions(Wizard):
    __name__ = 'stock.shipment.in.asset.addoptions'

    start = StateTransition()
    show = StateView('stock.shipment.in.addoptions.start',
        'my_extensions.product_add_props_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Skip', 'skip_', 'tryton-forward'),
            Button('Save + Next', 'add_properties', 'tryton-ok', default=True),
            ])
    skip_ = StateTransition()
    done_ = StateTransition()
    add_properties = StateTransition()

    def transition_start(self):
        return 'show'

    def default_show(self, fields):
        pool = Pool()
        Shipment = pool.get('stock.shipment.in')
        shipment_id = Transaction().context.get('active_id')
        if not shipment_id:
            return []
        shipment = Shipment(shipment_id)
        ship = [move.product for move in shipment.incoming_moves if move.product.serial_number]

        return {
            'product': ship[0].product.id,
        }

Yes I do, because it’s a wizard. The user adds the properties to the One2Many list and after the “Save + Next” the properties are stored in the database. This worked perfectly until version 5.4 (5.4.1)

I tried that, but then the on_change_with_property_set is not called anymore.

To me is seems that the One2Many takes the wrong model.

I narrowed it down to two parts:

  1. https://hg.tryton.org/trytond/file/5.4/trytond/model/model.py#l244 -> Replace this function with the code from 5.2
  2. trytond: 57534f543882 trytond/url.py remove the __slots__ = () from the URLMixin class

I will try to narrow point 1 a bit more.

Just a small question about the url.py, should the URLAccessor class not be added to the __all__ array?

Because now Tryton is more strict about assigning value to unknown fields.
Your code was probably buggy before but Tryton did not detect it.

The problem is that you define properties as a One2Many using product field of stock.shipment.in.addoptions.add but this field is not pointing to stock.shipment.in.addoptions.start but to product.product. So you have incoherence in your schema.

1 Like

Thanks a lot! That did it. It just works now!

Indeed, I had a typo in @fields.depends in an other module, that worked in 5.2, but in 5.4 I got an error. That was an easy one, this one was a lot harder, but it works now.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.