diff --git a/doc/03_module_dev_03.rst b/doc/03_module_dev_03.rst index fd82f36bff1..da0e6da1090 100644 --- a/doc/03_module_dev_03.rst +++ b/doc/03_module_dev_03.rst @@ -37,7 +37,6 @@ There are two types of views: .. note:: Since OpenERP 4.1, form views can also contain graphs. - Form views ---------- @@ -388,6 +387,33 @@ The easiest method to compute real statistics on objects is: You can get en example in all modules of the form: report\_.... Example: report_crm. +Controlling view actions +------------------------ + +When defining a view, the following attributes can be added on the +opening element of the view (i.e. ``
``, ````...) + +``create`` + set to ``false`` to hide the link / button which allows to create a new + record. + +``delete`` + set to ``false`` to hide the link / button which allows to remove a + record. + +``edit`` + set to ``false`` to hide the link / button which allows to + edit a record. + + +These attributes are available on form, tree, kanban and gantt +views. They are normally automatically set from the access rights of +the users, but can be forced globally in the view definition. A +possible use case for these attributes is to define an inner tree view +for a one2many relation inside a form view, in which the user cannot +add or remove related records, but only edit the existing ones (which +are presumably created through another way, such as a wizard). + Calendar Views -------------- @@ -680,6 +706,7 @@ toolbar its descendants will be displayed in the main tree. The value is ignored for flat lists. + Grouping Elements +++++++++++++++++ diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index aaaa8513925..b6ca52d7b41 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -110,7 +110,7 @@ class report_xml(osv.osv): kwargs = {} new_report = report_sxw('report.'+r['report_name'], r['model'], opj('addons',r['report_rml'] or '/'), header=r['header'], register=False, **kwargs) - elif r['report_xsl']: + elif r['report_xsl'] and r['report_xml']: new_report = report_rml('report.'+r['report_name'], r['model'], opj('addons',r['report_xml']), r['report_xsl'] and opj('addons',r['report_xsl']), register=False) diff --git a/openerp/addons/base/ir/ir_mail_server.py b/openerp/addons/base/ir/ir_mail_server.py index e8f96d13bd9..da04308e68b 100644 --- a/openerp/addons/base/ir/ir_mail_server.py +++ b/openerp/addons/base/ir/ir_mail_server.py @@ -404,7 +404,8 @@ class ir_mail_server(osv.osv): # The email's "Envelope From" (Return-Path), and all recipient addresses must only contain ASCII characters. from_rfc2822 = extract_rfc2822_addresses(smtp_from) - assert len(from_rfc2822) == 1, "Malformed 'Return-Path' or 'From' address - it may only contain plain ASCII characters" + assert len(set(from_rfc2822)) == 1, ("Malformed 'Return-Path' or 'From' address: %r - " + "It should contain one plain ASCII email") % smtp_from smtp_from = from_rfc2822[0] email_to = message['To'] email_cc = message['Cc'] diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index e40f6eb5c3a..d7e433a6b47 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -125,11 +125,15 @@ class view(osv.osv): try: fvg = self.pool[view.model].fields_view_get(cr, uid, view_id=view.id, view_type=view.type, context=context) return fvg['arch'] - except: + except Exception: _logger.exception("Can't render view %s for model: %s", view.xml_id, view.model) return False def _check_xml(self, cr, uid, ids, context=None): + if context is None: + context = {} + context['check_view_ids'] = ids + for view in self.browse(cr, uid, ids, context): # Sanity check: the view should not break anything upon rendering! view_arch_utf8 = self._check_render_view(cr, uid, view, context=context) @@ -175,13 +179,15 @@ class view(osv.osv): :rtype: list of tuples :return: [(view_arch,view_id), ...] """ + user_groups = frozenset(self.pool.get('res.users').browse(cr, 1, uid, context).groups_id) if self.pool._init: # Module init currently in progress, only consider views from modules whose code was already loaded + check_view_ids = context and context.get('check_view_ids') or (0,) query = """SELECT v.id FROM ir_ui_view v LEFT JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id) - WHERE v.inherit_id=%s AND v.model=%s AND (md.module IS NULL or md.module in %s) + WHERE v.inherit_id=%s AND v.model=%s AND (md.module in %s OR v.id in %s) ORDER BY priority""" - query_params = (view_id, model, tuple(self.pool._init_modules)) + query_params = (view_id, model, tuple(self.pool._init_modules), tuple(check_view_ids)) else: # Modules fully loaded, consider all views query = """SELECT v.id FROM ir_ui_view v diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 912b8809f38..e724cdad013 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -508,6 +508,16 @@ class res_partner(osv.osv, format_address): def write(self, cr, uid, ids, vals, context=None): if isinstance(ids, (int, long)): ids = [ids] + #res.partner must only allow to set the company_id of a partner if it + #is the same as the company of all users that inherit from this partner + #(this is to allow the code from res_users to write to the partner!) or + #if setting the company_id to False (this is compatible with any user company) + if vals.get('company_id'): + for partner in self.browse(cr, uid, ids, context=context): + if partner.user_ids: + user_companies = set([user.company_id.id for user in partner.user_ids]) + if len(user_companies) > 1 or vals['company_id'] not in user_companies: + raise osv.except_osv(_("Warning"),_("You can not change the company as the partner/user has multiple user linked with different companies.")) result = super(res_partner,self).write(cr, uid, ids, vals, context=context) for partner in self.browse(cr, uid, ids, context=context): self._fields_sync(cr, uid, partner, vals, context) @@ -608,14 +618,15 @@ class res_partner(osv.osv, format_address): if operator in ('=ilike', '=like'): operator = operator[1:] query_args = {'name': search_name} - limit_str = '' + query = ('''SELECT id FROM res_partner + WHERE email ''' + operator + ''' %(name)s + OR display_name ''' + operator + ''' %(name)s + ORDER BY display_name + ''') if limit: - limit_str = ' limit %(limit)s' + query += ' limit %(limit)s' query_args['limit'] = limit - cr.execute('''SELECT partner.id FROM res_partner partner - LEFT JOIN res_partner company ON partner.parent_id = company.id - WHERE partner.email ''' + operator +''' %(name)s OR - partner.display_name ''' + operator + ' %(name)s ' + limit_str, query_args) + cr.execute(query, query_args) ids = map(lambda x: x[0], cr.fetchall()) ids = self.search(cr, uid, [('id', 'in', ids)] + args, limit=limit, context=context) if ids: diff --git a/openerp/addons/base/res/res_users.py b/openerp/addons/base/res/res_users.py index 09be95a1acf..6dbaa2f9660 100644 --- a/openerp/addons/base/res/res_users.py +++ b/openerp/addons/base/res/res_users.py @@ -280,6 +280,13 @@ class res_users(osv.osv): return result + def create(self, cr, uid, vals, context=None): + user_id = super(res_users, self).create(cr, uid, vals, context=context) + user = self.browse(cr, uid, user_id, context=context) + if user.partner_id.company_id: + user.partner_id.write({'company_id': user.company_id.id}) + return user_id + def write(self, cr, uid, ids, values, context=None): if not hasattr(ids, '__iter__'): ids = [ids] @@ -294,7 +301,11 @@ class res_users(osv.osv): uid = 1 # safe fields only, so we write as super-user to bypass access rights res = super(res_users, self).write(cr, uid, ids, values, context=context) - + if 'company_id' in values: + for user in self.browse(cr, uid, ids, context=context): + # if partner is global we keep it that way + if user.partner_id.company_id and user.partner_id.company_id.id != values['company_id']: + user.partner_id.write({'company_id': user.company_id.id}) # clear caches linked to the users self.pool['ir.model.access'].call_cache_clearing_methods(cr) clear = partial(self.pool['ir.rule'].clear_cache, cr) diff --git a/openerp/addons/base/security/ir.model.access.csv b/openerp/addons/base/security/ir.model.access.csv index e3198abb8fa..e17ffb314e1 100644 --- a/openerp/addons/base/security/ir.model.access.csv +++ b/openerp/addons/base/security/ir.model.access.csv @@ -110,9 +110,7 @@ "access_res_bank_user","res_bank user","model_res_bank","group_user",1,0,0,0 "access_multi_company_default user","multi_company_default all","model_multi_company_default",,1,0,0,0 "access_multi_company_default manager","multi_company_default Manager","model_multi_company_default","group_erp_manager",1,1,1,1 -"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_ir_filter all","ir_filters all","model_ir_filters",,1,1,1,1 "access_ir_config_parameter","ir_config_parameter","model_ir_config_parameter",,1,0,0,0 "access_ir_mail_server","ir_mail_server","model_ir_mail_server","group_system",1,1,1,1 "access_ir_actions_client","ir_actions_client all","model_ir_actions_client",,1,0,0,0 diff --git a/openerp/addons/base/tests/__init__.py b/openerp/addons/base/tests/__init__.py index e54fc892282..e3a07ec532c 100644 --- a/openerp/addons/base/tests/__init__.py +++ b/openerp/addons/base/tests/__init__.py @@ -6,6 +6,7 @@ import test_menu import test_res_config import test_res_lang import test_search +import test_views checks = [ test_base, @@ -16,4 +17,5 @@ checks = [ test_res_config, test_res_lang, test_search, + test_views, ] diff --git a/openerp/addons/base/tests/test_views.py b/openerp/addons/base/tests/test_views.py new file mode 100644 index 00000000000..74f6f847dba --- /dev/null +++ b/openerp/addons/base/tests/test_views.py @@ -0,0 +1,44 @@ +import unittest2 + +import openerp.tests.common as common +from openerp.osv.orm import except_orm +from openerp.tools import mute_logger + +class test_views(common.TransactionCase): + + @mute_logger('openerp.osv.orm', 'openerp.addons.base.ir.ir_ui_view') + def test_00_init_check_views(self): + Views = self.registry('ir.ui.view') + + self.assertTrue(Views.pool._init) + + error_msg = "Invalid XML for View Architecture" + # test arch check is call for views without xmlid during registry initialization + with self.assertRaisesRegexp(except_orm, error_msg): + Views.create(self.cr, self.uid, { + 'name': 'Test View #1', + 'model': 'ir.ui.view', + 'arch': """ + + + + """, + }) + + # same for inherited views + with self.assertRaisesRegexp(except_orm, error_msg): + # Views.pudb = True + Views.create(self.cr, self.uid, { + 'name': 'Test View #2', + 'model': 'ir.ui.view', + 'inherit_id': self.browse_ref('base.view_view_tree').id, + 'arch': """ + + + + """, + }) + + +if __name__ == '__main__': + unittest2.main() diff --git a/openerp/modules/loading.py b/openerp/modules/loading.py index 52ca356ffd8..f669d038b20 100644 --- a/openerp/modules/loading.py +++ b/openerp/modules/loading.py @@ -329,13 +329,21 @@ def load_modules(db, force_demo=False, status=None, update_module=False): # 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, update_module) - processed_modules.extend(processed) - if update_module: - states_to_load = ['to install'] - processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, update_module) - processed_modules.extend(processed) + # IMPORTANT 2: We have to loop here until all relevant modules have been + # processed, because in some rare cases the dependencies have + # changed, and modules that depend on an uninstalled module + # will not be processed on the first pass. + # It's especially useful for migrations. + previously_processed = -1 + while previously_processed < len(processed_modules): + previously_processed = len(processed_modules) + processed_modules += load_marked_modules(cr, graph, + ['installed', 'to upgrade', 'to remove'], + force, status, report, loaded_modules, update_module) + if update_module: + processed_modules += load_marked_modules(cr, graph, + ['to install'], force, status, report, + loaded_modules, update_module) # load custom models cr.execute('select model from ir_model where state=%s', ('manual',)) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 6e3a6277384..8858b51e0f9 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2189,7 +2189,7 @@ class BaseModel(object): are applied """ - sql_inherit = self.pool.get('ir.ui.view').get_inheriting_views_arch(cr, user, inherit_id, self._name) + sql_inherit = self.pool.get('ir.ui.view').get_inheriting_views_arch(cr, user, inherit_id, self._name, context=context) for (view_arch, view_id) in sql_inherit: source = apply_inheritance_specs(source, view_arch, view_id) source = apply_view_inheritance(cr, user, source, view_id)