diff --git a/htmx_contact/main.py b/htmx_contact/main.py index 185c1ad..9390e85 100644 --- a/htmx_contact/main.py +++ b/htmx_contact/main.py @@ -1,15 +1,23 @@ +import logging + from flask import Blueprint +from flask import flash from flask import redirect from flask import render_template from flask import request from flask_login import current_user from flask_login import login_required +from pydantic import ValidationError from sqlalchemy import and_ from sqlalchemy import or_ from sqlalchemy import select from htmx_contact import Session from htmx_contact.models import Contact +from htmx_contact.validators import NewContactValidator +from htmx_contact.validators import repack_validation_errors + +logger = logging.getLogger(__name__) bp = Blueprint("main", __name__, url_prefix="/") @@ -47,6 +55,24 @@ def contacts(): return render_template("contacts.html", contacts=results) -@bp.route("/contacts/new", methods=["GET"]) +@bp.route("/contacts/new", methods=["GET", "POST"]) def new_contact(): - return render_template("new_contact.html") + if request.method == "POST": + try: + validated_form = NewContactValidator( + # first_name=request.form.get("first_name"), + # last_name=request.form.get("last_name"), + # phone=request.form.get("phone"), + # email=request.form.get("email"), + **request.form + ) + with Session.begin() as session: + contact = Contact(**validated_form) + session.add(contact) + session.commit() + flash("New Contacted added!") + return redirect("/contacts") + except ValidationError as error: + logger.info("validation error: %r", error) + return render_template("new_contact.html", errors=repack_validation_errors(error.errors())) + return render_template("new_contact.html", errors={}) diff --git a/htmx_contact/templates/new_contact.html b/htmx_contact/templates/new_contact.html index 286bef6..f51c259 100644 --- a/htmx_contact/templates/new_contact.html +++ b/htmx_contact/templates/new_contact.html @@ -2,4 +2,35 @@ {% block content %}

New Contact

+ + +
+
+ Contact Values +

+ + + {{ errors.get('email','') }} +

+

+ + + {{ errors.get('first_name','') }} +

+

+ + + {{ errors.get('last_name', '') }} +

+

+ + + {{ errors.get('phone','') }} +

+ +
+
+

+ Back +

{% endblock %} diff --git a/htmx_contact/validators.py b/htmx_contact/validators.py new file mode 100644 index 0000000..07d2d9b --- /dev/null +++ b/htmx_contact/validators.py @@ -0,0 +1,34 @@ +import re + +from pydantic import BaseModel +from pydantic import Field +from pydantic import field_validator + +from htmx_contact.models import PHONE_REGEX + +EMAIL_REGEX = r'^[\w\-\.]+@([\w-]+\.)+[\w-]{2,}$' + + +def repack_validation_errors(errors): + repackaged_errors = {error.get('loc')[0]: error.get('msg') for error in errors} + return repackaged_errors + + +class NewContactValidator(BaseModel): + first_name: str = Field(min_length=2, max_length=30) + last_name: str = Field(min_length=2, max_length=30) + phone: str = Field(max_length=30) + email: str = Field(max_length=120) + + # Field Validators used to prevent regex patterns returned in error message + @field_validator('phone') + def must_match_phone_pattern(value): + if not bool(re.match(PHONE_REGEX, value)): + raise ValueError("not a valid phone number. Please review and resubmit.") + return value + + @field_validator('email') + def must_match_email_pattern(value): + if not bool(re.match(EMAIL_REGEX, value)): + raise ValueError("not a valid email. Please review and resubmit.") + return value diff --git a/tests/test_validators.py b/tests/test_validators.py new file mode 100644 index 0000000..ab67b03 --- /dev/null +++ b/tests/test_validators.py @@ -0,0 +1,21 @@ +import pytest +from pydantic import ValidationError + +from htmx_contact.validators import NewContactValidator +from htmx_contact.validators import repack_validation_errors + + +def test_new_contact_errors(): + with pytest.raises(ValidationError) as error: + NewContactValidator(first_name="Drew", last_name="bednar", phone="666-7777-3333", email="not a valid email") + assert len(error.value.errors()) == 2 + + +def test_repackage_errors(): + with pytest.raises(ValidationError) as error: + NewContactValidator(first_name="Drew", last_name="bednar", phone="666-7777-3333", email="not a valid email") + + repackaged_errors = repack_validation_errors(error.value.errors()) + assert len(repackaged_errors) == 2 + assert 'not a valid phone number' in repackaged_errors['phone'] + assert 'not a valid email' in repackaged_errors['email']