Odoo - prevent button from closing wizard - python

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',
}

Related

Auto populate products in initial demands (stock.move table) in internal transfer (stock.picking) Error

All I want to do is from Location>in Current Stock Smart button (which is stock.quant table)
It allows selection of multiple records.
then I add action server button like post expense button.
There is no problem with that and I already add or auto populate the product in initial demand (stock.move table) in internal transfer (stock.picking table)
but the problem is this when I click save it it tells that "The following fields are invalid: Stock Moves" but the stock moves is the the initial demand that I auto populate the data.
This is my code that auto populates the data in initial demand tab.
#api.onchange('stock_quant_ids')
def onchange_stock_quant_ids(self):
print 'onchange_stock_quant_ids'
stock_move_lines = []
for stock_quant_rec in self.stock_quant_ids:
print 'stock_quant_rec.product_id.name>', stock_quant_rec.product_id.name
# stock_move_lines.append(stock_quant_rec.id)
stock_move_lines.append({
'picking_id': self.id,
'name': stock_quant_rec.product_id.name,
'product_id': stock_quant_rec.product_id.id,
'product_uom_qty': stock_quant_rec.qty,
'product_uom': stock_quant_rec.product_uom_id.id,
'state': 'draft',
})
self.move_lines = stock_move_lines
and this is the code that will pop up the picking windows when you click post expense
#api.multi
def post_expense(self):
stock_quant_ids = []
stock_quant_holder = self.browse(self._context.get('active_ids'))
for rec in stock_quant_holder:
stock_quant_ids.append(rec.id)
view_id = self.env.ref('stock.view_picking_form')
return {
'name': _('Transfer Expense'),
'type': 'ir.actions.act_window',
'res_model': 'stock.picking',
'view_type': 'form',
'view_mode': 'form',
'target': 'new',
'view_id': view_id.id,
'views': [(view_id.id, 'form')],
'context': {
'default_stock_quant_ids': stock_quant_ids,
'default_state': 'draft',
# 'default_bool_loc': True,
'default_picking_type_id': 5,
'default_location_id': self.location_id.id,
'default_location_dest_id': self.location_id.expense_location_id.id,
}
}
I Hope anyone can help me because I work this for almost a weeks. Thank you in advance.

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, ..})
]
},

What's happening with these transient models' IDs?

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.

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,
}

Openerp: onChange event to create lines on account move

I have a field amount on the Journal Entries (account move form) and I need to define an onChange event which automatically inserts the lines once I fill in the amount. But I am not sure how.
Yesterday I had to do something similar to your requirement. I had a salo order(m2o) field in purchase ...on_change of sale_order I had to fill the purchase order lines ...see hope it can help you.
class purchase_order(osv.osv):
_inherit = 'purchase.order'
_columns = {
'sale_order':fields.many2one('sale.order','Sale Order'),
'purchase_type':
fields.selection([
('order','Purchase Order'),
('job','Job Order')
],
'Purchase Type',
required=True,
states={
'confirmed':[('readonly',True)],
'approved':[('readonly',True)],
},
select=True,
help="Define type of purchase order.",
),
}
def onchange_saleorder(self,cr,uid,ids,order,context=None):
res={}
lis=[]
sorder_id=self.pool.get('sale.order').browse(cr,uid,order)
for line in sorder_id.order_line:
print "uom",line.product_id.uom_id.id
res={'product_id':line.product_id.id,
'name':line.product_id.name,
'product_qty':1,
'product_uom':line.product_id.uom_id.id,
'price_unit':line.price_unit,
'date_planned':time.strftime('%Y-%m-%d'),}
lis.append(res)
print "list is .........",lis
res={'value':{'order_line':lis}} // here order_line is o2m field of purchase.
return res
Your on_change method has to return a list of dictionaries made of line values.
For instance:
res['value']['line_ids'] = [
{
'account_id': 1,
'debit': 100,
},
{
'account_id': 2,
'credit': 100,
}]
Also, see the recompute_voucher_lines method of account.voucher as example.
sometimes error '0' will be generate so we have to write a code with like this.
In case of above Example
lis.append((0,0,res))

Categories

Resources