Skip to content

Commit 40ca575

Browse files
Merge branch 'develop' of https://github.com/PythonFreeCourse/calendar into feature/weather_forecast
2 parents a91c53f + ab45a02 commit 40ca575

36 files changed

+1077
-246
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ ipython_config.py
8888
# pyenv
8989
.python-version
9090

91+
# pycharm
92+
.idea/
93+
9194
# pipenv
9295
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
9396
# However, in case of collaboration, if having platform-specific dependencies or dependencies

app/config.py.example

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ from fastapi_mail import ConnectionConfig
44

55
# flake8: noqa
66

7+
# general
8+
DOMAIN = 'Our-Domain'
79

810
# DATABASE
911
DEVELOPMENT_DATABASE_STRING = "sqlite:///./dev.db"
@@ -16,10 +18,15 @@ AVATAR_SIZE = (120, 120)
1618
# API-KEYS
1719
WEATHER_API_KEY = os.getenv('WEATHER_API_KEY')
1820

21+
# export
22+
ICAL_VERSION = '2.0'
23+
PRODUCT_ID = '-//Our product id//'
24+
25+
# email
1926
email_conf = ConnectionConfig(
2027
MAIL_USERNAME=os.getenv("MAIL_USERNAME") or "user",
2128
MAIL_PASSWORD=os.getenv("MAIL_PASSWORD") or "password",
22-
MAIL_FROM=os.getenv("MAIL_FROM") or "a@a.com",
29+
MAIL_FROM=os.getenv("MAIL_FROM") or "a@a.com",
2330
MAIL_PORT=587,
2431
MAIL_SERVER="smtp.gmail.com",
2532
MAIL_TLS=True,

app/database/database.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from app import config
88

9-
109
SQLALCHEMY_DATABASE_URL = os.getenv(
1110
"DATABASE_CONNECTION_STRING", config.DEVELOPMENT_DATABASE_STRING)
1211

app/database/models.py

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,76 @@
1+
from datetime import datetime
2+
13
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String
24
from sqlalchemy.orm import relationship
35

4-
from .database import Base
6+
from app.database.database import Base
7+
8+
9+
class UserEvent(Base):
10+
__tablename__ = "user_event"
11+
12+
id = Column(Integer, primary_key=True, index=True)
13+
user_id = Column('user_id', Integer, ForeignKey('users.id'))
14+
event_id = Column('event_id', Integer, ForeignKey('events.id'))
15+
16+
events = relationship("Event", back_populates="participants")
17+
participants = relationship("User", back_populates="events")
18+
19+
def __repr__(self):
20+
return f'<UserEvent ({self.participants}, {self.events})>'
521

622

723
class User(Base):
824
__tablename__ = "users"
925

1026
id = Column(Integer, primary_key=True, index=True)
11-
username = Column(String, unique=True)
12-
email = Column(String, unique=True)
13-
password = Column(String)
27+
username = Column(String, unique=True, nullable=False)
28+
email = Column(String, unique=True, nullable=False)
29+
password = Column(String, nullable=False)
1430
full_name = Column(String)
1531
description = Column(String, default="Happy new user!")
1632
avatar = Column(String, default="profile.png")
33+
is_active = Column(Boolean, default=False)
1734

18-
is_active = Column(Boolean, default=True)
35+
events = relationship("UserEvent", back_populates="participants")
1936

20-
events = relationship(
21-
"Event", cascade="all, delete", back_populates="owner")
37+
def __repr__(self):
38+
return f'<User {self.id}>'
2239

2340

2441
class Event(Base):
2542
__tablename__ = "events"
2643

2744
id = Column(Integer, primary_key=True, index=True)
28-
title = Column(String)
29-
content = Column(String)
45+
title = Column(String, nullable=False)
3046
start = Column(DateTime, nullable=False)
3147
end = Column(DateTime, nullable=False)
48+
content = Column(String)
49+
location = Column(String)
50+
51+
owner = relationship("User")
3252
owner_id = Column(Integer, ForeignKey("users.id"))
53+
participants = relationship("UserEvent", back_populates="events")
54+
55+
def __repr__(self):
56+
return f'<Event {self.id}>'
57+
58+
59+
class Invitation(Base):
60+
__tablename__ = "invitations"
61+
62+
id = Column(Integer, primary_key=True, index=True)
63+
status = Column(String, nullable=False, default="unread")
64+
recipient_id = Column(Integer, ForeignKey("users.id"))
65+
event_id = Column(Integer, ForeignKey("events.id"))
66+
creation = Column(DateTime, default=datetime.now)
67+
68+
recipient = relationship("User")
69+
event = relationship("Event")
3370

34-
owner = relationship("User", back_populates="events")
71+
def __repr__(self):
72+
return (
73+
f'<Invitation '
74+
f'({self.event.owner}'
75+
f'to {self.recipient})>'
76+
)

app/internal/agenda_events.py

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,35 @@
11
from datetime import date, timedelta
2-
from typing import List, Optional
2+
from typing import List, Optional, Union, Iterator
33

4-
from app.database.models import Event
5-
from app.database.database import SessionLocal
64
import arrow
7-
from sqlalchemy.exc import SQLAlchemyError
5+
from sqlalchemy.orm import Session
6+
7+
from app.database.models import Event
8+
from app.routers.event import sort_by_date
9+
from app.routers.user import get_all_user_events
810

911

1012
def get_events_per_dates(
11-
session: SessionLocal,
13+
session: Session,
1214
user_id: int,
1315
start: Optional[date],
1416
end: Optional[date]
15-
) -> List[Event]:
16-
"""Read from the db. Return a list of all the user events between
17-
the relevant dates."""
17+
) -> Union[Iterator[Event], list]:
18+
"""Read from the db. Return a list of all
19+
the user events between the relevant dates."""
20+
1821
if start > end:
1922
return []
20-
try:
21-
events = (
22-
session.query(Event).filter(Event.owner_id == user_id)
23-
.filter(Event.start.between(start, end + timedelta(days=1)))
24-
.order_by(Event.start).all()
25-
)
26-
except SQLAlchemyError:
27-
return []
28-
else:
29-
return events
23+
24+
return (
25+
filter_dates(
26+
sort_by_date(
27+
get_all_user_events(session, user_id)
28+
),
29+
start,
30+
end,
31+
)
32+
)
3033

