Development Book V17:Managing Sitemaps for the Odoo Website

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.

whatsapp_icon
location

Calicut

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

location

Kochi

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

location

Bangalore

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

Send Us A Message