Add custom product details layout and shop page enhancements

- Introduced a new product details layout in `product_details_page.xml` with styled elements and hidden original Odoo components for a premium look.
- Enhanced the product form view in `product_views.xml` to include a new field for marking popular deals.
- Updated the configuration settings view in `res_config_settings_views.xml` to add a delivery option toggle during checkout.
- Reorganized the shop page layout in `shop_page.xml` to include dynamic categories and attributes, along with a new arrivals section.
- Created a new snippets file `snippets.xml` for potential future custom drag-and-drop blocks.
- Added a script in `parse_content.py` for parsing specific content from a markdown file.
This commit is contained in:
Alaguraj0361 2026-06-08 19:46:45 +05:30
parent a1734b5d08
commit 42ff2972e4
182 changed files with 5886 additions and 4 deletions

View File

@ -1,11 +1,10 @@
from odoo import api, SUPERUSER_ID
def uninstall_hook(cr, registry):
def uninstall_hook(env):
"""
Synchronized uninstallation: When Dine360 Restaurant Suite is uninstalled,
automatically trigger uninstallation for all its core sub-modules.
"""
env = api.Environment(cr, SUPERUSER_ID, {})
modules_to_uninstall = [
'dine360_dashboard',
'dine360_restaurant',
@ -22,5 +21,4 @@ def uninstall_hook(cr, registry):
if modules:
# Mark modules for uninstallation
# button_immediate_uninstall will trigger the full process including registry reload
modules.button_immediate_uninstall()
modules.button_uninstall()

View File

@ -0,0 +1,2 @@
# Meta module for Dine360 Shivasakthi
from .hooks import uninstall_hook

View File

@ -0,0 +1,44 @@
{
'name': 'Dine360 Shivasakthi Restaurant Suite',
'version': '1.0.0',
'license': 'LGPL-3',
'category': 'Website',
'summary': 'Installs all Dine360 Shivasakthi Restaurant modules',
'author': 'Dine360',
'depends': [
'dine360_restaurant',
'dine360_order_channels',
'dine360_dashboard',
'dine360_theme_shivasakthi',
'dine360_kds',
'dine360_reservation',
'dine360_uber',
'dine360_recipe',
'dine360_self_order',
'dine360_online_orders',
'dine360_pos_navbar',
'mail',
'calendar',
'contacts',
'crm',
'sale_management',
'board',
'point_of_sale',
'account',
'website',
'purchase',
'stock',
'hr',
],
'uninstall_hook': 'uninstall_hook',
'data': [
'views/apps_kanban_menu.xml',
],
'assets': {
'web.assets_backend': [
'Dine360_Shivasakthi/static/src/css/apps_kanban_fix.css',
],
},
'installable': True,
'application': True,
}

View File

@ -0,0 +1,24 @@
from odoo import api, SUPERUSER_ID
def uninstall_hook(env):
"""
Synchronized uninstallation: When Dine360 Shivasakthi Restaurant Suite is uninstalled,
automatically trigger uninstallation for all its core sub-modules.
"""
modules_to_uninstall = [
'dine360_dashboard',
'dine360_restaurant',
'dine360_theme_shivasakthi',
'dine360_kds',
'dine360_reservation'
]
# Search for these modules if they are installed
modules = env['ir.module.module'].search([
('name', 'in', modules_to_uninstall),
('state', '=', 'installed')
])
if modules:
# Mark modules for uninstallation
modules.button_uninstall()

View File

@ -0,0 +1,31 @@
/* Fix Apps kanban icon sizing and dropdown overlap */
.o_modules_kanban .oe_module_vignette,
.o_modules_kanban .o_kanban_record {
overflow: visible;
}
.o_modules_kanban .o_kanban_record {
position: relative;
z-index: 1;
}
.o_modules_kanban .oe_module_icon {
width: 64px;
height: 64px;
display: flex;
align-items: center;
justify-content: center;
}
.o_modules_kanban .oe_module_icon img {
width: 64px;
height: 64px;
object-fit: contain;
}
.o_modules_kanban .oe_module_vignette .dropdown-menu {
right: 0;
left: auto;
min-width: 160px;
z-index: 1060;
}

View File

@ -0,0 +1,15 @@
<odoo>
<data>
<template id="dine360_apps_kanban_menu_shivasakthi" inherit_id="base.module_view_kanban" name="Dine360 Apps Kanban Menu Shivasakthi">
<xpath expr="//t[@t-name='kanban-menu']" position="replace">
<t t-name="kanban-menu">
<t t-set="installed" t-value="record.state.raw_value == 'installed'"/>
<a type="edit" class="dropdown-item">Module Info</a>
<a t-if="record.website.raw_value" role="menuitem" class="dropdown-item o-hidden-ios" t-att-href="record.website.raw_value" target="_blank">Learn More</a>
<a t-if="installed" name="button_immediate_upgrade" type="object" role="menuitem" class="dropdown-item" groups="base.group_system">Upgrade</a>
<a t-if="installed" name="button_uninstall_wizard" type="object" role="menuitem" class="dropdown-item" groups="base.group_system">Uninstall</a>
</t>
</xpath>
</template>
</data>
</odoo>

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import controllers
from . import models

View File

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
{
'name': 'Dine360 Theme Shivasakthi',
'description': 'Custom theme for Dine360 Shivasakthi website',
'category': 'Theme/Website',
'version': '1.0',
'author': 'Dine360',
'depends': ['website', 'website_sale', 'website_crm'],
'data': [
'views/layout.xml', # Header, Footer, and Global Layout changes
'views/pages.xml', # specific page content (Home, About, etc.)
'views/about_page.xml', # About Page
'views/contact_page.xml',# Contact Page
'views/faq_page.xml', # FAQ Page
'views/blog_page.xml', # Blog Page
'views/blog_detail_page.xml', # Blog Detail Page
'views/shop_page.xml', # Shop Page
'views/checkout_address.xml', # Checkout Address page (remove Company/VAT)
'views/product_details_page.xml', # Customized Product Details Page
'views/product_views.xml', # Product Form custom fields
'views/snippets.xml', # Custom Building Blocks
'views/options.xml', # Options for snippets (if any)
'views/res_config_settings_views.xml', # Backend Settings
'data/website_data.xml', # Data for creating pages
],
'assets': {
'web.assets_frontend': [
'dine360_theme_shivasakthi/static/src/scss/primary_variables.scss',
'dine360_theme_shivasakthi/static/src/scss/theme.scss',
'dine360_theme_shivasakthi/static/src/scss/shop.scss',
'dine360_theme_shivasakthi/static/src/js/deal_switcher.js',
'dine360_theme_shivasakthi/static/src/js/category_slider.js',
],
},
'images': [
'static/description/cover.png',
],
'installable': True,
'application': True,
'license': 'LGPL-3',
}

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import main

View File

@ -0,0 +1,131 @@
from markupsafe import Markup
import json
import os
from odoo import http
from odoo.http import request
# Helper to load blogs from JSON
def get_blogs():
json_path = os.path.join(os.path.dirname(__file__), '../data/blogs.json')
if os.path.exists(json_path):
with open(json_path, 'r', encoding='utf-8-sig') as f:
blogs = json.load(f)
# Reversing so the last added blog appears first
return blogs[::-1] if isinstance(blogs, list) else blogs
return []
class ContactController(http.Controller):
@http.route('/contactus/submit', type='http', auth="public", website=True, csrf=True)
def contact_submit(self, **post):
import logging
_logger = logging.getLogger(__name__)
name = post.get('name')
email = post.get('email_from')
phone = post.get('phone')
subject = post.get('subject')
message = post.get('description')
# Format the email content
email_content = f"""
<div style="font-family: Arial, sans-serif; padding: 20px; border: 1px solid #ddd; border-radius: 8px;">
<h2 style="color: #ffb800; border-bottom: 2px solid #ffb800; padding-bottom: 10px;">New Contact Form Submission - Shiva Sakthi</h2>
<table style="width: 100%; border-collapse: collapse; margin-top: 15px;">
<tr>
<td style="padding: 8px; font-weight: bold; width: 30%;">Full Name:</td>
<td style="padding: 8px;">{name}</td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">Email:</td>
<td style="padding: 8px;">{email}</td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">Phone:</td>
<td style="padding: 8px;">{phone}</td>
</tr>
<tr>
<td style="padding: 8px; font-weight: bold;">Subject:</td>
<td style="padding: 8px;">{subject}</td>
</tr>
</table>
<div style="margin-top: 20px; padding: 15px; background-color: #F8F9FA; border-left: 4px solid #ffb800;">
<strong style="display: block; margin-bottom: 10px;">Message:</strong>
<div style="white-space: pre-wrap;">{message}</div>
</div>
</div>
"""
company_email = request.env['res.company'].sudo().search([], limit=1).email or 'info@shivasakthi.ca'
mail_values = {
'subject': f"Contact Form: {subject or 'Inquiry'} from {name}",
'body_html': email_content,
'email_to': 'info@shivasakthi.ca',
'email_from': company_email,
'reply_to': email,
}
# Create and send the mail
try:
mail = request.env['mail.mail'].sudo().create(mail_values)
mail.send()
_logger.info("Contact form email sent successfully to %s", mail_values['email_to'])
except Exception as e:
_logger.error("Failed to send contact form email: %s", str(e))
return request.render('dine360_theme_shivasakthi.contact_thank_you')
class BlogController(http.Controller):
@http.route(['/blog', '/blog/page/<int:page>'], type='http', auth='public', website=True)
def blog_list(self, page=1, **post):
all_blogs = get_blogs()
# Pagination Settings
per_page = 12
total = len(all_blogs)
# Odoo's built-in pager
pager = request.website.pager(
url='/blog',
total=total,
page=page,
step=per_page,
scope=5,
url_args=post,
)
# Extract blogs for current page
start = (page - 1) * per_page
end = start + per_page
blogs_paged = all_blogs[start:end]
return request.render('dine360_theme_shivasakthi.blog_page', {
'blog_posts': blogs_paged,
'pager': pager,
})
@http.route(['/blog/<string:slug>'], type='http', auth='public', website=True)
def blog_detail(self, slug, **post):
all_blogs = get_blogs()
blog_post = next((b for b in all_blogs if b['slug'] == slug), None)
if not blog_post:
return request.not_found()
recent_posts = [b for b in all_blogs if b['slug'] != slug][:3]
# Get categories and counts
categories = {}
for b in all_blogs:
cat = b.get('category', 'Restaurant')
categories[cat] = categories.get(cat, 0) + 1
return request.render('dine360_theme_shivasakthi.blog_detail_layout', {
'blog_title': blog_post.get('title'),
'blog_img': blog_post.get('image'),
'blog_date': blog_post.get('date'),
'blog_category': blog_post.get('category'),
'blog_content': Markup(blog_post.get('content') or ''),
'recent_posts': recent_posts,
'categories': categories,
})

View File

@ -0,0 +1,38 @@
[
{
"id": 1,
"title": "The Secrets of Crafting the Perfect Crispy Dosa",
"slug": "secrets-perfect-crispy-dosa",
"image": "/dine360_theme_shivasakthi/static/src/img/assets/images/blog/blog_dosa_making.jpg",
"category": "Recipe",
"date": "May 15, 2026",
"day": "15",
"month": "May",
"excerpt": "Discover the culinary art behind Shiva Sakthi's signature dosas. Learn how our chefs achieve the perfect crispiness using age-old fermentation techniques and spices.",
"content": "<p>At Shiva Sakthi Restaurant, the dosa isn't just a dish; it's a craft perfected over decades. The secret to our golden, paper-thin crispy dosas lies in the fermentation process. Our chefs soak premium quality parboiled rice and urad dal, grinding them to a silky batter that is fermented for exactly 12 hours at a precise temperature.</p><p>Cooked on a heavy iron griddle seasoned with sesame oil, each dosa is hand-spread in quick circular motions. Whether you enjoy the classic Masala Dosa or our fiery Mysore Dosa, every bite delivers the authentic taste of South India.</p>"
},
{
"id": 2,
"title": "Why Our Unlimited Thali is Mississauga's Favorite Lunch Choice",
"slug": "unlimited-thali-mississauga-favorite",
"image": "/dine360_theme_shivasakthi/static/src/img/assets/images/blog/blog_thali_experience.jpg",
"category": "Dining",
"date": "June 1, 2026",
"day": "01",
"month": "Jun",
"excerpt": "A feast fit for royalty! Read about the journey of our Unlimited South Indian Thali, featuring a harmonious blend of curries, rasam, and fresh payasam.",
"content": "<p>For anyone craving a comprehensive South Indian feast, the Shiva Sakthi Unlimited Thali is the ultimate experience. Served on a traditional banana leaf, it offers a rotating menu of up to 10 authentic items, including Sambar, Rasam, Kara Kuzhambu, Poriyal, Kootu, and crunchy appalams, ending with a sweet, rich Payasam.</p><p>We believe in hospitality that makes you feel at home. That's why our thali is served with unlimited refills of rice and curries, cooked fresh every day with locally sourced ingredients and spices imported from South India.</p>"
},
{
"id": 3,
"title": "A Guide to South Indian Spices and Their Health Benefits",
"slug": "guide-south-indian-spices-benefits",
"image": "/dine360_theme_shivasakthi/static/src/img/assets/images/blog/blog_south_indian_spices.jpg",
"category": "Health",
"date": "June 7, 2026",
"day": "07",
"month": "Jun",
"excerpt": "Explore the medicinal and culinary secrets of mustard seeds, curry leaves, and asafoetida that give our dishes their rich flavor profile.",
"content": "<p>South Indian cuisine is celebrated not just for its rich aromas but also for its digestive and therapeutic benefits. Key spices such as turmeric, mustard seeds, fenugreek, and curry leaves are rich in antioxidants and aid digestion.</p><p>At Shiva Sakthi, we use these spices mindfully. Tempered in hot oil at the end of cooking (a process called *tadka*), they release essential oils that enhance the taste and wellness of our sambars, curries, and chutneys.</p>"
}
]

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Create the /about, /faq, /blog pages explicitly and link them to views -->
<record id="about_page_shivasakthi" model="website.page">
<field name="url">/about</field>
<field name="is_published">True</field>
<field name="view_id" ref="dine360_theme_shivasakthi.about_page"/>
<field name="name">About Us</field>
<field name="website_indexed" eval="True"/>
</record>
<record id="faq_page_shivasakthi" model="website.page">
<field name="url">/faq</field>
<field name="is_published">True</field>
<field name="view_id" ref="dine360_theme_shivasakthi.faq_page"/>
<field name="name">FAQ</field>
<field name="website_indexed" eval="True"/>
</record>
<record id="blog_page_shivasakthi" model="website.page">
<field name="url">/blog</field>
<field name="is_published">True</field>
<field name="view_id" ref="dine360_theme_shivasakthi.blog_page"/>
<field name="name">Blog</field>
<field name="website_indexed" eval="True"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import product_template
from . import res_config_settings
from . import website

View File

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
from odoo import models, fields
class ProductTemplate(models.Model):
_inherit = 'product.template'
is_popular_deal = fields.Boolean(
string='Is Popular Deal',
default=False,
help='Check this to show this product in Popular Deals section on Homepage'
)

View File

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
from odoo import models, fields
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
enable_delivery_option = fields.Boolean(
related='website_id.enable_delivery_option',
readonly=False,
string='Enable Delivery Option'
)

View File

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
import json
import os
from odoo import models, fields, modules
class Website(models.Model):
_inherit = 'website'
enable_delivery_option = fields.Boolean(
string='Enable Delivery Option',
default=True,
help='If disabled, customers will not see the Delivery option on the checkout page.'
)
def get_recent_blogs(self, limit=3):
"""Reads blog posts from blogs.json and returns the latest slice."""
addon_path = modules.get_module_path('dine360_theme_shivasakthi')
json_path = os.path.join(addon_path, 'data', 'blogs.json')
if os.path.exists(json_path):
try:
with open(json_path, 'r', encoding='utf-8-sig') as f:
blogs = json.load(f)
if isinstance(blogs, list):
return blogs[::-1][:limit]
return blogs[:limit] if isinstance(blogs, list) else []
except Exception:
return []
return []

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 88 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 365 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 906 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Some files were not shown because too many files have changed in this diff Show More