Skip to content

Commit 9ce9b13

Browse files
authored
Merge cbb8ec2 into 26b1a58
2 parents 26b1a58 + cbb8ec2 commit 9ce9b13

File tree

3 files changed

+128
-66
lines changed

3 files changed

+128
-66
lines changed

app/routers/event.py

Lines changed: 78 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
from datetime import datetime as dt
1+
from datetime import datetime
22
from operator import attrgetter
33
from typing import Any, Dict, List, Optional
44

5-
from fastapi import APIRouter, Depends, Request
5+
from fastapi import APIRouter, Depends, HTTPException, Request
6+
from loguru import logger
67
from sqlalchemy.exc import SQLAlchemyError
78
from sqlalchemy.orm import Session
89
from starlette import status
910
from starlette.responses import RedirectResponse
10-
from starlette.status import HTTP_302_FOUND
1111

1212
from app.database.database import get_db
1313
from app.database.models import Event, User, UserEvent
@@ -34,10 +34,10 @@ async def create_new_event(request: Request, session=Depends(get_db)):
3434
data = await request.form()
3535
title = data['title']
3636
content = data['description']
37-
start = dt.strptime(data['start_date'] + ' ' + data['start_time'],
38-
'%Y-%m-%d %H:%M')
39-
end = dt.strptime(data['end_date'] + ' ' + data['end_time'],
40-
'%Y-%m-%d %H:%M')
37+
start = datetime.strptime(data['start_date'] + ' ' + data['start_time'],
38+
'%Y-%m-%d %H:%M')
39+
end = datetime.strptime(data['end_date'] + ' ' + data['end_time'],
40+
'%Y-%m-%d %H:%M')
4141
user = session.query(User).filter_by(id=1).first()
4242
user = user if user else create_user("u", "p", "e@mail.com", session)
4343
owner_id = user.id
@@ -51,7 +51,7 @@ async def create_new_event(request: Request, session=Depends(get_db)):
5151
event = create_event(session, title, start, end, owner_id, content,
5252
location)
5353
return RedirectResponse(router.url_path_for('eventview', id=event.id),
54-
status_code=HTTP_302_FOUND)
54+
status_code=status.HTTP_302_FOUND)
5555

5656

5757
@router.get("/view/{id}")
@@ -60,43 +60,68 @@ async def eventview(request: Request, id: int):
6060
{"request": request, "event_id": id})
6161

6262

63+
UPDATE_EVENTS_FIELDS = {
64+
'title': str,
65+
'start': datetime,
66+
'end': datetime,
67+
'content': (str, type(None)),
68+
'location': (str, type(None))
69+
}
70+
71+
6372
def by_id(db: Session, event_id: int) -> Event:
6473
"""Select event by id"""
6574

6675
return db.query(Event).filter(Event.id == event_id).first()
6776

6877

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

7281
return start_date < end_date
7382

7483

75-
def is_it_possible_to_change_dates(
76-
db: Session, old_event: Event, event: Dict[str, Any]) -> bool:
77-
return is_date_before(
78-
event.get('start', old_event.start),
79-
event.get('end', old_event.end))
84+
def is_change_dates_allowed(
85+
old_event: Event, event: Dict[str, Any]) -> bool:
86+
try:
87+
return is_date_before(
88+
event.get('start', old_event.start),
89+
event.get('end', old_event.end))
90+
except TypeError:
91+
return False
92+
93+
94+
def is_fields_types_valid(to_check: Dict[str, Any], types: Dict[str, Any]):
95+
"""validate dictionary values by dictionary of types"""
96+
errors = []
97+
for field_name, field_type in to_check.items():
98+
if types[field_name] and not isinstance(field_type, types[field_name]):
99+
errors.append(
100+
f"{field_name} is '{type(field_type).__name__}' and"
101+
+ f"it should be from type '{types[field_name].__name__}'")
102+
logger.warning(errors)
103+
if errors:
104+
raise HTTPException(
105+
status_code=status.HTTP_400_BAD_REQUEST, detail=errors)
80106

81107

82-
def get_items_that_can_be_updated(event: Dict[str, Any]) -> Dict[str, Any]:
83-
"""Extract only that keys to update"""
108+
def get_event_with_editable_fields_only(event: Dict[str, Any]
109+
) -> Dict[str, Any]:
110+
"""Remove all keys that are not allowed to update"""
84111

85-
return {i: event[i] for i in (
86-
'title', 'start', 'end', 'content', 'location') if i in event}
112+
return {i: event[i] for i in UPDATE_EVENTS_FIELDS if i in event}
87113

88114

89115
def update_event(event_id: int, event: Dict, db: Session
90116
) -> Optional[Event]:
91117
# TODO Check if the user is the owner of the event.
92118

93-
event_to_update = get_items_that_can_be_updated(event)
94-
if not event_to_update:
95-
return None
119+
event_to_update = get_event_with_editable_fields_only(event)
120+
is_fields_types_valid(event_to_update, UPDATE_EVENTS_FIELDS)
96121
try:
97-
old_event = by_id(db=db, event_id=event_id)
98-
if old_event is None or not is_it_possible_to_change_dates(
99-
db, old_event, event_to_update):
122+
old_event = by_id(db, event_id)
123+
forbidden = not is_change_dates_allowed(old_event, event_to_update)
124+
if not event_to_update or old_event is None or forbidden:
100125
return None
101126

102127
# Update database
@@ -105,9 +130,10 @@ def update_event(event_id: int, event: Dict, db: Session
105130
db.commit()
106131

107132
# TODO: Send emails to recipients.
108-
except (AttributeError, SQLAlchemyError, TypeError):
133+
return by_id(db, event_id)
134+
except (AttributeError, SQLAlchemyError) as e:
135+
logger.error(str(e))
109136
return None
110-
return by_id(db=db, event_id=event_id)
111137

112138

113139
def create_event(db, title, start, end, owner_id, content=None, location=None):
@@ -149,28 +175,40 @@ def get_participants_emails_by_event(db: Session, event_id: int) -> List[str]:
149175
all()]
150176

151177

152-
@router.delete("/{event_id}")
153-
def delete_event(request: Request,
154-
event_id: int,
155-
db: Session = Depends(get_db)):
156-
157-
# TODO: Check if the user is the owner of the event.
158-
event = by_id(db, event_id)
159-
participants = get_participants_emails_by_event(db, event_id)
178+
def _delete_event(db: Session, event: Event):
160179
try:
161180
# Delete event
162181
db.delete(event)
163182

164183
# Delete user_event
165-
db.query(UserEvent).filter(UserEvent.event_id == event_id).delete()
184+
db.query(UserEvent).filter(UserEvent.event_id == event.id).delete()
166185

167186
db.commit()
168187

169-
except (SQLAlchemyError, TypeError):
170-
return templates.TemplateResponse(
171-
"event/eventview.html", {"request": request, "event_id": event_id},
172-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
173-
if participants and event.start > dt.now():
188+
except (SQLAlchemyError, AttributeError) as e:
189+
logger.error(str(e))
190+
raise HTTPException(
191+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
192+
detail="Deletion failed")
193+
194+
195+
@router.delete("/{event_id}")
196+
def delete_event(event_id: int,
197+
db: Session = Depends(get_db)):
198+
199+
# TODO: Check if the user is the owner of the event.
200+
try:
201+
event = by_id(db, event_id)
202+
except AttributeError as e:
203+
logger.error(str(e) + "Could not connect to database")
204+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
205+
detail="Could not connect to database")
206+
if not event:
207+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
208+
detail="The event was not found")
209+
participants = get_participants_emails_by_event(db, event_id)
210+
_delete_event(db, event)
211+
if participants and event.start > datetime.now():
174212
pass
175213
# TODO: Send them a cancellation notice
176214
# if the deletion is successful

requirements.txt

-1.59 KB
Binary file not shown.

tests/test_event.py

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from datetime import datetime
22

33
import pytest
4+
from fastapi import HTTPException
45
from starlette import status
5-
from starlette.status import HTTP_302_FOUND
66

77
from app.database.models import Event
8-
from app.routers.event import by_id, update_event
8+
from app.routers.event import (_delete_event, by_id, delete_event,
9+
is_change_dates_allowed, update_event)
910

1011
CORRECT_EVENT_FORM_DATA = {
1112
'title': 'test title',
@@ -36,7 +37,7 @@
3637
}
3738

3839
INVALID_UPDATE_OPTIONS = [
39-
{}, {"test": "test"}, {"start": "20.01.2020"},
40+
{}, {"test": "test"},
4041
{"start": datetime(2020, 2, 2), "end": datetime(2020, 1, 1)},
4142
{"start": datetime(2030, 2, 2)}, {"end": datetime(1990, 1, 1)},
4243
]
@@ -57,27 +58,37 @@ def test_eventview_without_id(self, client):
5758
response = client.get("/event/view")
5859
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
5960

