[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/...)
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
import odoo
|
||||
from odoo import http, models
|
||||
from odoo import fields
|
||||
from odoo.http import request
|
||||
from itertools import islice
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
import odoo
|
||||
|
||||
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
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -89,6 +88,53 @@ class Http(models.AbstractModel):
|
||||
return request.website.default_lang_id
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
return super(View, self).get_default_lang_code()
|
||||
|
||||
+377
-164
@@ -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
|
||||
url = page.url
|
||||
|
||||
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': '#'
|
||||
})
|
||||
page_key = _('Page')
|
||||
|
||||
# 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)
|
||||
]
|
||||
# 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)
|
||||
|
||||
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
|
||||
})
|
||||
# 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,72 +459,6 @@ class Website(models.Model):
|
||||
return self.env.ref('website.action_website').read()[0]
|
||||
|
||||
|
||||
class Menu(models.Model):
|
||||
|
||||
_name = "website.menu"
|
||||
_description = "Website Menu"
|
||||
|
||||
_parent_store = True
|
||||
_parent_order = 'sequence'
|
||||
_order = "sequence"
|
||||
|
||||
def _default_sequence(self):
|
||||
menu = self.search([], limit=1, order="sequence DESC")
|
||||
return menu.sequence or 0
|
||||
|
||||
name = fields.Char('Menu', required=True, translate=True)
|
||||
url = fields.Char('Url', default='')
|
||||
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
|
||||
parent_id = fields.Many2one('website.menu', 'Parent Menu', index=True, ondelete="cascade")
|
||||
child_id = fields.One2many('website.menu', 'parent_id', string='Child Menus')
|
||||
parent_left = fields.Integer('Parent Left', index=True)
|
||||
parent_right = fields.Integer('Parent Rigth', index=True)
|
||||
|
||||
# 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):
|
||||
menu_node = dict(
|
||||
id=node.id,
|
||||
name=node.name,
|
||||
url=node.url,
|
||||
new_window=node.new_window,
|
||||
sequence=node.sequence,
|
||||
parent_id=node.parent_id.id,
|
||||
children=[],
|
||||
)
|
||||
for child in node.child_id:
|
||||
menu_node['children'].append(make_tree(child))
|
||||
return menu_node
|
||||
if menu_id:
|
||||
menu = self.browse(menu_id)
|
||||
else:
|
||||
menu = self.env['website'].browse(website_id).menu_id
|
||||
return make_tree(menu)
|
||||
|
||||
@api.model
|
||||
def save(self, website_id, data):
|
||||
def replace_id(old_id, new_id):
|
||||
for menu in data['data']:
|
||||
if menu['id'] == old_id:
|
||||
menu['id'] = new_id
|
||||
if menu['parent_id'] == old_id:
|
||||
menu['parent_id'] = new_id
|
||||
to_delete = data['to_delete']
|
||||
if to_delete:
|
||||
self.browse(to_delete).unlink()
|
||||
for menu in data['data']:
|
||||
mid = menu['id']
|
||||
if isinstance(mid, pycompat.string_types):
|
||||
new_menu = self.create({'name': menu['name']})
|
||||
replace_id(mid, new_menu.id)
|
||||
for menu in data['data']:
|
||||
self.browse(menu['id']).write(menu)
|
||||
return True
|
||||
|
||||
|
||||
class SeoMetadata(models.AbstractModel):
|
||||
|
||||
_name = 'website.seo.metadata'
|
||||
@@ -538,3 +494,260 @@ class WebsitePublishedMixin(models.AbstractModel):
|
||||
'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"
|
||||
_description = "Website Menu"
|
||||
|
||||
_parent_store = True
|
||||
_parent_order = 'sequence'
|
||||
_order = "sequence"
|
||||
|
||||
def _default_sequence(self):
|
||||
menu = self.search([], limit=1, order="sequence DESC")
|
||||
return menu.sequence or 0
|
||||
|
||||
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
|
||||
parent_id = fields.Many2one('website.menu', 'Parent Menu', index=True, ondelete="cascade")
|
||||
child_id = fields.One2many('website.menu', 'parent_id', string='Child Menus')
|
||||
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.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))
|
||||
return menu_node
|
||||
if menu_id:
|
||||
menu = self.browse(menu_id)
|
||||
else:
|
||||
menu = self.env['website'].browse(website_id).menu_id
|
||||
return make_tree(menu)
|
||||
|
||||
@api.model
|
||||
def save(self, website_id, data):
|
||||
def replace_id(old_id, new_id):
|
||||
for menu in data['data']:
|
||||
if menu['id'] == old_id:
|
||||
menu['id'] = new_id
|
||||
if menu['parent_id'] == old_id:
|
||||
menu['parent_id'] = new_id
|
||||
to_delete = data['to_delete']
|
||||
if to_delete:
|
||||
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 WebsiteRedirect(models.Model):
|
||||
_name = "website.redirect"
|
||||
_description = "Website Redirect"
|
||||
_order = "sequence, id"
|
||||
_rec_name = 'url_from'
|
||||
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,68 +541,11 @@ 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.
|
||||
@@ -419,58 +592,104 @@ var ContentMenu = websiteNavbarData.WebsiteNavbarActionWidget.extend({
|
||||
|
||||
return def;
|
||||
},
|
||||
/**
|
||||
* Asks the user for the new name of the page, then reloads to the new page
|
||||
* with the correct name if saved.
|
||||
*
|
||||
* @private
|
||||
* @returns {Deferred}
|
||||
* Unresolved if the page's name is changed and saved as the page
|
||||
* will be reloaded.
|
||||
* Resolved otherwise.
|
||||
*/
|
||||
_renamePage: function () {
|
||||
var def = $.Deferred();
|
||||
|
||||
_pageProperties: function () {
|
||||
var self = this;
|
||||
var moID = self._getMainObject().id;
|
||||
var context = weContext.get();
|
||||
|
||||
// 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),
|
||||
});
|
||||
return renameDef;
|
||||
}).then(function (newName) {
|
||||
// Rename the page with the user's choice
|
||||
return self._rpc({
|
||||
model: 'website',
|
||||
method: 'rename_page',
|
||||
args: [moID, newName],
|
||||
context: context,
|
||||
});
|
||||
}).then(function (newTechnicalName) {
|
||||
// Redirects to the new renamed page
|
||||
window.location.href = ('/page/' + encodeURIComponent(newTechnicalName));
|
||||
}, def.reject.bind(def));
|
||||
|
||||
return def;
|
||||
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
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* 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,
|
||||
});
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// 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 context = weContext.get();
|
||||
|
||||
var def = $.Deferred();
|
||||
// Search the page dependencies
|
||||
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 confirmDef;
|
||||
}).then(function () {
|
||||
// Delete the page if the user confirmed
|
||||
return self._rpc({
|
||||
model: 'website.page',
|
||||
method: 'delete_page',
|
||||
args: [pageId],
|
||||
context: context,
|
||||
});
|
||||
}).then(function () {
|
||||
window.location.reload(true);
|
||||
}, def.reject.bind(def));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
websiteNavbarData.websiteNavbarRegistry.add(ContentMenu, '#content-menu');
|
||||
websiteRootData.websiteRootRegistry.add(PageManagement, '#edit_website_pages');
|
||||
|
||||
return {
|
||||
PagePropertiesDialog: PagePropertiesDialog,
|
||||
ContentMenu: ContentMenu,
|
||||
EditMenuDialog: EditMenuDialog,
|
||||
MenuEntryDialog: MenuEntryDialog,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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',
|
||||
}, {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
}]);
|
||||
});
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from . import main
|
||||
@@ -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})
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"}'/>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -10,7 +10,6 @@ import mimetypes
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import unicodedata
|
||||
|
||||
import werkzeug
|
||||
import werkzeug.exceptions
|
||||
@@ -157,6 +156,14 @@ class IrHttp(models.AbstractModel):
|
||||
response.data = base64.b64decode(datas)
|
||||
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
|
||||
@@ -164,9 +171,9 @@ class IrHttp(models.AbstractModel):
|
||||
# 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']:
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user