Skip to content

Feature/past invites to same events #126

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions app/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class Event(Base):
color = Column(String, nullable=True)

owner_id = Column(Integer, ForeignKey("users.id"))
invitees = Column(String)
color = Column(String, nullable=True)
category_id = Column(Integer, ForeignKey("categories.id"))

owner = relationship("User")
Expand Down
31 changes: 30 additions & 1 deletion app/internal/event.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
import logging
import re

from email_validator import validate_email, EmailSyntaxError
from fastapi import HTTPException

from starlette.status import HTTP_400_BAD_REQUEST

from app.database.models import Event

ZOOM_REGEX = re.compile(r'https://.*?\.zoom.us/[a-z]/.[^.,\b\s]+')


def validate_zoom_link(location):
def raise_if_zoom_link_invalid(location):
if ZOOM_REGEX.search(location) is None:
raise HTTPException(status_code=HTTP_400_BAD_REQUEST,
detail="VC type with no valid zoom link")


def get_invited_emails(invited_from_form):
invited_emails = []
for invited_email in invited_from_form.split(','):
invited_email = invited_email.strip()
try:
validate_email(invited_email, check_deliverability=False)
invited_emails.append(invited_email)
except EmailSyntaxError:
logging.error(f'{invited_email} is not a valid email address')

return invited_emails


def get_uninvited_regular_emails(session, owner_id, title, invited_emails):
regular_invitees = set()
invitees_query = session.query(Event).with_entities(Event.invitees)
similar_events_invitees = invitees_query.filter(Event.owner_id == owner_id,
Event.title == title).all()
for record in similar_events_invitees:
regular_invitees.update(record[0].split(','))

return regular_invitees - set(invited_emails)
79 changes: 45 additions & 34 deletions app/routers/event.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from datetime import datetime
from datetime import datetime as dt
from operator import attrgetter
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, HTTPException, Request
from loguru import logger
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
Expand All @@ -12,11 +11,25 @@

from app.database.database import get_db
from app.database.models import Event, User, UserEvent
from app.dependencies import logger
from app.dependencies import templates
from app.internal.event import validate_zoom_link
from app.internal.event import (raise_if_zoom_link_invalid, get_invited_emails,
get_uninvited_regular_emails)
from app.internal.utils import create_model
from app.routers.user import create_user

TIME_FORMAT = '%Y-%m-%d %H:%M'

UPDATE_EVENTS_FIELDS = {
'title': str,
'start': dt,
'end': dt,
'content': (str, type(None)),
'location': (str, type(None)),
'category_id': (int, type(None))
}


router = APIRouter(
prefix="/event",
tags=["event"],
Expand All @@ -35,10 +48,10 @@ async def create_new_event(request: Request, session=Depends(get_db)):
data = await request.form()
title = data['title']
content = data['description']
start = datetime.strptime(data['start_date'] + ' ' + data['start_time'],
'%Y-%m-%d %H:%M')
end = datetime.strptime(data['end_date'] + ' ' + data['end_time'],
'%Y-%m-%d %H:%M')
start = dt.strptime(data['start_date'] + ' ' + data['start_time'],
TIME_FORMAT)
end = dt.strptime(data['end_date'] + ' ' + data['end_time'],
TIME_FORMAT)
user = session.query(User).filter_by(id=1).first()
user = user if user else create_user(username="u",
password="p",
Expand All @@ -52,14 +65,20 @@ async def create_new_event(request: Request, session=Depends(get_db)):
location = data['location']
category_id = data.get('category_id')

invited_emails = get_invited_emails(data['invited'])
uninvited_contacts = get_uninvited_regular_emails(session, owner_id,
title, invited_emails)

if is_zoom:
validate_zoom_link(location)
raise_if_zoom_link_invalid(location)

event = create_event(session, title, start, end, owner_id, content,
location, category_id=category_id)
return RedirectResponse(router.url_path_for('eventview',
event_id=event.id),
status_code=status.HTTP_302_FOUND)
location, invited_emails, category_id=category_id)
message = ''
if uninvited_contacts:
message = f'Forgot to invite {", ".join(uninvited_contacts)} maybe?'
return RedirectResponse(router.url_path_for('eventview', event_id=event.id)
+ f'?{message}', status_code=status.HTTP_302_FOUND)


@router.get("/{event_id}")
Expand All @@ -69,20 +88,12 @@ async def eventview(request: Request, event_id: int,
start_format = '%A, %d/%m/%Y %H:%M'
end_format = ('%H:%M' if event.start.date() == event.end.date()
else start_format)
message = request.query_params.get('message', '')
return templates.TemplateResponse("event/eventview.html",
{"request": request, "event": event,
"start_format": start_format,
"end_format": end_format})


UPDATE_EVENTS_FIELDS = {
'title': str,
'start': datetime,
'end': datetime,
'content': (str, type(None)),
'location': (str, type(None)),
'category_id': (int, type(None))
}
"end_format": end_format,
"message": message})


def by_id(db: Session, event_id: int) -> Event:
Expand Down Expand Up @@ -115,10 +126,8 @@ def by_id(db: Session, event_id: int) -> Event:
return event


def is_end_date_before_start_date(
start_date: datetime, end_date: datetime) -> bool:
def is_end_date_before_start_date(start_date: dt, end_date: dt) -> bool:
"""Check if the start date is earlier than the end date"""

