I’ve generated:
- ‘NGS Contact Form’ doctype (add Guest permission Read/Create)
- ‘NGS Contact Form’ web form (linked to the upper doctype)
- Builder page with a default form (full_name, email, message)
Beblow is the Embed HTML of the Builder page:
<script>
(() => {
console.log("[A] embed script loaded (no-turnstile)");
const WEB_FORM_ROUTE = "ngs-contact-form";
const $ = id => document.getElementById(id);
const el = {
full_name: $("full_name"),
email: $("email") || $("email_id"),
message: $("message"),
btn: $("submitBtn"),
msg: $("msgBox")
};
function setBusy(on){
if(!el.btn) return;
if(on){
if(!el.btn.dataset.origHtml) el.btn.dataset.origHtml = el.btn.innerHTML;
el.btn.disabled = true;
el.btn.setAttribute("disabled","disabled");
el.btn.setAttribute("aria-busy","true");
el.btn.classList.add("is-disabled","btn-disabled","disabled");
el.btn.innerText = "Submitting...";
}else{
el.btn.disabled = false;
el.btn.removeAttribute("disabled");
el.btn.removeAttribute("aria-busy");
el.btn.classList.remove("is-disabled","btn-disabled","disabled");
el.btn.innerHTML = el.btn.dataset.origHtml || "Submit";
}
}
function toast(t, ok){
if(!el.msg) return;
el.msg.textContent = t || "";
el.msg.style.padding = "6px 10px";
el.msg.style.borderRadius = "6px";
el.msg.style.marginTop = "8px";
el.msg.style.background = t ? (ok ? "#e8fff1" : "#ffecec") : "transparent";
}
async function submitForm(ev){
console.log("[C] submit handler fired, btn:", ev?.target);
const payload = {
full_name: el.full_name?.value || "",
email: el.email?.value || "",
message: el.message?.value || ""
// no turnstile_token
};
console.log("[D] payload:", payload);
console.log("[D1] web_form param:", WEB_FORM_ROUTE);
if(!WEB_FORM_ROUTE){ toast("web_form 缺失,无法提交", false); return; }
if(!payload.full_name || !payload.email || !payload.message){
toast("请填写完整信息。", false); return;
}
const body = new URLSearchParams({ web_form: WEB_FORM_ROUTE, data: JSON.stringify(payload) });
console.log("[E] form-data:", body.toString());
setBusy(true); toast("Submitting...", true);
try{
console.log("[F] fetch start");
const res = await fetch("/api/method/frappe.website.doctype.web_form.web_form.accept", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"X-Frappe-CSRF-Token": (window.csrf_token || window.frappe?.csrf_token || "")
},
credentials: "include",
body
});
console.log("[G] fetch done status:", res.status);
const json = await res.json().catch(()=>({}));
console.log("[G1] accept response:", json);
if (res.ok && !json.exc){
toast("OK:已创建记录。", true);
["full_name","email","message"].forEach(id => { const i = $(id); if(i) i.value = ""; });
} else {
toast("FAIL: " + (json.message || JSON.stringify(json)), false);
}
}catch(err){
console.error("[Gx] network/error:", err);
toast("网络错误", false);
}finally{
setBusy(false);
console.log("[H] finalize");
}
}
if (el.btn){
el.btn.type = "button";
el.btn.addEventListener("click", e => { e.preventDefault(); submitForm(e); });
console.log("[A2] click bound on button");
} else {
console.warn("[A2] no button found; binding form submit instead");
const form = (el.full_name || el.email || el.message)?.closest?.("form") || document.querySelector("form");
form?.addEventListener("submit", e => { e.preventDefault(); submitForm(e); });
}
})();
</script>
and ngs_contact_form.py:
class NGSContactForm(Document):
def before_insert(self):
self.full_name = (self.full_name or "").strip()
self.email = (self.email or "").strip()
self.message = (self.message or "").strip()
if not self.full_name or not self.email or not self.message:
frappe.throw(_("Full Name, Email and Message are required."))
safe_name = re.sub(r"[^\w\s-]", "", self.full_name).replace(" ", "_")
self.contact_id = make_autoname(f"CT-{safe_name}-.###")
self.name = self.contact_id
if len(self.message) > 8000:
frappe.throw(_("Message is too long."))
ip = _get_client_ip()
_rate_limit(f"ngs-contact:ip:{ip}", limit=5, ttl=300) # 5 分钟最多 5 次
_rate_limit(f"ngs-contact:email:{self.email}", limit=5, ttl=300)
But when I fill the form and click the submit button, the console report the error:
(index):164 POST https://athenomics.com/api/method/frappe.website.doctype.web_form.web_form.accept 404 (Not Found)
submitForm @ (index):164
(anonymous) @ (index):192Understand this error
(index):173 [G] fetch done status: 404
(index):175 [G1] accept response: {exc_type: 'DoesNotExistError', _server_messages: '["{\\"message\\": \\"DocType None not found\\", \\"titl…1d96f298c6d1fa7626c234d7ea52a85160f537acd7c4\\"}"]'}
Why there is a ‘DoesNotExistError’? It doesn’t make any sense.
Any idea or suggestions are welcome.
Thanks,