[MERGE] Merged with main server

bzr revid: tde@openerp.com-20120402071259-pot0kr7yrkdcd2wj
This commit is contained in:
Thibault Delavallée 2012-04-02 09:12:59 +02:00
commit 5fd95c5769
34 changed files with 1537 additions and 1065 deletions

View File

@ -75,6 +75,7 @@
'security/base_security.xml',
'publisher_warranty/publisher_warranty_view.xml',
'security/ir.model.access.csv',
'security/ir.model.access-1.csv', # res.partner.address is deprecated; it is still there for backward compability only and will be removed in next version
'res/res_widget_view.xml',
'res/res_widget_data.xml',
'publisher_warranty/publisher_warranty_data.xml',

View File

@ -1058,14 +1058,9 @@
<record id="main_partner" model="res.partner">
<field name="name">Your Company</field>
<!-- Address and Company ID will be set later -->
<field name="address" eval="[]"/>
<field name="company_id" eval="None"/>
<field name="customer" eval="False"/>
</record>
<record id="main_address" model="res.partner.address">
<field name="partner_id" ref="main_partner"/>
<field name="type">default</field>
<field name="company_id" eval="None"/>
<field name="is_company" eval="True"/>
</record>
<!-- Currencies -->
@ -1102,9 +1097,6 @@
<record id="main_partner" model="res.partner">
<field name="company_id" ref="main_company"/>
</record>
<record id="main_address" model="res.partner.address">
<field name="company_id" ref="main_company"/>
</record>
<record id="EUR" model="res.currency">
<field name="company_id" ref="main_company"/>
</record>

View File

@ -2,8 +2,8 @@
<openerp>
<data noupdate="1">
<record id="demo_address" model="res.partner.address">
<field name="name">Fabien Dupont</field>
<record id="demo_address" model="res.partner">
<field name="name">Fabien D'souza</field>
<field name="street">Chaussee de Namur</field>
<field name="zip">1367</field>
<field name="city">Gerompont</field>

File diff suppressed because it is too large Load Diff

View File

@ -510,7 +510,7 @@ class actions_server(osv.osv):
'code':fields.text('Python Code', help="Python code to be executed if condition is met.\n"
"It is a Python block that can use the same values as for the condition field"),
'sequence': fields.integer('Sequence', help="Important when you deal with multiple actions, the execution order will be decided based on this, low number is higher priority."),
'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create)."),
'model_id': fields.many2one('ir.model', 'Object', required=True, help="Select the object on which the action will work (read, write, create).", ondelete='cascade'),
'action_id': fields.many2one('ir.actions.actions', 'Client Action', help="Select the Action Window, Report, Wizard to be executed."),
'trigger_name': fields.selection(_select_signals, string='Trigger Signal', size=128, help="The workflow signal to trigger"),
'wkf_model_id': fields.many2one('ir.model', 'Target Object', help="The object that should receive the workflow signal (must have an associated workflow)"),

