This question has been flagged
2 Replies
12002 Views

Let's say i have 3 models: User, Item and Box:

class User(models.Model):
    _name = 'example.user'

    item_id = fields.Many2one('example.item')
    box_id = fields.Many2one('example.box')

class Box(models.Model):
    _name = 'example.box'

    item_ids = fields.Many2many('example.item')
    user_ids = fields.One2many('example.user', 'box_id')

class Item(models.Model):
    _name = 'example.item'

    box_ids = fields.Many2many('example.box')
    user_ids = fields.One2many('example.user', 'item_id')

The concept is this:

One box can contain many items, for instance let's say BoxA has ItemA, ItemB

Our item table contains these entries: ItemA, ItemB, and ItemC

One user can be linked with a box, and when he's linked he can only own one item from that box. For instance, we want UserA to be linked to BoxA and own ItemA


My problem is that when UserA is linked with BoxA he can connect to any item he wants because the field item_id is a simple Many2one type.


I've tried googling to find a technique to add a domain of the type

domain=[ ('id', 'in', [ item.id for item in self.box_id.item_ids ] ]

But that is obviously impossible. I've tried linking the domain to a function:

class User(models.Model)
    _name = 'example.user'

    @api.model
    def _get_box_items_domain(self):
        print('This works but it's an @api.model function, so it's useless!')

    item_id = fields.Many2one('example.item', domain=_get_box_items_domain)

But this will obviously not work, because it's an @api.model decorated function, which means that "self" is not the current object but is just a class we can use to point to the environment.

I've tried decorating the function with @api.one or @api.multi, but then the function never runs and the print statement never even shows up in the logs.

I've tried adding context to the xml for the form view:

<record model="ir.ui.view">
    <field name="name">example.user.form</field>
    <field name="model">example.user</field>
    <field name="arch" type="xml">
        <form>
            <field name="box_id" />
            <field name="item_id" context="{'user_id': id}" />
        </form>
    </field>
</record>

But the context does not get added to the domain function when it is called.


The last thing i tried was using @api.onchange to change the context when we changed the box:

@api.onchange('box_id')
def _onchange_box_id(self):
domain = {}
    item_ids_list = []

    for item in self.box_id.item_ids:
        item_ids_list.append(item.id)

    domain = { 'item_id': [ ('id', 'in', item_ids_list) ] }
    return { 'domain': domain }
This works but only when we change the box first. If we never change the box it just displays all the items. Even worse, if we change the box then go back to the list of users, click on a different user, click edit and then check the items we can select they're the same items the other user was able to select.

So how do i solve this issue? The obvious solution would be to display all the items and just add an @api.constrains and throwing a ValidationError when the user tries to save. But if i have 500 items and each box only has 20, then it would become a pain in the ass for the user to select one of the 20 items that belong to the box of the huge list in the form edit view.


Any solutions?

Avatar
Discard
Best Answer

Dear

Ilian Georgiev

Easy To do, Just try this domain in your XML


<field name="item_id" domain="[('box_ids','in',[box_id])]}"/>

Avatar
Discard
Author Best Answer

Hey Ayman,

Sorry i can't comment on your answer directly, apparently i need karma for that.

Just wanted to say your solution works perfectly! Thank you very much. Now i have a problem with the box view form, however.

Let's say that i added another field to the box

class Box(models.Model):
    _name = 'example.box'

    default_item_id = fields.Many2one('example.item')
    item_ids = fields.Many2many('example.item')
    user_ids = fields.One2many('example.user', 'box_id')

The default item is the one that all users who get linked to the box get by default, if the user does not specify one.

I have added this domain to the box for view:

<field name="default_item_id" domain="[('box_ids', 'in', [id])]" />
<field name="item_ids" />
<field name="user_ids" />

It works correctly when i open the form and start editing (as in, it displays only the items that are currently in the item_ids field) but when i add new items to the "item_ids" field the selection for the "default_item_id" does not get updated.


Any idea how i could solve this issue?

Avatar
Discard