[MERGE] Merged with trunk server.
bzr revid: tde@openerp.com-20120628083131-8ohyrtubya539mr7
This commit is contained in:
commit
5f784034c5
|
@ -347,6 +347,39 @@ CREATE TABLE ir_model_data (
|
|||
res_id integer, primary key(id)
|
||||
);
|
||||
|
||||
-- Records foreign keys and constraints installed by a module (so they can be
|
||||
-- removed when the module is uninstalled):
|
||||
-- - for a foreign key: type is 'f',
|
||||
-- - for a constraint: type is 'u' (this is the convention PostgreSQL uses).
|
||||
CREATE TABLE ir_model_constraint (
|
||||
id serial NOT NULL,
|
||||
create_uid integer,
|
||||
create_date timestamp without time zone,
|
||||
write_date timestamp without time zone,
|
||||
write_uid integer,
|
||||
date_init timestamp without time zone,
|
||||
date_update timestamp without time zone,
|
||||
module integer NOT NULL references ir_module_module on delete restrict,
|
||||
model integer NOT NULL references ir_model on delete restrict,
|
||||
type character varying(1) NOT NULL,
|
||||
name character varying(128) NOT NULL
|
||||
);
|
||||
|
||||
-- Records relation tables (i.e. implementing many2many) installed by a module
|
||||
-- (so they can be removed when the module is uninstalled).
|
||||
CREATE TABLE ir_model_relation (
|
||||
id serial NOT NULL,
|
||||
create_uid integer,
|
||||
create_date timestamp without time zone,
|
||||
write_date timestamp without time zone,
|
||||
write_uid integer,
|
||||
date_init timestamp without time zone,
|
||||
date_update timestamp without time zone,
|
||||
module integer NOT NULL references ir_module_module on delete restrict,
|
||||
model integer NOT NULL references ir_model on delete restrict,
|
||||
name character varying(128) NOT NULL
|
||||
);
|
||||
|
||||
---------------------------------
|
||||
-- Users
|
||||
---------------------------------
|
||||
|
|
|
@ -467,7 +467,7 @@ msgstr ""
|
|||
#. module: base
|
||||
#: field:res.bank,email:0
|
||||
#: field:res.partner.address,email:0
|
||||
msgid "E-Mail"
|
||||
msgid "Email
|
||||
msgstr ""
|
||||
|
||||
#. module: base
|
||||
|
@ -544,16 +544,16 @@ msgid "\n"
|
|||
"==========================================================================\n"
|
||||
"\n"
|
||||
"You can define your multiple levels of recall through the menu:\n"
|
||||
" Accounting/Configuration/Miscellaneous/Follow-Ups\n"
|
||||
" Accounting/Configuration/Miscellaneous/Follow-ups\n"
|
||||
"\n"
|
||||
"Once it is defined, you can automatically print recalls every day through simply clicking on the menu:\n"
|
||||
" Accounting/Periodical Processing/Billing/Send followups\n"
|
||||
" Accounting/Periodical Processing/Billing/Send follow-ups\n"
|
||||
"\n"
|
||||
"It will generate a PDF with all the letters according to the the\n"
|
||||
"different levels of recall defined. You can define different policies\n"
|
||||
"for different companies. You can also send mail to the customer.\n"
|
||||
"\n"
|
||||
"Note that if you want to check the followup level for a given partner/account entry, you can do from in the menu:\n"
|
||||
"Note that if you want to check the follow-up level for a given partner/account entry, you can do from in the menu:\n"
|
||||
" Accounting/Reporting/Generic Reporting/Partners/Follow-ups Sent\n"
|
||||
"\n"
|
||||
""
|
||||
|
@ -7592,7 +7592,7 @@ msgstr ""
|
|||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_email_template
|
||||
msgid "E-Mail Templates"
|
||||
msgid "Email Templates
|
||||
msgstr ""
|
||||
|
||||
#. module: base
|
||||
|
@ -9455,7 +9455,7 @@ msgstr ""
|
|||
|
||||
#. module: base
|
||||
#: field:res.partner,email:0
|
||||
msgid "E-mail"
|
||||
msgid "Email
|
||||
msgstr ""
|
||||
|
||||
#. module: base
|
||||
|
@ -12688,7 +12688,7 @@ msgstr ""
|
|||
|
||||
#. module: base
|
||||
#: model:ir.module.module,shortdesc:base.module_account_followup
|
||||
msgid "Followup Management"
|
||||
msgid "Follow-up Management"
|
||||
msgstr ""
|
||||
|
||||
#. module: base
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
##############################################################################
|
||||
|
||||
import ir_model
|
||||
import ir_model_constraint
|
||||
import ir_model_relation
|
||||
import ir_sequence
|
||||
import ir_needaction
|
||||
import ir_ui_menu
|
||||
|
|
|
@ -1127,6 +1127,65 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_model_constraint_form">
|
||||
<field name="name">ir.model.constraint.form</field>
|
||||
<field name="model">ir.model.constraint</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Model Constraints">
|
||||
<field name="type"/>
|
||||
<field name="name"/>
|
||||
<field name="module"/>
|
||||
<field name="model"/>
|
||||
<newline/>
|
||||
<field name="date_update" />
|
||||
<field name="date_init" />
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_model_constraint_list" model="ir.ui.view">
|
||||
<field name="name">ir.model.constraint.list</field>
|
||||
<field name="model">ir.model.constraint</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Model Constraints">
|
||||
<field name="type"/>
|
||||
<field name="name"/>
|
||||
<field name="module"/>
|
||||
<field name="model"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_model_relation_form">
|
||||
<field name="name">ir.model.relation.form</field>
|
||||
<field name="model">ir.model.relation</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="ManyToMany Relations">
|
||||
<field name="name"/>
|
||||
<field name="module"/>
|
||||
<field name="model"/>
|
||||
<newline/>
|
||||
<field name="date_update" />
|
||||
<field name="date_init" />
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_model_relation_list" model="ir.ui.view">
|
||||
<field name="name">ir.model.relation.list</field>
|
||||
<field name="model">ir.model.relation</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="ManyToMany Relations">
|
||||
<field name="name"/>
|
||||
<field name="module"/>
|
||||
<field name="model"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_model_model" model="ir.actions.act_window">
|
||||
<field name="name">Models</field>
|
||||
|
@ -1156,6 +1215,24 @@
|
|||
<menuitem action="action_model_data" id="ir_model_data_menu" parent="base.next_id_5"
|
||||
groups="base.group_no_one"/>
|
||||
|
||||
<record id="action_model_constraint" model="ir.actions.act_window">
|
||||
<field name="name">Model Constraints</field>
|
||||
<field name="res_model">ir.model.constraint</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_model_constraint_list"/>
|
||||
</record>
|
||||
<menuitem action="action_model_constraint" id="ir_model_constraint_menu" parent="base.next_id_9"
|
||||
groups="base.group_no_one"/>
|
||||
|
||||
<record id="action_model_relation" model="ir.actions.act_window">
|
||||
<field name="name">ManyToMany Relations</field>
|
||||
<field name="res_model">ir.model.relation</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_model_relation_list"/>
|
||||
</record>
|
||||
<menuitem action="action_model_relation" id="ir_model_relation_menu" parent="base.next_id_9"
|
||||
groups="base.group_no_one"/>
|
||||
|
||||
<!-- Translations -->
|
||||
|
||||
<record id="view_translation_search" model="ir.ui.view">
|
||||
|
|
|
@ -161,7 +161,7 @@ def encode_rfc2822_address_header(header_text):
|
|||
|
||||
|
||||
class ir_mail_server(osv.osv):
|
||||
"""Represents an SMTP server, able to send outgoing e-mails, with SSL and TLS capabilities."""
|
||||
"""Represents an SMTP server, able to send outgoing emails, with SSL and TLS capabilities."""
|
||||
_name = "ir.mail_server"
|
||||
|
||||
_columns = {
|
||||
|
@ -396,7 +396,7 @@ class ir_mail_server(osv.osv):
|
|||
MailDeliveryException and logs root cause.
|
||||
"""
|
||||
smtp_from = message['Return-Path'] or message['From']
|
||||
assert smtp_from, "The Return-Path or From header is required for any outbound e-mail"
|
||||
assert smtp_from, "The Return-Path or From header is required for any outbound email"
|
||||
|
||||
# The email's "Envelope From" (Return-Path), and all recipient addresses must only contain ASCII characters.
|
||||
from_rfc2822 = extract_rfc2822_addresses(smtp_from)
|
||||
|
|
|
@ -29,8 +29,7 @@ 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
|
||||
from openerp.osv.orm import except_orm, browse_record
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -864,7 +863,7 @@ 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):
|
||||
def _module_data_uninstall(self, cr, uid, modules_to_remove, 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
|
||||
|
@ -875,6 +874,8 @@ class ir_model_data(osv.osv):
|
|||
This step is performed as part of the full uninstallation of a module.
|
||||
"""
|
||||
|
||||
ids = self.search(cr, uid, [('module', 'in', modules_to_remove)])
|
||||
|
||||
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')))
|
||||
|
||||
|
@ -884,7 +885,6 @@ class ir_model_data(osv.osv):
|
|||
ids_set = set(ids)
|
||||
wkf_todo = []
|
||||
to_unlink = []
|
||||
to_drop_table = []
|
||||
ids.sort()
|
||||
ids.reverse()
|
||||
for data in self.browse(cr, uid, ids, context):
|
||||
|
@ -893,42 +893,6 @@ class ir_model_data(osv.osv):
|
|||
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)
|
||||
|
@ -948,17 +912,12 @@ class ir_model_data(osv.osv):
|
|||
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
|
||||
continue
|
||||
_logger.info('Deleting %s@%s', res_id, model)
|
||||
try:
|
||||
self.pool.get(model).unlink(cr, uid, [res_id], context=context)
|
||||
|
@ -970,11 +929,18 @@ class ir_model_data(osv.osv):
|
|||
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')
|
||||
|
||||
ir_model_relation = self.pool.get('ir.model.relation')
|
||||
relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove)])
|
||||
ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context)
|
||||
|
||||
unlink_if_refcount((model, res_id) for model, res_id in to_unlink
|
||||
if model == 'ir.model')
|
||||
|
||||
cr.commit()
|
||||
|
||||
self.unlink(cr, uid, ids, context)
|
||||
|
||||
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.
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
import logging
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import fields
|
||||
from openerp.osv.orm import Model
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class ir_model_constraint(Model):
|
||||
"""
|
||||
This model tracks PostgreSQL foreign keys and constraints used by OpenERP
|
||||
models.
|
||||
"""
|
||||
_name = 'ir.model.constraint'
|
||||
_columns = {
|
||||
'name': fields.char('Constraint', required=True, size=128, select=1,
|
||||
help="PostgreSQL constraint or foreign key name."),
|
||||
'model': fields.many2one('ir.model', string='Model',
|
||||
required=True, select=1),
|
||||
'module': fields.many2one('ir.module.module', string='Module',
|
||||
required=True, select=1),
|
||||
'type': fields.char('Constraint Type', required=True, size=1, select=1,
|
||||
help="Type of the constraint: `f` for a foreign key, "
|
||||
"`u` for other constraints."),
|
||||
'date_update': fields.datetime('Update Date'),
|
||||
'date_init': fields.datetime('Initialization Date')
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
('module_name_uniq', 'unique(name, module)',
|
||||
'Constraints with the same name are unique per module.'),
|
||||
]
|
||||
|
||||
def _module_data_uninstall(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
Delete PostgreSQL foreign keys and constraints tracked by this model.
|
||||
"""
|
||||
|
||||
if uid != SUPERUSER_ID 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 {})
|
||||
|
||||
ids_set = set(ids)
|
||||
ids.sort()
|
||||
ids.reverse()
|
||||
to_unlink = []
|
||||
for data in self.browse(cr, uid, ids, context):
|
||||
model = data.model.model
|
||||
model_obj = self.pool.get(model)
|
||||
name = openerp.tools.ustr(data.name)
|
||||
typ = data.type
|
||||
|
||||
# double-check we are really going to delete all the owners of this schema element
|
||||
cr.execute("""SELECT id from ir_model_constraint where name=%s""", (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 typ == 'f':
|
||||
# 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)
|
||||
|
||||
if typ == 'u':
|
||||
# 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)
|
||||
|
||||
to_unlink.append(data.id)
|
||||
self.unlink(cr, uid, to_unlink, context)
|
|
@ -0,0 +1,65 @@
|
|||
import logging
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import fields
|
||||
from openerp.osv.orm import Model
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class ir_model_relation(Model):
|
||||
"""
|
||||
This model tracks PostgreSQL tables used to implement OpenERP many2many
|
||||
relations.
|
||||
"""
|
||||
_name = 'ir.model.relation'
|
||||
_columns = {
|
||||
'name': fields.char('Relation Name', required=True, size=128, select=1,
|
||||
help="PostgreSQL table name implementing a many2many relation."),
|
||||
'model': fields.many2one('ir.model', string='Model',
|
||||
required=True, select=1),
|
||||
'module': fields.many2one('ir.module.module', string='Module',
|
||||
required=True, select=1),
|
||||
'date_update': fields.datetime('Update Date'),
|
||||
'date_init': fields.datetime('Initialization Date')
|
||||
}
|
||||
|
||||
def _module_data_uninstall(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
Delete PostgreSQL many2many relations tracked by this model.
|
||||
"""
|
||||
|
||||
if uid != SUPERUSER_ID 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')))
|
||||
|
||||
ids_set = set(ids)
|
||||
to_drop_table = []
|
||||
ids.sort()
|
||||
ids.reverse()
|
||||
to_unlink = []
|
||||
for data in self.browse(cr, uid, ids, context):
|
||||
model = data.model
|
||||
model_obj = self.pool.get(model)
|
||||
name = openerp.tools.ustr(data.name)
|
||||
|
||||
# double-check we are really going to delete all the owners of this schema element
|
||||
cr.execute("""SELECT id from ir_model_relation where name = %s""", (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
|
||||
|
||||
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)
|
||||
|
||||
to_unlink.append(data.id)
|
||||
|
||||
self.unlink(cr, uid, to_unlink, context)
|
||||
|
||||
# drop m2m relation tables
|
||||
for table in to_drop_table:
|
||||
cr.execute('DROP TABLE %s CASCADE'% (table),)
|
||||
_logger.info('Dropped table %s', table)
|
||||
|
||||
cr.commit()
|
|
@ -378,10 +378,11 @@ class module(osv.osv):
|
|||
including the deletion of all database structures created by the module:
|
||||
tables, columns, constraints, etc."""
|
||||
ir_model_data = self.pool.get('ir.model.data')
|
||||
ir_model_constraint = self.pool.get('ir.model.constraint')
|
||||
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)
|
||||
constraint_ids = ir_model_constraint.search(cr, uid, [('module', 'in', modules_to_remove)])
|
||||
ir_model_constraint._module_data_uninstall(cr, uid, constraint_ids, context)
|
||||
ir_model_data._module_data_uninstall(cr, uid, modules_to_remove, context)
|
||||
self.write(cr, uid, ids, {'state': 'uninstalled'})
|
||||
return True
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ class Bank(osv.osv):
|
|||
'state': fields.many2one("res.country.state", 'Fed. State',
|
||||
domain="[('country_id', '=', country)]"),
|
||||
'country': fields.many2one('res.country', 'Country'),
|
||||
'email': fields.char('E-Mail', size=64),
|
||||
'email': fields.char('Email', size=64),
|
||||
'phone': fields.char('Phone', size=64),
|
||||
'fax': fields.char('Fax', size=64),
|
||||
'active': fields.boolean('Active'),
|
||||
|
|
|
@ -89,7 +89,7 @@ class res_partner_category(osv.osv):
|
|||
'active' : fields.boolean('Active', help="The active field allows you to hide the category without removing it."),
|
||||
'parent_left' : fields.integer('Left parent', select=True),
|
||||
'parent_right' : fields.integer('Right parent', select=True),
|
||||
'partner_ids': fields.many2many('res.partner', 'res_partner_category_rel', 'category_id', 'partner_id', 'Partners'),
|
||||
'partner_ids': fields.many2many('res.partner', id1='category_id', id2='partner_id', string='Partners'),
|
||||
}
|
||||
_constraints = [
|
||||
(osv.osv._check_recursion, 'Error ! You can not create recursive categories.', ['parent_id'])
|
||||
|
@ -171,7 +171,7 @@ class res_partner(osv.osv):
|
|||
'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'), # 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', 'Tags'),
|
||||
'category_id': fields.many2many('res.partner.category', id1='partner_id', id2='category_id', string='Tags'),
|
||||
'credit_limit': fields.float(string='Credit Limit'),
|
||||
'ean13': fields.char('EAN13', size=13),
|
||||
'active': fields.boolean('Active'),
|
||||
|
@ -190,7 +190,7 @@ class res_partner(osv.osv):
|
|||
'state_id': fields.many2one("res.country.state", '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),
|
||||
'email': fields.char('Email', size=240),
|
||||
'phone': fields.char('Phone', size=64),
|
||||
'fax': fields.char('Fax', size=64),
|
||||
'mobile': fields.char('Mobile', size=64),
|
||||
|
@ -495,7 +495,7 @@ class res_partner_address(osv.osv):
|
|||
'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'),
|
||||
'email': fields.char('E-Mail', size=240),
|
||||
'email': fields.char('Email', size=240),
|
||||
'phone': fields.char('Phone', size=64),
|
||||
'fax': fields.char('Fax', size=64),
|
||||
'mobile': fields.char('Mobile', size=64),
|
||||
|
|
|
@ -54,7 +54,10 @@ class test_ir_values(common.TransactionCase):
|
|||
ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action', ['unexisting_model'], 'ir.actions.act_window,10', isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action 2', ['unexisting_model'], 'ir.actions.act_window,11', isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'client_action_multi', 'Side Wizard', ['unexisting_model'], 'ir.actions.act_window,12', isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'client_print_multi', 'Nice Report', ['unexisting_model'], 'ir.actions.report.xml,45', isobject=True)
|
||||
report_ids = self.registry('ir.actions.report.xml').search(self.cr, self.uid, [], {})
|
||||
reports = self.registry('ir.actions.report.xml').browse(self.cr, self.uid, report_ids, {})
|
||||
report_id = [report.id for report in reports if not report.groups_id][0] # assume at least one
|
||||
ir_values.set(self.cr, self.uid, 'action', 'client_print_multi', 'Nice Report', ['unexisting_model'], 'ir.actions.report.xml,%s'%report_id, isobject=True)
|
||||
ir_values.set(self.cr, self.uid, 'action', 'client_action_relate', 'Related Stuff', ['unexisting_model'], 'ir.actions.act_window,14', isobject=True)
|
||||
|
||||
# Replace one action binding to set a new name.
|
||||
|
@ -86,7 +89,7 @@ class test_ir_values(common.TransactionCase):
|
|||
assert len(actions) == 1, "Mismatching number of bound actions"
|
||||
assert len(actions[0]) == 3, "Malformed action definition"
|
||||
assert actions[0][1] == 'Nice Report', 'Bound action does not match definition'
|
||||
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 45, 'Bound action does not match definition'
|
||||
assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == report_id, 'Bound action does not match definition'
|
||||
|
||||
actions = ir_values.get(self.cr, self.uid, 'action', 'client_action_relate', ['unexisting_model'])
|
||||
assert len(actions) == 1, "Mismatching number of bound actions"
|
||||
|
@ -96,4 +99,4 @@ class test_ir_values(common.TransactionCase):
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest2.main()
|
||||
unittest2.main()
|
||||
|
|
|
@ -493,7 +493,7 @@ def get_test_modules(module, submodule, explode):
|
|||
# It seems the module has no `tests` sub-module, no problem.
|
||||
pass
|
||||
else:
|
||||
print 'Can not `import %s`.' % module
|
||||
_logger.exception('Can not `import %s`.', module)
|
||||
return []
|
||||
|
||||
# Discover available test sub-modules.
|
||||
|
@ -554,6 +554,7 @@ def run_unit_tests(module_name):
|
|||
"""
|
||||
import unittest2
|
||||
ms = get_test_modules(module_name, '__fast_suite__', explode=False)
|
||||
# TODO: No need to try again if the above call failed because of e.g. a syntax error.
|
||||
ms.extend(get_test_modules(module_name, '__sanity_checks__', explode=False))
|
||||
suite = unittest2.TestSuite()
|
||||
for m in ms:
|
||||
|
|
|
@ -70,11 +70,6 @@ _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_.]+$')
|
||||
|
||||
|
@ -1762,6 +1757,10 @@ class BaseModel(object):
|
|||
trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('help'))
|
||||
if trans:
|
||||
node.set('help', trans)
|
||||
if node.get('placeholder'):
|
||||
trans = self.pool.get('ir.translation')._get_source(cr, user, self._name, 'view', context['lang'], node.get('placeholder'))
|
||||
if trans:
|
||||
node.set('placeholder', trans)
|
||||
|
||||
for f in node:
|
||||
if children or (node.tag == 'field' and f.tag in ('filter','separator')):
|
||||
|
@ -2737,13 +2736,45 @@ 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))
|
||||
def _save_constraint(self, cr, constraint_name, type):
|
||||
"""
|
||||
Record the creation of a constraint for this model, to make it possible
|
||||
to delete it later when the module is uninstalled. Type can be either
|
||||
'f' or 'u' depending on the constraing being a foreign key or not.
|
||||
"""
|
||||
assert type in ('f', 'u')
|
||||
cr.execute("""
|
||||
SELECT 1 FROM ir_model_constraint, ir_module_module
|
||||
WHERE ir_model_constraint.module=ir_module_module.id
|
||||
AND ir_model_constraint.name=%s
|
||||
AND ir_module_module.name=%s
|
||||
""", (constraint_name, 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))
|
||||
cr.execute("""
|
||||
INSERT INTO ir_model_constraint
|
||||
(name, date_init, date_update, module, model, type)
|
||||
VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC',
|
||||
(SELECT id FROM ir_module_module WHERE name=%s),
|
||||
(SELECT id FROM ir_model WHERE model=%s), %s)""",
|
||||
(constraint_name, self._module, self._name, type))
|
||||
|
||||
def _save_relation_table(self, cr, relation_table):
|
||||
"""
|
||||
Record the creation of a many2many for this model, to make it possible
|
||||
to delete it later when the module is uninstalled.
|
||||
"""
|
||||
cr.execute("""
|
||||
SELECT 1 FROM ir_model_relation, ir_module_module
|
||||
WHERE ir_model_relation.module=ir_module_module.id
|
||||
AND ir_model_relation.name=%s
|
||||
AND ir_module_module.name=%s
|
||||
""", (relation_table, self._module))
|
||||
if not cr.rowcount:
|
||||
cr.execute("""INSERT INTO ir_model_relation (name, date_init, date_update, module, model)
|
||||
VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC',
|
||||
(SELECT id FROM ir_module_module WHERE name=%s),
|
||||
(SELECT id FROM ir_model WHERE model=%s))""",
|
||||
(relation_table, self._module, self._name))
|
||||
|
||||
# checked version: for direct m2o starting from `self`
|
||||
def _m2o_add_foreign_key_checked(self, source_field, dest_model, ondelete):
|
||||
|
@ -3084,7 +3115,7 @@ class BaseModel(object):
|
|||
""" 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))
|
||||
self._save_constraint(cr, "%s_%s_fkey" % (t, k), 'f')
|
||||
cr.commit()
|
||||
del self._foreign_keys
|
||||
|
||||
|
@ -3173,7 +3204,7 @@ class BaseModel(object):
|
|||
|
||||
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)
|
||||
self._save_relation_table(cr, 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):
|
||||
|
@ -3209,7 +3240,7 @@ 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)
|
||||
self._save_constraint(cr, conname, 'u')
|
||||
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 = {
|
||||
|
|
|
@ -11,6 +11,7 @@ See the :ref:`test-framework` section in the :ref:`features` list.
|
|||
import test_expression
|
||||
import test_ir_sequence
|
||||
import test_orm
|
||||
import test_uninstall
|
||||
|
||||
fast_suite = [
|
||||
test_ir_sequence,
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import models
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'test-uninstall',
|
||||
'version': '0.1',
|
||||
'category': 'Tests',
|
||||
'description': """A module to test the uninstall feature.""",
|
||||
'author': 'OpenERP SA',
|
||||
'maintainer': 'OpenERP SA',
|
||||
'website': 'http://www.openerp.com',
|
||||
'depends': ['base'],
|
||||
'data': [],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import openerp
|
||||
from openerp.osv import fields
|
||||
from openerp.osv.orm import Model
|
||||
|
||||
class test_uninstall_model(Model):
|
||||
"""
|
||||
This model uses different types of columns to make it possible to test
|
||||
the uninstall feature of OpenERP.
|
||||
"""
|
||||
_name = 'test_uninstall.model'
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64),
|
||||
'ref': fields.many2one('res.users', string='User'),
|
||||
'rel': fields.many2many('res.users', string='Users'),
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', 'Each name must be unique.')
|
||||
]
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,90 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This assumes an existing but uninitialized database.
|
||||
import psycopg2
|
||||
import unittest2
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
import common
|
||||
|
||||
DB = common.DB
|
||||
ADMIN_USER_ID = common.ADMIN_USER_ID
|
||||
|
||||
def registry(model):
|
||||
return openerp.modules.registry.RegistryManager.get(DB)[model]
|
||||
|
||||
def cursor():
|
||||
return openerp.modules.registry.RegistryManager.get(DB).db.cursor()
|
||||
|
||||
def get_module(module_name):
|
||||
registry = openerp.modules.registry.RegistryManager.get(DB)
|
||||
return registry.get(module_name)
|
||||
|
||||
def reload_registry():
|
||||
openerp.modules.registry.RegistryManager.new(
|
||||
DB, update_module=True)
|
||||
|
||||
def search_registry(model_name, domain):
|
||||
cr = cursor()
|
||||
model = registry(model_name)
|
||||
record_ids = model.search(cr, SUPERUSER_ID, domain, {})
|
||||
cr.close()
|
||||
return record_ids
|
||||
|
||||
def install_module(module_name):
|
||||
ir_module_module = registry('ir.module.module')
|
||||
cr = cursor()
|
||||
module_ids = ir_module_module.search(cr, SUPERUSER_ID,
|
||||
[('name', '=', module_name)], {})
|
||||
assert len(module_ids) == 1
|
||||
ir_module_module.button_install(cr, SUPERUSER_ID, module_ids, {})
|
||||
cr.commit()
|
||||
cr.close()
|
||||
reload_registry()
|
||||
|
||||
def uninstall_module(module_name):
|
||||
ir_module_module = registry('ir.module.module')
|
||||
cr = cursor()
|
||||
module_ids = ir_module_module.search(cr, SUPERUSER_ID,
|
||||
[('name', '=', module_name)], {})
|
||||
assert len(module_ids) == 1
|
||||
ir_module_module.button_uninstall(cr, SUPERUSER_ID, module_ids, {})
|
||||
cr.commit()
|
||||
cr.close()
|
||||
reload_registry()
|
||||
|
||||
class test_uninstall(unittest2.TestCase):
|
||||
"""
|
||||
Test the install/uninstall of a test module. The module is available in
|
||||
`openerp.tests` which should be present in the addons-path.
|
||||
"""
|
||||
|
||||
def test_01_install(self):
|
||||
""" Check a few things showing the module is installed. """
|
||||
install_module('test_uninstall')
|
||||
assert get_module('test_uninstall.model')
|
||||
|
||||
assert search_registry('ir.model.data',
|
||||
[('module', '=', 'test_uninstall')])
|
||||
|
||||
assert search_registry('ir.model.fields',
|
||||
[('model', '=', 'test_uninstall.model')])
|
||||
|
||||
def test_02_uninstall(self):
|
||||
""" Check a few things showing the module is uninstalled. """
|
||||
uninstall_module('test_uninstall')
|
||||
assert not get_module('test_uninstall.model')
|
||||
|
||||
assert not search_registry('ir.model.data',
|
||||
[('module', '=', 'test_uninstall')])
|
||||
|
||||
assert not search_registry('ir.model.fields',
|
||||
[('model', '=', 'test_uninstall.model')])
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest2.main()
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
Loading…
Reference in New Issue