What's happening with these transient models' IDs? - python

THE CODE
I have the following transient models:
class MoveLotsManager(models.TransientModel):
_name = 'move.lots.manager'
product_lots_available = fields.One2many(
comodel_name='move.product.lot.available',
inverse_name='manager_id',
string='Available lots',
)
class MoveProductLotAvailable(models.TransientModel):
_name = 'move.product.lot.available'
manager_id = fields.Many2one(
comodel_name='move.lots.manager',
string='Lots Manager',
)
name = fields.Char(
string='Name',
)
#api.one
#api.onchange('name')
def onchange_name(self):
# LOGGER 4
_logger.info(self.manager_id)
# LOGGER 5
_logger.info(self.manager_id.id)
As you can see, they both are connected through a 1:N relationship. I open the transient models' views this way:
#api.multi
def open_move_lots_manager_wizard(self):
self.ensure_one()
wizard_id = self.env.ref(
'my_module.view_move_lots_manager_wizard').id
default_lots = [(
(0, 0, {
'name': 'My lot',
})
)]
lots_manager = self.env['move.lots.manager'].create({
'product_lots_available': default_lots,
})
# LOGGER 1
_logger.info(lots_manager)
# LOGGER 2
_logger.info(lots_manager.id)
# LOGGER 3
_logger.info(lots_manager.product_lots_available.mapped('manager_id'))
return {
'name': _('Lots manager'),
'view_type': 'form',
'view_mode': 'form',
'view_id': False,
'res_id': lots_manager.id,
'views': [(wizard_id, 'form'), ],
'res_model': 'move.lots.manager',
'type': 'ir.actions.act_window',
'target': 'new',
'flags': {
'form': {
'action_buttons': False,
},
},
}
MY PURPOSE
In the onchange_name method of the model move.product.lot.available, I want to access to its related manager (and their fields).
THE EXPECTED BEHAVIOUR
Imagine the move.lots.manager I've just created has the ID 11.
LOGGER 1: move.lots.manager(11,)
LOGGER 2: 11
LOGGER 3: move.lots.manager(11,)
LOGGER 4: move.lots.manager(11,)
LOGGER 5: 11
THE CURRENT BEHAVIOUR
LOGGER 1: move.lots.manager(11,)
LOGGER 2: 11
LOGGER 3: move.lots.manager(11,)
LOGGER 4: move.lots.manager(<openerp.models.NewId object at 0x7fd1a60cb850>,)
LOGGER 5: <openerp.models.NewId object at 0x7fd1a60cb850>
MY QUESTION
I know that transient models aren't stored in a permanent way, they're removed after a while, according to the system parameters of the database. But, while they're in the database, they do have an ID. And I can get it as you see, in fact, when I have the transient model form opened (with the developer mode activated) and I click on View Metadata option, I can see ID: 11... So why can't I get it from the "child" transient model?

Take a look into onchange() line 4971 and following. Odoo is creating a new record in cache/memory and will later, after the onchange is done, update the own cache with the provided data.
If you really need the ID or other fields use <record>._origin.<field>.
EDIT: Don't use api.one decorator on onchange methods. They are triggered on singleton records anyway.

Related

Create a delivery order in inventory from custom module odoo 13

I have created a custom module, in this module, I have a button. The button should create a delivery order in the inventory module ( just like the confirm button in the Sales module). But when I click it, It gives me the error in the pic, and this is my code.
def delivery_order(self):
delivery = self.env['stock.picking'].create({
# 'type': 'out_invoice',
'state': 'assigned',
'picking_type_id': 1,
'partner_id': self.partner_id.id,
'location_id': 1,
'location_dest_id': 1,
'origin': self.name,
'move_ids_without_package': [(0, 0, {
'product_id': self.product_id.id,
'product_uom_qty': self.selected_weight,
'picking_type_id': 1,
})]})
return delivery
You should add name field in the create method because it is a mandatory field, which causes the error.

how to automatically fill a One2many

I am currently using the native module of odoo [Sales] -sales orders and I want to pass the registration of a One2many field at the moment of pressing the button that redirects me to another new formula that I have created and has a field One2Mnay.
example:
this is the native module of odo where I am registering my sales orders:
I have created a new botton that is red says [registrar cotizacion] ,
called method: [open_budget] which allows to open a new form that I created and has a field one2many :
My models :
Models of new form :
class BudgetTwo(models.Model):
_name = 'budget.two'
name = fields.Char(string ='Nombre', copy=False, index=True ,default ="Nuevo")
partner_id =fields.Many2one('res.partner' ,string ='Cliente', copy=False, index=True,required=True)
deliver_date = fields.Date(string ='Fecha de Entrega')
expiration_date = fields.Date(string ='Fecha de expiración')
pay_place =fields.Many2one('account.payment.term' ,string='Plazo de Pago')
order_line = fields.One2many('budget.table','budget_id' ,string = 'Pedidos' )
total = fields.Float(string = 'Total:' ,compute="_total")
btn_d = fields.Boolean(default = False)
Inheritance of the odoo's own module:
class InheritOrder(models.Model):
_inherit = 'sale.order'
#api.multi
def open_budget(self):
if self.order_line:
for element in self.order_line:
data = element.product_id.id
else:print('none')
print(data)
return {
'name': ('Payments'),
'view_type': 'form',
'view_mode': 'form',
'res_model': 'budget.two',
'view_id': False,
'type': 'ir.actions.act_window',
'context': {
'default_partner_id': self.partner_id.id,
'default_order_line.product_id': data,
'default_order_line.product_id': data,
},
}
the native module of odoo has a field One2many that are the orders, when I press the new botton loogre redirect to the new form that I created and created a dictionary which has a key called context I can pass the records that I have in the fields but the record that I have in the one2many values ​​are not passed.
Finally :
'context': {
'default_partner_id': self.partner_id.id,
'default_order_line.product_id': data,
'default_order_line.product_id': data,
},
desauld_pernert_id if the record passes but default_order_line.product_id does not pass the record that is the on2many, as you can see:
You can try like this
class InheritOrder(models.Model):
_inherit = 'sale.order'
#api.multi
def open_budget(self):
ctx = dict()
if self.order_line:
products={}
for line in self.order_line:
# You can get whatever field from sale order line
products.update({'product_id': line.product_id.id})
product_line.append((0, 0, products))
ctx.update({
'default_order_line': product_line,
'default_partner_id': self.partner_id.id,
})
return {
'name': ('Payments'),
'view_type': 'form',
'view_mode': 'form',
'res_model': 'budget.two',
'view_id': False,
'type': 'ir.actions.act_window',
'context': ctx,
}
There is a special syntax to pass values to x2many fields.
If you want to create new lines you should use:
'context': {
'default_partner_id': self.partner_id.id,
'default_order_line.product_id': [(0, 0, {'field_name': its value,..}),
...,
(0, 0, {'field_name': its value, ..})
]
},

Pass move_lines into stock.picking from another model - Odoo v8

I have this method:
#api.multi
def create_printy(self):
copy_record = self.env['stock.picking']
for record in self:
order_lines = []
for rec in record.order_lines:
order_lines.append(
(0,0,
{
'product_id': rec.isbn.id,
'product_qty': rec.qty,
}
))
sp_types = self.env['stock.picking.type'].search([
('code', '=', 'outgoing')
])
if len(sp_types) > 0:
copy_record.create({
'origin': record.name,
'picking_type_id': sp_types[0].id,
'move_lines': order_lines,
'move_type': 'direct',
'priority': '1',
'company_id': record.company_id.id,
})
I'm trying to create a stock.picking from another model.
But, with this method, I have problems for move_lines which on stock.picking is related to stock.move.
Right now, it throws me this:
Integrity Error
The operation cannot be completed, probably due to the following:
- deletion: you may be trying to delete a record while other records still reference it
- creation/update: a mandatory field is not correctly set
[object with reference: Product Unit of Measure - product.uom]
I know there are a few stock.move required fields which aren't present in my order_lines field.
So, my question is, how can I pass for example product.uom or date_expected which are required from my model?
Is there some similar way as it is done with picking_type_id in my example. for One2many field?
You could take a look of what happens when you do not fill these fields in the interface (and look what they get by default). For example, in stock.move Odoo takes the product_uom from product_id field (through an onchange method). And the date_expected is filled in by default with the current date. So:
#api.multi
def create_printy(self):
copy_record = self.env['stock.picking']
for record in self:
order_lines = []
for rec in record.order_lines:
order_lines.append(
(0,0,
{
'product_id': rec.isbn.id,
'product_uom': rec.isbn.uom_id.id,
'date_expected': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'product_qty': rec.qty,
}
))
sp_types = self.env['stock.picking.type'].search([
('code', '=', 'outgoing')
])
if len(sp_types) > 0:
copy_record.create({
'origin': record.name,
'picking_type_id': sp_types[0].id,
'move_lines': order_lines,
'move_type': 'direct',
'priority': '1',
'company_id': record.company_id.id,
})
For date_expected you will have to import in your .py file (out of your classes) the following:
import time
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT

Odoo - prevent button from closing wizard

I have a transient model that serves as a dialog. In my form view I have a button like this:
<footer states="partnerId">
<button name="check_tax_id" string="Tovább" type="object"/>
</footer>
The button invokes this function (I can confirm it actually invokes):
#api.one
def check_tax_id(self, context=None):
self.state = "partnerDetails"
return None;
My problem is that the dialog window is closed immediately once I click this button!
What am I doing wrong?
Solution 0
#api.multi
def check_tax_id(self):
self.ensure_one()
self.name = "New name"
return {
"type": "ir.actions.do_nothing",
}
This solution was provided here by Tadeusz Karpinski.
Solution 1
You can return a new form with the same record id.
#api.multi
def check_tax_id(self):
self.ensure_one()
self.name = "New name"
return {
'context': self.env.context,
'view_type': 'form',
'view_mode': 'form',
'res_model': 'model_name',
'res_id': self.id,
'view_id': False,
'type': 'ir.actions.act_window',
'target': 'new',
}
Solution 2
You can create a widget in jQuery. This will open the wizard and you can assign the behaviour you want to the buttons manually. You can use the call function to call python functions as well:
[...]
new instance.web.Dialog(this, {
title: _t("Title"),
width: '95%',
buttons: [
{ text: _t("First button"), click: function() { self.first_button(); }},
{ text: _t("Second button"), click: function() { self.second_button(); }},
{ text: _t("Close"), click: function() { dialog.close(); }},
],
});
[...]
Solution 3
Of course you can override the create method as well to avoid the creation of the record in some cases
Solution 4
One last option. Create a workflow with a state field. Create workflow buttons in order to send signals to change the state. You can show or hide the rest of the fields using the attrs attribute and the state field. But I do not know if that would adapt to your needs.
In my case this code works.
#api.multi
def test(self):
l = logging.getLogger()
l.warn("xD")
return {
"type": "ir.actions.do_nothing",
}
The simplest this to do is :
#api.multi
def null_action(self):
return {
"type": "set_scrollTop",
}
As the type is used to call any method on the class ActionManager (javascript)
It's better than "type": "ir.actions.do_nothing" which generate an exception (this attribute doesn't exist)
yesterday I bumped on this same issue. I needed to show a button to do something without submitting the whole wizaard. I worked around it by not using a button at all. It's pretty simple and effective. What you need:
a boolean flag in your wizard model
an onchange attached to the flag (that replaces you sumbmit function)
replace the button in the view w/ the flag w/ invisible="1" and a label to be styled as a button
Here's the code:
source_it = fields.Boolean(string='Source')
[...]
def action_source(self):
# do stuff
#api.onchange('source_it')
def onchange_source_it(self):
if self.env.context.get('sourcing_now') or not self.source_it:
return
self.action_source()
[...]
<label for="source_it" class="pull-left btn btn-success" />
<field name="source_it" invisible="1" />
The trick works because when a label has for attribute is going to act like the checkbox itself, so if you click on the label you are actually switching the checkbox.
What you can do is have the button open another wizard passing context with all the values entered into the first wizard. This allows you to execute some function ie. your button. And maintain the state of your wizard. So the default value for fields in your wizard must check context first and fallback to something else.
Here is an example:
class MyWizard(models.TransientModel):
_name = 'myaddon.mywizard'
def _get_default_char(self):
return self._context.get('mychar',"")
mychar = fields.Char(string="My Char", default=_get_default_char)
#api.multi
def my_button(self):
# Execute Function Here
# reload wizard with context
return {
'view_type': 'form',
'view_mode': 'form',
'res_model': 'myaddon.mywizard',
'type': 'ir.actions.act_window',
'target': 'new',
'context': '{"mychar":'HELLO WORLD'}',
}
on odoo 7
def traszero(self ,cr ,uid ,ids ,context=None):
data_obj = self.pool.get('stock.return.picking.line')
ret_wizard = self.browse(cr, uid, ids, context=context)
if ret_wizard.product_return_moves:
line_ids = ret_wizard.product_return_moves.mapped('id')
data_obj.write(cr, uid, line_ids, {'quantity': 0}, context=context)
return {'name':"Return Shipment",
'res_model':"stock.return.picking",
'src_model':"stock.picking",
'view_mode':"form",
'target':"new",
'key2':"client_action_multi",
'multi':"True",
'res_id':ids[0],
'type': 'ir.actions.act_window',
}

