Skip to content

Feat: Event View Backend #159

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
merged 27 commits into from
Feb 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
835faf7
Moved the route to top of file
OdeYec Jan 29, 2021
3141482
refractor!: by_id to get_event_by_id
OdeYec Jan 30, 2021
973415c
fix: changed delete_event to match new get_event_by_id
OdeYec Jan 30, 2021
70704de
feat: improved eventview's get endpoint
OdeYec Jan 30, 2021
60bdf78
refractor: moved logger from main to dependencies
OdeYec Jan 30, 2021
6d0182c
refractor: event_test_client to use create_test_client
OdeYec Jan 30, 2021
ec1ebb7
ui: changed static placeholders to actual event data
OdeYec Jan 30, 2021
9b8200c
fix: added exception handling
OdeYec Jan 30, 2021
d1e786d
fix: changed error code in test
OdeYec Jan 30, 2021
4b1a735
fix: changed update_event to fit new get_event_by_id
OdeYec Jan 30, 2021
7487a91
refactor: lint fix
OdeYec Jan 30, 2021
f4f8e8e
refactor: minor fixes
OdeYec Jan 30, 2021
f42f0c5
fix: split exception catching to raise more accurate HTTP Exceptions
OdeYec Jan 30, 2021
d47b925
refractor: lint fixes
OdeYec Jan 30, 2021
50442a2
refractor: lint fixes
OdeYec Jan 30, 2021
76983d4
Merge branch 'develop' into event_view_backend
yammesicka Jan 31, 2021
7cf4660
refractor: fixed conflicts
OdeYec Feb 1, 2021
e91025d
Merge remote-tracking branch 'origin/event_view_backend' into event_v…
OdeYec Feb 1, 2021
4558f11
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
OdeYec Feb 1, 2021
2ab3c3d
changed eventview route to /{event_id}/
OdeYec Feb 1, 2021
5d9ce54
changed eventview route to /{event_id}/
OdeYec Feb 1, 2021
8e4a25c
fix: added back some exception handling to delete_event
OdeYec Feb 1, 2021
3a07c2d
refractor: lint will be the death of me
OdeYec Feb 1, 2021
3689be9
refractor: lint fixt
OdeYec Feb 1, 2021
0e43d44
fix: added back some exception handling to delete_event
OdeYec Feb 1, 2021
d87244a
refractor: lint will be the death of me
OdeYec Feb 1, 2021
d78df37
Merge remote-tracking branch 'origin/event_view_backend' into event_v…
OdeYec 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
1 change: 0 additions & 1 deletion app/internal/event.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import re

from fastapi import HTTPException

from starlette.status import HTTP_400_BAD_REQUEST

ZOOM_REGEX = re.compile(r'https://.*?\.zoom.us/[a-z]/.[^.,\b\s]+')
Expand Down
2 changes: 1 addition & 1 deletion app/internal/quotes/load_quotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ def is_quotes_table_empty(session: Session) -> bool:

def load_daily_quotes(session: Session) -> None:
"""This function loads the daily quotes to the db,
if they weren't already loaden"""
if they weren't already loaded"""
if is_quotes_table_empty(session):
add_quotes_to_db(session)
8 changes: 4 additions & 4 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
from app.config import PSQL_ENVIRONMENT
from app.database import models
from app.database.database import engine, get_db
from app.dependencies import (
MEDIA_PATH, STATIC_PATH, templates, logger)
from app.internal.quotes import load_quotes, daily_quotes
from app.dependencies import (logger, MEDIA_PATH, STATIC_PATH, templates)
from app.internal.quotes import daily_quotes, load_quotes
from app.routers import (
agenda, dayview, email, event, invitation, profile, search, telegram,
whatsapp
)
)
from app.telegram.bot import telegram_bot


Expand All @@ -33,6 +32,7 @@ def create_tables(engine, psql_environment):

load_quotes.load_daily_quotes(next(get_db()))

app.logger = logger

app.include_router(profile.router)
app.include_router(event.router)
Expand Down
138 changes: 86 additions & 52 deletions app/routers/event.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
from datetime import datetime as dt
from datetime import datetime
from operator import attrgetter
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, Request
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
from starlette import status
from starlette.responses import RedirectResponse
from starlette.status import HTTP_302_FOUND

