Using pydantic to validate form data
continuous-integration/drone/push Build is failing Details

master
Drew Bednar 1 year ago
parent 901a548d2e
commit 31ed66b1f5

@ -1,15 +1,23 @@
import logging
from flask import Blueprint from flask import Blueprint
from flask import flash
from flask import redirect from flask import redirect
from flask import render_template from flask import render_template
from flask import request from flask import request
from flask_login import current_user from flask_login import current_user
from flask_login import login_required from flask_login import login_required
from pydantic import ValidationError
from sqlalchemy import and_ from sqlalchemy import and_
from sqlalchemy import or_ from sqlalchemy import or_
from sqlalchemy import select from sqlalchemy import select
from htmx_contact import Session from htmx_contact import Session
from htmx_contact.models import Contact 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="/") bp = Blueprint("main", __name__, url_prefix="/")
@ -47,6 +55,24 @@ def contacts():
return render_template("contacts.html", contacts=results) return render_template("contacts.html", contacts=results)
@bp.route("/contacts/new", methods=["GET"]) @bp.route("/contacts/new", methods=["GET", "POST"])
def new_contact(): 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={})

@ -2,4 +2,35 @@
{% block content %} {% block content %}
<h1>New Contact</h1> <h1>New Contact</h1>
<form action="/contacts/new" method="post">
<fieldset>
<legend>Contact Values</legend>
<p>
<label for="email">Email</label>
<input name="email" id="email" type="email" placeholder="Email">
<span class="error">{{ errors.get('email','') }}</span>
</p>
<p>
<label for="first_name">First Name</label>
<input name="first_name" id="first_name" type="text" placeholder="First Name">
<span class="error">{{ errors.get('first_name','') }}</span>
</p>
<p>
<label for="last_name">Last Name</label>
<input name="last_name" id="last_name" type="text" placeholder="Last Name">
<span class="error">{{ errors.get('last_name', '') }}</span>
</p>
<p>
<label for="phone">Phone</label>
<input name="phone" id="phone" type="text" placeholder="Phone">
<span class="error">{{ errors.get('phone','') }}</span>
</p>
<button>Save</button>
</fieldset>
</form>
<p>
<a href="/contacts">Back</a>
</p>
{% endblock %} {% endblock %}

@ -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

@ -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']
Loading…
Cancel
Save