diff --git a/README.md b/README.md
index 6e9f5acc8b..bf21689f3b 100644
--- a/README.md
+++ b/README.md
@@ -22,18 +22,21 @@ Available addons
----------------
addon | version | maintainers | summary
--- | --- | --- | ---
-[website_altcha](website_altcha/) | 18.0.1.0.1 | | Use self hosted privacy friendly captcha for verifying website users are not bots
+[website_altcha](website_altcha/) | 18.0.1.1.0 | | Use self hosted privacy friendly captcha for verifying website users are not bots
[website_analytics_matomo](website_analytics_matomo/) | 18.0.1.0.0 | | Track website users using matomo
-[website_blog_scheduled_publication](website_blog_scheduled_publication/) | 18.0.1.0.0 | | Schedule blog posts publication date
+[website_blog_scheduled_publication](website_blog_scheduled_publication/) | 18.0.1.1.0 | | Schedule blog posts publication date
[website_breadcrumb](website_breadcrumb/) | 18.0.1.0.0 | | Let you have breadcrumbs in website pages
[website_cf_turnstile_login](website_cf_turnstile_login/) | 18.0.1.0.0 | | Add Cloudflare Turnstile captcha to login form
[website_cookiebot](website_cookiebot/) | 18.0.1.0.0 | | Ask for cookies consent connecting with Cookiebot
[website_cookiefirst](website_cookiefirst/) | 18.0.2.0.0 | | Cookiefirst integration
[website_crm_quick_answer](website_crm_quick_answer/) | 18.0.1.0.0 | | Add an automatic answer for contacts asking for info
+[website_event_altcha](website_event_altcha/) | 18.0.1.1.0 | | Support event registration with ALTCHA
+[website_exclude_sitemap](website_exclude_sitemap/) | 18.0.1.0.0 | | Exclude addresses from the sitemap.xml file according to the values defined in the configuration
[website_form_require_legal](website_form_require_legal/) | 18.0.1.0.2 | | Add possibility to require confirm legal terms.
[website_forum_subscription](website_forum_subscription/) | 18.0.1.0.2 | | Adds a button to allow subscription from the website
[website_google_tag_manager](website_google_tag_manager/) | 18.0.1.0.0 | | Add support for Google Tag Manager
[website_legal_page](website_legal_page/) | 18.0.1.0.0 | | Website Legal Page
+[website_llms](website_llms/) | 18.0.1.0.0 | | This module adds support for serving a /llms.txt file in the website root. The content can be configured per website in the website settings.
[website_menu_by_user_status](website_menu_by_user_status/) | 18.0.1.0.0 | | Allow to manage the display of website.menus
[website_odoo_debranding](website_odoo_debranding/) | 18.0.1.0.0 | | Remove Odoo Branding from Website
[website_partner_form](website_partner_form/) | 18.0.1.0.0 | | Allow to edit partner website fields on partner form view, in the back office
@@ -42,6 +45,7 @@ addon | version | maintainers | summary
[website_search_header](website_search_header/) | 18.0.1.0.0 | | Website Search in Header
[website_select2](website_select2/) | 18.0.1.0.0 | | Integrate select2 in Odoo websites
[website_snippet_country_dropdown](website_snippet_country_dropdown/) | 18.0.1.0.1 | | Allow to select country in a dropdown
+[website_social_media_link](website_social_media_link/) | 18.0.1.0.0 | | Display Social Medial Links on Website back-end.
[website_user_login_redirect_custom](website_user_login_redirect_custom/) | 18.0.1.0.0 | | Redirect website/portal user to custom URL after login or signup
[website_whatsapp](website_whatsapp/) | 18.0.1.0.1 | | Whatsapp integration
diff --git a/setup/_metapackage/pyproject.toml b/setup/_metapackage/pyproject.toml
index 257b45616f..8b0e583850 100644
--- a/setup/_metapackage/pyproject.toml
+++ b/setup/_metapackage/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "odoo-addons-oca-website"
-version = "18.0.20260727.0"
+version = "18.0.20260917.0"
dependencies = [
"odoo-addon-website_altcha==18.0.*",
"odoo-addon-website_analytics_matomo==18.0.*",
@@ -10,10 +10,13 @@ dependencies = [
"odoo-addon-website_cookiebot==18.0.*",
"odoo-addon-website_cookiefirst==18.0.*",
"odoo-addon-website_crm_quick_answer==18.0.*",
+ "odoo-addon-website_event_altcha==18.0.*",
+ "odoo-addon-website_exclude_sitemap==18.0.*",
"odoo-addon-website_form_require_legal==18.0.*",
"odoo-addon-website_forum_subscription==18.0.*",
"odoo-addon-website_google_tag_manager==18.0.*",
"odoo-addon-website_legal_page==18.0.*",
+ "odoo-addon-website_llms==18.0.*",
"odoo-addon-website_menu_by_user_status==18.0.*",
"odoo-addon-website_odoo_debranding==18.0.*",
"odoo-addon-website_partner_form==18.0.*",
@@ -22,6 +25,7 @@ dependencies = [
"odoo-addon-website_search_header==18.0.*",
"odoo-addon-website_select2==18.0.*",
"odoo-addon-website_snippet_country_dropdown==18.0.*",
+ "odoo-addon-website_social_media_link==18.0.*",
"odoo-addon-website_user_login_redirect_custom==18.0.*",
"odoo-addon-website_whatsapp==18.0.*",
]
diff --git a/website_altcha/README.rst b/website_altcha/README.rst
index 327a2d7d84..8f4f7a2765 100644
--- a/website_altcha/README.rst
+++ b/website_altcha/README.rst
@@ -11,7 +11,7 @@ Privacy Friendly Captcha
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:47240fe67bd99327829258a0f654f2a147a5332dca76e6e66d67df44800779c8
+ !! source digest: sha256:83e03d4eacacddd6d9b4c9b29eed8eee8f9d1bde28564186bfb553e8f5a29b6f
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
@@ -126,6 +126,14 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
+.. |maintainer-hbrunn| image:: https://github.com/hbrunn.png?size=40px
+ :target: https://github.com/hbrunn
+ :alt: hbrunn
+
+Current `maintainer `__:
+
+|maintainer-hbrunn|
+
This module is part of the `OCA/website `_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/website_altcha/__manifest__.py b/website_altcha/__manifest__.py
index a1a71c6110..73817e4d8b 100644
--- a/website_altcha/__manifest__.py
+++ b/website_altcha/__manifest__.py
@@ -5,24 +5,28 @@
"name": "Privacy Friendly Captcha",
"summary": """Use self hosted privacy friendly captcha for
verifying website users are not bots""",
- "version": "18.0.1.0.1",
+ "version": "18.0.1.1.0",
"license": "AGPL-3",
"author": "Dixmit,Odoo Community Association (OCA)",
+ "maintainers": ["hbrunn"],
"website": "https://github.com/OCA/website",
"depends": [
"website",
],
"assets": {
"web.assets_frontend": [
- "website_altcha/static/src/**/*.esm.js",
- "website_altcha/static/src/**/*.xml",
- "website_altcha/static/src/**/*.scss",
+ "website_altcha/static/src/*.esm.js",
+ "website_altcha/static/src/*.xml",
+ "website_altcha/static/src/*.scss",
],
"web.altcha_libs": [
"website_altcha/static/lib/altcha.js",
"website_altcha/static/lib/altcha-l10n.js",
"website_altcha/static/lib/altcha.css",
],
+ "web.assets_tests": [
+ "website_altcha/static/tests/tours/*.esm.js",
+ ],
},
"external_dependencies": {
"python": ["altcha"],
@@ -31,4 +35,7 @@
"security/ir.model.access.csv",
"views/res_config_settings_views.xml",
],
+ "demo": [
+ "demo/website_page.xml",
+ ],
}
diff --git a/website_altcha/demo/website_page.xml b/website_altcha/demo/website_page.xml
new file mode 100644
index 0000000000..ad2f5d5e13
--- /dev/null
+++ b/website_altcha/demo/website_page.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+ /website_altcha_demo
+
+
+
+
diff --git a/website_altcha/i18n/fr.po b/website_altcha/i18n/fr.po
new file mode 100644
index 0000000000..f025ce9373
--- /dev/null
+++ b/website_altcha/i18n/fr.po
@@ -0,0 +1,217 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * website_altcha
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2026-08-11 18:46+0000\n"
+"Last-Translator: Thibault REY \n"
+"Language-Team: none\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n > 1;\n"
+"X-Generator: Weblate 5.15.2\n"
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid ""
+". In that case the website_altcha_form class only takes care that "
+"the javascript code is loaded and activated."
+msgstr ""
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.res_config_settings_view_form
+msgid "Add Altcha functionality"
+msgstr ""
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.res_config_settings_view_form
+msgid "Algorithm"
+msgstr "Algorithme"
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_res_config_settings__altcha_algorithm
+#: model:ir.model.fields,field_description:website_altcha.field_website__altcha_algorithm
+msgid "Altcha Algorithm"
+msgstr "Algorithme Altcha"
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_res_config_settings__altcha_cost
+#: model:ir.model.fields,field_description:website_altcha.field_website__altcha_cost
+msgid "Altcha Cost"
+msgstr "Coût du challenge Altcha"
+
+#. module: website_altcha
+#: model:ir.model,name:website_altcha.model_altcha_key
+#: model:ir.model.fields,field_description:website_altcha.field_res_config_settings__altcha_key
+#: model:ir.model.fields,field_description:website_altcha.field_website__altcha_key
+msgid "Altcha Key"
+msgstr "Clé Altcha"
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_res_config_settings__altcha_private_key
+#: model:ir.model.fields,field_description:website_altcha.field_website__altcha_private_key
+msgid "Altcha Private Key"
+msgstr "Clé privée Altcha"
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_res_config_settings__altcha_timeout
+#: model:ir.model.fields,field_description:website_altcha.field_website__altcha_timeout
+msgid "Altcha Timeout"
+msgstr "Délai d'attente Altcha"
+
+#. module: website_altcha
+#: model:ir.model,name:website_altcha.model_res_config_settings
+msgid "Config Settings"
+msgstr "Paramètres de configuration"
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.res_config_settings_view_form
+msgid "Cost"
+msgstr "Coût"
+
+#. module: website_altcha
+#: model:ir.model.fields,help:website_altcha.field_res_config_settings__altcha_cost
+#: model:ir.model.fields,help:website_altcha.field_website__altcha_cost
+msgid ""
+"Cost factor for the hashing algorithm.\n"
+" For PBKDF2, this is the number of iterations.\n"
+" Default is 5000 for PBKDF2/SHA-512."
+msgstr ""
+"Facteur de coût pour l'algorithme de hachage.\n"
+" Pour PBKDF2, il s'agit du nombre d'itérations.\n"
+" La valeur par défaut est 5000 pour PBKDF2/SHA-512."
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_altcha_key__create_uid
+msgid "Created by"
+msgstr "Créé par"
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_altcha_key__create_date
+msgid "Created on"
+msgstr "Créé le"
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_altcha_key__display_name
+msgid "Display Name"
+msgstr "Nom"
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_res_config_settings__altcha_enabled
+msgid "Enable Altcha"
+msgstr "Activer Altcha"
+
+#. module: website_altcha
+#: model:ir.model.fields,help:website_altcha.field_res_config_settings__altcha_enabled
+msgid "Enable Altcha functionality on the website."
+msgstr "Activer Altcha sur le site web."
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_altcha_key__expires_at
+msgid "Expires At"
+msgstr "Expire le"
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.res_config_settings_view_form
+msgid "HMAC key secret"
+msgstr "Clé secrète HMAC"
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.res_config_settings_view_form
+msgid "HMAC secret"
+msgstr "Secret HMAC"
+
+#. module: website_altcha
+#: model:ir.model,name:website_altcha.model_ir_http
+msgid "HTTP Routing"
+msgstr "Routage HTTP"
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_altcha_key__id
+msgid "ID"
+msgstr "ID"
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_altcha_key__key
+msgid "Key"
+msgstr "Clé"
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_altcha_key__write_uid
+msgid "Last Updated by"
+msgstr "Dernière mise à jour par"
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_altcha_key__write_date
+msgid "Last Updated on"
+msgstr "Dernière mise à jour le"
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid ""
+"Note in both cases, you'll have to verify the token you received in your "
+"custom controller."
+msgstr ""
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid "Submit"
+msgstr ""
+
+#. module: website_altcha
+#. odoo-python
+#: code:addons/website_altcha/models/ir_http.py:0
+msgid "Suspicious activity detected by Altcha"
+msgstr "Activité suspecte détectée par Altcha"
+
+#. module: website_altcha
+#: model:ir.model.constraint,message:website_altcha.constraint_altcha_key_key_uniq
+msgid "The Altcha key must be unique."
+msgstr "La clé Altcha doit être unique."
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid ""
+"This is a demo form for website_altcha. Note how it automatically inserts "
+"the widget in the container containing the submit button"
+msgstr ""
+
+#. module: website_altcha
+#: model:ir.model.fields,help:website_altcha.field_res_config_settings__altcha_timeout
+#: model:ir.model.fields,help:website_altcha.field_website__altcha_timeout
+msgid "Time in minutes before a captcha expires. Default is 5 minutes."
+msgstr ""
+"Durée en minutes avant l'expiration d'un captcha. La valeur par défaut est "
+"de 5 minutes."
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.res_config_settings_view_form
+msgid "Timeout"
+msgstr "Délais d'attente"
+
+#. module: website_altcha
+#: model:ir.model.fields,field_description:website_altcha.field_altcha_key__used
+msgid "Used"
+msgstr "Utilisé"
+
+#. module: website_altcha
+#: model:ir.model,name:website_altcha.model_website
+msgid "Website"
+msgstr "Site web"
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid ""
+"You can however also place the widget yourself and configure it according to "
+"the"
+msgstr ""
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid "documentation"
+msgstr ""
diff --git a/website_altcha/i18n/it.po b/website_altcha/i18n/it.po
index 4f917f8381..01b17686cf 100644
--- a/website_altcha/i18n/it.po
+++ b/website_altcha/i18n/it.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
-"PO-Revision-Date: 2026-06-03 09:46+0000\n"
+"PO-Revision-Date: 2026-09-21 20:37+0000\n"
"Last-Translator: mymage \n"
"Language-Team: none\n"
"Language: it\n"
@@ -16,6 +16,15 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15.2\n"
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid ""
+". In that case the website_altcha_form class only takes care that "
+"the javascript code is loaded and activated."
+msgstr ""
+". In tal caso, la classe website_altcha_form si occupa "
+"esclusivamente di far sì che il codice JavaScript venga caricato e attivato."
+
#. module: website_altcha
#: model_terms:ir.ui.view,arch_db:website_altcha.res_config_settings_view_form
msgid "Add Altcha functionality"
@@ -144,6 +153,20 @@ msgstr "Ultimo aggiornamento di"
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid ""
+"Note in both cases, you'll have to verify the token you received in your "
+"custom controller."
+msgstr ""
+"Nota: in entrambi i casi, dovrai verificare il token ricevuto nel tuo "
+"controller personalizzato."
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid "Submit"
+msgstr "Invia"
+
#. module: website_altcha
#. odoo-python
#: code:addons/website_altcha/models/ir_http.py:0
@@ -155,6 +178,15 @@ msgstr "Attività sospetta rilevata da Altcha"
msgid "The Altcha key must be unique."
msgstr "La chiave Altcha deve essere univoca."
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid ""
+"This is a demo form for website_altcha. Note how it automatically inserts "
+"the widget in the container containing the submit button"
+msgstr ""
+"Questo è un modulo dimostrativo per website_altcha. Notare come inserisca "
+"automaticamente il widget nel contenitore che ospita il pulsante di invio"
+
#. module: website_altcha
#: model:ir.model.fields,help:website_altcha.field_res_config_settings__altcha_timeout
#: model:ir.model.fields,help:website_altcha.field_website__altcha_timeout
@@ -176,3 +208,17 @@ msgstr "Usata"
#: model:ir.model,name:website_altcha.model_website
msgid "Website"
msgstr "Sito web"
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid ""
+"You can however also place the widget yourself and configure it according to "
+"the"
+msgstr ""
+"Tuttavia, si può anche posizionare il widget autonomamente e configurarlo "
+"secondo le proprie esigenze"
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid "documentation"
+msgstr "documentazione"
diff --git a/website_altcha/i18n/website_altcha.pot b/website_altcha/i18n/website_altcha.pot
index 5ffdfad4f3..5ec58bd77a 100644
--- a/website_altcha/i18n/website_altcha.pot
+++ b/website_altcha/i18n/website_altcha.pot
@@ -13,6 +13,13 @@ msgstr ""
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid ""
+". In that case the website_altcha_form class only takes care that "
+"the javascript code is loaded and activated."
+msgstr ""
+
#. module: website_altcha
#: model_terms:ir.ui.view,arch_db:website_altcha.res_config_settings_view_form
msgid "Add Altcha functionality"
@@ -138,6 +145,18 @@ msgstr ""
msgid "Last Updated on"
msgstr ""
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid ""
+"Note in both cases, you'll have to verify the token you received in your "
+"custom controller."
+msgstr ""
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid "Submit"
+msgstr ""
+
#. module: website_altcha
#. odoo-python
#: code:addons/website_altcha/models/ir_http.py:0
@@ -149,6 +168,13 @@ msgstr ""
msgid "The Altcha key must be unique."
msgstr ""
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid ""
+"This is a demo form for website_altcha. Note how it automatically inserts "
+"the widget in the container containing the submit button"
+msgstr ""
+
#. module: website_altcha
#: model:ir.model.fields,help:website_altcha.field_res_config_settings__altcha_timeout
#: model:ir.model.fields,help:website_altcha.field_website__altcha_timeout
@@ -169,3 +195,15 @@ msgstr ""
#: model:ir.model,name:website_altcha.model_website
msgid "Website"
msgstr ""
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid ""
+"You can however also place the widget yourself and configure it according to"
+" the"
+msgstr ""
+
+#. module: website_altcha
+#: model_terms:ir.ui.view,arch_db:website_altcha.demo_page_view
+msgid "documentation"
+msgstr ""
diff --git a/website_altcha/static/description/index.html b/website_altcha/static/description/index.html
index cc3b4dafcc..9ec2c0824a 100644
--- a/website_altcha/static/description/index.html
+++ b/website_altcha/static/description/index.html
@@ -372,7 +372,7 @@
Privacy Friendly Captcha
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:47240fe67bd99327829258a0f654f2a147a5332dca76e6e66d67df44800779c8
+!! source digest: sha256:83e03d4eacacddd6d9b4c9b29eed8eee8f9d1bde28564186bfb553e8f5a29b6f
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
This module allows to use a Captcha System completly handled by Odoo.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
diff --git a/website_altcha/static/lib/altcha.js b/website_altcha/static/lib/altcha.js
index 1b70b870b7..7dab37aa64 100644
--- a/website_altcha/static/lib/altcha.js
+++ b/website_altcha/static/lib/altcha.js
@@ -1,4 +1,3 @@
-/* @odoo-module */
const DEV = false;
var is_array = Array.isArray;
var index_of = Array.prototype.indexOf;
diff --git a/website_altcha/static/src/altcha.esm.js b/website_altcha/static/src/altcha.esm.js
new file mode 100644
index 0000000000..996bfbb97a
--- /dev/null
+++ b/website_altcha/static/src/altcha.esm.js
@@ -0,0 +1,49 @@
+import {loadBundle} from "@web/core/assets";
+import {renderToString} from "@web/core/utils/render";
+import {session} from "@web/session";
+
+export const AltchaBaseFunctionality = {
+ altcha_prepend_to: "div.oe_login_buttons",
+ altcha_enabled: false,
+
+ altcha_init() {
+ this.altcha_enabled = Boolean(session.altcha_public_key);
+ },
+
+ /**
+ * Loads the altcha libraries.
+ *
+ * @returns {Promise|Boolean} promise if libs are loading else false if the altcha key is empty.
+ */
+ async altcha_load_libs() {
+ if (this.altcha_enabled) {
+ this._altchaReady = loadBundle(`web.altcha_libs`);
+ return this._altchaReady;
+ }
+ return false;
+ },
+ altcha_insert_widget() {
+ if (this.altcha_enabled && !this.$el.find("altcha-widget").length) {
+ this.$el
+ .find(this.altcha_prepend_to)
+ .prepend(renderToString("website_altcha.AltchaWidget", {}));
+ }
+ },
+};
+
+export const AltchaLegacyClassFunctionality = {
+ ...AltchaBaseFunctionality,
+ init() {
+ this._super(...arguments);
+ this.altcha_init();
+ },
+
+ async willStart() {
+ this.altcha_load_libs();
+ return this._super(...arguments);
+ },
+ start() {
+ this.altcha_insert_widget();
+ return this._super(...arguments);
+ },
+};
diff --git a/website_altcha/static/src/signup/altcha.scss b/website_altcha/static/src/altcha.scss
similarity index 100%
rename from website_altcha/static/src/signup/altcha.scss
rename to website_altcha/static/src/altcha.scss
diff --git a/website_altcha/static/src/signup/altcha.xml b/website_altcha/static/src/altcha.xml
similarity index 100%
rename from website_altcha/static/src/signup/altcha.xml
rename to website_altcha/static/src/altcha.xml
diff --git a/website_altcha/static/src/altcha/altcha.esm.js b/website_altcha/static/src/altcha/altcha.esm.js
deleted file mode 100644
index 4f18e82e33..0000000000
--- a/website_altcha/static/src/altcha/altcha.esm.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/* global document */
-
-import {loadBundle} from "@web/core/assets";
-import {session} from "@web/session";
-
-export class Altcha {
- /**
- * @override
- */
- constructor() {
- this._publicKey = session.altcha_public_key;
- }
- /**
- * Loads the altcha libraries.
- *
- * @returns {Promise|Boolean} promise if libs are loading else false if the altcha key is empty.
- */
- loadLibs() {
- if (this._publicKey) {
- this._altchaReady = loadBundle(`web.altcha_libs`);
- return this._altchaReady.then(() =>
- Boolean(document.querySelector(".o_altcha_widget"))
- );
- }
- return false;
- }
-}
diff --git a/website_altcha/static/src/generic.esm.js b/website_altcha/static/src/generic.esm.js
new file mode 100644
index 0000000000..c1130b6845
--- /dev/null
+++ b/website_altcha/static/src/generic.esm.js
@@ -0,0 +1,9 @@
+import {AltchaLegacyClassFunctionality} from "@website_altcha/altcha.esm";
+import publicWidget from "@web/legacy/js/public/public_widget";
+
+publicWidget.registry.GenericAltcha = publicWidget.Widget.extend({
+ ...AltchaLegacyClassFunctionality,
+ selector: "form.website_altcha_form, form.js_website_submit_form",
+ altcha_prepend_to:
+ "*:has( > input[type='submit']), *:has( > button[type='submit'])",
+});
diff --git a/website_altcha/static/src/signup.esm.js b/website_altcha/static/src/signup.esm.js
new file mode 100644
index 0000000000..49954f71b2
--- /dev/null
+++ b/website_altcha/static/src/signup.esm.js
@@ -0,0 +1,12 @@
+import {AltchaLegacyClassFunctionality} from "./altcha.esm";
+import publicWidget from "@web/legacy/js/public/public_widget";
+
+publicWidget.registry.SignupAltcha = publicWidget.Widget.extend({
+ ...AltchaLegacyClassFunctionality,
+ selector: ".oe_signup_form",
+});
+
+publicWidget.registry.ResetPasswordAltcha = publicWidget.Widget.extend({
+ ...AltchaLegacyClassFunctionality,
+ selector: ".oe_reset_password_form",
+});
diff --git a/website_altcha/static/src/signup/signup.esm.js b/website_altcha/static/src/signup/signup.esm.js
deleted file mode 100644
index fb5de1032d..0000000000
--- a/website_altcha/static/src/signup/signup.esm.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import "@website/snippets/s_website_form/000";
-import {Altcha} from "@website_altcha/altcha/altcha.esm";
-import publicWidget from "@web/legacy/js/public/public_widget";
-import {renderToString} from "@web/core/utils/render";
-
-export const AltchaFunctionality = {
- init() {
- this._super(...arguments);
- this._altcha = new Altcha();
- },
-
- async willStart() {
- this._altcha.loadLibs();
- return this._super(...arguments);
- },
- start: function () {
- if (this._altcha._publicKey && !this.$el.find(".o_altcha_widget").length) {
- this.$el
- .find("div.oe_login_buttons")
- .prepend(renderToString("website_altcha.AltchaWidget", {}));
- }
- return this._super(...arguments);
- },
-};
-
-publicWidget.registry.SignupAltcha = publicWidget.Widget.extend({
- ...AltchaFunctionality,
- selector: ".oe_signup_form",
-});
-
-publicWidget.registry.ResetPasswordAltcha = publicWidget.Widget.extend({
- ...AltchaFunctionality,
- selector: ".oe_reset_password_form",
-});
-
-publicWidget.registry.s_website_form.include({
- init() {
- this._super(...arguments);
- this._altcha = new Altcha();
- },
-
- async willStart() {
- this._altcha.loadLibs();
- return this._super(...arguments);
- },
-
- /**
- * @override
- */
- start: function () {
- const res = this._super(...arguments);
- if (this.$target[0].classList.contains("s_website_form_no_recaptcha")) {
- return res;
- }
- if (this._altcha._publicKey) {
- this.$el
- .find(".s_website_form_submit")
- .before(renderToString("website_altcha.AltchaWidget", {}));
- }
- return res;
- },
-});
-publicWidget.registry.EditModeWebsiteForm.include({
- start: function () {
- const res = this._super(...arguments);
- if (this.editableMode) {
- // We should delete the altcha widget in edit mode
- this.$el.find(".o_altcha_widget").remove();
- this.$el.find(".o_altcha_widget_container").remove();
- }
- return res;
- },
-});
diff --git a/website_altcha/static/src/website_form.esm.js b/website_altcha/static/src/website_form.esm.js
new file mode 100644
index 0000000000..f97201195d
--- /dev/null
+++ b/website_altcha/static/src/website_form.esm.js
@@ -0,0 +1,36 @@
+import "@website/snippets/s_website_form/000";
+import {AltchaLegacyClassFunctionality} from "./altcha.esm";
+import publicWidget from "@web/legacy/js/public/public_widget";
+import {renderToString} from "@web/core/utils/render";
+
+publicWidget.registry.s_website_form.include({
+ ...AltchaLegacyClassFunctionality,
+});
+publicWidget.registry.s_website_form.include({
+ altcha_insert_widget() {
+ // Intentional no-op
+ },
+ start: function () {
+ const res = this._super(...arguments);
+ if (this.$target[0].classList.contains("s_website_form_no_recaptcha")) {
+ return res;
+ }
+ if (this.altcha_enabled) {
+ this.$el
+ .find(".s_website_form_submit")
+ .before(renderToString("website_altcha.AltchaWidget", {}));
+ }
+ return res;
+ },
+});
+publicWidget.registry.EditModeWebsiteForm.include({
+ start: function () {
+ const res = this._super(...arguments);
+ if (this.editableMode) {
+ // We should delete the altcha widget in edit mode
+ this.$el.find(".o_altcha_widget").remove();
+ this.$el.find(".o_altcha_widget_container").remove();
+ }
+ return res;
+ },
+});
diff --git a/website_altcha/static/tests/tours/website_altcha_contact.esm.js b/website_altcha/static/tests/tours/website_altcha_contact.esm.js
new file mode 100644
index 0000000000..68321c3d08
--- /dev/null
+++ b/website_altcha/static/tests/tours/website_altcha_contact.esm.js
@@ -0,0 +1,41 @@
+/* Copyright 2026 Hunki Enterprises BV */
+import {registry} from "@web/core/registry";
+
+registry.category("web_tour.tours").add("website_altcha_contact", {
+ steps: () => [
+ {
+ content: "Fill in a name",
+ trigger: "input[name='name']",
+ run: "fill Testname",
+ },
+ {
+ content: "Fill in an email",
+ trigger: "input[name='email_from']",
+ run: "fill test_website_altcha@test.com",
+ },
+ {
+ content: "Fill in a subject",
+ trigger: "input[name='subject']",
+ run: "fill the subject",
+ },
+ {
+ content: "Fill in a question",
+ trigger: "textarea[name='description']",
+ run: "fill the question",
+ },
+ {
+ content: "Verify altcha",
+ trigger: "altcha-widget input[type='checkbox']",
+ run: "click",
+ },
+ {
+ content: "Verify altcha value exists",
+ trigger: "input[name='altcha']:not(:visible):not(:empty)",
+ },
+ {
+ content: "Click Submit",
+ trigger: "div.s_website_form_submit a",
+ run: "click",
+ },
+ ],
+});
diff --git a/website_altcha/static/tests/tours/website_altcha_generic.esm.js b/website_altcha/static/tests/tours/website_altcha_generic.esm.js
new file mode 100644
index 0000000000..3d4ad89cf0
--- /dev/null
+++ b/website_altcha/static/tests/tours/website_altcha_generic.esm.js
@@ -0,0 +1,21 @@
+/* Copyright 2026 Hunki Enterprises BV */
+import {registry} from "@web/core/registry";
+
+registry.category("web_tour.tours").add("website_altcha_generic", {
+ steps: () => [
+ {
+ content: "Verify altcha",
+ trigger: "altcha-widget input[type='checkbox']",
+ run: "click",
+ },
+ {
+ content: "Verify altcha value exists",
+ trigger: "input[name='altcha']:not(:visible):not(:empty)",
+ },
+ {
+ content: "Click Submit",
+ trigger: "button[type='submit']",
+ run: "click",
+ },
+ ],
+});
diff --git a/website_altcha/static/tests/tours/website_altcha_signup.esm.js b/website_altcha/static/tests/tours/website_altcha_signup.esm.js
new file mode 100644
index 0000000000..824087f07d
--- /dev/null
+++ b/website_altcha/static/tests/tours/website_altcha_signup.esm.js
@@ -0,0 +1,26 @@
+/* Copyright 2026 Hunki Enterprises BV */
+import {registry} from "@web/core/registry";
+
+registry.category("web_tour.tours").add("website_altcha_signup", {
+ steps: () => [
+ {
+ content: "Fill in a login",
+ trigger: "input[name='login']",
+ run: "fill admin",
+ },
+ {
+ content: "Verify altcha",
+ trigger: "altcha-widget input[type='checkbox']",
+ run: "click",
+ },
+ {
+ content: "Verify altcha value exists",
+ trigger: "input[name='altcha']:not(:visible):not(:empty)",
+ },
+ {
+ content: "Click Submit",
+ trigger: "button[type='submit']",
+ run: "click",
+ },
+ ],
+});
diff --git a/website_altcha/tests/__init__.py b/website_altcha/tests/__init__.py
index 65d22d8cf1..2fb64ca391 100644
--- a/website_altcha/tests/__init__.py
+++ b/website_altcha/tests/__init__.py
@@ -1 +1,2 @@
from . import test_altcha
+from . import test_altcha_tours
diff --git a/website_altcha/tests/common.py b/website_altcha/tests/common.py
new file mode 100644
index 0000000000..dadd8112b2
--- /dev/null
+++ b/website_altcha/tests/common.py
@@ -0,0 +1,18 @@
+# Copyright 2026 Dixmit
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo.tests import HttpCase, tagged
+
+
+@tagged("-at_install", "post_install")
+class Common(HttpCase):
+ @classmethod
+ def setUpClass(cls):
+ result = super().setUpClass()
+ cls.website = cls.env["website"].get_current_website()
+ cls.website.altcha_key = "test_key"
+ cls.website.altcha_private_key = "test_secret_key"
+ cls.website.altcha_cost = 2
+ # Forcing a small power of 2 cost for allowing scrypt to work
+ # and make everything faster
+ return result
diff --git a/website_altcha/tests/test_altcha.py b/website_altcha/tests/test_altcha.py
index 462f16638b..0fea6cb962 100644
--- a/website_altcha/tests/test_altcha.py
+++ b/website_altcha/tests/test_altcha.py
@@ -8,13 +8,15 @@
from requests.exceptions import HTTPError
from odoo.exceptions import UserError
-from odoo.tests import Form, HttpCase
+from odoo.tests import Form
from odoo.tools import mute_logger
from odoo.addons.website.tools import MockRequest
+from .common import Common
-class TestAltchaCreation(HttpCase):
+
+class TestAltchaCreation(Common):
@classmethod
def setUpClass(cls):
result = super().setUpClass()
diff --git a/website_altcha/tests/test_altcha_tours.py b/website_altcha/tests/test_altcha_tours.py
new file mode 100644
index 0000000000..4cfeef523e
--- /dev/null
+++ b/website_altcha/tests/test_altcha_tours.py
@@ -0,0 +1,24 @@
+# Copyright 2026 Hunki Enterprises BV
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo.addons.website_altcha.tests.common import Common
+
+
+class TestWebsiteAltchaTours(Common):
+ def test_contact_tour(self):
+ """
+ Run the contact test tour
+ """
+ self.start_tour("/contactus", "website_altcha_contact")
+
+ def test_signup_tour(self):
+ """
+ Run the signup test tour
+ """
+ self.start_tour("/web/reset_password", "website_altcha_signup")
+
+ def test_generic_tour(self):
+ """
+ Run the generic test tour
+ """
+ self.start_tour("/website_altcha_demo", "website_altcha_generic")
diff --git a/website_analytics_matomo/i18n/sl.po b/website_analytics_matomo/i18n/sl.po
index a09b524f50..150e30c190 100644
--- a/website_analytics_matomo/i18n/sl.po
+++ b/website_analytics_matomo/i18n/sl.po
@@ -9,70 +9,72 @@ msgstr ""
"Project-Id-Version: Odoo Server 11.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-01-16 03:45+0000\n"
-"PO-Revision-Date: 2018-01-16 03:45+0000\n"
-"Last-Translator: OCA Transbot , 2018\n"
+"PO-Revision-Date: 2026-08-11 04:59+0000\n"
+"Last-Translator: Matjaz Mozetic \n"
"Language-Team: Slovenian (https://www.transifex.com/oca/teams/23907/sl/)\n"
"Language: sl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
-"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || "
-"n%100==4 ? 2 : 3);\n"
+"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || "
+"n%100==4 ? 2 : 3;\n"
+"X-Generator: Weblate 5.15.2\n"
#. module: website_analytics_matomo
#: model_terms:ir.ui.view,arch_db:website_analytics_matomo.res_config_settings_view_form
msgid "Active Time (seconds)"
-msgstr ""
+msgstr "Aktivni čas (v sekundah)"
#. module: website_analytics_matomo
#: model:ir.model,name:website_analytics_matomo.model_res_config_settings
msgid "Config Settings"
-msgstr ""
+msgstr "Nastavitve"
#. module: website_analytics_matomo
#: model_terms:ir.ui.view,arch_db:website_analytics_matomo.res_config_settings_view_form
msgid "Enable User ID"
-msgstr ""
+msgstr "Omogoči ID uporabnika"
#. module: website_analytics_matomo
#: model_terms:ir.ui.view,arch_db:website_analytics_matomo.res_config_settings_view_form
msgid "Enable heartbeat"
-msgstr ""
+msgstr "Omogoči srčni utrip"
#. module: website_analytics_matomo
#: model:ir.model.fields,help:website_analytics_matomo.field_res_config_settings__matomo_heartbeat_timer
#: model:ir.model.fields,help:website_analytics_matomo.field_website__matomo_heartbeat_timer
msgid "How many seconds a tab needs to be active to be counted as viewed."
msgstr ""
+"Koliko sekund mora biti zavihek aktiven, da se lahko obravnava kot ogledan."
#. module: website_analytics_matomo
#: model:ir.model.fields,field_description:website_analytics_matomo.field_res_config_settings__has_matomo_analytics
#: model:ir.model.fields,field_description:website_analytics_matomo.field_website__has_matomo_analytics
msgid "Matomo Analytics"
-msgstr ""
+msgstr "Matomo analitika"
#. module: website_analytics_matomo
#: model:ir.model.fields,field_description:website_analytics_matomo.field_res_config_settings__matomo_enable_heartbeat
#: model:ir.model.fields,field_description:website_analytics_matomo.field_website__matomo_enable_heartbeat
msgid "Matomo Enable Heartbeat"
-msgstr ""
+msgstr "Matomo omogoči srčni utrip"
#. module: website_analytics_matomo
#: model:ir.model.fields,field_description:website_analytics_matomo.field_res_config_settings__matomo_enable_userid
#: model:ir.model.fields,field_description:website_analytics_matomo.field_website__matomo_enable_userid
msgid "Matomo Enable Userid"
-msgstr ""
+msgstr "Matomo omogoči ID uporabnika"
#. module: website_analytics_matomo
#: model:ir.model.fields,field_description:website_analytics_matomo.field_website__matomo_get_userid
msgid "Matomo Get Userid"
-msgstr ""
+msgstr "Matomo pridobi ID uporabnika"
#. module: website_analytics_matomo
#: model:ir.model.fields,field_description:website_analytics_matomo.field_res_config_settings__matomo_heartbeat_timer
#: model:ir.model.fields,field_description:website_analytics_matomo.field_website__matomo_heartbeat_timer
msgid "Matomo Heartbeat Timer"
-msgstr ""
+msgstr "Matomo – časomer srčnega utripa"
#. module: website_analytics_matomo
#: model:ir.model.fields,field_description:website_analytics_matomo.field_res_config_settings__matomo_analytics_host
@@ -83,7 +85,7 @@ msgstr "Matomo gostitelj"
#. module: website_analytics_matomo
#: model:ir.model.fields,field_description:website_analytics_matomo.field_website__matomo_analytics_host_url
msgid "Matomo host URL"
-msgstr ""
+msgstr "Matomo URL gostitelja"
#. module: website_analytics_matomo
#: model:ir.model.fields,field_description:website_analytics_matomo.field_res_config_settings__matomo_analytics_id
@@ -100,6 +102,11 @@ msgid ""
"page or focuses away from the tab)\n"
" then a ping request will be sent."
msgstr ""
+"Nastavite čas aktivnosti v sekundah. Šele če je bila stran ogledana vsaj "
+"toliko\n"
+" časa (in če uporabnik zapusti stran ali "
+"preusmeri pozornost stran od zavihka),\n"
+" bo ping-poizvedba poslana."
#. module: website_analytics_matomo
#: model:ir.model.fields,help:website_analytics_matomo.field_res_config_settings__matomo_analytics_id
@@ -113,11 +120,12 @@ msgstr "Matomo ID za identifikacijo spletne strani"
msgid ""
"The host/path your Matomo installation is accessible by on the internet."
msgstr ""
+"Gostitelj/pot, prek katere je vaša namestitev Matomo dostopna na internetu."
#. module: website_analytics_matomo
#: model_terms:ir.ui.view,arch_db:website_analytics_matomo.res_config_settings_view_form
msgid "Track visits in Matomo Analytics"
-msgstr ""
+msgstr "Sledenje obiskom v Matomo Analytics"
#. module: website_analytics_matomo
#: model:ir.model,name:website_analytics_matomo.model_website
@@ -127,7 +135,7 @@ msgstr "Spletna stran"
#. module: website_analytics_matomo
#: model_terms:ir.ui.view,arch_db:website_analytics_matomo.res_config_settings_view_form
msgid "https://matomo.host.name/"
-msgstr ""
+msgstr "https://matomo.host.name/"
#~ msgid ""
#~ "The host/path your Matomo installation is accessible by on the internet. "
diff --git a/website_blog_scheduled_publication/README.rst b/website_blog_scheduled_publication/README.rst
index 05c2e8efe4..6377a80a27 100644
--- a/website_blog_scheduled_publication/README.rst
+++ b/website_blog_scheduled_publication/README.rst
@@ -11,7 +11,7 @@ Blog Scheduled Publication
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:cef753abd2d2190dffaeb9c0e46f4a7f4668b6f462166a90b86c8a8e16dc88d1
+ !! source digest: sha256:8e0d97fc54eec30888d9864ded2931bb756cc3cfb3303dbd434cab7c2aabcc0a
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
diff --git a/website_blog_scheduled_publication/__manifest__.py b/website_blog_scheduled_publication/__manifest__.py
index 421c6b6a70..357fcdb6ff 100644
--- a/website_blog_scheduled_publication/__manifest__.py
+++ b/website_blog_scheduled_publication/__manifest__.py
@@ -4,7 +4,7 @@
{
"name": "Blog Scheduled Publication",
"summary": "Schedule blog posts publication date",
- "version": "18.0.1.0.0",
+ "version": "18.0.1.1.0",
"author": "Escodoo, Odoo Community Association (OCA)",
"maintainers": ["marcelsavegnago", "CristianoMafraJunior"],
"license": "AGPL-3",
diff --git a/website_blog_scheduled_publication/static/description/index.html b/website_blog_scheduled_publication/static/description/index.html
index b4104c8801..ba21b69df0 100644
--- a/website_blog_scheduled_publication/static/description/index.html
+++ b/website_blog_scheduled_publication/static/description/index.html
@@ -372,7 +372,7 @@
Blog Scheduled Publication
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-!! source digest: sha256:cef753abd2d2190dffaeb9c0e46f4a7f4668b6f462166a90b86c8a8e16dc88d1
+!! source digest: sha256:8e0d97fc54eec30888d9864ded2931bb756cc3cfb3303dbd434cab7c2aabcc0a
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
This module allows you to schedule blog posts for automatic publication
diff --git a/website_blog_scheduled_publication/views/blog_post_views.xml b/website_blog_scheduled_publication/views/blog_post_views.xml
index 05d5ab4e65..00b7d0c6fd 100644
--- a/website_blog_scheduled_publication/views/blog_post_views.xml
+++ b/website_blog_scheduled_publication/views/blog_post_views.xml
@@ -125,7 +125,7 @@
Scheduled Blog Postsblog.post
- calendar,tree,form
+ calendar,list,form[('scheduled_publication_date', '!=', False)]{}
diff --git a/website_event_altcha/README.rst b/website_event_altcha/README.rst
new file mode 100644
index 0000000000..a093f1b4ec
--- /dev/null
+++ b/website_event_altcha/README.rst
@@ -0,0 +1,89 @@
+.. image:: https://odoo-community.org/readme-banner-image
+ :target: https://odoo-community.org/get-involved?utm_source=readme
+ :alt: Odoo Community Association
+
+=============================================
+Privacy Friendly Captcha - Event Registration
+=============================================
+
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:f2eb50353d5a95993455af76b139a72c0735ecfa07a9cf5e016198b817a5d06c
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fwebsite-lightgray.png?logo=github
+ :target: https://github.com/OCA/website/tree/18.0/website_event_altcha
+ :alt: OCA/website
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/website-18-0/website-18-0-website_event_altcha
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/website&target_branch=18.0
+ :alt: Try me on Runboat
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+This module adds ALTCHA support to event registration forms
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+-------
+
+* Hunki Enterprises BV
+
+Contributors
+------------
+
+- Holger Brunn
+ (https://hunki-enterprises.com)
+
+Maintainers
+-----------
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+.. |maintainer-hbrunn| image:: https://github.com/hbrunn.png?size=40px
+ :target: https://github.com/hbrunn
+ :alt: hbrunn
+
+Current `maintainer `__:
+
+|maintainer-hbrunn|
+
+This module is part of the `OCA/website `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/website_event_altcha/__init__.py b/website_event_altcha/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/website_event_altcha/__manifest__.py b/website_event_altcha/__manifest__.py
new file mode 100644
index 0000000000..8134b74d0b
--- /dev/null
+++ b/website_event_altcha/__manifest__.py
@@ -0,0 +1,25 @@
+# Copyright 2026 Hunki Enterprises BV
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+{
+ "name": "Privacy Friendly Captcha - Event Registration",
+ "summary": "Support event registration with ALTCHA",
+ "version": "18.0.1.1.0",
+ "license": "AGPL-3",
+ "author": "Hunki Enterprises BV,Odoo Community Association (OCA)",
+ "maintainers": ["hbrunn"],
+ "website": "https://github.com/OCA/website",
+ "depends": [
+ "website_altcha",
+ "website_event",
+ ],
+ "assets": {
+ "web.assets_frontend": [
+ "website_event_altcha/static/src/*.esm.js",
+ ],
+ "web.assets_tests": [
+ "website_event_altcha/static/tests/tours/website_event_altcha.esm.js",
+ ],
+ },
+ "auto_install": True,
+}
diff --git a/website_event_altcha/i18n/it.po b/website_event_altcha/i18n/it.po
new file mode 100644
index 0000000000..c0d5085374
--- /dev/null
+++ b/website_event_altcha/i18n/it.po
@@ -0,0 +1,14 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
diff --git a/website_event_altcha/i18n/website_event_altcha.pot b/website_event_altcha/i18n/website_event_altcha.pot
new file mode 100644
index 0000000000..aadee09bfe
--- /dev/null
+++ b/website_event_altcha/i18n/website_event_altcha.pot
@@ -0,0 +1,13 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
diff --git a/website_event_altcha/pyproject.toml b/website_event_altcha/pyproject.toml
new file mode 100644
index 0000000000..4231d0cccb
--- /dev/null
+++ b/website_event_altcha/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["whool"]
+build-backend = "whool.buildapi"
diff --git a/website_event_altcha/readme/CONTRIBUTORS.md b/website_event_altcha/readme/CONTRIBUTORS.md
new file mode 100644
index 0000000000..cf8bf7205a
--- /dev/null
+++ b/website_event_altcha/readme/CONTRIBUTORS.md
@@ -0,0 +1 @@
+- Holger Brunn \ (https://hunki-enterprises.com)
diff --git a/website_event_altcha/readme/DESCRIPTION.md b/website_event_altcha/readme/DESCRIPTION.md
new file mode 100644
index 0000000000..33a3690849
--- /dev/null
+++ b/website_event_altcha/readme/DESCRIPTION.md
@@ -0,0 +1 @@
+This module adds ALTCHA support to event registration forms
diff --git a/website_event_altcha/static/description/icon.png b/website_event_altcha/static/description/icon.png
new file mode 100644
index 0000000000..d8e30bafa5
Binary files /dev/null and b/website_event_altcha/static/description/icon.png differ
diff --git a/website_event_altcha/static/description/index.html b/website_event_altcha/static/description/index.html
new file mode 100644
index 0000000000..b515601620
--- /dev/null
+++ b/website_event_altcha/static/description/index.html
@@ -0,0 +1,432 @@
+
+
+
+
+
+README.rst
+
+
+
+
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+
diff --git a/website_event_altcha/static/src/EventRegistrationForm.esm.js b/website_event_altcha/static/src/EventRegistrationForm.esm.js
new file mode 100644
index 0000000000..f375ef6324
--- /dev/null
+++ b/website_event_altcha/static/src/EventRegistrationForm.esm.js
@@ -0,0 +1,32 @@
+/* Copyright 2026 Hunki Enterprises BV */
+import {AltchaLegacyClassFunctionality} from "@website_altcha/altcha.esm";
+import EventRegistrationForm from "@website_event/js/website_event";
+
+EventRegistrationForm.include({
+ ...AltchaLegacyClassFunctionality,
+});
+
+EventRegistrationForm.include({
+ altcha_prepend_to: "div.modal-footer",
+ start() {
+ const result = this._super(...arguments);
+ const altcha_widget = this.el.querySelector("altcha-widget");
+ if (altcha_widget) {
+ const button = this.el.querySelector("button.btn-primary");
+ altcha_widget.addEventListener("statechange", (ev) => {
+ button.disabled = ev.detail.state !== "verified";
+ });
+ altcha_widget.addEventListener("load", () => {
+ button.disabled = altcha_widget.getState() !== "verified";
+ });
+ }
+ return result;
+ },
+ _addTurnstile(form) {
+ const altcha_widget = this.el.querySelector("altcha-widget");
+ if (altcha_widget) {
+ form.querySelector("div.modal-footer").prepend(altcha_widget);
+ }
+ this._super(...arguments);
+ },
+});
diff --git a/website_event_altcha/static/src/ticketDetailsWidget.esm.js b/website_event_altcha/static/src/ticketDetailsWidget.esm.js
new file mode 100644
index 0000000000..0a3c3548e0
--- /dev/null
+++ b/website_event_altcha/static/src/ticketDetailsWidget.esm.js
@@ -0,0 +1,20 @@
+/* Copyright 2026 Hunki Enterprises BV */
+import ticketDetailsWidget from "@website_event/js/website_event_ticket_details";
+
+ticketDetailsWidget.include({
+ start() {
+ this.disableSubmitIfUnverified();
+ return this._super(...arguments);
+ },
+ _onTicketQuantityChange() {
+ this._super(...arguments);
+ this.disableSubmitIfUnverified();
+ },
+ disableSubmitIfUnverified() {
+ const altcha_widget = this.el.querySelector("altcha-widget");
+ if (altcha_widget) {
+ const button = this.el.querySelector("button.btn-primary");
+ button.disabled = altcha_widget.getState() !== "verified";
+ }
+ },
+});
diff --git a/website_event_altcha/static/tests/tours/website_event_altcha.esm.js b/website_event_altcha/static/tests/tours/website_event_altcha.esm.js
new file mode 100644
index 0000000000..74983b2a3f
--- /dev/null
+++ b/website_event_altcha/static/tests/tours/website_event_altcha.esm.js
@@ -0,0 +1,51 @@
+/* Copyright 2026 Hunki Enterprises BV */
+import {registry} from "@web/core/registry";
+
+registry.category("web_tour.tours").add("website_event_altcha", {
+ steps: () => [
+ {
+ content: "Click register button",
+ trigger: "button[data-bs-target='#modal_ticket_registration']",
+ run: "click",
+ },
+ {
+ content: "Select a ticket",
+ trigger: "#o_wevent_tickets_collapse select",
+ run: "selectByIndex 1",
+ },
+ {
+ content: "Verify altcha",
+ trigger: "altcha-widget input[type='checkbox']",
+ run: "click",
+ },
+ {
+ content: "Click Submit",
+ trigger: "#registration_form button[type='submit']:enabled",
+ run: "click",
+ },
+ {
+ content: "Fill in name",
+ trigger: "#attendee_registration input[name*='-name-']:enabled",
+ async run() {
+ this.anchor.value = "Testname";
+ },
+ },
+ {
+ content: "Fill in email",
+ trigger: "#attendee_registration input[name*='-email-']:enabled",
+ async run() {
+ this.anchor.value = "test_website_altcha@test.com";
+ },
+ },
+ {
+ content: "Verify altcha value exists",
+ trigger:
+ "#attendee_registration input[name='altcha']:not(:visible):not(:empty)",
+ },
+ {
+ content: "Click submit",
+ trigger: "#attendee_registration button[type='submit']",
+ run: "click",
+ },
+ ],
+});
diff --git a/website_event_altcha/tests/__init__.py b/website_event_altcha/tests/__init__.py
new file mode 100644
index 0000000000..75a0c88ebe
--- /dev/null
+++ b/website_event_altcha/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_website_event_altcha
diff --git a/website_event_altcha/tests/test_website_event_altcha.py b/website_event_altcha/tests/test_website_event_altcha.py
new file mode 100644
index 0000000000..f23594165b
--- /dev/null
+++ b/website_event_altcha/tests/test_website_event_altcha.py
@@ -0,0 +1,13 @@
+# Copyright 2026 Hunki Enterprises BV
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo.addons.website_altcha.tests.common import Common
+
+
+class TestWebsiteEventAltcha(Common):
+ def test_event_registration_with_questions(self):
+ """
+ Test an event registration that contains questions
+ """
+ event = self.env.ref("event.event_7")
+ self.start_tour(f"/event/{event.id}", "website_event_altcha")
diff --git a/website_exclude_sitemap/README.rst b/website_exclude_sitemap/README.rst
new file mode 100644
index 0000000000..ebf263394e
--- /dev/null
+++ b/website_exclude_sitemap/README.rst
@@ -0,0 +1,250 @@
+.. image:: https://odoo-community.org/readme-banner-image
+ :target: https://odoo-community.org/get-involved?utm_source=readme
+ :alt: Odoo Community Association
+
+=======================
+Website Exclude Sitemap
+=======================
+
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:1ccf5cfe1f1d791baa26c077f275917693c8d97994ffa35c33769fd3ddd03198
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fwebsite-lightgray.png?logo=github
+ :target: https://github.com/OCA/website/tree/18.0/website_exclude_sitemap
+ :alt: OCA/website
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/website-18-0/website-18-0-website_exclude_sitemap
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/website&target_branch=18.0
+ :alt: Try me on Runboat
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+This module lets website administrators exclude public website URLs from
+``/sitemap.xml``.
+
+The module only affects sitemap generation. It does not unpublish pages,
+change access rights, or block direct access to the excluded URLs.
+
+It also clears the cached sitemap when website pages are created,
+deleted, or their URL changes, so sitemap URLs are refreshed in the next
+sitemap generation.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Use Cases / Context
+===================
+
+Odoo includes public website URLs in the generated sitemap.
+
+Some public URLs can be useful for direct navigation but add little or
+no SEO value when exposed in ``/sitemap.xml``.
+
+This module was created to keep the sitemap focused on strategic public
+pages while leaving normal website access untouched.
+
+Configuration
+=============
+
+Configure sitemap exclusions:
+
+1. Go to **Website > Configuration > Settings**.
+2. Locate the **Sitemap Exclusions** setting.
+3. Enter one or more paths or glob patterns.
+4. Save the settings.
+
+Values can be separated by:
+
+- line breaks;
+- commas;
+- semicolons.
+
+For example, these values are equivalent:
+
+.. code:: text
+
+ /customers/
+ /livechat
+ /blog/*/feed
+ /jobs/apply/
+ /profile/
+
+.. code:: text
+
+ /customers/, /livechat, /blog/*/feed, /jobs/apply/, /profile/
+
+.. code:: text
+
+ /customers/; /livechat; /blog/*/feed
+ /jobs/apply/, /profile/
+
+Lines starting with ``#`` are comments.
+
+See the usage section for the full pattern syntax: ``*`` matches inside
+one path segment, ``**`` crosses ``/``, and a pattern ending in ``/`` or
+in ``/**`` excludes that path and everything below it.
+
+The default value only covers paths that any Odoo website has. Add the
+paths that are specific to your own deployment, for example:
+
+.. code:: text
+
+ /website/info
+ /create-container-error
+ /machine-creation
+
+``/website/info`` is already left out of the sitemap by the core when
+its view is inactive; add it here when that view stays active.
+
+Use the **Reload Sitemap** button in the same settings block to clear
+the cached sitemap manually.
+
+Usage
+=====
+
+Sitemap exclusions:
+
+1. Create or publish website pages as usual.
+
+2. Configure the paths or patterns to exclude in **Website >
+ Configuration > Settings > Sitemap Exclusions**.
+
+ |Sitemap exclusions|
+
+3. Open ``/sitemap.xml``.
+
+4. Confirm that excluded URLs are absent and non-excluded public pages
+ are still present.
+
+Pattern syntax:
+
++------------------+--------------------------+-------------------------+
+| Pattern | Matches | Does not match |
++==================+==========================+=========================+
+| ``/livechat`` | ``/livechat`` | ``/livechat/room`` |
++------------------+--------------------------+-------------------------+
+| ``/customers/`` | ``/customers``, | ``/customers-list`` |
+| | ``/customers/acme``, | |
+| | ``/customers/acme/2024`` | |
++------------------+--------------------------+-------------------------+
+| ``/blog/*/feed`` | ``/blog/news/feed`` | ``/blog/a/b/feed``, |
+| | | ``/blog/news`` |
++------------------+--------------------------+-------------------------+
+| ``/solutions*`` | ``/solutions``, | ``/solutions/cloud`` |
+| | ``/solutions-cloud`` | |
++------------------+--------------------------+-------------------------+
+| ``/jobs/**`` | ``/jobs``, | ``/jobs-list`` |
+| | ``/jobs/apply/1``, | |
+| | ``/jobs/a/b/c`` | |
++------------------+--------------------------+-------------------------+
+
+In short:
+
+- ``*`` matches inside a single path segment; it never crosses a ``/``.
+- ``**`` crosses ``/``, so it matches any number of segments.
+- A pattern ending in ``/`` or in ``/**`` is a prefix: it excludes that
+ path and everything below it.
+- Any other pattern matches that exact path. The trailing slash of the
+ URL is ignored, so ``/livechat`` and ``/livechat/`` are the same path.
+
+Example:
+
+- configure ``/customers/``;
+- publish a page at ``/customers``;
+- publish another page at ``/customers/other``;
+- open ``/sitemap.xml``.
+
+Both URLs are excluded, because ``/customers/`` is a prefix. Use
+``/customers`` without the trailing slash to exclude only the first one.
+
+Ways to update the sitemap.xml:
+
+1. The sitemap cache is automatically cleared when website pages are
+ created or deleted, and when their URL, publication state,
+ indexation, publication date or website changes.
+
+2. The manual **Reload Sitemap** button can be used when a forced
+ sitemap cache clear is needed.
+
+ |Reload manual sitemap|
+
+.. |Sitemap exclusions| image:: https://raw.githubusercontent.com/OCA/website/18.0/website_exclude_sitemap/static/img/readme/config_sitemap_exclusions.png
+.. |Reload manual sitemap| image:: https://raw.githubusercontent.com/OCA/website/18.0/website_exclude_sitemap/static/img/readme/reload_manual_sitemap.png
+
+Known issues / Roadmap
+======================
+
+The exclusion list is a denylist evaluated after Odoo has collected
+every sitemap source, so it only removes URLs, never adds them. When a
+record is marked as indexed but its URL matches an exclusion pattern,
+the pattern wins and the URL stays out of ``/sitemap.xml``.
+
+Nothing warns about that in the record itself: a page whose **Indexed**
+toggle is on, or a published blog post, gives no hint that a global
+pattern is keeping it out of the sitemap. The only place where the
+exclusions are visible is \**Website
+
+ Configuration > Settings*\*.
+
+A future version could show the conflict where the record is edited, for
+example a message next to the **Indexed** toggle of ``website.page``.
+Covering models from other modules, such as ``blog.post``, belongs in
+bridge modules instead, to keep this module depending on ``website``
+alone.
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+-------
+
+* Binhex
+
+Contributors
+------------
+
+- `Binhex `__:
+
+ - Edilio Escalona Almira e.escalona@binhex.cloud
+
+Maintainers
+-----------
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+This module is part of the `OCA/website `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/website_exclude_sitemap/__init__.py b/website_exclude_sitemap/__init__.py
new file mode 100644
index 0000000000..ab9413bd19
--- /dev/null
+++ b/website_exclude_sitemap/__init__.py
@@ -0,0 +1,5 @@
+# Copyright 2026 Binhex
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from . import models
+from . import wizards
diff --git a/website_exclude_sitemap/__manifest__.py b/website_exclude_sitemap/__manifest__.py
new file mode 100644
index 0000000000..3a40d8a4d8
--- /dev/null
+++ b/website_exclude_sitemap/__manifest__.py
@@ -0,0 +1,20 @@
+# Copyright 2026 Binhex
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+{
+ "name": "Website Exclude Sitemap",
+ "summary": """
+ Exclude addresses from the sitemap.xml file according to
+ the values defined in the configuration
+ """,
+ "category": "Website",
+ "version": "18.0.1.0.0",
+ "author": "Binhex, Odoo Community Association (OCA)",
+ "website": "https://github.com/OCA/website",
+ "license": "AGPL-3",
+ "depends": [
+ "website",
+ ],
+ "data": [
+ "wizards/res_config_settings_views.xml",
+ ],
+}
diff --git a/website_exclude_sitemap/i18n/it.po b/website_exclude_sitemap/i18n/it.po
new file mode 100644
index 0000000000..9cba8a4ed7
--- /dev/null
+++ b/website_exclude_sitemap/i18n/it.po
@@ -0,0 +1,77 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * website_exclude_sitemap
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#. module: website_exclude_sitemap
+#: model:ir.model,name:website_exclude_sitemap.model_res_config_settings
+msgid "Config Settings"
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model_terms:ir.ui.view,arch_db:website_exclude_sitemap.res_config_settings_view_form
+msgid "Exclude specific URLs or wildcard patterns from sitemap.xml."
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model:ir.model.fields,help:website_exclude_sitemap.field_res_config_settings__sitemap_excluded_paths
+#: model:ir.model.fields,help:website_exclude_sitemap.field_website__sitemap_excluded_paths
+msgid ""
+"One path or pattern per line, comma, or semicolon. * matches inside one path"
+" segment, ** across segments, and a trailing / excludes a path and "
+"everything below it. Examples: /contactus, /solutions*, /blog/*/feed, "
+"/customers/, /jobs/**"
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model:ir.model,name:website_exclude_sitemap.model_website_page
+msgid "Page"
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model_terms:ir.ui.view,arch_db:website_exclude_sitemap.res_config_settings_view_form
+msgid "Reload Sitemap"
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model_terms:ir.ui.view,arch_db:website_exclude_sitemap.res_config_settings_view_form
+msgid ""
+"Separate paths with line breaks, commas, or semicolons.\n"
+" Use * inside a segment, ** across segments, and a\n"
+" trailing / to exclude a path and everything below it."
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model:ir.model.fields,field_description:website_exclude_sitemap.field_res_config_settings__sitemap_excluded_paths
+#: model:ir.model.fields,field_description:website_exclude_sitemap.field_website__sitemap_excluded_paths
+#: model_terms:ir.ui.view,arch_db:website_exclude_sitemap.res_config_settings_view_form
+msgid "Sitemap Exclusions"
+msgstr ""
+
+#. module: website_exclude_sitemap
+#. odoo-python
+#: code:addons/website_exclude_sitemap/wizards/res_config_settings.py:0
+msgid "Sitemap reloaded"
+msgstr ""
+
+#. module: website_exclude_sitemap
+#. odoo-python
+#: code:addons/website_exclude_sitemap/wizards/res_config_settings.py:0
+msgid "The cached sitemap has been cleared for the current website."
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model:ir.model,name:website_exclude_sitemap.model_website
+msgid "Website"
+msgstr ""
diff --git a/website_exclude_sitemap/i18n/website_exclude_sitemap.pot b/website_exclude_sitemap/i18n/website_exclude_sitemap.pot
new file mode 100644
index 0000000000..c370fc3a3a
--- /dev/null
+++ b/website_exclude_sitemap/i18n/website_exclude_sitemap.pot
@@ -0,0 +1,76 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * website_exclude_sitemap
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: website_exclude_sitemap
+#: model:ir.model,name:website_exclude_sitemap.model_res_config_settings
+msgid "Config Settings"
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model_terms:ir.ui.view,arch_db:website_exclude_sitemap.res_config_settings_view_form
+msgid "Exclude specific URLs or wildcard patterns from sitemap.xml."
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model:ir.model.fields,help:website_exclude_sitemap.field_res_config_settings__sitemap_excluded_paths
+#: model:ir.model.fields,help:website_exclude_sitemap.field_website__sitemap_excluded_paths
+msgid ""
+"One path or pattern per line, comma, or semicolon. * matches inside one path"
+" segment, ** across segments, and a trailing / excludes a path and "
+"everything below it. Examples: /contactus, /solutions*, /blog/*/feed, "
+"/customers/, /jobs/**"
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model:ir.model,name:website_exclude_sitemap.model_website_page
+msgid "Page"
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model_terms:ir.ui.view,arch_db:website_exclude_sitemap.res_config_settings_view_form
+msgid "Reload Sitemap"
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model_terms:ir.ui.view,arch_db:website_exclude_sitemap.res_config_settings_view_form
+msgid ""
+"Separate paths with line breaks, commas, or semicolons.\n"
+" Use * inside a segment, ** across segments, and a\n"
+" trailing / to exclude a path and everything below it."
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model:ir.model.fields,field_description:website_exclude_sitemap.field_res_config_settings__sitemap_excluded_paths
+#: model:ir.model.fields,field_description:website_exclude_sitemap.field_website__sitemap_excluded_paths
+#: model_terms:ir.ui.view,arch_db:website_exclude_sitemap.res_config_settings_view_form
+msgid "Sitemap Exclusions"
+msgstr ""
+
+#. module: website_exclude_sitemap
+#. odoo-python
+#: code:addons/website_exclude_sitemap/wizards/res_config_settings.py:0
+msgid "Sitemap reloaded"
+msgstr ""
+
+#. module: website_exclude_sitemap
+#. odoo-python
+#: code:addons/website_exclude_sitemap/wizards/res_config_settings.py:0
+msgid "The cached sitemap has been cleared for the current website."
+msgstr ""
+
+#. module: website_exclude_sitemap
+#: model:ir.model,name:website_exclude_sitemap.model_website
+msgid "Website"
+msgstr ""
diff --git a/website_exclude_sitemap/models/__init__.py b/website_exclude_sitemap/models/__init__.py
new file mode 100644
index 0000000000..5898d43284
--- /dev/null
+++ b/website_exclude_sitemap/models/__init__.py
@@ -0,0 +1,5 @@
+# Copyright 2026 Binhex
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from . import website
+from . import website_page
diff --git a/website_exclude_sitemap/models/website.py b/website_exclude_sitemap/models/website.py
new file mode 100644
index 0000000000..9619718ac8
--- /dev/null
+++ b/website_exclude_sitemap/models/website.py
@@ -0,0 +1,159 @@
+# Copyright 2026 Binhex
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+import re
+from urllib.parse import urlsplit
+
+from odoo import api, fields, models, tools
+
+SITEMAP_DEFAULT_EXCLUDED_PATHS = (
+ "/customers/",
+ "/livechat",
+ "/blog/*/feed",
+ "/jobs/apply/",
+ "/profile/",
+)
+
+SITEMAP_DEFAULT_EXCLUDED_PATHS_TEXT = "\n".join(SITEMAP_DEFAULT_EXCLUDED_PATHS)
+
+SITEMAP_WILDCARDS = {"**": ".*", "*": "[^/]*"}
+
+
+class Website(models.Model):
+ _inherit = "website"
+
+ sitemap_excluded_paths = fields.Text(
+ string="Sitemap Exclusions",
+ default=SITEMAP_DEFAULT_EXCLUDED_PATHS_TEXT,
+ help=(
+ "One path or pattern per line, comma, or semicolon. "
+ "* matches inside one path segment, ** across segments, and a "
+ "trailing / excludes a path and everything below it. "
+ "Examples: /contactus, /solutions*, /blog/*/feed, /customers/, "
+ "/jobs/**"
+ ),
+ )
+
+ @staticmethod
+ def _normalize_sitemap_path(value):
+ """Return the path to compare, without its trailing slash."""
+ value = (value or "").strip()
+ if not value:
+ return "/"
+ if "://" in value:
+ parsed = urlsplit(value)
+ value = parsed.path or "/"
+ if parsed.query:
+ value = f"{value}?{parsed.query}"
+ if not value.startswith("/"):
+ value = f"/{value}"
+ return "/" if value == "/" else value.rstrip("/")
+
+ @classmethod
+ def _normalize_sitemap_pattern(cls, value):
+ """Return the pattern, keeping the trailing slash that marks a prefix."""
+ value = (value or "").strip()
+ normalized = cls._normalize_sitemap_path(value)
+ if normalized == "/" or not value.endswith("/"):
+ return normalized
+ return f"{normalized}/"
+
+ @classmethod
+ def _split_sitemap_patterns(cls, excluded_paths):
+ """Split the raw exclusions text into normalized patterns."""
+ return [
+ cls._normalize_sitemap_pattern(pattern)
+ for pattern in re.split(r"[\n,;]+", excluded_paths or "")
+ if pattern.strip() and not pattern.lstrip().startswith("#")
+ ]
+
+ def _get_sitemap_excluded_patterns(self):
+ self.ensure_one()
+ return self._split_sitemap_patterns(self.sitemap_excluded_paths)
+
+ @classmethod
+ def _compile_sitemap_pattern(cls, pattern):
+ """Compile an exclusion pattern into a regex matched against a path.
+
+ ``*`` matches inside a single path segment, ``**`` crosses ``/``, and a
+ pattern ending in ``/`` or in ``/**`` is a prefix: it matches that path
+ and everything below it.
+ """
+ is_prefix = False
+ if pattern.endswith("/**"):
+ is_prefix = True
+ pattern = pattern[: -len("/**")]
+ elif pattern != "/" and pattern.endswith("/"):
+ is_prefix = True
+ pattern = pattern[:-1]
+ body = "".join(
+ SITEMAP_WILDCARDS.get(token, re.escape(token))
+ for token in re.split(r"(\*\*|\*)", pattern)
+ )
+ return re.compile(f"^{body}(/.*)?$" if is_prefix else f"^{body}$")
+
+ @tools.ormcache("excluded_paths")
+ def _get_sitemap_excluded_regexes(self, excluded_paths):
+ """Return the compiled patterns of the given raw exclusions text.
+
+ The result is cached per database on the text itself, so the patterns
+ are compiled once instead of once per sitemap entry.
+ """
+ return tuple(
+ self._compile_sitemap_pattern(pattern)
+ for pattern in self._split_sitemap_patterns(excluded_paths)
+ )
+
+ def _get_own_sitemap_excluded_regexes(self):
+ self.ensure_one()
+ return self._get_sitemap_excluded_regexes(self.sitemap_excluded_paths or "")
+
+ def _is_sitemap_path_excluded(self, path):
+ self.ensure_one()
+ normalized_path = self._normalize_sitemap_path(path)
+ return any(
+ regex.match(normalized_path)
+ for regex in self._get_own_sitemap_excluded_regexes()
+ )
+
+ def _clear_sitemap_cache(self):
+ IrAttachment = self.env["ir.attachment"].sudo()
+ for website in self:
+ IrAttachment.search(
+ [
+ ("type", "=", "binary"),
+ ("url", "=like", f"/sitemap-{website.id}-%"),
+ ]
+ ).unlink()
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ websites = super().create(vals_list)
+ websites.filtered("sitemap_excluded_paths")._clear_sitemap_cache()
+ return websites
+
+ def write(self, vals):
+ result = super().write(vals)
+ if "sitemap_excluded_paths" in vals:
+ self.env.registry.clear_cache()
+ self._clear_sitemap_cache()
+ return result
+
+ def _enumerate_pages(self, query_string=None, force=False):
+ """Drop the excluded URLs from the sitemap enumeration.
+
+ ``force=True`` means the caller wants every page, not the sitemap
+ listing: it is used by ``search_pages`` to feed the link autocomplete of
+ the editor. The exclusions only apply to the sitemap, so that call is
+ left untouched.
+ """
+ self.ensure_one()
+ pages = super()._enumerate_pages(query_string=query_string, force=force)
+ if force:
+ yield from pages
+ return
+ regexes = self._get_own_sitemap_excluded_regexes()
+ for page in pages:
+ path = self._normalize_sitemap_path(page.get("loc"))
+ if not any(regex.match(path) for regex in regexes):
+ yield page
diff --git a/website_exclude_sitemap/models/website_page.py b/website_exclude_sitemap/models/website_page.py
new file mode 100644
index 0000000000..6b984cd451
--- /dev/null
+++ b/website_exclude_sitemap/models/website_page.py
@@ -0,0 +1,41 @@
+# Copyright 2026 Binhex
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
+
+from odoo import api, models
+
+
+class WebsitePage(models.Model):
+ _inherit = "website.page"
+
+ _SITEMAP_CACHE_FIELDS = {
+ "url",
+ "website_published",
+ "website_indexed",
+ "date_publish",
+ "website_id",
+ }
+
+ def _get_sitemap_cache_websites(self):
+ website_id = self.website_id
+ if any(not page.website_id for page in self):
+ website_id |= self.env["website"].search([])
+ return website_id
+
+ @api.model_create_multi
+ def create(self, vals_list):
+ pages = super().create(vals_list)
+ pages._get_sitemap_cache_websites()._clear_sitemap_cache()
+ return pages
+
+ def write(self, vals):
+ websites = self._get_sitemap_cache_websites()
+ result = super().write(vals)
+ if self._SITEMAP_CACHE_FIELDS.intersection(vals):
+ (websites | self._get_sitemap_cache_websites())._clear_sitemap_cache()
+ return result
+
+ def unlink(self):
+ websites = self._get_sitemap_cache_websites()
+ result = super().unlink()
+ websites._clear_sitemap_cache()
+ return result
diff --git a/website_exclude_sitemap/pyproject.toml b/website_exclude_sitemap/pyproject.toml
new file mode 100644
index 0000000000..4231d0cccb
--- /dev/null
+++ b/website_exclude_sitemap/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["whool"]
+build-backend = "whool.buildapi"
diff --git a/website_exclude_sitemap/readme/CONFIGURE.md b/website_exclude_sitemap/readme/CONFIGURE.md
new file mode 100644
index 0000000000..40e4acd3a8
--- /dev/null
+++ b/website_exclude_sitemap/readme/CONFIGURE.md
@@ -0,0 +1,52 @@
+Configure sitemap exclusions:
+
+1. Go to **Website > Configuration > Settings**.
+2. Locate the **Sitemap Exclusions** setting.
+3. Enter one or more paths or glob patterns.
+4. Save the settings.
+
+Values can be separated by:
+
+- line breaks;
+- commas;
+- semicolons.
+
+For example, these values are equivalent:
+
+```text
+/customers/
+/livechat
+/blog/*/feed
+/jobs/apply/
+/profile/
+```
+
+```text
+/customers/, /livechat, /blog/*/feed, /jobs/apply/, /profile/
+```
+
+```text
+/customers/; /livechat; /blog/*/feed
+/jobs/apply/, /profile/
+```
+
+Lines starting with `#` are comments.
+
+See the usage section for the full pattern syntax: `*` matches inside one path
+segment, `**` crosses `/`, and a pattern ending in `/` or in `/**` excludes that
+path and everything below it.
+
+The default value only covers paths that any Odoo website has. Add the paths
+that are specific to your own deployment, for example:
+
+```text
+/website/info
+/create-container-error
+/machine-creation
+```
+
+`/website/info` is already left out of the sitemap by the core when its view is
+inactive; add it here when that view stays active.
+
+Use the **Reload Sitemap** button in the same settings block to clear the
+cached sitemap manually.
diff --git a/website_exclude_sitemap/readme/CONTEXT.md b/website_exclude_sitemap/readme/CONTEXT.md
new file mode 100644
index 0000000000..c8bddc83cd
--- /dev/null
+++ b/website_exclude_sitemap/readme/CONTEXT.md
@@ -0,0 +1,7 @@
+Odoo includes public website URLs in the generated sitemap.
+
+Some public URLs can be useful for direct navigation but add little or no SEO value when exposed
+in `/sitemap.xml`.
+
+This module was created to keep the sitemap focused on strategic public pages
+while leaving normal website access untouched.
diff --git a/website_exclude_sitemap/readme/CONTRIBUTORS.md b/website_exclude_sitemap/readme/CONTRIBUTORS.md
new file mode 100644
index 0000000000..4c895949c9
--- /dev/null
+++ b/website_exclude_sitemap/readme/CONTRIBUTORS.md
@@ -0,0 +1,2 @@
+- [Binhex](https://www.binhex.cloud):
+ - Edilio Escalona Almira
diff --git a/website_exclude_sitemap/readme/DESCRIPTION.md b/website_exclude_sitemap/readme/DESCRIPTION.md
new file mode 100644
index 0000000000..9efe28a477
--- /dev/null
+++ b/website_exclude_sitemap/readme/DESCRIPTION.md
@@ -0,0 +1,8 @@
+This module lets website administrators exclude public website URLs from
+`/sitemap.xml`.
+
+The module only affects sitemap generation. It does not unpublish pages, change
+access rights, or block direct access to the excluded URLs.
+
+It also clears the cached sitemap when website pages are created, deleted, or
+their URL changes, so sitemap URLs are refreshed in the next sitemap generation.
diff --git a/website_exclude_sitemap/readme/ROADMAP.md b/website_exclude_sitemap/readme/ROADMAP.md
new file mode 100644
index 0000000000..1b4ea35171
--- /dev/null
+++ b/website_exclude_sitemap/readme/ROADMAP.md
@@ -0,0 +1,14 @@
+The exclusion list is a denylist evaluated after Odoo has collected every
+sitemap source, so it only removes URLs, never adds them. When a record is
+marked as indexed but its URL matches an exclusion pattern, the pattern wins and
+the URL stays out of `/sitemap.xml`.
+
+Nothing warns about that in the record itself: a page whose **Indexed** toggle is
+on, or a published blog post, gives no hint that a global pattern is keeping it
+out of the sitemap. The only place where the exclusions are visible is **Website
+> Configuration > Settings**.
+
+A future version could show the conflict where the record is edited, for example
+a message next to the **Indexed** toggle of `website.page`. Covering models from
+other modules, such as `blog.post`, belongs in bridge modules instead, to keep
+this module depending on `website` alone.
diff --git a/website_exclude_sitemap/readme/USAGE.md b/website_exclude_sitemap/readme/USAGE.md
new file mode 100644
index 0000000000..d64f6898f3
--- /dev/null
+++ b/website_exclude_sitemap/readme/USAGE.md
@@ -0,0 +1,51 @@
+Sitemap exclusions:
+
+1. Create or publish website pages as usual.
+2. Configure the paths or patterns to exclude in **Website > Configuration >
+ Settings > Sitemap Exclusions**.
+
+ 
+
+3. Open `/sitemap.xml`.
+4. Confirm that excluded URLs are absent and non-excluded public pages are still
+ present.
+
+Pattern syntax:
+
+| Pattern | Matches | Does not match |
+| -------------- | ------------------------------------------------------- | ---------------------------------- |
+| `/livechat` | `/livechat` | `/livechat/room` |
+| `/customers/` | `/customers`, `/customers/acme`, `/customers/acme/2024` | `/customers-list` |
+| `/blog/*/feed` | `/blog/news/feed` | `/blog/a/b/feed`, `/blog/news` |
+| `/solutions*` | `/solutions`, `/solutions-cloud` | `/solutions/cloud` |
+| `/jobs/**` | `/jobs`, `/jobs/apply/1`, `/jobs/a/b/c` | `/jobs-list` |
+
+In short:
+
+- `*` matches inside a single path segment; it never crosses a `/`.
+- `**` crosses `/`, so it matches any number of segments.
+- A pattern ending in `/` or in `/**` is a prefix: it excludes that path and
+ everything below it.
+- Any other pattern matches that exact path. The trailing slash of the URL is
+ ignored, so `/livechat` and `/livechat/` are the same path.
+
+Example:
+
+- configure `/customers/`;
+- publish a page at `/customers`;
+- publish another page at `/customers/other`;
+- open `/sitemap.xml`.
+
+Both URLs are excluded, because `/customers/` is a prefix. Use `/customers`
+without the trailing slash to exclude only the first one.
+
+Ways to update the sitemap.xml:
+
+1. The sitemap cache is automatically cleared when website pages are created or
+ deleted, and when their URL, publication state, indexation, publication date
+ or website changes.
+
+2. The manual **Reload Sitemap** button can be used when a forced sitemap cache clear is
+ needed.
+
+ 
diff --git a/website_exclude_sitemap/static/description/icon.png b/website_exclude_sitemap/static/description/icon.png
new file mode 100644
index 0000000000..1dcc49c24f
Binary files /dev/null and b/website_exclude_sitemap/static/description/icon.png differ
diff --git a/website_exclude_sitemap/static/description/index.html b/website_exclude_sitemap/static/description/index.html
new file mode 100644
index 0000000000..29abdf15f5
--- /dev/null
+++ b/website_exclude_sitemap/static/description/index.html
@@ -0,0 +1,602 @@
+
+
+
+
+
+README.rst
+
+
+
+
This module lets website administrators exclude public website URLs from
+/sitemap.xml.
+
The module only affects sitemap generation. It does not unpublish pages,
+change access rights, or block direct access to the excluded URLs.
+
It also clears the cached sitemap when website pages are created,
+deleted, or their URL changes, so sitemap URLs are refreshed in the next
+sitemap generation.
See the usage section for the full pattern syntax: * matches inside
+one path segment, ** crosses /, and a pattern ending in / or
+in /** excludes that path and everything below it.
+
The default value only covers paths that any Odoo website has. Add the
+paths that are specific to your own deployment, for example:
* matches inside a single path segment; it never crosses a /.
+
** crosses /, so it matches any number of segments.
+
A pattern ending in / or in /** is a prefix: it excludes that
+path and everything below it.
+
Any other pattern matches that exact path. The trailing slash of the
+URL is ignored, so /livechat and /livechat/ are the same path.
+
+
Example:
+
+
configure /customers/;
+
publish a page at /customers;
+
publish another page at /customers/other;
+
open /sitemap.xml.
+
+
Both URLs are excluded, because /customers/ is a prefix. Use
+/customers without the trailing slash to exclude only the first one.
+
Ways to update the sitemap.xml:
+
+
The sitemap cache is automatically cleared when website pages are
+created or deleted, and when their URL, publication state,
+indexation, publication date or website changes.
+
+
The manual Reload Sitemap button can be used when a forced
+sitemap cache clear is needed.
The exclusion list is a denylist evaluated after Odoo has collected
+every sitemap source, so it only removes URLs, never adds them. When a
+record is marked as indexed but its URL matches an exclusion pattern,
+the pattern wins and the URL stays out of /sitemap.xml.
+
Nothing warns about that in the record itself: a page whose Indexed
+toggle is on, or a published blog post, gives no hint that a global
+pattern is keeping it out of the sitemap. The only place where the
+exclusions are visible is **Website
+
+Configuration > Settings**.
+
A future version could show the conflict where the record is edited, for
+example a message next to the Indexed toggle of website.page.
+Covering models from other modules, such as blog.post, belongs in
+bridge modules instead, to keep this module depending on website
+alone.
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
This module is part of the OCA/website project on GitHub.
+ Separate paths with line breaks, commas, or semicolons.
+ Use * inside a segment, ** across segments, and a
+ trailing / to exclude a path and everything below it.
+
+
+
+
+
+
+
+
diff --git a/website_llms/README.rst b/website_llms/README.rst
new file mode 100644
index 0000000000..7bf50701ca
--- /dev/null
+++ b/website_llms/README.rst
@@ -0,0 +1,133 @@
+.. image:: https://odoo-community.org/readme-banner-image
+ :target: https://odoo-community.org/get-involved?utm_source=readme
+ :alt: Odoo Community Association
+
+================
+Website llms.txt
+================
+
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:5b4d2d68e4bf8a6dd957be85d6de049252474e4a2f5276e2ee794d3e0e768f86
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fwebsite-lightgray.png?logo=github
+ :target: https://github.com/OCA/website/tree/18.0/website_llms
+ :alt: OCA/website
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/website-18-0/website-18-0-website_llms
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/website&target_branch=18.0
+ :alt: Try me on Runboat
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+This module adds support for serving a /llms.txt file in the website
+root. The content can be configured per website in the website settings.
+
+The llms.txt file provides information for Large Language Models (LLMs)
+about your website, including company information, services, and content
+links.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Usage
+=====
+
+Configuration
+-------------
+
+1. Go to **Website > Configuration > Settings**
+2. In the **Website Info** section, find the **llms.txt Content** field
+3. Enter the content you want to serve at /llms.txt
+4. Save the settings
+
+Usage
+-----
+
+After configuration, the /llms.txt file will be available at your
+website root:
+
+- If content is configured: The configured content will be served
+- If content is empty: A default content will be generated based on your
+ website information
+
+Example content format:
+
+::
+
+ # Your Website — Information for LLMs
+
+ ## Company
+ - About: https://yourdomain.com/aboutus
+ - Contact: https://yourdomain.com/contactus
+
+ ## Services
+ - Service 1: https://yourdomain.com/service1
+ - Service 2: https://yourdomain.com/service2
+
+ ## Content
+ - Blog: https://yourdomain.com/blog
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+-------
+
+* Escodoo
+
+Contributors
+------------
+
+- `Escodoo `__:
+
+ - Marcel Savegnago
+
+Maintainers
+-----------
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+.. |maintainer-marcelsavegnago| image:: https://github.com/marcelsavegnago.png?size=40px
+ :target: https://github.com/marcelsavegnago
+ :alt: marcelsavegnago
+
+Current `maintainer `__:
+
+|maintainer-marcelsavegnago|
+
+This module is part of the `OCA/website `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/website_llms/__init__.py b/website_llms/__init__.py
new file mode 100644
index 0000000000..f7209b1710
--- /dev/null
+++ b/website_llms/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import controllers
diff --git a/website_llms/__manifest__.py b/website_llms/__manifest__.py
new file mode 100644
index 0000000000..a3da0d420a
--- /dev/null
+++ b/website_llms/__manifest__.py
@@ -0,0 +1,19 @@
+{
+ "name": "Website llms.txt",
+ "version": "18.0.1.0.0",
+ "category": "Website",
+ "summary": """
+ This module adds support for serving a /llms.txt file in the website root.
+ The content can be configured per website in the website settings.
+ """,
+ "website": "https://github.com/OCA/website",
+ "author": "Escodoo, Odoo Community Association (OCA)",
+ "maintainers": ["marcelsavegnago"],
+ "depends": ["website"],
+ "data": [
+ "views/res_config_settings.xml",
+ ],
+ "installable": True,
+ "application": False,
+ "license": "AGPL-3",
+}
diff --git a/website_llms/controllers/__init__.py b/website_llms/controllers/__init__.py
new file mode 100644
index 0000000000..12a7e529b6
--- /dev/null
+++ b/website_llms/controllers/__init__.py
@@ -0,0 +1 @@
+from . import main
diff --git a/website_llms/controllers/main.py b/website_llms/controllers/main.py
new file mode 100644
index 0000000000..b76d2a1da0
--- /dev/null
+++ b/website_llms/controllers/main.py
@@ -0,0 +1,47 @@
+# Copyright 2026 - TODAY, Marcel Savegnago
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+from odoo import http
+from odoo.http import request
+
+
+class LlmsTxtController(http.Controller):
+ @http.route(
+ "/llms.txt",
+ type="http",
+ auth="public",
+ website=True,
+ sitemap=False,
+ methods=["GET"],
+ csrf=False,
+ )
+ def llms_txt(self, **kwargs):
+ """
+ Serve the llms.txt file with content configured in website settings.
+ If no content is configured, returns a default message.
+ """
+ website = request.website
+ content = website.llms_txt_content or ""
+
+ # If content is empty, return a default message
+ if not content.strip():
+ base_url = website.domain or request.httprequest.host_url.rstrip("/")
+ content = f"""# {website.name} — Information for LLMs
+
+## Company
+- Website: {base_url}
+- About: {base_url}/aboutus
+- Contact: {base_url}/contactus
+
+## Content
+- Blog: {base_url}/blog
+"""
+
+ # Ensure content ends with a newline
+ content = content.strip() + "\n"
+
+ headers = [
+ ("Content-Type", "text/plain; charset=utf-8"),
+ ("Cache-Control", "public, max-age=3600"),
+ ]
+
+ return request.make_response(content, headers=headers)
diff --git a/website_llms/i18n/it.po b/website_llms/i18n/it.po
new file mode 100644
index 0000000000..46cc945652
--- /dev/null
+++ b/website_llms/i18n/it.po
@@ -0,0 +1,80 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * website_llms
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2026-06-10 15:46+0000\n"
+"Last-Translator: mymage \n"
+"Language-Team: none\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 5.15.2\n"
+
+#. module: website_llms
+#: model_terms:ir.ui.view,arch_db:website_llms.res_config_settings_view_form_inherit_website_llms
+msgid ""
+"# Your Website — Information for LLMs\n"
+"\n"
+"## Company\n"
+"- About: https://yourdomain.com/aboutus\n"
+"- Contact: https://yourdomain.com/contactus\n"
+"\n"
+"## Content\n"
+"- Blog: https://yourdomain.com/blog"
+msgstr ""
+"# Il tuo sito web — Informazioni per i programmi LLM\n"
+"\n"
+"## Azienda\n"
+"- Chi siamo: https://yourdomain.com/aboutus\n"
+"- Contatti: https://yourdomain.com/contactus\n"
+"\n"
+"## Contenuti\n"
+"- Blog: https://yourdomain.com/blog"
+
+#. module: website_llms
+#: model:ir.model,name:website_llms.model_res_config_settings
+msgid "Config Settings"
+msgstr "Impostazioni configurazione"
+
+#. module: website_llms
+#: model:ir.model.fields,help:website_llms.field_res_config_settings__llms_txt_content
+#: model:ir.model.fields,help:website_llms.field_website__llms_txt_content
+msgid ""
+"Content to be served at /llms.txt. This file provides information for Large "
+"Language Models (LLMs) about your website."
+msgstr ""
+"Il contenuto verrà servito all'indirizzo /llms.txt. Questo file fornisce "
+"informazioni sui modelli linguistici di grandi dimensioni (LLM) relativi al "
+"tuo sito web."
+
+#. module: website_llms
+#: model_terms:ir.ui.view,arch_db:website_llms.res_config_settings_view_form_inherit_website_llms
+msgid ""
+"Content to be served at /llms.txt. This file provides\n"
+" information for Large Language Models (LLMs) about your website.\n"
+" Leave empty to use default content."
+msgstr ""
+"Il contenuto verrà visualizzato all'indirizzo /llms.txt. Questo "
+"file fornisce\n"
+" informazioni sui modelli linguistici di grandi "
+"dimensioni (LLM) relativi al tuo sito web.\n"
+" Lasciare vuoto per utilizzare il contenuto "
+"predefinito."
+
+#. module: website_llms
+#: model:ir.model,name:website_llms.model_website
+msgid "Website"
+msgstr "Sito web"
+
+#. module: website_llms
+#: model:ir.model.fields,field_description:website_llms.field_res_config_settings__llms_txt_content
+#: model:ir.model.fields,field_description:website_llms.field_website__llms_txt_content
+#: model_terms:ir.ui.view,arch_db:website_llms.res_config_settings_view_form_inherit_website_llms
+msgid "llms.txt Content"
+msgstr "Contenuto llms.txt"
diff --git a/website_llms/i18n/website_llms.pot b/website_llms/i18n/website_llms.pot
new file mode 100644
index 0000000000..667788e22d
--- /dev/null
+++ b/website_llms/i18n/website_llms.pot
@@ -0,0 +1,60 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * website_llms
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: website_llms
+#: model_terms:ir.ui.view,arch_db:website_llms.res_config_settings_view_form_inherit_website_llms
+msgid ""
+"# Your Website — Information for LLMs\n"
+"\n"
+"## Company\n"
+"- About: https://yourdomain.com/aboutus\n"
+"- Contact: https://yourdomain.com/contactus\n"
+"\n"
+"## Content\n"
+"- Blog: https://yourdomain.com/blog"
+msgstr ""
+
+#. module: website_llms
+#: model:ir.model,name:website_llms.model_res_config_settings
+msgid "Config Settings"
+msgstr ""
+
+#. module: website_llms
+#: model:ir.model.fields,help:website_llms.field_res_config_settings__llms_txt_content
+#: model:ir.model.fields,help:website_llms.field_website__llms_txt_content
+msgid ""
+"Content to be served at /llms.txt. This file provides information for Large "
+"Language Models (LLMs) about your website."
+msgstr ""
+
+#. module: website_llms
+#: model_terms:ir.ui.view,arch_db:website_llms.res_config_settings_view_form_inherit_website_llms
+msgid ""
+"Content to be served at /llms.txt. This file provides\n"
+" information for Large Language Models (LLMs) about your website.\n"
+" Leave empty to use default content."
+msgstr ""
+
+#. module: website_llms
+#: model:ir.model,name:website_llms.model_website
+msgid "Website"
+msgstr ""
+
+#. module: website_llms
+#: model:ir.model.fields,field_description:website_llms.field_res_config_settings__llms_txt_content
+#: model:ir.model.fields,field_description:website_llms.field_website__llms_txt_content
+#: model_terms:ir.ui.view,arch_db:website_llms.res_config_settings_view_form_inherit_website_llms
+msgid "llms.txt Content"
+msgstr ""
diff --git a/website_llms/models/__init__.py b/website_llms/models/__init__.py
new file mode 100644
index 0000000000..74b81abfff
--- /dev/null
+++ b/website_llms/models/__init__.py
@@ -0,0 +1,2 @@
+from . import website
+from . import res_config_settings
diff --git a/website_llms/models/res_config_settings.py b/website_llms/models/res_config_settings.py
new file mode 100644
index 0000000000..4c6cc08f1c
--- /dev/null
+++ b/website_llms/models/res_config_settings.py
@@ -0,0 +1,15 @@
+# Copyright 2026 - TODAY, Marcel Savegnago
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+from odoo import fields, models
+
+
+class ResConfigSettings(models.TransientModel):
+ _inherit = "res.config.settings"
+
+ llms_txt_content = fields.Text(
+ string="llms.txt Content",
+ related="website_id.llms_txt_content",
+ readonly=False,
+ help="Content to be served at /llms.txt. This file provides information "
+ "for Large Language Models (LLMs) about your website.",
+ )
diff --git a/website_llms/models/website.py b/website_llms/models/website.py
new file mode 100644
index 0000000000..49d3fdce47
--- /dev/null
+++ b/website_llms/models/website.py
@@ -0,0 +1,14 @@
+# Copyright 2026 - TODAY, Marcel Savegnago
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+from odoo import fields, models
+
+
+class Website(models.Model):
+ _inherit = "website"
+
+ llms_txt_content = fields.Text(
+ string="llms.txt Content",
+ help="Content to be served at /llms.txt. This file provides information "
+ "for Large Language Models (LLMs) about your website.",
+ translate=False,
+ )
diff --git a/website_llms/pyproject.toml b/website_llms/pyproject.toml
new file mode 100644
index 0000000000..4231d0cccb
--- /dev/null
+++ b/website_llms/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["whool"]
+build-backend = "whool.buildapi"
diff --git a/website_llms/readme/CONTRIBUTORS.md b/website_llms/readme/CONTRIBUTORS.md
new file mode 100644
index 0000000000..f618905979
--- /dev/null
+++ b/website_llms/readme/CONTRIBUTORS.md
@@ -0,0 +1,2 @@
+- [Escodoo](https://www.escodoo.com.br):
+ - Marcel Savegnago \
diff --git a/website_llms/readme/DESCRIPTION.md b/website_llms/readme/DESCRIPTION.md
new file mode 100644
index 0000000000..a8c3f43be4
--- /dev/null
+++ b/website_llms/readme/DESCRIPTION.md
@@ -0,0 +1,6 @@
+This module adds support for serving a /llms.txt file in the website
+root. The content can be configured per website in the website settings.
+
+The llms.txt file provides information for Large Language Models (LLMs)
+about your website, including company information, services, and content
+links.
diff --git a/website_llms/readme/USAGE.md b/website_llms/readme/USAGE.md
new file mode 100644
index 0000000000..2c17f5c54e
--- /dev/null
+++ b/website_llms/readme/USAGE.md
@@ -0,0 +1,30 @@
+## Configuration
+
+1. Go to **Website \> Configuration \> Settings**
+2. In the **Website Info** section, find the **llms.txt Content** field
+3. Enter the content you want to serve at /llms.txt
+4. Save the settings
+
+## Usage
+
+After configuration, the /llms.txt file will be available at your
+website root:
+
+- If content is configured: The configured content will be served
+- If content is empty: A default content will be generated based on your
+ website information
+
+Example content format:
+
+ # Your Website — Information for LLMs
+
+ ## Company
+ - About: https://yourdomain.com/aboutus
+ - Contact: https://yourdomain.com/contactus
+
+ ## Services
+ - Service 1: https://yourdomain.com/service1
+ - Service 2: https://yourdomain.com/service2
+
+ ## Content
+ - Blog: https://yourdomain.com/blog
diff --git a/website_llms/static/description/icon.png b/website_llms/static/description/icon.png
new file mode 100644
index 0000000000..1dcc49c24f
Binary files /dev/null and b/website_llms/static/description/icon.png differ
diff --git a/website_llms/static/description/index.html b/website_llms/static/description/index.html
new file mode 100644
index 0000000000..429aaf9d97
--- /dev/null
+++ b/website_llms/static/description/index.html
@@ -0,0 +1,480 @@
+
+
+
+
+
+README.rst
+
+
+
+
This module adds support for serving a /llms.txt file in the website
+root. The content can be configured per website in the website settings.
+
The llms.txt file provides information for Large Language Models (LLMs)
+about your website, including company information, services, and content
+links.
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+
diff --git a/website_llms/tests/__init__.py b/website_llms/tests/__init__.py
new file mode 100644
index 0000000000..b5a3200448
--- /dev/null
+++ b/website_llms/tests/__init__.py
@@ -0,0 +1 @@
+from . import test_controller
diff --git a/website_llms/tests/test_controller.py b/website_llms/tests/test_controller.py
new file mode 100644
index 0000000000..3403328002
--- /dev/null
+++ b/website_llms/tests/test_controller.py
@@ -0,0 +1,117 @@
+# Copyright 2026 - TODAY, Marcel Savegnago
+# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
+
+from odoo.tests.common import HttpCase, tagged
+
+
+@tagged("post_install", "-at_install")
+class TestLlmsTxtController(HttpCase):
+ def setUp(self):
+ super().setUp()
+ self.website = self.env["website"].sudo().get_current_website()
+
+ def _wait_remaining_requests(self, timeout=10):
+ # Bypass this to prevent the test suite from hanging for 10 seconds per test.
+ pass
+
+ def test_manifest_coverage(self):
+ """Test to execute the manifest file to satisfy coverage."""
+ import importlib.util
+ import os
+
+ manifest_path = os.path.join(
+ os.path.dirname(os.path.dirname(__file__)), "__manifest__.py"
+ )
+ spec = importlib.util.spec_from_file_location(
+ "website_llms.manifest", manifest_path
+ )
+ manifest_mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(manifest_mod)
+ self.assertTrue(isinstance(manifest_mod.__dict__, dict))
+
+ def test_llms_txt_with_configured_content(self):
+ """Test /llms.txt endpoint with configured content."""
+ # Configure custom content
+ custom_content = """# My Website — Information for LLMs
+
+## Company
+- About: https://example.com/about
+- Contact: https://example.com/contact
+
+## Services
+- Service 1: https://example.com/service1
+"""
+ self.website.llms_txt_content = custom_content
+
+ # Make request
+ response = self.url_open("/llms.txt", timeout=20)
+
+ # Assertions
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(
+ response.headers.get("Content-Type"),
+ "text/plain; charset=utf-8",
+ )
+ self.assertEqual(
+ response.headers.get("Cache-Control"),
+ "public, max-age=3600",
+ )
+ # Content should end with newline
+ self.assertTrue(response.text.endswith("\n"))
+ # Should contain configured content
+ self.assertIn("My Website", response.text)
+ self.assertIn("https://example.com/about", response.text)
+
+ def test_llms_txt_without_content(self):
+ """Test /llms.txt endpoint without configured content (default)."""
+ # Ensure no content is configured
+ self.website.llms_txt_content = False
+
+ # Make request
+ response = self.url_open("/llms.txt", timeout=20)
+
+ # Assertions
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(
+ response.headers.get("Content-Type"),
+ "text/plain; charset=utf-8",
+ )
+ self.assertEqual(
+ response.headers.get("Cache-Control"),
+ "public, max-age=3600",
+ )
+ # Content should end with newline
+ self.assertTrue(response.text.endswith("\n"))
+ # Should contain default content with website name
+ self.assertIn(self.website.name, response.text)
+ self.assertIn("Information for LLMs", response.text)
+ self.assertIn("## Company", response.text)
+ self.assertIn("## Content", response.text)
+
+ def test_llms_txt_with_empty_content(self):
+ """Test /llms.txt endpoint with empty content (should use default)."""
+ # Set empty content
+ self.website.llms_txt_content = ""
+
+ # Make request
+ response = self.url_open("/llms.txt", timeout=20)
+
+ # Assertions
+ self.assertEqual(response.status_code, 200)
+ # Should contain default content
+ self.assertIn(self.website.name, response.text)
+ self.assertIn("Information for LLMs", response.text)
+
+ def test_llms_txt_with_whitespace_content(self):
+ """Test /llms.txt endpoint with whitespace-only content (should use default)."""
+ # Set whitespace-only content
+ self.website.llms_txt_content = " \n\t "
+
+ # Make request
+ response = self.url_open("/llms.txt", timeout=20)
+
+ # Assertions
+ self.assertEqual(response.status_code, 200)
+ # Should contain default content (whitespace is stripped)
+ self.assertIn(self.website.name, response.text)
+ self.assertIn("Information for LLMs", response.text)
diff --git a/website_llms/views/res_config_settings.xml b/website_llms/views/res_config_settings.xml
new file mode 100644
index 0000000000..894af6f455
--- /dev/null
+++ b/website_llms/views/res_config_settings.xml
@@ -0,0 +1,38 @@
+
+
+
+
+ res.config.settings
+
+
+
+
+
+
+
+
+
+
+ Content to be served at /llms.txt. This file provides
+ information for Large Language Models (LLMs) about your website.
+ Leave empty to use default content.
+
+
+
+
+
+
+
+
+
diff --git a/website_product_document_download_counter/i18n/sl.po b/website_product_document_download_counter/i18n/sl.po
new file mode 100644
index 0000000000..0916c225b5
--- /dev/null
+++ b/website_product_document_download_counter/i18n/sl.po
@@ -0,0 +1,56 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * website_product_document_download_counter
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2026-08-11 07:46+0000\n"
+"Last-Translator: Matjaz Mozetic \n"
+"Language-Team: none\n"
+"Language: sl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || "
+"n%100==4 ? 2 : 3;\n"
+"X-Generator: Weblate 5.15.2\n"
+
+#. module: website_product_document_download_counter
+#. odoo-python
+#: code:addons/website_product_document_download_counter/models/product_document.py:0
+msgid "Can't download a document not shown on product page"
+msgstr "Ne morem prenesti dokumenta, ki ni prikazan na strani proizvoda"
+
+#. module: website_product_document_download_counter
+#. odoo-python
+#: code:addons/website_product_document_download_counter/models/product_document.py:0
+msgid "Can't download an inactive document"
+msgstr "Ne morem prenesti neaktivnega dokumenta"
+
+#. module: website_product_document_download_counter
+#: model:ir.model.fields,field_description:website_product_document_download_counter.field_product_document__download_count
+msgid "Download Count"
+msgstr "Št. prenosov"
+
+#. module: website_product_document_download_counter
+#: model:ir.model.fields,field_description:website_product_document_download_counter.field_product_document__download_count_enabled
+msgid "Download Count Enabled"
+msgstr "Števec prenosov omogočen"
+
+#. module: website_product_document_download_counter
+#: model:ir.model.fields,help:website_product_document_download_counter.field_product_document__download_count_enabled
+msgid "Enable download counting for this document when published on website"
+msgstr ""
+"Omogoči štetje prenosov tega dokumenta kadar je objavljen na spletni strani"
+
+#. module: website_product_document_download_counter
+#: model:ir.model.fields,help:website_product_document_download_counter.field_product_document__download_count
+msgid "Number of times this document has been downloaded"
+msgstr "Število prenosov tega dokumenta"
+
+#. module: website_product_document_download_counter
+#: model:ir.model,name:website_product_document_download_counter.model_product_document
+msgid "Product Document"
+msgstr "Dokument proizvoda"
diff --git a/website_social_media_link/README.rst b/website_social_media_link/README.rst
new file mode 100644
index 0000000000..8ce14f3bcd
--- /dev/null
+++ b/website_social_media_link/README.rst
@@ -0,0 +1,103 @@
+.. image:: https://odoo-community.org/readme-banner-image
+ :target: https://odoo-community.org/get-involved?utm_source=readme
+ :alt: Odoo Community Association
+
+============================
+Website - Social Media Links
+============================
+
+..
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! source digest: sha256:88ad463c621184b8de9692b19b2b15193505d0b794236c6c2a40d9c845b45ccd
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fwebsite-lightgray.png?logo=github
+ :target: https://github.com/OCA/website/tree/18.0/website_social_media_link
+ :alt: OCA/website
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/website-18-0/website-18-0-website_social_media_link
+ :alt: Translate me on Weblate
+.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
+ :target: https://runboat.odoo-community.org/builds?repo=OCA/website&target_branch=18.0
+ :alt: Try me on Runboat
+
+|badge1| |badge2| |badge3| |badge4| |badge5|
+
+This module is a bugfix. In odoo, when creating a new website, the URLs
+of the social media are copied from ``res.company`` model to ``website``
+model. Once done, it is not possible to user to change data on
+``website`` model, if an URL change, or a new social media is used.
+
+This module adds Social Medial links on websites back-end form view.
+
+|website|
+
+.. |website| image:: https://raw.githubusercontent.com/OCA/website/18.0/website_social_media_link/static/description/website_form.png
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Known issues / Roadmap
+======================
+
+We can stop use this module as soon as this patch has been reviewed and
+merged https://github.com/odoo/odoo/pull/237668.
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+-------
+
+* GRAP
+
+Contributors
+------------
+
+- Sylvain LE GAL
+
+Maintainers
+-----------
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+.. |maintainer-legalsylvain| image:: https://github.com/legalsylvain.png?size=40px
+ :target: https://github.com/legalsylvain
+ :alt: legalsylvain
+
+Current `maintainer `__:
+
+|maintainer-legalsylvain|
+
+This module is part of the `OCA/website `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/website_social_media_link/__init__.py b/website_social_media_link/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/website_social_media_link/__manifest__.py b/website_social_media_link/__manifest__.py
new file mode 100644
index 0000000000..b0c6cf2798
--- /dev/null
+++ b/website_social_media_link/__manifest__.py
@@ -0,0 +1,14 @@
+# Copyright 2026 - Today: GRAP https://www.grap.coop
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
+{
+ "name": "Website - Social Media Links",
+ "summary": "Display Social Medial Links on Website back-end.",
+ "version": "18.0.1.0.0",
+ "author": "GRAP, Odoo Community Association (OCA)",
+ "maintainers": ["legalsylvain"],
+ "website": "https://github.com/OCA/website",
+ "license": "AGPL-3",
+ "category": "Website",
+ "depends": ["website"],
+ "data": ["views/view_website.xml"],
+}
diff --git a/website_social_media_link/i18n/fr.po b/website_social_media_link/i18n/fr.po
new file mode 100644
index 0000000000..ba9ad86c75
--- /dev/null
+++ b/website_social_media_link/i18n/fr.po
@@ -0,0 +1,20 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * website_social_media_link
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: website_social_media_link
+#: model_terms:ir.ui.view,arch_db:website_social_media_link.view_website_form
+msgid "Social Media"
+msgstr "Média Sociaux"
diff --git a/website_social_media_link/i18n/it.po b/website_social_media_link/i18n/it.po
new file mode 100644
index 0000000000..1041a247ce
--- /dev/null
+++ b/website_social_media_link/i18n/it.po
@@ -0,0 +1,22 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * website_social_media_link
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: 2026-09-21 20:37+0000\n"
+"Last-Translator: mymage \n"
+"Language-Team: none\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Weblate 5.15.2\n"
+
+#. module: website_social_media_link
+#: model_terms:ir.ui.view,arch_db:website_social_media_link.view_website_form
+msgid "Social Media"
+msgstr "Social media"
diff --git a/website_social_media_link/i18n/website_social_media_link.pot b/website_social_media_link/i18n/website_social_media_link.pot
new file mode 100644
index 0000000000..966de4dc69
--- /dev/null
+++ b/website_social_media_link/i18n/website_social_media_link.pot
@@ -0,0 +1,19 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * website_social_media_link
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 18.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: website_social_media_link
+#: model_terms:ir.ui.view,arch_db:website_social_media_link.view_website_form
+msgid "Social Media"
+msgstr ""
diff --git a/website_social_media_link/pyproject.toml b/website_social_media_link/pyproject.toml
new file mode 100644
index 0000000000..4231d0cccb
--- /dev/null
+++ b/website_social_media_link/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["whool"]
+build-backend = "whool.buildapi"
diff --git a/website_social_media_link/readme/CONTRIBUTORS.md b/website_social_media_link/readme/CONTRIBUTORS.md
new file mode 100644
index 0000000000..3442186d90
--- /dev/null
+++ b/website_social_media_link/readme/CONTRIBUTORS.md
@@ -0,0 +1 @@
+- Sylvain LE GAL \<\>
diff --git a/website_social_media_link/readme/DESCRIPTION.md b/website_social_media_link/readme/DESCRIPTION.md
new file mode 100644
index 0000000000..0859daa5f2
--- /dev/null
+++ b/website_social_media_link/readme/DESCRIPTION.md
@@ -0,0 +1,11 @@
+This module is a bugfix.
+In odoo, when creating a new website, the URLs of the social
+media are copied from `res.company` model to `website` model.
+Once done, it is not possible to user to change data
+on `website` model, if an URL change, or a new social media
+is used.
+
+This module adds Social Medial links on websites back-end
+form view.
+
+
diff --git a/website_social_media_link/readme/ROADMAP.md b/website_social_media_link/readme/ROADMAP.md
new file mode 100644
index 0000000000..9c718a0b9b
--- /dev/null
+++ b/website_social_media_link/readme/ROADMAP.md
@@ -0,0 +1,3 @@
+We can stop use this module as soon as this patch has been reviewed
+and merged https://github.com/odoo/odoo/pull/237668.
+
\ No newline at end of file
diff --git a/website_social_media_link/static/description/icon.png b/website_social_media_link/static/description/icon.png
new file mode 100644
index 0000000000..1dcc49c24f
Binary files /dev/null and b/website_social_media_link/static/description/icon.png differ
diff --git a/website_social_media_link/static/description/index.html b/website_social_media_link/static/description/index.html
new file mode 100644
index 0000000000..5d73f32389
--- /dev/null
+++ b/website_social_media_link/static/description/index.html
@@ -0,0 +1,442 @@
+
+
+
+
+
+README.rst
+
+
+
+
This module is a bugfix. In odoo, when creating a new website, the URLs
+of the social media are copied from res.company model to website
+model. Once done, it is not possible to user to change data on
+website model, if an URL change, or a new social media is used.
+
This module adds Social Medial links on websites back-end form view.
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us to smash it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.