Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/send email #88

Merged
merged 62 commits into from
Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
a581d50
Added files for sending invitations by email
annashtirberg Jan 19, 2021
b4f8082
Added files for sending invitations by email
annashtirberg Jan 19, 2021
afe8234
Add base html file and mount static directory.
leddcode Jan 11, 2021
34b13a0
Minor fix
leddcode Jan 11, 2021
bb9d880
Add profile page - basic layout
leddcode Jan 11, 2021
7e8f43b
Update file tree schema
leddcode Jan 11, 2021
bd1e792
Create python-app.yml
yammesicka Jan 13, 2021
2f8b1e4
Update python-app.yml
yammesicka Jan 13, 2021
ed30098
docs: add how to run in readme
ellenc345 Jan 15, 2021
c761950
docs: add info how to run
ellenc345 Jan 15, 2021
215a0cb
ci: Stop the build if there are flake8 errors
yammesicka Jan 17, 2021
bfc70db
Added files for sending invitations by email
annashtirberg Jan 19, 2021
e045af5
Added files for sending invitations by email
annashtirberg Jan 19, 2021
e4ddf13
removed file
annashtirberg Jan 19, 2021
ac618b2
Removed file
annashtirberg Jan 19, 2021
0ae7a5a
Updated requirements.txt
annashtirberg Jan 19, 2021
4d34624
Fixed line too long errors
annashtirberg Jan 19, 2021
05c4b6e
Fixed line too long errors
annashtirberg Jan 19, 2021
8a60612
Changed tests configurations
annashtirberg Jan 19, 2021
a80faf0
Removed test config file
annashtirberg Jan 19, 2021
541e4fe
fixed paths
annashtirberg Jan 19, 2021
7dfd230
fixed line too long
annashtirberg Jan 19, 2021
2d0c6f8
added fake smtp server fixure to tests
annashtirberg Jan 19, 2021
8fc77e6
Updated requirements.txt
annashtirberg Jan 19, 2021
0e167cd
changed fixture for smtpd to include configuration
annashtirberg Jan 19, 2021
8d8a864
fixed email pattern checking
annashtirberg Jan 19, 2021
d782fe5
fixed a bad practice that Yuval Cagan reviewed me
annashtirberg Jan 19, 2021
5ff27d0
Added documentation to some function
annashtirberg Jan 19, 2021
3cd740c
Fixed line to long
annashtirberg Jan 19, 2021
e1cc002
Fixed line too long
annashtirberg Jan 19, 2021
b63cd81
fix status stuff
annashtirberg Jan 20, 2021
4e40092
Merged with upstream
annashtirberg Jan 20, 2021
3234c5c
gitignore additions
annashtirberg Jan 20, 2021
aa4967f
Fixed Yam comments
annashtirberg Jan 20, 2021
dac2bd5
Fixed Yam comments on conftest
annashtirberg Jan 20, 2021
9442599
try config for all
annashtirberg Jan 20, 2021
1948832
Merge branch 'develop' into feature/send-email
annashtirberg Jan 21, 2021
faacc4f
working on merging with main mail feature
annashtirberg Jan 23, 2021
3797401
pulled from upstream
annashtirberg Jan 27, 2021
61310b2
pulled upstream
annashtirberg Jan 30, 2021
b048f0a
fixed stuff
annashtirberg Jan 30, 2021
380ec54
added internal mail sending functions
annashtirberg Jan 30, 2021
7118f7d
added internal mail sending functions
annashtirberg Jan 30, 2021
fa3e26d
moved send function to use internal internal function
annashtirberg Jan 30, 2021
a7e88b3
moved send function to use internal function
annashtirberg Jan 30, 2021
7a05faa
added send file with email function and send invitation with email fu…
annashtirberg Jan 30, 2021
a4d0d67
added tests
annashtirberg Jan 30, 2021
cadc5c4
added tests
annashtirberg Jan 30, 2021
c77b6a1
fixed flake8 error
annashtirberg Jan 30, 2021
c3c92d8
fixed flake8 error
annashtirberg Jan 30, 2021
aa62cc0
fixed flake8 error
annashtirberg Jan 30, 2021
13c4613
Fixed pull request issues
annashtirberg Jan 31, 2021
4ac8f73
Fixed pull request issues (configuration)
annashtirberg Jan 31, 2021
a9c068c
Fixed flake8 error
annashtirberg Jan 31, 2021
ca3e532
Added a test for invitation parameters
annashtirberg Feb 1, 2021
cbb4e0b
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
annashtirberg Feb 1, 2021
859e834
Added a tests for coverage
annashtirberg Feb 1, 2021
41ebd6a
Fixed flake8 errors
annashtirberg Feb 1, 2021
6513233
Add tests
annashtirberg Feb 1, 2021
b827bda
Fixed flake8 error
annashtirberg Feb 1, 2021
acabbd9
Add tests
annashtirberg Feb 1, 2021
1fb1c4d
Fixed flake8 error
annashtirberg Feb 1, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
dev.db
test.db
config.py
.vscode/settings.json

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down Expand Up @@ -142,4 +141,5 @@ dmypy.json
.vscode/
app/.vscode/

