O9: align Admissions, Contact Us and Login with the homepage design, real Metatroncube logo/favicon, and a chain of placeholder-content leaks found underneath
Per discussion: the marketing homepage looked polished but the rest of the public site (the admission form, contact us, the backend login screen) was still bare stock Odoo, and the site nav still showed stock Odoo's own default logo/favicon rather than Metatroncube's real ones. This brings the same design system (Manrope + Work Sans, the navy/indigo palette) to all three pages and wires up the two supplied brand assets - static/img/logo.png (the wordmark, used as website.logo, so it renders in every page's nav automatically) and static/img/favicon.png (the cube mark, website.favicon). Loaded via a post_init_hook (hooks.py) rather than a plain <record>: website's two stock demo sites (website.default_website, website.website2) are both noupdate=True, and Python is the tool that isn't gated by that the way a <record>/<function> tag would be on an already-installed database (see hooks.py's own docstring for the exact mechanism and its fresh-install-only limitation). Admissions keeps its exact form markup/field names/model untouched - only the surrounding hero and "what happens next" strip are new, added via position="before"/"after" around the existing <section> so the stock s_website_form JS widget this page depends on is never touched. Restyling contact us and the login screen surfaced a chain of real, separate placeholder-content bugs, each found by looking at the next page down rather than assuming the previous fix was complete: 1. website.contactus's sidebar hardcodes "My Company" and a fake US street address - shared/DEMO_SCRIPT.md explicitly bans placeholder content, and it had been live here the whole time. Reading res_company fields instead means it shows whichever school is actually active, same as the admission form's own live program list. 2. website.footer_custom - the footer on EVERY page, not just contact us - carries the identical yourcompany.example.com / +1 555-555-5556 placeholder, plus a "Products/Services/Legal" link list to nowhere and dead social icons. Fixing #1 alone would have left this sitting directly underneath it on every single page. 3. website.header_text_element, the nav bar's own phone/email widget, carries the same fake number - a fourth, separate occurrence, this time in the header. First pass here fixed only its "phone_mail" variant, assumed (wrongly) to be what this site's header actually uses; the live page still showed the fake number afterward. It turned out both nav slots (desktop, mobile) use the plain default branch instead - caught only by re-checking the real page rather than trusting that assumption, and fixed every variant that carries contact info this time, not just the one guessed at. 4. Underneath all three page-level fixes: base.main_company itself still carried stock Odoo's own demo phone (+1 555-555-5556) and email (info@yourcompany.com) - the page-level fixes above only surfaced this because they read real company data for the first time; the data itself needed fixing too. Real Indian/Canadian contact details added to both demo companies in mc_theme_demo.xml. 5. web.login_layout hardcodes its own separate "Powered by Odoo" link - a third distinct occurrence, different from the portal sidebar text and the web.brand_promotion badge already fixed, reached by the very first screen anyone doing this demo sees. Full suite (all 9 modules + web_responsive) re-verified together after every fix in this chain: 0 failed, 0 error(s), tests up to 93. One correction to this session's own earlier work: mc_cast_users_demo.xml claimed <function> "isn't subject to" the noupdate/mode guard that blocks a plain <record> on an already-installed database - false, odoo/tools/convert.py's _tag_function has the exact same "if self.noupdate and self.mode != 'init': return" check. What actually fixed that earlier bug was applying the change as a one-off manual Python snippet, not the tag choice - the same manual-application pattern used again here for base.main_company's contact fields on the already-running school_demo database. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
ff96bf0f81
commit
cf656377ef
@ -1,5 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Same type pairing as mc_education_theme's marketing homepage
|
||||
(Manrope for headings, Work Sans for body) so a visitor moving
|
||||
from the homepage to this page doesn't land on a visibly
|
||||
different, unstyled product. Loaded here too, not only from
|
||||
theme, so this module still looks intentional if installed on
|
||||
its own - matching CLAUDE.md's "each addon installs
|
||||
independently" rule; the browser dedupes the identical URL if
|
||||
both happen to be loaded on the same site. -->
|
||||
<template id="admission_fonts" inherit_id="website.layout" name="Admission form fonts">
|
||||
<xpath expr="//head" position="inside">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@600;700;800&family=Work+Sans:wght@400;500;600;700&display=swap" rel="stylesheet"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
Hand-written to match the DOM structure/classes of Odoo core's own
|
||||
s_website_form snippet (website/views/snippets/s_website_form.xml)
|
||||
@ -9,13 +25,86 @@
|
||||
Odoo's generic /website/form/<model> controller; see
|
||||
data/mc_applicant_website_form_data.xml for the field whitelist
|
||||
that is the actual security boundary on that path.
|
||||
|
||||
The hero band and "what happens next" strip around the form are
|
||||
presentation only, matching the site design system - they do not
|
||||
touch a single field, name or class inside <form>, since that
|
||||
exact markup is what the stock form.js widget depends on.
|
||||
-->
|
||||
<template id="admission_apply_page" name="Admission Application">
|
||||
<t t-call="website.layout">
|
||||
<div id="wrap">
|
||||
<div id="wrap" class="mc-admissions">
|
||||
<style>
|
||||
.mc-admissions{ --navy:#131B2E; --navy-2:#1C2A46; --indigo:#4B3F92; --indigo-2:#2E63B8;
|
||||
--paper:#F6F7FB; --card:#FFFFFF; --line:#E4E7F0; --ink:#16192B; --ink-soft:#5B6178;
|
||||
--shadow:0 1px 2px rgba(19,27,46,.06), 0 14px 34px -16px rgba(19,27,46,.22);
|
||||
font-family:'Work Sans', system-ui, sans-serif; color:var(--ink); background:var(--paper);
|
||||
}
|
||||
.mc-admissions *{ box-sizing:border-box; }
|
||||
.mc-admissions h1, .mc-admissions h2, .mc-admissions h3{
|
||||
font-family:'Manrope', system-ui, sans-serif; font-weight:800; letter-spacing:-.01em;
|
||||
color:var(--ink); text-wrap:balance; margin:0;
|
||||
}
|
||||
.mc-adm-hero{
|
||||
background: radial-gradient(900px 380px at 90% -10%, rgba(75,63,146,.5), transparent 60%),
|
||||
linear-gradient(180deg, var(--navy), var(--navy-2));
|
||||
color:#fff; padding:52px 24px 60px; text-align:center;
|
||||
}
|
||||
.mc-adm-hero .pill{
|
||||
display:inline-block; font-size:.72rem; letter-spacing:.08em; text-transform:uppercase;
|
||||
font-weight:600; color:#EFE9FF; background:linear-gradient(90deg, var(--indigo), var(--indigo-2));
|
||||
padding:6px 14px; border-radius:20px; margin-bottom:16px;
|
||||
}
|
||||
.mc-adm-hero h1{ color:#fff; font-size:clamp(1.6rem,3.4vw,2.3rem); max-width:20ch; margin:0 auto; }
|
||||
.mc-adm-hero p{ color:#C4C9E2; max-width:52ch; margin:14px auto 0; font-size:1rem; line-height:1.6; }
|
||||
|
||||
.mc-adm-body{ max-width:900px; margin:-30px auto 0; padding:0 20px 64px; position:relative; }
|
||||
.mc-adm-card{
|
||||
background:var(--card); border:1px solid var(--line); border-radius:16px;
|
||||
box-shadow:var(--shadow); padding:36px 32px;
|
||||
}
|
||||
@media (max-width:600px){ .mc-adm-card{ padding:26px 18px; } }
|
||||
.mc-adm-card h1{ font-size:1.4rem; margin-bottom:6px; }
|
||||
.mc-adm-card .lede{ color:var(--ink-soft); margin-bottom:26px; font-size:.94rem; }
|
||||
|
||||
.mc-admissions .s_website_form_label{ font-weight:600; color:var(--ink); font-size:.88rem; }
|
||||
.mc-admissions .s_website_form_mark{ color:var(--indigo); }
|
||||
.mc-admissions .form-control, .mc-admissions .form-select{
|
||||
border-color:var(--line); border-radius:9px; padding:10px 12px; font-family:'Work Sans', sans-serif;
|
||||
}
|
||||
.mc-admissions .form-control:focus, .mc-admissions .form-select:focus{
|
||||
border-color:var(--indigo); box-shadow:0 0 0 3px rgba(75,63,146,.15);
|
||||
}
|
||||
.mc-admissions .btn-primary{
|
||||
background:linear-gradient(90deg, var(--indigo), var(--indigo-2)); border:none;
|
||||
padding:11px 26px; border-radius:9px; font-weight:600;
|
||||
}
|
||||
.mc-admissions .btn-primary:hover{ filter:brightness(1.08); }
|
||||
|
||||
.mc-adm-steps{ max-width:900px; margin:0 auto; padding:0 20px 70px; }
|
||||
.mc-adm-steps h2{ font-size:1.1rem; text-align:center; margin-bottom:24px; }
|
||||
.mc-steps-row{ display:grid; grid-template-columns:repeat(5,1fr); gap:10px; }
|
||||
@media (max-width:760px){ .mc-steps-row{ grid-template-columns:repeat(2,1fr); } }
|
||||
.mc-step{ background:var(--card); border:1px solid var(--line); border-radius:12px; padding:16px 12px; text-align:center; }
|
||||
.mc-step .n{
|
||||
width:26px; height:26px; border-radius:50%; background:var(--indigo); color:#fff;
|
||||
display:flex; align-items:center; justify-content:center; margin:0 auto 8px;
|
||||
font-family:'Manrope',sans-serif; font-weight:700; font-size:.8rem;
|
||||
}
|
||||
.mc-step .t{ font-size:.82rem; font-weight:600; color:var(--ink); }
|
||||
</style>
|
||||
|
||||
<div class="mc-adm-hero">
|
||||
<span class="pill">Admissions Open</span>
|
||||
<h1>Apply for Admission</h1>
|
||||
<p>Fill in your child's details, attach a birth certificate, and submit - no office visit needed. You'll get an application number the moment you submit, and can track its progress from Applied through to Enrolled.</p>
|
||||
</div>
|
||||
|
||||
<div class="mc-adm-body">
|
||||
<div class="mc-adm-card">
|
||||
<section class="s_website_form pt32 pb32" data-snippet="s_website_form">
|
||||
<div class="container-fluid">
|
||||
<h1 class="mb-4">Apply for Admission</h1>
|
||||
<h1 class="mb-4">Application Form</h1>
|
||||
<form action="/website/form/" method="post" enctype="multipart/form-data"
|
||||
class="o_mark_required" data-mark="*"
|
||||
data-model_name="mc.applicant"
|
||||
@ -134,6 +223,19 @@
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mc-adm-steps">
|
||||
<h2>What happens after you submit</h2>
|
||||
<div class="mc-steps-row">
|
||||
<div class="mc-step"><div class="n">1</div><div class="t">Applied</div></div>
|
||||
<div class="mc-step"><div class="n">2</div><div class="t">Document Verification</div></div>
|
||||
<div class="mc-step"><div class="n">3</div><div class="t">Interview</div></div>
|
||||
<div class="mc-step"><div class="n">4</div><div class="t">Offered</div></div>
|
||||
<div class="mc-step"><div class="n">5</div><div class="t">Enrolled</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
# No models: every branding field this module configures (res.company
|
||||
# logo/name/primary_color/secondary_color/currency, res.partner address)
|
||||
# already exists in stock Odoo. This module is data (demo/) and one view
|
||||
# override (views/), nothing more.
|
||||
# already exists in stock Odoo. This module is data (demo/) and view
|
||||
# overrides (views/), plus one post_init_hook (hooks.py) for the
|
||||
# website logo/favicon - see hooks.py for why that can't be a plain
|
||||
# XML <record>.
|
||||
from .hooks import post_init_hook
|
||||
|
||||
@ -19,6 +19,9 @@
|
||||
"data": [
|
||||
"views/portal_branding_templates.xml",
|
||||
"views/homepage_templates.xml",
|
||||
"views/contactus_templates.xml",
|
||||
"views/login_templates.xml",
|
||||
"views/footer_templates.xml",
|
||||
],
|
||||
"demo": [
|
||||
"demo/mc_theme_demo.xml",
|
||||
@ -26,4 +29,5 @@
|
||||
],
|
||||
"installable": True,
|
||||
"application": False,
|
||||
"post_init_hook": "post_init_hook",
|
||||
}
|
||||
|
||||
@ -25,10 +25,24 @@
|
||||
run, never as a later "apply a theme" change. The brand-new
|
||||
second company below has no such history, so its currency is
|
||||
set safely at creation. -->
|
||||
<!-- phone/email/street: found missing while restyling the public
|
||||
website - the contact us page and site footer now genuinely
|
||||
read these fields live (see mc_education_theme's
|
||||
contactus_templates.xml/footer_templates.xml), which is
|
||||
exactly what surfaced that this record was still carrying
|
||||
stock Odoo's own demo company's placeholder phone
|
||||
(+1 555-555-5556), email (info@yourcompany.com) and a fake US
|
||||
street address underneath the parts that WERE branded (name,
|
||||
city, colours, logo). shared/DEMO_SCRIPT.md bans placeholder
|
||||
content outright; a page that reads real data doesn't fix that
|
||||
on its own if the data underneath is still fake. -->
|
||||
<record id="base.main_company" model="res.company">
|
||||
<field name="name">St. Aloysius Public School</field>
|
||||
<field name="country_id" ref="base.in"/>
|
||||
<field name="street">45 Avinashi Road</field>
|
||||
<field name="city">Coimbatore</field>
|
||||
<field name="country_id" ref="base.in"/>
|
||||
<field name="phone">+91 422 435 6789</field>
|
||||
<field name="email">info@staloysiuscbe.edu.in</field>
|
||||
<field name="primary_color">#1B3A5C</field>
|
||||
<field name="secondary_color">#D4A017</field>
|
||||
<field name="logo" type="base64" file="mc_education_theme/static/demo/logo_st_aloysius.png"/>
|
||||
@ -36,9 +50,12 @@
|
||||
|
||||
<record id="demo_company_waterloo_heights" model="res.company">
|
||||
<field name="name">Waterloo Heights Academy</field>
|
||||
<field name="currency_id" ref="base.CAD"/>
|
||||
<field name="country_id" ref="base.ca"/>
|
||||
<field name="street">75 University Avenue West</field>
|
||||
<field name="city">Waterloo</field>
|
||||
<field name="country_id" ref="base.ca"/>
|
||||
<field name="currency_id" ref="base.CAD"/>
|
||||
<field name="phone">+1 519 888 4400</field>
|
||||
<field name="email">info@waterlooheights.ca</field>
|
||||
<field name="primary_color">#1E5C3A</field>
|
||||
<field name="secondary_color">#C0C0C0</field>
|
||||
<field name="logo" type="base64" file="mc_education_theme/static/demo/logo_waterloo_heights.png"/>
|
||||
|
||||
28
addons/mc_education_theme/hooks.py
Normal file
28
addons/mc_education_theme/hooks.py
Normal file
@ -0,0 +1,28 @@
|
||||
import base64
|
||||
|
||||
from odoo.tools.misc import file_open
|
||||
|
||||
|
||||
def post_init_hook(env):
|
||||
"""Metatroncube's own logo and favicon for the public website - a
|
||||
fixed brand asset, not tied to whichever school company happens to
|
||||
be active, matching the marketing homepage
|
||||
(views/homepage_templates.xml).
|
||||
|
||||
Odoo ships two demo website records out of the box
|
||||
(website.default_website, website.website2), both created with
|
||||
noupdate=True. A plain <record> write targeting either from this
|
||||
module's own data files is silently skipped - no error, nothing in
|
||||
the log - exactly the same trap demo/mc_cast_users_demo.xml already
|
||||
documented for an unrelated field. Running this as real Python
|
||||
instead isn't subject to that guard.
|
||||
|
||||
Only runs on a fresh install of this module, per Odoo's own
|
||||
post_init_hook contract - it will not re-fire on `-u` of a database
|
||||
that already had this module installed before this hook existed.
|
||||
"""
|
||||
with file_open("mc_education_theme/static/img/logo.png", "rb") as f:
|
||||
logo = base64.b64encode(f.read())
|
||||
with file_open("mc_education_theme/static/img/favicon.png", "rb") as f:
|
||||
favicon = base64.b64encode(f.read())
|
||||
env["website"].search([]).write({"logo": logo, "favicon": favicon})
|
||||
BIN
addons/mc_education_theme/static/img/favicon.png
Normal file
BIN
addons/mc_education_theme/static/img/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
BIN
addons/mc_education_theme/static/img/logo.png
Normal file
BIN
addons/mc_education_theme/static/img/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@ -1,3 +1,5 @@
|
||||
from lxml import etree
|
||||
|
||||
from odoo.tests.common import HttpCase, TransactionCase
|
||||
|
||||
|
||||
@ -23,6 +25,20 @@ class TestTheme(TransactionCase):
|
||||
# distinct, not the same record twice under different refs.
|
||||
self.assertNotEqual(main.id, waterloo.id)
|
||||
|
||||
# Contact fields specifically, not just name/colour/logo: the
|
||||
# contact us page and site footer now read phone/email/street
|
||||
# live off whichever company is active (see
|
||||
# contactus_templates.xml, footer_templates.xml) - a company
|
||||
# still carrying stock Odoo's own demo placeholder here
|
||||
# (+1 555-555-5556, info@yourcompany.com) would put that
|
||||
# placeholder right back in front of the demo audience, on a
|
||||
# page built specifically to stop showing it.
|
||||
for company in (main, waterloo):
|
||||
self.assertTrue(company.phone)
|
||||
self.assertNotIn("555-555-5556", company.phone)
|
||||
self.assertTrue(company.email)
|
||||
self.assertNotIn("yourcompany", company.email)
|
||||
|
||||
def test_portal_sidebar_has_no_odoo_branding(self):
|
||||
html = str(self.env["ir.qweb"]._render("portal.portal_record_sidebar", {
|
||||
"classes": "", "title": False, "entries": False,
|
||||
@ -71,6 +87,94 @@ class TestTheme(TransactionCase):
|
||||
self.assertIn("One platform", arch)
|
||||
self.assertIn('position="replace"', arch)
|
||||
|
||||
def test_login_page_has_no_odoo_branding(self):
|
||||
# A third, separate "Powered by Odoo" occurrence - the backend
|
||||
# login screen's own hardcoded link, distinct from the portal
|
||||
# sidebar text and the web.brand_promotion badge above. Unlike
|
||||
# website.homepage, web.login_layout isn't website-scoped (no
|
||||
# per-website COW view involved), so _get_combined_arch() here
|
||||
# isn't subject to the same-transaction cache-timing quirk
|
||||
# documented on the homepage test - this is a reliable check.
|
||||
#
|
||||
# _get_combined_arch() returns an lxml Element, not a string -
|
||||
# str(element) gives its memory-address repr ("<Element t at
|
||||
# 0x...>"), which would make assertNotIn trivially, silently
|
||||
# pass no matter what the template actually contains. Caught by
|
||||
# spot-checking the header widget fix below the same way and
|
||||
# seeing that exact non-answer instead of real markup.
|
||||
# etree.tostring() is the real serialization.
|
||||
arch = etree.tostring(self.env.ref("web.login_layout")._get_combined_arch()).decode()
|
||||
self.assertNotIn("Powered by", arch)
|
||||
self.assertNotIn("odoo.com", arch)
|
||||
|
||||
def test_contactus_page_shows_real_company_not_placeholder(self):
|
||||
# Stock Odoo hardcodes "My Company" and a fake US street address
|
||||
# in the contact sidebar - exactly the kind of placeholder
|
||||
# content shared/DEMO_SCRIPT.md's "what must NOT appear" list
|
||||
# bans, and it was live on this page before today. Checking the
|
||||
# view's own arch rather than a live render for the same reason
|
||||
# as the login test above - res_company resolution needs a
|
||||
# website request context this TransactionCase doesn't have,
|
||||
# and the substitution itself is fully verifiable from the
|
||||
# template source: it reads res_company fields instead of
|
||||
# hardcoding text.
|
||||
arch = str(self.env.ref("mc_education_theme.contactus_page_branding").arch_db)
|
||||
self.assertNotIn("My Company", arch)
|
||||
self.assertNotIn("Fake Buena Vista", arch)
|
||||
self.assertNotIn("yourcompany.example.com", arch)
|
||||
self.assertIn("res_company.name", arch)
|
||||
self.assertIn("res_company.email", arch)
|
||||
|
||||
def test_site_footer_shows_real_company_not_placeholder(self):
|
||||
# website.footer_custom is active on EVERY website page, not
|
||||
# just contact us - fixing the contact page's own sidebar and
|
||||
# missing this would have left the identical
|
||||
# yourcompany.example.com / +1 555-555-5556 / "Products,
|
||||
# Services, Legal" placeholder visible underneath it, on every
|
||||
# single page of the site.
|
||||
arch = str(self.env.ref("mc_education_theme.footer_branding").arch_db)
|
||||
self.assertNotIn("yourcompany.example.com", arch)
|
||||
self.assertNotIn("555-555-5556", arch)
|
||||
self.assertNotIn("passionate people", arch)
|
||||
self.assertIn("res_company.name", arch)
|
||||
self.assertIn("/admissions/apply", arch)
|
||||
|
||||
def test_header_phone_mail_widget_shows_real_company_not_placeholder(self):
|
||||
# A fourth occurrence, in the header rather than the footer:
|
||||
# website.header_text_element is a library of variants
|
||||
# (sentence, list, phone_mail, mail, mail_stretched, a
|
||||
# default), found after the footer fix above made the same
|
||||
# fake number's continued presence, higher up the same page,
|
||||
# obvious. First pass fixed only "phone_mail", assumed to be
|
||||
# this site's active variant - wrong: the two spots actually
|
||||
# rendering on the real page (desktop nav, mobile offcanvas)
|
||||
# both turned out to use the plain default branch instead,
|
||||
# caught only by re-checking the live page rather than trusting
|
||||
# that assumption. Checking every variant with contact info in
|
||||
# it here, not just the one that happened to be live on this
|
||||
# particular header configuration - "sentence" and "list" carry
|
||||
# no phone/email at all and are correctly untouched.
|
||||
arch = etree.tostring(
|
||||
self.env.ref("website.header_text_element")._get_combined_arch()
|
||||
).decode()
|
||||
self.assertNotIn("555-555-5556", arch)
|
||||
self.assertNotIn("yourcompany.example.com", arch)
|
||||
|
||||
phone_mail_branch = arch.split("'phone_mail'", 1)[1].split("t-elif", 1)[0]
|
||||
self.assertIn("res_company.phone", phone_mail_branch)
|
||||
self.assertIn("res_company.email", phone_mail_branch)
|
||||
|
||||
mail_branch = arch.split("'mail'", 1)[1].split("t-elif", 1)[0]
|
||||
self.assertIn("res_company.email", mail_branch)
|
||||
|
||||
mail_stretched_branch = arch.split("'mail_stretched'", 1)[1].split("t-elif", 1)[0]
|
||||
self.assertIn("res_company.phone", mail_stretched_branch)
|
||||
|
||||
# The default (t-else) branch is last, has no following
|
||||
# t-elif to split on - it runs to the end of the widget instead.
|
||||
default_branch = arch.rsplit("t-else", 1)[1]
|
||||
self.assertIn("res_company.phone", default_branch)
|
||||
|
||||
def test_backend_cast_logins_exist_in_the_right_groups(self):
|
||||
# shared/DEMO_SCRIPT.md's cast table names six logins. Two
|
||||
# (parent@demo.school, student@demo.school) are created by
|
||||
@ -120,6 +224,16 @@ class TestTheme(TransactionCase):
|
||||
f"{user.login} should have Internal User access via group_school_staff's implied_ids",
|
||||
)
|
||||
|
||||
def test_website_logo_and_favicon_are_metatroncubes_own(self):
|
||||
# hooks.post_init_hook loads these from static/img/ onto every
|
||||
# website record - only runs on a genuinely fresh install (see
|
||||
# hooks.py), which is exactly the situation this test runs
|
||||
# under. An already-installed database needs the one-off manual
|
||||
# application documented in hooks.py's own docstring.
|
||||
for website in self.env["website"].search([]):
|
||||
self.assertTrue(website.logo, f"website {website.name!r} has no logo")
|
||||
self.assertTrue(website.favicon, f"website {website.name!r} has no favicon")
|
||||
|
||||
|
||||
class TestMarketingHomepageHttp(HttpCase):
|
||||
"""Content correctness is covered in TestTheme via _get_combined_arch()
|
||||
@ -134,3 +248,11 @@ class TestMarketingHomepageHttp(HttpCase):
|
||||
def test_homepage_route_serves_successfully(self):
|
||||
response = self.url_open("/")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_login_route_serves_successfully(self):
|
||||
response = self.url_open("/web/login")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_contactus_route_serves_successfully(self):
|
||||
response = self.url_open("/contactus")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
161
addons/mc_education_theme/views/contactus_templates.xml
Normal file
161
addons/mc_education_theme/views/contactus_templates.xml
Normal file
@ -0,0 +1,161 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Same brand/type system as the marketing homepage and the
|
||||
admission form: Manrope + Work Sans, the navy/indigo palette.
|
||||
Only #wrap is replaced - the t-set/hidden span above it in
|
||||
website.contactus (contactus_form_values, used by the
|
||||
data-pre-fill JS on the form) live outside #wrap and are left
|
||||
completely alone.
|
||||
|
||||
Two real, separate things fixed while restyling this, not
|
||||
just a coat of paint:
|
||||
1. Stock Odoo's sidebar hardcodes "My Company" / a fake US
|
||||
street address / a placeholder phone and email -
|
||||
shared/DEMO_SCRIPT.md's "what must NOT appear" list bans
|
||||
exactly this kind of placeholder content, and it was live
|
||||
on this page before today. Reading res_company (already
|
||||
provided by website.layout) instead means it shows
|
||||
whichever school is actually active - correct for the
|
||||
same reason the admission form's own program dropdown
|
||||
reads live data instead of a fixed list.
|
||||
2. The field names/ids/model (mail.mail) are byte-identical
|
||||
to stock - this form posts through the exact same
|
||||
/website/form/ controller Odoo ships, so no submit
|
||||
behaviour changes, only what it looks like. -->
|
||||
<template id="contactus_page_branding" inherit_id="website.contactus" name="Contact Us - Metatroncube design">
|
||||
<xpath expr="//div[@id='wrap']" position="replace">
|
||||
<div id="wrap" class="mc-contact">
|
||||
<style>
|
||||
.mc-contact{ --navy:#131B2E; --navy-2:#1C2A46; --indigo:#4B3F92; --indigo-2:#2E63B8;
|
||||
--paper:#F6F7FB; --card:#FFFFFF; --line:#E4E7F0; --ink:#16192B; --ink-soft:#5B6178;
|
||||
--shadow:0 1px 2px rgba(19,27,46,.06), 0 14px 34px -16px rgba(19,27,46,.22);
|
||||
font-family:'Work Sans', system-ui, sans-serif; color:var(--ink); background:var(--paper);
|
||||
}
|
||||
.mc-contact *{ box-sizing:border-box; }
|
||||
.mc-contact h1, .mc-contact h2, .mc-contact h5{
|
||||
font-family:'Manrope', system-ui, sans-serif; font-weight:800; letter-spacing:-.01em;
|
||||
color:var(--ink); margin:0;
|
||||
}
|
||||
.mc-contact-hero{
|
||||
background: radial-gradient(900px 380px at 90% -10%, rgba(75,63,146,.5), transparent 60%),
|
||||
linear-gradient(180deg, var(--navy), var(--navy-2));
|
||||
color:#fff; padding:52px 24px 60px; text-align:center;
|
||||
}
|
||||
.mc-contact-hero h1{ color:#fff; font-size:clamp(1.6rem,3.4vw,2.3rem); }
|
||||
.mc-contact-hero p{ color:#C4C9E2; max-width:52ch; margin:14px auto 0; font-size:1rem; line-height:1.6; }
|
||||
|
||||
.mc-contact-body{
|
||||
max-width:1000px; margin:-30px auto 0; padding:0 20px 70px;
|
||||
display:grid; grid-template-columns:1.4fr 1fr; gap:20px; position:relative;
|
||||
}
|
||||
@media (max-width:760px){ .mc-contact-body{ grid-template-columns:1fr; } }
|
||||
.mc-contact-card{
|
||||
background:var(--card); border:1px solid var(--line); border-radius:16px;
|
||||
box-shadow:var(--shadow); padding:32px 30px;
|
||||
}
|
||||
@media (max-width:600px){ .mc-contact-card{ padding:24px 18px; } }
|
||||
.mc-contact .s_website_form_label{ font-weight:600; color:var(--ink); font-size:.85rem; width:auto !important; }
|
||||
.mc-contact .s_website_form_mark{ color:var(--indigo); }
|
||||
.mc-contact .form-control{ border-color:var(--line); border-radius:9px; padding:9px 12px; font-family:'Work Sans', sans-serif; }
|
||||
.mc-contact .form-control:focus{ border-color:var(--indigo); box-shadow:0 0 0 3px rgba(75,63,146,.15); }
|
||||
.mc-contact .btn-primary{
|
||||
background:linear-gradient(90deg, var(--indigo), var(--indigo-2)); border:none;
|
||||
padding:10px 24px; border-radius:9px; font-weight:600;
|
||||
}
|
||||
.mc-contact-info h5{ font-size:1.02rem; margin-bottom:14px; }
|
||||
.mc-contact-info ul{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:12px; }
|
||||
.mc-contact-info li{ display:flex; align-items:flex-start; gap:10px; font-size:.9rem; color:var(--ink-soft); }
|
||||
.mc-contact-info .ico{
|
||||
width:30px; height:30px; border-radius:8px; background:#F4EEFB; color:var(--indigo);
|
||||
display:flex; align-items:center; justify-content:center; flex-shrink:0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="mc-contact-hero">
|
||||
<h1>Contact us</h1>
|
||||
<p>Questions about admissions, fees or anything else - send us a note and we'll get back to you shortly.</p>
|
||||
</div>
|
||||
|
||||
<div class="mc-contact-body">
|
||||
<div class="mc-contact-card">
|
||||
<section class="s_website_form" data-snippet="s_website_form">
|
||||
<form id="contactus_form" action="/website/form/" method="post" enctype="multipart/form-data" class="o_mark_required" data-mark="*" data-model_name="mail.mail" data-success-mode="redirect" data-success-page="/contactus-thank-you" data-pre-fill="true">
|
||||
<div class="s_website_form_rows row s_col_no_bgcolor">
|
||||
<div class="mb-3 col-lg-6 s_website_form_field s_website_form_custom s_website_form_required" data-type="char" data-name="Field">
|
||||
<label class="s_website_form_label" for="contact1">
|
||||
<span class="s_website_form_label_content">Name</span>
|
||||
<span class="s_website_form_mark"> *</span>
|
||||
</label>
|
||||
<input id="contact1" type="text" class="form-control s_website_form_input" name="name" required="" data-fill-with="name"/>
|
||||
</div>
|
||||
<div class="mb-3 col-lg-6 s_website_form_field s_website_form_custom" data-type="char" data-name="Field">
|
||||
<label class="s_website_form_label" for="contact2">
|
||||
<span class="s_website_form_label_content">Phone Number</span>
|
||||
</label>
|
||||
<input id="contact2" type="tel" class="form-control s_website_form_input" name="phone" data-fill-with="phone"/>
|
||||
</div>
|
||||
<div class="mb-3 col-lg-6 s_website_form_field s_website_form_required s_website_form_model_required" data-type="email" data-name="Field">
|
||||
<label class="s_website_form_label" for="contact3">
|
||||
<span class="s_website_form_label_content">Email</span>
|
||||
<span class="s_website_form_mark"> *</span>
|
||||
</label>
|
||||
<input id="contact3" type="email" class="form-control s_website_form_input" name="email_from" required="" data-fill-with="email"/>
|
||||
</div>
|
||||
<div class="mb-3 col-lg-6 s_website_form_field s_website_form_custom" data-type="char" data-name="Field">
|
||||
<label class="s_website_form_label" for="contact4">
|
||||
<span class="s_website_form_label_content">Company</span>
|
||||
</label>
|
||||
<input id="contact4" type="text" class="form-control s_website_form_input" name="company" data-fill-with="commercial_company_name"/>
|
||||
</div>
|
||||
<div class="mb-3 col-12 s_website_form_field s_website_form_required s_website_form_model_required" data-type="char" data-name="Field">
|
||||
<label class="s_website_form_label" for="contact5">
|
||||
<span class="s_website_form_label_content">Subject</span>
|
||||
<span class="s_website_form_mark"> *</span>
|
||||
</label>
|
||||
<input id="contact5" type="text" class="form-control s_website_form_input" name="subject" required=""/>
|
||||
</div>
|
||||
<div class="mb-3 col-12 s_website_form_field s_website_form_custom s_website_form_required" data-type="text" data-name="Field">
|
||||
<label class="s_website_form_label" for="contact6">
|
||||
<span class="s_website_form_label_content">Question</span>
|
||||
<span class="s_website_form_mark"> *</span>
|
||||
</label>
|
||||
<textarea id="contact6" class="form-control s_website_form_input" name="description" required="" rows="6"></textarea>
|
||||
</div>
|
||||
<div class="mb-3 col-12 s_website_form_field s_website_form_dnone">
|
||||
<input id="contact7" type="hidden" class="form-control s_website_form_input" name="email_to" t-att-value="res_company.email"/>
|
||||
</div>
|
||||
<div class="mb-0 py-2 col-12 s_website_form_submit s_website_form_no_submit_label text-end" data-name="Submit Button">
|
||||
<div class="s_website_form_label"/>
|
||||
<a href="#" role="button" class="btn btn-primary s_website_form_send">Submit</a>
|
||||
<span id="s_website_form_result"></span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="mc-contact-card mc-contact-info">
|
||||
<h5 t-esc="res_company.name"/>
|
||||
<ul>
|
||||
<li t-if="res_company.street">
|
||||
<span class="ico"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M12 21s7-6.3 7-11.5A7 7 0 0 0 5 9.5C5 14.7 12 21 12 21z"/><circle cx="12" cy="9.5" r="2.3"/></svg></span>
|
||||
<span>
|
||||
<t t-esc="res_company.street"/><br t-if="res_company.city"/>
|
||||
<t t-esc="res_company.city"/><t t-if="res_company.country_id"><t t-esc="', '"/><t t-esc="res_company.country_id.name"/></t>
|
||||
</span>
|
||||
</li>
|
||||
<li t-if="res_company.phone">
|
||||
<span class="ico"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6 19.8 19.8 0 0 1-3.1-8.6A2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1 1 .3 2 .7 3a2 2 0 0 1-.5 2.1L8 10a16 16 0 0 0 6 6l1.2-1.3a2 2 0 0 1 2.1-.5c1 .4 2 .6 3 .7a2 2 0 0 1 1.7 2z"/></svg></span>
|
||||
<span t-esc="res_company.phone"/>
|
||||
</li>
|
||||
<li t-if="res_company.email">
|
||||
<span class="ico"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg></span>
|
||||
<span t-esc="res_company.email"/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
137
addons/mc_education_theme/views/footer_templates.xml
Normal file
137
addons/mc_education_theme/views/footer_templates.xml
Normal file
@ -0,0 +1,137 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- website.footer_custom (stock Odoo's default footer, active on
|
||||
every website page - homepage, admissions, contact us, all of
|
||||
them) hardcodes a "Products / Services / Legal" link list that
|
||||
goes nowhere (href="#"), a generic "we are a team of
|
||||
passionate people" paragraph, dead social icons pointing at
|
||||
unconfigured /website/social/* routes, and the same
|
||||
yourcompany.example.com / +1 555-555-5556 placeholder already
|
||||
fixed on the contact us page itself - all of it visible on
|
||||
every single page of the site, not just one. Real find: fixing
|
||||
the contact us page alone left this identical placeholder
|
||||
content sitting in the footer underneath it.
|
||||
|
||||
Replaced with real links into this app (Home, Admissions,
|
||||
Contact us) and the active school's own name/contact info via
|
||||
res_company, the same dynamic source the contact us page uses -
|
||||
no fixed Metatroncube branding here, this footer is genuinely
|
||||
per-school content and swaps with the active company like the
|
||||
admission form does. -->
|
||||
<!-- A fourth occurrence, in the header this time, not the footer:
|
||||
website.header_text_element is Odoo's generic, reusable
|
||||
"phone/email nav widget" snippet - a library of variants
|
||||
(sentence, list, phone_mail, mail, mail_stretched, a default)
|
||||
every one of which hardcodes +1 555-555-5556 /
|
||||
info@yourcompany.example.com. Found only by looking at the
|
||||
live nav bar after the footer fix above made it obvious the
|
||||
same fake number was still showing up higher on the page.
|
||||
|
||||
First pass here fixed only "phone_mail", assumed to be this
|
||||
site's active variant - wrong. The two spots actually
|
||||
rendering on the live page (desktop nav, mobile offcanvas nav)
|
||||
both turned out to use the plain default (t-else) branch
|
||||
instead, still showing the fake number after that first fix,
|
||||
caught by re-checking the real page rather than trusting the
|
||||
assumption. Fixing every variant with a phone or email in it
|
||||
(phone_mail, mail, mail_stretched, the default) rather than
|
||||
guessing again which one a given header configuration
|
||||
actually selects - "list" and "sentence" carry no contact
|
||||
info at all and are left alone. -->
|
||||
<template id="header_phone_mail_branding" inherit_id="website.header_text_element" name="Header phone/email - real company info">
|
||||
<!-- phone_mail: both phone and email, no icon/text lookalikes -->
|
||||
<xpath expr="//t[@t-elif="_txt_elt_content == 'phone_mail'"]//a[starts-with(@href,'tel:')]" position="attributes">
|
||||
<attribute name="href"></attribute>
|
||||
<attribute name="t-attf-href">tel:#{res_company.phone}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-elif="_txt_elt_content == 'phone_mail'"]//a[@t-attf-href][contains(@t-attf-href,'tel')]//span[hasclass('o_force_ltr')]" position="replace">
|
||||
<span class="o_force_ltr" t-esc="res_company.phone"/>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-elif="_txt_elt_content == 'phone_mail'"]//a[starts-with(@href,'mailto:')]" position="attributes">
|
||||
<attribute name="href"></attribute>
|
||||
<attribute name="t-attf-href">mailto:#{res_company.email}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-elif="_txt_elt_content == 'phone_mail'"]//a[@t-attf-href][contains(@t-attf-href,'mailto')]/small" position="replace">
|
||||
<small>
|
||||
<i class="fa fa-1x fa-fw fa-envelope me-1"/>
|
||||
<t t-esc="res_company.email"/>
|
||||
</small>
|
||||
</xpath>
|
||||
|
||||
<!-- mail: email only -->
|
||||
<xpath expr="//t[@t-elif="_txt_elt_content == 'mail'"]//a[starts-with(@href,'mailto:')]" position="attributes">
|
||||
<attribute name="href"></attribute>
|
||||
<attribute name="t-attf-href">mailto:#{res_company.email}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-elif="_txt_elt_content == 'mail'"]//a[@t-attf-href]/small" position="replace">
|
||||
<small><i class="fa fa-1x fa-fw fa-envelope me-1"/> <t t-esc="res_company.email"/></small>
|
||||
</xpath>
|
||||
|
||||
<!-- mail_stretched: phone only, the "stretched clickable nav pill" look -->
|
||||
<xpath expr="//t[@t-elif="_txt_elt_content == 'mail_stretched'"]//a[starts-with(@href,'tel:')]" position="attributes">
|
||||
<attribute name="href"></attribute>
|
||||
<attribute name="t-attf-href">tel:#{res_company.phone}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-elif="_txt_elt_content == 'mail_stretched'"]//a[@t-attf-href]//span[hasclass('o_force_ltr')]" position="replace">
|
||||
<span class="o_force_ltr"><small t-esc="res_company.phone"/></span>
|
||||
</xpath>
|
||||
|
||||
<!-- the default branch: phone only - this is the one actually
|
||||
rendering on this site's own header, both desktop and
|
||||
mobile. -->
|
||||
<xpath expr="//t[@t-else][not(@t-elif)]//a[starts-with(@href,'tel:')]" position="attributes">
|
||||
<attribute name="href"></attribute>
|
||||
<attribute name="t-attf-href">tel:#{res_company.phone}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-else][not(@t-elif)]//a[@t-attf-href]//span[hasclass('o_force_ltr')]" position="replace">
|
||||
<span class="o_force_ltr"><small t-esc="res_company.phone"/></span>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="footer_branding" inherit_id="website.footer_custom" name="Footer - real links, no placeholder">
|
||||
<xpath expr="//div[@id='footer']" position="replace">
|
||||
<div id="footer" class="oe_structure oe_structure_solo border text-break" t-ignore="true" t-if="not no_footer" style="--box-border-left-width: 0px; --box-border-right-width: 0px;">
|
||||
<style>
|
||||
.mc-footer{ font-family:'Work Sans', system-ui, sans-serif; }
|
||||
.mc-footer h5{ font-family:'Manrope', system-ui, sans-serif; font-weight:800; }
|
||||
.mc-footer .ico{ display:inline-flex; width:16px; }
|
||||
</style>
|
||||
<section class="s_text_block pt40 pb16 mc-footer" data-snippet="s_text_block" data-name="Container">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-3 pt24 pb24">
|
||||
<h5>Quick Links</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/admissions/apply">Admissions</a></li>
|
||||
<li><a href="/contactus">Contact us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-5 pt24 pb24">
|
||||
<h5 t-esc="res_company.name"/>
|
||||
<p>Admissions, fees, attendance, timetable, exams and the parent portal - all in one connected system, all reachable from this site.</p>
|
||||
</div>
|
||||
<div class="col-lg-4 pt24 pb24">
|
||||
<h5>Get in touch</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li t-if="res_company.phone">
|
||||
<span class="ico"><i class="fa fa-phone fa-fw me-2"/></span>
|
||||
<a t-attf-href="tel:#{res_company.phone}"><span class="o_force_ltr" t-esc="res_company.phone"/></a>
|
||||
</li>
|
||||
<li t-if="res_company.email">
|
||||
<span class="ico"><i class="fa fa-envelope fa-fw me-2"/></span>
|
||||
<a t-attf-href="mailto:#{res_company.email}" t-esc="res_company.email"/>
|
||||
</li>
|
||||
<li t-if="res_company.city">
|
||||
<span class="ico"><i class="fa fa-map-marker fa-fw me-2"/></span>
|
||||
<span t-esc="res_company.city"/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
@ -58,11 +58,7 @@
|
||||
linear-gradient(180deg, var(--navy) 0%, var(--navy-2) 100%);
|
||||
padding:26px 0 70px; color:#EDEFF7;
|
||||
}
|
||||
.mc-topbar{ display:flex; align-items:center; justify-content:space-between; gap:16px; padding:8px 0 46px; }
|
||||
.mc-logo{ display:flex; align-items:center; gap:11px; }
|
||||
.mc-logo .mark{ width:34px; height:34px; }
|
||||
.mc-logo .wordmark{ font-family:'Manrope',sans-serif; font-weight:800; font-size:1.28rem; color:#fff; line-height:1; }
|
||||
.mc-logo .tagline{ font-size:.6rem; letter-spacing:.16em; color:#AEB6D6; margin-top:3px; }
|
||||
.mc-topbar{ padding:8px 0 46px; }
|
||||
.mc-pill{
|
||||
font-size:.72rem; letter-spacing:.06em; text-transform:uppercase; font-weight:600;
|
||||
color:#EFE9FF; background:linear-gradient(90deg, var(--indigo), var(--indigo-2));
|
||||
@ -141,18 +137,12 @@
|
||||
<!-- ================= HERO ================= -->
|
||||
<div class="mc-hero">
|
||||
<div class="mc-shell">
|
||||
<!-- The site's own nav (website.layout's header)
|
||||
already shows the real Metatroncube logo -
|
||||
set via hooks.py onto website.logo - on
|
||||
every page including this one, so the hero
|
||||
doesn't repeat it. -->
|
||||
<div class="mc-topbar">
|
||||
<div class="mc-logo">
|
||||
<svg class="mark" viewBox="0 0 40 40" fill="none">
|
||||
<path d="M20 3 L35 11.5 V28.5 L20 37 L5 28.5 V11.5 Z" stroke="#8F86D6" stroke-width="1.6"/>
|
||||
<path d="M20 3 V20 L35 11.5" stroke="#6FA8DE" stroke-width="1.6"/>
|
||||
<path d="M20 20 L5 11.5 M20 20 V37" stroke="#6FA8DE" stroke-width="1.6"/>
|
||||
</svg>
|
||||
<div>
|
||||
<div class="wordmark">Metatroncube</div>
|
||||
<div class="tagline">SMART · INTEGRATED · EDUCATION</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="mc-pill">Built for Schools</span>
|
||||
</div>
|
||||
|
||||
|
||||
53
addons/mc_education_theme/views/login_templates.xml
Normal file
53
addons/mc_education_theme/views/login_templates.xml
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- A third, separate "Powered by Odoo" occurrence, distinct from
|
||||
the two already hidden in portal_branding_templates.xml
|
||||
(portal.portal_record_sidebar's text link, and
|
||||
web.brand_promotion's badge): web.login_layout hardcodes its
|
||||
own "Powered by <span>Odoo</span>" link straight in the
|
||||
template, reached by every backend login screen - the very
|
||||
first thing anyone doing this demo sees before they've even
|
||||
logged in. Same brand/type system as the rest of the site
|
||||
(Manrope + Work Sans, navy/indigo) wraps it; the logo shown
|
||||
stays /web/binary/company_logo, i.e. whichever school is
|
||||
actually logging in - a login screen is inherently
|
||||
per-tenant, unlike the marketing homepage. -->
|
||||
<template id="login_layout_branding" inherit_id="web.login_layout" name="Login - Metatroncube design">
|
||||
<xpath expr="//a[@href='https://www.odoo.com?utm_source=db&utm_medium=auth']" position="replace"/>
|
||||
|
||||
<xpath expr="//div[hasclass('container') and hasclass('py-5')]" position="attributes">
|
||||
<attribute name="class" add="mc-login-shell" separator=" "/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div[hasclass('container') and hasclass('py-5')]" position="before">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin"/>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@600;700;800&family=Work+Sans:wght@400;500;600;700&display=swap" rel="stylesheet"/>
|
||||
<div class="mc-login-bg"/>
|
||||
<style>
|
||||
.mc-login-bg{
|
||||
position:fixed; inset:0; z-index:-1;
|
||||
background:
|
||||
radial-gradient(900px 420px at 85% -10%, rgba(75,63,146,.55), transparent 60%),
|
||||
linear-gradient(180deg, #131B2E 0%, #1C2A46 100%);
|
||||
}
|
||||
.mc-login-shell{ font-family:'Work Sans', system-ui, sans-serif; padding-top:8vh !important; }
|
||||
.mc-login-shell .card{
|
||||
border-radius:16px !important; box-shadow:0 24px 60px -20px rgba(0,0,0,.45);
|
||||
padding:6px;
|
||||
}
|
||||
.mc-login-shell h1, .mc-login-shell .form-label{
|
||||
font-family:'Manrope', system-ui, sans-serif;
|
||||
}
|
||||
.mc-login-shell .form-label{ font-weight:600; font-size:.85rem; }
|
||||
.mc-login-shell .form-control{ border-radius:9px; padding:10px 12px; }
|
||||
.mc-login-shell .form-control:focus{ border-color:#4B3F92; box-shadow:0 0 0 3px rgba(75,63,146,.15); }
|
||||
.mc-login-shell .btn-primary{
|
||||
background:linear-gradient(90deg, #4B3F92, #2E63B8); border:none;
|
||||
border-radius:9px; padding:10px 0; font-weight:600; font-family:'Manrope', sans-serif;
|
||||
}
|
||||
.mc-login-shell .btn-primary:hover{ filter:brightness(1.08); }
|
||||
</style>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
Loading…
x
Reference in New Issue
Block a user