A sitemap plays a crucial role in enhancing the functionality
of a website. It serves as a comprehensive file housing
essential information about the website's pages and associated
files. Notably, search engines rely on sitemaps to efficiently
index and navigate through the diverse pages of your website,
ensuring optimal visibility and accessibility for users.
This blog provides insights into the process of altering an
existing sitemap specifically tailored for the Odoo website.
The approach involves utilizing the sitemap_xml_index method
to make modifications to the pre-existing sitemap structure.
from odoo import http
def create_sitemap(url, content):
return Attachment.create({
'raw': content.encode(),
'mimetype': mimetype,
'type': 'binary',
'name': url,
'url': url,
})
@http.route('/sitemap.xml', type='http', auth="public", website=True, multilang=False, sitemap=False)
def sitemap_xml_index(self, **kwargs):
current_website = request.website
Attachment = request.env['ir.attachment'].sudo()
View = request.env['ir.ui.view'].sudo()
mimetype = 'application/xml;charset=utf-8'
content = None
dom = [('url', '=', '/sitemap-%d.xml' % current_website.id), ('type', '=', 'binary')]
sitemap = Attachment.search(dom, limit=1)
if sitemap:
# Check if stored version is still valid
create_date = fields.Datetime.from_string(sitemap.create_date)
delta = datetime.datetime.now() - create_date
if delta < SITEMAP_CACHE_TIME:
content = base64.b64decode(sitemap.datas)
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)]
sitemaps = Attachment.search(dom)
sitemaps.unlink()
pages = 0
locs = request.website.with_user(request.website.user_id)._enumerate_pages()
while True:
values = {
'locs': islice(locs, 0, LOC_PER_SITEMAP),
'url_root': request.httprequest.url_root[:-1],
}
urls = View._render_template('website.sitemap_locs', values)
if urls.strip():
content = View._render_template('website.sitemap_xml', {'content': urls})
pages += 1
last_sitemap = create_sitemap('/sitemap-%d-%d.xml' % (current_website.id, pages), content)
else:
break
if not pages:
return request.not_found()
elif pages == 1:
# rename the -id-page.xml => -id.xml
last_sitemap.write({
'url': "/sitemap-%d.xml" % current_website.id,
'name': "/sitemap-%d.xml" % current_website.id,
})
else:
# TODO: in master/saas-15, move current_website_id in template directly
pages_with_website = ["%d-%d" % (current_website.id, p) for p in range(1, pages + 1)]
# Sitemaps must be split in several smaller files with a sitemap index
content = View._render_template('website.sitemap_index_xml', {
'pages': pages_with_website,
'url_root': request.httprequest.url_root,
})
create_sitemap('/sitemap-%d.xml' % current_website.id, content)
return request.make_response(content, [('Content-Type', mimetype)])
The inherent function is outlined in the controller module of
the website. It is invoked every time a sitemap file is
generated.
locs =
request.website.with_user(request.website.user_id)._enumerate_pages()
This piece of code is used to produce the site URL for the
sitemap. The definition involves leveraging the
_enumerate_pages function within the provided sample website.
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).
By default, returns template views marked as pages.
:param str query_string: a (user-provided) string, fetches pages
matching the string
:returns: a list of mappings with two keys: ``name`` is the displayable
name of the resource (page), ``url`` is the absolute URL
of the same.
:rtype: list({name: str, url: str})
"""
router = self.env['ir.http'].routing_map()
url_set = set()
sitemap_endpoint_done = set()
for rule in router.iter_rules():
if 'sitemap' in rule.endpoint.routing and rule.endpoint.routing['sitemap'] is not True:
if rule.endpoint in sitemap_endpoint_done:
continue
sitemap_endpoint_done.add(rule.endpoint)
func = rule.endpoint.routing['sitemap']
if func is False:
continue
for loc in func(self.env, rule, query_string):
yield loc
continue
if not self.rule_is_enumerable(rule):
continue
if 'sitemap' not in rule.endpoint.routing:
logger.warning('No Sitemap value provided for controller %s (%s)' %
(rule.endpoint.method, ','.join(rule.endpoint.routing['routes'])))
converters = rule._converters or {}
if query_string and not converters and (query_string not in rule.build({}, append_unknown=False)[1]):
continue
values = [{}]
# converters with a domain are processed after the other ones
convitems = sorted(
converters.items(),
key=lambda x: (hasattr(x[1], 'domain') and (x[1].domain != '[]'), rule._trace.index((True, x[0]))))
for (i, (name, converter)) in enumerate(convitems):
if 'website_id' in self.env[converter.model]._fields and (not converter.domain or converter.domain == '[]'):
converter.domain = "[('website_id', 'in', (False, current_website_id))]"
newval = []
for val in values:
query = i == len(convitems) - 1 and query_string
if query:
r = "".join([x[1] for x in rule._trace[1:] if not x[0]]) # remove model converter from route
query = sitemap_qs2dom(query, r, self.env[converter.model]._rec_name)
if query == FALSE_DOMAIN:
continue
for rec in converter.generate(self.env, args=val, dom=query):
newval.append(val.copy())
newval[-1].update({name: rec})
values = newval
for value in values:
domain_part, url = rule.build(value, append_unknown=False)
if not query_string or query_string.lower() in url.lower():
page = {'loc': url}
if url in url_set:
continue
url_set.add(url)
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), ('visibility', '=', False)]
# is_visible
domain += [
('website_published', '=', True), ('visibility', '=', False),
'|', ('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.view_id and page.view_id.priority != 16:
record['priority'] = min(round(page.view_id.priority / 32.0, 1), 1)
if page['write_date']:
record['lastmod'] = page['write_date'].date()
yield record
This function facilitates the seamless transition between
various pages on your website within the sitemap. Its utility
extends to the addition of new pages or URLs to enhance the
comprehensiveness of your sitemap.
Adding a Records Page to Your Sitemap
For adding a records page to sitemap first import the
following method.
Import slug from odoo.addons.http_routing.models.ir_http
Import from odoo.addons.website.models.ir_http sitemap_qs2dom
The slug serves the purpose of forming user-friendly URLs, and
its creation is grounded in the sitemap_qs2dom, a function
predominantly utilized for generating domains based on routes
and query strings.
Now, create a new method.
class Main(http.Controller):
def sitemap_records(env, rule, qs):
records = env[your.model]
dom = sitemap_qs2dom(qs, '/url', records._rec_name)
for r in records.search(dom):
loc = '/url/%s' % slug(r)
if not qs or qs.lower() in loc:
yield {'loc': loc}
The 'sitemap_records' is a Python generator function that gets
invoked whenever a sitemap is generated. Within this function,
a domain is created using 'sitemap_qs2dom'. Subsequently, this
generated domain is utilized for searching records. The
location is then determined by employing the 'slug()' method,
which contributes to obtaining a user-friendly URL.
Subsequently, incorporate the reference to the
'sitemap_records' function into the record detail root.
@http.route('/url/', type='http', auth="user", website=True, sitemap=sitemap_records)
def records_detail(self, record):
In this context, we provided a reference to the
'sitemap_records()' function to the root by utilizing the
'sitemap' keyword.
This functionality enables the addition of record pages to
'sitemap.xml'. In cases where there's no requirement for
record filtering and you simply want to list all records in
the sitemap, you can substitute a function reference with
True.
The sitemap undergoes an update every 12 hours. To observe the
changes, navigate to attachments, delete the current
'sitemap.xml', and open '/sitemap.xml' in a browser to see the
changes.