[MIGR] base: migrate base/res to the new API
- `ir.property`, improve API of method `get_by_record` - `res.bank`, `res.partner.bank` - `res.company` - `res.config`, `res.config.installer`, `res.config.settings`, fix usage of methods `get_default_` - `res.country`, `res.country.group`, `res.country.state` - `res.currency`, `res.currency.rate` - `res.font` - `res.lang` - `res.partner`, `res.partner.category`, `res.partner.title` - `res.request.link` - `res.groups`, `res.users`, `res.users.log`, change password wizard
This commit is contained in:
committed by
Raphael Collet
parent
1490e430e6
commit
7eab8e26d3
@@ -21,7 +21,7 @@
|
||||
% set access_url = access_action['type'] == 'ir.actions.act_url' and access_action['url'] or '/report/pdf/account.report_invoice/' + str(object.id)
|
||||
|
||||
<p>Dear
|
||||
% if object.partner_id.company_type == 'company' and object.child_ids:
|
||||
% if object.partner_id.is_company and object.child_ids:
|
||||
${object.partner_id.child_ids[0].name}
|
||||
% else :
|
||||
${object.partner_id.name}
|
||||
|
||||
@@ -452,8 +452,8 @@ class AccountInvoice(models.Model):
|
||||
res_dom = [('res_id', '=', 'res.partner,%s' % partner_id)]
|
||||
rec_prop = prop.search(rec_dom + res_dom) or prop.search(rec_dom)
|
||||
pay_prop = prop.search(pay_dom + res_dom) or prop.search(pay_dom)
|
||||
rec_account = rec_prop.get_by_record(rec_prop)
|
||||
pay_account = pay_prop.get_by_record(pay_prop)
|
||||
rec_account = rec_prop.get_by_record()
|
||||
pay_account = pay_prop.get_by_record()
|
||||
if not rec_account and not pay_account:
|
||||
action = self.env.ref('account.action_account_config')
|
||||
msg = _('Cannot find a chart of accounts for this company, You should configure it. \nPlease go to Account Configuration.')
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-5 col-xs-offset-7">
|
||||
<span t-field="o.name"/><br/>
|
||||
<span t-raw="o._address_display(None, None)[o.id].replace('\n\n', '\n').replace('\n', '<br>')"/>
|
||||
<span t-raw="o.contact_address.replace('\n\n', '\n').replace('\n', '<br>')"/>
|
||||
<span t-field="o.vat"/>
|
||||
</div>
|
||||
<p>
|
||||
|
||||
@@ -42,7 +42,7 @@ class base_config_settings(osv.osv_memory):
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
def get_default_company_share_partner(self, cr, uid, ids, fields, context=None):
|
||||
def get_default_company_share_partner(self, cr, uid, fields, context=None):
|
||||
partner_rule = self.pool['ir.model.data'].xmlid_to_object(cr, uid, 'base.res_partner_rule', context=context)
|
||||
return {
|
||||
'company_share_partner': not bool(partner_rule.active)
|
||||
|
||||
@@ -9,7 +9,7 @@ from werkzeug import url_encode
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import tools, api
|
||||
from openerp.addons.base.res.res_partner import format_address
|
||||
from openerp.addons.base.res.res_partner import FormatAddress
|
||||
from openerp.addons.crm import crm_stage
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
@@ -51,7 +51,7 @@ CRM_LEAD_FIELDS_TO_MERGE = ['name',
|
||||
'partner_name']
|
||||
|
||||
|
||||
class crm_lead(format_address, osv.osv):
|
||||
class crm_lead(FormatAddress, osv.osv):
|
||||
""" CRM Lead Case """
|
||||
_name = "crm.lead"
|
||||
_description = "Lead/Opportunity"
|
||||
|
||||
@@ -43,7 +43,7 @@ class crm_configuration(osv.TransientModel):
|
||||
alias_id = alias_ids and alias_ids[0] or False
|
||||
return alias_id
|
||||
|
||||
def get_default_generate_sales_team_alias(self, cr, uid, ids, context=None):
|
||||
def get_default_generate_sales_team_alias(self, cr, uid, fields, context=None):
|
||||
return {'generate_sales_team_alias': self.pool['ir.values'].get_default(
|
||||
cr, uid, 'sales.config.settings', 'generate_sales_team_alias')}
|
||||
|
||||
@@ -51,7 +51,7 @@ class crm_configuration(osv.TransientModel):
|
||||
config_value = self.browse(cr, uid, ids, context=context).generate_sales_team_alias
|
||||
self.pool['ir.values'].set_default(cr, uid, 'sales.config.settings', 'generate_sales_team_alias', config_value)
|
||||
|
||||
def get_default_alias_prefix(self, cr, uid, ids, context=None):
|
||||
def get_default_alias_prefix(self, cr, uid, fields, context=None):
|
||||
alias_name = False
|
||||
alias_id = self._find_default_lead_alias_id(cr, uid, context=context)
|
||||
if alias_id:
|
||||
|
||||
@@ -10,16 +10,15 @@ class calendar_config_settings(osv.TransientModel):
|
||||
'server_uri': fields.char('URI for tuto')
|
||||
}
|
||||
|
||||
def set_calset(self,cr,uid,ids,context=None) :
|
||||
def set_calset(self, cr, uid, ids, context=None) :
|
||||
params = self.pool['ir.config_parameter']
|
||||
myself = self.browse(cr,uid,ids[0],context=context)
|
||||
myself = self.browse(cr, uid, ids[0], context=context)
|
||||
params.set_param(cr, uid, 'google_calendar_client_id', (myself.cal_client_id or '').strip(), groups=['base.group_system'], context=None)
|
||||
params.set_param(cr, uid, 'google_calendar_client_secret', (myself.cal_client_secret or '').strip(), groups=['base.group_system'], context=None)
|
||||
|
||||
|
||||
def get_default_all(self,cr,uid,ids,context=None):
|
||||
def get_default_all(self, cr, uid, fields, context=None):
|
||||
params = self.pool.get('ir.config_parameter')
|
||||
|
||||
cal_client_id = params.get_param(cr, uid, 'google_calendar_client_id',default='',context=context)
|
||||
cal_client_secret = params.get_param(cr, uid, 'google_calendar_client_secret',default='',context=context)
|
||||
server_uri= "%s/google_account/authentication" % params.get_param(cr, uid, 'web.base.url',default="http://yourcompany.odoo.com",context=context)
|
||||
|
||||
@@ -11,8 +11,8 @@ class HrEquipmentConfigSettings(models.TransientModel):
|
||||
equipment_alias_prefix = fields.Char('Use the following alias to report internal equipment issue')
|
||||
alias_domain = fields.Char("Alias Domain")
|
||||
|
||||
@api.multi
|
||||
def get_default_alias_equipment(self):
|
||||
@api.model
|
||||
def get_default_alias_equipment(self, fields):
|
||||
alias_name = False
|
||||
alias_id = self.env.ref('hr_equipment.mail_alias_equipment')
|
||||
if alias_id:
|
||||
@@ -29,8 +29,8 @@ class HrEquipmentConfigSettings(models.TransientModel):
|
||||
alias_id.write({'alias_name': record.equipment_alias_prefix})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def get_default_alias_domain(self):
|
||||
@api.model
|
||||
def get_default_alias_domain(self, fields):
|
||||
alias_domain = self.env['ir.config_parameter'].get_param("mail.catchall.domain")
|
||||
if not alias_domain:
|
||||
domain = self.env["ir.config_parameter"].get_param("web.base.url")
|
||||
|
||||
@@ -15,15 +15,15 @@ class BaseConfiguration(models.TransientModel):
|
||||
alias_domain = fields.Char('Alias Domain', help="If you have setup a catch-all email domain redirected to "
|
||||
"the Odoo server, enter the domain name here.")
|
||||
|
||||
@api.multi
|
||||
def get_default_fail_counter(self):
|
||||
@api.model
|
||||
def get_default_fail_counter(self, fields):
|
||||
previous_date = datetime.datetime.now() - datetime.timedelta(days=30)
|
||||
return {
|
||||
'fail_counter': self.env['mail.mail'].sudo().search_count([('date', '>=', previous_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)), ('state', '=', 'exception')]),
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def get_default_alias_domain(self):
|
||||
@api.model
|
||||
def get_default_alias_domain(self, fields):
|
||||
alias_domain = self.env["ir.config_parameter"].get_param("mail.catchall.domain", default=None)
|
||||
if alias_domain is None:
|
||||
domain = self.env["ir.config_parameter"].get_param("web.base.url")
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<field name="lang">${object.partner_id.lang}</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<p>Dear
|
||||
% if object.partner_id.company_type == 'company' and object.child_ids:
|
||||
% if object.partner_id.is_company and object.child_ids:
|
||||
${object.partner_id.child_ids[0].name}
|
||||
% else :
|
||||
${object.partner_id.name}
|
||||
@@ -51,7 +51,7 @@ from ${object.company_id.name}.
|
||||
<field name="lang">${object.partner_id.lang}</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<p>Dear
|
||||
% if object.partner_id.company_type == 'company' and object.child_ids:
|
||||
% if object.partner_id.is_company and object.child_ids:
|
||||
${object.partner_id.child_ids[0].name}
|
||||
% else :
|
||||
${object.partner_id.name}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
% set access_url = is_online and access_action['url'] or '/report/pdf/sale.report_saleorder/' + str(object.id)
|
||||
|
||||
<p>Dear
|
||||
% if object.partner_id.company_type == 'company' and object.child_ids:
|
||||
% if object.partner_id.is_company and object.child_ids:
|
||||
${object.partner_id.child_ids[0].name}
|
||||
% else :
|
||||
${object.partner_id.name}
|
||||
|
||||
@@ -11,5 +11,4 @@ import res_font
|
||||
import res_company
|
||||
import res_users
|
||||
import res_request
|
||||
import res_lang
|
||||
import ir_property
|
||||
|
||||
@@ -2,16 +2,9 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from operator import itemgetter
|
||||
import time
|
||||
|
||||
from openerp import models, api
|
||||
from openerp.osv import osv, orm, fields
|
||||
from openerp.tools.misc import attrgetter
|
||||
from openerp.exceptions import UserError
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Properties
|
||||
# -------------------------------------------------------------------------
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
TYPE2FIELD = {
|
||||
'char': 'value_text',
|
||||
@@ -26,44 +19,37 @@ TYPE2FIELD = {
|
||||
'selection': 'value_text',
|
||||
}
|
||||
|
||||
class ir_property(osv.osv):
|
||||
|
||||
class Property(models.Model):
|
||||
_name = 'ir.property'
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', select=1),
|
||||
name = fields.Char(index=True)
|
||||
res_id = fields.Char(string='Resource', index=True, help="If not set, acts as a default value for new resources",)
|
||||
company_id = fields.Many2one('res.company', string='Company', index=True)
|
||||
fields_id = fields.Many2one('ir.model.fields', string='Field', ondelete='cascade', required=True, index=True)
|
||||
value_float = fields.Float(string='Value')
|
||||
value_integer = fields.Integer(string='Value')
|
||||
value_text = fields.Text(string='Value') # will contain (char, text)
|
||||
value_binary = fields.Binary(string='Value')
|
||||
value_reference = fields.Char(string='Value')
|
||||
value_datetime = fields.Datetime(string='Value')
|
||||
type = fields.Selection([('char', 'Char'),
|
||||
('float', 'Float'),
|
||||
('boolean', 'Boolean'),
|
||||
('integer', 'Integer'),
|
||||
('text', 'Text'),
|
||||
('binary', 'Binary'),
|
||||
('many2one', 'Many2One'),
|
||||
('date', 'Date'),
|
||||
('datetime', 'DateTime'),
|
||||
('selection', 'Selection'),
|
||||
],
|
||||
required=True,
|
||||
default='many2one',
|
||||
index=True)
|
||||
|
||||
'res_id': fields.char('Resource', help="If not set, acts as a default value for new resources", select=1),
|
||||
'company_id': fields.many2one('res.company', 'Company', select=1),
|
||||
'fields_id': fields.many2one('ir.model.fields', 'Field', ondelete='cascade', required=True, select=1),
|
||||
|
||||
'value_float' : fields.float('Value'),
|
||||
'value_integer' : fields.integer('Value'),
|
||||
'value_text' : fields.text('Value'), # will contain (char, text)
|
||||
'value_binary' : fields.binary('Value'),
|
||||
'value_reference': fields.char('Value'),
|
||||
'value_datetime' : fields.datetime('Value'),
|
||||
|
||||
'type' : fields.selection([('char', 'Char'),
|
||||
('float', 'Float'),
|
||||
('boolean', 'Boolean'),
|
||||
('integer', 'Integer'),
|
||||
('text', 'Text'),
|
||||
('binary', 'Binary'),
|
||||
('many2one', 'Many2One'),
|
||||
('date', 'Date'),
|
||||
('datetime', 'DateTime'),
|
||||
('selection', 'Selection'),
|
||||
],
|
||||
'Type',
|
||||
required=True,
|
||||
select=1),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'type': 'many2one',
|
||||
}
|
||||
|
||||
def _update_values(self, cr, uid, ids, values):
|
||||
@api.multi
|
||||
def _update_values(self, values):
|
||||
value = values.pop('value', None)
|
||||
if not value:
|
||||
return values
|
||||
@@ -71,8 +57,8 @@ class ir_property(osv.osv):
|
||||
prop = None
|
||||
type_ = values.get('type')
|
||||
if not type_:
|
||||
if ids:
|
||||
prop = self.browse(cr, uid, ids[0])
|
||||
if self:
|
||||
prop = self[0]
|
||||
type_ = prop.type
|
||||
else:
|
||||
type_ = self._defaults['type']
|
||||
@@ -82,7 +68,7 @@ class ir_property(osv.osv):
|
||||
raise UserError(_('Invalid type'))
|
||||
|
||||
if field == 'value_reference':
|
||||
if isinstance(value, orm.BaseModel):
|
||||
if isinstance(value, models.BaseModel):
|
||||
value = '%s,%d' % (value._name, value.id)
|
||||
elif isinstance(value, (int, long)):
|
||||
field_id = values.get('fields_id')
|
||||
@@ -91,68 +77,69 @@ class ir_property(osv.osv):
|
||||
raise ValueError()
|
||||
field_id = prop.fields_id
|
||||
else:
|
||||
field_id = self.pool.get('ir.model.fields').browse(cr, uid, field_id)
|
||||
field_id = self.env['ir.model.fields'].browse(field_id)
|
||||
|
||||
value = '%s,%d' % (field_id.relation, value)
|
||||
|
||||
values[field] = value
|
||||
return values
|
||||
|
||||
def write(self, cr, uid, ids, values, context=None):
|
||||
return super(ir_property, self).write(cr, uid, ids, self._update_values(cr, uid, ids, values), context=context)
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
return super(Property, self).write(self._update_values(values))
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
return super(ir_property, self).create(cr, uid, self._update_values(cr, uid, None, values), context=context)
|
||||
@api.model
|
||||
def create(self, values):
|
||||
return super(Property, self).create(self._update_values(values))
|
||||
|
||||
@api.v7
|
||||
def get_by_record(self, cr, uid, record, context=None):
|
||||
if record.type in ('char', 'text', 'selection'):
|
||||
return record.value_text
|
||||
elif record.type == 'float':
|
||||
return record.value_float
|
||||
elif record.type == 'boolean':
|
||||
return bool(record.value_integer)
|
||||
elif record.type == 'integer':
|
||||
return record.value_integer
|
||||
elif record.type == 'binary':
|
||||
return record.value_binary
|
||||
elif record.type == 'many2one':
|
||||
if not record.value_reference:
|
||||
return Property.get_by_record(self.browse(cr, uid, record.id, context=context))
|
||||
|
||||
@api.v8
|
||||
def get_by_record(self):
|
||||
self.ensure_one()
|
||||
if self.type in ('char', 'text', 'selection'):
|
||||
return self.value_text
|
||||
elif self.type == 'float':
|
||||
return self.value_float
|
||||
elif self.type == 'boolean':
|
||||
return bool(self.value_integer)
|
||||
elif self.type == 'integer':
|
||||
return self.value_integer
|
||||
elif self.type == 'binary':
|
||||
return self.value_binary
|
||||
elif self.type == 'many2one':
|
||||
if not self.value_reference:
|
||||
return False
|
||||
model, resource_id = record.value_reference.split(',')
|
||||
value = self.pool[model].browse(cr, uid, int(resource_id), context=context)
|
||||
return value.exists()
|
||||
elif record.type == 'datetime':
|
||||
return record.value_datetime
|
||||
elif record.type == 'date':
|
||||
if not record.value_datetime:
|
||||
model, resource_id = self.value_reference.split(',')
|
||||
return self.env[model].browse(int(resource_id)).exists()
|
||||
elif self.type == 'datetime':
|
||||
return self.value_datetime
|
||||
elif self.type == 'date':
|
||||
if not self.value_datetime:
|
||||
return False
|
||||
return time.strftime('%Y-%m-%d', time.strptime(record.value_datetime, '%Y-%m-%d %H:%M:%S'))
|
||||
return fields.Date.to_string(fields.Datetime.from_string(self.value_datetime))
|
||||
return False
|
||||
|
||||
def get(self, cr, uid, name, model, res_id=False, context=None):
|
||||
domain = self._get_domain(cr, uid, name, model, context=context)
|
||||
@api.model
|
||||
def get(self, name, model, res_id=False):
|
||||
domain = self._get_domain(name, model)
|
||||
if domain is not None:
|
||||
domain = [('res_id', '=', res_id)] + domain
|
||||
#make the search with company_id asc to make sure that properties specific to a company are given first
|
||||
nid = self.search(cr, uid, domain, limit=1, order='company_id asc', context=context)
|
||||
if not nid: return False
|
||||
record = self.browse(cr, uid, nid[0], context=context)
|
||||
return self.get_by_record(cr, uid, record, context=context)
|
||||
prop = self.search(domain, limit=1, order='company_id')
|
||||
if prop:
|
||||
return prop.get_by_record()
|
||||
return False
|
||||
|
||||
def _get_domain(self, cr, uid, prop_name, model, context=None):
|
||||
context = context or {}
|
||||
cr.execute('select id from ir_model_fields where name=%s and model=%s', (prop_name, model))
|
||||
res = cr.fetchone()
|
||||
def _get_domain(self, prop_name, model):
|
||||
self._cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", (prop_name, model))
|
||||
res = self._cr.fetchone()
|
||||
if not res:
|
||||
return None
|
||||
|
||||
cid = context.get('force_company')
|
||||
if not cid:
|
||||
company = self.pool.get('res.company')
|
||||
cid = company._company_default_get(cr, uid, model, res[0], context=context)
|
||||
|
||||
return [('fields_id', '=', res[0]), ('company_id', 'in', [cid, False])]
|
||||
company_id = self._context.get('force_company') or self.env['res.company']._company_default_get(model, res[0]).id
|
||||
return [('fields_id', '=', res[0]), ('company_id', 'in', [company_id, False])]
|
||||
|
||||
@api.model
|
||||
def get_multi(self, name, model, ids):
|
||||
@@ -179,7 +166,7 @@ class ir_property(osv.osv):
|
||||
# for a given res_id, take the first property only
|
||||
id = refs.pop(prop.res_id, None)
|
||||
if id is not None:
|
||||
result[id] = self.get_by_record(prop)
|
||||
result[id] = prop.get_by_record()
|
||||
|
||||
# set the default value to the ids that are not in result
|
||||
default_value = result.pop(False, False)
|
||||
@@ -223,7 +210,7 @@ class ir_property(osv.osv):
|
||||
value = clean(values[id])
|
||||
if value == default_value:
|
||||
prop.unlink()
|
||||
elif value != clean(prop.get_by_record(prop)):
|
||||
elif value != clean(prop.get_by_record()):
|
||||
prop.write({'value': value})
|
||||
|
||||
# create new properties for records that do not have one yet
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
import re
|
||||
|
||||
from openerp import api, fields, models, _
|
||||
from openerp.osv import expression
|
||||
from openerp.exceptions import UserError
|
||||
from odoo import api, fields, models
|
||||
from odoo.osv import expression
|
||||
|
||||
|
||||
def sanitize_account_number(acc_number):
|
||||
if acc_number:
|
||||
return re.sub(r'\W+', '', acc_number).upper()
|
||||
return False
|
||||
|
||||
|
||||
class Bank(models.Model):
|
||||
_description = 'Bank'
|
||||
_name = 'res.bank'
|
||||
@@ -49,6 +50,7 @@ class Bank(models.Model):
|
||||
banks = self.search(domain + args, limit=limit)
|
||||
return banks.name_get()
|
||||
|
||||
|
||||
class ResPartnerBank(models.Model):
|
||||
_name = 'res.partner.bank'
|
||||
_rec_name = 'acc_number'
|
||||
@@ -70,15 +72,15 @@ class ResPartnerBank(models.Model):
|
||||
('unique_number', 'unique(sanitized_acc_number)', 'Account Number must be unique'),
|
||||
]
|
||||
|
||||
@api.one
|
||||
@api.depends('acc_number')
|
||||
def _compute_sanitized_acc_number(self):
|
||||
self.sanitized_acc_number = sanitize_account_number(self.acc_number)
|
||||
for bank in self:
|
||||
bank.sanitized_acc_number = sanitize_account_number(bank.acc_number)
|
||||
|
||||
@api.one
|
||||
@api.depends('acc_type')
|
||||
@api.multi
|
||||
def _compute_acc_type(self):
|
||||
self.acc_type = 'bank'
|
||||
for bank in self:
|
||||
bank.acc_type = 'bank'
|
||||
|
||||
@api.model
|
||||
def search(self, args, offset=0, limit=None, order=None, count=False):
|
||||
|
||||
@@ -3,224 +3,15 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID, tools, api
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.tools import image_resize_image
|
||||
|
||||
|
||||
class res_company(osv.osv):
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class Company(models.Model):
|
||||
_name = "res.company"
|
||||
_description = 'Companies'
|
||||
_order = 'name'
|
||||
|
||||
def _get_address_data(self, cr, uid, ids, field_names, arg, context=None):
|
||||
""" Read the 'address' functional fields. """
|
||||
result = {}
|
||||
part_obj = self.pool.get('res.partner')
|
||||
for company in self.browse(cr, uid, ids, context=context):
|
||||
result[company.id] = {}.fromkeys(field_names, False)
|
||||
if company.partner_id:
|
||||
address_data = part_obj.address_get(cr, openerp.SUPERUSER_ID, [company.partner_id.id], adr_pref=['contact'])
|
||||
if address_data['contact']:
|
||||
address = part_obj.read(cr, openerp.SUPERUSER_ID, [address_data['contact']], field_names, context=context)[0]
|
||||
for field in field_names:
|
||||
result[company.id][field] = address[field] or False
|
||||
return result
|
||||
|
||||
def _set_address_data(self, cr, uid, company_id, name, value, arg, context=None):
|
||||
""" Write the 'address' functional fields. """
|
||||
company = self.browse(cr, uid, company_id, context=context)
|
||||
if company.partner_id:
|
||||
part_obj = self.pool.get('res.partner')
|
||||
address_data = part_obj.address_get(cr, uid, [company.partner_id.id], adr_pref=['contact'])
|
||||
address = address_data['contact']
|
||||
if address:
|
||||
part_obj.write(cr, uid, [address], {name: value or False}, context=context)
|
||||
else:
|
||||
part_obj.create(cr, uid, {name: value or False, 'parent_id': company.partner_id.id}, context=context)
|
||||
return True
|
||||
|
||||
def _get_logo_web(self, cr, uid, ids, _field_name, _args, context=None):
|
||||
result = dict.fromkeys(ids, False)
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
size = (180, None)
|
||||
result[record.id] = image_resize_image(record.partner_id.image, size)
|
||||
return result
|
||||
|
||||
def _get_companies_from_partner(self, cr, uid, ids, context=None):
|
||||
return self.pool['res.company'].search(cr, uid, [('partner_id', 'in', ids)], context=context)
|
||||
|
||||
_columns = {
|
||||
'name': fields.related('partner_id', 'name', string='Company Name', size=128, required=True, store=True, type='char'),
|
||||
'parent_id': fields.many2one('res.company', 'Parent Company', select=True),
|
||||
'child_ids': fields.one2many('res.company', 'parent_id', 'Child Companies'),
|
||||
'partner_id': fields.many2one('res.partner', 'Partner', required=True),
|
||||
'rml_header': fields.text('RML Header', required=True),
|
||||
'rml_header1': fields.char('Company Tagline', help="Appears by default on the top right corner of your printed documents (report header)."),
|
||||
'rml_header2': fields.text('RML Internal Header', required=True),
|
||||
'rml_header3': fields.text('RML Internal Header for Landscape Reports', required=True),
|
||||
'rml_footer': fields.text('Report Footer', help="Footer text displayed at the bottom of all reports."),
|
||||
'rml_footer_readonly': fields.related('rml_footer', type='text', string='Report Footer', readonly=True),
|
||||
'custom_footer': fields.boolean('Custom Footer', help="Check this to define the report footer manually. Otherwise it will be filled in automatically."),
|
||||
'font': fields.many2one('res.font', string="Font", domain=[('mode', 'in', ('Normal', 'Regular', 'all', 'Book'))],
|
||||
help="Set the font into the report header, it will be used as default font in the RML reports of the user company"),
|
||||
'logo': fields.related('partner_id', 'image', string="Logo", type="binary"),
|
||||
# logo_web: do not store in attachments, since the image is retrieved in SQL for
|
||||
# performance reasons (see addons/web/controllers/main.py, Binary.company_logo)
|
||||
'logo_web': fields.function(_get_logo_web, string="Logo Web", type="binary", store={
|
||||
'res.company': (lambda s, c, u, i, x: i, ['partner_id'], 10),
|
||||
'res.partner': (_get_companies_from_partner, ['image'], 10),
|
||||
}),
|
||||
'currency_id': fields.many2one('res.currency', 'Currency', required=True),
|
||||
'user_ids': fields.many2many('res.users', 'res_company_users_rel', 'cid', 'user_id', 'Accepted Users'),
|
||||
'account_no':fields.char('Account No.'),
|
||||
'street': fields.function(_get_address_data, fnct_inv=_set_address_data, size=128, type='char', string="Street", multi='address'),
|
||||
'street2': fields.function(_get_address_data, fnct_inv=_set_address_data, size=128, type='char', string="Street2", multi='address'),
|
||||
'zip': fields.function(_get_address_data, fnct_inv=_set_address_data, size=24, type='char', string="Zip", multi='address'),
|
||||
'city': fields.function(_get_address_data, fnct_inv=_set_address_data, size=24, type='char', string="City", multi='address'),
|
||||
'state_id': fields.function(_get_address_data, fnct_inv=_set_address_data, type='many2one', relation='res.country.state', string="Fed. State", multi='address'),
|
||||
'country_id': fields.function(_get_address_data, fnct_inv=_set_address_data, type='many2one', relation='res.country', string="Country", multi='address'),
|
||||
'email': fields.related('partner_id', 'email', size=64, type='char', string="Email", store=True),
|
||||
'phone': fields.related('partner_id', 'phone', size=64, type='char', string="Phone", store=True),
|
||||
'fax': fields.function(_get_address_data, fnct_inv=_set_address_data, size=64, type='char', string="Fax", multi='address'),
|
||||
'website': fields.related('partner_id', 'website', string="Website", type="char", size=64),
|
||||
'vat': fields.related('partner_id', 'vat', string="Tax ID", type="char", size=32),
|
||||
'company_registry': fields.char('Company Registry', size=64),
|
||||
'rml_paper_format': fields.selection([('a4', 'A4'), ('us_letter', 'US Letter')], "Paper Format", required=True, oldname='paper_format'),
|
||||
}
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', 'The company name must be unique !')
|
||||
]
|
||||
|
||||
@api.onchange('custom_footer', 'phone', 'fax', 'email', 'website', 'vat', 'company_registry')
|
||||
def onchange_footer(self):
|
||||
if not self.custom_footer:
|
||||
# first line (notice that missing elements are filtered out before the join)
|
||||
res = ' | '.join(filter(bool, [
|
||||
self.phone and '%s: %s' % (_('Phone'), self.phone),
|
||||
self.fax and '%s: %s' % (_('Fax'), self.fax),
|
||||
self.email and '%s: %s' % (_('Email'), self.email),
|
||||
self.website and '%s: %s' % (_('Website'), self.website),
|
||||
self.vat and '%s: %s' % (_('TIN'), self.vat),
|
||||
self.company_registry and '%s: %s' % (_('Reg'), self.company_registry),
|
||||
]))
|
||||
self.rml_footer_readonly = res
|
||||
self.rml_footer = res
|
||||
|
||||
def onchange_state(self, cr, uid, ids, state_id, context=None):
|
||||
if state_id:
|
||||
return {'value':{'country_id': self.pool.get('res.country.state').browse(cr, uid, state_id, context).country_id.id }}
|
||||
return {}
|
||||
|
||||
def onchange_font_name(self, cr, uid, ids, font, rml_header, rml_header2, rml_header3, context=None):
|
||||
""" To change default header style of all <para> and drawstring. """
|
||||
|
||||
def _change_header(header,font):
|
||||
""" Replace default fontname use in header and setfont tag """
|
||||
|
||||
default_para = re.sub('fontName.?=.?".*"', 'fontName="%s"'% font, header)
|
||||
return re.sub('(<setFont.?name.?=.?)(".*?")(.)', '\g<1>"%s"\g<3>'% font, default_para)
|
||||
|
||||
if not font:
|
||||
return True
|
||||
fontname = self.pool.get('res.font').browse(cr, uid, font, context=context).name
|
||||
return {'value':{
|
||||
'rml_header': _change_header(rml_header, fontname),
|
||||
'rml_header2':_change_header(rml_header2, fontname),
|
||||
'rml_header3':_change_header(rml_header3, fontname)
|
||||
}}
|
||||
|
||||
def on_change_country(self, cr, uid, ids, country_id, context=None):
|
||||
res = {'domain': {'state_id': []}}
|
||||
currency_id = self._get_euro(cr, uid, context=context)
|
||||
if country_id:
|
||||
currency_id = self.pool.get('res.country').browse(cr, uid, country_id, context=context).currency_id.id
|
||||
res['domain'] = {'state_id': [('country_id','=',country_id)]}
|
||||
res['value'] = {'currency_id': currency_id}
|
||||
return res
|
||||
|
||||
def name_search(self, cr, uid, name='', args=None, operator='ilike', context=None, limit=100):
|
||||
context = dict(context or {})
|
||||
if context.pop('user_preference', None):
|
||||
# We browse as superuser. Otherwise, the user would be able to
|
||||
# select only the currently visible companies (according to rules,
|
||||
# which are probably to allow to see the child companies) even if
|
||||
# she belongs to some other companies.
|
||||
user = self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context)
|
||||
cmp_ids = list(set([user.company_id.id] + [cmp.id for cmp in user.company_ids]))
|
||||
uid = SUPERUSER_ID
|
||||
args = (args or []) + [('id', 'in', cmp_ids)]
|
||||
return super(res_company, self).name_search(cr, uid, name=name, args=args, operator=operator, context=context, limit=limit)
|
||||
|
||||
@api.returns('self')
|
||||
def _company_default_get(self, cr, uid, object=False, field=False, context=None):
|
||||
"""
|
||||
Returns the default company (the user's company)
|
||||
The 'object' and 'field' arguments are ignored but left here for
|
||||
backward compatibility and potential override.
|
||||
"""
|
||||
return self.pool['res.users']._get_company(cr, uid, context=context)
|
||||
|
||||
@tools.ormcache('uid', 'company')
|
||||
def _get_company_children(self, cr, uid=None, company=None):
|
||||
if not company:
|
||||
return []
|
||||
ids = self.search(cr, uid, [('parent_id','child_of',[company])])
|
||||
return ids
|
||||
|
||||
def _get_partner_hierarchy(self, cr, uid, company_id, context=None):
|
||||
if company_id:
|
||||
parent_id = self.browse(cr, uid, company_id)['parent_id']
|
||||
if parent_id:
|
||||
return self._get_partner_hierarchy(cr, uid, parent_id.id, context)
|
||||
else:
|
||||
return self._get_partner_descendance(cr, uid, company_id, [], context)
|
||||
return []
|
||||
|
||||
def _get_partner_descendance(self, cr, uid, company_id, descendance, context=None):
|
||||
descendance.append(self.browse(cr, uid, company_id).partner_id.id)
|
||||
for child_id in self._get_company_children(cr, uid, company_id):
|
||||
if child_id != company_id:
|
||||
descendance = self._get_partner_descendance(cr, uid, child_id, descendance)
|
||||
return descendance
|
||||
|
||||
#
|
||||
# This function restart the cache on the _get_company_children method
|
||||
#
|
||||
def cache_restart(self, cr):
|
||||
self._get_company_children.clear_cache(self)
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
if not vals.get('name', False) or vals.get('partner_id', False):
|
||||
self.cache_restart(cr)
|
||||
return super(res_company, self).create(cr, uid, vals, context=context)
|
||||
obj_partner = self.pool.get('res.partner')
|
||||
partner_id = obj_partner.create(cr, uid, {'name': vals['name'], 'is_company':True, 'image': vals.get('logo', False), 'customer': False}, context=context)
|
||||
vals.update({'partner_id': partner_id})
|
||||
self.cache_restart(cr)
|
||||
company_id = super(res_company, self).create(cr, uid, vals, context=context)
|
||||
obj_partner.write(cr, uid, [partner_id], {'company_id': company_id}, context=context)
|
||||
return company_id
|
||||
|
||||
def write(self, cr, uid, ids, values, context=None):
|
||||
self.cache_restart(cr)
|
||||
return super(res_company, self).write(cr, uid, ids, values, context=context)
|
||||
|
||||
def _get_euro(self, cr, uid, context=None):
|
||||
rate_obj = self.pool.get('res.currency.rate')
|
||||
rate_id = rate_obj.search(cr, uid, [('rate', '=', 1)], context=context)
|
||||
return rate_id and rate_obj.browse(cr, uid, rate_id[0], context=context).currency_id.id or False
|
||||
|
||||
def _get_logo(self, cr, uid, ids):
|
||||
return open(os.path.join( tools.config['root_path'], 'addons', 'base', 'res', 'res_company_logo.png'), 'rb') .read().encode('base64')
|
||||
|
||||
def _get_font(self, cr, uid, ids):
|
||||
font_obj = self.pool.get('res.font')
|
||||
res = font_obj.search(cr, uid, [('family', '=', 'Helvetica'), ('mode', '=', 'all')], limit=1)
|
||||
return res and res[0] or False
|
||||
|
||||
_header = """
|
||||
<header>
|
||||
@@ -247,19 +38,8 @@ class res_company(osv.osv):
|
||||
</header>"""
|
||||
|
||||
_header2 = _header % (539, 772, "1.0cm", "28.3cm", "11.1cm", "28.3cm", "1.0cm 28.1cm 20.1cm 28.1cm")
|
||||
|
||||
_header3 = _header % (786, 525, 25, 555, 440, 555, "25 550 818 550")
|
||||
|
||||
def _get_header(self,cr,uid,ids):
|
||||
try :
|
||||
header_file = tools.file_open(os.path.join('base', 'report', 'corporate_rml_header.rml'))
|
||||
try:
|
||||
return header_file.read()
|
||||
finally:
|
||||
header_file.close()
|
||||
except:
|
||||
return self._header_a4
|
||||
|
||||
_header_main = """
|
||||
<header>
|
||||
<pageTemplate>
|
||||
@@ -311,24 +91,249 @@ class res_company(osv.osv):
|
||||
_header_a4 = _header_main % ('21.7cm', '27.7cm', '27.7cm', '27.7cm', '27.8cm', '27.3cm', '25.3cm', '25.0cm', '25.0cm', '24.6cm', '24.6cm', '24.5cm', '24.5cm')
|
||||
_header_letter = _header_main % ('20cm', '26.0cm', '26.0cm', '26.0cm', '26.1cm', '25.6cm', '23.6cm', '23.3cm', '23.3cm', '22.9cm', '22.9cm', '22.8cm', '22.8cm')
|
||||
|
||||
def onchange_rml_paper_format(self, cr, uid, ids, rml_paper_format, context=None):
|
||||
if rml_paper_format == 'us_letter':
|
||||
return {'value': {'rml_header': self._header_letter}}
|
||||
return {'value': {'rml_header': self._header_a4}}
|
||||
def _get_header(self):
|
||||
try:
|
||||
header_file = tools.file_open(os.path.join(
|
||||
'base', 'report', 'corporate_rml_header.rml'))
|
||||
try:
|
||||
return header_file.read()
|
||||
finally:
|
||||
header_file.close()
|
||||
except:
|
||||
return self._header_a4
|
||||
|
||||
def act_discover_fonts(self, cr, uid, ids, context=None):
|
||||
return self.pool.get("res.font").font_scan(cr, uid, context=context)
|
||||
def _get_font(self):
|
||||
return self.env['res.font'].search([('family', '=', 'Helvetica'), ('mode', '=', 'all')], limit=1)
|
||||
|
||||
_defaults = {
|
||||
'currency_id': _get_euro,
|
||||
'rml_paper_format': 'a4',
|
||||
'rml_header':_get_header,
|
||||
'rml_header2': _header2,
|
||||
'rml_header3': _header3,
|
||||
'logo':_get_logo,
|
||||
'font':_get_font,
|
||||
}
|
||||
def _get_logo(self):
|
||||
return open(os.path.join(tools.config['root_path'], 'addons', 'base', 'res', 'res_company_logo.png'), 'rb') .read().encode('base64')
|
||||
|
||||
_constraints = [
|
||||
(osv.osv._check_recursion, 'Error! You can not create recursive companies.', ['parent_id'])
|
||||
@api.model
|
||||
def _get_euro(self):
|
||||
return self.env['res.currency.rate'].search([('rate', '=', 1)], limit=1).currency_id
|
||||
|
||||
name = fields.Char(related='partner_id.name', string='Company Name', required=True, store=True)
|
||||
parent_id = fields.Many2one('res.company', string='Parent Company', index=True)
|
||||
child_ids = fields.One2many('res.company', 'parent_id', string='Child Companies')
|
||||
partner_id = fields.Many2one('res.partner', string='Partner', required=True)
|
||||
rml_header = fields.Text(required=True, default=_get_header)
|
||||
rml_header1 = fields.Char(string='Company Tagline', help="Appears by default on the top right corner of your printed documents (report header).")
|
||||
rml_header2 = fields.Text(string='RML Internal Header', required=True, default=_header2)
|
||||
rml_header3 = fields.Text(string='RML Internal Header for Landscape Reports', required=True, default=_header3)
|
||||
rml_footer = fields.Text(string='Report Footer', help="Footer text displayed at the bottom of all reports.")
|
||||
rml_footer_readonly = fields.Text(related='rml_footer', string='Report Footer', readonly=True)
|
||||
custom_footer = fields.Boolean(help="Check this to define the report footer manually. Otherwise it will be filled in automatically.")
|
||||
font = fields.Many2one('res.font', string="Font", default=lambda self: self._get_font(),
|
||||
domain=[('mode', 'in', ('Normal', 'Regular', 'all', 'Book'))],
|
||||
help="Set the font into the report header, it will be used as default font in the RML reports of the user company")
|
||||
logo = fields.Binary(related='partner_id.image', default=_get_logo)
|
||||
# logo_web: do not store in attachments, since the image is retrieved in SQL for
|
||||
# performance reasons (see addons/web/controllers/main.py, Binary.company_logo)
|
||||
logo_web = fields.Binary(compute='_compute_logo_web', store=True)
|
||||
currency_id = fields.Many2one('res.currency', string='Currency', required=True, default=lambda self: self._get_euro())
|
||||
user_ids = fields.Many2many('res.users', 'res_company_users_rel', 'cid', 'user_id', string='Accepted Users')
|
||||
account_no = fields.Char(string='Account No.')
|
||||
street = fields.Char(compute='_compute_address', inverse='_inverse_street')
|
||||
street2 = fields.Char(compute='_compute_address', inverse='_inverse_street2')
|
||||
zip = fields.Char(compute='_compute_address', inverse='_inverse_zip')
|
||||
city = fields.Char(compute='_compute_address', inverse='_inverse_city')
|
||||
state_id = fields.Many2one('res.country.state', compute='_compute_address', inverse='_inverse_state', string="Fed. State")
|
||||
bank_ids = fields.One2many('res.partner.bank', 'company_id', string='Bank Accounts', help='Bank accounts related to this company')
|
||||
country_id = fields.Many2one('res.country', compute='_compute_address', inverse='_inverse_country', string="Country")
|
||||
email = fields.Char(related='partner_id.email', store=True)
|
||||
phone = fields.Char(related='partner_id.phone', store=True)
|
||||
fax = fields.Char(compute='_compute_address', inverse='_inverse_fax')
|
||||
website = fields.Char(related='partner_id.website')
|
||||
vat = fields.Char(related='partner_id.vat', string="Tax ID")
|
||||
company_registry = fields.Char()
|
||||
rml_paper_format = fields.Selection([('a4', 'A4'), ('us_letter', 'US Letter')], string="Paper Format", required=True, default='a4', oldname='paper_format')
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', 'The company name must be unique !')
|
||||
]
|
||||
|
||||
# TODO @api.depends(): currently now way to formulate the dependency on the
|
||||
# partner's contact address
|
||||
def _compute_address(self):
|
||||
for company in self.filtered(lambda company: company.partner_id):
|
||||
address_data = company.partner_id.sudo().address_get(adr_pref=['contact'])
|
||||
if address_data['contact']:
|
||||
partner = company.partner_id.browse(address_data['contact'])
|
||||
company.street = partner.street
|
||||
company.street2 = partner.street2
|
||||
company.city = partner.city
|
||||
company.zip = partner.zip
|
||||
company.state_id = partner.state_id
|
||||
company.country_id = partner.country_id
|
||||
company.fax = partner.fax
|
||||
|
||||
def _inverse_street(self):
|
||||
for company in self:
|
||||
company.partner_id.street = company.street
|
||||
|
||||
def _inverse_street2(self):
|
||||
for company in self:
|
||||
company.partner_id.street2 = company.street2
|
||||
|
||||
def _inverse_zip(self):
|
||||
for company in self:
|
||||
company.partner_id.zip = company.zip
|
||||
|
||||
def _inverse_city(self):
|
||||
for company in self:
|
||||
company.partner_id.city = company.city
|
||||
|
||||
def _inverse_state(self):
|
||||
for company in self:
|
||||
company.partner_id.state_id = company.state_id
|
||||
|
||||
def _inverse_country(self):
|
||||
for company in self:
|
||||
company.partner_id.country_id = company.country_id
|
||||
|
||||
def _inverse_fax(self):
|
||||
for company in self:
|
||||
company.partner_id.fax = company.fax
|
||||
|
||||
@api.depends('partner_id', 'partner_id.image')
|
||||
def _compute_logo_web(self):
|
||||
for company in self:
|
||||
company.logo_web = tools.image_resize_image(company.partner_id.image, (180, None))
|
||||
|
||||
@api.onchange('custom_footer', 'phone', 'fax', 'email', 'website', 'vat', 'company_registry')
|
||||
def onchange_footer(self):
|
||||
if not self.custom_footer:
|
||||
# first line (notice that missing elements are filtered out before the join)
|
||||
res = ' | '.join(filter(bool, [
|
||||
self.phone and '%s: %s' % (_('Phone'), self.phone),
|
||||
self.fax and '%s: %s' % (_('Fax'), self.fax),
|
||||
self.email and '%s: %s' % (_('Email'), self.email),
|
||||
self.website and '%s: %s' % (_('Website'), self.website),
|
||||
self.vat and '%s: %s' % (_('TIN'), self.vat),
|
||||
self.company_registry and '%s: %s' % (_('Reg'), self.company_registry),
|
||||
]))
|
||||
self.rml_footer_readonly = res
|
||||
self.rml_footer = res
|
||||
|
||||
@api.onchange('state_id')
|
||||
def _onchange_state(self):
|
||||
self.country_id = self.state_id.country_id
|
||||
|
||||
@api.onchange('font')
|
||||
def _onchange_font_name(self):
|
||||
""" To change default header style of all <para> and drawstring. """
|
||||
def _change_header(header, font):
|
||||
""" Replace default fontname use in header and setfont tag """
|
||||
default_para = re.sub(r'fontName.?=.?".*"', 'fontName="%s"' % font, header)
|
||||
return re.sub(r'(<setFont.?name.?=.?)(".*?")(.)', '\g<1>"%s"\g<3>' % font, default_para)
|
||||
|
||||
if self.font:
|
||||
fontname = self.font.name
|
||||
self.rml_header = _change_header(self.rml_header, fontname)
|
||||
self.rml_header2 = _change_header(self.rml_header2, fontname)
|
||||
self.rml_header3 = _change_header(self.rml_header3, fontname)
|
||||
|
||||
@api.multi
|
||||
def on_change_country(self, country_id):
|
||||
# This function is called from account/models/chart_template.py, hence decorated with `multi`.
|
||||
self.ensure_one()
|
||||
currency_id = self._get_euro()
|
||||
if country_id:
|
||||
currency_id = self.env['res.country'].browse(country_id).currency_id.id
|
||||
return {'value': {'currency_id': currency_id}}
|
||||
|
||||
@api.onchange('country_id')
|
||||
def _onchange_country_id_wrapper(self):
|
||||
res = {'domain': {'state_id': []}}
|
||||
if self.country_id:
|
||||
res['domain']['state_id'] = [('country_id', '=', self.country_id.id)]
|
||||
values = self.on_change_country(self.country_id.id)['value']
|
||||
for fname, value in values.iteritems():
|
||||
setattr(self, fname, value)
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
||||
context = dict(self.env.context)
|
||||
newself = self
|
||||
if context.pop('user_preference', None):
|
||||
# We browse as superuser. Otherwise, the user would be able to
|
||||
# select only the currently visible companies (according to rules,
|
||||
# which are probably to allow to see the child companies) even if
|
||||
# she belongs to some other companies.
|
||||
companies = self.env.user.company_id + self.env.user.company_ids
|
||||
args = (args or []) + [('id', 'in', companies.ids)]
|
||||
newself = newself.sudo()
|
||||
return super(Company, newself.with_context(context)).name_search(name=name, args=args, operator=operator, limit=limit)
|
||||
|
||||
@api.model
|
||||
@api.returns('self', lambda value: value.id)
|
||||
def _company_default_get(self, object=False, field=False):
|
||||
""" Returns the default company (usually the user's company).
|
||||
The 'object' and 'field' arguments are ignored but left here for
|
||||
backward compatibility and potential override.
|
||||
"""
|
||||
return self.env['res.users']._get_company()
|
||||
|
||||
@api.model
|
||||
@tools.ormcache('self.env.uid', 'company')
|
||||
def _get_company_children(self, company=None):
|
||||
if not company:
|
||||
return []
|
||||
return self.search([('parent_id', 'child_of', [company])]).ids
|
||||
|
||||
@api.multi
|
||||
def _get_partner_hierarchy(self):
|
||||
self.ensure_one()
|
||||
parent = self.parent_id
|
||||
if parent:
|
||||
return parent._get_partner_hierarchy()
|
||||
else:
|
||||
return self._get_partner_descendance([])
|
||||
|
||||
@api.multi
|
||||
def _get_partner_descendance(self, descendance):
|
||||
self.ensure_one()
|
||||
descendance.append(self.partner_id.id)
|
||||
for child_id in self._get_company_children(self.id):
|
||||
if child_id != self.id:
|
||||
descendance = self.browse(child_id)._get_partner_descendance(descendance)
|
||||
return descendance
|
||||
|
||||
# deprecated, use clear_caches() instead
|
||||
def cache_restart(self):
|
||||
self.clear_caches()
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if not vals.get('name') or vals.get('partner_id'):
|
||||
self.clear_caches()
|
||||
return super(Company, self).create(vals)
|
||||
partner = self.env['res.partner'].create({'name': vals['name'], 'is_company': True, 'image': vals.get('logo'), 'customer': False})
|
||||
vals['partner_id'] = partner.id
|
||||
self.clear_caches()
|
||||
company = super(Company, self).create(vals)
|
||||
partner.write({'company_id': company.id})
|
||||
return company
|
||||
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
self.clear_caches()
|
||||
return super(Company, self).write(values)
|
||||
|
||||
@api.onchange('rml_paper_format')
|
||||
def _onchange_rml_paper_format(self):
|
||||
if self.rml_paper_format == 'us_letter':
|
||||
self.rml_header = self._header_letter
|
||||
else:
|
||||
self.rml_header = self._header_a4
|
||||
|
||||
@api.multi
|
||||
def act_discover_fonts(self):
|
||||
self.ensure_one()
|
||||
return self.env["res.font"].font_scan()
|
||||
|
||||
@api.constrains('parent_id')
|
||||
def _check_parent_id(self):
|
||||
if not self._check_recursion():
|
||||
raise ValidationError(_('Error ! You cannot create recursive companies.'))
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
<field name="street" placeholder="Street..." class="o_address_street"/>
|
||||
<field name="street2" placeholder="Street 2..." class="o_address_street"/>
|
||||
<field name="city" placeholder="City" class="o_address_city"/>
|
||||
<field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}' on_change="onchange_state(state_id)"/>
|
||||
<field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}'/>
|
||||
<field name="zip" placeholder="ZIP" class="o_address_zip"/>
|
||||
<field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True}' on_change="on_change_country(country_id)"/>
|
||||
<field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True}'/>
|
||||
</div>
|
||||
<field name="website" widget="url" placeholder="e.g. www.odoo.com"/>
|
||||
</group>
|
||||
@@ -65,10 +65,10 @@
|
||||
<group string="Configuration (RML)" groups="base.group_no_one">
|
||||
<group col="2" colspan="2">
|
||||
<button name="%(preview_rml_report)d" string="Preview RML Report" type="action" colspan="2"/>
|
||||
<field name="rml_paper_format" on_change="onchange_rml_paper_format(rml_paper_format)"/>
|
||||
<field name="rml_paper_format"/>
|
||||
</group>
|
||||
<group col="3" colspan="2">
|
||||
<field name="font" class="oe_inline" on_change="onchange_font_name(font, rml_header, rml_header2, rml_header3)"/>
|
||||
<field name="font" class="oe_inline"/>
|
||||
<button string="(reload fonts)" name="act_discover_fonts" type="object" class="oe_link"/>
|
||||
</group>
|
||||
<group col="1" colspan="2">
|
||||
|
||||
@@ -2,41 +2,37 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import json
|
||||
import logging
|
||||
from operator import attrgetter
|
||||
import re
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools import ustr
|
||||
from openerp.tools.translate import _
|
||||
from openerp import exceptions
|
||||
from operator import attrgetter, add
|
||||
from lxml import etree
|
||||
from openerp.exceptions import UserError
|
||||
|
||||
from odoo import api, models, registry, SUPERUSER_ID, _
|
||||
from odoo.exceptions import AccessError, RedirectWarning, UserError
|
||||
from odoo.tools import ustr
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class res_config_module_installation_mixin(object):
|
||||
def _install_modules(self, cr, uid, modules, context):
|
||||
"""Install the requested modules.
|
||||
return the next action to execute
|
||||
class ResConfigModuleInstallationMixin(object):
|
||||
@api.model
|
||||
def _install_modules(self, modules):
|
||||
""" Install the requested modules.
|
||||
|
||||
modules is a list of tuples
|
||||
(mod_name, browse_record | None)
|
||||
:param modules: a list of tuples (module_name, module_record)
|
||||
:return: the next action to execute
|
||||
"""
|
||||
ir_module = self.pool.get('ir.module.module')
|
||||
to_install_ids = []
|
||||
to_install_modules = self.env['ir.module.module']
|
||||
to_install_missing_names = []
|
||||
|
||||
for name, module in modules:
|
||||
if not module:
|
||||
to_install_missing_names.append(name)
|
||||
elif module.state == 'uninstalled':
|
||||
to_install_ids.append(module.id)
|
||||
to_install_modules += module
|
||||
result = None
|
||||
if to_install_ids:
|
||||
result = ir_module.button_immediate_install(cr, uid, to_install_ids, context=context)
|
||||
if to_install_modules:
|
||||
result = to_install_modules.button_immediate_install()
|
||||
#FIXME: if result is not none, the corresponding todo will be skipped because it was just marked done
|
||||
if to_install_missing_names:
|
||||
return {
|
||||
@@ -47,7 +43,8 @@ class res_config_module_installation_mixin(object):
|
||||
|
||||
return result
|
||||
|
||||
class res_config_configurable(osv.osv_memory):
|
||||
|
||||
class ResConfigConfigurable(models.TransientModel):
|
||||
''' Base classes for new-style configuration items
|
||||
|
||||
Configuration items should inherit from this class, implement
|
||||
@@ -56,34 +53,23 @@ class res_config_configurable(osv.osv_memory):
|
||||
'''
|
||||
_name = 'res.config'
|
||||
|
||||
def _next_action(self, cr, uid, context=None):
|
||||
Todos = self.pool['ir.actions.todo']
|
||||
def _next_action(self):
|
||||
Todos = self.env['ir.actions.todo']
|
||||
_logger.info('getting next %s', Todos)
|
||||
|
||||
active_todos = Todos.browse(cr, uid,
|
||||
Todos.search(cr, uid, ['&', ('type', '=', 'automatic'), ('state','=','open')]),
|
||||
context=context)
|
||||
active_todos = Todos.search(['&', ('type', '=', 'automatic'), ('state', '=', 'open')])
|
||||
user_groups = self.env.user.groups_id
|
||||
|
||||
user_groups = set(map(
|
||||
lambda g: g.id,
|
||||
self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))
|
||||
for todo in active_todos:
|
||||
if not todo.groups_id or (todo.groups_id & user_groups):
|
||||
return todo
|
||||
|
||||
valid_todos_for_user = [
|
||||
todo for todo in active_todos
|
||||
if not todo.groups_id or bool(user_groups.intersection((
|
||||
group.id for group in todo.groups_id)))
|
||||
]
|
||||
if valid_todos_for_user:
|
||||
return valid_todos_for_user[0]
|
||||
|
||||
return None
|
||||
|
||||
def _next(self, cr, uid, context=None):
|
||||
def _next(self):
|
||||
_logger.info('getting next operation')
|
||||
next = self._next_action(cr, uid, context=context)
|
||||
next = self._next_action()
|
||||
_logger.info('next action is %s', next)
|
||||
if next:
|
||||
return next.action_launch(context=context)
|
||||
return next.action_launch()
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
@@ -91,16 +77,19 @@ class res_config_configurable(osv.osv_memory):
|
||||
'url': '/web',
|
||||
}
|
||||
|
||||
def start(self, cr, uid, ids, context=None):
|
||||
return self.next(cr, uid, ids, context)
|
||||
@api.multi
|
||||
def start(self):
|
||||
return self.next()
|
||||
|
||||
def next(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def next(self):
|
||||
""" Returns the next todo action to execute (using the default
|
||||
sort order)
|
||||
"""
|
||||
return self._next(cr, uid, context=context)
|
||||
return self._next()
|
||||
|
||||
def execute(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def execute(self):
|
||||
""" Method called when the user clicks on the ``Next`` button.
|
||||
|
||||
Execute *must* be overloaded unless ``action_next`` is overloaded
|
||||
@@ -112,7 +101,8 @@ class res_config_configurable(osv.osv_memory):
|
||||
raise NotImplementedError(
|
||||
'Configuration items need to implement execute')
|
||||
|
||||
def cancel(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def cancel(self):
|
||||
""" Method called when the user click on the ``Skip`` button.
|
||||
|
||||
``cancel`` should be overloaded instead of ``action_skip``. As with
|
||||
@@ -126,7 +116,8 @@ class res_config_configurable(osv.osv_memory):
|
||||
"""
|
||||
pass
|
||||
|
||||
def action_next(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def action_next(self):
|
||||
""" Action handler for the ``next`` event.
|
||||
|
||||
Sets the status of the todo the event was sent from to
|
||||
@@ -134,11 +125,10 @@ class res_config_configurable(osv.osv_memory):
|
||||
an action dictionary -- executes the action provided by calling
|
||||
``next``.
|
||||
"""
|
||||
next = self.execute(cr, uid, ids, context=context)
|
||||
if next: return next
|
||||
return self.next(cr, uid, ids, context=context)
|
||||
return self.execute() or self.next()
|
||||
|
||||
def action_skip(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def action_skip(self):
|
||||
""" Action handler for the ``skip`` event.
|
||||
|
||||
Sets the status of the todo the event was sent from to
|
||||
@@ -146,11 +136,10 @@ class res_config_configurable(osv.osv_memory):
|
||||
an action dictionary -- executes the action provided by calling
|
||||
``next``.
|
||||
"""
|
||||
next = self.cancel(cr, uid, ids, context=context)
|
||||
if next: return next
|
||||
return self.next(cr, uid, ids, context=context)
|
||||
return self.cancel() or self.next()
|
||||
|
||||
def action_cancel(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def action_cancel(self):
|
||||
""" Action handler for the ``cancel`` event. That event isn't
|
||||
generated by the res.config.view.base inheritable view, the
|
||||
inherited view has to overload one of the buttons (or add one
|
||||
@@ -161,22 +150,20 @@ class res_config_configurable(osv.osv_memory):
|
||||
an action dictionary -- executes the action provided by calling
|
||||
``next``.
|
||||
"""
|
||||
next = self.cancel(cr, uid, ids, context=context)
|
||||
if next: return next
|
||||
return self.next(cr, uid, ids, context=context)
|
||||
return self.cancel() or self.next()
|
||||
|
||||
class res_config_installer(osv.osv_memory, res_config_module_installation_mixin):
|
||||
|
||||
class ResConfigInstaller(models.TransientModel, ResConfigModuleInstallationMixin):
|
||||
""" New-style configuration base specialized for addons selection
|
||||
and installation.
|
||||
|
||||
Basic usage
|
||||
-----------
|
||||
|
||||
Subclasses can simply define a number of _columns as
|
||||
fields.boolean objects. The keys (column names) should be the
|
||||
names of the addons to install (when selected). Upon action
|
||||
execution, selected boolean fields (and those only) will be
|
||||
interpreted as addons to install, and batch-installed.
|
||||
Subclasses can simply define a number of boolean fields. The field names
|
||||
should be the names of the addons to install (when selected). Upon action
|
||||
execution, selected boolean fields (and those only) will be interpreted as
|
||||
addons to install, and batch-installed.
|
||||
|
||||
Additional addons
|
||||
-----------------
|
||||
@@ -235,7 +222,7 @@ class res_config_installer(osv.osv_memory, res_config_module_installation_mixin)
|
||||
returns a ``set`` of addons to install (addons selected by the
|
||||
above methods minus addons from the *basic* set which are already
|
||||
installed) [#]_ so an overloader can simply manipulate the ``set``
|
||||
returned by ``res_config_installer.modules_to_install`` to add or
|
||||
returned by ``ResConfigInstaller.modules_to_install`` to add or
|
||||
remove addons.
|
||||
|
||||
Skipping the installer
|
||||
@@ -262,7 +249,7 @@ class res_config_installer(osv.osv_memory, res_config_module_installation_mixin)
|
||||
|
||||
_install_if = {}
|
||||
|
||||
def already_installed(self, cr, uid, context=None):
|
||||
def already_installed(self):
|
||||
""" For each module, check if it's already installed and if it
|
||||
is return its name
|
||||
|
||||
@@ -270,10 +257,9 @@ class res_config_installer(osv.osv_memory, res_config_module_installation_mixin)
|
||||
installer
|
||||
:rtype: [str]
|
||||
"""
|
||||
return map(attrgetter('name'),
|
||||
self._already_installed(cr, uid, context=context))
|
||||
return map(attrgetter('name'), self._already_installed())
|
||||
|
||||
def _already_installed(self, cr, uid, context=None):
|
||||
def _already_installed(self):
|
||||
""" For each module (boolean fields in a res.config.installer),
|
||||
check if it's already installed (either 'to install', 'to upgrade'
|
||||
or 'installed') and if it is return the module's record
|
||||
@@ -281,19 +267,12 @@ class res_config_installer(osv.osv_memory, res_config_module_installation_mixin)
|
||||
:returns: a list of all installed modules in this installer
|
||||
:rtype: recordset (collection of Record)
|
||||
"""
|
||||
modules = self.pool['ir.module.module']
|
||||
selectable = [name for name, field in self._fields.iteritems()
|
||||
if field.type == 'boolean']
|
||||
return self.env['ir.module.module'].search([('name', 'in', selectable),
|
||||
('state', 'in', ['to install', 'installed', 'to upgrade'])])
|
||||
|
||||
selectable = [field for field in self._columns
|
||||
if type(self._columns[field]) is fields.boolean]
|
||||
return modules.browse(
|
||||
cr, uid,
|
||||
modules.search(cr, uid,
|
||||
[('name','in',selectable),
|
||||
('state','in',['to install', 'installed', 'to upgrade'])],
|
||||
context=context),
|
||||
context=context)
|
||||
|
||||
def modules_to_install(self, cr, uid, ids, context=None):
|
||||
def modules_to_install(self):
|
||||
""" selects all modules to install:
|
||||
|
||||
* checked boolean fields
|
||||
@@ -312,47 +291,39 @@ class res_config_installer(osv.osv_memory, res_config_module_installation_mixin)
|
||||
* not already installed
|
||||
"""
|
||||
base = set(module_name
|
||||
for installer in self.read(cr, uid, ids, context=context)
|
||||
for installer in self.read()
|
||||
for module_name, to_install in installer.iteritems()
|
||||
if module_name != 'id'
|
||||
if type(self._columns.get(module_name)) is fields.boolean
|
||||
if to_install)
|
||||
if self._fields[module_name].type == 'boolean' and to_install)
|
||||
|
||||
hooks_results = set()
|
||||
for module in base:
|
||||
hook = getattr(self, '_if_%s'% module, None)
|
||||
if hook:
|
||||
hooks_results.update(hook(cr, uid, ids, context=None) or set())
|
||||
hooks_results.update(hook() or set())
|
||||
|
||||
additionals = set(
|
||||
module for requirements, consequences \
|
||||
in self._install_if.iteritems()
|
||||
if base.issuperset(requirements)
|
||||
for module in consequences)
|
||||
additionals = set(module
|
||||
for requirements, consequences in self._install_if.iteritems()
|
||||
if base.issuperset(requirements)
|
||||
for module in consequences)
|
||||
|
||||
return (base | hooks_results | additionals).difference(
|
||||
self.already_installed(cr, uid, context))
|
||||
return (base | hooks_results | additionals) - set(self.already_installed())
|
||||
|
||||
def default_get(self, cr, uid, fields_list, context=None):
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
''' If an addon is already installed, check it by default
|
||||
'''
|
||||
defaults = super(res_config_installer, self).default_get(
|
||||
cr, uid, fields_list, context=context)
|
||||
defaults = super(ResConfigInstaller, self).default_get(fields_list)
|
||||
return dict(defaults, **dict.fromkeys(self.already_installed(), True))
|
||||
|
||||
return dict(defaults,
|
||||
**dict.fromkeys(
|
||||
self.already_installed(cr, uid, context=context),
|
||||
True))
|
||||
|
||||
def fields_get(self, cr, uid, fields=None, context=None, write_access=True, attributes=None):
|
||||
@api.model
|
||||
def fields_get(self, fields=None, write_access=True, attributes=None):
|
||||
""" If an addon is already installed, set it to readonly as
|
||||
res.config.installer doesn't handle uninstallations of already
|
||||
installed addons
|
||||
"""
|
||||
fields = super(res_config_installer, self).fields_get(
|
||||
cr, uid, fields, context, write_access, attributes)
|
||||
fields = super(ResConfigInstaller, self).fields_get(fields, write_access=write_access, attributes=attributes)
|
||||
|
||||
for name in self.already_installed(cr, uid, context=context):
|
||||
for name in self.already_installed():
|
||||
if name not in fields:
|
||||
continue
|
||||
fields[name].update(
|
||||
@@ -361,34 +332,33 @@ class res_config_installer(osv.osv_memory, res_config_module_installation_mixin)
|
||||
_('\n\nThis addon is already installed on your system'))
|
||||
return fields
|
||||
|
||||
def execute(self, cr, uid, ids, context=None):
|
||||
to_install = list(self.modules_to_install(
|
||||
cr, uid, ids, context=context))
|
||||
@api.multi
|
||||
def execute(self):
|
||||
to_install = list(self.modules_to_install())
|
||||
_logger.info('Selecting addons %s to install', to_install)
|
||||
|
||||
ir_module = self.pool.get('ir.module.module')
|
||||
IrModule = self.env['ir.module.module']
|
||||
modules = []
|
||||
for name in to_install:
|
||||
mod_ids = ir_module.search(cr, uid, [('name', '=', name)])
|
||||
record = ir_module.browse(cr, uid, mod_ids[0], context) if mod_ids else None
|
||||
modules.append((name, record))
|
||||
module = IrModule.search([('name', '=', name)], limit=1)
|
||||
modules.append((name, module))
|
||||
|
||||
return self._install_modules(cr, uid, modules, context=context)
|
||||
return self._install_modules(modules)
|
||||
|
||||
class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
|
||||
|
||||
class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin):
|
||||
""" Base configuration wizard for application settings. It provides support for setting
|
||||
default values, assigning groups to employee users, and installing modules.
|
||||
To make such a 'settings' wizard, define a model like::
|
||||
|
||||
class my_config_wizard(osv.osv_memory):
|
||||
class MyConfigWizard(models.TransientModel):
|
||||
_name = 'my.settings'
|
||||
_inherit = 'res.config.settings'
|
||||
_columns = {
|
||||
'default_foo': fields.type(..., default_model='my.model'),
|
||||
'group_bar': fields.boolean(..., group='base.group_user', implied_group='my.group'),
|
||||
'module_baz': fields.boolean(...),
|
||||
'other_field': fields.type(...),
|
||||
}
|
||||
|
||||
default_foo = fields.type(..., default_model='my.model'),
|
||||
group_bar = fields.Boolean(..., group='base.group_user', implied_group='my.group'),
|
||||
module_baz = fields.Boolean(...),
|
||||
other_field = fields.type(...),
|
||||
|
||||
The method ``execute`` provides some support based on a naming convention:
|
||||
|
||||
@@ -424,17 +394,21 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
|
||||
"""
|
||||
_name = 'res.config.settings'
|
||||
|
||||
def copy(self, cr, uid, id, values, context=None):
|
||||
@api.multi
|
||||
def copy(self, values):
|
||||
raise UserError(_("Cannot duplicate configuration!"), "")
|
||||
|
||||
def fields_view_get(self, cr, user, view_id=None, view_type='form',
|
||||
context=None, toolbar=False, submenu=False):
|
||||
ret_val = super(res_config_settings, self).fields_view_get(
|
||||
cr, user, view_id=view_id, view_type=view_type, context=context,
|
||||
# TODO: Find replacement for 'onchange' attribute in view with dynamic
|
||||
# api.onchange(...) and migrate the onchange_module(...) accordingly.
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type='form',
|
||||
toolbar=False, submenu=False):
|
||||
ret_val = super(ResConfigSettings, self).fields_view_get(
|
||||
view_id=view_id, view_type=view_type,
|
||||
toolbar=toolbar, submenu=submenu)
|
||||
|
||||
can_install_modules = self.pool['ir.module.module'].check_access_rights(
|
||||
cr, user, 'write', raise_exception=False)
|
||||
can_install_modules = self.env['ir.module.module'].check_access_rights(
|
||||
'write', raise_exception=False)
|
||||
|
||||
doc = etree.XML(ret_val['arch'])
|
||||
|
||||
@@ -454,17 +428,16 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
|
||||
ret_val['arch'] = etree.tostring(doc)
|
||||
return ret_val
|
||||
|
||||
def onchange_module(self, cr, uid, ids, field_value, module_name, context=None):
|
||||
module_pool = self.pool.get('ir.module.module')
|
||||
module_ids = module_pool.search(
|
||||
cr, SUPERUSER_ID, [('name', '=', module_name.replace("module_", '')),
|
||||
('state','in', ['to install', 'installed', 'to upgrade'])],
|
||||
context=context)
|
||||
@api.multi
|
||||
def onchange_module(self, field_value, module_name):
|
||||
ModuleSudo = self.env['ir.module.module'].sudo()
|
||||
modules = ModuleSudo.search(
|
||||
[('name', '=', module_name.replace("module_", '')),
|
||||
('state', 'in', ['to install', 'installed', 'to upgrade'])])
|
||||
|
||||
if module_ids and not field_value:
|
||||
dep_ids = module_pool.downstream_dependencies(cr, SUPERUSER_ID, module_ids, context=context)
|
||||
dep_name = [x.shortdesc for x in module_pool.browse(
|
||||
cr, SUPERUSER_ID, dep_ids + module_ids, context=context)]
|
||||
if modules and not field_value:
|
||||
dep_ids = modules.downstream_dependencies()
|
||||
dep_name = (ModuleSudo.browse(dep_ids) + modules).mapped('shortdesc')
|
||||
message = '\n'.join(dep_name)
|
||||
return {
|
||||
'warning': {
|
||||
@@ -474,7 +447,8 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
|
||||
}
|
||||
return {}
|
||||
|
||||
def _get_classified_fields(self, cr, uid, context=None):
|
||||
@api.model
|
||||
def _get_classified_fields(self):
|
||||
""" return a dictionary with the fields classified by category::
|
||||
|
||||
{ 'default': [('default_foo', 'model', 'foo'), ...],
|
||||
@@ -483,38 +457,36 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
|
||||
'other': ['other_field', ...],
|
||||
}
|
||||
"""
|
||||
ir_model_data = self.pool['ir.model.data']
|
||||
ir_module = self.pool['ir.module.module']
|
||||
def ref(xml_id):
|
||||
mod, xml = xml_id.split('.', 1)
|
||||
return ir_model_data.get_object(cr, uid, mod, xml, context=context)
|
||||
IrModule = self.env['ir.module.module']
|
||||
ref = self.env.ref
|
||||
|
||||
defaults, groups, modules, others = [], [], [], []
|
||||
for name, field in self._columns.items():
|
||||
for name, field in self._fields.iteritems():
|
||||
if name.startswith('default_') and hasattr(field, 'default_model'):
|
||||
defaults.append((name, field.default_model, name[8:]))
|
||||
elif name.startswith('group_') and (isinstance(field, fields.boolean) or isinstance(field, fields.selection)) \
|
||||
and hasattr(field, 'implied_group'):
|
||||
field_groups = getattr(field, 'group', 'base.group_user').split(',')
|
||||
groups.append((name, map(ref, field_groups), ref(field.implied_group)))
|
||||
elif name.startswith('module_') and (isinstance(field, fields.boolean) or isinstance(field, fields.selection)):
|
||||
mod_ids = ir_module.search(cr, SUPERUSER_ID, [('name', '=', name[7:])])
|
||||
record = ir_module.browse(cr, SUPERUSER_ID, mod_ids[0], context) if mod_ids else None
|
||||
modules.append((name, record))
|
||||
elif name.startswith('group_') and field.type in ('boolean', 'selection') and \
|
||||
hasattr(field, 'implied_group'):
|
||||
field_group_xmlids = getattr(field, 'group', 'base.group_user').split(',')
|
||||
field_groups = reduce(add, map(ref, field_group_xmlids))
|
||||
groups.append((name, field_groups, ref(field.implied_group)))
|
||||
elif name.startswith('module_') and field.type in ('boolean', 'selection'):
|
||||
module = IrModule.sudo().search([('name', '=', name[7:])], limit=1)
|
||||
modules.append((name, module))
|
||||
else:
|
||||
others.append(name)
|
||||
|
||||
return {'default': defaults, 'group': groups, 'module': modules, 'other': others}
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
ir_values = self.pool['ir.values']
|
||||
classified = self._get_classified_fields(cr, uid, context)
|
||||
@api.model
|
||||
def default_get(self, fields):
|
||||
IrValues = self.env['ir.values']
|
||||
classified = self._get_classified_fields()
|
||||
|
||||
res = super(res_config_settings, self).default_get(cr, uid, fields, context)
|
||||
res = super(ResConfigSettings, self).default_get(fields)
|
||||
|
||||
# defaults: take the corresponding default value they set
|
||||
for name, model, field in classified['default']:
|
||||
value = ir_values.get_default(cr, uid, model, field)
|
||||
value = IrValues.get_default(model, field)
|
||||
if value is not None:
|
||||
res[name] = value
|
||||
|
||||
@@ -524,76 +496,66 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
|
||||
|
||||
# modules: which modules are installed/to install
|
||||
for name, module in classified['module']:
|
||||
res[name] = False if not module else module.state in ('installed', 'to install', 'to upgrade')
|
||||
res[name] = module.state in ('installed', 'to install', 'to upgrade')
|
||||
if self._fields[name].type == 'selection':
|
||||
res[name] = int(res[name])
|
||||
|
||||
# other fields: call all methods that start with 'get_default_'
|
||||
for method in dir(self):
|
||||
if method.startswith('get_default_'):
|
||||
res.update(getattr(self, method)(cr, uid, fields, context))
|
||||
res.update(getattr(self, method)(fields))
|
||||
|
||||
return res
|
||||
|
||||
def execute(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
@api.multi
|
||||
def execute(self):
|
||||
self.ensure_one()
|
||||
if not self.env.user._is_admin():
|
||||
raise AccessError(_("Only administrators can change the settings"))
|
||||
|
||||
context = dict(context, active_test=False)
|
||||
if not self.pool['res.users']._is_admin(cr, uid, [uid]):
|
||||
raise openerp.exceptions.AccessError(_("Only administrators can change the settings"))
|
||||
|
||||
ir_values = self.pool['ir.values']
|
||||
ir_module = self.pool['ir.module.module']
|
||||
res_groups = self.pool['res.groups']
|
||||
|
||||
classified = self._get_classified_fields(cr, uid, context=context)
|
||||
|
||||
config = self.browse(cr, uid, ids[0], context)
|
||||
self = self.with_context(active_test=False)
|
||||
classified = self._get_classified_fields()
|
||||
|
||||
# default values fields
|
||||
IrValues = self.env['ir.values'].sudo()
|
||||
for name, model, field in classified['default']:
|
||||
ir_values.set_default(cr, SUPERUSER_ID, model, field, config[name])
|
||||
IrValues.set_default(model, field, self[name])
|
||||
|
||||
# group fields: modify group / implied groups
|
||||
for name, groups, implied_group in classified['group']:
|
||||
gids = map(int, groups)
|
||||
if config[name]:
|
||||
res_groups.write(cr, uid, gids, {'implied_ids': [(4, implied_group.id)]}, context=context)
|
||||
if self[name]:
|
||||
groups.write({'implied_ids': [(4, implied_group.id)]})
|
||||
else:
|
||||
res_groups.write(cr, uid, gids, {'implied_ids': [(3, implied_group.id)]}, context=context)
|
||||
uids = set()
|
||||
for group in groups:
|
||||
uids.update(map(int, group.users))
|
||||
implied_group.write({'users': [(3, u) for u in uids]})
|
||||
groups.write({'implied_ids': [(3, implied_group.id)]})
|
||||
implied_group.write({'users': [(3, user.id) for user in groups.mapped('users')]})
|
||||
|
||||
# other fields: execute all methods that start with 'set_'
|
||||
for method in dir(self):
|
||||
if method.startswith('set_'):
|
||||
getattr(self, method)(cr, uid, ids, context)
|
||||
getattr(self, method)()
|
||||
|
||||
# module fields: install/uninstall the selected modules
|
||||
to_install = []
|
||||
to_uninstall_ids = []
|
||||
to_uninstall_modules = self.env['ir.module.module']
|
||||
lm = len('module_')
|
||||
for name, module in classified['module']:
|
||||
if config[name]:
|
||||
if self[name]:
|
||||
to_install.append((name[lm:], module))
|
||||
else:
|
||||
if module and module.state in ('installed', 'to upgrade'):
|
||||
to_uninstall_ids.append(module.id)
|
||||
to_uninstall_modules += module
|
||||
|
||||
if to_uninstall_ids:
|
||||
ir_module.button_immediate_uninstall(cr, uid, to_uninstall_ids, context=context)
|
||||
if to_uninstall_modules:
|
||||
to_uninstall_modules.button_immediate_uninstall()
|
||||
|
||||
action = self._install_modules(cr, uid, to_install, context=context)
|
||||
action = self._install_modules(to_install)
|
||||
if action:
|
||||
return action
|
||||
|
||||
# After the uninstall/install calls, the self.pool is no longer valid.
|
||||
# So we reach into the RegistryManager directly.
|
||||
res_config = openerp.modules.registry.RegistryManager.get(cr.dbname)['res.config']
|
||||
config = res_config.next(cr, uid, [], context=context) or {}
|
||||
ResConfig = registry(self._cr.dbname)['res.config']
|
||||
config = ResConfig.browse(self._cr, self._uid, [], self._context).next() or {}
|
||||
if config.get('type') not in ('ir.actions.act_window_close',):
|
||||
return config
|
||||
|
||||
@@ -603,32 +565,24 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
|
||||
'tag': 'reload',
|
||||
}
|
||||
|
||||
def cancel(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def cancel(self):
|
||||
# ignore the current record, and send the action to reopen the view
|
||||
act_window = self.pool['ir.actions.act_window']
|
||||
action_ids = act_window.search(cr, uid, [('res_model', '=', self._name)])
|
||||
if action_ids:
|
||||
return act_window.read(cr, uid, action_ids[0], [], context=context)
|
||||
actions = self.env['ir.actions.act_window'].search([('res_model', '=', self._name)], limit=1)
|
||||
if actions:
|
||||
return actions.read()[0]
|
||||
return {}
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
""" Override name_get method to return an appropriate configuration wizard
|
||||
name, and not the generated name."""
|
||||
action = self.env['ir.actions.act_window'].search([('res_model', '=', self._name)], limit=1)
|
||||
name = action.name or self._name
|
||||
return [(record.id, name) for record in self]
|
||||
|
||||
if not ids:
|
||||
return []
|
||||
# name_get may receive int id instead of an id list
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
|
||||
act_window = self.pool['ir.actions.act_window']
|
||||
action_ids = act_window.search(cr, uid, [('res_model', '=', self._name)], context=context)
|
||||
name = self._name
|
||||
if action_ids:
|
||||
name = act_window.read(cr, uid, action_ids[0], ['name'], context=context)['name']
|
||||
return [(record.id, name) for record in self.browse(cr, uid , ids, context=context)]
|
||||
|
||||
def get_option_path(self, cr, uid, menu_xml_id, context=None):
|
||||
@api.model
|
||||
def get_option_path(self, menu_xml_id):
|
||||
"""
|
||||
Fetch the path to a specified configuration view and the action id to access it.
|
||||
|
||||
@@ -638,13 +592,11 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
|
||||
- t[0]: string: full path to the menuitem (e.g.: "Settings/Configuration/Sales")
|
||||
- t[1]: int or long: id of the menuitem's action
|
||||
"""
|
||||
module_name, menu_xml_id = menu_xml_id.split('.')
|
||||
dummy, menu_id = self.pool['ir.model.data'].get_object_reference(cr, uid, module_name, menu_xml_id)
|
||||
ir_ui_menu = self.pool['ir.ui.menu'].browse(cr, uid, menu_id, context=context)
|
||||
|
||||
ir_ui_menu = self.env.ref(menu_xml_id)
|
||||
return (ir_ui_menu.complete_name, ir_ui_menu.action.id)
|
||||
|
||||
def get_option_name(self, cr, uid, full_field_name, context=None):
|
||||
@api.model
|
||||
def get_option_name(self, full_field_name):
|
||||
"""
|
||||
Fetch the human readable name of a specified configuration option.
|
||||
|
||||
@@ -653,10 +605,15 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
|
||||
:return string: human readable name of the field (e.g.: "Create leads from incoming mails")
|
||||
"""
|
||||
model_name, field_name = full_field_name.rsplit('.', 1)
|
||||
return self.env[model_name].fields_get([field_name])[field_name]['string']
|
||||
|
||||
return self.pool[model_name].fields_get(cr, uid, allfields=[field_name], context=context)[field_name]['string']
|
||||
|
||||
@api.v7
|
||||
def get_config_warning(self, cr, msg, context=None):
|
||||
recs = self.browse(cr, SUPERUSER_ID, [], context)
|
||||
return ResConfigSettings.get_config_warning(recs, msg)
|
||||
|
||||
@api.v8
|
||||
def get_config_warning(self, msg):
|
||||
"""
|
||||
Helper: return a Warning exception with the given message where the %(field:xxx)s
|
||||
and/or %(menu:yyy)s are replaced by the human readable field's name and/or menuitem's
|
||||
@@ -684,12 +641,11 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
|
||||
In order to do that, you can use the double percent (%%) to escape your new substitution, like so:
|
||||
Cannot find any account journal of %s type for this company.\n\nYou can create one in the %%(menu:account.menu_account_config)s.
|
||||
"""
|
||||
|
||||
res_config_obj = openerp.registry(cr.dbname)['res.config.settings']
|
||||
regex_path = r'%\(((?:menu|field):[a-z_\.]*)\)s'
|
||||
self = self.sudo()
|
||||
|
||||
# Process the message
|
||||
# 1/ find the menu and/or field references, put them in a list
|
||||
regex_path = r'%\(((?:menu|field):[a-z_\.]*)\)s'
|
||||
references = re.findall(regex_path, msg, flags=re.I)
|
||||
|
||||
# 2/ fetch the menu and/or field replacement values (full path and
|
||||
@@ -699,11 +655,11 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
|
||||
for item in references:
|
||||
ref_type, ref = item.split(':')
|
||||
if ref_type == 'menu':
|
||||
values[item], action_id = res_config_obj.get_option_path(cr, SUPERUSER_ID, ref, context=context)
|
||||
values[item], action_id = self.get_option_path(ref)
|
||||
elif ref_type == 'field':
|
||||
values[item] = res_config_obj.get_option_name(cr, SUPERUSER_ID, ref, context=context)
|
||||
values[item] = self.get_option_name(ref)
|
||||
|
||||
# 3/ substitute and return the result
|
||||
if (action_id):
|
||||
return exceptions.RedirectWarning(msg % values, action_id, _('Go to the configuration panel'))
|
||||
return exceptions.UserError(msg % values)
|
||||
return RedirectWarning(msg % values, action_id, _('Go to the configuration panel'))
|
||||
return UserError(msg % values)
|
||||
|
||||
@@ -2,100 +2,93 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
from openerp.osv import fields, osv
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
def location_name_search(self, cr, user, name='', args=None, operator='ilike',
|
||||
context=None, limit=100):
|
||||
if not args:
|
||||
@api.model
|
||||
def location_name_search(self, name='', args=None, operator='ilike', limit=100):
|
||||
if args is None:
|
||||
args = []
|
||||
|
||||
ids = []
|
||||
records = self.browse()
|
||||
if len(name) == 2:
|
||||
ids = self.search(cr, user, [('code', 'ilike', name)] + args,
|
||||
limit=limit, context=context)
|
||||
records = self.search([('code', 'ilike', name)] + args, limit=limit)
|
||||
|
||||
search_domain = [('name', operator, name)]
|
||||
if ids:
|
||||
search_domain.append(('id', 'not in', ids))
|
||||
ids.extend(self.search(cr, user, search_domain + args,
|
||||
limit=limit, context=context))
|
||||
if records:
|
||||
search_domain.append(('id', 'not in', records.ids))
|
||||
records += self.search(search_domain + args, limit=limit)
|
||||
|
||||
locations = self.name_get(cr, user, ids, context)
|
||||
return sorted(locations, key=lambda (id, name): ids.index(id))
|
||||
# the field 'display_name' calls name_get() to get its value
|
||||
return [(record.id, record.display_name) for record in records]
|
||||
|
||||
|
||||
class Country(osv.osv):
|
||||
class Country(models.Model):
|
||||
_name = 'res.country'
|
||||
_description = 'Country'
|
||||
_columns = {
|
||||
'name': fields.char('Country Name',
|
||||
help='The full name of the country.', required=True, translate=True),
|
||||
'code': fields.char('Country Code', size=2,
|
||||
help='The ISO country code in two chars.\n'
|
||||
'You can use this field for quick search.'),
|
||||
'address_format': fields.text('Address Format', help="""You can state here the usual format to use for the \
|
||||
_order = 'name'
|
||||
|
||||
name = fields.Char(string='Country Name', required=True, translate=True, help='The full name of the country.')
|
||||
code = fields.Char(string='Country Code', size=2,
|
||||
help='The ISO country code in two chars. \nYou can use this field for quick search.')
|
||||
address_format = fields.Text(help="""You can state here the usual format to use for the \
|
||||
addresses belonging to this country.\n\nYou can use the python-style string patern with all the field of the address \
|
||||
(for example, use '%(street)s' to display the field 'street') plus
|
||||
\n%(state_name)s: the name of the state
|
||||
\n%(state_code)s: the code of the state
|
||||
\n%(country_name)s: the name of the country
|
||||
\n%(country_code)s: the code of the country"""),
|
||||
'currency_id': fields.many2one('res.currency', 'Currency'),
|
||||
'image': fields.binary("Image", attachment=True),
|
||||
'phone_code': fields.integer('Country Calling Code'),
|
||||
'country_group_ids': fields.many2many('res.country.group', 'res_country_res_country_group_rel', 'res_country_id', 'res_country_group_id', string='Country Groups'),
|
||||
'state_ids': fields.one2many('res.country.state', 'country_id', string='States'),
|
||||
}
|
||||
\n%(country_code)s: the code of the country""",
|
||||
default='%(street)s\n%(street2)s\n%(city)s %(state_code)s %(zip)s\n%(country_name)s')
|
||||
currency_id = fields.Many2one('res.currency', string='Currency')
|
||||
image = fields.Binary(attachment=True)
|
||||
phone_code = fields.Integer(string='Country Calling Code')
|
||||
country_group_ids = fields.Many2many('res.country.group', 'res_country_res_country_group_rel',
|
||||
'res_country_id', 'res_country_group_id', string='Country Groups')
|
||||
state_ids = fields.One2many('res.country.state', 'country_id', string='States')
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)',
|
||||
'The name of the country must be unique !'),
|
||||
('code_uniq', 'unique (code)',
|
||||
'The code of the country must be unique !')
|
||||
]
|
||||
_defaults = {
|
||||
'address_format': "%(street)s\n%(street2)s\n%(city)s %(state_code)s %(zip)s\n%(country_name)s",
|
||||
}
|
||||
_order = 'name'
|
||||
|
||||
name_search = location_name_search
|
||||
|
||||
def create(self, cursor, user, vals, context=None):
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if vals.get('code'):
|
||||
vals['code'] = vals['code'].upper()
|
||||
return super(Country, self).create(cursor, user, vals, context=context)
|
||||
return super(Country, self).create(vals)
|
||||
|
||||
def write(self, cursor, user, ids, vals, context=None):
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
if vals.get('code'):
|
||||
vals['code'] = vals['code'].upper()
|
||||
return super(Country, self).write(cursor, user, ids, vals, context=context)
|
||||
return super(Country, self).write(vals)
|
||||
|
||||
def get_address_fields(self, cr, uid, ids, context=None):
|
||||
res = {}
|
||||
for country in self.browse(cr, uid, ids, context=context):
|
||||
res[country.id] = re.findall('\((.+?)\)', country.address_format)
|
||||
return res
|
||||
@api.multi
|
||||
def get_address_fields(self):
|
||||
return {'country_id': re.findall(r'\((.+?)\)', country.address_format) for country in self}
|
||||
|
||||
|
||||
class CountryGroup(osv.osv):
|
||||
class CountryGroup(models.Model):
|
||||
_description = "Country Group"
|
||||
_name = 'res.country.group'
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True),
|
||||
'country_ids': fields.many2many('res.country', 'res_country_res_country_group_rel', 'res_country_group_id', 'res_country_id', string='Countries'),
|
||||
}
|
||||
|
||||
name = fields.Char(required=True)
|
||||
country_ids = fields.Many2many('res.country', 'res_country_res_country_group_rel',
|
||||
'res_country_group_id', 'res_country_id', string='Countries')
|
||||
|
||||
|
||||
class CountryState(osv.osv):
|
||||
class CountryState(models.Model):
|
||||
_description = "Country state"
|
||||
_name = 'res.country.state'
|
||||
_columns = {
|
||||
'country_id': fields.many2one('res.country', 'Country', required=True),
|
||||
'name': fields.char('State Name', required=True,
|
||||
help='Administrative divisions of a country. E.g. Fed. State, Departement, Canton'),
|
||||
'code': fields.char('State Code', size=3,
|
||||
help='The state code in max. three chars.', required=True),
|
||||
}
|
||||
_order = 'code'
|
||||
|
||||
country_id = fields.Many2one('res.country', string='Country', required=True)
|
||||
name = fields.Char(string='State Name', required=True,
|
||||
help='Administrative divisions of a country. E.g. Fed. State, Departement, Canton')
|
||||
code = fields.Char(string='State Code', size=3, help='The state code in max. three chars.', required=True)
|
||||
|
||||
name_search = location_name_search
|
||||
|
||||
@@ -1,134 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import time
|
||||
import math
|
||||
|
||||
from openerp import api, fields as fields2
|
||||
from openerp import tools
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools import float_round, float_is_zero, float_compare
|
||||
import json
|
||||
from odoo import api, fields, models, tools, _
|
||||
|
||||
CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?')
|
||||
|
||||
class res_currency(osv.osv):
|
||||
|
||||
def _get_current_rate(self, cr, uid, ids, name, arg, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
res = {}
|
||||
|
||||
date = context.get('date') or fields2.Datetime.now()
|
||||
company_id = context.get('company_id') or self.pool['res.users']._get_company(cr, uid, context=context)
|
||||
for id in ids:
|
||||
cr.execute("""SELECT rate FROM res_currency_rate
|
||||
WHERE currency_id = %s
|
||||
AND name <= %s
|
||||
AND (company_id is null
|
||||
OR company_id = %s)
|
||||
ORDER BY company_id, name desc LIMIT 1""",
|
||||
(id, date, company_id))
|
||||
if cr.rowcount:
|
||||
res[id] = cr.fetchone()[0]
|
||||
else:
|
||||
res[id] = 1
|
||||
return res
|
||||
|
||||
def _decimal_places(self, cr, uid, ids, name, arg, context=None):
|
||||
res = {}
|
||||
for id in ids:
|
||||
rounding = self.browse(cr, uid, id, context=context).rounding
|
||||
rounding = (0 < rounding < 1) and rounding or 1
|
||||
res[id] = int(math.ceil(math.log10(1 / rounding)))
|
||||
return res
|
||||
|
||||
def _decimal_places(self, cr, uid, ids, name, arg, context=None):
|
||||
res = {}
|
||||
for currency in self.browse(cr, uid, ids, context=context):
|
||||
if currency.rounding > 0 and currency.rounding < 1:
|
||||
res[currency.id] = int(math.ceil(math.log10(1/currency.rounding)))
|
||||
else:
|
||||
res[currency.id] = 0
|
||||
return res
|
||||
|
||||
class Currency(models.Model):
|
||||
_name = "res.currency"
|
||||
_description = "Currency"
|
||||
_columns = {
|
||||
# Note: 'code' column was removed as of v6.0, the 'name' should now hold the ISO code.
|
||||
'name': fields.char('Currency', size=3, required=True, help="Currency Code (ISO 4217)"),
|
||||
'symbol': fields.char('Symbol', size=4, help="Currency sign, to be used when printing amounts."),
|
||||
'rate': fields.function(_get_current_rate, string='Current Rate', digits=(12,6),
|
||||
help='The rate of the currency to the currency of rate 1.'),
|
||||
'rate_ids': fields.one2many('res.currency.rate', 'currency_id', 'Rates'),
|
||||
'rounding': fields.float('Rounding Factor', digits=(12,6)),
|
||||
'decimal_places': fields.function(_decimal_places, string='Decimal Places', type='integer'),
|
||||
'active': fields.boolean('Active'),
|
||||
'position': fields.selection([('after','After Amount'),('before','Before Amount')], 'Symbol Position', help="Determines where the currency symbol should be placed after or before the amount.")
|
||||
}
|
||||
_defaults = {
|
||||
'active': 1,
|
||||
'position' : 'after',
|
||||
'rounding': 0.01,
|
||||
}
|
||||
_order = "name"
|
||||
|
||||
# Note: 'code' column was removed as of v6.0, the 'name' should now hold the ISO code.
|
||||
name = fields.Char(string='Currency', size=3, required=True, help="Currency Code (ISO 4217)")
|
||||
symbol = fields.Char(help="Currency sign, to be used when printing amounts.")
|
||||
rate = fields.Float(compute='_compute_current_rate', string='Current Rate', digits=(12, 6),
|
||||
help='The rate of the currency to the currency of rate 1.')
|
||||
rate_ids = fields.One2many('res.currency.rate', 'currency_id', string='Rates')
|
||||
rounding = fields.Float(string='Rounding Factor', digits=(12, 6), default=0.01)
|
||||
decimal_places = fields.Integer(compute='_compute_decimal_places')
|
||||
active = fields.Boolean(default=True)
|
||||
position = fields.Selection([('after', 'After Amount'), ('before', 'Before Amount')], default='after',
|
||||
string='Symbol Position', help="Determines where the currency symbol should be placed after or before the amount.")
|
||||
date = fields.Date(compute='_compute_date')
|
||||
|
||||
_sql_constraints = [
|
||||
('unique_name', 'unique (name)', 'The currency code must be unique!'),
|
||||
]
|
||||
_order = "name"
|
||||
|
||||
date = fields2.Date(compute='compute_date')
|
||||
@api.multi
|
||||
def _compute_current_rate(self):
|
||||
date = self._context.get('date') or fields.Datetime.now()
|
||||
company_id = self._context.get('company_id') or self.env.user._get_company().id
|
||||
# the subquery selects the last rate before 'date' for the given currency/company
|
||||
query = """SELECT c.id, (SELECT r.rate FROM res_currency_rate r
|
||||
WHERE r.currency_id = c.id AND r.name <= %s
|
||||
AND (r.company_id IS NULL OR r.company_id = %s)
|
||||
ORDER BY r.company_id, r.name DESC
|
||||
LIMIT 1) AS rate
|
||||
FROM res_currency c
|
||||
WHERE c.id IN %s"""
|
||||
self._cr.execute(query, (date, company_id, tuple(self.ids)))
|
||||
currency_rates = dict(self._cr.fetchall())
|
||||
for currency in self:
|
||||
currency.rate = currency_rates.get(currency.id, 1.0)
|
||||
|
||||
@api.one
|
||||
@api.multi
|
||||
@api.depends('rounding')
|
||||
def _compute_decimal_places(self):
|
||||
for currency in self:
|
||||
if 0 < currency.rounding < 1:
|
||||
currency.decimal_places = int(math.ceil(math.log10(1/currency.rounding)))
|
||||
else:
|
||||
currency.decimal_places = 0
|
||||
|
||||
@api.multi
|
||||
@api.depends('rate_ids.name')
|
||||
def compute_date(self):
|
||||
self.date = self.rate_ids[:1].name
|
||||
def _compute_date(self):
|
||||
for currency in self:
|
||||
currency.date = currency.rate_ids[:1].name
|
||||
|
||||
def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
|
||||
if not args:
|
||||
args = []
|
||||
results = super(res_currency,self)\
|
||||
.name_search(cr, user, name, args, operator=operator, context=context, limit=limit)
|
||||
@api.model
|
||||
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
||||
results = super(Currency, self).name_search(name, args, operator=operator, limit=limit)
|
||||
if not results:
|
||||
name_match = CURRENCY_DISPLAY_PATTERN.match(name)
|
||||
if name_match:
|
||||
results = super(res_currency,self)\
|
||||
.name_search(cr, user, name_match.group(1), args, operator=operator, context=context, limit=limit)
|
||||
results = super(Currency, self).name_search(name_match.group(1), args, operator=operator, limit=limit)
|
||||
return results
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
if not ids:
|
||||
return []
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
reads = self.read(cr, uid, ids, ['name','symbol'], context=context, load='_classic_write')
|
||||
return [(x['id'], tools.ustr(x['name'])) for x in reads]
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
return [(currency.id, tools.ustr(currency.name)) for currency in self]
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if not default:
|
||||
default = {}
|
||||
default.update(name=_("%s (copy)")
|
||||
% (self.browse(cr, uid, id, context=context).name))
|
||||
return super(res_currency, self).copy(
|
||||
cr, uid, id, default=default, context=context)
|
||||
@api.multi
|
||||
def copy(self, default=None):
|
||||
self.ensure_one()
|
||||
default = dict(default or {}, name=_("%s (copy)") % self.name)
|
||||
return super(Currency, self).copy(default=default)
|
||||
|
||||
@api.cr_uid_records
|
||||
@api.v7
|
||||
def round(self, cr, uid, currency, amount):
|
||||
"""Return ``amount`` rounded according to ``currency``'s
|
||||
rounding rules.
|
||||
return Currency.round(currency, amount)
|
||||
|
||||
@api.v8
|
||||
def round(self, amount):
|
||||
"""Return ``amount`` rounded according to ``self``'s rounding rules.
|
||||
|
||||
:param Record currency: currency for which we are rounding
|
||||
:param float amount: the amount to round
|
||||
:return: rounded float
|
||||
|
||||
With the new API, call it like: ``currency.round(amount)``.
|
||||
"""
|
||||
return float_round(amount, precision_rounding=currency.rounding)
|
||||
# TODO: Need to check why it calls round() from sale.py, _amount_all() with *No* ID after below commits,
|
||||
# https://github.com/odoo/odoo/commit/36ee1ad813204dcb91e9f5f20d746dff6f080ac2
|
||||
# https://github.com/odoo/odoo/commit/0b6058c585d7d9a57bd7581b8211f20fca3ec3f7
|
||||
# Removing self.ensure_one() will make few test cases to break of modules event_sale, sale_mrp and stock_dropshipping.
|
||||
#self.ensure_one()
|
||||
return tools.float_round(amount, precision_rounding=self.rounding)
|
||||
|
||||
@api.cr_uid_records
|
||||
@api.v7
|
||||
def compare_amounts(self, cr, uid, currency, amount1, amount2):
|
||||
return Currency.compare_amounts(currency, amount1, amount2)
|
||||
|
||||
@api.v8
|
||||
def compare_amounts(self, amount1, amount2):
|
||||
"""Compare ``amount1`` and ``amount2`` after rounding them according to the
|
||||
given currency's precision..
|
||||
An amount is considered lower/greater than another amount if their rounded
|
||||
@@ -140,7 +119,6 @@ class res_currency(osv.osv):
|
||||
they respectively round to 0.01 and 0.0, even though
|
||||
0.006-0.002 = 0.004 which would be considered zero at 2 digits precision.
|
||||
|
||||
:param Record currency: currency for which we are rounding
|
||||
:param float amount1: first amount to compare
|
||||
:param float amount2: second amount to compare
|
||||
:return: (resp.) -1, 0 or 1, if ``amount1`` is (resp.) lower than,
|
||||
@@ -149,45 +127,41 @@ class res_currency(osv.osv):
|
||||
|
||||
With the new API, call it like: ``currency.compare_amounts(amount1, amount2)``.
|
||||
"""
|
||||
return float_compare(amount1, amount2, precision_rounding=currency.rounding)
|
||||
return tools.float_compare(amount1, amount2, precision_rounding=self.rounding)
|
||||
|
||||
@api.cr_uid_records
|
||||
@api.v7
|
||||
def is_zero(self, cr, uid, currency, amount):
|
||||
"""Returns true if ``amount`` is small enough to be treated as
|
||||
zero according to ``currency``'s rounding rules.
|
||||
return Currency.is_zero(currency, amount)
|
||||
|
||||
Warning: ``is_zero(amount1-amount2)`` is not always equivalent to
|
||||
@api.v8
|
||||
def is_zero(self, amount):
|
||||
"""Returns true if ``amount`` is small enough to be treated as
|
||||
zero according to current currency's rounding rules.
|
||||
Warning: ``is_zero(amount1-amount2)`` is not always equivalent to
|
||||
``compare_amounts(amount1,amount2) == 0``, as the former will round after
|
||||
computing the difference, while the latter will round before, giving
|
||||
different results for e.g. 0.006 and 0.002 at 2 digits precision.
|
||||
|
||||
:param Record currency: currency for which we are rounding
|
||||
:param float amount: amount to compare with currency's zero
|
||||
|
||||
With the new API, call it like: ``currency.is_zero(amount)``.
|
||||
"""
|
||||
return float_is_zero(amount, precision_rounding=currency.rounding)
|
||||
return tools.float_is_zero(amount, precision_rounding=self.rounding)
|
||||
|
||||
def _get_conversion_rate(self, cr, uid, from_currency, to_currency, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
ctx = context.copy()
|
||||
from_currency = self.browse(cr, uid, from_currency.id, context=ctx)
|
||||
to_currency = self.browse(cr, uid, to_currency.id, context=ctx)
|
||||
return to_currency.rate/from_currency.rate
|
||||
@api.model
|
||||
def _get_conversion_rate(self, from_currency, to_currency):
|
||||
from_currency = from_currency.with_env(self.env)
|
||||
to_currency = to_currency.with_env(self.env)
|
||||
return to_currency.rate / from_currency.rate
|
||||
|
||||
def _compute(self, cr, uid, from_currency, to_currency, from_amount, round=True, context=None):
|
||||
if (to_currency.id == from_currency.id):
|
||||
if round:
|
||||
return self.round(cr, uid, to_currency, from_amount)
|
||||
else:
|
||||
return from_amount
|
||||
@api.model
|
||||
def _compute(self, from_currency, to_currency, from_amount, round=True):
|
||||
if (to_currency == from_currency):
|
||||
amount = to_currency.round(from_amount) if round else from_amount
|
||||
else:
|
||||
rate = self._get_conversion_rate(cr, uid, from_currency, to_currency, context=context)
|
||||
if round:
|
||||
return self.round(cr, uid, to_currency, from_amount * rate)
|
||||
else:
|
||||
return from_amount * rate
|
||||
rate = self._get_conversion_rate(from_currency, to_currency)
|
||||
amount = to_currency.round(from_amount * rate) if round else from_amount * rate
|
||||
return amount
|
||||
|
||||
@api.v7
|
||||
def compute(self, cr, uid, from_currency_id, to_currency_id, from_amount,
|
||||
@@ -215,23 +189,23 @@ class res_currency(osv.osv):
|
||||
# apply rounding
|
||||
return to_currency.round(to_amount) if round else to_amount
|
||||
|
||||
@api.v7
|
||||
def get_format_currencies_js_function(self, cr, uid, context=None):
|
||||
@api.model
|
||||
def get_format_currencies_js_function(self):
|
||||
""" Returns a string that can be used to instanciate a javascript function that formats numbers as currencies.
|
||||
That function expects the number as first parameter and the currency id as second parameter.
|
||||
If the currency id parameter is false or undefined, the company currency is used.
|
||||
"""
|
||||
company_currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
|
||||
company_currency = self.env.user.with_env(self.env).company_id.currency_id
|
||||
function = ""
|
||||
for row in self.search_read(cr, uid, domain=[], fields=['id', 'name', 'symbol', 'decimal_places', 'position'], context=context):
|
||||
symbol = row['symbol'] or row['name']
|
||||
format_number_str = "openerp.web.format_value(arguments[0], {type: 'float', digits: [69," + str(row['decimal_places']) + "]}, 0.00)"
|
||||
if row['position'] == 'after':
|
||||
return_str = "return " + format_number_str + " + '\\xA0' + " + json.dumps(symbol) + ";"
|
||||
for currency in self.search([]):
|
||||
symbol = currency.symbol or currency.name
|
||||
format_number_str = "openerp.web.format_value(arguments[0], {type: 'float', digits: [69,%s]}, 0.00)" % currency.decimal_places
|
||||
if currency.position == 'after':
|
||||
return_str = "return %s + '\\xA0' + %s;" % (format_number_str, json.dumps(symbol))
|
||||
else:
|
||||
return_str = "return " + json.dumps(symbol) + " + '\\xA0' + " + format_number_str + ";"
|
||||
function += "if (arguments[1] === " + str(row['id']) + ") { " + return_str + " }"
|
||||
if (row['id'] == company_currency_id):
|
||||
return_str = "return %s + '\\xA0' + %s;" % (json.dumps(symbol), format_number_str)
|
||||
function += "if (arguments[1] === %s) { %s }" % (currency.id, return_str)
|
||||
if (currency == company_currency):
|
||||
company_currency_format = return_str
|
||||
function = "if (arguments[1] === false || arguments[1] === undefined) {" + company_currency_format + " }" + function
|
||||
return function
|
||||
@@ -254,30 +228,25 @@ class res_currency(osv.osv):
|
||||
"""
|
||||
|
||||
|
||||
class res_currency_rate(osv.osv):
|
||||
class CurrencyRate(models.Model):
|
||||
_name = "res.currency.rate"
|
||||
_description = "Currency Rate"
|
||||
|
||||
_columns = {
|
||||
'name': fields.datetime('Date', required=True, select=True),
|
||||
'rate': fields.float('Rate', digits=(12, 6), help='The rate of the currency to the currency of rate 1'),
|
||||
'currency_id': fields.many2one('res.currency', 'Currency', readonly=True),
|
||||
'company_id': fields.many2one('res.company', 'Company')
|
||||
}
|
||||
_defaults = {
|
||||
'name': lambda *a: time.strftime('%Y-%m-%d 00:00:00'),
|
||||
}
|
||||
_order = "name desc"
|
||||
|
||||
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=80):
|
||||
name = fields.Datetime(string='Date', required=True, index=True, default=fields.Date.today() + ' 00:00:00')
|
||||
rate = fields.Float(digits=(12, 6), help='The rate of the currency to the currency of rate 1')
|
||||
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True)
|
||||
company_id = fields.Many2one('res.company', string='Company')
|
||||
|
||||
@api.model
|
||||
def name_search(self, name, args=None, operator='ilike', limit=80):
|
||||
if operator in ['=', '!=']:
|
||||
try:
|
||||
date_format = '%Y-%m-%d'
|
||||
if context.get('lang'):
|
||||
lang_obj = self.pool['res.lang']
|
||||
lang_ids = lang_obj.search(cr, user, [('code', '=', context['lang'])], context=context)
|
||||
if lang_ids:
|
||||
date_format = lang_obj.browse(cr, user, lang_ids[0], context=context).date_format
|
||||
if self._context.get('lang'):
|
||||
langs = self.env['res.lang'].search([('code', '=', self._context['lang'])])
|
||||
if langs:
|
||||
date_format = langs.date_format
|
||||
name = time.strftime('%Y-%m-%d', time.strptime(name, date_format))
|
||||
except ValueError:
|
||||
try:
|
||||
@@ -286,4 +255,4 @@ class res_currency_rate(osv.osv):
|
||||
return []
|
||||
name = ''
|
||||
operator = 'ilike'
|
||||
return super(res_currency_rate, self).name_search(cr, user, name, args=args, operator=operator, context=context, limit=limit)
|
||||
return super(CurrencyRate, self).name_search(name, args=args, operator=operator, limit=limit)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from reportlab.pdfbase import ttfonts
|
||||
from openerp.modules.registry import RegistryManager
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.report.render.rml2pdf import customfonts
|
||||
|
||||
import logging
|
||||
from reportlab.pdfbase import ttfonts
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo.modules.registry import RegistryManager
|
||||
from odoo.report.render.rml2pdf import customfonts
|
||||
|
||||
"""This module allows the mapping of some system-available TTF fonts to
|
||||
the reportlab engine.
|
||||
@@ -37,42 +37,42 @@ BUILTIN_ALTERNATIVES = [
|
||||
('Courier', 'bolditalic', ["FreeMonoBoldOblique", "DejaVuSansMono-BoldOblique"]),
|
||||
]
|
||||
|
||||
class res_font(osv.Model):
|
||||
|
||||
class ResFont(models.Model):
|
||||
_name = "res.font"
|
||||
_description = 'Fonts available'
|
||||
_order = 'family,name,id'
|
||||
_rec_name = 'family'
|
||||
|
||||
_columns = {
|
||||
'family': fields.char("Font family", required=True),
|
||||
'name': fields.char("Font Name", required=True),
|
||||
'path': fields.char("Path", required=True),
|
||||
'mode': fields.char("Mode", required=True),
|
||||
}
|
||||
family = fields.Char(string="Font family", required=True)
|
||||
name = fields.Char(string="Font Name", required=True)
|
||||
path = fields.Char(required=True)
|
||||
mode = fields.Char(required=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('name_font_uniq', 'unique(family, name)', 'You can not register two fonts with the same name'),
|
||||
]
|
||||
|
||||
def font_scan(self, cr, uid, lazy=False, context=None):
|
||||
@api.model
|
||||
def font_scan(self, lazy=False):
|
||||
"""Action of loading fonts
|
||||
In lazy mode will scan the filesystem only if there is no founts in the database and sync if no font in CustomTTFonts
|
||||
In not lazy mode will force scan filesystem and sync
|
||||
"""
|
||||
if lazy:
|
||||
# lazy loading, scan only if no fonts in db
|
||||
found_fonts_ids = self.search(cr, uid, [('path', '!=', '/dev/null')], context=context)
|
||||
if not found_fonts_ids:
|
||||
fonts = self.search([('path', '!=', '/dev/null')])
|
||||
if not fonts:
|
||||
# no scan yet or no font found on the system, scan the filesystem
|
||||
self._scan_disk(cr, uid, context=context)
|
||||
self._scan_disk()
|
||||
elif len(customfonts.CustomTTFonts) == 0:
|
||||
# CustomTTFonts list is empty
|
||||
self._sync(cr, uid, context=context)
|
||||
self._sync()
|
||||
else:
|
||||
self._scan_disk(cr, uid, context=context)
|
||||
self._scan_disk()
|
||||
return True
|
||||
|
||||
def _scan_disk(self, cr, uid, context=None):
|
||||
def _scan_disk(self):
|
||||
"""Scan the file system and register the result in database"""
|
||||
found_fonts = []
|
||||
for font_path in customfonts.list_all_sysfonts():
|
||||
@@ -84,29 +84,23 @@ class res_font(osv.Model):
|
||||
_logger.warning("Could not register Font %s: %s", font_path, ex)
|
||||
|
||||
for family, name, path, mode in found_fonts:
|
||||
if not self.search(cr, uid, [('family', '=', family), ('name', '=', name)], context=context):
|
||||
self.create(cr, uid, {
|
||||
'family': family, 'name': name,
|
||||
'path': path, 'mode': mode,
|
||||
}, context=context)
|
||||
if not self.search([('family', '=', family), ('name', '=', name)]):
|
||||
self.create({'family': family, 'name': name, 'path': path, 'mode': mode})
|
||||
|
||||
# remove fonts not present on the disk anymore
|
||||
existing_font_names = [name for (family, name, path, mode) in found_fonts]
|
||||
inexistant_fonts = self.search(cr, uid, [('name', 'not in', existing_font_names), ('path', '!=', '/dev/null')], context=context)
|
||||
if inexistant_fonts:
|
||||
self.unlink(cr, uid, inexistant_fonts, context=context)
|
||||
# Remove inexistent fonts
|
||||
self.search([('name', 'not in', existing_font_names), ('path', '!=', '/dev/null')]).unlink()
|
||||
|
||||
RegistryManager.signal_caches_change(cr.dbname)
|
||||
self._sync(cr, uid, context=context)
|
||||
return True
|
||||
RegistryManager.signal_caches_change(self._cr.dbname)
|
||||
return self._sync()
|
||||
|
||||
def _sync(self, cr, uid, context=None):
|
||||
def _sync(self):
|
||||
"""Set the customfonts.CustomTTFonts list to the content of the database"""
|
||||
customfonts.CustomTTFonts = []
|
||||
local_family_modes = set()
|
||||
local_font_paths = {}
|
||||
found_fonts_ids = self.search(cr, uid, [('path', '!=', '/dev/null')], context=context)
|
||||
for font in self.browse(cr, uid, found_fonts_ids, context=None):
|
||||
for font in self.search([('path', '!=', '/dev/null')]):
|
||||
local_family_modes.add((font.family, font.mode))
|
||||
local_font_paths[font.name] = font.path
|
||||
customfonts.CustomTTFonts.append((font.family, font.name, font.path, font.mode))
|
||||
@@ -134,4 +128,4 @@ class res_font(osv.Model):
|
||||
def clear_caches(self):
|
||||
"""Force worker to resync at next report loading by setting an empty font list"""
|
||||
customfonts.CustomTTFonts = []
|
||||
return super(res_font, self).clear_caches()
|
||||
return super(ResFont, self).clear_caches()
|
||||
|
||||
+147
-157
@@ -2,20 +2,21 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import locale
|
||||
from locale import localeconv
|
||||
import logging
|
||||
from operator import itemgetter
|
||||
import re
|
||||
from operator import itemgetter
|
||||
|
||||
from openerp import tools, SUPERUSER_ID
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
from openerp.tools.translate import _
|
||||
from openerp.exceptions import UserError
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.tools.safe_eval import safe_eval as eval
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class lang(osv.osv):
|
||||
DEFAULT_DATE_FORMAT = '%m/%d/%Y'
|
||||
DEFAULT_TIME_FORMAT = '%H:%M:%S'
|
||||
|
||||
|
||||
class Lang(models.Model):
|
||||
_name = "res.lang"
|
||||
_description = "Languages"
|
||||
_order = "active desc,name"
|
||||
@@ -23,38 +24,70 @@ class lang(osv.osv):
|
||||
_disallowed_datetime_patterns = tools.DATETIME_FORMATS_MAP.keys()
|
||||
_disallowed_datetime_patterns.remove('%y') # this one is in fact allowed, just not good practice
|
||||
|
||||
def install_lang(self, cr, uid, **args):
|
||||
"""
|
||||
name = fields.Char(required=True)
|
||||
code = fields.Char(string='Locale Code', required=True, help='This field is used to set/get locales for user')
|
||||
iso_code = fields.Char(string='ISO code', help='This ISO code is the name of po files to use for translations')
|
||||
translatable = fields.Boolean()
|
||||
active = fields.Boolean()
|
||||
direction = fields.Selection([('ltr', 'Left-to-Right'), ('rtl', 'Right-to-Left')], required=True, default='ltr')
|
||||
date_format = fields.Char(string='Date Format', required=True, default=DEFAULT_DATE_FORMAT)
|
||||
time_format = fields.Char(string='Time Format', required=True, default=DEFAULT_TIME_FORMAT)
|
||||
grouping = fields.Char(string='Separator Format', required=True, default='[]',
|
||||
help="The Separator Format should be like [,n] where 0 < n :starting from Unit digit. "
|
||||
"-1 will end the separation. e.g. [3,2,-1] will represent 106500 to be 1,06,500; "
|
||||
"[1,2,-1] will represent it to be 106,50,0;[3] will represent it as 106,500. "
|
||||
"Provided ',' as the thousand separator in each case.")
|
||||
decimal_point = fields.Char(string='Decimal Separator', required=True, default='.')
|
||||
thousands_sep = fields.Char(string='Thousands Separator', default=',')
|
||||
|
||||
This method is called from openerp/addons/base/base_data.xml to load
|
||||
some language and set it as the default for every partners. The
|
||||
language is set via tools.config by the RPC 'create' method on the
|
||||
'db' object. This is a fragile solution and something else should be
|
||||
found.
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique(name)', 'The name of the language must be unique !'),
|
||||
('code_uniq', 'unique(code)', 'The code of the language must be unique !'),
|
||||
]
|
||||
|
||||
"""
|
||||
# config['load_language'] is a comma-separated list or None
|
||||
lang = (tools.config.get('load_language') or 'en_US').split(',')[0]
|
||||
lang_ids = self.search(cr, uid, [('code','=', lang)])
|
||||
if not lang_ids:
|
||||
self.load_lang(cr, uid, lang)
|
||||
ir_values_obj = self.pool.get('ir.values')
|
||||
default_value = ir_values_obj.get(cr, uid, 'default', False, ['res.partner'])
|
||||
if not default_value:
|
||||
ir_values_obj.set(cr, uid, 'default', False, 'lang', ['res.partner'], lang)
|
||||
# set language of main company, created directly by db bootstrap SQL
|
||||
user = self.pool['res.users'].browse(cr, uid, uid)
|
||||
if not user.company_id.partner_id.lang:
|
||||
user.company_id.partner_id.write({'lang': lang})
|
||||
return True
|
||||
@api.constrains('active')
|
||||
def _check_active(self):
|
||||
# do not check during installation
|
||||
if self.env.registry.ready and not self.search_count([]):
|
||||
raise ValidationError(_('At least one language must be active.'))
|
||||
|
||||
def load_lang(self, cr, uid, lang, lang_name=None):
|
||||
@api.constrains('time_format', 'date_format')
|
||||
def _check_format(self):
|
||||
for lang in self:
|
||||
for pattern in lang._disallowed_datetime_patterns:
|
||||
if (lang.time_format and pattern in lang.time_format) or \
|
||||
(lang.date_format and pattern in lang.date_format):
|
||||
raise ValidationError(_('Invalid date/time format directive specified. '
|
||||
'Please refer to the list of allowed directives, '
|
||||
'displayed when you edit a language.'))
|
||||
|
||||
@api.constrains('grouping')
|
||||
def _check_grouping(self):
|
||||
warning = _('The Separator Format should be like [,n] where 0 < n :starting from Unit digit. '
|
||||
'-1 will end the separation. e.g. [3,2,-1] will represent 106500 to be 1,06,500;'
|
||||
'[1,2,-1] will represent it to be 106,50,0;[3] will represent it as 106,500. '
|
||||
'Provided as the thousand separator in each case.')
|
||||
for lang in self:
|
||||
try:
|
||||
if not all(isinstance(x, int) for x in eval(lang.grouping)):
|
||||
raise ValidationError(warning)
|
||||
except Exception:
|
||||
raise ValidationError(warning)
|
||||
|
||||
@api.model_cr
|
||||
def _register_hook(self):
|
||||
# check that there is at least one active language
|
||||
if not self.search_count([]):
|
||||
_logger.error("No language is active.")
|
||||
|
||||
@api.model
|
||||
def load_lang(self, lang, lang_name=None):
|
||||
""" Create the given language if necessary, and make it active. """
|
||||
# if the language exists, simply make it active
|
||||
lang_ids = self.search(cr, uid, [('code', '=', lang)], context={'active_test': False})
|
||||
if lang_ids:
|
||||
self.write(cr, uid, lang_ids, {'active': True})
|
||||
return lang_ids[0]
|
||||
language = self.with_context(active_test=False).search([('code', '=', lang)], limit=1)
|
||||
if language:
|
||||
language.write({'active': True})
|
||||
return language.id
|
||||
|
||||
# create the language with locale information
|
||||
fail = True
|
||||
@@ -108,164 +141,126 @@ class lang(osv.osv):
|
||||
'thousands_sep' : fix_xa0(str(conv['thousands_sep'])),
|
||||
'grouping' : str(conv.get('grouping', [])),
|
||||
}
|
||||
lang_id = False
|
||||
try:
|
||||
lang_id = self.create(cr, uid, lang_info)
|
||||
return self.create(lang_info).id
|
||||
finally:
|
||||
tools.resetlocale()
|
||||
return lang_id
|
||||
|
||||
def _register_hook(self, cr):
|
||||
# check that there is at least one active language
|
||||
if not self.search_count(cr, SUPERUSER_ID, []):
|
||||
_logger.error("No language is active.")
|
||||
@api.model
|
||||
def install_lang(self):
|
||||
"""
|
||||
|
||||
def _check_active(self, cr, uid, ids, context=None):
|
||||
# do not check during installation
|
||||
return not self.pool.ready or bool(self.search_count(cr, uid, []))
|
||||
This method is called from openerp/addons/base/base_data.xml to load
|
||||
some language and set it as the default for every partners. The
|
||||
language is set via tools.config by the RPC 'create' method on the
|
||||
'db' object. This is a fragile solution and something else should be
|
||||
found.
|
||||
|
||||
def _check_format(self, cr, uid, ids, context=None):
|
||||
for lang in self.browse(cr, uid, ids, context=context):
|
||||
for pattern in self._disallowed_datetime_patterns:
|
||||
if (lang.time_format and pattern in lang.time_format)\
|
||||
or (lang.date_format and pattern in lang.date_format):
|
||||
return False
|
||||
"""
|
||||
# config['load_language'] is a comma-separated list or None
|
||||
lang_code = (tools.config.get('load_language') or 'en_US').split(',')[0]
|
||||
lang = self.search([('code', '=', lang_code)])
|
||||
if not lang:
|
||||
self.load_lang(lang_code)
|
||||
IrValues = self.env['ir.values']
|
||||
default_value = IrValues.get('default', False, ['res.partner'])
|
||||
if not default_value:
|
||||
IrValues.set('default', False, 'lang', ['res.partner'], lang_code)
|
||||
# set language of main company, created directly by db bootstrap SQL
|
||||
partner = self.env.user.company_id.partner_id
|
||||
if not partner.lang:
|
||||
partner.write({'lang': lang_code})
|
||||
return True
|
||||
|
||||
def _check_grouping(self, cr, uid, ids, context=None):
|
||||
for lang in self.browse(cr, uid, ids, context=context):
|
||||
try:
|
||||
if not all(isinstance(x, int) for x in eval(lang.grouping)):
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
@api.model
|
||||
@api.returns('self', lambda value: value.id)
|
||||
@tools.ormcache('code')
|
||||
def _lang_get(self, code):
|
||||
return (self.search([('code', '=', code)]) or
|
||||
self.search([('code', '=', 'en_US')]) or
|
||||
self.search([], limit=1))
|
||||
|
||||
def _get_default_date_format(self, cursor, user, context=None):
|
||||
return '%m/%d/%Y'
|
||||
|
||||
def _get_default_time_format(self, cursor, user, context=None):
|
||||
return '%H:%M:%S'
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True),
|
||||
'code': fields.char('Locale Code', size=16, required=True, help='This field is used to set/get locales for user'),
|
||||
'iso_code': fields.char('ISO code', size=16, required=False, help='This ISO code is the name of po files to use for translations'),
|
||||
'translatable': fields.boolean('Translatable'),
|
||||
'active': fields.boolean('Active'),
|
||||
'direction': fields.selection([('ltr', 'Left-to-Right'), ('rtl', 'Right-to-Left')], 'Direction', required=True),
|
||||
'date_format':fields.char('Date Format', required=True),
|
||||
'time_format':fields.char('Time Format', required=True),
|
||||
'grouping':fields.char('Separator Format', required=True,help="The Separator Format should be like [,n] where 0 < n :starting from Unit digit.-1 will end the separation. e.g. [3,2,-1] will represent 106500 to be 1,06,500;[1,2,-1] will represent it to be 106,50,0;[3] will represent it as 106,500. Provided ',' as the thousand separator in each case."),
|
||||
'decimal_point':fields.char('Decimal Separator', required=True),
|
||||
'thousands_sep':fields.char('Thousands Separator'),
|
||||
}
|
||||
_defaults = {
|
||||
'active': False,
|
||||
'translatable': False,
|
||||
'direction': 'ltr',
|
||||
'date_format':_get_default_date_format,
|
||||
'time_format':_get_default_time_format,
|
||||
'grouping': '[]',
|
||||
'decimal_point': '.',
|
||||
'thousands_sep': ',',
|
||||
}
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', 'The name of the language must be unique !'),
|
||||
('code_uniq', 'unique (code)', 'The code of the language must be unique !'),
|
||||
]
|
||||
|
||||
_constraints = [
|
||||
(_check_active, "At least one language must be active.", ['active']),
|
||||
(_check_format, 'Invalid date/time format directive specified. Please refer to the list of allowed directives, displayed when you edit a language.', ['time_format', 'date_format']),
|
||||
(_check_grouping, "The Separator Format should be like [,n] where 0 < n :starting from Unit digit.-1 will end the separation. e.g. [3,2,-1] will represent 106500 to be 1,06,500;[1,2,-1] will represent it to be 106,50,0;[3] will represent it as 106,500. Provided ',' as the thousand separator in each case.", ['grouping'])
|
||||
]
|
||||
|
||||
@tools.ormcache('lang')
|
||||
def _lang_get(self, cr, uid, lang):
|
||||
lang_ids = self.search(cr, uid, [('code', '=', lang)]) or \
|
||||
self.search(cr, uid, [('code', '=', 'en_US')]) or \
|
||||
self.search(cr, uid, [])
|
||||
return lang_ids[0]
|
||||
|
||||
@tools.ormcache('lang', 'monetary')
|
||||
@api.v7
|
||||
def _lang_data_get(self, cr, uid, lang, monetary=False):
|
||||
if type(lang) in (str, unicode):
|
||||
if isinstance(lang, basestring):
|
||||
lang = self._lang_get(cr, uid, lang)
|
||||
conv = localeconv()
|
||||
lang_obj = self.browse(cr, uid, lang)
|
||||
thousands_sep = lang_obj.thousands_sep or conv[monetary and 'mon_thousands_sep' or 'thousands_sep']
|
||||
decimal_point = lang_obj.decimal_point
|
||||
grouping = lang_obj.grouping
|
||||
return self.browse(cr, uid, lang)._data_get(monetary)
|
||||
|
||||
@tools.ormcache('self.code', 'monetary')
|
||||
def _data_get(self, monetary=False):
|
||||
conv = locale.localeconv()
|
||||
thousands_sep = self.thousands_sep or conv[monetary and 'mon_thousands_sep' or 'thousands_sep']
|
||||
decimal_point = self.decimal_point
|
||||
grouping = self.grouping
|
||||
return grouping, thousands_sep, decimal_point
|
||||
|
||||
@api.model
|
||||
@tools.ormcache()
|
||||
def get_available(self, cr, uid, context=None):
|
||||
def get_available(self):
|
||||
""" Return the available languages as a list of (code, name) sorted by name. """
|
||||
langs = self.browse(cr, uid, self.search(cr, uid, [], context={'active_test': False}))
|
||||
langs = self.with_context(active_test=False).search([])
|
||||
return sorted([(lang.code, lang.name) for lang in langs], key=itemgetter(1))
|
||||
|
||||
@api.model
|
||||
@tools.ormcache()
|
||||
def get_installed(self, cr, uid, context=None):
|
||||
def get_installed(self):
|
||||
""" Return the installed languages as a list of (code, name) sorted by name. """
|
||||
langs = self.browse(cr, uid, self.search(cr, uid, []))
|
||||
langs = self.with_context(active_test=True).search([])
|
||||
return sorted([(lang.code, lang.name) for lang in langs], key=itemgetter(1))
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
self.clear_caches()
|
||||
return super(lang, self).create(cr, uid, vals, context=context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
|
||||
if 'code' in vals:
|
||||
for rec in self.browse(cr, uid, ids, context):
|
||||
if rec.code != vals['code']:
|
||||
raise UserError(_("Language code cannot be modified."))
|
||||
|
||||
if vals.get('active') == False:
|
||||
users = self.pool.get('res.users')
|
||||
for current_id in ids:
|
||||
current_language = self.browse(cr, uid, current_id, context=context)
|
||||
if users.search(cr, uid, [('lang', '=', current_language.code)], context=context):
|
||||
raise UserError(_("Cannot unactivate a language that is currently used by users."))
|
||||
return super(Lang, self).create(vals)
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
lang_codes = self.mapped('code')
|
||||
if 'code' in vals and any(code != vals['code'] for code in lang_codes):
|
||||
raise UserError(_("Language code cannot be modified."))
|
||||
if vals.get('active') == False and self.env['res.users'].search([('lang', 'in', lang_codes)]):
|
||||
raise UserError(_("Cannot unactivate a language that is currently used by users."))
|
||||
self.clear_caches()
|
||||
return super(lang, self).write(cr, uid, ids, vals, context)
|
||||
return super(Lang, self).write(vals)
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
languages = self.read(cr, uid, ids, ['code','active'], context=context)
|
||||
for language in languages:
|
||||
ctx_lang = context.get('lang')
|
||||
if language['code']=='en_US':
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
for language in self:
|
||||
if language.code == 'en_US':
|
||||
raise UserError(_("Base Language 'en_US' can not be deleted!"))
|
||||
if ctx_lang and (language['code']==ctx_lang):
|
||||
ctx_lang = self._context.get('lang')
|
||||
if ctx_lang and (language.code == ctx_lang):
|
||||
raise UserError(_("You cannot delete the language which is User's Preferred Language!"))
|
||||
if language['active']:
|
||||
if language.active:
|
||||
raise UserError(_("You cannot delete the language which is Active!\nPlease de-activate the language first."))
|
||||
trans_obj = self.pool.get('ir.translation')
|
||||
trans_ids = trans_obj.search(cr, uid, [('lang','=',language['code'])], context=context)
|
||||
trans_obj.unlink(cr, uid, trans_ids, context=context)
|
||||
self.env['ir.translation'].search([('lang', '=', language.code)]).unlink()
|
||||
self.clear_caches()
|
||||
return super(lang, self).unlink(cr, uid, ids, context=context)
|
||||
return super(Lang, self).unlink()
|
||||
|
||||
#
|
||||
# IDS: can be a list of IDS or a list of XML_IDS
|
||||
#
|
||||
@api.v7
|
||||
def format(self, cr, uid, ids, percent, value, grouping=False, monetary=False, context=None):
|
||||
# Refering the old code, `ids` is expected to have only one value(ID or XML_ID) inside list, hence used ids[0].
|
||||
lang_id = ids[0]
|
||||
if isinstance(lang_id, (str, unicode)):
|
||||
lang_id = self._lang_get(cr, uid, lang_id)
|
||||
lang = self.browse(cr, uid, lang_id, context=context)
|
||||
return Lang.format(lang, percent, value, grouping=grouping, monetary=monetary)
|
||||
|
||||
@api.v8
|
||||
def format(self, percent, value, grouping=False, monetary=False):
|
||||
""" Format() will return the language-specific output for float values"""
|
||||
self.ensure_one()
|
||||
if percent[0] != '%':
|
||||
raise ValueError("format() must be given exactly one %char format specifier")
|
||||
raise ValueError(_("format() must be given exactly one %char format specifier"))
|
||||
|
||||
formatted = percent % value
|
||||
|
||||
# floats and decimal ints need special action!
|
||||
if grouping:
|
||||
lang_grouping, thousands_sep, decimal_point = \
|
||||
self._lang_data_get(cr, uid, ids[0], monetary)
|
||||
lang_grouping, thousands_sep, decimal_point = self._data_get(monetary)
|
||||
eval_lang_grouping = eval(lang_grouping)
|
||||
|
||||
if percent[-1] in 'eEfFgG':
|
||||
@@ -279,11 +274,6 @@ class lang(osv.osv):
|
||||
|
||||
return formatted
|
||||
|
||||
# import re, operator
|
||||
# _percent_re = re.compile(r'%(?:\((?P<key>.*?)\))?'
|
||||
# r'(?P<modifiers>[-#0-9 +*.hlL]*?)[eEfFgGdiouxXcrs%]')
|
||||
|
||||
lang()
|
||||
|
||||
def split(l, counts):
|
||||
"""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -123,7 +123,7 @@
|
||||
<field name="street" placeholder="Street..." class="o_address_street"/>
|
||||
<field name="street2" placeholder="Street 2..." class="o_address_street"/>
|
||||
<field name="city" placeholder="City" class="o_address_city"/>
|
||||
<field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}' on_change="onchange_state(state_id)"/>
|
||||
<field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}'/>
|
||||
<field name="zip" placeholder="ZIP" class="o_address_zip"/>
|
||||
<field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/>
|
||||
</div>
|
||||
@@ -155,7 +155,6 @@
|
||||
<field name="is_company" invisible="1"/>
|
||||
<field name="company_type" widget="radio"
|
||||
class="oe_edit_only"
|
||||
on_change="on_change_company_type(company_type)"
|
||||
options="{'horizontal': true}"/>
|
||||
<h1>
|
||||
<field name="name" default_focus="1" placeholder="Name" attrs="{'required' : [('type', '=', 'contact')]}"/>
|
||||
@@ -164,8 +163,7 @@
|
||||
<field name="parent_id"
|
||||
placeholder="Company"
|
||||
domain="[('is_company', '=', True)]" context="{'default_is_company': True, 'default_supplier': supplier, 'default_customer': customer}"
|
||||
attrs="{'invisible': [('is_company','=', True),('parent_id', '=', False)]}"
|
||||
on_change="onchange_parent_id(parent_id, type)"/>
|
||||
attrs="{'invisible': [('is_company','=', True),('parent_id', '=', False)]}"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -184,7 +182,7 @@
|
||||
attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}"/>
|
||||
<field name="city" placeholder="City" class="o_address_city"
|
||||
attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}"/>
|
||||
<field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}' on_change="onchange_state(state_id)"
|
||||
<field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}'
|
||||
attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}" context="{'country_id': country_id, 'zip': zip}"/>
|
||||
<field name="zip" placeholder="ZIP" class="o_address_zip"
|
||||
attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}"/>
|
||||
@@ -279,7 +277,7 @@
|
||||
<field name="street" placeholder="Street..." class="o_address_street"/>
|
||||
<field name="street2" placeholder="Street 2..." class="o_address_street"/>
|
||||
<field name="city" placeholder="City" class="o_address_city"/>
|
||||
<field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}' on_change="onchange_state(state_id)" context="{'country_id': country_id, 'zip': zip}"/>
|
||||
<field name="state_id" class="o_address_state" placeholder="State" options='{"no_open": True}' context="{'country_id': country_id, 'zip': zip}"/>
|
||||
<field name="zip" placeholder="ZIP" class="o_address_zip"/>
|
||||
<field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/>
|
||||
</div>
|
||||
@@ -545,7 +543,7 @@
|
||||
<field eval="6" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Contact Tags">
|
||||
<field name="complete_name"/>
|
||||
<field name="display_name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp.osv import osv, fields
|
||||
from odoo import api, fields, models
|
||||
|
||||
def referencable_models(self, cr, uid, context=None):
|
||||
obj = self.pool.get('res.request.link')
|
||||
ids = obj.search(cr, uid, [], context=context)
|
||||
res = obj.read(cr, uid, ids, ['object', 'name'], context)
|
||||
return [(r['object'], r['name']) for r in res]
|
||||
|
||||
class res_request_link(osv.osv):
|
||||
@api.model
|
||||
def referenceable_models(self):
|
||||
return [(link.object, link.name) for link in self.env['res.request.link'].search([])]
|
||||
|
||||
|
||||
class ResRequestLink(models.Model):
|
||||
_name = 'res.request.link'
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True, translate=True),
|
||||
'object': fields.char('Object', required=True),
|
||||
'priority': fields.integer('Priority'),
|
||||
}
|
||||
_defaults = {
|
||||
'priority': 5,
|
||||
}
|
||||
_order = 'priority'
|
||||
|
||||
name = fields.Char(required=True, translate=True)
|
||||
object = fields.Char(required=True)
|
||||
priority = fields.Integer(default=5)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -141,7 +141,7 @@
|
||||
<field name="email" invisible="1"/>
|
||||
<label for="login" class="oe_edit_only" string="Email Address"/>
|
||||
<h2>
|
||||
<field name="login" on_change="on_change_login(login)"
|
||||
<field name="login"
|
||||
placeholder="email@yourcompany.com"/>
|
||||
</h2>
|
||||
<label for="company_id" class="oe_edit_only" groups="base.group_light_multi_company"/>
|
||||
@@ -182,7 +182,7 @@
|
||||
<h1><field name="name" required="1"/></h1>
|
||||
<field name="email" invisible="1"/>
|
||||
<label for="login" class="oe_edit_only" string="Email Address"/>
|
||||
<h2><field name="login" on_change="on_change_login(login)"/></h2>
|
||||
<h2><field name="login"/></h2>
|
||||
<group>
|
||||
<field name="partner_id" readonly="1" required="0" groups="base.group_no_one"
|
||||
attrs="{'invisible': [('id', '=', False)]}"/>
|
||||
|
||||
@@ -24,19 +24,10 @@ class TestACL(common.TransactionCase):
|
||||
|
||||
def _set_field_groups(self, model, field_name, groups):
|
||||
field = model._fields[field_name]
|
||||
column = model._columns[field_name]
|
||||
old_groups = field.groups
|
||||
old_prefetch = column._prefetch
|
||||
|
||||
field.groups = groups
|
||||
column.groups = groups
|
||||
column._prefetch = False
|
||||
|
||||
@self.addCleanup
|
||||
def cleanup():
|
||||
field.groups = old_groups
|
||||
column.groups = old_groups
|
||||
column._prefetch = old_prefetch
|
||||
self.patch(field, 'groups', groups)
|
||||
if field.column:
|
||||
self.patch(field.column, 'groups', groups)
|
||||
self.patch(field.column, '_prefetch', False)
|
||||
|
||||
def test_field_visibility_restriction(self):
|
||||
"""Check that model-level ``groups`` parameter effectively restricts access to that
|
||||
|
||||
+2
-4
@@ -1397,10 +1397,8 @@ class BaseModel(object):
|
||||
return res
|
||||
|
||||
def _rec_name_fallback(self, cr, uid, context=None):
|
||||
rec_name = self._rec_name
|
||||
if rec_name not in self._columns:
|
||||
rec_name = self._columns.keys()[0] if len(self._columns.keys()) > 0 else "id"
|
||||
return rec_name
|
||||
# if self._rec_name is set, it belongs to self._fields
|
||||
return self._rec_name or 'id'
|
||||
|
||||
#
|
||||
# Overload this method if you need a window title which depends on the context
|
||||
|
||||
+8
-10
@@ -154,17 +154,15 @@ class TransactionCase(BaseCase):
|
||||
self.cr.rollback()
|
||||
self.cr.close()
|
||||
|
||||
def patch(self, obj, key, val):
|
||||
""" Do the patch ``setattr(obj, key, val)``, and prepare cleanup. """
|
||||
old = getattr(obj, key)
|
||||
setattr(obj, key, val)
|
||||
self.addCleanup(setattr, obj, key, old)
|
||||
|
||||
def patch_order(self, model, order):
|
||||
m_e = self.env[model]
|
||||
m_r = self.registry(model)
|
||||
|
||||
old_order = m_e._order
|
||||
|
||||
@self.addCleanup
|
||||
def cleanup():
|
||||
m_r._order = type(m_e)._order = old_order
|
||||
|
||||
m_r._order = type(m_e)._order = order
|
||||
""" Patch the order of the given model (name), and prepare cleanup. """
|
||||
self.patch(type(self.env[model]), '_order', order)
|
||||
|
||||
|
||||
class SingleTransactionCase(BaseCase):
|
||||
|
||||
@@ -294,6 +294,14 @@ def reverse_enumerate(l):
|
||||
"""
|
||||
return izip(xrange(len(l)-1, -1, -1), reversed(l))
|
||||
|
||||
def partition(pred, elems):
|
||||
""" Return a pair equivalent to:
|
||||
``filter(pred, elems), filter(lambda x: not pred(x), elems)` """
|
||||
yes, nos = [], []
|
||||
for elem in elems:
|
||||
(yes if pred(elem) else nos).append(elem)
|
||||
return yes, nos
|
||||
|
||||
def topological_sort(elems):
|
||||
""" Return a list of elements sorted so that their dependencies are listed
|
||||
before them in the result.
|
||||
|
||||
Reference in New Issue
Block a user