[MERGE] osv: improved user-friendly error message for violation of NOT_NULL constraints

bzr revid: odo@openerp.com-20100521105042-0fp6cdmb3mc6khzb
bzr revid: odo@openerp.com-20100521135626-679j9of9pn9w5bdp
bzr revid: odo@openerp.com-20100521162142-p2j60p1ro0jhsrkd
This commit is contained in:
OpenERP R&D Framework Team 2010-05-21 18:21:42 +02:00 committed by Olivier Dony
commit 4699eabe75
8 changed files with 77 additions and 94 deletions

View File

@ -109,11 +109,13 @@
<notebook colspan="4">
<page string="User">
<field name="address_id" select="1"/>
<field name="user_email" widget="email"/>
<field name="company_id" required="1"/>
<field name="action_id" required="True"/>
<field domain="[('usage','=','menu')]" name="menu_id" required="True"/>
<field name="context_lang"/>
<field name="context_tz"/>
<field name="view" readonly="0" />
<group colspan="2" col="2">
<separator string="Signature" colspan="2"/>
<field colspan="2" name="signature" nolabel="1"/>

View File

@ -0,0 +1,16 @@
# -*- coding: utf8 -*-
__name__ = "res.partner.address: change type of 'function' field many2one to char"
def migrate(cr, version):
change_column_type(cr,'res_partner_address')
def change_column_type(cr,table):
cr.execute('SELECT id, name FROM res_partner_function')
all_function = cr.fetchall()
cr.execute('ALTER TABLE %s ADD COLUMN temp_function VARCHAR(64)' % table)
for fn in all_function:
cr.execute("UPDATE %s SET temp_function = '%s' WHERE function = %s" % (table,fn[1],fn[0]))
cr.execute("ALTER TABLE %s DROP COLUMN function CASCADE" % table)
cr.execute("ALTER TABLE %s RENAME COLUMN temp_function TO function" % table)

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
@ -15,7 +15,7 @@
# 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/>.
#
##############################################################################
@ -27,21 +27,6 @@ import ir
import pooler
from tools.translate import _
class res_partner_function(osv.osv):
_name = 'res.partner.function'
_description = 'Function of the contact'
_columns = {
'name': fields.char('Function Name', size=64, required=True),
'code': fields.char('Code', size=8, required=True),
'ref':fields.char('Notes', size=32,),
}
_order = 'name'
_sql_constraints = [
('code_uniq', 'unique (code)', 'The Code of the Partner Function must be unique !')
]
res_partner_function()
class res_payterm(osv.osv):
_description = 'Payment term'
_name = 'res.payterm'
@ -149,12 +134,12 @@ 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'),
'phone': fields.related('address', 'phone', type='char', string='Phone'),
'city': fields.related('address', 'city', type='char', string='City'),
'phone': fields.related('address', 'phone', type='char', string='Phone'),
'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'),
'company_id': fields.many2one('res.company', 'Company', select=1),
'company_id': fields.many2one('res.company', 'Company', select=1),
}
def _default_category(self, cr, uid, context={}):
@ -291,7 +276,7 @@ class res_partner_address(osv.osv):
_columns = {
'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null', select=True, help="Keep empty for a private address, not related to partner."),
'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.many2one('res.partner.function', 'Function'),
'function': fields.char('Function', size=64),
'title': fields.selection(_contact_title_get, 'Title', size=32),
'name': fields.char('Contact Name', size=64, select=1),
'street': fields.char('Street', size=128),
@ -469,7 +454,7 @@ class res_partner_category(osv.osv):
_columns = {
'partner_ids': fields.many2many('res.partner', 'res_partner_category_rel', 'category_id', 'partner_id', 'Partners'),
}
res_partner_category()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -33,19 +33,6 @@
<field name="shortcut">M.</field>
</record>
<record id="function_director" model="res.partner.function">
<field name="name">Director</field>
<field name="code">CEO</field>
</record>
<record id="function_it" model="res.partner.function">
<field name="name">Chief Technical Officer</field>
<field name="code">CTO</field>
</record>
<record id="function_sale" model="res.partner.function">
<field name="name">Salesman</field>
<field name="code">SAL</field>
</record>
<!-- Default bank account description -->
<record id="bank_normal" model="res.partner.bank.type">
<field name="name">Bank account</field>

View File

