From 5ab86e3dd2e4c0af9fb333f09a7e9fdfe91f0766 Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Wed, 10 Feb 2021 21:41:10 +0200 Subject: [PATCH 01/16] Created menstrual model and partial join form --- app/database/models.py | 8 ++++++++ app/main.py | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/database/models.py b/app/database/models.py index 3e7c1782..383a0e05 100644 --- a/app/database/models.py +++ b/app/database/models.py @@ -176,3 +176,11 @@ def __repr__(self): f'{self.start_day_in_month}/{self.start_month}-' f'{self.end_day_in_month}/{self.end_month}>' ) + + +class UserMenstrualPeriodLength(Base): + __tablename__ = "user_menstrual_period_length" + + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, ForeignKey("users.id")) + period_length = Column(Integer) \ No newline at end of file diff --git a/app/main.py b/app/main.py index 08b27769..a928014e 100644 --- a/app/main.py +++ b/app/main.py @@ -9,7 +9,8 @@ from app.internal import daily_quotes, json_data_loader from app.routers import ( agenda, calendar, categories, dayview, email, - event, invitation, profile, search, telegram, whatsapp + event, invitation, profile, search, telegram, whatsapp, + menstrual_predictor ) from app.telegram.bot import telegram_bot @@ -46,6 +47,7 @@ def create_tables(engine, psql_environment): search.router, telegram.router, whatsapp.router, + menstrual_predictor.router, ] for router in routers_to_include: From 783493f4bc0f0e23ded1950503d8f788a075cecd Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Tue, 16 Feb 2021 10:33:39 +0200 Subject: [PATCH 02/16] commit to pull develop --- app/database/models.py | 14 +++-- app/routers/menstrual_predictor.py | 61 +++++++++++---------- app/templates/join_menstrual_predictor.html | 24 ++++++-- 3 files changed, 58 insertions(+), 41 deletions(-) diff --git a/app/database/models.py b/app/database/models.py index dfae7d9b..a2f05068 100644 --- a/app/database/models.py +++ b/app/database/models.py @@ -286,6 +286,14 @@ def __repr__(self): ) +class UserMenstrualPeriodLength(Base): + __tablename__ = "user_menstrual_period_length" + + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False, unique=True) + period_length = Column(Integer) + + # insert language data # Credit to adrihanu https://stackoverflow.com/users/9127249/adrihanu @@ -297,9 +305,3 @@ def insert_data(target, session: Session, **kw): event.listen(Language.__table__, 'after_create', insert_data) -class UserMenstrualPeriodLength(Base): - __tablename__ = "user_menstrual_period_length" - - id = Column(Integer, primary_key=True, index=True) - user_id = Column(Integer, ForeignKey("users.id")) - period_length = Column(Integer) diff --git a/app/routers/menstrual_predictor.py b/app/routers/menstrual_predictor.py index 2d027a6f..6b094285 100644 --- a/app/routers/menstrual_predictor.py +++ b/app/routers/menstrual_predictor.py @@ -8,11 +8,12 @@ from starlette.status import HTTP_302_FOUND from starlette.templating import Jinja2Templates -from app.database.models import Event +from app.database.models import Event, UserMenstrualPeriodLength from app.dependencies import get_db, templates from app.routers.share import accept from app.routers.event import create_event from app.routers.user import create_user +from app.internal.utils import create_model, get_current_user from loguru import logger @@ -47,19 +48,31 @@ async def submit_join_form( # db.add(event) # db.commit() data = await request.form() - # invite_id = list(data.values())[0] - - # invitation = get_invitation_by_id(invite_id, session=db) - # accept(invitation, db) - # url = router.url_path_for("view_invitations") + current_user = get_current_user(session=db) + + logger.error(f'current user, {current_user.id}') + logger.error(data['avg_period_length']) + + user_menstrual_period_length = { + "user_id": current_user.id, + "period_length": data['avg_period_length'], + } + last_period_date = datetime.datetime.strptime(data['last_period_date'], '%Y-%m-%d') + try: + new_signed_up_to_period = create_model(session=db, model_class=UserMenstrualPeriodLength, **user_menstrual_period_length) + except SQLAlchemyError as err: + logger.warning('User already signed up to the service, hurray') + db.rollback() url = '/' - generate_predicted_period_dates(db, 5, datetime.datetime.now(), 1) - period_days = get_all_period_days(db, user_id=1) + # if not is_date_before_today(last_period_date): + # return RedirectResponse(url=url, status_code=HTTP_302_FOUND) + generate_predicted_period_dates(db, data['avg_period_length'], last_period_date, current_user.id) + period_days = get_all_period_days(db, user_id=current_user.id) logger.error(period_days) - logger.warning(data) - remove_existing_period_dates(db, user_id=1) - period_days = get_all_period_days(db, user_id=1) + # logger.warning(data) + # remove_existing_period_dates(db, user_id=current_user.id) + period_days = get_all_period_days(db, user_id=current_user.id) for day in period_days: logger.error(day.start) @@ -70,30 +83,25 @@ def remove_existing_period_dates(db, user_id): period_days = (db.query(Event). filter(Event.owner_id == user_id). filter(Event.category_id == MENSTRUAL_PERIOD_CATEGORY_ID). - all()) - db.delete(period_days) + filter(Event.start > datetime.datetime.now()). + delete()) + # db.delete(period_days) db.commit() def generate_predicted_period_dates(db, period_length, last_period_time, user_id): - for i in range(period_length): + for i in range(int(period_length)): delta = datetime.timedelta(i + 1) period_date = last_period_time + delta event = create_event(db, 'period day', period_date, period_date, user_id, category_id=MENSTRUAL_PERIOD_CATEGORY_ID) - db.add(event) - db.commit() + # db.add(event) + # db.commit() def get_all_period_days(session: Session, user_id: int) -> List[Event]: """Returns all period days filter by user id.""" try: - # 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()] period_days = list(session.query(Event). filter(Event.owner_id == user_id). filter(Event.category_id == MENSTRUAL_PERIOD_CATEGORY_ID). @@ -106,10 +114,5 @@ def get_all_period_days(session: Session, user_id: int) -> List[Event]: return period_days -# def get_invitation_by_id( -# invitation_id: int, session: Session -# ) -> Union[Invitation, None]: -# """Returns a invitation by an id. -# if id does not exist, returns None.""" - -# return session.query(Invitation).filter_by(id=invitation_id).first() +def is_date_before_today(received_date: datetime): + return received_date < datetime.datetime.now() diff --git a/app/templates/join_menstrual_predictor.html b/app/templates/join_menstrual_predictor.html index e4f47e2c..f1b721f2 100644 --- a/app/templates/join_menstrual_predictor.html +++ b/app/templates/join_menstrual_predictor.html @@ -12,12 +12,12 @@

Please fill in your details