from app.database.database import get_db
from app.database.models import Event, User, UserEvent
from app.dependencies import templates
from app.dependencies import logger, templates
from app.internal.event import validate_zoom_link
from app.internal.utils import create_model
from app.routers.user import create_user
Expand All @@ -34,10 +35,10 @@ async def create_new_event(request: Request, session=Depends(get_db)):
data = await request.form()
title = data['title']
content = data['description']
start = dt.strptime(data['start_date'] + ' ' + data['start_time'],
'%Y-%m-%d %H:%M')
end = dt.strptime(data['end_date'] + ' ' + data['end_time'],
'%Y-%m-%d %H:%M')
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')
user = session.query(User).filter_by(id=1).first()
user = user if user else create_user("u", "p", "[email protected]", session)
owner_id = user.id
Expand All @@ -50,30 +51,86 @@ async def create_new_event(request: Request, session=Depends(get_db)):

event = create_event(session, title, start, end, owner_id, content,
location)
return RedirectResponse(router.url_path_for('eventview', id=event.id),
return RedirectResponse(router.url_path_for('eventview',
event_id=event.id),
status_code=HTTP_302_FOUND)


@router.get("/view/{id}")
async def eventview(request: Request, id: int):
@router.get("/{event_id}")
async def eventview(request: Request, event_id: int,
db: Session = Depends(get_db)):
try:
event = get_event_by_id(db, event_id)
except NoResultFound:
raise HTTPException(status_code=404, detail="Event not found")
except MultipleResultsFound:
raise HTTPException(status_code=500, detail="Multiple events found")
start_format = '%A, %d/%m/%Y %H:%M'
end_format = ('%H:%M' if event.start.date() == event.end.date()
else start_format)
return templates.TemplateResponse("event/eventview.html",
{"request": request, "event_id": id})
{"request": request, "event": event,
"start_format": start_format,
"end_format": end_format})


@router.delete("/{event_id}")
def delete_event(request: Request, event_id: int,
db: Session = Depends(get_db)):
# TODO: Check if the user is the owner of the event.
try:
event = get_event_by_id(db, event_id)
except NoResultFound:
raise HTTPException(status_code=404, detail="Event not found")
except MultipleResultsFound:
raise HTTPException(status_code=500, detail="Multiple events found")

participants = get_participants_emails_by_event(db, event_id)

try:
db.delete(event)
db.query(UserEvent).filter_by(event_id=event_id).delete()
db.commit()
except (SQLAlchemyError, TypeError):
return templates.TemplateResponse(
"event/eventview.html", {"request": request, "event_id": event_id},
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)

if participants and event.start > datetime.now():
pass
# TODO: Send them a cancellation notice
# if the deletion is successful
return RedirectResponse(
url="/calendar", status_code=status.HTTP_200_OK)

def by_id(db: Session, event_id: int) -> Event:
"""Select event by id"""

return db.query(Event).filter(Event.id == event_id).first()
def get_event_by_id(db: Session, event_id: int) -> Event:
"""Gets a single event by id"""
if not isinstance(db, Session):
raise AttributeError(
f'Could not connect to database. '
f'db instance type received: {type(db)}')
try:
event = db.query(Event).filter_by(id=event_id).one()
except NoResultFound:
raise NoResultFound(f"Event ID does not exist. ID: {event_id}")
except MultipleResultsFound:
error_message = (
f'Multiple results found when getting event. Expected only one. '
f'ID: {event_id}')
logger.critical(error_message)
raise MultipleResultsFound(error_message)
return event


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

return start_date < end_date


def is_it_possible_to_change_dates(
db: Session, old_event: Event, event: Dict[str, Any]) -> bool:
def is_it_possible_to_change_dates(old_event: Event,
event: Dict[str, Any]) -> bool:
return is_date_before(
event.get('start', old_event.start),
event.get('end', old_event.end))
Expand All @@ -94,9 +151,13 @@ def update_event(event_id: int, event: Dict, db: Session
if not event_to_update:
return None
try:
old_event = by_id(db=db, event_id=event_id)
if old_event is None or not is_it_possible_to_change_dates(
db, old_event, event_to_update):
old_event = get_event_by_id(db, event_id)
except NoResultFound:
raise HTTPException(status_code=404, detail="Event not found")
except MultipleResultsFound:
raise HTTPException(status_code=500, detail="Multiple events found")
try:
if not is_it_possible_to_change_dates(old_event, event_to_update):
return None

# Update database
Expand All @@ -107,7 +168,8 @@ def update_event(event_id: int, event: Dict, db: Session
# TODO: Send emails to recipients.
except (AttributeError, SQLAlchemyError, TypeError):
return None
return by_id(db=db, event_id=event_id)

return get_event_by_id(db=db, event_id=event_id)


def create_event(db, title, start, end, owner_id, content=None, location=None):
Expand Down Expand Up @@ -141,38 +203,10 @@ 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).
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()]