60-
def test_eventedit_post_correct(self, client, user):
61-
response = client.post(client.app.url_path_for('create_new_event'),
62-
data=CORRECT_EVENT_FORM_DATA)
61+
def test_eventedit_post_correct(self, event_test_client, user):
62+
response = event_test_client.post(
63+
event_test_client.app.url_path_for(
64+
'create_new_event'), data=CORRECT_EVENT_FORM_DATA)
6365
assert response.ok
64-
assert response.status_code == HTTP_302_FOUND
65-
assert (client.app.url_path_for('eventview', id=1).strip('1')
66-
in response.headers['location'])
67-
68-
def test_eventedit_post_wrong(self, client, user):
69-
response = client.post(client.app.url_path_for('create_new_event'),
70-
data=WRONG_EVENT_FORM_DATA)
66+
assert response.status_code == status.HTTP_302_FOUND
67+
assert (event_test_client.app.url_path_for(
68+
'eventview', id=1).strip('1') in response.headers['location'])
69+
70+
def test_eventedit_post_wrong(self, event_test_client, user):
71+
response = event_test_client.post(
72+
event_test_client.app.url_path_for(
73+
'create_new_event'), data=WRONG_EVENT_FORM_DATA)
7174
assert response.json()['detail'] == 'VC type with no valid zoom link'
7275

73-
@staticmethod
7476
@pytest.mark.parametrize("data", INVALID_UPDATE_OPTIONS)
75-
def test_invalid_update(event, data, session):
77+
def test_invalid_update(self, event, data, session):
7678
assert update_event(event_id=event.id,
7779
event=data, db=session) is None
7880

79-
@staticmethod
80-
def test_successful_update(event, session):
81+
def test_fields_types_invalid(self, event, session):
82+
data = {"start": "20.01.2020"}
83+
with pytest.raises(HTTPException):
84+
response = update_event(event_id=event.id, event=data, db=session)
85+
assert response.status_code == status.HTTP_400_BAD_REQUEST
86+
87+
def test_not_is_change_dates_allowed(self, event):
88+
data = {"start": "20.01.2020"}
89+
assert is_change_dates_allowed(event, data) is False
90+
91+
def test_successful_update(self, event, session):
8192
data = {
8293
"title": "successful",
8394
"start": datetime(2021, 1, 20),
@@ -87,14 +98,12 @@ def test_successful_update(event, session):
8798
assert "successful" in update_event(
8899
event_id=event.id, event=data, db=session).title
89100

90-
@staticmethod
91-
def test_update_db_close(event):
101+
def test_update_db_close(self, event):
92102
data = {"title": "Problem connecting to db", }
93103
assert update_event(event_id=event.id,
94104
event=data, db=None) is None
95105

96-
@staticmethod
97-
def test_update_event_does_not_exist(event, session):
106+
def test_update_event_does_not_exist(self, event, session):
98107
data = {
99108
"content": "An update test for an event does not exist"
100109
}
@@ -104,11 +113,26 @@ def test_update_event_does_not_exist(event, session):
104113
def test_repr(self, event):
105114
assert event.__repr__() == f'<Event {event.id}>'
106115

116+
def test_no_connection_to_db_in_delete(self, event):
117+
with pytest.raises(HTTPException):
118+
response = delete_event(event_id=1, db=None)
119+
assert (
120+
response.status_code ==
121+
status.HTTP_500_INTERNAL_SERVER_ERROR
122+
)
123+
124+
def test_no_connection_to_db_in_internal_deletion(self, event):
125+
with pytest.raises(HTTPException):
126+
assert (
127+
_delete_event(event=event, db=None).status_code ==
128+
status.HTTP_500_INTERNAL_SERVER_ERROR
129+
)
130+
107131
def test_successful_deletion(self, event_test_client, session, event):
108-
respons = event_test_client.delete("/event/1")
109-
assert respons.ok
132+
response = event_test_client.delete("/event/1")
133+
assert response.ok
110134
assert by_id(db=session, event_id=1) is None
111135

112-
def test_delete_failed(self, event_test_client, event):
113-
respons = event_test_client.delete("/event/2")
114-
assert respons.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
136+
def test_deleting_an_event_does_not_exist(self, event_test_client, event):
137+
response = event_test_client.delete("/event/2")
138+
assert response.status_code == status.HTTP_404_NOT_FOUND

0 commit comments

Comments
 (0)