Added send-email feature

This commit is contained in:
Aykhan 2023-09-11 21:58:24 +04:00
parent 6d45d1c604
commit a1b3d23c37
10 changed files with 161 additions and 38 deletions

View File

@ -1 +1,7 @@
SECRET_KEY="SECRET_KEY" SECRET_KEY="SECRET_KEY"
SMTP_HOST="smtp.gmail.com"
SMTP_PORT=587
SMTP_SSL_TLS=False
SMTP_USER="SMTP_USER"
SMTP_PASSWORD="SMTP_PASSWORD"
EMAIL_RECIPIENTS=["EMAIL_RECIPIENT", "EMAIL_RECIPIENT"]

View File

@ -1,7 +1,12 @@
from pydantic import PostgresDsn from typing import Optional
from pydantic_settings import BaseSettings
from pathlib import Path from pathlib import Path
from pydantic_settings import BaseSettings
from pydantic import (
EmailStr,
PostgresDsn
)
class Settings(BaseSettings): class Settings(BaseSettings):
PROJECT_NAME: str = 'FastAPI Portfolio & Blog' PROJECT_NAME: str = 'FastAPI Portfolio & Blog'
@ -36,5 +41,14 @@ class Settings(BaseSettings):
path=self.POSTGRES_DB path=self.POSTGRES_DB
) )
SMTP_SSL_TLS: bool = True
SMTP_PORT: int = 587
SMTP_HOST: str = "smtp.gmail.com"
SMTP_USER: EmailStr
SMTP_PASSWORD: str
EMAILS_FROM_NAME: str = PROJECT_NAME
EMAIL_RECIPIENTS: list[EmailStr] = []
settings = Settings() settings = Settings()

View File