Closing Wizard on button click in OpenERP 7

I open the wizard from button in OpenERP 7. But when click on button Compute of wizard my wizard got close but I do not want to close the wizard on button click of Compute instead of my wizard close when click on button Close of wizard.
I am using OpenERP 7.
class test_pass_student(osv.osv_memory):
_name = 'test.pass.student'
_column ={
'pass_id': fields.many2one('pass.student', 'Passed'),
'student_id':fields.many2one('student.student', 'Student'),
}
test_pass_student()
def _reopen(self, res_id, model):
return {'type': 'ir.actions.act_window',
'view_mode': 'form',
'view_type': 'form',
'res_id': res_id,
'res_model': self._name,
'target': 'new',
'context': {
'default_model': model,
},
}
class pass_student(osv.osv_memory):
_name = 'pass.student'
_columns = {
'student_id':fields.many2one('student.student', 'Student'),
'lines': fields.one2many('test.pass.student','pass_id', 'Passed students'),
}
def add_student(self, cr, uid, ids,context=None):
lines_obj = self.pool.get('test.pass.student')
for record in self.browse(cr,uid,ids,context):
for line in record.student_id.scores:
if line.pass_score > 50:
lines_obj.create(cr,uid,{'pass_id': record.id,'student_id':line.student_id.id})
return _reopen(self, record.id, record._model)
pass_student()
Shen S select first student check, if his/her marks greater than 50 then added in one2many, and then again check another student, same things repeat again.
The default behavior for wizard buttons (with type="object) as of OpenERP 6.1 (hence in 7.0 as well) is to immediately close the wizard pop-up. The method called by the button can return an action definition dictionary that will be executed.
When you do not want the wizard to close it is usually because you have several steps. As multi-steps wizards usually have different form views, their button methods simply return actions to open the same wizard record using the next step's view (it could also be the same view if it needs to be displayed again).
You can find examples in the official addons source code, for example in the mail.compose.message wizard modified by the email_template module, that uses a similar trick to re-open itself.
This question and this other one may also contain useful examples.
For closing the wizard on button click add this code on view form xml:
<button string="Cancel" class="oe_link" special="cancel"/>
no need to write separate method to open wizard again. You can just take object reference and return it with view id. for example.
def add_student(self, cr, uid, ids,context=None):
model_data_obj = self.pool.get('ir.model.data')
lines_obj = self.pool.get('test.pass.student')
for record in self.browse(cr,uid,ids,context):
for line in record.student_id.scores:
if line.pass_score > 50:
lines_obj.create(cr,uid,{'pass_id': record.id,'student_id':line.student_id.id})
view_rec = model_data_obj.get_object_reference(cr, uid, 'pass_student', 'add_student_form_view_id')
view_id = view_rec and view_rec[1] or False
return {
'view_type': 'form',
'view_id' : [view_id],
'view_mode': 'form',
'res_model': 'pass.student',
'type': 'ir.actions.act_window',
'target': 'new',
'context': context
}
Hope it will help you!
I reply myself, in the wizard if, instead of button type workflow, I put button type object and there trigger wf works (no close), but is that the right path?
If someone need this is my object button event code (for my picking.import.wizard wizard):
def signal_import_load_2(self, cr, uid, ids, context=None):
import netsvc
wf_service = netsvc.LocalService("workflow")
wf_service.trg_validate(uid, 'picking.import.wizard', ids[0], 'signal_import_load', cr)
view_id = self.pool.get('ir.ui.view').search(cr,uid,[('model','=','picking.import.wizard'), ('name','=','Wizard import picking from CSV')])
return {
'type': 'ir.actions.act_window',
'name': "Import",
'res_model': 'picking.import.wizard',
'res_id': ids[0],
'view_type': 'form',
'view_mode': 'form',
'view_id': view_id,
'target': 'new',
'nodestroy': True,
}

Categories

Resources