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