app/routers/stam
# PyCharm
.idea
11 changes: 11 additions & 0 deletions app/config.py.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import os

from fastapi_mail import ConnectionConfig
from pydantic import BaseSettings
from starlette.templating import Jinja2Templates


class Settings(BaseSettings):
Expand Down Expand Up @@ -45,6 +46,15 @@ email_conf = ConnectionConfig(
USE_CREDENTIALS=True,
)

templates = Jinja2Templates(directory=os.path.join("app", "templates"))

# application name
CALENDAR_SITE_NAME = "Calendar"
# link to the home page of the application
CALENDAR_HOME_PAGE = "calendar.pythonic.guru"
# link to the application registration page
CALENDAR_REGISTRATION_PAGE = r"calendar.pythonic.guru/registration"

# import
MAX_FILE_SIZE_MB = 5 # 5MB
VALID_FILE_EXTENSION = (".txt", ".csv", ".ics") # Can import only these files.
Expand All @@ -54,6 +64,7 @@ EVENT_HEADER_NOT_EMPTY = 1 # 1- for not empty, 0- for empty.
EVENT_HEADER_LIMIT = 50 # Max characters for event header.
EVENT_CONTENT_LIMIT = 500 # Max characters for event characters.
MAX_EVENTS_START_DATE = 10 # Max Events with the same start date.

# PATHS
STATIC_ABS_PATH = os.path.abspath("static")

Expand Down
5 changes: 3 additions & 2 deletions app/database/models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from datetime import datetime

from app.config import PSQL_ENVIRONMENT
from app.database.database import Base
from sqlalchemy import (DDL, Boolean, Column, DateTime, ForeignKey, Index,
Integer, String, event)
from sqlalchemy.dialects.postgresql import TSVECTOR
from sqlalchemy.orm import relationship

from app.config import PSQL_ENVIRONMENT
from app.database.database import Base


class UserEvent(Base):
__tablename__ = "user_event"
Expand Down
156 changes: 148 additions & 8 deletions app/internal/email.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
from app.config import email_conf
import os
from typing import List, Optional


from app.config import (email_conf, templates,
CALENDAR_SITE_NAME, CALENDAR_HOME_PAGE,
CALENDAR_REGISTRATION_PAGE)
from app.database.models import Event, User
from fastapi import BackgroundTasks
from fastapi import BackgroundTasks, UploadFile
from fastapi_mail import FastMail, MessageSchema
from pydantic import EmailStr
from pydantic.errors import EmailError
from sqlalchemy.orm.session import Session

mail = FastMail(email_conf)
Expand Down Expand Up @@ -31,10 +39,142 @@ def send(
User.id == user_to_send).first()
if not user_to_send or not event_used:
return False
message = MessageSchema(
subject=f"{title} {event_used.title}",
recipients={"email": [user_to_send.email]}.get("email"),
body=f"begins at:{event_used.start} : {event_used.content}",
)
background_tasks.add_task(mail.send_message, message)
if not verify_email_pattern(user_to_send.email):
return False

subject = f"{title} {event_used.title}"
recipients = {"email": [user_to_send.email]}.get("email")
body = f"begins at:{event_used.start} : {event_used.content}"

