diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 1092ec33aeaa7c37259bb7ebc518860410388350..5b45f8dd02abe53b266875317cf539b6fd911b93 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 c4fd782bea2349a8e49cc4dde1d7c1ba74324045..d994237e094ec44ad1cff8424d1071684bc40a05 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 ebe66757b096c51a554893475b08e614c6b3bb9d..f79aa77ddc9a358d28f1d23a4436cdc28bed7249 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 8b58c3efd6107b769f6bc0c9090aa393c0380800..b80b93bc3a5fa53d5aac056f6a6c4217a92deaeb 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 66f843cd03a7b14e5713f70891b0571e1f363370..a33982b38600f7206c69a217ce1df2a293702126 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 e2e94233861dcabc56c052022da256c7b1dcdebe..2ede7b27b9d818be2b9aac5b022869804e78920b 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 016313b5e44cfc40cd327e368ba9823a19b6b17a..a2fbed7492e5856dbd761b5583713b84380ced35 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 502598193a637e7b3c2b745115b32d28e211f59c..620c612ecf15bbc699c0e7d1aae8bc39bfd802d7 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -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> diff --git a/addons/project_issue/report/project_issue_report_view.xml b/addons/project_issue/report/project_issue_report_view.xml index 3382c6e36255869ae587843051343cb3b4f0b3e8..6747387513414ff730c0aeba20982c85eaeaca43 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 @@ <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> diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml index 8402f9965f5a652a27627124b3e3004ed604f75c..9228e71e1316822a79e272e36c671a9361207781 100644 --- a/addons/purchase/purchase_view.xml +++ b/addons/purchase/purchase_view.xml @@ -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> diff --git a/addons/resource/resource.py b/addons/resource/resource.py index 5a43234ac46ed7ba2286014ab0e2845e358e3c27..b16fd2105933bb5d48a342c680349f466584c942 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 42dad7d1062b4877942bdd2d2bfb06e66b94bb31..818b3ebe1afe1bc4952c978751e87eb8dde0d2d9 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 566179c2a3861223076e695906229102d1279a46..7ed70f7bc6854397e95a41d547756d67f63b9ef1 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 d93cb131e0592665af2c69af867e906694b7f790..828347b68a55412641b21004048881fd3d237d7b 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: '<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 } }; diff --git a/addons/web_kanban/static/src/xml/web_kanban.xml b/addons/web_kanban/static/src/xml/web_kanban.xml index c1d504e51be69a40fe5fc9980e53eea824608d83..aebdbacb9dda838bc560788994b35b584e07346b 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 @@ <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> diff --git a/addons/website_livechat/website_livechat_data.xml b/addons/website_livechat/website_livechat_data.xml index 55c59895664de1e365ddd5e69a84478873b853c1..5e37397c471f46ee95b31b628dc3d7c45f2837e4 100644 --- a/addons/website_livechat/website_livechat_data.xml +++ b/addons/website_livechat/website_livechat_data.xml @@ -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> diff --git a/addons/website_sale_options/models/sale_order.py b/addons/website_sale_options/models/sale_order.py index f16feaecc7b7319dfadc7ecc370d08c9fb4e9f08..85eaab214f7003cdb9f2df7341c670ac48613980 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 f1ddf4f1b427633b78d718756b3286846a7cd4c7..7102c009264701ddd0569ee3459e57d6aeea2520 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 = {} + + 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 - 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] + 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) + + # 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 - 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) + res = super(users_view, self).read(cr, uid, ids, other_fields, context=context, load=load) - if res and group_fields: + # 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} - - def change_password_button(self, cr, uid, id, context=None): - wizard = self.browse(cr, uid, id, context=context)[0] + 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) + ] + + _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 ebbe71e0521d1ac292eaefcceccc17fd94004159..f620dfe53a97fad252c12225e8f966bedcec51a7 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 b663d911f60ec1646cfa6d5a3c009d5a2cc18918..8039e698669d4ad65957297a473718eebe9a4518 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 d85ce82eccb7457fd803d568554e49fc163b4622..1b3cdabe29c459dbd6cfa0735cf2a0cd141ad227 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 af55db1f1cbbf825668cfe4e8770869cecb9f84a..8beb60d53b7129aac29e692f36f9e7ccd9d80cab 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 e00204caf9e79d5d3cc259e5bc097eaa3072fa27..2d9c38bbd4a050a634c43de1f6208bc1f3cc1bcf 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