Skip to content
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8928b17
update ORM and added export for an event
IdanPelled Jan 12, 2021
79874a7
update ORM and added export for an event
IdanPelled Jan 12, 2021
bc53da7
Merge branch 'main' of https://github.com/PythonFreeCourse/calendar i…
IdanPelled Jan 12, 2021
8f4e690
Added in app shareable events
IdanPelled Jan 13, 2021
25d1b80
upstream merge
IdanPelled Jan 13, 2021
8fb1035
docs: add type annotation and fix folder structure
IdanPelled Jan 13, 2021
149b571
docs: add type annotation and fix folder structure
IdanPelled Jan 13, 2021
be96e5d
docs: fix documentation
IdanPelled Jan 14, 2021
9b2a861
add: tests
IdanPelled Jan 15, 2021
aa654ac
add: timezone support
IdanPelled Jan 16, 2021
5227c11
add: session management
IdanPelled Jan 16, 2021
56877c6
fix bug
IdanPelled Jan 16, 2021
eda3c31
split conftest file
IdanPelled Jan 16, 2021
c24830a
split conftest file
IdanPelled Jan 16, 2021
69f81e1
move "utils" folder into "internal" folder
IdanPelled Jan 17, 2021
0dc1ae6
change file structure
IdanPelled Jan 18, 2021
868ba44
Merge branch 'develop' into feature/shareable-event
IdanPelled Jan 18, 2021
7c2edd0
Merge branch 'develop' feature/shareable-event
IdanPelled Jan 19, 2021
3883d5e
feat: enable invited users to view events
IdanPelled Jan 19, 2021
e39c43d
Merge branch 'develop into feature/shareable-event
IdanPelled Jan 19, 2021
6add784
feat: flake8 changes
IdanPelled Jan 19, 2021
cccd7b7
fix: requirements bug
IdanPelled Jan 19, 2021
5faf0ba
fix: requirements bug
IdanPelled Jan 19, 2021
bdd4d8b
feat: flake8 changes
IdanPelled Jan 19, 2021
d629a25
add: tests
IdanPelled Jan 19, 2021
046162d
feat: flake8 changes
IdanPelled Jan 19, 2021
0bcbff8
add: tests
IdanPelled Jan 20, 2021
9462602
feat: flake8 changes
IdanPelled Jan 20, 2021
053b156
edit: file structure
IdanPelled Jan 20, 2021
7684719
edit: file structure
IdanPelled Jan 20, 2021
8b97c29
feat: add route tests
IdanPelled Jan 20, 2021
ba09ff0
fix: test bug
IdanPelled Jan 21, 2021
44f30a0
Merge branch 'develop' into feature/shareable-event
IdanPelled Jan 21, 2021
3400fe9
remove: config.py
IdanPelled Jan 21, 2021
e0c75f5
fix: type annotation
IdanPelled Jan 22, 2021
3ec5f37
add: minor changes
IdanPelled Jan 22, 2021
b9198d5
feat: flake8 changes
IdanPelled Jan 22, 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ ipython_config.py
# pyenv
.python-version

# pycharm
.idea/

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
Expand Down
31 changes: 31 additions & 0 deletions app/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import os

from fastapi_mail import ConnectionConfig
# flake8: noqa

# general
DOMAIN = 'Our-Domain'

# DATABASE
DEVELOPMENT_DATABASE_STRING = "sqlite:///./dev.db"

# MEDIA
MEDIA_DIRECTORY = 'media'
PICTURE_EXTENSION = '.png'
AVATAR_SIZE = (120, 120)

# export
ICAL_VERSION = '2.0'
PRODUCT_ID = '-//Our product id//'

# email
email_conf = ConnectionConfig(
MAIL_USERNAME=os.getenv("MAIL_USERNAME") or "user",
MAIL_PASSWORD=os.getenv("MAIL_PASSWORD") or "password",
MAIL_FROM=os.getenv("MAIL_FROM") or "a@a.com",
MAIL_PORT=587,
MAIL_SERVER="smtp.gmail.com",
MAIL_TLS=True,
MAIL_SSL=False,
USE_CREDENTIALS=True,
)
9 changes: 8 additions & 1 deletion app/config.py.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import os
from fastapi_mail import ConnectionConfig
# flake8: noqa

