summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Kersten <jke@odoo.com>2018-07-24 17:01:44 +0200
committerJeremy Kersten <jke@odoo.com>2018-08-13 20:16:34 +0200
commit602807acdf20ff4f75a1abb7f5fa1be4db3f532d (patch)
tree94b96796b1ef42c41be2e320e6bce973787dc9ed
parenta45bb90fac04e454e9792dfea82772227e963e17 (diff)
[FIX] website: improve/fix menu, copy on unlink, clone page, unique_path, sale_report, ...
Fix bug from the initial poc of JOV Fix menu creation - creating a menu would create a 'container' menu in the DB (.create is call without website_id) then writing on it would copy (if condition to cow) the menu with a website_id leaving the first one as a menu container unused. - website_menu should always have a website_id we dont want to support the multiwebsite system that allow to have generic/specific menu unlink/write is useless now since we always have a website_id Make unique_path website dependent, 2 distinct website can have a page with same name Make Sale report multiwebsite compliant Make website_id on sale_order / account.invoice as a related stored from partner_id. Co-authored-by: Derie Romain <rde@odoo.com> Co-authored-by: Kersten Jérémy <jke@odoo.com>
-rw-r--r--addons/auth_signup/models/res_config_settings.py4
-rw-r--r--addons/sale/models/res_config_settings.py4
-rw-r--r--addons/website/data/website_data.xml5
-rw-r--r--addons/website/data/website_demo.xml2
-rw-r--r--addons/website/models/ir_attachment.py7
-rw-r--r--addons/website/models/ir_http.py2
-rw-r--r--addons/website/models/ir_ui_view.py6
-rw-r--r--addons/website/models/website.py112
-rw-r--r--addons/website/static/src/js/content/menu.js2
-rw-r--r--addons/website/static/src/js/content/website_root.js2
-rw-r--r--addons/website/static/src/js/menu/content.js2
-rw-r--r--addons/website/static/src/js/widgets/theme.js3
-rw-r--r--addons/website/static/src/scss/website.ui.scss2
-rw-r--r--addons/website/static/src/xml/website.backend.xml4
-rw-r--r--addons/website/static/src/xml/website.pageProperties.xml16
-rw-r--r--addons/website/tests/test_menu.py121
-rw-r--r--addons/website/tests/test_page.py106
-rw-r--r--addons/website/views/res_config_settings_views.xml2
-rw-r--r--addons/website/views/website_navbar_templates.xml25
-rw-r--r--addons/website/views/website_templates.xml2
-rw-r--r--addons/website/views/website_views.xml3
-rw-r--r--addons/website_blog/data/website_blog_data.xml1
-rw-r--r--addons/website_blog/views/website_blog_views.xml1
-rw-r--r--addons/website_event/models/event.py3
-rw-r--r--addons/website_mass_mailing/views/snippets_templates.xml2
-rw-r--r--addons/website_sale/models/__init__.py1
-rw-r--r--addons/website_sale/models/account_invoice.py12
-rw-r--r--addons/website_sale/report/sale_report.py15
-rw-r--r--addons/website_sale/views/account_views.xml11
-rw-r--r--addons/website_sale/views/product_views.xml2
-rw-r--r--odoo/addons/base/models/ir_http.py7
31 files changed, 262 insertions, 225 deletions
diff --git a/addons/auth_signup/models/res_config_settings.py b/addons/auth_signup/models/res_config_settings.py
index f0b9c1385f4..c9ea615e827 100644
--- a/addons/auth_signup/models/res_config_settings.py
+++ b/addons/auth_signup/models/res_config_settings.py
@@ -11,8 +11,8 @@ class ResConfigSettings(models.TransientModel):
auth_signup_reset_password = fields.Boolean(string='Enable password reset from Login page', config_parameter='auth_signup.reset_password')
auth_signup_uninvited = fields.Selection([
- ('b2b', 'On invitation (B2B)'),
- ('b2c', 'Free sign up (B2C)'),
+ ('b2b', 'On invitation'),
+ ('b2c', 'Free sign up'),
], string='Customer Account', default='b2b', config_parameter='auth_signup.invitation_scope')
auth_signup_template_user_id = fields.Many2one('res.users', string='Template user for new users created through signup',
config_parameter='base.template_portal_user_id')
diff --git a/addons/sale/models/res_config_settings.py b/addons/sale/models/res_config_settings.py
index 6bd63b6d094..b99e547d6ad 100644
--- a/addons/sale/models/res_config_settings.py
+++ b/addons/sale/models/res_config_settings.py
@@ -52,8 +52,8 @@ class ResConfigSettings(models.TransientModel):
module_website_sale_digital = fields.Boolean("Sell digital products - provide downloadable content on your customer portal")
auth_signup_uninvited = fields.Selection([
- ('b2b', 'On invitation (B2B)'),
- ('b2c', 'Free sign up (B2C)'),
+ ('b2b', 'On invitation'),
+ ('b2c', 'Free sign up'),
], string='Customer Account', default='b2b', config_parameter='auth_signup.invitation_scope')
module_delivery = fields.Boolean("Shipping Costs")
diff --git a/addons/website/data/website_data.xml b/addons/website/data/website_data.xml
index 2c5c131f5e7..4842dab2625 100644
--- a/addons/website/data/website_data.xml
+++ b/addons/website/data/website_data.xml
@@ -10,7 +10,8 @@
</record>
<record id="main_menu" model="website.menu">
- <field name="name">Top Menu</field>
+ <field name="name">Top Menu</field>
+ <field name="website_id" ref="default_website"/>
</record>
<record id="menu_homepage" model="website.menu">
@@ -18,6 +19,7 @@
<field name="url">/</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">10</field>
+ <field name="website_id" ref="default_website"/>
</record>
<record id="menu_contactus" model="website.menu">
@@ -25,6 +27,7 @@
<field name="url">/contactus</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">60</field>
+ <field name="website_id" ref="default_website"/>
</record>
<!-- split between ir_ui_view & website_page record to keep external_id on ir_ui_view -->
diff --git a/addons/website/data/website_demo.xml b/addons/website/data/website_demo.xml
index d03a65ce01d..61bda1e784e 100644
--- a/addons/website/data/website_demo.xml
+++ b/addons/website/data/website_demo.xml
@@ -176,7 +176,7 @@ response = request.render("website.template_partner_comment", {
<record id="website2_menu_contactus" model="website.menu">
<field name="name">Contact us</field>
<field name="url">/contactus</field>
- <field name="parent_id" ref="website.main_menu"/>
+ <field name="parent_id" search="[('website_id', '=', ref('website2')), ('parent_id', '=', False)]"/>
<field name="sequence" type="int">60</field>
<field name="website_id" ref="website2"/>
<field name="page_id" ref="website2_contactus_page" />
diff --git a/addons/website/models/ir_attachment.py b/addons/website/models/ir_attachment.py
index 7bbcea66607..3225fa0918f 100644
--- a/addons/website/models/ir_attachment.py
+++ b/addons/website/models/ir_attachment.py
@@ -14,6 +14,13 @@ class Attachment(models.Model):
website_url = fields.Char(string="Website URL", related='local_url', deprecated=True)
@api.model
+ def create(self, vals):
+ website = self.env['website'].get_current_website()
+ if website and 'website_id' not in vals and 'not_force_website_id' not in self.env.context:
+ vals['website_id'] = website.id
+ return super(Attachment, self).create(vals)
+
+ @api.model
def get_serving_groups(self):
return super(Attachment, self).get_serving_groups() + ['website.group_website_designer']
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index 2a55e609199..620e2cfd739 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -179,7 +179,7 @@ class Http(models.AbstractModel):
return response
except Exception as e:
if 'werkzeug' in config['dev_mode'] and (not isinstance(exception, QWebException) or not exception.qweb.get('cause')):
- raise
+ raise e
exception = e
values = dict(
diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py
index 50f61c33909..3148bb984eb 100644
--- a/addons/website/models/ir_ui_view.py
+++ b/addons/website/models/ir_ui_view.py
@@ -75,9 +75,8 @@ class View(models.Model):
# reuse the COW mechanism to create
# website-specific copies, it will take
# care of creating pages and menus.
- view.with_context(website_id=website.id).write({'key': '%s [website %s]' % (view.key, website.id)})
+ view.with_context(website_id=website.id).write({'name': view.name})
- self |= self.with_context(active_test=False).search([('key', 'in', self.filtered('key').mapped('key'))])
result = super(View, self).unlink()
self.clear_caches()
return result
@@ -108,9 +107,6 @@ class View(models.Model):
new_page = page.copy({
'view_id': new_view.id,
})
- for menu in page.menu_ids:
- # trigger COW
- menu.write({'page_id': new_page.id})
@api.model
def get_related_views(self, key, bundles=False):
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
index 5a927dec45e..e8eea753f80 100644
--- a/addons/website/models/website.py
+++ b/addons/website/models/website.py
@@ -99,15 +99,15 @@ class Website(models.Model):
installed_theme_id = fields.Many2one('ir.module.module')
theme_ids = fields.Many2many('ir.module.module', 'website_theme', 'website_id', 'ir_module_module_id')
auth_signup_uninvited = fields.Selection([
- ('b2b', 'On invitation (B2B)'),
- ('b2c', 'Free sign up (B2C)'),
+ ('b2b', 'On invitation'),
+ ('b2c', 'Free sign up'),
], string='Customer Account', default='b2b')
@api.multi
def _compute_menu(self):
Menu = self.env['website.menu']
for website in self:
- website.menu_id = Menu.search([('parent_id', '=', False), ('website_id', 'in', (website.id, False))], order='id', limit=1).id
+ website.menu_id = Menu.search([('parent_id', '=', False), ('website_id', '=', website.id)], order='id', limit=1).id
# cf. Wizard hack in website_views.xml
def noop(self, *args, **kwargs):
@@ -162,6 +162,18 @@ class Website(models.Model):
self.homepage_id = self.env['website.page'].search([('website_id', '=', self.id),
('key', '=', standard_homepage.key)])
+ top_menu = self.env['website.menu'].create({
+ 'name': 'Top Menu for website ' + str(self.id),
+ 'website_id': self.id,
+ 'sequence': 0
+ })
+ self.menu_id = top_menu.id
+ self.env['website.menu'].create({
+ 'name': 'Home', 'url': '/',
+ 'website_id': self.id,
+ 'parent_id': top_menu.id,
+ 'sequence': 10
+ })
@api.model
def new_page(self, name=False, add_menu=False, template='website.default_page', ispage=True, namespace=None):
@@ -196,10 +208,11 @@ class Website(models.Model):
if view.arch_fs:
view.arch_fs = False
+ website = self.get_current_website()
if ispage:
page = self.env['website.page'].create({
'url': page_url,
- 'website_id': self.get_current_website().id,
+ 'website_id': website.id, # remove it if only one webiste or not?
'view_id': view.id,
})
result['view_id'] = view.id
@@ -207,9 +220,9 @@ class Website(models.Model):
self.env['website.menu'].create({
'name': name,
'url': page_url,
- 'parent_id': self.get_current_website().menu_id.id,
+ 'parent_id': website.menu_id.id,
'page_id': page.id,
- 'website_id': self.get_current_website().id,
+ 'website_id': website.id,
})
return result
@@ -222,7 +235,10 @@ class Website(models.Model):
:param page_url : the url to be checked for uniqueness
"""
inc = 0
- domain_static = self.get_current_website().website_domain()
+ # we only want a unique_path for website specific.
+ # we need to be able to have /url for website=False, and /url for website=1
+ # in case of duplicate, page manager will allow you to manage this case
+ domain_static = [('website_id', '=', self.get_current_website().id)] # .website_domain()
page_temp = page_url
while self.env['website.page'].with_context(active_test=False).sudo().search([('url', '=', page_temp)] + domain_static):
inc += 1
@@ -877,7 +893,10 @@ class Page(models.Model):
# is copied from.
# (eg: website_version: an ir.ui.view record with the same key is
# expected to be the same ir.ui.view but from another version)
- new_view = view.copy({'key': view.key + '.copy', 'name': '%s %s' % (view.name, _('(copy)'))})
+ website_id = default.get('website_id')
+ # website_id should be set during copy to avoid copying a generic page to another generic copy and then write on it
+ # which would result in the generic page copied to a generic one and a specific one
+ new_view = view.copy({'key': view.key + '.copy', 'name': '%s %s' % (view.name, _('(copy)')), 'website_id': website_id.id})
default = {
'name': '%s %s' % (self.name, _('(copy)')),
'url': self.env['website'].get_unique_path(self.url),
@@ -891,8 +910,10 @@ class Page(models.Model):
:param page_id : website.page identifier
"""
page = self.browse(int(page_id))
- new_page = page.copy()
- if clone_menu:
+ new_page = page.copy(dict(website_id=self.env['website'].get_current_website()))
+ # Should not clone menu if the page was cloned from one website to another
+ # Eg: Cloning a generic page (no website) will create a page with a website, we can't clone menu (not same container)
+ if clone_menu and new_page.website_id == page.website_id:
menu = self.env['website.menu'].search([('page_id', '=', page_id)], limit=1)
if menu:
# If the page being cloned has a menu, clone it too
@@ -909,7 +930,7 @@ class Page(models.Model):
for page in self:
# Other pages linked to the ir_ui_view of the page being deleted (will it even be possible?)
pages_linked_to_iruiview = self.search(
- [('view_id', '=', self.view_id.id), ('id', '!=', self.id)]
+ [('view_id', '=', page.view_id.id), ('id', '!=', page.id)]
)
if len(pages_linked_to_iruiview) == 0 and not page.view_id.inherit_children_ids:
# If there is no other pages linked to that ir_ui_view, we can delete the ir_ui_view
@@ -946,37 +967,27 @@ class Menu(models.Model):
parent_path = fields.Char(index=True)
is_visible = fields.Boolean(compute='_compute_visible', string='Is Visible')
- @api.multi
- def unlink(self):
- '''This implements COU (copy-on-unlink). When deleting a generic menu
- item website-specific menu items will be created.'''
- current_website_id = self._context.get('website_id')
-
- if current_website_id and not self._context.get('no_cow'):
- for menu in self.filtered(lambda menu: not menu.website_id):
- for website in self.env['website'].search([('id', '!=', current_website_id)]):
- # reuse the COW mechanism to create website-specific copies
- menu.with_context(website_id=website.id).write({})
-
- return super(Menu, self).unlink()
-
- @api.multi
- def write(self, vals):
- '''This implements COW (copy-on-write). This way editing websites does
- not impact other websites and newly created websites will only
- contain the default menus.
+ @api.model
+ def create(self, vals):
+ ''' In case a menu without a website_id is trying to be created, we duplicate
+ it for every website.
+ Note: Particulary useful when installing a module that adds a menu like
+ /shop. So every website has the shop menu.
'''
- current_website_id = self._context.get('website_id')
-
- if current_website_id and not self.website_id and not self._context.get('no_cow'):
- new_website_specific_menu = self.copy({'website_id': current_website_id})
-
- for child in self.child_id:
- child.write({'parent_id': new_website_specific_menu.id})
-
- return new_website_specific_menu.write(vals)
-
- return super(Menu, self).write(vals)
+ if vals.get('website_id'):
+ return super(Menu, self).create(vals)
+ elif self._context.get('website_id'):
+ vals.update({'website_id': self._context.get('website_id')})
+ return super(Menu, self).create(vals)
+ else:
+ # create for every site
+ for website in self.env['website'].search([]):
+ vals.update({
+ 'website_id': website.id,
+ 'parent_id': website.menu_id.id,
+ })
+ res = super(Menu, self).create(vals)
+ return res # create loop, what to return ? last created record ?
@api.one
def _compute_visible(self):
@@ -1000,21 +1011,6 @@ class Menu(models.Model):
url = '/%s' % self.url
return url
- def get_children_for_current_website(self):
- most_specific_child_menus = self.env['website.menu']
- website_id = self._context.get('website_id')
-
- if not website_id:
- return self.child_id
-
- for child in self.child_id:
- if child.website_id and child.website_id.id == website_id:
- most_specific_child_menus |= child
- elif not child.website_id and not any(child.clean_url() == child2.clean_url() and child2.website_id.id == website_id for child2 in self.child_id):
- most_specific_child_menus |= child
-
- return most_specific_child_menus
-
# would be better to take a menu_id as argument
@api.model
def get_tree(self, website_id, menu_id=None):
@@ -1031,7 +1027,7 @@ class Menu(models.Model):
children=[],
is_homepage=is_homepage,
)
- for child in node.get_children_for_current_website():
+ for child in node.child_id:
menu_node['children'].append(make_tree(child))
return menu_node
if menu_id:
@@ -1055,7 +1051,7 @@ class Menu(models.Model):
mid = menu['id']
# new menu are prefixed by new-
if isinstance(mid, pycompat.string_types):
- new_menu = self.create({'name': menu['name']})
+ new_menu = self.create({'name': menu['name'], 'website_id': website_id})
replace_id(mid, new_menu.id)
for menu in data['data']:
menu_id = self.browse(menu['id'])
diff --git a/addons/website/static/src/js/content/menu.js b/addons/website/static/src/js/content/menu.js
index 6effb471ec9..3511f51a70f 100644
--- a/addons/website/static/src/js/content/menu.js
+++ b/addons/website/static/src/js/content/menu.js
@@ -67,7 +67,7 @@ sAnimation.registry.affixMenu = sAnimation.Class.extend({
// Reset opened menus
this.$dropdowns.removeClass('show');
- this.$navbarCollapses.removeClass('in').attr('aria-expanded', false);
+ this.$navbarCollapses.removeClass('show').attr('aria-expanded', false);
},
});
diff --git a/addons/website/static/src/js/content/website_root.js b/addons/website/static/src/js/content/website_root.js
index 0f3fff53dfc..a51c8011e92 100644
--- a/addons/website/static/src/js/content/website_root.js
+++ b/addons/website/static/src/js/content/website_root.js
@@ -287,7 +287,7 @@ var WebsiteRoot = BodyManager.extend({
* @param {OdooEvent} ev
*/
_multiWebsiteSwitch: function (ev) {
- var website_id_to_switch_to = ev.target.getAttribute('website-id');
+ var website_id_to_switch_to = ev.currentTarget.getAttribute('website-id');
this._rpc({
route: '/website/force_website',
params: {
diff --git a/addons/website/static/src/js/menu/content.js b/addons/website/static/src/js/menu/content.js
index bcb46553b65..91494a4018b 100644
--- a/addons/website/static/src/js/menu/content.js
+++ b/addons/website/static/src/js/menu/content.js
@@ -542,7 +542,7 @@ var EditMenuDialog = weWidgets.Dialog.extend({
this._rpc({
model: 'website.menu',
method: 'save',
- args: [[context.website_id], { data: data, to_delete: self.to_delete }],
+ args: [context.website_id, { data: data, to_delete: self.to_delete }],
context: context,
}).then(function () {
return _super();
diff --git a/addons/website/static/src/js/widgets/theme.js b/addons/website/static/src/js/widgets/theme.js
index 50434604677..ce73fd35d3d 100644
--- a/addons/website/static/src/js/widgets/theme.js
+++ b/addons/website/static/src/js/widgets/theme.js
@@ -55,7 +55,6 @@ var ThemeCustomizeDialog = Dialog.extend({
*/
start: function () {
var self = this;
-
this._generateDialogHTML();
this.$modal.addClass('o_theme_customize_modal');
@@ -77,7 +76,7 @@ var ThemeCustomizeDialog = Dialog.extend({
this._loadViews()
);
},
-
+
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
diff --git a/addons/website/static/src/scss/website.ui.scss b/addons/website/static/src/scss/website.ui.scss
index a7be7e98290..f146b3de677 100644
--- a/addons/website/static/src/scss/website.ui.scss
+++ b/addons/website/static/src/scss/website.ui.scss
@@ -38,7 +38,7 @@ body.o_connected_user {
}
}
.o_menu_systray {
- a {
+ > li > a {
padding: 0 $grid-gutter-width/2;
&.css_edit_dynamic{
diff --git a/addons/website/static/src/xml/website.backend.xml b/addons/website/static/src/xml/website.backend.xml
index 6bdb10f24c6..5508d4be9d9 100644
--- a/addons/website/static/src/xml/website.backend.xml
+++ b/addons/website/static/src/xml/website.backend.xml
@@ -70,9 +70,9 @@
<button class="btn btn-secondary js_date_range" data-date="month">Last Month</button>
<button class="btn btn-secondary js_date_range" data-date="year">Last Year</button>
</div>
- <div class="btn-group btn-group-sm pull-right" style="margin-right: 20px;">
+ <div class="btn-group pull-right" style="margin-right: 20px;">
<t t-foreach="widget.websites" t-as="website">
- <button t-attf-class="btn btn-sm btn-default js_website #{website.selected ? 'active' : ''}"
+ <button t-attf-class="btn btn-default js_website #{website.selected ? 'active' : ''}"
t-att-data-website-id="website.id">
<t t-esc="website.name"/>
</button>
diff --git a/addons/website/static/src/xml/website.pageProperties.xml b/addons/website/static/src/xml/website.pageProperties.xml
index 7ac590cda2f..cf4719d5951 100644
--- a/addons/website/static/src/xml/website.pageProperties.xml
+++ b/addons/website/static/src/xml/website.pageProperties.xml
@@ -45,7 +45,7 @@
<li class="nav-item"><a aria-controls="advances_page_info" role="tab" data-toggle="tab" class="nav-link" href="#advances_page_info">Publish</a></li>
</ul>
<div class="tab-content mt16">
- <div role="tabpanel" id="basic_page_info" class="tab-pane fade in active">
+ <div role="tabpanel" id="basic_page_info" class="tab-pane fade show active">
<div class="form-group row">
<label class="col-form-label col-md-3" for="page_name">Page Name</label>
<div class="col-md-9">
@@ -88,7 +88,7 @@
</div>
</div>
<div role="tabpanel" id="advances_page_info" class="tab-pane fade">
- <div class="form-group">
+ <div class="form-group row">
<label class="control-label col-md-5" for="is_menu">Show in Top Menu</label>
<div class="col-sm-2">
<label class="o_switch" for="is_menu" >
@@ -97,7 +97,7 @@
</label>
</div>
</div>
- <div class="form-group">
+ <div class="form-group row">
<label class="control-label col-md-5" for="is_homepage">Use as Homepage</label>
<div class="col-sm-2">
<label class="o_switch" for="is_homepage" >
@@ -106,7 +106,7 @@
</label>
</div>
</div>
- <div class="form-group">
+ <div class="form-group row">
<label class="control-label col-md-5" for="is_indexed">
Indexed
<i class="fa fa-question-circle-o" title="Hide this page from search results" role="img" aria-label="Info"></i>
@@ -118,7 +118,7 @@
</label>
</div>
</div>
- <div class="form-group">
+ <div class="form-group row">
<label class="control-label col-md-5" for="is_published">Publish</label>
<div class="col-sm-2">
<label class="o_switch js_publish_btn" for="is_published">
@@ -127,7 +127,7 @@
</label>
</div>
</div>
- <div t-if='widget.page.website_id' class="form-group" id="website_restriction">
+ <div t-if='widget.page.website_id' class="form-group row" id="website_restriction">
<!-- TODO hide with multi-website group -->
<label class="control-label col-md-5" for="share_page_info">
Share this page
@@ -142,9 +142,9 @@
</a>
</div>
</div>
- <div class="form-group">
+ <div class="form-group row">
<label class="control-label col-md-5" for="date_publish">Publishing Date</label>
- <div class="col-sm-7">
+ <div class="col-md-7">
<div class='input-group date' id='date_publish_container'>
<input type='text' class="form-control" name="date_publish" id="date_publish" />
<div class="input-group-append">
diff --git a/addons/website/tests/test_menu.py b/addons/website/tests/test_menu.py
index e01b020bef8..8ceab5b6bf5 100644
--- a/addons/website/tests/test_menu.py
+++ b/addons/website/tests/test_menu.py
@@ -3,10 +3,19 @@ from odoo.tests import common
class TestMenu(common.TransactionCase):
- def setUp(self):
- super(TestMenu, self).setUp()
+
+ def test_website_own_menu_container(self):
+ Website = self.env['website']
Menu = self.env['website.menu']
+ total_menus = Menu.search_count([])
+ Website.create({'name': 'new website'})
+ self.assertEqual(total_menus + 2, Menu.search_count([]), "New website's bootstraping should have created a container menu and home menu")
+
+ def test_menu_got_duplicated(self):
+ Menu = self.env['website.menu']
+ total_menu_items = Menu.search_count([])
+
self.menu_root = Menu.create({
'name': 'Root',
})
@@ -16,95 +25,27 @@ class TestMenu(common.TransactionCase):
'parent_id': self.menu_root.id,
})
- self.menu_leaf1 = Menu.create({
- 'name': 'Leaf 1',
- 'parent_id': self.menu_child.id,
- })
-
- self.menu_leaf2 = Menu.create({
- 'name': 'Leaf 2',
- 'parent_id': self.menu_root.id,
- })
-
- # create an extra website so we have 3 in total
- self.website_test = self.env['website'].create({'domain': 'test'})
-
- def test_cow_leaf(self):
- Menu = self.env['website.menu']
-
- # backend write, no COW
- total_menu_items = Menu.search_count([])
- self.menu_leaf1.write({'name': 'Leaf 1 modified'})
- self.assertEqual(total_menu_items, Menu.search_count([]))
-
- # write through website 1
- self.menu_leaf1.with_context(website_id=1).write({'name': 'Leaf 1 Website 1'})
- self.assertEqual(self.menu_leaf1.name, 'Leaf 1 modified')
- self.assertEqual(total_menu_items + 1, Menu.search_count([]))
- new_menu = Menu.search([('name', '=', 'Leaf 1 Website 1')])
- self.assertEqual(new_menu.website_id.id, 1)
-
- def test_cow_root(self):
- Menu = self.env['website.menu']
-
- # backend write, no COW
- total_menu_items = Menu.search_count([])
- self.menu_root.write({'name': 'Root 1 modified'})
- self.assertEqual(total_menu_items, Menu.search_count([]))
-
- # write through website 1
- self.menu_root.with_context(website_id=1).write({'name': 'Root Website 1'})
- self.assertEqual(self.menu_root.name, 'Root 1 modified')
- self.assertEqual(total_menu_items + 4, Menu.search_count([]))
+ self.assertEqual(total_menu_items + 4, Menu.search_count([]), "Creating a menu without a website_id should create this menu for every website_id")
- # verify parent/child structure
- website_specific_leaf2 = Menu.search([('name', '=', 'Leaf 2'), ('website_id', '=', 1)])
- website_specific_leaf1 = Menu.search([('name', '=', 'Leaf 1'), ('website_id', '=', 1)])
- website_specific_child = Menu.search([('name', '=', 'Child'), ('website_id', '=', 1)])
- website_specific_root = Menu.search([('name', '=', 'Root Website 1'), ('website_id', '=', 1)])
- self.assertEqual(website_specific_leaf2.parent_id.id, website_specific_root.id)
- self.assertEqual(website_specific_leaf1.parent_id.id, website_specific_child.id)
- self.assertEqual(website_specific_child.parent_id.id, website_specific_root.id)
-
- def test_cou_leaf(self):
+ def test_menu_count(self):
Menu = self.env['website.menu']
-
- # backend unlink, no COU
- self.menu_leaf1.unlink()
- self.assertEqual(len(Menu.search([('name', '=', 'Leaf 1')])), 0)
-
total_menu_items = Menu.search_count([])
- self.menu_leaf2.with_context(website_id=1).unlink()
-
- website_specific_menus = Menu.search([('name', '=', 'Leaf 2')])
- self.assertEqual(len(website_specific_menus), 2)
- self.assertEqual(set(website_specific_menus.mapped('website_id').ids), set([2, self.website_test.id]))
-
- # exactly 2 website-specific menu items were created and one was deleted
- self.assertEqual(total_menu_items + 1, Menu.search_count([]))
-
- def test_cou_root_backend(self):
- Menu = self.env['website.menu']
- total_menu_items = Menu.search_count([])
- self.menu_root.unlink()
-
- # should have deleted root and all children
- self.assertEqual(total_menu_items - 4, Menu.search_count([]))
-
- def test_cou_root_frontend(self):
- Menu = self.env['website.menu']
- total_menu_items = Menu.search_count([])
- self.menu_root.with_context(website_id=1).unlink()
-
- # should have removed 4 generic menus and created root and all children for 2 remaining websites
- self.assertEqual(Menu.search_count([('name', '=', 'Root'), ('website_id', '=', 2)]), 1)
- self.assertEqual(Menu.search_count([('name', '=', 'Child'), ('website_id', '=', 2)]), 1)
- self.assertEqual(Menu.search_count([('name', '=', 'Leaf 1'), ('website_id', '=', 2)]), 1)
- self.assertEqual(Menu.search_count([('name', '=', 'Leaf 2'), ('website_id', '=', 2)]), 1)
-
- self.assertEqual(Menu.search_count([('name', '=', 'Root'), ('website_id', '=', self.website_test.id)]), 1)
- self.assertEqual(Menu.search_count([('name', '=', 'Child'), ('website_id', '=', self.website_test.id)]), 1)
- self.assertEqual(Menu.search_count([('name', '=', 'Leaf 1'), ('website_id', '=', self.website_test.id)]), 1)
- self.assertEqual(Menu.search_count([('name', '=', 'Leaf 2'), ('website_id', '=', self.website_test.id)]), 1)
- self.assertEqual(total_menu_items + 4, Menu.search_count([]))
+ top_menu = self.env['website'].get_current_website().menu_id
+ data = [
+ {
+ 'id': 'new-1',
+ 'parent_id': top_menu.id,
+ 'name': 'New Menu Specific 1',
+ 'url': '/new-specific-1',
+ },
+ {
+ 'id': 'new-2',
+ 'parent_id': top_menu.id,
+ 'name': 'New Menu Specific 2',
+ 'url': '/new-specific-2',
+ }
+ ]
+ Menu.save(1, {'data': data, 'to_delete': []})
+
+ self.assertEqual(total_menu_items + 2, Menu.search_count([]), "Creating 2 new menus should create only 2 menus records")
diff --git a/addons/website/tests/test_page.py b/addons/website/tests/test_page.py
index 76bfa52e51d..3f717740e79 100644
--- a/addons/website/tests/test_page.py
+++ b/addons/website/tests/test_page.py
@@ -1,5 +1,5 @@
# coding: utf-8
-from odoo.tests import common
+from odoo.tests import common, HttpCase
class TestPage(common.TransactionCase):
@@ -31,8 +31,58 @@ class TestPage(common.TransactionCase):
self.page_1_menu = Menu.create({
'name': 'Page 1 menu',
'page_id': self.page_1.id,
+ 'website_id': 1,
})
+ def test_copy_page(self):
+ View = self.env['ir.ui.view']
+ Page = self.env['website.page']
+ Menu = self.env['website.menu']
+ # Specific page
+ self.specific_view = View.create({
+ 'name': 'Base',
+ 'type': 'qweb',
+ 'arch': '<div>Specific View</div>',
+ 'key': 'test.specific_view',
+ })
+ self.page_specific = Page.create({
+ 'view_id': self.specific_view.id,
+ 'url': '/page_specific',
+ 'website_id': 1,
+ })
+ self.page_specific_menu = Menu.create({
+ 'name': 'Page Specific menu',
+ 'page_id': self.page_specific.id,
+ 'website_id': 1,
+ })
+ total_pages = Page.search_count([])
+ total_menus = Menu.search_count([])
+ # Copying a specific page should create a new page with an unique URL (suffixed by -X)
+ Page.clone_page(self.page_specific.id, True)
+ cloned_page = Page.search([('url', '=', '/page_specific-1')])
+ cloned_menu = Menu.search([('url', '=', '/page_specific-1')])
+ self.assertEqual(len(cloned_page), 1, "A page with an URL /page_specific-1 should've been created")
+ self.assertEqual(Page.search_count([]), total_pages + 1, "Should have cloned the page")
+ # It should also copy its menu with new url/name/page_id (if the page has a menu)
+ self.assertEqual(len(cloned_menu), 1, "A specific page (with a menu) being cloned should have it's menu also cloned")
+ self.assertEqual(cloned_menu.page_id, cloned_page, "The new cloned menu and the new cloned page should be linked (m2o)")
+ self.assertEqual(Menu.search_count([]), total_menus + 1, "Should have cloned the page menu")
+
+ total_pages = Page.search_count([])
+ total_menus = Menu.search_count([])
+
+ # Copying a generic page should create a specific page with same URL
+ Page.clone_page(self.page_1.id, True)
+ cloned_generic_page = Page.search([('url', '=', '/page_1'), ('id', '!=', self.page_1.id), ('website_id', '!=', False)])
+ self.assertEqual(len(cloned_generic_page), 1, "A generic page being cloned should create a specific one for the current website")
+ self.assertEqual(cloned_generic_page.url, self.page_1.url, "The URL of the cloned specific page should be the same as the generic page it has been cloned from")
+ self.assertEqual(Page.search_count([]), total_pages + 1, "Should have cloned the generic page as a specific page for this website")
+ self.assertEqual(Menu.search_count([]), total_menus, "It should not create a new menu as the generic page's menu belong to another website")
+ # Except if the URL already exists for this website (its the case now that we already cloned it once)
+ Page.clone_page(self.page_1.id, True)
+ cloned_generic_page_2 = Page.search([('url', '=', '/page_1-1'), ('id', '!=', self.page_1.id)])
+ self.assertEqual(len(cloned_generic_page_2), 1, "A generic page being cloned should create a specific page with a new URL if there is already a specific page with that URL")
+
def test_cow_page(self):
Menu = self.env['website.menu']
Page = self.env['website.page']
@@ -50,27 +100,23 @@ class TestPage(common.TransactionCase):
# edit through frontend
self.page_1.with_context(website_id=1).write({'arch': '<div>website 1 content</div>'})
- # should have created website-specific copies for:
- # - page
- # - menu
- # - view x2 (base view + extension view)
- # and shouldn't have touched original records
+ # 1. should have created website-specific copies for:
+ # - page
+ # - view x2 (base view + extension view)
+ # 2. should not have created menu copy as menus are not shared/COW
+ # 3. and shouldn't have touched original records
self.assertEqual(total_pages + 1, Page.search_count([]))
- self.assertEqual(total_menus + 1, Menu.search_count([]))
+ self.assertEqual(total_menus, Menu.search_count([]))
self.assertEqual(total_views + 2, View.search_count([]))
self.assertEqual(self.page_1.arch, '<div>modified base content</div>')
self.assertEqual(bool(self.page_1.website_id), False)
- self.assertEqual(bool(self.page_1_menu.website_id), False)
new_page = Page.search([('url', '=', '/page_1'), ('id', '!=', self.page_1.id)])
self.assertEqual(new_page.website_id.id, 1)
self.assertEqual(new_page.view_id.inherit_children_ids[0].website_id.id, 1)
self.assertEqual(new_page.arch, '<div>website 1 content</div>')
- new_menu = new_page.menu_ids[0]
- self.assertEqual(new_menu.website_id.id, 1)
-
def test_cow_extension_view(self):
''' test cow on extension view itself (like web_editor would do in the frontend) '''
Menu = self.env['website.menu']
@@ -98,14 +144,12 @@ class TestPage(common.TransactionCase):
self.assertEqual(self.extension_view.arch, '<div>modified extension content</div>')
self.assertEqual(bool(self.page_1.website_id), False)
- self.assertEqual(bool(self.page_1_menu.website_id), False)
new_view = View.search([('name', '=', 'Extension'), ('website_id', '=', 1)])
self.assertEqual(new_view.arch, '<div>website 1 content</div>')
self.assertEqual(new_view.website_id.id, 1)
def test_cou_page_backend(self):
- Menu = self.env['website.menu']
Page = self.env['website.page']
View = self.env['ir.ui.view']
@@ -113,12 +157,10 @@ class TestPage(common.TransactionCase):
self.extension_view.unlink()
self.page_1.unlink()
- self.assertEqual(Menu.search_count([('name', '=', 'Page 1 menu')]), 0)
self.assertEqual(Page.search_count([('url', '=', '/page_1')]), 0)
self.assertEqual(View.search_count([('name', 'in', ('Base', 'Extension'))]), 0)
def test_cou_page_frontend(self):
- Menu = self.env['website.menu']
Page = self.env['website.page']
View = self.env['ir.ui.view']
@@ -129,8 +171,40 @@ class TestPage(common.TransactionCase):
self.assertEqual(bool(self.base_view.exists()), False)
self.assertEqual(bool(self.page_1.exists()), False)
+ # Not COU but deleting a page will delete its menu (cascade)
self.assertEqual(bool(self.page_1_menu.exists()), False)
- self.assertEqual(Menu.search([('name', '=', 'Page 1 menu')]).website_id.id, 2)
self.assertEqual(Page.search([('url', '=', '/page_1')]).website_id.id, 2)
self.assertEqual(View.search([('name', 'in', ('Base', 'Extension'))]).mapped('website_id').id, 2)
+
+
+class Crawler(HttpCase):
+ def test_unpublished_page(self):
+ Page = self.env['website.page']
+ View = self.env['ir.ui.view']
+ base_view = View.create({
+ 'name': 'Base',
+ 'type': 'qweb',
+ 'arch': '''<t name="Homepage" t-name="website.base_view">
+ <t t-call="website.layout">
+ I am a generic page
+ </t>
+ </t>''',
+ 'key': 'test.base_view',
+ })
+ generic_page = Page.create({
+ 'view_id': base_view.id,
+ 'url': '/page_1',
+ 'website_published': True,
+ })
+
+ specific_page = generic_page.copy({'website_id': self.env['website'].get_current_website().id})
+ specific_page.write({'website_published': False, 'arch': generic_page.arch.replace('I am a generic page', 'I am a specific page')})
+
+ r = self.url_open(specific_page.url)
+ self.assertEqual(r.status_code, 404, "Restricted users should see a 404 and not the generic one as we unpublished the specific one")
+
+ self.authenticate('admin', 'admin')
+ r = self.url_open(specific_page.url)
+ self.assertEqual(r.status_code, 200, "Admin should see the specific unpublished page")
+ self.assertEqual('I am a specific page' in r.text, True, "Admin should see the specific unpublished page")
diff --git a/addons/website/views/res_config_settings_views.xml b/addons/website/views/res_config_settings_views.xml
index d8b44e6c65f..597895debcc 100644
--- a/addons/website/views/res_config_settings_views.xml
+++ b/addons/website/views/res_config_settings_views.xml
@@ -330,7 +330,7 @@
id="menu_website_websites_list"
action="action_website_list"
parent="menu_website_global_configuration"
- groups="website.group_multi_website"
+ groups="base.group_no_one"
sequence="10"
/>
diff --git a/addons/website/views/website_navbar_templates.xml b/addons/website/views/website_navbar_templates.xml
index 7f0adea2f56..f4e2d7c24dc 100644
--- a/addons/website/views/website_navbar_templates.xml
+++ b/addons/website/views/website_navbar_templates.xml
@@ -77,20 +77,21 @@
<li class="o_mobile_preview" id="mobile-menu">
<a data-action="show-mobile-preview" href="#"><span title="Mobile preview" role="img" aria-label="Mobile preview" class="fa fa-mobile"/></a>
</li>
- <li class="dropdown" groups="website.group_multi_website">
- <a class="dropdown-toggle waves" data-toggle="dropdown" href="#"><t t-esc="multi_website_selected_website['name']"/> <span class="caret"/></a>
- <ul class="dropdown-menu" role="menu">
- <li t-as="multi_website_website" t-foreach="multi_website_websites">
- <a href="#">
- <t t-if="multi_website_selected_website['website_id'] != multi_website_website['website_id']">
- <span class="oe_menu_text js_multi_website_switch" t-att-website-id="str(multi_website_website['website_id'])">
- <t t-esc="multi_website_website['name']"/>
- </span>
- </t>
+ <li groups="website.group_multi_website">
+ <a class="dropdown-toggle waves" data-toggle="dropdown" href="#"><t t-esc="multi_website_selected_website['name']"/></a>
+ <div class="dropdown-menu" role="menu">
+ <t t-foreach="multi_website_websites" t-as="multi_website_website">
+ <a role="menuitem" href="#"
+ t-if="multi_website_selected_website['website_id'] != multi_website_website['website_id']"
+ class="dropdown-item oe_menu_text js_multi_website_switch"
+ t-att-website-id="str(multi_website_website['website_id'])"
+ >
+ <span t-esc="multi_website_website['name']" />
</a>
- </li>
- </ul>
+ </t>
+ </div>
</li>
+
<!-- Page Edition -->
<li class="o_new_content_menu" id="new-content-menu">
<a href="#"><span class="fa fa-plus"/>New</a>
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index 5ff55756fc9..b6dc1f51f92 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -210,7 +210,7 @@
<attribute name="t-if">website.user_id != user_id</attribute>
</xpath>
<xpath expr="//header//ul[@id='top_menu']/li[hasclass('divider')]" position="before">
- <t t-foreach="website.menu_id.get_children_for_current_website()" t-as="submenu">
+ <t t-foreach="website.menu_id.child_id" t-as="submenu">
<t t-call="website.submenu">
<t t-set="submenu_type" t-value="'nav'"/>
</t>
diff --git a/addons/website/views/website_views.xml b/addons/website/views/website_views.xml
index 050110af288..c0e1b4c842c 100644
--- a/addons/website/views/website_views.xml
+++ b/addons/website/views/website_views.xml
@@ -159,8 +159,9 @@
<tree string="Website Pages" default_order="name">
<field name="name"/>
<field name="url"/>
+ <field name="website_id" groups="website.group_multi_website"/>
<field name="website_indexed"/>
- <field name="website_published"/>
+ <field name="website_published" string="Page Published"/>
<field name="create_uid" invisible="1"/>
<field name="write_uid"/>
<field name="write_date"/>
diff --git a/addons/website_blog/data/website_blog_data.xml b/addons/website_blog/data/website_blog_data.xml
index 142820f109b..d4826083dd8 100644
--- a/addons/website_blog/data/website_blog_data.xml
+++ b/addons/website_blog/data/website_blog_data.xml
@@ -10,7 +10,6 @@
<field name="url" eval="'/blog/'+str(ref('website_blog.blog_blog_1'))"/>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">40</field>
- <field name="website_id" ref="website.default_website"/>
</record>
<!-- Pre loaded covers -->
diff --git a/addons/website_blog/views/website_blog_views.xml b/addons/website_blog/views/website_blog_views.xml
index d4fe3bcfc57..ddeee669d33 100644
--- a/addons/website_blog/views/website_blog_views.xml
+++ b/addons/website_blog/views/website_blog_views.xml
@@ -73,6 +73,7 @@
<field name="name" placeholder="Blog Post Title"/>
<field name="subtitle" placeholder="Blog Subtitle"/>
<field name="tag_ids" widget="many2many_tags"/>
+ <field name="website_id" groups="website.group_multi_website"/>
</group>
<group string="Publishing Options">
<field name="author_id"/>
diff --git a/addons/website_event/models/event.py b/addons/website_event/models/event.py
index f82baa95456..5bb045a320f 100644
--- a/addons/website_event/models/event.py
+++ b/addons/website_event/models/event.py
@@ -84,7 +84,7 @@ class Event(models.Model):
event.menu_id.unlink()
elif event.website_menu:
if not event.menu_id:
- root_menu = self.env['website.menu'].create({'name': event.name})
+ root_menu = self.env['website.menu'].create({'name': event.name, 'website_id': event.id})
event.menu_id = root_menu
for sequence, (name, url, xml_id) in enumerate(event._get_menu_entries()):
event._create_menu(sequence, name, url, xml_id)
@@ -99,6 +99,7 @@ class Event(models.Model):
'url': url,
'parent_id': self.menu_id.id,
'sequence': sequence,
+ 'website_id': self.id,
})
return menu
diff --git a/addons/website_mass_mailing/views/snippets_templates.xml b/addons/website_mass_mailing/views/snippets_templates.xml
index 6a2413e9d20..5f991f7bfcb 100644
--- a/addons/website_mass_mailing/views/snippets_templates.xml
+++ b/addons/website_mass_mailing/views/snippets_templates.xml
@@ -29,7 +29,7 @@
<template id="s_newsletter_subscribe_popup" name="Newsletter Popup">
<div class="o_newsletter_popup" data-list-id="0">
- <div class="alert alert-warning alert-dismissible fade in css_non_editable_mode_hidden o_not_editable" role="status">
+ <div class="alert alert-warning alert-dismissible fade show css_non_editable_mode_hidden o_not_editable" role="status">
<strong>Newsletter Popup!</strong> The newsletter popup snippet effect is active on this page. Click <a id="edit_dialog" href="#">Here To Edit Dialog Content</a>
</div>
<div role="dialog" class="modal modal-dialog modal-md fade fade-custom" id="o_newsletter_popup" tabindex="-1">
diff --git a/addons/website_sale/models/__init__.py b/addons/website_sale/models/__init__.py
index d6c4695f4e4..ed453ce17ac 100644
--- a/addons/website_sale/models/__init__.py
+++ b/addons/website_sale/models/__init__.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from . import account_invoice
from . import crm_team
from . import ir_http
from . import mail_compose_message
diff --git a/addons/website_sale/models/account_invoice.py b/addons/website_sale/models/account_invoice.py
new file mode 100644
index 00000000000..5696f4ace36
--- /dev/null
+++ b/addons/website_sale/models/account_invoice.py
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class AccountInvoice(models.Model):
+ _inherit = 'account.invoice'
+
+ website_id = fields.Many2one('website', related='partner_id.website_id', string='Website',
+ help='Website through which this invoice was created.',
+ store=True, readonly=True)
diff --git a/addons/website_sale/report/sale_report.py b/addons/website_sale/report/sale_report.py
index 9427fc320fe..6ecf7967cad 100644
--- a/addons/website_sale/report/sale_report.py
+++ b/addons/website_sale/report/sale_report.py
@@ -8,14 +8,7 @@ class SaleReport(models.Model):
website_id = fields.Many2one('website', readonly=True)
- def _select(self):
- select = super(SaleReport, self)._select()
- select += ', s.website_id as website_id'
-
- return select
-
- def _group_by(self):
- group_by = super(SaleReport, self)._group_by()
- group_by += ', s.website_id'
-
- return group_by
+ def _query(self, with_clause='', fields={}, groupby='', from_clause=''):
+ fields['website_id'] = ", s.website_id as website_id"
+ groupby += ', s.website_id'
+ return super(SaleReport, self)._query(with_clause, fields, groupby, from_clause)
diff --git a/addons/website_sale/views/account_views.xml b/addons/website_sale/views/account_views.xml
index 53954449ae7..22ffabfeadb 100644
--- a/addons/website_sale/views/account_views.xml
+++ b/addons/website_sale/views/account_views.xml
@@ -21,4 +21,15 @@
<field name="search_view_id" ref="product.product_pricelist_view_search" />
<field name="context">{"default_base":'list_price'}</field>
</record>
+
+ <record id="account_invoice_view_form" model="ir.ui.view">
+ <field name="name">account.invoice.form</field>
+ <field name="model">account.invoice</field>
+ <field name="inherit_id" ref="account.invoice_form"/>
+ <field name="arch" type="xml">
+ <field name="fiscal_position_id" position="after">
+ <field name="website_id" groups="website.group_multi_website"/>
+ </field>
+ </field>
+ </record>
</odoo> \ No newline at end of file
diff --git a/addons/website_sale/views/product_views.xml b/addons/website_sale/views/product_views.xml
index 219eaab5909..5e9d8b55eb8 100644
--- a/addons/website_sale/views/product_views.xml
+++ b/addons/website_sale/views/product_views.xml
@@ -224,7 +224,7 @@
<field name="arch" type="xml">
<field name='currency_id' position='after'>
<field name="selectable"/>
- <field name="website_id" string="Allow to use on " placeholder="None website" groups="website.group_multi_website"/>
+ <field name="website_id" string="Allow to use on " placeholder="None website"/>
</field>
</field>
</record>
diff --git a/odoo/addons/base/models/ir_http.py b/odoo/addons/base/models/ir_http.py
index bad11dd241c..4ab82909c5b 100644
--- a/odoo/addons/base/models/ir_http.py
+++ b/odoo/addons/base/models/ir_http.py
@@ -171,9 +171,10 @@ class IrHttp(models.AbstractModel):
if serve:
return serve
- # Don't handle exception but use werkeug debugger if server in --dev mode
- if 'werkzeug' in tools.config['dev_mode']:
- raise
+ # Don't handle exception but use werkzeug debugger if server in --dev mode
+ if 'werkzeug' in tools.config['dev_mode'] and not isinstance(exception, werkzeug.exceptions.NotFound):
+ raise exception
+
try:
return request._handle_exception(exception)
except AccessDenied: