Odoo 16 Development Book

Managing Sitemaps for the Odoo Website

A sitemap is important for any website. It is a file that contains information about the website's pages and other files on the website. Search engines use sitemaps to index the pages of your website.

This blog describes how to modify an existing sitemap for the Odoo website. We can use the sitemap_xml_index method for changing the already existing sitemap. Ie,

@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
   def create_sitemap(url, content):
       return Attachment.create({
           'raw': content.encode(),
           'mimetype': mimetype,
           'type': 'binary',
           'name': url,
           'url': url,
   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)
       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)
       if not pages:
           return request.not_found()
       elif pages == 1:
           # rename the -id-page.xml => -id.xml
               'url': "/sitemap-%d.xml" % current_website.id,
               'name': "/sitemap-%d.xml" % current_website.id,
           # 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)])

This native function is defined within the website module controller. This function is called each time a sitemap file is generated.

locs = request.website.with_user(request.website.user_id)._enumerate_pages()

This code will generate the site URL for the sitemap. Here it is defined using the _enumerate_pages function in the 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:
           func = rule.endpoint.routing['sitemap']
           if func is False:
           for loc in func(self.env, rule, query_string):
               yield loc
       if not self.rule_is_enumerable(rule):
       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]):
       values = [{}]
       # converters with a domain are processed after the other ones
       convitems = sorted(
           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:
               for rec in converter.generate(self.env, args=val, dom=query):
                   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:
               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

You can use this function to switch between pages of your website in your sitemap. This function can be used when you need to add a new page or a new URL to your sitemap. You can use this function.

Adding a Records Page to Your Sitemap

To add a Records page to your sitemap, you must 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 is used to generate user-friendly URLs and created on the basis of sitemap_qs2dom is primarily used to create domains based on routes and query strings.

Next, create a new method in main.py.

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}

where sitemap_records is a Python generator function. This function is called each time a sitemap is generated. This function generates a domain with sitemap_qs2dom. And then used that generated domain to search for records. The location is then generated by the slug() method. Get a user-friendly URL by using the slug method.

Then you can add the sitemap_records function reference to the record detail root. in short,

@http.route('/url/', type='http', auth="user", website=True, sitemap=sitemap_records)
    def records_detail(self, record):

Here we passed a reference to the sitemap_records() function to the root using the sitemap keyword.

This allows you to add record pages to sitemap.xml. If you don't need to filter records and just want to list all records in the sitemap, you can pass True instead of a function reference.

Finally, update the module with your changes. The sitemap is updated every 12 hours. So if you want to see the changes, go to attachments and delete the existing sitemap.xml. Open /sitemap.xml in a browser to see the changes.



Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635



Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.



Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message