Merge branch '8.0' into 8.0-pos-imps2-fva
This commit is contained in:
@@ -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'))
|
||||
|
||||
+11
-5
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'),
|
||||
}
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
+15
-4
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -90,16 +90,18 @@
|
||||
<field name="description" placeholder="Add an internal note..." groups="base.group_user"/>
|
||||
</page>
|
||||
<page string="Extra Info" groups="project.group_project_manager,project.group_project_user">
|
||||
<group string="Statistics">
|
||||
<field name="day_open"/>
|
||||
<field name="day_close"/>
|
||||
<field name="working_hours_open" widget="float_time"/>
|
||||
<field name="working_hours_close" widget="float_time"/>
|
||||
<field name="inactivity_days"/>
|
||||
<field name="days_since_creation"/>
|
||||
</group>
|
||||
<group string="Status" groups="base.group_no_one">
|
||||
<field name="active"/>
|
||||
<group>
|
||||
<group string="Statistics">
|
||||
<field name="day_open"/>
|
||||
<field name="day_close"/>
|
||||
<field name="working_hours_open" widget="float_time"/>
|
||||
<field name="working_hours_close" widget="float_time"/>
|
||||
<field name="inactivity_days"/>
|
||||
<field name="days_since_creation"/>
|
||||
</group>
|
||||
<group string="Status" groups="base.group_no_one">
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
@@ -21,13 +21,6 @@
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="context">{'group_by': ['project_id', 'user_id']}</field>
|
||||
</record>
|
||||
<record id="filter_issue_report_reviewer" model="ir.filters">
|
||||
<field name="name">By Reviewer</field>
|
||||
<field name="model_id">project.issue.report</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="user_id" eval="False"/>
|
||||
<field name="context">{'group_by': ['project_id', 'reviewer_id']}</field>
|
||||
</record>
|
||||
|
||||
<record id="view_project_issue_report_filter" model="ir.ui.view">
|
||||
<field name="name">project.issue.report.select</field>
|
||||
|
||||
@@ -587,7 +587,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<div name="options" position="inside">
|
||||
<div>
|
||||
<field name="purchase_ok" attrs="{'readonly': [('is_product_variant', '=', False)]}"/>
|
||||
<field name="purchase_ok"/>
|
||||
<label for="purchase_ok"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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: '<search><field name="dummy" widget="dummy"/></search>'
|
||||
};
|
||||
@@ -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 } };
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
<ul class="oe_dropdown_menu oe_kanban_group_dropdown">
|
||||
<li><a data-action="toggle_fold" href="#">Fold</a></li>
|
||||
<t t-if="widget.view.grouped_by_m2o and widget.value">
|
||||
<li t-if="parent.is_action_enabled('group_edit')"><a data-action="edit" href="#">Edit</a></li>
|
||||
<li t-if="parent.is_action_enabled('group_delete')"><a data-action="delete" href="#">Delete</a></li>
|
||||
<li t-if="parent && parent.is_action_enabled('group_edit')"><a data-action="edit" href="#">Edit</a></li>
|
||||
<li t-if="parent && parent.is_action_enabled('group_delete')"><a data-action="delete" href="#">Delete</a></li>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="channel_website" model="im_livechat.channel">
|
||||
<field name="name">YourWebsiteWithOdoo.com</field>
|
||||
<field name="default_message">Hello, how may I help you?</field>
|
||||
</record>
|
||||
|
||||
<record id="website.default_website" model="website">
|
||||
<field name="channel_id" ref="im_livechat.channel_website"></field>
|
||||
<field name="channel_id" ref="website_livechat.channel_website"></field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
+83
-46
@@ -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):
|
||||
|
||||
+23
-21
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user