[MIG] base: migrate base/ir to the new API

This commit is contained in:
Deep Bundela
2016-03-21 13:46:44 +01:00
committed by Raphael Collet
parent 348fa4f958
commit eb04aa1e39
44 changed files with 2347 additions and 2628 deletions
+7 -9
View File
@@ -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})
+4 -4
View File
@@ -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)
+29 -34
View File
@@ -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
-1
View File
@@ -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')]}"/>
+1 -1
View File
@@ -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
+8 -21
View File
@@ -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': ['&amp;', attrs="{'invisible': ['&amp;',
'|', ('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>
+12 -10
View File
@@ -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
+2 -5
View File
@@ -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>
+31 -32
View File
@@ -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>
+71 -81
View File
@@ -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})
+2 -4
View File
@@ -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>
+10 -13
View File
@@ -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')
}
+7 -8
View File
@@ -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)
+67 -82
View File
@@ -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,56 +79,52 @@ 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)
@@ -122,38 +133,12 @@ class ir_filters(osv.osv):
('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'
+2 -2
View File
@@ -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>
+24 -23
View File
@@ -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
+12 -21
View File
@@ -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),
}
+3 -5
View File
@@ -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>
+69 -76
View File
@@ -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)
@@ -239,8 +237,8 @@ class ir_mail_server(osv.osv):
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"
@@ -414,14 +409,14 @@ class ir_mail_server(osv.osv):
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>
File diff suppressed because it is too large Load Diff
+3 -5
View File
@@ -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>
+2 -4
View File
@@ -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>
+8 -6
View File
@@ -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
View File
@@ -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
+2 -4
View File
@@ -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>
+71 -75
View File
@@ -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
+2 -4
View File
@@ -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>
+34 -33
View File
@@ -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)
+2 -4
View File
@@ -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>
+120 -133
View File
@@ -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
@@ -360,17 +346,18 @@ class ir_translation(osv.osv):
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>
+106 -120
View File
@@ -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
+2 -4
View File
@@ -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
+2 -4
View File
@@ -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
View File
@@ -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)
+5 -7
View File
@@ -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>
+2 -4
View File
@@ -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">
+1 -1
View File
@@ -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
) )