diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py
index 1092ec33aea..5b45f8dd02a 100644
--- a/addons/account/account_invoice.py
+++ b/addons/account/account_invoice.py
@@ -572,8 +572,8 @@ class account_invoice(models.Model):
if 'journal_id' in journal_defaults:
values['journal_id'] = journal_defaults['journal_id']
if not values.get('journal_id'):
- field_desc = journals.fields_get(['journal_id'])
- type_label = next(t for t, label in field_desc['journal_id']['selection'] if t == journal_type)
+ field_desc = journals.fields_get(['type'])
+ type_label = next(t for t, label in field_desc['type']['selection'] if t == journal_type)
action = self.env.ref('account.action_account_journal_form')
msg = _('Cannot find any account journal of type "%s" for this company, You should create one.\n Please go to Journal Configuration') % type_label
raise RedirectWarning(msg, action.id, _('Go to the configuration panel'))
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index c4fd782bea2..d994237e094 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -229,10 +229,12 @@ class crm_lead(format_address, osv.osv):
'user_id': fields.many2one('res.users', 'Salesperson', select=True, track_visibility='onchange'),
'referred': fields.char('Referred By'),
'date_open': fields.datetime('Assigned', readonly=True),
- 'day_open': fields.function(_compute_day, string='Days to Open', \
- multi='day_open', type="float", store=True),
- 'day_close': fields.function(_compute_day, string='Days to Close', \
- multi='day_open', type="float", store=True),
+ 'day_open': fields.function(_compute_day, string='Days to Assign',
+ multi='day_open', type="float",
+ store={'crm.lead': (lambda self, cr, uid, ids, c={}: ids, ['date_open'], 10)}),
+ 'day_close': fields.function(_compute_day, string='Days to Close',
+ multi='day_open', type="float",
+ store={'crm.lead': (lambda self, cr, uid, ids, c={}: ids, ['date_closed'], 10)}),
'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
# Messaging and marketing
@@ -887,6 +889,8 @@ class crm_lead(format_address, osv.osv):
context['default_type'] = vals.get('type')
if vals.get('section_id') and not context.get('default_section_id'):
context['default_section_id'] = vals.get('section_id')
+ if vals.get('user_id'):
+ vals['date_open'] = fields.datetime.now()
# context: no_log, because subtype already handle this
create_context = dict(context, mail_create_nolog=True)
@@ -896,7 +900,9 @@ class crm_lead(format_address, osv.osv):
# stage change: update date_last_stage_update
if 'stage_id' in vals:
vals['date_last_stage_update'] = fields.datetime.now()
- # stage change with new stage: update probability
+ if vals.get('user_id'):
+ vals['date_open'] = fields.datetime.now()
+ # stage change with new stage: update probability and date_closed
if vals.get('stage_id') and not vals.get('probability'):
onchange_stage_values = self.onchange_stage_id(cr, uid, ids, vals.get('stage_id'), context=context)['value']
vals.update(onchange_stage_values)
diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py
index ebe66757b09..f79aa77ddc9 100644
--- a/addons/email_template/wizard/mail_compose_message.py
+++ b/addons/email_template/wizard/mail_compose_message.py
@@ -19,7 +19,7 @@
#
##############################################################################
-from openerp import tools, SUPERUSER_ID
+from openerp import tools
from openerp.osv import osv, fields
@@ -54,6 +54,8 @@ class mail_compose_message(osv.TransientModel):
res.get('model'), res.get('res_id'), context=context
)['value']
)
+ if fields is not None:
+ [res.pop(field, None) for field in res.keys() if field not in fields]
return res
_columns = {
@@ -113,7 +115,9 @@ class mail_compose_message(osv.TransientModel):
}
values.setdefault('attachment_ids', list()).append(ir_attach_obj.create(cr, uid, data_attach, context=context))
else:
- values = self.default_get(cr, uid, ['subject', 'body', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'], context=context)
+ default_context = dict(context, default_composition_mode=composition_mode, default_model=model, default_res_id=res_id)
+ default_values = self.default_get(cr, uid, ['composition_mode', 'model', 'res_id', 'subject', 'body', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'], context=default_context)
+ values = dict((key, default_values[key]) for key in ['subject', 'body', 'email_from', 'reply_to', 'attachment_ids', 'mail_server_id'] if key in default_values)
if values.get('body_html'):
values['body'] = values.pop('body_html')
diff --git a/addons/hr_contract/hr_contract.py b/addons/hr_contract/hr_contract.py
index 8b58c3efd61..b80b93bc3a5 100644
--- a/addons/hr_contract/hr_contract.py
+++ b/addons/hr_contract/hr_contract.py
@@ -20,6 +20,7 @@
##############################################################################
import time
+from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
class hr_employee(osv.osv):
@@ -41,7 +42,7 @@ class hr_employee(osv.osv):
def _contracts_count(self, cr, uid, ids, field_name, arg, context=None):
Contract = self.pool['hr.contract']
return {
- employee_id: Contract.search_count(cr,uid, [('employee_id', '=', employee_id)], context=context)
+ employee_id: Contract.search_count(cr, SUPERUSER_ID, [('employee_id', '=', employee_id)], context=context)
for employee_id in ids
}
@@ -53,7 +54,7 @@ class hr_employee(osv.osv):
'vehicle': fields.char('Company Vehicle'),
'vehicle_distance': fields.integer('Home-Work Dist.', help="In kilometers"),
'contract_ids': fields.one2many('hr.contract', 'employee_id', 'Contracts'),
- 'contract_id':fields.function(_get_latest_contract, string='Contract', type='many2one', relation="hr.contract", help='Latest contract of the employee'),
+ 'contract_id': fields.function(_get_latest_contract, string='Contract', type='many2one', relation="hr.contract", help='Latest contract of the employee'),
'contracts_count': fields.function(_contracts_count, type='integer', string='Contracts'),
}
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index 66f843cd03a..a33982b3860 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -68,7 +68,7 @@ class mail_compose_message(osv.TransientModel):
result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
# v6.1 compatibility mode
- result['composition_mode'] = result.get('composition_mode', context.get('mail.compose.message.mode'))
+ result['composition_mode'] = result.get('composition_mode', context.get('mail.compose.message.mode', 'comment'))
result['model'] = result.get('model', context.get('active_model'))
result['res_id'] = result.get('res_id', context.get('active_id'))
result['parent_id'] = result.get('parent_id', context.get('message_id'))
@@ -97,6 +97,9 @@ class mail_compose_message(osv.TransientModel):
if result['model'] == 'res.users' and result['res_id'] == uid:
result['model'] = 'res.partner'
result['res_id'] = self.pool.get('res.users').browse(cr, uid, uid).partner_id.id
+
+ if fields is not None:
+ [result.pop(field, None) for field in result.keys() if field not in fields]
return result
def _get_composition_mode_selection(self, cr, uid, context=None):
diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py
index e2e94233861..2ede7b27b9d 100644
--- a/addons/mrp/mrp.py
+++ b/addons/mrp/mrp.py
@@ -239,16 +239,27 @@ class mrp_bom(osv.osv):
"""
if properties is None:
properties = []
- domain = None
if product_id:
- domain = ['|',('product_id', '=', product_id),('product_tmpl_id.product_variant_ids', '=', product_id)]
- else:
+ if not product_tmpl_id:
+ product_tmpl_id = self.pool['product.product'].browse(cr, uid, product_id).product_tmpl_id.id
+ domain = [
+ '|',
+ ('product_id', '=', product_id),
+ '&',
+ ('product_id', '=', False),
+ ('product_tmpl_id', '=', product_tmpl_id)
+ ]
+ elif product_tmpl_id:
domain = [('product_id', '=', False), ('product_tmpl_id', '=', product_tmpl_id)]
+ else:
+ # neither product nor template, makes no sense to search
+ return False
if product_uom:
domain += [('product_uom','=',product_uom)]
domain = domain + [ '|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
'|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
- ids = self.search(cr, uid, domain)
+ # order to prioritize bom with product_id over the one without
+ ids = self.search(cr, uid, domain, order='product_id')
for bom in self.pool.get('mrp.bom').browse(cr, uid, ids):
if not set(map(int,bom.property_ids or [])) - set(properties or []):
return bom.id
diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py
index 016313b5e44..a2fbed7492e 100644
--- a/addons/project_issue/project_issue.py
+++ b/addons/project_issue/project_issue.py
@@ -25,9 +25,11 @@ from openerp import api
from openerp import SUPERUSER_ID
from openerp import tools
from openerp.osv import fields, osv, orm
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools import html2plaintext
from openerp.tools.translate import _
+
class project_issue_version(osv.Model):
_name = "project.issue.version"
_order = "name desc"
@@ -129,73 +131,51 @@ class project_issue(osv.Model):
@return: difference between current date and log date
@param context: A standard dictionary for contextual values
"""
- cal_obj = self.pool.get('resource.calendar')
- res_obj = self.pool.get('resource.resource')
+ Calendar = self.pool['resource.calendar']
- res = {}
+ res = dict.fromkeys(ids, dict())
for issue in self.browse(cr, uid, ids, context=context):
-
+ values = {
+ 'day_open': 0.0, 'day_close': 0.0,
+ 'working_hours_open': 0.0, 'working_hours_close': 0.0,
+ 'days_since_creation': 0.0, 'inactivity_days': 0.0,
+ }
# if the working hours on the project are not defined, use default ones (8 -> 12 and 13 -> 17 * 5), represented by None
- if not issue.project_id or not issue.project_id.resource_calendar_id:
- working_hours = None
+ calendar_id = None
+ if issue.project_id and issue.project_id.resource_calendar_id:
+ calendar_id = issue.project_id.resource_calendar_id.id
+
+ dt_create_date = datetime.strptime(issue.create_date, DEFAULT_SERVER_DATETIME_FORMAT)
+
+ if issue.date_open:
+ dt_date_open = datetime.strptime(issue.date_open, DEFAULT_SERVER_DATETIME_FORMAT)
+ values['day_open'] = (dt_date_open - dt_create_date).total_seconds() / (24.0 * 3600)
+ values['working_hours_open'] = Calendar._interval_hours_get(
+ cr, uid, calendar_id, dt_create_date, dt_date_open,
+ timezone_from_uid=issue.user_id.id or uid,
+ exclude_leaves=False, context=context)
+
+ if issue.date_closed:
+ dt_date_closed = datetime.strptime(issue.date_closed, DEFAULT_SERVER_DATETIME_FORMAT)
+ values['day_close'] = (dt_date_closed - dt_create_date).total_seconds() / (24.0 * 3600)
+ values['working_hours_close'] = Calendar._interval_hours_get(
+ cr, uid, calendar_id, dt_create_date, dt_date_closed,
+ timezone_from_uid=issue.user_id.id or uid,
+ exclude_leaves=False, context=context)
+
+ days_since_creation = datetime.today() - dt_create_date
+ values['days_since_creation'] = days_since_creation.days
+ if issue.date_action_last:
+ inactive_days = datetime.today() - datetime.strptime(issue.date_action_last, DEFAULT_SERVER_DATETIME_FORMAT)
+ elif issue.date_last_stage_update:
+ inactive_days = datetime.today() - datetime.strptime(issue.date_last_stage_update, DEFAULT_SERVER_DATETIME_FORMAT)
else:
- working_hours = issue.project_id.resource_calendar_id.id
+ inactive_days = datetime.today() - datetime.strptime(issue.create_date, DEFAULT_SERVER_DATETIME_FORMAT)
+ values['inactivity_days'] = inactive_days.days
- res[issue.id] = {}
+ # filter only required values
for field in fields:
- duration = 0
- ans = False
- hours = 0
-
- date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
- if field in ['working_hours_open','day_open']:
- if issue.date_open:
- date_open = datetime.strptime(issue.date_open, "%Y-%m-%d %H:%M:%S")
- ans = date_open - date_create
- date_until = issue.date_open
- #Calculating no. of working hours to open the issue
- hours = cal_obj._interval_hours_get(cr, uid, working_hours,
- date_create,
- date_open,
- timezone_from_uid=issue.user_id.id or uid,
- exclude_leaves=False,
- context=context)
- elif field in ['working_hours_close','day_close']:
- if issue.date_closed:
- date_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S")
- date_until = issue.date_closed
- ans = date_close - date_create
- #Calculating no. of working hours to close the issue
- hours = cal_obj._interval_hours_get(cr, uid, working_hours,
- date_create,
- date_close,
- timezone_from_uid=issue.user_id.id or uid,
- exclude_leaves=False,
- context=context)
- elif field in ['days_since_creation']:
- if issue.create_date:
- days_since_creation = datetime.today() - datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
- res[issue.id][field] = days_since_creation.days
- continue
-
- elif field in ['inactivity_days']:
- res[issue.id][field] = 0
- if issue.date_action_last:
- inactive_days = datetime.today() - datetime.strptime(issue.date_action_last, '%Y-%m-%d %H:%M:%S')
- res[issue.id][field] = inactive_days.days
- continue
- if ans:
- resource_id = False
- if issue.user_id:
- resource_ids = res_obj.search(cr, uid, [('user_id','=',issue.user_id.id)])
- if resource_ids and len(resource_ids):
- resource_id = resource_ids[0]
- duration = float(ans.days) + float(ans.seconds)/(24*3600)
-
- if field in ['working_hours_open','working_hours_close']:
- res[issue.id][field] = hours
- elif field in ['day_open','day_close']:
- res[issue.id][field] = duration
+ res[issue.id][field] = values[field]
return res
@@ -230,11 +210,12 @@ class project_issue(osv.Model):
if work.task_id:
issues += issue_pool.search(cr, uid, [('task_id','=',work.task_id.id)])
return issues
+
_columns = {
'id': fields.integer('ID', readonly=True),
'name': fields.char('Issue', required=True),
'active': fields.boolean('Active', required=False),
- 'create_date': fields.datetime('Creation Date', readonly=True,select=True),
+ 'create_date': fields.datetime('Creation Date', readonly=True, select=True),
'write_date': fields.datetime('Update Date', readonly=True),
'days_since_creation': fields.function(_compute_day, string='Days since creation date', \
multi='compute_day', type="integer", help="Difference in days between creation date and current date"),
@@ -254,9 +235,9 @@ class project_issue(osv.Model):
required=False),
'email_from': fields.char('Email', size=128, help="These people will receive email.", select=1),
'email_cc': fields.char('Watchers Emails', size=256, help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
- 'date_open': fields.datetime('Opened', readonly=True,select=True),
+ 'date_open': fields.datetime('Assigned', readonly=True, select=True),
# Project Issue fields
- 'date_closed': fields.datetime('Closed', readonly=True,select=True),
+ 'date_closed': fields.datetime('Closed', readonly=True, select=True),
'date': fields.datetime('Date'),
'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
'channel': fields.char('Channel', help="Communication channel."),
@@ -269,17 +250,21 @@ class project_issue(osv.Model):
'project_id': fields.many2one('project.project', 'Project', track_visibility='onchange', select=True),
'duration': fields.float('Duration'),
'task_id': fields.many2one('project.task', 'Task', domain="[('project_id','=',project_id)]"),
- 'day_open': fields.function(_compute_day, string='Days to Open', \
- multi='compute_day', type="float", store=True),
- 'day_close': fields.function(_compute_day, string='Days to Close', \
- multi='compute_day', type="float", store=True),
+ 'day_open': fields.function(_compute_day, string='Days to Assign',
+ multi='compute_day', type="float",
+ store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_open'], 10)}),
+ 'day_close': fields.function(_compute_day, string='Days to Close',
+ multi='compute_day', type="float",
+ store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_closed'], 10)}),
'user_id': fields.many2one('res.users', 'Assigned to', required=False, select=1, track_visibility='onchange'),
- 'working_hours_open': fields.function(_compute_day, string='Working Hours to Open the Issue', \
- multi='compute_day', type="float", store=True),
- 'working_hours_close': fields.function(_compute_day, string='Working Hours to Close the Issue', \
- multi='compute_day', type="float", store=True),
- 'inactivity_days': fields.function(_compute_day, string='Days since last action', \
- multi='compute_day', type="integer", help="Difference in days between last action and current date"),
+ 'working_hours_open': fields.function(_compute_day, string='Working Hours to assign the Issue',
+ multi='compute_day', type="float",
+ store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_open'], 10)}),
+ 'working_hours_close': fields.function(_compute_day, string='Working Hours to close the Issue',
+ multi='compute_day', type="float",
+ store={'project.issue': (lambda self, cr, uid, ids, c={}: ids, ['date_closed'], 10)}),
+ 'inactivity_days': fields.function(_compute_day, string='Days since last action',
+ multi='compute_day', type="integer", help="Difference in days between last action and current date"),
'color': fields.integer('Color Index'),
'user_email': fields.related('user_id', 'email', type='char', string='User Email', readonly=True),
'date_action_last': fields.datetime('Last Action', readonly=1),
@@ -312,13 +297,16 @@ class project_issue(osv.Model):
default = {}
default = default.copy()
default.update(name=_('%s (copy)') % (issue['name']))
- return super(project_issue, self).copy(cr, uid, id, default=default,
- context=context)
+ return super(project_issue, self).copy(cr, uid, id, default=default, context=context)
def create(self, cr, uid, vals, context=None):
context = dict(context or {})
if vals.get('project_id') and not context.get('default_project_id'):
context['default_project_id'] = vals.get('project_id')
+ if vals.get('user_id'):
+ vals['date_open'] = fields.datetime.now()
+ if 'stage_id' in vals:
+ vals.update(self.onchange_stage_id(cr, uid, None, vals.get('stage_id'), context=context)['value'])
# context: no_log, because subtype already handle this
create_context = dict(context, mail_create_nolog=True)
@@ -327,12 +315,13 @@ class project_issue(osv.Model):
def write(self, cr, uid, ids, vals, context=None):
# stage change: update date_last_stage_update
if 'stage_id' in vals:
+ vals.update(self.onchange_stage_id(cr, uid, ids, vals.get('stage_id'), context=context)['value'])
vals['date_last_stage_update'] = fields.datetime.now()
if 'kanban_state' not in vals:
vals['kanban_state'] = 'normal'
# user_id change: update date_start
if vals.get('user_id'):
- vals['date_start'] = fields.datetime.now()
+ vals['date_open'] = fields.datetime.now()
return super(project_issue, self).write(cr, uid, ids, vals, context)
@@ -363,6 +352,14 @@ class project_issue(osv.Model):
# Stage management
# -------------------------------------------------------
+ def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
+ if not stage_id:
+ return {'value': {}}
+ stage = self.pool['project.task.type'].browse(cr, uid, stage_id, context=context)
+ if stage.fold:
+ return {'value': {'date_closed': fields.datetime.now()}}
+ return {'value': {'date_closed': False}}
+
def stage_find(self, cr, uid, cases, section_id, domain=[], order='sequence', context=None):
""" Override of the base.stage method
Parameter of the stage search taken from the issue:
diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml
index 502598193a6..620c612ecf1 100644
--- a/addons/project_issue/project_issue_view.xml
+++ b/addons/project_issue/project_issue_view.xml
@@ -90,16 +90,18 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/project_issue/report/project_issue_report_view.xml b/addons/project_issue/report/project_issue_report_view.xml
index 3382c6e3625..67473875134 100644
--- a/addons/project_issue/report/project_issue_report_view.xml
+++ b/addons/project_issue/report/project_issue_report_view.xml
@@ -21,13 +21,6 @@
{'group_by': ['project_id', 'user_id']}
-
- By Reviewer
- project.issue.report
- []
-
- {'group_by': ['project_id', 'reviewer_id']}
-
project.issue.report.select
diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml
index 8402f9965f5..9228e71e131 100644
--- a/addons/purchase/purchase_view.xml
+++ b/addons/purchase/purchase_view.xml
@@ -587,7 +587,7 @@
diff --git a/addons/resource/resource.py b/addons/resource/resource.py
index 5a43234ac46..b16fd210593 100644
--- a/addons/resource/resource.py
+++ b/addons/resource/resource.py
@@ -329,7 +329,8 @@ class resource_calendar(osv.osv):
# no calendar: try to use the default_interval, then return directly
if id is None:
if default_interval:
- intervals.append((start_dt.replace(hour=default_interval[0]), start_dt.replace(hour=default_interval[1])))
+ working_interval = (start_dt.replace(hour=default_interval[0], minute=0, second=0), start_dt.replace(hour=default_interval[1], minute=0, second=0))
+ intervals = self.interval_remove_leaves(working_interval, work_limits)
return intervals
working_intervals = []
@@ -371,10 +372,16 @@ class resource_calendar(osv.osv):
resource_id=None, default_interval=None, context=None):
hours = 0.0
for day in rrule.rrule(rrule.DAILY, dtstart=start_dt,
- until=end_dt + datetime.timedelta(days=1),
+ until=(end_dt + datetime.timedelta(days=1)).replace(hour=0, minute=0, second=0),
byweekday=self.get_weekdays(cr, uid, id, context=context)):
+ day_start_dt = day.replace(hour=0, minute=0, second=0)
+ if start_dt and day.date() == start_dt.date():
+ day_start_dt = start_dt
+ day_end_dt = day.replace(hour=23, minute=59, second=59)
+ if end_dt and day.date() == end_dt.date():
+ day_end_dt = end_dt
hours += self.get_working_hours_of_date(
- cr, uid, id, start_dt=day,
+ cr, uid, id, start_dt=day_start_dt, end_dt=day_end_dt,
compute_leaves=compute_leaves, resource_id=resource_id,
default_interval=default_interval,
context=context)
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index 42dad7d1062..818b3ebe1af 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -2598,7 +2598,7 @@ class stock_inventory(osv.osv):
domain += ' and lot_id = %s'
args += (inventory.lot_id.id,)
if inventory.product_id:
- domain += 'and product_id = %s'
+ domain += ' and product_id = %s'
args += (inventory.product_id.id,)
if inventory.package_id:
domain += ' and package_id = %s'
diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js
index 566179c2a38..7ed70f7bc68 100644
--- a/addons/web/static/src/js/search.js
+++ b/addons/web/static/src/js/search.js
@@ -1963,7 +1963,7 @@ instance.web.search.Advanced = instance.web.search.Input.extend({
context: this.view.dataset.context
}).done(function(data) {
self.fields = {
- id: { string: 'ID', type: 'id' }
+ id: { string: 'ID', type: 'id', searchable: true }
};
_.each(data, function(field_def, field_name) {
if (field_def.selectable !== false && field_name != 'id') {
@@ -2037,7 +2037,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @
this._super(parent);
this.fields = _(fields).chain()
.map(function(val, key) { return _.extend({}, val, {'name': key}); })
- .filter(function (field) { return !field.deprecated && (field.store === void 0 || field.store || field.fnct_search); })
+ .filter(function (field) { return !field.deprecated && field.searchable; })
.sortBy(function(field) {return field.string;})
.value();
this.attrs = {_: _, fields: this.fields, selected: null};
diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js
index d93cb131e05..828347b68a5 100644
--- a/addons/web/static/test/search.js
+++ b/addons/web/static/test/search.js
@@ -157,7 +157,7 @@ var makeSearchView = function (instance, dummy_widget_attributes, defaults) {
return {
type: 'search',
fields: {
- dummy: {type: 'char', string: "Dummy"}
+ dummy: {type: 'char', string: "Dummy", searchable: true}
},
arch: ''
};
@@ -168,7 +168,7 @@ var makeSearchView = function (instance, dummy_widget_attributes, defaults) {
};
instance.session.responses['dummy.model:fields_get'] = function () {
return {
- dummy: {type: 'char', string: 'Dummy'}
+ dummy: {type: 'char', string: 'Dummy', searchable: true}
};
};
instance.client = { action_manager: { inner_action: undefined } };
diff --git a/addons/web_kanban/static/src/xml/web_kanban.xml b/addons/web_kanban/static/src/xml/web_kanban.xml
index c1d504e51be..aebdbacb9dd 100644
--- a/addons/web_kanban/static/src/xml/web_kanban.xml
+++ b/addons/web_kanban/static/src/xml/web_kanban.xml
@@ -45,8 +45,8 @@
diff --git a/addons/website_livechat/website_livechat_data.xml b/addons/website_livechat/website_livechat_data.xml
index 55c59895664..5e37397c471 100644
--- a/addons/website_livechat/website_livechat_data.xml
+++ b/addons/website_livechat/website_livechat_data.xml
@@ -2,8 +2,13 @@
+
+ YourWebsiteWithOdoo.com
+ Hello, how may I help you?
+
+
-
+
diff --git a/addons/website_sale_options/models/sale_order.py b/addons/website_sale_options/models/sale_order.py
index f16feaecc7b..85eaab214f7 100644
--- a/addons/website_sale_options/models/sale_order.py
+++ b/addons/website_sale_options/models/sale_order.py
@@ -45,7 +45,7 @@ class sale_order(osv.Model):
line.write({
"name": _("%s\nOption for: %s") % (line.name, linked.product_id.name_get()[0][1]),
"linked_line_id": linked_line_id
- }, context=context)
+ })
# select linked product
option_ids = [l.id for l in so.order_line if l.linked_line_id.id == line.id]
diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py
index f1ddf4f1b42..7102c009264 100644
--- a/openerp/addons/base/res/res_users.py
+++ b/openerp/addons/base/res/res_users.py
@@ -22,6 +22,7 @@
import itertools
import logging
from functools import partial
+from itertools import repeat
from lxml import etree
from lxml.builder import E
@@ -650,25 +651,30 @@ class users_implied(osv.osv):
# Naming conventions for reified groups fields:
# - boolean field 'in_group_ID' is True iff
# ID is in 'groups_id'
-# - boolean field 'in_groups_ID1_..._IDk' is True iff
-# any of ID1, ..., IDk is in 'groups_id'
# - selection field 'sel_groups_ID1_..._IDk' is ID iff
# ID is in 'groups_id' and ID is maximal in the set {ID1, ..., IDk}
#----------------------------------------------------------
-def name_boolean_group(id): return 'in_group_' + str(id)
-def name_boolean_groups(ids): return 'in_groups_' + '_'.join(map(str, ids))
-def name_selection_groups(ids): return 'sel_groups_' + '_'.join(map(str, ids))
+def name_boolean_group(id):
+ return 'in_group_' + str(id)
+
+def name_selection_groups(ids):
+ return 'sel_groups_' + '_'.join(map(str, ids))
+
+def is_boolean_group(name):
+ return name.startswith('in_group_')
+
+def is_selection_groups(name):
+ return name.startswith('sel_groups_')
-def is_boolean_group(name): return name.startswith('in_group_')
-def is_boolean_groups(name): return name.startswith('in_groups_')
-def is_selection_groups(name): return name.startswith('sel_groups_')
def is_reified_group(name):
- return is_boolean_group(name) or is_boolean_groups(name) or is_selection_groups(name)
+ return is_boolean_group(name) or is_selection_groups(name)
-def get_boolean_group(name): return int(name[9:])
-def get_boolean_groups(name): return map(int, name[10:].split('_'))
-def get_selection_groups(name): return map(int, name[11:].split('_'))
+def get_boolean_group(name):
+ return int(name[9:])
+
+def get_selection_groups(name):
+ return map(int, name[11:].split('_'))
def partition(f, xs):
"return a pair equivalent to (filter(f, xs), filter(lambda x: not f(x), xs))"
@@ -789,45 +795,39 @@ class users_view(osv.osv):
_inherit = 'res.users'
def create(self, cr, uid, values, context=None):
- self._set_reified_groups(values)
+ values = self._remove_reified_groups(values)
return super(users_view, self).create(cr, uid, values, context)
def write(self, cr, uid, ids, values, context=None):
- self._set_reified_groups(values)
+ values = self._remove_reified_groups(values)
return super(users_view, self).write(cr, uid, ids, values, context)
- def _set_reified_groups(self, values):
- """ reflect reified group fields in values['groups_id'] """
- if 'groups_id' in values:
- # groups are already given, ignore group fields
- for f in filter(is_reified_group, values.iterkeys()):
- del values[f]
- return
+ def _remove_reified_groups(self, values):
+ """ return `values` without reified group fields """
+ add, rem = [], []
+ values1 = {}
- add, remove = [], []
- for f in values.keys():
- if is_boolean_group(f):
- target = add if values.pop(f) else remove
- target.append(get_boolean_group(f))
- elif is_boolean_groups(f):
- if not values.pop(f):
- remove.extend(get_boolean_groups(f))
- elif is_selection_groups(f):
- remove.extend(get_selection_groups(f))
- selected = values.pop(f)
- if selected:
- add.append(selected)
- # update values *only* if groups are being modified, otherwise
- # we introduce spurious changes that might break the super.write() call.
- if add or remove:
- # remove groups in 'remove' and add groups in 'add'
- values['groups_id'] = [(3, id) for id in remove] + [(4, id) for id in add]
+ for key, val in values.iteritems():
+ if is_boolean_group(key):
+ (add if val else rem).append(get_boolean_group(key))
+ elif is_selection_groups(key):
+ rem += get_selection_groups(key)
+ if val:
+ add.append(val)
+ else:
+ values1[key] = val
+
+ if 'groups_id' not in values and (add or rem):
+ # remove group ids in `rem` and add group ids in `add`
+ values1['groups_id'] = zip(repeat(3), rem) + zip(repeat(4), add)
+
+ return values1
def default_get(self, cr, uid, fields, context=None):
group_fields, fields = partition(is_reified_group, fields)
fields1 = (fields + ['groups_id']) if group_fields else fields
values = super(users_view, self).default_get(cr, uid, fields1, context)
- self._get_reified_groups(group_fields, values)
+ self._add_reified_groups(group_fields, values)
# add "default_groups_ref" inside the context to set default value for group_id with xml values
if 'groups_id' in fields and isinstance(context.get("default_groups_ref"), list):
@@ -846,29 +846,35 @@ class users_view(osv.osv):
return values
def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
- fields_get = fields if fields is not None else self.fields_get(cr, uid, context=context).keys()
- group_fields, _ = partition(is_reified_group, fields_get)
+ # determine whether reified groups fields are required, and which ones
+ fields1 = fields or self.fields_get(cr, uid, context=context).keys()
+ group_fields, other_fields = partition(is_reified_group, fields1)
- inject_groups_id = group_fields and fields and 'groups_id' not in fields
- if inject_groups_id:
- fields.append('groups_id')
- res = super(users_view, self).read(cr, uid, ids, fields, context=context, load=load)
+ # read regular fields (other_fields); add 'groups_id' if necessary
+ drop_groups_id = False
+ if group_fields and fields:
+ if 'groups_id' not in other_fields:
+ other_fields.append('groups_id')
+ drop_groups_id = True
+ else:
+ other_fields = fields
- if res and group_fields:
+ res = super(users_view, self).read(cr, uid, ids, other_fields, context=context, load=load)
+
+ # post-process result to add reified group fields
+ if group_fields:
for values in (res if isinstance(res, list) else [res]):
- self._get_reified_groups(group_fields, values)
- if inject_groups_id:
+ self._add_reified_groups(group_fields, values)
+ if drop_groups_id:
values.pop('groups_id', None)
return res
- def _get_reified_groups(self, fields, values):
- """ compute the given reified group fields from values['groups_id'] """
+ def _add_reified_groups(self, fields, values):
+ """ add the given reified group fields into `values` """
gids = set(parse_m2m(values.get('groups_id') or []))
for f in fields:
if is_boolean_group(f):
values[f] = get_boolean_group(f) in gids
- elif is_boolean_groups(f):
- values[f] = not gids.isdisjoint(get_boolean_groups(f))
elif is_selection_groups(f):
selected = [gid for gid in get_selection_groups(f) if gid in gids]
values[f] = selected and selected[-1] or False
@@ -915,29 +921,26 @@ class change_password_wizard(osv.TransientModel):
'user_ids': fields.one2many('change.password.user', 'wizard_id', string='Users'),
}
- def default_get(self, cr, uid, fields, context=None):
- if context == None:
+ def _default_user_ids(self, cr, uid, context=None):
+ if context is None:
context = {}
- user_ids = context.get('active_ids', [])
- wiz_id = context.get('active_id', None)
- res = []
- users = self.pool.get('res.users').browse(cr, uid, user_ids, context=context)
- for user in users:
- res.append((0, 0, {
- 'wizard_id': wiz_id,
- 'user_id': user.id,
- 'user_login': user.login,
- }))
- return {'user_ids': res}
+ user_model = self.pool['res.users']
+ user_ids = context.get('active_model') == 'res.users' and context.get('active_ids') or []
+ return [
+ (0, 0, {'user_id': user.id, 'user_login': user.login})
+ for user in user_model.browse(cr, uid, user_ids, context=context)
+ ]
- def change_password_button(self, cr, uid, id, context=None):
- wizard = self.browse(cr, uid, id, context=context)[0]
+ _defaults = {
+ 'user_ids': _default_user_ids,
+ }
+
+ def change_password_button(self, cr, uid, ids, context=None):
+ wizard = self.browse(cr, uid, ids, context=context)[0]
need_reload = any(uid == user.user_id.id for user in wizard.user_ids)
- line_ids = [user.id for user in wizard.user_ids]
+ line_ids = [user.id for user in wizard.user_ids]
self.pool.get('change.password.user').change_password_button(cr, uid, line_ids, context=context)
- # don't keep temporary password copies in the database longer than necessary
- self.pool.get('change.password.user').write(cr, uid, line_ids, {'new_passwd': False}, context=context)
if need_reload:
return {
@@ -965,8 +968,10 @@ class change_password_user(osv.TransientModel):
}
def change_password_button(self, cr, uid, ids, context=None):
- for user in self.browse(cr, uid, ids, context=context):
- self.pool.get('res.users').write(cr, uid, user.user_id.id, {'password': user.new_passwd})
+ for line in self.browse(cr, uid, ids, context=context):
+ line.user_id.write({'password': line.new_passwd})
+ # don't keep temporary passwords in the database longer than necessary
+ self.write(cr, uid, ids, {'new_passwd': False}, context=context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/addons/test_inherit/models.py b/openerp/addons/test_inherit/models.py
index ebbe71e0521..f620dfe53a9 100644
--- a/openerp/addons/test_inherit/models.py
+++ b/openerp/addons/test_inherit/models.py
@@ -7,6 +7,7 @@ class mother(models.Model):
name = fields.Char('Name', required=True)
surname = fields.Char(compute='_compute_surname')
+ state = fields.Selection([('a', 'A'), ('b', 'B')])
@api.one
@api.depends('name')
@@ -35,6 +36,9 @@ class mother(models.Model):
# extend the name field by adding a default value
name = fields.Char(default='Unknown')
+ # extend the selection of the state field
+ state = fields.Selection(selection_add=[('c', 'C')])
+
# override the computed field, and extend its dependencies
@api.one
@api.depends('field_in_mother')
@@ -44,4 +48,11 @@ class mother(models.Model):
else:
super(mother, self)._compute_surname()
+
+class mother(models.Model):
+ _inherit = 'test.inherit.mother'
+
+ # extend again the selection of the state field
+ state = fields.Selection(selection_add=[('d', 'D')])
+
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/addons/test_inherit/tests/test_inherit.py b/openerp/addons/test_inherit/tests/test_inherit.py
index b663d911f60..8039e698669 100644
--- a/openerp/addons/test_inherit/tests/test_inherit.py
+++ b/openerp/addons/test_inherit/tests/test_inherit.py
@@ -9,15 +9,15 @@ class test_inherits(common.TransactionCase):
# is accessible from the child model. This test has been written
# to verify the purpose of the inheritance computing of the class
# in the openerp.osv.orm._build_model.
- mother = self.registry('test.inherit.mother')
- daugther = self.registry('test.inherit.daugther')
+ mother = self.env['test.inherit.mother']
+ daugther = self.env['test.inherit.daugther']
self.assertIn('field_in_mother', mother._fields)
self.assertIn('field_in_mother', daugther._fields)
def test_field_extension(self):
""" check the extension of a field in an inherited model """
- mother = self.registry('test.inherit.mother')
+ mother = self.env['test.inherit.mother']
field = mother._fields['name']
# the field should inherit required=True, and have a default value
@@ -26,11 +26,19 @@ class test_inherits(common.TransactionCase):
def test_depends_extension(self):
""" check that @depends on overridden compute methods extends dependencies """
- mother = self.registry('test.inherit.mother')
+ mother = self.env['test.inherit.mother']
field = mother._fields['surname']
# the field dependencies are added
self.assertItemsEqual(field.depends, ['name', 'field_in_mother'])
+ def test_selection_extension(self):
+ """ check that attribute selection_add=... extends selection on fields. """
+ mother = self.env['test.inherit.mother']
+ field = mother._fields['state']
+
+ # the extra values are added
+ self.assertEqual(field.selection, [('a', 'A'), ('b', 'B'), ('c', 'C'), ('d', 'D')])
+
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/openerp/fields.py b/openerp/fields.py
index d85ce82eccb..1b3cdabe29c 100644
--- a/openerp/fields.py
+++ b/openerp/fields.py
@@ -284,6 +284,8 @@ class Field(object):
required = False
states = None
groups = False # csv list of group xml ids
+ change_default = None # whether the field may trigger a "user-onchange"
+ deprecated = None # whether the field is ... deprecated
def __init__(self, string=None, **kwargs):
kwargs['string'] = string
@@ -408,7 +410,8 @@ class Field(object):
self.depends = ('.'.join(self.related),)
self.compute = self._compute_related
self.inverse = self._inverse_related
- self.search = self._search_related
+ if field._description_searchable(env):
+ self.search = self._search_related
# copy attributes from field to self (string, help, etc.)
for attr, prop in self.related_attrs:
@@ -417,9 +420,9 @@ class Field(object):
def _compute_related(self, records):
""" Compute the related field `self` on `records`. """
- for record in records:
+ for record, sudo_record in zip(records, records.sudo()):
# bypass access rights check when traversing the related path
- value = record.sudo() if record.id else record
+ value = sudo_record if record.id else record
# traverse the intermediate fields, and keep at most one record
for name in self.related[:-1]:
value = value[name][:1]
@@ -524,23 +527,27 @@ class Field(object):
def get_description(self, env):
""" Return a dictionary that describes the field `self`. """
desc = {'type': self.type}
- # determine 'store'
- if self.store:
- # if the corresponding column is a function field, check the column
- column = env[self.model_name]._columns.get(self.name)
- desc['store'] = bool(getattr(column, 'store', True))
- else:
- desc['store'] = False
- # determine other attributes
for attr, prop in self.description_attrs:
value = getattr(self, prop)
if callable(value):
value = value(env)
- if value:
+ if value is not None:
desc[attr] = value
+
return desc
# properties used by get_description()
+
+ def _description_store(self, env):
+ if self.store:
+ # if the corresponding column is a function field, check the column
+ column = env[self.model_name]._columns.get(self.name)
+ return bool(getattr(column, 'store', True))
+ return False
+
+ def _description_searchable(self, env):
+ return self._description_store(env) or bool(self.search)
+
_description_depends = property(attrgetter('depends'))
_description_related = property(attrgetter('related'))
_description_company_dependent = property(attrgetter('company_dependent'))
@@ -548,6 +555,8 @@ class Field(object):
_description_required = property(attrgetter('required'))
_description_states = property(attrgetter('states'))
_description_groups = property(attrgetter('groups'))
+ _description_change_default = property(attrgetter('change_default'))
+ _description_deprecated = property(attrgetter('deprecated'))
def _description_string(self, env):
if self.string and env.lang:
@@ -599,6 +608,8 @@ class Field(object):
_column_required = property(attrgetter('required'))
_column_states = property(attrgetter('states'))
_column_groups = property(attrgetter('groups'))
+ _column_change_default = property(attrgetter('change_default'))
+ _column_deprecated = property(attrgetter('deprecated'))
############################################################################
#
@@ -609,11 +620,13 @@ class Field(object):
""" return the null value for this field in the given environment """
return False
- def convert_to_cache(self, value, env, validate=True):
+ def convert_to_cache(self, value, record, validate=True):
""" convert `value` to the cache level in `env`; `value` may come from
an assignment, or have the format of methods :meth:`BaseModel.read`
or :meth:`BaseModel.write`
+ :param record: the target record for the assignment, or an empty recordset
+
:param bool validate: when True, field-specific validation of
`value` will be performed
"""
@@ -698,7 +711,7 @@ class Field(object):
record.ensure_one()
# adapt value to the cache level
- value = self.convert_to_cache(value, env)
+ value = self.convert_to_cache(value, record)
if env.in_draft or not record.id:
# determine dependent fields
@@ -861,7 +874,7 @@ class Boolean(Field):
""" Boolean field. """
type = 'boolean'
- def convert_to_cache(self, value, env, validate=True):
+ def convert_to_cache(self, value, record, validate=True):
return bool(value)
def convert_to_export(self, value, env):
@@ -874,7 +887,7 @@ class Integer(Field):
""" Integer field. """
type = 'integer'
- def convert_to_cache(self, value, env, validate=True):
+ def convert_to_cache(self, value, record, validate=True):
return int(value or 0)
def convert_to_read(self, value, use_name_get=True):
@@ -914,7 +927,7 @@ class Float(Field):
_column_digits = property(lambda self: not callable(self._digits) and self._digits)
_column_digits_compute = property(lambda self: callable(self._digits) and self._digits)
- def convert_to_cache(self, value, env, validate=True):
+ def convert_to_cache(self, value, record, validate=True):
# apply rounding here, otherwise value in cache may be wrong!
if self.digits:
return float_round(float(value or 0.0), precision_digits=self.digits[1])
@@ -948,7 +961,7 @@ class Char(_String):
_related_size = property(attrgetter('size'))
_description_size = property(attrgetter('size'))
- def convert_to_cache(self, value, env, validate=True):
+ def convert_to_cache(self, value, record, validate=True):
return bool(value) and ustr(value)[:self.size]
@@ -962,7 +975,7 @@ class Text(_String):
"""
type = 'text'
- def convert_to_cache(self, value, env, validate=True):
+ def convert_to_cache(self, value, record, validate=True):
return bool(value) and ustr(value)
@@ -970,7 +983,7 @@ class Html(_String):
""" Html field. """
type = 'html'
- def convert_to_cache(self, value, env, validate=True):
+ def convert_to_cache(self, value, record, validate=True):
return bool(value) and html_sanitize(value)
@@ -1019,7 +1032,7 @@ class Date(Field):
""" Convert a :class:`date` value into the format expected by the ORM. """
return value.strftime(DATE_FORMAT)
- def convert_to_cache(self, value, env, validate=True):
+ def convert_to_cache(self, value, record, validate=True):
if not value:
return False
if isinstance(value, basestring):
@@ -1084,7 +1097,7 @@ class Datetime(Field):
""" Convert a :class:`datetime` value into the format expected by the ORM. """
return value.strftime(DATETIME_FORMAT)
- def convert_to_cache(self, value, env, validate=True):
+ def convert_to_cache(self, value, record, validate=True):
if not value:
return False
if isinstance(value, basestring):
@@ -1109,12 +1122,16 @@ class Selection(Field):
It is given as either a list of pairs (`value`, `string`), or a
model method, or a method name.
+ :param selection_add: provides an extension of the selection in the case
+ of an overridden field. It is a list of pairs (`value`, `string`).
+
The attribute `selection` is mandatory except in the case of related
fields (see :ref:`field-related`) or field extensions
(see :ref:`field-incremental-definition`).
"""
type = 'selection'
- selection = None # [(value, string), ...], model method or method name
+ selection = None # [(value, string), ...], function or method name
+ selection_add = None # [(value, string), ...]
def __init__(self, selection=None, string=None, **kwargs):
if callable(selection):
@@ -1128,6 +1145,23 @@ class Selection(Field):
field = self.related_field
self.selection = lambda model: field._description_selection(model.env)
+ def _setup_regular(self, env):
+ super(Selection, self)._setup_regular(env)
+ # determine selection (applying extensions)
+ cls = type(env[self.model_name])
+ selection = None
+ for field in resolve_all_mro(cls, self.name, reverse=True):
+ if isinstance(field, type(self)):
+ # We cannot use field.selection or field.selection_add here
+ # because those attributes are overridden by `set_class_name`.
+ if 'selection' in field._attrs:
+ selection = field._attrs['selection']
+ if 'selection_add' in field._attrs:
+ selection = selection + field._attrs['selection_add']
+ else:
+ selection = None
+ self.selection = selection
+
def _description_selection(self, env):
""" return the selection list (pairs (value, label)); labels are
translated according to context language
@@ -1164,10 +1198,10 @@ class Selection(Field):
selection = selection(env[self.model_name])
return [value for value, _ in selection]
- def convert_to_cache(self, value, env, validate=True):
+ def convert_to_cache(self, value, record, validate=True):
if not validate:
return value or False
- if value in self.get_values(env):
+ if value in self.get_values(record.env):
return value
elif not value:
return False
@@ -1204,14 +1238,14 @@ class Reference(Selection):
_column_size = property(attrgetter('size'))
- def convert_to_cache(self, value, env, validate=True):
+ def convert_to_cache(self, value, record, validate=True):
if isinstance(value, BaseModel):
- if ((not validate or value._name in self.get_values(env))
+ if ((not validate or value._name in self.get_values(record.env))
and len(value) <= 1):
- return value.with_env(env) or False
+ return value.with_env(record.env) or False
elif isinstance(value, basestring):
res_model, res_id = value.split(',')
- return env[res_model].browse(int(res_id))
+ return record.env[res_model].browse(int(res_id))
elif not value:
return False
raise ValueError("Wrong value for %s: %r" % (self, value))
@@ -1302,19 +1336,19 @@ class Many2one(_Relational):
""" Update the cached value of `self` for `records` with `value`. """
records._cache[self] = value
- def convert_to_cache(self, value, env, validate=True):
+ def convert_to_cache(self, value, record, validate=True):
if isinstance(value, (NoneType, int)):
- return env[self.comodel_name].browse(value)
+ return record.env[self.comodel_name].browse(value)
if isinstance(value, BaseModel):
if value._name == self.comodel_name and len(value) <= 1:
- return value.with_env(env)
+ return value.with_env(record.env)
raise ValueError("Wrong value for %s: %r" % (self, value))
elif isinstance(value, tuple):
- return env[self.comodel_name].browse(value[0])
+ return record.env[self.comodel_name].browse(value[0])
elif isinstance(value, dict):
- return env[self.comodel_name].new(value)
+ return record.env[self.comodel_name].new(value)
else:
- return env[self.comodel_name].browse(value)
+ return record.env[self.comodel_name].browse(value)
def convert_to_read(self, value, use_name_get=True):
if use_name_get and value:
@@ -1355,27 +1389,30 @@ class _RelationalMulti(_Relational):
for record in records:
record._cache[self] = record[self.name] | value
- def convert_to_cache(self, value, env, validate=True):
+ def convert_to_cache(self, value, record, validate=True):
if isinstance(value, BaseModel):
if value._name == self.comodel_name:
- return value.with_env(env)
+ return value.with_env(record.env)
elif isinstance(value, list):
# value is a list of record ids or commands
- result = env[self.comodel_name]
+ if not record.id:
+ record = record.browse() # new record has no value
+ result = record[self.name]
+ # modify result with the commands;
+ # beware to not introduce duplicates in result
for command in value:
if isinstance(command, (tuple, list)):
if command[0] == 0:
result += result.new(command[2])
elif command[0] == 1:
- record = result.browse(command[1])
- record.update(command[2])
- result += record
+ result.browse(command[1]).update(command[2])
elif command[0] == 2:
- pass
+ # note: the record will be deleted by write()
+ result -= result.browse(command[1])
elif command[0] == 3:
- pass
+ result -= result.browse(command[1])
elif command[0] == 4:
- result += result.browse(command[1])
+ result += result.browse(command[1]) - result
elif command[0] == 5:
result = result.browse()
elif command[0] == 6:
@@ -1383,10 +1420,10 @@ class _RelationalMulti(_Relational):
elif isinstance(command, dict):
result += result.new(command)
else:
- result += result.browse(command)
+ result += result.browse(command) - result
return result
elif not value:
- return self.null(env)
+ return self.null(record.env)
raise ValueError("Wrong value for %s: %s" % (self, value))
def convert_to_read(self, value, use_name_get=True):
diff --git a/openerp/models.py b/openerp/models.py
index af55db1f1cb..8beb60d53b7 100644
--- a/openerp/models.py
+++ b/openerp/models.py
@@ -841,10 +841,10 @@ class BaseModel(object):
ir_model_data = self.sudo().env['ir.model.data']
data = ir_model_data.search([('model', '=', self._name), ('res_id', '=', self.id)])
if data:
- if data.module:
- return '%s.%s' % (data.module, data.name)
+ if data[0].module:
+ return '%s.%s' % (data[0].module, data[0].name)
else:
- return data.name
+ return data[0].name
else:
postfix = 0
name = '%s_%s' % (self._table, self.id)
@@ -1729,12 +1729,7 @@ class BaseModel(object):
:rtype: list
:return: list of pairs ``(id, text_repr)`` for all matching records.
"""
- args = list(args or [])
- if not self._rec_name:
- _logger.warning("Cannot execute name_search, no _rec_name defined on %s", self._name)
- elif not (name == '' and operator == 'ilike'):
- args += [(self._rec_name, operator, name)]
- return self.search(args, limit=limit).name_get()
+ return self._name_search(name, args, operator, limit=limit)
def _name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100, name_get_uid=None):
# private implementation of name_search, allows passing a dedicated user
@@ -2621,7 +2616,7 @@ class BaseModel(object):
if isinstance(f, fields.many2one) or (isinstance(f, fields.function) and f._type == 'many2one' and f.store):
dest_model = self.pool[f._obj]
- if dest_model._table != 'ir_actions':
+ if dest_model._auto and dest_model._table != 'ir_actions':
self._m2o_fix_foreign_key(cr, self._table, k, dest_model, f.ondelete)
# The field doesn't exist in database. Create it if necessary.
@@ -3627,7 +3622,8 @@ class BaseModel(object):
# put the values of pure new-style fields into cache, and inverse them
if new_vals:
- self._cache.update(self._convert_to_cache(new_vals))
+ for record in self:
+ record._cache.update(record._convert_to_cache(new_vals, update=True))
for key in new_vals:
self._fields[key].determine_inverse(self)
@@ -4973,9 +4969,10 @@ class BaseModel(object):
""" stuff to do right after the registry is built """
pass
- def _patch_method(self, name, method):
+ @classmethod
+ def _patch_method(cls, name, method):
""" Monkey-patch a method for all instances of this model. This replaces
- the method called `name` by `method` in `self`'s class.
+ the method called `name` by `method` in the given class.
The original method is then accessible via ``method.origin``, and it
can be restored with :meth:`~._revert_method`.
@@ -4996,7 +4993,6 @@ class BaseModel(object):
# restore the original method
model._revert_method('write')
"""
- cls = type(self)
origin = getattr(cls, name)
method.origin = origin
# propagate decorators from origin to method, and apply api decorator
@@ -5004,11 +5000,11 @@ class BaseModel(object):
wrapped.origin = origin
setattr(cls, name, wrapped)
- def _revert_method(self, name):
- """ Revert the original method of `self` called `name`.
+ @classmethod
+ def _revert_method(cls, name):
+ """ Revert the original method called `name` in the given class.
See :meth:`~._patch_method`.
"""
- cls = type(self)
method = getattr(cls, name)
setattr(cls, name, method.origin)
@@ -5098,11 +5094,17 @@ class BaseModel(object):
context = dict(args[0] if args else self._context, **kwargs)
return self.with_env(self.env(context=context))
- def _convert_to_cache(self, values, validate=True):
- """ Convert the `values` dictionary into cached values. """
+ def _convert_to_cache(self, values, update=False, validate=True):
+ """ Convert the `values` dictionary into cached values.
+
+ :param update: whether the conversion is made for updating `self`;
+ this is necessary for interpreting the commands of *2many fields
+ :param validate: whether values must be checked
+ """
fields = self._fields
+ target = self if update else self.browse()
return {
- name: fields[name].convert_to_cache(value, self.env, validate=validate)
+ name: fields[name].convert_to_cache(value, target, validate=validate)
for name, value in values.iteritems()
if name in fields
}
@@ -5191,7 +5193,7 @@ class BaseModel(object):
exist in the database.
"""
record = self.browse([NewId()])
- record._cache.update(self._convert_to_cache(values))
+ record._cache.update(record._convert_to_cache(values, update=True))
if record.env.in_onchange:
# The cache update does not set inverse fields, so do it manually.
diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py
index e00204caf9e..2d9c38bbd4a 100644
--- a/openerp/osv/fields.py
+++ b/openerp/osv/fields.py
@@ -146,8 +146,10 @@ class _column(object):
('translate', self.translate),
('domain', self._domain),
('context', self._context),
+ ('change_default', self.change_default),
+ ('deprecated', self.deprecated),
]
- return dict(item for item in items if items[1])
+ return dict(item for item in items if item[1])
def restart(self):
pass