HTML form: GET the form, POST it, redirect on success¶
The classic Post/Redirect/Get pattern, implemented with EDRI's HTML API:
GET /contact— renders an empty form.POST /contact— validates input, stores it, then issues aRedirectResponseDirectiveto send the browser to a thank-you page.GET /contact/thanks— renders confirmation.
The point of the redirect is to prevent the browser from re-submitting on refresh.
Events¶
# events/contact.py
from http import HTTPMethod
from edri.api.dataclass.api_event import api, ApiType
from edri.dataclass.event import Event, event
from edri.dataclass.response import Response, response
from edri.utility.function import inject
from edri.utility.validation import StringValidator
@response
class ContactFormResponse(Response):
error: str | None
@response
class ContactThanksResponse(Response):
pass
@api(
url="/contact",
template="contact/form.j2",
method=HTTPMethod.GET,
exclude={ApiType.WS},
)
class ContactForm(Event):
response: ContactFormResponse
@api(
url="/contact",
template="contact/form.j2", # rendered only if validation fails
method=HTTPMethod.POST,
exclude={ApiType.WS},
)
class ContactSubmit(Event):
name: inject(StringValidator, minimum_length=1, maximum_length=80)
message: inject(StringValidator, minimum_length=1, maximum_length=2000)
response: ContactFormResponse
@api(
url="/contact/thanks",
template="contact/thanks.j2",
method=HTTPMethod.GET,
exclude={ApiType.WS},
)
class ContactThanks(Event):
response: ContactThanksResponse
Manager¶
# managers/contact.py
from edri.abstract import ManagerBase, request
from edri.dataclass.directive.html import RedirectResponseDirective
from edri.dataclass.response import ResponseStatus
from events.contact import ContactForm, ContactSubmit, ContactThanks
class ContactManager(ManagerBase):
def __init__(self, router_queue):
super().__init__(router_queue)
self._messages: list[dict] = []
@request
def show_form(self, event: ContactForm) -> None:
event.response.error = None
@request
def submit(self, event: ContactSubmit) -> None:
# `StringValidator` already enforced length, so this is a stand-in
# for *business* validation that wasn't worth a custom validator.
if "@" in event.name:
event.response.error = "Please enter a name, not an email."
event.response.set_status(ResponseStatus.FAILED)
return
self._messages.append({"name": event.name, "message": event.message})
# PRG: tell the browser to GET /contact/thanks.
event.response.set_status(ResponseStatus.OK)
event.response.add_directive(RedirectResponseDirective(ContactThanks))
@request
def thanks(self, event: ContactThanks) -> None:
# Nothing to fill in — the template is static.
event.response.set_status(ResponseStatus.OK)
A few things worth pointing out:
RedirectResponseDirective(ContactThanks)takes the event class, not a URL. The HTML layer expands it to the URL declared onContactThanks.@api(url=...). If the target event had path placeholders, you'd pass them as keyword arguments (RedirectResponseDirective(UserPage, user_id=42)).When validation succeeds, the directive overrides the default
200rendering with a302redirect; the template is not used.When validation fails, no directive is added — the template
contact/form.j2renders and can showdata.error.
Templates¶
templates/contact/form.j2:
<!doctype html>
<html>
<body>
<h1>Contact us</h1>
{% if data.error %}
<p style="color:red">{{ data.error }}</p>
{% endif %}
<form action="{% url 'ContactSubmit' %}" method="post">
<label>Name: <input name="name" required></label><br>
<label>Message: <textarea name="message" required></textarea></label><br>
<button type="submit">Send</button>
</form>
</body>
</html>
templates/contact/thanks.j2:
<!doctype html>
<html>
<body>
<h1>Thanks!</h1>
<p>Your message was received.</p>
<a href="{% url 'ContactForm' %}">Send another</a>
</body>
</html>
The {% url 'EventName' %} tag turns the event class name into the URL declared on its @api(...) decorator — see URL generation in templates.
Entry point¶
# main.py
from edri import EDRI
from managers.contact import ContactManager
if __name__ == "__main__":
edri = EDRI()
edri.add_component(ContactManager(edri.router_queue))
edri.start_api()
edri.run()
Make sure EDRI_TEMPLATE_PATH points at the directory containing your contact/ folder (default: templates/). See Templates and assets.
Trying it¶
# render the form
curl -sL http://localhost:8080/contact
# submit
curl -sL -X POST http://localhost:8080/contact \
-d "name=Alice&message=Hello"
# -> 302 Location: /contact/thanks -> the thanks page
# submit with a validation failure
curl -sL -X POST http://localhost:8080/contact \
-d "name=a@b.c&message=oops"
# -> 200 with the form, response.error rendered