Skip to content

Commit a908f4d

Browse files
authored
Update event - Improving the code (#138)
* Add basic update event
1 parent c9e6c7d commit a908f4d

File tree

4 files changed

+198
-114
lines changed

4 files changed

+198
-114
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# PyLander
22

3-
<p align="center">
4-
<img title="Apache-2.0" src="https://img.shields.io/github/license/PythonFreeCourse/calendar.svg">
3+
<p style="text-align:center">
4+
<img title="Apache-2.0" alt="License Apache-2.0 icon" src="https://img.shields.io/github/license/PythonFreeCourse/calendar.svg">
55
</p>
66

77
👋 Welcome to Open Source Calendar built with Python. 🐍
@@ -39,5 +39,11 @@ cp app/config.py.example app/configuration.py
3939
# Edit the variables' values.
4040
uvicorn app.main:app --reload
4141
```
42+
### Running tests
43+
```shell
44+
python -m pytest --cov-report term-missing --cov=app tests
45+
```
46+
4247
## Contributing
4348
View [contributing guidelines](https://github.com/PythonFreeCourse/calendar/blob/master/CONTRIBUTING.md).
49+

app/routers/event.py

Lines changed: 121 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
from typing import Any, Dict, List, Optional
44

55
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 sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
910
from starlette import status
1011
from starlette.responses import RedirectResponse
11-
from starlette.status import HTTP_302_FOUND
1212

1313
from app.database.database import get_db
1414
from app.database.models import Event, User, UserEvent
15-
from app.dependencies import logger, templates
15+
from app.dependencies import templates
1616
from app.internal.event import validate_zoom_link
1717
from app.internal.utils import create_model
1818
from app.routers.user import create_user
@@ -53,18 +53,13 @@ async def create_new_event(request: Request, session=Depends(get_db)):
5353
location)
5454
return RedirectResponse(router.url_path_for('eventview',
5555
event_id=event.id),
56-
status_code=HTTP_302_FOUND)
56+
status_code=status.HTTP_302_FOUND)
5757

5858

5959
@router.get("/{event_id}")
6060
async def eventview(request: Request, event_id: int,
6161
db: Session = Depends(get_db)):
62-
try:
63-
event = get_event_by_id(db, event_id)
64-
except NoResultFound:
65-
raise HTTPException(status_code=404, detail="Event not found")
66-
except MultipleResultsFound:
67-
raise HTTPException(status_code=500, detail="Multiple events found")
62+
event = by_id(db, event_id)
6863
start_format = '%A, %d/%m/%Y %H:%M'
6964
end_format = ('%H:%M' if event.start.date() == event.end.date()
7065
else start_format)
@@ -74,102 +69,116 @@ async def eventview(request: Request, event_id: int,
7469
"end_format": end_format})
7570

7671

77-
@router.delete("/{event_id}")
78-
def delete_event(request: Request, event_id: int,
79-
db: Session = Depends(get_db)):
80-
# TODO: Check if the user is the owner of the event.
81-
try:
82-
event = get_event_by_id(db, event_id)
83-
except NoResultFound:
84-
raise HTTPException(status_code=404, detail="Event not found")
85-
except MultipleResultsFound:
86-
raise HTTPException(status_code=500, detail="Multiple events found")
87-
88-
participants = get_participants_emails_by_event(db, event_id)
89-
90-
try:
91-
db.delete(event)
92-
db.query(UserEvent).filter_by(event_id=event_id).delete()
93-
db.commit()
94-
except (SQLAlchemyError, TypeError):
95-
return templates.TemplateResponse(
96-
"event/eventview.html", {"request": request, "event_id": event_id},
97-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
98-
99-
if participants and event.start > datetime.now():
100-
pass
101-
# TODO: Send them a cancellation notice
102-
# if the deletion is successful
103-
return RedirectResponse(
104-
url="/calendar", status_code=status.HTTP_200_OK)
72+
UPDATE_EVENTS_FIELDS = {
73+
'title': str,
74+
'start': datetime,
75+
'end': datetime,
76+
'content': (str, type(None)),
77+
'location': (str, type(None))
78+
}
10579

10680

107-
def get_event_by_id(db: Session, event_id: int) -> Event:
108-
"""Gets a single event by id"""
81+
def by_id(db: Session, event_id: int) -> Event:
82+
"""Get a single event by id"""
10983
if not isinstance(db, Session):
110-
raise AttributeError(
84+
error_message = (
11185
f'Could not connect to database. '
11286
f'db instance type received: {type(db)}')
87+
logger.critical(error_message)
88+
raise HTTPException(
89+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
90+
detail=error_message)
91+
11392
try:
11493
event = db.query(Event).filter_by(id=event_id).one()
11594
except NoResultFound:
116-
raise NoResultFound(f"Event ID does not exist. ID: {event_id}")
95+
error_message = f"Event ID does not exist. ID: {event_id}"
96+
logger.exception(error_message)
97+
raise HTTPException(
98+
status_code=status.HTTP_404_NOT_FOUND,
99+
detail=error_message)
117100
except MultipleResultsFound:
118101
error_message = (
119102
f'Multiple results found when getting event. Expected only one. '
120103
f'ID: {event_id}')
121104
logger.critical(error_message)
122-
raise MultipleResultsFound(error_message)
105+
raise HTTPException(
106+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
107+
detail=error_message)
123108
return event
124109

125110

126-
def is_date_before(start_date: datetime, end_date: datetime) -> bool:
111+
def is_end_date_before_start_date(
112+
start_date: datetime, end_date: datetime) -> bool:
127113
"""Check if the start date is earlier than the end date"""
128114

129-
return start_date < end_date
130-
115+
return start_date > end_date
131116

132-
def is_it_possible_to_change_dates(old_event: Event,
133-
event: Dict[str, Any]) -> bool:
134-
return is_date_before(
135-
event.get('start', old_event.start),
136-
event.get('end', old_event.end))
137-
138-
139-
def get_items_that_can_be_updated(event: Dict[str, Any]) -> Dict[str, Any]:
140-
"""Extract only that keys to update"""
141-
142-
return {i: event[i] for i in (
143-
'title', 'start', 'end', 'content', 'location') if i in event}
144-
145-
146-
def update_event(event_id: int, event: Dict, db: Session
147-
) -> Optional[Event]:
148-
# TODO Check if the user is the owner of the event.
149117

150-
event_to_update = get_items_that_can_be_updated(event)
151-
if not event_to_update:
152-
return None
118+
def check_change_dates_allowed(
119+
old_event: Event, event: Dict[str, Any]):
120+
allowed = 1
153121
try:
154-
old_event = get_event_by_id(db, event_id)
155-
except NoResultFound:
156-
raise HTTPException(status_code=404, detail="Event not found")
157-
except MultipleResultsFound:
158-
raise HTTPException(status_code=500, detail="Multiple events found")
122+
start_date = event.get('start', old_event.start)
123+
end_date = event.get('end', old_event.end)
124+
if is_end_date_before_start_date(start_date, end_date):
125+
allowed = 0
126+
except TypeError:
127+
allowed = 0
128+
if allowed == 0:
129+
raise HTTPException(
130+
status_code=status.HTTP_400_BAD_REQUEST,
131+
detail="Invalid times")
132+
133+
134+
def is_fields_types_valid(to_check: Dict[str, Any], types: Dict[str, Any]):
135+
"""validate dictionary values by dictionary of types"""
136+
errors = []
137+
for field_name, field_type in to_check.items():
138+
if types[field_name] and not isinstance(field_type, types[field_name]):
139+
errors.append(
140+
f"{field_name} is '{type(field_type).__name__}' and"
141+
+ f"it should be from type '{types[field_name].__name__}'")
142+
logger.warning(errors)
143+
if errors:
144+
raise HTTPException(
145+
status_code=status.HTTP_400_BAD_REQUEST, detail=errors)
146+
147+
148+
def get_event_with_editable_fields_only(event: Dict[str, Any]
149+
) -> Dict[str, Any]:
150+
"""Remove all keys that are not allowed to update"""
151+
152+
return {i: event[i] for i in UPDATE_EVENTS_FIELDS if i in event}
153+
154+
155+
def _update_event(db: Session, event_id: int, event_to_update: Dict) -> Event:
159156
try:
160-
if not is_it_possible_to_change_dates(old_event, event_to_update):
161-
return None
162-
163157
# Update database
164158
db.query(Event).filter(Event.id == event_id).update(
165159
event_to_update, synchronize_session=False)
160+
166161
db.commit()
162+
return by_id(db, event_id)
163+
except (AttributeError, SQLAlchemyError) as e:
164+
logger.exception(str(e))
165+
raise HTTPException(
166+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
167+
detail="Internal server error")
167168

168-
# TODO: Send emails to recipients.
169-
except (AttributeError, SQLAlchemyError, TypeError):
170-
return None
171169

172-
return get_event_by_id(db=db, event_id=event_id)
170+
def update_event(event_id: int, event: Dict, db: Session
171+
) -> Optional[Event]:
172+
# TODO Check if the user is the owner of the event.
173+
old_event = by_id(db, event_id)
174+
event_to_update = get_event_with_editable_fields_only(event)
175+
is_fields_types_valid(event_to_update, UPDATE_EVENTS_FIELDS)
176+
check_change_dates_allowed(old_event, event_to_update)
177+
if not event_to_update:
178+
return None
179+
event_updated = _update_event(db, event_id, event_to_update)
180+
# TODO: Send emails to recipients.
181+
return event_updated
173182

174183

175184
def create_event(db, title, start, end, owner_id, content=None, location=None):
@@ -203,10 +212,42 @@ def get_participants_emails_by_event(db: Session, event_id: int) -> List[str]:
203212
"""Returns a list of all the email address of the event invited users,
204213
by event id."""
205214

206-
return (
207-
[email[0] for email in db.query(User.email).
215+
return [email[0] for email in db.query(User.email).
208216
select_from(Event).
209217
join(UserEvent, UserEvent.event_id == Event.id).
210218
join(User, User.id == UserEvent.user_id).
211219
filter(Event.id == event_id).
212-
all()])
220+
all()]
221+
222+
223+
def _delete_event(db: Session, event: Event):
224+
try:
225+
# Delete event
226+
db.delete(event)
227+
228+
# Delete user_event
229+
db.query(UserEvent).filter(UserEvent.event_id == event.id).delete()
230+
231+
db.commit()
232+
233+
except (SQLAlchemyError, AttributeError) as e:
234+
logger.exception(str(e))
235+
raise HTTPException(
236+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
237+
detail="Deletion failed")
238+
239+
240+
@router.delete("/{event_id}")
241+
def delete_event(event_id: int,
242+
db: Session = Depends(get_db)):
243+
244+
# TODO: Check if the user is the owner of the event.
245+
event = by_id(db, event_id)
246+
participants = get_participants_emails_by_event(db, event_id)
247+
_delete_event(db, event)
248+
if participants and event.start > datetime.now():
249+
pass
250+
# TODO: Send them a cancellation notice
251+
# if the deletion is successful
252+
return RedirectResponse(
253+
url="/calendar", status_code=status.HTTP_200_OK)

requirements.txt

-1.56 KB
Binary file not shown.

0 commit comments

Comments
 (0)