@router.delete("/{event_id}")
def delete_event(request: Request,
event_id: int,
db: Session = Depends(get_db)):

# TODO: Check if the user is the owner of the event.
event = by_id(db, event_id)
participants = get_participants_emails_by_event(db, event_id)
try:
# Delete event
db.delete(event)

# Delete user_event
db.query(UserEvent).filter(UserEvent.event_id == event_id).delete()

db.commit()

except (SQLAlchemyError, TypeError):
return templates.TemplateResponse(
"event/eventview.html", {"request": request, "event_id": event_id},
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
if participants and event.start > dt.now():
pass
# TODO: Send them a cancellation notice
# if the deletion is successful
return RedirectResponse(
url="/calendar", status_code=status.HTTP_200_OK)
all()])
28 changes: 13 additions & 15 deletions app/templates/event/partials/view_event_details_tab.html
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
<div class="event_info_row title">
<div class="event_info_row title" style="border-bottom: 4px solid {{event.color}}">
<div class="event_info_row_start">
<h1>EVENT TITLE</h1>
<h1>{{event.title}}</h1>
</div>
<div class="event_info_row_end">
<span class="icon">AVAILABILITY</span>
<span class="icon">PRIVACY</span>
<!-- <span class="icon">AVAILABILITY</span>-->
<!-- <span class="icon">PRIVACY</span>-->
</div>
</div>
<div class="event_info_row">
<span class="icon">ICON</span>
<time datetime="DD/MM/YYYY HH:MI">DAY, DD/MM/YYYY HH:MI</time>
<time datetime="{{event.start}}">{{event.start.strftime(start_format)}}</time>
-
<time datetime="DD/MM/YYYY HH:MI">HH:Mi</time>
<time datetime="{{event.end}}">{{event.end.strftime(end_format)}}</time>
</div>

<div class="event_info_row">
<span class="icon">ICON</span>
<span>Repeats every INTERVAL</span>
</div>
<!--<div class="event_info_row">-->
<!-- <span class="icon">ICON</span>-->
<!-- <span>Repeats every INTERVAL</span>-->
<!--</div>-->

<div class="event_info_row">
<span class="icon">ICON</span>
<address>LOCATION / <a href="#">VC URL</a></address>
<address>{{event.location}}</address>
<!-- <address>LOCATION / <a href="#">VC URL</a></address>-->
</div>

<p class="event_info_row">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus consectetur quis ex ac molestie. Fusce libero
ligula, dictum ac sollicitudin sed, consequat in nisi. Suspendisse feugiat diam quis efficitur aliquet. Duis purus
mauris, luctus ultrices dictum id, fermentum et ex. Nunc in elementum mauris. Maecenas at tincidunt lorem. Sed quis
ante commodo, tincidunt tortor at, tristique nisl. Donec at velit ultricies, viverra tellus at, ultrices ligula.
{{event.content}}
</p>
21 changes: 7 additions & 14 deletions tests/client_fixture.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from fastapi.testclient import TestClient
import pytest

from app import main
from app.database.database import Base
from app.database.models import User
from app.main import app
from app.routers import agenda, event, invitation, profile
from fastapi.testclient import TestClient

from tests.conftest import get_test_db, test_engine


Expand Down Expand Up @@ -40,6 +40,11 @@ def home_test_client():
yield from create_test_client(main.get_db)


@pytest.fixture(scope="session")
def event_test_client():
yield from create_test_client(event.get_db)


@pytest.fixture(scope="session")
def profile_test_client():
Base.metadata.create_all(bind=test_engine)
Expand All @@ -62,15 +67,3 @@ def get_test_placeholder_user():
full_name='FakeName',
telegram_id='666666'
)


@pytest.fixture(scope="session")
def event_test_client():
Base.metadata.create_all(bind=test_engine)
app.dependency_overrides[event.get_db] = get_test_db

with TestClient(app) as client:
yield client

app.dependency_overrides = {}
Base.metadata.drop_all(bind=test_engine)
Loading