[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:
Ravi Gohil
2016-02-23 12:06:25 +01:00
committed by Raphael Collet
parent 1490e430e6
commit 7eab8e26d3
30 changed files with 1787 additions and 2062 deletions
+1 -1
View File
@@ -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) % 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 <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} ${object.partner_id.child_ids[0].name}
% else : % else :
${object.partner_id.name} ${object.partner_id.name}
+2 -2
View File
@@ -452,8 +452,8 @@ class AccountInvoice(models.Model):
res_dom = [('res_id', '=', 'res.partner,%s' % partner_id)] res_dom = [('res_id', '=', 'res.partner,%s' % partner_id)]
rec_prop = prop.search(rec_dom + res_dom) or prop.search(rec_dom) 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) pay_prop = prop.search(pay_dom + res_dom) or prop.search(pay_dom)
rec_account = rec_prop.get_by_record(rec_prop) rec_account = rec_prop.get_by_record()
pay_account = pay_prop.get_by_record(pay_prop) pay_account = pay_prop.get_by_record()
if not rec_account and not pay_account: if not rec_account and not pay_account:
action = self.env.ref('account.action_account_config') 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.') msg = _('Cannot find a chart of accounts for this company, You should configure it. \nPlease go to Account Configuration.')
+1 -1
View File
@@ -6,7 +6,7 @@
<div class="row"> <div class="row">
<div class="col-xs-5 col-xs-offset-7"> <div class="col-xs-5 col-xs-offset-7">
<span t-field="o.name"/><br/> <span t-field="o.name"/><br/>
<span t-raw="o._address_display(None, None)[o.id].replace('\n\n', '\n').replace('\n', '&lt;br&gt;')"/> <span t-raw="o.contact_address.replace('\n\n', '\n').replace('\n', '&lt;br&gt;')"/>
<span t-field="o.vat"/> <span t-field="o.vat"/>
</div> </div>
<p> <p>
+1 -1
View File
@@ -42,7 +42,7 @@ class base_config_settings(osv.osv_memory):
'target': 'current', '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) partner_rule = self.pool['ir.model.data'].xmlid_to_object(cr, uid, 'base.res_partner_rule', context=context)
return { return {
'company_share_partner': not bool(partner_rule.active) 'company_share_partner': not bool(partner_rule.active)
+2 -2
View File
@@ -9,7 +9,7 @@ from werkzeug import url_encode
from openerp import SUPERUSER_ID from openerp import SUPERUSER_ID
from openerp import tools, api 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.addons.crm import crm_stage
from openerp.osv import fields, osv from openerp.osv import fields, osv
from openerp.tools.translate import _ from openerp.tools.translate import _
@@ -51,7 +51,7 @@ CRM_LEAD_FIELDS_TO_MERGE = ['name',
'partner_name'] 'partner_name']
class crm_lead(format_address, osv.osv): class crm_lead(FormatAddress, osv.osv):
""" CRM Lead Case """ """ CRM Lead Case """
_name = "crm.lead" _name = "crm.lead"
_description = "Lead/Opportunity" _description = "Lead/Opportunity"
+2 -2
View File
@@ -43,7 +43,7 @@ class crm_configuration(osv.TransientModel):
alias_id = alias_ids and alias_ids[0] or False alias_id = alias_ids and alias_ids[0] or False
return alias_id 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( return {'generate_sales_team_alias': self.pool['ir.values'].get_default(
cr, uid, 'sales.config.settings', 'generate_sales_team_alias')} 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 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) 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_name = False
alias_id = self._find_default_lead_alias_id(cr, uid, context=context) alias_id = self._find_default_lead_alias_id(cr, uid, context=context)
if alias_id: if alias_id:
+3 -4
View File
@@ -10,16 +10,15 @@ class calendar_config_settings(osv.TransientModel):
'server_uri': fields.char('URI for tuto') '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'] 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_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) 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') params = self.pool.get('ir.config_parameter')
cal_client_id = params.get_param(cr, uid, 'google_calendar_client_id',default='',context=context) 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) 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) server_uri= "%s/google_account/authentication" % params.get_param(cr, uid, 'web.base.url',default="http://yourcompany.odoo.com",context=context)
+4 -4
View File
@@ -11,8 +11,8 @@ class HrEquipmentConfigSettings(models.TransientModel):
equipment_alias_prefix = fields.Char('Use the following alias to report internal equipment issue') equipment_alias_prefix = fields.Char('Use the following alias to report internal equipment issue')
alias_domain = fields.Char("Alias Domain") alias_domain = fields.Char("Alias Domain")
@api.multi @api.model
def get_default_alias_equipment(self): def get_default_alias_equipment(self, fields):
alias_name = False alias_name = False
alias_id = self.env.ref('hr_equipment.mail_alias_equipment') alias_id = self.env.ref('hr_equipment.mail_alias_equipment')
if alias_id: if alias_id:
@@ -29,8 +29,8 @@ class HrEquipmentConfigSettings(models.TransientModel):
alias_id.write({'alias_name': record.equipment_alias_prefix}) alias_id.write({'alias_name': record.equipment_alias_prefix})
return True return True
@api.multi @api.model
def get_default_alias_domain(self): def get_default_alias_domain(self, fields):
alias_domain = self.env['ir.config_parameter'].get_param("mail.catchall.domain") alias_domain = self.env['ir.config_parameter'].get_param("mail.catchall.domain")
if not alias_domain: if not alias_domain:
domain = self.env["ir.config_parameter"].get_param("web.base.url") domain = self.env["ir.config_parameter"].get_param("web.base.url")
+4 -4
View File
@@ -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 " 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.") "the Odoo server, enter the domain name here.")
@api.multi @api.model
def get_default_fail_counter(self): def get_default_fail_counter(self, fields):
previous_date = datetime.datetime.now() - datetime.timedelta(days=30) previous_date = datetime.datetime.now() - datetime.timedelta(days=30)
return { return {
'fail_counter': self.env['mail.mail'].sudo().search_count([('date', '>=', previous_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)), ('state', '=', 'exception')]), 'fail_counter': self.env['mail.mail'].sudo().search_count([('date', '>=', previous_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)), ('state', '=', 'exception')]),
} }
@api.multi @api.model
def get_default_alias_domain(self): def get_default_alias_domain(self, fields):
alias_domain = self.env["ir.config_parameter"].get_param("mail.catchall.domain", default=None) alias_domain = self.env["ir.config_parameter"].get_param("mail.catchall.domain", default=None)
if alias_domain is None: if alias_domain is None:
domain = self.env["ir.config_parameter"].get_param("web.base.url") domain = self.env["ir.config_parameter"].get_param("web.base.url")
+2 -2
View File
@@ -15,7 +15,7 @@
<field name="lang">${object.partner_id.lang}</field> <field name="lang">${object.partner_id.lang}</field>
<field name="body_html"><![CDATA[ <field name="body_html"><![CDATA[
<p>Dear <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} ${object.partner_id.child_ids[0].name}
% else : % else :
${object.partner_id.name} ${object.partner_id.name}
@@ -51,7 +51,7 @@ from ${object.company_id.name}.
<field name="lang">${object.partner_id.lang}</field> <field name="lang">${object.partner_id.lang}</field>
<field name="body_html"><![CDATA[ <field name="body_html"><![CDATA[
<p>Dear <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} ${object.partner_id.child_ids[0].name}
% else : % else :
${object.partner_id.name} ${object.partner_id.name}
+1 -1
View File
@@ -21,7 +21,7 @@
% set access_url = is_online and access_action['url'] or '/report/pdf/sale.report_saleorder/' + str(object.id) % set access_url = is_online and access_action['url'] or '/report/pdf/sale.report_saleorder/' + str(object.id)
<p>Dear <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} ${object.partner_id.child_ids[0].name}
% else : % else :
${object.partner_id.name} ${object.partner_id.name}
-1
View File
@@ -11,5 +11,4 @@ import res_font
import res_company import res_company
import res_users import res_users
import res_request import res_request
import res_lang
import ir_property import ir_property
+78 -91
View File
@@ -2,16 +2,9 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
from operator import itemgetter from operator import itemgetter
import time
from openerp import models, api from odoo import api, fields, models, _
from openerp.osv import osv, orm, fields from odoo.exceptions import UserError
from openerp.tools.misc import attrgetter
from openerp.exceptions import UserError
# -------------------------------------------------------------------------
# Properties
# -------------------------------------------------------------------------
TYPE2FIELD = { TYPE2FIELD = {
'char': 'value_text', 'char': 'value_text',
@@ -26,44 +19,37 @@ TYPE2FIELD = {
'selection': 'value_text', 'selection': 'value_text',
} }
class ir_property(osv.osv):
class Property(models.Model):
_name = 'ir.property' _name = 'ir.property'
_columns = { name = fields.Char(index=True)
'name': fields.char('Name', select=1), 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), @api.multi
'company_id': fields.many2one('res.company', 'Company', select=1), def _update_values(self, values):
'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):
value = values.pop('value', None) value = values.pop('value', None)
if not value: if not value:
return values return values
@@ -71,8 +57,8 @@ class ir_property(osv.osv):
prop = None prop = None
type_ = values.get('type') type_ = values.get('type')
if not type_: if not type_:
if ids: if self:
prop = self.browse(cr, uid, ids[0]) prop = self[0]
type_ = prop.type type_ = prop.type
else: else:
type_ = self._defaults['type'] type_ = self._defaults['type']
@@ -82,7 +68,7 @@ class ir_property(osv.osv):
raise UserError(_('Invalid type')) raise UserError(_('Invalid type'))
if field == 'value_reference': if field == 'value_reference':
if isinstance(value, orm.BaseModel): if isinstance(value, models.BaseModel):
value = '%s,%d' % (value._name, value.id) value = '%s,%d' % (value._name, value.id)
elif isinstance(value, (int, long)): elif isinstance(value, (int, long)):
field_id = values.get('fields_id') field_id = values.get('fields_id')
@@ -91,68 +77,69 @@ class ir_property(osv.osv):
raise ValueError() raise ValueError()
field_id = prop.fields_id field_id = prop.fields_id
else: 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) value = '%s,%d' % (field_id.relation, value)
values[field] = value values[field] = value
return values return values
def write(self, cr, uid, ids, values, context=None): @api.multi
return super(ir_property, self).write(cr, uid, ids, self._update_values(cr, uid, ids, values), context=context) def write(self, values):
return super(Property, self).write(self._update_values(values))
def create(self, cr, uid, values, context=None): @api.model
return super(ir_property, self).create(cr, uid, self._update_values(cr, uid, None, values), context=context) 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): def get_by_record(self, cr, uid, record, context=None):
if record.type in ('char', 'text', 'selection'): return Property.get_by_record(self.browse(cr, uid, record.id, context=context))
return record.value_text
elif record.type == 'float': @api.v8
return record.value_float def get_by_record(self):
elif record.type == 'boolean': self.ensure_one()
return bool(record.value_integer) if self.type in ('char', 'text', 'selection'):
elif record.type == 'integer': return self.value_text
return record.value_integer elif self.type == 'float':
elif record.type == 'binary': return self.value_float
return record.value_binary elif self.type == 'boolean':
elif record.type == 'many2one': return bool(self.value_integer)
if not record.value_reference: 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 return False
model, resource_id = record.value_reference.split(',') model, resource_id = self.value_reference.split(',')
value = self.pool[model].browse(cr, uid, int(resource_id), context=context) return self.env[model].browse(int(resource_id)).exists()
return value.exists() elif self.type == 'datetime':
elif record.type == 'datetime': return self.value_datetime
return record.value_datetime elif self.type == 'date':
elif record.type == 'date': if not self.value_datetime:
if not record.value_datetime:
return False 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 return False
def get(self, cr, uid, name, model, res_id=False, context=None): @api.model
domain = self._get_domain(cr, uid, name, model, context=context) def get(self, name, model, res_id=False):
domain = self._get_domain(name, model)
if domain is not None: if domain is not None:
domain = [('res_id', '=', res_id)] + domain 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 #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) prop = self.search(domain, limit=1, order='company_id')
if not nid: return False if prop:
record = self.browse(cr, uid, nid[0], context=context) return prop.get_by_record()
return self.get_by_record(cr, uid, record, context=context)
return False return False
def _get_domain(self, cr, uid, prop_name, model, context=None): def _get_domain(self, prop_name, model):
context = context or {} self._cr.execute("SELECT id FROM ir_model_fields WHERE name=%s AND model=%s", (prop_name, model))
cr.execute('select id from ir_model_fields where name=%s and model=%s', (prop_name, model)) res = self._cr.fetchone()
res = cr.fetchone()
if not res: if not res:
return None return None
company_id = self._context.get('force_company') or self.env['res.company']._company_default_get(model, res[0]).id
cid = context.get('force_company') return [('fields_id', '=', res[0]), ('company_id', 'in', [company_id, False])]
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])]
@api.model @api.model
def get_multi(self, name, model, ids): 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 # for a given res_id, take the first property only
id = refs.pop(prop.res_id, None) id = refs.pop(prop.res_id, None)
if id is not 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 # set the default value to the ids that are not in result
default_value = result.pop(False, False) default_value = result.pop(False, False)
@@ -223,7 +210,7 @@ class ir_property(osv.osv):
value = clean(values[id]) value = clean(values[id])
if value == default_value: if value == default_value:
prop.unlink() prop.unlink()
elif value != clean(prop.get_by_record(prop)): elif value != clean(prop.get_by_record()):
prop.write({'value': value}) prop.write({'value': value})
# create new properties for records that do not have one yet # create new properties for records that do not have one yet
+10 -8
View File
@@ -2,15 +2,16 @@
import re import re
from openerp import api, fields, models, _ from odoo import api, fields, models
from openerp.osv import expression from odoo.osv import expression
from openerp.exceptions import UserError
def sanitize_account_number(acc_number): def sanitize_account_number(acc_number):
if acc_number: if acc_number:
return re.sub(r'\W+', '', acc_number).upper() return re.sub(r'\W+', '', acc_number).upper()
return False return False
class Bank(models.Model): class Bank(models.Model):
_description = 'Bank' _description = 'Bank'
_name = 'res.bank' _name = 'res.bank'
@@ -49,6 +50,7 @@ class Bank(models.Model):
banks = self.search(domain + args, limit=limit) banks = self.search(domain + args, limit=limit)
return banks.name_get() return banks.name_get()
class ResPartnerBank(models.Model): class ResPartnerBank(models.Model):
_name = 'res.partner.bank' _name = 'res.partner.bank'
_rec_name = 'acc_number' _rec_name = 'acc_number'
@@ -70,15 +72,15 @@ class ResPartnerBank(models.Model):
('unique_number', 'unique(sanitized_acc_number)', 'Account Number must be unique'), ('unique_number', 'unique(sanitized_acc_number)', 'Account Number must be unique'),
] ]
@api.one
@api.depends('acc_number') @api.depends('acc_number')
def _compute_sanitized_acc_number(self): 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.multi
@api.depends('acc_type')
def _compute_acc_type(self): def _compute_acc_type(self):
self.acc_type = 'bank' for bank in self:
bank.acc_type = 'bank'
@api.model @api.model
def search(self, args, offset=0, limit=None, order=None, count=False): def search(self, args, offset=0, limit=None, order=None, count=False):
+246 -241
View File
@@ -3,225 +3,16 @@
import os import os
import re import re
import openerp
from openerp import SUPERUSER_ID, tools, api from odoo import api, fields, models, tools, _
from openerp.osv import fields, osv from odoo.exceptions import ValidationError
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): class Company(models.Model):
_name = "res.company" _name = "res.company"
_description = 'Companies' _description = 'Companies'
_order = 'name' _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 = """
<header> <header>
<pageTemplate> <pageTemplate>
@@ -247,19 +38,8 @@ class res_company(osv.osv):
</header>""" </header>"""
_header2 = _header % (539, 772, "1.0cm", "28.3cm", "11.1cm", "28.3cm", "1.0cm 28.1cm 20.1cm 28.1cm") _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") _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_main = """
<header> <header>
<pageTemplate> <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_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') _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): def _get_header(self):
if rml_paper_format == 'us_letter': try:
return {'value': {'rml_header': self._header_letter}} header_file = tools.file_open(os.path.join(
return {'value': {'rml_header': self._header_a4}} '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): def _get_font(self):
return self.pool.get("res.font").font_scan(cr, uid, context=context) return self.env['res.font'].search([('family', '=', 'Helvetica'), ('mode', '=', 'all')], limit=1)
_defaults = { def _get_logo(self):
'currency_id': _get_euro, return open(os.path.join(tools.config['root_path'], 'addons', 'base', 'res', 'res_company_logo.png'), 'rb') .read().encode('base64')
'rml_paper_format': 'a4',
'rml_header':_get_header,
'rml_header2': _header2,
'rml_header3': _header3,
'logo':_get_logo,
'font':_get_font,
}
_constraints = [ @api.model
(osv.osv._check_recursion, 'Error! You can not create recursive companies.', ['parent_id']) 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.'))
+4 -4
View File
@@ -30,9 +30,9 @@
<field name="street" placeholder="Street..." class="o_address_street"/> <field name="street" placeholder="Street..." class="o_address_street"/>
<field name="street2" placeholder="Street 2..." 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="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="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> </div>
<field name="website" widget="url" placeholder="e.g. www.odoo.com"/> <field name="website" widget="url" placeholder="e.g. www.odoo.com"/>
</group> </group>
@@ -65,10 +65,10 @@
<group string="Configuration (RML)" groups="base.group_no_one"> <group string="Configuration (RML)" groups="base.group_no_one">
<group col="2" colspan="2"> <group col="2" colspan="2">
<button name="%(preview_rml_report)d" string="Preview RML Report" type="action" 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>
<group col="3" colspan="2"> <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"/> <button string="(reload fonts)" name="act_discover_fonts" type="object" class="oe_link"/>
</group> </group>
<group col="1" colspan="2"> <group col="1" colspan="2">
+183 -227
View File
@@ -2,41 +2,37 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
import json import json
import logging import logging
from operator import attrgetter
import re import re
import openerp from operator import attrgetter, add
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 lxml import etree 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__) _logger = logging.getLogger(__name__)
class res_config_module_installation_mixin(object): class ResConfigModuleInstallationMixin(object):
def _install_modules(self, cr, uid, modules, context): @api.model
"""Install the requested modules. def _install_modules(self, modules):
return the next action to execute """ Install the requested modules.
modules is a list of tuples :param modules: a list of tuples (module_name, module_record)
(mod_name, browse_record | None) :return: the next action to execute
""" """
ir_module = self.pool.get('ir.module.module') to_install_modules = self.env['ir.module.module']
to_install_ids = []
to_install_missing_names = [] to_install_missing_names = []
for name, module in modules: for name, module in modules:
if not module: if not module:
to_install_missing_names.append(name) to_install_missing_names.append(name)
elif module.state == 'uninstalled': elif module.state == 'uninstalled':
to_install_ids.append(module.id) to_install_modules += module
result = None result = None
if to_install_ids: if to_install_modules:
result = ir_module.button_immediate_install(cr, uid, to_install_ids, context=context) 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 #FIXME: if result is not none, the corresponding todo will be skipped because it was just marked done
if to_install_missing_names: if to_install_missing_names:
return { return {
@@ -47,7 +43,8 @@ class res_config_module_installation_mixin(object):
return result return result
class res_config_configurable(osv.osv_memory):
class ResConfigConfigurable(models.TransientModel):
''' Base classes for new-style configuration items ''' Base classes for new-style configuration items
Configuration items should inherit from this class, implement Configuration items should inherit from this class, implement
@@ -56,34 +53,23 @@ class res_config_configurable(osv.osv_memory):
''' '''
_name = 'res.config' _name = 'res.config'
def _next_action(self, cr, uid, context=None): def _next_action(self):
Todos = self.pool['ir.actions.todo'] Todos = self.env['ir.actions.todo']
_logger.info('getting next %s', Todos) _logger.info('getting next %s', Todos)
active_todos = Todos.browse(cr, uid, active_todos = Todos.search(['&', ('type', '=', 'automatic'), ('state', '=', 'open')])
Todos.search(cr, uid, ['&', ('type', '=', 'automatic'), ('state','=','open')]), user_groups = self.env.user.groups_id
context=context)
user_groups = set(map( for todo in active_todos:
lambda g: g.id, if not todo.groups_id or (todo.groups_id & user_groups):
self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id)) return todo
valid_todos_for_user = [ def _next(self):
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):
_logger.info('getting next operation') _logger.info('getting next operation')
next = self._next_action(cr, uid, context=context) next = self._next_action()
_logger.info('next action is %s', next) _logger.info('next action is %s', next)
if next: if next:
return next.action_launch(context=context) return next.action_launch()
return { return {
'type': 'ir.actions.act_url', 'type': 'ir.actions.act_url',
@@ -91,16 +77,19 @@ class res_config_configurable(osv.osv_memory):
'url': '/web', 'url': '/web',
} }
def start(self, cr, uid, ids, context=None): @api.multi
return self.next(cr, uid, ids, context) 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 """ Returns the next todo action to execute (using the default
sort order) 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. """ Method called when the user clicks on the ``Next`` button.
Execute *must* be overloaded unless ``action_next`` is overloaded Execute *must* be overloaded unless ``action_next`` is overloaded
@@ -112,7 +101,8 @@ class res_config_configurable(osv.osv_memory):
raise NotImplementedError( raise NotImplementedError(
'Configuration items need to implement execute') '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. """ Method called when the user click on the ``Skip`` button.
``cancel`` should be overloaded instead of ``action_skip``. As with ``cancel`` should be overloaded instead of ``action_skip``. As with
@@ -126,7 +116,8 @@ class res_config_configurable(osv.osv_memory):
""" """
pass pass
def action_next(self, cr, uid, ids, context=None): @api.multi
def action_next(self):
""" Action handler for the ``next`` event. """ Action handler for the ``next`` event.
Sets the status of the todo the event was sent from to 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 an action dictionary -- executes the action provided by calling
``next``. ``next``.
""" """
next = self.execute(cr, uid, ids, context=context) return self.execute() or self.next()
if next: return next
return self.next(cr, uid, ids, context=context)
def action_skip(self, cr, uid, ids, context=None): @api.multi
def action_skip(self):
""" Action handler for the ``skip`` event. """ Action handler for the ``skip`` event.
Sets the status of the todo the event was sent from to 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 an action dictionary -- executes the action provided by calling
``next``. ``next``.
""" """
next = self.cancel(cr, uid, ids, context=context) return self.cancel() or self.next()
if next: return next
return self.next(cr, uid, ids, context=context)
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 """ Action handler for the ``cancel`` event. That event isn't
generated by the res.config.view.base inheritable view, the generated by the res.config.view.base inheritable view, the
inherited view has to overload one of the buttons (or add one 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 an action dictionary -- executes the action provided by calling
``next``. ``next``.
""" """
next = self.cancel(cr, uid, ids, context=context) return self.cancel() or self.next()
if next: return next
return self.next(cr, uid, ids, context=context)
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 """ New-style configuration base specialized for addons selection
and installation. and installation.
Basic usage Basic usage
----------- -----------
Subclasses can simply define a number of _columns as Subclasses can simply define a number of boolean fields. The field names
fields.boolean objects. The keys (column names) should be the should be the names of the addons to install (when selected). Upon action
names of the addons to install (when selected). Upon action execution, selected boolean fields (and those only) will be interpreted as
execution, selected boolean fields (and those only) will be addons to install, and batch-installed.
interpreted as addons to install, and batch-installed.
Additional addons 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 returns a ``set`` of addons to install (addons selected by the
above methods minus addons from the *basic* set which are already above methods minus addons from the *basic* set which are already
installed) [#]_ so an overloader can simply manipulate the ``set`` 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. remove addons.
Skipping the installer Skipping the installer
@@ -262,7 +249,7 @@ class res_config_installer(osv.osv_memory, res_config_module_installation_mixin)
_install_if = {} _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 """ For each module, check if it's already installed and if it
is return its name is return its name
@@ -270,10 +257,9 @@ class res_config_installer(osv.osv_memory, res_config_module_installation_mixin)
installer installer
:rtype: [str] :rtype: [str]
""" """
return map(attrgetter('name'), return map(attrgetter('name'), self._already_installed())
self._already_installed(cr, uid, context=context))
def _already_installed(self, cr, uid, context=None): def _already_installed(self):
""" For each module (boolean fields in a res.config.installer), """ For each module (boolean fields in a res.config.installer),
check if it's already installed (either 'to install', 'to upgrade' check if it's already installed (either 'to install', 'to upgrade'
or 'installed') and if it is return the module's record 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 :returns: a list of all installed modules in this installer
:rtype: recordset (collection of Record) :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 def modules_to_install(self):
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):
""" selects all modules to install: """ selects all modules to install:
* checked boolean fields * checked boolean fields
@@ -312,47 +291,39 @@ class res_config_installer(osv.osv_memory, res_config_module_installation_mixin)
* not already installed * not already installed
""" """
base = set(module_name 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() for module_name, to_install in installer.iteritems()
if module_name != 'id' if self._fields[module_name].type == 'boolean' and to_install)
if type(self._columns.get(module_name)) is fields.boolean
if to_install)
hooks_results = set() hooks_results = set()
for module in base: for module in base:
hook = getattr(self, '_if_%s'% module, None) hook = getattr(self, '_if_%s'% module, None)
if hook: if hook:
hooks_results.update(hook(cr, uid, ids, context=None) or set()) hooks_results.update(hook() or set())
additionals = set( additionals = set(module
module for requirements, consequences \ for requirements, consequences in self._install_if.iteritems()
in self._install_if.iteritems() if base.issuperset(requirements)
if base.issuperset(requirements) for module in consequences)
for module in consequences)
return (base | hooks_results | additionals).difference( return (base | hooks_results | additionals) - set(self.already_installed())
self.already_installed(cr, uid, context))
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 ''' If an addon is already installed, check it by default
''' '''
defaults = super(res_config_installer, self).default_get( defaults = super(ResConfigInstaller, self).default_get(fields_list)
cr, uid, fields_list, context=context) return dict(defaults, **dict.fromkeys(self.already_installed(), True))
return dict(defaults, @api.model
**dict.fromkeys( def fields_get(self, fields=None, write_access=True, attributes=None):
self.already_installed(cr, uid, context=context),
True))
def fields_get(self, cr, uid, fields=None, context=None, write_access=True, attributes=None):
""" If an addon is already installed, set it to readonly as """ If an addon is already installed, set it to readonly as
res.config.installer doesn't handle uninstallations of already res.config.installer doesn't handle uninstallations of already
installed addons installed addons
""" """
fields = super(res_config_installer, self).fields_get( fields = super(ResConfigInstaller, self).fields_get(fields, write_access=write_access, attributes=attributes)
cr, uid, fields, context, write_access, attributes)
for name in self.already_installed(cr, uid, context=context): for name in self.already_installed():
if name not in fields: if name not in fields:
continue continue
fields[name].update( 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')) _('\n\nThis addon is already installed on your system'))
return fields return fields
def execute(self, cr, uid, ids, context=None): @api.multi
to_install = list(self.modules_to_install( def execute(self):
cr, uid, ids, context=context)) to_install = list(self.modules_to_install())
_logger.info('Selecting addons %s to install', 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 = [] modules = []
for name in to_install: for name in to_install:
mod_ids = ir_module.search(cr, uid, [('name', '=', name)]) module = IrModule.search([('name', '=', name)], limit=1)
record = ir_module.browse(cr, uid, mod_ids[0], context) if mod_ids else None modules.append((name, module))
modules.append((name, record))
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 """ Base configuration wizard for application settings. It provides support for setting
default values, assigning groups to employee users, and installing modules. default values, assigning groups to employee users, and installing modules.
To make such a 'settings' wizard, define a model like:: To make such a 'settings' wizard, define a model like::
class my_config_wizard(osv.osv_memory): class MyConfigWizard(models.TransientModel):
_name = 'my.settings' _name = 'my.settings'
_inherit = 'res.config.settings' _inherit = 'res.config.settings'
_columns = {
'default_foo': fields.type(..., default_model='my.model'), default_foo = fields.type(..., default_model='my.model'),
'group_bar': fields.boolean(..., group='base.group_user', implied_group='my.group'), group_bar = fields.Boolean(..., group='base.group_user', implied_group='my.group'),
'module_baz': fields.boolean(...), module_baz = fields.Boolean(...),
'other_field': fields.type(...), other_field = fields.type(...),
}
The method ``execute`` provides some support based on a naming convention: 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' _name = 'res.config.settings'
def copy(self, cr, uid, id, values, context=None): @api.multi
def copy(self, values):
raise UserError(_("Cannot duplicate configuration!"), "") raise UserError(_("Cannot duplicate configuration!"), "")
def fields_view_get(self, cr, user, view_id=None, view_type='form', # TODO: Find replacement for 'onchange' attribute in view with dynamic
context=None, toolbar=False, submenu=False): # api.onchange(...) and migrate the onchange_module(...) accordingly.
ret_val = super(res_config_settings, self).fields_view_get( @api.model
cr, user, view_id=view_id, view_type=view_type, context=context, 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) toolbar=toolbar, submenu=submenu)
can_install_modules = self.pool['ir.module.module'].check_access_rights( can_install_modules = self.env['ir.module.module'].check_access_rights(
cr, user, 'write', raise_exception=False) 'write', raise_exception=False)
doc = etree.XML(ret_val['arch']) 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) ret_val['arch'] = etree.tostring(doc)
return ret_val return ret_val
def onchange_module(self, cr, uid, ids, field_value, module_name, context=None): @api.multi
module_pool = self.pool.get('ir.module.module') def onchange_module(self, field_value, module_name):
module_ids = module_pool.search( ModuleSudo = self.env['ir.module.module'].sudo()
cr, SUPERUSER_ID, [('name', '=', module_name.replace("module_", '')), modules = ModuleSudo.search(
('state','in', ['to install', 'installed', 'to upgrade'])], [('name', '=', module_name.replace("module_", '')),
context=context) ('state', 'in', ['to install', 'installed', 'to upgrade'])])
if module_ids and not field_value: if modules and not field_value:
dep_ids = module_pool.downstream_dependencies(cr, SUPERUSER_ID, module_ids, context=context) dep_ids = modules.downstream_dependencies()
dep_name = [x.shortdesc for x in module_pool.browse( dep_name = (ModuleSudo.browse(dep_ids) + modules).mapped('shortdesc')
cr, SUPERUSER_ID, dep_ids + module_ids, context=context)]
message = '\n'.join(dep_name) message = '\n'.join(dep_name)
return { return {
'warning': { 'warning': {
@@ -474,7 +447,8 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
} }
return {} 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:: """ return a dictionary with the fields classified by category::
{ 'default': [('default_foo', 'model', 'foo'), ...], { 'default': [('default_foo', 'model', 'foo'), ...],
@@ -483,38 +457,36 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
'other': ['other_field', ...], 'other': ['other_field', ...],
} }
""" """
ir_model_data = self.pool['ir.model.data'] IrModule = self.env['ir.module.module']
ir_module = self.pool['ir.module.module'] ref = self.env.ref
def ref(xml_id):
mod, xml = xml_id.split('.', 1)
return ir_model_data.get_object(cr, uid, mod, xml, context=context)
defaults, groups, modules, others = [], [], [], [] 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'): if name.startswith('default_') and hasattr(field, 'default_model'):
defaults.append((name, field.default_model, name[8:])) defaults.append((name, field.default_model, name[8:]))
elif name.startswith('group_') and (isinstance(field, fields.boolean) or isinstance(field, fields.selection)) \ elif name.startswith('group_') and field.type in ('boolean', 'selection') and \
and hasattr(field, 'implied_group'): hasattr(field, 'implied_group'):
field_groups = getattr(field, 'group', 'base.group_user').split(',') field_group_xmlids = getattr(field, 'group', 'base.group_user').split(',')
groups.append((name, map(ref, field_groups), ref(field.implied_group))) field_groups = reduce(add, map(ref, field_group_xmlids))
elif name.startswith('module_') and (isinstance(field, fields.boolean) or isinstance(field, fields.selection)): groups.append((name, field_groups, ref(field.implied_group)))
mod_ids = ir_module.search(cr, SUPERUSER_ID, [('name', '=', name[7:])]) elif name.startswith('module_') and field.type in ('boolean', 'selection'):
record = ir_module.browse(cr, SUPERUSER_ID, mod_ids[0], context) if mod_ids else None module = IrModule.sudo().search([('name', '=', name[7:])], limit=1)
modules.append((name, record)) modules.append((name, module))
else: else:
others.append(name) others.append(name)
return {'default': defaults, 'group': groups, 'module': modules, 'other': others} return {'default': defaults, 'group': groups, 'module': modules, 'other': others}
def default_get(self, cr, uid, fields, context=None): @api.model
ir_values = self.pool['ir.values'] def default_get(self, fields):
classified = self._get_classified_fields(cr, uid, context) 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 # defaults: take the corresponding default value they set
for name, model, field in classified['default']: 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: if value is not None:
res[name] = value 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 # modules: which modules are installed/to install
for name, module in classified['module']: 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': if self._fields[name].type == 'selection':
res[name] = int(res[name]) res[name] = int(res[name])
# other fields: call all methods that start with 'get_default_' # other fields: call all methods that start with 'get_default_'
for method in dir(self): for method in dir(self):
if method.startswith('get_default_'): if method.startswith('get_default_'):
res.update(getattr(self, method)(cr, uid, fields, context)) res.update(getattr(self, method)(fields))
return res return res
def execute(self, cr, uid, ids, context=None): @api.multi
if context is None: def execute(self):
context = {} self.ensure_one()
if not self.env.user._is_admin():
raise AccessError(_("Only administrators can change the settings"))
context = dict(context, active_test=False) self = self.with_context(active_test=False)
if not self.pool['res.users']._is_admin(cr, uid, [uid]): classified = self._get_classified_fields()
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)
# default values fields # default values fields
IrValues = self.env['ir.values'].sudo()
for name, model, field in classified['default']: 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 # group fields: modify group / implied groups
for name, groups, implied_group in classified['group']: for name, groups, implied_group in classified['group']:
gids = map(int, groups) if self[name]:
if config[name]: groups.write({'implied_ids': [(4, implied_group.id)]})
res_groups.write(cr, uid, gids, {'implied_ids': [(4, implied_group.id)]}, context=context)
else: else:
res_groups.write(cr, uid, gids, {'implied_ids': [(3, implied_group.id)]}, context=context) groups.write({'implied_ids': [(3, implied_group.id)]})
uids = set() implied_group.write({'users': [(3, user.id) for user in groups.mapped('users')]})
for group in groups:
uids.update(map(int, group.users))
implied_group.write({'users': [(3, u) for u in uids]})
# other fields: execute all methods that start with 'set_' # other fields: execute all methods that start with 'set_'
for method in dir(self): for method in dir(self):
if method.startswith('set_'): if method.startswith('set_'):
getattr(self, method)(cr, uid, ids, context) getattr(self, method)()
# module fields: install/uninstall the selected modules # module fields: install/uninstall the selected modules
to_install = [] to_install = []
to_uninstall_ids = [] to_uninstall_modules = self.env['ir.module.module']
lm = len('module_') lm = len('module_')
for name, module in classified['module']: for name, module in classified['module']:
if config[name]: if self[name]:
to_install.append((name[lm:], module)) to_install.append((name[lm:], module))
else: else:
if module and module.state in ('installed', 'to upgrade'): if module and module.state in ('installed', 'to upgrade'):
to_uninstall_ids.append(module.id) to_uninstall_modules += module
if to_uninstall_ids: if to_uninstall_modules:
ir_module.button_immediate_uninstall(cr, uid, to_uninstall_ids, context=context) to_uninstall_modules.button_immediate_uninstall()
action = self._install_modules(cr, uid, to_install, context=context) action = self._install_modules(to_install)
if action: if action:
return action return action
# After the uninstall/install calls, the self.pool is no longer valid. # After the uninstall/install calls, the self.pool is no longer valid.
# So we reach into the RegistryManager directly. # So we reach into the RegistryManager directly.
res_config = openerp.modules.registry.RegistryManager.get(cr.dbname)['res.config'] ResConfig = registry(self._cr.dbname)['res.config']
config = res_config.next(cr, uid, [], context=context) or {} config = ResConfig.browse(self._cr, self._uid, [], self._context).next() or {}
if config.get('type') not in ('ir.actions.act_window_close',): if config.get('type') not in ('ir.actions.act_window_close',):
return config return config
@@ -603,32 +565,24 @@ class res_config_settings(osv.osv_memory, res_config_module_installation_mixin):
'tag': 'reload', '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 # ignore the current record, and send the action to reopen the view
act_window = self.pool['ir.actions.act_window'] actions = self.env['ir.actions.act_window'].search([('res_model', '=', self._name)], limit=1)
action_ids = act_window.search(cr, uid, [('res_model', '=', self._name)]) if actions:
if action_ids: return actions.read()[0]
return act_window.read(cr, uid, action_ids[0], [], context=context)
return {} 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 """ Override name_get method to return an appropriate configuration wizard
name, and not the generated name.""" 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: @api.model
return [] def get_option_path(self, menu_xml_id):
# 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):
""" """
Fetch the path to a specified configuration view and the action id to access it. 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[0]: string: full path to the menuitem (e.g.: "Settings/Configuration/Sales")
- t[1]: int or long: id of the menuitem's action - t[1]: int or long: id of the menuitem's action
""" """
module_name, menu_xml_id = menu_xml_id.split('.') ir_ui_menu = self.env.ref(menu_xml_id)
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)
return (ir_ui_menu.complete_name, ir_ui_menu.action.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. 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") :return string: human readable name of the field (e.g.: "Create leads from incoming mails")
""" """
model_name, field_name = full_field_name.rsplit('.', 1) 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): 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 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 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: 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. Cannot find any account journal of %s type for this company.\n\nYou can create one in the %%(menu:account.menu_account_config)s.
""" """
self = self.sudo()
res_config_obj = openerp.registry(cr.dbname)['res.config.settings']
regex_path = r'%\(((?:menu|field):[a-z_\.]*)\)s'
# Process the message # Process the message
# 1/ find the menu and/or field references, put them in a list # 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) references = re.findall(regex_path, msg, flags=re.I)
# 2/ fetch the menu and/or field replacement values (full path and # 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: for item in references:
ref_type, ref = item.split(':') ref_type, ref = item.split(':')
if ref_type == 'menu': 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': 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 # 3/ substitute and return the result
if (action_id): if (action_id):
return exceptions.RedirectWarning(msg % values, action_id, _('Go to the configuration panel')) return RedirectWarning(msg % values, action_id, _('Go to the configuration panel'))
return exceptions.UserError(msg % values) return UserError(msg % values)
+47 -54
View File
@@ -2,100 +2,93 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
import re 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', @api.model
context=None, limit=100): def location_name_search(self, name='', args=None, operator='ilike', limit=100):
if not args: if args is None:
args = [] args = []
ids = [] records = self.browse()
if len(name) == 2: if len(name) == 2:
ids = self.search(cr, user, [('code', 'ilike', name)] + args, records = self.search([('code', 'ilike', name)] + args, limit=limit)
limit=limit, context=context)
search_domain = [('name', operator, name)] search_domain = [('name', operator, name)]
if ids: if records:
search_domain.append(('id', 'not in', ids)) search_domain.append(('id', 'not in', records.ids))
ids.extend(self.search(cr, user, search_domain + args, records += self.search(search_domain + args, limit=limit)
limit=limit, context=context))
locations = self.name_get(cr, user, ids, context) # the field 'display_name' calls name_get() to get its value
return sorted(locations, key=lambda (id, name): ids.index(id)) return [(record.id, record.display_name) for record in records]
class Country(osv.osv): class Country(models.Model):
_name = 'res.country' _name = 'res.country'
_description = 'Country' _description = 'Country'
_columns = { _order = 'name'
'name': fields.char('Country Name',
help='The full name of the country.', required=True, translate=True), name = fields.Char(string='Country Name', required=True, translate=True, help='The full name of the country.')
'code': fields.char('Country Code', size=2, code = fields.Char(string='Country Code', size=2,
help='The ISO country code in two chars.\n' help='The ISO country code in two chars. \nYou can use this field for quick search.')
'You can use this field for quick search.'), address_format = fields.Text(help="""You can state here the usual format to use for the \
'address_format': fields.text('Address Format', 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 \ 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 (for example, use '%(street)s' to display the field 'street') plus
\n%(state_name)s: the name of the state \n%(state_name)s: the name of the state
\n%(state_code)s: the code of the state \n%(state_code)s: the code of the state
\n%(country_name)s: the name of the country \n%(country_name)s: the name of the country
\n%(country_code)s: the code of the country"""), \n%(country_code)s: the code of the country""",
'currency_id': fields.many2one('res.currency', 'Currency'), default='%(street)s\n%(street2)s\n%(city)s %(state_code)s %(zip)s\n%(country_name)s')
'image': fields.binary("Image", attachment=True), currency_id = fields.Many2one('res.currency', string='Currency')
'phone_code': fields.integer('Country Calling Code'), image = fields.Binary(attachment=True)
'country_group_ids': fields.many2many('res.country.group', 'res_country_res_country_group_rel', 'res_country_id', 'res_country_group_id', string='Country Groups'), phone_code = fields.Integer(string='Country Calling Code')
'state_ids': fields.one2many('res.country.state', 'country_id', string='States'), 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 = [ _sql_constraints = [
('name_uniq', 'unique (name)', ('name_uniq', 'unique (name)',
'The name of the country must be unique !'), 'The name of the country must be unique !'),
('code_uniq', 'unique (code)', ('code_uniq', 'unique (code)',
'The code of the country must be unique !') '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 name_search = location_name_search
def create(self, cursor, user, vals, context=None): @api.model
def create(self, vals):
if vals.get('code'): if vals.get('code'):
vals['code'] = vals['code'].upper() 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'): if vals.get('code'):
vals['code'] = vals['code'].upper() 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): @api.multi
res = {} def get_address_fields(self):
for country in self.browse(cr, uid, ids, context=context): return {'country_id': re.findall(r'\((.+?)\)', country.address_format) for country in self}
res[country.id] = re.findall('\((.+?)\)', country.address_format)
return res
class CountryGroup(osv.osv): class CountryGroup(models.Model):
_description = "Country Group" _description = "Country Group"
_name = 'res.country.group' _name = 'res.country.group'
_columns = {
'name': fields.char('Name', required=True), 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'), 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" _description = "Country state"
_name = 'res.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' _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 name_search = location_name_search
+121 -152
View File
@@ -1,134 +1,113 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
import math
import re import re
import time import time
import math
from openerp import api, fields as fields2 from odoo import api, fields, models, tools, _
from openerp import tools
from openerp.osv import fields, osv
from openerp.tools import float_round, float_is_zero, float_compare
import json
CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?') 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" _name = "res.currency"
_description = "Currency" _description = "Currency"
_columns = { _order = "name"
# 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)"), # Note: 'code' column was removed as of v6.0, the 'name' should now hold the ISO code.
'symbol': fields.char('Symbol', size=4, help="Currency sign, to be used when printing amounts."), name = fields.Char(string='Currency', size=3, required=True, help="Currency Code (ISO 4217)")
'rate': fields.function(_get_current_rate, string='Current Rate', digits=(12,6), symbol = fields.Char(help="Currency sign, to be used when printing amounts.")
help='The rate of the currency to the currency of rate 1.'), rate = fields.Float(compute='_compute_current_rate', string='Current Rate', digits=(12, 6),
'rate_ids': fields.one2many('res.currency.rate', 'currency_id', 'Rates'), help='The rate of the currency to the currency of rate 1.')
'rounding': fields.float('Rounding Factor', digits=(12,6)), rate_ids = fields.One2many('res.currency.rate', 'currency_id', string='Rates')
'decimal_places': fields.function(_decimal_places, string='Decimal Places', type='integer'), rounding = fields.Float(string='Rounding Factor', digits=(12, 6), default=0.01)
'active': fields.boolean('Active'), decimal_places = fields.Integer(compute='_compute_decimal_places')
'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.") active = fields.Boolean(default=True)
} position = fields.Selection([('after', 'After Amount'), ('before', 'Before Amount')], default='after',
_defaults = { string='Symbol Position', help="Determines where the currency symbol should be placed after or before the amount.")
'active': 1, date = fields.Date(compute='_compute_date')
'position' : 'after',
'rounding': 0.01,
}
_sql_constraints = [ _sql_constraints = [
('unique_name', 'unique (name)', 'The currency code must be unique!'), ('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') @api.depends('rate_ids.name')
def compute_date(self): def _compute_date(self):
self.date = self.rate_ids[:1].name 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): @api.model
if not args: def name_search(self, name='', args=None, operator='ilike', limit=100):
args = [] results = super(Currency, self).name_search(name, args, operator=operator, limit=limit)
results = super(res_currency,self)\
.name_search(cr, user, name, args, operator=operator, context=context, limit=limit)
if not results: if not results:
name_match = CURRENCY_DISPLAY_PATTERN.match(name) name_match = CURRENCY_DISPLAY_PATTERN.match(name)
if name_match: if name_match:
results = super(res_currency,self)\ results = super(Currency, self).name_search(name_match.group(1), args, operator=operator, limit=limit)
.name_search(cr, user, name_match.group(1), args, operator=operator, context=context, limit=limit)
return results return results
def name_get(self, cr, uid, ids, context=None): @api.multi
if not ids: def name_get(self):
return [] return [(currency.id, tools.ustr(currency.name)) for currency in self]
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]
def copy(self, cr, uid, id, default=None, context=None): @api.multi
if context is None: def copy(self, default=None):
context = {} self.ensure_one()
if not default: default = dict(default or {}, name=_("%s (copy)") % self.name)
default = {} return super(Currency, self).copy(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.cr_uid_records @api.v7
def round(self, cr, uid, currency, amount): def round(self, cr, uid, currency, amount):
"""Return ``amount`` rounded according to ``currency``'s return Currency.round(currency, amount)
rounding rules.
@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 :param float amount: the amount to round
:return: rounded float :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): 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 """Compare ``amount1`` and ``amount2`` after rounding them according to the
given currency's precision.. given currency's precision..
An amount is considered lower/greater than another amount if their rounded 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 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. 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 amount1: first amount to compare
:param float amount2: second amount to compare :param float amount2: second amount to compare
:return: (resp.) -1, 0 or 1, if ``amount1`` is (resp.) lower than, :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)``. 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): def is_zero(self, cr, uid, currency, amount):
"""Returns true if ``amount`` is small enough to be treated as return Currency.is_zero(currency, amount)
zero according to ``currency``'s rounding rules.
@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 Warning: ``is_zero(amount1-amount2)`` is not always equivalent to
``compare_amounts(amount1,amount2) == 0``, as the former will round after ``compare_amounts(amount1,amount2) == 0``, as the former will round after
computing the difference, while the latter will round before, giving computing the difference, while the latter will round before, giving
different results for e.g. 0.006 and 0.002 at 2 digits precision. 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 :param float amount: amount to compare with currency's zero
With the new API, call it like: ``currency.is_zero(amount)``. 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): @api.model
if context is None: def _get_conversion_rate(self, from_currency, to_currency):
context = {} from_currency = from_currency.with_env(self.env)
ctx = context.copy() to_currency = to_currency.with_env(self.env)
from_currency = self.browse(cr, uid, from_currency.id, context=ctx) return to_currency.rate / from_currency.rate
to_currency = self.browse(cr, uid, to_currency.id, context=ctx)
return to_currency.rate/from_currency.rate
def _compute(self, cr, uid, from_currency, to_currency, from_amount, round=True, context=None): @api.model
if (to_currency.id == from_currency.id): def _compute(self, from_currency, to_currency, from_amount, round=True):
if round: if (to_currency == from_currency):
return self.round(cr, uid, to_currency, from_amount) amount = to_currency.round(from_amount) if round else from_amount
else:
return from_amount
else: else:
rate = self._get_conversion_rate(cr, uid, from_currency, to_currency, context=context) rate = self._get_conversion_rate(from_currency, to_currency)
if round: amount = to_currency.round(from_amount * rate) if round else from_amount * rate
return self.round(cr, uid, to_currency, from_amount * rate) return amount
else:
return from_amount * rate
@api.v7 @api.v7
def compute(self, cr, uid, from_currency_id, to_currency_id, from_amount, def compute(self, cr, uid, from_currency_id, to_currency_id, from_amount,
@@ -215,23 +189,23 @@ class res_currency(osv.osv):
# apply rounding # apply rounding
return to_currency.round(to_amount) if round else to_amount return to_currency.round(to_amount) if round else to_amount
@api.v7 @api.model
def get_format_currencies_js_function(self, cr, uid, context=None): def get_format_currencies_js_function(self):
""" Returns a string that can be used to instanciate a javascript function that formats numbers as currencies. """ 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. 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. 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 = "" function = ""
for row in self.search_read(cr, uid, domain=[], fields=['id', 'name', 'symbol', 'decimal_places', 'position'], context=context): for currency in self.search([]):
symbol = row['symbol'] or row['name'] symbol = currency.symbol or currency.name
format_number_str = "openerp.web.format_value(arguments[0], {type: 'float', digits: [69," + str(row['decimal_places']) + "]}, 0.00)" format_number_str = "openerp.web.format_value(arguments[0], {type: 'float', digits: [69,%s]}, 0.00)" % currency.decimal_places
if row['position'] == 'after': if currency.position == 'after':
return_str = "return " + format_number_str + " + '\\xA0' + " + json.dumps(symbol) + ";" return_str = "return %s + '\\xA0' + %s;" % (format_number_str, json.dumps(symbol))
else: else:
return_str = "return " + json.dumps(symbol) + " + '\\xA0' + " + format_number_str + ";" return_str = "return %s + '\\xA0' + %s;" % (json.dumps(symbol), format_number_str)
function += "if (arguments[1] === " + str(row['id']) + ") { " + return_str + " }" function += "if (arguments[1] === %s) { %s }" % (currency.id, return_str)
if (row['id'] == company_currency_id): if (currency == company_currency):
company_currency_format = return_str company_currency_format = return_str
function = "if (arguments[1] === false || arguments[1] === undefined) {" + company_currency_format + " }" + function function = "if (arguments[1] === false || arguments[1] === undefined) {" + company_currency_format + " }" + function
return 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" _name = "res.currency.rate"
_description = "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" _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 ['=', '!=']: if operator in ['=', '!=']:
try: try:
date_format = '%Y-%m-%d' date_format = '%Y-%m-%d'
if context.get('lang'): if self._context.get('lang'):
lang_obj = self.pool['res.lang'] langs = self.env['res.lang'].search([('code', '=', self._context['lang'])])
lang_ids = lang_obj.search(cr, user, [('code', '=', context['lang'])], context=context) if langs:
if lang_ids: date_format = langs.date_format
date_format = lang_obj.browse(cr, user, lang_ids[0], context=context).date_format
name = time.strftime('%Y-%m-%d', time.strptime(name, date_format)) name = time.strftime('%Y-%m-%d', time.strptime(name, date_format))
except ValueError: except ValueError:
try: try:
@@ -286,4 +255,4 @@ class res_currency_rate(osv.osv):
return [] return []
name = '' name = ''
operator = 'ilike' 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)
+28 -34
View File
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
from 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 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 """This module allows the mapping of some system-available TTF fonts to
the reportlab engine. the reportlab engine.
@@ -37,42 +37,42 @@ BUILTIN_ALTERNATIVES = [
('Courier', 'bolditalic', ["FreeMonoBoldOblique", "DejaVuSansMono-BoldOblique"]), ('Courier', 'bolditalic', ["FreeMonoBoldOblique", "DejaVuSansMono-BoldOblique"]),
] ]
class res_font(osv.Model):
class ResFont(models.Model):
_name = "res.font" _name = "res.font"
_description = 'Fonts available' _description = 'Fonts available'
_order = 'family,name,id' _order = 'family,name,id'
_rec_name = 'family' _rec_name = 'family'
_columns = { family = fields.Char(string="Font family", required=True)
'family': fields.char("Font family", required=True), name = fields.Char(string="Font Name", required=True)
'name': fields.char("Font Name", required=True), path = fields.Char(required=True)
'path': fields.char("Path", required=True), mode = fields.Char(required=True)
'mode': fields.char("Mode", required=True),
}
_sql_constraints = [ _sql_constraints = [
('name_font_uniq', 'unique(family, name)', 'You can not register two fonts with the same name'), ('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 """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 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 In not lazy mode will force scan filesystem and sync
""" """
if lazy: if lazy:
# lazy loading, scan only if no fonts in db # lazy loading, scan only if no fonts in db
found_fonts_ids = self.search(cr, uid, [('path', '!=', '/dev/null')], context=context) fonts = self.search([('path', '!=', '/dev/null')])
if not found_fonts_ids: if not fonts:
# no scan yet or no font found on the system, scan the filesystem # 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: elif len(customfonts.CustomTTFonts) == 0:
# CustomTTFonts list is empty # CustomTTFonts list is empty
self._sync(cr, uid, context=context) self._sync()
else: else:
self._scan_disk(cr, uid, context=context) self._scan_disk()
return True return True
def _scan_disk(self, cr, uid, context=None): def _scan_disk(self):
"""Scan the file system and register the result in database""" """Scan the file system and register the result in database"""
found_fonts = [] found_fonts = []
for font_path in customfonts.list_all_sysfonts(): 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) _logger.warning("Could not register Font %s: %s", font_path, ex)
for family, name, path, mode in found_fonts: for family, name, path, mode in found_fonts:
if not self.search(cr, uid, [('family', '=', family), ('name', '=', name)], context=context): if not self.search([('family', '=', family), ('name', '=', name)]):
self.create(cr, uid, { self.create({'family': family, 'name': name, 'path': path, 'mode': mode})
'family': family, 'name': name,
'path': path, 'mode': mode,
}, context=context)
# remove fonts not present on the disk anymore # remove fonts not present on the disk anymore
existing_font_names = [name for (family, name, path, mode) in found_fonts] 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) # Remove inexistent fonts
if inexistant_fonts: self.search([('name', 'not in', existing_font_names), ('path', '!=', '/dev/null')]).unlink()
self.unlink(cr, uid, inexistant_fonts, context=context)
RegistryManager.signal_caches_change(cr.dbname) RegistryManager.signal_caches_change(self._cr.dbname)
self._sync(cr, uid, context=context) return self._sync()
return True
def _sync(self, cr, uid, context=None): def _sync(self):
"""Set the customfonts.CustomTTFonts list to the content of the database""" """Set the customfonts.CustomTTFonts list to the content of the database"""
customfonts.CustomTTFonts = [] customfonts.CustomTTFonts = []
local_family_modes = set() local_family_modes = set()
local_font_paths = {} local_font_paths = {}
found_fonts_ids = self.search(cr, uid, [('path', '!=', '/dev/null')], context=context) for font in self.search([('path', '!=', '/dev/null')]):
for font in self.browse(cr, uid, found_fonts_ids, context=None):
local_family_modes.add((font.family, font.mode)) local_family_modes.add((font.family, font.mode))
local_font_paths[font.name] = font.path local_font_paths[font.name] = font.path
customfonts.CustomTTFonts.append((font.family, font.name, font.path, font.mode)) customfonts.CustomTTFonts.append((font.family, font.name, font.path, font.mode))
@@ -134,4 +128,4 @@ class res_font(osv.Model):
def clear_caches(self): def clear_caches(self):
"""Force worker to resync at next report loading by setting an empty font list""" """Force worker to resync at next report loading by setting an empty font list"""
customfonts.CustomTTFonts = [] customfonts.CustomTTFonts = []
return super(res_font, self).clear_caches() return super(ResFont, self).clear_caches()
+147 -157
View File
@@ -2,20 +2,21 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
import locale import locale
from locale import localeconv
import logging import logging
from operator import itemgetter
import re import re
from operator import itemgetter
from openerp import tools, SUPERUSER_ID from odoo import api, fields, models, tools, _
from openerp.osv import fields, osv from odoo.tools.safe_eval import safe_eval as eval
from openerp.tools.safe_eval import safe_eval as eval from odoo.exceptions import UserError, ValidationError
from openerp.tools.translate import _
from openerp.exceptions import UserError
_logger = logging.getLogger(__name__) _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" _name = "res.lang"
_description = "Languages" _description = "Languages"
_order = "active desc,name" _order = "active desc,name"
@@ -23,38 +24,70 @@ class lang(osv.osv):
_disallowed_datetime_patterns = tools.DATETIME_FORMATS_MAP.keys() _disallowed_datetime_patterns = tools.DATETIME_FORMATS_MAP.keys()
_disallowed_datetime_patterns.remove('%y') # this one is in fact allowed, just not good practice _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 _sql_constraints = [
some language and set it as the default for every partners. The ('name_uniq', 'unique(name)', 'The name of the language must be unique !'),
language is set via tools.config by the RPC 'create' method on the ('code_uniq', 'unique(code)', 'The code of the language must be unique !'),
'db' object. This is a fragile solution and something else should be ]
found.
""" @api.constrains('active')
# config['load_language'] is a comma-separated list or None def _check_active(self):
lang = (tools.config.get('load_language') or 'en_US').split(',')[0] # do not check during installation
lang_ids = self.search(cr, uid, [('code','=', lang)]) if self.env.registry.ready and not self.search_count([]):
if not lang_ids: raise ValidationError(_('At least one language must be active.'))
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
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. """ """ Create the given language if necessary, and make it active. """
# if the language exists, simply make it active # if the language exists, simply make it active
lang_ids = self.search(cr, uid, [('code', '=', lang)], context={'active_test': False}) language = self.with_context(active_test=False).search([('code', '=', lang)], limit=1)
if lang_ids: if language:
self.write(cr, uid, lang_ids, {'active': True}) language.write({'active': True})
return lang_ids[0] return language.id
# create the language with locale information # create the language with locale information
fail = True fail = True
@@ -108,164 +141,126 @@ class lang(osv.osv):
'thousands_sep' : fix_xa0(str(conv['thousands_sep'])), 'thousands_sep' : fix_xa0(str(conv['thousands_sep'])),
'grouping' : str(conv.get('grouping', [])), 'grouping' : str(conv.get('grouping', [])),
} }
lang_id = False
try: try:
lang_id = self.create(cr, uid, lang_info) return self.create(lang_info).id
finally: finally:
tools.resetlocale() tools.resetlocale()
return lang_id
def _register_hook(self, cr): @api.model
# check that there is at least one active language def install_lang(self):
if not self.search_count(cr, SUPERUSER_ID, []): """
_logger.error("No language is active.")
def _check_active(self, cr, uid, ids, context=None): This method is called from openerp/addons/base/base_data.xml to load
# do not check during installation some language and set it as the default for every partners. The
return not self.pool.ready or bool(self.search_count(cr, uid, [])) 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): # config['load_language'] is a comma-separated list or None
for pattern in self._disallowed_datetime_patterns: lang_code = (tools.config.get('load_language') or 'en_US').split(',')[0]
if (lang.time_format and pattern in lang.time_format)\ lang = self.search([('code', '=', lang_code)])
or (lang.date_format and pattern in lang.date_format): if not lang:
return False 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 return True
def _check_grouping(self, cr, uid, ids, context=None): @api.model
for lang in self.browse(cr, uid, ids, context=context): @api.returns('self', lambda value: value.id)
try: @tools.ormcache('code')
if not all(isinstance(x, int) for x in eval(lang.grouping)): def _lang_get(self, code):
return False return (self.search([('code', '=', code)]) or
except Exception: self.search([('code', '=', 'en_US')]) or
return False self.search([], limit=1))
return True
def _get_default_date_format(self, cursor, user, context=None): @api.v7
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')
def _lang_data_get(self, cr, uid, lang, monetary=False): 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) lang = self._lang_get(cr, uid, lang)
conv = localeconv() return self.browse(cr, uid, lang)._data_get(monetary)
lang_obj = self.browse(cr, uid, lang)
thousands_sep = lang_obj.thousands_sep or conv[monetary and 'mon_thousands_sep' or 'thousands_sep'] @tools.ormcache('self.code', 'monetary')
decimal_point = lang_obj.decimal_point def _data_get(self, monetary=False):
grouping = lang_obj.grouping 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 return grouping, thousands_sep, decimal_point
@api.model
@tools.ormcache() @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. """ """ 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)) return sorted([(lang.code, lang.name) for lang in langs], key=itemgetter(1))
@api.model
@tools.ormcache() @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. """ """ 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)) 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() self.clear_caches()
return super(lang, self).create(cr, uid, vals, context=context) return super(Lang, self).create(vals)
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."))
@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() 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): @api.multi
if context is None: def unlink(self):
context = {} for language in self:
languages = self.read(cr, uid, ids, ['code','active'], context=context) if language.code == 'en_US':
for language in languages:
ctx_lang = context.get('lang')
if language['code']=='en_US':
raise UserError(_("Base Language 'en_US' can not be deleted!")) 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!")) 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.")) raise UserError(_("You cannot delete the language which is Active!\nPlease de-activate the language first."))
trans_obj = self.pool.get('ir.translation') self.env['ir.translation'].search([('lang', '=', language.code)]).unlink()
trans_ids = trans_obj.search(cr, uid, [('lang','=',language['code'])], context=context)
trans_obj.unlink(cr, uid, trans_ids, context=context)
self.clear_caches() 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 # 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): 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""" """ Format() will return the language-specific output for float values"""
self.ensure_one()
if percent[0] != '%': 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 formatted = percent % value
# floats and decimal ints need special action! # floats and decimal ints need special action!
if grouping: if grouping:
lang_grouping, thousands_sep, decimal_point = \ lang_grouping, thousands_sep, decimal_point = self._data_get(monetary)
self._lang_data_get(cr, uid, ids[0], monetary)
eval_lang_grouping = eval(lang_grouping) eval_lang_grouping = eval(lang_grouping)
if percent[-1] in 'eEfFgG': if percent[-1] in 'eEfFgG':
@@ -279,11 +274,6 @@ class lang(osv.osv):
return formatted return formatted
# import re, operator
# _percent_re = re.compile(r'%(?:\((?P<key>.*?)\))?'
# r'(?P<modifiers>[-#0-9 +*.hlL]*?)[eEfFgGdiouxXcrs%]')
lang()
def split(l, counts): def split(l, counts):
""" """
File diff suppressed because it is too large Load Diff
+5 -7
View File
@@ -123,7 +123,7 @@
<field name="street" placeholder="Street..." class="o_address_street"/> <field name="street" placeholder="Street..." class="o_address_street"/>
<field name="street2" placeholder="Street 2..." 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="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="zip" placeholder="ZIP" class="o_address_zip"/>
<field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/> <field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/>
</div> </div>
@@ -155,7 +155,6 @@
<field name="is_company" invisible="1"/> <field name="is_company" invisible="1"/>
<field name="company_type" widget="radio" <field name="company_type" widget="radio"
class="oe_edit_only" class="oe_edit_only"
on_change="on_change_company_type(company_type)"
options="{'horizontal': true}"/> options="{'horizontal': true}"/>
<h1> <h1>
<field name="name" default_focus="1" placeholder="Name" attrs="{'required' : [('type', '=', 'contact')]}"/> <field name="name" default_focus="1" placeholder="Name" attrs="{'required' : [('type', '=', 'contact')]}"/>
@@ -164,8 +163,7 @@
<field name="parent_id" <field name="parent_id"
placeholder="Company" placeholder="Company"
domain="[('is_company', '=', True)]" context="{'default_is_company': True, 'default_supplier': supplier, 'default_customer': customer}" domain="[('is_company', '=', True)]" context="{'default_is_company': True, 'default_supplier': supplier, 'default_customer': customer}"
attrs="{'invisible': [('is_company','=', True),('parent_id', '=', False)]}" attrs="{'invisible': [('is_company','=', True),('parent_id', '=', False)]}"/>
on_change="onchange_parent_id(parent_id, type)"/>
</div> </div>
</div> </div>
@@ -184,7 +182,7 @@
attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}"/> attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}"/>
<field name="city" placeholder="City" class="o_address_city" <field name="city" placeholder="City" class="o_address_city"
attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}"/> 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}"/> attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}" context="{'country_id': country_id, 'zip': zip}"/>
<field name="zip" placeholder="ZIP" class="o_address_zip" <field name="zip" placeholder="ZIP" class="o_address_zip"
attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}"/> attrs="{'readonly': [('type', '=', 'contact'),('parent_id', '!=', False)]}"/>
@@ -279,7 +277,7 @@
<field name="street" placeholder="Street..." class="o_address_street"/> <field name="street" placeholder="Street..." class="o_address_street"/>
<field name="street2" placeholder="Street 2..." 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="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="zip" placeholder="ZIP" class="o_address_zip"/>
<field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/> <field name="country_id" placeholder="Country" class="o_address_country" options='{"no_open": True, "no_create": True}'/>
</div> </div>
@@ -545,7 +543,7 @@
<field eval="6" name="priority"/> <field eval="6" name="priority"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="Contact Tags"> <tree string="Contact Tags">
<field name="complete_name"/> <field name="display_name"/>
</tree> </tree>
</field> </field>
</record> </record>
+11 -15
View File
@@ -1,22 +1,18 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import osv, 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' _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' _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
+2 -2
View File
@@ -141,7 +141,7 @@
<field name="email" invisible="1"/> <field name="email" invisible="1"/>
<label for="login" class="oe_edit_only" string="Email Address"/> <label for="login" class="oe_edit_only" string="Email Address"/>
<h2> <h2>
<field name="login" on_change="on_change_login(login)" <field name="login"
placeholder="email@yourcompany.com"/> placeholder="email@yourcompany.com"/>
</h2> </h2>
<label for="company_id" class="oe_edit_only" groups="base.group_light_multi_company"/> <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> <h1><field name="name" required="1"/></h1>
<field name="email" invisible="1"/> <field name="email" invisible="1"/>
<label for="login" class="oe_edit_only" string="Email Address"/> <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> <group>
<field name="partner_id" readonly="1" required="0" groups="base.group_no_one" <field name="partner_id" readonly="1" required="0" groups="base.group_no_one"
attrs="{'invisible': [('id', '=', False)]}"/> attrs="{'invisible': [('id', '=', False)]}"/>
+4 -13
View File
@@ -24,19 +24,10 @@ class TestACL(common.TransactionCase):
def _set_field_groups(self, model, field_name, groups): def _set_field_groups(self, model, field_name, groups):
field = model._fields[field_name] field = model._fields[field_name]
column = model._columns[field_name] self.patch(field, 'groups', groups)
old_groups = field.groups if field.column:
old_prefetch = column._prefetch self.patch(field.column, 'groups', groups)
self.patch(field.column, '_prefetch', False)
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
def test_field_visibility_restriction(self): def test_field_visibility_restriction(self):
"""Check that model-level ``groups`` parameter effectively restricts access to that """Check that model-level ``groups`` parameter effectively restricts access to that
+2 -4
View File
@@ -1397,10 +1397,8 @@ class BaseModel(object):
return res return res
def _rec_name_fallback(self, cr, uid, context=None): def _rec_name_fallback(self, cr, uid, context=None):
rec_name = self._rec_name # if self._rec_name is set, it belongs to self._fields
if rec_name not in self._columns: return self._rec_name or 'id'
rec_name = self._columns.keys()[0] if len(self._columns.keys()) > 0 else "id"
return rec_name
# #
# Overload this method if you need a window title which depends on the context # Overload this method if you need a window title which depends on the context
+8 -10
View File
@@ -154,17 +154,15 @@ class TransactionCase(BaseCase):
self.cr.rollback() self.cr.rollback()
self.cr.close() 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): def patch_order(self, model, order):
m_e = self.env[model] """ Patch the order of the given model (name), and prepare cleanup. """
m_r = self.registry(model) self.patch(type(self.env[model]), '_order', order)
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
class SingleTransactionCase(BaseCase): class SingleTransactionCase(BaseCase):
+8
View File
@@ -294,6 +294,14 @@ def reverse_enumerate(l):
""" """
return izip(xrange(len(l)-1, -1, -1), reversed(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): def topological_sort(elems):
""" Return a list of elements sorted so that their dependencies are listed """ Return a list of elements sorted so that their dependencies are listed
before them in the result. before them in the result.