# general
DOMAIN = 'Our-Domain'

# DATABASE
DEVELOPMENT_DATABASE_STRING = "sqlite:///./dev.db"
Expand All @@ -12,10 +14,15 @@ MEDIA_DIRECTORY = 'media'
PICTURE_EXTENSION = '.png'
AVATAR_SIZE = (120, 120)

# export
ICAL_VERSION = '2.0'
PRODUCT_ID = '-//Our product id//'

# email
email_conf = ConnectionConfig(
MAIL_USERNAME=os.getenv("MAIL_USERNAME") or "user",
MAIL_PASSWORD=os.getenv("MAIL_PASSWORD") or "password",
MAIL_FROM=os.getenv("MAIL_FROM") or "a@a.com",
MAIL_FROM=os.getenv("MAIL_FROM") or "a@a.com",
MAIL_PORT=587,
MAIL_SERVER="smtp.gmail.com",
MAIL_TLS=True,
Expand Down
1 change: 0 additions & 1 deletion app/database/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from app import config


SQLALCHEMY_DATABASE_URL = os.getenv(
"DATABASE_CONNECTION_STRING", config.DEVELOPMENT_DATABASE_STRING)

Expand Down
62 changes: 52 additions & 10 deletions app/database/models.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,76 @@
from datetime import datetime

from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String
from sqlalchemy.orm import relationship

from .database import Base
from app.database.database import Base


class UserEvent(Base):
__tablename__ = "user_event"

id = Column(Integer, primary_key=True, index=True)
user_id = Column('user_id', Integer, ForeignKey('users.id'))
event_id = Column('event_id', Integer, ForeignKey('events.id'))

events = relationship("Event", back_populates="participants")
participants = relationship("User", back_populates="events")

def __repr__(self):
return f'<UserEvent ({self.participants}, {self.events})>'


class User(Base):
__tablename__ = "users"

id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True)
email = Column(String, unique=True)
password = Column(String)
username = Column(String, unique=True, nullable=False)
email = Column(String, unique=True, nullable=False)
password = Column(String, nullable=False)
full_name = Column(String)
description = Column(String, default="Happy new user!")
avatar = Column(String, default="profile.png")
is_active = Column(Boolean, default=False)

is_active = Column(Boolean, default=True)
events = relationship("UserEvent", back_populates="participants")

events = relationship(
"Event", cascade="all, delete", back_populates="owner")
def __repr__(self):
return f'<User {self.id}>'


class Event(Base):
__tablename__ = "events"

id = Column(Integer, primary_key=True, index=True)
title = Column(String)
content = Column(String)
title = Column(String, nullable=False)
start = Column(DateTime, nullable=False)
end = Column(DateTime, nullable=False)
content = Column(String)
location = Column(String)

owner = relationship("User")
owner_id = Column(Integer, ForeignKey("users.id"))
participants = relationship("UserEvent", back_populates="events")

def __repr__(self):
return f'<Event {self.id}>'


class Invitation(Base):
__tablename__ = "invitations"

id = Column(Integer, primary_key=True, index=True)
status = Column(String, nullable=False, default="unread")
recipient_id = Column(Integer, ForeignKey("users.id"))
event_id = Column(Integer, ForeignKey("events.id"))
creation = Column(DateTime, default=datetime.now)

recipient = relationship("User")
event = relationship("Event")

owner = relationship("User", back_populates="events")
def __repr__(self):
return (
f'<Invitation '
f'({self.event.owner}'
f'to {self.recipient})>'
)
50 changes: 32 additions & 18 deletions app/internal/agenda_events.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
from datetime import date, timedelta
from typing import List, Optional

from app.database.models import Event
from app.database.database import SessionLocal
import arrow
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session

from app.database.models import Event
from app.internal.events import sort_by_date
from app.routers.user import get_all_user_events


def get_events_per_dates(
session: SessionLocal,
session: Session,
user_id: int,
start: Optional[date],
end: Optional[date]
) -> List[Event]:
"""Read from the db. Return a list of all the user events between
the relevant dates."""
) -> List[Event]:
"""Read from the db. Return a list of all
the user events between the relevant dates."""

