From aec1f3da2b2000636ad5e31d5fd75309bf7f5b77 Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwerburgh Date: Mon, 11 Feb 2019 08:41:03 +0100 Subject: [PATCH 1/3] [ADD] Add view autofix functions Those functions are never called from odoo but allows to be called externally. (server actions, migrations, ...) --- odoo/addons/base/models/ir_ui_view.py | 147 +++++++++++++++ odoo/addons/base/tests/test_views.py | 253 +++++++++++++++++++++++++- 2 files changed, 399 insertions(+), 1 deletion(-) diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index 6c700bed92db1..73b4d2d945533 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -1447,3 +1447,150 @@ def _validate_module_views(self, module): view._check_xml() except Exception as e: self.raise_view_error("Can't validate view:\n%s" % e, view.id) + + def _fix_faulty_views(self, delete=False): + """ Use the _get_faulty_views and take appropriate actions to fix the issue """ + for view in self: + view_id = view.id # allow to use even if view is deleted + faulty_views = view._get_faulty_views(view,self.env['ir.ui.view']) + if faulty_views: + self._cr.execute('SAVEPOINT end_validation') + faulty_views._clean_properly(delete) + self._cr.execute("select count(id) from ir_ui_view where id = %s", (view_id,)) + cnt = self.env.cr.fetchone()[0] + if cnt == 0 or view._is_valid(): + _logger.info("View %s can be fixed by disabling/deleting following views: %s" % (view_id,faulty_views)) + self._cr.execute('RELEASE end_validation') + else: + self._cr.execute('ROLLBACK TO end_validation') + self._cr.execute('RELEASE end_validation') + _logger.warning("Issue occurs: The proposition made to fix the view %s (disable/delete %s) is not recommended" % (view,faulty_views)) + # should not happend + + def _get_faulty_views(self,parent_view, disabled_views): + """ Returns the faulty views + To determine the faulty views, we travel the tree recursively. + Disable all children, then enable one by one. If there is an issue. + The activated node or at least one of its children are faulty. + Disable faulty node and continue recursion. + This function is never called from odoo code but allows to be called from a server action. + Please don't remove + params: + parent_view - ir.ui.view - Parent view used by recusion to keep the top parent view + disabled_views - ir.ui.view - Disabled views + usage: + view1._get_faulty_views(view1, self.env['ir.ui.view']) + """ + self.ensure_one() + if parent_view==self: + if self._is_valid(): + return disabled_views + self._cr.execute('SAVEPOINT test_fix_views') + temp_disabled_views=self._deactivate_lvl1_children() + if self._is_valid(): + if self._has_active_children() or len(temp_disabled_views)>1: + for child_view in (temp_disabled_views): + v =child_view._get_faulty_views(self,disabled_views) + for vv in v: + if vv not in disabled_views: # ir.ui.view(1,2) not in ir.ui.view(1,3,4) + disabled_views+=vv # This loop avoid duplicates ir.ui.view(1,1,2,3,4) + else: + self.active=False + disabled_views+=self + else: + self.active=False + disabled_views+=self + self._cr.execute('ROLLBACK TO test_fix_views') + self._cr.execute('RELEASE test_fix_views') + return disabled_views + else: + assert parent_view._is_valid() + self.active=True + if parent_view._is_valid(): + return disabled_views + if self._has_active_children(): + temp_disabled_views=self._deactivate_lvl1_children() + if parent_view._is_valid(): + # problem is in one of the children + for child_view in (temp_disabled_views): + v =child_view._get_faulty_views(parent_view,disabled_views) + for vv in v: + if vv not in disabled_views: + disabled_views+=vv + return disabled_views + else: + self.active=False + disabled_views+=self + return disabled_views + else: + self.active=False + disabled_views+=self + return disabled_views + + def _deactivate_lvl1_children(self): + """deactivate and return views that has been deactivated taking care of view priority""" + temp_disabled_views= self._get_lvl1_children() + for v in temp_disabled_views: + v.active=False + return temp_disabled_views + + def _get_lvl1_children(self): + """return lvl1 children for the given view pay attention to order""" + return self.inherit_children_ids + + def _has_active_children(self): + return len(self._get_lvl1_children()) + + def _is_valid(self): + try: + self._check_xml() # _check_xml includes inherits + return True + except Exception as e: + # NOTE: e contains the reason why the view is invalid + return False + + def _clean_properly(self, delete=False): + # list of standard module in odoo + module_list=["base","test_inherits","test_inherit","test_limits","test_exceptions","test_uninstall","test_translation_import","test_main_flows","test_read_group","test_documentation_examples","test_new_api","test_assetsbundle","test_access_rights","test_impex","test_testing_utilities","test_pylint","test_convert","test_mimetypes","test_inherits_depends","test_converter","test_performance","account","account_3way_match","account_accountant","account_analytic_default","account_asset","account_bank_statement_import","account_bank_statement_import_camt","account_bank_statement_import_csv","account_bank_statement_import_ofx","account_bank_statement_import_qif","account_batch_payment","account_budget","account_cancel","account_check_printing","account_deferred_revenue","account_facturx","account_intrastat","account_invoice_extract","account_lock","account_online_sync","account_payment","account_plaid","account_predictive_bills","account_reports","account_reports_followup","account_sepa","account_sepa_direct_debit","account_tax_python","account_taxcloud","account_test","account_voucher","account_yodlee","analytic","analytic_enterprise","association","auth_ldap","auth_oauth","auth_password_policy","auth_password_policy_signup","auth_signup","barcodes","barcodes_mobile","base_address_city","base_address_extended","base_automation","base_automation_hr_contract","base_gengo","base_geolocalize","base_iban","base_import","base_import_module","base_setup","base_sparse_field","base_vat","board","bus","calendar","calendar_sms","contacts","crm","crm_enterprise","crm_livechat","crm_phone_validation","crm_project","crm_reveal","currency_rate_live","decimal_precision","delivery","delivery_barcode","delivery_bpost","delivery_dhl","delivery_easypost","delivery_fedex","delivery_hs_code","delivery_ups","delivery_usps","digest","document","documents","documents_account","documents_product","documents_project","documents_sign","event","event_barcode","event_barcode_mobile","event_enterprise","event_sale","fetchmail","fleet","gamification","gamification_sale_crm","google_account","google_calendar","google_drive","google_spreadsheet","helpdesk","helpdesk_sale_timesheet","helpdesk_timesheet","hr","hr_appraisal","hr_attendance","hr_contract","hr_contract_salary","hr_expense","hr_expense_check","hr_gamification","hr_holidays","hr_holidays_gantt","hr_maintenance","hr_org_chart","hr_payroll","hr_payroll_account","hr_recruitment","hr_recruitment_survey","hr_timesheet","hr_timesheet_attendance","http_routing","hw_blackbox_be","hw_drivers","hw_escpos","hw_posbox_homepage","hw_posbox_upgrade","hw_proxy","hw_scale","hw_scanner","hw_screen","iap","im_livechat","im_livechat_mail_bot","im_support","inter_company_rules","iot","l10n_ae","l10n_ar","l10n_ar_reports","l10n_at","l10n_at_reports","l10n_au","l10n_au_aba","l10n_au_reports","l10n_be","l10n_be_coda","l10n_be_hr_payroll","l10n_be_hr_payroll_account","l10n_be_hr_payroll_fleet","l10n_be_intrastat","l10n_be_intrastat_2019","l10n_be_invoice_bba","l10n_be_reports","l10n_bo","l10n_bo_reports","l10n_br","l10n_br_reports","l10n_ca","l10n_ca_check_printing","l10n_ch","l10n_ch_reports","l10n_cl","l10n_cl_reports","l10n_cn","l10n_cn_city","l10n_cn_small_business","l10n_cn_standard","l10n_co","l10n_co_edi","l10n_co_reports","l10n_cr","l10n_de","l10n_de_reports","l10n_de_skr03","l10n_de_skr04","l10n_do","l10n_do_reports","l10n_ec","l10n_es","l10n_es_real_estates","l10n_es_reports","l10n_et","l10n_et_reports","l10n_eu_service","l10n_fr","l10n_fr_certification","l10n_fr_fec","l10n_fr_hr_payroll","l10n_fr_pos_cert","l10n_fr_reports","l10n_fr_sale_closing","l10n_generic_coa","l10n_gr","l10n_gr_reports","l10n_gt","l10n_hk","l10n_hn","l10n_hr","l10n_hr_reports","l10n_hu","l10n_hu_reports","l10n_in","l10n_in_hr_payroll","l10n_in_purchase","l10n_in_reports","l10n_in_sale","l10n_in_schedule6","l10n_in_stock","l10n_it","l10n_jp","l10n_jp_reports","l10n_lu","l10n_lu_reports","l10n_ma","l10n_ma_reports","l10n_multilang","l10n_mx","l10n_mx_edi","l10n_mx_edi_customs","l10n_mx_edi_external_trade","l10n_mx_edi_landing","l10n_mx_edi_payment","l10n_mx_edi_payment_bank","l10n_mx_reports","l10n_mx_tax_cash_basis","l10n_nl","l10n_nl_intrastat","l10n_nl_reports","l10n_no","l10n_no_reports","l10n_nz","l10n_pa","l10n_pe","l10n_pl","l10n_pl_reports","l10n_pt","l10n_ro","l10n_ro_reports","l10n_sa","l10n_sg","l10n_sg_reports","l10n_si","l10n_si_reports","l10n_syscohada","l10n_th","l10n_th_reports","l10n_tr","l10n_uk","l10n_uk_reports","l10n_us","l10n_us_check_printing","l10n_us_reports","l10n_uy","l10n_uy_reports","l10n_ve","l10n_vn","l10n_vn_reports","link_tracker","lunch","mail","mail_bot","mail_enterprise","mail_github","maintenance","marketing_automation","mass_mailing","mass_mailing_crm","mass_mailing_event","mass_mailing_event_track","mass_mailing_sale","mass_mailing_themes","membership","mrp","mrp_account","mrp_bom_cost","mrp_byproduct","mrp_maintenance","mrp_mps","mrp_plm","mrp_workorder","mrp_zebra","note","note_pad","ocn_client","pad","pad_project","partner_autocomplete","partner_autocomplete_address_extended","payment","payment_adyen","payment_authorize","payment_buckaroo","payment_ogone","payment_paypal","payment_payumoney","payment_sips","payment_stripe","payment_transfer","phone_validation","point_of_sale","portal","pos_blackbox_be","pos_cache","pos_discount","pos_iot","pos_loyalty","pos_mercury","pos_reprint","pos_restaurant","pos_restaurant_iot","pos_sale","procurement_jit","product","product_email_template","product_expiry","product_margin","project","project_enterprise","project_forecast","project_timesheet_forecast","project_timesheet_forecast_sale","project_timesheet_holidays","project_timesheet_synchro","purchase","purchase_mrp","purchase_mrp_workorder_quality","purchase_requisition","purchase_stock","quality","quality_control","quality_iot","quality_mrp","quality_mrp_iot","quality_mrp_workorder","rating","repair","resource","sale","sale_account_taxcloud","sale_coupon","sale_coupon_delivery","sale_crm","sale_ebay","sale_enterprise","sale_expense","sale_intrastat","sale_management","sale_margin","sale_mrp","sale_purchase","sale_quotation_builder","sale_stock","sale_subscription","sale_subscription_asset","sale_subscription_dashboard","sale_timesheet","sales_team","sign","sms","snailmail","snailmail_account","snailmail_account_reports_followup","social_media","stock","stock_account","stock_account_enterprise","stock_barcode","stock_barcode_mobile","stock_dropshipping","stock_enterprise","stock_intrastat","stock_landed_costs","stock_picking_batch","stock_zebra","studio_customization","survey","survey_crm","test_mail","test_marketing_automation","test_mass_mailing","theme_anelusia","theme_anelusia_sale","theme_artists","theme_artists_sale","theme_avantgarde","theme_avantgarde_blog","theme_beauty","theme_beauty_sale","theme_bewise","theme_bistro","theme_bookstore","theme_bookstore_sale","theme_bootswatch","theme_clean","theme_common","theme_default","theme_enark","theme_graphene","theme_graphene_blog","theme_kea","theme_kea_sale","theme_kiddo","theme_kiddo_sale","theme_loftspace","theme_loftspace_sale","theme_monglia","theme_monglia_sale","theme_nano","theme_notes","theme_notes_sale","theme_odoo_experts","theme_odoo_experts_sale","theme_orchid","theme_orchid_sale","theme_real_estate","theme_real_estate_sale","theme_treehouse","theme_vehicle","theme_vehicle_sale","theme_yes","theme_yes_sale","theme_zap","timesheet_grid","timesheet_grid_sale","transifex","uom","utm","voip","voip_onsip","web","web_cohort","web_dashboard","web_diagram","web_editor","web_enterprise","web_gantt","web_grid","web_kanban_gauge","web_mobile","web_settings_dashboard","web_studio","web_tour","web_unsplash","website","website_animate","website_blog","website_calendar","website_crm","website_crm_partner_assign","website_crm_phone_validation","website_crm_score","website_customer","website_delivery_ups","website_enterprise","website_event","website_event_questions","website_event_sale","website_event_track","website_form","website_form_editor","website_form_project","website_forum","website_gengo","website_google_map","website_helpdesk","website_helpdesk_form","website_helpdesk_forum","website_helpdesk_livechat","website_helpdesk_slides","website_hr","website_hr_recruitment","website_links","website_livechat","website_mail","website_mail_channel","website_mass_mailing","website_membership","website_partner","website_payment","website_rating","website_sale","website_sale_account_taxcloud","website_sale_comparison","website_sale_coupon","website_sale_dashboard","website_sale_delivery","website_sale_digital","website_sale_link_tracker","website_sale_management","website_sale_stock","website_sale_taxcloud_delivery","website_sale_wishlist","website_slides","website_studio","website_survey","website_theme_install","website_twitter","website_twitter_wall"] + for view in self: + module="" + is_studio=False + has_xml_id=False + query_cnt = """SELECT count(*) cnt + FROM ir_ui_view v + JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id) + WHERE v.id=%s""" + self._cr.execute(query_cnt, (view.id,)) + if self._cr.fetchone()[0]>0: + query = """SELECT md.module, coalesce(studio,false) studio + FROM ir_ui_view v + JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id) + WHERE v.id=%s""" + self._cr.execute(query, (view.id,)) + module, is_studio = self._cr.fetchone() + has_xml_id=True + if module not in (module_list): + # Not supported Customisation. + # If deactivated, if the view is set to active in the customer xml, this can lead to issue. + # To avoid those issue, just delete then view. + if delete: + view.with_context(_force_unlink=True).unlink() + else: + view.active=False + else: + if is_studio and module in module_list: + # Presume this is a supported studio customization. + # As supported, shouldn't be deactivated or removed. + _logger.warning("Studio view %s generates issue" %(self)) + if not is_studio and module in module_list: + # Presume this is a normal odoo view. + # Can be deactivated + view.active=False + if module not in module_list: + # Not supported Customisation. + # If deactivated, if the view is set to active in the customer xml, this can lead to issue. + # To avoid those issue, just delete then view. + if delete: + view.with_context(_force_unlink=True).unlink() + else: + view.active=False diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py index 461fe6277c316..ca5495e305bdb 100644 --- a/odoo/addons/base/tests/test_views.py +++ b/odoo/addons/base/tests/test_views.py @@ -9,7 +9,7 @@ from odoo.osv.orm import modifiers_tests from odoo.exceptions import ValidationError -from odoo.tests import common +from odoo.tests import common, tagged from odoo.tools import mute_logger @@ -2030,3 +2030,254 @@ def test_render(self): content3 = self.env['ir.qweb'].with_context(check_view_ids=[view1.id, view2.id, view3.id]).render('base.dummy_primary_ext') self.assertNotEqual(content1, content3) + +@tagged('post_install', '-at_install') +class TestFixFaultyViewsComplex(common.TransactionCase): + """ Test _fix_faulty_views() + """ + + def setUp(self): + super(TestFixFaultyViewsComplex, self).setUp() + self.View = self.env['ir.ui.view'] + """ Create a complex case to fix (links are inherits) + + +---view1111 + +----- view11---------view111----+ + | +---view1112 + | + view1 ---------+----- view12---------view121 + | + | +-----view131 + +----- view13----+ + +-----view132 + + """ + self.view1 = self.env['ir.ui.view'].create({ + 'name': "bob", + 'model': 'ir.ui.view', + 'arch': """ +
+ + + """ + }) + self.view11 = self.env['ir.ui.view'].create({ + 'name': "bob", + 'model': 'ir.ui.view', + 'inherit_id': self.view1.id, + 'arch': """ + + + + """ + }) + self.view12 = self.env['ir.ui.view'].create({ + 'name': "bob", + 'model': 'ir.ui.view', + 'inherit_id': self.view1.id, + 'arch': """ + + + + """ + }) + self.view13 = self.env['ir.ui.view'].create({ + 'name': "bob", + 'model': 'ir.ui.view', + 'inherit_id': self.view1.id, + 'arch': """ + + + + """ + }) + self.view111 = self.env['ir.ui.view'].create({ + 'name': "bob", + 'model': 'ir.ui.view', + 'inherit_id': self.view11.id, + 'arch': """ + + + + """ + }) + self.view121 = self.env['ir.ui.view'].create({ + 'name': "bob", + 'model': 'ir.ui.view', + 'inherit_id': self.view12.id, + 'arch': """ + + + + """ + }) + self.view131 = self.env['ir.ui.view'].create({ + 'name': "bob", + 'model': 'ir.ui.view', + 'inherit_id': self.view13.id, + 'arch': """ + + + + """ + }) + self.view132 = self.env['ir.ui.view'].create({ + 'name': "bob", + 'model': 'ir.ui.view', + 'inherit_id': self.view13.id, + 'arch': """ + + + + """ + }) + self.view1111 = self.env['ir.ui.view'].create({ + 'name': "bob", + 'model': 'ir.ui.view', + 'inherit_id': self.view111.id, + 'arch': """ + + + + """ + }) + self.view1112 = self.env['ir.ui.view'].create({ + 'name': "bob", + 'model': 'ir.ui.view', + 'inherit_id': self.view111.id, + 'arch': """ + + + + """ + }) + + def test_sanity_check(self): + faulty_views = self.view1._get_faulty_views( + self.view1, self.env['ir.ui.view']) + assert len(faulty_views) <= 0 + + def test_fix_view_test1(self): + # invalidate view1111 only + + # check all is ok + faulty_views = self.view1._get_faulty_views( + self.view1, self.env['ir.ui.view']) + assert len(faulty_views) <= 0 + # detect the issue + self.cr.execute("""update ir_ui_view set arch_db= + ' + + + + ' + where id=%s""" % (self.view1111.id)) + self.env['ir.ui.view'].invalidate_cache() + # can only creates valid views, can update it with wrong values in sql but good values stay in the cache + # detect the issue + faulty_views = self.view1._get_faulty_views( + self.view1, self.env['ir.ui.view']) + self.assertEqual(self.view1111, faulty_views) + # solve the issue (with deactivation) + self.view1._fix_faulty_views() + active_views = [self.view1, self.view11, self.view12, self.view111, + self.view121, self.view1112, self.view13, self.view131, self.view132] + deactivated_views = [self.view1111] + assert all(item.active for item in active_views) + assert all(not item.active for item in deactivated_views) + # solve the issue (with delete) + self.view1111.active = True + self.view1._fix_faulty_views(delete=True) + self.env.cr.execute( + "select count(id) from ir_ui_view where id = %s", (self.view1111.id,)) + cnt = self.env.cr.fetchone()[0] + assert cnt == 0 + + def test_fix_view_test2(self): + # invalidate view1111 and view131 + + # check all is ok + faulty_views = self.view1._get_faulty_views( + self.view1, self.env['ir.ui.view']) + assert len(faulty_views) <= 0 + # detect the issue + self.cr.execute("""update ir_ui_view set arch_db= + ' + + + + ' + where id=%s""" % (self.view1111.id)) + self.cr.execute("""update ir_ui_view set arch_db= + ' + + + + ' + where id=%s""" % (self.view131.id)) + self.env['ir.ui.view'].invalidate_cache() + # can only creates valid views, can update it with wrong values in sql but good values stay in the cache + # detect the issue + faulty_views = self.view1._get_faulty_views( + self.view1, self.env['ir.ui.view']) + self.assertEqual(self.view1111+self.view131, faulty_views) + # solve the issue (with deactivation) + self.view1._fix_faulty_views() + active_views = [self.view1, self.view11, self.view12, self.view111, + self.view121, self.view1112, self.view13, self.view132] + deactivated_views = [self.view1111, self.view131] + assert all(item.active for item in active_views) + assert all(not item.active for item in deactivated_views) + # solve the issue (with delete) + self.view1111.active = True + self.view131.active = True + self.view1._fix_faulty_views(delete=True) + self.env.cr.execute( + "select count(id) from ir_ui_view where id = %s or id = %s", (self.view1111.id, self.view131.id,)) + cnt = self.env.cr.fetchone()[0] + assert cnt == 0 + + def test_fix_view_test3(self): + # invalidate view1111 and view13 + + # check all is ok + faulty_views = self.view1._get_faulty_views( + self.view1, self.env['ir.ui.view']) + assert len(faulty_views) <= 0 + # detect the issue + self.cr.execute("""update ir_ui_view set arch_db= + ' + + + + ' + where id=%s""" % (self.view1111.id)) + self.cr.execute("""update ir_ui_view set arch_db= + ' + + + + ' + where id=%s""" % (self.view13.id)) + self.env['ir.ui.view'].invalidate_cache() + # can only creates valid views, can update it with wrong values in sql but good values stay in the cache + # detect the issue + faulty_views = self.view1._get_faulty_views( + self.view1, self.env['ir.ui.view']) + self.assertEqual(self.view1111+self.view13, faulty_views) + # solve the issue (with deactivation) + self.view1._fix_faulty_views() + active_views = [self.view1, self.view11, self.view12, + self.view111, self.view121, self.view1112] + deactivated_views = [self.view1111, + self.view13, self.view131, self.view132] + assert all(item.active for item in active_views) + assert all(not item.active for item in deactivated_views) + # solve the issue (with delete) + self.view1111.active = True + self.view131.active = True + self.view1._fix_faulty_views(delete=True) + self.env.cr.execute( + "select count(id) from ir_ui_view where id = %s or id = %s", (self.view1111.id, self.view131.id,)) + cnt = self.env.cr.fetchone()[0] + assert cnt == 0 \ No newline at end of file From 7c3d92ed70ccb90ea0a9cff1b287bb5d7968b366 Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwerburgh Date: Thu, 4 Apr 2019 13:11:52 +0200 Subject: [PATCH 2/3] [IMP] With comments in the PR --- odoo/addons/base/models/ir_ui_view.py | 80 +++++++++------------------ odoo/addons/base/tests/test_views.py | 21 +++---- 2 files changed, 34 insertions(+), 67 deletions(-) diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index 73b4d2d945533..8ceac4be898cf 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -1452,7 +1452,7 @@ def _fix_faulty_views(self, delete=False): """ Use the _get_faulty_views and take appropriate actions to fix the issue """ for view in self: view_id = view.id # allow to use even if view is deleted - faulty_views = view._get_faulty_views(view,self.env['ir.ui.view']) + faulty_views = view._get_faulty_views() if faulty_views: self._cr.execute('SAVEPOINT end_validation') faulty_views._clean_properly(delete) @@ -1467,7 +1467,7 @@ def _fix_faulty_views(self, delete=False): _logger.warning("Issue occurs: The proposition made to fix the view %s (disable/delete %s) is not recommended" % (view,faulty_views)) # should not happend - def _get_faulty_views(self,parent_view, disabled_views): + def _get_faulty_views(self,parent_view=None): """ Returns the faulty views To determine the faulty views, we travel the tree recursively. Disable all children, then enable one by one. If there is an issue. @@ -1475,25 +1475,22 @@ def _get_faulty_views(self,parent_view, disabled_views): Disable faulty node and continue recursion. This function is never called from odoo code but allows to be called from a server action. Please don't remove - params: - parent_view - ir.ui.view - Parent view used by recusion to keep the top parent view - disabled_views - ir.ui.view - Disabled views usage: - view1._get_faulty_views(view1, self.env['ir.ui.view']) + view1._get_faulty_views() """ self.ensure_one() + disabled_views = self.env['ir.ui.view'] + parent_view = parent_view or self if parent_view==self: if self._is_valid(): return disabled_views self._cr.execute('SAVEPOINT test_fix_views') - temp_disabled_views=self._deactivate_lvl1_children() + temp_disabled_views=self._deactivate_lvl1_children() #ordered by priority ( by default) if self._is_valid(): - if self._has_active_children() or len(temp_disabled_views)>1: + if len(self.inherit_children_ids) or len(temp_disabled_views)>1: for child_view in (temp_disabled_views): - v =child_view._get_faulty_views(self,disabled_views) - for vv in v: - if vv not in disabled_views: # ir.ui.view(1,2) not in ir.ui.view(1,3,4) - disabled_views+=vv # This loop avoid duplicates ir.ui.view(1,1,2,3,4) + for v in child_view._get_faulty_views(self): + disabled_views|=v else: self.active=False disabled_views+=self @@ -1508,15 +1505,13 @@ def _get_faulty_views(self,parent_view, disabled_views): self.active=True if parent_view._is_valid(): return disabled_views - if self._has_active_children(): + if len(self.inherit_children_ids): temp_disabled_views=self._deactivate_lvl1_children() if parent_view._is_valid(): # problem is in one of the children for child_view in (temp_disabled_views): - v =child_view._get_faulty_views(parent_view,disabled_views) - for vv in v: - if vv not in disabled_views: - disabled_views+=vv + for v in child_view._get_faulty_views(parent_view): + disabled_views|=v return disabled_views else: self.active=False @@ -1529,46 +1524,30 @@ def _get_faulty_views(self,parent_view, disabled_views): def _deactivate_lvl1_children(self): """deactivate and return views that has been deactivated taking care of view priority""" - temp_disabled_views= self._get_lvl1_children() - for v in temp_disabled_views: - v.active=False + temp_disabled_views= self.inherit_children_ids + temp_disabled_views.update({'active': False}) return temp_disabled_views - - def _get_lvl1_children(self): - """return lvl1 children for the given view pay attention to order""" - return self.inherit_children_ids - - def _has_active_children(self): - return len(self._get_lvl1_children()) def _is_valid(self): try: - self._check_xml() # _check_xml includes inherits - return True - except Exception as e: + return self._check_xml() # _check_xml includes inherits + except ValidationError as e: # NOTE: e contains the reason why the view is invalid return False + except etree.XMLSyntaxError as e: + return False def _clean_properly(self, delete=False): # list of standard module in odoo module_list=["base","test_inherits","test_inherit","test_limits","test_exceptions","test_uninstall","test_translation_import","test_main_flows","test_read_group","test_documentation_examples","test_new_api","test_assetsbundle","test_access_rights","test_impex","test_testing_utilities","test_pylint","test_convert","test_mimetypes","test_inherits_depends","test_converter","test_performance","account","account_3way_match","account_accountant","account_analytic_default","account_asset","account_bank_statement_import","account_bank_statement_import_camt","account_bank_statement_import_csv","account_bank_statement_import_ofx","account_bank_statement_import_qif","account_batch_payment","account_budget","account_cancel","account_check_printing","account_deferred_revenue","account_facturx","account_intrastat","account_invoice_extract","account_lock","account_online_sync","account_payment","account_plaid","account_predictive_bills","account_reports","account_reports_followup","account_sepa","account_sepa_direct_debit","account_tax_python","account_taxcloud","account_test","account_voucher","account_yodlee","analytic","analytic_enterprise","association","auth_ldap","auth_oauth","auth_password_policy","auth_password_policy_signup","auth_signup","barcodes","barcodes_mobile","base_address_city","base_address_extended","base_automation","base_automation_hr_contract","base_gengo","base_geolocalize","base_iban","base_import","base_import_module","base_setup","base_sparse_field","base_vat","board","bus","calendar","calendar_sms","contacts","crm","crm_enterprise","crm_livechat","crm_phone_validation","crm_project","crm_reveal","currency_rate_live","decimal_precision","delivery","delivery_barcode","delivery_bpost","delivery_dhl","delivery_easypost","delivery_fedex","delivery_hs_code","delivery_ups","delivery_usps","digest","document","documents","documents_account","documents_product","documents_project","documents_sign","event","event_barcode","event_barcode_mobile","event_enterprise","event_sale","fetchmail","fleet","gamification","gamification_sale_crm","google_account","google_calendar","google_drive","google_spreadsheet","helpdesk","helpdesk_sale_timesheet","helpdesk_timesheet","hr","hr_appraisal","hr_attendance","hr_contract","hr_contract_salary","hr_expense","hr_expense_check","hr_gamification","hr_holidays","hr_holidays_gantt","hr_maintenance","hr_org_chart","hr_payroll","hr_payroll_account","hr_recruitment","hr_recruitment_survey","hr_timesheet","hr_timesheet_attendance","http_routing","hw_blackbox_be","hw_drivers","hw_escpos","hw_posbox_homepage","hw_posbox_upgrade","hw_proxy","hw_scale","hw_scanner","hw_screen","iap","im_livechat","im_livechat_mail_bot","im_support","inter_company_rules","iot","l10n_ae","l10n_ar","l10n_ar_reports","l10n_at","l10n_at_reports","l10n_au","l10n_au_aba","l10n_au_reports","l10n_be","l10n_be_coda","l10n_be_hr_payroll","l10n_be_hr_payroll_account","l10n_be_hr_payroll_fleet","l10n_be_intrastat","l10n_be_intrastat_2019","l10n_be_invoice_bba","l10n_be_reports","l10n_bo","l10n_bo_reports","l10n_br","l10n_br_reports","l10n_ca","l10n_ca_check_printing","l10n_ch","l10n_ch_reports","l10n_cl","l10n_cl_reports","l10n_cn","l10n_cn_city","l10n_cn_small_business","l10n_cn_standard","l10n_co","l10n_co_edi","l10n_co_reports","l10n_cr","l10n_de","l10n_de_reports","l10n_de_skr03","l10n_de_skr04","l10n_do","l10n_do_reports","l10n_ec","l10n_es","l10n_es_real_estates","l10n_es_reports","l10n_et","l10n_et_reports","l10n_eu_service","l10n_fr","l10n_fr_certification","l10n_fr_fec","l10n_fr_hr_payroll","l10n_fr_pos_cert","l10n_fr_reports","l10n_fr_sale_closing","l10n_generic_coa","l10n_gr","l10n_gr_reports","l10n_gt","l10n_hk","l10n_hn","l10n_hr","l10n_hr_reports","l10n_hu","l10n_hu_reports","l10n_in","l10n_in_hr_payroll","l10n_in_purchase","l10n_in_reports","l10n_in_sale","l10n_in_schedule6","l10n_in_stock","l10n_it","l10n_jp","l10n_jp_reports","l10n_lu","l10n_lu_reports","l10n_ma","l10n_ma_reports","l10n_multilang","l10n_mx","l10n_mx_edi","l10n_mx_edi_customs","l10n_mx_edi_external_trade","l10n_mx_edi_landing","l10n_mx_edi_payment","l10n_mx_edi_payment_bank","l10n_mx_reports","l10n_mx_tax_cash_basis","l10n_nl","l10n_nl_intrastat","l10n_nl_reports","l10n_no","l10n_no_reports","l10n_nz","l10n_pa","l10n_pe","l10n_pl","l10n_pl_reports","l10n_pt","l10n_ro","l10n_ro_reports","l10n_sa","l10n_sg","l10n_sg_reports","l10n_si","l10n_si_reports","l10n_syscohada","l10n_th","l10n_th_reports","l10n_tr","l10n_uk","l10n_uk_reports","l10n_us","l10n_us_check_printing","l10n_us_reports","l10n_uy","l10n_uy_reports","l10n_ve","l10n_vn","l10n_vn_reports","link_tracker","lunch","mail","mail_bot","mail_enterprise","mail_github","maintenance","marketing_automation","mass_mailing","mass_mailing_crm","mass_mailing_event","mass_mailing_event_track","mass_mailing_sale","mass_mailing_themes","membership","mrp","mrp_account","mrp_bom_cost","mrp_byproduct","mrp_maintenance","mrp_mps","mrp_plm","mrp_workorder","mrp_zebra","note","note_pad","ocn_client","pad","pad_project","partner_autocomplete","partner_autocomplete_address_extended","payment","payment_adyen","payment_authorize","payment_buckaroo","payment_ogone","payment_paypal","payment_payumoney","payment_sips","payment_stripe","payment_transfer","phone_validation","point_of_sale","portal","pos_blackbox_be","pos_cache","pos_discount","pos_iot","pos_loyalty","pos_mercury","pos_reprint","pos_restaurant","pos_restaurant_iot","pos_sale","procurement_jit","product","product_email_template","product_expiry","product_margin","project","project_enterprise","project_forecast","project_timesheet_forecast","project_timesheet_forecast_sale","project_timesheet_holidays","project_timesheet_synchro","purchase","purchase_mrp","purchase_mrp_workorder_quality","purchase_requisition","purchase_stock","quality","quality_control","quality_iot","quality_mrp","quality_mrp_iot","quality_mrp_workorder","rating","repair","resource","sale","sale_account_taxcloud","sale_coupon","sale_coupon_delivery","sale_crm","sale_ebay","sale_enterprise","sale_expense","sale_intrastat","sale_management","sale_margin","sale_mrp","sale_purchase","sale_quotation_builder","sale_stock","sale_subscription","sale_subscription_asset","sale_subscription_dashboard","sale_timesheet","sales_team","sign","sms","snailmail","snailmail_account","snailmail_account_reports_followup","social_media","stock","stock_account","stock_account_enterprise","stock_barcode","stock_barcode_mobile","stock_dropshipping","stock_enterprise","stock_intrastat","stock_landed_costs","stock_picking_batch","stock_zebra","studio_customization","survey","survey_crm","test_mail","test_marketing_automation","test_mass_mailing","theme_anelusia","theme_anelusia_sale","theme_artists","theme_artists_sale","theme_avantgarde","theme_avantgarde_blog","theme_beauty","theme_beauty_sale","theme_bewise","theme_bistro","theme_bookstore","theme_bookstore_sale","theme_bootswatch","theme_clean","theme_common","theme_default","theme_enark","theme_graphene","theme_graphene_blog","theme_kea","theme_kea_sale","theme_kiddo","theme_kiddo_sale","theme_loftspace","theme_loftspace_sale","theme_monglia","theme_monglia_sale","theme_nano","theme_notes","theme_notes_sale","theme_odoo_experts","theme_odoo_experts_sale","theme_orchid","theme_orchid_sale","theme_real_estate","theme_real_estate_sale","theme_treehouse","theme_vehicle","theme_vehicle_sale","theme_yes","theme_yes_sale","theme_zap","timesheet_grid","timesheet_grid_sale","transifex","uom","utm","voip","voip_onsip","web","web_cohort","web_dashboard","web_diagram","web_editor","web_enterprise","web_gantt","web_grid","web_kanban_gauge","web_mobile","web_settings_dashboard","web_studio","web_tour","web_unsplash","website","website_animate","website_blog","website_calendar","website_crm","website_crm_partner_assign","website_crm_phone_validation","website_crm_score","website_customer","website_delivery_ups","website_enterprise","website_event","website_event_questions","website_event_sale","website_event_track","website_form","website_form_editor","website_form_project","website_forum","website_gengo","website_google_map","website_helpdesk","website_helpdesk_form","website_helpdesk_forum","website_helpdesk_livechat","website_helpdesk_slides","website_hr","website_hr_recruitment","website_links","website_livechat","website_mail","website_mail_channel","website_mass_mailing","website_membership","website_partner","website_payment","website_rating","website_sale","website_sale_account_taxcloud","website_sale_comparison","website_sale_coupon","website_sale_dashboard","website_sale_delivery","website_sale_digital","website_sale_link_tracker","website_sale_management","website_sale_stock","website_sale_taxcloud_delivery","website_sale_wishlist","website_slides","website_studio","website_survey","website_theme_install","website_twitter","website_twitter_wall"] for view in self: - module="" - is_studio=False - has_xml_id=False - query_cnt = """SELECT count(*) cnt - FROM ir_ui_view v - JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id) - WHERE v.id=%s""" - self._cr.execute(query_cnt, (view.id,)) - if self._cr.fetchone()[0]>0: - query = """SELECT md.module, coalesce(studio,false) studio - FROM ir_ui_view v - JOIN ir_model_data md ON (md.model = 'ir.ui.view' AND md.res_id = v.id) - WHERE v.id=%s""" - self._cr.execute(query, (view.id,)) - module, is_studio = self._cr.fetchone() - has_xml_id=True + module="" + is_studio=False + xml_id = len(view._get_external_ids()) + if xml_id: + module = self.env['ir.model.data'].browse(xml_id).module + if self.env['ir.module.module'].search([('name','=','web_studio'),('state','=','installed')]): + is_studio= self.studio if module not in (module_list): # Not supported Customisation. # If deactivated, if the view is set to active in the customer xml, this can lead to issue. @@ -1578,18 +1557,13 @@ def _clean_properly(self, delete=False): else: view.active=False else: - if is_studio and module in module_list: + if is_studio: # Presume this is a supported studio customization. # As supported, shouldn't be deactivated or removed. _logger.warning("Studio view %s generates issue" %(self)) - if not is_studio and module in module_list: + if not is_studio: # Presume this is a normal odoo view. # Can be deactivated - view.active=False - if module not in module_list: - # Not supported Customisation. - # If deactivated, if the view is set to active in the customer xml, this can lead to issue. - # To avoid those issue, just delete then view. if delete: view.with_context(_force_unlink=True).unlink() else: diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py index ca5495e305bdb..f5801e3116ff4 100644 --- a/odoo/addons/base/tests/test_views.py +++ b/odoo/addons/base/tests/test_views.py @@ -2153,16 +2153,14 @@ def setUp(self): }) def test_sanity_check(self): - faulty_views = self.view1._get_faulty_views( - self.view1, self.env['ir.ui.view']) + faulty_views = self.view1._get_faulty_views() assert len(faulty_views) <= 0 def test_fix_view_test1(self): # invalidate view1111 only # check all is ok - faulty_views = self.view1._get_faulty_views( - self.view1, self.env['ir.ui.view']) + faulty_views = self.view1._get_faulty_views() assert len(faulty_views) <= 0 # detect the issue self.cr.execute("""update ir_ui_view set arch_db= @@ -2175,8 +2173,7 @@ def test_fix_view_test1(self): self.env['ir.ui.view'].invalidate_cache() # can only creates valid views, can update it with wrong values in sql but good values stay in the cache # detect the issue - faulty_views = self.view1._get_faulty_views( - self.view1, self.env['ir.ui.view']) + faulty_views = self.view1._get_faulty_views() self.assertEqual(self.view1111, faulty_views) # solve the issue (with deactivation) self.view1._fix_faulty_views() @@ -2197,8 +2194,7 @@ def test_fix_view_test2(self): # invalidate view1111 and view131 # check all is ok - faulty_views = self.view1._get_faulty_views( - self.view1, self.env['ir.ui.view']) + faulty_views = self.view1._get_faulty_views() assert len(faulty_views) <= 0 # detect the issue self.cr.execute("""update ir_ui_view set arch_db= @@ -2218,8 +2214,7 @@ def test_fix_view_test2(self): self.env['ir.ui.view'].invalidate_cache() # can only creates valid views, can update it with wrong values in sql but good values stay in the cache # detect the issue - faulty_views = self.view1._get_faulty_views( - self.view1, self.env['ir.ui.view']) + faulty_views = self.view1._get_faulty_views() self.assertEqual(self.view1111+self.view131, faulty_views) # solve the issue (with deactivation) self.view1._fix_faulty_views() @@ -2241,8 +2236,7 @@ def test_fix_view_test3(self): # invalidate view1111 and view13 # check all is ok - faulty_views = self.view1._get_faulty_views( - self.view1, self.env['ir.ui.view']) + faulty_views = self.view1._get_faulty_views() assert len(faulty_views) <= 0 # detect the issue self.cr.execute("""update ir_ui_view set arch_db= @@ -2262,8 +2256,7 @@ def test_fix_view_test3(self): self.env['ir.ui.view'].invalidate_cache() # can only creates valid views, can update it with wrong values in sql but good values stay in the cache # detect the issue - faulty_views = self.view1._get_faulty_views( - self.view1, self.env['ir.ui.view']) + faulty_views = self.view1._get_faulty_views() self.assertEqual(self.view1111+self.view13, faulty_views) # solve the issue (with deactivation) self.view1._fix_faulty_views() From 52f804b17e3e8aa9fecfb7e51191eb0e89345504 Mon Sep 17 00:00:00 2001 From: Nicolas Vannieuwerburgh Date: Wed, 10 Apr 2019 09:27:45 +0200 Subject: [PATCH 3/3] [IMP] with other comments for the PR --- odoo/addons/base/models/ir_ui_view.py | 103 ++++++++++++++------------ 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index 8ceac4be898cf..75b505bdfb137 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -27,7 +27,7 @@ from odoo.http import request from odoo.modules.module import get_resource_from_path, get_resource_path from odoo.osv import orm -from odoo.tools import config, graph, ConstantMapping, SKIPPED_ELEMENT_TYPES, pycompat +from odoo.tools import config, graph, ConstantMapping, SKIPPED_ELEMENT_TYPES, pycompat, mute_logger from odoo.tools.convert import _fix_multiple_roots from odoo.tools.parse_version import parse_version from odoo.tools.safe_eval import safe_eval @@ -1451,103 +1451,104 @@ def _validate_module_views(self, module): def _fix_faulty_views(self, delete=False): """ Use the _get_faulty_views and take appropriate actions to fix the issue """ for view in self: - view_id = view.id # allow to use even if view is deleted + view_id = view.id # allow to use even if view is deleted faulty_views = view._get_faulty_views() if faulty_views: self._cr.execute('SAVEPOINT end_validation') faulty_views._clean_properly(delete) - self._cr.execute("select count(id) from ir_ui_view where id = %s", (view_id,)) - cnt = self.env.cr.fetchone()[0] - if cnt == 0 or view._is_valid(): - _logger.info("View %s can be fixed by disabling/deleting following views: %s" % (view_id,faulty_views)) + if view.exists() or view._is_valid(): + _logger.info( + "View %s can be fixed by disabling/deleting following views: %s" % (view_id, faulty_views)) self._cr.execute('RELEASE end_validation') else: self._cr.execute('ROLLBACK TO end_validation') self._cr.execute('RELEASE end_validation') - _logger.warning("Issue occurs: The proposition made to fix the view %s (disable/delete %s) is not recommended" % (view,faulty_views)) + _logger.warning( + "Issue occurs: The proposition made to fix the view %s (disable/delete %s) is not recommended" % (view, faulty_views)) # should not happend - def _get_faulty_views(self,parent_view=None): - """ Returns the faulty views - To determine the faulty views, we travel the tree recursively. - Disable all children, then enable one by one. If there is an issue. - The activated node or at least one of its children are faulty. - Disable faulty node and continue recursion. - This function is never called from odoo code but allows to be called from a server action. - Please don't remove - usage: - view1._get_faulty_views() + def _get_faulty_views(self, first_call=True): + """ Returns the faulty views. + + To determine the faulty views, we travel the tree recursively. + Disable all children, then enable one by one. If there is an issue, + the activated node or at least one of its children are faulty. + Disable faulty node and continue recursion. + This function is never called from odoo code but allows to be called from a server action. + Please don't remove """ self.ensure_one() disabled_views = self.env['ir.ui.view'] - parent_view = parent_view or self - if parent_view==self: + parent_view = self + while parent_view.mode != 'primary': + parent_view = parent_view.inherit_id + if first_call: if self._is_valid(): return disabled_views self._cr.execute('SAVEPOINT test_fix_views') - temp_disabled_views=self._deactivate_lvl1_children() #ordered by priority ( by default) + # ordered by priority ( by default) + temp_disabled_views = self._deactivate_lvl1_children() if self._is_valid(): - if len(self.inherit_children_ids) or len(temp_disabled_views)>1: + if len(self.inherit_children_ids) or len(temp_disabled_views) > 1: for child_view in (temp_disabled_views): - for v in child_view._get_faulty_views(self): - disabled_views|=v + for v in child_view._get_faulty_views(first_call=False): + disabled_views |= v else: - self.active=False - disabled_views+=self + self.active = False + disabled_views |= self else: - self.active=False - disabled_views+=self + self.active = False + disabled_views |= self self._cr.execute('ROLLBACK TO test_fix_views') self._cr.execute('RELEASE test_fix_views') return disabled_views else: - assert parent_view._is_valid() - self.active=True + self.active = True if parent_view._is_valid(): return disabled_views if len(self.inherit_children_ids): - temp_disabled_views=self._deactivate_lvl1_children() + temp_disabled_views = self._deactivate_lvl1_children() if parent_view._is_valid(): # problem is in one of the children for child_view in (temp_disabled_views): - for v in child_view._get_faulty_views(parent_view): - disabled_views|=v + for v in child_view._get_faulty_views(first_call=False): + disabled_views |= v return disabled_views else: - self.active=False - disabled_views+=self + self.active = False + disabled_views |= self return disabled_views else: - self.active=False - disabled_views+=self + self.active = False + disabled_views |= self return disabled_views def _deactivate_lvl1_children(self): """deactivate and return views that has been deactivated taking care of view priority""" - temp_disabled_views= self.inherit_children_ids + temp_disabled_views = self.inherit_children_ids temp_disabled_views.update({'active': False}) return temp_disabled_views + @mute_logger('odoo.addons.base.models.ir_ui_view') def _is_valid(self): try: - return self._check_xml() # _check_xml includes inherits - except ValidationError as e: + return self._check_xml() # _check_xml includes inherits + except (ValidationError,etree.XMLSyntaxError, ValueError) as e: # NOTE: e contains the reason why the view is invalid return False - except etree.XMLSyntaxError as e: - return False def _clean_properly(self, delete=False): # list of standard module in odoo - module_list=["base","test_inherits","test_inherit","test_limits","test_exceptions","test_uninstall","test_translation_import","test_main_flows","test_read_group","test_documentation_examples","test_new_api","test_assetsbundle","test_access_rights","test_impex","test_testing_utilities","test_pylint","test_convert","test_mimetypes","test_inherits_depends","test_converter","test_performance","account","account_3way_match","account_accountant","account_analytic_default","account_asset","account_bank_statement_import","account_bank_statement_import_camt","account_bank_statement_import_csv","account_bank_statement_import_ofx","account_bank_statement_import_qif","account_batch_payment","account_budget","account_cancel","account_check_printing","account_deferred_revenue","account_facturx","account_intrastat","account_invoice_extract","account_lock","account_online_sync","account_payment","account_plaid","account_predictive_bills","account_reports","account_reports_followup","account_sepa","account_sepa_direct_debit","account_tax_python","account_taxcloud","account_test","account_voucher","account_yodlee","analytic","analytic_enterprise","association","auth_ldap","auth_oauth","auth_password_policy","auth_password_policy_signup","auth_signup","barcodes","barcodes_mobile","base_address_city","base_address_extended","base_automation","base_automation_hr_contract","base_gengo","base_geolocalize","base_iban","base_import","base_import_module","base_setup","base_sparse_field","base_vat","board","bus","calendar","calendar_sms","contacts","crm","crm_enterprise","crm_livechat","crm_phone_validation","crm_project","crm_reveal","currency_rate_live","decimal_precision","delivery","delivery_barcode","delivery_bpost","delivery_dhl","delivery_easypost","delivery_fedex","delivery_hs_code","delivery_ups","delivery_usps","digest","document","documents","documents_account","documents_product","documents_project","documents_sign","event","event_barcode","event_barcode_mobile","event_enterprise","event_sale","fetchmail","fleet","gamification","gamification_sale_crm","google_account","google_calendar","google_drive","google_spreadsheet","helpdesk","helpdesk_sale_timesheet","helpdesk_timesheet","hr","hr_appraisal","hr_attendance","hr_contract","hr_contract_salary","hr_expense","hr_expense_check","hr_gamification","hr_holidays","hr_holidays_gantt","hr_maintenance","hr_org_chart","hr_payroll","hr_payroll_account","hr_recruitment","hr_recruitment_survey","hr_timesheet","hr_timesheet_attendance","http_routing","hw_blackbox_be","hw_drivers","hw_escpos","hw_posbox_homepage","hw_posbox_upgrade","hw_proxy","hw_scale","hw_scanner","hw_screen","iap","im_livechat","im_livechat_mail_bot","im_support","inter_company_rules","iot","l10n_ae","l10n_ar","l10n_ar_reports","l10n_at","l10n_at_reports","l10n_au","l10n_au_aba","l10n_au_reports","l10n_be","l10n_be_coda","l10n_be_hr_payroll","l10n_be_hr_payroll_account","l10n_be_hr_payroll_fleet","l10n_be_intrastat","l10n_be_intrastat_2019","l10n_be_invoice_bba","l10n_be_reports","l10n_bo","l10n_bo_reports","l10n_br","l10n_br_reports","l10n_ca","l10n_ca_check_printing","l10n_ch","l10n_ch_reports","l10n_cl","l10n_cl_reports","l10n_cn","l10n_cn_city","l10n_cn_small_business","l10n_cn_standard","l10n_co","l10n_co_edi","l10n_co_reports","l10n_cr","l10n_de","l10n_de_reports","l10n_de_skr03","l10n_de_skr04","l10n_do","l10n_do_reports","l10n_ec","l10n_es","l10n_es_real_estates","l10n_es_reports","l10n_et","l10n_et_reports","l10n_eu_service","l10n_fr","l10n_fr_certification","l10n_fr_fec","l10n_fr_hr_payroll","l10n_fr_pos_cert","l10n_fr_reports","l10n_fr_sale_closing","l10n_generic_coa","l10n_gr","l10n_gr_reports","l10n_gt","l10n_hk","l10n_hn","l10n_hr","l10n_hr_reports","l10n_hu","l10n_hu_reports","l10n_in","l10n_in_hr_payroll","l10n_in_purchase","l10n_in_reports","l10n_in_sale","l10n_in_schedule6","l10n_in_stock","l10n_it","l10n_jp","l10n_jp_reports","l10n_lu","l10n_lu_reports","l10n_ma","l10n_ma_reports","l10n_multilang","l10n_mx","l10n_mx_edi","l10n_mx_edi_customs","l10n_mx_edi_external_trade","l10n_mx_edi_landing","l10n_mx_edi_payment","l10n_mx_edi_payment_bank","l10n_mx_reports","l10n_mx_tax_cash_basis","l10n_nl","l10n_nl_intrastat","l10n_nl_reports","l10n_no","l10n_no_reports","l10n_nz","l10n_pa","l10n_pe","l10n_pl","l10n_pl_reports","l10n_pt","l10n_ro","l10n_ro_reports","l10n_sa","l10n_sg","l10n_sg_reports","l10n_si","l10n_si_reports","l10n_syscohada","l10n_th","l10n_th_reports","l10n_tr","l10n_uk","l10n_uk_reports","l10n_us","l10n_us_check_printing","l10n_us_reports","l10n_uy","l10n_uy_reports","l10n_ve","l10n_vn","l10n_vn_reports","link_tracker","lunch","mail","mail_bot","mail_enterprise","mail_github","maintenance","marketing_automation","mass_mailing","mass_mailing_crm","mass_mailing_event","mass_mailing_event_track","mass_mailing_sale","mass_mailing_themes","membership","mrp","mrp_account","mrp_bom_cost","mrp_byproduct","mrp_maintenance","mrp_mps","mrp_plm","mrp_workorder","mrp_zebra","note","note_pad","ocn_client","pad","pad_project","partner_autocomplete","partner_autocomplete_address_extended","payment","payment_adyen","payment_authorize","payment_buckaroo","payment_ogone","payment_paypal","payment_payumoney","payment_sips","payment_stripe","payment_transfer","phone_validation","point_of_sale","portal","pos_blackbox_be","pos_cache","pos_discount","pos_iot","pos_loyalty","pos_mercury","pos_reprint","pos_restaurant","pos_restaurant_iot","pos_sale","procurement_jit","product","product_email_template","product_expiry","product_margin","project","project_enterprise","project_forecast","project_timesheet_forecast","project_timesheet_forecast_sale","project_timesheet_holidays","project_timesheet_synchro","purchase","purchase_mrp","purchase_mrp_workorder_quality","purchase_requisition","purchase_stock","quality","quality_control","quality_iot","quality_mrp","quality_mrp_iot","quality_mrp_workorder","rating","repair","resource","sale","sale_account_taxcloud","sale_coupon","sale_coupon_delivery","sale_crm","sale_ebay","sale_enterprise","sale_expense","sale_intrastat","sale_management","sale_margin","sale_mrp","sale_purchase","sale_quotation_builder","sale_stock","sale_subscription","sale_subscription_asset","sale_subscription_dashboard","sale_timesheet","sales_team","sign","sms","snailmail","snailmail_account","snailmail_account_reports_followup","social_media","stock","stock_account","stock_account_enterprise","stock_barcode","stock_barcode_mobile","stock_dropshipping","stock_enterprise","stock_intrastat","stock_landed_costs","stock_picking_batch","stock_zebra","studio_customization","survey","survey_crm","test_mail","test_marketing_automation","test_mass_mailing","theme_anelusia","theme_anelusia_sale","theme_artists","theme_artists_sale","theme_avantgarde","theme_avantgarde_blog","theme_beauty","theme_beauty_sale","theme_bewise","theme_bistro","theme_bookstore","theme_bookstore_sale","theme_bootswatch","theme_clean","theme_common","theme_default","theme_enark","theme_graphene","theme_graphene_blog","theme_kea","theme_kea_sale","theme_kiddo","theme_kiddo_sale","theme_loftspace","theme_loftspace_sale","theme_monglia","theme_monglia_sale","theme_nano","theme_notes","theme_notes_sale","theme_odoo_experts","theme_odoo_experts_sale","theme_orchid","theme_orchid_sale","theme_real_estate","theme_real_estate_sale","theme_treehouse","theme_vehicle","theme_vehicle_sale","theme_yes","theme_yes_sale","theme_zap","timesheet_grid","timesheet_grid_sale","transifex","uom","utm","voip","voip_onsip","web","web_cohort","web_dashboard","web_diagram","web_editor","web_enterprise","web_gantt","web_grid","web_kanban_gauge","web_mobile","web_settings_dashboard","web_studio","web_tour","web_unsplash","website","website_animate","website_blog","website_calendar","website_crm","website_crm_partner_assign","website_crm_phone_validation","website_crm_score","website_customer","website_delivery_ups","website_enterprise","website_event","website_event_questions","website_event_sale","website_event_track","website_form","website_form_editor","website_form_project","website_forum","website_gengo","website_google_map","website_helpdesk","website_helpdesk_form","website_helpdesk_forum","website_helpdesk_livechat","website_helpdesk_slides","website_hr","website_hr_recruitment","website_links","website_livechat","website_mail","website_mail_channel","website_mass_mailing","website_membership","website_partner","website_payment","website_rating","website_sale","website_sale_account_taxcloud","website_sale_comparison","website_sale_coupon","website_sale_dashboard","website_sale_delivery","website_sale_digital","website_sale_link_tracker","website_sale_management","website_sale_stock","website_sale_taxcloud_delivery","website_sale_wishlist","website_slides","website_studio","website_survey","website_theme_install","website_twitter","website_twitter_wall"] + module_list = ["base", "test_inherits", "test_inherit", "test_limits", "test_exceptions", "test_uninstall", "test_translation_import", "test_main_flows", "test_read_group", "test_documentation_examples", "test_new_api", "test_assetsbundle", "test_access_rights", "test_impex", "test_testing_utilities", "test_pylint", "test_convert", "test_mimetypes", "test_inherits_depends", "test_converter", "test_performance", "account", "account_3way_match", "account_accountant", "account_analytic_default", "account_asset", "account_bank_statement_import", "account_bank_statement_import_camt", "account_bank_statement_import_csv", "account_bank_statement_import_ofx", "account_bank_statement_import_qif", "account_batch_payment", "account_budget", "account_cancel", "account_check_printing", "account_deferred_revenue", "account_facturx", "account_intrastat", "account_invoice_extract", "account_lock", "account_online_sync", "account_payment", "account_plaid", "account_predictive_bills", "account_reports", "account_reports_followup", "account_sepa", "account_sepa_direct_debit", "account_tax_python", "account_taxcloud", "account_test", "account_voucher", "account_yodlee", "analytic", "analytic_enterprise", "association", "auth_ldap", "auth_oauth", "auth_password_policy", "auth_password_policy_signup", "auth_signup", "barcodes", "barcodes_mobile", "base_address_city", "base_address_extended", "base_automation", "base_automation_hr_contract", "base_gengo", "base_geolocalize", "base_iban", "base_import", "base_import_module", "base_setup", "base_sparse_field", "base_vat", "board", "bus", "calendar", "calendar_sms", "contacts", "crm", "crm_enterprise", "crm_livechat", "crm_phone_validation", "crm_project", "crm_reveal", "currency_rate_live", "decimal_precision", "delivery", "delivery_barcode", "delivery_bpost", "delivery_dhl", "delivery_easypost", "delivery_fedex", "delivery_hs_code", "delivery_ups", "delivery_usps", "digest", "document", "documents", "documents_account", "documents_product", "documents_project", "documents_sign", "event", "event_barcode", "event_barcode_mobile", "event_enterprise", "event_sale", "fetchmail", "fleet", "gamification", "gamification_sale_crm", "google_account", "google_calendar", "google_drive", "google_spreadsheet", "helpdesk", "helpdesk_sale_timesheet", "helpdesk_timesheet", "hr", "hr_appraisal", "hr_attendance", "hr_contract", "hr_contract_salary", "hr_expense", "hr_expense_check", "hr_gamification", "hr_holidays", "hr_holidays_gantt", "hr_maintenance", "hr_org_chart", "hr_payroll", "hr_payroll_account", "hr_recruitment", "hr_recruitment_survey", "hr_timesheet", "hr_timesheet_attendance", "http_routing", "hw_blackbox_be", "hw_drivers", "hw_escpos", "hw_posbox_homepage", "hw_posbox_upgrade", "hw_proxy", "hw_scale", "hw_scanner", "hw_screen", "iap", "im_livechat", "im_livechat_mail_bot", "im_support", "inter_company_rules", "iot", "l10n_ae", "l10n_ar", "l10n_ar_reports", "l10n_at", "l10n_at_reports", "l10n_au", "l10n_au_aba", "l10n_au_reports", "l10n_be", "l10n_be_coda", "l10n_be_hr_payroll", "l10n_be_hr_payroll_account", "l10n_be_hr_payroll_fleet", "l10n_be_intrastat", "l10n_be_intrastat_2019", "l10n_be_invoice_bba", "l10n_be_reports", "l10n_bo", "l10n_bo_reports", "l10n_br", "l10n_br_reports", "l10n_ca", "l10n_ca_check_printing", "l10n_ch", "l10n_ch_reports", "l10n_cl", "l10n_cl_reports", "l10n_cn", "l10n_cn_city", "l10n_cn_small_business", "l10n_cn_standard", "l10n_co", "l10n_co_edi", "l10n_co_reports", "l10n_cr", "l10n_de", "l10n_de_reports", "l10n_de_skr03", "l10n_de_skr04", "l10n_do", "l10n_do_reports", "l10n_ec", "l10n_es", "l10n_es_real_estates", "l10n_es_reports", "l10n_et", "l10n_et_reports", "l10n_eu_service", "l10n_fr", "l10n_fr_certification", "l10n_fr_fec", "l10n_fr_hr_payroll", "l10n_fr_pos_cert", "l10n_fr_reports", "l10n_fr_sale_closing", "l10n_generic_coa", "l10n_gr", "l10n_gr_reports", "l10n_gt", "l10n_hk", "l10n_hn", "l10n_hr", "l10n_hr_reports", "l10n_hu", "l10n_hu_reports", "l10n_in", "l10n_in_hr_payroll", "l10n_in_purchase", "l10n_in_reports", "l10n_in_sale", "l10n_in_schedule6", "l10n_in_stock", "l10n_it", "l10n_jp", "l10n_jp_reports", "l10n_lu", "l10n_lu_reports", "l10n_ma", "l10n_ma_reports", "l10n_multilang", "l10n_mx", "l10n_mx_edi", "l10n_mx_edi_customs", "l10n_mx_edi_external_trade", "l10n_mx_edi_landing", "l10n_mx_edi_payment", "l10n_mx_edi_payment_bank", "l10n_mx_reports", "l10n_mx_tax_cash_basis", "l10n_nl", "l10n_nl_intrastat", "l10n_nl_reports", "l10n_no", "l10n_no_reports", "l10n_nz", "l10n_pa", "l10n_pe", "l10n_pl", "l10n_pl_reports", "l10n_pt", "l10n_ro", "l10n_ro_reports", "l10n_sa", "l10n_sg", "l10n_sg_reports", "l10n_si", "l10n_si_reports", "l10n_syscohada", "l10n_th", + "l10n_th_reports", "l10n_tr", "l10n_uk", "l10n_uk_reports", "l10n_us", "l10n_us_check_printing", "l10n_us_reports", "l10n_uy", "l10n_uy_reports", "l10n_ve", "l10n_vn", "l10n_vn_reports", "link_tracker", "lunch", "mail", "mail_bot", "mail_enterprise", "mail_github", "maintenance", "marketing_automation", "mass_mailing", "mass_mailing_crm", "mass_mailing_event", "mass_mailing_event_track", "mass_mailing_sale", "mass_mailing_themes", "membership", "mrp", "mrp_account", "mrp_bom_cost", "mrp_byproduct", "mrp_maintenance", "mrp_mps", "mrp_plm", "mrp_workorder", "mrp_zebra", "note", "note_pad", "ocn_client", "pad", "pad_project", "partner_autocomplete", "partner_autocomplete_address_extended", "payment", "payment_adyen", "payment_authorize", "payment_buckaroo", "payment_ogone", "payment_paypal", "payment_payumoney", "payment_sips", "payment_stripe", "payment_transfer", "phone_validation", "point_of_sale", "portal", "pos_blackbox_be", "pos_cache", "pos_discount", "pos_iot", "pos_loyalty", "pos_mercury", "pos_reprint", "pos_restaurant", "pos_restaurant_iot", "pos_sale", "procurement_jit", "product", "product_email_template", "product_expiry", "product_margin", "project", "project_enterprise", "project_forecast", "project_timesheet_forecast", "project_timesheet_forecast_sale", "project_timesheet_holidays", "project_timesheet_synchro", "purchase", "purchase_mrp", "purchase_mrp_workorder_quality", "purchase_requisition", "purchase_stock", "quality", "quality_control", "quality_iot", "quality_mrp", "quality_mrp_iot", "quality_mrp_workorder", "rating", "repair", "resource", "sale", "sale_account_taxcloud", "sale_coupon", "sale_coupon_delivery", "sale_crm", "sale_ebay", "sale_enterprise", "sale_expense", "sale_intrastat", "sale_management", "sale_margin", "sale_mrp", "sale_purchase", "sale_quotation_builder", "sale_stock", "sale_subscription", "sale_subscription_asset", "sale_subscription_dashboard", "sale_timesheet", "sales_team", "sign", "sms", "snailmail", "snailmail_account", "snailmail_account_reports_followup", "social_media", "stock", "stock_account", "stock_account_enterprise", "stock_barcode", "stock_barcode_mobile", "stock_dropshipping", "stock_enterprise", "stock_intrastat", "stock_landed_costs", "stock_picking_batch", "stock_zebra", "studio_customization", "survey", "survey_crm", "test_mail", "test_marketing_automation", "test_mass_mailing", "theme_anelusia", "theme_anelusia_sale", "theme_artists", "theme_artists_sale", "theme_avantgarde", "theme_avantgarde_blog", "theme_beauty", "theme_beauty_sale", "theme_bewise", "theme_bistro", "theme_bookstore", "theme_bookstore_sale", "theme_bootswatch", "theme_clean", "theme_common", "theme_default", "theme_enark", "theme_graphene", "theme_graphene_blog", "theme_kea", "theme_kea_sale", "theme_kiddo", "theme_kiddo_sale", "theme_loftspace", "theme_loftspace_sale", "theme_monglia", "theme_monglia_sale", "theme_nano", "theme_notes", "theme_notes_sale", "theme_odoo_experts", "theme_odoo_experts_sale", "theme_orchid", "theme_orchid_sale", "theme_real_estate", "theme_real_estate_sale", "theme_treehouse", "theme_vehicle", "theme_vehicle_sale", "theme_yes", "theme_yes_sale", "theme_zap", "timesheet_grid", "timesheet_grid_sale", "transifex", "uom", "utm", "voip", "voip_onsip", "web", "web_cohort", "web_dashboard", "web_diagram", "web_editor", "web_enterprise", "web_gantt", "web_grid", "web_kanban_gauge", "web_mobile", "web_settings_dashboard", "web_studio", "web_tour", "web_unsplash", "website", "website_animate", "website_blog", "website_calendar", "website_crm", "website_crm_partner_assign", "website_crm_phone_validation", "website_crm_score", "website_customer", "website_delivery_ups", "website_enterprise", "website_event", "website_event_questions", "website_event_sale", "website_event_track", "website_form", "website_form_editor", "website_form_project", "website_forum", "website_gengo", "website_google_map", "website_helpdesk", "website_helpdesk_form", "website_helpdesk_forum", "website_helpdesk_livechat", "website_helpdesk_slides", "website_hr", "website_hr_recruitment", "website_links", "website_livechat", "website_mail", "website_mail_channel", "website_mass_mailing", "website_membership", "website_partner", "website_payment", "website_rating", "website_sale", "website_sale_account_taxcloud", "website_sale_comparison", "website_sale_coupon", "website_sale_dashboard", "website_sale_delivery", "website_sale_digital", "website_sale_link_tracker", "website_sale_management", "website_sale_stock", "website_sale_taxcloud_delivery", "website_sale_wishlist", "website_slides", "website_studio", "website_survey", "website_theme_install", "website_twitter", "website_twitter_wall"] for view in self: - module="" - is_studio=False + module = "" + is_studio = False xml_id = len(view._get_external_ids()) if xml_id: module = self.env['ir.model.data'].browse(xml_id).module - if self.env['ir.module.module'].search([('name','=','web_studio'),('state','=','installed')]): - is_studio= self.studio + if self.env['ir.module.module'].search([('name', '=', 'web_studio'), ('state', '=', 'installed')]): + is_studio = self.studio if module not in (module_list): # Not supported Customisation. # If deactivated, if the view is set to active in the customer xml, this can lead to issue. @@ -1555,16 +1556,20 @@ def _clean_properly(self, delete=False): if delete: view.with_context(_force_unlink=True).unlink() else: - view.active=False + view.active = False else: if is_studio: # Presume this is a supported studio customization. # As supported, shouldn't be deactivated or removed. - _logger.warning("Studio view %s generates issue" %(self)) + _logger.warning("Studio view %s generates issue" % (self)) + if delete: + view.with_context(_force_unlink=True).unlink() + else: + view.active = False if not is_studio: # Presume this is a normal odoo view. # Can be deactivated if delete: view.with_context(_force_unlink=True).unlink() else: - view.active=False + view.active = False