@ -9,41 +9,6 @@
<menuitem id="menu_base_config_partner" name="Partners" parent="menu_base_config" sequence="10" groups="base.group_extended"/>
<menuitem id="menu_base_config_contact" name="Contacts" parent="menu_base_config" sequence="20" />
<!--
================================
Function
================================
-->
<record id="view_partner_function_form" model="ir.ui.view">
<field name="name">res.partner.function.form</field>
<field name="model">res.partner.function</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Contact Functions">
<field name="name" select="1"/>
<field name="code" select="1"/>
</form>
</field>
</record>
<record id="view_partner_function_tree" model="ir.ui.view">
<field name="name">res.partner.function.tree</field>
<field name="model">res.partner.function</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Contact Functions">
<field name="code"/>
<field name="name"/>
</tree>
</field>
</record>
<record id="action_partner_function_form" model="ir.actions.act_window">
<field name="name">Contact Functions</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.partner.function</field>
<field name="view_type">form</field>
</record>
<menuitem action="action_partner_function_form" id="menu_partner_function_form" parent="base.menu_base_config_contact" sequence="20"/>
<!--
=====================
Partner Address

View File

@ -22,6 +22,7 @@
from osv import fields,osv
from osv.orm import except_orm, browse_record
import tools
import operator
import pytz
import pooler
from tools.translate import _
@ -82,6 +83,11 @@ class groups(osv.osv):
default['name'] = default['name'] + _(' (copy)')
return super(groups, self).copy(cr, uid, id, default, 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')
return data_obj.browse(cr, uid, extended_group_data_id, context=context).res_id
groups()
class roles(osv.osv):
@ -167,38 +173,46 @@ class users(osv.osv):
body=self.get_welcome_mail_body(
cr, uid, context=context) % user)
def update_user_group(self, cr, uid, ids, name, value, arg, context):
""" update User groups according to the interface.
def _set_interface_type(self, cr, uid, ids, name, value, arg, context=None):
"""Implementation of 'view' function field setter, sets the type of interface of the users.
@param name: Name of the field
@param arg: User defined argument
@param value: new value returned
@return: True/False
"""
if not value: return False
if not isinstance(ids, list):
ids = [ids]
if not value or value not in ['simple','extended']:
return False
group_obj = self.pool.get('res.groups')
extended_group = group_obj.search(cr,uid,[('name','ilike','Extended')])[0]
user_groups = self.read(cr, uid, ids, ['groups_id'])[0]['groups_id']
if value == 'simple':
cr.execute(""" delete from res_groups_users_rel where gid=%s and uid=ANY(%s)""",(extended_group, ids))
else:
self.write(cr, uid, ids,{'groups_id':[(6, 0, list(set([extended_group] + user_groups)))]})
extended_group_id = group_obj.get_extended_interface_group(cr, uid, context=context)
self.write(cr, uid, ids, { 'groups_id': [((3 if value == 'simple' else 4), extended_group_id)]}, context=context)
return True
def _set_interface(self, cr, uid, ids, name, args, context=None):
""" Sets interface for the User.
def _get_interface_type(self, cr, uid, ids, name, args, context=None):
"""Implementation of 'view' function field getter, returns the type of interface of the users.
@param field_name: Name of the field
@param arg: User defined argument
@return: Dictionary of values
"""
group_obj = self.pool.get('res.groups')
extended_group = group_obj.search(cr,uid,[('name','ilike','Extended')])[0]
user_groups = self.read(cr, uid, ids, ['groups_id'])[0]['groups_id']
if extended_group not in user_groups:
return {ids[0]:'simple'}
else:
return {ids[0]:'extended'}
extended_group_id = group_obj.get_extended_interface_group(cr, uid, context=context)
extended_users = group_obj.read(cr, uid, extended_group_id, ['users'], context=context)['users']
return dict(zip(ids, ['extended' if user in extended_users else 'simple' for user in ids]))
def _email_get(self, cr, uid, ids, name, arg, context=None):
return dict(map(operator.attrgetter('id', 'address_id.email'), self.browse(cr, uid, ids, context=context)))
def _email_set(self, cr, uid, ids, name, value, arg, context=None):
if not isinstance(ids,list):
ids = [ids]
address_obj = self.pool.get('res.partner.address')
for user in self.browse(cr, uid, ids, context=context):
if user.address_id:
address_obj.write(cr, uid, user.address_id.id, {'email': value or None}, context=context)
else:
address_id = address_obj.create(cr, uid, {'name': user.name, 'email': value or None}, context=context)
self.write(cr, uid, ids, {'address_id': address_id}, context)
return True
_columns = {
'name': fields.char('Name', size=64, required=True, select=True,
@ -228,9 +242,10 @@ class users(osv.osv):
'context_tz': fields.selection(_tz_get, 'Timezone', size=64,
help="The user's timezone, used to perform timezone conversions "
"between the server and the client."),
'view': fields.function(_set_interface, method=True, type='selection', fnct_inv=update_user_group,
'view': fields.function(_get_interface_type, method=True, type='selection', fnct_inv=_set_interface_type,
selection=[('simple','Simplified'),('extended','Extended')],
string='Interface', help="Choose between the simplified interface and the extended one"),
'user_email': fields.function(_email_get, method=True, fnct_inv=_email_set, string='Email', type="char"),
}
def read(self,cr, uid, ids, fields=None, context=None, load='_classic_read'):

View File

@ -73,8 +73,6 @@
"access_res_partner_event_group_partner_manager","res_partner_event group_partner_manager","model_res_partner_event","group_partner_manager",1,1,1,1
"access_res_partner_event_type_group_partner_manager","res_partner_event_type group_partner_manager","model_res_partner_event_type","group_partner_manager",1,1,1,1
"access_res_partner_event_type_group_user","res_partner_event_type group_user","model_res_partner_event_type","group_user",1,0,0,0
"access_res_partner_function_group_user","res_partner_function group_user","model_res_partner_function","group_partner_manager",1,1,1,1
"access_res_partner_function_group_partner_manager","res_partner_function group_partner_manager","model_res_partner_function","group_user",1,0,0,0
"access_res_partner_som_group_user","res_partner_som group_user","model_res_partner_som","group_partner_manager",1,1,1,1
"access_res_partner_som_group_partner_manager","res_partner_som group_partner_manager","model_res_partner_som","group_user",1,0,0,0
"access_res_partner_title_group_user","res_partner_title group_user","model_res_partner_title","group_partner_manager",1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
73 access_res_partner_event_group_partner_manager res_partner_event group_partner_manager model_res_partner_event group_partner_manager 1 1 1 1
74 access_res_partner_event_type_group_partner_manager res_partner_event_type group_partner_manager model_res_partner_event_type group_partner_manager 1 1 1 1
75 access_res_partner_event_type_group_user res_partner_event_type group_user model_res_partner_event_type group_user 1 0 0 0
access_res_partner_function_group_user res_partner_function group_user model_res_partner_function group_partner_manager 1 1 1 1
access_res_partner_function_group_partner_manager res_partner_function group_partner_manager model_res_partner_function group_user 1 0 0 0
76 access_res_partner_som_group_user res_partner_som group_user model_res_partner_som group_partner_manager 1 1 1 1
77 access_res_partner_som_group_partner_manager res_partner_som group_partner_manager model_res_partner_som group_user 1 0 0 0
78 access_res_partner_title_group_user res_partner_title group_user model_res_partner_title group_partner_manager 1 1 1 1

View File

@ -30,7 +30,7 @@ import copy
import sys
import traceback
import logging
from psycopg2 import IntegrityError
from psycopg2 import IntegrityError, errorcodes
from tools.func import wraps
@ -65,7 +65,22 @@ class osv_pool(netsvc.Service):
for key in self._sql_error.keys():
if key in inst[0]:
self.abortResponse(1, 'Constraint Error', 'warning', self._sql_error[key])
self.abortResponse(1, 'Integrity Error', 'warning', inst[0])
if inst.pgcode == errorcodes.NOT_NULL_VIOLATION:
msg = 'Sorry, this record cannot be deleted at the moment because other records still reference it.'
self.logger.debug("IntegrityError", exc_info=True)
try:
context = inst.pgerror.split('"public".')[1]
model_name = table = context.split('"')[1]
model = table.replace("_",".")
model_obj = self.get(model)
if model_obj:
model_name = model_obj._description or model_obj._name
msg += '\n\n[object with reference: %s - %s]' % (model_name, model)
except Exception:
pass
self.abortResponse(1, 'Integrity Error', 'warning', msg)
else:
self.abortResponse(1, 'Integrity Error', 'warning', inst[0])
except Exception, e:
self.logger.exception("Uncaught exception")
raise