return start_date > end_date


Expand Down Expand Up @@ -190,9 +199,12 @@ def update_event(event_id: int, event: Dict, db: Session
def create_event(db: Session, title: str, start, end, owner_id: int,
content: str = None,
location: str = None,
invitees: List[str] = None,
category_id: int = None):
"""Creates an event and an association."""

invitees_concatenated = ','.join(invitees or [])

event = create_model(
db, Event,
title=title,
Expand All @@ -201,6 +213,7 @@ def create_event(db: Session, title: str, start, end, owner_id: int,
content=content,
owner_id=owner_id,
location=location,
invitees=invitees_concatenated,
category_id=category_id,
)
create_model(
Expand All @@ -221,13 +234,11 @@ def sort_by_date(events: List[Event]) -> List[Event]:
def get_participants_emails_by_event(db: Session, event_id: int) -> List[str]:
"""Returns a list of all the email address of the event invited users,
by event id."""

return [email[0] for email in db.query(User.email).
select_from(Event).
join(UserEvent, UserEvent.event_id == Event.id).
join(User, User.id == UserEvent.user_id).
filter(Event.id == event_id).
all()]
return [email[0] for email in
db.query(User.email).select_from(Event).join(
UserEvent, UserEvent.event_id == Event.id).join(
User, User.id == UserEvent.user_id).filter(
Event.id == event_id).all()]


def _delete_event(db: Session, event: Event):
Expand All @@ -254,7 +265,7 @@ def delete_event(event_id: int,
event = by_id(db, event_id)
participants = get_participants_emails_by_event(db, event_id)
_delete_event(db, event)
if participants and event.start > datetime.now():
if participants and event.start > dt.now():
pass
# TODO: Send them a cancellation notice
# if the deletion is successful
Expand Down
3 changes: 3 additions & 0 deletions app/templates/event/eventview.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<body>
<div class = "event_view_wrapper">
<!-- Temporary nav layout based on bootstrap -->
<div class="forgot-to-invite">
<h2>{{ message }}</h2>
</div>
<ul class="nav nav-tabs" id="event_view_nav" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="eventdetails-tab" data-toggle="tab" href="#eventdetails" role="tab"
Expand Down
5 changes: 5 additions & 0 deletions app/templates/event/partials/edit_event_details_tab.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
<input type="text" name="location" placeholder="VC URL/Location">
</div>

<div class="form_row">
<label for="invited">Invited emails: </label>
<input type="text" id="invited" name="invited" placeholder="Invited emails, separated by commas">
</div>

<div class="form_row textarea">
<textarea name="description" placeholder="Description"></textarea>
</div>
Expand Down
59 changes: 57 additions & 2 deletions tests/test_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
'description': 'content',
'color': 'red',
'availability': 'busy',
'privacy': 'public'
'privacy': 'public',
'invited': 'a@a.com,b@b.com'
}

WRONG_EVENT_FORM_DATA = {
Expand All @@ -34,7 +35,23 @@
'description': 'content',
'color': 'red',
'availability': 'busy',
'privacy': 'public'
'privacy': 'public',
'invited': 'a@a.com,b@b.com'
}

BAD_EMAILS_FORM_DATA = {
'title': 'test title',
'start_date': '2021-01-28',
'start_time': '15:59',
'end_date': '2021-01-27',
'end_time': '15:01',
'location_type': 'vc_url',
'location': 'https://us02web.zoom.us/j/875384596',
'description': 'content',
'color': 'red',
'availability': 'busy',
'privacy': 'public',
'invited': 'a@a.com,b@b.com,ccc'
}

NONE_UPDATE_OPTIONS = [
Expand Down Expand Up @@ -66,6 +83,44 @@ def test_eventview_with_id(event_test_client, session, event):
f'{event_detail} not in view event page'


def test_eventview_without_id(client):
response = client.get("/event/view")
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY


def test_eventedit_missing_old_invites(client, user):
response = client.post(client.app.url_path_for('create_new_event'),
data=CORRECT_EVENT_FORM_DATA)
assert response.ok
assert response.status_code == status.HTTP_302_FOUND

different_invitees_event = CORRECT_EVENT_FORM_DATA.copy()
different_invitees_event['invited'] = 'c@c.com,d@d.com'
response = client.post(client.app.url_path_for('create_new_event'),
data=different_invitees_event)
assert response.ok
assert response.status_code == status.HTTP_302_FOUND
for invitee in CORRECT_EVENT_FORM_DATA["invited"].split(","):
assert invitee in response.headers['location']


def test_eventedit_bad_emails(client, user):
response = client.post(client.app.url_path_for('create_new_event'),
data=BAD_EMAILS_FORM_DATA)
assert response.ok
assert response.status_code == status.HTTP_302_FOUND

different_invitees_event = CORRECT_EVENT_FORM_DATA.copy()
different_invitees_event['invited'] = 'c@c.com,d@d.com'
response = client.post(client.app.url_path_for('create_new_event'),
data=different_invitees_event)
assert response.ok
assert response.status_code == status.HTTP_302_FOUND
for invitee in CORRECT_EVENT_FORM_DATA["invited"].split(","):
assert invitee in response.headers['location']
assert 'ccc' not in response.headers['location']


def test_eventedit_post_correct(client, user):
"""
Test create new event successfully.
Expand Down