[MIG] base: migrate base/ir to the new API
This commit is contained in:
committed by
Raphael Collet
parent
348fa4f958
commit
eb04aa1e39
@@ -1,14 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from openerp import SUPERUSER_ID
|
from odoo import models
|
||||||
from openerp.osv import osv
|
|
||||||
|
|
||||||
class ir_configparameter(osv.Model):
|
|
||||||
|
class IrConfigParameter(models.Model):
|
||||||
_inherit = 'ir.config_parameter'
|
_inherit = 'ir.config_parameter'
|
||||||
|
|
||||||
def init(self, cr, force=False):
|
def init(self, force=False):
|
||||||
super(ir_configparameter, self).init(cr, force=force)
|
super(IrConfigParameter, self).init(force=force)
|
||||||
if force:
|
if force:
|
||||||
IMD = self.pool['ir.model.data']
|
oauth_oe = self.env.ref('auth_oauth.provider_openerp')
|
||||||
oauth_oe = IMD.xmlid_to_object(cr, SUPERUSER_ID, 'auth_oauth.provider_openerp')
|
oauth_oe.write({'client_id': self.sudo().get_param('database.uuid')})
|
||||||
dbuuid = self.get_param(cr, SUPERUSER_ID, 'database.uuid')
|
|
||||||
oauth_oe.write({'client_id': dbuuid})
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class ir_actions_report_xml(orm.Model):
|
|||||||
" is printed on a separate HTML but memory and disk usage are wider.")
|
" is printed on a separate HTML but memory and disk usage are wider.")
|
||||||
}
|
}
|
||||||
|
|
||||||
def _lookup_report(self, cr, name):
|
def _lookup_report(self, name):
|
||||||
"""
|
"""
|
||||||
Look up a report definition.
|
Look up a report definition.
|
||||||
"""
|
"""
|
||||||
@@ -41,8 +41,8 @@ class ir_actions_report_xml(orm.Model):
|
|||||||
if not isinstance(new_report, WebKitParser):
|
if not isinstance(new_report, WebKitParser):
|
||||||
new_report = None
|
new_report = None
|
||||||
else:
|
else:
|
||||||
cr.execute("SELECT * FROM ir_act_report_xml WHERE report_name=%s and report_type=%s", (name, 'webkit'))
|
self.env.cr.execute("SELECT * FROM ir_act_report_xml WHERE report_name=%s and report_type=%s", (name, 'webkit'))
|
||||||
r = cr.dictfetchone()
|
r = self.env.cr.dictfetchone()
|
||||||
if r:
|
if r:
|
||||||
if r['parser']:
|
if r['parser']:
|
||||||
parser = operator.attrgetter(r['parser'])(openerp.addons)
|
parser = operator.attrgetter(r['parser'])(openerp.addons)
|
||||||
@@ -59,4 +59,4 @@ class ir_actions_report_xml(orm.Model):
|
|||||||
if new_report:
|
if new_report:
|
||||||
return new_report
|
return new_report
|
||||||
else:
|
else:
|
||||||
return super(ir_actions_report_xml, self)._lookup_report(cr, name)
|
return super(ir_actions_report_xml, self)._lookup_report(name)
|
||||||
|
|||||||
@@ -2,64 +2,59 @@
|
|||||||
|
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
from openerp.http import request
|
from odoo.http import request
|
||||||
from openerp.osv import fields, osv
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
class actions_server(osv.Model):
|
class actions_server(models.Model):
|
||||||
""" Add website option in server actions. """
|
""" Add website option in server actions. """
|
||||||
_name = 'ir.actions.server'
|
_name = 'ir.actions.server'
|
||||||
_inherit = ['ir.actions.server']
|
_inherit = 'ir.actions.server'
|
||||||
|
|
||||||
def _compute_website_url(self, cr, uid, id, website_path, xml_id, context=None):
|
xml_id = fields.Char(compute=models.Model.get_xml_id, string="External ID",
|
||||||
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url', context=context)
|
help="ID of the action if defined in a XML file")
|
||||||
link = website_path or xml_id or (id and '%d' % id) or ''
|
website_path = fields.Char()
|
||||||
|
website_url = fields.Char(compute='_get_website_url', help='The full URL to access the server action through the website.')
|
||||||
|
website_published = fields.Boolean(string='Available on the Website', copy=False,
|
||||||
|
help='A code server action can be executed from the website, using a dedicated '
|
||||||
|
'controller. The address is <base>/website/action/<website_path>. '
|
||||||
|
'Set this field as True to allow users to run this action. If it '
|
||||||
|
'is set to False the action cannot be run through the website.')
|
||||||
|
|
||||||
|
def _compute_website_url(self, website_path, xml_id):
|
||||||
|
base_url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||||
|
link = website_path or xml_id or (self.id and '%d' % self.id) or ''
|
||||||
if base_url and link:
|
if base_url and link:
|
||||||
path = '%s/%s' % ('/website/action', link)
|
path = '%s/%s' % ('/website/action', link)
|
||||||
return '%s' % urlparse.urljoin(base_url, path)
|
return '%s' % urlparse.urljoin(base_url, path)
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def _get_website_url(self, cr, uid, ids, name, args, context=None):
|
@api.depends('website_url')
|
||||||
res = dict.fromkeys(ids, False)
|
def _get_website_url(self):
|
||||||
for action in self.browse(cr, uid, ids, context=context):
|
for action in self:
|
||||||
if action.state == 'code' and action.website_published:
|
if action.state == 'code' and action.website_published:
|
||||||
res[action.id] = self._compute_website_url(cr, uid, action.id, action.website_path, action.xml_id, context=context)
|
action.website_url = self._compute_website_url(action.website_path, action.xml_id)
|
||||||
return res
|
|
||||||
|
|
||||||
_columns = {
|
@api.onchange('website_path', 'xml_id')
|
||||||
'xml_id': fields.function(
|
def on_change_website_path(self):
|
||||||
osv.osv.get_xml_id, type='char', string="External ID",
|
|
||||||
help="ID of the action if defined in a XML file"),
|
|
||||||
'website_path': fields.char('Website Path'),
|
|
||||||
'website_url': fields.function(
|
|
||||||
_get_website_url, type='char', string='Website URL',
|
|
||||||
help='The full URL to access the server action through the website.'),
|
|
||||||
'website_published': fields.boolean(
|
|
||||||
'Available on the Website', copy=False,
|
|
||||||
help='A code server action can be executed from the website, using a dedicated '
|
|
||||||
'controller. The address is <base>/website/action/<website_path>. '
|
|
||||||
'Set this field as True to allow users to run this action. If it '
|
|
||||||
'is set to False the action cannot be run through the website.'),
|
|
||||||
}
|
|
||||||
|
|
||||||
def on_change_website_path(self, cr, uid, ids, website_path, xml_id, context=None):
|
|
||||||
values = {
|
values = {
|
||||||
'website_url': self._compute_website_url(cr, uid, ids and ids[0] or None, website_path, xml_id, context=context)
|
'website_url': self._compute_website_url(self.website_path, self.xml_id)
|
||||||
}
|
}
|
||||||
return {'value': values}
|
return {'value': values}
|
||||||
|
|
||||||
def _get_eval_context(self, cr, uid, action, context=None):
|
@api.model
|
||||||
|
def _get_eval_context(self, action):
|
||||||
""" Override to add the request object in eval_context. """
|
""" Override to add the request object in eval_context. """
|
||||||
eval_context = super(actions_server, self)._get_eval_context(cr, uid, action, context=context)
|
eval_context = super(actions_server, self)._get_eval_context(action)
|
||||||
if action.state == 'code':
|
if action.state == 'code':
|
||||||
eval_context['request'] = request
|
eval_context['request'] = request
|
||||||
return eval_context
|
return eval_context
|
||||||
|
|
||||||
def run_action_code_multi(self, cr, uid, action, eval_context=None, context=None):
|
def run_action_code_multi(self, action, eval_context=None):
|
||||||
""" Override to allow returning response the same way action is already
|
""" Override to allow returning response the same way action is already
|
||||||
returned by the basic server action behavior. Note that response has
|
returned by the basic server action behavior. Note that response has
|
||||||
priority over action, avoid using both. """
|
priority over action, avoid using both. """
|
||||||
res = super(actions_server, self).run_action_code_multi(cr, uid, action, eval_context, context)
|
res = super(actions_server, self).run_action_code_multi(action, eval_context)
|
||||||
if 'response' in eval_context:
|
if 'response' in eval_context:
|
||||||
return eval_context['response']
|
return eval_context['response']
|
||||||
return res
|
return res
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
<xpath expr="//field[@name='sequence']" position="after">
|
<xpath expr="//field[@name='sequence']" position="after">
|
||||||
<field name="xml_id" invisible="1"/>
|
<field name="xml_id" invisible="1"/>
|
||||||
<field name="website_path"
|
<field name="website_path"
|
||||||
on_change="on_change_website_path(website_path, xml_id, context)"
|
|
||||||
attrs="{'invisible': ['|', ('website_published', '!=', True), ('state', '!=', 'code')]}"/>
|
attrs="{'invisible': ['|', ('website_published', '!=', True), ('state', '!=', 'code')]}"/>
|
||||||
<field name="website_url" readonly="1" widget="url"
|
<field name="website_url" readonly="1" widget="url"
|
||||||
attrs="{'invisible': ['|', ('website_published', '!=', True), ('state', '!=', 'code')]}"/>
|
attrs="{'invisible': ['|', ('website_published', '!=', True), ('state', '!=', 'code')]}"/>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
<field name="name">Choose Your Theme Todo Action</field>
|
<field name="name">Choose Your Theme Todo Action</field>
|
||||||
<field name="model_id" ref="model_ir_module_module"/>
|
<field name="model_id" ref="model_ir_module_module"/>
|
||||||
<field name="code">
|
<field name="code">
|
||||||
self.update_list(cr, uid, context=context)
|
self.update_list()
|
||||||
action = {
|
action = {
|
||||||
'type': 'ir.actions.act_url',
|
'type': 'ir.actions.act_url',
|
||||||
'url': '/web?reload#action=website_theme_install.theme_install_kanban_action', # the ?reload option is there to fool the webclient into thinking it is a different location and so to force a reload
|
'url': '/web?reload#action=website_theme_install.theme_install_kanban_action', # the ?reload option is there to fool the webclient into thinking it is a different location and so to force a reload
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
|
|
||||||
<!-- ir.actions -->
|
<!-- ir.actions -->
|
||||||
|
|
||||||
@@ -275,8 +274,7 @@
|
|||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="type" invisible="1"/>
|
<field name="type" invisible="1"/>
|
||||||
<field name="model_id"
|
<field name="model_id"/>
|
||||||
on_change="on_change_model_id(model_id, wkf_model_id, crud_model_id)"/>
|
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
@@ -297,15 +295,12 @@
|
|||||||
</p>
|
</p>
|
||||||
<group attrs="{'invisible': [('model_id', '=', False)]}">
|
<group attrs="{'invisible': [('model_id', '=', False)]}">
|
||||||
<field name="use_relational_model" widget="radio"
|
<field name="use_relational_model" widget="radio"
|
||||||
on_change="on_change_wkf_wonfig(use_relational_model, wkf_field_id, wkf_model_id, model_id)"
|
|
||||||
attrs="{'readonly': [('model_id', '=', False)]}"/>
|
attrs="{'readonly': [('model_id', '=', False)]}"/>
|
||||||
<field name="wkf_field_id" context="{'key': ''}"
|
<field name="wkf_field_id" context="{'key': ''}"
|
||||||
on_change="on_change_wkf_wonfig(use_relational_model, wkf_field_id, wkf_model_id, model_id)"
|
|
||||||
attrs="{'required': [('state', '=', 'trigger'), ('use_relational_model', '=', 'relational')],
|
attrs="{'required': [('state', '=', 'trigger'), ('use_relational_model', '=', 'relational')],
|
||||||
'invisible': [('use_relational_model', '=', 'base')]}"
|
'invisible': [('use_relational_model', '=', 'base')]}"
|
||||||
domain="[('model_id', '=', model_id), ('ttype', 'in', ['many2one'])]"/>
|
domain="[('model_id', '=', model_id), ('ttype', 'in', ['many2one'])]"/>
|
||||||
<field name="wkf_model_id" invisible="1"
|
<field name="wkf_model_id" invisible="1"/>
|
||||||
on_change="on_change_wkf_model_id(wkf_model_id)"/>
|
|
||||||
<field name="wkf_model_name" invisible="1"/>
|
<field name="wkf_model_name" invisible="1"/>
|
||||||
<field name="wkf_transition_id" attrs="{'required': [('state', '=', 'trigger')]}"
|
<field name="wkf_transition_id" attrs="{'required': [('state', '=', 'trigger')]}"
|
||||||
domain="[('wkf_id.osv', '=', wkf_model_name)]"/>
|
domain="[('wkf_id.osv', '=', wkf_model_name)]"/>
|
||||||
@@ -327,11 +322,9 @@
|
|||||||
</p>
|
</p>
|
||||||
<group attrs="{'invisible': [('model_id', '=', False)]}">
|
<group attrs="{'invisible': [('model_id', '=', False)]}">
|
||||||
<field name="use_create" widget="radio"
|
<field name="use_create" widget="radio"
|
||||||
on_change="on_change_crud_config(state, use_create, use_write, ref_object, crud_model_id, model_id)"
|
|
||||||
attrs="{'invisible': [('state', '!=', 'object_create')]}"/>
|
attrs="{'invisible': [('state', '!=', 'object_create')]}"/>
|
||||||
|
|
||||||
<field name="use_write" widget="radio"
|
<field name="use_write" widget="radio"
|
||||||
on_change="on_change_crud_config(state, use_create, use_write, ref_object, crud_model_id, model_id)"
|
|
||||||
attrs="{'invisible': [('state', '!=', 'object_write')]}"/>
|
attrs="{'invisible': [('state', '!=', 'object_write')]}"/>
|
||||||
|
|
||||||
<label for="ref_object" string=" "
|
<label for="ref_object" string=" "
|
||||||
@@ -342,12 +335,10 @@
|
|||||||
attrs="{'invisible': ['&',
|
attrs="{'invisible': ['&',
|
||||||
'|', ('state', '!=', 'object_write'), ('use_write', '!=', 'other'),
|
'|', ('state', '!=', 'object_write'), ('use_write', '!=', 'other'),
|
||||||
'|', ('state', '!=', 'object_create'), ('use_create', '!=', 'copy_other')]}">
|
'|', ('state', '!=', 'object_create'), ('use_create', '!=', 'copy_other')]}">
|
||||||
<field name="ref_object" nolabel="1"
|
<field name="ref_object" nolabel="1"/>
|
||||||
on_change="on_change_crud_config(state, use_create, use_write, ref_object, crud_model_id, model_id)"/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<field name="crud_model_id"
|
<field name="crud_model_id"
|
||||||
on_change="on_change_crud_model_id(crud_model_id)"
|
|
||||||
attrs="{'invisible': ['|', ('state', '!=', 'object_create'), ('use_create', '!=', 'new_other')]}"/>
|
attrs="{'invisible': ['|', ('state', '!=', 'object_create'), ('use_create', '!=', 'new_other')]}"/>
|
||||||
<field name="crud_model_name" invisible="1"/>
|
<field name="crud_model_name" invisible="1"/>
|
||||||
|
|
||||||
@@ -376,7 +367,6 @@
|
|||||||
<li>object.partner_id.currency_id</li>
|
<li>object.partner_id.currency_id</li>
|
||||||
</ul>
|
</ul>
|
||||||
<field name="write_expression"
|
<field name="write_expression"
|
||||||
on_change="on_change_write_expression(write_expression, model_id)"
|
|
||||||
attrs="{'required': [('state', '=', 'object_write'), ('use_write', '=', 'expression')]}"/>
|
attrs="{'required': [('state', '=', 'object_write'), ('use_write', '=', 'expression')]}"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -445,18 +435,16 @@
|
|||||||
</p>
|
</p>
|
||||||
<field name="model_object_field"
|
<field name="model_object_field"
|
||||||
attrs="{'invisible': [('model_id', '=', False)]}"
|
attrs="{'invisible': [('model_id', '=', False)]}"
|
||||||
domain="[('model_id', '=', model_id), ('ttype', '!=', 'one2many'), ('ttype', '!=', 'many2many')]"
|
domain="[('model_id', '=', model_id), ('ttype', '!=', 'one2many'), ('ttype', '!=', 'many2many')]"/>
|
||||||
on_change="onchange_sub_model_object_value_field(model_object_field)"/>
|
|
||||||
<field name="sub_object" readonly="1" attrs="{'invisible': [('model_id', '=', False)]}"/>
|
<field name="sub_object" readonly="1" attrs="{'invisible': [('model_id', '=', False)]}"/>
|
||||||
<field name="sub_model_object_field"
|
<field name="sub_model_object_field"
|
||||||
domain="[('model_id', '=', sub_object), ('ttype', '!=', 'one2many'), ('ttype', '!=', 'many2many')]"
|
domain="[('model_id', '=', sub_object), ('ttype', '!=', 'one2many'), ('ttype', '!=', 'many2many')]"
|
||||||
attrs="{'readonly':[('sub_object','=',False)],
|
attrs="{'readonly':[('sub_object','=',False)],
|
||||||
'required':[('sub_object','!=',False)],
|
'required':[('sub_object','!=',False)],
|
||||||
'invisible': [('model_id', '=', False)]}"
|
'invisible': [('model_id', '=', False)]}"/>
|
||||||
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
|
|
||||||
<field name="copyvalue" attrs="{'invisible': [('model_id', '=', False)]}"/>
|
<field name="copyvalue" attrs="{'invisible': [('model_id', '=', False)]}"/>
|
||||||
<h3 colspan="2">Find the ID of a record in the database</h3>
|
<h3 colspan="2">Find the ID of a record in the database</h3>
|
||||||
<field name="id_object" on_change="onchange_id_object(id_object)"/>
|
<field name="id_object"/>
|
||||||
<field name="id_value" />
|
<field name="id_value" />
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
@@ -583,5 +571,4 @@ if config.get('type') not in ('ir.actions.act_window_close',):
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
<!-- Attachment -->
|
<!-- Attachment -->
|
||||||
<record id="view_attachment_form" model="ir.ui.view">
|
<record id="view_attachment_form" model="ir.ui.view">
|
||||||
<field name="model">ir.attachment</field>
|
<field name="model">ir.attachment</field>
|
||||||
@@ -104,5 +103,4 @@
|
|||||||
</record>
|
</record>
|
||||||
<menuitem action="action_attachment" id="menu_action_attachment" parent="base.next_id_9"/>
|
<menuitem action="action_attachment" id="menu_action_attachment" parent="base.next_id_9"/>
|
||||||
|
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
@@ -3,31 +3,33 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from openerp import models
|
from odoo import api, models
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AutoVacuum(models.TransientModel):
|
class AutoVacuum(models.TransientModel):
|
||||||
""" Expose the vacuum method to the cron jobs mechanism. """
|
""" Expose the vacuum method to the cron jobs mechanism. """
|
||||||
_name = 'ir.autovacuum'
|
_name = 'ir.autovacuum'
|
||||||
|
|
||||||
|
@api.model
|
||||||
def _gc_transient_models(self, cr, uid, *args, **kwargs):
|
def _gc_transient_models(self):
|
||||||
for model in self.pool.itervalues():
|
for model in self.pool.itervalues():
|
||||||
if model.is_transient():
|
if model.is_transient():
|
||||||
model._transient_vacuum(cr, uid, force=True)
|
model._transient_vacuum(self._cr, self._uid, force=True)
|
||||||
|
|
||||||
def _gc_user_logs(self, cr, uid, *args, **kwargs):
|
def _gc_user_logs(self):
|
||||||
cr.execute("""
|
self.env.cr.execute("""
|
||||||
DELETE FROM res_users_log log1 WHERE EXISTS (
|
DELETE FROM res_users_log log1 WHERE EXISTS (
|
||||||
SELECT 1 FROM res_users_log log2
|
SELECT 1 FROM res_users_log log2
|
||||||
WHERE log1.create_uid = log2.create_uid
|
WHERE log1.create_uid = log2.create_uid
|
||||||
AND log1.create_date < log2.create_date
|
AND log1.create_date < log2.create_date
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
_logger.info("GC'd %d user log entries", cr.rowcount)
|
_logger.info("GC'd %d user log entries", self.env.cr.rowcount)
|
||||||
|
|
||||||
def power_on(self, cr, uid, *args, **kwargs):
|
@api.model
|
||||||
self._gc_transient_models(cr, uid, *args, **kwargs)
|
def power_on(self):
|
||||||
self._gc_user_logs(cr, uid, *args, **kwargs)
|
self._gc_transient_models()
|
||||||
|
self._gc_user_logs()
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
<record model="ir.cron" id="autovacuum_job">
|
<record model="ir.cron" id="autovacuum_job">
|
||||||
<field name='name'>Auto-vacuum internal data</field>
|
<field name='name'>Auto-vacuum internal data</field>
|
||||||
<field name='interval_number'>1</field>
|
<field name='interval_number'>1</field>
|
||||||
@@ -11,6 +10,4 @@
|
|||||||
<field name="function">power_on</field>
|
<field name="function">power_on</field>
|
||||||
<field name="args">()</field>
|
<field name="args">()</field>
|
||||||
</record>
|
</record>
|
||||||
|
</odoo>
|
||||||
</data>
|
|
||||||
</openerp>
|
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ Store database-specific configuration parameters
|
|||||||
import uuid
|
import uuid
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from openerp import SUPERUSER_ID
|
from odoo import api, fields, models
|
||||||
from openerp.osv import osv, fields
|
from odoo.tools import misc, config, ormcache
|
||||||
from openerp.tools import misc, config, ormcache
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A dictionary holding some configuration parameters to be initialized when the database is created.
|
A dictionary holding some configuration parameters to be initialized when the database is created.
|
||||||
@@ -22,35 +21,34 @@ _default_parameters = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ir_config_parameter(osv.osv):
|
class IrConfigParameter(models.Model):
|
||||||
"""Per-database storage of configuration key-value pairs."""
|
"""Per-database storage of configuration key-value pairs."""
|
||||||
|
|
||||||
_name = 'ir.config_parameter'
|
_name = 'ir.config_parameter'
|
||||||
_rec_name = 'key'
|
_rec_name = 'key'
|
||||||
|
|
||||||
_columns = {
|
key = fields.Char(required=True, index=True)
|
||||||
'key': fields.char('Key', required=True, select=1),
|
value = fields.Text(required=True)
|
||||||
'value': fields.text('Value', required=True),
|
group_ids = fields.Many2many('res.groups', 'ir_config_parameter_groups_rel', 'icp_id', 'group_id', string='Groups')
|
||||||
'group_ids': fields.many2many('res.groups', 'ir_config_parameter_groups_rel', 'icp_id', 'group_id', string='Groups'),
|
|
||||||
}
|
|
||||||
|
|
||||||
_sql_constraints = [
|
_sql_constraints = [
|
||||||
('key_uniq', 'unique (key)', 'Key must be unique.')
|
('key_uniq', 'unique (key)', 'Key must be unique.')
|
||||||
]
|
]
|
||||||
|
|
||||||
def init(self, cr, force=False):
|
def init(self, force=False):
|
||||||
"""
|
"""
|
||||||
Initializes the parameters listed in _default_parameters.
|
Initializes the parameters listed in _default_parameters.
|
||||||
It overrides existing parameters if force is ``True``.
|
It overrides existing parameters if force is ``True``.
|
||||||
"""
|
"""
|
||||||
for key, func in _default_parameters.iteritems():
|
for key, func in _default_parameters.iteritems():
|
||||||
# force=True skips search and always performs the 'if' body (because ids=False)
|
# force=True skips search and always performs the 'if' body (because ids=False)
|
||||||
ids = not force and self.search(cr, SUPERUSER_ID, [('key','=',key)])
|
self = not force and self.sudo().search([('key', '=', key)])
|
||||||
if not ids:
|
if not self.ids:
|
||||||
value, groups = func()
|
value, groups = func()
|
||||||
self.set_param(cr, SUPERUSER_ID, key, value, groups=groups)
|
self.sudo().set_param(key, value, groups=groups)
|
||||||
|
|
||||||
def get_param(self, cr, uid, key, default=False, context=None):
|
@api.model
|
||||||
|
def get_param(self, key, default=False):
|
||||||
"""Retrieve the value for a given key.
|
"""Retrieve the value for a given key.
|
||||||
|
|
||||||
:param string key: The key of the parameter value to retrieve.
|
:param string key: The key of the parameter value to retrieve.
|
||||||
@@ -58,10 +56,10 @@ class ir_config_parameter(osv.osv):
|
|||||||
:return: The value of the parameter, or ``default`` if it does not exist.
|
:return: The value of the parameter, or ``default`` if it does not exist.
|
||||||
:rtype: string
|
:rtype: string
|
||||||
"""
|
"""
|
||||||
result = self._get_param(cr, uid, key)
|
result = self._get_param(key)
|
||||||
if result is None:
|
if result:
|
||||||
return default
|
return result
|
||||||
return result
|
return default
|
||||||
|
|
||||||
@ormcache('uid', 'key')
|
@ormcache('uid', 'key')
|
||||||
def _get_param(self, cr, uid, key):
|
def _get_param(self, cr, uid, key):
|
||||||
@@ -70,7 +68,8 @@ class ir_config_parameter(osv.osv):
|
|||||||
return None
|
return None
|
||||||
return params[0]['value']
|
return params[0]['value']
|
||||||
|
|
||||||
def set_param(self, cr, uid, key, value, groups=(), context=None):
|
@api.model
|
||||||
|
def set_param(self, key, value, groups=()):
|
||||||
"""Sets the value of a parameter.
|
"""Sets the value of a parameter.
|
||||||
|
|
||||||
:param string key: The key of the parameter value to set.
|
:param string key: The key of the parameter value to set.
|
||||||
@@ -81,35 +80,35 @@ class ir_config_parameter(osv.osv):
|
|||||||
:rtype: string
|
:rtype: string
|
||||||
"""
|
"""
|
||||||
self._get_param.clear_cache(self)
|
self._get_param.clear_cache(self)
|
||||||
ids = self.search(cr, uid, [('key','=',key)], context=context)
|
self = self.search([('key', '=', key)])
|
||||||
|
|
||||||
gids = []
|
gids = []
|
||||||
for group_xml in groups:
|
for group_xml in groups:
|
||||||
res_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, group_xml)
|
res_id = self.env['ir.model.data'].xmlid_to_res_id(group_xml)
|
||||||
if res_id:
|
if res_id:
|
||||||
gids.append((4, res_id))
|
gids.append((4, res_id))
|
||||||
|
|
||||||
vals = {'value': value}
|
vals = {'value': value}
|
||||||
if gids:
|
if gids:
|
||||||
vals.update(group_ids=gids)
|
vals.update(group_ids=gids)
|
||||||
if ids:
|
if self.ids:
|
||||||
param = self.browse(cr, uid, ids[0], context=context)
|
|
||||||
old = param.value
|
|
||||||
if value is not False and value is not None:
|
if value is not False and value is not None:
|
||||||
self.write(cr, uid, ids, vals, context=context)
|
self.write(vals)
|
||||||
else:
|
else:
|
||||||
self.unlink(cr, uid, ids, context=context)
|
self.unlink()
|
||||||
return old
|
return self.value
|
||||||
else:
|
else:
|
||||||
vals.update(key=key)
|
vals.update(key=key)
|
||||||
if value is not False and value is not None:
|
if value is not False and value is not None:
|
||||||
self.create(cr, uid, vals, context=context)
|
self.create(vals)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def write(self, cr, uid, ids, vals, context=None):
|
@api.multi
|
||||||
|
def write(self, vals):
|
||||||
self._get_param.clear_cache(self)
|
self._get_param.clear_cache(self)
|
||||||
return super(ir_config_parameter, self).write(cr, uid, ids, vals, context=context)
|
return super(IrConfigParameter, self).write(vals)
|
||||||
|
|
||||||
def unlink(self, cr, uid, ids, context=None):
|
@api.multi
|
||||||
|
def unlink(self):
|
||||||
self._get_param.clear_cache(self)
|
self._get_param.clear_cache(self)
|
||||||
return super(ir_config_parameter, self).unlink(cr, uid, ids, context=context)
|
return super(IrConfigParameter, self).unlink()
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<openerp>
|
||||||
<data>
|
|
||||||
<record model="ir.ui.view" id="view_ir_config_search">
|
<record model="ir.ui.view" id="view_ir_config_search">
|
||||||
<field name="model">ir.config_parameter</field>
|
<field name="model">ir.config_parameter</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
@@ -36,5 +35,4 @@
|
|||||||
</record>
|
</record>
|
||||||
<act_window name="System Parameters" res_model="ir.config_parameter" id="ir_config_list_action"/>
|
<act_window name="System Parameters" res_model="ir.config_parameter" id="ir_config_list_action"/>
|
||||||
<menuitem id="ir_config_menu" name="System Parameters" parent="menu_ir_property" action="ir_config_list_action"/>
|
<menuitem id="ir_config_menu" name="System Parameters" parent="menu_ir_property" action="ir_config_list_action"/>
|
||||||
</data>
|
|
||||||
</openerp>
|
</openerp>
|
||||||
|
|||||||
@@ -4,22 +4,20 @@ import logging
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import psycopg2
|
import psycopg2
|
||||||
|
import pytz
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
import pytz
|
|
||||||
|
|
||||||
import openerp
|
import odoo
|
||||||
from openerp import SUPERUSER_ID, netsvc, api
|
from odoo import api, fields, models, _
|
||||||
from openerp.osv import fields, osv
|
from odoo.exceptions import UserError
|
||||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||||
from openerp.tools.safe_eval import safe_eval as eval
|
from odoo.tools.safe_eval import safe_eval as eval
|
||||||
from openerp.tools.translate import _
|
|
||||||
from openerp.modules import load_information_from_description_file
|
|
||||||
from openerp.exceptions import UserError
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
BASE_VERSION = load_information_from_description_file('base')['version']
|
BASE_VERSION = odoo.modules.load_information_from_description_file('base')['version']
|
||||||
|
|
||||||
|
|
||||||
def str2tuple(s):
|
def str2tuple(s):
|
||||||
return eval('tuple(%s)' % (s or ''))
|
return eval('tuple(%s)' % (s or ''))
|
||||||
@@ -33,58 +31,49 @@ _intervalTypes = {
|
|||||||
'minutes': lambda interval: relativedelta(minutes=interval),
|
'minutes': lambda interval: relativedelta(minutes=interval),
|
||||||
}
|
}
|
||||||
|
|
||||||
class ir_cron(osv.osv):
|
|
||||||
|
class ir_cron(models.Model):
|
||||||
""" Model describing cron jobs (also called actions or tasks).
|
""" Model describing cron jobs (also called actions or tasks).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: perhaps in the future we could consider a flag on ir.cron jobs
|
# TODO: perhaps in the future we could consider a flag on ir.cron jobs
|
||||||
# that would cause database wake-up even if the database has not been
|
# that would cause database wake-up even if the database has not been
|
||||||
# loaded yet or was already unloaded (e.g. 'force_db_wakeup' or something)
|
# loaded yet or was already unloaded (e.g. 'force_db_wakeup' or something)
|
||||||
# See also openerp.cron
|
# See also odoo.cron
|
||||||
|
|
||||||
_name = "ir.cron"
|
_name = "ir.cron"
|
||||||
_order = 'name'
|
_order = 'name'
|
||||||
_columns = {
|
|
||||||
'name': fields.char('Name', required=True),
|
|
||||||
'user_id': fields.many2one('res.users', 'User', required=True),
|
|
||||||
'active': fields.boolean('Active'),
|
|
||||||
'interval_number': fields.integer('Interval Number',help="Repeat every x."),
|
|
||||||
'interval_type': fields.selection( [('minutes', 'Minutes'),
|
|
||||||
('hours', 'Hours'), ('work_days','Work Days'), ('days', 'Days'),('weeks', 'Weeks'), ('months', 'Months')], 'Interval Unit'),
|
|
||||||
'numbercall': fields.integer('Number of Calls', help='How many times the method is called,\na negative number indicates no limit.'),
|
|
||||||
'doall' : fields.boolean('Repeat Missed', help="Specify if missed occurrences should be executed when the server restarts."),
|
|
||||||
'nextcall' : fields.datetime('Next Execution Date', required=True, help="Next planned execution date for this job."),
|
|
||||||
'model': fields.char('Object', help="Model name on which the method to be called is located, e.g. 'res.partner'."),
|
|
||||||
'function': fields.char('Method', help="Name of the method to be called when this job is processed."),
|
|
||||||
'args': fields.text('Arguments', help="Arguments to be passed to the method, e.g. (uid,)."),
|
|
||||||
'priority': fields.integer('Priority', help='The priority of the job, as an integer: 0 means higher priority, 10 means lower priority.')
|
|
||||||
}
|
|
||||||
|
|
||||||
_defaults = {
|
name = fields.Char(required=True)
|
||||||
'nextcall' : lambda *a: time.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
user_id = fields.Many2one('res.users', string='User', default=lambda self: self.env.user, required=True)
|
||||||
'priority' : 5,
|
active = fields.Boolean(default=True)
|
||||||
'user_id' : lambda obj,cr,uid,context: uid,
|
interval_number = fields.Integer(default=1, help="Repeat every x.")
|
||||||
'interval_number' : 1,
|
interval_type = fields.Selection([('minutes', 'Minutes'),
|
||||||
'interval_type' : 'months',
|
('hours', 'Hours'),
|
||||||
'numbercall' : 1,
|
('work_days', 'Work Days'),
|
||||||
'active' : 1,
|
('days', 'Days'),
|
||||||
}
|
('weeks', 'Weeks'),
|
||||||
|
('months', 'Months')], string='Interval Unit', default='months')
|
||||||
|
numbercall = fields.Integer(string='Number of Calls', default=1, help='How many times the method is called,\na negative number indicates no limit.')
|
||||||
|
doall = fields.Boolean(string='Repeat Missed', help="Specify if missed occurrences should be executed when the server restarts.")
|
||||||
|
nextcall = fields.Datetime(string='Next Execution Date', required=True, default=lambda *a: time.strftime(DEFAULT_SERVER_DATETIME_FORMAT), help="Next planned execution date for this job.")
|
||||||
|
model = fields.Char(string='Object', help="Model name on which the method to be called is located, e.g. 'res.partner'.")
|
||||||
|
function = fields.Char(string='Method', help="Name of the method to be called when this job is processed.")
|
||||||
|
args = fields.Text(string='Arguments', help="Arguments to be passed to the method, e.g. (uid,).")
|
||||||
|
priority = fields.Integer(default=5, help='The priority of the job, as an integer: 0 means higher priority, 10 means lower priority.')
|
||||||
|
|
||||||
def _check_args(self, cr, uid, ids, context=None):
|
@api.constrains('args')
|
||||||
|
def _check_args(self):
|
||||||
try:
|
try:
|
||||||
for this in self.browse(cr, uid, ids, context):
|
for this in self:
|
||||||
str2tuple(this.args)
|
str2tuple(this.args)
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
raise Exception('Invalid arguments')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
_constraints = [
|
|
||||||
(_check_args, 'Invalid arguments', ['args']),
|
|
||||||
]
|
|
||||||
|
|
||||||
def method_direct_trigger(self, cr, uid, ids, context=None):
|
def method_direct_trigger(self, cr, uid, ids, context=None):
|
||||||
if context is None:
|
if context is None:
|
||||||
context={}
|
context = {}
|
||||||
cron_obj = self.browse(cr, uid, ids, context=context)
|
cron_obj = self.browse(cr, uid, ids, context=context)
|
||||||
for cron in cron_obj:
|
for cron in cron_obj:
|
||||||
self._callback(cr, uid, cron_obj.model, cron_obj.function, cron_obj.args, cron_obj.id)
|
self._callback(cr, uid, cron_obj.model, cron_obj.function, cron_obj.args, cron_obj.id)
|
||||||
@@ -117,20 +106,20 @@ class ir_cron(osv.osv):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
args = str2tuple(args)
|
args = str2tuple(args)
|
||||||
openerp.modules.registry.RegistryManager.check_registry_signaling(cr.dbname)
|
odoo.modules.registry.RegistryManager.check_registry_signaling(cr.dbname)
|
||||||
registry = openerp.registry(cr.dbname)
|
registry = odoo.registry(cr.dbname)
|
||||||
if model_name in registry:
|
if model_name in registry:
|
||||||
model = registry[model_name]
|
model = registry[model_name]
|
||||||
if hasattr(model, method_name):
|
if hasattr(model, method_name):
|
||||||
log_depth = (None if _logger.isEnabledFor(logging.DEBUG) else 1)
|
log_depth = (None if _logger.isEnabledFor(logging.DEBUG) else 1)
|
||||||
netsvc.log(_logger, logging.DEBUG, 'cron.object.execute', (cr.dbname,uid,'*',model_name,method_name)+tuple(args), depth=log_depth)
|
odoo.netsvc.log(_logger, logging.DEBUG, 'cron.object.execute', (cr.dbname, uid, '*', model_name, method_name)+tuple(args), depth=log_depth)
|
||||||
if _logger.isEnabledFor(logging.DEBUG):
|
if _logger.isEnabledFor(logging.DEBUG):
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
getattr(model, method_name)(cr, uid, *args)
|
getattr(model, method_name)(cr, uid, *args)
|
||||||
if _logger.isEnabledFor(logging.DEBUG):
|
if _logger.isEnabledFor(logging.DEBUG):
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
_logger.debug('%.3fs (%s, %s)' % (end_time - start_time, model_name, method_name))
|
_logger.debug('%.3fs (%s, %s)' % (end_time - start_time, model_name, method_name))
|
||||||
openerp.modules.registry.RegistryManager.signal_caches_change(cr.dbname)
|
odoo.modules.registry.RegistryManager.signal_caches_change(cr.dbname)
|
||||||
else:
|
else:
|
||||||
msg = "Method `%s.%s` does not exist." % (model_name, method_name)
|
msg = "Method `%s.%s` does not exist." % (model_name, method_name)
|
||||||
_logger.warning(msg)
|
_logger.warning(msg)
|
||||||
@@ -150,8 +139,8 @@ class ir_cron(osv.osv):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
with api.Environment.manage():
|
with api.Environment.manage():
|
||||||
now = fields.datetime.context_timestamp(job_cr, job['user_id'], datetime.now())
|
now = odoo.osv.fields.datetime.context_timestamp(job_cr, job['user_id'], datetime.now())
|
||||||
nextcall = fields.datetime.context_timestamp(job_cr, job['user_id'], datetime.strptime(job['nextcall'], DEFAULT_SERVER_DATETIME_FORMAT))
|
nextcall = odoo.osv.fields.datetime.context_timestamp(job_cr, job['user_id'], datetime.strptime(job['nextcall'], DEFAULT_SERVER_DATETIME_FORMAT))
|
||||||
numbercall = job['numbercall']
|
numbercall = job['numbercall']
|
||||||
|
|
||||||
ok = False
|
ok = False
|
||||||
@@ -167,8 +156,8 @@ class ir_cron(osv.osv):
|
|||||||
if not numbercall:
|
if not numbercall:
|
||||||
addsql = ', active=False'
|
addsql = ', active=False'
|
||||||
cron_cr.execute("UPDATE ir_cron SET nextcall=%s, numbercall=%s"+addsql+" WHERE id=%s",
|
cron_cr.execute("UPDATE ir_cron SET nextcall=%s, numbercall=%s"+addsql+" WHERE id=%s",
|
||||||
(nextcall.astimezone(pytz.UTC).strftime(DEFAULT_SERVER_DATETIME_FORMAT), numbercall, job['id']))
|
(nextcall.astimezone(pytz.UTC).strftime(DEFAULT_SERVER_DATETIME_FORMAT), numbercall, job['id']))
|
||||||
self.invalidate_cache(job_cr, SUPERUSER_ID)
|
self.invalidate_cache(job_cr, odoo.SUPERUSER_ID)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
job_cr.commit()
|
job_cr.commit()
|
||||||
@@ -186,7 +175,7 @@ class ir_cron(osv.osv):
|
|||||||
|
|
||||||
If a job was processed, returns True, otherwise returns False.
|
If a job was processed, returns True, otherwise returns False.
|
||||||
"""
|
"""
|
||||||
db = openerp.sql_db.db_connect(db_name)
|
db = odoo.sql_db.db_connect(db_name)
|
||||||
threading.current_thread().dbname = db_name
|
threading.current_thread().dbname = db_name
|
||||||
cr = db.cursor()
|
cr = db.cursor()
|
||||||
jobs = []
|
jobs = []
|
||||||
@@ -237,7 +226,7 @@ class ir_cron(osv.osv):
|
|||||||
_logger.debug('Starting job `%s`.', job['name'])
|
_logger.debug('Starting job `%s`.', job['name'])
|
||||||
job_cr = db.cursor()
|
job_cr = db.cursor()
|
||||||
try:
|
try:
|
||||||
registry = openerp.registry(db_name)
|
registry = odoo.registry(db_name)
|
||||||
registry[cls._name]._process_job(job_cr, job, lock_cr)
|
registry[cls._name]._process_job(job_cr, job, lock_cr)
|
||||||
except Exception:
|
except Exception:
|
||||||
_logger.exception('Unexpected exception while processing cron job %r', job)
|
_logger.exception('Unexpected exception while processing cron job %r', job)
|
||||||
@@ -256,48 +245,49 @@ class ir_cron(osv.osv):
|
|||||||
# we're exiting due to an exception while acquiring the lock
|
# we're exiting due to an exception while acquiring the lock
|
||||||
lock_cr.close()
|
lock_cr.close()
|
||||||
|
|
||||||
if hasattr(threading.current_thread(), 'dbname'): # cron job could have removed it as side-effect
|
if hasattr(threading.current_thread(), 'dbname'): # cron job could have removed it as side-effect
|
||||||
del threading.current_thread().dbname
|
del threading.current_thread().dbname
|
||||||
|
|
||||||
def _try_lock(self, cr, uid, ids, context=None):
|
def _try_lock(self):
|
||||||
"""Try to grab a dummy exclusive write-lock to the rows with the given ids,
|
"""Try to grab a dummy exclusive write-lock to the rows with the given ids,
|
||||||
to make sure a following write() or unlink() will not block due
|
to make sure a following write() or unlink() will not block due
|
||||||
to a process currently executing those cron tasks"""
|
to a process currently executing those cron tasks"""
|
||||||
try:
|
try:
|
||||||
cr.execute("""SELECT id FROM "%s" WHERE id IN %%s FOR UPDATE NOWAIT""" % self._table,
|
self.env.cr.execute("""SELECT id FROM "%s" WHERE id IN %%s FOR UPDATE NOWAIT""" % self._table,
|
||||||
(tuple(ids),), log_exceptions=False)
|
(tuple(self.ids),), log_exceptions=False)
|
||||||
except psycopg2.OperationalError:
|
except psycopg2.OperationalError:
|
||||||
cr.rollback() # early rollback to allow translations to work for the user feedback
|
self.env.cr.rollback() # early rollback to allow translations to work for the user feedback
|
||||||
raise UserError(_("Record cannot be modified right now: "
|
raise UserError(_("Record cannot be modified right now: "
|
||||||
"This cron task is currently being executed and may not be modified "
|
"This cron task is currently being executed and may not be modified "
|
||||||
"Please try again in a few minutes"))
|
"Please try again in a few minutes"))
|
||||||
|
|
||||||
def create(self, cr, uid, vals, context=None):
|
@api.model
|
||||||
res = super(ir_cron, self).create(cr, uid, vals, context=context)
|
def create(self, vals):
|
||||||
return res
|
return super(ir_cron, self).create(vals)
|
||||||
|
|
||||||
def write(self, cr, uid, ids, vals, context=None):
|
@api.multi
|
||||||
self._try_lock(cr, uid, ids, context)
|
def write(self, vals):
|
||||||
res = super(ir_cron, self).write(cr, uid, ids, vals, context=context)
|
self._try_lock()
|
||||||
return res
|
return super(ir_cron, self).write(vals)
|
||||||
|
|
||||||
def unlink(self, cr, uid, ids, context=None):
|
@api.multi
|
||||||
self._try_lock(cr, uid, ids, context)
|
def unlink(self):
|
||||||
res = super(ir_cron, self).unlink(cr, uid, ids, context=context)
|
self._try_lock()
|
||||||
return res
|
return super(ir_cron, self).unlink()
|
||||||
|
|
||||||
def try_write(self, cr, uid, ids, values, context=None):
|
@api.multi
|
||||||
|
def try_write(self, values):
|
||||||
try:
|
try:
|
||||||
with cr.savepoint():
|
with self.env.cr.savepoint():
|
||||||
cr.execute("""SELECT id FROM "%s" WHERE id IN %%s FOR UPDATE NOWAIT""" % self._table,
|
self.env.cr.execute("""SELECT id FROM "%s" WHERE id IN %%s FOR UPDATE NOWAIT""" % self._table,
|
||||||
(tuple(ids),), log_exceptions=False)
|
(tuple(self.ids),), log_exceptions=False)
|
||||||
except psycopg2.OperationalError:
|
except psycopg2.OperationalError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
return super(ir_cron, self).write(cr, uid, ids, values, context=context)
|
return super(ir_cron, self).write(values)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def toggle(self, cr, uid, ids, model, domain, context=None):
|
@api.model
|
||||||
active = bool(self.pool[model].search_count(cr, uid, domain, context=context))
|
def toggle(self, model, domain):
|
||||||
|
active = bool(self.env[model].search_count(domain))
|
||||||
return self.try_write(cr, uid, ids, {'active': active}, context=context)
|
return self.try_write({'active': active})
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
<!-- ir.cron -->
|
<!-- ir.cron -->
|
||||||
<record id="ir_cron_view" model="ir.ui.view">
|
<record id="ir_cron_view" model="ir.ui.view">
|
||||||
<field name="model">ir.cron</field>
|
<field name="model">ir.cron</field>
|
||||||
@@ -90,5 +89,4 @@
|
|||||||
</record>
|
</record>
|
||||||
<menuitem id="menu_ir_cron_act" action="ir_cron_act" parent="base.menu_automation"/>
|
<menuitem id="menu_ir_cron_act" action="ir_cron_act" parent="base.menu_automation"/>
|
||||||
|
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
@@ -1,24 +1,21 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from openerp.osv import fields,osv
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
class ir_exports(osv.osv):
|
class IrExports(models.Model):
|
||||||
_name = "ir.exports"
|
_name = "ir.exports"
|
||||||
_order = 'name'
|
_order = 'name'
|
||||||
_columns = {
|
|
||||||
'name': fields.char('Export Name'),
|
name = fields.Char(string='Export Name')
|
||||||
'resource': fields.char('Resource', select=True),
|
resource = fields.Char(select=True)
|
||||||
'export_fields': fields.one2many('ir.exports.line', 'export_id',
|
export_fields = fields.One2many('ir.exports.line', 'export_id', string='Export ID', copy=True)
|
||||||
'Export ID', copy=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ir_exports_line(osv.osv):
|
class IrExportsLine(models.Model):
|
||||||
_name = 'ir.exports.line'
|
_name = 'ir.exports.line'
|
||||||
_order = 'id'
|
_order = 'id'
|
||||||
_columns = {
|
|
||||||
'name': fields.char('Field Name'),
|
name = fields.Char(string='Field Name')
|
||||||
'export_id': fields.many2one('ir.exports', 'Export', select=True, ondelete='cascade'),
|
export_id = fields.Many2one('ir.exports', string='Export', select=True, ondelete='cascade')
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import datetime
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import itertools
|
import itertools
|
||||||
import time
|
import time
|
||||||
@@ -7,8 +8,8 @@ import time
|
|||||||
import psycopg2
|
import psycopg2
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from openerp import models, fields, api, _
|
from odoo import api, fields, models, _
|
||||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, ustr
|
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, ustr
|
||||||
|
|
||||||
REFERENCING_FIELDS = {None, 'id', '.id'}
|
REFERENCING_FIELDS = {None, 'id', '.id'}
|
||||||
def only_ref_fields(record):
|
def only_ref_fields(record):
|
||||||
@@ -33,7 +34,7 @@ class ImportWarning(Warning):
|
|||||||
class ConversionNotFound(ValueError): pass
|
class ConversionNotFound(ValueError): pass
|
||||||
|
|
||||||
|
|
||||||
class ir_fields_converter(models.AbstractModel):
|
class IrFieldsConverter(models.AbstractModel):
|
||||||
_name = 'ir.fields.converter'
|
_name = 'ir.fields.converter'
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
@@ -389,8 +390,7 @@ class ir_fields_converter(models.AbstractModel):
|
|||||||
|
|
||||||
subfield, w1 = self._referencing_subfield(record)
|
subfield, w1 = self._referencing_subfield(record)
|
||||||
|
|
||||||
reference = record[subfield]
|
id, _, w2 = self.db_id_for(model, field, subfield, record[subfield])
|
||||||
id, _, w2 = self.db_id_for(model, field, subfield, reference)
|
|
||||||
return id, w1 + w2
|
return id, w1 + w2
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
@@ -435,8 +435,7 @@ class ir_fields_converter(models.AbstractModel):
|
|||||||
if refs:
|
if refs:
|
||||||
subfield, w1 = self._referencing_subfield(refs)
|
subfield, w1 = self._referencing_subfield(refs)
|
||||||
warnings.extend(w1)
|
warnings.extend(w1)
|
||||||
reference = record[subfield]
|
id, _, w2 = self.db_id_for(model, field, subfield, record[subfield])
|
||||||
id, _, w2 = self.db_id_for(model, field, subfield, reference)
|
|
||||||
warnings.extend(w2)
|
warnings.extend(w2)
|
||||||
|
|
||||||
writable = convert(exclude_ref_fields(record), log)
|
writable = convert(exclude_ref_fields(record), log)
|
||||||
|
|||||||
@@ -1,33 +1,51 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from openerp import exceptions
|
from odoo import exceptions
|
||||||
from openerp.osv import osv, fields
|
from odoo import api, fields, models, _
|
||||||
from openerp.tools.translate import _
|
|
||||||
|
|
||||||
class ir_filters(osv.osv):
|
|
||||||
|
class IrFilters(models.Model):
|
||||||
_name = 'ir.filters'
|
_name = 'ir.filters'
|
||||||
_description = 'Filters'
|
_description = 'Filters'
|
||||||
|
_order = 'model_id, name, id desc'
|
||||||
|
|
||||||
def _list_all_models(self, cr, uid, context=None):
|
def _list_all_models(self):
|
||||||
cr.execute("SELECT model, name FROM ir_model ORDER BY name")
|
self.env.cr.execute("SELECT model, name FROM ir_model ORDER BY name")
|
||||||
return cr.fetchall()
|
return self.env.cr.fetchall()
|
||||||
|
|
||||||
def copy(self, cr, uid, id, default=None, context=None):
|
name = fields.Char(string='Filter Name', translate=True, required=True)
|
||||||
name = self.read(cr, uid, [id], ['name'])[0]['name']
|
user_id = fields.Many2one('res.users', string='User', ondelete='cascade', default=lambda self: self._uid,
|
||||||
default.update({'name':_('%s (copy)') % name})
|
help="The user this filter is private to. When left empty the filter is public "
|
||||||
return super(ir_filters, self).copy(cr, uid, id, default, context)
|
"and available to all users.")
|
||||||
|
domain = fields.Text(default='[]', required=True)
|
||||||
|
context = fields.Text(default='{}', required=True)
|
||||||
|
sort = fields.Text(default='[]', required=True)
|
||||||
|
model_id = fields.Selection(selection='_list_all_models', string='Model', required=True)
|
||||||
|
is_default = fields.Boolean(string='Default filter')
|
||||||
|
action_id = fields.Many2one('ir.actions.actions', string='Action', ondelete='cascade',
|
||||||
|
help="The menu action this filter applies to. "
|
||||||
|
"When left empty the filter applies to all menus "
|
||||||
|
"for this model.")
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
|
||||||
def _get_action_domain(self, cr, uid, action_id=None):
|
@api.multi
|
||||||
|
def copy(self, default=None):
|
||||||
|
self.ensure_one()
|
||||||
|
default.update({'name': _('%s (copy)') % self.name})
|
||||||
|
return super(IrFilters, self).copy(default)
|
||||||
|
|
||||||
|
def _get_action_domain(self, action_id=None):
|
||||||
"""Return a domain component for matching filters that are visible in the
|
"""Return a domain component for matching filters that are visible in the
|
||||||
same context (menu/view) as the given action."""
|
same context (menu/view) as the given action."""
|
||||||
if action_id:
|
if action_id:
|
||||||
# filters specific to this menu + global ones
|
# filters specific to this menu + global ones
|
||||||
return [('action_id', 'in' , [action_id, False])]
|
return [('action_id', 'in', [action_id, False])]
|
||||||
# only global ones
|
# only global ones
|
||||||
return [('action_id', '=', False)]
|
return [('action_id', '=', False)]
|
||||||
|
|
||||||
def get_filters(self, cr, uid, model, action_id=None, context=None):
|
@api.model
|
||||||
|
def get_filters(self, model, action_id=None):
|
||||||
"""Obtain the list of filters available for the user on the given model.
|
"""Obtain the list of filters available for the user on the given model.
|
||||||
|
|
||||||
:param action_id: optional ID of action to restrict filters to this action
|
:param action_id: optional ID of action to restrict filters to this action
|
||||||
@@ -40,15 +58,12 @@ class ir_filters(osv.osv):
|
|||||||
"""
|
"""
|
||||||
# available filters: private filters (user_id=uid) and public filters (uid=NULL),
|
# available filters: private filters (user_id=uid) and public filters (uid=NULL),
|
||||||
# and filters for the action (action_id=action_id) or global (action_id=NULL)
|
# and filters for the action (action_id=action_id) or global (action_id=NULL)
|
||||||
context = self.pool['res.users'].context_get(cr, uid)
|
action_domain = self._get_action_domain(action_id)
|
||||||
action_domain = self._get_action_domain(cr, uid, action_id)
|
filter_ids = self.search(action_domain + [('model_id', '=', model), ('user_id', 'in', [self._uid, False])])
|
||||||
filter_ids = self.search(cr, uid, action_domain +
|
my_filters = filter_ids.with_context(self.env.user.context_get()).read(['name', 'is_default', 'domain', 'context', 'user_id', 'sort'])
|
||||||
[('model_id','=',model),('user_id','in',[uid, False])])
|
|
||||||
my_filters = self.read(cr, uid, filter_ids,
|
|
||||||
['name', 'is_default', 'domain', 'context', 'user_id', 'sort'], context=context)
|
|
||||||
return my_filters
|
return my_filters
|
||||||
|
|
||||||
def _check_global_default(self, cr, uid, vals, matching_filters, context=None):
|
def _check_global_default(self, vals, matching_filters):
|
||||||
""" _check_global_default(cursor, UID, dict, list(dict), dict) -> None
|
""" _check_global_default(cursor, UID, dict, list(dict), dict) -> None
|
||||||
|
|
||||||
Checks if there is a global default for the model_id requested.
|
Checks if there is a global default for the model_id requested.
|
||||||
@@ -64,96 +79,66 @@ class ir_filters(osv.osv):
|
|||||||
:raises openerp.exceptions.Warning: if there is an existing default and
|
:raises openerp.exceptions.Warning: if there is an existing default and
|
||||||
we're not updating it
|
we're not updating it
|
||||||
"""
|
"""
|
||||||
action_domain = self._get_action_domain(cr, uid, vals.get('action_id'))
|
action_domain = self._get_action_domain(vals.get('action_id'))
|
||||||
existing_default = self.search(cr, uid, action_domain + [
|
existing_default = self.search(action_domain + [
|
||||||
('model_id', '=', vals['model_id']),
|
('model_id', '=', vals['model_id']),
|
||||||
('user_id', '=', False),
|
('user_id', '=', False),
|
||||||
('is_default', '=', True)], context=context)
|
('is_default', '=', True)])
|
||||||
|
|
||||||
if not existing_default: return
|
if not existing_default:
|
||||||
if matching_filters and \
|
return
|
||||||
(matching_filters[0]['id'] == existing_default[0]):
|
if matching_filters and (matching_filters[0]['id'] == existing_default.id):
|
||||||
return
|
return
|
||||||
|
|
||||||
raise exceptions.Warning(
|
raise exceptions.Warning(_("There is already a shared filter set as default for %(model)s, delete or change it before setting a new default") % {'model': vals.get('model_id')})
|
||||||
_("There is already a shared filter set as default for %(model)s, delete or change it before setting a new default") % {
|
|
||||||
'model': vals['model_id']
|
|
||||||
})
|
|
||||||
|
|
||||||
def create_or_replace(self, cr, uid, vals, context=None):
|
@api.model
|
||||||
lower_name = vals['name'].lower()
|
def create_or_replace(self, vals):
|
||||||
action_id = vals.get('action_id')
|
action_id = vals.get('action_id')
|
||||||
current_filters = self.get_filters(cr, uid, vals['model_id'], action_id)
|
current_filters = self.get_filters(vals['model_id'], action_id)
|
||||||
matching_filters = [f for f in current_filters
|
matching_filters = [f for f in current_filters
|
||||||
if f['name'].lower() == lower_name
|
if f['name'].lower() == vals['name'].lower()
|
||||||
# next line looks for matching user_ids (specific or global), i.e.
|
# next line looks for matching user_ids (specific or global), i.e.
|
||||||
# f.user_id is False and vals.user_id is False or missing,
|
# f.user_id is False and vals.user_id is False or missing,
|
||||||
# or f.user_id.id == vals.user_id
|
# or f.user_id.id == vals.user_id
|
||||||
if (f['user_id'] and f['user_id'][0]) == vals.get('user_id', False)]
|
if (f['user_id'] and f['user_id'][0]) == vals.get('user_id')]
|
||||||
|
|
||||||
if vals.get('is_default'):
|
if vals.get('is_default'):
|
||||||
if vals.get('user_id'):
|
if vals.get('user_id'):
|
||||||
# Setting new default: any other default that belongs to the user
|
# Setting new default: any other default that belongs to the user
|
||||||
# should be turned off
|
# should be turned off
|
||||||
action_domain = self._get_action_domain(cr, uid, action_id)
|
action_domain = self._get_action_domain(action_id)
|
||||||
act_ids = self.search(cr, uid, action_domain + [
|
act_ids = self.search(action_domain + [
|
||||||
('model_id', '=', vals['model_id']),
|
('model_id', '=', vals['model_id']),
|
||||||
('user_id', '=', vals['user_id']),
|
('user_id', '=', vals['user_id']),
|
||||||
('is_default', '=', True),
|
('is_default', '=', True),
|
||||||
], context=context)
|
])
|
||||||
if act_ids:
|
if act_ids:
|
||||||
self.write(cr, uid, act_ids, {'is_default': False}, context=context)
|
act_ids.write({'is_default': False})
|
||||||
else:
|
else:
|
||||||
self._check_global_default(
|
self._check_global_default(vals, matching_filters)
|
||||||
cr, uid, vals, matching_filters, context=None)
|
|
||||||
|
|
||||||
# When a filter exists for the same (name, model, user) triple, we simply
|
# When a filter exists for the same (name, model, user) triple, we simply
|
||||||
# replace its definition (considering action_id irrelevant here)
|
# replace its definition (considering action_id irrelevant here)
|
||||||
if matching_filters:
|
if matching_filters:
|
||||||
self.write(cr, uid, matching_filters[0]['id'], vals, context)
|
self.browse(matching_filters[0]['id']).write(vals)
|
||||||
return matching_filters[0]['id']
|
return matching_filters[0]['id']
|
||||||
|
|
||||||
return self.create(cr, uid, vals, context)
|
return self.create(vals)
|
||||||
|
|
||||||
_sql_constraints = [
|
_sql_constraints = [
|
||||||
# Partial constraint, complemented by unique index (see below)
|
# Partial constraint, complemented by unique index (see below)
|
||||||
# Still useful to keep because it provides a proper error message when a violation
|
# Still useful to keep because it provides a proper error message when a violation
|
||||||
# occurs, as it shares the same prefix as the unique index.
|
# occurs, as it shares the same prefix as the unique index.
|
||||||
('name_model_uid_unique', 'unique (name, model_id, user_id, action_id)', 'Filter names must be unique'),
|
('name_model_uid_unique', 'unique (name, model_id, user_id, action_id)', 'Filter names must be unique'),
|
||||||
]
|
]
|
||||||
|
|
||||||
def _auto_init(self, cr, context=None):
|
def _auto_init(self):
|
||||||
result = super(ir_filters, self)._auto_init(cr, context)
|
result = super(IrFilters, self)._auto_init()
|
||||||
# Use unique index to implement unique constraint on the lowercase name (not possible using a constraint)
|
# Use unique index to implement unique constraint on the lowercase name (not possible using a constraint)
|
||||||
cr.execute("DROP INDEX IF EXISTS ir_filters_name_model_uid_unique_index") # drop old index w/o action
|
self.env.cr.execute("DROP INDEX IF EXISTS ir_filters_name_model_uid_unique_index") # drop old index w/o action
|
||||||
cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = 'ir_filters_name_model_uid_unique_action_index'")
|
self.env.cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = 'ir_filters_name_model_uid_unique_action_index'")
|
||||||
if not cr.fetchone():
|
if not self.env.cr.fetchone():
|
||||||
cr.execute("""CREATE UNIQUE INDEX "ir_filters_name_model_uid_unique_action_index" ON ir_filters
|
self.env.cr.execute("""CREATE UNIQUE INDEX "ir_filters_name_model_uid_unique_action_index" ON ir_filters
|
||||||
(lower(name), model_id, COALESCE(user_id,-1), COALESCE(action_id,-1))""")
|
(lower(name), model_id, COALESCE(user_id,-1), COALESCE(action_id,-1))""")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
_columns = {
|
|
||||||
'name': fields.char('Filter Name', translate=True, required=True),
|
|
||||||
'user_id': fields.many2one('res.users', 'User', ondelete='cascade',
|
|
||||||
help="The user this filter is private to. When left empty the filter is public "
|
|
||||||
"and available to all users."),
|
|
||||||
'domain': fields.text('Domain', required=True),
|
|
||||||
'context': fields.text('Context', required=True),
|
|
||||||
'sort': fields.text('Sort', required=True),
|
|
||||||
'model_id': fields.selection(_list_all_models, 'Model', required=True),
|
|
||||||
'is_default': fields.boolean('Default filter'),
|
|
||||||
'action_id': fields.many2one('ir.actions.actions', 'Action', ondelete='cascade',
|
|
||||||
help="The menu action this filter applies to. "
|
|
||||||
"When left empty the filter applies to all menus "
|
|
||||||
"for this model."),
|
|
||||||
'active': fields.boolean('Active')
|
|
||||||
}
|
|
||||||
_defaults = {
|
|
||||||
'domain': '[]',
|
|
||||||
'context':'{}',
|
|
||||||
'sort': '[]',
|
|
||||||
'user_id': lambda self,cr,uid,context=None: uid,
|
|
||||||
'is_default': False,
|
|
||||||
'active': True
|
|
||||||
}
|
|
||||||
_order = 'model_id, name, id desc'
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data noupdate="1">
|
<data noupdate="1">
|
||||||
<!-- Restrict modifications on ir.filters to owner only -->
|
<!-- Restrict modifications on ir.filters to owner only -->
|
||||||
<record id="ir_filters_rule" model="ir.rule">
|
<record id="ir_filters_rule" model="ir.rule">
|
||||||
@@ -73,4 +73,4 @@
|
|||||||
<menuitem parent="base.next_id_2" name="User-defined Filters"
|
<menuitem parent="base.next_id_2" name="User-defined Filters"
|
||||||
id="menu_ir_filters" action="actions_ir_filters_view" sequence="5"/>
|
id="menu_ir_filters" action="actions_ir_filters_view" sequence="5"/>
|
||||||
</data>
|
</data>
|
||||||
</openerp>
|
</odoo>
|
||||||
|
|||||||
@@ -18,18 +18,17 @@ import werkzeug.routing
|
|||||||
import werkzeug.urls
|
import werkzeug.urls
|
||||||
import werkzeug.utils
|
import werkzeug.utils
|
||||||
|
|
||||||
import openerp
|
import odoo
|
||||||
import openerp.exceptions
|
from odoo import models, tools
|
||||||
import openerp.models
|
from odoo import http
|
||||||
from openerp import http
|
from odoo.http import request, STATIC_CACHE
|
||||||
from openerp.http import request, STATIC_CACHE
|
from odoo.modules.module import get_resource_path, get_module_path
|
||||||
from openerp.modules.module import get_resource_path, get_module_path
|
|
||||||
from openerp.osv import osv, orm
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
UID_PLACEHOLDER = object()
|
UID_PLACEHOLDER = object()
|
||||||
|
|
||||||
|
|
||||||
class ModelConverter(werkzeug.routing.BaseConverter):
|
class ModelConverter(werkzeug.routing.BaseConverter):
|
||||||
|
|
||||||
def __init__(self, url_map, model=False):
|
def __init__(self, url_map, model=False):
|
||||||
@@ -45,6 +44,7 @@ class ModelConverter(werkzeug.routing.BaseConverter):
|
|||||||
def to_url(self, value):
|
def to_url(self, value):
|
||||||
return value.id
|
return value.id
|
||||||
|
|
||||||
|
|
||||||
class ModelsConverter(werkzeug.routing.BaseConverter):
|
class ModelsConverter(werkzeug.routing.BaseConverter):
|
||||||
|
|
||||||
def __init__(self, url_map, model=False):
|
def __init__(self, url_map, model=False):
|
||||||
@@ -59,12 +59,13 @@ class ModelsConverter(werkzeug.routing.BaseConverter):
|
|||||||
def to_url(self, value):
|
def to_url(self, value):
|
||||||
return ",".join(i.id for i in value)
|
return ",".join(i.id for i in value)
|
||||||
|
|
||||||
|
|
||||||
class SignedIntConverter(werkzeug.routing.NumberConverter):
|
class SignedIntConverter(werkzeug.routing.NumberConverter):
|
||||||
regex = r'-?\d+'
|
regex = r'-?\d+'
|
||||||
num_convert = int
|
num_convert = int
|
||||||
|
|
||||||
|
|
||||||
class ir_http(osv.AbstractModel):
|
class IrHttp(models.AbstractModel):
|
||||||
_name = 'ir.http'
|
_name = 'ir.http'
|
||||||
_description = "HTTP routing"
|
_description = "HTTP routing"
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ class ir_http(osv.AbstractModel):
|
|||||||
|
|
||||||
def _auth_method_public(self):
|
def _auth_method_public(self):
|
||||||
if not request.session.uid:
|
if not request.session.uid:
|
||||||
dummy, request.uid = self.pool['ir.model.data'].get_object_reference(request.cr, openerp.SUPERUSER_ID, 'base', 'public_user')
|
dummy, request.uid = self.env['ir.model.data'].sudo().get_object_reference(request.cr, 'base', 'public_user')
|
||||||
else:
|
else:
|
||||||
request.uid = request.session.uid
|
request.uid = request.session.uid
|
||||||
|
|
||||||
@@ -96,22 +97,22 @@ class ir_http(osv.AbstractModel):
|
|||||||
# what if error in security.check()
|
# what if error in security.check()
|
||||||
# -> res_users.check()
|
# -> res_users.check()
|
||||||
# -> res_users.check_credentials()
|
# -> res_users.check_credentials()
|
||||||
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
|
except (odoo.exceptions.AccessDenied, http.SessionExpiredException):
|
||||||
# All other exceptions mean undetermined status (e.g. connection pool full),
|
# All other exceptions mean undetermined status (e.g. connection pool full),
|
||||||
# let them bubble up
|
# let them bubble up
|
||||||
request.session.logout(keep_db=True)
|
request.session.logout(keep_db=True)
|
||||||
if request.uid is None:
|
if request.uid is None:
|
||||||
getattr(self, "_auth_method_%s" % auth_method)()
|
getattr(self, "_auth_method_%s" % auth_method)()
|
||||||
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException, werkzeug.exceptions.HTTPException):
|
except (odoo.exceptions.AccessDenied, http.SessionExpiredException, werkzeug.exceptions.HTTPException):
|
||||||
raise
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
_logger.info("Exception during request Authentication.", exc_info=True)
|
_logger.info("Exception during request Authentication.", exc_info=True)
|
||||||
raise openerp.exceptions.AccessDenied()
|
raise odoo.exceptions.AccessDenied()
|
||||||
return auth_method
|
return auth_method
|
||||||
|
|
||||||
def _serve_attachment(self):
|
def _serve_attachment(self):
|
||||||
domain = [('type', '=', 'binary'), ('url', '=', request.httprequest.path)]
|
domain = [('type', '=', 'binary'), ('url', '=', request.httprequest.path)]
|
||||||
attach = self.pool['ir.attachment'].search_read(request.cr, openerp.SUPERUSER_ID, domain, ['__last_update', 'datas', 'name', 'mimetype', 'checksum'], context=request.context)
|
attach = self.pool['ir.attachment'].search_read(request.cr, odoo.SUPERUSER_ID, domain, ['__last_update', 'datas', 'name', 'mimetype', 'checksum'], context=request.context)
|
||||||
if attach:
|
if attach:
|
||||||
wdate = attach[0]['__last_update']
|
wdate = attach[0]['__last_update']
|
||||||
datas = attach[0]['datas'] or ''
|
datas = attach[0]['datas'] or ''
|
||||||
@@ -123,7 +124,7 @@ class ir_http(osv.AbstractModel):
|
|||||||
return werkzeug.utils.redirect(name, 301)
|
return werkzeug.utils.redirect(name, 301)
|
||||||
|
|
||||||
response = werkzeug.wrappers.Response()
|
response = werkzeug.wrappers.Response()
|
||||||
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
|
server_format = tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
|
||||||
try:
|
try:
|
||||||
response.last_modified = datetime.datetime.strptime(wdate, server_format + '.%f')
|
response.last_modified = datetime.datetime.strptime(wdate, server_format + '.%f')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -151,11 +152,11 @@ class ir_http(osv.AbstractModel):
|
|||||||
return attach
|
return attach
|
||||||
|
|
||||||
# Don't handle exception but use werkeug debugger if server in --dev mode
|
# Don't handle exception but use werkeug debugger if server in --dev mode
|
||||||
if openerp.tools.config['dev_mode']:
|
if tools.config['dev_mode']:
|
||||||
raise
|
raise
|
||||||
try:
|
try:
|
||||||
return request._handle_exception(exception)
|
return request._handle_exception(exception)
|
||||||
except openerp.exceptions.AccessDenied:
|
except odoo.exceptions.AccessDenied:
|
||||||
return werkzeug.exceptions.Forbidden()
|
return werkzeug.exceptions.Forbidden()
|
||||||
|
|
||||||
def _dispatch(self):
|
def _dispatch(self):
|
||||||
@@ -190,26 +191,26 @@ class ir_http(osv.AbstractModel):
|
|||||||
def _postprocess_args(self, arguments, rule):
|
def _postprocess_args(self, arguments, rule):
|
||||||
""" post process arg to set uid on browse records """
|
""" post process arg to set uid on browse records """
|
||||||
for name, arg in arguments.items():
|
for name, arg in arguments.items():
|
||||||
if isinstance(arg, orm.browse_record) and arg._uid is UID_PLACEHOLDER:
|
if isinstance(arg, odoo.osv.orm.browse_record) and arg._uid is UID_PLACEHOLDER:
|
||||||
arguments[name] = arg.sudo(request.uid)
|
arguments[name] = arg.sudo(request.uid)
|
||||||
try:
|
try:
|
||||||
arg.exists()
|
arg.exists()
|
||||||
except openerp.models.MissingError:
|
except models.MissingError:
|
||||||
return self._handle_exception(werkzeug.exceptions.NotFound())
|
return self._handle_exception(werkzeug.exceptions.NotFound())
|
||||||
|
|
||||||
def routing_map(self):
|
def routing_map(self):
|
||||||
if not hasattr(self, '_routing_map'):
|
if not hasattr(self, '_routing_map'):
|
||||||
_logger.info("Generating routing map")
|
_logger.info("Generating routing map")
|
||||||
installed = request.registry._init_modules - {'web'}
|
installed = request.registry._init_modules - {'web'}
|
||||||
if openerp.tools.config['test_enable']:
|
if tools.config['test_enable']:
|
||||||
installed.add(openerp.modules.module.current_test)
|
installed.add(odoo.modules.module.current_test)
|
||||||
mods = [''] + openerp.conf.server_wide_modules + sorted(installed)
|
mods = [''] + odoo.conf.server_wide_modules + sorted(installed)
|
||||||
self._routing_map = http.routing_map(mods, False, converters=self._get_converters())
|
self._routing_map = http.routing_map(mods, False, converters=self._get_converters())
|
||||||
|
|
||||||
return self._routing_map
|
return self._routing_map
|
||||||
|
|
||||||
def content_disposition(self, filename):
|
def content_disposition(self, filename):
|
||||||
filename = openerp.tools.ustr(filename)
|
filename = tools.ustr(filename)
|
||||||
escaped = urllib2.quote(filename.encode('utf8'))
|
escaped = urllib2.quote(filename.encode('utf8'))
|
||||||
browser = request.httprequest.user_agent.browser
|
browser = request.httprequest.user_agent.browser
|
||||||
version = int((request.httprequest.user_agent.version or '0').split('.')[0])
|
version = int((request.httprequest.user_agent.version or '0').split('.')[0])
|
||||||
@@ -255,7 +256,7 @@ class ir_http(osv.AbstractModel):
|
|||||||
# check read access
|
# check read access
|
||||||
try:
|
try:
|
||||||
last_update = obj['__last_update']
|
last_update = obj['__last_update']
|
||||||
except openerp.exceptions.AccessError:
|
except odoo.exceptions.AccessError:
|
||||||
return (403, [], None)
|
return (403, [], None)
|
||||||
|
|
||||||
status, headers, content = None, [], None
|
status, headers, content = None, [], None
|
||||||
|
|||||||
@@ -1,27 +1,18 @@
|
|||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
import logging
|
from odoo import fields, models
|
||||||
|
|
||||||
from openerp.osv import osv, fields
|
|
||||||
from openerp.tools.translate import _
|
|
||||||
|
|
||||||
class ir_logging(osv.Model):
|
class IrLogging(models.Model):
|
||||||
_name = 'ir.logging'
|
_name = 'ir.logging'
|
||||||
_order = 'id DESC'
|
_order = 'id DESC'
|
||||||
|
|
||||||
EXCEPTIONS_TYPE = [
|
create_date = fields.Datetime(readonly=True)
|
||||||
('client', 'Client'),
|
create_uid = fields.Integer(string='Uid', readonly=True) # Integer not m2o is intentionnal
|
||||||
('server', 'Server')
|
name = fields.Char(required=True)
|
||||||
]
|
type = fields.Selection([('client', 'Client'), ('server', 'Server')], required=True, select=True)
|
||||||
|
dbname = fields.Char(string='Database Name', select=True)
|
||||||
_columns = {
|
level = fields.Char(select=True)
|
||||||
'create_date': fields.datetime('Create Date', readonly=True),
|
message = fields.Text(required=True)
|
||||||
'create_uid': fields.integer('Uid', readonly=True), # Integer not m2o is intentionnal
|
path = fields.Char(required=True)
|
||||||
'name': fields.char('Name', required=True),
|
func = fields.Char(string='Function', required=True)
|
||||||
'type': fields.selection(EXCEPTIONS_TYPE, string='Type', required=True, select=True),
|
line = fields.Char(required=True)
|
||||||
'dbname': fields.char('Database Name', select=True),
|
|
||||||
'level': fields.char('Level', select=True),
|
|
||||||
'message': fields.text('Message', required=True),
|
|
||||||
'path': fields.char('Path', required=True),
|
|
||||||
'func': fields.char('Function', required=True),
|
|
||||||
'line': fields.char('Line', required=True),
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
<record model="ir.ui.view" id="ir_logging_form_view">
|
<record model="ir.ui.view" id="ir_logging_form_view">
|
||||||
<field name="model">ir.logging</field>
|
<field name="model">ir.logging</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
@@ -62,5 +61,4 @@
|
|||||||
<field name="search_view_id" ref="ir_logging_search_view" />
|
<field name="search_view_id" ref="ir_logging_search_view" />
|
||||||
</record>
|
</record>
|
||||||
<menuitem parent="base.next_id_9" id="ir_logging_all_menu" action="ir_logging_all_act" />
|
<menuitem parent="base.next_id_9" id="ir_logging_all_menu" action="ir_logging_all_act" />
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
@@ -1,29 +1,26 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from email.mime.text import MIMEText
|
from email import Encoders
|
||||||
from email.mime.base import MIMEBase
|
|
||||||
from email.mime.multipart import MIMEMultipart
|
|
||||||
from email.charset import Charset
|
from email.charset import Charset
|
||||||
from email.header import Header
|
from email.header import Header
|
||||||
from email.utils import formatdate, make_msgid, COMMASPACE, getaddresses, formataddr
|
from email.mime.base import MIMEBase
|
||||||
from email import Encoders
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.utils import COMMASPACE, formataddr, formatdate, getaddresses, make_msgid
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import smtplib
|
import smtplib
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from openerp import SUPERUSER_ID
|
from odoo import api, fields, models, tools, _
|
||||||
from openerp.osv import osv, fields
|
from odoo.exceptions import except_orm
|
||||||
from openerp.tools.translate import _
|
from odoo.exceptions import UserError
|
||||||
from openerp.tools import html2text
|
from odoo.tools import html2text
|
||||||
import openerp.tools as tools
|
|
||||||
from openerp.exceptions import UserError
|
|
||||||
from openerp.exceptions import except_orm
|
|
||||||
|
|
||||||
# ustr was originally from tools.misc.
|
# ustr was originally from tools.misc.
|
||||||
# it is moved to loglevels until we refactor tools.
|
# it is moved to loglevels until we refactor tools.
|
||||||
from openerp.loglevels import ustr
|
from odoo.loglevels import ustr
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
_test_logger = logging.getLogger('openerp.tests')
|
_test_logger = logging.getLogger('openerp.tests')
|
||||||
@@ -34,6 +31,7 @@ class MailDeliveryException(except_orm):
|
|||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
super(MailDeliveryException, self).__init__(name, value)
|
super(MailDeliveryException, self).__init__(name, value)
|
||||||
|
|
||||||
|
|
||||||
class WriteToLogger(object):
|
class WriteToLogger(object):
|
||||||
"""debugging helper: behave as a fd and pipe to logger at the given level"""
|
"""debugging helper: behave as a fd and pipe to logger at the given level"""
|
||||||
def __init__(self, logger, level=logging.DEBUG):
|
def __init__(self, logger, level=logging.DEBUG):
|
||||||
@@ -58,6 +56,7 @@ def try_coerce_ascii(string_utf8):
|
|||||||
return
|
return
|
||||||
return string_utf8
|
return string_utf8
|
||||||
|
|
||||||
|
|
||||||
def encode_header(header_text):
|
def encode_header(header_text):
|
||||||
"""Returns an appropriate representation of the given header value,
|
"""Returns an appropriate representation of the given header value,
|
||||||
suitable for direct assignment as a header value in an
|
suitable for direct assignment as a header value in an
|
||||||
@@ -72,7 +71,8 @@ def encode_header(header_text):
|
|||||||
that will perform the appropriate RFC2047 encoding of
|
that will perform the appropriate RFC2047 encoding of
|
||||||
non-ASCII values.
|
non-ASCII values.
|
||||||
"""
|
"""
|
||||||
if not header_text: return ""
|
if not header_text:
|
||||||
|
return ""
|
||||||
# convert anything to utf-8, suitable for testing ASCIIness, as 7-bit chars are
|
# convert anything to utf-8, suitable for testing ASCIIness, as 7-bit chars are
|
||||||
# encoded as ASCII in utf-8
|
# encoded as ASCII in utf-8
|
||||||
header_text_utf8 = tools.ustr(header_text).encode('utf-8')
|
header_text_utf8 = tools.ustr(header_text).encode('utf-8')
|
||||||
@@ -82,7 +82,8 @@ def encode_header(header_text):
|
|||||||
# that will take care of RFC2047-encoding it as
|
# that will take care of RFC2047-encoding it as
|
||||||
# 7-bit string.
|
# 7-bit string.
|
||||||
return header_text_ascii if header_text_ascii\
|
return header_text_ascii if header_text_ascii\
|
||||||
else Header(header_text_utf8, 'utf-8')
|
else Header(header_text_utf8, 'utf-8')
|
||||||
|
|
||||||
|
|
||||||
def encode_header_param(param_text):
|
def encode_header_param(param_text):
|
||||||
"""Returns an appropriate RFC2047 encoded representation of the given
|
"""Returns an appropriate RFC2047 encoded representation of the given
|
||||||
@@ -98,25 +99,29 @@ def encode_header_param(param_text):
|
|||||||
ASCII string containing the RFC2047 encoded text.
|
ASCII string containing the RFC2047 encoded text.
|
||||||
"""
|
"""
|
||||||
# For details see the encode_header() method that uses the same logic
|
# For details see the encode_header() method that uses the same logic
|
||||||
if not param_text: return ""
|
if not param_text:
|
||||||
|
return ""
|
||||||
param_text_utf8 = tools.ustr(param_text).encode('utf-8')
|
param_text_utf8 = tools.ustr(param_text).encode('utf-8')
|
||||||
param_text_ascii = try_coerce_ascii(param_text_utf8)
|
param_text_ascii = try_coerce_ascii(param_text_utf8)
|
||||||
return param_text_ascii if param_text_ascii\
|
return param_text_ascii if param_text_ascii\
|
||||||
else Charset('utf8').header_encode(param_text_utf8)
|
else Charset('utf8').header_encode(param_text_utf8)
|
||||||
|
|
||||||
# TODO master, remove me, no longer used internaly
|
# TODO master, remove me, no longer used internaly
|
||||||
name_with_email_pattern = re.compile(r'("[^<@>]+")\s*<([^ ,<@]+@[^> ,]+)>')
|
name_with_email_pattern = re.compile(r'("[^<@>]+")\s*<([^ ,<@]+@[^> ,]+)>')
|
||||||
address_pattern = re.compile(r'([^ ,<@]+@[^> ,]+)')
|
address_pattern = re.compile(r'([^ ,<@]+@[^> ,]+)')
|
||||||
|
|
||||||
|
|
||||||
def extract_rfc2822_addresses(text):
|
def extract_rfc2822_addresses(text):
|
||||||
"""Returns a list of valid RFC2822 addresses
|
"""Returns a list of valid RFC2822 addresses
|
||||||
that can be found in ``source``, ignoring
|
that can be found in ``source``, ignoring
|
||||||
malformed ones and non-ASCII ones.
|
malformed ones and non-ASCII ones.
|
||||||
"""
|
"""
|
||||||
if not text: return []
|
if not text:
|
||||||
|
return []
|
||||||
candidates = address_pattern.findall(tools.ustr(text).encode('utf-8'))
|
candidates = address_pattern.findall(tools.ustr(text).encode('utf-8'))
|
||||||
return filter(try_coerce_ascii, candidates)
|
return filter(try_coerce_ascii, candidates)
|
||||||
|
|
||||||
|
|
||||||
def encode_rfc2822_address_header(header_text):
|
def encode_rfc2822_address_header(header_text):
|
||||||
"""If ``header_text`` contains non-ASCII characters,
|
"""If ``header_text`` contains non-ASCII characters,
|
||||||
attempts to locate patterns of the form
|
attempts to locate patterns of the form
|
||||||
@@ -134,41 +139,32 @@ def encode_rfc2822_address_header(header_text):
|
|||||||
return COMMASPACE.join(map(encode_addr, addresses))
|
return COMMASPACE.join(map(encode_addr, addresses))
|
||||||
|
|
||||||
|
|
||||||
class ir_mail_server(osv.osv):
|
class IrMailServer(models.Model):
|
||||||
"""Represents an SMTP server, able to send outgoing emails, with SSL and TLS capabilities."""
|
"""Represents an SMTP server, able to send outgoing emails, with SSL and TLS capabilities."""
|
||||||
_name = "ir.mail_server"
|
_name = "ir.mail_server"
|
||||||
|
|
||||||
NO_VALID_RECIPIENT = ("At least one valid recipient address should be "
|
NO_VALID_RECIPIENT = ("At least one valid recipient address should be "
|
||||||
"specified for outgoing emails (To/Cc/Bcc)")
|
"specified for outgoing emails (To/Cc/Bcc)")
|
||||||
|
|
||||||
_columns = {
|
name = fields.Char(string='Description', required=True, index=True)
|
||||||
'name': fields.char('Description', required=True, select=True),
|
smtp_host = fields.Char(string='SMTP Server', required=True, help="Hostname or IP of SMTP server")
|
||||||
'smtp_host': fields.char('SMTP Server', required=True, help="Hostname or IP of SMTP server"),
|
smtp_port = fields.Integer(string='SMTP Port', size=5, required=True, default=25, help="SMTP Port. Usually 465 for SSL, and 25 or 587 for other cases.")
|
||||||
'smtp_port': fields.integer('SMTP Port', size=5, required=True, help="SMTP Port. Usually 465 for SSL, and 25 or 587 for other cases."),
|
smtp_user = fields.Char(string='Username', size=64, help="Optional username for SMTP authentication")
|
||||||
'smtp_user': fields.char('Username', size=64, help="Optional username for SMTP authentication"),
|
smtp_pass = fields.Char(string='Password', size=64, help="Optional password for SMTP authentication")
|
||||||
'smtp_pass': fields.char('Password', size=64, help="Optional password for SMTP authentication"),
|
smtp_encryption = fields.Selection([('none', 'None'),
|
||||||
'smtp_encryption': fields.selection([('none','None'),
|
('starttls', 'TLS (STARTTLS)'),
|
||||||
('starttls','TLS (STARTTLS)'),
|
('ssl', 'SSL/TLS')],
|
||||||
('ssl','SSL/TLS')],
|
string='Connection Security', required=True, default='none',
|
||||||
string='Connection Security', required=True,
|
help="Choose the connection encryption scheme:\n"
|
||||||
help="Choose the connection encryption scheme:\n"
|
"- None: SMTP sessions are done in cleartext.\n"
|
||||||
"- None: SMTP sessions are done in cleartext.\n"
|
"- TLS (STARTTLS): TLS encryption is requested at start of SMTP session (Recommended)\n"
|
||||||
"- TLS (STARTTLS): TLS encryption is requested at start of SMTP session (Recommended)\n"
|
"- SSL/TLS: SMTP sessions are encrypted with SSL/TLS through a dedicated port (default: 465)")
|
||||||
"- SSL/TLS: SMTP sessions are encrypted with SSL/TLS through a dedicated port (default: 465)"),
|
smtp_debug = fields.Boolean(string='Debugging', help="If enabled, the full output of SMTP sessions will "
|
||||||
'smtp_debug': fields.boolean('Debugging', help="If enabled, the full output of SMTP sessions will "
|
"be written to the server log at DEBUG level"
|
||||||
"be written to the server log at DEBUG level"
|
"(this is very verbose and may include confidential info!)")
|
||||||
"(this is very verbose and may include confidential info!)"),
|
sequence = fields.Integer(string='Priority', default=10, help="When no specific mail server is requested for a mail, the highest priority one "
|
||||||
'sequence': fields.integer('Priority', help="When no specific mail server is requested for a mail, the highest priority one "
|
"is used. Default priority is 10 (smaller number = higher priority)")
|
||||||
"is used. Default priority is 10 (smaller number = higher priority)"),
|
active = fields.Boolean(default=True)
|
||||||
'active': fields.boolean('Active')
|
|
||||||
}
|
|
||||||
|
|
||||||
_defaults = {
|
|
||||||
'smtp_port': 25,
|
|
||||||
'active': True,
|
|
||||||
'sequence': 10,
|
|
||||||
'smtp_encryption': 'none',
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# Make sure we pipe the smtplib outputs to our own DEBUG logger
|
# Make sure we pipe the smtplib outputs to our own DEBUG logger
|
||||||
@@ -176,13 +172,14 @@ class ir_mail_server(osv.osv):
|
|||||||
logpiper = WriteToLogger(_logger)
|
logpiper = WriteToLogger(_logger)
|
||||||
smtplib.stderr = logpiper
|
smtplib.stderr = logpiper
|
||||||
smtplib.stdout = logpiper
|
smtplib.stdout = logpiper
|
||||||
super(ir_mail_server, self).__init__(*args,**kwargs)
|
super(IrMailServer, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def name_get(self, cr, uid, ids, context=None):
|
@api.multi
|
||||||
return [(a["id"], "(%s)" % (a['name'])) for a in self.read(cr, uid, ids, ['name'], context=context)]
|
def name_get(self):
|
||||||
|
return [(a.id, "(%s)" % (a.name)) for a in self]
|
||||||
|
|
||||||
def test_smtp_connection(self, cr, uid, ids, context=None):
|
def test_smtp_connection(self):
|
||||||
for smtp_server in self.browse(cr, uid, ids, context=context):
|
for smtp_server in self:
|
||||||
smtp = False
|
smtp = False
|
||||||
try:
|
try:
|
||||||
smtp = self.connect(smtp_server.smtp_host, smtp_server.smtp_port, user=smtp_server.smtp_user,
|
smtp = self.connect(smtp_server.smtp_host, smtp_server.smtp_port, user=smtp_server.smtp_user,
|
||||||
@@ -192,7 +189,8 @@ class ir_mail_server(osv.osv):
|
|||||||
raise UserError(_("Connection Test Failed! Here is what we got instead:\n %s") % tools.ustr(e))
|
raise UserError(_("Connection Test Failed! Here is what we got instead:\n %s") % tools.ustr(e))
|
||||||
finally:
|
finally:
|
||||||
try:
|
try:
|
||||||
if smtp: smtp.quit()
|
if smtp:
|
||||||
|
smtp.quit()
|
||||||
except Exception:
|
except Exception:
|
||||||
# ignored, just a consequence of the previous exception
|
# ignored, just a consequence of the previous exception
|
||||||
pass
|
pass
|
||||||
@@ -214,7 +212,7 @@ class ir_mail_server(osv.osv):
|
|||||||
if encryption == 'ssl':
|
if encryption == 'ssl':
|
||||||
if not 'SMTP_SSL' in smtplib.__all__:
|
if not 'SMTP_SSL' in smtplib.__all__:
|
||||||
raise UserError(_("Your OpenERP Server does not support SMTP-over-SSL. You could use STARTTLS instead."
|
raise UserError(_("Your OpenERP Server does not support SMTP-over-SSL. You could use STARTTLS instead."
|
||||||
"If SSL is needed, an upgrade to Python 2.6 on the server-side should do the trick."))
|
"If SSL is needed, an upgrade to Python 2.6 on the server-side should do the trick."))
|
||||||
connection = smtplib.SMTP_SSL(host, port)
|
connection = smtplib.SMTP_SSL(host, port)
|
||||||
else:
|
else:
|
||||||
connection = smtplib.SMTP(host, port)
|
connection = smtplib.SMTP(host, port)
|
||||||
@@ -234,13 +232,13 @@ class ir_mail_server(osv.osv):
|
|||||||
# certain hashing schemes, like HMAC.
|
# certain hashing schemes, like HMAC.
|
||||||
# See also bug #597143 and python issue #5285
|
# See also bug #597143 and python issue #5285
|
||||||
user = tools.ustr(user).encode('utf-8')
|
user = tools.ustr(user).encode('utf-8')
|
||||||
password = tools.ustr(password).encode('utf-8')
|
password = tools.ustr(password).encode('utf-8')
|
||||||
connection.login(user, password)
|
connection.login(user, password)
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
|
def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
|
||||||
attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None,
|
attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None,
|
||||||
body_alternative=None, subtype_alternative='plain'):
|
body_alternative=None, subtype_alternative='plain'):
|
||||||
"""Constructs an RFC2822 email.message.Message object based on the keyword arguments passed, and returns it.
|
"""Constructs an RFC2822 email.message.Message object based on the keyword arguments passed, and returns it.
|
||||||
|
|
||||||
:param string email_from: sender email address
|
:param string email_from: sender email address
|
||||||
@@ -277,7 +275,7 @@ class ir_mail_server(osv.osv):
|
|||||||
# Note: we must force all strings to to 8-bit utf-8 when crafting message,
|
# Note: we must force all strings to to 8-bit utf-8 when crafting message,
|
||||||
# or use encode_header() for headers, which does it automatically.
|
# or use encode_header() for headers, which does it automatically.
|
||||||
|
|
||||||
headers = headers or {} # need valid dict later
|
headers = headers or {} # need valid dict later
|
||||||
|
|
||||||
if not email_cc: email_cc = []
|
if not email_cc: email_cc = []
|
||||||
if not email_bcc: email_bcc = []
|
if not email_bcc: email_bcc = []
|
||||||
@@ -345,7 +343,7 @@ class ir_mail_server(osv.osv):
|
|||||||
msg.attach(part)
|
msg.attach(part)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def _get_default_bounce_address(self, cr, uid, context=None):
|
def _get_default_bounce_address(self):
|
||||||
'''Compute the default bounce address.
|
'''Compute the default bounce address.
|
||||||
|
|
||||||
The default bounce address is used to set the envelop address if no
|
The default bounce address is used to set the envelop address if no
|
||||||
@@ -358,17 +356,14 @@ class ir_mail_server(osv.osv):
|
|||||||
If "mail.catchall.domain" is not set, return None.
|
If "mail.catchall.domain" is not set, return None.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
get_param = self.pool['ir.config_parameter'].get_param
|
get_param = self.env['ir.config_parameter'].sudo().get_param
|
||||||
postmaster = get_param(cr, SUPERUSER_ID, 'mail.bounce.alias',
|
postmaster = get_param('mail.bounce.alias', default='postmaster-odoo')
|
||||||
default='postmaster-odoo',
|
domain = get_param('mail.catchall.domain')
|
||||||
context=context,)
|
|
||||||
domain = get_param(cr, SUPERUSER_ID, 'mail.catchall.domain', context=context)
|
|
||||||
if postmaster and domain:
|
if postmaster and domain:
|
||||||
return '%s@%s' % (postmaster, domain)
|
return '%s@%s' % (postmaster, domain)
|
||||||
|
|
||||||
def send_email(self, cr, uid, message, mail_server_id=None, smtp_server=None, smtp_port=None,
|
def send_email(self, message, mail_server_id=None, smtp_server=None, smtp_port=None,
|
||||||
smtp_user=None, smtp_password=None, smtp_encryption=None, smtp_debug=False,
|
smtp_user=None, smtp_password=None, smtp_encryption=None, smtp_debug=False):
|
||||||
context=None):
|
|
||||||
"""Sends an email directly (no queuing).
|
"""Sends an email directly (no queuing).
|
||||||
|
|
||||||
No retries are done, the caller should handle MailDeliveryException in order to ensure that
|
No retries are done, the caller should handle MailDeliveryException in order to ensure that
|
||||||
@@ -399,7 +394,7 @@ class ir_mail_server(osv.osv):
|
|||||||
# Path (VERP) to detect no-longer valid email addresses.
|
# Path (VERP) to detect no-longer valid email addresses.
|
||||||
smtp_from = message['Return-Path']
|
smtp_from = message['Return-Path']
|
||||||
if not smtp_from:
|
if not smtp_from:
|
||||||
smtp_from = self._get_default_bounce_address(cr, uid, context=context)
|
smtp_from = self._get_default_bounce_address()
|
||||||
if not smtp_from:
|
if not smtp_from:
|
||||||
smtp_from = message['From']
|
smtp_from = message['From']
|
||||||
assert smtp_from, "The Return-Path or From header is required for any outbound email"
|
assert smtp_from, "The Return-Path or From header is required for any outbound email"
|
||||||
@@ -413,15 +408,15 @@ class ir_mail_server(osv.osv):
|
|||||||
email_to = message['To']
|
email_to = message['To']
|
||||||
email_cc = message['Cc']
|
email_cc = message['Cc']
|
||||||
email_bcc = message['Bcc']
|
email_bcc = message['Bcc']
|
||||||
|
|
||||||
smtp_to_list = filter(None, tools.flatten(map(extract_rfc2822_addresses,[email_to, email_cc, email_bcc])))
|
smtp_to_list = filter(None, tools.flatten(map(extract_rfc2822_addresses, [email_to, email_cc, email_bcc])))
|
||||||
assert smtp_to_list, self.NO_VALID_RECIPIENT
|
assert smtp_to_list, self.NO_VALID_RECIPIENT
|
||||||
|
|
||||||
x_forge_to = message['X-Forge-To']
|
x_forge_to = message['X-Forge-To']
|
||||||
if x_forge_to:
|
if x_forge_to:
|
||||||
# `To:` header forged, e.g. for posting on mail.channels, to avoid confusion
|
# `To:` header forged, e.g. for posting on mail.channels, to avoid confusion
|
||||||
del message['X-Forge-To']
|
del message['X-Forge-To']
|
||||||
del message['To'] # avoid multiple To: headers!
|
del message['To'] # avoid multiple To: headers!
|
||||||
message['To'] = x_forge_to
|
message['To'] = x_forge_to
|
||||||
|
|
||||||
# Do not actually send emails in testing mode!
|
# Do not actually send emails in testing mode!
|
||||||
@@ -432,11 +427,9 @@ class ir_mail_server(osv.osv):
|
|||||||
# Get SMTP Server Details from Mail Server
|
# Get SMTP Server Details from Mail Server
|
||||||
mail_server = None
|
mail_server = None
|
||||||
if mail_server_id:
|
if mail_server_id:
|
||||||
mail_server = self.browse(cr, SUPERUSER_ID, mail_server_id)
|
mail_server = self.sudo().browse(mail_server_id)
|
||||||
elif not smtp_server:
|
elif not smtp_server:
|
||||||
mail_server_ids = self.search(cr, SUPERUSER_ID, [], order='sequence', limit=1)
|
mail_server = self.sudo().search([], order='sequence', limit=1)
|
||||||
if mail_server_ids:
|
|
||||||
mail_server = self.browse(cr, SUPERUSER_ID, mail_server_ids[0])
|
|
||||||
|
|
||||||
if mail_server:
|
if mail_server:
|
||||||
smtp_server = mail_server.smtp_host
|
smtp_server = mail_server.smtp_host
|
||||||
@@ -452,10 +445,10 @@ class ir_mail_server(osv.osv):
|
|||||||
smtp_user = smtp_user or tools.config.get('smtp_user')
|
smtp_user = smtp_user or tools.config.get('smtp_user')
|
||||||
smtp_password = smtp_password or tools.config.get('smtp_password')
|
smtp_password = smtp_password or tools.config.get('smtp_password')
|
||||||
if smtp_encryption is None and tools.config.get('smtp_ssl'):
|
if smtp_encryption is None and tools.config.get('smtp_ssl'):
|
||||||
smtp_encryption = 'starttls' # STARTTLS is the new meaning of the smtp_ssl flag as of v7.0
|
smtp_encryption = 'starttls' # STARTTLS is the new meaning of the smtp_ssl flag as of v7.0
|
||||||
|
|
||||||
if not smtp_server:
|
if not smtp_server:
|
||||||
raise UserError(_("Missing SMTP Server")+ "\n" + _("Please define at least one SMTP server, or provide the SMTP parameters explicitly."))
|
raise UserError(_("Missing SMTP Server") + "\n" + _("Please define at least one SMTP server, or provide the SMTP parameters explicitly."))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message_id = message['Message-Id']
|
message_id = message['Message-Id']
|
||||||
@@ -464,7 +457,7 @@ class ir_mail_server(osv.osv):
|
|||||||
if smtp_server.startswith('maildir:/'):
|
if smtp_server.startswith('maildir:/'):
|
||||||
from mailbox import Maildir
|
from mailbox import Maildir
|
||||||
maildir_path = smtp_server[8:]
|
maildir_path = smtp_server[8:]
|
||||||
mdir = Maildir(maildir_path, factory=None, create = True)
|
mdir = Maildir(maildir_path, factory=None, create=True)
|
||||||
mdir.add(message.as_string(True))
|
mdir.add(message.as_string(True))
|
||||||
return message_id
|
return message_id
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
<!-- ir.mail.server -->
|
<!-- ir.mail.server -->
|
||||||
<record model="ir.ui.view" id="ir_mail_server_form">
|
<record model="ir.ui.view" id="ir_mail_server_form">
|
||||||
<field name="model">ir.mail_server</field>
|
<field name="model">ir.mail_server</field>
|
||||||
@@ -58,5 +57,4 @@
|
|||||||
<field name="search_view_id" ref="view_ir_mail_server_search"/>
|
<field name="search_view_id" ref="view_ir_mail_server_search"/>
|
||||||
</record>
|
</record>
|
||||||
<menuitem id="menu_mail_servers" parent="menu_email" action="action_ir_mail_server_list" sequence="15" groups="base.group_no_one"/>
|
<menuitem id="menu_mail_servers" parent="menu_email" action="action_ir_mail_server_list" sequence="15" groups="base.group_no_one"/>
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
+492
-534
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
|
|
||||||
<report
|
<report
|
||||||
id="report_ir_model_overview"
|
id="report_ir_model_overview"
|
||||||
@@ -10,6 +9,5 @@
|
|||||||
name="base.report_irmodeloverview"
|
name="base.report_irmodeloverview"
|
||||||
file="base.report_irmodeloverview"/>
|
file="base.report_irmodeloverview"/>
|
||||||
|
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
|
|
||||||
<!-- menu_create from model form -->
|
<!-- menu_create from model form -->
|
||||||
<record id="view_model_menu_create" model="ir.ui.view">
|
<record id="view_model_menu_create" model="ir.ui.view">
|
||||||
@@ -562,5 +561,4 @@
|
|||||||
</record>
|
</record>
|
||||||
<menuitem action="ir_access_act" id="menu_ir_access_act" parent="base.menu_security"/>
|
<menuitem action="ir_access_act" id="menu_ir_access_act" parent="base.menu_security"/>
|
||||||
|
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from openerp.osv import osv
|
from odoo import api, models
|
||||||
|
|
||||||
|
|
||||||
class ir_needaction_mixin(osv.AbstractModel):
|
class IrNeedactionMixin(models.AbstractModel):
|
||||||
"""Mixin class for objects using the need action feature.
|
"""Mixin class for objects using the need action feature.
|
||||||
|
|
||||||
Need action feature can be used by models that have to be able to
|
Need action feature can be used by models that have to be able to
|
||||||
@@ -28,7 +28,8 @@ class ir_needaction_mixin(osv.AbstractModel):
|
|||||||
# Addons API
|
# Addons API
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
|
|
||||||
def _needaction_domain_get(self, cr, uid, context=None):
|
@api.model
|
||||||
|
def _needaction_domain_get(self):
|
||||||
""" Returns the domain to filter records that require an action
|
""" Returns the domain to filter records that require an action
|
||||||
:return: domain or False is no action
|
:return: domain or False is no action
|
||||||
"""
|
"""
|
||||||
@@ -38,10 +39,11 @@ class ir_needaction_mixin(osv.AbstractModel):
|
|||||||
# "Need action" API
|
# "Need action" API
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
|
|
||||||
def _needaction_count(self, cr, uid, domain=None, context=None):
|
@api.model
|
||||||
|
def _needaction_count(self, domain=None):
|
||||||
""" Get the number of actions uid has to perform. """
|
""" Get the number of actions uid has to perform. """
|
||||||
dom = self._needaction_domain_get(cr, uid, context=context)
|
dom = self._needaction_domain_get()
|
||||||
if not dom:
|
if not dom:
|
||||||
return 0
|
return 0
|
||||||
res = self.search(cr, uid, (domain or []) + dom, limit=100, order='id DESC', context=context)
|
res = self.search((domain or []) + dom, limit=100, order='id DESC')
|
||||||
return len(res)
|
return len(res)
|
||||||
|
|||||||
+176
-133
@@ -4,8 +4,8 @@ import copy
|
|||||||
import cStringIO
|
import cStringIO
|
||||||
import datetime
|
import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
|
||||||
import itertools
|
import itertools
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
@@ -18,35 +18,35 @@ from urlparse import urlparse
|
|||||||
|
|
||||||
import babel
|
import babel
|
||||||
import babel.dates
|
import babel.dates
|
||||||
|
import psycopg2
|
||||||
import werkzeug
|
import werkzeug
|
||||||
from lxml import etree, html
|
from lxml import etree, html
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import psycopg2
|
|
||||||
|
|
||||||
import openerp.http
|
import odoo.http
|
||||||
import openerp.tools
|
import odoo.tools
|
||||||
from openerp.tools.func import lazy_property
|
import odoo.tools.lru
|
||||||
import openerp.tools.lru
|
from odoo import api, models, _
|
||||||
from openerp.exceptions import QWebException
|
from odoo.exceptions import QWebException
|
||||||
from openerp.fields import Datetime
|
from odoo.http import request
|
||||||
from openerp.http import request
|
from odoo.modules.module import get_resource_path
|
||||||
from openerp.tools.safe_eval import safe_eval as eval
|
from odoo.tools import html_escape as escape
|
||||||
from openerp.osv import osv, orm, fields
|
from odoo.tools.func import lazy_property
|
||||||
from openerp.tools import html_escape as escape
|
from odoo.tools.misc import find_in_path
|
||||||
from openerp.tools.misc import find_in_path
|
from odoo.tools.safe_eval import safe_eval as eval
|
||||||
from openerp.tools.translate import _
|
|
||||||
from openerp.modules.module import get_resource_path
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
MAX_CSS_RULES = 4095
|
MAX_CSS_RULES = 4095
|
||||||
|
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
# QWeb template engine
|
# QWeb template engine
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
class QWebTemplateNotFound(QWebException):
|
class QWebTemplateNotFound(QWebException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def raise_qweb_exception(etype=None, **kw):
|
def raise_qweb_exception(etype=None, **kw):
|
||||||
if etype is None:
|
if etype is None:
|
||||||
etype = QWebException
|
etype = QWebException
|
||||||
@@ -60,12 +60,14 @@ def raise_qweb_exception(etype=None, **kw):
|
|||||||
e.qweb['cause'] = original
|
e.qweb['cause'] = original
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def _build_attribute(name, value):
|
def _build_attribute(name, value):
|
||||||
value = escape(value)
|
value = escape(value)
|
||||||
if isinstance(name, unicode): name = name.encode('utf-8')
|
if isinstance(name, unicode): name = name.encode('utf-8')
|
||||||
if isinstance(value, unicode): value = value.encode('utf-8')
|
if isinstance(value, unicode): value = value.encode('utf-8')
|
||||||
return ' %s="%s"' % (name, value)
|
return ' %s="%s"' % (name, value)
|
||||||
|
|
||||||
|
|
||||||
class FileSystemLoader(object):
|
class FileSystemLoader(object):
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
# TODO: support multiple files #add_file() + add cache
|
# TODO: support multiple files #add_file() + add cache
|
||||||
@@ -86,6 +88,7 @@ class FileSystemLoader(object):
|
|||||||
arch = etree.tostring(root, encoding='utf-8', xml_declaration=True)
|
arch = etree.tostring(root, encoding='utf-8', xml_declaration=True)
|
||||||
return arch
|
return arch
|
||||||
|
|
||||||
|
|
||||||
class QWebContext(dict):
|
class QWebContext(dict):
|
||||||
def __init__(self, cr, uid, data, loader=None, context=None):
|
def __init__(self, cr, uid, data, loader=None, context=None):
|
||||||
self.cr = cr
|
self.cr = cr
|
||||||
@@ -114,7 +117,8 @@ class QWebContext(dict):
|
|||||||
def __copy__(self):
|
def __copy__(self):
|
||||||
return self.copy()
|
return self.copy()
|
||||||
|
|
||||||
class QWeb(orm.AbstractModel):
|
|
||||||
|
class QWeb(models.AbstractModel):
|
||||||
""" Base QWeb rendering engine
|
""" Base QWeb rendering engine
|
||||||
|
|
||||||
* to customize ``t-field`` rendering, subclass ``ir.qweb.field`` and
|
* to customize ``t-field`` rendering, subclass ``ir.qweb.field`` and
|
||||||
@@ -225,6 +229,7 @@ class QWeb(orm.AbstractModel):
|
|||||||
def eval_bool(self, expr, qwebcontext):
|
def eval_bool(self, expr, qwebcontext):
|
||||||
return int(bool(self.eval(expr, qwebcontext)))
|
return int(bool(self.eval(expr, qwebcontext)))
|
||||||
|
|
||||||
|
@api.cr_uid_ids_context
|
||||||
def render(self, cr, uid, id_or_xml_id, qwebcontext=None, loader=None, context=None):
|
def render(self, cr, uid, id_or_xml_id, qwebcontext=None, loader=None, context=None):
|
||||||
""" render(cr, uid, id_or_xml_id, qwebcontext=None, loader=None, context=None)
|
""" render(cr, uid, id_or_xml_id, qwebcontext=None, loader=None, context=None)
|
||||||
|
|
||||||
@@ -247,7 +252,7 @@ class QWeb(orm.AbstractModel):
|
|||||||
qwebcontext['__caller__'] = stack[-1]
|
qwebcontext['__caller__'] = stack[-1]
|
||||||
stack.append(id_or_xml_id)
|
stack.append(id_or_xml_id)
|
||||||
qwebcontext['__stack__'] = stack
|
qwebcontext['__stack__'] = stack
|
||||||
qwebcontext['xmlid'] = str(stack[0]) # Temporary fix
|
qwebcontext['xmlid'] = str(stack[0]) # Temporary fix
|
||||||
|
|
||||||
element = self.get_template(id_or_xml_id, qwebcontext)
|
element = self.get_template(id_or_xml_id, qwebcontext)
|
||||||
element.attrib.pop("name", False)
|
element.attrib.pop("name", False)
|
||||||
@@ -259,7 +264,7 @@ class QWeb(orm.AbstractModel):
|
|||||||
|
|
||||||
debugger = element.get('t-debug')
|
debugger = element.get('t-debug')
|
||||||
if debugger is not None:
|
if debugger is not None:
|
||||||
if openerp.tools.config['dev_mode']:
|
if odoo.tools.config['dev_mode']:
|
||||||
__import__(debugger).set_trace() # pdb, ipdb, pudb, ...
|
__import__(debugger).set_trace() # pdb, ipdb, pudb, ...
|
||||||
else:
|
else:
|
||||||
_logger.warning("@t-debug in template '%s' is only available in --dev mode" % qwebcontext['__template__'])
|
_logger.warning("@t-debug in template '%s' is only available in --dev mode" % qwebcontext['__template__'])
|
||||||
@@ -378,7 +383,7 @@ class QWeb(orm.AbstractModel):
|
|||||||
return self.render_element(element, template_attributes, generated_attributes, qwebcontext, inner)
|
return self.render_element(element, template_attributes, generated_attributes, qwebcontext, inner)
|
||||||
|
|
||||||
def _iterate(self, iterable):
|
def _iterate(self, iterable):
|
||||||
if isinstance (iterable, collections.Mapping):
|
if isinstance(iterable, collections.Mapping):
|
||||||
return iterable.iteritems()
|
return iterable.iteritems()
|
||||||
|
|
||||||
return itertools.izip(*itertools.tee(iterable))
|
return itertools.izip(*itertools.tee(iterable))
|
||||||
@@ -540,11 +545,12 @@ class QWeb(orm.AbstractModel):
|
|||||||
return True
|
return True
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
# QWeb Fields converters
|
# QWeb Fields converters
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
|
|
||||||
class FieldConverter(osv.AbstractModel):
|
class FieldConverter(models.AbstractModel):
|
||||||
""" Used to convert a t-field specification into an output HTML field.
|
""" Used to convert a t-field specification into an output HTML field.
|
||||||
|
|
||||||
:meth:`~.to_html` is the entry point of this conversion from QWeb, it:
|
:meth:`~.to_html` is the entry point of this conversion from QWeb, it:
|
||||||
@@ -556,9 +562,9 @@ class FieldConverter(osv.AbstractModel):
|
|||||||
"""
|
"""
|
||||||
_name = 'ir.qweb.field'
|
_name = 'ir.qweb.field'
|
||||||
|
|
||||||
def attributes(self, cr, uid, field_name, record, options,
|
@api.model
|
||||||
source_element, g_att, t_att, qweb_context,
|
def attributes(self, field_name, record, options,
|
||||||
context=None):
|
source_element, g_att, t_att, qweb_context):
|
||||||
""" attributes(cr, uid, field_name, record, options, source_element, g_att, t_att, qweb_context, context=None)
|
""" attributes(cr, uid, field_name, record, options, source_element, g_att, t_att, qweb_context, context=None)
|
||||||
|
|
||||||
Generates the metadata attributes (prefixed by ``data-oe-`` for the
|
Generates the metadata attributes (prefixed by ``data-oe-`` for the
|
||||||
@@ -592,7 +598,8 @@ class FieldConverter(osv.AbstractModel):
|
|||||||
data.append(('data-oe-readonly', 1))
|
data.append(('data-oe-readonly', 1))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
@api.model
|
||||||
|
def value_to_html(self, value, field, options=None):
|
||||||
""" value_to_html(cr, uid, value, field, options=None, context=None)
|
""" value_to_html(cr, uid, value, field, options=None, context=None)
|
||||||
|
|
||||||
Converts a single value to its HTML version/output
|
Converts a single value to its HTML version/output
|
||||||
@@ -600,17 +607,18 @@ class FieldConverter(osv.AbstractModel):
|
|||||||
if not value: return ''
|
if not value: return ''
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
|
@api.model
|
||||||
|
def record_to_html(self, field_name, record, options=None):
|
||||||
""" record_to_html(cr, uid, field_name, record, options=None, context=None)
|
""" record_to_html(cr, uid, field_name, record, options=None, context=None)
|
||||||
|
|
||||||
Converts the specified field of the browse_record ``record`` to HTML
|
Converts the specified field of the browse_record ``record`` to HTML
|
||||||
"""
|
"""
|
||||||
field = record._fields[field_name]
|
field = record._fields[field_name]
|
||||||
return self.value_to_html(
|
return self.value_to_html(record[field_name], field, options=options)
|
||||||
cr, uid, record[field_name], field, options=options, context=context)
|
|
||||||
|
|
||||||
def to_html(self, cr, uid, field_name, record, options,
|
@api.model
|
||||||
source_element, t_att, g_att, qweb_context, context=None):
|
def to_html(self, field_name, record, options,
|
||||||
|
source_element, t_att, g_att, qweb_context):
|
||||||
""" to_html(cr, uid, field_name, record, options, source_element, t_att, g_att, qweb_context, context=None)
|
""" to_html(cr, uid, field_name, record, options, source_element, t_att, g_att, qweb_context, context=None)
|
||||||
|
|
||||||
Converts a ``t-field`` to its HTML output. A ``t-field`` may be
|
Converts a ``t-field`` to its HTML output. A ``t-field`` may be
|
||||||
@@ -621,7 +629,7 @@ class FieldConverter(osv.AbstractModel):
|
|||||||
field's own ``_type``.
|
field's own ``_type``.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
content = self.record_to_html(cr, uid, field_name, record, options, context=context)
|
content = self.record_to_html(field_name, record, options)
|
||||||
if options.get('html-escape', True):
|
if options.get('html-escape', True):
|
||||||
content = escape(content)
|
content = escape(content)
|
||||||
elif hasattr(content, '__html__'):
|
elif hasattr(content, '__html__'):
|
||||||
@@ -631,22 +639,21 @@ class FieldConverter(osv.AbstractModel):
|
|||||||
field_name, record._name, exc_info=True)
|
field_name, record._name, exc_info=True)
|
||||||
content = None
|
content = None
|
||||||
|
|
||||||
inherit_branding = context and context.get('inherit_branding')
|
inherit_branding = self.env.context and self.env.context.get('inherit_branding')
|
||||||
if not inherit_branding and context and context.get('inherit_branding_auto'):
|
if not inherit_branding and self.env.context and self.env.context.get('inherit_branding_auto'):
|
||||||
inherit_branding = self.pool['ir.model.access'].check(cr, uid, record._name, 'write', False, context=context)
|
inherit_branding = self.env['ir.model.access'].check(record._name, 'write', False)
|
||||||
translate = context and context.get('edit_translations') and context.get('translatable') and getattr(record._fields[field_name], 'translate', False)
|
translate = self.env.context and self.env.context.get('edit_translations') and self.env.context.get('translatable') and getattr(record._fields[field_name], 'translate', False)
|
||||||
|
|
||||||
if inherit_branding or translate:
|
if inherit_branding or translate:
|
||||||
# add branding attributes
|
# add branding attributes
|
||||||
g_att += ''.join(
|
g_att += ''.join(
|
||||||
_build_attribute(name, value)
|
_build_attribute(name, value)
|
||||||
for name, value in self.attributes(
|
for name, value in self.attributes(
|
||||||
cr, uid, field_name, record, options,
|
field_name, record, options,
|
||||||
source_element, g_att, t_att, qweb_context,
|
source_element, g_att, t_att, qweb_context)
|
||||||
context=context)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.render_element(cr, uid, source_element, t_att, g_att,
|
return self.render_element(source_element, t_att, g_att,
|
||||||
qweb_context, content)
|
qweb_context, content)
|
||||||
|
|
||||||
def qweb_object(self):
|
def qweb_object(self):
|
||||||
@@ -661,7 +668,8 @@ class FieldConverter(osv.AbstractModel):
|
|||||||
return self.qweb_object().render_element(
|
return self.qweb_object().render_element(
|
||||||
source_element, t_att, g_att, qweb_context, content or '')
|
source_element, t_att, g_att, qweb_context, content or '')
|
||||||
|
|
||||||
def user_lang(self, cr, uid, context):
|
@api.model
|
||||||
|
def user_lang(self):
|
||||||
""" user_lang(cr, uid, context)
|
""" user_lang(cr, uid, context)
|
||||||
|
|
||||||
Fetches the res.lang object corresponding to the language code stored
|
Fetches the res.lang object corresponding to the language code stored
|
||||||
@@ -670,41 +678,41 @@ class FieldConverter(osv.AbstractModel):
|
|||||||
|
|
||||||
:returns: res.lang browse_record
|
:returns: res.lang browse_record
|
||||||
"""
|
"""
|
||||||
if context is None: context = {}
|
|
||||||
|
|
||||||
lang_code = context.get('lang') or 'en_US'
|
lang_code = self.env.context.get('lang') or 'en_US'
|
||||||
Lang = self.pool['res.lang']
|
Lang = self.env['res.lang']
|
||||||
|
|
||||||
return Lang.browse(cr, uid, Lang._lang_get(cr, uid, lang_code), context=context)
|
return Lang.browse(Lang._lang_get(lang_code))
|
||||||
|
|
||||||
class IntegerConverter(osv.AbstractModel):
|
|
||||||
|
class IntegerConverter(models.AbstractModel):
|
||||||
_name = 'ir.qweb.field.integer'
|
_name = 'ir.qweb.field.integer'
|
||||||
_inherit = 'ir.qweb.field'
|
_inherit = 'ir.qweb.field'
|
||||||
|
|
||||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
@api.model
|
||||||
if context is None:
|
def value_to_html(self, value, field, options=None):
|
||||||
context = {}
|
|
||||||
|
|
||||||
lang_code = context.get('lang') or 'en_US'
|
lang_code = self.env.context.get('lang') or 'en_US'
|
||||||
return self.pool['res.lang'].format(cr, uid, [lang_code], '%d', value, grouping=True)
|
return self.pool['res.lang'].format(self.env.cr, self.env.uid, [lang_code], '%d', value, grouping=True)
|
||||||
|
|
||||||
class FloatConverter(osv.AbstractModel):
|
|
||||||
|
class FloatConverter(models.AbstractModel):
|
||||||
_name = 'ir.qweb.field.float'
|
_name = 'ir.qweb.field.float'
|
||||||
_inherit = 'ir.qweb.field'
|
_inherit = 'ir.qweb.field'
|
||||||
|
|
||||||
def precision(self, cr, uid, field, options=None, context=None):
|
@api.model
|
||||||
|
def precision(self, field, options=None):
|
||||||
_, precision = field.digits or (None, None)
|
_, precision = field.digits or (None, None)
|
||||||
return precision
|
return precision
|
||||||
|
|
||||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
@api.model
|
||||||
if context is None:
|
def value_to_html(self, value, field, options=None):
|
||||||
context = {}
|
precision = self.precision(field, options=options)
|
||||||
precision = self.precision(cr, uid, field, options=options, context=context)
|
|
||||||
fmt = '%f' if precision is None else '%.{precision}f'
|
fmt = '%f' if precision is None else '%.{precision}f'
|
||||||
|
|
||||||
lang_code = context.get('lang') or 'en_US'
|
lang_code = self.env.context.get('lang') or 'en_US'
|
||||||
lang = self.pool['res.lang']
|
lang = self.pool['res.lang']
|
||||||
formatted = lang.format(cr, uid, [lang_code], fmt.format(precision=precision), value, grouping=True)
|
formatted = lang.format(self.env.cr, self.env.uid, [lang_code], fmt.format(precision=precision), value, grouping=True)
|
||||||
|
|
||||||
# %f does not strip trailing zeroes. %g does but its precision causes
|
# %f does not strip trailing zeroes. %g does but its precision causes
|
||||||
# it to switch to scientific notation starting at a million *and* to
|
# it to switch to scientific notation starting at a million *and* to
|
||||||
@@ -714,60 +722,66 @@ class FloatConverter(osv.AbstractModel):
|
|||||||
formatted = re.sub(r'(?:(0|\d+?)0+)$', r'\1', formatted)
|
formatted = re.sub(r'(?:(0|\d+?)0+)$', r'\1', formatted)
|
||||||
return formatted
|
return formatted
|
||||||
|
|
||||||
class DateConverter(osv.AbstractModel):
|
|
||||||
|
class DateConverter(models.AbstractModel):
|
||||||
_name = 'ir.qweb.field.date'
|
_name = 'ir.qweb.field.date'
|
||||||
_inherit = 'ir.qweb.field'
|
_inherit = 'ir.qweb.field'
|
||||||
|
|
||||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
@api.model
|
||||||
if not value or len(value)<10: return ''
|
def value_to_html(self, value, field, options=None):
|
||||||
lang = self.user_lang(cr, uid, context=context)
|
if not value or len(value) < 10: return ''
|
||||||
|
lang = self.user_lang()
|
||||||
locale = babel.Locale.parse(lang.code)
|
locale = babel.Locale.parse(lang.code)
|
||||||
|
|
||||||
if isinstance(value, basestring):
|
if isinstance(value, basestring):
|
||||||
value = datetime.datetime.strptime(
|
value = datetime.datetime.strptime(
|
||||||
value[:10], openerp.tools.DEFAULT_SERVER_DATE_FORMAT)
|
value[:10], odoo.tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||||
|
|
||||||
if options and 'format' in options:
|
if options and 'format' in options:
|
||||||
pattern = options['format']
|
pattern = options['format']
|
||||||
else:
|
else:
|
||||||
strftime_pattern = lang.date_format
|
strftime_pattern = lang.date_format
|
||||||
pattern = openerp.tools.posix_to_ldml(strftime_pattern, locale=locale)
|
pattern = odoo.tools.posix_to_ldml(strftime_pattern, locale=locale)
|
||||||
|
|
||||||
return babel.dates.format_date(
|
return babel.dates.format_date(
|
||||||
value, format=pattern,
|
value, format=pattern,
|
||||||
locale=locale)
|
locale=locale)
|
||||||
|
|
||||||
class DateTimeConverter(osv.AbstractModel):
|
|
||||||
|
class DateTimeConverter(models.AbstractModel):
|
||||||
_name = 'ir.qweb.field.datetime'
|
_name = 'ir.qweb.field.datetime'
|
||||||
_inherit = 'ir.qweb.field'
|
_inherit = 'ir.qweb.field'
|
||||||
|
|
||||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
@api.model
|
||||||
|
def value_to_html(self, value, field, options=None):
|
||||||
if not value: return ''
|
if not value: return ''
|
||||||
lang = self.user_lang(cr, uid, context=context)
|
lang = self.user_lang()
|
||||||
locale = babel.Locale.parse(lang.code)
|
locale = babel.Locale.parse(lang.code)
|
||||||
|
|
||||||
if isinstance(value, basestring):
|
if isinstance(value, basestring):
|
||||||
value = datetime.datetime.strptime(
|
value = datetime.datetime.strptime(
|
||||||
value, openerp.tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
value, odoo.tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||||
value = fields.datetime.context_timestamp(
|
|
||||||
cr, uid, timestamp=value, context=context)
|
value = odoo.osv.fields.datetime.context_timestamp(self.env.cr, self.env.uid, timestamp=value, context=self.env.context)
|
||||||
|
|
||||||
if options and 'format' in options:
|
if options and 'format' in options:
|
||||||
pattern = options['format']
|
pattern = options['format']
|
||||||
else:
|
else:
|
||||||
strftime_pattern = (u"%s %s" % (lang.date_format, lang.time_format))
|
strftime_pattern = (u"%s %s" % (lang.date_format, lang.time_format))
|
||||||
pattern = openerp.tools.posix_to_ldml(strftime_pattern, locale=locale)
|
pattern = odoo.tools.posix_to_ldml(strftime_pattern, locale=locale)
|
||||||
|
|
||||||
if options and options.get('hide_seconds'):
|
if options and options.get('hide_seconds'):
|
||||||
pattern = pattern.replace(":ss", "").replace(":s", "")
|
pattern = pattern.replace(":ss", "").replace(":s", "")
|
||||||
|
|
||||||
return babel.dates.format_datetime(value, format=pattern, locale=locale)
|
return babel.dates.format_datetime(value, format=pattern, locale=locale)
|
||||||
|
|
||||||
class TextConverter(osv.AbstractModel):
|
|
||||||
|
class TextConverter(models.AbstractModel):
|
||||||
_name = 'ir.qweb.field.text'
|
_name = 'ir.qweb.field.text'
|
||||||
_inherit = 'ir.qweb.field'
|
_inherit = 'ir.qweb.field'
|
||||||
|
|
||||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
@api.model
|
||||||
|
def value_to_html(self, value, field, options=None):
|
||||||
"""
|
"""
|
||||||
Escapes the value and converts newlines to br. This is bullshit.
|
Escapes the value and converts newlines to br. This is bullshit.
|
||||||
"""
|
"""
|
||||||
@@ -775,36 +789,42 @@ class TextConverter(osv.AbstractModel):
|
|||||||
|
|
||||||
return nl2br(value, options=options)
|
return nl2br(value, options=options)
|
||||||
|
|
||||||
class SelectionConverter(osv.AbstractModel):
|
|
||||||
|
class SelectionConverter(models.AbstractModel):
|
||||||
_name = 'ir.qweb.field.selection'
|
_name = 'ir.qweb.field.selection'
|
||||||
_inherit = 'ir.qweb.field'
|
_inherit = 'ir.qweb.field'
|
||||||
|
|
||||||
def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
|
@api.model
|
||||||
|
def record_to_html(self, field_name, record, options=None):
|
||||||
value = record[field_name]
|
value = record[field_name]
|
||||||
if not value: return ''
|
if not value: return ''
|
||||||
field = record._fields[field_name]
|
field = record._fields[field_name]
|
||||||
selection = dict(field.get_description(record.env)['selection'])
|
selection = dict(field.get_description(record.env)['selection'])
|
||||||
return self.value_to_html(
|
return self.value_to_html(selection[value], field, options=options)
|
||||||
cr, uid, selection[value], field, options=options)
|
|
||||||
|
|
||||||
class ManyToOneConverter(osv.AbstractModel):
|
|
||||||
|
class ManyToOneConverter(models.AbstractModel):
|
||||||
_name = 'ir.qweb.field.many2one'
|
_name = 'ir.qweb.field.many2one'
|
||||||
_inherit = 'ir.qweb.field'
|
_inherit = 'ir.qweb.field'
|
||||||
|
|
||||||
def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
|
@api.model
|
||||||
|
def record_to_html(self, field_name, record, options=None):
|
||||||
[read] = record.read([field_name])
|
[read] = record.read([field_name])
|
||||||
if not read[field_name]: return ''
|
if not read[field_name]: return ''
|
||||||
_, value = read[field_name]
|
_, value = read[field_name]
|
||||||
return nl2br(value, options=options)
|
return nl2br(value, options=options)
|
||||||
|
|
||||||
class HTMLConverter(osv.AbstractModel):
|
|
||||||
|
class HTMLConverter(models.AbstractModel):
|
||||||
_name = 'ir.qweb.field.html'
|
_name = 'ir.qweb.field.html'
|
||||||
_inherit = 'ir.qweb.field'
|
_inherit = 'ir.qweb.field'
|
||||||
|
|
||||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
@api.model
|
||||||
|
def value_to_html(self, value, field, options=None):
|
||||||
return HTMLSafe(value or '')
|
return HTMLSafe(value or '')
|
||||||
|
|
||||||
class ImageConverter(osv.AbstractModel):
|
|
||||||
|
class ImageConverter(models.AbstractModel):
|
||||||
""" ``image`` widget rendering, inserts a data:uri-using image tag in the
|
""" ``image`` widget rendering, inserts a data:uri-using image tag in the
|
||||||
document. May be overridden by e.g. the website module to generate links
|
document. May be overridden by e.g. the website module to generate links
|
||||||
instead.
|
instead.
|
||||||
@@ -816,18 +836,20 @@ class ImageConverter(osv.AbstractModel):
|
|||||||
_name = 'ir.qweb.field.image'
|
_name = 'ir.qweb.field.image'
|
||||||
_inherit = 'ir.qweb.field'
|
_inherit = 'ir.qweb.field'
|
||||||
|
|
||||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
@api.model
|
||||||
|
def value_to_html(self, value, field, options=None):
|
||||||
try:
|
try:
|
||||||
image = Image.open(cStringIO.StringIO(value.decode('base64')))
|
image = Image.open(cStringIO.StringIO(value.decode('base64')))
|
||||||
image.verify()
|
image.verify()
|
||||||
except IOError:
|
except IOError:
|
||||||
raise ValueError("Non-image binary fields can not be converted to HTML")
|
raise ValueError("Non-image binary fields can not be converted to HTML")
|
||||||
except: # image.verify() throws "suitable exceptions", I have no idea what they are
|
except: # image.verify() throws "suitable exceptions", I have no idea what they are
|
||||||
raise ValueError("Invalid image content")
|
raise ValueError("Invalid image content")
|
||||||
|
|
||||||
return HTMLSafe('<img src="data:%s;base64,%s">' % (Image.MIME[image.format], value))
|
return HTMLSafe('<img src="data:%s;base64,%s">' % (Image.MIME[image.format], value))
|
||||||
|
|
||||||
class MonetaryConverter(osv.AbstractModel):
|
|
||||||
|
class MonetaryConverter(models.AbstractModel):
|
||||||
""" ``monetary`` converter, has a mandatory option
|
""" ``monetary`` converter, has a mandatory option
|
||||||
``display_currency`` only if field is not of type Monetary.
|
``display_currency`` only if field is not of type Monetary.
|
||||||
Otherwise, if we are in presence of a monetary field, the field definition must
|
Otherwise, if we are in presence of a monetary field, the field definition must
|
||||||
@@ -844,16 +866,16 @@ class MonetaryConverter(osv.AbstractModel):
|
|||||||
_name = 'ir.qweb.field.monetary'
|
_name = 'ir.qweb.field.monetary'
|
||||||
_inherit = 'ir.qweb.field'
|
_inherit = 'ir.qweb.field'
|
||||||
|
|
||||||
def to_html(self, cr, uid, field_name, record, options,
|
@api.model
|
||||||
source_element, t_att, g_att, qweb_context, context=None):
|
def to_html(self, field_name, record, options,
|
||||||
|
source_element, t_att, g_att, qweb_context):
|
||||||
options['_qweb_context'] = qweb_context
|
options['_qweb_context'] = qweb_context
|
||||||
return super(MonetaryConverter, self).to_html(
|
return super(MonetaryConverter, self).to_html(
|
||||||
cr, uid, field_name, record, options,
|
field_name, record, options,
|
||||||
source_element, t_att, g_att, qweb_context, context=context)
|
source_element, t_att, g_att, qweb_context)
|
||||||
|
|
||||||
def record_to_html(self, cr, uid, field_name, record, options, context=None):
|
@api.model
|
||||||
if context is None:
|
def record_to_html(self, field_name, record, options):
|
||||||
context = {}
|
|
||||||
Currency = self.pool['res.currency']
|
Currency = self.pool['res.currency']
|
||||||
cur_field = record._fields[field_name]
|
cur_field = record._fields[field_name]
|
||||||
display_currency = False
|
display_currency = False
|
||||||
@@ -862,7 +884,7 @@ class MonetaryConverter(osv.AbstractModel):
|
|||||||
display_currency = record[cur_field.currency_field]
|
display_currency = record[cur_field.currency_field]
|
||||||
#otherwise fall back to old method
|
#otherwise fall back to old method
|
||||||
if not display_currency:
|
if not display_currency:
|
||||||
display_currency = self.display_currency(cr, uid, options['display_currency'], options)
|
display_currency = self.display_currency(options['display_currency'], options)
|
||||||
|
|
||||||
# lang.format mandates a sprintf-style format. These formats are non-
|
# lang.format mandates a sprintf-style format. These formats are non-
|
||||||
# minimal (they have a default fixed precision instead), and
|
# minimal (they have a default fixed precision instead), and
|
||||||
@@ -874,14 +896,13 @@ class MonetaryConverter(osv.AbstractModel):
|
|||||||
from_amount = record[field_name]
|
from_amount = record[field_name]
|
||||||
|
|
||||||
if options.get('from_currency'):
|
if options.get('from_currency'):
|
||||||
from_currency = self.display_currency(cr, uid, options['from_currency'], options)
|
from_currency = self.display_currency(options['from_currency'], options)
|
||||||
from_amount = Currency.compute(cr, uid, from_currency.id, display_currency.id, from_amount)
|
from_amount = Currency.compute(self.env.cr, self.env.uid, from_currency.id, display_currency.id, from_amount)
|
||||||
|
|
||||||
lang_code = context.get('lang') or 'en_US'
|
lang_code = self.env.context.get('lang') or 'en_US'
|
||||||
lang = self.pool['res.lang']
|
lang = self.pool['res.lang']
|
||||||
formatted_amount = lang.format(cr, uid, [lang_code],
|
formatted_amount = lang.format(self.env.cr, self.env.uid, [lang_code],
|
||||||
fmt, Currency.round(cr, uid, display_currency, from_amount),
|
fmt, Currency.round(self.env.cr, self.env.uid, display_currency, from_amount), grouping=True, monetary=True)
|
||||||
grouping=True, monetary=True)
|
|
||||||
|
|
||||||
pre = post = u''
|
pre = post = u''
|
||||||
if display_currency.position == 'before':
|
if display_currency.position == 'before':
|
||||||
@@ -896,9 +917,8 @@ class MonetaryConverter(osv.AbstractModel):
|
|||||||
symbol=display_currency.symbol,
|
symbol=display_currency.symbol,
|
||||||
))
|
))
|
||||||
|
|
||||||
def display_currency(self, cr, uid, currency, options):
|
def display_currency(self, currency, options):
|
||||||
return self.qweb_object().eval_object(
|
return self.qweb_object().eval_object(currency, options['_qweb_context'])
|
||||||
currency, options['_qweb_context'])
|
|
||||||
|
|
||||||
TIMEDELTA_UNITS = (
|
TIMEDELTA_UNITS = (
|
||||||
('year', 3600 * 24 * 365),
|
('year', 3600 * 24 * 365),
|
||||||
@@ -909,7 +929,9 @@ TIMEDELTA_UNITS = (
|
|||||||
('minute', 60),
|
('minute', 60),
|
||||||
('second', 1)
|
('second', 1)
|
||||||
)
|
)
|
||||||
class DurationConverter(osv.AbstractModel):
|
|
||||||
|
|
||||||
|
class DurationConverter(models.AbstractModel):
|
||||||
""" ``duration`` converter, to display integral or fractional values as
|
""" ``duration`` converter, to display integral or fractional values as
|
||||||
human-readable time spans (e.g. 1.5 as "1 hour 30 minutes").
|
human-readable time spans (e.g. 1.5 as "1 hour 30 minutes").
|
||||||
|
|
||||||
@@ -924,15 +946,15 @@ class DurationConverter(osv.AbstractModel):
|
|||||||
_name = 'ir.qweb.field.duration'
|
_name = 'ir.qweb.field.duration'
|
||||||
_inherit = 'ir.qweb.field'
|
_inherit = 'ir.qweb.field'
|
||||||
|
|
||||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
@api.model
|
||||||
|
def value_to_html(self, value, field, options=None):
|
||||||
units = dict(TIMEDELTA_UNITS)
|
units = dict(TIMEDELTA_UNITS)
|
||||||
if value < 0:
|
if value < 0:
|
||||||
raise ValueError(_("Durations can't be negative"))
|
raise ValueError(_("Durations can't be negative"))
|
||||||
if not options or options.get('unit') not in units:
|
if not options or options.get('unit') not in units:
|
||||||
raise ValueError(_("A unit must be provided to duration widgets"))
|
raise ValueError(_("A unit must be provided to duration widgets"))
|
||||||
|
|
||||||
locale = babel.Locale.parse(
|
locale = babel.Locale.parse(self.user_lang().code)
|
||||||
self.user_lang(cr, uid, context=context).code)
|
|
||||||
factor = units[options['unit']]
|
factor = units[options['unit']]
|
||||||
|
|
||||||
sections = []
|
sections = []
|
||||||
@@ -947,14 +969,14 @@ class DurationConverter(osv.AbstractModel):
|
|||||||
return ' '.join(sections)
|
return ' '.join(sections)
|
||||||
|
|
||||||
|
|
||||||
class RelativeDatetimeConverter(osv.AbstractModel):
|
class RelativeDatetimeConverter(models.AbstractModel):
|
||||||
_name = 'ir.qweb.field.relative'
|
_name = 'ir.qweb.field.relative'
|
||||||
_inherit = 'ir.qweb.field'
|
_inherit = 'ir.qweb.field'
|
||||||
|
|
||||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
@api.model
|
||||||
parse_format = openerp.tools.DEFAULT_SERVER_DATETIME_FORMAT
|
def value_to_html(self, value, field, options=None):
|
||||||
locale = babel.Locale.parse(
|
parse_format = odoo.tools.DEFAULT_SERVER_DATETIME_FORMAT
|
||||||
self.user_lang(cr, uid, context=context).code)
|
locale = babel.Locale.parse(self.user_lang().code)
|
||||||
|
|
||||||
if isinstance(value, basestring):
|
if isinstance(value, basestring):
|
||||||
value = datetime.datetime.strptime(value, parse_format)
|
value = datetime.datetime.strptime(value, parse_format)
|
||||||
@@ -962,17 +984,15 @@ class RelativeDatetimeConverter(osv.AbstractModel):
|
|||||||
# value should be a naive datetime in UTC. So is fields.Datetime.now()
|
# value should be a naive datetime in UTC. So is fields.Datetime.now()
|
||||||
reference = datetime.datetime.strptime(field.now(), parse_format)
|
reference = datetime.datetime.strptime(field.now(), parse_format)
|
||||||
|
|
||||||
return babel.dates.format_timedelta(
|
return babel.dates.format_timedelta(value - reference, add_direction=True, locale=locale)
|
||||||
value - reference, add_direction=True, locale=locale)
|
|
||||||
|
|
||||||
class Contact(orm.AbstractModel):
|
|
||||||
|
class Contact(models.AbstractModel):
|
||||||
_name = 'ir.qweb.field.contact'
|
_name = 'ir.qweb.field.contact'
|
||||||
_inherit = 'ir.qweb.field.many2one'
|
_inherit = 'ir.qweb.field.many2one'
|
||||||
|
|
||||||
def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
|
@api.model
|
||||||
if context is None:
|
def record_to_html(self, field_name, record, options=None):
|
||||||
context = {}
|
|
||||||
|
|
||||||
if options is None:
|
if options is None:
|
||||||
options = {}
|
options = {}
|
||||||
opf = options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"]
|
opf = options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"]
|
||||||
@@ -998,15 +1018,17 @@ class Contact(orm.AbstractModel):
|
|||||||
'options': options
|
'options': options
|
||||||
}
|
}
|
||||||
|
|
||||||
html = self.pool["ir.ui.view"].render(cr, uid, "base.contact", val, engine='ir.qweb', context=context).decode('utf8')
|
html = self.env['ir.ui.view']._model.render(self.env.cr, self.env.uid, "base.contact", val, engine='ir.qweb', context=self.env.context).decode('utf8')
|
||||||
|
|
||||||
return HTMLSafe(html)
|
return HTMLSafe(html)
|
||||||
|
|
||||||
class QwebView(orm.AbstractModel):
|
|
||||||
|
class QwebView(models.AbstractModel):
|
||||||
_name = 'ir.qweb.field.qweb'
|
_name = 'ir.qweb.field.qweb'
|
||||||
_inherit = 'ir.qweb.field.many2one'
|
_inherit = 'ir.qweb.field.many2one'
|
||||||
|
|
||||||
def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
|
@api.model
|
||||||
|
def record_to_html(self, field_name, record, options=None):
|
||||||
if not getattr(record, field_name):
|
if not getattr(record, field_name):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -1016,13 +1038,14 @@ class QwebView(orm.AbstractModel):
|
|||||||
_logger.warning("%s.%s must be a 'ir.ui.view' model." % (record, field_name))
|
_logger.warning("%s.%s must be a 'ir.ui.view' model." % (record, field_name))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
ctx = (context or {}).copy()
|
ctx = (self.env.context or {}).copy()
|
||||||
ctx['object'] = record
|
ctx['object'] = record
|
||||||
html = view.render(ctx, engine='ir.qweb', context=ctx).decode('utf8')
|
html = view._model.render(self.env.cr, self.env.uid, ctx, engine='ir.qweb', context=self.env.ctx).decode('utf8')
|
||||||
|
|
||||||
return HTMLSafe(html)
|
return HTMLSafe(html)
|
||||||
|
|
||||||
class QwebWidget(osv.AbstractModel):
|
|
||||||
|
class QwebWidget(models.AbstractModel):
|
||||||
_name = 'ir.qweb.widget'
|
_name = 'ir.qweb.widget'
|
||||||
|
|
||||||
def _format(self, inner, options, qwebcontext):
|
def _format(self, inner, options, qwebcontext):
|
||||||
@@ -1031,7 +1054,8 @@ class QwebWidget(osv.AbstractModel):
|
|||||||
def format(self, inner, options, qwebcontext):
|
def format(self, inner, options, qwebcontext):
|
||||||
return escape(self._format(inner, options, qwebcontext))
|
return escape(self._format(inner, options, qwebcontext))
|
||||||
|
|
||||||
class QwebWidgetMonetary(osv.AbstractModel):
|
|
||||||
|
class QwebWidgetMonetary(models.AbstractModel):
|
||||||
_name = 'ir.qweb.widget.monetary'
|
_name = 'ir.qweb.widget.monetary'
|
||||||
_inherit = 'ir.qweb.widget'
|
_inherit = 'ir.qweb.widget'
|
||||||
|
|
||||||
@@ -1054,6 +1078,7 @@ class QwebWidgetMonetary(osv.AbstractModel):
|
|||||||
formatted_amount, pre=pre, post=post
|
formatted_amount, pre=pre, post=post
|
||||||
).format(symbol=display.symbol,)
|
).format(symbol=display.symbol,)
|
||||||
|
|
||||||
|
|
||||||
class HTMLSafe(object):
|
class HTMLSafe(object):
|
||||||
""" HTMLSafe string wrapper, Werkzeug's escape() has special handling for
|
""" HTMLSafe string wrapper, Werkzeug's escape() has special handling for
|
||||||
objects with a ``__html__`` methods but AFAIK does not provide any such
|
objects with a ``__html__`` methods but AFAIK does not provide any such
|
||||||
@@ -1062,21 +1087,26 @@ class HTMLSafe(object):
|
|||||||
Wrapping a string in HTML will prevent its escaping
|
Wrapping a string in HTML will prevent its escaping
|
||||||
"""
|
"""
|
||||||
__slots__ = ['string']
|
__slots__ = ['string']
|
||||||
|
|
||||||
def __init__(self, string):
|
def __init__(self, string):
|
||||||
self.string = string
|
self.string = string
|
||||||
|
|
||||||
def __html__(self):
|
def __html__(self):
|
||||||
return self.string
|
return self.string
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
s = self.string
|
s = self.string
|
||||||
if isinstance(s, unicode):
|
if isinstance(s, unicode):
|
||||||
return s.encode('utf-8')
|
return s.encode('utf-8')
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
s = self.string
|
s = self.string
|
||||||
if isinstance(s, str):
|
if isinstance(s, str):
|
||||||
return s.decode('utf-8')
|
return s.decode('utf-8')
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
def nl2br(string, options=None):
|
def nl2br(string, options=None):
|
||||||
""" Converts newlines to HTML linebreaks in ``string``. Automatically
|
""" Converts newlines to HTML linebreaks in ``string``. Automatically
|
||||||
escapes content unless options['html-escape'] is set to False, and returns
|
escapes content unless options['html-escape'] is set to False, and returns
|
||||||
@@ -1092,15 +1122,20 @@ def nl2br(string, options=None):
|
|||||||
string = escape(string)
|
string = escape(string)
|
||||||
return HTMLSafe(string.replace('\n', '<br>\n'))
|
return HTMLSafe(string.replace('\n', '<br>\n'))
|
||||||
|
|
||||||
|
|
||||||
def get_field_type(field, options):
|
def get_field_type(field, options):
|
||||||
""" Gets a t-field's effective type from the field definition and its options """
|
""" Gets a t-field's effective type from the field definition and its options """
|
||||||
return options.get('widget', field.type)
|
return options.get('widget', field.type)
|
||||||
|
|
||||||
|
|
||||||
class AssetError(Exception):
|
class AssetError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AssetNotFound(AssetError):
|
class AssetNotFound(AssetError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class AssetsBundle(object):
|
class AssetsBundle(object):
|
||||||
rx_css_import = re.compile("(@import[^;{]+;?)", re.M)
|
rx_css_import = re.compile("(@import[^;{]+;?)", re.M)
|
||||||
rx_preprocess_imports = re.compile("""(@import\s?['"]([^'"]+)['"](;?))""")
|
rx_preprocess_imports = re.compile("""(@import\s?['"]([^'"]+)['"](;?))""")
|
||||||
@@ -1242,14 +1277,14 @@ class AssetsBundle(object):
|
|||||||
('url', '=like', '/web/content/%-%/{0}%.{1}'.format(self.xmlid, type)), # The wilcards are id, version and pagination number (if any)
|
('url', '=like', '/web/content/%-%/{0}%.{1}'.format(self.xmlid, type)), # The wilcards are id, version and pagination number (if any)
|
||||||
'!', ('url', '=like', '/web/content/%-{}/%'.format(self.version))
|
'!', ('url', '=like', '/web/content/%-{}/%'.format(self.version))
|
||||||
]
|
]
|
||||||
attachment_ids = ira.search(self.cr, openerp.SUPERUSER_ID, domain, context=self.context)
|
attachment_ids = ira.search(self.cr, odoo.SUPERUSER_ID, domain, context=self.context)
|
||||||
return ira.unlink(self.cr, openerp.SUPERUSER_ID, attachment_ids, context=self.context)
|
return ira.unlink(self.cr, odoo.SUPERUSER_ID, attachment_ids, context=self.context)
|
||||||
|
|
||||||
def get_attachments(self, type, inc=None):
|
def get_attachments(self, type, inc=None):
|
||||||
ira = self.registry['ir.attachment']
|
ira = self.registry['ir.attachment']
|
||||||
domain = [('url', '=like', '/web/content/%%-%s/%s%s.%s' % (self.version, self.xmlid, ('%%' if inc is None else '.%s' % inc), type))]
|
domain = [('url', '=like', '/web/content/%%-%s/%s%s.%s' % (self.version, self.xmlid, ('%%' if inc is None else '.%s' % inc), type))]
|
||||||
attachment_ids = ira.search(self.cr, openerp.SUPERUSER_ID, domain, order='name asc', context=self.context)
|
attachment_ids = ira.search(self.cr, odoo.SUPERUSER_ID, domain, order='name asc', context=self.context)
|
||||||
return ira.browse(self.cr, openerp.SUPERUSER_ID, attachment_ids, context=self.context)
|
return ira.browse(self.cr, odoo.SUPERUSER_ID, attachment_ids, context=self.context)
|
||||||
|
|
||||||
def save_attachment(self, type, content, inc=None):
|
def save_attachment(self, type, content, inc=None):
|
||||||
ira = self.registry['ir.attachment']
|
ira = self.registry['ir.attachment']
|
||||||
@@ -1264,21 +1299,21 @@ class AssetsBundle(object):
|
|||||||
'public': True,
|
'public': True,
|
||||||
'datas': content.encode('utf8').encode('base64'),
|
'datas': content.encode('utf8').encode('base64'),
|
||||||
}
|
}
|
||||||
attachment_id = ira.create(self.cr, openerp.SUPERUSER_ID, values, context=self.context)
|
attachment_id = ira.create(self.cr, odoo.SUPERUSER_ID, values, context=self.context)
|
||||||
|
|
||||||
url = '/web/content/%s-%s/%s' % (attachment_id, self.version, fname)
|
url = '/web/content/%s-%s/%s' % (attachment_id, self.version, fname)
|
||||||
values = {
|
values = {
|
||||||
'name': url,
|
'name': url,
|
||||||
'url': url,
|
'url': url,
|
||||||
}
|
}
|
||||||
ira.write(self.cr, openerp.SUPERUSER_ID, attachment_id, values, context=self.context)
|
ira.write(self.cr, odoo.SUPERUSER_ID, attachment_id, values, context=self.context)
|
||||||
|
|
||||||
if self.context.get('commit_assetsbundle') is True:
|
if self.context.get('commit_assetsbundle') is True:
|
||||||
self.cr.commit()
|
self.cr.commit()
|
||||||
|
|
||||||
self.clean_attachments(type)
|
self.clean_attachments(type)
|
||||||
|
|
||||||
return ira.browse(self.cr, openerp.SUPERUSER_ID, attachment_id, context=self.context)
|
return ira.browse(self.cr, odoo.SUPERUSER_ID, attachment_id, context=self.context)
|
||||||
|
|
||||||
def js(self):
|
def js(self):
|
||||||
attachments = self.get_attachments('js')
|
attachments = self.get_attachments('js')
|
||||||
@@ -1412,6 +1447,7 @@ class AssetsBundle(object):
|
|||||||
def compile_css(self, cmd, source):
|
def compile_css(self, cmd, source):
|
||||||
"""Sanitizes @import rules, remove duplicates @import rules, then compile"""
|
"""Sanitizes @import rules, remove duplicates @import rules, then compile"""
|
||||||
imports = []
|
imports = []
|
||||||
|
|
||||||
def sanitize(matchobj):
|
def sanitize(matchobj):
|
||||||
ref = matchobj.group(2)
|
ref = matchobj.group(2)
|
||||||
line = '@import "%s"%s' % (ref, matchobj.group(3))
|
line = '@import "%s"%s' % (ref, matchobj.group(3))
|
||||||
@@ -1452,6 +1488,7 @@ class AssetsBundle(object):
|
|||||||
error += '\n - %s' % (asset.url if asset.url else '<inline sass>')
|
error += '\n - %s' % (asset.url if asset.url else '<inline sass>')
|
||||||
return error
|
return error
|
||||||
|
|
||||||
|
|
||||||
class WebAsset(object):
|
class WebAsset(object):
|
||||||
html_url = '%s'
|
html_url = '%s'
|
||||||
|
|
||||||
@@ -1483,7 +1520,7 @@ class WebAsset(object):
|
|||||||
fields = ['__last_update', 'datas', 'mimetype']
|
fields = ['__last_update', 'datas', 'mimetype']
|
||||||
domain = [('type', '=', 'binary'), ('url', '=', self.url)]
|
domain = [('type', '=', 'binary'), ('url', '=', self.url)]
|
||||||
ira = self.registry['ir.attachment']
|
ira = self.registry['ir.attachment']
|
||||||
attach = ira.search_read(self.cr, openerp.SUPERUSER_ID, domain, fields, context=self.context)
|
attach = ira.search_read(self.cr, odoo.SUPERUSER_ID, domain, fields, context=self.context)
|
||||||
self._ir_attach = attach[0]
|
self._ir_attach = attach[0]
|
||||||
except Exception:
|
except Exception:
|
||||||
raise AssetNotFound("Could not find %s" % self.name)
|
raise AssetNotFound("Could not find %s" % self.name)
|
||||||
@@ -1498,7 +1535,7 @@ class WebAsset(object):
|
|||||||
if self._filename:
|
if self._filename:
|
||||||
return datetime.datetime.fromtimestamp(os.path.getmtime(self._filename))
|
return datetime.datetime.fromtimestamp(os.path.getmtime(self._filename))
|
||||||
elif self._ir_attach:
|
elif self._ir_attach:
|
||||||
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
|
server_format = odoo.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
|
||||||
last_update = self._ir_attach['__last_update']
|
last_update = self._ir_attach['__last_update']
|
||||||
try:
|
try:
|
||||||
return datetime.datetime.strptime(last_update, server_format + '.%f')
|
return datetime.datetime.strptime(last_update, server_format + '.%f')
|
||||||
@@ -1538,6 +1575,7 @@ class WebAsset(object):
|
|||||||
content = self.content
|
content = self.content
|
||||||
return '\n/* %s */\n%s' % (self.name, content)
|
return '\n/* %s */\n%s' % (self.name, content)
|
||||||
|
|
||||||
|
|
||||||
class JavascriptAsset(WebAsset):
|
class JavascriptAsset(WebAsset):
|
||||||
def minify(self):
|
def minify(self):
|
||||||
return self.with_header(rjsmin(self.content))
|
return self.with_header(rjsmin(self.content))
|
||||||
@@ -1554,6 +1592,7 @@ class JavascriptAsset(WebAsset):
|
|||||||
else:
|
else:
|
||||||
return '<script type="text/javascript" charset="utf-8">%s</script>' % self.with_header()
|
return '<script type="text/javascript" charset="utf-8">%s</script>' % self.with_header()
|
||||||
|
|
||||||
|
|
||||||
class StylesheetAsset(WebAsset):
|
class StylesheetAsset(WebAsset):
|
||||||
rx_import = re.compile(r"""@import\s+('|")(?!'|"|/|https?://)""", re.U)
|
rx_import = re.compile(r"""@import\s+('|")(?!'|"|/|https?://)""", re.U)
|
||||||
rx_url = re.compile(r"""url\s*\(\s*('|"|)(?!'|"|/|https?://|data:)""", re.U)
|
rx_url = re.compile(r"""url\s*\(\s*('|"|)(?!'|"|/|https?://|data:)""", re.U)
|
||||||
@@ -1615,6 +1654,7 @@ class StylesheetAsset(WebAsset):
|
|||||||
else:
|
else:
|
||||||
return '<style type="text/css"%s>%s</style>' % (media, self.with_header())
|
return '<style type="text/css"%s>%s</style>' % (media, self.with_header())
|
||||||
|
|
||||||
|
|
||||||
class PreprocessedCSS(StylesheetAsset):
|
class PreprocessedCSS(StylesheetAsset):
|
||||||
html_url = '%s.css'
|
html_url = '%s.css'
|
||||||
rx_import = None
|
rx_import = None
|
||||||
@@ -1629,6 +1669,7 @@ class PreprocessedCSS(StylesheetAsset):
|
|||||||
def get_command(self):
|
def get_command(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class SassStylesheetAsset(PreprocessedCSS):
|
class SassStylesheetAsset(PreprocessedCSS):
|
||||||
rx_indent = re.compile(r'^( +|\t+)', re.M)
|
rx_indent = re.compile(r'^( +|\t+)', re.M)
|
||||||
indent = None
|
indent = None
|
||||||
@@ -1661,6 +1702,7 @@ class SassStylesheetAsset(PreprocessedCSS):
|
|||||||
return [sass, '--stdin', '-t', 'compressed', '--unix-newlines', '--compass',
|
return [sass, '--stdin', '-t', 'compressed', '--unix-newlines', '--compass',
|
||||||
'-r', 'bootstrap-sass']
|
'-r', 'bootstrap-sass']
|
||||||
|
|
||||||
|
|
||||||
class LessStylesheetAsset(PreprocessedCSS):
|
class LessStylesheetAsset(PreprocessedCSS):
|
||||||
def get_command(self):
|
def get_command(self):
|
||||||
try:
|
try:
|
||||||
@@ -1673,6 +1715,7 @@ class LessStylesheetAsset(PreprocessedCSS):
|
|||||||
lesspath = get_resource_path('web', 'static', 'lib', 'bootstrap', 'less')
|
lesspath = get_resource_path('web', 'static', 'lib', 'bootstrap', 'less')
|
||||||
return [lessc, '-', '--clean-css', '--no-js', '--no-color', '--include-path=%s' % lesspath]
|
return [lessc, '-', '--clean-css', '--no-js', '--no-color', '--include-path=%s' % lesspath]
|
||||||
|
|
||||||
|
|
||||||
def rjsmin(script):
|
def rjsmin(script):
|
||||||
""" Minify js with a clever regex.
|
""" Minify js with a clever regex.
|
||||||
Taken from http://opensource.perlig.de/rjsmin
|
Taken from http://opensource.perlig.de/rjsmin
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
<template id="contact">
|
<template id="contact">
|
||||||
<address t-ignore="true" class="mb0" itemscope="itemscope" itemtype="http://schema.org/Organization">
|
<address t-ignore="true" class="mb0" itemscope="itemscope" itemtype="http://schema.org/Organization">
|
||||||
<div t-if="not (('name' in fields) or (address and 'address' in fields) or (city and 'city' in fields) or (mobile and 'mobile' in fields) or (fax and 'fax' in fields) or (website and 'website' in fields) or (email and 'email' in fields))" class="css_non_editable_mode_hidden">
|
<div t-if="not (('name' in fields) or (address and 'address' in fields) or (city and 'city' in fields) or (mobile and 'mobile' in fields) or (fax and 'fax' in fields) or (website and 'website' in fields) or (email and 'email' in fields))" class="css_non_editable_mode_hidden">
|
||||||
@@ -36,5 +35,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</address>
|
</address>
|
||||||
</template>
|
</template>
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
@@ -2,41 +2,20 @@
|
|||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from openerp import SUPERUSER_ID
|
import odoo
|
||||||
from openerp import tools
|
from odoo import api, fields, models, _
|
||||||
from openerp.osv import fields, osv, expression
|
from odoo import SUPERUSER_ID
|
||||||
from openerp.tools.safe_eval import safe_eval as eval
|
from odoo import tools
|
||||||
from openerp.tools.misc import unquote as unquote
|
from odoo.osv import expression
|
||||||
|
from odoo.tools.safe_eval import safe_eval as eval
|
||||||
|
from odoo.tools.misc import unquote as unquote
|
||||||
|
|
||||||
class ir_rule(osv.osv):
|
|
||||||
|
class IrRule(models.Model):
|
||||||
_name = 'ir.rule'
|
_name = 'ir.rule'
|
||||||
_order = 'name'
|
_order = 'name, model_id DESC'
|
||||||
_MODES = ['read', 'write', 'create', 'unlink']
|
_MODES = ['read', 'write', 'create', 'unlink']
|
||||||
|
|
||||||
def _eval_context_for_combinations(self):
|
|
||||||
"""Returns a dictionary to use as evaluation context for
|
|
||||||
ir.rule domains, when the goal is to obtain python lists
|
|
||||||
that are easier to parse and combine, but not to
|
|
||||||
actually execute them."""
|
|
||||||
return {'user': unquote('user'),
|
|
||||||
'time': unquote('time')}
|
|
||||||
|
|
||||||
def _eval_context(self, cr, uid):
|
|
||||||
"""Returns a dictionary to use as evaluation context for
|
|
||||||
ir.rule domains."""
|
|
||||||
return {'user': self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid),
|
|
||||||
'time':time}
|
|
||||||
|
|
||||||
def _domain_force_get(self, cr, uid, ids, field_name, arg, context=None):
|
|
||||||
res = {}
|
|
||||||
eval_context = self._eval_context(cr, uid)
|
|
||||||
for rule in self.browse(cr, uid, ids, context):
|
|
||||||
if rule.domain_force:
|
|
||||||
res[rule.id] = expression.normalize_domain(eval(rule.domain_force, eval_context))
|
|
||||||
else:
|
|
||||||
res[rule.id] = []
|
|
||||||
return res
|
|
||||||
|
|
||||||
def _get_value(self, cr, uid, ids, field_name, arg, context=None):
|
def _get_value(self, cr, uid, ids, field_name, arg, context=None):
|
||||||
res = {}
|
res = {}
|
||||||
for rule in self.browse(cr, uid, ids, context):
|
for rule in self.browse(cr, uid, ids, context):
|
||||||
@@ -46,44 +25,56 @@ class ir_rule(osv.osv):
|
|||||||
res[rule.id] = False
|
res[rule.id] = False
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _check_model_obj(self, cr, uid, ids, context=None):
|
_columns = {'global': odoo.osv.fields.function(_get_value, string='Global', type='boolean', store=True, help="If no group is specified the rule is global and applied to everyone")}
|
||||||
return not any(self.pool[rule.model_id.model].is_transient() for rule in self.browse(cr, uid, ids, context))
|
|
||||||
|
|
||||||
def _check_model_name(self, cr, uid, ids, context=None):
|
name = fields.Char(index=True)
|
||||||
# Don't allow rules on rules records (this model).
|
active = fields.Boolean(default=True, help="If you uncheck the active field, it will disable the record rule without deleting it (if you delete a native record rule, it may be re-created when you reload the module).")
|
||||||
return not any(rule.model_id.model == self._name for rule in self.browse(cr, uid, ids, context))
|
model_id = fields.Many2one('ir.model', string='Object', index=True, required=True, ondelete="cascade")
|
||||||
|
groups = fields.Many2many('res.groups', 'rule_group_rel', 'rule_group_id', 'group_id')
|
||||||
|
domain_force = fields.Text(string='Domain')
|
||||||
|
domain = fields.Binary(compute='_domain_force_get', string='Domain')
|
||||||
|
perm_read = fields.Boolean(string='Apply for Read', default=True)
|
||||||
|
perm_write = fields.Boolean(string='Apply for Write', default=True)
|
||||||
|
perm_create = fields.Boolean(string='Apply for Create', default=True)
|
||||||
|
perm_unlink = fields.Boolean(string='Apply for Delete', default=True)
|
||||||
|
|
||||||
_columns = {
|
|
||||||
'name': fields.char('Name', select=1),
|
|
||||||
'active': fields.boolean('Active', help="If you uncheck the active field, it will disable the record rule without deleting it (if you delete a native record rule, it may be re-created when you reload the module)."),
|
|
||||||
'model_id': fields.many2one('ir.model', 'Object',select=1, required=True, ondelete="cascade"),
|
|
||||||
'global': fields.function(_get_value, string='Global', type='boolean', store=True, help="If no group is specified the rule is global and applied to everyone"),
|
|
||||||
'groups': fields.many2many('res.groups', 'rule_group_rel', 'rule_group_id', 'group_id', 'Groups'),
|
|
||||||
'domain_force': fields.text('Domain'),
|
|
||||||
'domain': fields.function(_domain_force_get, string='Domain', type='binary'),
|
|
||||||
'perm_read': fields.boolean('Apply for Read'),
|
|
||||||
'perm_write': fields.boolean('Apply for Write'),
|
|
||||||
'perm_create': fields.boolean('Apply for Create'),
|
|
||||||
'perm_unlink': fields.boolean('Apply for Delete')
|
|
||||||
}
|
|
||||||
|
|
||||||
_order = 'model_id DESC'
|
|
||||||
|
|
||||||
_defaults = {
|
|
||||||
'active': True,
|
|
||||||
'perm_read': True,
|
|
||||||
'perm_write': True,
|
|
||||||
'perm_create': True,
|
|
||||||
'perm_unlink': True,
|
|
||||||
'global': True,
|
|
||||||
}
|
|
||||||
_sql_constraints = [
|
_sql_constraints = [
|
||||||
('no_access_rights', 'CHECK (perm_read!=False or perm_write!=False or perm_create!=False or perm_unlink!=False)', 'Rule must have at least one checked access right !'),
|
('no_access_rights', 'CHECK (perm_read!=False or perm_write!=False or perm_create!=False or perm_unlink!=False)', 'Rule must have at least one checked access right !'),
|
||||||
]
|
]
|
||||||
_constraints = [
|
|
||||||
(_check_model_obj, 'Rules can not be applied on Transient models.', ['model_id']),
|
def _eval_context_for_combinations(self):
|
||||||
(_check_model_name, 'Rules can not be applied on the Record Rules model.', ['model_id']),
|
"""Returns a dictionary to use as evaluation context for
|
||||||
]
|
ir.rule domains, when the goal is to obtain python lists
|
||||||
|
that are easier to parse and combine, but not to
|
||||||
|
actually execute them."""
|
||||||
|
return {'user': unquote('user'),
|
||||||
|
'time': unquote('time')}
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _eval_context(self):
|
||||||
|
"""Returns a dictionary to use as evaluation context for
|
||||||
|
ir.rule domains."""
|
||||||
|
return {'user': self.env.user, 'time': time}
|
||||||
|
|
||||||
|
@api.depends('domain')
|
||||||
|
def _domain_force_get(self):
|
||||||
|
eval_context = self._eval_context()
|
||||||
|
for rule in self:
|
||||||
|
if rule.domain_force:
|
||||||
|
rule.domain = expression.normalize_domain(eval(rule.domain_force, eval_context))
|
||||||
|
else:
|
||||||
|
rule.domain = []
|
||||||
|
|
||||||
|
@api.constrains('model_id')
|
||||||
|
def _check_model_obj(self):
|
||||||
|
if any(self.pool[rule.model_id.model].is_transient() for rule in self):
|
||||||
|
raise UserWarning(_('Rules can not be applied on Transient models.'))
|
||||||
|
|
||||||
|
@api.constrains('model_id')
|
||||||
|
def _check_model_name(self):
|
||||||
|
# Don't allow rules on rules records (this model).
|
||||||
|
if any(rule.model_id.model == self._name for rule in self):
|
||||||
|
raise UserWarning(_('Rules can not be applied on the Record Rules model.'))
|
||||||
|
|
||||||
@tools.ormcache('uid', 'model_name', 'mode')
|
@tools.ormcache('uid', 'model_name', 'mode')
|
||||||
def _compute_domain(self, cr, uid, model_name, mode="read"):
|
def _compute_domain(self, cr, uid, model_name, mode="read"):
|
||||||
@@ -125,32 +116,37 @@ class ir_rule(osv.osv):
|
|||||||
return domain
|
return domain
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def clear_cache(self, cr, uid):
|
@api.model
|
||||||
|
def clear_cache(self):
|
||||||
""" Deprecated, use `clear_caches` instead. """
|
""" Deprecated, use `clear_caches` instead. """
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
|
|
||||||
def domain_get(self, cr, uid, model_name, mode='read', context=None):
|
@api.model
|
||||||
dom = self._compute_domain(cr, uid, model_name, mode)
|
def domain_get(self, model_name, mode='read'):
|
||||||
|
dom = self._compute_domain(model_name, mode)
|
||||||
if dom:
|
if dom:
|
||||||
# _where_calc is called as superuser. This means that rules can
|
# _where_calc is called as superuser. This means that rules can
|
||||||
# involve objects on which the real uid has no acces rights.
|
# involve objects on which the real uid has no acces rights.
|
||||||
# This means also there is no implicit restriction (e.g. an object
|
# This means also there is no implicit restriction (e.g. an object
|
||||||
# references another object the user can't see).
|
# references another object the user can't see).
|
||||||
query = self.pool[model_name]._where_calc(cr, SUPERUSER_ID, dom, active_test=False)
|
query = self.env[model_name].sudo()._where_calc(dom, active_test=False)
|
||||||
return query.where_clause, query.where_clause_params, query.tables
|
return query.where_clause, query.where_clause_params, query.tables
|
||||||
return [], [], ['"' + self.pool[model_name]._table + '"']
|
return [], [], ['"' + self.env[model_name]._table + '"']
|
||||||
|
|
||||||
def unlink(self, cr, uid, ids, context=None):
|
@api.multi
|
||||||
res = super(ir_rule, self).unlink(cr, uid, ids, context=context)
|
def unlink(self):
|
||||||
|
res = super(IrRule, self).unlink()
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def create(self, cr, uid, vals, context=None):
|
@api.model
|
||||||
res = super(ir_rule, self).create(cr, uid, vals, context=context)
|
def create(self, vals):
|
||||||
|
res = super(IrRule, self).create(vals)
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def write(self, cr, uid, ids, vals, context=None):
|
@api.multi
|
||||||
res = super(ir_rule, self).write(cr, uid, ids, vals, context=context)
|
def write(self, vals):
|
||||||
|
res = super(IrRule, self).write(vals)
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
return res
|
return res
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
<!-- Rules -->
|
<!-- Rules -->
|
||||||
<record id="view_rule_form" model="ir.ui.view">
|
<record id="view_rule_form" model="ir.ui.view">
|
||||||
<field name="model">ir.rule</field>
|
<field name="model">ir.rule</field>
|
||||||
@@ -95,5 +94,4 @@
|
|||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
from datetime import datetime, timedelta
|
||||||
import logging
|
import logging
|
||||||
import pytz
|
import pytz
|
||||||
import time
|
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from odoo import api, fields, models, _
|
||||||
from openerp import _, api, fields, models
|
from odoo.exceptions import UserError
|
||||||
from openerp.exceptions import UserError
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -21,6 +20,7 @@ def _create_sequence(cr, seq_name, number_increment, number_next):
|
|||||||
sql = "CREATE SEQUENCE %s INCREMENT BY %%s START WITH %%s" % seq_name
|
sql = "CREATE SEQUENCE %s INCREMENT BY %%s START WITH %%s" % seq_name
|
||||||
cr.execute(sql, (number_increment, number_next))
|
cr.execute(sql, (number_increment, number_next))
|
||||||
|
|
||||||
|
|
||||||
def _drop_sequence(cr, seq_names):
|
def _drop_sequence(cr, seq_names):
|
||||||
""" Drop the PostreSQL sequence if it exists.
|
""" Drop the PostreSQL sequence if it exists.
|
||||||
|
|
||||||
@@ -34,6 +34,7 @@ def _drop_sequence(cr, seq_names):
|
|||||||
# object depends on it.
|
# object depends on it.
|
||||||
cr.execute("DROP SEQUENCE IF EXISTS %s RESTRICT " % names)
|
cr.execute("DROP SEQUENCE IF EXISTS %s RESTRICT " % names)
|
||||||
|
|
||||||
|
|
||||||
def _alter_sequence(cr, seq_name, number_increment=None, number_next=None):
|
def _alter_sequence(cr, seq_name, number_increment=None, number_next=None):
|
||||||
""" Alter a PostreSQL sequence.
|
""" Alter a PostreSQL sequence.
|
||||||
|
|
||||||
@@ -52,10 +53,12 @@ def _alter_sequence(cr, seq_name, number_increment=None, number_next=None):
|
|||||||
statement += " RESTART WITH %d" % (number_next, )
|
statement += " RESTART WITH %d" % (number_next, )
|
||||||
cr.execute(statement)
|
cr.execute(statement)
|
||||||
|
|
||||||
|
|
||||||
def _select_nextval(cr, seq_name):
|
def _select_nextval(cr, seq_name):
|
||||||
cr.execute("SELECT nextval('%s')" % seq_name)
|
cr.execute("SELECT nextval('%s')" % seq_name)
|
||||||
return cr.fetchone()
|
return cr.fetchone()
|
||||||
|
|
||||||
|
|
||||||
def _update_nogap(self, number_increment):
|
def _update_nogap(self, number_increment):
|
||||||
number_next = self.number_next
|
number_next = self.number_next
|
||||||
self.env.cr.execute("SELECT number_next FROM %s WHERE id=%s FOR UPDATE NOWAIT" % (self._table, self.id))
|
self.env.cr.execute("SELECT number_next FROM %s WHERE id=%s FOR UPDATE NOWAIT" % (self._table, self.id))
|
||||||
@@ -64,7 +67,7 @@ def _update_nogap(self, number_increment):
|
|||||||
return number_next
|
return number_next
|
||||||
|
|
||||||
|
|
||||||
class ir_sequence(models.Model):
|
class IrSequence(models.Model):
|
||||||
""" Sequence model.
|
""" Sequence model.
|
||||||
|
|
||||||
The sequence model allows to define and use so-called sequence objects.
|
The sequence model allows to define and use so-called sequence objects.
|
||||||
@@ -97,30 +100,29 @@ class ir_sequence(models.Model):
|
|||||||
record.write({'number_next': record.number_next_actual or 0})
|
record.write({'number_next': record.number_next_actual or 0})
|
||||||
|
|
||||||
name = fields.Char(required=True)
|
name = fields.Char(required=True)
|
||||||
code = fields.Char('Sequence Code')
|
code = fields.Char(strring='Sequence Code')
|
||||||
implementation = fields.Selection(
|
implementation = fields.Selection([('standard', 'Standard'), ('no_gap', 'No gap')],
|
||||||
[('standard', 'Standard'), ('no_gap', 'No gap')],
|
string='Implementation', required=True, default='standard',
|
||||||
'Implementation', required=True, default='standard',
|
help="Two sequence object implementations are offered: Standard "
|
||||||
help="Two sequence object implementations are offered: Standard "
|
"and 'No gap'. The later is slower than the former but forbids any"
|
||||||
"and 'No gap'. The later is slower than the former but forbids any"
|
"gap in the sequence (while they are possible in the former).")
|
||||||
" gap in the sequence (while they are possible in the former).")
|
|
||||||
active = fields.Boolean(default=True)
|
active = fields.Boolean(default=True)
|
||||||
prefix = fields.Char(help="Prefix value of the record for the sequence")
|
prefix = fields.Char(help="Prefix value of the record for the sequence")
|
||||||
suffix = fields.Char(help="Suffix value of the record for the sequence")
|
suffix = fields.Char(help="Suffix value of the record for the sequence")
|
||||||
number_next = fields.Integer('Next Number', required=True, default=1, help="Next number of this sequence")
|
number_next = fields.Integer(string='Next Number', required=True, default=1, help="Next number of this sequence")
|
||||||
number_next_actual = fields.Integer(compute='_get_number_next_actual', inverse='_set_number_next_actual',
|
number_next_actual = fields.Integer(compute='_get_number_next_actual', inverse='_set_number_next_actual',
|
||||||
required=True, string='Next Number', default=1,
|
required=True, string='Next Number', default=1,
|
||||||
help="Next number that will be used. This number can be incremented "
|
help="Next number that will be used. This number can be incremented "
|
||||||
"frequently so the displayed value might already be obsolete")
|
"frequently so the displayed value might already be obsolete")
|
||||||
number_increment = fields.Integer('Step', required=True, default=1,
|
number_increment = fields.Integer(string='Step', required=True, default=1,
|
||||||
help="The next number of the sequence will be incremented by this number")
|
help="The next number of the sequence will be incremented by this number")
|
||||||
padding = fields.Integer('Sequence Size', required=True, default=0,
|
padding = fields.Integer(string='Sequence Size', required=True, default=0,
|
||||||
help="Odoo will automatically adds some '0' on the left of the "
|
help="Odoo will automatically adds some '0' on the left of the "
|
||||||
"'Next Number' to get the required padding size.")
|
"'Next Number' to get the required padding size.")
|
||||||
company_id = fields.Many2one('res.company', 'Company',
|
company_id = fields.Many2one('res.company', string='Company',
|
||||||
default=lambda s: s.env['res.company']._company_default_get('ir.sequence'))
|
default=lambda s: s.env['res.company']._company_default_get('ir.sequence'))
|
||||||
use_date_range = fields.Boolean('Use subsequences per date_range')
|
use_date_range = fields.Boolean(string='Use subsequences per date_range')
|
||||||
date_range_ids = fields.One2many('ir.sequence.date_range', 'sequence_id', 'Subsequences')
|
date_range_ids = fields.One2many('ir.sequence.date_range', 'sequence_id', string='Subsequences')
|
||||||
|
|
||||||
def init(self, cr):
|
def init(self, cr):
|
||||||
return # Don't do the following index yet.
|
return # Don't do the following index yet.
|
||||||
@@ -141,7 +143,7 @@ class ir_sequence(models.Model):
|
|||||||
def create(self, values):
|
def create(self, values):
|
||||||
""" Create a sequence, in implementation == standard a fast gaps-allowed PostgreSQL sequence is used.
|
""" Create a sequence, in implementation == standard a fast gaps-allowed PostgreSQL sequence is used.
|
||||||
"""
|
"""
|
||||||
seq = super(ir_sequence, self).create(values)
|
seq = super(IrSequence, self).create(values)
|
||||||
if values.get('implementation', 'standard') == 'standard':
|
if values.get('implementation', 'standard') == 'standard':
|
||||||
_create_sequence(self.env.cr, "ir_sequence_%03d" % seq.id, values.get('number_increment', 1), values.get('number_next', 1))
|
_create_sequence(self.env.cr, "ir_sequence_%03d" % seq.id, values.get('number_increment', 1), values.get('number_next', 1))
|
||||||
return seq
|
return seq
|
||||||
@@ -149,7 +151,7 @@ class ir_sequence(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def unlink(self):
|
def unlink(self):
|
||||||
_drop_sequence(self.env.cr, ["ir_sequence_%03d" % x.id for x in self])
|
_drop_sequence(self.env.cr, ["ir_sequence_%03d" % x.id for x in self])
|
||||||
return super(ir_sequence, self).unlink()
|
return super(IrSequence, self).unlink()
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def write(self, values):
|
def write(self, values):
|
||||||
@@ -178,7 +180,7 @@ class ir_sequence(models.Model):
|
|||||||
_create_sequence(self.env.cr, "ir_sequence_%03d" % seq.id, i, n)
|
_create_sequence(self.env.cr, "ir_sequence_%03d" % seq.id, i, n)
|
||||||
for sub_seq in seq.date_range_ids:
|
for sub_seq in seq.date_range_ids:
|
||||||
_create_sequence(self.env.cr, "ir_sequence_%03d_%03d" % (seq.id, sub_seq.id), i, n)
|
_create_sequence(self.env.cr, "ir_sequence_%03d_%03d" % (seq.id, sub_seq.id), i, n)
|
||||||
return super(ir_sequence, self).write(values)
|
return super(IrSequence, self).write(values)
|
||||||
|
|
||||||
def _next_do(self):
|
def _next_do(self):
|
||||||
if self.implementation == 'standard':
|
if self.implementation == 'standard':
|
||||||
@@ -308,7 +310,7 @@ class ir_sequence(models.Model):
|
|||||||
return self.get_id(code, 'code')
|
return self.get_id(code, 'code')
|
||||||
|
|
||||||
|
|
||||||
class ir_sequence_date_range(models.Model):
|
class IrSequenceDateRange(models.Model):
|
||||||
_name = 'ir.sequence.date_range'
|
_name = 'ir.sequence.date_range'
|
||||||
_rec_name = "sequence_id"
|
_rec_name = "sequence_id"
|
||||||
|
|
||||||
@@ -321,8 +323,7 @@ class ir_sequence_date_range(models.Model):
|
|||||||
else:
|
else:
|
||||||
# get number from postgres sequence. Cannot use currval, because that might give an error when
|
# get number from postgres sequence. Cannot use currval, because that might give an error when
|
||||||
# not having used nextval before.
|
# not having used nextval before.
|
||||||
query = "SELECT last_value, increment_by, is_called FROM ir_sequence_%03d_%03d" % (element.sequence_id.id, element.id)
|
self.env.cr.execute("SELECT last_value, increment_by, is_called FROM ir_sequence_%03d_%03d" % (element.sequence_id.id, element.id))
|
||||||
self.env.cr.execute(query)
|
|
||||||
(last_value, increment_by, is_called) = self.env.cr.fetchone()
|
(last_value, increment_by, is_called) = self.env.cr.fetchone()
|
||||||
if is_called:
|
if is_called:
|
||||||
element.number_next_actual = last_value + increment_by
|
element.number_next_actual = last_value + increment_by
|
||||||
@@ -333,14 +334,14 @@ class ir_sequence_date_range(models.Model):
|
|||||||
for record in self:
|
for record in self:
|
||||||
record.write({'number_next': record.number_next_actual or 0})
|
record.write({'number_next': record.number_next_actual or 0})
|
||||||
|
|
||||||
date_from = fields.Date('From', required=True)
|
date_from = fields.Date(string='From', required=True)
|
||||||
date_to = fields.Date('To', required=True)
|
date_to = fields.Date(string='To', required=True)
|
||||||
sequence_id = fields.Many2one("ir.sequence", 'Main Sequence', required=True, ondelete='cascade')
|
sequence_id = fields.Many2one("ir.sequence", string='Main Sequence', required=True, ondelete='cascade')
|
||||||
number_next = fields.Integer('Next Number', required=True, default=1, help="Next number of this sequence")
|
number_next = fields.Integer(string='Next Number', required=True, default=1, help="Next number of this sequence")
|
||||||
number_next_actual = fields.Integer(compute='_get_number_next_actual', inverse='_set_number_next_actual',
|
number_next_actual = fields.Integer(compute='_get_number_next_actual', inverse='_set_number_next_actual',
|
||||||
required=True, string='Next Number', default=1,
|
required=True, string='Next Number', default=1,
|
||||||
help="Next number that will be used. This number can be incremented "
|
help="Next number that will be used. This number can be incremented "
|
||||||
"frequently so the displayed value might already be obsolete")
|
"frequently so the displayed value might already be obsolete")
|
||||||
|
|
||||||
def _next(self):
|
def _next(self):
|
||||||
if self.sequence_id.implementation == 'standard':
|
if self.sequence_id.implementation == 'standard':
|
||||||
@@ -358,7 +359,7 @@ class ir_sequence_date_range(models.Model):
|
|||||||
def create(self, values):
|
def create(self, values):
|
||||||
""" Create a sequence, in implementation == standard a fast gaps-allowed PostgreSQL sequence is used.
|
""" Create a sequence, in implementation == standard a fast gaps-allowed PostgreSQL sequence is used.
|
||||||
"""
|
"""
|
||||||
seq = super(ir_sequence_date_range, self).create(values)
|
seq = super(IrSequenceDateRange, self).create(values)
|
||||||
main_seq = seq.sequence_id
|
main_seq = seq.sequence_id
|
||||||
if main_seq.implementation == 'standard':
|
if main_seq.implementation == 'standard':
|
||||||
_create_sequence(self.env.cr, "ir_sequence_%03d_%03d" % (main_seq.id, seq.id), main_seq.number_increment, values.get('number_next_actual', 1))
|
_create_sequence(self.env.cr, "ir_sequence_%03d_%03d" % (main_seq.id, seq.id), main_seq.number_increment, values.get('number_next_actual', 1))
|
||||||
@@ -367,11 +368,11 @@ class ir_sequence_date_range(models.Model):
|
|||||||
@api.multi
|
@api.multi
|
||||||
def unlink(self):
|
def unlink(self):
|
||||||
_drop_sequence(self.env.cr, ["ir_sequence_%03d_%03d" % (x.sequence_id.id, x.id) for x in self])
|
_drop_sequence(self.env.cr, ["ir_sequence_%03d_%03d" % (x.sequence_id.id, x.id) for x in self])
|
||||||
return super(ir_sequence_date_range, self).unlink()
|
return super(IrSequenceDateRange, self).unlink()
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def write(self, values):
|
def write(self, values):
|
||||||
if values.get('number_next'):
|
if values.get('number_next'):
|
||||||
seq_to_alter = self.filtered(lambda seq: seq.sequence_id.implementation == 'standard')
|
seq_to_alter = self.filtered(lambda seq: seq.sequence_id.implementation == 'standard')
|
||||||
seq_to_alter._alter_sequence(number_next=values.get('number_next'))
|
seq_to_alter._alter_sequence(number_next=values.get('number_next'))
|
||||||
return super(ir_sequence_date_range, self).write(values)
|
return super(IrSequenceDateRange, self).write(values)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
<!-- Sequences -->
|
<!-- Sequences -->
|
||||||
<record id="sequence_view" model="ir.ui.view">
|
<record id="sequence_view" model="ir.ui.view">
|
||||||
<field name="model">ir.sequence</field>
|
<field name="model">ir.sequence</field>
|
||||||
@@ -105,5 +104,4 @@
|
|||||||
</record>
|
</record>
|
||||||
<menuitem action="ir_sequence_form" id="menu_ir_sequence_form" parent="next_id_5"/>
|
<menuitem action="ir_sequence_form" id="menu_ir_sequence_form" parent="next_id_5"/>
|
||||||
|
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import logging
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from difflib import get_close_matches
|
from difflib import get_close_matches
|
||||||
import logging
|
|
||||||
|
|
||||||
from openerp import api, tools, SUPERUSER_ID
|
import odoo
|
||||||
import openerp.modules
|
from odoo import api, fields, models, tools, _
|
||||||
from openerp.osv import fields, osv
|
from odoo.exceptions import AccessError, UserError, ValidationError
|
||||||
from openerp.tools.translate import _
|
|
||||||
from openerp.exceptions import AccessError, UserError, ValidationError
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -25,7 +23,8 @@ TRANSLATION_TYPE = [
|
|||||||
('sql_constraint', 'SQL Constraint')
|
('sql_constraint', 'SQL Constraint')
|
||||||
]
|
]
|
||||||
|
|
||||||
class ir_translation_import_cursor(object):
|
|
||||||
|
class IrTranslationImportCursor(object):
|
||||||
"""Temporary cursor for optimizing mass insert into ir.translation
|
"""Temporary cursor for optimizing mass insert into ir.translation
|
||||||
|
|
||||||
Open it (attached to a sql cursor), feed it with translation data and
|
Open it (attached to a sql cursor), feed it with translation data and
|
||||||
@@ -168,28 +167,48 @@ class ir_translation_import_cursor(object):
|
|||||||
cr.execute("DROP TABLE %s" % self._table_name)
|
cr.execute("DROP TABLE %s" % self._table_name)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
class ir_translation(osv.osv):
|
|
||||||
|
class IrTranslation(models.Model):
|
||||||
_name = "ir.translation"
|
_name = "ir.translation"
|
||||||
_log_access = False
|
_log_access = False
|
||||||
|
|
||||||
def _get_language(self, cr, uid, context):
|
@api.model
|
||||||
lang_model = self.pool.get('res.lang')
|
def _get_language(self):
|
||||||
lang_ids = lang_model.search(cr, uid, [('translatable', '=', True)], context=context)
|
lang_model = self.env['res.lang']
|
||||||
lang_data = lang_model.read(cr, uid, lang_ids, ['code', 'name'], context=context)
|
lang = lang_model.search([('translatable', '=', True)])
|
||||||
return [(d['code'], d['name']) for d in lang_data]
|
return [(data.code, data.name) for data in lang]
|
||||||
|
|
||||||
def _get_src(self, cr, uid, ids, name, arg, context=None):
|
name = fields.Char(string='Translated field', required=True)
|
||||||
|
res_id = fields.Integer(string='Record ID', index=True)
|
||||||
|
lang = fields.Selection(selection='_get_language', string='Language')
|
||||||
|
type = fields.Selection(TRANSLATION_TYPE, string='Type', index=True)
|
||||||
|
src = fields.Text(string='Internal Source') # stored in database, kept for backward compatibility
|
||||||
|
source = fields.Text(compute='_get_src', inverse='_set_src', search='_search_src', string='Source term')
|
||||||
|
value = fields.Text(string='Translation Value')
|
||||||
|
module = fields.Char(index=True, help="Module this term belongs to")
|
||||||
|
|
||||||
|
state = fields.Selection([('to_translate', 'To Translate'),
|
||||||
|
('inprogress', 'Translation in Progress'),
|
||||||
|
('translated', 'Translated')],
|
||||||
|
string="Status", default='to_translate',
|
||||||
|
help="Automatically set to let administators find new terms that might need to be translated")
|
||||||
|
|
||||||
|
# aka gettext extracted-comments - we use them to flag openerp-web translation
|
||||||
|
# cfr: http://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/PO-Files.html
|
||||||
|
comments = fields.Text(string='Translation comments', index=True)
|
||||||
|
|
||||||
|
_sql_constraints = [('lang_fkey_res_lang', 'FOREIGN KEY(lang) REFERENCES res_lang(code)',
|
||||||
|
'Language code of translation item must be among known languages')]
|
||||||
|
|
||||||
|
@api.depends('source')
|
||||||
|
def _get_src(self):
|
||||||
''' Get source name for the translation. If object type is model then
|
''' Get source name for the translation. If object type is model then
|
||||||
return the value store in db. Otherwise return value store in src field
|
return the value store in db. Otherwise return value store in src field
|
||||||
'''
|
'''
|
||||||
if context is None:
|
for record in self:
|
||||||
context = {}
|
|
||||||
res = dict.fromkeys(ids, False)
|
|
||||||
for record in self.browse(cr, uid, ids, context=context):
|
|
||||||
res[record.id] = record.src
|
|
||||||
if record.type == 'model':
|
if record.type == 'model':
|
||||||
model_name, field_name = record.name.split(',')
|
model_name, field_name = record.name.split(',')
|
||||||
model = self.pool.get(model_name)
|
model = self.env[model_name]
|
||||||
if model is None:
|
if model is None:
|
||||||
continue
|
continue
|
||||||
field = model._fields.get(field_name)
|
field = model._fields.get(field_name)
|
||||||
@@ -197,74 +216,41 @@ class ir_translation(osv.osv):
|
|||||||
continue
|
continue
|
||||||
if not callable(field.translate):
|
if not callable(field.translate):
|
||||||
# Pass context without lang, need to read real stored field, not translation
|
# Pass context without lang, need to read real stored field, not translation
|
||||||
context_no_lang = dict(context, lang=None)
|
context_no_lang = dict(self.env.context, lang=None)
|
||||||
result = model.read(cr, uid, [record.res_id], [field_name], context=context_no_lang)
|
result = model.with_context(context_no_lang).browse(record.res_id).read([field_name])
|
||||||
res[record.id] = result[0][field_name] if result else False
|
record.source = result[0][field_name] if result else False
|
||||||
return res
|
|
||||||
|
|
||||||
def _set_src(self, cr, uid, id, name, value, args, context=None):
|
def _set_src(self):
|
||||||
''' When changing source term of a translation, change its value in db for
|
''' When changing source term of a translation, change its value in db for
|
||||||
the associated object, and the src field
|
the associated object, and the src field
|
||||||
'''
|
'''
|
||||||
if context is None:
|
if self.type == 'model':
|
||||||
context = {}
|
model_name, field_name = self.name.split(',')
|
||||||
record = self.browse(cr, uid, id, context=context)
|
model = self.env[model_name]
|
||||||
if record.type == 'model':
|
|
||||||
model_name, field_name = record.name.split(',')
|
|
||||||
model = self.pool.get(model_name)
|
|
||||||
field = model._fields[field_name]
|
field = model._fields[field_name]
|
||||||
if not callable(field.translate):
|
if not callable(field.translate):
|
||||||
# Make a context without language information, because we want
|
# Make a context without language information, because we want
|
||||||
# to write on the value stored in db and not on the one
|
# to write on the value stored in db and not on the one
|
||||||
# associated with the current language. Also not removing lang
|
# associated with the current language. Also not removing lang
|
||||||
# from context trigger an error when lang is different.
|
# from context trigger an error when lang is different.
|
||||||
context_wo_lang = context.copy()
|
context_wo_lang = self.env.context.copy()
|
||||||
context_wo_lang.pop('lang', None)
|
context_wo_lang.pop('lang', None)
|
||||||
model.write(cr, uid, [record.res_id], {field_name: value}, context=context_wo_lang)
|
model.with_context(context_wo_lang).browse(self.res_id).write({field_name: self.source})
|
||||||
return self.write(cr, uid, id, {'src': value}, context=context)
|
return self.write({'src': self.source})
|
||||||
|
|
||||||
def _search_src(self, cr, uid, obj, name, args, context):
|
@api.model
|
||||||
|
def _search_src(self, operator, operand):
|
||||||
''' the source term is stored on 'src' field '''
|
''' the source term is stored on 'src' field '''
|
||||||
res = []
|
res = []
|
||||||
for field, operator, value in args:
|
for field, operator, value in operand:
|
||||||
res.append(('src', operator, value))
|
res.append(('src', operator, value))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
_columns = {
|
def _auto_init(self):
|
||||||
'name': fields.char('Translated field', required=True),
|
super(IrTranslation, self)._auto_init()
|
||||||
'res_id': fields.integer('Record ID', select=True),
|
|
||||||
'lang': fields.selection(_get_language, string='Language'),
|
|
||||||
'type': fields.selection(TRANSLATION_TYPE, string='Type', select=True),
|
|
||||||
'src': fields.text('Internal Source'), # stored in database, kept for backward compatibility
|
|
||||||
'source': fields.function(_get_src, fnct_inv=_set_src, fnct_search=_search_src,
|
|
||||||
type='text', string='Source term'),
|
|
||||||
'value': fields.text('Translation Value'),
|
|
||||||
'module': fields.char('Module', help="Module this term belongs to", select=True),
|
|
||||||
|
|
||||||
'state': fields.selection(
|
self.env.cr.execute("SELECT indexname FROM pg_indexes WHERE indexname LIKE 'ir_translation_%'")
|
||||||
[('to_translate','To Translate'),
|
indexes = [row[0] for row in self.env.cr.fetchall()]
|
||||||
('inprogress','Translation in Progress'),
|
|
||||||
('translated','Translated')],
|
|
||||||
string="Status",
|
|
||||||
help="Automatically set to let administators find new terms that might need to be translated"),
|
|
||||||
|
|
||||||
# aka gettext extracted-comments - we use them to flag openerp-web translation
|
|
||||||
# cfr: http://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/PO-Files.html
|
|
||||||
'comments': fields.text('Translation comments', select=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
_defaults = {
|
|
||||||
'state': 'to_translate',
|
|
||||||
}
|
|
||||||
|
|
||||||
_sql_constraints = [ ('lang_fkey_res_lang', 'FOREIGN KEY(lang) REFERENCES res_lang(code)',
|
|
||||||
'Language code of translation item must be among known languages' ), ]
|
|
||||||
|
|
||||||
def _auto_init(self, cr, context=None):
|
|
||||||
super(ir_translation, self)._auto_init(cr, context)
|
|
||||||
|
|
||||||
cr.execute("SELECT indexname FROM pg_indexes WHERE indexname LIKE 'ir_translation_%'")
|
|
||||||
indexes = [row[0] for row in cr.fetchall()]
|
|
||||||
|
|
||||||
# Removed because there is a size limit on btree indexed values (problem with column src):
|
# Removed because there is a size limit on btree indexed values (problem with column src):
|
||||||
# cr.execute('CREATE INDEX ir_translation_ltns ON ir_translation (name, lang, type, src)')
|
# cr.execute('CREATE INDEX ir_translation_ltns ON ir_translation (name, lang, type, src)')
|
||||||
@@ -273,65 +259,65 @@ class ir_translation(osv.osv):
|
|||||||
# Removed because hash indexes are not compatible with postgres streaming replication:
|
# Removed because hash indexes are not compatible with postgres streaming replication:
|
||||||
# cr.execute('CREATE INDEX ir_translation_src_hash_idx ON ir_translation USING hash (src)')
|
# cr.execute('CREATE INDEX ir_translation_src_hash_idx ON ir_translation USING hash (src)')
|
||||||
if set(indexes) & set(['ir_translation_ltns', 'ir_translation_lts', 'ir_translation_src_hash_idx']):
|
if set(indexes) & set(['ir_translation_ltns', 'ir_translation_lts', 'ir_translation_src_hash_idx']):
|
||||||
cr.execute('DROP INDEX IF EXISTS ir_translation_ltns, ir_translation_lts, ir_translation_src_hash_idx')
|
self.env.cr.execute('DROP INDEX IF EXISTS ir_translation_ltns, ir_translation_lts, ir_translation_src_hash_idx')
|
||||||
cr.commit()
|
self.env.cr.commit()
|
||||||
|
|
||||||
# Add separate md5 index on src (no size limit on values, and good performance).
|
# Add separate md5 index on src (no size limit on values, and good performance).
|
||||||
if 'ir_translation_src_md5' not in indexes:
|
if 'ir_translation_src_md5' not in indexes:
|
||||||
cr.execute('CREATE INDEX ir_translation_src_md5 ON ir_translation (md5(src))')
|
self.env.cr.execute('CREATE INDEX ir_translation_src_md5 ON ir_translation (md5(src))')
|
||||||
cr.commit()
|
self.env.cr.commit()
|
||||||
|
|
||||||
if 'ir_translation_ltn' not in indexes:
|
if 'ir_translation_ltn' not in indexes:
|
||||||
cr.execute('CREATE INDEX ir_translation_ltn ON ir_translation (name, lang, type)')
|
self.env.cr.execute('CREATE INDEX ir_translation_ltn ON ir_translation (name, lang, type)')
|
||||||
cr.commit()
|
self.env.cr.commit()
|
||||||
|
|
||||||
def _check_selection_field_value(self, cr, uid, field, value, context=None):
|
@api.model
|
||||||
|
def _check_selection_field_value(self, field, value):
|
||||||
if field == 'lang':
|
if field == 'lang':
|
||||||
return
|
return
|
||||||
return super(ir_translation, self)._check_selection_field_value(cr, uid, field, value, context=context)
|
return super(IrTranslation, self)._check_selection_field_value(field, value)
|
||||||
|
|
||||||
def _get_ids(self, cr, uid, name, tt, lang, ids):
|
@api.model
|
||||||
translations = dict.fromkeys(ids, False)
|
def _get_ids(self, name, tt, lang, in_ids):
|
||||||
if ids:
|
if in_ids:
|
||||||
cr.execute('select res_id,value '
|
self.env.cr.execute('select res_id,value '
|
||||||
'from ir_translation '
|
'from ir_translation '
|
||||||
'where lang=%s '
|
'where lang=%s '
|
||||||
'and type=%s '
|
'and type=%s '
|
||||||
'and name=%s '
|
'and name=%s '
|
||||||
'and res_id IN %s',
|
'and res_id IN %s',
|
||||||
(lang,tt,name,tuple(ids)))
|
(lang, tt, name, tuple(in_ids)))
|
||||||
for res_id, value in cr.fetchall():
|
for res_id, value in self.env.cr.fetchall():
|
||||||
translations[res_id] = value
|
self.res_id = value
|
||||||
return translations
|
|
||||||
|
|
||||||
def _set_ids(self, cr, uid, name, tt, lang, ids, value, src=None):
|
@api.model
|
||||||
|
def _set_ids(self, name, tt, lang, rec_ids, value, src=None):
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
cr.execute('update ir_translation '
|
self.env.cr.execute('update ir_translation '
|
||||||
'set value=%s '
|
'set value=%s '
|
||||||
' , src=%s '
|
' , src=%s '
|
||||||
' , state=%s '
|
' , state=%s '
|
||||||
'where lang=%s '
|
'where lang=%s '
|
||||||
'and type=%s '
|
'and type=%s '
|
||||||
'and name=%s '
|
'and name=%s '
|
||||||
'and res_id IN %s '
|
'and res_id IN %s '
|
||||||
'returning res_id',
|
'returning res_id',
|
||||||
(value,src,'translated',lang,tt,name,tuple(ids),))
|
(value, src, 'translated', lang, tt, name, tuple(rec_ids),))
|
||||||
|
|
||||||
existing_ids = [x[0] for x in cr.fetchall()]
|
existing_ids = [x[0] for x in self.env.cr.fetchall()]
|
||||||
|
|
||||||
for id in list(set(ids) - set(existing_ids)):
|
for id in list(set(rec_ids) - set(existing_ids)):
|
||||||
self.create(cr, uid, {
|
self.create({'lang': lang,
|
||||||
'lang':lang,
|
'type': tt,
|
||||||
'type':tt,
|
'name': name,
|
||||||
'name':name,
|
'res_id': id,
|
||||||
'res_id':id,
|
'value': value,
|
||||||
'value':value,
|
'src': src,
|
||||||
'src':src,
|
'state': 'translated'}).id
|
||||||
'state':'translated'
|
return len(rec_ids)
|
||||||
})
|
|
||||||
return len(ids)
|
|
||||||
|
|
||||||
def _get_source_query(self, cr, uid, name, types, lang, source, res_id):
|
@api.model
|
||||||
|
def _get_source_query(self, name, types, lang, source, res_id):
|
||||||
if source:
|
if source:
|
||||||
# Note: the extra test on md5(src) is a hint for postgres to use the
|
# Note: the extra test on md5(src) is a hint for postgres to use the
|
||||||
# index ir_translation_src_md5
|
# index ir_translation_src_md5
|
||||||
@@ -356,21 +342,22 @@ class ir_translation(osv.osv):
|
|||||||
AND name=%s"""
|
AND name=%s"""
|
||||||
|
|
||||||
params = (lang or '', types, tools.ustr(name))
|
params = (lang or '', types, tools.ustr(name))
|
||||||
|
|
||||||
return (query, params)
|
return (query, params)
|
||||||
|
|
||||||
@tools.ormcache('name', 'types', 'lang', 'source', 'res_id')
|
@tools.ormcache('name', 'types', 'lang', 'source', 'res_id')
|
||||||
def __get_source(self, cr, uid, name, types, lang, source, res_id):
|
def __get_source(self, name, types, lang, source, res_id):
|
||||||
# res_id is a tuple or None, otherwise ormcache cannot cache it!
|
# res_id is a tuple or None, otherwise ormcache cannot cache it!
|
||||||
query, params = self._get_source_query(cr, uid, name, types, lang, source, res_id)
|
query, params = self._get_source_query(name, types, lang, source, res_id)
|
||||||
cr.execute(query, params)
|
self.env.cr.execute(query, params)
|
||||||
res = cr.fetchone()
|
res = self.env.cr.fetchone()
|
||||||
trad = res and res[0] or u''
|
trad = res and res[0] or u''
|
||||||
if source and not trad:
|
if source and not trad:
|
||||||
return tools.ustr(source)
|
return tools.ustr(source)
|
||||||
return trad
|
return trad
|
||||||
|
|
||||||
def _get_source(self, cr, uid, name, types, lang, source=None, res_id=None):
|
@api.model
|
||||||
|
def _get_source(self, name, types, lang, source=None, res_id=None):
|
||||||
"""
|
"""
|
||||||
Returns the translation for the given combination of name, type, language
|
Returns the translation for the given combination of name, type, language
|
||||||
and source. All values passed to this method should be unicode (not byte strings),
|
and source. All values passed to this method should be unicode (not byte strings),
|
||||||
@@ -396,7 +383,7 @@ class ir_translation(osv.osv):
|
|||||||
res_id = (res_id,)
|
res_id = (res_id,)
|
||||||
else:
|
else:
|
||||||
res_id = tuple(res_id)
|
res_id = tuple(res_id)
|
||||||
return self.__get_source(cr, uid, name, types, lang, source, res_id)
|
return self.__get_source(name, types, lang, source, res_id)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def _get_terms_query(self, field, records):
|
def _get_terms_query(self, field, records):
|
||||||
@@ -550,7 +537,7 @@ class ir_translation(osv.osv):
|
|||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def create(self, vals):
|
def create(self, vals):
|
||||||
record = super(ir_translation, self.sudo()).create(vals).with_env(self.env)
|
record = super(IrTranslation, self.sudo()).create(vals).with_env(self.env)
|
||||||
record.check('create')
|
record.check('create')
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
return record
|
return record
|
||||||
@@ -562,7 +549,7 @@ class ir_translation(osv.osv):
|
|||||||
elif vals.get('src') or not vals.get('value', True):
|
elif vals.get('src') or not vals.get('value', True):
|
||||||
vals.setdefault('state', 'to_translate')
|
vals.setdefault('state', 'to_translate')
|
||||||
self.check('write')
|
self.check('write')
|
||||||
result = super(ir_translation, self.sudo()).write(vals)
|
result = super(IrTranslation, self.sudo()).write(vals)
|
||||||
self.check('write')
|
self.check('write')
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
return result
|
return result
|
||||||
@@ -571,7 +558,7 @@ class ir_translation(osv.osv):
|
|||||||
def unlink(self):
|
def unlink(self):
|
||||||
self.check('unlink')
|
self.check('unlink')
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
return super(ir_translation, self.sudo()).unlink()
|
return super(IrTranslation, self.sudo()).unlink()
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def insert_missing(self, field, records):
|
def insert_missing(self, field, records):
|
||||||
@@ -675,17 +662,17 @@ class ir_translation(osv.osv):
|
|||||||
def _get_import_cursor(self, cr, uid, context=None):
|
def _get_import_cursor(self, cr, uid, context=None):
|
||||||
""" Return a cursor-like object for fast inserting translations
|
""" Return a cursor-like object for fast inserting translations
|
||||||
"""
|
"""
|
||||||
return ir_translation_import_cursor(cr, uid, self, context=context)
|
return IrTranslationImportCursor(cr, uid, self, context=context)
|
||||||
|
|
||||||
def load_module_terms(self, cr, modules, langs, context=None):
|
def load_module_terms(self, cr, modules, langs, context=None):
|
||||||
context_template = dict(context or {}) # local copy
|
context_template = dict(context or {}) # local copy
|
||||||
# make sure the given languages are active
|
# make sure the given languages are active
|
||||||
lang_obj = self.pool['res.lang']
|
lang_obj = self.pool['res.lang']
|
||||||
for lang in langs:
|
for lang in langs:
|
||||||
lang_obj.load_lang(cr, SUPERUSER_ID, lang)
|
lang_obj.load_lang(cr, odoo.SUPERUSER_ID, lang)
|
||||||
# load i18n files
|
# load i18n files
|
||||||
for module_name in modules:
|
for module_name in modules:
|
||||||
modpath = openerp.modules.get_module_path(module_name)
|
modpath = odoo.modules.get_module_path(module_name)
|
||||||
if not modpath:
|
if not modpath:
|
||||||
continue
|
continue
|
||||||
for lang in langs:
|
for lang in langs:
|
||||||
@@ -697,28 +684,28 @@ class ir_translation(osv.osv):
|
|||||||
|
|
||||||
# Step 1: for sub-languages, load base language first (e.g. es_CL.po is loaded over es.po)
|
# Step 1: for sub-languages, load base language first (e.g. es_CL.po is loaded over es.po)
|
||||||
if base_lang_code:
|
if base_lang_code:
|
||||||
base_trans_file = openerp.modules.get_module_resource(module_name, 'i18n', base_lang_code + '.po')
|
base_trans_file = odoo.modules.get_module_resource(module_name, 'i18n', base_lang_code + '.po')
|
||||||
if base_trans_file:
|
if base_trans_file:
|
||||||
_logger.info('module %s: loading base translation file %s for language %s', module_name, base_lang_code, lang)
|
_logger.info('module %s: loading base translation file %s for language %s', module_name, base_lang_code, lang)
|
||||||
tools.trans_load(cr, base_trans_file, lang, verbose=False, module_name=module_name, context=context)
|
tools.trans_load(cr, base_trans_file, lang, verbose=False, module_name=module_name, context=context)
|
||||||
context['overwrite'] = True # make sure the requested translation will override the base terms later
|
context['overwrite'] = True # make sure the requested translation will override the base terms later
|
||||||
|
|
||||||
# i18n_extra folder is for additional translations handle manually (eg: for l10n_be)
|
# i18n_extra folder is for additional translations handle manually (eg: for l10n_be)
|
||||||
base_trans_extra_file = openerp.modules.get_module_resource(module_name, 'i18n_extra', base_lang_code + '.po')
|
base_trans_extra_file = odoo.modules.get_module_resource(module_name, 'i18n_extra', base_lang_code + '.po')
|
||||||
if base_trans_extra_file:
|
if base_trans_extra_file:
|
||||||
_logger.info('module %s: loading extra base translation file %s for language %s', module_name, base_lang_code, lang)
|
_logger.info('module %s: loading extra base translation file %s for language %s', module_name, base_lang_code, lang)
|
||||||
tools.trans_load(cr, base_trans_extra_file, lang, verbose=False, module_name=module_name, context=context)
|
tools.trans_load(cr, base_trans_extra_file, lang, verbose=False, module_name=module_name, context=context)
|
||||||
context['overwrite'] = True # make sure the requested translation will override the base terms later
|
context['overwrite'] = True # make sure the requested translation will override the base terms later
|
||||||
|
|
||||||
# Step 2: then load the main translation file, possibly overriding the terms coming from the base language
|
# Step 2: then load the main translation file, possibly overriding the terms coming from the base language
|
||||||
trans_file = openerp.modules.get_module_resource(module_name, 'i18n', lang_code + '.po')
|
trans_file = odoo.modules.get_module_resource(module_name, 'i18n', lang_code + '.po')
|
||||||
if trans_file:
|
if trans_file:
|
||||||
_logger.info('module %s: loading translation file (%s) for language %s', module_name, lang_code, lang)
|
_logger.info('module %s: loading translation file (%s) for language %s', module_name, lang_code, lang)
|
||||||
tools.trans_load(cr, trans_file, lang, verbose=False, module_name=module_name, context=context)
|
tools.trans_load(cr, trans_file, lang, verbose=False, module_name=module_name, context=context)
|
||||||
elif lang_code != 'en_US':
|
elif lang_code != 'en_US':
|
||||||
_logger.info('module %s: no translation for language %s', module_name, lang_code)
|
_logger.info('module %s: no translation for language %s', module_name, lang_code)
|
||||||
|
|
||||||
trans_extra_file = openerp.modules.get_module_resource(module_name, 'i18n_extra', lang_code + '.po')
|
trans_extra_file = odoo.modules.get_module_resource(module_name, 'i18n_extra', lang_code + '.po')
|
||||||
if trans_extra_file:
|
if trans_extra_file:
|
||||||
_logger.info('module %s: loading extra translation file (%s) for language %s', module_name, lang_code, lang)
|
_logger.info('module %s: loading extra translation file (%s) for language %s', module_name, lang_code, lang)
|
||||||
tools.trans_load(cr, trans_extra_file, lang, verbose=False, module_name=module_name, context=context)
|
tools.trans_load(cr, trans_extra_file, lang, verbose=False, module_name=module_name, context=context)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
<!-- Translations -->
|
<!-- Translations -->
|
||||||
<record id="view_translation_search" model="ir.ui.view">
|
<record id="view_translation_search" model="ir.ui.view">
|
||||||
<field name="model">ir.translation</field>
|
<field name="model">ir.translation</field>
|
||||||
@@ -86,5 +85,4 @@
|
|||||||
|
|
||||||
<menuitem action="action_translation" id="menu_action_translation" parent="base.menu_translation_app" />
|
<menuitem action="action_translation" id="menu_action_translation" parent="base.menu_translation_app" />
|
||||||
|
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
@@ -4,25 +4,78 @@
|
|||||||
import base64
|
import base64
|
||||||
import operator
|
import operator
|
||||||
import re
|
import re
|
||||||
import threading
|
|
||||||
|
|
||||||
import openerp
|
import odoo
|
||||||
from openerp.osv import fields, osv
|
from odoo import api, fields, models, tools, _
|
||||||
from openerp import api, tools
|
from odoo.exceptions import ValidationError
|
||||||
from openerp.http import request
|
from odoo.http import request
|
||||||
from openerp.tools.safe_eval import safe_eval as eval
|
from odoo.tools.safe_eval import safe_eval as eval
|
||||||
from openerp.tools.translate import _
|
|
||||||
|
|
||||||
MENU_ITEM_SEPARATOR = "/"
|
MENU_ITEM_SEPARATOR = "/"
|
||||||
|
|
||||||
|
|
||||||
class ir_ui_menu(osv.osv):
|
class IrUiMenu(models.Model):
|
||||||
_name = 'ir.ui.menu'
|
_name = 'ir.ui.menu'
|
||||||
|
_order = "sequence,id"
|
||||||
|
_parent_store = True
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(ir_ui_menu, self).__init__(*args, **kwargs)
|
super(IrUiMenu, self).__init__(*args, **kwargs)
|
||||||
self.pool['ir.model.access'].register_cache_clearing_method(self._name, 'clear_caches')
|
self.pool['ir.model.access'].register_cache_clearing_method(self._name, 'clear_caches')
|
||||||
|
|
||||||
|
name = fields.Char(string='Menu', required=True, translate=True)
|
||||||
|
sequence = fields.Integer(default=10)
|
||||||
|
child_id = fields.One2many('ir.ui.menu', 'parent_id', string='Child IDs')
|
||||||
|
parent_id = fields.Many2one('ir.ui.menu', string='Parent Menu', index=True, ondelete="restrict")
|
||||||
|
parent_left = fields.Integer(index=True)
|
||||||
|
parent_right = fields.Integer(index=True)
|
||||||
|
groups_id = fields.Many2many('res.groups', 'ir_ui_menu_group_rel',
|
||||||
|
'menu_id', 'gid', string='Groups',
|
||||||
|
help="If you have groups, the visibility of this menu will be based on these groups. "\
|
||||||
|
"If this field is empty, Odoo will compute visibility based on the related object's read access.")
|
||||||
|
complete_name = fields.Char(compute='_get_full_name', string='Full Path')
|
||||||
|
web_icon = fields.Char(string='Web Icon File')
|
||||||
|
action = fields.Reference(selection=[('ir.actions.report.xml', 'ir.actions.report.xml'),
|
||||||
|
('ir.actions.act_window', 'ir.actions.act_window'),
|
||||||
|
('ir.actions.act_url', 'ir.actions.act_url'),
|
||||||
|
('ir.actions.server', 'ir.actions.server'),
|
||||||
|
('ir.actions.client', 'ir.actions.client')])
|
||||||
|
|
||||||
|
web_icon_data = fields.Binary(string='Web Icon Image', compute="_compute_web_icon", store=True, attachment=True)
|
||||||
|
|
||||||
|
@api.depends('web_icon')
|
||||||
|
def _compute_web_icon(self):
|
||||||
|
for menu in self:
|
||||||
|
menu.web_icon_data = self.read_image(menu.web_icon)
|
||||||
|
|
||||||
|
def read_image(self, path):
|
||||||
|
if not path:
|
||||||
|
return False
|
||||||
|
path_info = path.split(',')
|
||||||
|
icon_path = odoo.modules.get_module_resource(path_info[0], path_info[1])
|
||||||
|
icon_image = False
|
||||||
|
if icon_path:
|
||||||
|
try:
|
||||||
|
icon_file = tools.file_open(icon_path, 'rb')
|
||||||
|
icon_image = base64.encodestring(icon_file.read())
|
||||||
|
finally:
|
||||||
|
icon_file.close()
|
||||||
|
return icon_image
|
||||||
|
|
||||||
|
@api.depends('complete_name')
|
||||||
|
def _get_full_name(self):
|
||||||
|
for element in self:
|
||||||
|
element.complete_name = self._get_one_full_name(element)
|
||||||
|
|
||||||
|
def _get_one_full_name(self, elmt, level=6):
|
||||||
|
if level <= 0:
|
||||||
|
return '...'
|
||||||
|
if elmt.parent_id:
|
||||||
|
parent_path = self._get_one_full_name(elmt.parent_id, level-1) + MENU_ITEM_SEPARATOR
|
||||||
|
else:
|
||||||
|
parent_path = ''
|
||||||
|
return parent_path + elmt.name
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
@tools.ormcache('frozenset(self.env.user.groups_id.ids)', 'debug')
|
@tools.ormcache('frozenset(self.env.user.groups_id.ids)', 'debug')
|
||||||
def _visible_menu_ids(self, debug=False):
|
def _visible_menu_ids(self, debug=False):
|
||||||
@@ -75,7 +128,7 @@ class ir_ui_menu(osv.osv):
|
|||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
|
|
||||||
ids = super(ir_ui_menu, self).search(cr, uid, args, offset=0,
|
ids = super(IrUiMenu, self).search(cr, uid, args, offset=0,
|
||||||
limit=None, order=order, context=context, count=False)
|
limit=None, order=order, context=context, count=False)
|
||||||
|
|
||||||
if not ids:
|
if not ids:
|
||||||
@@ -98,99 +151,69 @@ class ir_ui_menu(osv.osv):
|
|||||||
return len(result)
|
return len(result)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def name_get(self, cr, uid, ids, context=None):
|
@api.multi
|
||||||
|
def name_get(self):
|
||||||
res = []
|
res = []
|
||||||
for id in ids:
|
for element in self:
|
||||||
elmt = self.browse(cr, uid, id, context=context)
|
res.append((element.id, self._get_one_full_name(element)))
|
||||||
res.append((id, self._get_one_full_name(elmt)))
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _get_full_name(self, cr, uid, ids, name=None, args=None, context=None):
|
@api.model
|
||||||
if context is None:
|
def create(self, values):
|
||||||
context = {}
|
|
||||||
res = {}
|
|
||||||
for elmt in self.browse(cr, uid, ids, context=context):
|
|
||||||
res[elmt.id] = self._get_one_full_name(elmt)
|
|
||||||
return res
|
|
||||||
|
|
||||||
def _get_one_full_name(self, elmt, level=6):
|
|
||||||
if level<=0:
|
|
||||||
return '...'
|
|
||||||
if elmt.parent_id:
|
|
||||||
parent_path = self._get_one_full_name(elmt.parent_id, level-1) + MENU_ITEM_SEPARATOR
|
|
||||||
else:
|
|
||||||
parent_path = ''
|
|
||||||
return parent_path + elmt.name
|
|
||||||
|
|
||||||
def create(self, cr, uid, values, context=None):
|
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
return super(ir_ui_menu, self).create(cr, uid, values, context=context)
|
return super(IrUiMenu, self).create(values)
|
||||||
|
|
||||||
def write(self, cr, uid, ids, values, context=None):
|
@api.multi
|
||||||
|
def write(self, values):
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
return super(ir_ui_menu, self).write(cr, uid, ids, values, context=context)
|
return super(IrUiMenu, self).write(values)
|
||||||
|
|
||||||
def unlink(self, cr, uid, ids, context=None):
|
@api.multi
|
||||||
|
def unlink(self):
|
||||||
# Detach children and promote them to top-level, because it would be unwise to
|
# Detach children and promote them to top-level, because it would be unwise to
|
||||||
# cascade-delete submenus blindly. We also can't use ondelete=set null because
|
# cascade-delete submenus blindly. We also can't use ondelete=set null because
|
||||||
# that is not supported when _parent_store is used (would silently corrupt it).
|
# that is not supported when _parent_store is used (would silently corrupt it).
|
||||||
# TODO: ideally we should move them under a generic "Orphans" menu somewhere?
|
# TODO: ideally we should move them under a generic "Orphans" menu somewhere?
|
||||||
if isinstance(ids, (int, long)):
|
local_context = dict(self.env.context or {})
|
||||||
ids = [ids]
|
|
||||||
local_context = dict(context or {})
|
|
||||||
local_context['ir.ui.menu.full_list'] = True
|
local_context['ir.ui.menu.full_list'] = True
|
||||||
direct_children_ids = self.search(cr, uid, [('parent_id', 'in', ids)], context=local_context)
|
direct_children = self.with_context(local_context).search([('parent_id', 'in', self.ids)])
|
||||||
if direct_children_ids:
|
if direct_children:
|
||||||
self.write(cr, uid, direct_children_ids, {'parent_id': False})
|
direct_children.write({'parent_id': False})
|
||||||
|
|
||||||
result = super(ir_ui_menu, self).unlink(cr, uid, ids, context=context)
|
result = super(IrUiMenu, self).unlink()
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def copy(self, cr, uid, id, default=None, context=None):
|
@api.multi
|
||||||
res = super(ir_ui_menu, self).copy(cr, uid, id, default=default, context=context)
|
def copy(self, default=None):
|
||||||
datas=self.read(cr,uid,[res],['name'])[0]
|
res = super(IrUiMenu, self).copy(default=default)
|
||||||
rex=re.compile('\([0-9]+\)')
|
rex = re.compile('\([0-9]+\)')
|
||||||
concat=rex.findall(datas['name'])
|
concat = rex.findall(res.name)
|
||||||
if concat:
|
if concat:
|
||||||
next_num=int(concat[0])+1
|
next_num = int(concat[0])+1
|
||||||
datas['name']=rex.sub(('(%d)'%next_num),datas['name'])
|
res.name = rex.sub(('(%d)' % next_num), res.name)
|
||||||
else:
|
else:
|
||||||
datas['name'] += '(1)'
|
res.name += '(1)'
|
||||||
self.write(cr,uid,[res],{'name':datas['name']})
|
res.write({'name': res.name})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def read_image(self, path):
|
@api.multi
|
||||||
if not path:
|
def get_needaction_data(self):
|
||||||
return False
|
|
||||||
path_info = path.split(',')
|
|
||||||
icon_path = openerp.modules.get_module_resource(path_info[0],path_info[1])
|
|
||||||
icon_image = False
|
|
||||||
if icon_path:
|
|
||||||
try:
|
|
||||||
icon_file = tools.file_open(icon_path,'rb')
|
|
||||||
icon_image = base64.encodestring(icon_file.read())
|
|
||||||
finally:
|
|
||||||
icon_file.close()
|
|
||||||
return icon_image
|
|
||||||
|
|
||||||
def get_needaction_data(self, cr, uid, ids, context=None):
|
|
||||||
""" Return for each menu entry of ids :
|
""" Return for each menu entry of ids :
|
||||||
- if it uses the needaction mechanism (needaction_enabled)
|
- if it uses the needaction mechanism (needaction_enabled)
|
||||||
- the needaction counter of the related action, taking into account
|
- the needaction counter of the related action, taking into account
|
||||||
the action domain
|
the action domain
|
||||||
"""
|
"""
|
||||||
if context is None:
|
|
||||||
context = {}
|
|
||||||
res = {}
|
res = {}
|
||||||
menu_ids = set()
|
menu_ids = set()
|
||||||
for menu in self.browse(cr, uid, ids, context=context):
|
for menu in self:
|
||||||
menu_ids.add(menu.id)
|
menu_ids.add(menu.id)
|
||||||
ctx = None
|
ctx = None
|
||||||
if menu.action and menu.action.type in ('ir.actions.act_window', 'ir.actions.client') and menu.action.context:
|
if menu.action and menu.action.type in ('ir.actions.act_window', 'ir.actions.client') and menu.action.context:
|
||||||
try:
|
try:
|
||||||
# use magical UnquoteEvalContext to ignore undefined client-side variables such as `active_id`
|
# use magical UnquoteEvalContext to ignore undefined client-side variables such as `active_id`
|
||||||
eval_ctx = tools.UnquoteEvalContext(**context)
|
ctxt = dict(self.env.context or {})
|
||||||
|
eval_ctx = tools.UnquoteEvalContext(**ctxt)
|
||||||
ctx = eval(menu.action.context, locals_dict=eval_ctx, nocopy=True) or None
|
ctx = eval(menu.action.context, locals_dict=eval_ctx, nocopy=True) or None
|
||||||
except Exception:
|
except Exception:
|
||||||
# if the eval still fails for some reason, we'll simply skip this menu
|
# if the eval still fails for some reason, we'll simply skip this menu
|
||||||
@@ -199,43 +222,44 @@ class ir_ui_menu(osv.osv):
|
|||||||
if menu_ref:
|
if menu_ref:
|
||||||
if not isinstance(menu_ref, list):
|
if not isinstance(menu_ref, list):
|
||||||
menu_ref = [menu_ref]
|
menu_ref = [menu_ref]
|
||||||
model_data_obj = self.pool.get('ir.model.data')
|
model_data_obj = self.env['ir.model.data']
|
||||||
for menu_data in menu_ref:
|
for menu_data in menu_ref:
|
||||||
try:
|
try:
|
||||||
model, id = model_data_obj.get_object_reference(cr, uid, menu_data.split('.')[0], menu_data.split('.')[1])
|
model, model_id = model_data_obj.get_object_reference(menu_data.split('.')[0], menu_data.split('.')[1])
|
||||||
if (model == 'ir.ui.menu'):
|
if (model == 'ir.ui.menu'):
|
||||||
menu_ids.add(id)
|
menu_ids.add(model_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
menu_ids = list(menu_ids)
|
menu_ids = list(menu_ids)
|
||||||
|
|
||||||
for menu in self.browse(cr, uid, menu_ids, context=context):
|
for menu in self:
|
||||||
res[menu.id] = {
|
res[menu.id] = {
|
||||||
'needaction_enabled': False,
|
'needaction_enabled': False,
|
||||||
'needaction_counter': False,
|
'needaction_counter': False,
|
||||||
}
|
}
|
||||||
if menu.action and menu.action.type in ('ir.actions.act_window', 'ir.actions.client') and menu.action.res_model:
|
if menu.action and menu.action.type in ('ir.actions.act_window', 'ir.actions.client') and menu.action.res_model:
|
||||||
if menu.action.res_model in self.pool:
|
if menu.action.res_model in self.pool:
|
||||||
obj = self.pool[menu.action.res_model]
|
obj = self.env[menu.action.res_model]
|
||||||
if obj._needaction:
|
if obj._needaction:
|
||||||
if menu.action.type == 'ir.actions.act_window':
|
if menu.action.type == 'ir.actions.act_window':
|
||||||
eval_context = self.pool['ir.actions.act_window']._get_eval_context(cr, uid, context=context)
|
eval_context = self.env['ir.actions.act_window']._get_eval_context()
|
||||||
dom = menu.action.domain and eval(menu.action.domain, eval_context) or []
|
dom = menu.action.domain and eval(menu.action.domain, eval_context) or []
|
||||||
else:
|
else:
|
||||||
dom = eval(menu.action.params_store or '{}', {'uid': uid}).get('domain')
|
dom = eval(menu.action.params_store or '{}', {'uid': self.env.uid}).get('domain')
|
||||||
res[menu.id]['needaction_enabled'] = obj._needaction
|
res[menu.id]['needaction_enabled'] = obj._needaction
|
||||||
res[menu.id]['needaction_counter'] = obj._needaction_count(cr, uid, dom, context=context)
|
res[menu.id]['needaction_counter'] = obj._needaction_count(dom)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def get_user_roots(self, cr, uid, context=None):
|
@api.model
|
||||||
|
@api.returns('self')
|
||||||
|
def get_user_roots(self):
|
||||||
""" Return all root menu ids visible for the user.
|
""" Return all root menu ids visible for the user.
|
||||||
|
|
||||||
:return: the root menu ids
|
:return: the root menu ids
|
||||||
:rtype: list(int)
|
:rtype: list(int)
|
||||||
"""
|
"""
|
||||||
menu_domain = [('parent_id', '=', False)]
|
return self.search([('parent_id', '=', False)])
|
||||||
return self.search(cr, uid, menu_domain, context=context)
|
|
||||||
|
|
||||||
@api.cr_uid_context
|
@api.cr_uid_context
|
||||||
@tools.ormcache_context('uid', keys=('lang',))
|
@tools.ormcache_context('uid', keys=('lang',))
|
||||||
@@ -300,41 +324,3 @@ class ir_ui_menu(osv.osv):
|
|||||||
key=operator.itemgetter('sequence'))
|
key=operator.itemgetter('sequence'))
|
||||||
|
|
||||||
return menu_root
|
return menu_root
|
||||||
|
|
||||||
_columns = {
|
|
||||||
'name': fields.char('Menu', required=True, translate=True),
|
|
||||||
'sequence': fields.integer('Sequence'),
|
|
||||||
'child_id': fields.one2many('ir.ui.menu', 'parent_id', 'Child IDs'),
|
|
||||||
'parent_id': fields.many2one('ir.ui.menu', 'Parent Menu', select=True, ondelete="restrict"),
|
|
||||||
'parent_left': fields.integer('Parent Left', select=True),
|
|
||||||
'parent_right': fields.integer('Parent Right', select=True),
|
|
||||||
'groups_id': fields.many2many('res.groups', 'ir_ui_menu_group_rel',
|
|
||||||
'menu_id', 'gid', 'Groups', help="If you have groups, the visibility of this menu will be based on these groups. "\
|
|
||||||
"If this field is empty, Odoo will compute visibility based on the related object's read access."),
|
|
||||||
'complete_name': fields.function(_get_full_name, string='Full Path', type='char'),
|
|
||||||
'web_icon': fields.char('Web Icon File'),
|
|
||||||
'action': fields.reference('Action', selection=[
|
|
||||||
('ir.actions.report.xml', 'ir.actions.report.xml'),
|
|
||||||
('ir.actions.act_window', 'ir.actions.act_window'),
|
|
||||||
('ir.actions.act_url', 'ir.actions.act_url'),
|
|
||||||
('ir.actions.server', 'ir.actions.server'),
|
|
||||||
('ir.actions.client', 'ir.actions.client'),
|
|
||||||
]),
|
|
||||||
}
|
|
||||||
|
|
||||||
web_icon_data = openerp.fields.Binary('Web Icon Image',
|
|
||||||
compute="_compute_web_icon", store=True, attachment=True)
|
|
||||||
|
|
||||||
@api.depends('web_icon')
|
|
||||||
def _compute_web_icon(self):
|
|
||||||
for menu in self:
|
|
||||||
menu.web_icon_data = self.read_image(menu.web_icon)
|
|
||||||
|
|
||||||
_constraints = [
|
|
||||||
(osv.osv._check_recursion, 'Error ! You can not create recursive Menu.', ['parent_id'])
|
|
||||||
]
|
|
||||||
_defaults = {
|
|
||||||
'sequence': 10,
|
|
||||||
}
|
|
||||||
_order = "sequence,id"
|
|
||||||
_parent_store = True
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
|
|
||||||
<record id="edit_menu_access" model="ir.ui.view">
|
<record id="edit_menu_access" model="ir.ui.view">
|
||||||
<field name="model">ir.ui.menu</field>
|
<field name="model">ir.ui.menu</field>
|
||||||
@@ -70,5 +69,4 @@
|
|||||||
</record>
|
</record>
|
||||||
<menuitem action="grant_menu_access" id="menu_grant_menu_access" parent="base.next_id_2" sequence="1"/>
|
<menuitem action="grant_menu_access" id="menu_grant_menu_access" parent="base.next_id_2" sequence="1"/>
|
||||||
|
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
<!-- View -->
|
<!-- View -->
|
||||||
<record id="view_view_form" model="ir.ui.view">
|
<record id="view_view_form" model="ir.ui.view">
|
||||||
<field name="model">ir.ui.view</field>
|
<field name="model">ir.ui.view</field>
|
||||||
@@ -136,5 +135,4 @@
|
|||||||
</record>
|
</record>
|
||||||
<menuitem id="menu_action_ui_view_custom" action="action_ui_view_custom" parent="base.next_id_2" sequence="3"/>
|
<menuitem id="menu_action_ui_view_custom" action="action_ui_view_custom" parent="base.next_id_2" sequence="3"/>
|
||||||
|
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
+131
-140
@@ -1,11 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
from openerp import tools
|
from odoo import api, fields, models, tools, _
|
||||||
from openerp.osv import osv, fields
|
from odoo.exceptions import AccessError, MissingError
|
||||||
from openerp.exceptions import AccessError, MissingError
|
from odoo.tools import pickle
|
||||||
from openerp.tools.translate import _
|
|
||||||
from openerp.tools import pickle
|
|
||||||
|
|
||||||
EXCLUDED_FIELDS = set((
|
EXCLUDED_FIELDS = set((
|
||||||
'report_sxw_content', 'report_rml_content', 'report_sxw', 'report_rml',
|
'report_sxw_content', 'report_rml_content', 'report_sxw', 'report_rml',
|
||||||
@@ -21,7 +19,7 @@ ACTION_SLOTS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ir_values(osv.osv):
|
class IrValues(models.Model):
|
||||||
"""Holds internal model-specific action bindings and user-defined default
|
"""Holds internal model-specific action bindings and user-defined default
|
||||||
field values. definitions. This is a legacy internal model, mixing
|
field values. definitions. This is a legacy internal model, mixing
|
||||||
two different concepts, and will likely be updated or replaced in a
|
two different concepts, and will likely be updated or replaced in a
|
||||||
@@ -70,124 +68,112 @@ class ir_values(osv.osv):
|
|||||||
users, and set by their UI clients calling :meth:`~.set_default`.
|
users, and set by their UI clients calling :meth:`~.set_default`.
|
||||||
These default values are then automatically used by the
|
These default values are then automatically used by the
|
||||||
ORM every time a new record is about to be created, i.e. when
|
ORM every time a new record is about to be created, i.e. when
|
||||||
:meth:`~openerp.osv.osv.osv.default_get`
|
:meth:`~odoo.osv.osv.osv.default_get`
|
||||||
or :meth:`~openerp.osv.osv.osv.create` are called.
|
or :meth:`~odoo.osv.osv.osv.create` are called.
|
||||||
|
|
||||||
.. rubric:: Usage: action bindings
|
.. rubric:: Usage: action bindings
|
||||||
|
|
||||||
Business applications will usually bind their actions during
|
Business applications will usually bind their actions during
|
||||||
installation, and OpenERP UI clients will apply them as defined,
|
installation, and Odoo UI clients will apply them as defined,
|
||||||
based on the list of actions included in the result of
|
based on the list of actions included in the result of
|
||||||
:meth:`~openerp.osv.osv.osv.fields_view_get`,
|
:meth:`~odoo.osv.osv.osv.fields_view_get`,
|
||||||
or directly returned by explicit calls to :meth:`~.get_actions`.
|
or directly returned by explicit calls to :meth:`~.get_actions`.
|
||||||
"""
|
"""
|
||||||
_name = 'ir.values'
|
_name = 'ir.values'
|
||||||
|
|
||||||
def _value_unpickle(self, cursor, user, ids, name, arg, context=None):
|
name = fields.Char(required=True)
|
||||||
res = {}
|
model = fields.Char(string='Model Name', select=True, required=True,
|
||||||
for record in self.browse(cursor, user, ids, context=context):
|
help="Model to which this entry applies")
|
||||||
value = record[name[:-9]]
|
|
||||||
|
# TODO: model_id and action_id should be read-write function fields
|
||||||
|
model_id = fields.Many2one('ir.model', string='Model (change only)',
|
||||||
|
help="Model to which this entry applies - "
|
||||||
|
"helper field for setting a model, will "
|
||||||
|
"automatically set the correct model name")
|
||||||
|
action_id = fields.Many2one('ir.actions.actions', string='Action (change only)',
|
||||||
|
help="Action bound to this entry - "
|
||||||
|
"helper field for binding an action, will "
|
||||||
|
"automatically set the correct reference")
|
||||||
|
|
||||||
|
value = fields.Text(help="Default value (pickled) or reference to an action")
|
||||||
|
value_unpickle = fields.Text(compute='_value_unpickle', inverse='_value_pickle',
|
||||||
|
string='Default value or action reference')
|
||||||
|
key = fields.Selection([('action', 'Action'), ('default', 'Default')],
|
||||||
|
string='Type', index=True, required=True, default='action',
|
||||||
|
help="- Action: an action attached to one slot of the given model\n"
|
||||||
|
"- Default: a default value for a model field")
|
||||||
|
key2 = fields.Char(string='Qualifier', index=True, default='tree_but_open',
|
||||||
|
help="For actions, one of the possible action slots: \n"
|
||||||
|
" - client_action_multi\n"
|
||||||
|
" - client_print_multi\n"
|
||||||
|
" - client_action_relate\n"
|
||||||
|
" - tree_but_open\n"
|
||||||
|
"For defaults, an optional condition")
|
||||||
|
res_id = fields.Integer(string='Record ID', index=True,
|
||||||
|
help="Database identifier of the record to which this applies. "
|
||||||
|
"0 = for all records")
|
||||||
|
user_id = fields.Many2one('res.users', string='User', ondelete='cascade', index=True,
|
||||||
|
help="If set, action binding only applies for this user.")
|
||||||
|
company_id = fields.Many2one('res.company', string='Company', ondelete='cascade', index=True,
|
||||||
|
help="If set, action binding only applies for this company")
|
||||||
|
|
||||||
|
@api.depends('value_unpickle')
|
||||||
|
def _value_unpickle(self):
|
||||||
|
for record in self:
|
||||||
|
value = record.value
|
||||||
if record.key == 'default' and value:
|
if record.key == 'default' and value:
|
||||||
# default values are pickled on the fly
|
# default values are pickled on the fly
|
||||||
try:
|
try:
|
||||||
value = str(pickle.loads(value))
|
value = str(pickle.loads(value))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
res[record.id] = value
|
record.value_unpickle = value
|
||||||
return res
|
|
||||||
|
|
||||||
def _value_pickle(self, cursor, user, id, name, value, arg, context=None):
|
def _value_pickle(self):
|
||||||
if context is None:
|
ctx = self.env.context.copy()
|
||||||
context = {}
|
|
||||||
ctx = context.copy()
|
|
||||||
if self.CONCURRENCY_CHECK_FIELD in ctx:
|
if self.CONCURRENCY_CHECK_FIELD in ctx:
|
||||||
del ctx[self.CONCURRENCY_CHECK_FIELD]
|
del ctx[self.CONCURRENCY_CHECK_FIELD]
|
||||||
record = self.browse(cursor, user, id, context=context)
|
if self.key == 'default':
|
||||||
if record.key == 'default':
|
|
||||||
# default values are pickled on the fly
|
# default values are pickled on the fly
|
||||||
value = pickle.dumps(value)
|
value = pickle.dumps(self.value)
|
||||||
self.write(cursor, user, id, {name[:-9]: value}, context=ctx)
|
self.write({value: value})
|
||||||
|
|
||||||
def onchange_object_id(self, cr, uid, ids, object_id, context=None):
|
@api.onchange('model_id')
|
||||||
if not object_id: return {}
|
def onchange_object_id(self):
|
||||||
act = self.pool.get('ir.model').browse(cr, uid, object_id, context=context)
|
if not self.model_id:
|
||||||
return {
|
return {}
|
||||||
'value': {'model': act.model}
|
return {'value': {'model': self.env['ir.model'].browse(self.model_id).model}}
|
||||||
}
|
|
||||||
|
|
||||||
def onchange_action_id(self, cr, uid, ids, action_id, context=None):
|
@api.onchange('action_id')
|
||||||
if not action_id: return {}
|
def onchange_action_id(self):
|
||||||
act = self.pool.get('ir.actions.actions').browse(cr, uid, action_id, context=context)
|
if not self.action_id:
|
||||||
return {
|
return {}
|
||||||
'value': {'value_unpickle': act.type+','+str(act.id)}
|
act = self.env['ir.actions.actions'].browse(self.action_id)
|
||||||
}
|
return {'value': {'value_unpickle': act.type + ',' + str(act.id)}}
|
||||||
|
|
||||||
_columns = {
|
def _auto_init(self):
|
||||||
'name': fields.char('Name', required=True),
|
super(IrValues, self)._auto_init()
|
||||||
'model': fields.char('Model Name', select=True, required=True,
|
self.env.cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_values_key_model_key2_res_id_user_id_idx\'')
|
||||||
help="Model to which this entry applies"),
|
if not self.env.cr.fetchone():
|
||||||
|
self.env.cr.execute('CREATE INDEX ir_values_key_model_key2_res_id_user_id_idx ON ir_values (key, model, key2, res_id, user_id)')
|
||||||
|
|
||||||
# TODO: model_id and action_id should be read-write function fields
|
@api.model
|
||||||
'model_id': fields.many2one('ir.model', 'Model (change only)', size=128,
|
def create(self, vals):
|
||||||
help="Model to which this entry applies - "
|
|
||||||
"helper field for setting a model, will "
|
|
||||||
"automatically set the correct model name"),
|
|
||||||
'action_id': fields.many2one('ir.actions.actions', 'Action (change only)',
|
|
||||||
help="Action bound to this entry - "
|
|
||||||
"helper field for binding an action, will "
|
|
||||||
"automatically set the correct reference"),
|
|
||||||
|
|
||||||
'value': fields.text('Value', help="Default value (pickled) or reference to an action"),
|
|
||||||
'value_unpickle': fields.function(_value_unpickle, fnct_inv=_value_pickle,
|
|
||||||
type='text',
|
|
||||||
string='Default value or action reference'),
|
|
||||||
'key': fields.selection([('action','Action'),('default','Default')],
|
|
||||||
'Type', select=True, required=True,
|
|
||||||
help="- Action: an action attached to one slot of the given model\n"
|
|
||||||
"- Default: a default value for a model field"),
|
|
||||||
'key2' : fields.char('Qualifier', select=True,
|
|
||||||
help="For actions, one of the possible action slots: \n"
|
|
||||||
" - client_action_multi\n"
|
|
||||||
" - client_print_multi\n"
|
|
||||||
" - client_action_relate\n"
|
|
||||||
" - tree_but_open\n"
|
|
||||||
"For defaults, an optional condition"
|
|
||||||
,),
|
|
||||||
'res_id': fields.integer('Record ID', select=True,
|
|
||||||
help="Database identifier of the record to which this applies. "
|
|
||||||
"0 = for all records"),
|
|
||||||
'user_id': fields.many2one('res.users', 'User', ondelete='cascade', select=True,
|
|
||||||
help="If set, action binding only applies for this user."),
|
|
||||||
'company_id': fields.many2one('res.company', 'Company', ondelete='cascade', select=True,
|
|
||||||
help="If set, action binding only applies for this company")
|
|
||||||
}
|
|
||||||
_defaults = {
|
|
||||||
'key': 'action',
|
|
||||||
'key2': 'tree_but_open',
|
|
||||||
}
|
|
||||||
|
|
||||||
def _auto_init(self, cr, context=None):
|
|
||||||
super(ir_values, self)._auto_init(cr, context)
|
|
||||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_values_key_model_key2_res_id_user_id_idx\'')
|
|
||||||
if not cr.fetchone():
|
|
||||||
cr.execute('CREATE INDEX ir_values_key_model_key2_res_id_user_id_idx ON ir_values (key, model, key2, res_id, user_id)')
|
|
||||||
|
|
||||||
def create(self, cr, uid, vals, context=None):
|
|
||||||
res = super(ir_values, self).create(cr, uid, vals, context=context)
|
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
return res
|
return super(IrValues, self).create(vals)
|
||||||
|
|
||||||
def write(self, cr, uid, ids, vals, context=None):
|
@api.multi
|
||||||
res = super(ir_values, self).write(cr, uid, ids, vals, context=context)
|
def write(self, vals):
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
return res
|
return super(IrValues, self).write(vals)
|
||||||
|
|
||||||
def unlink(self, cr, uid, ids, context=None):
|
@api.multi
|
||||||
res = super(ir_values, self).unlink(cr, uid, ids, context=context)
|
def unlink(self):
|
||||||
self.clear_caches()
|
self.clear_caches()
|
||||||
return res
|
return super(IrValues, self).unlink()
|
||||||
|
|
||||||
def set_default(self, cr, uid, model, field_name, value, for_all_users=True, company_id=False, condition=False):
|
@api.model
|
||||||
|
def set_default(self, model, field_name, value, for_all_users=True, company_id=False, condition=False):
|
||||||
"""Defines a default value for the given model and field_name. Any previous
|
"""Defines a default value for the given model and field_name. Any previous
|
||||||
default for the same scope (model, field_name, value, for_all_users, company_id, condition)
|
default for the same scope (model, field_name, value, for_all_users, company_id, condition)
|
||||||
will be replaced and lost in the process.
|
will be replaced and lost in the process.
|
||||||
@@ -225,8 +211,7 @@ class ir_values(osv.osv):
|
|||||||
value = value.encode('utf8')
|
value = value.encode('utf8')
|
||||||
if company_id is True:
|
if company_id is True:
|
||||||
# should be company-specific, need to get company id
|
# should be company-specific, need to get company id
|
||||||
user = self.pool.get('res.users').browse(cr, uid, uid)
|
company_id = self.env.user.company_id.id
|
||||||
company_id = user.company_id.id
|
|
||||||
|
|
||||||
# remove existing defaults for the same scope
|
# remove existing defaults for the same scope
|
||||||
search_criteria = [
|
search_criteria = [
|
||||||
@@ -234,22 +219,23 @@ class ir_values(osv.osv):
|
|||||||
('key2', '=', condition and condition[:200]),
|
('key2', '=', condition and condition[:200]),
|
||||||
('model', '=', model),
|
('model', '=', model),
|
||||||
('name', '=', field_name),
|
('name', '=', field_name),
|
||||||
('user_id', '=', False if for_all_users else uid),
|
('user_id', '=', False if for_all_users else self._uid),
|
||||||
('company_id','=', company_id)
|
('company_id', '=', company_id)
|
||||||
]
|
]
|
||||||
self.unlink(cr, uid, self.search(cr, uid, search_criteria))
|
self.search(search_criteria).unlink()
|
||||||
|
|
||||||
return self.create(cr, uid, {
|
return self.create({
|
||||||
'name': field_name,
|
'name': field_name,
|
||||||
'value': pickle.dumps(value),
|
'value': pickle.dumps(value),
|
||||||
'model': model,
|
'model': model,
|
||||||
'key': 'default',
|
'key': 'default',
|
||||||
'key2': condition and condition[:200],
|
'key2': condition and condition[:200],
|
||||||
'user_id': False if for_all_users else uid,
|
'user_id': False if for_all_users else self._uid,
|
||||||
'company_id': company_id,
|
'company_id': company_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_default(self, cr, uid, model, field_name, for_all_users=True, company_id=False, condition=False):
|
@api.model
|
||||||
|
def get_default(self, model, field_name, for_all_users=True, company_id=False, condition=False):
|
||||||
""" Return the default value defined for model, field_name, users, company and condition.
|
""" Return the default value defined for model, field_name, users, company and condition.
|
||||||
Return ``None`` if no such default exists.
|
Return ``None`` if no such default exists.
|
||||||
"""
|
"""
|
||||||
@@ -258,13 +244,14 @@ class ir_values(osv.osv):
|
|||||||
('key2', '=', condition and condition[:200]),
|
('key2', '=', condition and condition[:200]),
|
||||||
('model', '=', model),
|
('model', '=', model),
|
||||||
('name', '=', field_name),
|
('name', '=', field_name),
|
||||||
('user_id', '=', False if for_all_users else uid),
|
('user_id', '=', False if for_all_users else self._uid),
|
||||||
('company_id','=', company_id)
|
('company_id', '=', company_id)
|
||||||
]
|
]
|
||||||
defaults = self.browse(cr, uid, self.search(cr, uid, search_criteria))
|
defaults = self.search(search_criteria)
|
||||||
return pickle.loads(defaults[0].value.encode('utf-8')) if defaults else None
|
return pickle.loads(defaults.value.encode('utf-8')) if defaults else None
|
||||||
|
|
||||||
def get_defaults(self, cr, uid, model, condition=False):
|
@api.model
|
||||||
|
def get_defaults(self, model, condition=False):
|
||||||
"""Returns any default values that are defined for the current model and user,
|
"""Returns any default values that are defined for the current model and user,
|
||||||
(and match ``condition``, if specified), previously registered via
|
(and match ``condition``, if specified), previously registered via
|
||||||
:meth:`~.set_default`.
|
:meth:`~.set_default`.
|
||||||
@@ -303,31 +290,33 @@ class ir_values(osv.osv):
|
|||||||
)
|
)
|
||||||
%s
|
%s
|
||||||
ORDER BY v.user_id, u.company_id"""
|
ORDER BY v.user_id, u.company_id"""
|
||||||
params = ('default', model, uid, uid)
|
params = ('default', model, self._uid, self._uid)
|
||||||
if condition:
|
if condition:
|
||||||
query %= 'AND v.key2 = %s'
|
query %= 'AND v.key2 = %s'
|
||||||
params += (condition[:200],)
|
params += (condition[:200],)
|
||||||
else:
|
else:
|
||||||
query %= 'AND v.key2 is NULL'
|
query %= 'AND v.key2 is NULL'
|
||||||
cr.execute(query, params)
|
self.env.cr.execute(query, params)
|
||||||
|
|
||||||
# keep only the highest priority default for each field
|
# keep only the highest priority default for each field
|
||||||
defaults = {}
|
defaults = {}
|
||||||
for row in cr.dictfetchall():
|
for row in self.env.cr.dictfetchall():
|
||||||
defaults.setdefault(row['name'],
|
defaults.setdefault(row['name'],
|
||||||
(row['id'], row['name'], pickle.loads(row['value'].encode('utf-8'))))
|
(row['id'], row['name'], pickle.loads(row['value'].encode('utf-8'))))
|
||||||
return defaults.values()
|
return defaults.values()
|
||||||
|
|
||||||
# use ormcache: this is called a lot by BaseModel.default_get()!
|
# use ormcache: this is called a lot by BaseModel.default_get()!
|
||||||
@tools.ormcache('uid', 'model', 'condition')
|
@api.model
|
||||||
def get_defaults_dict(self, cr, uid, model, condition=False):
|
@tools.ormcache('self.env.uid', 'model', 'condition')
|
||||||
|
def get_defaults_dict(self, model, condition=False):
|
||||||
""" Returns a dictionary mapping field names with their corresponding
|
""" Returns a dictionary mapping field names with their corresponding
|
||||||
default value. This method simply improves the returned value of
|
default value. This method simply improves the returned value of
|
||||||
:meth:`~.get_defaults`.
|
:meth:`~.get_defaults`.
|
||||||
"""
|
"""
|
||||||
return dict((f, v) for i, f, v in self.get_defaults(cr, uid, model, condition))
|
return dict((f, v) for i, f, v in self.get_defaults(model, condition))
|
||||||
|
|
||||||
def set_action(self, cr, uid, name, action_slot, model, action, res_id=False):
|
@api.model
|
||||||
|
def set_action(self, name, action_slot, model, action, res_id=False):
|
||||||
"""Binds an the given action to the given model's action slot - for later
|
"""Binds an the given action to the given model's action slot - for later
|
||||||
retrieval via :meth:`~.get_actions`. Any existing binding of the same action
|
retrieval via :meth:`~.get_actions`. Any existing binding of the same action
|
||||||
to the same slot is first removed, allowing an update of the action's name.
|
to the same slot is first removed, allowing an update of the action's name.
|
||||||
@@ -355,22 +344,23 @@ class ir_values(osv.osv):
|
|||||||
('key', '=', 'action'),
|
('key', '=', 'action'),
|
||||||
('key2', '=', action_slot),
|
('key2', '=', action_slot),
|
||||||
('model', '=', model),
|
('model', '=', model),
|
||||||
('res_id', '=', res_id or 0), # int field -> NULL == 0
|
('res_id', '=', res_id or 0), # int field -> NULL == 0
|
||||||
('value', '=', action),
|
('value', '=', action),
|
||||||
]
|
]
|
||||||
self.unlink(cr, uid, self.search(cr, uid, search_criteria))
|
self.search(search_criteria).unlink()
|
||||||
|
|
||||||
return self.create(cr, uid, {
|
return self.create({
|
||||||
'key': 'action',
|
'key': 'action',
|
||||||
'key2': action_slot,
|
'key2': action_slot,
|
||||||
'model': model,
|
'model': model,
|
||||||
'res_id': res_id,
|
'res_id': res_id,
|
||||||
'name': name,
|
'name': name,
|
||||||
'value': action,
|
'value': action,
|
||||||
})
|
}).id
|
||||||
|
|
||||||
@tools.ormcache_context('uid', 'action_slot', 'model', 'res_id', keys=('lang',))
|
@api.model
|
||||||
def get_actions(self, cr, uid, action_slot, model, res_id=False, context=None):
|
@tools.ormcache_context('self.env.uid', 'action_slot', 'model', 'res_id', keys=('lang',))
|
||||||
|
def get_actions(self, action_slot, model, res_id=False):
|
||||||
"""Retrieves the list of actions bound to the given model's action slot.
|
"""Retrieves the list of actions bound to the given model's action slot.
|
||||||
See the class description for more details about the various action
|
See the class description for more details about the various action
|
||||||
slots: :class:`~.ir_values`.
|
slots: :class:`~.ir_values`.
|
||||||
@@ -398,21 +388,20 @@ class ir_values(osv.osv):
|
|||||||
OR v.res_id IS NULL
|
OR v.res_id IS NULL
|
||||||
OR v.res_id = 0)
|
OR v.res_id = 0)
|
||||||
ORDER BY v.id"""
|
ORDER BY v.id"""
|
||||||
cr.execute(query, ('action', action_slot, model, res_id or None))
|
self.env.cr.execute(query, ('action', action_slot, model, res_id or None))
|
||||||
|
|
||||||
# map values to their corresponding action record
|
# map values to their corresponding action record
|
||||||
actions = []
|
actions = []
|
||||||
for id, name, value in cr.fetchall():
|
for id, name, value in self.env.cr.fetchall():
|
||||||
if not value:
|
if not value:
|
||||||
continue # skip if undefined
|
continue # skip if undefined
|
||||||
action_model, action_id = value.split(',')
|
action_model, action_id = value.split(',')
|
||||||
if action_model not in self.pool:
|
if action_model not in self.pool:
|
||||||
continue # unknown model? skip it!
|
continue # unknown model? skip it!
|
||||||
action = self.pool[action_model].browse(cr, uid, int(action_id), context)
|
action = self.env[action_model].browse(int(action_id))
|
||||||
actions.append((id, name, action))
|
actions.append((id, name, action))
|
||||||
|
|
||||||
# process values and their action
|
# process values and their action
|
||||||
user = self.pool['res.users'].browse(cr, uid, uid, context)
|
|
||||||
results = {}
|
results = {}
|
||||||
for id, name, action in actions:
|
for id, name, action in actions:
|
||||||
fields = [field for field in action._fields if field not in EXCLUDED_FIELDS]
|
fields = [field for field in action._fields if field not in EXCLUDED_FIELDS]
|
||||||
@@ -423,7 +412,7 @@ class ir_values(osv.osv):
|
|||||||
for field in fields
|
for field in fields
|
||||||
}
|
}
|
||||||
if action._name in ('ir.actions.report.xml', 'ir.actions.act_window'):
|
if action._name in ('ir.actions.report.xml', 'ir.actions.act_window'):
|
||||||
if action.groups_id and not action.groups_id & user.groups_id:
|
if action.groups_id and not action.groups_id & self.env.user.groups_id:
|
||||||
if name == 'Menuitem':
|
if name == 'Menuitem':
|
||||||
raise AccessError(_('You do not have the permission to perform this operation!!!'))
|
raise AccessError(_('You do not have the permission to perform this operation!!!'))
|
||||||
continue
|
continue
|
||||||
@@ -454,7 +443,8 @@ class ir_values(osv.osv):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
# Backards-compatibility adapter layer to retrofit into split API
|
# Backards-compatibility adapter layer to retrofit into split API
|
||||||
def set(self, cr, uid, key, key2, name, models, value, replace=True, isobject=False, meta=False, preserve_user=False, company=False):
|
@api.model
|
||||||
|
def set(self, key, key2, name, models, value, replace=True, isobject=False, meta=False, preserve_user=False, company=False):
|
||||||
"""Deprecated legacy method to set default values and bind actions to models' action slots.
|
"""Deprecated legacy method to set default values and bind actions to models' action slots.
|
||||||
Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.set_default`
|
Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.set_default`
|
||||||
(``key=='default'``) or :meth:`~.set_action` (``key == 'action'``).
|
(``key=='default'``) or :meth:`~.set_action` (``key == 'action'``).
|
||||||
@@ -463,16 +453,17 @@ class ir_values(osv.osv):
|
|||||||
"""
|
"""
|
||||||
assert key in ['default', 'action'], "ir.values entry keys must be in ['default','action']"
|
assert key in ['default', 'action'], "ir.values entry keys must be in ['default','action']"
|
||||||
if key == 'default':
|
if key == 'default':
|
||||||
def do_set(model,res_id):
|
def do_set(model, res_id):
|
||||||
return self.set_default(cr, uid, model, field_name=name, value=value,
|
return self.set_default(model, field_name=name, value=value,
|
||||||
for_all_users=(not preserve_user), company_id=company,
|
for_all_users=(not preserve_user), company_id=company,
|
||||||
condition=key2)
|
condition=key2)
|
||||||
elif key == 'action':
|
elif key == 'action':
|
||||||
def do_set(model,res_id):
|
def do_set(model, res_id):
|
||||||
return self.set_action(cr, uid, name, action_slot=key2, model=model, action=value, res_id=res_id)
|
return self.set_action(name, action_slot=key2, model=model, action=value, res_id=res_id)
|
||||||
return self._map_legacy_model_list(models, do_set)
|
return self._map_legacy_model_list(models, do_set)
|
||||||
|
|
||||||
def get(self, cr, uid, key, key2, models, meta=False, context=None, res_id_req=False, without_user=True, key2_req=True):
|
@api.model
|
||||||
|
def get(self, key, key2, models, meta=False, res_id_req=False, without_user=True, key2_req=True):
|
||||||
"""Deprecated legacy method to get the list of default values or actions bound to models' action slots.
|
"""Deprecated legacy method to get the list of default values or actions bound to models' action slots.
|
||||||
Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.get_defaults`
|
Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.get_defaults`
|
||||||
(``key=='default'``) or :meth:`~.get_actions` (``key == 'action'``)
|
(``key=='default'``) or :meth:`~.get_actions` (``key == 'action'``)
|
||||||
@@ -482,9 +473,9 @@ class ir_values(osv.osv):
|
|||||||
"""
|
"""
|
||||||
assert key in ['default', 'action'], "ir.values entry keys must be in ['default','action']"
|
assert key in ['default', 'action'], "ir.values entry keys must be in ['default','action']"
|
||||||
if key == 'default':
|
if key == 'default':
|
||||||
def do_get(model,res_id):
|
def do_get(model, res_id):
|
||||||
return self.get_defaults(cr, uid, model, condition=key2)
|
return self.get_defaults(model, condition=key2)
|
||||||
elif key == 'action':
|
elif key == 'action':
|
||||||
def do_get(model,res_id):
|
def do_get(model, res_id):
|
||||||
return self.get_actions(cr, uid, action_slot=key2, model=model, res_id=res_id, context=context)
|
return self.get_actions(action_slot=key2, model=model, res_id=res_id)
|
||||||
return self._map_legacy_model_list(models, do_get, merge_results=True)
|
return self._map_legacy_model_list(models, do_get, merge_results=True)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
<!-- Values -->
|
<!-- Values -->
|
||||||
<record id="values_view_form_action" model="ir.ui.view">
|
<record id="values_view_form_action" model="ir.ui.view">
|
||||||
<field name="name">ir.values.form.action</field>
|
<field name="name">ir.values.form.action</field>
|
||||||
@@ -10,13 +9,13 @@
|
|||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="model_id" on_change="onchange_object_id(model_id)"/>
|
<field name="model_id"/>
|
||||||
<field name="model"/>
|
<field name="model"/>
|
||||||
<field name="res_id"/>
|
<field name="res_id"/>
|
||||||
<field name="key2"/>
|
<field name="key2"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="action_id" on_change="onchange_action_id(action_id)"/>
|
<field name="action_id"/>
|
||||||
<field name="value_unpickle" colspan="4" string="Action Reference"/>
|
<field name="value_unpickle" colspan="4" string="Action Reference"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
@@ -33,7 +32,7 @@
|
|||||||
<group>
|
<group>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="model"/>
|
<field name="model"/>
|
||||||
<field name="model_id" on_change="onchange_object_id(model_id)"/>
|
<field name="model_id"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="key2" string="Condition"/>
|
<field name="key2" string="Condition"/>
|
||||||
@@ -123,5 +122,4 @@
|
|||||||
</record>
|
</record>
|
||||||
<menuitem action="act_values_form_defaults" id="menu_values_form_defaults" parent="next_id_6"/>
|
<menuitem action="act_values_form_defaults" id="menu_values_form_defaults" parent="next_id_6"/>
|
||||||
|
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<odoo>
|
||||||
<data>
|
|
||||||
<template id="report_irmodeloverview">
|
<template id="report_irmodeloverview">
|
||||||
<t t-call="report.html_container">
|
<t t-call="report.html_container">
|
||||||
<t t-foreach="docs" t-as="o">
|
<t t-foreach="docs" t-as="o">
|
||||||
@@ -93,5 +92,4 @@
|
|||||||
</t>
|
</t>
|
||||||
</t>
|
</t>
|
||||||
</template>
|
</template>
|
||||||
</data>
|
</odoo>
|
||||||
</openerp>
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<field name="type">ir.actions.server</field>
|
<field name="type">ir.actions.server</field>
|
||||||
<field name="model_id" ref="model_ir_module_module" />
|
<field name="model_id" ref="model_ir_module_module" />
|
||||||
<field name="state">code</field>
|
<field name="state">code</field>
|
||||||
<field name="code">self.button_immediate_install(cr, uid, context.get('active_ids', []), context=context)</field>
|
<field name="code">self._model.button_immediate_install(self._cr, self._uid, self.env.context.get('active_ids', []), context=self.env.context)</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record model="ir.values" id="action_module_immediate_install">
|
<record model="ir.values" id="action_module_immediate_install">
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class TestQWeb(common.TransactionCase):
|
|||||||
ctx.update(params)
|
ctx.update(params)
|
||||||
result = doc.find('result[@id="{}"]'.format(template)).text
|
result = doc.find('result[@id="{}"]'.format(template)).text
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
qweb.render(template, qwebcontext=ctx).strip(),
|
qweb._model.render(self.cr, self.uid, template, qwebcontext=ctx).strip(),
|
||||||
(result or u'').strip().encode('utf-8'),
|
(result or u'').strip().encode('utf-8'),
|
||||||
template
|
template
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user