[MIG] base: migrate base/ir to the new API
This commit is contained in:
committed by
Raphael Collet
parent
348fa4f958
commit
eb04aa1e39
@@ -1,14 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv
|
||||
from odoo import models
|
||||
|
||||
class ir_configparameter(osv.Model):
|
||||
|
||||
class IrConfigParameter(models.Model):
|
||||
_inherit = 'ir.config_parameter'
|
||||
|
||||
def init(self, cr, force=False):
|
||||
super(ir_configparameter, self).init(cr, force=force)
|
||||
def init(self, force=False):
|
||||
super(IrConfigParameter, self).init(force=force)
|
||||
if force:
|
||||
IMD = self.pool['ir.model.data']
|
||||
oauth_oe = IMD.xmlid_to_object(cr, SUPERUSER_ID, 'auth_oauth.provider_openerp')
|
||||
dbuuid = self.get_param(cr, SUPERUSER_ID, 'database.uuid')
|
||||
oauth_oe.write({'client_id': dbuuid})
|
||||
oauth_oe = self.env.ref('auth_oauth.provider_openerp')
|
||||
oauth_oe.write({'client_id': self.sudo().get_param('database.uuid')})
|
||||
|
||||
@@ -25,7 +25,7 @@ class ir_actions_report_xml(orm.Model):
|
||||
" 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.
|
||||
"""
|
||||
@@ -41,8 +41,8 @@ class ir_actions_report_xml(orm.Model):
|
||||
if not isinstance(new_report, WebKitParser):
|
||||
new_report = None
|
||||
else:
|
||||
cr.execute("SELECT * FROM ir_act_report_xml WHERE report_name=%s and report_type=%s", (name, 'webkit'))
|
||||
r = cr.dictfetchone()
|
||||
self.env.cr.execute("SELECT * FROM ir_act_report_xml WHERE report_name=%s and report_type=%s", (name, 'webkit'))
|
||||
r = self.env.cr.dictfetchone()
|
||||
if r:
|
||||
if r['parser']:
|
||||
parser = operator.attrgetter(r['parser'])(openerp.addons)
|
||||
@@ -59,4 +59,4 @@ class ir_actions_report_xml(orm.Model):
|
||||
if new_report:
|
||||
return new_report
|
||||
else:
|
||||
return super(ir_actions_report_xml, self)._lookup_report(cr, name)
|
||||
return super(ir_actions_report_xml, self)._lookup_report(name)
|
||||
|
||||
@@ -2,64 +2,59 @@
|
||||
|
||||
import urlparse
|
||||
|
||||
from openerp.http import request
|
||||
from openerp.osv import fields, osv
|
||||
from odoo.http import request
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class actions_server(osv.Model):
|
||||
class actions_server(models.Model):
|
||||
""" Add website option in server actions. """
|
||||
_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):
|
||||
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url', context=context)
|
||||
link = website_path or xml_id or (id and '%d' % id) or ''
|
||||
xml_id = fields.Char(compute=models.Model.get_xml_id, string="External ID",
|
||||
help="ID of the action if defined in a XML file")
|
||||
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:
|
||||
path = '%s/%s' % ('/website/action', link)
|
||||
return '%s' % urlparse.urljoin(base_url, path)
|
||||
return ''
|
||||
|
||||
def _get_website_url(self, cr, uid, ids, name, args, context=None):
|
||||
res = dict.fromkeys(ids, False)
|
||||
for action in self.browse(cr, uid, ids, context=context):
|
||||
@api.depends('website_url')
|
||||
def _get_website_url(self):
|
||||
for action in self:
|
||||
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)
|
||||
return res
|
||||
action.website_url = self._compute_website_url(action.website_path, action.xml_id)
|
||||
|
||||
_columns = {
|
||||
'xml_id': fields.function(
|
||||
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):
|
||||
@api.onchange('website_path', 'xml_id')
|
||||
def on_change_website_path(self):
|
||||
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}
|
||||
|
||||
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. """
|
||||
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':
|
||||
eval_context['request'] = request
|
||||
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
|
||||
returned by the basic server action behavior. Note that response has
|
||||
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:
|
||||
return eval_context['response']
|
||||
return res
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<xpath expr="//field[@name='sequence']" position="after">
|
||||
<field name="xml_id" invisible="1"/>
|
||||
<field name="website_path"
|
||||
on_change="on_change_website_path(website_path, xml_id, context)"
|
||||
attrs="{'invisible': ['|', ('website_published', '!=', True), ('state', '!=', 'code')]}"/>
|
||||
<field name="website_url" readonly="1" widget="url"
|
||||
attrs="{'invisible': ['|', ('website_published', '!=', True), ('state', '!=', 'code')]}"/>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<field name="name">Choose Your Theme Todo Action</field>
|
||||
<field name="model_id" ref="model_ir_module_module"/>
|
||||
<field name="code">
|
||||
self.update_list(cr, uid, context=context)
|
||||
self.update_list()
|
||||
action = {
|
||||
'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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
|
||||
<!-- ir.actions -->
|
||||
|
||||
@@ -275,8 +274,7 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="type" invisible="1"/>
|
||||
<field name="model_id"
|
||||
on_change="on_change_model_id(model_id, wkf_model_id, crud_model_id)"/>
|
||||
<field name="model_id"/>
|
||||
<field name="state"/>
|
||||
</group>
|
||||
<group>
|
||||
@@ -297,15 +295,12 @@
|
||||
</p>
|
||||
<group attrs="{'invisible': [('model_id', '=', False)]}">
|
||||
<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)]}"/>
|
||||
<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')],
|
||||
'invisible': [('use_relational_model', '=', 'base')]}"
|
||||
domain="[('model_id', '=', model_id), ('ttype', 'in', ['many2one'])]"/>
|
||||
<field name="wkf_model_id" invisible="1"
|
||||
on_change="on_change_wkf_model_id(wkf_model_id)"/>
|
||||
<field name="wkf_model_id" invisible="1"/>
|
||||
<field name="wkf_model_name" invisible="1"/>
|
||||
<field name="wkf_transition_id" attrs="{'required': [('state', '=', 'trigger')]}"
|
||||
domain="[('wkf_id.osv', '=', wkf_model_name)]"/>
|
||||
@@ -327,11 +322,9 @@
|
||||
</p>
|
||||
<group attrs="{'invisible': [('model_id', '=', False)]}">
|
||||
<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')]}"/>
|
||||
|
||||
<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')]}"/>
|
||||
|
||||
<label for="ref_object" string=" "
|
||||
@@ -342,12 +335,10 @@
|
||||
attrs="{'invisible': ['&',
|
||||
'|', ('state', '!=', 'object_write'), ('use_write', '!=', 'other'),
|
||||
'|', ('state', '!=', 'object_create'), ('use_create', '!=', 'copy_other')]}">
|
||||
<field name="ref_object" nolabel="1"
|
||||
on_change="on_change_crud_config(state, use_create, use_write, ref_object, crud_model_id, model_id)"/>
|
||||
<field name="ref_object" nolabel="1"/>
|
||||
</div>
|
||||
|
||||
<field name="crud_model_id"
|
||||
on_change="on_change_crud_model_id(crud_model_id)"
|
||||
attrs="{'invisible': ['|', ('state', '!=', 'object_create'), ('use_create', '!=', 'new_other')]}"/>
|
||||
<field name="crud_model_name" invisible="1"/>
|
||||
|
||||
@@ -376,7 +367,6 @@
|
||||
<li>object.partner_id.currency_id</li>
|
||||
</ul>
|
||||
<field name="write_expression"
|
||||
on_change="on_change_write_expression(write_expression, model_id)"
|
||||
attrs="{'required': [('state', '=', 'object_write'), ('use_write', '=', 'expression')]}"/>
|
||||
</div>
|
||||
|
||||
@@ -445,18 +435,16 @@
|
||||
</p>
|
||||
<field name="model_object_field"
|
||||
attrs="{'invisible': [('model_id', '=', False)]}"
|
||||
domain="[('model_id', '=', model_id), ('ttype', '!=', 'one2many'), ('ttype', '!=', 'many2many')]"
|
||||
on_change="onchange_sub_model_object_value_field(model_object_field)"/>
|
||||
domain="[('model_id', '=', model_id), ('ttype', '!=', 'one2many'), ('ttype', '!=', 'many2many')]"/>
|
||||
<field name="sub_object" readonly="1" attrs="{'invisible': [('model_id', '=', False)]}"/>
|
||||
<field name="sub_model_object_field"
|
||||
domain="[('model_id', '=', sub_object), ('ttype', '!=', 'one2many'), ('ttype', '!=', 'many2many')]"
|
||||
attrs="{'readonly':[('sub_object','=',False)],
|
||||
'required':[('sub_object','!=',False)],
|
||||
'invisible': [('model_id', '=', False)]}"
|
||||
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
|
||||
'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>
|
||||
<field name="id_object" on_change="onchange_id_object(id_object)"/>
|
||||
<field name="id_object"/>
|
||||
<field name="id_value" />
|
||||
</group>
|
||||
</group>
|
||||
@@ -583,5 +571,4 @@ if config.get('type') not in ('ir.actions.act_window_close',):
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<!-- Attachment -->
|
||||
<record id="view_attachment_form" model="ir.ui.view">
|
||||
<field name="model">ir.attachment</field>
|
||||
@@ -104,5 +103,4 @@
|
||||
</record>
|
||||
<menuitem action="action_attachment" id="menu_action_attachment" parent="base.next_id_9"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -3,31 +3,33 @@
|
||||
|
||||
import logging
|
||||
|
||||
from openerp import models
|
||||
from odoo import api, models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AutoVacuum(models.TransientModel):
|
||||
""" Expose the vacuum method to the cron jobs mechanism. """
|
||||
_name = 'ir.autovacuum'
|
||||
|
||||
|
||||
def _gc_transient_models(self, cr, uid, *args, **kwargs):
|
||||
@api.model
|
||||
def _gc_transient_models(self):
|
||||
for model in self.pool.itervalues():
|
||||
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):
|
||||
cr.execute("""
|
||||
def _gc_user_logs(self):
|
||||
self.env.cr.execute("""
|
||||
DELETE FROM res_users_log log1 WHERE EXISTS (
|
||||
SELECT 1 FROM res_users_log log2
|
||||
WHERE log1.create_uid = log2.create_uid
|
||||
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):
|
||||
self._gc_transient_models(cr, uid, *args, **kwargs)
|
||||
self._gc_user_logs(cr, uid, *args, **kwargs)
|
||||
@api.model
|
||||
def power_on(self):
|
||||
self._gc_transient_models()
|
||||
self._gc_user_logs()
|
||||
return True
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<record model="ir.cron" id="autovacuum_job">
|
||||
<field name='name'>Auto-vacuum internal data</field>
|
||||
<field name='interval_number'>1</field>
|
||||
@@ -11,6 +10,4 @@
|
||||
<field name="function">power_on</field>
|
||||
<field name="args">()</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -7,9 +7,8 @@ Store database-specific configuration parameters
|
||||
import uuid
|
||||
import datetime
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools import misc, config, ormcache
|
||||
from odoo import api, fields, models
|
||||
from odoo.tools import misc, config, ormcache
|
||||
|
||||
"""
|
||||
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."""
|
||||
|
||||
_name = 'ir.config_parameter'
|
||||
_rec_name = 'key'
|
||||
|
||||
_columns = {
|
||||
'key': fields.char('Key', required=True, select=1),
|
||||
'value': fields.text('Value', required=True),
|
||||
'group_ids': fields.many2many('res.groups', 'ir_config_parameter_groups_rel', 'icp_id', 'group_id', string='Groups'),
|
||||
}
|
||||
key = fields.Char(required=True, index=True)
|
||||
value = fields.Text(required=True)
|
||||
group_ids = fields.Many2many('res.groups', 'ir_config_parameter_groups_rel', 'icp_id', 'group_id', string='Groups')
|
||||
|
||||
_sql_constraints = [
|
||||
('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.
|
||||
It overrides existing parameters if force is ``True``.
|
||||
"""
|
||||
for key, func in _default_parameters.iteritems():
|
||||
# force=True skips search and always performs the 'if' body (because ids=False)
|
||||
ids = not force and self.search(cr, SUPERUSER_ID, [('key','=',key)])
|
||||
if not ids:
|
||||
self = not force and self.sudo().search([('key', '=', key)])
|
||||
if not self.ids:
|
||||
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.
|
||||
|
||||
: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.
|
||||
:rtype: string
|
||||
"""
|
||||
result = self._get_param(cr, uid, key)
|
||||
if result is None:
|
||||
return default
|
||||
return result
|
||||
result = self._get_param(key)
|
||||
if result:
|
||||
return result
|
||||
return default
|
||||
|
||||
@ormcache('uid', 'key')
|
||||
def _get_param(self, cr, uid, key):
|
||||
@@ -70,7 +68,8 @@ class ir_config_parameter(osv.osv):
|
||||
return None
|
||||
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.
|
||||
|
||||
:param string key: The key of the parameter value to set.
|
||||
@@ -81,35 +80,35 @@ class ir_config_parameter(osv.osv):
|
||||
:rtype: string
|
||||
"""
|
||||
self._get_param.clear_cache(self)
|
||||
ids = self.search(cr, uid, [('key','=',key)], context=context)
|
||||
self = self.search([('key', '=', key)])
|
||||
|
||||
gids = []
|
||||
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:
|
||||
gids.append((4, res_id))
|
||||
|
||||
vals = {'value': value}
|
||||
if gids:
|
||||
vals.update(group_ids=gids)
|
||||
if ids:
|
||||
param = self.browse(cr, uid, ids[0], context=context)
|
||||
old = param.value
|
||||
if self.ids:
|
||||
if value is not False and value is not None:
|
||||
self.write(cr, uid, ids, vals, context=context)
|
||||
self.write(vals)
|
||||
else:
|
||||
self.unlink(cr, uid, ids, context=context)
|
||||
return old
|
||||
self.unlink()
|
||||
return self.value
|
||||
else:
|
||||
vals.update(key=key)
|
||||
if value is not False and value is not None:
|
||||
self.create(cr, uid, vals, context=context)
|
||||
self.create(vals)
|
||||
return False
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
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)
|
||||
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"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="view_ir_config_search">
|
||||
<field name="model">ir.config_parameter</field>
|
||||
<field name="arch" type="xml">
|
||||
@@ -36,5 +35,4 @@
|
||||
</record>
|
||||
<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"/>
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -4,22 +4,20 @@ import logging
|
||||
import threading
|
||||
import time
|
||||
import psycopg2
|
||||
import pytz
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import pytz
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID, netsvc, api
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.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
|
||||
import odoo
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from odoo.tools.safe_eval import safe_eval as eval
|
||||
|
||||
_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):
|
||||
return eval('tuple(%s)' % (s or ''))
|
||||
@@ -33,58 +31,49 @@ _intervalTypes = {
|
||||
'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).
|
||||
"""
|
||||
|
||||
# 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
|
||||
# loaded yet or was already unloaded (e.g. 'force_db_wakeup' or something)
|
||||
# See also openerp.cron
|
||||
# See also odoo.cron
|
||||
|
||||
_name = "ir.cron"
|
||||
_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 = {
|
||||
'nextcall' : lambda *a: time.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'priority' : 5,
|
||||
'user_id' : lambda obj,cr,uid,context: uid,
|
||||
'interval_number' : 1,
|
||||
'interval_type' : 'months',
|
||||
'numbercall' : 1,
|
||||
'active' : 1,
|
||||
}
|
||||
name = fields.Char(required=True)
|
||||
user_id = fields.Many2one('res.users', string='User', default=lambda self: self.env.user, required=True)
|
||||
active = fields.Boolean(default=True)
|
||||
interval_number = fields.Integer(default=1, help="Repeat every x.")
|
||||
interval_type = fields.Selection([('minutes', 'Minutes'),
|
||||
('hours', 'Hours'),
|
||||
('work_days', 'Work Days'),
|
||||
('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:
|
||||
for this in self.browse(cr, uid, ids, context):
|
||||
for this in self:
|
||||
str2tuple(this.args)
|
||||
except Exception:
|
||||
return False
|
||||
raise Exception('Invalid arguments')
|
||||
return True
|
||||
|
||||
_constraints = [
|
||||
(_check_args, 'Invalid arguments', ['args']),
|
||||
]
|
||||
|
||||
def method_direct_trigger(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context={}
|
||||
context = {}
|
||||
cron_obj = self.browse(cr, uid, ids, context=context)
|
||||
for cron in cron_obj:
|
||||
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:
|
||||
args = str2tuple(args)
|
||||
openerp.modules.registry.RegistryManager.check_registry_signaling(cr.dbname)
|
||||
registry = openerp.registry(cr.dbname)
|
||||
odoo.modules.registry.RegistryManager.check_registry_signaling(cr.dbname)
|
||||
registry = odoo.registry(cr.dbname)
|
||||
if model_name in registry:
|
||||
model = registry[model_name]
|
||||
if hasattr(model, method_name):
|
||||
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):
|
||||
start_time = time.time()
|
||||
getattr(model, method_name)(cr, uid, *args)
|
||||
if _logger.isEnabledFor(logging.DEBUG):
|
||||
end_time = time.time()
|
||||
_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:
|
||||
msg = "Method `%s.%s` does not exist." % (model_name, method_name)
|
||||
_logger.warning(msg)
|
||||
@@ -150,8 +139,8 @@ class ir_cron(osv.osv):
|
||||
"""
|
||||
try:
|
||||
with api.Environment.manage():
|
||||
now = 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))
|
||||
now = odoo.osv.fields.datetime.context_timestamp(job_cr, job['user_id'], datetime.now())
|
||||
nextcall = odoo.osv.fields.datetime.context_timestamp(job_cr, job['user_id'], datetime.strptime(job['nextcall'], DEFAULT_SERVER_DATETIME_FORMAT))
|
||||
numbercall = job['numbercall']
|
||||
|
||||
ok = False
|
||||
@@ -167,8 +156,8 @@ class ir_cron(osv.osv):
|
||||
if not numbercall:
|
||||
addsql = ', active=False'
|
||||
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']))
|
||||
self.invalidate_cache(job_cr, SUPERUSER_ID)
|
||||
(nextcall.astimezone(pytz.UTC).strftime(DEFAULT_SERVER_DATETIME_FORMAT), numbercall, job['id']))
|
||||
self.invalidate_cache(job_cr, odoo.SUPERUSER_ID)
|
||||
|
||||
finally:
|
||||
job_cr.commit()
|
||||
@@ -186,7 +175,7 @@ class ir_cron(osv.osv):
|
||||
|
||||
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
|
||||
cr = db.cursor()
|
||||
jobs = []
|
||||
@@ -237,7 +226,7 @@ class ir_cron(osv.osv):
|
||||
_logger.debug('Starting job `%s`.', job['name'])
|
||||
job_cr = db.cursor()
|
||||
try:
|
||||
registry = openerp.registry(db_name)
|
||||
registry = odoo.registry(db_name)
|
||||
registry[cls._name]._process_job(job_cr, job, lock_cr)
|
||||
except Exception:
|
||||
_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
|
||||
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
|
||||
|
||||
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,
|
||||
to make sure a following write() or unlink() will not block due
|
||||
to a process currently executing those cron tasks"""
|
||||
try:
|
||||
cr.execute("""SELECT id FROM "%s" WHERE id IN %%s FOR UPDATE NOWAIT""" % self._table,
|
||||
(tuple(ids),), log_exceptions=False)
|
||||
self.env.cr.execute("""SELECT id FROM "%s" WHERE id IN %%s FOR UPDATE NOWAIT""" % self._table,
|
||||
(tuple(self.ids),), log_exceptions=False)
|
||||
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: "
|
||||
"This cron task is currently being executed and may not be modified "
|
||||
"Please try again in a few minutes"))
|
||||
"This cron task is currently being executed and may not be modified "
|
||||
"Please try again in a few minutes"))
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
res = super(ir_cron, self).create(cr, uid, vals, context=context)
|
||||
return res
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
return super(ir_cron, self).create(vals)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
self._try_lock(cr, uid, ids, context)
|
||||
res = super(ir_cron, self).write(cr, uid, ids, vals, context=context)
|
||||
return res
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
self._try_lock()
|
||||
return super(ir_cron, self).write(vals)
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
self._try_lock(cr, uid, ids, context)
|
||||
res = super(ir_cron, self).unlink(cr, uid, ids, context=context)
|
||||
return res
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
self._try_lock()
|
||||
return super(ir_cron, self).unlink()
|
||||
|
||||
def try_write(self, cr, uid, ids, values, context=None):
|
||||
@api.multi
|
||||
def try_write(self, values):
|
||||
try:
|
||||
with cr.savepoint():
|
||||
cr.execute("""SELECT id FROM "%s" WHERE id IN %%s FOR UPDATE NOWAIT""" % self._table,
|
||||
(tuple(ids),), log_exceptions=False)
|
||||
with self.env.cr.savepoint():
|
||||
self.env.cr.execute("""SELECT id FROM "%s" WHERE id IN %%s FOR UPDATE NOWAIT""" % self._table,
|
||||
(tuple(self.ids),), log_exceptions=False)
|
||||
except psycopg2.OperationalError:
|
||||
pass
|
||||
else:
|
||||
return super(ir_cron, self).write(cr, uid, ids, values, context=context)
|
||||
return super(ir_cron, self).write(values)
|
||||
return False
|
||||
|
||||
def toggle(self, cr, uid, ids, model, domain, context=None):
|
||||
active = bool(self.pool[model].search_count(cr, uid, domain, context=context))
|
||||
|
||||
return self.try_write(cr, uid, ids, {'active': active}, context=context)
|
||||
@api.model
|
||||
def toggle(self, model, domain):
|
||||
active = bool(self.env[model].search_count(domain))
|
||||
return self.try_write({'active': active})
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<!-- ir.cron -->
|
||||
<record id="ir_cron_view" model="ir.ui.view">
|
||||
<field name="model">ir.cron</field>
|
||||
@@ -90,5 +89,4 @@
|
||||
</record>
|
||||
<menuitem id="menu_ir_cron_act" action="ir_cron_act" parent="base.menu_automation"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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"
|
||||
_order = 'name'
|
||||
_columns = {
|
||||
'name': fields.char('Export Name'),
|
||||
'resource': fields.char('Resource', select=True),
|
||||
'export_fields': fields.one2many('ir.exports.line', 'export_id',
|
||||
'Export ID', copy=True),
|
||||
}
|
||||
|
||||
name = fields.Char(string='Export Name')
|
||||
resource = fields.Char(select=True)
|
||||
export_fields = fields.One2many('ir.exports.line', 'export_id', string='Export ID', copy=True)
|
||||
|
||||
|
||||
class ir_exports_line(osv.osv):
|
||||
class IrExportsLine(models.Model):
|
||||
_name = 'ir.exports.line'
|
||||
_order = 'id'
|
||||
_columns = {
|
||||
'name': fields.char('Field Name'),
|
||||
'export_id': fields.many2one('ir.exports', 'Export', select=True, ondelete='cascade'),
|
||||
}
|
||||
|
||||
name = fields.Char(string='Field Name')
|
||||
export_id = fields.Many2one('ir.exports', string='Export', select=True, ondelete='cascade')
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import functools
|
||||
import itertools
|
||||
import time
|
||||
@@ -7,8 +8,8 @@ import time
|
||||
import psycopg2
|
||||
import pytz
|
||||
|
||||
from openerp import models, fields, api, _
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, ustr
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, ustr
|
||||
|
||||
REFERENCING_FIELDS = {None, 'id', '.id'}
|
||||
def only_ref_fields(record):
|
||||
@@ -33,7 +34,7 @@ class ImportWarning(Warning):
|
||||
class ConversionNotFound(ValueError): pass
|
||||
|
||||
|
||||
class ir_fields_converter(models.AbstractModel):
|
||||
class IrFieldsConverter(models.AbstractModel):
|
||||
_name = 'ir.fields.converter'
|
||||
|
||||
@api.model
|
||||
@@ -389,8 +390,7 @@ class ir_fields_converter(models.AbstractModel):
|
||||
|
||||
subfield, w1 = self._referencing_subfield(record)
|
||||
|
||||
reference = record[subfield]
|
||||
id, _, w2 = self.db_id_for(model, field, subfield, reference)
|
||||
id, _, w2 = self.db_id_for(model, field, subfield, record[subfield])
|
||||
return id, w1 + w2
|
||||
|
||||
@api.model
|
||||
@@ -435,8 +435,7 @@ class ir_fields_converter(models.AbstractModel):
|
||||
if refs:
|
||||
subfield, w1 = self._referencing_subfield(refs)
|
||||
warnings.extend(w1)
|
||||
reference = record[subfield]
|
||||
id, _, w2 = self.db_id_for(model, field, subfield, reference)
|
||||
id, _, w2 = self.db_id_for(model, field, subfield, record[subfield])
|
||||
warnings.extend(w2)
|
||||
|
||||
writable = convert(exclude_ref_fields(record), log)
|
||||
|
||||
@@ -1,33 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp import exceptions
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.translate import _
|
||||
from odoo import exceptions
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
class ir_filters(osv.osv):
|
||||
|
||||
class IrFilters(models.Model):
|
||||
_name = 'ir.filters'
|
||||
_description = 'Filters'
|
||||
_order = 'model_id, name, id desc'
|
||||
|
||||
def _list_all_models(self, cr, uid, context=None):
|
||||
cr.execute("SELECT model, name FROM ir_model ORDER BY name")
|
||||
return cr.fetchall()
|
||||
def _list_all_models(self):
|
||||
self.env.cr.execute("SELECT model, name FROM ir_model ORDER BY name")
|
||||
return self.env.cr.fetchall()
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
name = self.read(cr, uid, [id], ['name'])[0]['name']
|
||||
default.update({'name':_('%s (copy)') % name})
|
||||
return super(ir_filters, self).copy(cr, uid, id, default, context)
|
||||
name = fields.Char(string='Filter Name', translate=True, required=True)
|
||||
user_id = fields.Many2one('res.users', string='User', ondelete='cascade', default=lambda self: self._uid,
|
||||
help="The user this filter is private to. When left empty the filter is public "
|
||||
"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
|
||||
same context (menu/view) as the given action."""
|
||||
if action_id:
|
||||
# filters specific to this menu + global ones
|
||||
return [('action_id', 'in' , [action_id, False])]
|
||||
return [('action_id', 'in', [action_id, False])]
|
||||
# only global ones
|
||||
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.
|
||||
|
||||
: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),
|
||||
# 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(cr, uid, action_id)
|
||||
filter_ids = self.search(cr, uid, action_domain +
|
||||
[('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)
|
||||
action_domain = self._get_action_domain(action_id)
|
||||
filter_ids = self.search(action_domain + [('model_id', '=', model), ('user_id', 'in', [self._uid, False])])
|
||||
my_filters = filter_ids.with_context(self.env.user.context_get()).read(['name', 'is_default', 'domain', 'context', 'user_id', 'sort'])
|
||||
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
|
||||
|
||||
Checks if there is a global default for the model_id requested.
|
||||
@@ -64,96 +79,66 @@ class ir_filters(osv.osv):
|
||||
:raises openerp.exceptions.Warning: if there is an existing default and
|
||||
we're not updating it
|
||||
"""
|
||||
action_domain = self._get_action_domain(cr, uid, vals.get('action_id'))
|
||||
existing_default = self.search(cr, uid, action_domain + [
|
||||
action_domain = self._get_action_domain(vals.get('action_id'))
|
||||
existing_default = self.search(action_domain + [
|
||||
('model_id', '=', vals['model_id']),
|
||||
('user_id', '=', False),
|
||||
('is_default', '=', True)], context=context)
|
||||
('is_default', '=', True)])
|
||||
|
||||
if not existing_default: return
|
||||
if matching_filters and \
|
||||
(matching_filters[0]['id'] == existing_default[0]):
|
||||
if not existing_default:
|
||||
return
|
||||
if matching_filters and (matching_filters[0]['id'] == existing_default.id):
|
||||
return
|
||||
|
||||
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['model_id']
|
||||
})
|
||||
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')})
|
||||
|
||||
def create_or_replace(self, cr, uid, vals, context=None):
|
||||
lower_name = vals['name'].lower()
|
||||
@api.model
|
||||
def create_or_replace(self, vals):
|
||||
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
|
||||
if f['name'].lower() == lower_name
|
||||
# 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,
|
||||
# or f.user_id.id == vals.user_id
|
||||
if (f['user_id'] and f['user_id'][0]) == vals.get('user_id', False)]
|
||||
if f['name'].lower() == vals['name'].lower()
|
||||
# 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,
|
||||
# or f.user_id.id == vals.user_id
|
||||
if (f['user_id'] and f['user_id'][0]) == vals.get('user_id')]
|
||||
|
||||
if vals.get('is_default'):
|
||||
if vals.get('user_id'):
|
||||
# Setting new default: any other default that belongs to the user
|
||||
# should be turned off
|
||||
action_domain = self._get_action_domain(cr, uid, action_id)
|
||||
act_ids = self.search(cr, uid, action_domain + [
|
||||
('model_id', '=', vals['model_id']),
|
||||
('user_id', '=', vals['user_id']),
|
||||
('is_default', '=', True),
|
||||
], context=context)
|
||||
action_domain = self._get_action_domain(action_id)
|
||||
act_ids = self.search(action_domain + [
|
||||
('model_id', '=', vals['model_id']),
|
||||
('user_id', '=', vals['user_id']),
|
||||
('is_default', '=', True),
|
||||
])
|
||||
if act_ids:
|
||||
self.write(cr, uid, act_ids, {'is_default': False}, context=context)
|
||||
act_ids.write({'is_default': False})
|
||||
else:
|
||||
self._check_global_default(
|
||||
cr, uid, vals, matching_filters, context=None)
|
||||
self._check_global_default(vals, matching_filters)
|
||||
|
||||
# When a filter exists for the same (name, model, user) triple, we simply
|
||||
# replace its definition (considering action_id irrelevant here)
|
||||
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 self.create(cr, uid, vals, context)
|
||||
return self.create(vals)
|
||||
|
||||
_sql_constraints = [
|
||||
# Partial constraint, complemented by unique index (see below)
|
||||
# Still useful to keep because it provides a proper error message when a violation
|
||||
# occurs, as it shares the same prefix as the unique index.
|
||||
# occurs, as it shares the same prefix as the unique index.
|
||||
('name_model_uid_unique', 'unique (name, model_id, user_id, action_id)', 'Filter names must be unique'),
|
||||
]
|
||||
|
||||
def _auto_init(self, cr, context=None):
|
||||
result = super(ir_filters, self)._auto_init(cr, context)
|
||||
def _auto_init(self):
|
||||
result = super(IrFilters, self)._auto_init()
|
||||
# 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
|
||||
cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = 'ir_filters_name_model_uid_unique_action_index'")
|
||||
if not cr.fetchone():
|
||||
cr.execute("""CREATE UNIQUE INDEX "ir_filters_name_model_uid_unique_action_index" ON ir_filters
|
||||
self.env.cr.execute("DROP INDEX IF EXISTS ir_filters_name_model_uid_unique_index") # drop old index w/o action
|
||||
self.env.cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = 'ir_filters_name_model_uid_unique_action_index'")
|
||||
if not self.env.cr.fetchone():
|
||||
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))""")
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Filter Name', translate=True, required=True),
|
||||
'user_id': fields.many2one('res.users', 'User', ondelete='cascade',
|
||||
help="The user this filter is private to. When left empty the filter is public "
|
||||
"and available to all users."),
|
||||
'domain': fields.text('Domain', required=True),
|
||||
'context': fields.text('Context', required=True),
|
||||
'sort': fields.text('Sort', required=True),
|
||||
'model_id': fields.selection(_list_all_models, 'Model', required=True),
|
||||
'is_default': fields.boolean('Default filter'),
|
||||
'action_id': fields.many2one('ir.actions.actions', 'Action', ondelete='cascade',
|
||||
help="The menu action this filter applies to. "
|
||||
"When left empty the filter applies to all menus "
|
||||
"for this model."),
|
||||
'active': fields.boolean('Active')
|
||||
}
|
||||
_defaults = {
|
||||
'domain': '[]',
|
||||
'context':'{}',
|
||||
'sort': '[]',
|
||||
'user_id': lambda self,cr,uid,context=None: uid,
|
||||
'is_default': False,
|
||||
'active': True
|
||||
}
|
||||
_order = 'model_id, name, id desc'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- Restrict modifications on ir.filters to owner only -->
|
||||
<record id="ir_filters_rule" model="ir.rule">
|
||||
@@ -73,4 +73,4 @@
|
||||
<menuitem parent="base.next_id_2" name="User-defined Filters"
|
||||
id="menu_ir_filters" action="actions_ir_filters_view" sequence="5"/>
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -18,18 +18,17 @@ import werkzeug.routing
|
||||
import werkzeug.urls
|
||||
import werkzeug.utils
|
||||
|
||||
import openerp
|
||||
import openerp.exceptions
|
||||
import openerp.models
|
||||
from openerp import http
|
||||
from openerp.http import request, STATIC_CACHE
|
||||
from openerp.modules.module import get_resource_path, get_module_path
|
||||
from openerp.osv import osv, orm
|
||||
import odoo
|
||||
from odoo import models, tools
|
||||
from odoo import http
|
||||
from odoo.http import request, STATIC_CACHE
|
||||
from odoo.modules.module import get_resource_path, get_module_path
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
UID_PLACEHOLDER = object()
|
||||
|
||||
|
||||
class ModelConverter(werkzeug.routing.BaseConverter):
|
||||
|
||||
def __init__(self, url_map, model=False):
|
||||
@@ -45,6 +44,7 @@ class ModelConverter(werkzeug.routing.BaseConverter):
|
||||
def to_url(self, value):
|
||||
return value.id
|
||||
|
||||
|
||||
class ModelsConverter(werkzeug.routing.BaseConverter):
|
||||
|
||||
def __init__(self, url_map, model=False):
|
||||
@@ -59,12 +59,13 @@ class ModelsConverter(werkzeug.routing.BaseConverter):
|
||||
def to_url(self, value):
|
||||
return ",".join(i.id for i in value)
|
||||
|
||||
|
||||
class SignedIntConverter(werkzeug.routing.NumberConverter):
|
||||
regex = r'-?\d+'
|
||||
num_convert = int
|
||||
|
||||
|
||||
class ir_http(osv.AbstractModel):
|
||||
class IrHttp(models.AbstractModel):
|
||||
_name = 'ir.http'
|
||||
_description = "HTTP routing"
|
||||
|
||||
@@ -84,7 +85,7 @@ class ir_http(osv.AbstractModel):
|
||||
|
||||
def _auth_method_public(self):
|
||||
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:
|
||||
request.uid = request.session.uid
|
||||
|
||||
@@ -96,22 +97,22 @@ class ir_http(osv.AbstractModel):
|
||||
# what if error in security.check()
|
||||
# -> res_users.check()
|
||||
# -> 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),
|
||||
# let them bubble up
|
||||
request.session.logout(keep_db=True)
|
||||
if request.uid is None:
|
||||
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
|
||||
except Exception:
|
||||
_logger.info("Exception during request Authentication.", exc_info=True)
|
||||
raise openerp.exceptions.AccessDenied()
|
||||
raise odoo.exceptions.AccessDenied()
|
||||
return auth_method
|
||||
|
||||
def _serve_attachment(self):
|
||||
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:
|
||||
wdate = attach[0]['__last_update']
|
||||
datas = attach[0]['datas'] or ''
|
||||
@@ -123,7 +124,7 @@ class ir_http(osv.AbstractModel):
|
||||
return werkzeug.utils.redirect(name, 301)
|
||||
|
||||
response = werkzeug.wrappers.Response()
|
||||
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
|
||||
server_format = tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
|
||||
try:
|
||||
response.last_modified = datetime.datetime.strptime(wdate, server_format + '.%f')
|
||||
except ValueError:
|
||||
@@ -151,11 +152,11 @@ class ir_http(osv.AbstractModel):
|
||||
return attach
|
||||
|
||||
# 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
|
||||
try:
|
||||
return request._handle_exception(exception)
|
||||
except openerp.exceptions.AccessDenied:
|
||||
except odoo.exceptions.AccessDenied:
|
||||
return werkzeug.exceptions.Forbidden()
|
||||
|
||||
def _dispatch(self):
|
||||
@@ -190,26 +191,26 @@ class ir_http(osv.AbstractModel):
|
||||
def _postprocess_args(self, arguments, rule):
|
||||
""" post process arg to set uid on browse records """
|
||||
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)
|
||||
try:
|
||||
arg.exists()
|
||||
except openerp.models.MissingError:
|
||||
except models.MissingError:
|
||||
return self._handle_exception(werkzeug.exceptions.NotFound())
|
||||
|
||||
def routing_map(self):
|
||||
if not hasattr(self, '_routing_map'):
|
||||
_logger.info("Generating routing map")
|
||||
installed = request.registry._init_modules - {'web'}
|
||||
if openerp.tools.config['test_enable']:
|
||||
installed.add(openerp.modules.module.current_test)
|
||||
mods = [''] + openerp.conf.server_wide_modules + sorted(installed)
|
||||
if tools.config['test_enable']:
|
||||
installed.add(odoo.modules.module.current_test)
|
||||
mods = [''] + odoo.conf.server_wide_modules + sorted(installed)
|
||||
self._routing_map = http.routing_map(mods, False, converters=self._get_converters())
|
||||
|
||||
return self._routing_map
|
||||
|
||||
def content_disposition(self, filename):
|
||||
filename = openerp.tools.ustr(filename)
|
||||
filename = tools.ustr(filename)
|
||||
escaped = urllib2.quote(filename.encode('utf8'))
|
||||
browser = request.httprequest.user_agent.browser
|
||||
version = int((request.httprequest.user_agent.version or '0').split('.')[0])
|
||||
@@ -255,7 +256,7 @@ class ir_http(osv.AbstractModel):
|
||||
# check read access
|
||||
try:
|
||||
last_update = obj['__last_update']
|
||||
except openerp.exceptions.AccessError:
|
||||
except odoo.exceptions.AccessError:
|
||||
return (403, [], None)
|
||||
|
||||
status, headers, content = None, [], None
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
# 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'
|
||||
_order = 'id DESC'
|
||||
|
||||
EXCEPTIONS_TYPE = [
|
||||
('client', 'Client'),
|
||||
('server', 'Server')
|
||||
]
|
||||
|
||||
_columns = {
|
||||
'create_date': fields.datetime('Create Date', readonly=True),
|
||||
'create_uid': fields.integer('Uid', readonly=True), # Integer not m2o is intentionnal
|
||||
'name': fields.char('Name', required=True),
|
||||
'type': fields.selection(EXCEPTIONS_TYPE, string='Type', required=True, select=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),
|
||||
}
|
||||
create_date = fields.Datetime(readonly=True)
|
||||
create_uid = fields.Integer(string='Uid', readonly=True) # Integer not m2o is intentionnal
|
||||
name = fields.Char(required=True)
|
||||
type = fields.Selection([('client', 'Client'), ('server', 'Server')], required=True, select=True)
|
||||
dbname = fields.Char(string='Database Name', select=True)
|
||||
level = fields.Char(select=True)
|
||||
message = fields.Text(required=True)
|
||||
path = fields.Char(required=True)
|
||||
func = fields.Char(string='Function', required=True)
|
||||
line = fields.Char(required=True)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record model="ir.ui.view" id="ir_logging_form_view">
|
||||
<field name="model">ir.logging</field>
|
||||
<field name="arch" type="xml">
|
||||
@@ -62,5 +61,4 @@
|
||||
<field name="search_view_id" ref="ir_logging_search_view" />
|
||||
</record>
|
||||
<menuitem parent="base.next_id_9" id="ir_logging_all_menu" action="ir_logging_all_act" />
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.base import MIMEBase
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email import Encoders
|
||||
from email.charset import Charset
|
||||
from email.header import Header
|
||||
from email.utils import formatdate, make_msgid, COMMASPACE, getaddresses, formataddr
|
||||
from email import Encoders
|
||||
from email.mime.base import MIMEBase
|
||||
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 re
|
||||
import smtplib
|
||||
import threading
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import html2text
|
||||
import openerp.tools as tools
|
||||
from openerp.exceptions import UserError
|
||||
from openerp.exceptions import except_orm
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import except_orm
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import html2text
|
||||
|
||||
# ustr was originally from tools.misc.
|
||||
# it is moved to loglevels until we refactor tools.
|
||||
from openerp.loglevels import ustr
|
||||
from odoo.loglevels import ustr
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
_test_logger = logging.getLogger('openerp.tests')
|
||||
@@ -34,6 +31,7 @@ class MailDeliveryException(except_orm):
|
||||
def __init__(self, name, value):
|
||||
super(MailDeliveryException, self).__init__(name, value)
|
||||
|
||||
|
||||
class WriteToLogger(object):
|
||||
"""debugging helper: behave as a fd and pipe to logger at the given level"""
|
||||
def __init__(self, logger, level=logging.DEBUG):
|
||||
@@ -58,6 +56,7 @@ def try_coerce_ascii(string_utf8):
|
||||
return
|
||||
return string_utf8
|
||||
|
||||
|
||||
def encode_header(header_text):
|
||||
"""Returns an appropriate representation of the given header value,
|
||||
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
|
||||
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
|
||||
# encoded as ASCII in 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
|
||||
# 7-bit string.
|
||||
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):
|
||||
"""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.
|
||||
"""
|
||||
# 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_ascii = try_coerce_ascii(param_text_utf8)
|
||||
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
|
||||
name_with_email_pattern = re.compile(r'("[^<@>]+")\s*<([^ ,<@]+@[^> ,]+)>')
|
||||
address_pattern = re.compile(r'([^ ,<@]+@[^> ,]+)')
|
||||
|
||||
|
||||
def extract_rfc2822_addresses(text):
|
||||
"""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.
|
||||
"""
|
||||
if not text: return []
|
||||
if not text:
|
||||
return []
|
||||
candidates = address_pattern.findall(tools.ustr(text).encode('utf-8'))
|
||||
return filter(try_coerce_ascii, candidates)
|
||||
|
||||
|
||||
def encode_rfc2822_address_header(header_text):
|
||||
"""If ``header_text`` contains non-ASCII characters,
|
||||
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))
|
||||
|
||||
|
||||
class ir_mail_server(osv.osv):
|
||||
class IrMailServer(models.Model):
|
||||
"""Represents an SMTP server, able to send outgoing emails, with SSL and TLS capabilities."""
|
||||
_name = "ir.mail_server"
|
||||
|
||||
NO_VALID_RECIPIENT = ("At least one valid recipient address should be "
|
||||
"specified for outgoing emails (To/Cc/Bcc)")
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Description', required=True, select=True),
|
||||
'smtp_host': fields.char('SMTP Server', required=True, help="Hostname or IP of SMTP server"),
|
||||
'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('Username', size=64, help="Optional username for SMTP authentication"),
|
||||
'smtp_pass': fields.char('Password', size=64, help="Optional password for SMTP authentication"),
|
||||
'smtp_encryption': fields.selection([('none','None'),
|
||||
('starttls','TLS (STARTTLS)'),
|
||||
('ssl','SSL/TLS')],
|
||||
string='Connection Security', required=True,
|
||||
help="Choose the connection encryption scheme:\n"
|
||||
"- None: SMTP sessions are done in cleartext.\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)"),
|
||||
'smtp_debug': fields.boolean('Debugging', help="If enabled, the full output of SMTP sessions will "
|
||||
"be written to the server log at DEBUG level"
|
||||
"(this is very verbose and may include confidential info!)"),
|
||||
'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)"),
|
||||
'active': fields.boolean('Active')
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'smtp_port': 25,
|
||||
'active': True,
|
||||
'sequence': 10,
|
||||
'smtp_encryption': 'none',
|
||||
}
|
||||
name = fields.Char(string='Description', required=True, index=True)
|
||||
smtp_host = fields.Char(string='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_user = fields.Char(string='Username', size=64, help="Optional username for SMTP authentication")
|
||||
smtp_pass = fields.Char(string='Password', size=64, help="Optional password for SMTP authentication")
|
||||
smtp_encryption = fields.Selection([('none', 'None'),
|
||||
('starttls', 'TLS (STARTTLS)'),
|
||||
('ssl', 'SSL/TLS')],
|
||||
string='Connection Security', required=True, default='none',
|
||||
help="Choose the connection encryption scheme:\n"
|
||||
"- None: SMTP sessions are done in cleartext.\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)")
|
||||
smtp_debug = fields.Boolean(string='Debugging', help="If enabled, the full output of SMTP sessions will "
|
||||
"be written to the server log at DEBUG level"
|
||||
"(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 "
|
||||
"is used. Default priority is 10 (smaller number = higher priority)")
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# 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)
|
||||
smtplib.stderr = 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):
|
||||
return [(a["id"], "(%s)" % (a['name'])) for a in self.read(cr, uid, ids, ['name'], context=context)]
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
return [(a.id, "(%s)" % (a.name)) for a in self]
|
||||
|
||||
def test_smtp_connection(self, cr, uid, ids, context=None):
|
||||
for smtp_server in self.browse(cr, uid, ids, context=context):
|
||||
def test_smtp_connection(self):
|
||||
for smtp_server in self:
|
||||
smtp = False
|
||||
try:
|
||||
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))
|
||||
finally:
|
||||
try:
|
||||
if smtp: smtp.quit()
|
||||
if smtp:
|
||||
smtp.quit()
|
||||
except Exception:
|
||||
# ignored, just a consequence of the previous exception
|
||||
pass
|
||||
@@ -214,7 +212,7 @@ class ir_mail_server(osv.osv):
|
||||
if encryption == 'ssl':
|
||||
if not 'SMTP_SSL' in smtplib.__all__:
|
||||
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)
|
||||
else:
|
||||
connection = smtplib.SMTP(host, port)
|
||||
@@ -234,13 +232,13 @@ class ir_mail_server(osv.osv):
|
||||
# certain hashing schemes, like HMAC.
|
||||
# See also bug #597143 and python issue #5285
|
||||
user = tools.ustr(user).encode('utf-8')
|
||||
password = tools.ustr(password).encode('utf-8')
|
||||
password = tools.ustr(password).encode('utf-8')
|
||||
connection.login(user, password)
|
||||
return connection
|
||||
|
||||
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,
|
||||
body_alternative=None, subtype_alternative='plain'):
|
||||
attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None,
|
||||
body_alternative=None, subtype_alternative='plain'):
|
||||
"""Constructs an RFC2822 email.message.Message object based on the keyword arguments passed, and returns it.
|
||||
|
||||
: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,
|
||||
# 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_bcc: email_bcc = []
|
||||
@@ -345,7 +343,7 @@ class ir_mail_server(osv.osv):
|
||||
msg.attach(part)
|
||||
return msg
|
||||
|
||||
def _get_default_bounce_address(self, cr, uid, context=None):
|
||||
def _get_default_bounce_address(self):
|
||||
'''Compute the default bounce address.
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
get_param = self.pool['ir.config_parameter'].get_param
|
||||
postmaster = get_param(cr, SUPERUSER_ID, 'mail.bounce.alias',
|
||||
default='postmaster-odoo',
|
||||
context=context,)
|
||||
domain = get_param(cr, SUPERUSER_ID, 'mail.catchall.domain', context=context)
|
||||
get_param = self.env['ir.config_parameter'].sudo().get_param
|
||||
postmaster = get_param('mail.bounce.alias', default='postmaster-odoo')
|
||||
domain = get_param('mail.catchall.domain')
|
||||
if postmaster and domain:
|
||||
return '%s@%s' % (postmaster, domain)
|
||||
|
||||
def send_email(self, cr, uid, message, mail_server_id=None, smtp_server=None, smtp_port=None,
|
||||
smtp_user=None, smtp_password=None, smtp_encryption=None, smtp_debug=False,
|
||||
context=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):
|
||||
"""Sends an email directly (no queuing).
|
||||
|
||||
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.
|
||||
smtp_from = message['Return-Path']
|
||||
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:
|
||||
smtp_from = message['From']
|
||||
assert smtp_from, "The Return-Path or From header is required for any outbound email"
|
||||
@@ -413,15 +408,15 @@ class ir_mail_server(osv.osv):
|
||||
email_to = message['To']
|
||||
email_cc = message['Cc']
|
||||
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
|
||||
|
||||
x_forge_to = message['X-Forge-To']
|
||||
if x_forge_to:
|
||||
# `To:` header forged, e.g. for posting on mail.channels, to avoid confusion
|
||||
del message['X-Forge-To']
|
||||
del message['To'] # avoid multiple To: headers!
|
||||
del message['To'] # avoid multiple To: headers!
|
||||
message['To'] = x_forge_to
|
||||
|
||||
# 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
|
||||
mail_server = None
|
||||
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:
|
||||
mail_server_ids = self.search(cr, SUPERUSER_ID, [], order='sequence', limit=1)
|
||||
if mail_server_ids:
|
||||
mail_server = self.browse(cr, SUPERUSER_ID, mail_server_ids[0])
|
||||
mail_server = self.sudo().search([], order='sequence', limit=1)
|
||||
|
||||
if mail_server:
|
||||
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_password = smtp_password or tools.config.get('smtp_password')
|
||||
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:
|
||||
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:
|
||||
message_id = message['Message-Id']
|
||||
@@ -464,7 +457,7 @@ class ir_mail_server(osv.osv):
|
||||
if smtp_server.startswith('maildir:/'):
|
||||
from mailbox import Maildir
|
||||
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))
|
||||
return message_id
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<!-- ir.mail.server -->
|
||||
<record model="ir.ui.view" id="ir_mail_server_form">
|
||||
<field name="model">ir.mail_server</field>
|
||||
@@ -58,5 +57,4 @@
|
||||
<field name="search_view_id" ref="view_ir_mail_server_search"/>
|
||||
</record>
|
||||
<menuitem id="menu_mail_servers" parent="menu_email" action="action_ir_mail_server_list" sequence="15" groups="base.group_no_one"/>
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
+492
-534
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<report
|
||||
id="report_ir_model_overview"
|
||||
@@ -10,6 +9,5 @@
|
||||
name="base.report_irmodeloverview"
|
||||
file="base.report_irmodeloverview"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
|
||||
<!-- menu_create from model form -->
|
||||
<record id="view_model_menu_create" model="ir.ui.view">
|
||||
@@ -562,5 +561,4 @@
|
||||
</record>
|
||||
<menuitem action="ir_access_act" id="menu_ir_access_act" parent="base.menu_security"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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.
|
||||
|
||||
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
|
||||
#------------------------------------------------------
|
||||
|
||||
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
|
||||
:return: domain or False is no action
|
||||
"""
|
||||
@@ -38,10 +39,11 @@ class ir_needaction_mixin(osv.AbstractModel):
|
||||
# "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. """
|
||||
dom = self._needaction_domain_get(cr, uid, context=context)
|
||||
dom = self._needaction_domain_get()
|
||||
if not dom:
|
||||
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)
|
||||
|
||||
+176
-133
@@ -4,8 +4,8 @@ import copy
|
||||
import cStringIO
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
@@ -18,35 +18,35 @@ from urlparse import urlparse
|
||||
|
||||
import babel
|
||||
import babel.dates
|
||||
import psycopg2
|
||||
import werkzeug
|
||||
from lxml import etree, html
|
||||
from PIL import Image
|
||||
import psycopg2
|
||||
|
||||
import openerp.http
|
||||
import openerp.tools
|
||||
from openerp.tools.func import lazy_property
|
||||
import openerp.tools.lru
|
||||
from openerp.exceptions import QWebException
|
||||
from openerp.fields import Datetime
|
||||
from openerp.http import request
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.osv import osv, orm, fields
|
||||
from openerp.tools import html_escape as escape
|
||||
from openerp.tools.misc import find_in_path
|
||||
from openerp.tools.translate import _
|
||||
from openerp.modules.module import get_resource_path
|
||||
import odoo.http
|
||||
import odoo.tools
|
||||
import odoo.tools.lru
|
||||
from odoo import api, models, _
|
||||
from odoo.exceptions import QWebException
|
||||
from odoo.http import request
|
||||
from odoo.modules.module import get_resource_path
|
||||
from odoo.tools import html_escape as escape
|
||||
from odoo.tools.func import lazy_property
|
||||
from odoo.tools.misc import find_in_path
|
||||
from odoo.tools.safe_eval import safe_eval as eval
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
MAX_CSS_RULES = 4095
|
||||
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# QWeb template engine
|
||||
#--------------------------------------------------------------------
|
||||
class QWebTemplateNotFound(QWebException):
|
||||
pass
|
||||
|
||||
|
||||
def raise_qweb_exception(etype=None, **kw):
|
||||
if etype is None:
|
||||
etype = QWebException
|
||||
@@ -60,12 +60,14 @@ def raise_qweb_exception(etype=None, **kw):
|
||||
e.qweb['cause'] = original
|
||||
raise
|
||||
|
||||
|
||||
def _build_attribute(name, value):
|
||||
value = escape(value)
|
||||
if isinstance(name, unicode): name = name.encode('utf-8')
|
||||
if isinstance(value, unicode): value = value.encode('utf-8')
|
||||
return ' %s="%s"' % (name, value)
|
||||
|
||||
|
||||
class FileSystemLoader(object):
|
||||
def __init__(self, path):
|
||||
# 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)
|
||||
return arch
|
||||
|
||||
|
||||
class QWebContext(dict):
|
||||
def __init__(self, cr, uid, data, loader=None, context=None):
|
||||
self.cr = cr
|
||||
@@ -114,7 +117,8 @@ class QWebContext(dict):
|
||||
def __copy__(self):
|
||||
return self.copy()
|
||||
|
||||
class QWeb(orm.AbstractModel):
|
||||
|
||||
class QWeb(models.AbstractModel):
|
||||
""" Base QWeb rendering engine
|
||||
|
||||
* to customize ``t-field`` rendering, subclass ``ir.qweb.field`` and
|
||||
@@ -225,6 +229,7 @@ class QWeb(orm.AbstractModel):
|
||||
def eval_bool(self, 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):
|
||||
""" 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]
|
||||
stack.append(id_or_xml_id)
|
||||
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.attrib.pop("name", False)
|
||||
@@ -259,7 +264,7 @@ class QWeb(orm.AbstractModel):
|
||||
|
||||
debugger = element.get('t-debug')
|
||||
if debugger is not None:
|
||||
if openerp.tools.config['dev_mode']:
|
||||
if odoo.tools.config['dev_mode']:
|
||||
__import__(debugger).set_trace() # pdb, ipdb, pudb, ...
|
||||
else:
|
||||
_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)
|
||||
|
||||
def _iterate(self, iterable):
|
||||
if isinstance (iterable, collections.Mapping):
|
||||
if isinstance(iterable, collections.Mapping):
|
||||
return iterable.iteritems()
|
||||
|
||||
return itertools.izip(*itertools.tee(iterable))
|
||||
@@ -540,11 +545,12 @@ class QWeb(orm.AbstractModel):
|
||||
return True
|
||||
return default
|
||||
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
# QWeb Fields converters
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
class FieldConverter(osv.AbstractModel):
|
||||
class FieldConverter(models.AbstractModel):
|
||||
""" 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:
|
||||
@@ -556,9 +562,9 @@ class FieldConverter(osv.AbstractModel):
|
||||
"""
|
||||
_name = 'ir.qweb.field'
|
||||
|
||||
def attributes(self, cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att, qweb_context,
|
||||
context=None):
|
||||
@api.model
|
||||
def attributes(self, field_name, record, options,
|
||||
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)
|
||||
|
||||
Generates the metadata attributes (prefixed by ``data-oe-`` for the
|
||||
@@ -592,7 +598,8 @@ class FieldConverter(osv.AbstractModel):
|
||||
data.append(('data-oe-readonly', 1))
|
||||
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)
|
||||
|
||||
Converts a single value to its HTML version/output
|
||||
@@ -600,17 +607,18 @@ class FieldConverter(osv.AbstractModel):
|
||||
if not value: return ''
|
||||
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)
|
||||
|
||||
Converts the specified field of the browse_record ``record`` to HTML
|
||||
"""
|
||||
field = record._fields[field_name]
|
||||
return self.value_to_html(
|
||||
cr, uid, record[field_name], field, options=options, context=context)
|
||||
return self.value_to_html(record[field_name], field, options=options)
|
||||
|
||||
def to_html(self, cr, uid, field_name, record, options,
|
||||
source_element, t_att, g_att, qweb_context, context=None):
|
||||
@api.model
|
||||
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)
|
||||
|
||||
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``.
|
||||
"""
|
||||
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):
|
||||
content = escape(content)
|
||||
elif hasattr(content, '__html__'):
|
||||
@@ -631,22 +639,21 @@ class FieldConverter(osv.AbstractModel):
|
||||
field_name, record._name, exc_info=True)
|
||||
content = None
|
||||
|
||||
inherit_branding = context and context.get('inherit_branding')
|
||||
if not inherit_branding and context and context.get('inherit_branding_auto'):
|
||||
inherit_branding = self.pool['ir.model.access'].check(cr, uid, record._name, 'write', False, context=context)
|
||||
translate = context and context.get('edit_translations') and context.get('translatable') and getattr(record._fields[field_name], 'translate', False)
|
||||
inherit_branding = self.env.context and self.env.context.get('inherit_branding')
|
||||
if not inherit_branding and self.env.context and self.env.context.get('inherit_branding_auto'):
|
||||
inherit_branding = self.env['ir.model.access'].check(record._name, 'write', 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:
|
||||
# add branding attributes
|
||||
g_att += ''.join(
|
||||
_build_attribute(name, value)
|
||||
for name, value in self.attributes(
|
||||
cr, uid, field_name, record, options,
|
||||
source_element, g_att, t_att, qweb_context,
|
||||
context=context)
|
||||
field_name, record, options,
|
||||
source_element, g_att, t_att, qweb_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)
|
||||
|
||||
def qweb_object(self):
|
||||
@@ -661,7 +668,8 @@ class FieldConverter(osv.AbstractModel):
|
||||
return self.qweb_object().render_element(
|
||||
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)
|
||||
|
||||
Fetches the res.lang object corresponding to the language code stored
|
||||
@@ -670,41 +678,41 @@ class FieldConverter(osv.AbstractModel):
|
||||
|
||||
:returns: res.lang browse_record
|
||||
"""
|
||||
if context is None: context = {}
|
||||
|
||||
lang_code = context.get('lang') or 'en_US'
|
||||
Lang = self.pool['res.lang']
|
||||
lang_code = self.env.context.get('lang') or 'en_US'
|
||||
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'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
|
||||
lang_code = context.get('lang') or 'en_US'
|
||||
return self.pool['res.lang'].format(cr, uid, [lang_code], '%d', value, grouping=True)
|
||||
lang_code = self.env.context.get('lang') or 'en_US'
|
||||
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'
|
||||
_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)
|
||||
return precision
|
||||
|
||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
precision = self.precision(cr, uid, field, options=options, context=context)
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
precision = self.precision(field, options=options)
|
||||
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']
|
||||
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
|
||||
# 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)
|
||||
return formatted
|
||||
|
||||
class DateConverter(osv.AbstractModel):
|
||||
|
||||
class DateConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.date'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
||||
if not value or len(value)<10: return ''
|
||||
lang = self.user_lang(cr, uid, context=context)
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
if not value or len(value) < 10: return ''
|
||||
lang = self.user_lang()
|
||||
locale = babel.Locale.parse(lang.code)
|
||||
|
||||
if isinstance(value, basestring):
|
||||
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:
|
||||
pattern = options['format']
|
||||
else:
|
||||
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(
|
||||
value, format=pattern,
|
||||
locale=locale)
|
||||
|
||||
class DateTimeConverter(osv.AbstractModel):
|
||||
|
||||
class DateTimeConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.datetime'
|
||||
_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 ''
|
||||
lang = self.user_lang(cr, uid, context=context)
|
||||
lang = self.user_lang()
|
||||
locale = babel.Locale.parse(lang.code)
|
||||
|
||||
if isinstance(value, basestring):
|
||||
value = datetime.datetime.strptime(
|
||||
value, openerp.tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
value = fields.datetime.context_timestamp(
|
||||
cr, uid, timestamp=value, context=context)
|
||||
value, odoo.tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
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:
|
||||
pattern = options['format']
|
||||
else:
|
||||
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'):
|
||||
pattern = pattern.replace(":ss", "").replace(":s", "")
|
||||
|
||||
return babel.dates.format_datetime(value, format=pattern, locale=locale)
|
||||
|
||||
class TextConverter(osv.AbstractModel):
|
||||
|
||||
class TextConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.text'
|
||||
_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.
|
||||
"""
|
||||
@@ -775,36 +789,42 @@ class TextConverter(osv.AbstractModel):
|
||||
|
||||
return nl2br(value, options=options)
|
||||
|
||||
class SelectionConverter(osv.AbstractModel):
|
||||
|
||||
class SelectionConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.selection'
|
||||
_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]
|
||||
if not value: return ''
|
||||
field = record._fields[field_name]
|
||||
selection = dict(field.get_description(record.env)['selection'])
|
||||
return self.value_to_html(
|
||||
cr, uid, selection[value], field, options=options)
|
||||
return self.value_to_html(selection[value], field, options=options)
|
||||
|
||||
class ManyToOneConverter(osv.AbstractModel):
|
||||
|
||||
class ManyToOneConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.many2one'
|
||||
_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])
|
||||
if not read[field_name]: return ''
|
||||
_, value = read[field_name]
|
||||
return nl2br(value, options=options)
|
||||
|
||||
class HTMLConverter(osv.AbstractModel):
|
||||
|
||||
class HTMLConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.html'
|
||||
_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 '')
|
||||
|
||||
class ImageConverter(osv.AbstractModel):
|
||||
|
||||
class ImageConverter(models.AbstractModel):
|
||||
""" ``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
|
||||
instead.
|
||||
@@ -816,18 +836,20 @@ class ImageConverter(osv.AbstractModel):
|
||||
_name = 'ir.qweb.field.image'
|
||||
_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:
|
||||
image = Image.open(cStringIO.StringIO(value.decode('base64')))
|
||||
image.verify()
|
||||
except IOError:
|
||||
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")
|
||||
|
||||
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
|
||||
``display_currency`` only if field is not of type Monetary.
|
||||
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'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
def to_html(self, cr, uid, field_name, record, options,
|
||||
source_element, t_att, g_att, qweb_context, context=None):
|
||||
@api.model
|
||||
def to_html(self, field_name, record, options,
|
||||
source_element, t_att, g_att, qweb_context):
|
||||
options['_qweb_context'] = qweb_context
|
||||
return super(MonetaryConverter, self).to_html(
|
||||
cr, uid, field_name, record, options,
|
||||
source_element, t_att, g_att, qweb_context, context=context)
|
||||
field_name, record, options,
|
||||
source_element, t_att, g_att, qweb_context)
|
||||
|
||||
def record_to_html(self, cr, uid, field_name, record, options, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
@api.model
|
||||
def record_to_html(self, field_name, record, options):
|
||||
Currency = self.pool['res.currency']
|
||||
cur_field = record._fields[field_name]
|
||||
display_currency = False
|
||||
@@ -862,7 +884,7 @@ class MonetaryConverter(osv.AbstractModel):
|
||||
display_currency = record[cur_field.currency_field]
|
||||
#otherwise fall back to old method
|
||||
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-
|
||||
# minimal (they have a default fixed precision instead), and
|
||||
@@ -874,14 +896,13 @@ class MonetaryConverter(osv.AbstractModel):
|
||||
from_amount = record[field_name]
|
||||
|
||||
if options.get('from_currency'):
|
||||
from_currency = self.display_currency(cr, uid, options['from_currency'], options)
|
||||
from_amount = Currency.compute(cr, uid, from_currency.id, display_currency.id, from_amount)
|
||||
from_currency = self.display_currency(options['from_currency'], options)
|
||||
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']
|
||||
formatted_amount = lang.format(cr, uid, [lang_code],
|
||||
fmt, Currency.round(cr, uid, display_currency, from_amount),
|
||||
grouping=True, monetary=True)
|
||||
formatted_amount = lang.format(self.env.cr, self.env.uid, [lang_code],
|
||||
fmt, Currency.round(self.env.cr, self.env.uid, display_currency, from_amount), grouping=True, monetary=True)
|
||||
|
||||
pre = post = u''
|
||||
if display_currency.position == 'before':
|
||||
@@ -896,9 +917,8 @@ class MonetaryConverter(osv.AbstractModel):
|
||||
symbol=display_currency.symbol,
|
||||
))
|
||||
|
||||
def display_currency(self, cr, uid, currency, options):
|
||||
return self.qweb_object().eval_object(
|
||||
currency, options['_qweb_context'])
|
||||
def display_currency(self, currency, options):
|
||||
return self.qweb_object().eval_object(currency, options['_qweb_context'])
|
||||
|
||||
TIMEDELTA_UNITS = (
|
||||
('year', 3600 * 24 * 365),
|
||||
@@ -909,7 +929,9 @@ TIMEDELTA_UNITS = (
|
||||
('minute', 60),
|
||||
('second', 1)
|
||||
)
|
||||
class DurationConverter(osv.AbstractModel):
|
||||
|
||||
|
||||
class DurationConverter(models.AbstractModel):
|
||||
""" ``duration`` converter, to display integral or fractional values as
|
||||
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'
|
||||
_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)
|
||||
if value < 0:
|
||||
raise ValueError(_("Durations can't be negative"))
|
||||
if not options or options.get('unit') not in units:
|
||||
raise ValueError(_("A unit must be provided to duration widgets"))
|
||||
|
||||
locale = babel.Locale.parse(
|
||||
self.user_lang(cr, uid, context=context).code)
|
||||
locale = babel.Locale.parse(self.user_lang().code)
|
||||
factor = units[options['unit']]
|
||||
|
||||
sections = []
|
||||
@@ -947,14 +969,14 @@ class DurationConverter(osv.AbstractModel):
|
||||
return ' '.join(sections)
|
||||
|
||||
|
||||
class RelativeDatetimeConverter(osv.AbstractModel):
|
||||
class RelativeDatetimeConverter(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.relative'
|
||||
_inherit = 'ir.qweb.field'
|
||||
|
||||
def value_to_html(self, cr, uid, value, field, options=None, context=None):
|
||||
parse_format = openerp.tools.DEFAULT_SERVER_DATETIME_FORMAT
|
||||
locale = babel.Locale.parse(
|
||||
self.user_lang(cr, uid, context=context).code)
|
||||
@api.model
|
||||
def value_to_html(self, value, field, options=None):
|
||||
parse_format = odoo.tools.DEFAULT_SERVER_DATETIME_FORMAT
|
||||
locale = babel.Locale.parse(self.user_lang().code)
|
||||
|
||||
if isinstance(value, basestring):
|
||||
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()
|
||||
reference = datetime.datetime.strptime(field.now(), parse_format)
|
||||
|
||||
return babel.dates.format_timedelta(
|
||||
value - reference, add_direction=True, locale=locale)
|
||||
return babel.dates.format_timedelta(value - reference, add_direction=True, locale=locale)
|
||||
|
||||
class Contact(orm.AbstractModel):
|
||||
|
||||
class Contact(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.contact'
|
||||
_inherit = 'ir.qweb.field.many2one'
|
||||
|
||||
def record_to_html(self, cr, uid, field_name, record, options=None, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
@api.model
|
||||
def record_to_html(self, field_name, record, options=None):
|
||||
if options is None:
|
||||
options = {}
|
||||
opf = options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"]
|
||||
@@ -998,15 +1018,17 @@ class Contact(orm.AbstractModel):
|
||||
'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)
|
||||
|
||||
class QwebView(orm.AbstractModel):
|
||||
|
||||
class QwebView(models.AbstractModel):
|
||||
_name = 'ir.qweb.field.qweb'
|
||||
_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):
|
||||
return None
|
||||
|
||||
@@ -1016,13 +1038,14 @@ class QwebView(orm.AbstractModel):
|
||||
_logger.warning("%s.%s must be a 'ir.ui.view' model." % (record, field_name))
|
||||
return None
|
||||
|
||||
ctx = (context or {}).copy()
|
||||
ctx = (self.env.context or {}).copy()
|
||||
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)
|
||||
|
||||
class QwebWidget(osv.AbstractModel):
|
||||
|
||||
class QwebWidget(models.AbstractModel):
|
||||
_name = 'ir.qweb.widget'
|
||||
|
||||
def _format(self, inner, options, qwebcontext):
|
||||
@@ -1031,7 +1054,8 @@ class QwebWidget(osv.AbstractModel):
|
||||
def format(self, inner, options, qwebcontext):
|
||||
return escape(self._format(inner, options, qwebcontext))
|
||||
|
||||
class QwebWidgetMonetary(osv.AbstractModel):
|
||||
|
||||
class QwebWidgetMonetary(models.AbstractModel):
|
||||
_name = 'ir.qweb.widget.monetary'
|
||||
_inherit = 'ir.qweb.widget'
|
||||
|
||||
@@ -1054,6 +1078,7 @@ class QwebWidgetMonetary(osv.AbstractModel):
|
||||
formatted_amount, pre=pre, post=post
|
||||
).format(symbol=display.symbol,)
|
||||
|
||||
|
||||
class HTMLSafe(object):
|
||||
""" HTMLSafe string wrapper, Werkzeug's escape() has special handling for
|
||||
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
|
||||
"""
|
||||
__slots__ = ['string']
|
||||
|
||||
def __init__(self, string):
|
||||
self.string = string
|
||||
|
||||
def __html__(self):
|
||||
return self.string
|
||||
|
||||
def __str__(self):
|
||||
s = self.string
|
||||
if isinstance(s, unicode):
|
||||
return s.encode('utf-8')
|
||||
return s
|
||||
|
||||
def __unicode__(self):
|
||||
s = self.string
|
||||
if isinstance(s, str):
|
||||
return s.decode('utf-8')
|
||||
return s
|
||||
|
||||
|
||||
def nl2br(string, options=None):
|
||||
""" Converts newlines to HTML linebreaks in ``string``. Automatically
|
||||
escapes content unless options['html-escape'] is set to False, and returns
|
||||
@@ -1092,15 +1122,20 @@ def nl2br(string, options=None):
|
||||
string = escape(string)
|
||||
return HTMLSafe(string.replace('\n', '<br>\n'))
|
||||
|
||||
|
||||
def get_field_type(field, options):
|
||||
""" Gets a t-field's effective type from the field definition and its options """
|
||||
return options.get('widget', field.type)
|
||||
|
||||
|
||||
class AssetError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AssetNotFound(AssetError):
|
||||
pass
|
||||
|
||||
|
||||
class AssetsBundle(object):
|
||||
rx_css_import = re.compile("(@import[^;{]+;?)", re.M)
|
||||
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/%-{}/%'.format(self.version))
|
||||
]
|
||||
attachment_ids = ira.search(self.cr, openerp.SUPERUSER_ID, domain, context=self.context)
|
||||
return ira.unlink(self.cr, openerp.SUPERUSER_ID, attachment_ids, context=self.context)
|
||||
attachment_ids = ira.search(self.cr, odoo.SUPERUSER_ID, domain, context=self.context)
|
||||
return ira.unlink(self.cr, odoo.SUPERUSER_ID, attachment_ids, context=self.context)
|
||||
|
||||
def get_attachments(self, type, inc=None):
|
||||
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))]
|
||||
attachment_ids = ira.search(self.cr, openerp.SUPERUSER_ID, domain, order='name asc', context=self.context)
|
||||
return ira.browse(self.cr, openerp.SUPERUSER_ID, attachment_ids, context=self.context)
|
||||
attachment_ids = ira.search(self.cr, odoo.SUPERUSER_ID, domain, order='name asc', context=self.context)
|
||||
return ira.browse(self.cr, odoo.SUPERUSER_ID, attachment_ids, context=self.context)
|
||||
|
||||
def save_attachment(self, type, content, inc=None):
|
||||
ira = self.registry['ir.attachment']
|
||||
@@ -1264,21 +1299,21 @@ class AssetsBundle(object):
|
||||
'public': True,
|
||||
'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)
|
||||
values = {
|
||||
'name': 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:
|
||||
self.cr.commit()
|
||||
|
||||
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):
|
||||
attachments = self.get_attachments('js')
|
||||
@@ -1412,6 +1447,7 @@ class AssetsBundle(object):
|
||||
def compile_css(self, cmd, source):
|
||||
"""Sanitizes @import rules, remove duplicates @import rules, then compile"""
|
||||
imports = []
|
||||
|
||||
def sanitize(matchobj):
|
||||
ref = matchobj.group(2)
|
||||
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>')
|
||||
return error
|
||||
|
||||
|
||||
class WebAsset(object):
|
||||
html_url = '%s'
|
||||
|
||||
@@ -1483,7 +1520,7 @@ class WebAsset(object):
|
||||
fields = ['__last_update', 'datas', 'mimetype']
|
||||
domain = [('type', '=', 'binary'), ('url', '=', self.url)]
|
||||
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]
|
||||
except Exception:
|
||||
raise AssetNotFound("Could not find %s" % self.name)
|
||||
@@ -1498,7 +1535,7 @@ class WebAsset(object):
|
||||
if self._filename:
|
||||
return datetime.datetime.fromtimestamp(os.path.getmtime(self._filename))
|
||||
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']
|
||||
try:
|
||||
return datetime.datetime.strptime(last_update, server_format + '.%f')
|
||||
@@ -1538,6 +1575,7 @@ class WebAsset(object):
|
||||
content = self.content
|
||||
return '\n/* %s */\n%s' % (self.name, content)
|
||||
|
||||
|
||||
class JavascriptAsset(WebAsset):
|
||||
def minify(self):
|
||||
return self.with_header(rjsmin(self.content))
|
||||
@@ -1554,6 +1592,7 @@ class JavascriptAsset(WebAsset):
|
||||
else:
|
||||
return '<script type="text/javascript" charset="utf-8">%s</script>' % self.with_header()
|
||||
|
||||
|
||||
class StylesheetAsset(WebAsset):
|
||||
rx_import = re.compile(r"""@import\s+('|")(?!'|"|/|https?://)""", re.U)
|
||||
rx_url = re.compile(r"""url\s*\(\s*('|"|)(?!'|"|/|https?://|data:)""", re.U)
|
||||
@@ -1615,6 +1654,7 @@ class StylesheetAsset(WebAsset):
|
||||
else:
|
||||
return '<style type="text/css"%s>%s</style>' % (media, self.with_header())
|
||||
|
||||
|
||||
class PreprocessedCSS(StylesheetAsset):
|
||||
html_url = '%s.css'
|
||||
rx_import = None
|
||||
@@ -1629,6 +1669,7 @@ class PreprocessedCSS(StylesheetAsset):
|
||||
def get_command(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class SassStylesheetAsset(PreprocessedCSS):
|
||||
rx_indent = re.compile(r'^( +|\t+)', re.M)
|
||||
indent = None
|
||||
@@ -1661,6 +1702,7 @@ class SassStylesheetAsset(PreprocessedCSS):
|
||||
return [sass, '--stdin', '-t', 'compressed', '--unix-newlines', '--compass',
|
||||
'-r', 'bootstrap-sass']
|
||||
|
||||
|
||||
class LessStylesheetAsset(PreprocessedCSS):
|
||||
def get_command(self):
|
||||
try:
|
||||
@@ -1673,6 +1715,7 @@ class LessStylesheetAsset(PreprocessedCSS):
|
||||
lesspath = get_resource_path('web', 'static', 'lib', 'bootstrap', 'less')
|
||||
return [lessc, '-', '--clean-css', '--no-js', '--no-color', '--include-path=%s' % lesspath]
|
||||
|
||||
|
||||
def rjsmin(script):
|
||||
""" Minify js with a clever regex.
|
||||
Taken from http://opensource.perlig.de/rjsmin
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<template id="contact">
|
||||
<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">
|
||||
@@ -36,5 +35,4 @@
|
||||
</div>
|
||||
</address>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -2,41 +2,20 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import time
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import tools
|
||||
from openerp.osv import fields, osv, expression
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.tools.misc import unquote as unquote
|
||||
import odoo
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import SUPERUSER_ID
|
||||
from odoo import tools
|
||||
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'
|
||||
_order = 'name'
|
||||
_order = 'name, model_id DESC'
|
||||
_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):
|
||||
res = {}
|
||||
for rule in self.browse(cr, uid, ids, context):
|
||||
@@ -46,44 +25,56 @@ class ir_rule(osv.osv):
|
||||
res[rule.id] = False
|
||||
return res
|
||||
|
||||
def _check_model_obj(self, cr, uid, ids, context=None):
|
||||
return not any(self.pool[rule.model_id.model].is_transient() for rule in self.browse(cr, uid, ids, context))
|
||||
_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")}
|
||||
|
||||
def _check_model_name(self, cr, uid, ids, context=None):
|
||||
# Don't allow rules on rules records (this model).
|
||||
return not any(rule.model_id.model == self._name for rule in self.browse(cr, uid, ids, context))
|
||||
name = fields.Char(index=True)
|
||||
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).")
|
||||
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 = [
|
||||
('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']),
|
||||
(_check_model_name, 'Rules can not be applied on the Record Rules model.', ['model_id']),
|
||||
]
|
||||
|
||||
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')}
|
||||
|
||||
@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')
|
||||
def _compute_domain(self, cr, uid, model_name, mode="read"):
|
||||
@@ -125,32 +116,37 @@ class ir_rule(osv.osv):
|
||||
return domain
|
||||
return []
|
||||
|
||||
def clear_cache(self, cr, uid):
|
||||
@api.model
|
||||
def clear_cache(self):
|
||||
""" Deprecated, use `clear_caches` instead. """
|
||||
self.clear_caches()
|
||||
|
||||
def domain_get(self, cr, uid, model_name, mode='read', context=None):
|
||||
dom = self._compute_domain(cr, uid, model_name, mode)
|
||||
@api.model
|
||||
def domain_get(self, model_name, mode='read'):
|
||||
dom = self._compute_domain(model_name, mode)
|
||||
if dom:
|
||||
# _where_calc is called as superuser. This means that rules can
|
||||
# involve objects on which the real uid has no acces rights.
|
||||
# This means also there is no implicit restriction (e.g. an object
|
||||
# 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 [], [], ['"' + self.pool[model_name]._table + '"']
|
||||
return [], [], ['"' + self.env[model_name]._table + '"']
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
res = super(ir_rule, self).unlink(cr, uid, ids, context=context)
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
res = super(IrRule, self).unlink()
|
||||
self.clear_caches()
|
||||
return res
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
res = super(ir_rule, self).create(cr, uid, vals, context=context)
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
res = super(IrRule, self).create(vals)
|
||||
self.clear_caches()
|
||||
return res
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
res = super(ir_rule, self).write(cr, uid, ids, vals, context=context)
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
res = super(IrRule, self).write(vals)
|
||||
self.clear_caches()
|
||||
return res
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<!-- Rules -->
|
||||
<record id="view_rule_form" model="ir.ui.view">
|
||||
<field name="model">ir.rule</field>
|
||||
@@ -95,5 +94,4 @@
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import pytz
|
||||
import time
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from openerp import _, api, fields, models
|
||||
from openerp.exceptions import UserError
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_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
|
||||
cr.execute(sql, (number_increment, number_next))
|
||||
|
||||
|
||||
def _drop_sequence(cr, seq_names):
|
||||
""" Drop the PostreSQL sequence if it exists.
|
||||
|
||||
@@ -34,6 +34,7 @@ def _drop_sequence(cr, seq_names):
|
||||
# object depends on it.
|
||||
cr.execute("DROP SEQUENCE IF EXISTS %s RESTRICT " % names)
|
||||
|
||||
|
||||
def _alter_sequence(cr, seq_name, number_increment=None, number_next=None):
|
||||
""" 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, )
|
||||
cr.execute(statement)
|
||||
|
||||
|
||||
def _select_nextval(cr, seq_name):
|
||||
cr.execute("SELECT nextval('%s')" % seq_name)
|
||||
return cr.fetchone()
|
||||
|
||||
|
||||
def _update_nogap(self, number_increment):
|
||||
number_next = self.number_next
|
||||
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
|
||||
|
||||
|
||||
class ir_sequence(models.Model):
|
||||
class IrSequence(models.Model):
|
||||
""" Sequence model.
|
||||
|
||||
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})
|
||||
|
||||
name = fields.Char(required=True)
|
||||
code = fields.Char('Sequence Code')
|
||||
implementation = fields.Selection(
|
||||
[('standard', 'Standard'), ('no_gap', 'No gap')],
|
||||
'Implementation', required=True, default='standard',
|
||||
help="Two sequence object implementations are offered: Standard "
|
||||
"and 'No gap'. The later is slower than the former but forbids any"
|
||||
" gap in the sequence (while they are possible in the former).")
|
||||
code = fields.Char(strring='Sequence Code')
|
||||
implementation = fields.Selection([('standard', 'Standard'), ('no_gap', 'No gap')],
|
||||
string='Implementation', required=True, default='standard',
|
||||
help="Two sequence object implementations are offered: Standard "
|
||||
"and 'No gap'. The later is slower than the former but forbids any"
|
||||
"gap in the sequence (while they are possible in the former).")
|
||||
active = fields.Boolean(default=True)
|
||||
prefix = fields.Char(help="Prefix 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',
|
||||
required=True, string='Next Number', default=1,
|
||||
help="Next number that will be used. This number can be incremented "
|
||||
"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")
|
||||
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 "
|
||||
"'Next Number' to get the required padding size.")
|
||||
company_id = fields.Many2one('res.company', 'Company',
|
||||
"'Next Number' to get the required padding size.")
|
||||
company_id = fields.Many2one('res.company', string='Company',
|
||||
default=lambda s: s.env['res.company']._company_default_get('ir.sequence'))
|
||||
use_date_range = fields.Boolean('Use subsequences per date_range')
|
||||
date_range_ids = fields.One2many('ir.sequence.date_range', 'sequence_id', 'Subsequences')
|
||||
use_date_range = fields.Boolean(string='Use subsequences per date_range')
|
||||
date_range_ids = fields.One2many('ir.sequence.date_range', 'sequence_id', string='Subsequences')
|
||||
|
||||
def init(self, cr):
|
||||
return # Don't do the following index yet.
|
||||
@@ -141,7 +143,7 @@ class ir_sequence(models.Model):
|
||||
def create(self, values):
|
||||
""" 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':
|
||||
_create_sequence(self.env.cr, "ir_sequence_%03d" % seq.id, values.get('number_increment', 1), values.get('number_next', 1))
|
||||
return seq
|
||||
@@ -149,7 +151,7 @@ class ir_sequence(models.Model):
|
||||
@api.multi
|
||||
def unlink(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
|
||||
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)
|
||||
for sub_seq in seq.date_range_ids:
|
||||
_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):
|
||||
if self.implementation == 'standard':
|
||||
@@ -308,7 +310,7 @@ class ir_sequence(models.Model):
|
||||
return self.get_id(code, 'code')
|
||||
|
||||
|
||||
class ir_sequence_date_range(models.Model):
|
||||
class IrSequenceDateRange(models.Model):
|
||||
_name = 'ir.sequence.date_range'
|
||||
_rec_name = "sequence_id"
|
||||
|
||||
@@ -321,8 +323,7 @@ class ir_sequence_date_range(models.Model):
|
||||
else:
|
||||
# get number from postgres sequence. Cannot use currval, because that might give an error when
|
||||
# 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(query)
|
||||
self.env.cr.execute("SELECT last_value, increment_by, is_called FROM ir_sequence_%03d_%03d" % (element.sequence_id.id, element.id))
|
||||
(last_value, increment_by, is_called) = self.env.cr.fetchone()
|
||||
if is_called:
|
||||
element.number_next_actual = last_value + increment_by
|
||||
@@ -333,14 +334,14 @@ class ir_sequence_date_range(models.Model):
|
||||
for record in self:
|
||||
record.write({'number_next': record.number_next_actual or 0})
|
||||
|
||||
date_from = fields.Date('From', required=True)
|
||||
date_to = fields.Date('To', required=True)
|
||||
sequence_id = fields.Many2one("ir.sequence", 'Main Sequence', required=True, ondelete='cascade')
|
||||
number_next = fields.Integer('Next Number', required=True, default=1, help="Next number of this sequence")
|
||||
date_from = fields.Date(string='From', required=True)
|
||||
date_to = fields.Date(string='To', required=True)
|
||||
sequence_id = fields.Many2one("ir.sequence", string='Main Sequence', required=True, ondelete='cascade')
|
||||
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',
|
||||
required=True, string='Next Number', default=1,
|
||||
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):
|
||||
if self.sequence_id.implementation == 'standard':
|
||||
@@ -358,7 +359,7 @@ class ir_sequence_date_range(models.Model):
|
||||
def create(self, values):
|
||||
""" 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
|
||||
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))
|
||||
@@ -367,11 +368,11 @@ class ir_sequence_date_range(models.Model):
|
||||
@api.multi
|
||||
def unlink(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
|
||||
def write(self, values):
|
||||
if values.get('number_next'):
|
||||
seq_to_alter = self.filtered(lambda seq: seq.sequence_id.implementation == 'standard')
|
||||
seq_to_alter._alter_sequence(number_next=values.get('number_next'))
|
||||
return super(ir_sequence_date_range, self).write(values)
|
||||
return super(IrSequenceDateRange, self).write(values)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<!-- Sequences -->
|
||||
<record id="sequence_view" model="ir.ui.view">
|
||||
<field name="model">ir.sequence</field>
|
||||
@@ -105,5 +104,4 @@
|
||||
</record>
|
||||
<menuitem action="ir_sequence_form" id="menu_ir_sequence_form" parent="next_id_5"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from difflib import get_close_matches
|
||||
import logging
|
||||
|
||||
from openerp import api, tools, SUPERUSER_ID
|
||||
import openerp.modules
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp.exceptions import AccessError, UserError, ValidationError
|
||||
import odoo
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import AccessError, UserError, ValidationError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -25,7 +23,8 @@ TRANSLATION_TYPE = [
|
||||
('sql_constraint', 'SQL Constraint')
|
||||
]
|
||||
|
||||
class ir_translation_import_cursor(object):
|
||||
|
||||
class IrTranslationImportCursor(object):
|
||||
"""Temporary cursor for optimizing mass insert into ir.translation
|
||||
|
||||
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)
|
||||
return True
|
||||
|
||||
class ir_translation(osv.osv):
|
||||
|
||||
class IrTranslation(models.Model):
|
||||
_name = "ir.translation"
|
||||
_log_access = False
|
||||
|
||||
def _get_language(self, cr, uid, context):
|
||||
lang_model = self.pool.get('res.lang')
|
||||
lang_ids = lang_model.search(cr, uid, [('translatable', '=', True)], context=context)
|
||||
lang_data = lang_model.read(cr, uid, lang_ids, ['code', 'name'], context=context)
|
||||
return [(d['code'], d['name']) for d in lang_data]
|
||||
@api.model
|
||||
def _get_language(self):
|
||||
lang_model = self.env['res.lang']
|
||||
lang = lang_model.search([('translatable', '=', True)])
|
||||
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
|
||||
return the value store in db. Otherwise return value store in src field
|
||||
'''
|
||||
if context is None:
|
||||
context = {}
|
||||
res = dict.fromkeys(ids, False)
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
res[record.id] = record.src
|
||||
for record in self:
|
||||
if record.type == 'model':
|
||||
model_name, field_name = record.name.split(',')
|
||||
model = self.pool.get(model_name)
|
||||
model = self.env[model_name]
|
||||
if model is None:
|
||||
continue
|
||||
field = model._fields.get(field_name)
|
||||
@@ -197,74 +216,41 @@ class ir_translation(osv.osv):
|
||||
continue
|
||||
if not callable(field.translate):
|
||||
# Pass context without lang, need to read real stored field, not translation
|
||||
context_no_lang = dict(context, lang=None)
|
||||
result = model.read(cr, uid, [record.res_id], [field_name], context=context_no_lang)
|
||||
res[record.id] = result[0][field_name] if result else False
|
||||
return res
|
||||
context_no_lang = dict(self.env.context, lang=None)
|
||||
result = model.with_context(context_no_lang).browse(record.res_id).read([field_name])
|
||||
record.source = result[0][field_name] if result else False
|
||||
|
||||
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
|
||||
the associated object, and the src field
|
||||
'''
|
||||
if context is None:
|
||||
context = {}
|
||||
record = self.browse(cr, uid, id, context=context)
|
||||
if record.type == 'model':
|
||||
model_name, field_name = record.name.split(',')
|
||||
model = self.pool.get(model_name)
|
||||
if self.type == 'model':
|
||||
model_name, field_name = self.name.split(',')
|
||||
model = self.env[model_name]
|
||||
field = model._fields[field_name]
|
||||
if not callable(field.translate):
|
||||
# Make a context without language information, because we want
|
||||
# to write on the value stored in db and not on the one
|
||||
# associated with the current language. Also not removing lang
|
||||
# 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)
|
||||
model.write(cr, uid, [record.res_id], {field_name: value}, context=context_wo_lang)
|
||||
return self.write(cr, uid, id, {'src': value}, context=context)
|
||||
model.with_context(context_wo_lang).browse(self.res_id).write({field_name: self.source})
|
||||
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 '''
|
||||
res = []
|
||||
for field, operator, value in args:
|
||||
for field, operator, value in operand:
|
||||
res.append(('src', operator, value))
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Translated field', required=True),
|
||||
'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),
|
||||
def _auto_init(self):
|
||||
super(IrTranslation, self)._auto_init()
|
||||
|
||||
'state': fields.selection(
|
||||
[('to_translate','To Translate'),
|
||||
('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()]
|
||||
self.env.cr.execute("SELECT indexname FROM pg_indexes WHERE indexname LIKE 'ir_translation_%'")
|
||||
indexes = [row[0] for row in self.env.cr.fetchall()]
|
||||
|
||||
# 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)')
|
||||
@@ -273,65 +259,65 @@ class ir_translation(osv.osv):
|
||||
# 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)')
|
||||
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')
|
||||
cr.commit()
|
||||
self.env.cr.execute('DROP INDEX IF EXISTS ir_translation_ltns, ir_translation_lts, ir_translation_src_hash_idx')
|
||||
self.env.cr.commit()
|
||||
|
||||
# Add separate md5 index on src (no size limit on values, and good performance).
|
||||
if 'ir_translation_src_md5' not in indexes:
|
||||
cr.execute('CREATE INDEX ir_translation_src_md5 ON ir_translation (md5(src))')
|
||||
cr.commit()
|
||||
self.env.cr.execute('CREATE INDEX ir_translation_src_md5 ON ir_translation (md5(src))')
|
||||
self.env.cr.commit()
|
||||
|
||||
if 'ir_translation_ltn' not in indexes:
|
||||
cr.execute('CREATE INDEX ir_translation_ltn ON ir_translation (name, lang, type)')
|
||||
cr.commit()
|
||||
self.env.cr.execute('CREATE INDEX ir_translation_ltn ON ir_translation (name, lang, type)')
|
||||
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':
|
||||
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):
|
||||
translations = dict.fromkeys(ids, False)
|
||||
if ids:
|
||||
cr.execute('select res_id,value '
|
||||
'from ir_translation '
|
||||
'where lang=%s '
|
||||
'and type=%s '
|
||||
'and name=%s '
|
||||
'and res_id IN %s',
|
||||
(lang,tt,name,tuple(ids)))
|
||||
for res_id, value in cr.fetchall():
|
||||
translations[res_id] = value
|
||||
return translations
|
||||
@api.model
|
||||
def _get_ids(self, name, tt, lang, in_ids):
|
||||
if in_ids:
|
||||
self.env.cr.execute('select res_id,value '
|
||||
'from ir_translation '
|
||||
'where lang=%s '
|
||||
'and type=%s '
|
||||
'and name=%s '
|
||||
'and res_id IN %s',
|
||||
(lang, tt, name, tuple(in_ids)))
|
||||
for res_id, value in self.env.cr.fetchall():
|
||||
self.res_id = value
|
||||
|
||||
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()
|
||||
cr.execute('update ir_translation '
|
||||
'set value=%s '
|
||||
' , src=%s '
|
||||
' , state=%s '
|
||||
'where lang=%s '
|
||||
'and type=%s '
|
||||
'and name=%s '
|
||||
'and res_id IN %s '
|
||||
'returning res_id',
|
||||
(value,src,'translated',lang,tt,name,tuple(ids),))
|
||||
self.env.cr.execute('update ir_translation '
|
||||
'set value=%s '
|
||||
' , src=%s '
|
||||
' , state=%s '
|
||||
'where lang=%s '
|
||||
'and type=%s '
|
||||
'and name=%s '
|
||||
'and res_id IN %s '
|
||||
'returning res_id',
|
||||
(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)):
|
||||
self.create(cr, uid, {
|
||||
'lang':lang,
|
||||
'type':tt,
|
||||
'name':name,
|
||||
'res_id':id,
|
||||
'value':value,
|
||||
'src':src,
|
||||
'state':'translated'
|
||||
})
|
||||
return len(ids)
|
||||
for id in list(set(rec_ids) - set(existing_ids)):
|
||||
self.create({'lang': lang,
|
||||
'type': tt,
|
||||
'name': name,
|
||||
'res_id': id,
|
||||
'value': value,
|
||||
'src': src,
|
||||
'state': 'translated'}).id
|
||||
return len(rec_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:
|
||||
# Note: the extra test on md5(src) is a hint for postgres to use the
|
||||
# index ir_translation_src_md5
|
||||
@@ -356,21 +342,22 @@ class ir_translation(osv.osv):
|
||||
AND name=%s"""
|
||||
|
||||
params = (lang or '', types, tools.ustr(name))
|
||||
|
||||
|
||||
return (query, params)
|
||||
|
||||
@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!
|
||||
query, params = self._get_source_query(cr, uid, name, types, lang, source, res_id)
|
||||
cr.execute(query, params)
|
||||
res = cr.fetchone()
|
||||
query, params = self._get_source_query(name, types, lang, source, res_id)
|
||||
self.env.cr.execute(query, params)
|
||||
res = self.env.cr.fetchone()
|
||||
trad = res and res[0] or u''
|
||||
if source and not trad:
|
||||
return tools.ustr(source)
|
||||
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
|
||||
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,)
|
||||
else:
|
||||
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
|
||||
def _get_terms_query(self, field, records):
|
||||
@@ -550,7 +537,7 @@ class ir_translation(osv.osv):
|
||||
|
||||
@api.model
|
||||
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')
|
||||
self.clear_caches()
|
||||
return record
|
||||
@@ -562,7 +549,7 @@ class ir_translation(osv.osv):
|
||||
elif vals.get('src') or not vals.get('value', True):
|
||||
vals.setdefault('state', 'to_translate')
|
||||
self.check('write')
|
||||
result = super(ir_translation, self.sudo()).write(vals)
|
||||
result = super(IrTranslation, self.sudo()).write(vals)
|
||||
self.check('write')
|
||||
self.clear_caches()
|
||||
return result
|
||||
@@ -571,7 +558,7 @@ class ir_translation(osv.osv):
|
||||
def unlink(self):
|
||||
self.check('unlink')
|
||||
self.clear_caches()
|
||||
return super(ir_translation, self.sudo()).unlink()
|
||||
return super(IrTranslation, self.sudo()).unlink()
|
||||
|
||||
@api.model
|
||||
def insert_missing(self, field, records):
|
||||
@@ -675,17 +662,17 @@ class ir_translation(osv.osv):
|
||||
def _get_import_cursor(self, cr, uid, context=None):
|
||||
""" 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):
|
||||
context_template = dict(context or {}) # local copy
|
||||
# make sure the given languages are active
|
||||
lang_obj = self.pool['res.lang']
|
||||
for lang in langs:
|
||||
lang_obj.load_lang(cr, SUPERUSER_ID, lang)
|
||||
lang_obj.load_lang(cr, odoo.SUPERUSER_ID, lang)
|
||||
# load i18n files
|
||||
for module_name in modules:
|
||||
modpath = openerp.modules.get_module_path(module_name)
|
||||
modpath = odoo.modules.get_module_path(module_name)
|
||||
if not modpath:
|
||||
continue
|
||||
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)
|
||||
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:
|
||||
_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)
|
||||
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)
|
||||
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:
|
||||
_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)
|
||||
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
|
||||
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:
|
||||
_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)
|
||||
elif lang_code != 'en_US':
|
||||
_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:
|
||||
_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)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<!-- Translations -->
|
||||
<record id="view_translation_search" model="ir.ui.view">
|
||||
<field name="model">ir.translation</field>
|
||||
@@ -86,5 +85,4 @@
|
||||
|
||||
<menuitem action="action_translation" id="menu_action_translation" parent="base.menu_translation_app" />
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -4,25 +4,78 @@
|
||||
import base64
|
||||
import operator
|
||||
import re
|
||||
import threading
|
||||
|
||||
import openerp
|
||||
from openerp.osv import fields, osv
|
||||
from openerp import api, tools
|
||||
from openerp.http import request
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.tools.translate import _
|
||||
import odoo
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.http import request
|
||||
from odoo.tools.safe_eval import safe_eval as eval
|
||||
|
||||
MENU_ITEM_SEPARATOR = "/"
|
||||
|
||||
|
||||
class ir_ui_menu(osv.osv):
|
||||
class IrUiMenu(models.Model):
|
||||
_name = 'ir.ui.menu'
|
||||
_order = "sequence,id"
|
||||
_parent_store = True
|
||||
|
||||
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')
|
||||
|
||||
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
|
||||
@tools.ormcache('frozenset(self.env.user.groups_id.ids)', 'debug')
|
||||
def _visible_menu_ids(self, debug=False):
|
||||
@@ -75,7 +128,7 @@ class ir_ui_menu(osv.osv):
|
||||
if context is None:
|
||||
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)
|
||||
|
||||
if not ids:
|
||||
@@ -98,99 +151,69 @@ class ir_ui_menu(osv.osv):
|
||||
return len(result)
|
||||
return result
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
res = []
|
||||
for id in ids:
|
||||
elmt = self.browse(cr, uid, id, context=context)
|
||||
res.append((id, self._get_one_full_name(elmt)))
|
||||
for element in self:
|
||||
res.append((element.id, self._get_one_full_name(element)))
|
||||
return res
|
||||
|
||||
def _get_full_name(self, cr, uid, ids, name=None, args=None, context=None):
|
||||
if context is None:
|
||||
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):
|
||||
@api.model
|
||||
def create(self, values):
|
||||
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()
|
||||
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
|
||||
# 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).
|
||||
# TODO: ideally we should move them under a generic "Orphans" menu somewhere?
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
local_context = dict(context or {})
|
||||
local_context = dict(self.env.context or {})
|
||||
local_context['ir.ui.menu.full_list'] = True
|
||||
direct_children_ids = self.search(cr, uid, [('parent_id', 'in', ids)], context=local_context)
|
||||
if direct_children_ids:
|
||||
self.write(cr, uid, direct_children_ids, {'parent_id': False})
|
||||
direct_children = self.with_context(local_context).search([('parent_id', 'in', self.ids)])
|
||||
if direct_children:
|
||||
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()
|
||||
return result
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
res = super(ir_ui_menu, self).copy(cr, uid, id, default=default, context=context)
|
||||
datas=self.read(cr,uid,[res],['name'])[0]
|
||||
rex=re.compile('\([0-9]+\)')
|
||||
concat=rex.findall(datas['name'])
|
||||
@api.multi
|
||||
def copy(self, default=None):
|
||||
res = super(IrUiMenu, self).copy(default=default)
|
||||
rex = re.compile('\([0-9]+\)')
|
||||
concat = rex.findall(res.name)
|
||||
if concat:
|
||||
next_num=int(concat[0])+1
|
||||
datas['name']=rex.sub(('(%d)'%next_num),datas['name'])
|
||||
next_num = int(concat[0])+1
|
||||
res.name = rex.sub(('(%d)' % next_num), res.name)
|
||||
else:
|
||||
datas['name'] += '(1)'
|
||||
self.write(cr,uid,[res],{'name':datas['name']})
|
||||
res.name += '(1)'
|
||||
res.write({'name': res.name})
|
||||
return res
|
||||
|
||||
def read_image(self, path):
|
||||
if not path:
|
||||
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):
|
||||
@api.multi
|
||||
def get_needaction_data(self):
|
||||
""" Return for each menu entry of ids :
|
||||
- if it uses the needaction mechanism (needaction_enabled)
|
||||
- the needaction counter of the related action, taking into account
|
||||
the action domain
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
res = {}
|
||||
menu_ids = set()
|
||||
for menu in self.browse(cr, uid, ids, context=context):
|
||||
for menu in self:
|
||||
menu_ids.add(menu.id)
|
||||
ctx = None
|
||||
if menu.action and menu.action.type in ('ir.actions.act_window', 'ir.actions.client') and menu.action.context:
|
||||
try:
|
||||
# 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
|
||||
except Exception:
|
||||
# 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 not isinstance(menu_ref, list):
|
||||
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:
|
||||
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'):
|
||||
menu_ids.add(id)
|
||||
menu_ids.add(model_id)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
menu_ids = list(menu_ids)
|
||||
|
||||
for menu in self.browse(cr, uid, menu_ids, context=context):
|
||||
for menu in self:
|
||||
res[menu.id] = {
|
||||
'needaction_enabled': 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.res_model in self.pool:
|
||||
obj = self.pool[menu.action.res_model]
|
||||
obj = self.env[menu.action.res_model]
|
||||
if obj._needaction:
|
||||
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 []
|
||||
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_counter'] = obj._needaction_count(cr, uid, dom, context=context)
|
||||
res[menu.id]['needaction_counter'] = obj._needaction_count(dom)
|
||||
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: the root menu ids
|
||||
:rtype: list(int)
|
||||
"""
|
||||
menu_domain = [('parent_id', '=', False)]
|
||||
return self.search(cr, uid, menu_domain, context=context)
|
||||
return self.search([('parent_id', '=', False)])
|
||||
|
||||
@api.cr_uid_context
|
||||
@tools.ormcache_context('uid', keys=('lang',))
|
||||
@@ -300,41 +324,3 @@ class ir_ui_menu(osv.osv):
|
||||
key=operator.itemgetter('sequence'))
|
||||
|
||||
return menu_root
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Menu', required=True, translate=True),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'child_id': fields.one2many('ir.ui.menu', 'parent_id', 'Child IDs'),
|
||||
'parent_id': fields.many2one('ir.ui.menu', 'Parent Menu', select=True, ondelete="restrict"),
|
||||
'parent_left': fields.integer('Parent Left', select=True),
|
||||
'parent_right': fields.integer('Parent Right', select=True),
|
||||
'groups_id': fields.many2many('res.groups', 'ir_ui_menu_group_rel',
|
||||
'menu_id', 'gid', 'Groups', help="If you have groups, the visibility of this menu will be based on these groups. "\
|
||||
"If this field is empty, Odoo will compute visibility based on the related object's read access."),
|
||||
'complete_name': fields.function(_get_full_name, string='Full Path', type='char'),
|
||||
'web_icon': fields.char('Web Icon File'),
|
||||
'action': fields.reference('Action', selection=[
|
||||
('ir.actions.report.xml', 'ir.actions.report.xml'),
|
||||
('ir.actions.act_window', 'ir.actions.act_window'),
|
||||
('ir.actions.act_url', 'ir.actions.act_url'),
|
||||
('ir.actions.server', 'ir.actions.server'),
|
||||
('ir.actions.client', 'ir.actions.client'),
|
||||
]),
|
||||
}
|
||||
|
||||
web_icon_data = openerp.fields.Binary('Web Icon Image',
|
||||
compute="_compute_web_icon", store=True, attachment=True)
|
||||
|
||||
@api.depends('web_icon')
|
||||
def _compute_web_icon(self):
|
||||
for menu in self:
|
||||
menu.web_icon_data = self.read_image(menu.web_icon)
|
||||
|
||||
_constraints = [
|
||||
(osv.osv._check_recursion, 'Error ! You can not create recursive Menu.', ['parent_id'])
|
||||
]
|
||||
_defaults = {
|
||||
'sequence': 10,
|
||||
}
|
||||
_order = "sequence,id"
|
||||
_parent_store = True
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
|
||||
<record id="edit_menu_access" model="ir.ui.view">
|
||||
<field name="model">ir.ui.menu</field>
|
||||
@@ -70,5 +69,4 @@
|
||||
</record>
|
||||
<menuitem action="grant_menu_access" id="menu_grant_menu_access" parent="base.next_id_2" sequence="1"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<!-- View -->
|
||||
<record id="view_view_form" model="ir.ui.view">
|
||||
<field name="model">ir.ui.view</field>
|
||||
@@ -136,5 +135,4 @@
|
||||
</record>
|
||||
<menuitem id="menu_action_ui_view_custom" action="action_ui_view_custom" parent="base.next_id_2" sequence="3"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
+131
-140
@@ -1,11 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp import tools
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.exceptions import AccessError, MissingError
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import pickle
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import AccessError, MissingError
|
||||
from odoo.tools import pickle
|
||||
|
||||
EXCLUDED_FIELDS = set((
|
||||
'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
|
||||
field values. definitions. This is a legacy internal model, mixing
|
||||
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`.
|
||||
These default values are then automatically used by the
|
||||
ORM every time a new record is about to be created, i.e. when
|
||||
:meth:`~openerp.osv.osv.osv.default_get`
|
||||
or :meth:`~openerp.osv.osv.osv.create` are called.
|
||||
:meth:`~odoo.osv.osv.osv.default_get`
|
||||
or :meth:`~odoo.osv.osv.osv.create` are called.
|
||||
|
||||
.. rubric:: Usage: action bindings
|
||||
|
||||
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
|
||||
: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`.
|
||||
"""
|
||||
_name = 'ir.values'
|
||||
|
||||
def _value_unpickle(self, cursor, user, ids, name, arg, context=None):
|
||||
res = {}
|
||||
for record in self.browse(cursor, user, ids, context=context):
|
||||
value = record[name[:-9]]
|
||||
name = fields.Char(required=True)
|
||||
model = fields.Char(string='Model Name', select=True, required=True,
|
||||
help="Model to which this entry applies")
|
||||
|
||||
# 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:
|
||||
# default values are pickled on the fly
|
||||
try:
|
||||
value = str(pickle.loads(value))
|
||||
except Exception:
|
||||
pass
|
||||
res[record.id] = value
|
||||
return res
|
||||
record.value_unpickle = value
|
||||
|
||||
def _value_pickle(self, cursor, user, id, name, value, arg, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
ctx = context.copy()
|
||||
def _value_pickle(self):
|
||||
ctx = self.env.context.copy()
|
||||
if self.CONCURRENCY_CHECK_FIELD in ctx:
|
||||
del ctx[self.CONCURRENCY_CHECK_FIELD]
|
||||
record = self.browse(cursor, user, id, context=context)
|
||||
if record.key == 'default':
|
||||
if self.key == 'default':
|
||||
# default values are pickled on the fly
|
||||
value = pickle.dumps(value)
|
||||
self.write(cursor, user, id, {name[:-9]: value}, context=ctx)
|
||||
value = pickle.dumps(self.value)
|
||||
self.write({value: value})
|
||||
|
||||
def onchange_object_id(self, cr, uid, ids, object_id, context=None):
|
||||
if not object_id: return {}
|
||||
act = self.pool.get('ir.model').browse(cr, uid, object_id, context=context)
|
||||
return {
|
||||
'value': {'model': act.model}
|
||||
}
|
||||
@api.onchange('model_id')
|
||||
def onchange_object_id(self):
|
||||
if not self.model_id:
|
||||
return {}
|
||||
return {'value': {'model': self.env['ir.model'].browse(self.model_id).model}}
|
||||
|
||||
def onchange_action_id(self, cr, uid, ids, action_id, context=None):
|
||||
if not action_id: return {}
|
||||
act = self.pool.get('ir.actions.actions').browse(cr, uid, action_id, context=context)
|
||||
return {
|
||||
'value': {'value_unpickle': act.type+','+str(act.id)}
|
||||
}
|
||||
@api.onchange('action_id')
|
||||
def onchange_action_id(self):
|
||||
if not self.action_id:
|
||||
return {}
|
||||
act = self.env['ir.actions.actions'].browse(self.action_id)
|
||||
return {'value': {'value_unpickle': act.type + ',' + str(act.id)}}
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True),
|
||||
'model': fields.char('Model Name', select=True, required=True,
|
||||
help="Model to which this entry applies"),
|
||||
def _auto_init(self):
|
||||
super(IrValues, self)._auto_init()
|
||||
self.env.cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = \'ir_values_key_model_key2_res_id_user_id_idx\'')
|
||||
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
|
||||
'model_id': fields.many2one('ir.model', 'Model (change only)', size=128,
|
||||
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)
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
self.clear_caches()
|
||||
return res
|
||||
return super(IrValues, self).create(vals)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
res = super(ir_values, self).write(cr, uid, ids, vals, context=context)
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
self.clear_caches()
|
||||
return res
|
||||
return super(IrValues, self).write(vals)
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
res = super(ir_values, self).unlink(cr, uid, ids, context=context)
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
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
|
||||
default for the same scope (model, field_name, value, for_all_users, company_id, condition)
|
||||
will be replaced and lost in the process.
|
||||
@@ -225,8 +211,7 @@ class ir_values(osv.osv):
|
||||
value = value.encode('utf8')
|
||||
if company_id is True:
|
||||
# should be company-specific, need to get company id
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid)
|
||||
company_id = user.company_id.id
|
||||
company_id = self.env.user.company_id.id
|
||||
|
||||
# remove existing defaults for the same scope
|
||||
search_criteria = [
|
||||
@@ -234,22 +219,23 @@ class ir_values(osv.osv):
|
||||
('key2', '=', condition and condition[:200]),
|
||||
('model', '=', model),
|
||||
('name', '=', field_name),
|
||||
('user_id', '=', False if for_all_users else uid),
|
||||
('company_id','=', company_id)
|
||||
]
|
||||
self.unlink(cr, uid, self.search(cr, uid, search_criteria))
|
||||
('user_id', '=', False if for_all_users else self._uid),
|
||||
('company_id', '=', company_id)
|
||||
]
|
||||
self.search(search_criteria).unlink()
|
||||
|
||||
return self.create(cr, uid, {
|
||||
return self.create({
|
||||
'name': field_name,
|
||||
'value': pickle.dumps(value),
|
||||
'model': model,
|
||||
'key': 'default',
|
||||
'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,
|
||||
})
|
||||
|
||||
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 ``None`` if no such default exists.
|
||||
"""
|
||||
@@ -258,13 +244,14 @@ class ir_values(osv.osv):
|
||||
('key2', '=', condition and condition[:200]),
|
||||
('model', '=', model),
|
||||
('name', '=', field_name),
|
||||
('user_id', '=', False if for_all_users else uid),
|
||||
('company_id','=', company_id)
|
||||
]
|
||||
defaults = self.browse(cr, uid, self.search(cr, uid, search_criteria))
|
||||
return pickle.loads(defaults[0].value.encode('utf-8')) if defaults else None
|
||||
('user_id', '=', False if for_all_users else self._uid),
|
||||
('company_id', '=', company_id)
|
||||
]
|
||||
defaults = self.search(search_criteria)
|
||||
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,
|
||||
(and match ``condition``, if specified), previously registered via
|
||||
:meth:`~.set_default`.
|
||||
@@ -303,31 +290,33 @@ class ir_values(osv.osv):
|
||||
)
|
||||
%s
|
||||
ORDER BY v.user_id, u.company_id"""
|
||||
params = ('default', model, uid, uid)
|
||||
params = ('default', model, self._uid, self._uid)
|
||||
if condition:
|
||||
query %= 'AND v.key2 = %s'
|
||||
params += (condition[:200],)
|
||||
else:
|
||||
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
|
||||
defaults = {}
|
||||
for row in cr.dictfetchall():
|
||||
for row in self.env.cr.dictfetchall():
|
||||
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()
|
||||
|
||||
# use ormcache: this is called a lot by BaseModel.default_get()!
|
||||
@tools.ormcache('uid', 'model', 'condition')
|
||||
def get_defaults_dict(self, cr, uid, model, condition=False):
|
||||
@api.model
|
||||
@tools.ormcache('self.env.uid', 'model', 'condition')
|
||||
def get_defaults_dict(self, model, condition=False):
|
||||
""" Returns a dictionary mapping field names with their corresponding
|
||||
default value. This method simply improves the returned value of
|
||||
: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
|
||||
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.
|
||||
@@ -355,22 +344,23 @@ class ir_values(osv.osv):
|
||||
('key', '=', 'action'),
|
||||
('key2', '=', action_slot),
|
||||
('model', '=', model),
|
||||
('res_id', '=', res_id or 0), # int field -> NULL == 0
|
||||
('res_id', '=', res_id or 0), # int field -> NULL == 0
|
||||
('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',
|
||||
'key2': action_slot,
|
||||
'model': model,
|
||||
'res_id': res_id,
|
||||
'name': name,
|
||||
'value': action,
|
||||
})
|
||||
}).id
|
||||
|
||||
@tools.ormcache_context('uid', 'action_slot', 'model', 'res_id', keys=('lang',))
|
||||
def get_actions(self, cr, uid, action_slot, model, res_id=False, context=None):
|
||||
@api.model
|
||||
@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.
|
||||
See the class description for more details about the various action
|
||||
slots: :class:`~.ir_values`.
|
||||
@@ -398,21 +388,20 @@ class ir_values(osv.osv):
|
||||
OR v.res_id IS NULL
|
||||
OR v.res_id = 0)
|
||||
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
|
||||
actions = []
|
||||
for id, name, value in cr.fetchall():
|
||||
for id, name, value in self.env.cr.fetchall():
|
||||
if not value:
|
||||
continue # skip if undefined
|
||||
action_model, action_id = value.split(',')
|
||||
if action_model not in self.pool:
|
||||
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))
|
||||
|
||||
# process values and their action
|
||||
user = self.pool['res.users'].browse(cr, uid, uid, context)
|
||||
results = {}
|
||||
for id, name, action in actions:
|
||||
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
|
||||
}
|
||||
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':
|
||||
raise AccessError(_('You do not have the permission to perform this operation!!!'))
|
||||
continue
|
||||
@@ -454,7 +443,8 @@ class ir_values(osv.osv):
|
||||
return results
|
||||
|
||||
# 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.
|
||||
Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.set_default`
|
||||
(``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']"
|
||||
if key == 'default':
|
||||
def do_set(model,res_id):
|
||||
return self.set_default(cr, uid, model, field_name=name, value=value,
|
||||
def do_set(model, res_id):
|
||||
return self.set_default(model, field_name=name, value=value,
|
||||
for_all_users=(not preserve_user), company_id=company,
|
||||
condition=key2)
|
||||
elif key == 'action':
|
||||
def do_set(model,res_id):
|
||||
return self.set_action(cr, uid, name, action_slot=key2, model=model, action=value, res_id=res_id)
|
||||
def do_set(model, 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)
|
||||
|
||||
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.
|
||||
Now dispatches to the newer API methods according to the value of ``key``: :meth:`~.get_defaults`
|
||||
(``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']"
|
||||
if key == 'default':
|
||||
def do_get(model,res_id):
|
||||
return self.get_defaults(cr, uid, model, condition=key2)
|
||||
def do_get(model, res_id):
|
||||
return self.get_defaults(model, condition=key2)
|
||||
elif key == 'action':
|
||||
def do_get(model,res_id):
|
||||
return self.get_actions(cr, uid, action_slot=key2, model=model, res_id=res_id, context=context)
|
||||
def do_get(model, res_id):
|
||||
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)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<!-- Values -->
|
||||
<record id="values_view_form_action" model="ir.ui.view">
|
||||
<field name="name">ir.values.form.action</field>
|
||||
@@ -10,13 +9,13 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="model_id" on_change="onchange_object_id(model_id)"/>
|
||||
<field name="model_id"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
<field name="key2"/>
|
||||
</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"/>
|
||||
</group>
|
||||
</group>
|
||||
@@ -33,7 +32,7 @@
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="model"/>
|
||||
<field name="model_id" on_change="onchange_object_id(model_id)"/>
|
||||
<field name="model_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="key2" string="Condition"/>
|
||||
@@ -123,5 +122,4 @@
|
||||
</record>
|
||||
<menuitem action="act_values_form_defaults" id="menu_values_form_defaults" parent="next_id_6"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<template id="report_irmodeloverview">
|
||||
<t t-call="report.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
@@ -93,5 +92,4 @@
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<field name="type">ir.actions.server</field>
|
||||
<field name="model_id" ref="model_ir_module_module" />
|
||||
<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 model="ir.values" id="action_module_immediate_install">
|
||||
|
||||
@@ -128,7 +128,7 @@ class TestQWeb(common.TransactionCase):
|
||||
ctx.update(params)
|
||||
result = doc.find('result[@id="{}"]'.format(template)).text
|
||||
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'),
|
||||
template
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user