Skip to content

Commit edcf49c

Browse files
authored
Merge pull request #61 from PureDreamer/feature/email_send
Feat/Basic Email use
2 parents 058eb63 + de1c620 commit edcf49c

File tree

11 files changed

+187
-14
lines changed

11 files changed

+187
-14
lines changed

app/config.py

Whitespace-only changes.

app/config.py.example

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import os
2+
3+
from fastapi_mail import ConnectionConfig
14
# flake8: noqa
25

36

@@ -8,3 +11,14 @@ DEVELOPMENT_DATABASE_STRING = "sqlite:///./dev.db"
811
MEDIA_DIRECTORY = 'media'
912
PICTURE_EXTENSION = '.png'
1013
AVATAR_SIZE = (120, 120)
14+
15+
email_conf = ConnectionConfig(
16+
MAIL_USERNAME=os.getenv("MAIL_USERNAME") or "user",
17+
MAIL_PASSWORD=os.getenv("MAIL_PASSWORD") or "password",
18+
MAIL_FROM=os.getenv("MAIL_FROM") or "a@a.com",
19+
MAIL_PORT=587,
20+
MAIL_SERVER="smtp.gmail.com",
21+
MAIL_TLS=True,
22+
MAIL_SSL=False,
23+
USE_CREDENTIALS=True,
24+
)

app/internal/email.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from app.config import email_conf
2+
from app.database.models import Event, User
3+
from fastapi import BackgroundTasks
4+
from fastapi_mail import FastMail, MessageSchema
5+
from sqlalchemy.orm.session import Session
6+
7+
mail = FastMail(email_conf)
8+
9+
10+
def send(
11+
session: Session, event_used: int, user_to_send: int,
12+
title: str, background_tasks: BackgroundTasks = BackgroundTasks
13+
) -> bool:
14+
"""This function is being used to send emails in the background.
15+
It takes an event and a user and it sends the event to the user.
16+
17+
Args:
18+
session(Session): The session to redirect to the database.
19+
title (str): Title of the email that is being sent.
20+
event_used (int): Id number of the event that is used.
21+
user_to_send (int): Id number of user that we want to notify.
22+
background_tasks (BackgroundTasks): Function from fastapi that lets
23+
you apply tasks in the background.
24+
25+
Returns:
26+
bool: Returns True if the email was sent, else returns False.
27+
"""
28+
event_used = session.query(Event).filter(
29+
Event.id == event_used).first()
30+
user_to_send = session.query(User).filter(
31+
User.id == user_to_send).first()
32+
if not user_to_send or not event_used:
33+
return False
34+
message = MessageSchema(
35+
subject=f"{title} {event_used.title}",
36+
recipients={"email": [user_to_send.email]}.get("email"),
37+
body=f"begins at:{event_used.start} : {event_used.content}",
38+
)
39+
background_tasks.add_task(mail.send_message, message)
40+
return True

app/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from app.database.database import engine
66
from app.dependencies import (
77
MEDIA_PATH, STATIC_PATH, templates)
8-
from app.routers import agenda, event, profile
8+
from app.routers import agenda, event, profile, email
99

1010

1111
models.Base.metadata.create_all(bind=engine)
@@ -17,11 +17,13 @@
1717
app.include_router(profile.router)
1818
app.include_router(event.router)
1919
app.include_router(agenda.router)
20+
app.include_router(email.router)
2021

2122

2223
@app.get("/")
2324
async def home(request: Request):
2425
return templates.TemplateResponse("home.html", {
2526
"request": request,
2627
"message": "Hello, World!"
28+
2729
})

app/media/fake_user.png

3.47 KB
Loading

app/routers/email.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from app.database.database import get_db
2+
from app.internal.email import send as internal_send
3+
from fastapi import APIRouter, BackgroundTasks, Depends, Form, HTTPException
4+
from sqlalchemy.orm.session import Session
5+
from starlette.responses import RedirectResponse
6+
7+
router = APIRouter(
8+
prefix="/email",
9+
tags=["email"],
10+
responses={404: {"description": "Not found"}},
11+
)
12+
13+
14+
@router.post("/send")
15+
async def send(
16+
db: Session = Depends(get_db),
17+
send_to: str = "/",
18+
title: str = Form(...),
19+
event_used: str = Form(...),
20+
user_to_send: str = Form(...),
21+
background_tasks: BackgroundTasks = BackgroundTasks
22+
) -> RedirectResponse:
23+
if not internal_send(
24+
title=title, event_used=event_used,
25+
user_to_send=user_to_send,
26+
background_tasks=background_tasks, session=db):
27+
raise HTTPException(status_code=404, detail="Couldn't send the email!")
28+
return RedirectResponse(send_to, status_code=303)