@ -40,30 +40,6 @@ app = FastAPI(
app.include_router(main_router) app.include_router(main_router)
# templates = Jinja2Templates(directory=main_path / 'templates')
# @app.get("/", response_class=HTMLResponse)
# async def read_item(request: Request):
# return templates.TemplateResponse("test.html", {"request": request})
# from pydantic import BaseModel
# class User(BaseModel):
# username: str
# password: str
# @app.post("/", response_class=HTMLResponse)
# async def read_item(request: Request, user: User):
# print(user)
# return templates.TemplateResponse("index.html", {"request": request})
# @app.get("/t", response_class=HTMLResponse)
# async def read_item(request: Request):
# return templates.TemplateResponse("index.html", {"request": request})
@app.exception_handler(ValidationError) @app.exception_handler(ValidationError)
async def validation_exception_handler(request, exc): async def validation_exception_handler(request, exc):

14
src/app/schemas/main.py Normal file
View File

@ -0,0 +1,14 @@
from dataclasses import dataclass
from typing import Optional
from fastapi import Form
from pydantic import EmailStr
@dataclass
class SendEmail:
name: str = Form(..., max_length=50)
email: EmailStr = Form(...)
phone: Optional[str] = Form(None, max_length=20)
message: str = Form(..., max_length=1000)

View File

@ -56,18 +56,18 @@
<div class="col-md-10 col-lg-8 mx-auto"> <div class="col-md-10 col-lg-8 mx-auto">
<h1>Contact me</h1> <h1>Contact me</h1>
<p>Want to get in touch? Fill out the form below to send me a message and I will get back to you as soon as possible!</p> <p>Want to get in touch? Fill out the form below to send me a message and I will get back to you as soon as possible!</p>
<form id="contactForm" name="sentMessage"> <form action="/send-email" method="POST" id="contactForm" name="sentMessage">
<div class="control-group"> <div class="control-group">
<div class="form-floating controls mb-3"><input class="form-control" type="text" id="name" required="" placeholder="Name"><label class="form-label" for="name">Name</label><small class="form-text text-danger help-block"></small></div> <div class="form-floating controls mb-3"><input name="name" class="form-control" type="text" id="name" required maxlength="50" placeholder="Name"><label class="form-label" for="name">Name</label><small class="form-text text-danger help-block"></small></div>
</div> </div>
<div class="control-group"> <div class="control-group">
<div class="form-floating controls mb-3"><input class="form-control" type="email" id="email" required="" placeholder="Email Address"><label class="form-label">Email Address</label><small class="form-text text-danger help-block"></small></div> <div class="form-floating controls mb-3"><input class="form-control" name="email" type="email" id="email" required placeholder="Email Address"><label class="form-label">Email Address</label><small class="form-text text-danger help-block"></small></div>
</div> </div>
<div class="control-group"> <div class="control-group">
<div class="form-floating controls mb-3"><input class="form-control" type="tel" id="phone" required="" placeholder="Phone Number"><label class="form-label">Phone Number</label><small class="form-text text-danger help-block"></small></div> <div class="form-floating controls mb-3"><input class="form-control" name="phone" type="tel" id="phone" maxlength="20" placeholder="Phone Number"><label class="form-label">Phone Number</label><small class="form-text text-danger help-block"></small></div>
</div> </div>
<div class="control-group"> <div class="control-group">
<div class="form-floating controls mb-3"><textarea class="form-control" id="message" data-validation-required-message="Please enter a message." required="" placeholder="Message" style="height: 150px;"></textarea><label class="form-label">Message</label><small class="form-text text-danger help-block"></small></div> <div class="form-floating controls mb-3"><textarea class="form-control" name="message" id="message" maxlength="1000" data-validation-required-message="Please enter a message." required="" placeholder="Message" style="height: 150px;"></textarea><label class="form-label">Message</label><small class="form-text text-danger help-block"></small></div>
</div> </div>
<div id="success"></div> <div id="success"></div>
<div class="mb-3"><button class="btn btn-primary" id="sendMessageButton" type="submit">Send</button></div> <div class="mb-3"><button class="btn btn-primary" id="sendMessageButton" type="submit">Send</button></div>

37
src/app/utils/email.py Normal file
View File

@ -0,0 +1,37 @@
from functools import partial
from fastapi_mail import (
FastMail,
MessageSchema,
ConnectionConfig,
MessageType
)
from app.core.config import settings
def send_email_notification(
subject: str,
body: str
) -> partial:
if settings.EMAIL_RECIPIENTS:
conf = ConnectionConfig(
MAIL_USERNAME = settings.SMTP_USER,
MAIL_PASSWORD = settings.SMTP_PASSWORD,
MAIL_FROM = settings.SMTP_USER,
MAIL_PORT = settings.SMTP_PORT,
MAIL_SERVER = settings.SMTP_HOST,
MAIL_SSL_TLS = settings.SMTP_SSL_TLS,
MAIL_STARTTLS = True
)
message = MessageSchema(
subject = subject,
recipients = settings.EMAIL_RECIPIENTS,
body = body,
subtype=MessageType.plain
)
fast_mail = FastMail(conf)
return partial(fast_mail.send_message, message)

View File

@ -1,12 +1,13 @@
from typing import Annotated from typing import Annotated
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi import ( from fastapi import (
APIRouter, APIRouter,
Query, Query,
Request, Request,
Depends Depends,
BackgroundTasks
) )
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@ -14,6 +15,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app import crud from app import crud
from app.core.config import settings from app.core.config import settings
from app.schemas import ListPostInTemplate from app.schemas import ListPostInTemplate
from app.schemas.main import SendEmail
from app.utils.email import send_email_notification
from app.views.depends import get_async_db from app.views.depends import get_async_db
@ -30,6 +33,31 @@ async def home(request: Request):
) )
@router.post('/send-email')
async def send_email(
request: Request,
background_tasks: BackgroundTasks,
form_data: SendEmail = Depends()
):
body = f"name: {form_data.name}\n"\
f"email: {form_data.email}\n"\
f"phone: {form_data.phone}\n"\
f"message: {form_data.message}"
background_tasks.add_task(
send_email_notification(
subject = f"Portfolio Blog (by {form_data.email})",
body = body
)
)
return RedirectResponse(
str(request.url_for('home')) + '#contact',
status_code=303
)
@router.get('/blog', response_class=HTMLResponse) @router.get('/blog', response_class=HTMLResponse)
async def blog( async def blog(
request: Request, request: Request,

View File

@ -23,9 +23,6 @@ if config.config_file_name is not None:
# target_metadata = mymodel.Base.metadata # target_metadata = mymodel.Base.metadata
########### Custom Config ########### ########### Custom Config ###########
from os import getenv
print('-'*100)
print(getenv('POSTGRES_USER'))
from app.core.config import settings from app.core.config import settings
from app.db.base import Base from app.db.base import Base

52
src/poetry.lock generated
View File

@ -11,6 +11,21 @@ files = [
{file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"}, {file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"},
] ]
[[package]]
name = "aiosmtplib"
version = "2.0.2"
description = "asyncio SMTP client"
optional = false
python-versions = ">=3.7,<4.0"
files = [
{file = "aiosmtplib-2.0.2-py3-none-any.whl", hash = "sha256:1e631a7a3936d3e11c6a144fb8ffd94bb4a99b714f2cb433e825d88b698e37bc"},
{file = "aiosmtplib-2.0.2.tar.gz", hash = "sha256:138599a3227605d29a9081b646415e9e793796ca05322a78f69179f0135016a3"},
]
[package.extras]
docs = ["sphinx (>=5.3.0,<6.0.0)", "sphinx_autodoc_typehints (>=1.7.0,<2.0.0)"]
uvloop = ["uvloop (>=0.14,<0.15)", "uvloop (>=0.14,<0.15)", "uvloop (>=0.17,<0.18)"]
[[package]] [[package]]
name = "alembic" name = "alembic"
version = "1.12.0" version = "1.12.0"
@ -115,6 +130,17 @@ files = [
docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
test = ["flake8 (>=5.0,<6.0)", "uvloop (>=0.15.3)"] test = ["flake8 (>=5.0,<6.0)", "uvloop (>=0.15.3)"]
[[package]]
name = "blinker"
version = "1.6.2"
description = "Fast, simple object-to-object and broadcast signaling"
optional = false
python-versions = ">=3.7"
files = [
{file = "blinker-1.6.2-py3-none-any.whl", hash = "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"},
{file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"},
]
[[package]] [[package]]
name = "cffi" name = "cffi"
version = "1.15.1" version = "1.15.1"
@ -347,6 +373,30 @@ typing-extensions = ">=4.5.0"
[package.extras] [package.extras]
all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
[[package]]
name = "fastapi-mail"
version = "1.4.1"
description = "Simple lightweight mail library for FastApi"
optional = false
python-versions = ">=3.8.1,<4.0"
files = [
{file = "fastapi_mail-1.4.1-py3-none-any.whl", hash = "sha256:fa5ef23b2dea4d3ba4587f4bbb53f8f15274124998fb4e40629b3b636c76c398"},
{file = "fastapi_mail-1.4.1.tar.gz", hash = "sha256:9095b713bd9d3abb02fe6d7abb637502aaf680b52e177d60f96273ef6bc8bb70"},
]
[package.dependencies]
aiosmtplib = ">=2.0,<3.0"
blinker = ">=1.5,<2.0"
email-validator = ">=2.0,<3.0"
Jinja2 = ">=3.0,<4.0"
pydantic = ">=2.0,<3.0"
pydantic_settings = ">=2.0,<3.0"
starlette = ">=0.24,<1.0"
[package.extras]
httpx = ["httpx[httpx] (>=0.23,<0.24)"]
redis = ["redis[redis] (>=4.3,<5.0)"]
[[package]] [[package]]
name = "greenlet" name = "greenlet"
version = "2.0.2" version = "2.0.2"
@ -1064,4 +1114,4 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)",
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "98704ea52451d766de4392fa5dc14eaa8eb336758ae4e74a7ecc618439b5af17" content-hash = "333a9be3573863da41b6a2615cd28aa3a6daf2e608a76da1476af72d343dae52"

View File

@ -22,6 +22,7 @@ python-slugify = "^8.0.1"
pillow = "^10.0.0" pillow = "^10.0.0"
aiofiles = "^23.2.1" aiofiles = "^23.2.1"
python-jose = {extras = ["cryptography"], version = "^3.3.0"} python-jose = {extras = ["cryptography"], version = "^3.3.0"}
fastapi-mail = "^1.4.1"
[build-system] [build-system]