View File

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# OpenERP, Open Source Business Applications
# Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -23,20 +24,21 @@ import re
import time
import types
from osv import fields,osv
import netsvc
from osv.orm import except_orm, browse_record
import tools
from tools.safe_eval import safe_eval as eval
from tools import config
from tools.translate import _
import pooler
from openerp.osv import fields,osv
from openerp import netsvc, pooler, tools
from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools import config
from openerp.tools.translate import _
from openerp.osv.orm import except_orm, browse_record, EXT_ID_PREFIX_FK, \
EXT_ID_PREFIX_M2M_TABLE, EXT_ID_PREFIX_CONSTRAINT
_logger = logging.getLogger(__name__)
MODULE_UNINSTALL_FLAG = '_force_unlink'
def _get_fields_type(self, cr, uid, context=None):
# Avoid too many nested `if`s below, as RedHat's Python 2.6
# break on it. See bug 939653.
# break on it. See bug 939653.
return sorted([(k,k) for k,v in fields.__dict__.iteritems()
if type(v) == types.TypeType and \
issubclass(v, fields._column) and \
@ -73,7 +75,7 @@ class ir_model(osv.osv):
def _search_osv_memory(self, cr, uid, model, name, domain, context=None):
if not domain:
return []
field, operator, value = domain[0]
__, operator, value = domain[0]
if operator not in ['=', '!=']:
raise osv.except_osv(_('Invalid search criterions'), _('The osv_memory field can only be compared with = and != operator.'))
value = bool(value) if operator == '=' else not bool(value)
@ -136,13 +138,33 @@ class ir_model(osv.osv):
super(ir_model, self).search(cr, uid, domain, limit=limit, context=context),
context=context)
def _drop_table(self, cr, uid, ids, context=None):
for model in self.browse(cr, uid, ids, context):
model_pool = self.pool.get(model.model)
cr.execute('select relkind from pg_class where relname=%s', (model_pool._table,))
result = cr.fetchone()
if result and result[0] == 'v':
cr.execute('DROP view %s' % (model_pool._table,))
elif result and result[0] == 'r':
cr.execute('DROP TABLE %s' % (model_pool._table,))
return True
def unlink(self, cr, user, ids, context=None):
for model in self.browse(cr, user, ids, context):
if model.state != 'manual':
raise except_orm(_('Error'), _("You can not remove the model '%s' !") %(model.name,))
# Prevent manual deletion of module tables
if context is None: context = {}
if isinstance(ids, (int, long)):
ids = [ids]
if not context.get(MODULE_UNINSTALL_FLAG) and \
any(model.state != 'manual' for model in self.browse(cr, user, ids, context)):
raise except_orm(_('Error'), _("Model '%s' contains module data and cannot be removed!") % (model.name,))
self._drop_table(cr, user, ids, context)
res = super(ir_model, self).unlink(cr, user, ids, context)
pooler.restart_pool(cr.dbname)
if not context.get(MODULE_UNINSTALL_FLAG):
# only reload pool for normal unlink. For module uninstall the
# reload is done independently in openerp.modules.loading
pooler.restart_pool(cr.dbname)
return res
def write(self, cr, user, ids, vals, context=None):
@ -266,16 +288,30 @@ class ir_model_fields(osv.osv):
('size_gt_zero', 'CHECK (size>0)',_size_gt_zero_msg ),
]
def _drop_column(self, cr, uid, ids, context=None):
for field in self.browse(cr, uid, ids, context):
model = self.pool.get(field.model)
cr.execute('select relkind from pg_class where relname=%s', (model._table,))
result = cr.fetchone()
cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s' and column_name='%s'" %(model._table, field.name))
column_name = cr.fetchone()
if column_name and (result and result[0] == 'r'):
cr.execute('ALTER table "%s" DROP column "%s" cascade' % (model._table, field.name))
model._columns.pop(field.name, None)
return True
def unlink(self, cr, user, ids, context=None):
for field in self.browse(cr, user, ids, context):
if field.state <> 'manual':
raise except_orm(_('Error'), _("You cannot remove the field '%s' !") %(field.name,))
#
# MAY BE ADD A ALTER TABLE DROP ?
#
#Removing _columns entry for that table
self.pool.get(field.model)._columns.pop(field.name,None)
return super(ir_model_fields, self).unlink(cr, user, ids, context)
# Prevent manual deletion of module columns
if context is None: context = {}
if isinstance(ids, (int, long)):
ids = [ids]
if not context.get(MODULE_UNINSTALL_FLAG) and \
any(field.state != 'manual' for field in self.browse(cr, user, ids, context)):
raise except_orm(_('Error'), _("This column contains module data and cannot be removed!"))
self._drop_column(cr, user, ids, context)
res = super(ir_model_fields, self).unlink(cr, user, ids, context)
return res
def create(self, cr, user, vals, context=None):
if 'model_id' in vals:
@ -295,7 +331,7 @@ class ir_model_fields(osv.osv):
raise except_orm(_('Error'), _("Custom fields must have a name that starts with 'x_' !"))
if vals.get('relation',False) and not self.pool.get('ir.model').search(cr, user, [('model','=',vals['relation'])]):
raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
raise except_orm(_('Error'), _("Model %s does not exist!") % vals['relation'])
if self.pool.get(vals['model']):
self.pool.get(vals['model']).__init__(self.pool, cr)
@ -414,7 +450,7 @@ class ir_model_fields(osv.osv):
ctx = context.copy()
ctx.update({'select': vals.get('select_level','0'),'update_custom_fields':True})
for model_key, patch_struct in models_patch.items():
for __, patch_struct in models_patch.items():
obj = patch_struct[0]
for col_name, col_prop, val in patch_struct[1]:
setattr(obj._columns[col_name], col_prop, val)
@ -622,10 +658,10 @@ class ir_model_data(osv.osv):
def __init__(self, pool, cr):
osv.osv.__init__(self, pool, cr)
self.doinit = True
# also stored in pool to avoid being discarded along with this osv instance
if getattr(pool, 'model_data_reference_ids', None) is None:
self.pool.model_data_reference_ids = {}
self.loads = self.pool.model_data_reference_ids
def _auto_init(self, cr, context=None):
@ -670,6 +706,7 @@ class ir_model_data(osv.osv):
id = False
return id
def unlink(self, cr, uid, ids, context=None):
""" Regular unlink method, but make sure to clear the caches. """
self._get_id.clear_cache(self)
@ -680,17 +717,14 @@ class ir_model_data(osv.osv):
model_obj = self.pool.get(model)
if not context:
context = {}
# records created during module install should result in res.log entries that are already read!
context = dict(context, res_log_read=True)
if xml_id and ('.' in xml_id):
assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id)
module, xml_id = xml_id.split('.')
if (not xml_id) and (not self.doinit):
return False
action_id = False
if xml_id:
cr.execute('''SELECT imd.id, imd.res_id, md.id, imd.model
FROM ir_model_data imd LEFT JOIN %s md ON (imd.res_id = md.id)
@ -794,52 +828,139 @@ class ir_model_data(osv.osv):
cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name))
return True
def _module_data_uninstall(self, cr, uid, ids, context=None):
"""Deletes all the records referenced by the ir.model.data entries
``ids`` along with their corresponding database backed (including
dropping tables, columns, FKs, etc, as long as there is no other
ir.model.data entry holding a reference to them (which indicates that
they are still owned by another module).
Attempts to perform the deletion in an appropriate order to maximize
the chance of gracefully deleting all records.
This step is performed as part of the full uninstallation of a module.
"""
if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"):
raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module')))
context = dict(context or {})
context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
ids_set = set(ids)
wkf_todo = []
to_unlink = []
to_drop_table = []
ids.sort()
ids.reverse()
for data in self.browse(cr, uid, ids, context):
model = data.model
res_id = data.res_id
model_obj = self.pool.get(model)
name = tools.ustr(data.name)
if name.startswith(EXT_ID_PREFIX_FK) or name.startswith(EXT_ID_PREFIX_M2M_TABLE)\
or name.startswith(EXT_ID_PREFIX_CONSTRAINT):
# double-check we are really going to delete all the owners of this schema element
cr.execute("""SELECT id from ir_model_data where name = %s and res_id IS NULL""", (data.name,))
external_ids = [x[0] for x in cr.fetchall()]
if (set(external_ids)-ids_set):
# as installed modules have defined this element we must not delete it!
continue
if name.startswith(EXT_ID_PREFIX_FK):
name = name[len(EXT_ID_PREFIX_FK):]
# test if FK exists on this table (it could be on a related m2m table, in which case we ignore it)
cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('f', name, model_obj._table))
if cr.fetchone():
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
_logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
continue
if name.startswith(EXT_ID_PREFIX_M2M_TABLE):
name = name[len(EXT_ID_PREFIX_M2M_TABLE):]
cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,))
if cr.fetchone() and not name in to_drop_table:
to_drop_table.append(name)
continue
if name.startswith(EXT_ID_PREFIX_CONSTRAINT):
name = name[len(EXT_ID_PREFIX_CONSTRAINT):]
# test if constraint exists
cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid)
WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", ('u', name, model_obj._table))
if cr.fetchone():
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
_logger.info('Dropped CONSTRAINT %s@%s', name, model)
continue
pair_to_unlink = (model, res_id)
if pair_to_unlink not in to_unlink:
to_unlink.append(pair_to_unlink)
if model == 'workflow.activity':
# Special treatment for workflow activities: temporarily revert their
# incoming transition and trigger an update to force all workflow items
# to move out before deleting them
cr.execute('select res_type,res_id from wkf_instance where id IN (select inst_id from wkf_workitem where act_id=%s)', (res_id,))
wkf_todo.extend(cr.fetchall())
cr.execute("update wkf_transition set condition='True', group_id=NULL, signal=NULL,act_to=act_from,act_from=%s where act_to=%s", (res_id,res_id))
wf_service = netsvc.LocalService("workflow")
for model,res_id in wkf_todo:
try:
wf_service.trg_write(uid, model, res_id, cr)
except:
_logger.info('Unable to force processing of workflow for item %s@%s in order to leave activity to be deleted', res_id, model)
# drop m2m relation tables
for table in to_drop_table:
cr.execute('DROP TABLE %s CASCADE'% (table),)
_logger.info('Dropped table %s', table)
def unlink_if_refcount(to_unlink):
for model, res_id in to_unlink:
external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
if (set(external_ids)-ids_set):
# if other modules have defined this record, we must not delete it
return
_logger.info('Deleting %s@%s', res_id, model)
try:
self.pool.get(model).unlink(cr, uid, [res_id], context=context)
except:
_logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
# Remove non-model records first, then model fields, and finish with models
unlink_if_refcount((model, res_id) for model, res_id in to_unlink
if model not in ('ir.model','ir.model.fields'))
unlink_if_refcount((model, res_id) for model, res_id in to_unlink
if model == 'ir.model.fields')
unlink_if_refcount((model, res_id) for model, res_id in to_unlink
if model == 'ir.model')
cr.commit()
def _process_end(self, cr, uid, modules):
""" Clear records removed from updated module data.
This method is called at the end of the module loading process.
It is meant to removed records that are no longer present in the
updated data. Such records are recognised as the one with an xml id
and a module in ir_model_data and noupdate set to false, but not
present in self.loads.
"""
if not modules:
return True
modules = list(modules)
module_in = ",".join(["%s"] * len(modules))
cr.execute('select id,name,model,res_id,module from ir_model_data where module IN (' + module_in + ') and noupdate=%s', modules + [False])
wkf_todo = []
to_unlink = []
for (id, name, model, res_id,module) in cr.fetchall():
cr.execute("""SELECT id,name,model,res_id,module FROM ir_model_data
WHERE module IN %s AND res_id IS NOT NULL AND noupdate=%s""",
(tuple(modules), False))
for (id, name, model, res_id, module) in cr.fetchall():
if (module,name) not in self.loads:
to_unlink.append((model,res_id))
if model=='workflow.activity':
cr.execute('select res_type,res_id from wkf_instance where id IN (select inst_id from wkf_workitem where act_id=%s)', (res_id,))
wkf_todo.extend(cr.fetchall())
cr.execute("update wkf_transition set condition='True', group_id=NULL, signal=NULL,act_to=act_from,act_from=%s where act_to=%s", (res_id,res_id))
cr.execute("delete from wkf_transition where act_to=%s", (res_id,))
for model,id in wkf_todo:
wf_service = netsvc.LocalService("workflow")
wf_service.trg_write(uid, model, id, cr)
cr.commit()
if not config.get('import_partial'):
for (model, res_id) in to_unlink:
if self.pool.get(model):
_logger.info('Deleting %s@%s', res_id, model)
try:
self.pool.get(model).unlink(cr, uid, [res_id])
cr.commit()
except Exception:
cr.rollback()
_logger.warning(
'Could not delete obsolete record with id: %d of model %s\n'
'There should be some relation that points to this resource\n'
'You should manually fix this and restart with --update=module',
res_id, model)
return True
ir_model_data()
self.pool.get(model).unlink(cr, uid, [res_id])
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
# OpenERP, Open Source Business Applications
# Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -15,19 +15,18 @@
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import fields, osv
from tools import graph
import netsvc
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp import netsvc
class workflow(osv.osv):
_name = "workflow"
_table = "wkf"
_order = "name"
# _log_access = False
_columns = {
'name': fields.char('Name', size=64, required=True),
'osv': fields.char('Resource Object', size=64, required=True,select=True),
@ -46,78 +45,33 @@ class workflow(osv.osv):
return super(workflow, self).write(cr, user, ids, vals, context=context)
def get_active_workitems(self, cr, uid, res, res_id, context=None):
cr.execute('select * from wkf where osv=%s limit 1',(res,))
wkfinfo = cr.dictfetchone()
workitems = []
if wkfinfo:
cr.execute('SELECT id FROM wkf_instance \
WHERE res_id=%s AND wkf_id=%s \
ORDER BY state LIMIT 1',
(res_id, wkfinfo['id']))
inst_id = cr.fetchone()
cr.execute('select act_id,count(*) from wkf_workitem where inst_id=%s group by act_id', (inst_id,))
workitems = dict(cr.fetchall())
workitems = dict(cr.fetchall())
return {'wkf': wkfinfo, 'workitems': workitems}
#
# scale = (vertical-distance, horizontal-distance, min-node-width(optional), min-node-height(optional), margin(default=20))
#
# def graph_get(self, cr, uid, id, scale, context={}):
#
# nodes= []
# nodes_name = []
# transitions = []
# start = []
# tres = {}
# no_ancester = []
# workflow = self.browse(cr, uid, id, context)
# for a in workflow.activities:
# nodes_name.append((a.id,a.name))
# nodes.append(a.id)
# if a.flow_start:
# start.append(a.id)
# else:
# if not a.in_transitions:
# no_ancester.append(a.id)
#
# for t in a.out_transitions:
# transitions.append((a.id, t.act_to.id))
# tres[t.id] = (a.id, t.act_to.id)
#
#
# g = graph(nodes, transitions, no_ancester)
# g.process(start)
# g.scale(*scale)
# result = g.result_get()
# results = {}
#
# for node in nodes_name:
# results[str(node[0])] = result[node[0]]
# results[str(node[0])]['name'] = node[1]
#
# return {'nodes': results, 'transitions': tres}
def create(self, cr, user, vals, context=None):
if not context:
context={}
wf_service = netsvc.LocalService("workflow")
wf_service.clear_cache(cr, user)
return super(workflow, self).create(cr, user, vals, context=context)
workflow()
class wkf_activity(osv.osv):
_name = "workflow.activity"
_table = "wkf_activity"
_order = "name"
# _log_access = False
_columns = {
'name': fields.char('Name', size=64, required=True),
'wkf_id': fields.many2one('workflow', 'Workflow', required=True, select=True, ondelete='cascade'),
@ -138,22 +92,29 @@ class wkf_activity(osv.osv):
'join_mode': lambda *a: 'XOR',
'split_mode': lambda *a: 'XOR',
}
def unlink(self, cr, uid, ids, context=None):
if context is None: context = {}
if not context.get('_force_unlink') and self.pool.get('workflow.workitem').search(cr, uid, [('act_id', 'in', ids)]):
raise osv.except_osv(_('Operation forbidden'),
_('Please make sure no workitems refer to an activity before deleting it!'))
super(wkf_activity, self).unlink(cr, uid, ids, context=context)
wkf_activity()
class wkf_transition(osv.osv):
_table = "wkf_transition"
_name = "workflow.transition"
# _log_access = False
_rec_name = 'signal'
_columns = {
'trigger_model': fields.char('Trigger Object', size=128),
'trigger_expr_id': fields.char('Trigger Expression', size=128),
'signal': fields.char('Signal (button Name)', size=64,
'signal': fields.char('Signal (button Name)', size=64,
help="When the operation of transition comes from a button pressed in the client form, "\
"signal tests the name of the pressed button. If signal is NULL, no button is necessary to validate this transition."),
'group_id': fields.many2one('res.groups', 'Group Required',
'group_id': fields.many2one('res.groups', 'Group Required',
help="The group that a user must have to be authorized to validate this transition."),
'condition': fields.char('Condition', required=True, size=128,
'condition': fields.char('Condition', required=True, size=128,
help="Expression to be satisfied if we want the transition done."),
'act_from': fields.many2one('workflow.activity', 'Source Activity', required=True, select=True, ondelete='cascade',
help="Source activity. When this activity is over, the condition is tested to determine if we can start the ACT_TO activity."),
@ -194,7 +155,7 @@ class wkf_workitem(osv.osv):
_log_access = False
_rec_name = 'state'
_columns = {
'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="restrict", select=True),
'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="cascade", select=True),
'wkf_id': fields.related('act_id','wkf_id', type='many2one', relation='workflow', string='Workflow'),
'subflow_id': fields.many2one('workflow.instance', 'Subflow', ondelete="cascade", select=True),
'inst_id': fields.many2one('workflow.instance', 'Instance', required=True, ondelete="cascade", select=True),

View File

@ -2,8 +2,7 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
# Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -19,26 +18,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base64
import cStringIO
import imp
import logging
import os
import re
import StringIO
import urllib
import zipfile
import zipimport
import openerp.modules as addons
import pooler
import release
import tools
from tools.parse_version import parse_version
from tools.translate import _
from osv import fields, osv, orm
from openerp import modules, pooler, release, tools
from openerp.tools.parse_version import parse_version
from openerp.tools.translate import _
from openerp.osv import fields, osv, orm
_logger = logging.getLogger(__name__)
@ -95,7 +84,7 @@ class module(osv.osv):
def get_module_info(cls, name):
info = {}
try:
info = addons.load_information_from_description_file(name)
info = modules.load_information_from_description_file(name)
info['version'] = release.major_version + '.' + info['version']
except Exception:
_logger.debug('Error when trying to fetch informations for '
@ -123,7 +112,7 @@ class module(osv.osv):
if field_name is None or 'menus_by_module' in field_name:
dmodels.append('ir.ui.menu')
assert dmodels, "no models for %s" % field_name
for module_rec in self.browse(cr, uid, ids, context=context):
res[module_rec.id] = {
'menus_by_module': [],
@ -165,7 +154,7 @@ class module(osv.osv):
except Exception, e:
_logger.warning('Unknown error while fetching data of %s',
module_rec.name, exc_info=True)
for key, value in res.iteritems():
for key, _ in res.iteritems():
for k, v in res[key].iteritems():
res[key][k] = "\n".join(sorted(v))
return res
@ -184,7 +173,7 @@ class module(osv.osv):
# installed_version refer the latest version (the one on disk)
# latest_version refer the installed version (the one in database)
# published_version refer the version available on the repository
'installed_version': fields.function(_get_latest_version,
'installed_version': fields.function(_get_latest_version,
string='Latest version', type='char'),
'latest_version': fields.char('Installed version', size=64, readonly=True),
'published_version': fields.char('Published Version', size=64, readonly=True),
@ -276,7 +265,7 @@ class module(osv.osv):
while parts:
part = parts.pop()
try:
f, path, descr = imp.find_module(part, path and [path] or None)
_, path, _ = imp.find_module(part, path and [path] or None)
except ImportError:
raise ImportError('No module named %s' % (pydep,))
@ -343,7 +332,6 @@ class module(osv.osv):
# Mark them to be installed.
if to_install_ids:
self.button_install(cr, uid, to_install_ids, context=context)
return dict(ACTION_DICT, name=_('Install'))
def button_immediate_install(self, cr, uid, ids, context=None):
@ -356,7 +344,7 @@ class module(osv.osv):
"""
self.button_install(cr, uid, ids, context=context)
cr.commit()
db, pool = pooler.restart_pool(cr.dbname, update_module=True)
_, pool = pooler.restart_pool(cr.dbname, update_module=True)
config = pool.get('res.config').next(cr, uid, [], context=context) or {}
if config.get('type') not in ('ir.actions.reload', 'ir.actions.act_window_close'):
@ -376,20 +364,49 @@ class module(osv.osv):
self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False})
return True
def module_uninstall(self, cr, uid, ids, context=None):
"""Perform the various steps required to uninstall a module completely
including the deletion of all database structures created by the module:
tables, columns, constraints, etc."""
ir_model_data = self.pool.get('ir.model.data')
modules_to_remove = [m.name for m in self.browse(cr, uid, ids, context)]
data_ids = ir_model_data.search(cr, uid, [('module', 'in', modules_to_remove)])
ir_model_data._module_data_uninstall(cr, uid, data_ids, context)
ir_model_data.unlink(cr, uid, data_ids, context)
self.write(cr, uid, ids, {'state': 'uninstalled'})
return True
def downstream_dependencies(self, cr, uid, ids, known_dep_ids=None,
exclude_states=['uninstalled','uninstallable','to remove'],
context=None):
"""Return the ids of all modules that directly or indirectly depend
on the given module `ids`, and that satisfy the `exclude_states`
filter"""
if not ids: return []
known_dep_ids = set(known_dep_ids or [])
cr.execute('''SELECT DISTINCT m.id
FROM
ir_module_module_dependency d
JOIN
ir_module_module m ON (d.module_id=m.id)
WHERE
d.name IN (SELECT name from ir_module_module where id in %s) AND
m.state NOT IN %s AND
m.id NOT IN %s ''',
(tuple(ids),tuple(exclude_states), tuple(known_dep_ids or ids)))
new_dep_ids = set([m[0] for m in cr.fetchall()])
missing_mod_ids = new_dep_ids - known_dep_ids
known_dep_ids |= new_dep_ids
if missing_mod_ids:
known_dep_ids |= set(self.downstream_dependencies(cr, uid, list(missing_mod_ids),
known_dep_ids, exclude_states,context))
return list(known_dep_ids)
def button_uninstall(self, cr, uid, ids, context=None):
for module in self.browse(cr, uid, ids):
cr.execute('''select m.state,m.name
from
ir_module_module_dependency d
join
ir_module_module m on (d.module_id=m.id)
where
d.name=%s and
m.state not in ('uninstalled','uninstallable','to remove')''', (module.name,))
res = cr.fetchall()
if res:
raise orm.except_orm(_('Error'), _('Some installed modules depend on the module you plan to Uninstall :\n %s') % '\n'.join(map(lambda x: '\t%s: %s' % (x[0], x[1]), res)))
self.write(cr, uid, ids, {'state': 'to remove'})
if any(m.name == 'base' for m in self.browse(cr, uid, ids)):
raise orm.except_orm(_('Error'), _("The `base` module cannot be uninstalled"))
dep_ids = self.downstream_dependencies(cr, uid, ids, context=context)
self.write(cr, uid, ids + dep_ids, {'state': 'to remove'})
return dict(ACTION_DICT, name=_('Uninstall'))
def button_uninstall_cancel(self, cr, uid, ids, context=None):
@ -463,7 +480,7 @@ class module(osv.osv):
known_mods_names = dict([(m.name, m) for m in known_mods])
# iterate through detected modules and update/create them in db
for mod_name in addons.get_modules():
for mod_name in modules.get_modules():
mod = known_mods_names.get(mod_name)
terp = self.get_module_info(mod_name)
values = self.get_values_from_terp(terp)
@ -472,7 +489,7 @@ class module(osv.osv):
updated_values = {}
for key in values:
old = getattr(mod, key)
updated = isinstance(values[key], basestring) and tools.ustr(values[key]) or values[key]
updated = isinstance(values[key], basestring) and tools.ustr(values[key]) or values[key]
if not old == updated:
updated_values[key] = values[key]
if terp.get('installable', True) and mod.state == 'uninstallable':
@ -482,7 +499,7 @@ class module(osv.osv):
if updated_values:
self.write(cr, uid, mod.id, updated_values)
else:
mod_path = addons.get_module_path(mod_name)
mod_path = modules.get_module_path(mod_name)
if not mod_path:
continue
if not terp or not terp.get('installable', True):
@ -511,7 +528,7 @@ class module(osv.osv):
if not download:
continue
zip_content = urllib.urlopen(mod.url).read()
fname = addons.get_module_path(str(mod.name)+'.zip', downloaded=True)
fname = modules.get_module_path(str(mod.name)+'.zip', downloaded=True)
try:
with open(fname, 'wb') as fp:
fp.write(zip_content)
@ -581,17 +598,17 @@ class module(osv.osv):
for mod in self.browse(cr, uid, ids):
if mod.state != 'installed':
continue
modpath = addons.get_module_path(mod.name)
modpath = modules.get_module_path(mod.name)
if not modpath:
# unable to find the module. we skip
continue
for lang in filter_lang:
iso_lang = tools.get_iso_codes(lang)
f = addons.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
f = modules.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
context2 = context and context.copy() or {}
if f and '_' in iso_lang:
iso_lang2 = iso_lang.split('_')[0]
f2 = addons.get_module_resource(mod.name, 'i18n', iso_lang2 + '.po')
f2 = modules.get_module_resource(mod.name, 'i18n', iso_lang2 + '.po')
if f2:
_logger.info('module %s: loading base translation file %s for language %s', mod.name, iso_lang2, lang)
tools.trans_load(cr, f2, lang, verbose=False, context=context)
@ -601,7 +618,7 @@ class module(osv.osv):
# like "en".
if (not f) and '_' in iso_lang:
iso_lang = iso_lang.split('_')[0]
f = addons.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
f = modules.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
if f:
_logger.info('module %s: loading translation file (%s) for language %s', mod.name, iso_lang, lang)
tools.trans_load(cr, f, lang, verbose=False, context=context2)
@ -660,8 +677,12 @@ class module_dependency(osv.osv):
return result
_columns = {
# The dependency name
'name': fields.char('Name', size=128, select=True),
# The module that depends on it
'module_id': fields.many2one('ir.module.module', 'Module', select=True, ondelete='cascade'),
'state': fields.function(_state, type='selection', selection=[
('uninstallable','Uninstallable'),
('uninstalled','Not Installed'),

View File

@ -153,7 +153,9 @@
<group col="6" colspan="2">
<button name="button_install" states="uninstalled" string="Install" icon="terp-gtk-jump-to-ltr" type="object"/>
<button name="button_install_cancel" states="to install" string="Cancel Install" icon="gtk-cancel" type="object"/>
<button name="button_uninstall" states="installed" string="Uninstall (beta)" icon="terp-dialog-close" type="object"/>
<button name="button_uninstall" states="installed" string="Uninstall (beta)"
icon="terp-dialog-close" type="object"
confirm="Do you confirm the uninstallation of this module? This will permanently erase all data currently stored by the module!"/>
<button name="button_uninstall_cancel" states="to remove" string="Cancel Uninstall" icon="gtk-cancel" type="object"/>
<button name="button_upgrade" states="installed" string="Upgrade" icon="terp-gtk-go-back-rtl" type="object"/>
<button name="button_upgrade_cancel" states="to upgrade" string="Cancel Upgrade" icon="gtk-cancel" type="object"/>

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
# OpenERP, Open Source Business Applications
# Copyright (C) 2004-2012 OpenERP SA (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@ -19,9 +19,9 @@
#
##############################################################################
import pooler
from osv import osv, fields
from tools.translate import _
from openerp import pooler
from openerp.osv import osv, fields
from openerp.tools.translate import _
class base_module_upgrade(osv.osv_memory):
""" Module Upgrade """
@ -34,13 +34,6 @@ class base_module_upgrade(osv.osv_memory):
}
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
""" Changes the view dynamically
@param self: The object pointer.
@param cr: A database cursor
@param uid: ID of the user currently logged in
@param context: A standard dictionary
@return: New arch of view.
"""
res = super(base_module_upgrade, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False)
if view_type != 'form':
return res
@ -71,45 +64,39 @@ class base_module_upgrade(osv.osv_memory):
return ids
def default_get(self, cr, uid, fields, context=None):
"""
This function checks for precondition before wizard executes
@param self: The object pointer
@param cr: the current row, from the database cursor,
@param uid: the current users ID for security checks,
@param fields: List of fields for default value
@param context: A standard dictionary for contextual values
"""
mod_obj = self.pool.get('ir.module.module')
ids = self.get_module_list(cr, uid, context=context)
res = mod_obj.read(cr, uid, ids, ['name','state'], context)
return {'module_info': '\n'.join(map(lambda x: x['name']+' : '+x['state'], res))}
def upgrade_module(self, cr, uid, ids, context=None):
mod_obj = self.pool.get('ir.module.module')
ids = mod_obj.search(cr, uid, [('state', 'in', ['to upgrade', 'to remove', 'to install'])])
unmet_packages = []
mod_dep_obj = self.pool.get('ir.module.module.dependency')
for mod in mod_obj.browse(cr, uid, ids):
depends_mod_ids = mod_dep_obj.search(cr, uid, [('module_id', '=', mod.id)])
for dep_mod in mod_dep_obj.browse(cr, uid, depends_mod_ids):
if dep_mod.state in ('unknown','uninstalled'):
unmet_packages.append(dep_mod.name)
if len(unmet_packages):
raise osv.except_osv(_('Unmet dependency !'), _('Following modules are not installed or unknown: %s') % ('\n\n' + '\n'.join(unmet_packages)))
mod_obj.download(cr, uid, ids, context=context)
cr.commit()
_db, pool = pooler.restart_pool(cr.dbname, update_module=True)
ir_module = self.pool.get('ir.module.module')
data_obj = pool.get('ir.model.data')
id2 = data_obj._get_id(cr, uid, 'base', 'view_base_module_upgrade_install')
if id2:
id2 = data_obj.browse(cr, uid, id2, context=context).res_id
# install/upgrade: double-check preconditions
ids = ir_module.search(cr, uid, [('state', 'in', ['to upgrade', 'to install'])])
if ids:
cr.execute("""SELECT d.name FROM ir_module_module m
JOIN ir_module_module_dependency d ON (m.id = d.module_id)
LEFT JOIN ir_module_module m2 ON (d.name = m2.name)
WHERE m.id in %s and (m2.state IS NULL or m2.state IN %s)""",
(tuple(ids), ('uninstalled',)))
unmet_packages = [x[0] for x in cr.fetchall()]
if unmet_packages:
raise osv.except_osv(_('Unmet dependency !'),
_('Following modules are not installed or unknown: %s') % ('\n\n' + '\n'.join(unmet_packages)))
ir_module.download(cr, uid, ids, context=context)
cr.commit() # save before re-creating cursor below
pooler.restart_pool(cr.dbname, update_module=True)
ir_model_data = self.pool.get('ir.model.data')
__, res_id = ir_model_data.get_object_reference(cr, uid, 'base', 'view_base_module_upgrade_install')
return {
'view_type': 'form',
'view_mode': 'form',
'res_model': 'base.module.upgrade',
'views': [(id2, 'form')],
'views': [(res_id, 'form')],
'view_id': False,
'type': 'ir.actions.act_window',
'target': 'new',
@ -118,6 +105,5 @@ class base_module_upgrade(osv.osv_memory):
def config(self, cr, uid, ids, context=None):
return self.pool.get('res.config').next(cr, uid, [], context=context)
base_module_upgrade()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -6,15 +6,13 @@
<rml_footer2 type="field" name="rml_footer2"/>
<title type="field" name="partner_id.title"/>
<name type="field" name="partner_id.name"/>
<address type="zoom" name="partner_id.address">
<street type="field" name="street"/>
<zip type="field" name="zip"/>
<city type="field" name="city"/>
<state type="field" name="state_id.name"/>
<country type="field" name="country_id.name"/>
<phone type="field" name="phone"/>
<email type="field" name="email"/>
</address>
<street type="field" name="street"/>
<zip type="field" name="zip"/>
<city type="field" name="city"/>
<state type="field" name="state_id.name"/>
<country type="field" name="country_id.name"/>
<phone type="field" name="phone"/>
<email type="field" name="email"/>
</corporation>
<user>
<name type="field" name="name"/>

View File

@ -22,18 +22,18 @@
<setFont name="Helvetica" size="10"/>
<drawRightString x="20cm" y="28.5cm"><xsl:value-of select="//corporate-header/corporation/rml_header1"/></drawRightString>
<drawString x="1cm" y="27cm"><xsl:value-of select="//corporate-header/corporation/address/street"/></drawString>
<drawString x="1cm" y="27cm"><xsl:value-of select="//corporate-header/corporation/street"/></drawString>
<drawString x="1cm" y="26.5cm">
<xsl:value-of select="//corporate-header/corporation/address/zip"/>
<xsl:value-of select="//corporate-header/corporation/zip"/>
<xsl:text> </xsl:text>
<xsl:value-of select="//corporate-header/corporation/address/city"/>
<xsl:value-of select="//corporate-header/corporation/city"/>
<xsl:text> - </xsl:text>
<xsl:value-of select="//corporate-header/corporation/address/country"/>
<xsl:value-of select="//corporate-header/corporation/country"/>
</drawString>
<drawString x="1cm" y="26cm">Phone:</drawString>
<drawRightString x="7cm" y="26cm"><xsl:value-of select="//corporate-header/corporation/address/phone"/></drawRightString>
<drawRightString x="7cm" y="26cm"><xsl:value-of select="//corporate-header/corporation/phone"/></drawRightString>
<drawString x="1cm" y="25.5cm">Mail:</drawString>
<drawRightString x="7cm" y="25.5cm"><xsl:value-of select="//corporate-header/corporation/address/email"/></drawRightString>
<drawRightString x="7cm" y="25.5cm"><xsl:value-of select="//corporate-header/corporation/email"/></drawRightString>
<!--page bottom-->
@ -57,18 +57,18 @@
<setFont name="Helvetica" size="10"/>
<drawRightString x="1cm" y="27.5cm"><xsl:value-of select="//corporate-header/corporation/rml_header1"/></drawRightString>
<drawString x="1cm" y="27cm"><xsl:value-of select="//corporate-header/corporation/address/street"/></drawString>
<drawString x="1cm" y="27cm"><xsl:value-of select="//corporate-header/corporation/street"/></drawString>
<drawString x="1cm" y="26.5cm">
<xsl:value-of select="//corporate-header/corporation/address/zip"/>
<xsl:value-of select="//corporate-header/corporation/zip"/>
<xsl:text> </xsl:text>
<xsl:value-of select="//corporate-header/corporation/address/city"/>
<xsl:value-of select="//corporate-header/corporation/city"/>
<xsl:text> - </xsl:text>
<xsl:value-of select="//corporate-header/corporation/address/country"/>
<xsl:value-of select="//corporate-header/corporation/country"/>
</drawString>
<drawString x="1cm" y="26cm">Phone:</drawString>
<drawRightString x="7cm" y="26cm"><xsl:value-of select="//corporate-header/corporation/address/phone"/></drawRightString>
<drawRightString x="7cm" y="26cm"><xsl:value-of select="//corporate-header/corporation/phone"/></drawRightString>
<drawString x="1cm" y="25.5cm">Mail:</drawString>
<drawRightString x="7cm" y="25.5cm"><xsl:value-of select="//corporate-header/corporation/address/email"/></drawRightString>
<drawRightString x="7cm" y="25.5cm"><xsl:value-of select="//corporate-header/corporation/email"/></drawRightString>
<!--page bottom-->

View File

@ -204,8 +204,8 @@
</table:table-cell>
</table:table-row>
</table:table>
<text:p text:style-name="P3">[[ company.partner_id.address and company.partner_id.address[0].street ]]</text:p>
<text:p text:style-name="P3">[[ company.partner_id.address and company.partner_id.address[0].zip ]] [[ company.partner_id.address and company.partner_id.address[0].city ]] - [[ company.partner_id.address and company.partner_id.address[0].country_id and company.partner_id.address[0].country_id.name ]]</text:p>
<text:p text:style-name="P3">[[ company.partner_id.street ]]</text:p>
<text:p text:style-name="P3">[[ company.partner_id.zip ]] [[ company.partner_id.city ]] - [[ company.partner_id.country_id and company.partner_id.country_id.name ]]</text:p>
<table:table table:name="Table3" table:style-name="Table3">
<table:table-column table:style-name="Table3.A"/>
<table:table-column table:style-name="Table3.B"/>
@ -214,7 +214,7 @@
<text:p text:style-name="P4">Phone :</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table3.A1" table:value-type="string">
<text:p text:style-name="P5">[[ company.partner_id.address and company.partner_id.address[0].phone ]]</text:p>
<text:p text:style-name="P5">[[ company.partner_id.phone ]]</text:p>
</table:table-cell>
</table:table-row>
<table:table-row>
@ -222,7 +222,7 @@
<text:p text:style-name="P4">Mail :</text:p>
</table:table-cell>
<table:table-cell table:style-name="Table3.A2" table:value-type="string">
<text:p text:style-name="P5">[[ company.partner_id.address and company.partner_id.address[0].email ]]</text:p>
<text:p text:style-name="P5">[[ company.partner_id.email ]]</text:p>
</table:table-cell>
</table:table-row>
</table:table>

View File

@ -14,33 +14,33 @@
<tr>
<td>
% if company['partner_id']['address'] and company['partner_id']['address'][0]['street']:
<small>${company.partner_id.address[0].street}</small></br>
% if company['partner_id']['street']:
<small>${company.partner_id.street}</small></br>
%endif
</td>
</tr>
<tr>
<td>
% if company['partner_id']['address'] and company['partner_id']['address'][0]['zip']:
<small>${company.partner_id.address[0].zip}
${company.partner_id.address[0].city}-${company.partner_id.address[0].country_id and company.partner_id.address[0].country_id.name}</small></br>
% if company['partner_id']['zip']:
<small>${company.partner_id.zip}
${company.partner_id.city}-${company.partner_id.country_id and company.partner_id.country_id.name}</small></br>
%endif
</td>
</tr>
<tr>
<td>
% if company['partner_id']['address'] and company['partner_id']['address'][0]['phone']:
<small><b>Phone:</b>${company.partner_id.address and company.partner_id.address[0].phone}</small></br>
% if company['partner_id']['phone']:
<small><b>Phone:</b>${company.partner_id.phone}</small></br>
%endif
</td>
</tr>
<tr>
<td>
% if company['partner_id']['address'] and company['partner_id']['address'][0]['email']:
<small><b>Mail:</b>${company.partner_id.address and company.partner_id.address[0].email}</small></br></<address>
% if company['partner_id']['email']:
<small><b>Mail:</b>${company.partner_id.email}</small></br></<address>
%endif
</td>
</tr>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -3,16 +3,12 @@
<address type="fields" name="id">
<company-title type="field" name="title.name"/>
<company-name type="field" name="name"/>
<contact type="zoom" name="address">
<type type="field" name="type"/>
<title type="field" name="title.name"/>
<name type="field" name="name"/>
<street type="field" name="street"/>
<street2 type="field" name="street2"/>
<zip type="field" name="zip"/>
<city type="field" name="city"/>
<state type="field" name="state_id.name"/>
<country type="field" name="country_id.name"/>
</contact>
<type type="field" name="type"/>
<street type="field" name="street"/>
<street2 type="field" name="street2"/>
<zip type="field" name="zip"/>
<city type="field" name="city"/>
<state type="field" name="state_id.name"/>
<country type="field" name="country_id.name"/>
</address>
</addresses>

View File

@ -58,31 +58,17 @@
<xsl:template match="address" mode="story">
<para style="nospace"><xsl:value-of select="company-name"/><xsl:text> </xsl:text><xsl:value-of select="company-title"/></para>
<xsl:choose>
<xsl:when test="count(contact[type='default']) >= 1">
<!-- apply the first 'contact' node with the type 'default' -->
<xsl:apply-templates select="contact[type='default'][1]"/>
</xsl:when>
<xsl:when test="count(contact[type='']) >= 1">
<!-- apply the first 'contact' node with an empty type -->
<xsl:apply-templates select="contact[type=''][1]"/>
</xsl:when>
<xsl:otherwise>
<!-- apply the first 'contact' node -->
<xsl:apply-templates select="contact[1]"/>
</xsl:otherwise>
</xsl:choose>
<para style="nospace"><xsl:value-of select="street"/></para>
<para style="nospace"><xsl:value-of select="street2"/></para>
<para style="nospace"><xsl:value-of select="zip"/><xsl:text> </xsl:text><xsl:value-of select="city"/></para>
<para style="nospace"><xsl:value-of select="state"/></para>
<para style="nospace"><xsl:value-of select="country"/></para>
<xsl:if test="position() &lt; last()">
<nextFrame/>
</xsl:if>
</xsl:template>
<xsl:template match="contact">
<para style="nospace"><xsl:value-of select="title"/><xsl:text> </xsl:text><xsl:value-of select="name"/></para>
<para style="nospace"><xsl:value-of select="street"/></para>
<para style="nospace"><xsl:value-of select="street2"/></para>
<para style="nospace"><xsl:value-of select="zip"/><xsl:text> </xsl:text><xsl:value-of select="city"/></para>
<para style="nospace"><xsl:value-of select="state"/></para>
<para style="nospace"><xsl:value-of select="country"/></para>
</xsl:template>
</xsl:stylesheet>

View File

@ -219,11 +219,11 @@ class res_partner_bank(osv.osv):
if partner_id:
part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
result['owner_name'] = part.name
result['street'] = part.address and part.address[0].street or False
result['city'] = part.address and part.address[0].city or False
result['zip'] = part.address and part.address[0].zip or False
result['country_id'] = part.address and part.address[0].country_id and part.address[0].country_id.id or False
result['state_id'] = part.address and part.address[0].state_id and part.address[0].state_id.id or False
result['street'] = part.street or False
result['city'] = part.city or False
result['zip'] = part.zip or False
result['country_id'] = part.country_id.id
result['state_id'] = part.state_id.id
return {'value': result}
res_partner_bank()

View File

@ -77,13 +77,12 @@ class res_company(osv.osv):
""" Read the 'address' functional fields. """
result = {}
part_obj = self.pool.get('res.partner')
address_obj = self.pool.get('res.partner.address')
for company in self.browse(cr, uid, ids, context=context):
result[company.id] = {}.fromkeys(field_names, False)
if company.partner_id:
address_data = part_obj.address_get(cr, uid, [company.partner_id.id], adr_pref=['default'])
if address_data['default']:
address = address_obj.read(cr, uid, address_data['default'], field_names, context=context)
address = part_obj.read(cr, uid, address_data['default'], field_names, context=context)
for field in field_names:
result[company.id][field] = address[field] or False
return result
@ -105,13 +104,12 @@ class res_company(osv.osv):
company = self.browse(cr, uid, company_id, context=context)
if company.partner_id:
part_obj = self.pool.get('res.partner')
address_obj = self.pool.get('res.partner.address')
address_data = part_obj.address_get(cr, uid, [company.partner_id.id], adr_pref=['default'])
address = address_data['default']
if address:
address_obj.write(cr, uid, [address], {name: value or False})
part_obj.write(cr, uid, [address], {name: value or False})
else:
address_obj.create(cr, uid, {name: value or False, 'partner_id': company.partner_id.id}, context=context)
part_obj.create(cr, uid, {name: value or False, 'parent_id': company.partner_id.id}, context=context)
return True
@ -126,7 +124,7 @@ class res_company(osv.osv):
'rml_header': fields.text('RML Header', required=True),
'rml_header2': fields.text('RML Internal Header', required=True),
'rml_header3': fields.text('RML Internal Header', required=True),
'logo': fields.binary('Logo'),
'logo': fields.related('partner_id', 'photo', string="Logo", type="binary"),
'currency_id': fields.many2one('res.currency', 'Currency', required=True),
'currency_ids': fields.one2many('res.currency', 'company_id', 'Currency'),
'user_ids': fields.many2many('res.users', 'res_company_users_rel', 'cid', 'user_id', 'Accepted Users'),
@ -229,7 +227,7 @@ class res_company(osv.osv):
self.cache_restart(cr)
return super(res_company, self).create(cr, uid, vals, context=context)
obj_partner = self.pool.get('res.partner')
partner_id = obj_partner.create(cr, uid, {'name': vals['name']}, context=context)
partner_id = obj_partner.create(cr, uid, {'name': vals['name'], 'is_company':True}, context=context)
vals.update({'partner_id': partner_id})
self.cache_restart(cr)
company_id = super(res_company, self).create(cr, uid, vals, context=context)
@ -296,12 +294,12 @@ class res_company(osv.osv):
<drawString x="1.3cm" y="%s">[[ company.partner_id.name ]]</drawString>
<drawString x="1.3cm" y="%s">[[ company.partner_id.address and company.partner_id.address[0].street or '' ]]</drawString>
<drawString x="1.3cm" y="%s">[[ company.partner_id.address and company.partner_id.address[0].zip or '' ]] [[ company.partner_id.address and company.partner_id.address[0].city or '' ]] - [[ company.partner_id.address and company.partner_id.address[0].country_id and company.partner_id.address[0].country_id.name or '']]</drawString>
<drawString x="1.3cm" y="%s">[[ company.partner_id.street or '' ]]</drawString>
<drawString x="1.3cm" y="%s">[[ company.partner_id.city or '' ]] - [[ company.partner_id.country_id and company.partner_id.country_id.name or '']]</drawString>
<drawString x="1.3cm" y="%s">Phone:</drawString>
<drawRightString x="7cm" y="%s">[[ company.partner_id.address and company.partner_id.address[0].phone or '' ]]</drawRightString>
<drawRightString x="7cm" y="%s">[[ company.partner_id.phone or '' ]]</drawRightString>
<drawString x="1.3cm" y="%s">Mail:</drawString>
<drawRightString x="7cm" y="%s">[[ company.partner_id.address and company.partner_id.address[0].email or '' ]]</drawRightString>
<drawRightString x="7cm" y="%s">[[ company.partner_id.email or '' ]]</drawRightString>
<lines>1.3cm %s 7cm %s</lines>
<!--page bottom-->

View File

@ -63,7 +63,7 @@ addresses belonging to this country.\n\nYou can use the python-style string pate
'The code of the country must be unique !')
]
_defaults = {
'address_format': "%(street)s\n%(street2)s\n%(city)s,%(state_code)s %(zip)s\n%(country_name)s",
'address_format': "%(company_name)s\n%(street)s\n%(street2)s\n%(city)s,%(state_code)s %(zip)s\n%(country_name)s",
}
_order='name'

View File

@ -19,12 +19,13 @@
#
##############################################################################
import os
import math
from osv import fields,osv
from osv import osv, fields
import tools
import pooler
from tools.translate import _
import logging
import pooler
class res_payterm(osv.osv):
_description = 'Payment term'
@ -33,7 +34,6 @@ class res_payterm(osv.osv):
_columns = {
'name': fields.char('Payment Term (short name)', size=64),
}
res_payterm()
class res_partner_category(osv.osv):
@ -45,7 +45,7 @@ class res_partner_category(osv.osv):
used to select the short version of the
category name (without the direct parent),
when set to ``'short'``. The default is
the long version."""
the long version."""
if context is None:
context = {}
if context.get('partner_category_display') == 'short':
@ -98,7 +98,6 @@ class res_partner_category(osv.osv):
_parent_store = True
_parent_order = 'name'
_order = 'parent_left'
res_partner_category()
class res_partner_title(osv.osv):
_name = 'res.partner.title'
@ -108,33 +107,41 @@ class res_partner_title(osv.osv):
'domain': fields.selection([('partner','Partner'),('contact','Contact')], 'Domain', required=True, size=24)
}
_order = 'name'
res_partner_title()
def _lang_get(self, cr, uid, context=None):
obj = self.pool.get('res.lang')
ids = obj.search(cr, uid, [], context=context)
res = obj.read(cr, uid, ids, ['code', 'name'], context)
lang_pool = self.pool.get('res.lang')
ids = lang_pool.search(cr, uid, [], context=context)
res = lang_pool.read(cr, uid, ids, ['code', 'name'], context)
return [(r['code'], r['name']) for r in res] + [('','')]
POSTAL_ADDRESS_FIELDS = ('street', 'street2', 'zip', 'city', 'state_id', 'country_id')
ADDRESS_FIELDS = POSTAL_ADDRESS_FIELDS + ('email', 'phone', 'fax', 'mobile', 'website', 'ref', 'lang')
class res_partner(osv.osv):
_description='Partner'
_name = "res.partner"
def _address_display(self, cr, uid, ids, name, args, context=None):
res={}
for partner in self.browse(cr, uid, ids, context=context):
res[partner.id] =self._display_address(cr, uid, partner, context=context)
return res
_order = "name"
_columns = {
'name': fields.char('Name', size=128, required=True, select=True),
'date': fields.date('Date', select=1),
'title': fields.many2one('res.partner.title','Partner Firm'),
'title': fields.many2one('res.partner.title','Title'),
'parent_id': fields.many2one('res.partner','Parent Partner'),
'child_ids': fields.one2many('res.partner', 'parent_id', 'Partner Ref.'),
'child_ids': fields.one2many('res.partner', 'parent_id', 'Contacts'),
'ref': fields.char('Reference', size=64, select=1),
'lang': fields.selection(_lang_get, 'Language', help="If the selected language is loaded in the system, all documents related to this partner will be printed in this language. If not, it will be english."),
'user_id': fields.many2one('res.users', 'Salesman', help='The internal user that is in charge of communicating with this partner if any.'),
'vat': fields.char('VAT',size=32 ,help="Value Added Tax number. Check the box if the partner is subjected to the VAT. Used by the VAT legal statement."),
'bank_ids': fields.one2many('res.partner.bank', 'partner_id', 'Banks'),
'website': fields.char('Website',size=64, help="Website of Partner."),
'website': fields.char('Website',size=64, help="Website of Partner or Company"),
'comment': fields.text('Notes'),
'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts'),
'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts'), # should be removed in version 7, but kept until then for backward compatibility
'category_id': fields.many2many('res.partner.category', 'res_partner_category_rel', 'partner_id', 'category_id', 'Categories'),
'events': fields.one2many('res.partner.event', 'partner_id', 'Events'),
'credit_limit': fields.float(string='Credit Limit'),
@ -142,41 +149,84 @@ class res_partner(osv.osv):
'active': fields.boolean('Active'),
'customer': fields.boolean('Customer', help="Check this box if the partner is a customer."),
'supplier': fields.boolean('Supplier', help="Check this box if the partner is a supplier. If it's not checked, purchase people will not see it when encoding a purchase order."),
'city': fields.related('address', 'city', type='char', string='City'),
'function': fields.related('address', 'function', type='char', string='function'),
'subname': fields.related('address', 'name', type='char', string='Contact Name'),
'phone': fields.related('address', 'phone', type='char', string='Phone'),
'mobile': fields.related('address', 'mobile', type='char', string='Mobile'),
'country': fields.related('address', 'country_id', type='many2one', relation='res.country', string='Country'),
'employee': fields.boolean('Employee', help="Check this box if the partner is an Employee."),
'email': fields.related('address', 'email', type='char', size=240, string='E-mail'),
'function': fields.char('Function', size=128),
'type': fields.selection( [('default','Default'),('invoice','Invoice'),
('delivery','Delivery'), ('contact','Contact'),
('other','Other')],
'Address Type', help="Used to select automatically the right address according to the context in sales and purchases documents."),
'street': fields.char('Street', size=128),
'street2': fields.char('Street2', size=128),
'zip': fields.char('Zip', change_default=True, size=24),
'city': fields.char('City', size=128),
'state_id': fields.many2one("res.country.state", 'Fed. State', domain="[('country_id','=',country_id)]"),
'country_id': fields.many2one('res.country', 'Country'),
'country': fields.related('country_id', type='many2one', relation='res.country', string='Country'), # for backward compatibility
'email': fields.char('E-Mail', size=240),
'phone': fields.char('Phone', size=64),
'fax': fields.char('Fax', size=64),
'mobile': fields.char('Mobile', size=64),
'birthdate': fields.char('Birthdate', size=64),
'is_company': fields.boolean('Company', help="Check if the contact is a company, otherwise it is a person"),
'use_parent_address': fields.boolean('Use Company Address', help="Select this if you want to set company's address information for this contact"),
'photo': fields.binary('Photo'),
'company_id': fields.many2one('res.company', 'Company', select=1),
'color': fields.integer('Color Index'),
'contact_address': fields.function(_address_display, type='char', string='Complete Address'),
}
def _default_category(self, cr, uid, context=None):
if context is None:
context = {}
if 'category_id' in context and context['category_id']:
if context.get('category_id'):
return [context['category_id']]
return []
return False
def _get_photo(self, cr, uid, is_company, context=None):
if is_company:
path = os.path.join( tools.config['root_path'], 'addons', 'base', 'res', 'company_icon.png')
else:
path = os.path.join( tools.config['root_path'], 'addons', 'base', 'res', 'photo.png')
return open(path, 'rb').read().encode('base64')
_defaults = {
'active': lambda *a: 1,
'customer': lambda *a: 1,
'active': True,
'customer': True,
'category_id': _default_category,
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'res.partner', context=c),
'color': 0,
'is_company': False,
'type': 'default',
'use_parent_address': True,
'photo': lambda self, cr, uid, context: self._get_photo(cr, uid, False, context),
}
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
name = self.read(cr, uid, [id], ['name'], context)[0]['name']
default.update({'name': name+ _(' (copy)'), 'events':[]})
default.update({'name': _('%s (copy)')%(name), 'events':[]})
return super(res_partner, self).copy(cr, uid, id, default, context)
def do_share(self, cr, uid, ids, *args):
return True
def onchange_type(self, cr, uid, ids, is_company, context=None):
value = {'title': False,
'photo': self._get_photo(cr, uid, is_company, context)}
if is_company:
value['parent_id'] = False
domain = {'title': [('domain', '=', 'partner')]}
else:
domain = {'title': [('domain', '=', 'contact')]}
return {'value': value, 'domain': domain}
def onchange_address(self, cr, uid, ids, use_parent_address, parent_id, context=None):
def value_or_id(val):
""" return val or val.id if val is a browse record """
return val if isinstance(val, (bool, int, long, float, basestring)) else val.id
if use_parent_address and parent_id:
parent = self.browse(cr, uid, parent_id, context=context)
return {'value': dict((key, value_or_id(parent[key])) for key in ADDRESS_FIELDS)}
return {}
def _check_ean_key(self, cr, uid, ids, context=None):
for partner_o in pooler.get_pool(cr.dbname).get('res.partner').read(cr, uid, ids, ['ean13',]):
@ -196,6 +246,43 @@ class res_partner(osv.osv):
# _constraints = [(_check_ean_key, 'Error: Invalid ean code', ['ean13'])]
def write(self, cr, uid, ids, vals, context=None):
# Update parent and siblings or children records
if isinstance(ids, (int, long)):
ids = [ids]
if vals.get('is_company')==False:
vals.update({'child_ids' : [(5,)]})
for partner in self.browse(cr, uid, ids, context=context):
update_ids = []
if partner.is_company:
domain_children = [('parent_id', '=', partner.id), ('use_parent_address', '=', True)]
update_ids = self.search(cr, uid, domain_children, context=context)
elif partner.parent_id:
if vals.get('use_parent_address')==True:
domain_siblings = [('parent_id', '=', partner.parent_id.id), ('use_parent_address', '=', True)]
update_ids = [partner.parent_id.id] + self.search(cr, uid, domain_siblings, context=context)
if 'use_parent_address' not in vals and partner.use_parent_address:
domain_siblings = [('parent_id', '=', partner.parent_id.id), ('use_parent_address', '=', True)]
update_ids = [partner.parent_id.id] + self.search(cr, uid, domain_siblings, context=context)
self.update_address(cr, uid, update_ids, vals, context)
return super(res_partner,self).write(cr, uid, ids, vals, context=context)
def create(self, cr, uid, vals, context=None):
if context is None:
context={}
# Update parent and siblings records
if vals.get('parent_id') and vals.get('use_parent_address'):
domain_siblings = [('parent_id', '=', vals['parent_id']), ('use_parent_address', '=', True)]
update_ids = [vals['parent_id']] + self.search(cr, uid, domain_siblings, context=context)
self.update_address(cr, uid, update_ids, vals, context)
if 'photo' not in vals :
vals['photo'] = self._get_photo(cr, uid, vals.get('is_company', False) or context.get('default_is_company'), context)
return super(res_partner,self).create(cr, uid, vals, context=context)
def update_address(self, cr, uid, ids, vals, context=None):
addr_vals = dict((key, vals[key]) for key in POSTAL_ADDRESS_FIELDS if vals.get(key))
return super(res_partner, self).write(cr, uid, ids, addr_vals, context)
def name_get(self, cr, uid, ids, context=None):
if context is None:
context = {}
@ -205,16 +292,28 @@ class res_partner(osv.osv):
rec_name = 'ref'
else:
rec_name = 'name'
res = [(r['id'], r[rec_name]) for r in self.read(cr, uid, ids, [rec_name], context)]
reads = self.read(cr, uid, ids, [rec_name,'parent_id'], context=context)
res = []
for record in reads:
name = record.get('name', '/')
if record['parent_id']:
name = "%s (%s)"%(name, record['parent_id'][1])
res.append((record['id'], name))
return res
def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
if not args:
args = []
# short-circuit ref match when possible
if name and operator in ('=', 'ilike', '=ilike', 'like'):
ids = self.search(cr, uid, [('ref', '=', name)] + args, limit=limit, context=context)
# search on the name of the contacts and of its company
name2 = operator == '=' and name or '%' + name + '%'
cr.execute('''SELECT partner.id FROM res_partner partner
LEFT JOIN res_partner company ON partner.parent_id = company.id
WHERE partner.name || ' (' || COALESCE(company.name,'') || ')'
''' + operator + ''' %s ''', (name2,))
ids = map(lambda x: x[0], cr.fetchall())
if args:
ids = self.search(cr, uid, [('id', 'in', ids)] + args, limit=limit, context=context)
if ids:
return self.name_get(cr, uid, ids, context)
return super(res_partner,self).name_search(cr, uid, name, args, operator=operator, context=context, limit=limit)
@ -222,9 +321,8 @@ class res_partner(osv.osv):
def _email_send(self, cr, uid, ids, email_from, subject, body, on_error=None):
partners = self.browse(cr, uid, ids)
for partner in partners:
if len(partner.address):
if partner.address[0].email:
tools.email_send(email_from, [partner.address[0].email], subject, body, on_error)
if partner.email:
tools.email_send(email_from, [partner.email], subject, body, on_error)
return True
def email_send(self, cr, uid, ids, email_from, subject, body, on_error=''):
@ -232,7 +330,6 @@ class res_partner(osv.osv):
self.pool.get('ir.cron').create(cr, uid, {
'name': 'Send Partner Emails',
'user_id': uid,
# 'nextcall': False,
'model': 'res.partner',
'function': '_email_send',
'args': repr([ids[:16], email_from, subject, body, on_error])
@ -243,20 +340,22 @@ class res_partner(osv.osv):
def address_get(self, cr, uid, ids, adr_pref=None):
if adr_pref is None:
adr_pref = ['default']
address_obj = self.pool.get('res.partner.address')
address_ids = address_obj.search(cr, uid, [('partner_id', 'in', ids)])
address_rec = address_obj.read(cr, uid, address_ids, ['type'])
res = list((addr['type'],addr['id']) for addr in address_rec)
adr = dict(res)
result = {}
# retrieve addresses from the partner itself and its children
res = []
# need to fix the ids ,It get False value in list like ids[False]
if ids and ids[0]!=False:
for p in self.browse(cr, uid, ids):
res.append((p.type, p.id))
res.extend((c.type, c.id) for c in p.child_ids)
address_dict = dict(reversed(res))
# get the id of the (first) default address if there is one,
# otherwise get the id of the first address in the list
default_address = False
if res:
default_address = adr.get('default', res[0][1])
else:
default_address = False
result = {}
for a in adr_pref:
result[a] = adr.get(a, default_address)
default_address = address_dict.get('default', res[0][1])
for adr in adr_pref:
result[adr] = address_dict.get(adr, default_address)
return result
def gen_next_ref(self, cr, uid, ids):
@ -282,23 +381,55 @@ class res_partner(osv.osv):
if (not context.get('category_id', False)):
return False
return _('Partners: ')+self.pool.get('res.partner.category').browse(cr, uid, context['category_id'], context).name
def main_partner(self, cr, uid):
''' Return the id of the main partner
'''
model_data = self.pool.get('ir.model.data')
return model_data.browse(
cr, uid,
model_data.search(cr, uid, [('module','=','base'),
('name','=','main_partner')])[0],
).res_id
res_partner()
return model_data.browse(cr, uid,
model_data.search(cr, uid, [('module','=','base'),
('name','=','main_partner')])[0],
).res_id
def _display_address(self, cr, uid, address, context=None):
'''
The purpose of this function is to build and return an address formatted accordingly to the
standards of the country where it belongs.
:param address: browse record of the res.partner.address to format
:returns: the address formatted in a display that fit its country habits (or the default ones
if not country is specified)
:rtype: string
'''
# get the information that will be injected into the display format
# get the address format
address_format = address.country_id and address.country_id.address_format or \
'%(company_name)s\n%(street)s\n%(street2)s\n%(city)s,%(state_code)s %(zip)s'
args = {
'state_code': address.state_id and address.state_id.code or '',
'state_name': address.state_id and address.state_id.name or '',
'country_code': address.country_id and address.country_id.code or '',
'country_name': address.country_id and address.country_id.name or '',
'company_name': address.parent_id and address.parent_id.name or '',
}
address_field = ['title', 'street', 'street2', 'zip', 'city']
for field in address_field :
args[field] = getattr(address, field) or ''
return address_format % args
# res.partner.address is deprecated; it is still there for backward compability only and will be removed in next version
class res_partner_address(osv.osv):
_description ='Partner Addresses'
_table = "res_partner"
_name = 'res.partner.address'
_order = 'type, name'
_columns = {
'partner_id': fields.many2one('res.partner', 'Partner Name', ondelete='set null', select=True, help="Keep empty for a private address, not related to partner."),
'parent_id': fields.many2one('res.partner', 'Company', ondelete='set null', select=True),
'partner_id': fields.related('parent_id', type='many2one', relation='res.partner', string='Partner'), # for backward compatibility
'type': fields.selection( [ ('default','Default'),('invoice','Invoice'), ('delivery','Delivery'), ('contact','Contact'), ('other','Other') ],'Address Type', help="Used to select automatically the right address according to the context in sales and purchases documents."),
'function': fields.char('Function', size=128),
'title': fields.many2one('res.partner.title','Title'),
@ -317,97 +448,29 @@ class res_partner_address(osv.osv):
'is_customer_add': fields.related('partner_id', 'customer', type='boolean', string='Customer'),
'is_supplier_add': fields.related('partner_id', 'supplier', type='boolean', string='Supplier'),
'active': fields.boolean('Active', help="Uncheck the active field to hide the contact."),
# 'company_id': fields.related('partner_id','company_id',type='many2one',relation='res.company',string='Company', store=True),
'company_id': fields.many2one('res.company', 'Company',select=1),
'color': fields.integer('Color Index'),
}
_defaults = {
'active': lambda *a: 1,
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'res.partner.address', context=c),
'active': True,
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'res.partner', context=c),
'color': 0,
'type': 'default',
}
def name_get(self, cr, user, ids, context=None):
if context is None:
context = {}
if not len(ids):
return []
res = []
for r in self.read(cr, user, ids, ['name','zip','country_id', 'city','partner_id', 'street']):
if context.get('contact_display', 'contact')=='partner' and r['partner_id']:
res.append((r['id'], r['partner_id'][1]))
else:
# make a comma-separated list with the following non-empty elements
elems = [r['name'], r['country_id'] and r['country_id'][1], r['city'], r['street']]
addr = ', '.join(filter(bool, elems))
if (context.get('contact_display', 'contact')=='partner_address') and r['partner_id']:
res.append((r['id'], "%s: %s" % (r['partner_id'][1], addr or '/')))
else:
res.append((r['id'], addr or '/'))
return res
def name_search(self, cr, user, name, args=None, operator='ilike', context=None, limit=100):
if not args:
args = []
if context is None:
context = {}
if not name:
ids = self.search(cr, user, args, limit=limit, context=context)
elif context.get('contact_display', 'contact') == 'partner':
ids = self.search(cr, user, [('partner_id', operator, name)] + args, limit=limit, context=context)
else:
# first lookup zip code, as it is a common and efficient way to search on these data
ids = self.search(cr, user, [('zip', '=', name)] + args, limit=limit, context=context)
# then search on other fields:
if context.get('contact_display', 'contact') == 'partner_address':
fields = ['partner_id', 'name', 'country_id', 'city', 'street']
else:
fields = ['name', 'country_id', 'city', 'street']
# Here we have to search the records that satisfy the domain:
# OR([[(f, operator, name)] for f in fields])) + args
# Searching on such a domain can be dramatically inefficient, due to the expansion made
# for field translations, and the handling of the disjunction by the DB engine itself.
# So instead, we search field by field until the search limit is reached.
while len(ids) < limit and fields:
f = fields.pop(0)
new_ids = self.search(cr, user, [(f, operator, name)] + args, limit=limit, context=context)
# extend ids with the ones in new_ids that are not in ids yet (and keep order)
old_ids = set(ids)
ids.extend([id for id in new_ids if id not in old_ids])
ids = ids[:limit]
return self.name_get(cr, user, ids, context=context)
def get_city(self, cr, uid, id):
return self.browse(cr, uid, id).city
def _display_address(self, cr, uid, address, context=None):
'''
The purpose of this function is to build and return an address formatted accordingly to the
standards of the country where it belongs.
:param address: browse record of the res.partner.address to format
:returns: the address formatted in a display that fit its country habits (or the default ones
if not country is specified)
:rtype: string
'''
# get the address format
address_format = address.country_id and address.country_id.address_format or \
'%(street)s\n%(street2)s\n%(city)s,%(state_code)s %(zip)s'
# get the information that will be injected into the display format
args = {
'state_code': address.state_id and address.state_id.code or '',
'state_name': address.state_id and address.state_id.name or '',
'country_code': address.country_id and address.country_id.code or '',
'country_name': address.country_id and address.country_id.name or '',
}
address_field = ['title', 'street', 'street2', 'zip', 'city']
for field in address_field :
args[field] = getattr(address, field) or ''
return address_format % args
res_partner_address()
def write(self, cr, uid, ids, vals, context=None):
logging.getLogger('res.partner').warning("Deprecated use of res.partner.address")
if 'partner_id' in vals:
vals['parent_id'] = vals.get('partner_id')
del(vals['partner_id'])
return self.pool.get('res.partner').write(cr, uid, ids, vals, context=context)
def create(self, cr, uid, vals, context=None):
logging.getLogger('res.partner').warning("Deprecated use of res.partner.address")
if 'partner_id' in vals:
vals['parent_id'] = vals.get('partner_id')
del(vals['partner_id'])
return self.pool.get('res.partner').create(cr, uid, vals, context=context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<report id="res_partner_address_report" model="res.partner" name="res.partner.address" string="Labels" xml="base/res/report/partner_address.xml" xsl="base/res/report/partner_address.xsl" groups="base.group_extended"/>
<report id="res_partner_address_report" model="res.partner" name="res.partner" string="Labels" xml="base/res/report/partner_address.xml" xsl="base/res/report/partner_address.xsl" groups="base.group_extended"/>
<!--
<report string="Business Cards" model="res.partner" name="res.partner.businesscard" xml="base/res/report/business_card.xml" xsl="base/res/report/business_card.xsl"/>
-->

View File

@ -202,6 +202,9 @@
<field name="view_id" ref="view_partner_address_form1"/>
<field name="act_window_id" ref="action_partner_address_form"/>
</record>
<!--menuitem action="action_partner_address_form" id="menu_partner_address_form"
groups="base.group_extended" name="Contacts"
parent="base.menu_address_book" sequence="30"/-->
<!--
=========================================
@ -310,14 +313,15 @@
<field name="type">tree</field>
<field eval="8" name="priority"/>
<field name="arch" type="xml">
<tree string="Partners">
<tree string="Contacts">
<field name="name"/>
<field name="ref" groups="base.group_extended"/>
<field name="function" invisible="1"/>
<field name="phone"/>
<field name="email"/>
<field name="city"/>
<field name="country"/>
<field name="user_id"/>
<field name="user_id" invisible="1"/>
<field name="is_company" invisible="1"/>
<field name="country" invisible="1"/>
<field name="country_id" invisible="1"/>
</tree>
</field>
</record>
@ -327,76 +331,140 @@
<field name="model">res.partner</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Partners" col='1'>
<group col="6" colspan="4">
<group colspan="5" col="6">
<field name="name" select="1"/>
<field name="ref" groups="base.group_extended"/>
<field domain="[('domain', '=', 'partner')]" name="title" size="0" groups="base.group_extended"/>
<field name="lang"/>
<form string="Partners">
<group col="8" colspan="4">
<group col="4" colspan="4">
<field name="name" required="1"/>
<field name="title" size="0" groups="base.group_extended" domain="[('domain', '=', 'contact')]"/>
<newline/>
<field name="function" attrs="{'invisible': [('is_company', '=', True)]}" colspan="4"/>
<field name="parent_id" string="Company" colspan="4" attrs="{'invisible': [('is_company','=', True)]}"
domain="[('is_company', '=', True)]" context="{'default_is_company': True}"
on_change="onchange_address(use_parent_address, parent_id)"/>
</group>
<group colspan="1" col="2">
<field name="customer" select="1"/>
<group col="2">
<field name="is_company" on_change="onchange_type(is_company)"/>
</group>
<group col="2">
<field name="customer"/>
<field name="supplier"/>
<!-- <field name="employee"/>-->
</group>
<group col="2">
<field name="photo" widget='image' nolabel="1"/>
</group>
</group>
<notebook colspan="4">
<page string="General">
<field colspan="4" mode="form,tree" name="address" nolabel="1" select="1" height="260">
<form string="Partner Contacts">
<group colspan="4" col="6">
<field name="name" string="Contact Name"/>
<field domain="[('domain', '=', 'contact')]" name="title" size="0"/>
<field name="function"/>
<group colspan="2">
<separator string="Address" colspan="4"/>
<field name="type" string="Type" attrs="{'invisible': [('is_company','=', True)]}"/>
<group colspan="2">
<field name="use_parent_address" attrs="{'invisible': [('parent_id', '=', False)]}"
on_change="onchange_address(use_parent_address, parent_id)"/>
</group>
<newline/>
<field name="street" colspan="4"/>
<field name="street2" colspan="4"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="state_id"/>
</group>
<group colspan="2">
<separator string="Communication" colspan="4"/>
<field name="lang" colspan="4"/>
<field name="phone" colspan="4"/>
<field name="mobile" colspan="4"/>
<field name="fax" colspan="4"/>
<field name="email" widget="email" colspan="4"/>
<field name="website" widget="url" colspan="4"/>
<field name="ref" groups="base.group_extended" colspan="4"/>
</group>
<group colspan="4" attrs="{'invisible': [('is_company','=', False)]}">
<field name="child_ids" context="{'default_parent_id': active_id}" nolabel="1">
<form string="Partners">
<group col="8" colspan="4">
<group col="4" colspan="4">
<field name="name" required="1"/>
<field name="title" size="0" groups="base.group_extended" domain="[('domain', '=', 'contact')]"/>
<newline/>
<field name="function" attrs="{'invisible': [('is_company', '=', True)]}" colspan="4"/>
<field name="parent_id" string="Company" colspan="4" attrs="{'invisible': [('is_company','=', True)]}"
domain="[('is_company', '=', True)]" context="{'default_is_company': True}"
on_change="onchange_address(use_parent_address, parent_id)" invisible="1"/>
</group>
<group col="2">
<field name="is_company" on_change="onchange_type(is_company)" invisible="1"/>
</group>
<group col="2">
<field name="customer"/>
<field name="supplier"/>
</group>
<group col="2">
<field name="photo" widget='image' nolabel="1"/>
</group>
</group>
<newline/>
<group colspan="2" col="4">
<separator string="Postal Address" colspan="4" col="4" />
<field name="type" string="Type" colspan="2"/>
<field name="street" colspan="4"/>
<field name="street2" colspan="4"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id" completion="1"/>
<field name="state_id"/>
</group>
<group colspan="2" col="2">
<separator string="Communication" colspan="2" col="2" />
<field name="phone"/>
<field name="mobile"/>
<field name="fax"/>
<field name="email" widget="email"/>
</group>
</form>
<tree string="Partner Contacts">
<field name="name"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="phone"/>
<field name="email"/>
</tree>
</field>
<group groups="base.group_extended">
<separator colspan="4" string="Categories"/>
<field colspan="4" name="category_id" nolabel="1"/>
<notebook colspan="4">
<page string="General">
<group colspan="2">
<separator string="Address" colspan="4"/>
<field name="type" string="Type" attrs="{'invisible': [('is_company','=', True)]}"/>
<group colspan="2">
<field name="use_parent_address" attrs="{'invisible': [('is_company','=', True)]}" on_change="onchange_address(use_parent_address, parent_id)"/>
</group>
<newline/>
<field name="street" colspan="4"/>
<field name="street2" colspan="4"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="state_id"/>
</group>
<group colspan="2">
<separator string="Communication" colspan="4"/>
<field name="lang" colspan="4"/>
<field name="phone" colspan="4"/>
<field name="mobile" colspan="4"/>
<field name="fax" colspan="4"/>
<field name="email" widget="email" colspan="4"/>
<field name="website" widget="url" colspan="4"/>
<field name="ref" groups="base.group_extended" colspan="4"/>
</group>
</page>
<page string="Sales &amp; Purchases" attrs="{'invisible': [('customer', '=', False), ('supplier', '=', False)]}">
<separator string="General Information" colspan="4"/>
<field name="user_id"/>
<field name="active" groups="base.group_extended"/>
<field name="date"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
<newline/>
</page>
<page string="Categories" groups="base.group_extended">
<field name="category_id" colspan="4" nolabel="1"/>
</page>
<page string="Notes">
<field name="comment" colspan="4" nolabel="1"/>
</page>
</notebook>
</form>
</field>
</group>
</page>
<page string="Sales &amp; Purchases">
<page string="Sales &amp; Purchases" attrs="{'invisible': [('customer', '=', False), ('supplier', '=', False)]}">
<separator string="General Information" colspan="4"/>
<field name="user_id"/>
<field name="active" groups="base.group_extended"/>
<field name="website" widget="url"/>
<field name="date"/>
<field name="parent_id" groups="base.group_extended"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
<newline/>
</page>
<page string="History" groups="base.group_extended" invisible="True">
</page>
<page string="Categories" groups="base.group_extended">
<field name="category_id" colspan="4" nolabel="1"/>
</page>
<page string="Notes">
<field colspan="4" name="comment" nolabel="1"/>
<field name="comment" colspan="4" nolabel="1"/>
</page>
</notebook>
</form>
@ -410,12 +478,15 @@
<field name="arch" type="xml">
<search string="Search Partner">
<group col='10' colspan='4'>
<filter string="Persons" name="type_person" icon="terp-personal" domain="[('is_company','=',0)]"/>
<filter string="Companies" name="type_company" icon="terp-partner" domain="[('is_company','=',1)]"/>
<separator orientation="vertical"/>
<filter string="Customers" name="customer" icon="terp-personal" domain="[('customer','=',1)]" help="Customer Partners"/>
<filter string="Suppliers" name="supplier" icon="terp-personal" domain="[('supplier','=',1)]" help="Supplier Partners"/>
<separator orientation="vertical"/>
<field name="name" select="1"/>
<field name="address" select="1"/>
<field name="country" select="1"/>
<!--field name="address" select="1"/-->
<!--field name="country" select="1"/-->
<field name="category_id" select="1" groups="base.group_extended"/>
<field name="user_id" select="1">
<filter help="My Partners" icon="terp-personal+" domain="[('user_id','=',uid)]"/>
@ -424,6 +495,7 @@
<newline />
<group expand="0" string="Group By...">
<filter string="Salesman" icon="terp-personal" domain="[]" context="{'group_by' : 'user_id'}" />
<filter string="Company" context="{'group_by': 'parent_id'}"/>
</group>
</search>
</field>
@ -431,66 +503,69 @@
<!-- Partner Kanban View -->
<record model="ir.ui.view" id="res_partner_kanban_view">
<field name="name">RES - PARTNER KANBAN</field>
<field name="name">res.partner.kanban</field>
<field name="model">res.partner</field>
<field name="type">kanban</field>
<field name="arch" type="xml">
<kanban>
<field name="color"/>
<field name="name"/>
<field name="title"/>
<field name="email"/>
<field name="parent_id"/>
<field name="is_company"/>
<field name="function"/>
<field name="phone"/>
<field name="street"/>
<field name="street2"/>
<field name="photo"/>
<field name="zip"/>
<field name="city"/>
<field name="country_id"/>
<field name="mobile"/>
<field name="state_id"/>
<templates>
<t t-name="kanban-box">
<t t-set="color" t-value="kanban_color(record.color.raw_value || record.name.raw_value)"/>
<div t-att-class="color + (record.color.raw_value == 1 ? ' oe_kanban_color_alert' : '')">
<div class="oe_kanban_box oe_kanban_color_border">
<div class="oe_kanban_box_header oe_kanban_color_bgdark oe_kanban_color_border oe_kanban_draghandle">
<table class="oe_kanban_table">
<tr>
<td class="oe_kanban_title1" align="left" valign="middle">
<field name="name"/>
</td>
<td valign="top" width="22">
<img t-att-src="kanban_gravatar(record.email.value, 22)" class="oe_kanban_gravatar"/>
</td>
</tr>
</table>
</div>
<div class="oe_kanban_box_content oe_kanban_color_bglight oe_kanban_box_show_onclick_trigger oe_kanban_color_border">
<table class="oe_kanban_table">
<tr>
<td valign="top" width="64" align="left">
<img src="/base/static/src/img/kanban_partner.png" width="64" height="64"/>
</td>
<td valign="top" align="left">
<div class="oe_kanban_title2">
<field name="title"/>
<t t-if="record.title.raw_value and record.country.raw_value">,</t>
<field name="country"/>
</div>
<div class="oe_kanban_title3">
<field name="subname"/>
<t t-if="record.subname.raw_value and record.function.raw_value">,</t>
<field name="function"/>
</div>
<div class="oe_kanban_title3">
<i><field name="email"/>
<t t-if="record.phone.raw_value and record.email.raw_value">,</t>
<field name="phone"/></i>
</div>
</td>
</tr>
</table>
</div>
<div class="oe_kanban_buttons_set oe_kanban_color_border oe_kanban_color_bglight oe_kanban_box_show_onclick">
<div class="oe_kanban_left">
<a string="Edit" icon="gtk-edit" type="edit"/>
<a string="Change Color" icon="color-picker" type="color" name="color"/>
<a title="Mail" t-att-href="'mailto:'+record.email.value" style="text-decoration: none;" >
<img src="/web/static/src/img/icons/terp-mail-message-new.png" border="0" width="16" height="16"/>
</a>
<t t-set="color" t-value="kanban_color(record.color.raw_value)"/>
<div t-att-class="color + (record.title.raw_value == 1 ? ' oe_kanban_color_alert' : '')">
<div class="oe_module_vignette">
<a type="edit">
<img t-att-src="kanban_image('res.partner', 'photo', record.id.value)" width="64" height="64" class="oe_module_icon"/>
</a>
<div class="oe_module_desc">
<div class="oe_kanban_box_content oe_kanban_color_bglight oe_kanban_box_show_onclick_trigger oe_kanban_color_border">
<table class="oe_kanban_table">
<tr>
<td class="oe_kanban_title1" align="left" valign="middle">
<h4><a type="edit"><field name="name"/></a>
<div t-if="record.parent_id.raw_value"><field name="parent_id"/></div>
</h4>
<i><div t-if="record.contact_address.raw_value"><field name="contact_address"/><br/></div>
<div t-if="record.email.raw_value">
<field name="email"/><br/></div>
<div t-if="record.mobile.raw_value">
<field name="mobile"/><br/>
</div>
<div t-if="!record.mobile.raw_value and record.phone.raw_value">
<field name="phone"/><br/>
</div></i>
</td>
<td t-if="record.is_company.raw_value" valign="top" align="right">
<!--img t-att-src="kanban_image('res.partner', 'photo', record.id.value)" class="oe_kanban_gravatar"/-->
</td>
</tr>
</table>
</div>
<br class="oe_kanban_clear"/>
<div class="oe_kanban_buttons_set oe_kanban_color_border oe_kanban_color_bglight oe_kanban_box_show_onclick">
<div class="oe_kanban_left">
<a string="Edit" icon="gtk-edit" type="edit"/>
<a string="Change Color" icon="color-picker" type="color" name="color"/>
<a t-if="record.email.raw_value" title="Mail" t-att-href="'mailto:'+record.email.value" style="text-decoration: none;" >
<img src="/web/static/src/img/icons/terp-mail-message-new.png" border="0" width="16" height="16"/>
</a>
</div>
<br class="oe_kanban_clear"/>
</div>
</div>
</div>
</div>
@ -501,32 +576,34 @@
</record>
<record id="action_partner_form" model="ir.actions.act_window">
<field name="name">Customers</field>
<field name="name">Contacts</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.partner</field>
<field name="view_type">form</field>
<field name="view_mode">kanban</field>
<field name="view_mode">tree,form,kanban</field>
<field name="context">{"search_default_customer":1}</field>
<field name="search_view_id" ref="view_res_partner_filter"/>
<field name="help">A customer is an entity you do business with, like a company or an organization. A customer can have several contacts or addresses which are the people working for this company. You can use the history tab, to follow all transactions related to a customer: sales order, emails, opportunities, claims, etc. If you use the email gateway, the Outlook or the Thunderbird plugin, don't forget to register emails to each contact so that the gateway will automatically attach incoming emails to the right partner.</field>
</record>
<record id="action_partner_form_view1" model="ir.actions.act_window.view">
<field eval="10" name="sequence"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_partner_tree"/>
<field eval="0" name="sequence"/>
<field name="view_mode">kanban</field>
<field name="view_id" ref="res_partner_kanban_view"/>
<field name="act_window_id" ref="action_partner_form"/>
</record>
<record id="action_partner_form_view2" model="ir.actions.act_window.view">
<field eval="20" name="sequence"/>
<field eval="2" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_partner_form"/>
<field name="act_window_id" ref="action_partner_form"/>
</record>
<menuitem
action="action_partner_form"
id="menu_partner_form"
parent="base.menu_sales"
sequence="8"/>
<record id="action_partner_tree_view1" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_partner_tree"/>
<field name="act_window_id" ref="action_partner_form"/>
</record>
<menuitem id="menu_partner_form" parent="base.menu_sales" action="action_partner_form" sequence="1"/>
<record id="action_partner_customer_form" model="ir.actions.act_window">
<field name="name">Customers</field>

View File

@ -113,21 +113,6 @@ class groups(osv.osv):
aid.write({'groups_id': [(4, gid)]})
return gid
def unlink(self, cr, uid, ids, context=None):
group_users = []
for record in self.read(cr, uid, ids, ['users'], context=context):
if record['users']:
group_users.extend(record['users'])
if group_users:
user_names = [user.name for user in self.pool.get('res.users').browse(cr, uid, group_users, context=context)]
user_names = list(set(user_names))
if len(user_names) >= 5:
user_names = user_names[:5] + ['...']
raise osv.except_osv(_('Warning !'),
_('Group(s) cannot be deleted, because some user(s) still belong to them: %s !') % \
', '.join(user_names))
return super(groups, self).unlink(cr, uid, ids, context=context)
def get_extended_interface_group(self, cr, uid, context=None):
data_obj = self.pool.get('ir.model.data')
extended_group_data_id = data_obj._get_id(cr, uid, 'base', 'group_extended')

View File

@ -60,7 +60,10 @@ class partner_massmail_wizard(osv.osv_memory):
ir_mail_server = self.pool.get('ir.mail_server')
emails_seen = set()
for partner in partners:
for adr in partner.address:
for adr in partner.child_ids:
if adr.is_company:
#we don't want to consider child companies but only the contacts
continue
if adr.email and not adr.email in emails_seen:
try:
emails_seen.add(adr.email)

View File

@ -0,0 +1,4 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_res_partner_address_group_partner_manager","res_partner_address group_partner_manager","model_res_partner_address","group_partner_manager",1,1,1,1
"access_res_partner_address_group_user","res_partner_address group_user","model_res_partner_address","group_user",1,0,0,0
"access_res_partner_address","res.partner.address","model_res_partner_address","group_system",1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_res_partner_address_group_partner_manager res_partner_address group_partner_manager model_res_partner_address group_partner_manager 1 1 1 1
3 access_res_partner_address_group_user res_partner_address group_user model_res_partner_address group_user 1 0 0 0
4 access_res_partner_address res.partner.address model_res_partner_address group_system 1 1 1 1

View File

@ -55,8 +55,6 @@
"access_res_lang_group_user","res_lang group_user","model_res_lang","group_system",1,1,1,1
"access_res_partner_group_partner_manager","res_partner group_partner_manager","model_res_partner","group_partner_manager",1,1,1,1
"access_res_partner_group_user","res_partner group_user","model_res_partner","group_user",1,0,0,0
"access_res_partner_address_group_partner_manager","res_partner_address group_partner_manager","model_res_partner_address","group_partner_manager",1,1,1,1
"access_res_partner_address_group_user","res_partner_address group_user","model_res_partner_address","group_user",1,0,0,0
"access_res_partner_bank_group_user","res_partner_bank group_user","model_res_partner_bank","group_user",1,0,0,0
"access_res_partner_bank_group_partner_manager","res_partner_bank group_partner_manager","model_res_partner_bank","group_partner_manager",1,1,1,1
"access_res_partner_bank_type_group_partner_manager","res_partner_bank_type group_partner_manager","model_res_partner_bank_type","group_partner_manager",1,1,1,1
@ -117,7 +115,6 @@
"access_ir_filter all","ir_filters all","model_ir_filters",,1,0,0,0
"access_ir_filter employee","ir_filters employee","model_ir_filters","group_user",1,1,1,1
"access_ir_filters","ir_filters_all","model_ir_filters",,1,1,1,1
"access_res_partner_address","res.partner.address","model_res_partner_address","group_system",1,1,1,1
"access_res_widget","res.widget","model_res_widget","group_erp_manager",1,1,1,1
"access_res_widget_user","res.widget.user","model_res_widget",,1,0,0,0
"access_res_log_all","res.log","model_res_log",,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
55 access_res_lang_group_user res_lang group_user model_res_lang group_system 1 1 1 1
56 access_res_partner_group_partner_manager res_partner group_partner_manager model_res_partner group_partner_manager 1 1 1 1
57 access_res_partner_group_user res_partner group_user model_res_partner group_user 1 0 0 0
access_res_partner_address_group_partner_manager res_partner_address group_partner_manager model_res_partner_address group_partner_manager 1 1 1 1
access_res_partner_address_group_user res_partner_address group_user model_res_partner_address group_user 1 0 0 0
58 access_res_partner_bank_group_user res_partner_bank group_user model_res_partner_bank group_user 1 0 0 0
59 access_res_partner_bank_group_partner_manager res_partner_bank group_partner_manager model_res_partner_bank group_partner_manager 1 1 1 1
60 access_res_partner_bank_type_group_partner_manager res_partner_bank_type group_partner_manager model_res_partner_bank_type group_partner_manager 1 1 1 1
115 access_ir_filter all ir_filters all model_ir_filters 1 0 0 0
116 access_ir_filter employee ir_filters employee model_ir_filters group_user 1 1 1 1
117 access_ir_filters ir_filters_all model_ir_filters 1 1 1 1
access_res_partner_address res.partner.address model_res_partner_address group_system 1 1 1 1
118 access_res_widget res.widget model_res_widget group_erp_manager 1 1 1 1
119 access_res_widget_user res.widget.user model_res_widget 1 0 0 0
120 access_res_log_all res.log model_res_log 1 1 1 1

View File

@ -68,20 +68,20 @@
-
Testing that some domain expressions work
-
!python {model: res.partner.address }: |
ids = self.search(cr, uid, [('partner_id','=','Agrolait')])
!python {model: res.partner }: |
ids = self.search(cr, uid, [('parent_id','=','Agrolait')])
assert len(ids) >= 1, ids
-
Trying the "in" operator, for scalar value
-
!python {model: res.partner.address }: |
ids = self.search(cr, uid, [('partner_id','in','Agrolait')])
!python {model: res.partner }: |
ids = self.search(cr, uid, [('parent_id','in','Agrolait')])
assert len(ids) >= 1, ids
-
Trying the "in" operator for list value
-
!python {model: res.partner.address }: |
ids = self.search(cr, uid, [('partner_id','in',['Agrolait','ASUStek'])])
!python {model: res.partner }: |
ids = self.search(cr, uid, [('parent_id','in',['Agrolait','ASUStek'])])
assert len(ids) >= 1, ids
-
Check we can use "in" operator for plain fields.
@ -92,11 +92,11 @@
-
Test one2many operator with empty search list
-
!assert {model: res.partner, search: "[('address', 'in', [])]", count: 0, string: "Ids should be empty"}
!assert {model: res.partner, search: "[('child_ids', 'in', [])]", count: 0, string: "Ids should be empty"}
-
Test one2many operator with False
-
!assert {model: res.partner, search: "[('address', '=', False)]"}:
!assert {model: res.partner, search: "[('child_ids', '=', False)]"}:
- address in (False, None, [])
-
Test many2many operator with empty search list
@ -110,7 +110,7 @@
-
Filtering on invalid value across x2many relationship should return an empty set
-
!assert {model: res.partner, search: "[('address.city','=','foo')]", count: 0, string: "Searching for address.city = foo should give empty results"}
!assert {model: res.partner, search: "[('child_ids.city','=','foo')]", count: 0, string: "Searching for address.city = foo should give empty results"}
-
Check if many2one works with empty search list
-
@ -515,7 +515,7 @@
vals = {'category_id': [(6, 0, [ref("base.res_partner_category_8")])],
'name': 'OpenERP Test',
'active': False,
'address': [(0, 0, {'country_id': ref("base.be")})]
'child_ids': [(0, 0, {'name': 'address of OpenERP Test', 'country_id': ref("base.be")})]
}
self.create(cr, uid, vals, context=context)
res_ids = self.search(cr, uid, [('category_id', 'ilike', 'supplier'), ('active', '=', False)])
@ -524,6 +524,6 @@
Testing for One2Many field with country Belgium and active=False
-
!python {model: res.partner }: |
res_ids = self.search(cr, uid, [('address.country_id','=','Belgium'),('active','=',False)])
res_ids = self.search(cr, uid, [('child_ids.country_id','=','Belgium'),('active','=',False)])
assert len(res_ids) != 0, "Record not Found with country Belgium and active False."

View File

@ -24,41 +24,25 @@
"""
import base64
import imp
import itertools
import logging
import os
import re
import sys
import threading
import zipfile
import zipimport
from cStringIO import StringIO
from os.path import join as opj
from zipfile import PyZipFile, ZIP_DEFLATED
import openerp
import openerp.modules.db
import openerp.modules.graph
import openerp.modules.migration
import openerp.netsvc as netsvc
import openerp.osv as osv
import openerp.pooler as pooler
import openerp.release as release
import openerp.tools as tools
import openerp.tools.osutil as osutil
import openerp.tools.assertion_report as assertion_report
from openerp.tools.safe_eval import safe_eval as eval
from openerp import SUPERUSER_ID
from openerp.tools.translate import _
from openerp.modules.module import \
get_modules, get_modules_with_version, \
load_information_from_description_file, \
get_module_resource, zip_directory, \
get_module_path, initialize_sys_path, \
from openerp.modules.module import initialize_sys_path, \
load_openerp_module, init_module_models
_logger = logging.getLogger(__name__)
@ -99,7 +83,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
threading.currentThread().testing = True
_load_data(cr, module_name, idref, mode, 'test')
return True
except Exception, e:
except Exception:
_logger.error(
'module %s: an exception occurred in a test', module_name)
return False
@ -178,7 +162,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
modobj = pool.get('ir.module.module')
if perform_checks:
modobj.check(cr, 1, [module_id])
modobj.check(cr, SUPERUSER_ID, [module_id])
idref = {}
@ -189,7 +173,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
if package.state=='to upgrade':
# upgrading the module information
modobj.write(cr, 1, [module_id], modobj.get_values_from_terp(package.data))
modobj.write(cr, SUPERUSER_ID, [module_id], modobj.get_values_from_terp(package.data))
load_init_xml(module_name, idref, mode)
load_update_xml(module_name, idref, mode)
load_data(module_name, idref, mode)
@ -218,9 +202,9 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
ver = release.major_version + '.' + package.data['version']
# Set new modules and dependencies
modobj.write(cr, 1, [module_id], {'state': 'installed', 'latest_version': ver})
modobj.write(cr, SUPERUSER_ID, [module_id], {'state': 'installed', 'latest_version': ver})
# Update translations for all installed languages
modobj.update_translations(cr, 1, [module_id], None)
modobj.update_translations(cr, SUPERUSER_ID, [module_id], None)
package.state = 'installed'
for kind in ('init', 'demo', 'update'):
@ -257,7 +241,7 @@ def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_m
while True:
cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
module_list = [name for (name,) in cr.fetchall() if name not in graph]
new_modules_in_graph = graph.add_modules(cr, module_list, force)
graph.add_modules(cr, module_list, force)
_logger.debug('Updating graph with %d more modules', len(module_list))
loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules)
processed_modules.extend(processed)
@ -321,15 +305,15 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
mods = [k for k in tools.config['init'] if tools.config['init'][k]]
if mods:
ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
if ids:
modobj.button_install(cr, 1, ids)
modobj.button_install(cr, SUPERUSER_ID, ids)
mods = [k for k in tools.config['update'] if tools.config['update'][k]]
if mods:
ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
if ids:
modobj.button_upgrade(cr, 1, ids)
modobj.button_upgrade(cr, SUPERUSER_ID, ids)
cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
@ -339,7 +323,11 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
# partially installed modules (i.e. installed/to upgrade), to
# offer a consistent system to the second part: installing
# newly selected modules.
states_to_load = ['installed', 'to upgrade']
# We include the modules 'to remove' in the first step, because
# they are part of the "currently installed" modules. They will
# be dropped in STEP 6 later, before restarting the loading
# process.
states_to_load = ['installed', 'to upgrade', 'to remove']
processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules)
processed_modules.extend(processed)
if update_module:
@ -350,9 +338,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
# load custom models
cr.execute('select model from ir_model where state=%s', ('manual',))
for model in cr.dictfetchall():
pool.get('ir.model').instanciate(cr, 1, model['model'], {})
pool.get('ir.model').instanciate(cr, SUPERUSER_ID, model['model'], {})
# STEP 4: Finish and cleanup
# STEP 4: Finish and cleanup installations
if processed_modules:
cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
for (model, name) in cr.fetchall():
@ -377,33 +365,18 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
_logger.warning("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
# Cleanup orphan records
pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
pool.get('ir.model.data')._process_end(cr, SUPERUSER_ID, processed_modules)
for kind in ('init', 'demo', 'update'):
tools.config[kind] = {}
cr.commit()
if update_module:
# Remove records referenced from ir_model_data for modules to be
# removed (and removed the references from ir_model_data).
cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
for mod_id, mod_name in cr.fetchall():
cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
for rmod, rid in cr.fetchall():
uid = 1
rmod_module= pool.get(rmod)
if rmod_module:
# TODO group by module so that we can delete multiple ids in a call
rmod_module.unlink(cr, uid, [rid])
else:
_logger.error('Could not locate %s to remove res=%d' % (rmod,rid))
cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
cr.commit()
# Remove menu items that are not referenced by any of other
# (child) menu item, ir_values, or ir_model_data.
# This code could be a method of ir_ui_menu.
# TODO: remove menu without actions of children
# STEP 5: Cleanup menus
# Remove menu items that are not referenced by any of other
# (child) menu item, ir_values, or ir_model_data.
# TODO: This code could be a method of ir_ui_menu. Remove menu without actions of children
if update_module:
while True:
cr.execute('''delete from
ir_ui_menu
@ -419,9 +392,19 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
else:
_logger.info('removed %d unused menus', cr.rowcount)
# Pretend that modules to be removed are actually uninstalled.
cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
cr.commit()
# STEP 6: Uninstall modules to remove
if update_module:
# Remove records referenced from ir_model_data for modules to be
# removed (and removed the references from ir_model_data).
cr.execute("SELECT id FROM ir_module_module WHERE state=%s", ('to remove',))
mod_ids_to_remove = [x[0] for x in cr.fetchall()]
if mod_ids_to_remove:
pool.get('ir.module.module').module_uninstall(cr, SUPERUSER_ID, mod_ids_to_remove)
# Recursive reload, should only happen once, because there should be no
# modules to remove next time
cr.commit()
_logger.info('Reloading registry once more after uninstalling modules')
return pooler.restart_pool(cr.dbname, force_demo, status, update_module)
if report.failures:
_logger.error('At least one test failed when loading the modules.')

View File

@ -71,6 +71,11 @@ _schema = logging.getLogger(__name__ + '.schema')
# List of etree._Element subclasses that we choose to ignore when parsing XML.
from openerp.tools import SKIPPED_ELEMENT_TYPES
# Prefixes for external IDs of schema elements
EXT_ID_PREFIX_FK = "_foreign_key_"
EXT_ID_PREFIX_M2M_TABLE = "_m2m_rel_table_"
EXT_ID_PREFIX_CONSTRAINT = "_constraint_"
regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
regex_object_name = re.compile(r'^[a-z0-9_.]+$')
@ -101,10 +106,10 @@ def transfer_node_to_modifiers(node, modifiers, context=None, in_tree_view=False
if node.get('states'):
if 'invisible' in modifiers and isinstance(modifiers['invisible'], list):
# TODO combine with AND or OR, use implicit AND for now.
modifiers['invisible'].append(('state', 'not in', node.get('states').split(',')))
# TODO combine with AND or OR, use implicit AND for now.
modifiers['invisible'].append(('state', 'not in', node.get('states').split(',')))
else:
modifiers['invisible'] = [('state', 'not in', node.get('states').split(','))]
modifiers['invisible'] = [('state', 'not in', node.get('states').split(','))]
for a in ('invisible', 'readonly', 'required'):
if node.get(a):
@ -895,7 +900,10 @@ class BaseModel(object):
parent_names = [parent_names]
else:
name = cls._name
# for res.parnter.address compatiblity, should be remove in v7
if 'res.partner.address' in parent_names:
parent_names.pop(parent_names.index('res.partner.address'))
parent_names.append('res.partner')
if not name:
raise TypeError('_name is mandatory in case of multiple inheritance')
@ -933,6 +941,7 @@ class BaseModel(object):
# If new class defines a constraint with
# same function name, we let it override
# the old one.
new[c2] = c
exist = True
break
@ -2743,6 +2752,14 @@ class BaseModel(object):
_schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
self._table, column['attname'])
# quick creation of ir.model.data entry to make uninstall of schema elements easier
def _make_ext_id(self, cr, ext_id):
cr.execute('SELECT 1 FROM ir_model_data WHERE name=%s AND module=%s', (ext_id, self._module))
if not cr.rowcount:
cr.execute("""INSERT INTO ir_model_data (name,date_init,date_update,module,model)
VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC', %s, %s)""",
(ext_id, self._module, self._name))
# checked version: for direct m2o starting from `self`
def _m2o_add_foreign_key_checked(self, source_field, dest_model, ondelete):
assert self.is_transient() or not dest_model.is_transient(), \
@ -2833,7 +2850,6 @@ class BaseModel(object):
update_custom_fields = context.get('update_custom_fields', False)
self._field_create(cr, context=context)
create = not self._table_exist(cr)
if getattr(self, '_auto', True):
if create:
@ -3068,7 +3084,7 @@ class BaseModel(object):
cr.commit() # start a new transaction
self._add_sql_constraints(cr)
self._add_sql_constraints(cr, context["module"])
if create:
self._execute_sql(cr)
@ -3079,11 +3095,11 @@ class BaseModel(object):
return todo_end
def _auto_end(self, cr, context=None):
""" Create the foreign keys recorded by _auto_init. """
for t, k, r, d in self._foreign_keys:
cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d))
self._make_ext_id(cr, "%s%s_%s_fkey" % (EXT_ID_PREFIX_FK, t, k))
cr.commit()
del self._foreign_keys
@ -3162,6 +3178,7 @@ class BaseModel(object):
def _o2m_raise_on_missing_reference(self, cr, f):
# TODO this check should be a method on fields.one2many.
other = self.pool.get(f._obj)
if other:
# TODO the condition could use fields_get_keys().
@ -3169,9 +3186,9 @@ class BaseModel(object):
if f._fields_id not in other._inherit_fields.keys():
raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
def _m2m_raise_or_create_relation(self, cr, f):
m2m_tbl, col1, col2 = f._sql_names(self)
self._make_ext_id(cr, EXT_ID_PREFIX_M2M_TABLE + m2m_tbl)
cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (m2m_tbl,))
if not cr.dictfetchall():
if not self.pool.get(f._obj):
@ -3179,7 +3196,6 @@ class BaseModel(object):
dest_model = self.pool.get(f._obj)
ref = dest_model._table
cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s")) WITH OIDS' % (m2m_tbl, col1, col2, col1, col2))
# create foreign key references with ondelete=cascade, unless the targets are SQL views
cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (ref,))
if not cr.fetchall():
@ -3195,7 +3211,7 @@ class BaseModel(object):
_schema.debug("Create table '%s': m2m relation between '%s' and '%s'", m2m_tbl, self._table, ref)
def _add_sql_constraints(self, cr):
def _add_sql_constraints(self, cr, module):
"""
Modify this model's database table constraints so they match the one in
@ -3208,9 +3224,9 @@ class BaseModel(object):
for (key, con, _) in self._sql_constraints:
conname = '%s_%s' % (self._table, key)
self._make_ext_id(cr, EXT_ID_PREFIX_CONSTRAINT + conname)
cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
existing_constraints = cr.dictfetchall()
sql_actions = {
'drop': {
'execute': False,
@ -3763,7 +3779,7 @@ class BaseModel(object):
wf_service = netsvc.LocalService("workflow")
for oid in ids:
wf_service.trg_delete(uid, self._name, oid, cr)
self.check_access_rule(cr, uid, ids, 'unlink', context=context)
pool_model_data = self.pool.get('ir.model.data')

View File

@ -299,7 +299,7 @@ class rml_parse(object):
parse_format = DEFAULT_SERVER_DATETIME_FORMAT
if isinstance(value, basestring):
# FIXME: the trimming is probably unreliable if format includes day/month names
# and those would need to be translated anyway.
# and those would need to be translated anyway.
date = datetime.strptime(value[:get_date_length(parse_format)], parse_format)
elif isinstance(value, time.struct_time):
date = datetime(*value[:6])
@ -321,7 +321,7 @@ class rml_parse(object):
return res
def display_address(self, address_browse_record):
return self.pool.get('res.partner.address')._display_address(self.cr, self.uid, address_browse_record)
return self.pool.get('res.partner')._display_address(self.cr, self.uid, address_browse_record)
def repeatIn(self, lst, name,nodes_parent=False):
ret_lst = []

View File

@ -86,8 +86,8 @@ class ReceiverEmail2Event(object):
def get_partners(self, headers, msg):
alladdresses = self.get_addresses(headers, msg)
address_ids = self.rpc(('res.partner.address', 'search', [('email', 'in', alladdresses)]))
addresses = self.rpc(('res.partner.address', 'read', address_ids))
address_ids = self.rpc(('res.partner', 'search', [('email', 'in', alladdresses)]))
addresses = self.rpc(('res.partner', 'read', address_ids))
return [x['partner_id'][0] for x in addresses]
def __call__(self, request):