if start > end:
return []
try:
events = (
session.query(Event).filter(Event.owner_id == user_id)
.filter(Event.start.between(start, end + timedelta(days=1)))
.order_by(Event.start).all()
)
except SQLAlchemyError:
return []
else:
return events

return (
filter_dates(
sort_by_date(
get_all_user_events(session, user_id)
),
start,
end,
)
)


def build_arrow_delta_granularity(diff: timedelta) -> List[str]:
Expand All @@ -51,5 +54,16 @@ def get_time_delta_string(start: date, end: date) -> str:
granularity = build_arrow_delta_granularity(diff)
duration_string = arrow_end.humanize(
arrow_start, only_distance=True, granularity=granularity
)
)
return duration_string


def filter_dates(
events: List[Event], start: Optional[date],
end: Optional[date]) -> List[Event]:
"""filter events by a time frame."""

yield from (
event for event in events
if start <= event.start.date() <= end
)
12 changes: 12 additions & 0 deletions app/internal/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from operator import attrgetter
from typing import List

from app.database.models import Event


def sort_by_date(events: List[Event]) -> List[Event]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add /sort to event.py in routers

"""Sorts the events by the start of the event."""

temp = events.copy()
temp.sort(key=attrgetter('start'))
return temp
22 changes: 22 additions & 0 deletions app/internal/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from sqlalchemy.orm import Session

from app.database.models import Base


def save(item, session: Session) -> bool:
"""Commits an instance to the db.
source: app.database.database.Base"""

if issubclass(item.__class__, Base):
session.add(item)
session.commit()
return True
return False


def create_model(session: Session, model_class, **kw):
"""Creates and saves a db model."""

instance = model_class(**kw)
save(instance, session)
return instance
7 changes: 3 additions & 4 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
from app.database.database import engine
from app.dependencies import (
MEDIA_PATH, STATIC_PATH, templates)
from app.routers import agenda, event, profile, email

from app.routers import agenda, event, profile, email, invitation

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

Expand All @@ -18,12 +17,12 @@
app.include_router(event.router)
app.include_router(agenda.router)
app.include_router(email.router)
app.include_router(invitation.router)


@app.get("/")
async def home(request: Request):
return templates.TemplateResponse("home.html", {
"request": request,
"message": "Hello, World!"

"message": "Hello, World!",
})
13 changes: 6 additions & 7 deletions app/routers/agenda.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,22 @@
from typing import Optional, Tuple

from fastapi import APIRouter, Depends, Request
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
from starlette.templating import _TemplateResponse

from app.database.database import get_db
from app.dependencies import templates
from app.internal import agenda_events


router = APIRouter()


def calc_dates_range_for_agenda(
start: Optional[date],
end: Optional[date],
days: Optional[int]
days: Optional[int],
) -> Tuple[date, date]:
"""Create start and end dates eccording to the parameters in the page."""
"""Create start and end dates according to the parameters in the page."""
if days is not None:
start = date.today()
end = start + timedelta(days=days)
Expand All @@ -35,8 +34,8 @@ def agenda(
db: Session = Depends(get_db),
start_date: Optional[date] = None,
end_date: Optional[date] = None,
days: Optional[int] = None
) -> Jinja2Templates:
days: Optional[int] = None,
) -> _TemplateResponse:
"""Route for the agenda page, using dates range or exact amount of days."""

user_id = 1 # there is no user session yet, so I use user id- 1.
Expand All @@ -58,5 +57,5 @@ def agenda(
"request": request,
"events": events,
"start_date": start_date,
"end_date": end_date
"end_date": end_date,
})
22 changes: 22 additions & 0 deletions app/routers/event.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from fastapi import APIRouter, Request

from app.database.models import Event, UserEvent
from app.dependencies import templates
from app.internal.utils import create_model

router = APIRouter(
prefix="/event",
Expand All @@ -19,3 +21,23 @@ async def eventedit(request: Request):
async def eventview(request: Request, id: int):
return templates.TemplateResponse("event/eventview.html",
{"request": request, "event_id": id})


def create_event(db, title, start, end, owner_id, content=None, location=None):
"""Creates an event and an association."""

event = create_model(
db, Event,
title=title,
start=start,
end=end,
content=content,
owner_id=owner_id,
location=location,
)
create_model(
db, UserEvent,
user_id=owner_id,
event_id=event.id
)
return event
Loading