background_tasks.add_task(send_internal,
subject=subject,
recipients=recipients,
body=body)
return True


def send_email_invitation(sender_name: str,
recipient_name: str,
recipient_mail: str,
background_tasks: BackgroundTasks = BackgroundTasks
) -> bool:
"""
This function takes as parameters the sender's name,
the recipient's name and his email address, configuration, and
sends the recipient an invitation to his email address in
the format HTML.
:param sender_name: str, the sender's name
:param recipient_name: str, the recipient's name
:param recipient_mail: str, the recipient's email address
:param background_tasks: (BackgroundTasks): Function from fastapi that lets
you apply tasks in the background.
:return: bool, True if the invitation was successfully
sent to the recipient, and False if the entered
email address is incorrect.
"""
if not verify_email_pattern(recipient_mail):
return False

if not recipient_name:
return False

if not sender_name:
return False

template = templates.get_template("invite_mail.html")
html = template.render(recipient=recipient_name, sender=sender_name,
site_name=CALENDAR_SITE_NAME,
registration_link=CALENDAR_REGISTRATION_PAGE,
home_link=CALENDAR_HOME_PAGE,
addr_to=recipient_mail)

subject = "Invitation"
recipients = [recipient_mail]
body = html
subtype = "html"

background_tasks.add_task(send_internal,
subject=subject,
recipients=recipients,
body=body,
subtype=subtype)
return True


def send_email_file(file_path: str,
recipient_mail: str,
background_tasks: BackgroundTasks = BackgroundTasks):
"""
his function takes as parameters the file's path,
the recipient's email address, configuration, and
sends the recipient an file to his email address.
:param file_path: str, the file's path
:param recipient_mail: str, the recipient's email address
:param background_tasks: (BackgroundTasks): Function from fastapi that lets
you apply tasks in the background.
:return: bool, True if the file was successfully
sent to the recipient, and False if the entered
email address is incorrect or file does not exist.
"""
if not verify_email_pattern(recipient_mail):
return False

if not os.path.exists(file_path):
return False

subject = "File"
recipients = [recipient_mail]
body = "file"
file_attachments = [file_path]

background_tasks.add_task(send_internal,
subject=subject,
recipients=recipients,
body=body,
file_attachments=file_attachments)
return True


async def send_internal(subject: str,
recipients: List[str],
body: str,
subtype: Optional[str] = None,
file_attachments: Optional[List[str]] = None):
if file_attachments is None:
file_attachments = []

message = MessageSchema(
subject=subject,
recipients=[EmailStr(recipient) for recipient in recipients],
body=body,
subtype=subtype,
attachments=[UploadFile(file_attachment)
for file_attachment in file_attachments])

return await send_internal_internal(message)


async def send_internal_internal(msg: MessageSchema):
"""
This function receives message and
configuration as parameters and sends the message.
:param msg: MessageSchema, message
:return: None
"""
await mail.send_message(msg)


def verify_email_pattern(email: str) -> bool:
"""
This function checks the correctness
of the entered email address
:param email: str, the entered email address
:return: bool,
True if the entered email address is correct,
False if the entered email address is incorrect.
"""
try:
EmailStr.validate(email)
return True
except EmailError:
return False
3 changes: 2 additions & 1 deletion app/internal/search.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from typing import List

from sqlalchemy.exc import SQLAlchemyError

from app.database.database import SessionLocal
from app.database.models import Event
from sqlalchemy.exc import SQLAlchemyError


def get_stripped_keywords(keywords: str) -> str:
Expand Down
4 changes: 2 additions & 2 deletions app/internal/weather_forecast.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import datetime
import frozendict
import functools

import frozendict
import requests

from app import config


# This feature requires an API KEY
# get yours free @ visual-crossing-weather.p.rapidapi.com

Expand Down
1 change: 0 additions & 1 deletion app/routers/dayview.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from app.database.models import Event, User
from app.dependencies import TEMPLATES_PATH


templates = Jinja2Templates(directory=TEMPLATES_PATH)