app/templates/demo/home_email.html

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{% extends "base.html" %}
2+
3+
{% block content %}
4+
5+
<div class="container mt-4">
6+
<h1>{{message}}</h1>
7+
</div>
8+
9+
<div>
10+
<form action="/email/send" method="post">
11+
<!-- Example of how to use sending email
12+
This is a testing using ids of evenets and users yet with
13+
simple manipulation you could send it how ever you want -->
14+
<label for="event_used">Event Id</label><br>
15+
<input type="text" value="{{ event_used }}" name="event_used"><br>
16+
<label for="user_to_send">User Id</label><br>
17+
<input type="text" value="{{user_to_send}}" name="user_to_send">
18+
<div>
19+
<select class="form-control" name="title">
20+
<option value="New Event:">New Event</option>
21+
<option value="Reminder:">Reminder</option>
22+
<option value="Canceled:">Canceled</option>
23+
<option value="Updated">Updated</option>
24+
</select>
25+
</div>
26+
<input type="submit" value="Accept">
27+
</form>
28+
{% endblock %}

app/templates/home.html

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
{% extends "base.html" %}
22

3-
43
{% block content %}
54

6-
<div class="container mt-4">
7-
<h1>{{message}}</h1>
8-
</div>
9-
5+
<div class="container mt-4">
6+
<h1>{{message}}</h1>
7+
</div>
108

11-
{% endblock %}
9+
{% endblock %}

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ click==7.1.2
88
colorama==0.4.4
99
coverage==5.3.1
1010
fastapi==0.63.0
11+
fastapi_mail==0.3.3.1
12+
faker==5.6.2
13+
smtpdfix==0.2.6
1114
h11==0.12.0
1215
h2==4.0.0
1316
hpack==4.0.0

tests/conftest.py

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
from fastapi.testclient import TestClient
2-
import pytest
3-
from sqlalchemy import create_engine
4-
from sqlalchemy.orm import sessionmaker
1+
import datetime
52

6-
from app.main import app
3+
import pytest
74
from app.database.database import Base, SessionLocal, engine
8-
from app.database.models import User
5+
from app.database.models import Event, User
6+
from app.main import app
97
from app.routers import profile
10-
8+
from faker import Faker
9+
from fastapi.testclient import TestClient
10+
from sqlalchemy import create_engine
11+
from sqlalchemy.orm import sessionmaker
12+
pytest_plugins = "smtpdfix"
1113

1214
SQLALCHEMY_TEST_DATABASE_URL = "sqlite:///./test.db"
1315

@@ -36,6 +38,30 @@ def session():
3638
Base.metadata.drop_all(bind=engine)
3739

3840

41+
@pytest.fixture
42+
def user(session):
43+
faker = Faker()
44+
user1 = User(username=faker.first_name(), email=faker.email())
45+
session.add(user1)
46+
session.commit()
47+
yield user1
48+
session.delete(user1)
49+
session.commit()
50+
51+
52+
@pytest.fixture
53+
def event(session, user):
54+
event1 = Event(
55+
title="Test Email", content="Test TEXT",
56+
start=datetime.datetime.now(),
57+
end=datetime.datetime.now(), owner_id=user.id)
58+
session.add(event1)
59+
session.commit()
60+
yield event1
61+
session.delete(event1)
62+
session.commit()
63+
64+
3965
def get_test_placeholder_user():
4066
return User(
4167
username='fake_user',

tests/test_email.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
3+
from app.internal.email import mail
4+
from fastapi import BackgroundTasks
5+
6+
7+
def test_email_send(client, user, event, smtpd):
8+
mail.config.SUPPRESS_SEND = 1
9+
mail.config.MAIL_SERVER = smtpd.hostname
10+
mail.config.MAIL_PORT = smtpd.port
11+
mail.config.USE_CREDENTIALS = False
12+
mail.config.MAIL_TLS = False
13+
with mail.record_messages() as outbox:
14+
response = client.post(
15+
"/email/send", data={
16+
"event_used": event.id, "user_to_send": user.id,
17+
"title": "Testing",
18+
"background_tasks": BackgroundTasks})
19+
assert len(outbox) == 1
20+
assert response.ok
21+
22+
23+
def test_failed_email_send(client, user, event, smtpd):
24+
mail.config.SUPPRESS_SEND = 1
25+
mail.config.MAIL_SERVER = smtpd.hostname
26+
mail.config.MAIL_PORT = smtpd.port
27+
with mail.record_messages() as outbox:
28+
response = client.post(
29+
"/email/send", data={
30+
"event_used": event.id + 1, "user_to_send": user.id,
31+
"title": "Testing",
32+
"background_tasks": BackgroundTasks})
33+
assert len(outbox) == 0
34+
assert not response.ok

0 commit comments

Comments
 (0)