{% if errors %}
{% else %} -
+
{% endif %}
- - Must be between 3 to 20 characters. + + Must be above 1 day.
- {% if errors %} -
- {% else %} -
- {% endif %} +
Must be above 1 day.
- - {% if errors and "username" in errors %} - {{ errors['username'] }}
- {% endif %}
- {% if errors %} -
- {% else %} -
- {% endif %} +
- {% if errors and "full_name" in errors %} - {{ errors["full_name"] }}
- {% endif %}
From a1a506249c96fb98226cabde3157d7c240fc8233 Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Wed, 17 Feb 2021 00:00:36 +0200 Subject: [PATCH 04/16] Menstrual predictor with part of tests --- app/database/models.py | 3 +- app/routers/menstrual_predictor.py | 47 ++++++++++++++---------------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/app/database/models.py b/app/database/models.py index b5c30a91..a6fa7e1b 100644 --- a/app/database/models.py +++ b/app/database/models.py @@ -337,7 +337,8 @@ class UserMenstrualPeriodLength(Base): __tablename__ = "user_menstrual_period_length" id = Column(Integer, primary_key=True, index=True) - user_id = Column(Integer, ForeignKey("users.id"), nullable=False, unique=True) + user_id = Column(Integer, ForeignKey("users.id"), + nullable=False, unique=True) period_length = Column(Integer) diff --git a/app/routers/menstrual_predictor.py b/app/routers/menstrual_predictor.py index 425059b4..1f593c24 100644 --- a/app/routers/menstrual_predictor.py +++ b/app/routers/menstrual_predictor.py @@ -1,5 +1,5 @@ import datetime -from typing import List, Union +from typing import List from fastapi import APIRouter, Depends, Request, HTTPException from fastapi.responses import RedirectResponse @@ -38,13 +38,15 @@ def join(request: Request, db: Session = Depends(get_db)): @router.get("/add-period-start/{start_date}") -def add_period_start(request: Request, start_date: str, db: Session = Depends(get_db)): +def add_period_start(request: Request, start_date: str, + db: Session = Depends(get_db)): try: period_start_date = datetime.datetime.strptime(start_date, '%Y-%m-%d') except ValueError as err: logger.exception(err) raise HTTPException( - status_code=400, detail="The given date doesn't match a date format YYYY-MM-DD") + status_code=400, + detail="The given date doesn't match a date format YYYY-MM-DD") else: current_user_id = get_current_user(db).id user_period_length = is_user_signed_up_to_menstrual_predictor( @@ -75,10 +77,13 @@ async def submit_join_form( data['last_period_date'], '%Y-%m-%d') try: new_signed_up_to_period = create_model( - session=db, model_class=UserMenstrualPeriodLength, **user_menstrual_period_length) - except SQLAlchemyError as err: + session=db, + model_class=UserMenstrualPeriodLength, + **user_menstrual_period_length) + except SQLAlchemyError: logger.info( - f'User {new_signed_up_to_period.user_id} already signed up to the service, hurray') + f'User {new_signed_up_to_period.user_id}' + 'already signed up to the service, hurray') db.rollback() url = '/' generate_predicted_period_dates( @@ -94,9 +99,6 @@ def get_period_days(request: Request, db: Session = Depends(get_db)): for i in range(len(period_days) - 1): gap = get_date_diff(period_days[i].start, period_days[i + 1].start) gaps_list.append(gap.days) - # logger.debug(period_days[i].start) - # logger.debug(period_days[i+1].start) - logger.debug(gaps_list) logger.critical(get_list_avg(gaps_list)) @@ -110,7 +112,7 @@ def get_avg_period_gap(db: Session, user_id): return get_list_avg(gaps_list) -def get_date_diff(date_1, date_2): +def get_date_diff(date_1: datetime, date_2: datetime): return date_2 - date_1 @@ -128,22 +130,16 @@ def remove_existing_period_dates(db: Session, user_id: int): logger.info('Removed all period predictions to create new ones') -# def add_period_start_date(db: Session, user_id: int, received_date: datetime): -# generate_predicted_period_dates( -# db, data['avg_period_length'], last_period_date, current_user.id) - - -def generate_predicted_period_dates(db, period_length, period_start_date, user_id): +def generate_predicted_period_dates( + db: Session, + period_length: str, + period_start_date: datetime, + user_id: int): delta = datetime.timedelta(int(period_length)) period_end_date = period_start_date + delta - event = create_event(db, 'period', period_start_date, period_end_date, - user_id, category_id=MENSTRUAL_PERIOD_CATEGORY_ID) - period_days = get_all_period_days(db, user_id=user_id) - # remove_existing_period_dates(db, user_id=current_user.id) - # period_days = sorted(get_all_period_days(db, user_id=user_id), key=lambda d: d.start) - # logger.error(period_days) - # for day in period_days: - # logger.error(day.start) + create_event(db, 'period', period_start_date, period_end_date, + user_id, category_id=MENSTRUAL_PERIOD_CATEGORY_ID) + get_all_period_days(db, user_id=user_id) def add_3_month_predictions(db, period_length, period_start_date, user_id): @@ -163,7 +159,8 @@ def get_all_period_days(session: Session, user_id: int) -> List[Event]: try: period_days = sorted((session.query(Event). filter(Event.owner_id == user_id). - filter(Event.category_id == MENSTRUAL_PERIOD_CATEGORY_ID). + filter(Event.category_id + == MENSTRUAL_PERIOD_CATEGORY_ID). all()), key=lambda d: d.start) except SQLAlchemyError as err: From 6bef2867e4e69b65e618b669ca290186ad5c4bcf Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Thu, 18 Feb 2021 21:57:31 +0200 Subject: [PATCH 05/16] partial tests not working properly --- app/database/models.py | 2 +- app/routers/menstrual_predictor.py | 31 ++++++++++++------------------ tests/conftest.py | 10 ++++++++++ tests/test_menstrual_predictor.py | 21 ++++++++++++++++++++ 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/app/database/models.py b/app/database/models.py index a6fa7e1b..44c3360c 100644 --- a/app/database/models.py +++ b/app/database/models.py @@ -339,7 +339,7 @@ class UserMenstrualPeriodLength(Base): id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=False, unique=True) - period_length = Column(Integer) + period_length = Column(Integer, nullable=False) # insert language data diff --git a/app/routers/menstrual_predictor.py b/app/routers/menstrual_predictor.py index 1f593c24..945b0183 100644 --- a/app/routers/menstrual_predictor.py +++ b/app/routers/menstrual_predictor.py @@ -25,7 +25,7 @@ @router.get("/") -def join(request: Request, db: Session = Depends(get_db)): +def join_menstrual_predictor(request: Request, db: Session = Depends(get_db)): current_user_id = get_current_user(db).id if not is_user_signed_up_to_menstrual_predictor(db, current_user_id): @@ -82,7 +82,7 @@ async def submit_join_form( **user_menstrual_period_length) except SQLAlchemyError: logger.info( - f'User {new_signed_up_to_period.user_id}' + 'Current user ' 'already signed up to the service, hurray') db.rollback() url = '/' @@ -92,16 +92,6 @@ async def submit_join_form( return RedirectResponse(url=url, status_code=HTTP_302_FOUND) -@router.get("/get-period-dates/") -def get_period_days(request: Request, db: Session = Depends(get_db)): - period_days = get_all_period_days(db, 1) - gaps_list = [] - for i in range(len(period_days) - 1): - gap = get_date_diff(period_days[i].start, period_days[i + 1].start) - gaps_list.append(gap.days) - logger.critical(get_list_avg(gaps_list)) - - def get_avg_period_gap(db: Session, user_id): period_days = get_all_period_days(db, user_id) gaps_list = [] @@ -137,20 +127,23 @@ def generate_predicted_period_dates( user_id: int): delta = datetime.timedelta(int(period_length)) period_end_date = period_start_date + delta - create_event(db, 'period', period_start_date, period_end_date, - user_id, category_id=MENSTRUAL_PERIOD_CATEGORY_ID) - get_all_period_days(db, user_id=user_id) + period_event = create_event( + db, 'period', period_start_date, period_end_date, + user_id, category_id=MENSTRUAL_PERIOD_CATEGORY_ID) + return period_event def add_3_month_predictions(db, period_length, period_start_date, user_id): avg_gap = get_avg_period_gap(db, user_id) avg_gap_delta = datetime.timedelta(avg_gap) - for i in range(4): - generate_predicted_period_dates( + generated_3_months = [] + for _ in range(4): + generated_period = generate_predicted_period_dates( db, period_length, period_start_date, user_id) + generated_3_months.append(generated_period) period_start_date += avg_gap_delta - logger.error(period_start_date) - logger.error(avg_gap_delta) + logger.info(f'Generated predictions: {generated_3_months}') + return generated_3_months def get_all_period_days(session: Session, user_id: int) -> List[Event]: diff --git a/tests/conftest.py b/tests/conftest.py index 4923a614..bb547618 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -79,3 +79,13 @@ def sqlite_engine(): @pytest.fixture def Calendar(): return calendar.Calendar(0) + + +@pytest.fixture(scope="session") +def module_session(): + Base.metadata.create_all(bind=test_engine) + session = get_test_db() + yield session + session.rollback() + session.close() + Base.metadata.drop_all(bind=test_engine) diff --git a/tests/test_menstrual_predictor.py b/tests/test_menstrual_predictor.py index 33b2c9e5..4e0bceca 100644 --- a/tests/test_menstrual_predictor.py +++ b/tests/test_menstrual_predictor.py @@ -1,3 +1,8 @@ +from app.routers.menstrual_predictor import get_all_period_days + +from app.internal.utils import get_current_user + + class TestMenstrualPredictor: PREDICTOR_PREFIX = "/menstrual_predictor" ADD_PERIOD_START = "/add-period-start" @@ -9,6 +14,21 @@ def test_menstrual_predictor_page_not_signed_up( assert resp.ok @staticmethod + def test_menstrual_predictor_sign_up( + client, module_session): + resp = client.post(TestMenstrualPredictor.PREDICTOR_PREFIX, + json={"avg_period_length": 8, + "last_period_date": "2020-11-07"}) + assert resp.__dict__ == '' + assert resp.ok + current_user_id = get_current_user(module_session).id + resp = client.get( + TestMenstrualPredictor.PREDICTOR_PREFIX + + TestMenstrualPredictor.ADD_PERIOD_START + "/2020-12-11") + period_days = get_all_period_days(module_session, current_user_id) + assert period_days + + @ staticmethod def test_add_period_date( client, session ): @@ -16,3 +36,4 @@ def test_add_period_date( TestMenstrualPredictor.PREDICTOR_PREFIX + TestMenstrualPredictor.ADD_PERIOD_START + "/2020-12-11") assert resp.ok + From 1f0ba12dfb028e994f1dc26069092777f64a1ede Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Sun, 21 Feb 2021 23:29:11 +0200 Subject: [PATCH 06/16] Removed conftest from changes --- app/database/models.py | 10 +- app/internal/menstrual_predictor_utils.py | 114 +++++++++++++++ app/main.py | 46 ++++-- app/routers/menstrual_predictor.py | 164 +++++++--------------- tests/test_menstrual_predictor.py | 43 +++--- 5 files changed, 227 insertions(+), 150 deletions(-) create mode 100644 app/internal/menstrual_predictor_utils.py diff --git a/app/database/models.py b/app/database/models.py index 248c7614..f3e80e67 100644 --- a/app/database/models.py +++ b/app/database/models.py @@ -389,11 +389,15 @@ class UserMenstrualPeriodLength(Base): __tablename__ = "user_menstrual_period_length" id = Column(Integer, primary_key=True, index=True) - user_id = Column(Integer, ForeignKey("users.id"), - nullable=False, unique=True) + user_id = Column( + Integer, + ForeignKey("users.id"), + nullable=False, + unique=True, + ) period_length = Column(Integer, nullable=False) - + class Joke(Base): __tablename__ = "jokes" diff --git a/app/internal/menstrual_predictor_utils.py b/app/internal/menstrual_predictor_utils.py new file mode 100644 index 00000000..b889405d --- /dev/null +++ b/app/internal/menstrual_predictor_utils.py @@ -0,0 +1,114 @@ +from datetime import datetime + +from loguru import logger + +from typing import List + +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy.orm import Session + +from app.database.models import Event, UserMenstrualPeriodLength + +from app.routers.event import create_event + + +MENSTRUAL_PERIOD_CATEGORY_ID = 111 + + +def get_avg_period_gap(db: Session, user_id): + period_days = get_all_period_days(db, user_id) + gaps_list = [] + + for i in range(len(period_days) - 1): + gap = get_date_diff(period_days[i].start, period_days[i + 1].start) + gaps_list.append(gap.days) + return get_list_avg(gaps_list) + + +def get_date_diff(date_1: datetime, date_2: datetime): + return date_2 - date_1 + + +def get_list_avg(received_list: List): + return sum(received_list) // len(received_list) + + +def remove_existing_period_dates(db: Session, user_id: int): + ( + db.query(Event) + .filter(Event.owner_id == user_id) + .filter(Event.category_id == MENSTRUAL_PERIOD_CATEGORY_ID) + .filter(Event.start > datetime.datetime.now()) + .delete() + ) + db.commit() + logger.info("Removed all period predictions to create new ones") + + +def generate_predicted_period_dates( + db: Session, + period_length: str, + period_start_date: datetime, + user_id: int, +): + delta = datetime.timedelta(int(period_length)) + period_end_date = period_start_date + delta + period_event = create_event( + db, + "period", + period_start_date, + period_end_date, + user_id, + category_id=MENSTRUAL_PERIOD_CATEGORY_ID, + ) + return period_event + + +def add_3_month_predictions(db, period_length, period_start_date, user_id): + avg_gap = get_avg_period_gap(db, user_id) + avg_gap_delta = datetime.timedelta(avg_gap) + generated_3_months = [] + for _ in range(4): + generated_period = generate_predicted_period_dates( + db, + period_length, + period_start_date, + user_id, + ) + generated_3_months.append(generated_period) + period_start_date += avg_gap_delta + logger.info(f"Generated predictions: {generated_3_months}") + return generated_3_months + + +def get_all_period_days(session: Session, user_id: int) -> List[Event]: + """Returns all period days filtered by user id.""" + + try: + period_days = sorted( + ( + session.query(Event) + .filter(Event.owner_id == user_id) + .filter(Event.category_id == MENSTRUAL_PERIOD_CATEGORY_ID) + .all() + ), + key=lambda d: d.start, + ) + + except SQLAlchemyError as err: + logger.exception(err) + return [] + else: + return period_days + + +def is_user_signed_up_to_menstrual_predictor(session: Session, user_id: int): + user_menstrual_period_length = ( + session.query(UserMenstrualPeriodLength) + .filter(user_id == user_id) + .first() + ) + if user_menstrual_period_length: + return user_menstrual_period_length.period_length + else: + return False diff --git a/app/main.py b/app/main.py index cd1c998b..d033be4d 100644 --- a/app/main.py +++ b/app/main.py @@ -17,11 +17,11 @@ def create_tables(engine, psql_environment): - if 'sqlite' in str(engine.url) and psql_environment: + if "sqlite" in str(engine.url) and psql_environment: raise models.PSQLEnvironmentError( "You're trying to use PSQL features on SQLite env.\n" "Please set app.config.PSQL_ENVIRONMENT to False " - "and run the app again." + "and run the app again.", ) else: models.Base.metadata.create_all(bind=engine) @@ -40,10 +40,33 @@ def create_tables(engine, psql_environment): set_ui_language() from app.routers import ( # noqa: E402 - about_us, agenda, calendar, categories, celebrity, credits, - currency, dayview, email, event, export, four_o_four, friendview, - google_connect, invitation, joke, login, logout, menstrual_predictor, profile, - register, search, telegram, user, weekview, weight, whatsapp, + about_us, + agenda, + calendar, + categories, + celebrity, + credits, + currency, + dayview, + email, + event, + export, + four_o_four, + friendview, + google_connect, + invitation, + joke, + login, + logout, + menstrual_predictor, + profile, + register, + search, + telegram, + user, + weekview, + weight, + whatsapp, ) json_data_loader.load_to_database(next(get_db())) @@ -106,10 +129,13 @@ async def swagger_ui_redirect(): @logger.catch() async def home(request: Request, db: Session = Depends(get_db)): quote = daily_quotes.get_quote_of_day(db) - return templates.TemplateResponse("index.html", { - "request": request, - "quote": quote, - }) + return templates.TemplateResponse( + "index.html", + { + "request": request, + "quote": quote, + }, + ) custom_openapi(app) diff --git a/app/routers/menstrual_predictor.py b/app/routers/menstrual_predictor.py index 945b0183..f25aa634 100644 --- a/app/routers/menstrual_predictor.py +++ b/app/routers/menstrual_predictor.py @@ -1,15 +1,21 @@ import datetime -from typing import List + +# from typing import List from fastapi import APIRouter, Depends, Request, HTTPException from fastapi.responses import RedirectResponse from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session -from starlette.status import HTTP_302_FOUND +from starlette.status import HTTP_302_FOUND, HTTP_400_BAD_REQUEST -from app.database.models import Event, UserMenstrualPeriodLength from app.dependencies import get_db, templates -from app.routers.event import create_event +from app.database.models import UserMenstrualPeriodLength +from app.internal.menstrual_predictor_utils import ( + add_3_month_predictions, + is_user_signed_up_to_menstrual_predictor, + generate_predicted_period_dates, + remove_existing_period_dates, +) from app.internal.utils import create_model, get_current_user from loguru import logger @@ -18,7 +24,7 @@ router = APIRouter( prefix="/menstrual_predictor", tags=["menstrual_predictor"], - dependencies=[Depends(get_db)] + dependencies=[Depends(get_db)], ) MENSTRUAL_PERIOD_CATEGORY_ID = 111 @@ -29,144 +35,78 @@ def join_menstrual_predictor(request: Request, db: Session = Depends(get_db)): current_user_id = get_current_user(db).id if not is_user_signed_up_to_menstrual_predictor(db, current_user_id): - logger.info('getting menstrual predictor') - return templates.TemplateResponse("join_menstrual_predictor.html", { - "request": request, - }) + return templates.TemplateResponse( + "join_menstrual_predictor.html", + { + "request": request, + }, + ) else: - return RedirectResponse(url='/', status_code=HTTP_302_FOUND) + return RedirectResponse(url="/", status_code=HTTP_302_FOUND) @router.get("/add-period-start/{start_date}") -def add_period_start(request: Request, start_date: str, - db: Session = Depends(get_db)): +def add_period_start( + request: Request, + start_date: str, + db: Session = Depends(get_db), +): try: - period_start_date = datetime.datetime.strptime(start_date, '%Y-%m-%d') + period_start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d") except ValueError as err: logger.exception(err) raise HTTPException( - status_code=400, - detail="The given date doesn't match a date format YYYY-MM-DD") + status_code=HTTP_400_BAD_REQUEST, + detail="The given date doesn't match a date format YYYY-MM-DD", + ) else: current_user_id = get_current_user(db).id user_period_length = is_user_signed_up_to_menstrual_predictor( - db, current_user_id) + db, + current_user_id, + ) remove_existing_period_dates(db, current_user_id) if user_period_length: add_3_month_predictions( - db, user_period_length, period_start_date, current_user_id) - logger.info('adding menstrual start date') - return RedirectResponse('/', status_code=303) + db, + user_period_length, + period_start_date, + current_user_id, + ) + logger.info("adding menstrual start date") + return RedirectResponse("/", status_code=HTTP_302_FOUND) @router.post("/") -async def submit_join_form( - request: Request, - db: Session = Depends(get_db) -): +async def submit_join_form(request: Request, db: Session = Depends(get_db)): data = await request.form() current_user = get_current_user(session=db) user_menstrual_period_length = { "user_id": current_user.id, - "period_length": data['avg_period_length'], + "period_length": data["avg_period_length"], } last_period_date = datetime.datetime.strptime( - data['last_period_date'], '%Y-%m-%d') + data["last_period_date"], + "%Y-%m-%d", + ) try: - new_signed_up_to_period = create_model( + create_model( session=db, model_class=UserMenstrualPeriodLength, - **user_menstrual_period_length) + **user_menstrual_period_length, + ) except SQLAlchemyError: - logger.info( - 'Current user ' - 'already signed up to the service, hurray') + logger.info("Current user already signed up to the service, hurray") db.rollback() - url = '/' + url = "/" generate_predicted_period_dates( - db, data['avg_period_length'], last_period_date, current_user.id) + db, + data["avg_period_length"], + last_period_date, + current_user.id, + ) return RedirectResponse(url=url, status_code=HTTP_302_FOUND) - - -def get_avg_period_gap(db: Session, user_id): - period_days = get_all_period_days(db, user_id) - gaps_list = [] - - for i in range(len(period_days) - 1): - gap = get_date_diff(period_days[i].start, period_days[i + 1].start) - gaps_list.append(gap.days) - return get_list_avg(gaps_list) - - -def get_date_diff(date_1: datetime, date_2: datetime): - return date_2 - date_1 - - -def get_list_avg(received_list: List): - return sum(received_list) // len(received_list) - - -def remove_existing_period_dates(db: Session, user_id: int): - (db.query(Event). - filter(Event.owner_id == user_id). - filter(Event.category_id == MENSTRUAL_PERIOD_CATEGORY_ID). - filter(Event.start > datetime.datetime.now()). - delete()) - db.commit() - logger.info('Removed all period predictions to create new ones') - - -def generate_predicted_period_dates( - db: Session, - period_length: str, - period_start_date: datetime, - user_id: int): - delta = datetime.timedelta(int(period_length)) - period_end_date = period_start_date + delta - period_event = create_event( - db, 'period', period_start_date, period_end_date, - user_id, category_id=MENSTRUAL_PERIOD_CATEGORY_ID) - return period_event - - -def add_3_month_predictions(db, period_length, period_start_date, user_id): - avg_gap = get_avg_period_gap(db, user_id) - avg_gap_delta = datetime.timedelta(avg_gap) - generated_3_months = [] - for _ in range(4): - generated_period = generate_predicted_period_dates( - db, period_length, period_start_date, user_id) - generated_3_months.append(generated_period) - period_start_date += avg_gap_delta - logger.info(f'Generated predictions: {generated_3_months}') - return generated_3_months - - -def get_all_period_days(session: Session, user_id: int) -> List[Event]: - """Returns all period days filtered by user id.""" - - try: - period_days = sorted((session.query(Event). - filter(Event.owner_id == user_id). - filter(Event.category_id - == MENSTRUAL_PERIOD_CATEGORY_ID). - all()), key=lambda d: d.start) - - except SQLAlchemyError as err: - logger.debug(err) - return [] - else: - return period_days - - -def is_user_signed_up_to_menstrual_predictor(session: Session, user_id: int): - user_menstrual_period_length = session.query( - UserMenstrualPeriodLength).filter(user_id == user_id).first() - if user_menstrual_period_length: - return user_menstrual_period_length.period_length - else: - return False diff --git a/tests/test_menstrual_predictor.py b/tests/test_menstrual_predictor.py index 4e0bceca..e61e1391 100644 --- a/tests/test_menstrual_predictor.py +++ b/tests/test_menstrual_predictor.py @@ -1,39 +1,32 @@ -from app.routers.menstrual_predictor import get_all_period_days - -from app.internal.utils import get_current_user - - class TestMenstrualPredictor: PREDICTOR_PREFIX = "/menstrual_predictor" ADD_PERIOD_START = "/add-period-start" @staticmethod - def test_menstrual_predictor_page_not_signed_up( - client, session): + def test_menstrual_predictor_page_not_signed_up(client, session): resp = client.get(TestMenstrualPredictor.PREDICTOR_PREFIX) assert resp.ok @staticmethod - def test_menstrual_predictor_sign_up( - client, module_session): - resp = client.post(TestMenstrualPredictor.PREDICTOR_PREFIX, - json={"avg_period_length": 8, - "last_period_date": "2020-11-07"}) - assert resp.__dict__ == '' + def test_menstrual_predictor_sign_up(client, session): + resp = client.post( + TestMenstrualPredictor.PREDICTOR_PREFIX, + json={"avg_period_length": 8, "last_period_date": "2020-11-07"}, + ) assert resp.ok - current_user_id = get_current_user(module_session).id + resp = client.get( - TestMenstrualPredictor.PREDICTOR_PREFIX + - TestMenstrualPredictor.ADD_PERIOD_START + "/2020-12-11") - period_days = get_all_period_days(module_session, current_user_id) - assert period_days + TestMenstrualPredictor.PREDICTOR_PREFIX + + TestMenstrualPredictor.ADD_PERIOD_START + + "/2020-12-11", + ) + assert resp.ok - @ staticmethod - def test_add_period_date( - client, session - ): + @staticmethod + def test_add_period_date(client, session): resp = client.get( - TestMenstrualPredictor.PREDICTOR_PREFIX + - TestMenstrualPredictor.ADD_PERIOD_START + "/2020-12-11") + TestMenstrualPredictor.PREDICTOR_PREFIX + + TestMenstrualPredictor.ADD_PERIOD_START + + "/2020-12-11", + ) assert resp.ok - From 16b01128b42c7493ac4d303cb0c3506797a70bab Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Sun, 21 Feb 2021 23:41:14 +0200 Subject: [PATCH 07/16] Added conftest --- tests/conftest.py | 61 ++++++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c7bf068c..848529ca 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,20 +8,20 @@ from app.database.models import Base pytest_plugins = [ - 'tests.user_fixture', - 'tests.event_fixture', - 'tests.dayview_fixture', - 'tests.invitation_fixture', - 'tests.association_fixture', - 'tests.client_fixture', - 'tests.asyncio_fixture', - 'tests.logger_fixture', - 'tests.category_fixture', - 'smtpdfix', - 'tests.quotes_fixture', - 'tests.zodiac_fixture', - 'tests.jokes_fixture', - 'tests.comment_fixture', + "tests.user_fixture", + "tests.event_fixture", + "tests.dayview_fixture", + "tests.invitation_fixture", + "tests.association_fixture", + "tests.client_fixture", + "tests.asyncio_fixture", + "tests.logger_fixture", + "tests.category_fixture", + "smtpdfix", + "tests.quotes_fixture", + "tests.zodiac_fixture", + "tests.jokes_fixture", + "tests.comment_fixture", ] # When testing in a PostgreSQL environment please make sure that: @@ -30,21 +30,22 @@ if PSQL_ENVIRONMENT: SQLALCHEMY_TEST_DATABASE_URL = ( - "postgresql://postgres:1234" - "@localhost/postgres" - ) - test_engine = create_engine( - SQLALCHEMY_TEST_DATABASE_URL + "postgresql://postgres:1234" "@localhost/postgres" ) + test_engine = create_engine(SQLALCHEMY_TEST_DATABASE_URL) else: SQLALCHEMY_TEST_DATABASE_URL = "sqlite:///./test.db" test_engine = create_engine( - SQLALCHEMY_TEST_DATABASE_URL, connect_args={"check_same_thread": False} + SQLALCHEMY_TEST_DATABASE_URL, + connect_args={"check_same_thread": False}, ) TestingSessionLocal = sessionmaker( - autocommit=False, autoflush=False, bind=test_engine) + autocommit=False, + autoflush=False, + bind=test_engine, +) def get_test_db(): @@ -65,11 +66,15 @@ def session(): def sqlite_engine(): SQLALCHEMY_TEST_DATABASE_URL = "sqlite:///./test.db" sqlite_test_engine = create_engine( - SQLALCHEMY_TEST_DATABASE_URL, connect_args={"check_same_thread": False} + SQLALCHEMY_TEST_DATABASE_URL, + connect_args={"check_same_thread": False}, ) TestingSession = sessionmaker( - autocommit=False, autoflush=False, bind=sqlite_test_engine) + autocommit=False, + autoflush=False, + bind=sqlite_test_engine, + ) yield sqlite_test_engine session = TestingSession() @@ -80,13 +85,3 @@ def sqlite_engine(): @pytest.fixture def Calendar(): return calendar.Calendar(0) - - -@pytest.fixture(scope="session") -def module_session(): - Base.metadata.create_all(bind=test_engine) - session = get_test_db() - yield session - session.rollback() - session.close() - Base.metadata.drop_all(bind=test_engine) From 9e0d0ca426f6d338577b9d32c68aaaa6f0f1a2e8 Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Mon, 22 Feb 2021 02:46:29 +0200 Subject: [PATCH 08/16] Fixed some issues with dependency injection --- app/internal/menstrual_predictor_utils.py | 2 +- app/routers/menstrual_predictor.py | 30 ++++++++++++++--------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/app/internal/menstrual_predictor_utils.py b/app/internal/menstrual_predictor_utils.py index b889405d..7a9e7bd8 100644 --- a/app/internal/menstrual_predictor_utils.py +++ b/app/internal/menstrual_predictor_utils.py @@ -1,4 +1,4 @@ -from datetime import datetime +import datetime from loguru import logger diff --git a/app/routers/menstrual_predictor.py b/app/routers/menstrual_predictor.py index f25aa634..a07b76fd 100644 --- a/app/routers/menstrual_predictor.py +++ b/app/routers/menstrual_predictor.py @@ -1,9 +1,9 @@ import datetime -# from typing import List from fastapi import APIRouter, Depends, Request, HTTPException from fastapi.responses import RedirectResponse +from loguru import logger from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from starlette.status import HTTP_302_FOUND, HTTP_400_BAD_REQUEST @@ -16,9 +16,9 @@ generate_predicted_period_dates, remove_existing_period_dates, ) -from app.internal.utils import create_model, get_current_user - -from loguru import logger +from app.internal.security.schema import CurrentUser +from app.internal.security.dependancies import current_user +from app.internal.utils import create_model router = APIRouter( @@ -31,8 +31,12 @@ @router.get("/") -def join_menstrual_predictor(request: Request, db: Session = Depends(get_db)): - current_user_id = get_current_user(db).id +def join_menstrual_predictor( + request: Request, + db: Session = Depends(get_db), + user: CurrentUser = Depends(current_user), +): + current_user_id = user.user_id if not is_user_signed_up_to_menstrual_predictor(db, current_user_id): return templates.TemplateResponse( @@ -50,6 +54,7 @@ def add_period_start( request: Request, start_date: str, db: Session = Depends(get_db), + user: CurrentUser = Depends(current_user), ): try: period_start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d") @@ -60,7 +65,7 @@ def add_period_start( detail="The given date doesn't match a date format YYYY-MM-DD", ) else: - current_user_id = get_current_user(db).id + current_user_id = user.user_id user_period_length = is_user_signed_up_to_menstrual_predictor( db, current_user_id, @@ -79,13 +84,16 @@ def add_period_start( @router.post("/") -async def submit_join_form(request: Request, db: Session = Depends(get_db)): +async def submit_join_form( + request: Request, + db: Session = Depends(get_db), + user: CurrentUser = Depends(current_user), +): data = await request.form() - current_user = get_current_user(session=db) user_menstrual_period_length = { - "user_id": current_user.id, + "user_id": user.user_id, "period_length": data["avg_period_length"], } last_period_date = datetime.datetime.strptime( @@ -106,7 +114,7 @@ async def submit_join_form(request: Request, db: Session = Depends(get_db)): db, data["avg_period_length"], last_period_date, - current_user.id, + user.user_id, ) return RedirectResponse(url=url, status_code=HTTP_302_FOUND) From 2a93d84b97fbf9750cd9972ecb633a325888b411 Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Mon, 22 Feb 2021 03:05:28 +0200 Subject: [PATCH 09/16] Added annotations in utils --- app/internal/menstrual_predictor_utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/internal/menstrual_predictor_utils.py b/app/internal/menstrual_predictor_utils.py index 7a9e7bd8..07eae5e1 100644 --- a/app/internal/menstrual_predictor_utils.py +++ b/app/internal/menstrual_predictor_utils.py @@ -15,7 +15,7 @@ MENSTRUAL_PERIOD_CATEGORY_ID = 111 -def get_avg_period_gap(db: Session, user_id): +def get_avg_period_gap(db: Session, user_id: int): period_days = get_all_period_days(db, user_id) gaps_list = [] @@ -64,7 +64,12 @@ def generate_predicted_period_dates( return period_event -def add_3_month_predictions(db, period_length, period_start_date, user_id): +def add_3_month_predictions( + db: Session, + period_length: str, + period_start_date: datetime, + user_id: int, +): avg_gap = get_avg_period_gap(db, user_id) avg_gap_delta = datetime.timedelta(avg_gap) generated_3_months = [] From 85eee50be4f24042b6d9510f2a28adbbfd33843c Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Thu, 25 Feb 2021 20:37:05 +0200 Subject: [PATCH 10/16] Fixed CR change requests by Yam --- app/internal/menstrual_predictor_utils.py | 73 +++++++++++++++------ app/routers/menstrual_predictor.py | 37 ++++------- app/templates/join_menstrual_predictor.html | 12 ++-- tests/test_menstrual_predictor.py | 2 +- 4 files changed, 71 insertions(+), 53 deletions(-) diff --git a/app/internal/menstrual_predictor_utils.py b/app/internal/menstrual_predictor_utils.py index 07eae5e1..39c41214 100644 --- a/app/internal/menstrual_predictor_utils.py +++ b/app/internal/menstrual_predictor_utils.py @@ -1,13 +1,20 @@ import datetime +from datetime import timedelta from loguru import logger -from typing import List +from typing import List, Union +from fastapi import Depends + +from sqlalchemy import asc from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from app.database.models import Event, UserMenstrualPeriodLength +from app.dependencies import get_db +from app.internal.security.schema import CurrentUser +from app.internal.security.dependancies import current_user from app.routers.event import create_event @@ -15,25 +22,30 @@ MENSTRUAL_PERIOD_CATEGORY_ID = 111 -def get_avg_period_gap(db: Session, user_id: int): +def get_avg_period_gap(db: Session, user_id: int) -> int: + GAP_IN_CASE_NO_PERIODS = 30 + period_days = get_all_period_days(db, user_id) gaps_list = [] + if len(period_days) <= 1: + return GAP_IN_CASE_NO_PERIODS + for i in range(len(period_days) - 1): gap = get_date_diff(period_days[i].start, period_days[i + 1].start) gaps_list.append(gap.days) return get_list_avg(gaps_list) -def get_date_diff(date_1: datetime, date_2: datetime): +def get_date_diff(date_1: datetime, date_2: datetime) -> timedelta: return date_2 - date_1 -def get_list_avg(received_list: List): +def get_list_avg(received_list: List) -> int: return sum(received_list) // len(received_list) -def remove_existing_period_dates(db: Session, user_id: int): +def remove_existing_period_dates(db: Session, user_id: int) -> None: ( db.query(Event) .filter(Event.owner_id == user_id) @@ -50,7 +62,7 @@ def generate_predicted_period_dates( period_length: str, period_start_date: datetime, user_id: int, -): +) -> Event: delta = datetime.timedelta(int(period_length)) period_end_date = period_start_date + delta period_event = create_event( @@ -69,7 +81,7 @@ def add_3_month_predictions( period_length: str, period_start_date: datetime, user_id: int, -): +) -> List[Event]: avg_gap = get_avg_period_gap(db, user_id) avg_gap_delta = datetime.timedelta(avg_gap) generated_3_months = [] @@ -86,28 +98,50 @@ def add_3_month_predictions( return generated_3_months +def add_prediction_events_if_valid( + period_start_date: datetime, + db: Session = Depends(get_db), + user: CurrentUser = Depends(current_user), +) -> None: + current_user_id = user.user_id + user_period_length = is_user_signed_up_to_menstrual_predictor( + db, + current_user_id, + ) + + remove_existing_period_dates(db, current_user_id) + if user_period_length: + add_3_month_predictions( + db, + user_period_length, + period_start_date, + current_user_id, + ) + + def get_all_period_days(session: Session, user_id: int) -> List[Event]: """Returns all period days filtered by user id.""" try: - period_days = sorted( - ( - session.query(Event) - .filter(Event.owner_id == user_id) - .filter(Event.category_id == MENSTRUAL_PERIOD_CATEGORY_ID) - .all() - ), - key=lambda d: d.start, + period_days = ( + session.query(Event) + .filter(Event.owner_id == user_id) + .filter(Event.category_id == MENSTRUAL_PERIOD_CATEGORY_ID) + .order_by(asc(Event.start)) + .all() ) except SQLAlchemyError as err: logger.exception(err) return [] - else: - return period_days + + return period_days -def is_user_signed_up_to_menstrual_predictor(session: Session, user_id: int): +def is_user_signed_up_to_menstrual_predictor( + session: Session, + user_id: int, +) -> Union[bool, int]: user_menstrual_period_length = ( session.query(UserMenstrualPeriodLength) .filter(user_id == user_id) @@ -115,5 +149,4 @@ def is_user_signed_up_to_menstrual_predictor(session: Session, user_id: int): ) if user_menstrual_period_length: return user_menstrual_period_length.period_length - else: - return False + return False diff --git a/app/routers/menstrual_predictor.py b/app/routers/menstrual_predictor.py index a07b76fd..a1b28c3a 100644 --- a/app/routers/menstrual_predictor.py +++ b/app/routers/menstrual_predictor.py @@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, Request, HTTPException -from fastapi.responses import RedirectResponse +from fastapi.responses import RedirectResponse, Response from loguru import logger from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session @@ -11,10 +11,9 @@ from app.dependencies import get_db, templates from app.database.models import UserMenstrualPeriodLength from app.internal.menstrual_predictor_utils import ( - add_3_month_predictions, + add_prediction_events_if_valid, is_user_signed_up_to_menstrual_predictor, generate_predicted_period_dates, - remove_existing_period_dates, ) from app.internal.security.schema import CurrentUser from app.internal.security.dependancies import current_user @@ -35,7 +34,7 @@ def join_menstrual_predictor( request: Request, db: Session = Depends(get_db), user: CurrentUser = Depends(current_user), -): +) -> Response: current_user_id = user.user_id if not is_user_signed_up_to_menstrual_predictor(db, current_user_id): @@ -45,8 +44,7 @@ def join_menstrual_predictor( "request": request, }, ) - else: - return RedirectResponse(url="/", status_code=HTTP_302_FOUND) + return RedirectResponse(url="/", status_code=HTTP_302_FOUND) @router.get("/add-period-start/{start_date}") @@ -55,7 +53,7 @@ def add_period_start( start_date: str, db: Session = Depends(get_db), user: CurrentUser = Depends(current_user), -): +) -> RedirectResponse: try: period_start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d") except ValueError as err: @@ -65,21 +63,8 @@ def add_period_start( detail="The given date doesn't match a date format YYYY-MM-DD", ) else: - current_user_id = user.user_id - user_period_length = is_user_signed_up_to_menstrual_predictor( - db, - current_user_id, - ) - - remove_existing_period_dates(db, current_user_id) - if user_period_length: - add_3_month_predictions( - db, - user_period_length, - period_start_date, - current_user_id, - ) - logger.info("adding menstrual start date") + add_prediction_events_if_valid(period_start_date, db, user) + logger.info("Adding menstrual start date") return RedirectResponse("/", status_code=HTTP_302_FOUND) @@ -88,16 +73,16 @@ async def submit_join_form( request: Request, db: Session = Depends(get_db), user: CurrentUser = Depends(current_user), -): +) -> RedirectResponse: data = await request.form() user_menstrual_period_length = { "user_id": user.user_id, - "period_length": data["avg_period_length"], + "period_length": data["avg-period-length"], } last_period_date = datetime.datetime.strptime( - data["last_period_date"], + data["last-period-date"], "%Y-%m-%d", ) try: @@ -112,7 +97,7 @@ async def submit_join_form( url = "/" generate_predicted_period_dates( db, - data["avg_period_length"], + data["avg-period-length"], last_period_date, user.user_id, ) diff --git a/app/templates/join_menstrual_predictor.html b/app/templates/join_menstrual_predictor.html index b330bbc7..3df0a9e8 100644 --- a/app/templates/join_menstrual_predictor.html +++ b/app/templates/join_menstrual_predictor.html @@ -4,9 +4,9 @@

Please fill in your details

- +
-
+
@@ -15,12 +15,12 @@

Please fill in your details

- +
-
+
- +
@@ -33,7 +33,7 @@

Please fill in your details

function validate_date_older_than_today(received_date){ return received_date < new Date(); } - let last_period_date_element = document.getElementById('last_period_date'); + let last_period_date_element = document.getElementById("last-period-date"); change_max_to_today_date(last_period_date_element); {% endblock %} diff --git a/tests/test_menstrual_predictor.py b/tests/test_menstrual_predictor.py index e61e1391..a91f86ef 100644 --- a/tests/test_menstrual_predictor.py +++ b/tests/test_menstrual_predictor.py @@ -11,7 +11,7 @@ def test_menstrual_predictor_page_not_signed_up(client, session): def test_menstrual_predictor_sign_up(client, session): resp = client.post( TestMenstrualPredictor.PREDICTOR_PREFIX, - json={"avg_period_length": 8, "last_period_date": "2020-11-07"}, + json={"avg-period-length": 8, "last-period-date": "2020-11-07"}, ) assert resp.ok From 73def228aa485350eada9578dbcb250553ad01c0 Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Thu, 25 Feb 2021 20:49:08 +0200 Subject: [PATCH 11/16] fixed wrong import caused by changed file in develop --- app/internal/menstrual_predictor_utils.py | 9 ++------- app/routers/menstrual_predictor.py | 10 ++++------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/app/internal/menstrual_predictor_utils.py b/app/internal/menstrual_predictor_utils.py index 39c41214..b5092061 100644 --- a/app/internal/menstrual_predictor_utils.py +++ b/app/internal/menstrual_predictor_utils.py @@ -1,24 +1,19 @@ import datetime from datetime import timedelta - -from loguru import logger - from typing import List, Union from fastapi import Depends - +from loguru import logger from sqlalchemy import asc from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from app.database.models import Event, UserMenstrualPeriodLength from app.dependencies import get_db +from app.internal.security.dependencies import current_user from app.internal.security.schema import CurrentUser -from app.internal.security.dependancies import current_user - from app.routers.event import create_event - MENSTRUAL_PERIOD_CATEGORY_ID = 111 diff --git a/app/routers/menstrual_predictor.py b/app/routers/menstrual_predictor.py index a1b28c3a..395d24a5 100644 --- a/app/routers/menstrual_predictor.py +++ b/app/routers/menstrual_predictor.py @@ -1,25 +1,23 @@ import datetime - -from fastapi import APIRouter, Depends, Request, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Request from fastapi.responses import RedirectResponse, Response from loguru import logger from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from starlette.status import HTTP_302_FOUND, HTTP_400_BAD_REQUEST -from app.dependencies import get_db, templates from app.database.models import UserMenstrualPeriodLength +from app.dependencies import get_db, templates from app.internal.menstrual_predictor_utils import ( add_prediction_events_if_valid, - is_user_signed_up_to_menstrual_predictor, generate_predicted_period_dates, + is_user_signed_up_to_menstrual_predictor, ) +from app.internal.security.dependencies import current_user from app.internal.security.schema import CurrentUser -from app.internal.security.dependancies import current_user from app.internal.utils import create_model - router = APIRouter( prefix="/menstrual_predictor", tags=["menstrual_predictor"], From 9a8b92e4574ad8e89d0212eb3197d01ce7e47934 Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Fri, 26 Feb 2021 06:48:03 +0200 Subject: [PATCH 12/16] Fixed yam code review change requests --- app/internal/menstrual_predictor_utils.py | 15 +++++++------ app/routers/menstrual_predictor.py | 23 ++++++++++--------- app/static/js/menstrual_predictor.js | 13 +++++++++++ app/templates/join_menstrual_predictor.html | 13 +---------- tests/test_menstrual_predictor.py | 25 +++++++++------------ 5 files changed, 44 insertions(+), 45 deletions(-) create mode 100644 app/static/js/menstrual_predictor.js diff --git a/app/internal/menstrual_predictor_utils.py b/app/internal/menstrual_predictor_utils.py index b5092061..de35d853 100644 --- a/app/internal/menstrual_predictor_utils.py +++ b/app/internal/menstrual_predictor_utils.py @@ -71,26 +71,27 @@ def generate_predicted_period_dates( return period_event -def add_3_month_predictions( +def add_n_month_predictions( db: Session, period_length: str, period_start_date: datetime, user_id: int, ) -> List[Event]: + N_MONTHS_GENERATED = 3 avg_gap = get_avg_period_gap(db, user_id) avg_gap_delta = datetime.timedelta(avg_gap) - generated_3_months = [] - for _ in range(4): + generated_months = [] + for _ in range(N_MONTHS_GENERATED + 1): generated_period = generate_predicted_period_dates( db, period_length, period_start_date, user_id, ) - generated_3_months.append(generated_period) + generated_months.append(generated_period) period_start_date += avg_gap_delta - logger.info(f"Generated predictions: {generated_3_months}") - return generated_3_months + logger.info(f"Generated predictions: {generated_months}") + return generated_months def add_prediction_events_if_valid( @@ -106,7 +107,7 @@ def add_prediction_events_if_valid( remove_existing_period_dates(db, current_user_id) if user_period_length: - add_3_month_predictions( + add_n_month_predictions( db, user_period_length, period_start_date, diff --git a/app/routers/menstrual_predictor.py b/app/routers/menstrual_predictor.py index 395d24a5..ae46e785 100644 --- a/app/routers/menstrual_predictor.py +++ b/app/routers/menstrual_predictor.py @@ -19,8 +19,8 @@ from app.internal.utils import create_model router = APIRouter( - prefix="/menstrual_predictor", - tags=["menstrual_predictor"], + prefix="/menstrual-predictor", + tags=["menstrual-predictor"], dependencies=[Depends(get_db)], ) @@ -35,17 +35,18 @@ def join_menstrual_predictor( ) -> Response: current_user_id = user.user_id - if not is_user_signed_up_to_menstrual_predictor(db, current_user_id): - return templates.TemplateResponse( - "join_menstrual_predictor.html", - { - "request": request, - }, - ) - return RedirectResponse(url="/", status_code=HTTP_302_FOUND) + if is_user_signed_up_to_menstrual_predictor(db, current_user_id): + return RedirectResponse(url="/", status_code=HTTP_302_FOUND) + + return templates.TemplateResponse( + "join_menstrual_predictor.html", + { + "request": request, + }, + ) -@router.get("/add-period-start/{start_date}") +@router.get("/add/{start_date}") def add_period_start( request: Request, start_date: str, diff --git a/app/static/js/menstrual_predictor.js b/app/static/js/menstrual_predictor.js new file mode 100644 index 00000000..7960ff19 --- /dev/null +++ b/app/static/js/menstrual_predictor.js @@ -0,0 +1,13 @@ +function change_max_to_today_date(el) { + today = new Date(); + today_str = today.toISOString().substring(0, 10); + el.max = today_str; +} +function validate_date_older_than_today(received_date) { + return received_date < new Date(); +} + +document.addEventListener('DOMContentLoaded', () => { +let last_period_date_element = document.getElementById("last-period-date"); +change_max_to_today_date(last_period_date_element); +}); diff --git a/app/templates/join_menstrual_predictor.html b/app/templates/join_menstrual_predictor.html index 3df0a9e8..064a9742 100644 --- a/app/templates/join_menstrual_predictor.html +++ b/app/templates/join_menstrual_predictor.html @@ -24,16 +24,5 @@

Please fill in your details

- + {% endblock %} diff --git a/tests/test_menstrual_predictor.py b/tests/test_menstrual_predictor.py index a91f86ef..a4850d1b 100644 --- a/tests/test_menstrual_predictor.py +++ b/tests/test_menstrual_predictor.py @@ -1,32 +1,27 @@ -class TestMenstrualPredictor: - PREDICTOR_PREFIX = "/menstrual_predictor" - ADD_PERIOD_START = "/add-period-start" +from app.routers.menstrual_predictor import router + +class TestMenstrualPredictor: @staticmethod def test_menstrual_predictor_page_not_signed_up(client, session): - resp = client.get(TestMenstrualPredictor.PREDICTOR_PREFIX) + resp = client.get(router.url_path_for("join_menstrual_predictor")) assert resp.ok @staticmethod def test_menstrual_predictor_sign_up(client, session): resp = client.post( - TestMenstrualPredictor.PREDICTOR_PREFIX, + router.url_path_for("join_menstrual_predictor"), json={"avg-period-length": 8, "last-period-date": "2020-11-07"}, ) assert resp.ok - resp = client.get( - TestMenstrualPredictor.PREDICTOR_PREFIX - + TestMenstrualPredictor.ADD_PERIOD_START - + "/2020-12-11", - ) + url = router.url_path_for("add_period_start", start_date="2020-12-11") + resp = client.get(url) + assert resp.ok @staticmethod def test_add_period_date(client, session): - resp = client.get( - TestMenstrualPredictor.PREDICTOR_PREFIX - + TestMenstrualPredictor.ADD_PERIOD_START - + "/2020-12-11", - ) + url = router.url_path_for("add_period_start", start_date="2020-12-11") + resp = client.get(url) assert resp.ok From dfa07614d779de34db162536eae8d86e52e49d30 Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Fri, 26 Feb 2021 08:17:11 +0200 Subject: [PATCH 13/16] small change to re run check --- app/routers/menstrual_predictor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routers/menstrual_predictor.py b/app/routers/menstrual_predictor.py index ae46e785..c06ebbab 100644 --- a/app/routers/menstrual_predictor.py +++ b/app/routers/menstrual_predictor.py @@ -91,7 +91,7 @@ async def submit_join_form( **user_menstrual_period_length, ) except SQLAlchemyError: - logger.info("Current user already signed up to the service, hurray") + logger.info("Current user already signed up to the service, hurray!") db.rollback() url = "/" generate_predicted_period_dates( From 91c69b9be45a479274a4387cdf6b474fcc7f402d Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Fri, 26 Feb 2021 17:24:19 +0200 Subject: [PATCH 14/16] Tried to add to settings --- app/static/grid_style.css | 3 - app/static/js/settings.js | 62 +++++++++++++------ .../partials/calendar/navigation.html | 3 + app/templates/settings.html | 18 ++++++ 4 files changed, 64 insertions(+), 22 deletions(-) diff --git a/app/static/grid_style.css b/app/static/grid_style.css index 1afdca10..dd3ecdc0 100644 --- a/app/static/grid_style.css +++ b/app/static/grid_style.css @@ -2,8 +2,6 @@ --backgroundcol: #F7F7F7; --textcolor: #222831; --start-of-month: #E9ECEf; - --primary-variant: #FFDE4D; - --secondary: #EF5454; --borders: #E7E7E7; --borders-variant: #F7F7F7; } @@ -12,7 +10,6 @@ --backgroundcol: #000000; --textcolor: #EEEEEE; --start-of-month: #8C28BF; - --secondary: #EF5454; --borders: #E7E7E7; --borders-variant: #F7F7F7; } diff --git a/app/static/js/settings.js b/app/static/js/settings.js index 77ee2d13..fa6f5da7 100644 --- a/app/static/js/settings.js +++ b/app/static/js/settings.js @@ -1,23 +1,47 @@ -document.addEventListener('DOMContentLoaded', () => { - const tabBtn = document.getElementsByClassName("tab"); - for (let i = 0; i < tabBtn.length; i++) { - const btn = document.getElementById("tab" + i); - btn.addEventListener('click', () => { - tabClick(btn.id, tabBtn); - }); - } -}); +document.addEventListener("DOMContentLoaded", () => { + const tabBtn = document.getElementsByClassName("tab"); + for (let i = 0; i < tabBtn.length; i++) { + const btn = document.getElementById("tab" + i); + btn.addEventListener("click", () => { + tabClick(btn.id, tabBtn); + }); + } + var menstrualSubscriptionSwitch = document.getElementById("switch3"); + menstrualSubscriptionSwitch.addEventListener("click", () => { + btnState = menstrualSubscriptionSwitch.checked; + if (btnState) { + fetch('/menstrual-predictor/') + .then(response => { -function tabClick(tab_id, tabBtn) { - let shownTab = document.querySelector(".tab-show"); - let selectedTabContent = document.querySelector(`#${tab_id}-content`); - shownTab.classList.remove("tab-show"); - shownTab.classList.add("tab-hide"); - for (btn of tabBtn) { - btn.children[0].classList.remove("active"); + text = response; + let subscriptionContainer = document.getElementById('menstrual-prediction-container'); + subscriptionContainer.innerHTML = text; + + }) + //.then(body => { + // console.log(body, 'body') + // subscriptionContainer.innerHTML= body; + // }); + // window.location = '/menstrual-predictor/' + console.log(menstrualSubscriptionSwitch.checked); } - document.getElementById(tab_id).classList.add("active"); - selectedTabContent.classList.remove("tab-hide"); - selectedTabContent.classList.add("tab-show"); + }); +}); +async function loadSubscriptionPage(response){ + data = await response.text(); + return data; +} +function toggleMenstrualPredictor() {} +function tabClick(tab_id, tabBtn) { + let shownTab = document.querySelector(".tab-show"); + let selectedTabContent = document.querySelector(`#${tab_id}-content`); + shownTab.classList.remove("tab-show"); + shownTab.classList.add("tab-hide"); + for (btn of tabBtn) { + btn.children[0].classList.remove("active"); + } + document.getElementById(tab_id).classList.add("active"); + selectedTabContent.classList.remove("tab-hide"); + selectedTabContent.classList.add("tab-show"); } diff --git a/app/templates/partials/calendar/navigation.html b/app/templates/partials/calendar/navigation.html index 0677bc87..4bd7b90a 100644 --- a/app/templates/partials/calendar/navigation.html +++ b/app/templates/partials/calendar/navigation.html @@ -39,6 +39,9 @@
+
+ +
diff --git a/app/templates/settings.html b/app/templates/settings.html index ee741658..2579ef14 100644 --- a/app/templates/settings.html +++ b/app/templates/settings.html @@ -49,6 +49,9 @@
  • Event settings
  • +
  • + Menstrual Predictor +
  • @@ -115,8 +118,23 @@

    View options

    + +
    +

    Menstrual Predictor settings

    +
    + + +
    +
    + + +
    +
    +
    +
    +
    {% endblock content %} From 8470ff7a1d8bdd17c2cb2b40c1f1b2a52b0ef41f Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Fri, 26 Feb 2021 23:52:17 +0200 Subject: [PATCH 15/16] Fixed more CR change requests, improved coverage --- app/internal/menstrual_predictor_utils.py | 5 ++--- app/routers/menstrual_predictor.py | 4 +--- app/static/js/menstrual_predictor.js | 12 ++++++------ tests/test_menstrual_predictor.py | 17 ++++++++++------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/internal/menstrual_predictor_utils.py b/app/internal/menstrual_predictor_utils.py index de35d853..06298c80 100644 --- a/app/internal/menstrual_predictor_utils.py +++ b/app/internal/menstrual_predictor_utils.py @@ -15,11 +15,11 @@ from app.routers.event import create_event MENSTRUAL_PERIOD_CATEGORY_ID = 111 +N_MONTHS_GENERATED = 3 +GAP_IN_CASE_NO_PERIODS = 30 def get_avg_period_gap(db: Session, user_id: int) -> int: - GAP_IN_CASE_NO_PERIODS = 30 - period_days = get_all_period_days(db, user_id) gaps_list = [] @@ -77,7 +77,6 @@ def add_n_month_predictions( period_start_date: datetime, user_id: int, ) -> List[Event]: - N_MONTHS_GENERATED = 3 avg_gap = get_avg_period_gap(db, user_id) avg_gap_delta = datetime.timedelta(avg_gap) generated_months = [] diff --git a/app/routers/menstrual_predictor.py b/app/routers/menstrual_predictor.py index c06ebbab..8af6c4a7 100644 --- a/app/routers/menstrual_predictor.py +++ b/app/routers/menstrual_predictor.py @@ -24,8 +24,6 @@ dependencies=[Depends(get_db)], ) -MENSTRUAL_PERIOD_CATEGORY_ID = 111 - @router.get("/") def join_menstrual_predictor( @@ -75,7 +73,7 @@ async def submit_join_form( ) -> RedirectResponse: data = await request.form() - + print(data) user_menstrual_period_length = { "user_id": user.user_id, "period_length": data["avg-period-length"], diff --git a/app/static/js/menstrual_predictor.js b/app/static/js/menstrual_predictor.js index 7960ff19..e657e77c 100644 --- a/app/static/js/menstrual_predictor.js +++ b/app/static/js/menstrual_predictor.js @@ -1,13 +1,13 @@ function change_max_to_today_date(el) { - today = new Date(); - today_str = today.toISOString().substring(0, 10); - el.max = today_str; + const today = new Date(); + const today_str = today.toISOString().substring(0, 10); + el.max = el.dataset.maxDate = today_str; } function validate_date_older_than_today(received_date) { return received_date < new Date(); } -document.addEventListener('DOMContentLoaded', () => { -let last_period_date_element = document.getElementById("last-period-date"); -change_max_to_today_date(last_period_date_element); +document.addEventListener("DOMContentLoaded", () => { + const last_period_date_element = document.getElementById("last-period-date"); + change_max_to_today_date(last_period_date_element); }); diff --git a/tests/test_menstrual_predictor.py b/tests/test_menstrual_predictor.py index a4850d1b..6e8cce3b 100644 --- a/tests/test_menstrual_predictor.py +++ b/tests/test_menstrual_predictor.py @@ -1,4 +1,5 @@ from app.routers.menstrual_predictor import router +from tests.test_login import test_login_successfull class TestMenstrualPredictor: @@ -8,20 +9,22 @@ def test_menstrual_predictor_page_not_signed_up(client, session): assert resp.ok @staticmethod - def test_menstrual_predictor_sign_up(client, session): - resp = client.post( - router.url_path_for("join_menstrual_predictor"), - json={"avg-period-length": 8, "last-period-date": "2020-11-07"}, + def test_menstrual_predictor_sign_up(security_test_client, session): + test_login_successfull(session, security_test_client) + resp = security_test_client.post( + router.url_path_for("submit_join_form"), + data={"avg-period-length": 8, "last-period-date": "2020-11-07"}, ) assert resp.ok url = router.url_path_for("add_period_start", start_date="2020-12-11") - resp = client.get(url) + resp = security_test_client.get(url) assert resp.ok @staticmethod - def test_add_period_date(client, session): + def test_add_period_date(security_test_client, session): + test_login_successfull(session, security_test_client) url = router.url_path_for("add_period_start", start_date="2020-12-11") - resp = client.get(url) + resp = security_test_client.get(url) assert resp.ok From f66da5aef484378eee2eef58b42f610c5650a13f Mon Sep 17 00:00:00 2001 From: Liad Noam Date: Sat, 27 Feb 2021 00:26:33 +0200 Subject: [PATCH 16/16] Fixed more CR change requests, improved coverage, retry because tests fail for unknown reason --- app/routers/menstrual_predictor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routers/menstrual_predictor.py b/app/routers/menstrual_predictor.py index 8af6c4a7..c351b805 100644 --- a/app/routers/menstrual_predictor.py +++ b/app/routers/menstrual_predictor.py @@ -89,7 +89,7 @@ async def submit_join_form( **user_menstrual_period_length, ) except SQLAlchemyError: - logger.info("Current user already signed up to the service, hurray!") + logger.info("Current user already signed up to the service, hurray!!") db.rollback() url = "/" generate_predicted_period_dates(