3134

3235
def build_arrow_delta_granularity(diff: timedelta) -> List[str]:
@@ -51,5 +54,16 @@ def get_time_delta_string(start: date, end: date) -> str:
5154
granularity = build_arrow_delta_granularity(diff)
5255
duration_string = arrow_end.humanize(
5356
arrow_start, only_distance=True, granularity=granularity
54-
)
57+
)
5558
return duration_string
59+
60+
61+
def filter_dates(
62+
events: List[Event], start: Optional[date],
63+
end: Optional[date]) -> Iterator[Event]:
64+
"""filter events by a time frame."""
65+
66+
yield from (
67+
event for event in events
68+
if start <= event.start.date() <= end
69+
)

app/internal/event.py

Whitespace-only changes.

app/internal/utils.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from sqlalchemy.orm import Session
2+
3+
from app.database.models import Base
4+
5+
6+
def save(item, session: Session) -> bool:
7+
"""Commits an instance to the db.
8+
source: app.database.database.Base"""
9+
10+
if issubclass(item.__class__, Base):
11+
session.add(item)
12+
session.commit()
13+
return True
14+
return False
15+
16+
17+
def create_model(session: Session, model_class, **kw):
18+
"""Creates and saves a db model."""
19+
20+
instance = model_class(**kw)
21+
save(instance, session)
22+
return instance

app/main.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
from app.database.database import engine
66
from app.dependencies import (
77
MEDIA_PATH, STATIC_PATH, templates)
8-
from app.routers import agenda, event, profile, email
9-
8+
from app.routers import agenda, event, profile, email, invitation
109

1110
models.Base.metadata.create_all(bind=engine)
1211

@@ -18,12 +17,12 @@
1817
app.include_router(event.router)
1918
app.include_router(agenda.router)
2019
app.include_router(email.router)
20+
app.include_router(invitation.router)
2121

2222

2323
@app.get("/")
2424
async def home(request: Request):
2525
return templates.TemplateResponse("home.html", {
2626
"request": request,
27-
"message": "Hello, World!"
28-
27+
"message": "Hello, World!",
2928
})

app/routers/agenda.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,22 @@
33
from typing import Optional, Tuple
44

55
from fastapi import APIRouter, Depends, Request
6-
from fastapi.templating import Jinja2Templates
76
from sqlalchemy.orm import Session
7+
from starlette.templating import _TemplateResponse
88

99
from app.database.database import get_db
1010
from app.dependencies import templates
1111
from app.internal import agenda_events
1212

13-
1413
router = APIRouter()
1514

1615

1716
def calc_dates_range_for_agenda(
1817
start: Optional[date],
1918
end: Optional[date],
20-
days: Optional[int]
19+
days: Optional[int],
2120
) -> Tuple[date, date]:
22-
"""Create start and end dates eccording to the parameters in the page."""
21+
"""Create start and end dates according to the parameters in the page."""
2322
if days is not None:
2423
start = date.today()
2524
end = start + timedelta(days=days)
@@ -35,8 +34,8 @@ def agenda(
3534
db: Session = Depends(get_db),
3635
start_date: Optional[date] = None,
3736
end_date: Optional[date] = None,
38-
days: Optional[int] = None
39-
) -> Jinja2Templates:
37+
days: Optional[int] = None,
38+
) -> _TemplateResponse:
4039
"""Route for the agenda page, using dates range or exact amount of days."""
4140

4241
user_id = 1 # there is no user session yet, so I use user id- 1.
@@ -58,5 +57,5 @@ def agenda(
5857
"request": request,
5958
"events": events,
6059
"start_date": start_date,
61-
"end_date": end_date
60+
"end_date": end_date,
6261
})

app/routers/event.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
from operator import attrgetter
2+
from typing import List
3+
14
from fastapi import APIRouter, Request
25

6+
from app.database.models import Event
7+
from app.database.models import UserEvent
38
from app.dependencies import templates
9+
from app.internal.utils import create_model
410

511
router = APIRouter(
612
prefix="/event",
@@ -19,3 +25,30 @@ async def eventedit(request: Request):
1925
async def eventview(request: Request, id: int):
2026
return templates.TemplateResponse("event/eventview.html",
2127
{"request": request, "event_id": id})
28+
29+
30+
def create_event(db, title, start, end, owner_id, content=None, location=None):
31+
"""Creates an event and an association."""
32+
33+
event = create_model(
34+
db, Event,
35+
title=title,
36+
start=start,
37+
end=end,
38+
content=content,
39+
owner_id=owner_id,
40+
location=location,
41+
)
42+
create_model(
43+
db, UserEvent,
44+
user_id=owner_id,
45+
event_id=event.id
46+
)
47+
return event
48+
49+
50+
def sort_by_date(events: List[Event]) -> List[Event]:
51+
"""Sorts the events by the start of the event."""
52+
53+
temp = events.copy()
54+
return sorted(temp, key=attrgetter('start'))

0 commit comments

Comments
 (0)