Add contact form submission handling and introduce theme_clicks2cart module with enhanced shop page and product item layouts.

This commit is contained in:
Alaguraj0361 2026-03-11 22:36:44 +05:30
parent 06bc61de9c
commit 048789595c
14 changed files with 744 additions and 37 deletions

View File

@ -57,36 +57,36 @@ class ContactController(http.Controller):
return request.render('dine360_theme_chennora.contact_thank_you')
class BlogController(http.Controller):
@http.route(['/blog'], type='http', auth='public', website=True)
def blog_list(self, **post):
blog_posts = request.env['chennora.blog.post'].sudo().search([('active', '=', True)])
return request.render('dine360_theme_chennora.blog_page', {
'blog_posts': blog_posts,
})
@http.route(['/blog/<string:slug>'], type='http', auth='public', website=True)
def blog_detail(self, slug, **post):
blog_post = request.env['chennora.blog.post'].sudo().search([('slug', '=', slug), ('active', '=', True)], limit=1)
if not blog_post:
return request.not_found()
recent_posts = request.env['chennora.blog.post'].sudo().search([('active', '=', True), ('id', '!=', blog_post.id)], limit=3)
# Get dynamic categories and counts
all_posts = request.env['chennora.blog.post'].sudo().search([('active', '=', True)])
categories = {}
for p in all_posts:
if p.category:
categories[p.category] = categories.get(p.category, 0) + 1
return request.render('dine360_theme_chennora.blog_detail_layout', {
'blog_title': blog_post.title,
'blog_img': blog_post.image,
'blog_date': blog_post.date.strftime('%B %d, %Y') if blog_post.date else '',
'blog_category': blog_post.category,
'blog_content': blog_post.content,
'recent_posts': recent_posts,
'categories': categories,
})
# class BlogController(http.Controller):
# @http.route(['/blog'], type='http', auth='public', website=True)
# def blog_list(self, **post):
# blog_posts = request.env['chennora.blog.post'].sudo().search([('active', '=', True)])
# return request.render('dine360_theme_chennora.blog_page', {
# 'blog_posts': blog_posts,
# })
#
# @http.route(['/blog/<string:slug>'], type='http', auth='public', website=True)
# def blog_detail(self, slug, **post):
# blog_post = request.env['chennora.blog.post'].sudo().search([('slug', '=', slug), ('active', '=', True)], limit=1)
# if not blog_post:
# return request.not_found()
#
# recent_posts = request.env['chennora.blog.post'].sudo().search([('active', '=', True), ('id', '!=', blog_post.id)], limit=3)
#
# # Get dynamic categories and counts
# all_posts = request.env['chennora.blog.post'].sudo().search([('active', '=', True)])
# categories = {}
# for p in all_posts:
# if p.category:
# categories[p.category] = categories.get(p.category, 0) + 1
#
# return request.render('dine360_theme_chennora.blog_detail_layout', {
# 'blog_title': blog_post.title,
# 'blog_img': blog_post.image,
# 'blog_date': blog_post.date.strftime('%B %d, %Y') if blog_post.date else '',
# 'blog_category': blog_post.category,
# 'blog_content': blog_post.content,
# 'recent_posts': recent_posts,
# 'categories': categories,
# })

View File

@ -1,2 +1,2 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers

View File

@ -5,10 +5,11 @@
'category': 'Theme/Website',
'version': '1.0',
'author': 'Dine360',
'depends': ['website', 'website_sale', 'website_crm'],
'depends': ['website', 'website_sale', 'website_crm', 'website_blog'],
'data': [
'views/layout.xml',
'views/pages.xml',
'views/shop_page.xml',
'views/snippets.xml',
'data/website_data.xml',
],
@ -16,6 +17,7 @@
'web.assets_frontend': [
'theme_clicks2cart/static/src/scss/primary_variables.scss',
'theme_clicks2cart/static/src/scss/theme.scss',
'theme_clicks2cart/static/src/scss/shop.scss',
'theme_clicks2cart/static/src/js/quickview.js',
],
},

View File

@ -0,0 +1 @@
from . import main

View File

@ -0,0 +1,15 @@
from odoo import http
from odoo.http import request
class Clicks2CartBlog(http.Controller):
@http.route(['/blog'], type='http', auth='public', website=True)
def blog_list(self, **post):
# Fetch actual blog posts if website_blog is installed
# Otherwise, just show the template
blog_posts = []
if request.env.get('blog.post'):
blog_posts = request.env['blog.post'].sudo().search([('website_published', '=', True)], limit=10)
return request.render('theme_clicks2cart.blog_page', {
'blog_posts': blog_posts,
})

View File

@ -1,7 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- We will exclude website creation from XML for now to prevent module loading issues
and focus on getting the module visible first. Website can be created in UI -->
<function model="website.page" name="write">
<function model="website.page" name="search">
<value eval="[('url', '=', '/blog'), ('website_id', '=', 6)]"/>
</function>
<value eval="{'view_id': ref('theme_clicks2cart.blog_page'), 'is_published': True}"/>
</function>
<record id="blog_page_link" model="website.page">
<field name="url">/blog</field>
<field name="is_published">True</field>
<field name="view_id" ref="theme_clicks2cart.blog_page"/>
<field name="name">Blog</field>
<field name="website_id" eval="6"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,428 @@
/* Shop Page Decorative Backgrounds */
#wrap.oe_structure:has(.oe_website_sale) {
background-image:
url('https://landing.engotheme.com/html/jenstore/demo/img/who.png'),
url('https://landing.engotheme.com/html/jenstore/demo/img/who.png');
background-repeat: no-repeat, no-repeat;
background-position: left center, right bottom;
background-size: 40% auto, 30% auto;
background-attachment: fixed, absolute;
background-color: #fff;
padding-top: 50px;
}
/* Hide Odoo Default Controls & Redundant Bars */
.o_wsale_products_searchbar_formres,
.o_wsale_products_searchbar_form,
.o_wsale_products_searchbar,
.o_wsale_apply_layout,
.products_pager,
.o_website_sale_search,
#o_wsale_offcanvas_content {
display: none !important;
}
/* Sidebar Styles */
#products_grid_before {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
width: 25% !important;
padding-right: 40px;
flex: 0 0 25% !important;
max-width: 25% !important;
.sidebar_title {
font-family: 'Outfit', sans-serif !important;
text-transform: uppercase !important;
letter-spacing: 2px !important;
font-weight: 800 !important;
font-size: 1.1rem !important;
color: #000 !important;
margin-bottom: 25px !important;
display: block;
border-bottom: 3px solid #000;
padding-bottom: 12px;
width: 100%;
}
.s_sidebar_card {
border: none !important;
background: transparent !important;
margin-bottom: 45px;
padding: 0;
/* Category specific tweaks */
#wsale_products_categories_collapse {
margin-top: -10px;
}
.nav-pills {
.nav-item {
border-bottom: 1px solid #f2f2f2;
&:last-child { border-bottom: none; }
}
.nav-link {
padding: 12px 0;
color: #333;
font-size: 0.95rem;
font-family: 'Outfit', sans-serif;
transition: all 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
text-transform: capitalize;
letter-spacing: 0.5px;
font-weight: 600;
background: transparent !important;
display: flex;
justify-content: space-between;
align-items: center;
&:hover, &.active {
color: #e6b3a3 !important;
padding-left: 10px;
}
span.badge {
font-weight: 400;
color: #999;
font-size: 0.8rem;
background: none;
padding: 0;
}
/* Plus/Minus Icons */
&[data-bs-toggle="collapse"]:after {
font-family: 'FontAwesome';
content: '\f067';
font-size: 0.75rem;
color: #bbb;
transition: all 0.3s;
}
&[aria-expanded="true"]:after {
content: '\f068';
color: #000;
}
}
}
}
/* Attributes Styling (Color circles, Sizes) */
.js_attributes {
.nav-item {
border: none;
margin-bottom: 35px;
/* Attribute headers */
h6, strong {
@extend .sidebar_title;
font-size: 0.95rem !important;
margin-top: 20px;
}
/* Color circles */
ul.nav-pills {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 15px;
border: none !important;
li.nav-item {
margin: 0;
border: none !important;
label.nav-link {
border: 1px solid #eee;
border-radius: 50% !important;
width: 32px;
height: 32px;
padding: 0;
transition: all 0.3s;
position: relative;
&.active {
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #000;
}
input { display: none; }
&:after { content: none !important; }
}
}
}
}
}
/* Price Filter Styling */
.o_wsale_price_range_selector {
padding: 0 10px;
margin-top: 15px;
.ui_slider_handle {
background: #000 !important;
border: none !important;
width: 16px !important;
height: 16px !important;
top: -6px !important;
border-radius: 50% !important;
cursor: pointer;
}
.ui_slider_range {
background: #000 !important;
}
}
}
/* Redirection adjustments for main grid */
#products_grid {
flex: 0 0 75% !important;
max-width: 75% !important;
width: 75% !important;
padding-left: 20px;
}
/* Product Grid Top Bar */
.products_header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 25px;
background: #f4f4f4;
margin-bottom: 50px;
font-family: 'Outfit', sans-serif;
border-radius: 2px;
.products_header_left {
display: flex;
align-items: center;
gap: 25px;
.btn_sidebar_toggle {
background: #fff;
border: 1px solid #ddd;
color: #000;
padding: 7px 18px;
font-size: 0.75rem;
text-transform: uppercase;
font-weight: 800;
letter-spacing: 1.5px;
}
.view_mode_icons {
display: flex;
gap: 15px;
color: #bbb;
font-size: 1.1rem;
i {
cursor: pointer;
transition: color 0.3s;
&.active { color: #000; }
&:hover { color: #000; }
}
}
.results_count {
color: #666;
font-size: 0.9rem;
font-weight: 500;
}
}
.products_header_right {
display: flex;
align-items: center;
gap: 20px;
.show_filter, .sort_filter {
display: flex;
align-items: center;
gap: 10px;
span { font-size: 0.85rem; color: #888; font-weight: 500; }
}
.form-select, .btn-sm {
font-size: 0.85rem;
font-weight: 700;
color: #333;
background: transparent;
border: 1px solid #eee;
padding: 5px 15px;
}
}
}
/* Product Cards */
.oe_product {
margin-bottom: 60px;
.oe_product_cart {
border: none !important;
background: transparent !important;
.oe_product_image {
position: relative;
overflow: hidden;
background: #f7f7f7;
aspect-ratio: 1/1.2;
display: flex;
align-items: center;
justify-content: center;
img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 1s cubic-bezier(0.165, 0.84, 0.44, 1);
}
.s_product_label {
position: absolute;
top: 0;
right: 0;
padding: 6px 15px;
font-size: 0.65rem;
font-weight: 900;
letter-spacing: 2.5px;
color: #fff;
z-index: 10;
font-family: 'Outfit', sans-serif;
&.label-sale { background: #e6b3a3; }
&.label-hot { background: #fd8b6b; }
}
.s_product_actions {
position: absolute;
bottom: -70px;
left: 0;
right: 0;
display: flex;
justify-content: center;
gap: 15px;
padding: 20px;
background: rgba(255, 255, 255, 0.98);
transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1);
z-index: 5;
.action-btn {
width: 45px;
height: 45px;
background: #fff;
border: 1px solid #efefef;
display: flex;
align-items: center;
justify-content: center;
color: #111;
border-radius: 50%;
transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
text-decoration: none;
font-size: 1rem;
&:hover {
background: #000;
color: #fff;
border-color: #000;
transform: translateY(-8px);
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
}
}
}
&:hover {
.oe_product_image {
img {
transform: scale(1.15);
}
.s_product_actions {
bottom: 0;
}
}
}
section {
padding: 25px 0 10px;
text-align: center;
h6 {
font-family: 'Outfit', sans-serif;
font-weight: 700;
font-size: 1.15rem;
margin-bottom: 10px;
letter-spacing: 0.5px;
a {
color: #000;
text-decoration: none;
transition: color 0.3s;
&:hover { color: #e6b3a3; }
}
}
.o_rating_star_card {
color: #ffc107;
font-size: 0.85rem;
margin-bottom: 12px;
display: flex;
justify-content: center;
gap: 3px;
}
.product_price {
font-family: 'Outfit', sans-serif;
font-weight: 900;
font-size: 1.25rem;
color: #000;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
del {
font-size: 0.95rem;
color: #ccc;
font-weight: 500;
text-decoration: line-through;
}
}
}
}
}
/* Pagination */
.o_wsale_products_grid_table_wrapper {
.pagination {
margin-top: 70px;
justify-content: flex-end;
gap: 12px;
.page-item {
.page-link {
border: 1px solid #eee;
background: #fcfcfc;
color: #000;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 0.95rem;
border-radius: 4px !important;
transition: all 0.3s;
&:hover {
background: #eee;
color: #000;
}
}
&.active .page-link {
background: #000 !important;
color: #fff !important;
border-color: #000 !important;
}
}
}
}

View File

@ -1254,6 +1254,56 @@ footer#bottom.o_footer {
}
}
.s_holiday_section {
position: relative;
// padding: 120px 0;
overflow: hidden;
.s_holiday_bg_accent {
position: absolute;
top: -150px;
left: -100px;
width: 450px;
height: 450px;
background-image: url('https://landing.engotheme.com/html/jenstore/demo/img/floral_accent_tl.png');
background-size: contain;
background-repeat: no-repeat;
opacity: 0.1;
z-index: 1;
pointer-events: none;
transform: rotate(-15deg);
}
.s_holiday_bg_accent_br {
position: absolute;
bottom: -100px;
right: -80px;
width: 400px;
height: 400px;
background-image: url('https://landing.engotheme.com/html/jenstore/demo/img/decor-flower.png');
background-size: contain;
background-repeat: no-repeat;
opacity: 0.12;
z-index: 1;
pointer-events: none;
transform: scaleX(-1) rotate(30deg);
}
.s_holiday_splash {
position: absolute;
top: 10%;
left: -5%;
width: 40vw;
height: 60vh;
background-image: url('https://landing.engotheme.com/html/jenstore/demo/img/wedding_brush_splash.png');
background-size: contain;
background-repeat: no-repeat;
opacity: 0.04;
z-index: 0;
pointer-events: none;
}
}
// =========================================
// Product Action Hover Icons (Global)
// =========================================

View File

@ -405,8 +405,10 @@
<!-- Holiday Section -->
<section class="s_holiday_section">
<!-- Top accent floral -->
<!-- Elementor-style Decorations -->
<div class="s_holiday_bg_accent" aria-hidden="true"></div>
<div class="s_holiday_bg_accent_br" aria-hidden="true"></div>
<div class="s_holiday_splash" aria-hidden="true"></div>
<div class="container">
<!-- Section Header -->
@ -839,4 +841,72 @@
</xpath>
</field>
</record>
<!-- Blog Page Template -->
<template id="blog_page" name="Blog Page">
<t t-call="website.layout">
<div id="wrap" class="oe_structure">
<section class="s_blog_page_header py-5" style="background: #fafafa; border-bottom: 1px solid #eee;">
<div class="container text-center">
<h1 class="display-4 fw-bold mb-3" style="font-family: 'Playfair Display', serif; color: #333;">Our Blog</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb justify-content-center mb-0" style="background: transparent;">
<li class="breadcrumb-item"><a href="/" class="text-decoration-none" style="color: #e6b3a3;">Home</a></li>
<li class="breadcrumb-item active text-muted" aria-current="page">Blog</li>
</ol>
</nav>
</div>
</section>
<section class="s_blog_listing py-5" style="background: #fff;">
<div class="container">
<div class="row g-4">
<t t-if="not blog_posts">
<!-- Fallback Static Content -->
<t t-foreach="[1,2,3,4,5,6]" t-as="i">
<div class="col-lg-4 col-md-6">
<div class="s_blog_card mb-4" style="transition: transform 0.3s ease;">
<div class="s_blog_img_wrap overflow-hidden" style="height: 250px;">
<img t-attf-src="/theme_clicks2cart/static/src/img/blog_listing_#{i}.png"
onerror="this.src='/theme_clicks2cart/static/src/img/flora_col.png'"
class="img-fluid w-100 h-100 object-fit-cover"
alt="Blog"/>
</div>
<div class="s_blog_info p-4 border border-top-0">
<div class="s_blog_meta mb-2" style="font-size: 0.8rem; color: #888; font-family: 'Outfit';">
<span><i class="fa fa-calendar-o me-1"></i> May 12, 2024</span>
<span class="mx-2">|</span>
<span><i class="fa fa-user-o me-1"></i> Admin</span>
</div>
<h4 class="s_blog_name mb-3" style="font-family: 'Playfair Display'; font-weight: 700; color: #222; font-size: 1.25rem;">The Secret of Beautiful Roses</h4>
<p class="s_blog_desc mb-3 text-muted" style="font-size: 0.9rem; line-height: 1.6;">Discover how to keep your roses fresh for weeks with our simple yet effective care secrets.</p>
<a href="#" class="btn-link text-decoration-none fw-bold" style="color: #e6b3a3; font-size: 0.9rem; letter-spacing: 1px;">READ MORE <i class="fa fa-long-arrow-right ms-1"></i></a>
</div>
</div>
</div>
</t>
</t>
<t t-foreach="blog_posts" t-as="blog">
<div class="col-lg-4 col-md-6">
<div class="s_blog_card mb-4">
<div class="s_blog_img_wrap overflow-hidden" style="height: 250px;">
<img t-att-src="website.image_url(blog, 'image_1024')" class="img-fluid w-100 h-100 object-fit-cover" t-att-alt="blog.name"/>
</div>
<div class="s_blog_info p-4 border border-top-0">
<div class="s_blog_meta mb-2" style="font-size: 0.8rem; color: #888; font-family: 'Outfit';">
<span t-field="blog.post_date" t-options='{"widget": "date"}'/>
</div>
<h4 class="s_blog_name mb-3" style="font-family: 'Playfair Display'; font-weight: 700; color: #222; font-size: 1.25rem;" t-field="blog.name"/>
<p class="s_blog_desc mb-3 text-muted" style="font-size: 0.9rem; line-height: 1.6;" t-field="blog.subtitle"/>
<a t-attf-href="/blog/#{blog.id}" class="btn-link text-decoration-none fw-bold" style="color: #e6b3a3; font-size: 0.9rem; letter-spacing: 1px;">READ MORE <i class="fa fa-long-arrow-right ms-1"></i></a>
</div>
</div>
</div>
</t>
</div>
</div>
</section>
</div>
</t>
</template>
</odoo>

View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Shop Page Banner & Layout Refinement -->
<template id="shop_banner_inherit" inherit_id="website_sale.products" name="Shop Banner">
<!-- Force Sidebar Visibility and Layout -->
<xpath expr="//div[contains(@class, 'o_wsale_products_main_row')]" position="attributes">
<attribute name="class" add="d-flex flex-row flex-wrap" separator=" "/>
</xpath>
<xpath expr="//*[@id='products_grid_before']" position="attributes">
<attribute name="class" remove="d-none" separator=" "/>
<attribute name="class" add="col-lg-3 d-block pe-4" separator=" "/>
</xpath>
<xpath expr="//*[@id='products_grid']" position="attributes">
<attribute name="class" remove="col-lg-12" separator=" "/>
<attribute name="class" add="col-lg-9" separator=" "/>
</xpath>
<!-- Customize Top Bar -->
<xpath expr="//div[hasclass('o_wsale_products_main_row')]" position="before">
<div class="products_header">
<div class="products_header_left">
<button class="btn btn_sidebar_toggle d-lg-none" data-bs-toggle="collapse" data-bs-target="#products_grid_before">
Show Sidebar
</button>
<div class="view_mode_icons d-none d-lg-flex">
<i class="fa fa-th active"/>
<i class="fa fa-list"/>
</div>
<div class="results_count d-none d-md-block">
<span>Showing 1 - 12 of 28 results</span>
</div>
</div>
<div class="products_header_right">
<div class="show_filter">
<span>Show</span>
<select class="form-select form-select-sm">
<option>12</option>
<option>24</option>
</select>
</div>
<div class="sort_filter">
<div class="dropdown">
<button class="btn btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
Default sorting
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Price: Low to High</a></li>
<li><a class="dropdown-item" href="#">Price: High to Low</a></li>
</ul>
</div>
</div>
</div>
</div>
</xpath>
</template>
<!-- Customize Product Item -->
<template id="shop_product_item_inherit" inherit_id="website_sale.products_item" name="Shop Product Item">
<xpath expr="//form" position="replace">
<t t-set="combination_info" t-value="product._get_combination_info(only_template=True, add_qty=add_qty or 1)"/>
<div class="oe_product_cart" t-att-data-publish="product.website_published and 'on' or 'off'" itemscope="itemscope" itemtype="http://schema.org/Product">
<div class="oe_product_image position-relative">
<!-- Badges -->
<t t-if="product.website_id.id % 2 == 0">
<span class="s_product_label label-sale">SALE</span>
</t>
<t t-else="">
<span class="s_product_label label-hot">HOT</span>
</t>
<a t-att-href="product_href" class="d-block h-100" itemprop="url">
<t t-set="image_holder" t-value="product._get_image_holder()"/>
<img t-att-src="website.image_url(image_holder, 'image_512')" class="img-fluid d-block mx-auto" t-att-alt="product.name" loading="lazy"/>
</a>
<!-- Hover Actions -->
<div class="s_product_actions">
<a href="#" class="action-btn quick-view"
t-att-data-name="product.name"
t-att-data-price="combination_info['price']"
t-att-data-img="website.image_url(product, 'image_1024')"
t-att-data-desc="product.description_sale or 'Premium floral arrangement for your special occasions.'">
<i class="fa fa-eye"></i>
</a>
<a href="#" class="action-btn add-to-cart"><i class="fa fa-shopping-cart"></i></a>
<a href="#" class="action-btn wishlist"><i class="fa fa-heart-o"></i></a>
</div>
</div>
<section>
<div class="o_rating_star_card">
<i class="fa fa-star"/>
<i class="fa fa-star"/>
<i class="fa fa-star"/>
<i class="fa fa-star"/>
<i class="fa fa-star-o"/>
</div>
<h6>
<a t-att-href="product_href" itemprop="name" t-field="product.name"/>
</h6>
<div class="product_price" itemprop="offers" itemscope="itemscope" itemtype="http://schema.org/Offer">
<span t-if="combination_info['price']" t-esc="combination_info['price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
<del t-if="combination_info['has_discounted_price']" t-esc="combination_info['list_price']" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
</div>
</section>
</div>
</xpath>
</template>
<!-- Upgrade Sidebar UI: Categories -->
<template id="shop_sidebar_categories_inherit" inherit_id="website_sale.products_categories" name="Shop Sidebar Categories">
<xpath expr="//div[@id='wsale_products_categories_collapse']" position="before">
<h6 class="sidebar_title">Categories</h6>
</xpath>
<xpath expr="//div[@id='wsale_products_categories_collapse']" position="attributes">
<attribute name="class" add="s_sidebar_card" separator=" "/>
</xpath>
</template>
<!-- Upgrade Sidebar UI: Attributes/Filters -->
<template id="shop_sidebar_attributes_inherit" inherit_id="website_sale.products_attributes" name="Shop Sidebar Attributes">
<xpath expr="//form" position="attributes">
<attribute name="class" add="s_sidebar_card" separator=" "/>
</xpath>
<xpath expr="//form//t[1]" position="before">
<!-- This will be styled via CSS if needed, or we can add individual headers -->
</xpath>
</template>
</odoo>