[MERGE] Merged with main server
bzr revid: tde@openerp.com-20120402071259-pot0kr7yrkdcd2wj
This commit is contained in:
commit
5fd95c5769
|
@ -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',
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
@ -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)"),
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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 user’s 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:
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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-->
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 |
|
@ -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>
|
||||
|
|
|
@ -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() < 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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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-->
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
@ -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"/>
|
||||
-->
|
||||
|
|
|
@ -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 & 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 & Purchases">
|
||||
<page string="Sales & 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>
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
|
@ -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
|
||||
|
|
|
|
@ -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."
|
||||
|
||||
|
|
|
@ -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.')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue