[FIX] website(_*): better ondelete management on website_id

Before this commit, every model having website_id would have the default
`set null` as ondelete value for that field. Exception for `ir.ui.view` and
`website.redirect` which are set to `cascade`.

This meant that deleting a website would basically transform all its specific
records into generic ones.
That would lead to unwanted behavior, such as multiple `ir.attachment` with
same URL, when 2 websites had been theme-customized. Deleting one of the
website would result in later crashes when trying to open theme customization
in the remaining website(s), since those website would find their specific scss
custom attachment as well as the generic one from the deleted website.

Probably more behavior might be problematic when deleting a website before this
commit.

To summarize the choice of this commit implementation, every model not existing
outside website module are set to `cascade` (website.redirect, website.menu,
product.wishlist..).
The rest should be in restrict, as we don't want to delete a whole forum, blog
or job, which should probably be managed case by case to decide what to do.
Thus, restrict is a good choice to notify user of what should be handled.

Some exception remains, ir.ui.view should be delete on cascade while sale.order
should be set to null.

opw-2038414
opw-2035249

closes odoo/odoo#35398

Signed-off-by: Jérémy Kersten (jke) <jke@openerp.com>
This commit is contained in:
Romain Derie
2019-08-13 10:02:32 +00:00
parent cb3c2e64c3
commit 2f361bec36
6 changed files with 71 additions and 6 deletions
+15 -2
View File
@@ -154,6 +154,19 @@ class Website(models.Model):
self.env['ir.qweb'].clear_caches()
return result
@api.multi
def unlink(self):
# Do not delete invoices, delete what's strictly necessary
attachments_to_unlink = self.env['ir.attachment'].search([
('website_id', 'in', self.ids),
'|', '|',
('key', '!=', False), # theme attachment
('url', 'ilike', '.custom.'), # customized theme attachment
('url', 'ilike', '.assets\\_'),
])
attachments_to_unlink.unlink()
return super(Website, self).unlink()
# ----------------------------------------------------------
# Page Management
# ----------------------------------------------------------
@@ -896,7 +909,7 @@ class WebsiteMultiMixin(models.AbstractModel):
_name = 'website.multi.mixin'
_description = 'Multi Website Mixin'
website_id = fields.Many2one('website', string='Website', help='Restrict publishing to this website.')
website_id = fields.Many2one('website', string='Website', ondelete='restrict', help='Restrict publishing to this website.')
@api.multi
def can_access_from_current_website(self, website_id=False):
@@ -1183,7 +1196,7 @@ class Menu(models.Model):
page_id = fields.Many2one('website.page', 'Related Page', ondelete='cascade')
new_window = fields.Boolean('New Window')
sequence = fields.Integer(default=_default_sequence)
website_id = fields.Many2one('website', 'Website')
website_id = fields.Many2one('website', 'Website', ondelete='cascade')
parent_id = fields.Many2one('website.menu', 'Parent Menu', index=True, ondelete="cascade")
child_id = fields.One2many('website.menu', 'parent_id', string='Child Menus')
parent_path = fields.Char(index=True)
+52
View File
@@ -5,6 +5,58 @@ import odoo
import odoo.tests
@odoo.tests.tagged('-at_install', 'post_install')
class TestUiCustomizeTheme(odoo.tests.HttpCase):
def test_01_attachment_website_unlink(self):
''' Some ir.attachment needs to be unlinked when a website is unlink,
otherwise some flows will just crash. That's the case when 2 website
have their theme color customized. Removing a website will make its
customized attachment generic, thus having 2 attachments with the
same URL available for other websites, leading to singleton errors
(among other).
But no all attachment should be deleted, eg we don't want to delete
a SO or invoice PDF coming from an ecommerce order.
'''
Website = self.env['website']
Page = self.env['website.page']
Attachment = self.env['ir.attachment']
website_default = Website.browse(1)
website_test = Website.create({'name': 'Website Test'})
# simulate attachment state when editing 2 theme through customize
custom_url = '/TEST/website/static/src/scss/options/colors/user_theme_color_palette.custom.web.assets_common.scss'
scss_attachment = Attachment.create({
'name': custom_url,
'type': 'binary',
'mimetype': 'text/scss',
'datas': '',
'datas_fname': custom_url,
'url': custom_url,
'website_id': website_default.id
})
scss_attachment.copy({'website_id': website_test.id})
# simulate PDF from ecommerce order
# Note: it will only have its website_id flag if the website has a domain
# equal to the current URL (fallback or get_current_website())
so_attachment = Attachment.create({
'name': 'SO036.pdf',
'type': 'binary',
'mimetype': 'application/pdf',
'datas': '',
'website_id': website_test.id
})
# avoid sql error on page website_id restrict
Page.search([('website_id', '=', website_test.id)]).unlink()
website_test.unlink()
self.assertEqual(Attachment.search_count([('url', '=', custom_url)]), 1, 'Should not left duplicates when deleting a website')
self.assertTrue(so_attachment.exists(), 'Most attachment should not be deleted')
self.assertFalse(so_attachment.website_id, 'Website should be removed')
@odoo.tests.tagged('-at_install', 'post_install')
class TestUiHtmlEditor(odoo.tests.HttpCase):
def test_html_editor_multiple_templates(self):
@@ -6,4 +6,4 @@ from odoo import fields, models
class PaymentAcquirer(models.Model):
_inherit = 'payment.acquirer'
website_id = fields.Many2one('website')
website_id = fields.Many2one('website', ondelete='restrict')
+1 -1
View File
@@ -23,7 +23,7 @@ class ProductPricelist(models.Model):
domain = [('company_id', '=', company_id)]
return self.env['website'].search(domain, limit=1)
website_id = fields.Many2one('website', string="Website", default=_default_website)
website_id = fields.Many2one('website', string="Website", ondelete='restrict', default=_default_website)
code = fields.Char(string='E-commerce Promotional Code', groups="base.group_user")
selectable = fields.Boolean(help="Allow the end user to choose this price list")
@@ -18,7 +18,7 @@ class ProductWishlist(models.Model):
currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', readonly=True)
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', help='Pricelist when added')
price = fields.Monetary(digits=0, currency_field='currency_id', string='Price', help='Price of the product when it has been added in the wishlist')
website_id = fields.Many2one('website', required=True)
website_id = fields.Many2one('website', ondelete='cascade', required=True)
active = fields.Boolean(default=True, required=True)
@api.model
@@ -8,7 +8,7 @@ class WebsiteTwitterTweet(models.Model):
_name = 'website.twitter.tweet'
_description = 'Website Twitter'
website_id = fields.Many2one('website', string='Website')
website_id = fields.Many2one('website', string='Website', ondelete='cascade')
screen_name = fields.Char(string='Screen Name')
tweet = fields.Text(string='Tweets')