From ba7d9ed902bb7adc40dd7bf9930f6a5b0043c946 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 3 Jul 2026 05:28:42 +0200 Subject: [PATCH 1/5] [DEL] website_altcha: unnecessary modification of upstream file --- website_altcha/static/lib/altcha.js | 1 - 1 file changed, 1 deletion(-) 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; From e96b01a22b89d4c822c820f5c23970665ab6e690 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 3 Jul 2026 11:48:49 +0200 Subject: [PATCH 2/5] [ADD] website_event_altcha --- website_event_altcha/README.rst | 85 ++++ website_event_altcha/__init__.py | 0 website_event_altcha/__manifest__.py | 25 + website_event_altcha/pyproject.toml | 3 + website_event_altcha/readme/CONTRIBUTORS.md | 1 + website_event_altcha/readme/DESCRIPTION.md | 1 + .../static/description/icon.png | Bin 0 -> 6771 bytes .../static/description/index.html | 426 ++++++++++++++++++ .../static/src/EventRegistrationForm.esm.js | 32 ++ .../static/src/ticketDetailsWidget.esm.js | 20 + .../tests/tours/website_event_altcha.esm.js | 51 +++ website_event_altcha/tests/__init__.py | 1 + .../tests/test_website_event_altcha.py | 13 + 13 files changed, 658 insertions(+) create mode 100644 website_event_altcha/README.rst create mode 100644 website_event_altcha/__init__.py create mode 100644 website_event_altcha/__manifest__.py create mode 100644 website_event_altcha/pyproject.toml create mode 100644 website_event_altcha/readme/CONTRIBUTORS.md create mode 100644 website_event_altcha/readme/DESCRIPTION.md create mode 100644 website_event_altcha/static/description/icon.png create mode 100644 website_event_altcha/static/description/index.html create mode 100644 website_event_altcha/static/src/EventRegistrationForm.esm.js create mode 100644 website_event_altcha/static/src/ticketDetailsWidget.esm.js create mode 100644 website_event_altcha/static/tests/tours/website_event_altcha.esm.js create mode 100644 website_event_altcha/tests/__init__.py create mode 100644 website_event_altcha/tests/test_website_event_altcha.py diff --git a/website_event_altcha/README.rst b/website_event_altcha/README.rst new file mode 100644 index 0000000000..fc6fce375b --- /dev/null +++ b/website_event_altcha/README.rst @@ -0,0 +1,85 @@ +============================================= +Privacy Friendly Captcha - Event Registration +============================================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:6ed4773e07efe30601ad749fad1acac11076acd30f5741f042de4108ef01205a + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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/licence-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..a4d93c073e --- /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.0.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/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 0000000000000000000000000000000000000000..d8e30bafa521c7207da9030458badd423a6c3ec8 GIT binary patch literal 6771 zcmchcRa6^aw8n7>?$9EoxKp4-11%IOZiN7)K+)pv?(PJaw73SR!Gjg|;9eYx7l)hw zeZBAZVP@8vHEYhy-us;W{k{{Wt}0IecmY5`K_U30@KFEj1&&zz%NFHfz{cLVh$dRan-DT}Uaftv3~pyUnGX9MrZ@#th}Fd-mR4gt*|8aM6I zg1})AwK@rEk!@ojML$B{jN`Qx1kVnlv?x8zYzJdjslU9A&5VdZ4Rc9%IZ^SJOnA;* zZew2S*v+|a!&9r`HD@F?>wzqxg!%-Bh6SRLQlbGAKsfBLOc)CS|6gu&{aiu`hi^cA zeQ$z-(D?ZBl=~?FqRygox1BK9k&aDgx3P^wb5qmPt`scJ{IvpzJ1m%rf(1Z85gy@F zItx!ECJG4XsNu^WFb!#2vA5iJ_2c338yy9QV=4LhImm<*l@HBKnnXB=yO|pTcf4W~gmX+=A z=M2=D6rZ%>&i8e`80K3VvA|wwdfyEUqoCM+V;R48v>=&U z<#A(WQ%^7YV|ZM+WleHqC6pz_$;gMtbBtCBM$p|KMHVi+YGrGE@Npz6LZ>v_vV9q5N-afFM7h+KuRBn-IR~2;%E}=&Ca(ny|yc1y#kzz07WM=={{o z_|eJEq;{dwX-h)48sB@5Q4MCtm@=d|ji?&M#5{I=8O%UMsq-bra|N~@!2q7L!{O%r zdbHT~>sMi6ioMKfSK2Vuq#Zi_)O6ZXvwYdCG>jl9QjsMGxTFhN&lQ{9cWf*OJ@l$9 zWlxyznJXEY8XcS2{YoH^ zFlM_`HDtil$J@ER-Al3Pl?aHMbaP`D8;DXdXRte3QC`a5^Yp6Dk>W*J}>E>{gcAw2{N+MTIwG z`6p@qNYb;)w;I^mie+=f(Ywyvu0>Sh>epeYkPbjJG5CS}hEP^9@+ z*QoE?<6*hZtG4fZUNP&!^NGC7x?zZV}^ZyqeskJsd}GEM=snN z!EID|vIuK59|l|O&<4M9dh%_awDS2w!l!(q$%*Oj|8UZA7-%u~2+;#+XwYZB3o``( zmuw$TI@caHB6#qL{FD2Nlz@~m=r%YOd+LMR!$2XqKl{t3XP!rft=}YowR8a;H$U8l z!C=%W{hq-AhPAHRHT`-yf&toBd*q1CK2%TzBd_7XYD+C{mh8y(tnMM7QNPab7c?AM z!0ZO*ca;;Y-1G^LaJ>0%5Xu#^=6)Q+Ag4*4os=DswP0c`tVSvv@X_{q-E~gLN&@TFfpC9N=O$uf%mp3 zUX(%7A<)e&7Oi5>kj|%}w7kyec9LRQd~;+^SY8Hg&aMs)Mw|3R7JHb6VVOK`^F3vJ z?euRCznxVr6f_O@u>V_-Nc^MhY|KT|-JP9A&Lq&*?Jz+KCbNhpf`%$C zDCl^Ia3szDZ$@wCQqIElo4ASF-ek#5-}k>nwVQaJZ*RR?{3$x|TOv{Jb z)sf9`F%uTnp)MPTj>o1Ipk~|$&;6pVpYmHje)d776u5UZ-F*~tp_DwD!JRiuko z8m;*Ykpcx!RFuG})X>(z6*}{Z^>p!0Z?BPyOPR~KG-E_oAf+rEo{HjixrYLyw6%g( z>VFpYz3H2+IE;=K^Y($TVaC1Om4l->WEp;4?Fl_#iGD+EYvjN1@2Gy(bl?eXDJYIk zNfzCOGqgwtuS=ZO|NIk|nG1DXx3NKEIj`i?rqQ!ET{8MhAqC|R!e2QT|DDW<(#;PrG*ic!x!N_$H3pO4`= z!1Ks-?VkP~(eaFFF50 zw$_$8`H8T%zAYF~`p*@O0?=(n!CKIri?hAN%{rf&l%d;EMzIJsKmsD=srl;iOvE*b`NMgHjkO0sSSF)*xPJ(istMfT;>?=C&=rN(~nq-xZ z&$E+i?|snP%RPELiMTac?29)M286K#rsG-L^=45A^oMk|Cf!${*Cg^)Sa;ZaSBQV^ zSXUNq-;pDPV7qA*f9Z%o$HNa@lEJGU007V$^T2PI|HiG1Ig9c0roZ)W*xgFtVpY>w z=43@ig)?ovp8zCCyy4A#S`h-K)Xh5I)1N=CblHV{4QPMExG}ezUsGLie=;e~jIrsf z-$IY~kT|s%9V&~-rshsg=^jgeoS9SkN`Pdmql=Pq@1jwEhp^6BFozJ|oe95138IYU zeLOK4EkURA(^I12C`WjO2sb&W{$x8X7onC?6%U347ydT%LVhQU>W?-V3r&ehB5DkCEw0 zT>Yx0&oQ+1sQrY}(k}ikt2@_n-JVNV3QrniSxiQgzP=SU zs+{p%iGLZJzmvMv@s2kDNIL8mROi4!{FSFfg~n>!g~E|_XFTECO!k<8qH z>ONwwPNf@b-LD%n8sE{k6`HY45dAPWFNlAHMBv!@b=*kfNe+4}i|8o=D5@^y;*!U* z!Zt#1nglS2tcBhMi+TF0zv7-Kzb#~z=23s~l1J(>l#$E%c+nU_EOd*;u)5l2*!rW} zLzglpq+sBM1hr79h7nQo9xV*o)mhg2Q8as=$WT2HLQ&8u^~~^$!+7a+9Xf1ow@e-V z{r5uWliRc{t7|?wFZZd7kR~#d-r_k`Klrv9>Y%0&*9xCE8U0e1?O@zpZhH5+hPtSxu15!lt2D*GNHTv_~cZZWK7uS2ayILB>CZL+QsE{P94abZ@Nh;m2ot(o&o{5xGiS`KZ}(v`5UQ)A zw0F}{!0&D?Cqf|FLyyz>k#eZ<3=UF0lot!pZ0)7tkCLW=d^hh@XNEd^#@#A zcWwz`al8jL;3kA59`6roabhwu!skJgktCYRsA1xcBA}YiuL1j{O)48J3d}k3444#% zGA-@c6o>-Iax4%qoVg(F8{YNw2%G!~+#TzadQ#)wiD%#&5Yj^D8i?QbIG!R67;E!- zTGyK^DsqfD%vDb{@Z3>m^L+U`m7GCSv$(GAASNbri3Qr$7LnTB`A(NMqXCGNOZZW5eIl88q$8!%u>8u>3 z4c}c_=9^@g3k3M1a4=Z&&reO;4CWra&xt2o+U#pId+bsJhM7d)c&ussftQgXuGe06 z^A(n;UisgcIG;<5=btDl!tm+O16Ay*ZU@;ahK2`eKE5>x#`l%%aj@Iu$YPiI{@8!E zRp2B_7?GAs7tX|b+1k*km+w>kLhCO&^U#p@)5&vnVo~|p4?*fL_p9ytL$Et_&dQq3 ze*O&sp_#4m04$)wYI;1OfxiB1lb!IMHH$<2)ycw7zk@PP(g;kXCSp~^h!a4`yqz|+mH$?Nu$+51sFvr!*1rV5X?9c zaBe1!=`?AeZa9>YTZNiG4$%gz`irN2K_AOK%o1z#U6bUAf5RvOUDCrQI$%}4xmt4g z)R8y*TmAOlwDi5GXX0KJ32P~(jzRWuFD|~(nU>mZh?*tlpZ>Ux%E~)rXFAm<^3~SO zwjAd*`gJbm3FLW&RQ#8&6Z2_;7IJ64Sk+%TB8SFZz=L6KXq1^zlOl9MTtfDd< zyP~3)|6|&o8?(;5wHrqt!)-R(E(JxODXhb=sq)`p&4z08^}ijJ9QJ@qja#V66w>QC z)y9nLcivI9{Qh0{`**6NZ8s$Mq%*6ch#-N*NZ92uLLc_txsB_Y4dcU%&3ehj8WM{``p6ex_0QSH7 z%w4=ZW4B~qX{08tPpD%>23F`5^+3`ds~{3q4raLCzD0Kh6(cF$={2tet;}GH+;n!L zI@WZlRA>_-%A%f$tps!0AXIwH=Oj#tS^2xeCl+VX>(h2q#%VM7n#3!TF9Qosr?=~cofm;XyT?z)kH8PIF4av zfh7PLWi>Z5D=k(VQ2(*VEZ&M6FZzChcmPHh(%ruSB_-uI`(F2-#bZV&FLlL6rw@X@ zGG2ihSDuJD7T7H{Wjk`C1zVUrQj)(zMwkev3&CJ>X{AOO;kF?y?8!u|YqqaByV#iV z*=<{K93J-e(qFJ47vLbzyh$r5VfCBX)(6Kju0Mtkq3PAENYl~LM(|R)M7g^S?cfTL zni_rQ-Llbe504}RFC2mDi|Geml98GrF^h!6ao0jC0W@;E8ADB5UZ+nNLpeK9%~^~S z)A!8~HdAk_$76h-xP*ic5Z zeNBQbfB!Z!R@ert=GJ(-_%=Numz8Bu>QP9awMPqlz2ZT4hRPYP zke&$aAS#^YAf2W3Bn_V@2_`VQ^|XQro<4Gh~@ue)pXpvr`8&mjIC zYbdfny3x|ofJppU3s94NiSIaXi-@IJ-QVY|oD1+UZWI#`tl8b|QPJwbb_qk4pR{!7 zQj_eaxaV5Doax~B+{sD>3-Fqji<2T?jJ7kCJU+PJcXZG2U?In+Jpe<8(<1 z;^Y}g(QtTtGU-+r-pV7turZR~e-odD;+}s>X_Je;WUdIa*U%V1+H2xs8I1;==v2hu zl(#lBN?>DlPc%q%wJ@KW$5BSmW#tSK@sVk}*txBkWuANfv(sd5J}Y?pakJMvv~RmE z&lB!}EOKg>T!+=8R_CG;*rM-M`7hXFazsX+c4;TYCS(iW=lS1RyS?3l0|G*0aEHsPkY-SS>dU=@RIs~+MY5490R^kv z>Pg&J2XR*ghvw^)uioSDkN!Q;iQ8LPAlzJAx_IEnEqTx!AatTXQ6z>0M{MkT9NaMD{=Q_3hp^ZfO zTSndsq+{pMvGBW#8FRTM8=8YmZeh{wXwJTh_ZFA)r1d=juz3A<78a2Xqq`Rp1q3hObkL?=`+ve6+R!BrHENY79!A{e`QBCh`K1H4V8T{6&2*xz%Ww(A6RpSs|e6ASFT0$nB3l+qNCkNO8RET z9&U6a+nnlvg@7om^>}5)J$I~pR^Lr5dqt8TEX=JggfqSEDDqT4$!dPI)YZ!pGJ~@d o^8d0|@_%+@PM=XxP@bQ=vivgZ@?}hrn@K32WK};_OB)CO4}1 + + + + +Privacy Friendly Captcha - Event Registration + + + +
+

Privacy Friendly Captcha - Event Registration

+ + +

Beta License: AGPL-3 OCA/website Translate me on Weblate Try me on Runboat

+

This module adds ALTCHA support to event registration forms

+

Table of contents

+ +
+

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
  • +
+
+ +
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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.

+

Current 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/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") From 20ee901350fa8ef555e1ed6c22b2ce918d90ad13 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 3 Jul 2026 11:51:09 +0200 Subject: [PATCH 3/5] [RFR] website_altcha: base test case --- website_altcha/tests/common.py | 18 ++++++++++++++++++ website_altcha/tests/test_altcha.py | 6 ++++-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 website_altcha/tests/common.py 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() From c9b4b3054c9d3c7d197a1403f6fdcfbf8a643a56 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 3 Jul 2026 12:22:22 +0200 Subject: [PATCH 4/5] [RFR] website_altcha: make it easier to extend & customize reorganize files for more logical imports, test all of this --- website_altcha/README.rst | 6 +- website_altcha/__manifest__.py | 12 ++- website_altcha/demo/website_page.xml | 36 +++++++++ website_altcha/static/description/index.html | 28 +++---- website_altcha/static/src/altcha.esm.js | 49 +++++++++++++ .../static/src/{signup => }/altcha.scss | 0 .../static/src/{signup => }/altcha.xml | 0 .../static/src/altcha/altcha.esm.js | 27 ------- website_altcha/static/src/generic.esm.js | 9 +++ website_altcha/static/src/signup.esm.js | 12 +++ .../static/src/signup/signup.esm.js | 73 ------------------- website_altcha/static/src/website_form.esm.js | 36 +++++++++ .../tests/tours/website_altcha_contact.esm.js | 41 +++++++++++ .../tests/tours/website_altcha_generic.esm.js | 21 ++++++ .../tests/tours/website_altcha_signup.esm.js | 26 +++++++ website_altcha/tests/__init__.py | 1 + website_altcha/tests/test_altcha_tours.py | 24 ++++++ 17 files changed, 276 insertions(+), 125 deletions(-) create mode 100644 website_altcha/demo/website_page.xml create mode 100644 website_altcha/static/src/altcha.esm.js rename website_altcha/static/src/{signup => }/altcha.scss (100%) rename website_altcha/static/src/{signup => }/altcha.xml (100%) delete mode 100644 website_altcha/static/src/altcha/altcha.esm.js create mode 100644 website_altcha/static/src/generic.esm.js create mode 100644 website_altcha/static/src/signup.esm.js delete mode 100644 website_altcha/static/src/signup/signup.esm.js create mode 100644 website_altcha/static/src/website_form.esm.js create mode 100644 website_altcha/static/tests/tours/website_altcha_contact.esm.js create mode 100644 website_altcha/static/tests/tours/website_altcha_generic.esm.js create mode 100644 website_altcha/static/tests/tours/website_altcha_signup.esm.js create mode 100644 website_altcha/tests/test_altcha_tours.py diff --git a/website_altcha/README.rst b/website_altcha/README.rst index e5d69cca7e..8207ad05f8 100644 --- a/website_altcha/README.rst +++ b/website_altcha/README.rst @@ -1,7 +1,3 @@ -.. 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 ======================== @@ -17,7 +13,7 @@ Privacy Friendly Captcha .. |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 +.. |badge2| image:: https://img.shields.io/badge/licence-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 diff --git a/website_altcha/__manifest__.py b/website_altcha/__manifest__.py index 2f506e0d84..aa41f690dd 100644 --- a/website_altcha/__manifest__.py +++ b/website_altcha/__manifest__.py @@ -14,15 +14,18 @@ ], "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 +34,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/static/description/index.html b/website_altcha/static/description/index.html index 961c70b429..bac903a923 100644 --- a/website_altcha/static/description/index.html +++ b/website_altcha/static/description/index.html @@ -3,7 +3,7 @@ -README.rst +Privacy Friendly Captcha -
+
+

Privacy Friendly Captcha

- - -Odoo Community Association - -
-

Privacy Friendly Captcha

-

Beta License: AGPL-3 OCA/website Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/website Translate me on Weblate Try me on Runboat

This module allows to use a Captcha System completly handled by Odoo.

It relies on Altcha (https://altcha.org), an OpenSource captcha alternative.

@@ -393,7 +388,7 @@

Privacy Friendly Captcha

-

Use Cases / Context

+

Use Cases / Context

Currently, Odoo provides 2 options:

  • Google Recaptcha relies on tracking of the user. It implies cookies
  • @@ -407,7 +402,7 @@

    Use Cases / Context

    to spend more CPU, making it costly at scale.

-

Configuration

+

Configuration

Go to Configuration/Website, check “Enable Altcha” under “Privacy”. Some extra parameters will appear with all the ALTCHA information. This parameters are website dependent.

@@ -436,7 +431,7 @@

Configuration

-

Bug Tracker

+

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 @@ -444,15 +439,15 @@

Bug Tracker

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • Dixmit
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -475,6 +470,5 @@

Maintainers

-
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/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") From 4c582b7298d61c9da20ade77ad92576d0a02a8e7 Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Fri, 3 Jul 2026 12:24:57 +0200 Subject: [PATCH 5/5] [IMP] website_altcha: add hbrunn as maintainer --- website_altcha/README.rst | 8 ++++++++ website_altcha/__manifest__.py | 1 + website_altcha/static/description/index.html | 2 ++ 3 files changed, 11 insertions(+) diff --git a/website_altcha/README.rst b/website_altcha/README.rst index 8207ad05f8..6d559a3429 100644 --- a/website_altcha/README.rst +++ b/website_altcha/README.rst @@ -122,6 +122,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 aa41f690dd..8c0f918e92 100644 --- a/website_altcha/__manifest__.py +++ b/website_altcha/__manifest__.py @@ -8,6 +8,7 @@ "version": "18.0.1.0.0", "license": "AGPL-3", "author": "Dixmit,Odoo Community Association (OCA)", + "maintainers": ["hbrunn"], "website": "https://github.com/OCA/website", "depends": [ "website", diff --git a/website_altcha/static/description/index.html b/website_altcha/static/description/index.html index bac903a923..5e079092ee 100644 --- a/website_altcha/static/description/index.html +++ b/website_altcha/static/description/index.html @@ -465,6 +465,8 @@

Maintainers

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.

+

Current 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.