Expand Down
42 changes: 42 additions & 0 deletions app/routers/email.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from app.database.database import get_db
from app.internal.email import send as internal_send
from app.internal.email import send_email_invitation
from fastapi import APIRouter, BackgroundTasks, Depends, Form, HTTPException
from pydantic import BaseModel, EmailStr
from pydantic.errors import EmailError
from sqlalchemy.orm.session import Session
from starlette.responses import RedirectResponse

Expand All @@ -26,3 +29,42 @@ async def send(
background_tasks=background_tasks, session=db):
raise HTTPException(status_code=404, detail="Couldn't send the email!")
return RedirectResponse(send_to, status_code=303)


INVALID_EMAIL_ADDRESS_ERROR_MESSAGE = "Please enter valid email address"
SUCCESSFULLY_SENT_EMAIL_MESSAGE = "Your message was sent successfully"


class InvitationParams(BaseModel):
send_to: str = "/"
sender_name: str
recipient_name: str
recipient_mail: str


@router.post("/invitation/")
def send_invitation(invitation: InvitationParams,
background_task: BackgroundTasks):
"""
This function sends the recipient an invitation
to his email address in the format HTML.
:param invitation: InvitationParams, invitation parameters
:param background_task: BackgroundTasks
:return: json response message,
error message if the entered email address is incorrect,
confirmation message if the invitation was successfully sent
"""
try:
EmailStr.validate(invitation.recipient_mail)
except EmailError:
raise HTTPException(
status_code=422,
detail=INVALID_EMAIL_ADDRESS_ERROR_MESSAGE)

if not send_email_invitation(
sender_name=invitation.sender_name,
recipient_name=invitation.recipient_name,
recipient_mail=invitation.recipient_mail,
background_tasks=background_task):
raise HTTPException(status_code=422, detail="Couldn't send the email!")
return RedirectResponse(invitation.send_to, status_code=303)
8 changes: 4 additions & 4 deletions app/routers/event_images.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from app import config
import re
from functools import lru_cache
from nltk.tokenize import word_tokenize
from typing import Optional
from word_forms.lemmatizer import lemmatize

import re
from nltk.tokenize import word_tokenize
from word_forms.lemmatizer import lemmatize

from app import config

FLAIRS_EXTENSION = '.jpg'
FLAIRS_REL_PATH = f'{config.STATIC_ABS_PATH}\\event_flairs'
Expand Down
2 changes: 1 addition & 1 deletion app/routers/export.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from datetime import datetime
from typing import List

from icalendar import Calendar, Event, vCalAddress, vText
import pytz
from icalendar import Calendar, Event, vCalAddress, vText

from app.config import DOMAIN, ICAL_VERSION, PRODUCT_ID
from app.database.models import Event as UserEvent
Expand Down
1 change: 0 additions & 1 deletion app/routers/invitation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from app.database.models import Invitation
from app.routers.share import accept


templates = Jinja2Templates(directory="app/templates")

router = APIRouter(
Expand Down
6 changes: 3 additions & 3 deletions app/routers/search.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from app.database.database import get_db
from app.dependencies import templates
from app.internal.search import get_results_by_keywords
from fastapi import APIRouter, Depends, Form, Request
from sqlalchemy.orm import Session

from app.database.database import get_db
from app.dependencies import templates
from app.internal.search import get_results_by_keywords

router = APIRouter()

Expand Down
2 changes: 1 addition & 1 deletion app/routers/share.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from sqlalchemy.orm import Session

from app.database.models import Event, Invitation, UserEvent
from app.internal.utils import save
from app.routers.export import event_to_ical
from app.routers.user import does_user_exist, get_users
from app.internal.utils import save


def sort_emails(
Expand Down
16 changes: 16 additions & 0 deletions app/templates/invite_mail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% extends "mail_base.html" %}


{% block content %}

<div class="container mt-4">
<h1>Hi, {{ recipient }}!</h1>
<h2>{{ sender }} invites you to join {{ site_name }}.</h2>
<p>if you want to join please click on the <a href={{ registration_link }}>link</a></p>
<p>if you want to get acquainted with our application please click on the <a href={{ home_link }}>link</a></p>
<p>This email has been sent to {{ recipient_mail }}.</p>
<p>If you think it was sent in error, please ignore it</p>
</div>


{% endblock %}
Loading