python xml 123 lines · 4 tabs

Validate a Flask Signup Form and Collect Per-Field Errors

Shared by codesnips Aug 2026
4 tabs
import re

EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")


class SignupValidator:
    def __init__(self, form, email_exists=None):
        self.raw = form
        self.email_exists = email_exists or (lambda e: False)
        self.errors = {}
        self.cleaned = {}

    def add(self, field, message):
        if field not in self.errors:
            self.errors[field] = message

    def validate(self):
        self._check_email()
        self._check_password()
        self._check_confirm()
        return self

    def _check_email(self):
        email = (self.raw.get("email") or "").strip().lower()
        self.cleaned["email"] = email
        if not email:
            self.add("email", "Email is required.")
        elif not EMAIL_RE.match(email):
            self.add("email", "Enter a valid email address.")
        elif self.email_exists(email):
            self.add("email", "That email is already registered.")

    def _check_password(self):
        pw = self.raw.get("password") or ""
        if len(pw) < 8:
            self.add("password", "Password must be at least 8 characters.")
        elif not any(c.isdigit() for c in pw):
            self.add("password", "Password must contain a number.")

    def _check_confirm(self):
        pw = self.raw.get("password") or ""
        confirm = self.raw.get("confirm") or ""
        if pw and confirm != pw:
            self.add("confirm", "Passwords do not match.")

    def is_valid(self):
        return not self.errors
4 files · python, xml Explain with highlit

This snippet shows a lightweight, dependency-free way to validate a signup form on the server and return a dictionary of per-field errors that a template can render inline next to each input. It avoids pulling in a full form library and instead builds a small, explicit validator that any Flask project can drop in.

In validators.py, the core abstraction is SignupValidator, which takes the raw form mapping and accumulates errors into self.errors, a dict keyed by field name. Each _check_* method appends a message only when a rule fails, and helper add guards against overwriting an existing message so the first, most relevant error per field wins. Email is checked with a deliberately conservative regex; the goal is to reject obvious garbage, not to fully implement RFC 5322, because strict email parsing belongs to actual delivery. The password rule enforces length and a digit, and _check_confirm cross-validates two fields, illustrating why collecting all errors at once beats raising on the first failure — the user sees every problem in a single round trip.

The is_valid method returns a boolean derived from whether errors is empty, and cleaned exposes normalized values (trimmed, lowercased email) so the view never re-derives them. This separation keeps the validator pure and testable: it touches no request context and no database, apart from an injected email_exists callback for the uniqueness check.

In auth.py, the signup view wires this together. On GET it renders the empty form; on POST it constructs the validator, passing a small lambda that queries User for uniqueness. When is_valid fails, the view re-renders signup.html with errors and the original form data so fields repopulate, and it sets a 400 status so the response is not cached as a success. Only on success does it create the User, commit, and redirect following the Post/Redirect/Get pattern to prevent duplicate submissions on refresh.

The trade-off is manual wiring versus the automatic binding of WTForms, but for a handful of fields the explicitness is easier to read and to unit test. A common pitfall this design handles is preserving user input on failure; another is the uniqueness race, which the unique index in models.py ultimately enforces even if two requests pass validation concurrently.


Related snips

Share this code

Here's the card — post it anywhere.

Validate a Flask Signup Form and Collect Per-Field Errors — share card
Link copied