diff options
| author | rde <rde@odoo.com> | 2017-09-15 12:36:59 +0200 |
|---|---|---|
| committer | Jeremy Kersten <jke@odoo.com> | 2017-09-15 15:29:37 +0200 |
| commit | 4ecbacaf59576a22ff45615a5aa5c67244e4fb93 (patch) | |
| tree | b1d38321d2838579ccb8adc2f3fcdd0765351fcc | |
| parent | 13a44d33bda1d7ff21e3de5de8b3c84f6a482a0d (diff) | |
[ADD] website: add new page management
website.page = old ir.ui.view with page=True
website.redirect is a new mechanism to replace in the futur the ir.attachment
mechanism of redirect.
From now, we don't have a specific /page controller to serve 'page'.
We use a new model website.page which is rendered if none route matches the url
and that the field 'url' on website.page matches the request.httprequest.path.
The order to serve a path is:
- Routes defines in controllers (/shop, /blog, ...)
- ir.attachment with name matching the path
- website.page with url matching the path
- website.redirect with url_from matching the path
- 404
To improve:
- allow regexp in website.redirect model
- allow to edit the view_arch from the page.management via redirect backend
(needed when traceback in the page, or when modifying a js/css/less/...)
52 files changed, 1614 insertions, 638 deletions
diff --git a/addons/http_routing/models/ir_http.py b/addons/http_routing/models/ir_http.py index c16eb70fdd5..0c477483ad1 100644 --- a/addons/http_routing/models/ir_http.py +++ b/addons/http_routing/models/ir_http.py @@ -29,7 +29,19 @@ odoo._geoip_resolver = None # Slug API # ------------------------------------------------------------ -def slugify(s, max_length=None): +def _guess_mimetype(ext=False, default='text/html'): + exts = { + '.css': 'text/css', + '.less': 'text/less', + '.js': 'text/javascript', + '.xml': 'text/xml', + '.csv': 'text/csv', + '.html': 'text/html', + } + return ext is not False and exts.get(ext, default) or exts + + +def slugify_one(s, max_length=None): """ Transform a string to a slug that can be used in a url path. This method will first try to do the job with python-slugify if present. Otherwise it will process string by stripping leading and ending spaces, @@ -53,6 +65,21 @@ def slugify(s, max_length=None): return slug_str[:max_length] +def slugify(s, max_length=None, path=False): + if not path: + return slugify_one(s, max_length=max_length) + else: + res = [] + for u in s.split('/'): + if slugify_one(u, max_length=max_length) != '': + res.append(slugify_one(u, max_length=max_length)) + # check if supported extension + path_no_ext, ext = os.path.splitext(s) + if ext and ext in _guess_mimetype(): + res[-1] = slugify_one(path_no_ext) + ext + return '/'.join(res) + + def slug(value): if isinstance(value, models.BaseModel): if isinstance(value.id, models.NewId): diff --git a/addons/portal/static/src/less/website.ui.less b/addons/portal/static/src/less/website.ui.less index 74ba4b59a63..c4b57e018bf 100644 --- a/addons/portal/static/src/less/website.ui.less +++ b/addons/portal/static/src/less/website.ui.less @@ -23,11 +23,27 @@ body.o_connected_user { border-radius: 0; } + .o_menu_sections { + a { + > .fa-plus, > .fa-pencil { + margin-right: @grid-gutter-width/4; + } + } + + .o_mobile_preview a { + text-align: center; + font-size: 20px; + } + } .o_menu_systray { a { padding: 0 @grid-gutter-width/2; - &[data-action="edit"], &[data-action="translate"] { + &.css_edit_dynamic{ + padding: 0 @grid-gutter-width/4; + } + + &[data-action="edit"], &[data-action="translate"], &.css_edit_dynamic { .button-variant(white, @odoo-brand-optional, @odoo-brand-optional); } diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js index 8f3d44ec75e..d25f1c631e2 100644 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ b/addons/web_editor/static/src/js/widgets/widgets.js @@ -1184,7 +1184,7 @@ var LinkDialog = Dialog.extend({ } } - if (!test && (!val || !$e[0].checkValidity())) { + if (!test && $e.prop('required') && (!val || !$e[0].checkValidity())) { // FIXME: error message $e.closest('.form-group').addClass('has-error'); $e.focus(); diff --git a/addons/web_editor/static/src/less/web_editor.common.less b/addons/web_editor/static/src/less/web_editor.common.less index c48fdf7df46..57c06c6f513 100644 --- a/addons/web_editor/static/src/less/web_editor.common.less +++ b/addons/web_editor/static/src/less/web_editor.common.less @@ -145,12 +145,6 @@ span[data-oe-type="monetary"] { // Menus // TODO should not be here but used by web_studio so must stay here for now ul.oe_menu_editor { - .fa-home { - display: none; - } - > li:first-child > div > span > .fa-home { - display: block; - } .oe_menu_placeholder { outline: 1px dashed #4183C4; } diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 29cedb60a3a..e7929af071e 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -2,25 +2,23 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import base64 import datetime -from itertools import islice import json -from xml.etree import ElementTree as ET import logging -import re - import requests import werkzeug.utils import werkzeug.wrappers +from itertools import islice +from xml.etree import ElementTree as ET + import odoo -from odoo import http, models -from odoo import fields -from odoo.http import request +from odoo import http, models, fields, _ +from odoo.http import request +from odoo.tools import pycompat, OrderedSet from odoo.addons.http_routing.models.ir_http import slug from odoo.addons.web.controllers.main import WebClient, Binary, Home - -from odoo.tools import pycompat, OrderedSet +from odoo.addons.portal.controllers.portal import pager as portal_pager logger = logging.getLogger(__name__) @@ -66,16 +64,20 @@ class Website(Home): @http.route('/', type='http', auth="public", website=True) def index(self, **kw): - page = 'homepage' - main_menu = request.env.ref('website.main_menu', raise_if_not_found=False) - if main_menu: - first_menu = main_menu.child_id and main_menu.child_id[0] - if first_menu: - if first_menu.url and (not (first_menu.url.startswith(('/page/', '/?', '/#')) or (first_menu.url == '/'))): - return request.redirect(first_menu.url) - if first_menu.url and first_menu.url.startswith('/page/'): - return request.env['ir.http'].reroute(first_menu.url) - return self.page(page) + homepage = request.website.homepage_id + if homepage and homepage.url != '/': + return request.env['ir.http'].reroute(homepage.url) + + website_page = request.env['ir.http']._serve_page() + if website_page: + return website_page + else: + top_menu = request.website.sudo().menu_id + first_menu = top_menu and top_menu.child_id and top_menu.child_id[0] + if first_menu and first_menu.url != '/' and (not (first_menu.url.startswith(('/?', '/#')))): + return request.redirect(first_menu.url) + + raise request.not_found() #------------------------------------------------------ # Login - overwrite of the web login so that regular users are redirected to the backend @@ -106,30 +108,6 @@ class Website(Home): redirect.set_cookie('frontend_lang', lang) return redirect - @http.route('/page/<page:page>', type='http', auth="public", website=True, cache=300) - def page(self, page, **opt): - values = { - 'path': page, - 'deletable': True, # used to add 'delete this page' in content menu - } - # /page/website.XXX --> /page/XXX - if page.startswith('website.'): - return request.redirect(b'/page/%s?%s' % (page[8:].encode('utf-8'), request.httprequest.query_string), code=301) - elif '.' not in page: - page = 'website.%s' % page - - try: - request.website.get_template(page) - except ValueError as e: - # page not found - if request.website.is_publisher(): - values.pop('deletable') - page = 'website.page_404' - else: - return request.env['ir.http']._handle_exception(e, 404) - - return request.render(page, values) - @http.route(['/website/country_infos/<model("res.country"):country>'], type='json', auth="public", methods=['POST'], website=True) def country_infos(self, country, **kw): fields = country.get_address_fields() @@ -140,7 +118,7 @@ class Website(Home): return request.render('website.robots', {'url_root': request.httprequest.url_root}, mimetype='text/plain') @http.route('/sitemap.xml', type='http', auth="public", website=True) - def sitemap_xml_index(self): + def sitemap_xml_index(self, **kwargs): current_website = request.website Attachment = request.env['ir.attachment'].sudo() View = request.env['ir.ui.view'].sudo() @@ -166,8 +144,8 @@ class Website(Home): if not content: # Remove all sitemaps in ir.attachments as we're going to regenerated them - dom = [('type', '=', 'binary'), '|', ('url', '=like' , '/sitemap-%d-%%.xml' % current_website.id), - ('url', '=' , '/sitemap-%d.xml' % current_website.id)] + dom = [('type', '=', 'binary'), '|', ('url', '=like', '/sitemap-%d-%%.xml' % current_website.id), + ('url', '=', '/sitemap-%d.xml' % current_website.id)] sitemaps = Attachment.search(dom) sitemaps.unlink() @@ -227,22 +205,44 @@ class Website(Home): # Edit #------------------------------------------------------ - @http.route('/website/add/<path:path>', type='http', auth="user", website=True) - def pagenew(self, path, noredirect=False, add_menu=None, template=False): - if template: - xml_id = request.env['website'].new_page(path, template=template) - else: - xml_id = request.env['website'].new_page(path) - if add_menu: - request.env['website.menu'].create({ - 'name': path, - 'url': "/page/" + xml_id[8:], - 'parent_id': request.website.menu_id.id, - 'website_id': request.website.id, - }) - # Reverse action in order to allow shortcut for /page/<website_xml_id> - url = "/page/" + re.sub(r"^website\.", '', xml_id) + @http.route(['/website/pages', '/website/pages/page/<int:page>'], type='http', auth="user", website=True) + def pages_management(self, page=1, sortby='name', search='', **kw): + Page = request.env['website.page'] + searchbar_sortings = { + 'url': {'label': _('Sort by Url'), 'order': 'url'}, + 'name': {'label': _('Sort by Name'), 'order': 'name'}, + } + # default sortby order + sort_order = searchbar_sortings.get(sortby, 'name')['order'] + + domain = ['|', ('website_ids', 'in', request.website.id), ('website_ids', '=', False)] + if search: + domain += ['|', ('name', 'ilike', search), ('url', 'ilike', search)] + + pages_count = Page.search_count(domain) + + pager = portal_pager( + url="/website/pages", + url_args={'sortby': sortby}, + total=pages_count, + page=page, + step=50 + ) + pages = Page.search(domain, order=sort_order, limit=50, offset=pager['offset']) + + values = { + 'pager': pager, + 'pages': pages, + 'search': search, + 'sortby': sortby, + 'searchbar_sortings': searchbar_sortings, + } + return request.render("website.edit_website_pages", values) + @http.route(['/website/add/', '/website/add/<path:path>'], type='http', auth="user", website=True) + def pagenew(self, path="", noredirect=False, add_menu=False, template=False): + template = template and dict(template=template) or {} + url = request.env['website'].new_page(path, add_menu=add_menu, **template) if noredirect: return werkzeug.wrappers.Response(url, mimetype='text/plain') return werkzeug.utils.redirect(url + "?enable_editor=1") @@ -380,7 +380,7 @@ class Website(Home): @http.route([ '/website/action/<path_or_xml_id_or_id>', '/website/action/<path_or_xml_id_or_id>/<path:path>', - ], type='http', auth="public", website=True) + ], type='http', auth="public", website=True) def actions_server(self, path_or_xml_id_or_id, **post): ServerActions = request.env['ir.actions.server'] action = action_id = None diff --git a/addons/website/data/website_data.xml b/addons/website/data/website_data.xml index f9f0c5a9935..61020e3d224 100644 --- a/addons/website/data/website_data.xml +++ b/addons/website/data/website_data.xml @@ -16,7 +16,7 @@ <record id="menu_homepage" model="website.menu"> <field name="name">Home</field> - <field name="url">/page/homepage</field> + <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"/> @@ -24,12 +24,151 @@ <record id="menu_contactus" model="website.menu"> <field name="name">Contact us</field> - <field name="url">/page/contactus</field> + <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> + <!-- + Keep homepage & contactus page split between ir_ui_view & website_page record + So external_id of the ir_ui_view record is still 'website.contactus' and not 'website.contactus_ir_ui_view' (same for homepage) + Indeed Odoo put a suffix on external_id on submodels records (inherits) with the model's name as suffix. + eg: record website_page is 'website.contactus' & ir_ui_view record is 'website.contactus_ir_ui_view' + but since website_crm_template.xml (and surely others) inherit the contactus ir_ui_view record (inherit_id="website.contactus"), + the external_id of the contactus ir_ui_view record still has to be 'website.contactus'. + (Put in another way, external_id 'website.contactus' can't be the external_id of the website_page record or the inherit will ofc fail) + (Or change inherit_id in related files but possibly alot. Safer this way) + --> + <record id="homepage" model="ir.ui.view"> + <field name="name">Home</field> + <field name="type">qweb</field> + <field name="key">website.homepage</field> + <field name="arch" type="xml"> + <t name="Home" priority="29" t-name="website.homepage"> + <t t-call="website.layout"> + <t t-set="pageName" t-value="'homepage'"/> + <div id="wrap" class="oe_structure oe_empty"/> + </t> + </t> + </field> + </record> + <record id="homepage_page" model="website.page"> + <field name="name">Home</field> + <field name="website_published">True</field> + <field name="url">/</field> + <field name="ir_ui_view_id" ref="homepage" /> + </record> + <record id="default_website" model="website"> + <field name="homepage_id" ref="homepage_page" /> + </record> + + <record id="contactus" model="ir.ui.view"> + <field name="name">Contact Us</field> + <field name="type">qweb</field> + <field name="key">website.contactus</field> + <field name="arch" type="xml"> + <t name="Contact Us" t-name="website.contactus"> + <t t-call="website.layout"> + <div id="wrap"> + <div class="oe_structure"/> + <div class="container"> + <h1>Contact us</h1> + <div class="row"> + <div class="col-md-8"> + <div class="oe_structure"> + <div> + <p>Contact us about anything related to our company or services.</p> + <p>We'll do our best to get back to you as soon as possible.</p> + </div> + </div> + <div class="text-center mt64" name="mail_button"> + <a t-attf-href="mailto:{{ res_company.email }}" class="btn btn-primary" id="o_contact_mail">Send us an email</a> + </div> + </div> + <div class="col-md-4 mb32"> + <t t-call="website.company_description"/> + </div> + </div> + </div> + <div class="oe_structure"/> + </div> + </t> + </t> + </field> + </record> + <record id="contactus_page" model="website.page"> + <field name="name">Contact Us</field> + <field name="url">/contactus</field> + <field name="website_published">True</field> + <field name="ir_ui_view_id" ref="contactus" /> + </record> + + <record id="aboutus" model="ir.ui.view"> + <field name="name">About us</field> + <field name="type">qweb</field> + <field name="key">website.aboutus</field> + <field name="arch" type="xml"> + <t name="About us" t-name="website.aboutus"> + <t t-call="website.layout"> + <div id="wrap"> + <div class="oe_structure"> + + <section> + <div class="container"> + <div class="row"> + <div class="col-md-12"> + <h1 class="text-center">About us</h1> + <h3 class="text-muted text-center">Great products for great people</h3> + </div> + </div> + </div> + </section> + + <section> + <div class="container"> + <div class="row"> + <div class="col-md-6 mt32"> + <p> + We are a team of passionate people whose goal is to improve everyone's + life through disruptive products. We build great products to solve your + business problems. + </p> + <p> + Our products are designed for small to medium size companies willing to optimize + their performance. + </p> + </div> + <div class="col-md-4 col-md-offset-2 mt16 mb16"> + <img src="/website/static/src/img/library/business_conference.jpg" class="img img-responsive shadow" alt="Our Team"/> + </div> + </div> + </div> + </section> + + </div> + <div class="oe_structure"></div> + </div> + </t> + </t> + </field> + </record> + + <record id="aboutus_page" model="website.page"> + <field name="name">About us</field> + <field name="website_published">True</field> + <field name="url">/aboutus</field> + <field name="ir_ui_view_id" ref="aboutus" /> + </record> + + <record id="menu_homepage" model="website.menu"> + <field name="page_id" ref="homepage_page" /> + </record> + + <record id="menu_contactus" model="website.menu"> + <field name="page_id" ref="contactus_page" /> + </record> + <record id="action_module_website" model="ir.actions.act_window"> <field name="name">Website Apps</field> <field name="res_model">ir.module.module</field> @@ -61,7 +200,7 @@ </record> <record id="action_website_homepage" model="ir.actions.act_url"> <field name="name">Website Homepage</field> - <field name="url">/page/homepage</field> + <field name="url">/</field> <field name="target">self</field> </record> diff --git a/addons/website/data/website_demo.xml b/addons/website/data/website_demo.xml index a58ff42a725..c1ecd0f054e 100644 --- a/addons/website/data/website_demo.xml +++ b/addons/website/data/website_demo.xml @@ -132,12 +132,11 @@ response = request.render("website.template_partner_comment", { </record> <record id="website2_homepage" model="ir.ui.view"> - <field name="name">Homepage</field> + <field name="name">Home</field> <field name="type">qweb</field> - <field name="key">website.homepage</field> - <field name="website_id" ref="website2"/> + <field name="key">website2.homepage</field> <field name="arch" type="xml"> - <t name="Homepage" priority="29" t-name="website.homepage"> + <t name="Home" priority="29" t-name="website2.homepage"> <t t-call="website.layout"> <div id="wrap" class="oe_structure oe_empty"> <div class="carousel slide mb32" id="myCarousel0" style="height: 320px;"> @@ -152,7 +151,7 @@ response = request.render("website.template_partner_comment", { <h2>Homepage 0.0.0.0</h2> <h3>Click to customize this text</h3> <p> - <a class="btn btn-success btn-large" href="/page/website.contactus">Contact us</a> + <a class="btn btn-success btn-large" href="/contactus">Contact us</a> </p> </div> <span class="carousel-img col-md-6 hidden-sm hidden-xs"> </span> @@ -172,27 +171,79 @@ response = request.render("website.template_partner_comment", { </t> </field> </record> + <record id="website2_homepage_page" model="website.page"> + <field name="name">Home</field> + <field name="website_published">True</field> + <field name="url">/</field> + <field name="ir_ui_view_id" ref="website2_homepage" /> + <field name="website_ids" eval="[(4, ref('website2'))]" /> + </record> + + <record id="website2_contactus" model="ir.ui.view"> + <field name="name">Contact Us</field> + <field name="type">qweb</field> + <field name="key">website2.contactus</field> + <field name="arch" type="xml"> + <t name="Contact Us" t-name="website2.contactus"> + <t t-call="website.layout"> + <div id="wrap"> + <div class="oe_structure"/> + <div class="container"> + <h1>Contact us</h1> + <div class="row"> + <div class="col-md-8"> + <div class="oe_structure"> + <div> + <p>Contact us about anything related to our company or services.</p> + <p>We'll do our best to get back to you as soon as possible.</p> + </div> + </div> + <div class="text-center mt64" name="mail_button"> + <a t-attf-href="mailto:{{ res_company.email }}" class="btn btn-primary" id="o_contact_mail">Send us an email</a> + </div> + </div> + <div class="col-md-4 mb32"> + <t t-call="website.company_description"/> + </div> + </div> + </div> + <div class="oe_structure"/> + </div> + </t> + </t> + </field> + </record> + <record id="website2_contactus_page" model="website.page"> + <field name="name">Home</field> + <field name="website_published">True</field> + <field name="url">/contactus</field> + <field name="ir_ui_view_id" ref="website2_contactus" /> + <field name="website_ids" eval="[(4, ref('website2'))]" /> + </record> + <!-- Menu & Homepage --> + <record id="website2" model="website"> + <field name="homepage_id" ref="website2_homepage_page" /> + </record> <record id="website2_main_menu" model="website.menu"> <field name="name">Top Menu</field> <field name="website_id" ref="website2"/> </record> - <record id="website2_menu_homepage" model="website.menu"> <field name="name">Home</field> - <field name="url">/page/homepage</field> + <field name="url">/</field> <field name="parent_id" ref="website.website2_main_menu"/> <field name="sequence" type="int">10</field> <field name="website_id" ref="website2"/> + <field name="page_id" ref="website2_homepage_page" /> </record> - <record id="website2_menu_contactus" model="website.menu"> <field name="name">Contact us</field> - <field name="url">/page/website.contactus</field> + <field name="url">/contactus</field> <field name="parent_id" ref="website.website2_main_menu"/> <field name="sequence" type="int">60</field> <field name="website_id" ref="website2"/> + <field name="page_id" ref="website2_contactus_page" /> </record> - </data> </odoo> diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py index 0f6369758cc..6853ff29422 100644 --- a/addons/website/models/ir_http.py +++ b/addons/website/models/ir_http.py @@ -2,8 +2,9 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import logging -import os import traceback +import datetime +import os import werkzeug import werkzeug.routing @@ -17,8 +18,7 @@ from odoo.tools import config from odoo.exceptions import QWebException from odoo.tools.safe_eval import safe_eval -from odoo.addons.http_routing.models.ir_http import ModelConverter - +from odoo.addons.http_routing.models.ir_http import ModelConverter, _guess_mimetype logger = logging.getLogger(__name__) @@ -34,7 +34,6 @@ class Http(models.AbstractModel): return dict( super(Http, cls)._get_converters(), model=ModelConverter, - page=PageConverter, ) @classmethod @@ -90,6 +89,53 @@ class Http(models.AbstractModel): return super(Http, cls)._get_default_lang() @classmethod + def _serve_page(cls): + req_page = request.httprequest.path + + domain = [('url', '=', req_page), '|', ('website_ids', 'in', request.website.id), ('website_ids', '=', False)] + + if not request.website.is_publisher: + domain += [('is_visible', '=', True)] + + mypage = request.env['website.page'].search(domain, limit=1) + _, ext = os.path.splitext(req_page) + if mypage: + return request.render(mypage.ir_ui_view_id.id, { + # 'path': req_page[1:], + 'deletable': True, + 'main_object': mypage, + }, mimetype=_guess_mimetype(ext)) + + return request.website.is_publisher() and request.render('website.page_404', {'path': req_page[1:]}) or False + + @classmethod + def _serve_redirect(cls): + req_page = request.httprequest.path + domain = [ + '|', ('website_id', '=', request.website.id), ('website_id', '=', False), + ('url_from', '=', req_page) + ] + return request.env['website.redirect'].search(domain, limit=1) + + @classmethod + def _serve_fallback(cls, exception): + # serve attachment before + parent = super(Http, cls)._serve_fallback(exception) + if parent: # attachment + return parent + + website_page = cls._serve_page() + if website_page: + return website_page + + redirect = cls._serve_redirect() + if redirect: + return request.redirect(redirect.url_to, code=redirect.type) + + return False + + + @classmethod def _handle_exception(cls, exception): code = 500 # default code is_website_request = bool(getattr(request, 'is_frontend', False) and getattr(request, 'website', False)) @@ -99,6 +145,7 @@ class Http(models.AbstractModel): else: try: response = super(Http, cls)._handle_exception(exception) + if isinstance(response, Exception): exception = response else: @@ -135,7 +182,7 @@ class Http(models.AbstractModel): if 'qweb_exception' in values: view = request.env["ir.ui.view"] views = view._views_get(exception.qweb['template']) - to_reset = views.filtered(lambda view: view.model_data_id.noupdate is True and not view.page) + to_reset = views.filtered(lambda view: view.model_data_id.noupdate is True and view.arch_fs) values['views'] = to_reset elif code == 403: logger.warn("403 Forbidden:\n\n%s", values['traceback']) @@ -178,30 +225,3 @@ class ModelConverter(ModelConverter): for record in Model.search_read(domain=domain, fields=['write_date', Model._rec_name]): if record.get(Model._rec_name, False): yield {'loc': (record['id'], record[Model._rec_name])} - - -class PageConverter(werkzeug.routing.PathConverter): - """ Only point of this converter is to bundle pages enumeration logic """ - - def generate(self, uid, query=None, args={}): - View = request.env['ir.ui.view'].sudo(uid) - domain = [('page', '=', True)] - query = query and query.startswith('website.') and query[8:] or query - if query: - domain += [('key', 'like', query)] - website = request.env['website'].get_current_website() - domain += ['|', ('website_id', '=', website.id), ('website_id', '=', False)] - - views = View.search_read(domain, fields=['key', 'priority', 'write_date'], order='name') - for view in views: - xid = view['key'].startswith('website.') and view['key'][8:] or view['key'] - # the 'page/homepage' url is indexed as '/', avoid aving the same page referenced twice - # when we will have an url mapping mechanism, replace this by a rule: page/homepage --> / - if xid == 'homepage': - continue - record = {'loc': xid} - if view['priority'] != 16: - record['__priority'] = min(round(view['priority'] / 32.0, 1), 1) - if view['write_date']: - record['__lastmod'] = view['write_date'][:10] - yield record diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index eec00e2e897..55c2d2f4fd4 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -18,7 +18,6 @@ class View(models.Model): _name = "ir.ui.view" _inherit = ["ir.ui.view", "website.seo.metadata"] - page = fields.Boolean("Whether this view is a web page template (complete)", default=False) customize_show = fields.Boolean("Show As Optional Inherit", default=False) website_id = fields.Many2one('website', ondelete='cascade', string="Website") @@ -142,4 +141,4 @@ class View(models.Model): lang_code = self.env['website'].browse(website_id).default_lang_code return lang_code else: - return super(View, self).get_default_lang_code()
\ No newline at end of file + return super(View, self).get_default_lang_code() diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 8208140b015..099bd6e1699 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -5,6 +5,7 @@ import inspect import logging import hashlib import re +import unicodedata from werkzeug import urls from werkzeug.exceptions import NotFound @@ -66,6 +67,7 @@ class Website(models.Model): cdn_filters = fields.Text('CDN Filters', default=lambda s: '\n'.join(DEFAULT_CDN_FILTERS), help="URL matching those filters will be rewritten using the CDN Base URL") partner_id = fields.Many2one(related='user_id.partner_id', relation='res.partner', string='Public Partner') menu_id = fields.Many2one('website.menu', compute='_compute_menu', string='Main Menu') + homepage_id = fields.Many2one('website.page', string='Homepage') favicon = fields.Binary(string="Website Favicon", help="This field holds the image used to display a favicon on the website.") @api.multi @@ -86,84 +88,74 @@ class Website(models.Model): #---------------------------------------------------------- # Page Management #---------------------------------------------------------- - @api.model - def new_page(self, name, template='website.default_page', ispage=True, namespace=None): + def new_page(self, name=False, add_menu=False, template='website.default_page', ispage=True, namespace=None): """ Create a new website page, and assign it a xmlid based on the given one :param name : the name of the page :param template : potential xml_id of the page to create :param namespace : module part of the xml_id if none, the template module name is used """ + if namespace: template_module = namespace else: - template_module, dummy = template.split('.') - website_id = self._context.get('website_id') - + template_module, _ = template.split('.') # completely arbitrary max_length - page_name = slugify(name, max_length=50) - page_xmlid = "%s.%s" % (template_module, page_name) + page_url = '/' + slugify(name, max_length=200, path=True) + page_key = self.get_unique_path(slugify(name, 50)) - # find a free xmlid - inc = 0 - domain_static = [('website_id', '=', False), ('website_id', '=', website_id)] - while self.env['ir.ui.view'].with_context(active_test=False).sudo().search([('key', '=', page_xmlid), '|'] + domain_static): - inc += 1 - page_xmlid = "%s.%s" % (template_module, page_name + ("-%s" % inc if inc else "")) - page_name += (inc and "-%s" % inc or "") + if not name: + name = 'Home' + page_key = 'home' - # new page template_record = self.env.ref(template) - key = '%s.%s' % (template_module, page_name) - page = template_record.copy({'website_id': website_id, 'key': key}) - page.with_context(lang=None).write({ - 'arch': page.arch.replace(template, page_xmlid), - 'name': page_name, - 'page': ispage, + website_id = self._context.get('website_id') + key = '%s.%s' % (template_module, page_key) + view = template_record.copy({'website_id': website_id, 'key': key}) + + view.with_context(lang=None).write({ + 'arch': template_record.arch.replace(template, key), + 'name': name, }) - return page_xmlid + if ispage: + page = self.env['website.page'].create({ + 'name': name, + 'url': page_url, + 'website_ids': [(6, None, [self.get_current_website().id])], + 'ir_ui_view_id': view.id + }) + if add_menu: + self.env['website.menu'].create({ + 'name': name, + 'url': page_url, + 'parent_id': self.get_current_website().menu_id.id, + 'page_id': page.id, + 'website_id': self.get_current_website().id, + }) + return page_url + + def get_unique_path(self, page_url): + """ Given an url, return that url suffixed by counter if it already exists + :param page_url : the url to be checked for uniqueness + """ + website_id = self.get_current_website().id + inc = 0 + domain_static = ['|', ('website_ids', '=', False), ('website_ids', 'in', website_id)] + page_temp = page_url + while self.env['website.page'].with_context(active_test=False).sudo().search([('url', '=', page_temp)] + domain_static): + inc += 1 + page_temp = page_url + (inc and "-%s" % inc or "") + return page_temp def key_to_view_id(self, view_id): return self.env['ir.ui.view'].search([ ('id', '=', view_id), '|', ('website_id', '=', self._context.get('website_id')), ('website_id', '=', False), - ('page', '=', True), ('type', '=', 'qweb') ]) @api.model - def delete_page(self, view_id): - """ Delete a page, given its identifier - :param view_id : ir.ui.view identifier - """ - view = self.key_to_view_id(view_id) - if view: - view.unlink() - - @api.model - def rename_page(self, view_id, new_name): - """ Change the name of the given page - :param view_id : id of the view to rename - :param new_name : name to use - """ - view = self.key_to_view_id(view_id) - if view: - # slugify the new name and prefix by module if - # not already done by end user - new_name = slugify(new_name, max_length=50) - prefix = view.key.split('.')[0] - if not new_name.startswith(prefix): - new_name = "%s.%s" % (prefix, new_name) - - view.write({ - 'key': new_name, - 'arch_db': view.arch_db.replace(view.key, new_name, 1) - }) - return new_name - return False - - @api.model - def page_search_dependencies(self, view_id=False): + def page_search_dependencies(self, page_id=False): """ Search dependencies just for information. It will not catch 100% of dependencies and False positive is more than possible Each module could add dependences in this dict @@ -171,60 +163,65 @@ class Website(models.Model): view, and the value is the list of text and link to the resource using given page """ dependencies = {} - if not view_id: + if not page_id: return dependencies - view = self.env['ir.ui.view'].browse(view_id) + page = self.env['website.page'].browse(int(page_id)) website_id = self._context.get('website_id') - name = view.key.replace("website.", "") - fullname = "website.%s" % name - - if view.page: - # search for page with link - page_search_dom = [ - '|', ('website_id', '=', website_id), ('website_id', '=', False), - '|', ('arch_db', 'ilike', '/page/%s' % name), ('arch_db', 'ilike', '/page/%s' % fullname) - ] - - page_key = _('Page') - pages = self.env['ir.ui.view'].search(page_search_dom) - for page in pages: - dependencies.setdefault(page_key, []) - if page.page: - dependencies[page_key].append({ - 'text': _('Page <b>%s</b> contains a link to this page') % page.key, - 'link': '/page/%s' % page.key - }) - else: - dependencies[page_key].append({ - 'text': _('Template <b>%s (id:%s)</b> contains a link to this page') % (page.key, page.id), - 'link': '#' - }) - - # search for menu with link - menu_search_dom = [ - '|', ('website_id', '=', website_id), ('website_id', '=', False), - '|', ('url', 'ilike', '/page/%s' % name), ('url', 'ilike', '/page/%s' % fullname) - ] - - menu_key = _('Menu') - menus = self.env['website.menu'].search(menu_search_dom) - for menu in menus: - dependencies.setdefault(menu_key, []).append({ - 'text': _('This page is in the menu <b>%s</b>') % menu.name, - 'link': False - }) + url = page.url + + + page_key = _('Page') + + # search for website_page with link + website_page_search_dom = [ + '|', ('website_ids', 'in', website_id), ('website_ids', '=', False), ('ir_ui_view_id.arch_db', 'ilike', url) + ] + pages = self.env['website.page'].search(website_page_search_dom) + page_view_ids = [] + for page in pages: + dependencies.setdefault(page_key, []) + dependencies[page_key].append({ + 'text': _('Page <b>%s</b> contains a link to this page') % page.url, + 'link': page.url + }) + page_view_ids.append(page.ir_ui_view_id.id) + + # search for ir_ui_view (not from a website_page) with link + page_search_dom = [ + '|', ('website_id', '=', website_id), ('website_id', '=', False), + ('arch_db', 'ilike', url), ('id', 'not in', page_view_ids) + ] + views = self.env['ir.ui.view'].search(page_search_dom) + for view in views: + dependencies.setdefault(page_key, []) + dependencies[page_key].append({ + 'text': _('Template <b>%s (id:%s)</b> contains a link to this page') % (view.key or view.name, view.id), + 'link': '#' + }) + # search for menu with link + menu_search_dom = [ + '|', ('website_id', '=', website_id), ('website_id', '=', False), ('url', 'ilike', '%s' % url) + ] + + menu_key = _('Menu') + menus = self.env['website.menu'].search(menu_search_dom) + for menu in menus: + dependencies.setdefault(menu_key, []).append({ + 'text': _('This page is in the menu <b>%s</b>') % menu.name, + 'link': False + }) return dependencies @api.model def page_exists(self, name, module='website'): try: - name = (name or "").replace("/page/website.", "").replace("/page/", "") + name = (name or "").replace("/website.", "").replace("/", "") if not name: return False return self.env.ref('%s.%s' % module, name) - except: + except Exception: return False #---------------------------------------------------------- @@ -348,7 +345,7 @@ class Website(models.Model): return all((arg in rule._converters) for arg in args) @api.multi - def enumerate_pages(self, query_string=None): + def enumerate_pages(self, query_string=None, force=False): """ Available pages in the website/CMS. This is mostly used for links generation and can be overridden by modules setting up new HTML controllers for dynamic pages (e.g. blog). @@ -360,13 +357,13 @@ class Website(models.Model): of the same. :rtype: list({name: str, url: str}) """ + router = request.httprequest.app.get_db_router(request.db) # Force enumeration to be performed as public user url_set = set() for rule in router.iter_rules(): if not self.rule_is_enumerable(rule): continue - converters = rule._converters or {} if query_string and not converters and (query_string not in rule.build([{}], append_unknown=False)[1]): continue @@ -400,12 +397,37 @@ class Website(models.Model): yield page + # '/' already has a http.route & is in the routing_map so it will already have an entry in the xml + domain = [('url', '!=', '/')] + if not force: + domain += [('website_indexed', '=', True)] + #is_visible + domain += [('website_published', '=', True), '|', ('date_publish', '!=', False), ('date_publish', '>', fields.Datetime.now())] + + if query_string: + domain += [('url', 'like', query_string)] + + pages = self.get_website_pages(domain) + + for page in pages: + record = {'loc': page['url'], 'id': page['id'], 'name': page['name']} + if page.ir_ui_view_id and page.ir_ui_view_id.priority != 16: + record['__priority'] = min(round(page.ir_ui_view_id.priority / 32.0, 1), 1) + if page['write_date']: + record['__lastmod'] = page['write_date'][:10] + yield record + + @api.multi + def get_website_pages(self, domain=[], order='name', limit=None): + domain += ['|', ('website_ids', 'in', self.get_current_website().id), ('website_ids', '=', False)] + pages = request.env['website.page'].search(domain, order='name', limit=limit) + return pages + @api.multi def search_pages(self, needle=None, limit=None): - name = re.sub(r"^/p(a(g(e(/(w(e(b(s(i(t(e(\.)?)?)?)?)?)?)?)?)?)?)?)?", "", needle or "") - name = slugify(name, max_length=50) + name = slugify(needle, max_length=50, path=True) res = [] - for page in self.enumerate_pages(query_string=name): + for page in self.enumerate_pages(query_string=name, force=True): res.append(page) if len(res) == limit: break @@ -437,6 +459,196 @@ class Website(models.Model): return self.env.ref('website.action_website').read()[0] +class SeoMetadata(models.AbstractModel): + + _name = 'website.seo.metadata' + _description = 'SEO metadata' + + website_meta_title = fields.Char("Website meta title", translate=True) + website_meta_description = fields.Text("Website meta description", translate=True) + website_meta_keywords = fields.Char("Website meta keywords", translate=True) + + +class WebsitePublishedMixin(models.AbstractModel): + + _name = "website.published.mixin" + + website_published = fields.Boolean('Visible in Website', copy=False) + website_url = fields.Char('Website URL', compute='_compute_website_url', help='The full URL to access the document through the website.') + + @api.multi + def _compute_website_url(self): + for record in self: + record.website_url = '#' + + @api.multi + def website_publish_button(self): + self.ensure_one() + if self.env.user.has_group('website.group_website_publisher') and self.website_url != '#': + return self.open_website_url() + return self.write({'website_published': not self.website_published}) + + def open_website_url(self): + return { + 'type': 'ir.actions.act_url', + 'url': self.website_url, + 'target': 'self', + } + + +class Page(models.Model): + _name = 'website.page' + _inherits = {'ir.ui.view': 'ir_ui_view_id'} + _inherit = 'website.published.mixin' + _description = 'Page' + + name = fields.Char('Page Name') + url = fields.Char('Page URL') + website_ids = fields.Many2many('website', string='Websites') + ir_ui_view_id = fields.Many2one('ir.ui.view', string='View', required=True, ondelete="cascade") + website_indexed = fields.Boolean('Page Indexed', default=True) + date_publish = fields.Datetime('Publishing Date') + # This is needed to be able to display if page is a menu in /website/page_management + menu_ids = fields.One2many('website.menu', 'page_id', 'Related Menus') + is_homepage = fields.Boolean(compute='_compute_homepage', string='Homepage') + is_visible = fields.Boolean(compute='_compute_visible', string='Is Visible') + + @api.one + def _compute_homepage(self): + self.is_homepage = self == self.env['website'].get_current_website().homepage_id + + @api.one + def _compute_visible(self): + self.is_visible = self.website_published and (not self.date_publish or self.date_publish < fields.Datetime.now()) + + @api.model + def get_page_info(self, id, website_id): + domain = ['|', ('website_ids', 'in', website_id), ('website_ids', '=', False), ('id', '=', id)] + item = self.search_read(domain, fields=['id', 'name', 'url', 'website_published', 'website_indexed', 'date_publish', 'menu_ids', 'is_homepage'], limit=1) + return item + + @api.model + def save_page_info(self, website_id, data): + website = self.env['website'].browse(website_id) + + if data['is_homepage'] and website.homepage_id.id != int(data['id']): + # If page is set as the new homepage, set it on website (only page can be set as homepage) + website.write({'homepage_id': data['id']}) + else: + if not data['is_homepage'] and website.homepage_id.id == int(data['id']): + # If the page is not a homepage, check if it was the homepage + website.write({'homepage_id': None}) + + #If URL has been edited, slug it + page = self.browse(int(data['id'])) + original_url = page.url + url = data['url'] + if page.url != url: + url = slugify(url, max_length=200, path=True) + + menu = self.env['website.menu'].search([('page_id', '=', int(data['id']))]) + if not data['is_menu']: + #If the page is no longer in menu, we should remove its website_menu + if menu: + menu.unlink() + else: + #The page is now a menu, check if has already one + if menu: + menu.write({'url': url}) + else: + self.env['website.menu'].create({ + 'name': data['name'], + 'url': url, + 'page_id': data['id'], + 'parent_id': website.menu_id.id, + 'website_id': website.id, + }) + + page.write({ + 'name': data['name'], 'url': url, + 'website_published': data['website_published'], + 'website_indexed': data['website_indexed'], + 'date_publish': data['date_publish'] or None + }) + + # Create redirect if needed + if data['create_redirect']: + self.env['website.redirect'].create({ + 'type': data['redirect_type'], + 'url_from': original_url, + 'url_to': url, + 'website_id': website.id, + }) + + return True + + @api.multi + def copy(self, default=None): + view = self.env['ir.ui.view'].browse(self.ir_ui_view_id.id) + new_view = view.copy() + default = { + 'name': self.name + ' (copy)', + 'url': self.env['website'].get_unique_path(self.url), + 'ir_ui_view_id': new_view.id, + } + return super(Page, self).copy(default=default) + + @api.model + def clone_page(self, page_id, clone_menu=True): + """ Clone a page, given its identifier + :param page_id : website.page identifier + """ + page = self.browse(int(page_id)) + new_page = page.copy() + if clone_menu: + 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 + new_menu = menu.copy() + new_menu.write({'url': new_page.url, 'name': menu.name + ' (copy)', 'page_id': new_page.id}) + + return new_page.url + '?enable_editor=1' + + @api.multi + def unlink(self): + """ When a website_page is deleted, the ORM does not delete its ir_ui_view. + So we got to delete it ourself, but only if the ir_ui_view is not used by another website_page. + """ + # Handle it's ir_ui_view + 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.env['website.page'].search( + [('ir_ui_view_id', '=', self.ir_ui_view_id.id), ('id', '!=', self.id)] + ) + if len(pages_linked_to_iruiview) == 0: + # If there is no other pages linked to that ir_ui_view, we can delete the ir_ui_view + self.env['ir.ui.view'].search([('id', '=', self.ir_ui_view_id.id)]).unlink() + # And then delete the website_page itself + return super(Page, self).unlink() + + @api.model + def delete_page(self, page_id): + """ Delete a page or a link, given its identifier + :param object_id : object identifier eg: menu-5 + """ + # If we are deleting a page (that could possibly be a menu with a page) + page = self.env['website.page'].browse(int(page_id)) + if page: + # Check if it is a menu with a page and also delete menu if so + menu = self.env['website.menu'].search([('page_id', '=', page.id)], limit=1) + if menu: + menu.unlink() + page.unlink() + + @api.multi + def write(self, vals): + self.ensure_one() + if 'url' in vals and not vals['url'].startswith('/'): + vals['url'] = '/' + vals['url'] + result = super(Page, self).write(vals) + return result + + class Menu(models.Model): _name = "website.menu" @@ -452,6 +664,7 @@ class Menu(models.Model): name = fields.Char('Menu', required=True, translate=True) url = fields.Char('Url', default='') + page_id = fields.Many2one('website.page', 'Related Page') new_window = fields.Boolean('New Window') sequence = fields.Integer(default=_default_sequence) website_id = fields.Many2one('website', 'Website') # TODO: support multiwebsite once done for ir.ui.views @@ -460,18 +673,35 @@ class Menu(models.Model): parent_left = fields.Integer('Parent Left', index=True) parent_right = fields.Integer('Parent Rigth', index=True) + @api.model + def clean_url(self): + # clean the url with heuristic + if self.page_id: + url = self.page_id.url + else: + url = self.url + if not self.url.startswith('/'): + if '@' in self.url and not self.url.startswith('mailto'): + url = 'mailto:%s' % self.url + elif not self.url.startswith('http'): + url = '/%s' % self.url + return url + # would be better to take a menu_id as argument @api.model def get_tree(self, website_id, menu_id=None): def make_tree(node): + page_id = node.page_id.id if node.page_id else None + is_homepage = page_id and self.env['website'].browse(website_id).homepage_id.id == page_id menu_node = dict( id=node.id, name=node.name, - url=node.url, + url=node.page_id.url if page_id else node.url, new_window=node.new_window, sequence=node.sequence, parent_id=node.parent_id.id, children=[], + is_homepage=is_homepage, ) for child in node.child_id: menu_node['children'].append(make_tree(child)) @@ -495,46 +725,29 @@ class Menu(models.Model): self.browse(to_delete).unlink() for menu in data['data']: mid = menu['id'] + # new menu are prefixed by new- if isinstance(mid, pycompat.string_types): new_menu = self.create({'name': menu['name']}) replace_id(mid, new_menu.id) for menu in data['data']: + # if the url match a website.page, set the m2o relation + page = self.env['website.page'].search([('url', '=', menu['url'])], limit=1) + if page: + menu['page_id'] = page.id self.browse(menu['id']).write(menu) - return True - - -class SeoMetadata(models.AbstractModel): - - _name = 'website.seo.metadata' - _description = 'SEO metadata' - - website_meta_title = fields.Char("Website meta title", translate=True) - website_meta_description = fields.Text("Website meta description", translate=True) - website_meta_keywords = fields.Char("Website meta keywords", translate=True) + return True -class WebsitePublishedMixin(models.AbstractModel): - - _name = "website.published.mixin" - - website_published = fields.Boolean('Visible in Website', copy=False) - website_url = fields.Char('Website URL', compute='_compute_website_url', help='The full URL to access the document through the website.') - - @api.multi - def _compute_website_url(self): - for record in self: - record.website_url = '#' - @api.multi - def website_publish_button(self): - self.ensure_one() - if self.env.user.has_group('website.group_website_publisher') and self.website_url != '#': - return self.open_website_url() - return self.write({'website_published': not self.website_published}) +class WebsiteRedirect(models.Model): + _name = "website.redirect" + _description = "Website Redirect" + _order = "sequence, id" + _rec_name = 'url_from' - def open_website_url(self): - return { - 'type': 'ir.actions.act_url', - 'url': self.website_url, - 'target': 'self', - } + type = fields.Selection([('301', 'Moved permanently'), ('302', 'Moved temporarily')], string='Redirection Type') + url_from = fields.Char('Redirect From') + url_to = fields.Char('Redirect To') + website_id = fields.Many2one('website', 'Website') + active = fields.Boolean(default=True) + sequence = fields.Integer(default=0) diff --git a/addons/website/security/ir.model.access.csv b/addons/website/security/ir.model.access.csv index 849724a4094..cfc93d39510 100644 --- a/addons/website/security/ir.model.access.csv +++ b/addons/website/security/ir.model.access.csv @@ -3,6 +3,10 @@ access_website_public,website,website.model_website,,1,0,0,0 access_website,website,website.model_website,group_website_designer,1,1,1,1 access_website_menu,access_website_menu,model_website_menu,,1,0,0,0 access_website_menu_designer,Web Menu Manager,model_website_menu,group_website_designer,1,1,1,1 +access_website_redirect,access_website_redirect,model_website_redirect,,1,0,0,0 +access_website_redirect_designer,Web Redirect Manager,model_website_redirect,group_website_designer,1,1,1,1 +access_website_page,access_website_page,model_website_page,,1,0,0,0 +access_website_page_designer,Web Page Manager,model_website_page,group_website_designer,1,1,1,1 access_website,web menu manager,website.model_website,group_website_designer,1,1,1,1 access_website_ir_ui_view,access_website_ir_ui_view,model_ir_ui_view,group_website_designer,1,1,1,1 access_seo_public,access_seo_public,model_website_seo_metadata,,1,0,0,0 diff --git a/addons/website/security/website_security.xml b/addons/website/security/website_security.xml index 2e7ad4cbe6f..99264d4e460 100644 --- a/addons/website/security/website_security.xml +++ b/addons/website/security/website_security.xml @@ -45,4 +45,10 @@ <field name="perm_create" eval="True"/> <field name="perm_unlink" eval="True"/> </record> + <record id="website_page_rule_public" model="ir.rule"> + <field name="name">website.page: portal/public: read published pages</field> + <field name="model_id" ref="website.model_website_page"/> + <field name="domain_force">[('website_published', '=', True)]</field> + <field name="groups" eval="[(4, ref('base.group_portal')), (4, ref('base.group_public'))]"/> + </record> </odoo> diff --git a/addons/website/static/src/js/content/website_root.js b/addons/website/static/src/js/content/website_root.js index 490151d38eb..74ab190f36d 100644 --- a/addons/website/static/src/js/content/website_root.js +++ b/addons/website/static/src/js/content/website_root.js @@ -195,6 +195,7 @@ var WebsiteRoot = BodyManager.extend({ }) .done(function (result) { $data.toggleClass("css_unpublished css_published"); + $data.find('input').prop("checked", result); $data.parents("[data-publish]").attr("data-publish", +result ? 'on' : 'off'); }) .fail(function (err, data) { diff --git a/addons/website/static/src/js/menu/content.js b/addons/website/static/src/js/menu/content.js index 1ed6e2305e0..9a7e38244cf 100644 --- a/addons/website/static/src/js/menu/content.js +++ b/addons/website/static/src/js/menu/content.js @@ -3,13 +3,241 @@ odoo.define('website.contentMenu', function (require) { var core = require('web.core'); var Dialog = require('web.Dialog'); +var time = require('web.time'); var weContext = require('web_editor.context'); var widget = require('web_editor.widget'); var websiteNavbarData = require('website.navbar'); +var websiteRootData = require('website.WebsiteRoot'); +var Widget = require('web.Widget'); var _t = core._t; var qweb = core.qweb; +var PagePropertiesDialog = widget.Dialog.extend({ + template: 'website.pagesMenu.page_info', + xmlDependencies: widget.Dialog.prototype.xmlDependencies.concat( + ['/website/static/src/xml/website.pageProperties.xml'] + ), + events: _.extend({}, widget.Dialog.prototype.events, { + 'keyup input#page_url': '_onUrlChanged', + 'change input#create_redirect': '_onCreateRedirectChanged', + }), + + /** + * @constructor + * @override + */ + init: function (parent, page_id, options) { + var self = this; + var serverUrl = window.location.origin + "/"; + var length_url = serverUrl.length; + var serverUrlTrunc = serverUrl; + if(length_url > 30) + serverUrlTrunc = serverUrl.slice(0,14) + '..' + serverUrl.slice(-14); + this.serverUrl = serverUrl; + this.serverUrlTrunc = serverUrlTrunc; + this.current_page_url = window.location.pathname; + this.page_id = page_id; + + var buttons = [ + {text: _t("Save"), classes: "btn-primary o_save_button", click: self.save}, + {text: _t("Discard"), close: true}, + ]; + if (options.fromPageManagement) { + buttons.push({text: _t("Go To Page"), icon: "fa-globe", classes: "btn-link pull-right", click: function(e){window.location.href = self.page.url;}}); + } + this._super(parent, _.extend({}, { + title: _t("Page Properties"), + size: 'medium', + buttons: buttons, + }, options || {})); + }, + /** + * @override + */ + willStart: function () { + var defs = [this._super.apply(this, arguments)]; + var self = this; + var context = weContext.get(); + + defs.push(this._rpc({ + model: 'website.page', + method: 'get_page_info', + args: [self.page_id,context.website_id], + kwargs: { + context: context + }, + }).then(function (page) { + page[0].url = page[0].url.startsWith('/') ? page[0].url.substring(1) : page[0].url; + self.page = page[0]; + })); + + defs.push(this._rpc({ + model: 'website.redirect', + method: 'fields_get', + }).then(function (fields) { + self.fields = fields; + })); + + return $.when.apply($, defs); + }, + /** + * @override + */ + start: function() { + var self = this; + var context = weContext.get(); + + this.$(".ask_for_redirect").hide(); + this.$(".redirect_type").hide(); + + //TODO: remove.html() & set self.var & display in view instead of controller (here) + this._getPageDependencies(self.page_id, context) + .then(function (dependencies) { + var dep = []; + _.each(dependencies, function( value, index ) { + if (dependencies[index].length > 0) { + dep.push(dependencies[index].length + ' ' + index.toLowerCase() + (dependencies[index].length > 1 ? 's': '')); + } + }); + if (dep.length > 0) { + $("#dependencies_redirect").html("(" + _("used in") + " " + dep.join(", ") + ")"); + } + }); + + var l10n = _t.database.parameters; + var datepickersOptions = { + minDate: moment({ y: 1900 }), + maxDate: moment().add(200, "y"), + calendarWeeks: true, + icons : { + time: 'fa fa-clock-o', + date: 'fa fa-calendar', + next: 'fa fa-chevron-right', + previous: 'fa fa-chevron-left', + up: 'fa fa-chevron-up', + down: 'fa fa-chevron-down', + }, + locale : moment.locale(), + format : time.strftime_to_moment_format(l10n.date_format +' '+ l10n.time_format), + widgetPositioning : { + horizontal: 'auto', + vertical: 'top', + }, + }; + if (self.page.date_publish) { + datepickersOptions.defaultDate = self.page.date_publish; + } + + this.$("#date_publish_container").datetimepicker(datepickersOptions); + + return this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + save: function(data){ + var self = this; + var context = weContext.get(); + var url = $(".o_page_management_info #page_url").val(); + + var date_publish = $(".o_page_management_info #date_publish").val(); + if (date_publish != "") { + date_publish = time.datetime_to_str(new Date(date_publish)); + } + var params = { + id: self.page.id, + name: $(".o_page_management_info #page_name").val(), + //replace duplicate following "/" by only one "/" + url: url.replace(/\/{2,}/g,"/"), + is_menu: $(".o_page_management_info #is_menu").prop('checked'), + is_homepage: $(".o_page_management_info #is_homepage").prop('checked'), + website_published: $(".o_page_management_info #is_published").prop('checked'), + create_redirect: $(".o_page_management_info #create_redirect").prop('checked'), + redirect_type: $(".o_page_management_info #redirect_type").val(), + website_indexed: $(".o_page_management_info #is_indexed").prop('checked'), + date_publish: date_publish, + }; + self._rpc({ + model: 'website.page', + method: 'save_page_info', + args: [[context.website_id], params], + kwargs: { + context: context + }, + }).then(function () { + // If from page manager: reload url, if from page itself: go to (possibly) new url + if (self._getMainObject().model == 'website.page') + window.location.href = url.toLowerCase(); + else + window.location.reload(true); + }); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Retrieves the page dependencies for the given object id. + * + * @private + * @param {integer} moID + * @param {Object} context + * @returns {Deferred<Array>} + */ + _getPageDependencies: function (moID, context) { + return this._rpc({ + model: 'website', + method: 'page_search_dependencies', + args: [moID], + context: context, + }); + }, + /** + * Returns information about the page main object. + * + * @private + * @returns {Object} model and id + */ + _getMainObject: function () { + var repr = $('html').data('main-object'); + var m = repr.match(/(.+)\((\d+),(.*)\)/); + return { + model: m[1], + id: m[2] | 0, + }; + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + _onUrlChanged: function () { + var url = this.$('input#page_url').val(); + if (url != this.page.url) { + this.$(".ask_for_redirect").show(); + } + else { + this.$(".ask_for_redirect").hide(); + } + }, + _onCreateRedirectChanged: function () { + var createRedirect = this.$('input#create_redirect').prop('checked'); + if (createRedirect) { + this.$(".redirect_type").show(); + } + else { + this.$(".redirect_type").hide(); + } + }, +}); + var MenuEntryDialog = widget.LinkDialog.extend({ xmlDependencies: widget.LinkDialog.prototype.xmlDependencies.concat( ['/website/static/src/xml/website.contentMenu.xml'] @@ -24,13 +252,16 @@ var MenuEntryDialog = widget.LinkDialog.extend({ data.isNewWindow = data.new_window; this.data = data; this.menu_link_options = options.menu_link_options; - return this._super.apply(this, arguments); + this._super(parent, _.extend({}, { + title: _t("Create Menu"), + }, options || {}), editor, data); }, /** * @override */ start: function () { var self = this; + self.$('#o_link_dialog_url_input').closest('.form-group').hide(); this.$('.o_link_dialog_preview').remove(); this.$('.window-new, .link-style').closest('.form-group').remove(); this.$('label[for="o_link_dialog_label_input"]').text(_t("Menu Label")); @@ -288,9 +519,8 @@ var EditMenuDialog = widget.Dialog.extend({ var ContentMenu = websiteNavbarData.WebsiteNavbarActionWidget.extend({ xmlDependencies: ['/website/static/src/xml/website.xml'], actions: _.extend({}, websiteNavbarData.WebsiteNavbarActionWidget.prototype.actions || {}, { - delete_page: '_deletePage', edit_menu: '_editMenu', - rename_page: '_renamePage', + page_properties: '_pageProperties', }), //-------------------------------------------------------------------------- @@ -311,69 +541,12 @@ var ContentMenu = websiteNavbarData.WebsiteNavbarActionWidget.extend({ id: m[2] | 0, }; }, - /** - * Retrieves the page dependencies for the given object id. - * - * @private - * @param {integer} moID - * @param {Object} context - * @returns {Deferred<Array>} - */ - _getPageDependencies: function (moID, context) { - return this._rpc({ - model: 'website', - method: 'page_search_dependencies', - args: [moID], - context: context, - }); - }, //-------------------------------------------------------------------------- // Actions //-------------------------------------------------------------------------- /** - * Searches for the page dependencies, inform the user about them then - * delete the page if the user agrees. - * - * @private - * @returns {Deferred} - */ - _deletePage: function () { - var self = this; - var moID = self._getMainObject().id; - var context = weContext.get(); - - var def = $.Deferred(); - - // Search the page dependencies - this._getPageDependencies(moID, context) - .then(function (dependencies) { - // Inform the user about those dependencies and ask him confirmation - var confirmDef = $.Deferred(); - Dialog.safeConfirm(self, "", { - title: _t("Delete Page"), - $content: $(qweb.render('website.delete_page', {dependencies: dependencies})), - confirm_callback: confirmDef.resolve.bind(confirmDef), - cancel_callback: def.resolve.bind(self), - }); - return confirmDef; - }).then(function () { - // Delete the page if the user confirmed - return self._rpc({ - model: 'website', - method: 'delete_page', - args: [moID], - context: context, - }); - }).then(function () { - // Redirect to homepage as the page is now deleted - window.location.href = '/'; - }, def.reject.bind(def)); - - return def; - }, - /** * Asks the user which menu to edit if multiple menus exist on the page. * Then opens the menu edition dialog. * Then executes the given callback once the edition is saved, to finally @@ -419,58 +592,104 @@ var ContentMenu = websiteNavbarData.WebsiteNavbarActionWidget.extend({ return def; }, + _pageProperties: function () { + var self = this; + var moID = self._getMainObject().id; + var dialog = new PagePropertiesDialog(self,moID,{}).open(); + return dialog; + }, +}); + + +var PageManagement = Widget.extend({ + events: { + 'click a.js_page_properties': '_onPagePropertiesButtonClick', + 'click a.js_clone_page': '_onClonePageButtonClick', + 'click a.js_delete_page': '_onDeletePageButtonClick', + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + /** - * Asks the user for the new name of the page, then reloads to the new page - * with the correct name if saved. + * Retrieves the page dependencies for the given object id. * * @private - * @returns {Deferred} - * Unresolved if the page's name is changed and saved as the page - * will be reloaded. - * Resolved otherwise. + * @param {integer} moID + * @param {Object} context + * @returns {Deferred<Array>} */ - _renamePage: function () { - var def = $.Deferred(); + _getPageDependencies: function (moID, context) { + return this._rpc({ + model: 'website', + method: 'page_search_dependencies', + args: [moID], + context: context, + }); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + _onPagePropertiesButtonClick: function (ev) { + var moID = $(ev.currentTarget).data('id'); + var dialog = new PagePropertiesDialog(this,moID, {'fromPageManagement': true}).open(); + return dialog; + }, + _onClonePageButtonClick: function (ev) { + var pageId = $(ev.currentTarget).data('id'); + var context = weContext.get(); + this._rpc({ + model: 'website.page', + method: 'clone_page', + args: [pageId], + kwargs: { + context: context, + }, + }).then(function (path) { + window.location.href = path; + }); + }, + _onDeletePageButtonClick: function (ev) { + var pageId = $(ev.currentTarget).data('id'); var self = this; - var moID = self._getMainObject().id; var context = weContext.get(); + var def = $.Deferred(); // Search the page dependencies - this._getPageDependencies(moID, context).then(function (dependencies) { - // Ask the user for the new page name and inform him about the dependencies - var renameDef = $.Deferred(); - var $content = $(qweb.render('website.rename_page', {dependencies: dependencies})); - Dialog.confirm(self, "", { - title: _t("Rename This Page"), - $content: $content, - confirm_callback: function () { - var $newNameInput = $content.find('input#new_name'); - renameDef.resolve($newNameInput.val()); - }, - cancel_callback: def.resolve.bind(def), + this._getPageDependencies(pageId, context) + .then(function (dependencies) { + // Inform the user about those dependencies and ask him confirmation + var confirmDef = $.Deferred(); + Dialog.safeConfirm(self, "", { + title: _t("Delete Page"), + $content: $(qweb.render('website.delete_page', {dependencies: dependencies})), + confirm_callback: confirmDef.resolve.bind(confirmDef), + cancel_callback: def.resolve.bind(self), }); - return renameDef; - }).then(function (newName) { - // Rename the page with the user's choice + return confirmDef; + }).then(function () { + // Delete the page if the user confirmed return self._rpc({ - model: 'website', - method: 'rename_page', - args: [moID, newName], + model: 'website.page', + method: 'delete_page', + args: [pageId], context: context, }); - }).then(function (newTechnicalName) { - // Redirects to the new renamed page - window.location.href = ('/page/' + encodeURIComponent(newTechnicalName)); + }).then(function () { + window.location.reload(true); }, def.reject.bind(def)); - - return def; }, }); + websiteNavbarData.websiteNavbarRegistry.add(ContentMenu, '#content-menu'); +websiteRootData.websiteRootRegistry.add(PageManagement, '#edit_website_pages'); return { + PagePropertiesDialog: PagePropertiesDialog, ContentMenu: ContentMenu, EditMenuDialog: EditMenuDialog, MenuEntryDialog: MenuEntryDialog, diff --git a/addons/website/static/src/js/menu/seo.js b/addons/website/static/src/js/menu/seo.js index 77b4d0a76e1..2a85b49bd59 100644 --- a/addons/website/static/src/js/menu/seo.js +++ b/addons/website/static/src/js/menu/seo.js @@ -375,8 +375,10 @@ var SeoConfigurator = Dialog.extend({ self.updateTable(removed); }); this.keywordList.insertAfter(this.$('.table thead')); - this.disableUnsavableFields(); - this.renderPreview(); + this.disableUnsavableFields().then(function(){ + self.renderPreview(); + }); + this.getLanguages(); this.updateTable(); }, @@ -396,7 +398,9 @@ var SeoConfigurator = Dialog.extend({ }, disableUnsavableFields: function () { var self = this; - this.loadMetaData().then(function (data) { + return this.loadMetaData().then(function (data) { + //If website.page, hide the google preview & tell user his page is currently unindexed + self.isIndexed = (data && ('website_indexed' in data)) ? data.website_indexed : true; self.canEditTitle = data && ('website_meta_title' in data); self.canEditDescription = data && ('website_meta_description' in data); self.canEditKeywords = data && ('website_meta_keywords' in data); @@ -474,6 +478,9 @@ var SeoConfigurator = Dialog.extend({ def.resolve(null); } else { var fields = ['website_meta_title', 'website_meta_description', 'website_meta_keywords']; + if (obj.model == 'website.page'){ + fields.push('website_indexed'); + } rpc.query({ model: obj.model, method: 'read', @@ -521,11 +528,20 @@ var SeoConfigurator = Dialog.extend({ }); }, renderPreview: function () { - var preview = new Preview(this, { - title: this.htmlPage.title(), - description: this.htmlPage.description(), - url: this.htmlPage.url(), - }); + var indexed = this.isIndexed; + var preview = ""; + if(indexed){ + preview = new Preview(this, { + title: this.htmlPage.title(), + description: this.htmlPage.description(), + url: this.htmlPage.url(), + }); + } + else{ + preview = new Preview(this, { + description: _("You have hidden this page from search results. It won't be indexed by search engine"), + }); + } var $preview = this.$('.js_seo_preview'); $preview.empty(); preview.appendTo($preview); diff --git a/addons/website/static/src/js/tours/rte.js b/addons/website/static/src/js/tours/rte.js index 35e7ad55bc4..c0119aec2a4 100644 --- a/addons/website/static/src/js/tours/rte.js +++ b/addons/website/static/src/js/tours/rte.js @@ -65,7 +65,7 @@ tour.register('rte_translator', { trigger: 'html:not(:has(#wrap p span)) .o_menu_systray a[data-action="translate"]', }, { content: "close modal", - trigger: '.modal-footer .btn-primary', + trigger: '.modal-footer .btn-default', }, { content: "check if translation is activate", trigger: '[data-oe-translation-id]', @@ -85,7 +85,7 @@ tour.register('rte_translator', { }, }, { content: "click on input", - trigger: 'input:first', + trigger: '#wrap input:first', extra_trigger: '#wrap .o_dirty font:first:contains(translated french text)', run: 'click', }, { diff --git a/addons/website/static/src/js/utils.js b/addons/website/static/src/js/utils.js index 40a4033540d..e7f76376480 100644 --- a/addons/website/static/src/js/utils.js +++ b/addons/website/static/src/js/utils.js @@ -7,7 +7,7 @@ var weContext = require('web_editor.context'); var qweb = core.qweb; /** - * Allows the given input to propose existing website pages. + * Allows the given input to propose existing website URLs. * * @param {ServicesMixin|Widget} self - an element capable to trigger an RPC * @param {jQuery} $input @@ -25,7 +25,12 @@ function autocompleteWithPages(self, $input) { }, }).then(function (exists) { var rs = _.map(exists, function (r) { - return r.loc; + if (show_only_website_page_model) { + return r.loc + ' (' + r.name + ')'; + } + else { + return r.loc; + } }); response(rs); }); diff --git a/addons/website/static/src/less/website.ui.components.less b/addons/website/static/src/less/website.ui.components.less index 409d72ba366..c1381f09e99 100644 --- a/addons/website/static/src/less/website.ui.components.less +++ b/addons/website/static/src/less/website.ui.components.less @@ -451,3 +451,31 @@ body .modal { content: initial; } } + +// Pages Management +.o_page_management_info .o_switch{ + padding-top:7px; +} +#edit_website_pages{ + .fa-check{ + color: #00a09d; + } + .fa-eye-slash{ + color: #00a09d; + } + th{ + background-color: #875A7B; + color: white; + opacity:0.8; + } + td{ + a.fa{ + color:#875A7B; + opacity:0.8; + margin-left:5px; + } + .text-muted{ + opacity:0.5; + } + } +}
\ No newline at end of file diff --git a/addons/website/static/src/xml/website.contentMenu.xml b/addons/website/static/src/xml/website.contentMenu.xml index 4d3ba998ef0..a5b7590ba53 100644 --- a/addons/website/static/src/xml/website.contentMenu.xml +++ b/addons/website/static/src/xml/website.contentMenu.xml @@ -8,7 +8,7 @@ <span class="js_menu_label"> <t t-esc="submenu.name"/> </span> - <i class="pull-right fa fa-home" style="margin-top: 3px"/> + <i t-if="submenu.is_homepage" class="pull-right fa fa-home" style="margin-top: 3px"/> </span> <span class="input-group-btn"> <button type="button" class="btn btn-primary js_edit_menu fa fa-pencil-square-o"/> @@ -54,8 +54,12 @@ <div class="form-group"> <label class="control-label col-sm-3">Link</label> <div class="col-sm-9"> - <input type="radio" name="link_menu_options" id="url" value="url" checked="checked"/> <label class="control-label mr16" for="url" >URL or Email</label> - <input type="radio" name="link_menu_options" id="new_page" value="new_page"/> <label class="control-label" for="new_page">New Page</label> + <div class="radio"> + <label class="checkbox-inline" for="new_page"><input type="radio" name="link_menu_options" id="new_page" value="new_page" checked="checked"/>New Page</label> + </div> + <div class="radio"> + <label class="checkbox-inline" for="url"><input type="radio" name="link_menu_options" id="url" value="url" />URL or Email</label> + </div> </div> </div> </t> diff --git a/addons/website/static/src/xml/website.pageProperties.xml b/addons/website/static/src/xml/website.pageProperties.xml new file mode 100644 index 00000000000..f21b0213f4e --- /dev/null +++ b/addons/website/static/src/xml/website.pageProperties.xml @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates id="template" xml:space="preserve"> +<div t-name="website.pagesMenu.page_info" class="o_page_management_info"> + <form class="form-horizontal"> + <ul class="nav nav-tabs"> + <li class="active"><a data-toggle="tab" href="#basic_page_info">Name</a></li> + <li><a data-toggle="tab" href="#advances_page_info">Publish</a></li> + </ul> + <div class="tab-content mt16"> + <div id="basic_page_info" class="tab-pane fade in active"> + <div class="form-group"> + <label class="control-label col-sm-3" for="page_name">Page Name</label> + <div class="col-sm-9"> + <input type="text" class="form-control input-sm" id="page_name" t-att-value="widget.page.name" /> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-3" for="page_url">Page Url</label> + <div class="col-sm-9"> + <div class="input-group"> + <span class="input-group-addon" t-att-title="widget.serverUrl"><small><t t-esc="widget.serverUrlTrunc" /></small></span> + <input type="text" class="form-control input-sm" id="page_url" t-att-value="widget.page.url" /> + </div> + </div> + </div> + <div class="form-group ask_for_redirect"> + <label class="control-label col-sm-3" for="is_homepage">Redirect old url</label> + <div class="col-sm-2"> + <a> + <label class="o_switch" for="create_redirect" > + <input type="checkbox" id="create_redirect"/> + <span/> + </label> + </a> + </div> + <div class="col-sm-7 mt4"> + <span class="text-muted" id="dependencies_redirect"></span> + </div> + </div> + <div class="form-group ask_for_redirect"> + <label class="control-label col-sm-3 redirect_type" for="redirect_type">Type</label> + <div class="col-sm-6 redirect_type"> + <select class="form-control" id="redirect_type"> + <option t-foreach="widget.fields.type.selection" t-as="field" t-att-value="field[0]"><t t-esc="field[0] + ': ' + field[1]" /></option> + </select> + </div> + </div> + </div> + <div id="advances_page_info" class="tab-pane fade"> + <div class="form-group"> + <label class="control-label col-sm-3" for="is_menu">Show in top menu</label> + <div class="col-sm-2"> + <a> + <label class="o_switch" for="is_menu" > + <input type="checkbox" t-att-checked="widget.page.menu_ids.length > 0 ? true : undefined" id="is_menu"/> + <span/> + </label> + </a> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-3" for="is_homepage">Use as homepage</label> + <div class="col-sm-2"> + <a> + <label class="o_switch" for="is_homepage" > + <input type="checkbox" t-att-checked="widget.page.is_homepage ? true : undefined" id="is_homepage"/> + <span/> + </label> + </a> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-3" for="is_indexed"> + Indexed + <i class="fa fa-question-circle-o" title="Hide this page from search results" aria-hidden="true"></i> + </label> + <div class="col-sm-2"> + <a> + <label class="o_switch" for="is_indexed" > + <input type="checkbox" t-att-checked="widget.page.website_indexed ? true : undefined" id="is_indexed"/> + <span/> + </label> + </a> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-3" for="is_published">Publish</label> + <div class="col-sm-2"> + <a> + <label class="o_switch js_publish_btn" for="is_published" > + <input type="checkbox" t-att-checked="widget.page.website_published ? true : undefined" id="is_published"/> + <span/> + </label> + </a> + </div> + </div> + <div class="form-group"> + <label class="control-label col-sm-3" for="date_publish">Publishing Date</label> + <div class="col-sm-9"> + <div class='input-group date' id='date_publish_container'> + <input type='text' class="form-control" name="date_publish" id="date_publish" /> + <span class="input-group-addon"> + <span class="fa fa-calendar"></span> + </span> + </div> + </div> + </div> + </div> + </div> + </form> +</div> +</templates> diff --git a/addons/website/static/src/xml/website.seo.xml b/addons/website/static/src/xml/website.seo.xml index a3018720515..7afd4ad32e9 100644 --- a/addons/website/static/src/xml/website.seo.xml +++ b/addons/website/static/src/xml/website.seo.xml @@ -11,7 +11,7 @@ <div class="col-md-6"> <div class="form-horizontal mt16" role="form"> <div class="form-group"> - <label for="seo_page_title" class="col-xs-3 control-label">Page title</label> + <label for="seo_page_title" class="col-xs-3 control-label">Page Title</label> <div class="col-xs-9"> <input type="text" name="seo_page_title" class="form-control" maxlength="70" size="70"/> </div> diff --git a/addons/website/views/res_config_settings_views.xml b/addons/website/views/res_config_settings_views.xml index 46437b86235..344974f07a1 100644 --- a/addons/website/views/res_config_settings_views.xml +++ b/addons/website/views/res_config_settings_views.xml @@ -110,7 +110,7 @@ <div class="o_setting_right_pane"> <label for="has_google_maps"/> <div class="text-muted"> - Show your address on a map in the <a href="/page/contactus">Contact Us</a> page + Show your address on a map in the <a href="/contactus">Contact Us</a> page </div> <div class="content-group" attrs="{'invisible': [('has_google_maps', '=', False)]}"> <div class="row mt16"> diff --git a/addons/website/views/snippets.xml b/addons/website/views/snippets.xml index 2d267a4a61c..754895e6661 100644 --- a/addons/website/views/snippets.xml +++ b/addons/website/views/snippets.xml @@ -27,7 +27,7 @@ <div class="carousel-content col-md-4 col-md-offset-1 mt128 mb96"> <h2>Your Slide Title</h2> <h4>Click to customize this text</h4> - <p><a href="/page/website.contactus" class="btn btn-warning btn-large">Contact us</a></p> + <p><a href="/contactus" class="btn btn-warning btn-large">Contact us</a></p> </div> <div class="carousel-img col-md-6 col-md-offset-1 hidden-sm hidden-xs mt48"> <img class="img-responsive" src="/web/image/website.s_banner_default_image" alt="Slider Odoo Image"/> @@ -94,7 +94,7 @@ <div class="mb32 col-md-offset-1 col-md-8"> <h1>Sell Online. Easily.</h1> <p>Write one sentence to convince visitor about your message.</p> - <a class="btn btn-warning btn-lg" href="/page/website.contactus">Contact us</a> + <a class="btn btn-warning btn-lg" href="/contactus">Contact us</a> </div> </div> </div> @@ -185,7 +185,7 @@ reinforce your image. </p> <p class="text-center"> - <a href="/page/website.contactus">Contact us »</a> + <a href="/contactus">Contact us »</a> </p> </div> </div> @@ -312,7 +312,7 @@ <p class="text-muted"> <i>Instant setup, satisfied or reimbursed.</i> </p> - <a href="/page/website.contactus" class="btn btn-primary btn-lg">Order now</a> + <a href="/contactus" class="btn btn-primary btn-lg">Order now</a> </div> </div> </div> @@ -334,7 +334,7 @@ </ul> <div class="panel-footer text-center"> <p class="text-muted"><i>Instant setup, satisfied or reimbursed.</i></p> - <a href="/page/website.contactus" class="btn btn-primary btn-lg">Order now</a> + <a href="/contactus" class="btn btn-primary btn-lg">Order now</a> </div> </div> </div> @@ -356,7 +356,7 @@ </ul> <div class="panel-footer text-center"> <p class="text-muted"><i>Instant setup, satisfied or reimbursed.</i></p> - <a href="/page/website.contactus" class="btn btn-primary btn-lg">Contact us</a> + <a href="/contactus" class="btn btn-primary btn-lg">Contact us</a> </div> </div> </div> @@ -449,7 +449,7 @@ </p> </div> <div class="col-md-3"> - <a href="/page/website.contactus" class="btn btn-primary btn-lg pull-right mt8"> + <a href="/contactus" class="btn btn-primary btn-lg pull-right mt8"> <span>Contact Us Now</span> <i class="fa fa-chevron-right"></i> </a> diff --git a/addons/website/views/website_navbar_templates.xml b/addons/website/views/website_navbar_templates.xml index 7bd6dceec09..95acebf9f06 100644 --- a/addons/website/views/website_navbar_templates.xml +++ b/addons/website/views/website_navbar_templates.xml @@ -33,12 +33,10 @@ <li t-if="editable" class="dropdown" id="content-menu"> <a id="content-menu-button" class="dropdown-toggle waves" data-toggle="dropdown" href="#">Content</a> <ul class="dropdown-menu" role="menu"> - <li> - <a data-action="edit_menu" href="#" title="Edit Top Menu">Edit Menu</a> - </li> + <li><a data-action="edit_menu" href="#" title="Edit Top Menu">Edit Menu</a></li> + <li><a href="/website/pages" title="Manage Your Website Pages">Manage Pages</a></li> <li t-if="deletable" role="separator" class="divider"></li> - <li t-if="deletable"><a href="#" data-action="rename_page">Rename Page</a></li> - <li t-if="deletable"><a href="#" data-action="delete_page">Delete Page</a></li> + <li t-if="deletable"><a href="#" data-action="page_properties">Page Properties</a></li> </ul> </li> <!-- Customize --> @@ -61,6 +59,16 @@ </ul> <ul class="o_menu_systray hidden-xs" groups="website.group_website_publisher"> + <li t-if="'website_published' in main_object.fields_get()" t-attf-class="js_publish_management #{main_object.website_published and 'css_published' or 'css_unpublished'}" t-att-data-id="main_object.id" t-att-data-object="main_object._name" t-att-data-controller="publish_controller"> + <a> + <label class="o_switch js_publish_btn" for="id" > + <input type="checkbox" t-att-checked="main_object.website_published" id="id"/> + <span/> + <span class="css_publish">Unpublished</span> + <span class="css_unpublish">Published</span> + </label> + </a> + </li> <!-- Mobile preview --> <li class="o_mobile_preview" id="mobile-menu"> <a data-action="show-mobile-preview" href="#"><span title="Mobile preview" class="fa fa-mobile"/></a> @@ -77,6 +85,18 @@ <li t-if="not translatable" id="edit-page-menu"> <a data-action="edit" href="#"><span class="fa fa-pencil"/>Edit</a> </li> + <li t-if="'website_published' in main_object.fields_get()"> + <a class="btn btn-primary btn-xs dropdown-toggle css_edit_dynamic" data-toggle="dropdown"> + <span class="caret"></span> + <span class="sr-only">Toggle Dropdown</span> + </a> + <ul class="dropdown-menu" role="menu"> + <li id="edit-in-backend"> + <a style="text-align: left;" t-attf-href="/web#return_label=Website&view_type=form&model=#{main_object._name}&id=#{main_object.id}&action=#{action}" + class="btn btn-link btn-xs" title='Edit in backend'>Edit in backend</a> + </li> + </ul> + </li> <li t-if="translatable"> <a data-action="translate" href="#">TRANSLATE</a> </li> diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index b69d59a41a9..1b2aab0d4b6 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -68,15 +68,15 @@ <!-- Layout --> <template id="submenu" name="Submenu"> <li t-if="not submenu.child_id" t-att-class=" - 'active' if submenu.url and submenu.url != '/' and unslug_url(request.httprequest.path) == unslug_url(submenu.url.replace('/page/website.', '/page/')) else None + 'active' if submenu.clean_url() and unslug_url(request.httprequest.path) == unslug_url(submenu.clean_url()) else None "> - <a t-att-href="(website.menu_id.child_id[0] == submenu) and '/' or submenu.url" t-ignore="true" t-att-target="'_blank' if submenu.new_window else None"> + <a t-att-href="submenu.clean_url()" t-ignore="true" t-att-target="'_blank' if submenu.new_window else None"> <span t-field="submenu.name"/> </a> </li> <li t-if="submenu.child_id" t-attf-class="dropdown #{ - (submenu.url and submenu.url != '/' and any([request.httprequest.path == child.url.replace('/page/website.', '/page/') for child in submenu.child_id if child.url]) or - (submenu.url and request.httprequest.path == submenu.url.replace('/page/website.', '/page/'))) and 'active' + (submenu.clean_url() and submenu.clean_url() != '/' and any([request.httprequest.path == child.url for child in submenu.child_id if child.url]) or + (submenu.clean_url() and request.httprequest.path == submenu.clean_url())) and 'active' }"> <a class="dropdown-toggle" data-toggle="dropdown" href="#"> <span t-field="submenu.name"/> <span class="caret" t-ignore="true"></span> @@ -299,7 +299,7 @@ <div class="col-md-4 col-lg-3" id="info"> <h4>Connect with us</h4> <ul class="list-unstyled"> - <li><a href="/page/website.contactus">Contact us</a></li> + <li><a href="/contactus">Contact us</a></li> </ul> <ul class="list-unstyled"> <li t-ignore="true"><i class="fa fa-phone"></i> <span t-field="res_company.phone"></span></li> @@ -317,7 +317,7 @@ <div class="col-md-4 col-lg-5 col-lg-offset-1"> <h4> <span t-field="res_company.name" /> - <small> - <a href="/page/website.aboutus">About us</a></small> + <small> - <a href="/aboutus">About us</a></small> </h4> <div> <p> @@ -628,7 +628,7 @@ <p>Maybe you were looking for one of these popular pages ?</p> <ul> <li><a href="/">Homepage</a></li> - <li><a href="/page/website.contactus/">Contact Us</a></li> + <li><a href="/contactus">Contact Us</a></li> </ul> </div> <t t-if="editable or request.debug"> @@ -653,7 +653,7 @@ <p>Maybe you were looking for one of these popular pages ?</p> <ul> <li><a href="/">Homepage</a></li> - <li><a href="/page/website.contactus/">Contact Us</a></li> + <li><a href="/contactus">Contact Us</a></li> </ul> </div> </div> @@ -801,14 +801,6 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml </sitemapindex> </template> -<!-- Actual pages --> -<template id="homepage" name="Homepage" page="True" priority="29"> - <t t-call="website.layout"> - <t t-set="pageName" t-value="'homepage'"/> - <div id="wrap" class="oe_structure oe_empty"/> - </t> -</template> - <template id="company_description" name="Company Description"> <address itemscope="itemscope" itemtype="http://schema.org/Organization"> <!-- TODO widget contact must add itemprop attributes --> @@ -826,77 +818,6 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml </t> </template> -<template id="contactus" name="Contact us" page="True"> - <t t-call="website.layout"> - <div id="wrap"> - <div class="oe_structure"/> - <div class="container"> - <h1>Contact us</h1> - <div class="row"> - <div class="col-md-8"> - <div class="oe_structure"> - <div> - <p>Contact us about anything related to our company or services.</p> - <p>We'll do our best to get back to you as soon as possible.</p> - </div> - </div> - <div class="text-center mt64" name="mail_button"> - <a t-attf-href="mailto:{{ res_company.email }}" class="btn btn-primary" id="o_contact_mail">Send us an email</a> - </div> - </div> - <div class="col-md-4 mb32"> - <t t-call="website.company_description"/> - </div> - </div> - </div> - <div class="oe_structure"/> - </div> - </t> -</template> - -<template id="aboutus" name="About us" page="True"> - <t t-call="website.layout"> - <div id="wrap"> - <div class="oe_structure"> - - <section> - <div class="container"> - <div class="row"> - <div class="col-md-12"> - <h1 class="text-center">About us</h1> - <h3 class="text-muted text-center">Great products for great people</h3> - </div> - </div> - </div> - </section> - - <section> - <div class="container"> - <div class="row"> - <div class="col-md-6 mt32"> - <p> - We are a team of passionate people whose goal is to improve everyone's - life through disruptive products. We build great products to solve your - business problems. - </p> - <p> - Our products are designed for small to medium size companies willing to optimize - their performance. - </p> - </div> - <div class="col-md-4 col-md-offset-2 mt16 mb16"> - <img src="/website/static/src/img/library/business_conference.jpg" class="img img-responsive shadow" alt="Our Team"/> - </div> - </div> - </div> - </section> - - </div> - <div class="oe_structure"></div> - </div> - </t> -</template> - <template id="website_search_box" name="Website Searchbox"> <div class="input-group"> <div class="oe_search"> @@ -907,4 +828,81 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml </span> </div> </template> + +<template id="index_management"> + <t groups="website.group_website_publisher" t-ignore="true"> + <div t-attf-class="pull-right js_index_management #{object.website_indexed and 'css_published' or 'css_unpublished'}" t-att-data-id="object.id" t-att-data-object="object._name"> + <button class="btn btn-danger js_index_btn">Unindexed</button> + <button class="btn btn-success js_index_btn">Indexed</button> + </div> + </t> +</template> + +<template id="edit_website_pages" name="Website Pages Management"> + <t t-call="website.layout"> + <div id="wrap"> + <div class="container" id="edit_website_pages"> + <form class="mt8 pull-right" role="search" t-attf-action="/website/pages_management" method="get"> + <t t-call="website.website_search_box"/> + </form> + <div t-if="searchbar_sortings" class="dropdown pull-right mt8 mr8"> + <button class="btn btn-default" type="button" data-toggle="dropdown"> + <span class="fa fa-sort fa-lg" /> + <span class='hidden-xs hidden-sm hidden-md' t-esc="searchbar_sortings[sortby].get('label', 'Newest')"/> + <span class="caret"></span> + </button> + <ul class="dropdown-menu" aria-labelledby="portal_searchbar_sortby"> + <li t-foreach="searchbar_sortings" t-as="option" t-attf-class="#{sortby == option and 'active'}"> + <a t-att-href="request.httprequest.path + '?' + keep_query('*', sortby=option)"> + <span t-esc="searchbar_sortings[option].get('label')"/> + </a> + </li> + </ul> + </div> + <h3 class="mt16">Pages Management</h3> + <t t-if="not pages"> + <div t-if="search" class="alert alert-warning mt8" role="alert"> + Your search '<t t-esc="search" />' did not match any pages. + </div> + <div t-else="" class="alert alert-warning mt8" role="alert"> + There are currently no pages for your website. + </div> + </t> + <div t-if="pages" class="table-responsive"> + <table class="table table-hover o_my_status_table"> + <thead> + <tr class="active"> + <th>Name</th> + <th>Url</th> + <th class="col-md-1 text-center"><i title="Is the page included in the main menu?" class="fa fa-thumb-tack" aria-hidden="true"></i></th> + <th class="col-md-1 text-center"><i title="Is the page published?" class="fa fa-eye" aria-hidden="true"></i></th> + <th class="col-md-1 text-center"><i title="Is the page indexed by search engines?" class="fa fa-globe" aria-hidden="true"></i></th> + <th class="col-md-1"></th> + </tr> + </thead> + <t t-foreach="pages" t-as="page"> + <tr> + <td><i t-if="page.is_homepage" class="fa fa-home"></i> <span style="word-break: break-all;" t-esc="page.name"/></td> + <td><a style="word-break: break-all;" t-attf-href="{{page.url}}"><t t-esc="page.url"/></a></td> + <td style="text-align:center"><i t-att-class="'fa fa-check' if page.menu_ids else 'fa fa-times text-muted'" aria-hidden="true"></i></td> + <t t-set='date_formatted'><t t-options='{"widget": "date"}' t-esc="page.date_publish"/></t> + <td style="text-align:center"><i t-att-title="not page.is_visible and page.website_published and 'This page will be visible on ' + date_formatted" t-att-class="'fa fa-check' if page.is_visible and page.website_published else 'fa fa-eye-slash' if not page.is_visible and page.website_published else 'fa fa-times text-muted'" aria-hidden="true"></i></td> + <td style="text-align:center"><i t-att-class="'fa fa-check' if page.website_indexed else 'fa fa-times text-muted'" aria-hidden="true"></i></td> + <td> + <a class="fa fa-cog js_page_properties" t-att-data-id="page.id" href="#" title="Manage this page"></a> + <a class="fa fa-clone js_clone_page" t-att-data-id="page.id" href="#" title="Clone this page"></a> + <a class="fa fa-trash js_delete_page" t-att-data-id="page.id" href="#" title="Delete this page"></a> + </td> + </tr> + </t> + </table> + </div> + <div t-if="pager" class="o_portal_pager text-center"> + <t t-call="website.pager"/> + </div> + </div> + </div> + </t> +</template> + </odoo> diff --git a/addons/website/views/website_views.xml b/addons/website/views/website_views.xml index 1b9b4c87fa5..ea4b5e83ef1 100644 --- a/addons/website/views/website_views.xml +++ b/addons/website/views/website_views.xml @@ -61,6 +61,117 @@ </field> </record> + <!-- website.redirect views --> + <record id="website_redirect_form_view" model="ir.ui.view"> + <field name="name">website.redirect.form</field> + <field name="model">website.redirect</field> + <field name="arch" type="xml"> + <form string="Website Redirect Settings"> + <sheet> + <group> + <group> + <field name="type"/> + <field name="url_from"/> + <field name="url_to"/> + </group> + <group> + <field name="website_id"/> + <field name="active"/> + <field name="sequence"/> + </group> + </group> + </sheet> + </form> + </field> + </record> + + <record id="website_redirect_tree_view" model="ir.ui.view"> + <field name="name">website.redirect.list</field> + <field name="model">website.redirect</field> + <field name="arch" type="xml"> + <tree string="Website Redirects"> + <field name="type"/> + <field name="url_from"/> + <field name="url_to"/> + <field name="website_id"/> + <field name="active"/> + <field name="sequence"/> + </tree> + </field> + </record> + + <record id="action_website_redirect_list" model="ir.actions.act_window"> + <field name="name">Website Redirects</field> + <field name="res_model">website.redirect</field> + <field name="view_type">form</field> + <field name="view_mode">tree,form</field> + <field name="view_id" ref="website_redirect_tree_view"/> + <field name="target">current</field> + </record> + + <menuitem name="Redirects" + id="menu_website_redirect_list" + action="action_website_redirect_list" + parent="website.menu_website_configuration" + groups="base.group_system"/> + + <!-- website.page views --> + <record id="website_pages_form_view" model="ir.ui.view"> + <field name="name">website.page.form</field> + <field name="model">website.page</field> + <field name="arch" type="xml"> + <form string="Website Page Settings"> + <sheet> + <group> + <group> + <field name="name"/> + <field name="url"/> + <field name="ir_ui_view_id"/> + </group> + <group> + <field name="website_indexed"/> + <field name="website_published"/> + <field name="date_publish"/> + </group> + </group> + <field name="website_ids"/> + <field name="menu_ids"/> + </sheet> + </form> + </field> + </record> + + <record id="website_pages_tree_view" model="ir.ui.view"> + <field name="name">website.page.list</field> + <field name="model">website.page</field> + <field name="arch" type="xml"> + <tree string="Website Pages"> + <field name="name"/> + <field name="url"/> + <field name="website_indexed"/> + <field name="website_published"/> + <field name="create_uid" invisible="1"/> + <field name="write_uid"/> + <field name="write_date"/> + </tree> + </field> + </record> + + <record id="action_website_pages_list" model="ir.actions.act_window"> + <field name="name">Website Pages</field> + <field name="res_model">website.page</field> + <field name="view_type">form</field> + <field name="view_mode">tree,form</field> + <field name="view_id" ref="website_pages_tree_view"/> + <field name="target">current</field> + </record> + + <menuitem name="Pages" + id="menu_website_pages_list" + action="action_website_pages_list" + parent="website.menu_website_configuration" + groups="base.group_system"/> + <!-- website.menu views --> <record id="action_website_menu" model="ir.actions.act_window"> <field name="name">Website Menu</field> diff --git a/addons/website_blog/models/website.py b/addons/website_blog/models/website.py index 3169070ea78..46007177654 100644 --- a/addons/website_blog/models/website.py +++ b/addons/website_blog/models/website.py @@ -8,15 +8,14 @@ class Website(models.Model): _inherit = "website" @api.model - def page_search_dependencies(self, view_id): - dep = super(Website, self).page_search_dependencies(view_id) + def page_search_dependencies(self, page_id=False): + dep = super(Website, self).page_search_dependencies(page_id=page_id) - view = self.env['ir.ui.view'].browse(view_id) - name = view.key.replace("website.", "") - fullname = "website.%s" % name + page = self.env['website.page'].browse(int(page_id)) + path = page.url dom = [ - '|', ('content', 'ilike', '/page/%s' % name), ('content', 'ilike', '/page/%s' % fullname) + ('content', 'ilike', path) ] posts = self.env['blog.post'].search(dom) if posts: diff --git a/addons/website_blog/static/src/js/website.tour.blog.js b/addons/website_blog/static/src/js/website.tour.blog.js index 1f0ccdb8e2d..dface1116c3 100644 --- a/addons/website_blog/static/src/js/website.tour.blog.js +++ b/addons/website_blog/static/src/js/website.tour.blog.js @@ -63,13 +63,13 @@ odoo.define("website_blog.tour", function (require) { content: _t("Once you have reviewed the content on mobile, close the preview."), position: "right", }, { - trigger: "button.btn-danger.js_publish_btn", + trigger: ".js_publish_management .js_publish_btn", extra_trigger: "body:not(.editor_enable)", position: "bottom", content: _t("<b>Publish your blog post</b> to make it visible to your visitors."), }, { trigger: "#customize-menu > a", - extra_trigger: "body:not(.editor_enable) .js_publish_management button.js_publish_btn.btn-success:visible", + extra_trigger: ".js_publish_management .js_publish_btn .css_unpublish:visible", content: _t("<b>That's it, your blog post is published!</b> Discover more features through the <i>Customize</i> menu."), position: "bottom", width: 500, diff --git a/addons/website_blog/views/website_blog_templates.xml b/addons/website_blog/views/website_blog_templates.xml index 534dea06da3..a9faa17b3ea 100644 --- a/addons/website_blog/views/website_blog_templates.xml +++ b/addons/website_blog/views/website_blog_templates.xml @@ -217,22 +217,20 @@ </template> <!-- Blog Post Complete --> +<template id="blog_edit_options" inherit_id="website.user_navbar" name="Edit Blog Options"> + <xpath expr="//li[@id='edit-in-backend']" position="after"> + <li groups="website.group_website_designer" t-if="main_object._name == 'blog.post'"> + <form class="duplicate hidden" action="/blog/post_duplicate" method="POST"> + <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/> + <input name="blog_post_id" t-att-value="blog_post.id if blog_post else None"/> + </form> + <a href="#" class="duplicate" onclick="$(this).prev('form').submit()">Duplicate</a> + </li> + </xpath> +</template> <template id="blog_post_complete" name="Blog Post"> <t t-call="website_blog.index"> <div id="title" class="blog_header" t-ignore="True"> - <div class="container" style="position: relative;"> - <t t-call="website.publish_management"> - <t t-set="object" t-value="blog_post"/> - <t t-set="publish_edit" t-value="True"/> - <li> - <form class="duplicate hidden" action="/blog/post_duplicate" method="POST"> - <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/> - <input name="blog_post_id" t-att-value="blog_post.id"/> - </form> - <a href="#" class="duplicate" onclick="$(this).prev('form').submit()">Duplicate</a> - </li> - </t> - </div> <t t-call="website_blog.blog_cover"> <t t-set="cover_properties" t-value="blog_post_cover_properties"/> diff --git a/addons/website_crm/data/website_crm_data.xml b/addons/website_crm/data/website_crm_data.xml index 8104f803ca1..c4672a1a267 100644 --- a/addons/website_crm/data/website_crm_data.xml +++ b/addons/website_crm/data/website_crm_data.xml @@ -22,7 +22,7 @@ <record id="action_open_website" model="ir.actions.act_url"> <field name="name">Website Contact Form</field> <field name="target">self</field> - <field name="url">/page/contactus</field> + <field name="url">/contactus</field> </record> <record id="base.open_menu" model="ir.actions.todo"> diff --git a/addons/website_crm/static/src/js/website_crm_tour.js b/addons/website_crm/static/src/js/website_crm_tour.js index e035111df0d..9d72e2a46b3 100644 --- a/addons/website_crm/static/src/js/website_crm_tour.js +++ b/addons/website_crm/static/src/js/website_crm_tour.js @@ -6,7 +6,7 @@ odoo.define('website_crm.tour', function(require) { tour.register('website_crm_tour', { test: true, - url: '/page/contactus', + url: '/contactus', wait_for: base.ready(), }, [{ content: "Complete name", diff --git a/addons/website_crm/views/res_config_settings_views.xml b/addons/website_crm/views/res_config_settings_views.xml index fd0c6da4fc1..2e91c4d2844 100644 --- a/addons/website_crm/views/res_config_settings_views.xml +++ b/addons/website_crm/views/res_config_settings_views.xml @@ -12,7 +12,7 @@ <div class="o_setting_right_pane"> <label for="id" string="Contact Form"/> <div class="text-muted" id="msg_contact_form_install_setting"> - Add a contact form in the <a href="/page/website.contactus">Contact Us</a> page + Add a contact form in the <a href="/contactus">Contact Us</a> page </div> <div class="row"> <label class="col-md-3 o_light_label" string="Sales Channel" for="crm_default_team_id"/> diff --git a/addons/website_crm/views/website_crm_templates.xml b/addons/website_crm/views/website_crm_templates.xml index f3c657ed3a3..f8e79eccee3 100644 --- a/addons/website_crm/views/website_crm_templates.xml +++ b/addons/website_crm/views/website_crm_templates.xml @@ -3,7 +3,7 @@ <template id="contactus_form" name="Contact Form" inherit_id="website.contactus" customize_show="True"> <xpath expr="//div[@name='mail_button']" position="replace"> <div> - <form action="/website_form/" method="post" data-model_name="crm.lead" data-success_page="/page/website_crm.contactus_thanks" class="s_website_form form-horizontal container-fluid mt32" enctype="multipart/form-data" data-editable-form="false"> + <form action="/website_form/" method="post" data-model_name="crm.lead" data-success_page="/contactus-thank-you" class="s_website_form form-horizontal container-fluid mt32" enctype="multipart/form-data" data-editable-form="false"> <div class="form-group form-field o_website_form_required_custom"> <label class="col-md-3 col-sm-4 control-label" for="contact_name">Your Name</label> <div class="col-md-7 col-sm-8"> @@ -50,38 +50,50 @@ </div> </xpath> </template> - - <template id="contactus_thanks" name="Contact us"> - <t t-call="website.layout"> - <div id="wrap"> - <div class="oe_structure"/> - <div class="container"> - <h1>Thanks!</h1> - <div class="row"> - <div class="col-md-8"> - <div class="alert alert-success"> - Your message has been sent successfully. - <button type="button" class="close" data-dismiss="alert">&times;</button> + <record id="contactus_thanks" model="ir.ui.view"> + <field name="name">Thanks (Contact us)</field> + <field name="type">qweb</field> + <field name="key">website_crm.contactus_thanks</field> + <field name="arch" type="xml"> + <t name="Thanks (Contact us)" t-name="website_crm.contactus_thanks"> + <t t-call="website.layout"> + <div id="wrap"> + <div class="oe_structure"/> + <div class="container"> + <h1>Thanks!</h1> + <div class="row"> + <div class="col-md-8"> + <div class="alert alert-success"> + Your message has been sent successfully. + <button type="button" class="close" data-dismiss="alert">&times;</button> + </div> + <p> + We will get back to you shortly. + </p><p class="mt64"> + If you have an emergency, do not hesitate to contact us by phone: + </p> + <ul class="list-unstyled"> + <li><i class="fa fa-phone"></i> : <span t-field="res_company.phone"/></li> + <li><i class="fa fa-envelope"></i> : <span t-field="res_company.email"/></li> + </ul> + </div> + <div class="col-md-4"> + <t t-call="website.company_description"/> </div> - <p> - We will get back to you shortly. - </p><p class="mt64"> - If you have an emergency, do not hesitate to contact us by phone: - </p> - <ul class="list-unstyled"> - <li><i class="fa fa-phone"></i> : <span t-field="res_company.phone"/></li> - <li><i class="fa fa-envelope"></i> : <span t-field="res_company.email"/></li> - </ul> - </div> - <div class="col-md-4"> - <t t-call="website.company_description"/> </div> </div> - </div> - <div class="oe_structure"/> - </div> + <div class="oe_structure"/> + </div> + </t> </t> - </template> + </field> + </record> + <record id="contactus_thanks_page" model="website.page"> + <field name="name">Thanks (Contact us)</field> + <field name="url">/contactus-thank-you</field> + <field name="website_published">True</field> + <field name="ir_ui_view_id" ref="contactus_thanks" /> + </record> <template id="website_crm_tests" name="Website CRM Tests" inherit_id="web.assets_common"> <xpath expr="." position="inside"> diff --git a/addons/website_event/models/event.py b/addons/website_event/models/event.py index a5e70798b8c..14f5aa31710 100644 --- a/addons/website_event/models/event.py +++ b/addons/website_event/models/event.py @@ -85,8 +85,8 @@ class Event(models.Model): for sequence, (name, url, xml_id) in enumerate(self._get_menu_entries()): if name not in existing_page_names: if not url: - newpath = self.env['website'].new_page(name + ' ' + self.name, xml_id, ispage=False) - url = "/event/" + slug(self) + "/page/" + newpath + newpath = self.env['website'].new_page(name + ' ' + self.name, template=xml_id, ispage=False) + url = "/event/" + slug(self) + "/page/" + newpath[1:] self.env['website.menu'].create({ 'name': name, 'url': url, diff --git a/addons/website_event/static/src/js/website.tour.event.js b/addons/website_event/static/src/js/website.tour.event.js index 215c9a5253c..b08af0705e3 100644 --- a/addons/website_event/static/src/js/website.tour.event.js +++ b/addons/website_event/static/src/js/website.tour.event.js @@ -33,14 +33,14 @@ odoo.define("website_event.tour", function (require) { content: _t("Once you click on save, your event is updated."), position: "bottom", }, { - trigger: "button.btn-danger.js_publish_btn", + trigger: ".js_publish_management .js_publish_btn", extra_trigger: "body:not(.editor_enable)", content: _t("Click to publish your event."), position: "top", }, { - trigger: ".js_publish_management button[data-toggle=\"dropdown\"]", - extra_trigger: ".js_publish_management button.js_publish_btn.btn-success:visible", + trigger: ".css_edit_dynamic", + extra_trigger: ".js_publish_management .js_publish_btn .css_unpublish:visible", content: _t("Click here to customize your event further."), - position: "left", + position: "bottom", }]); }); diff --git a/addons/website_event/views/event_templates.xml b/addons/website_event/views/event_templates.xml index df98b61b7c7..3781a94640a 100644 --- a/addons/website_event/views/event_templates.xml +++ b/addons/website_event/views/event_templates.xml @@ -202,6 +202,11 @@ </xpath> </template> +<template id="event_edit_options" inherit_id="website.user_navbar" name="Edit Event Options"> + <xpath expr="//li[@id='edit-page-menu']" position="after"> + <t t-if="main_object._name == 'event.event'" t-set="action" t-value="'event.action_event_view'" /> + </xpath> +</template> <template id="layout"> <t t-call="website.layout"> <div id="wrap" class="js_event o_website_event"> @@ -226,13 +231,6 @@ </t> </ol> </div> - <div class="col-sm-5" groups="event.group_event_manager"> - <t t-call="website.publish_management"> - <t t-set="object" t-value="event"/> - <t t-set="publish_edit" t-value="True"/> - <t t-set="action" t-value="'event.action_event_view'"/> - </t> - </div> </div> </div> <t t-raw="0"/> diff --git a/addons/website_event_track/views/event_track_templates.xml b/addons/website_event_track/views/event_track_templates.xml index 14eee014c6f..caf9d7d68e2 100644 --- a/addons/website_event_track/views/event_track_templates.xml +++ b/addons/website_event_track/views/event_track_templates.xml @@ -173,7 +173,11 @@ </template> - +<template id="track_edit_options" inherit_id="website.user_navbar" name="Edit Track Options"> + <xpath expr="//li[@id='edit-page-menu']" position="after"> + <t t-if="main_object._name == 'event.track'" t-set="action" t-value="'website_event_track.action_event_track'" /> + </xpath> +</template> <template id="track_view"> <t t-call="website.layout"> <div id="wrap" class="js_event"> @@ -186,11 +190,6 @@ </t> </ol> </div> - <t t-call="website.publish_management"> - <t t-set="object" t-value="track"/> - <t t-set="publish_edit" t-value="True"/> - <t t-set="action" t-value="'website_event_track.action_event_track'"/> - </t> </div> </div> </div> diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml index 2bd5e38cdc3..3d2315db252 100644 --- a/addons/website_forum/views/website_forum.xml +++ b/addons/website_forum/views/website_forum.xml @@ -1382,10 +1382,6 @@ <h1 class="mt0 page-header"> <span t-field="user.name"/> <small>profile</small> - <t t-call="website.publish_management"> - <t t-set="object" t-value="user"/> - <t t-set="publish_edit" t-value="True"/> - </t> </h1> <div class="row"> <div class="col-sm-2"> diff --git a/addons/website_hr/__init__.py b/addons/website_hr/__init__.py index bb0aae7ee63..be9f4fab00a 100644 --- a/addons/website_hr/__init__.py +++ b/addons/website_hr/__init__.py @@ -1,4 +1,3 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import controllers from . import models diff --git a/addons/website_hr/controllers/__init__.py b/addons/website_hr/controllers/__init__.py deleted file mode 100644 index 7fc0cd7cb93..00000000000 --- a/addons/website_hr/controllers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import main diff --git a/addons/website_hr/controllers/main.py b/addons/website_hr/controllers/main.py deleted file mode 100644 index 23559284cb5..00000000000 --- a/addons/website_hr/controllers/main.py +++ /dev/null @@ -1,15 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import http -from odoo.http import request - -class WebsiteHr(http.Controller): - - @http.route(['/page/website.aboutus', '/page/aboutus'], type='http', auth="public", website=True) - def blog(self, **post): - employees_domain = [] - if not request.env['res.users'].has_group('website.group_website_publisher'): - employees_domain += [('website_published', '=', True)] - employees = request.env['hr.employee'].search(employees_domain) - return request.render("website.aboutus", {'employees': employees}) diff --git a/addons/website_hr/data/website_hr_data.xml b/addons/website_hr/data/website_hr_data.xml index 71c3c467468..0ccd720491c 100644 --- a/addons/website_hr/data/website_hr_data.xml +++ b/addons/website_hr/data/website_hr_data.xml @@ -5,7 +5,7 @@ <record id="action_open_website" model="ir.actions.act_url"> <field name="name">Website About</field> <field name="target">self</field> - <field name="url">/page/aboutus</field> + <field name="url">/aboutus</field> </record> <record id="base.open_menu" model="ir.actions.todo"> <field name="action_id" ref="action_open_website"/> diff --git a/addons/website_hr/models/hr_employee.py b/addons/website_hr/models/hr_employee.py index 8d8304ad525..a73de64a5f2 100644 --- a/addons/website_hr/models/hr_employee.py +++ b/addons/website_hr/models/hr_employee.py @@ -13,4 +13,4 @@ class HrEmployee(models.Model): def _compute_website_url(self): super(HrEmployee, self)._compute_website_url() for employee in self: - employee.website_url = '/page/website.aboutus#team' + employee.website_url = '/aboutus#team' diff --git a/addons/website_hr/views/website_hr_templates.xml b/addons/website_hr/views/website_hr_templates.xml index ecadc7eae9d..f0d2df3b415 100644 --- a/addons/website_hr/views/website_hr_templates.xml +++ b/addons/website_hr/views/website_hr_templates.xml @@ -10,6 +10,9 @@ <template id="aboutus" inherit_id="website.aboutus" customize_show="True" name="Our Team"> <xpath expr="//div[hasclass('oe_structure')]" position="after"> <section class="container" id="team"> + <t t-set="is_publisher" t-value="request.env['res.users'].has_group('website.group_website_publisher')" /> + <t t-set="employees_domain" t-value="is_publisher and [] or [('website_published', '=', True)]" /> + <t t-set="employees" t-value="request.env['hr.employee'].search(employees_domain)" /> <div class="col-sm-12 text-center" t-if="len(employees)"> <h2>Our Team</h2> </div> diff --git a/addons/website_hr_recruitment/views/website_hr_recruitment_templates.xml b/addons/website_hr_recruitment/views/website_hr_recruitment_templates.xml index 41eb98b35c4..25322d25b35 100644 --- a/addons/website_hr_recruitment/views/website_hr_recruitment_templates.xml +++ b/addons/website_hr_recruitment/views/website_hr_recruitment_templates.xml @@ -38,7 +38,7 @@ <div class="col-md-12" id="jobs_grid"> <div t-if="not jobs"> <div class="text-center text-muted"> - <h3 class="css_editable_hidden"><a t-attf-href="/page/website.contactus">Contact us</a> for job opportunities.</h3> + <h3 class="css_editable_hidden"><a t-attf-href="/contactus">Contact us</a> for job opportunities.</h3> <h4 groups="hr_recruitment.group_hr_recruitment_manager"> Create new job pages from the <strong><i>+New</i></strong> top-right button. </h4> @@ -86,11 +86,6 @@ <div class="container"> <div class="pull-right"> <a t-attf-href="/jobs/apply/#{job.id}" class="btn btn-primary btn-lg pull-right mt32 mb4">Apply Now!</a> - <br/> - <t t-call="website.publish_management"> - <t t-set="object" t-value="job"/> - <t t-set="publish_edit" t-value="True"/> - </t> </div> <label class="mb0 mt16"><a href="/jobs">Jobs</a></label> / <h1 class="mb0 mt0" t-field="job.name"/> @@ -129,7 +124,7 @@ <div class="row"> <section id="forms"> - <form action="/website_form/" method="post" class="s_website_form form-horizontal container-fluid mt32" enctype="multipart/form-data" data-model_name="hr.applicant" data-success_page="/page/website_hr_recruitment.thankyou" t-att-data-form_field_department_id="job and job.department_id.id or False" t-att-data-form_field_job_id="job and job.id or False"> + <form action="/website_form/" method="post" class="s_website_form form-horizontal container-fluid mt32" enctype="multipart/form-data" data-model_name="hr.applicant" data-success_page="/job-thank-you" t-att-data-form_field_department_id="job and job.department_id.id or False" t-att-data-form_field_job_id="job and job.id or False"> <div class="form-group form-field o_website_form_required_custom"> <div class="col-md-3 col-sm-4 text-right"> <label class="control-label" for="partner_name">Your Name</label> @@ -390,59 +385,72 @@ </section> </template> -<template id="thankyou"> - <t t-call="website.layout"> - <div id="wrap"> - <div class="oe_structure"> - <div class="container"> - <div class="row"> - <div class="col-md-12"> - <h1 class="text-center">Congratulations!</h1> - <p class="text-center"> - Your application has been posted successfully. - </p> - </div> - <t t-if="request.session.get('form_builder_model_model', '') == 'hr.applicant'"> - <t t-set="job" t-value="request.website._website_form_last_record().sudo().job_id"/> - <t t-set="responsible" t-value="job and job.user_id.website_published and job.user_id "/> - </t> - <t t-if="responsible"> - <div class="col-md-12"> - <h3 class="mb32 text-center">Your application has been sent to:</h3> - </div> - <div class="col-md-1 col-md-offset-4"> - <p t-field="responsible.image_small" t-field-options="{'widget': 'image', 'class': 'img-circle center-block'}"/> - </div> - <div class="col-md-5 o_responsible_data"> - <h4 class="mt0" t-field="responsible.name"/> - <p t-field="responsible.function"/> - <t t-if='responsible.email'> - <i class="fa fa-envelope"></i> <a t-attf-href="mailto:#{responsible.email}" t-esc="responsible.email"/> +<record id="thankyou" model="ir.ui.view"> + <field name="name">Thank you (Recruitment)</field> + <field name="type">qweb</field> + <field name="key">website_hr_recruitment.thankyou</field> + <field name="arch" type="xml"> + <t name="Thank you (Recruitment)" t-name="website_hr_recruitment.thankyou"> + <t t-call="website.layout"> + <div id="wrap"> + <div class="oe_structure"> + <div class="container"> + <div class="row"> + <div class="col-md-12"> + <h1 class="text-center">Congratulations!</h1> + <p class="text-center"> + Your application has been posted successfully. + </p> + </div> + <t t-if="request.session.get('form_builder_model_model', '') == 'hr.applicant'"> + <t t-set="job" t-value="request.website._website_form_last_record().sudo().job_id"/> + <t t-set="responsible" t-value="job and job.user_id.website_published and job.user_id "/> </t> - <t t-if='responsible.phone'> - <br/><i class="fa fa-phone"></i> <span t-field="responsible.phone"/> + <t t-if="responsible"> + <div class="col-md-12"> + <h3 class="mb32 text-center">Your application has been sent to:</h3> + </div> + <div class="col-md-1 col-md-offset-4"> + <p t-field="responsible.image_small" t-field-options="{'widget': 'image', 'class': 'img-circle center-block'}"/> + </div> + <div class="col-md-5 o_responsible_data"> + <h4 class="mt0" t-field="responsible.name"/> + <p t-field="responsible.function"/> + <t t-if='responsible.email'> + <i class="fa fa-envelope"></i> <a t-attf-href="mailto:#{responsible.email}" t-esc="responsible.email"/> + </t> + <t t-if='responsible.phone'> + <br/><i class="fa fa-phone"></i> <span t-field="responsible.phone"/> + </t> + </div> + <div class="col-md-12 mt32 text-center"> + <span> + We usually reply between one and three days.<br/> + Feel free to contact him/her if you have further questions. + </span> + </div> </t> </div> - <div class="col-md-12 mt32 text-center"> - <span> - We usually reply between one and three days.<br/> - Feel free to contact him/her if you have further questions. - </span> + <div class="row" id="o_recruitment_thank_cta"> + <div class="col-md-12 text-center mt32 mb32"> + In the meantime, + <h3 class="mt8 mb32">Look around on our website:</h3> + <a href="/" class="btn btn-primary btn-lg">Continue To Our Website</a> + </div> </div> - </t> - </div> - <div class="row" id="o_recruitment_thank_cta"> - <div class="col-md-12 text-center mt32 mb32"> - In the meantime, - <h3 class="mt8 mb32">Look around on our website:</h3> - <a href="/" class="btn btn-primary btn-lg">Continue To Our Website</a> </div> </div> </div> - </div> - </div> - </t> -</template> + </t> + </t> + </field> +</record> +<record id="thankyou_page" model="website.page"> + <field name="name">Thank you (Recruitment)</field> + <field name="url">/job-thank-you</field> + <field name="website_published">True</field> + <field name="ir_ui_view_id" ref="thankyou" /> +</record> <template id="job_countries" inherit_id="website_hr_recruitment.index" active="False" customize_show="True" name="Filter by Countries"> <xpath expr="//div[@id='jobs_grid_left']" position="inside"> diff --git a/addons/website_mass_mailing/views/snippets_templates.xml b/addons/website_mass_mailing/views/snippets_templates.xml index f42c5cf505f..34ec54e4b19 100644 --- a/addons/website_mass_mailing/views/snippets_templates.xml +++ b/addons/website_mass_mailing/views/snippets_templates.xml @@ -112,7 +112,7 @@ <template id="s_mail_block_footer_social" inherit_id="mass_mailing.s_mail_block_footer_social"> <xpath expr="//td[hasclass('o_mail_footer_links')]" position="inside"> - <t> | <a href="/page/contactus" class="btn btn-link">Contact</a></t> + <t> | <a href="/contactus" class="btn btn-link">Contact</a></t> </xpath> <xpath expr="//tr" position="before"> <tr> @@ -125,7 +125,7 @@ <template id="s_mail_block_footer_social_left" inherit_id="mass_mailing.s_mail_block_footer_social_left"> <xpath expr="//div[hasclass('o_mail_footer_links')]" position="inside"> - <t> | <a href="/page/contactus" class="btn btn-link">Contact</a></t> + <t> | <a href="/contactus" class="btn btn-link">Contact</a></t> </xpath> <xpath expr="//td" position="after"> <td class="o_mail_footer_social"> diff --git a/addons/website_partner/views/website_partner_templates.xml b/addons/website_partner/views/website_partner_templates.xml index 7f1d9877751..38f0bb81fcf 100644 --- a/addons/website_partner/views/website_partner_templates.xml +++ b/addons/website_partner/views/website_partner_templates.xml @@ -15,10 +15,6 @@ </template> <template id="partner_detail" name="Partner Details"> - <t t-call="website.publish_management"> - <t t-set="object" t-value="partner"/> - <t t-set="publish_edit" t-value="True"/> - </t> <h1 class="col-md-12 text-center" id="partner_name" t-field="partner.display_name"/> <div class="col-md-4"> <div t-field="partner.image" t-options='{"widget": "image", "class": "center-block mb16"}'/> diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop.js b/addons/website_sale/static/src/js/website_sale_tour_shop.js index 4f4e64852a4..09a37d2e5a4 100644 --- a/addons/website_sale/static/src/js/website_sale_tour_shop.js +++ b/addons/website_sale/static/src/js/website_sale_tour_shop.js @@ -55,7 +55,7 @@ odoo.define("website_sale.tour_shop", function (require) { content: _t("Once you click on <b>Save</b>, your product is updated."), position: "bottom", }, { - trigger: ".js_publish_management button.js_publish_btn.btn-danger", + trigger: ".js_publish_management .js_publish_btn .css_publish", extra_trigger: "body:not(.editor_enable)", content: _t("Click on this button so your customers can see it."), position: "top", diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index 81a7c9e0578..1b8698792fd 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -378,6 +378,11 @@ </template> <!-- /shop/product product page --> + <template id="product_edit_options" inherit_id="website.user_navbar" name="Edit Product Options"> + <xpath expr="//li[@id='edit-page-menu']" position="after"> + <t t-if="main_object._name == 'product.template'" t-set="action" t-value="'product.product_template_action'" /> + </xpath> + </template> <template id="product" name="Product"> <t t-call="website.layout"> <t t-set="additional_title" t-value="product.name" /> @@ -401,11 +406,6 @@ <t t-call="website_sale.search" /> </div> <div id="website_published_button" class="col-sm-3"> - <t t-call="website.publish_management" groups="sales_team.group_sale_manager"> - <t t-set="object" t-value="product" /> - <t t-set="publish_edit" t-value="True" /> - <t t-set="action" t-value="'product.product_template_action'" /> - </t> </div> <div class="col-sm-2 text-right"> <t t-call="website_sale.pricelist_list" /> diff --git a/addons/website_slides/views/website_slides.xml b/addons/website_slides/views/website_slides.xml index aff237ee6a4..1d51e1aabb9 100644 --- a/addons/website_slides/views/website_slides.xml +++ b/addons/website_slides/views/website_slides.xml @@ -368,6 +368,11 @@ </template> <!-- Slide: main template: detailed view --> +<template id="slide_edit_options" inherit_id="website.user_navbar" name="Edit Slide Options"> + <xpath expr="//li[@id='edit-page-menu']" position="after"> + <t t-if="main_object._name == 'slide.slide'" t-set="action" t-value="'website_slides.action_slides_slides'" /> + </xpath> +</template> <template id="slide_detail_view" name="Slide Detailed View"> <t t-set="head"> <t t-if="not private"> @@ -411,13 +416,6 @@ <a t-attf-href="/slides/#{slug(slide.channel_id)}/tag/#{slug(slide_tag)}" t-attf-class="label label-default ml4 mb4 pull-right #{tag and tag.id == slide_tag.id and 'label-primary' ''}" t-field="slide_tag.name"/> </t> </div> - <div class="pull-right"> - <t t-call="website.publish_management"> - <t t-set="object" t-value="slide"/> - <t t-set="publish_edit" t-value="True"/> - <t t-set="action" t-value="'website_slides.action_slides_slides'"/> - </t> - </div> </div> </div> </div> diff --git a/odoo/addons/base/ir/ir_http.py b/odoo/addons/base/ir/ir_http.py index 87025f6f8ea..f4d830efe12 100644 --- a/odoo/addons/base/ir/ir_http.py +++ b/odoo/addons/base/ir/ir_http.py @@ -10,7 +10,6 @@ import mimetypes import os import re import sys -import unicodedata import werkzeug import werkzeug.exceptions @@ -158,15 +157,23 @@ class IrHttp(models.AbstractModel): return response @classmethod + def _serve_fallback(cls, exception): + # serve attachment + attach = cls._serve_attachment() + if attach: + return attach + return False + + @classmethod def _handle_exception(cls, exception): # If handle_exception returns something different than None, it will be used as a response # This is done first as the attachment path may # not match any HTTP controller if isinstance(exception, werkzeug.exceptions.HTTPException) and exception.code == 404: - attach = cls._serve_attachment() - if attach: - return attach + serve = cls._serve_fallback(exception) + if serve: + return serve # Don't handle exception but use werkeug debugger if server in --dev mode if 'werkzeug' in tools.config['dev_mode']: diff --git a/odoo/tools/convert.py b/odoo/tools/convert.py index d2c2ae6d0a7..60f9bfa83ad 100644 --- a/odoo/tools/convert.py +++ b/odoo/tools/convert.py @@ -702,8 +702,6 @@ form: module.record_id""" % (xml_id,) if groups: grp_lst = [("ref('%s')" % x) for x in groups.split(',')] record.append(Field(name="groups_id", eval="[(6, 0, ["+', '.join(grp_lst)+"])]")) - if el.attrib.pop('page', None) == 'True': - record.append(Field(name="page", eval="True")) if el.get('primary') == 'True': # Pseudo clone mode, we'll set the t-name to the full canonical xmlid el.append( |
