diff --git a/app/database/models.py b/app/database/models.py index 4466dc76..fe92ab92 100644 --- a/app/database/models.py +++ b/app/database/models.py @@ -191,17 +191,17 @@ class Category(Base): @staticmethod def create( - db_session: Session, + session: Session, name: str, color: str, user_id: int, ) -> Category: try: category = Category(name=name, color=color, user_id=user_id) - db_session.add(category) - db_session.flush() - db_session.commit() - db_session.refresh(category) + session.add(category) + session.flush() + session.commit() + session.refresh(category) except (SQLAlchemyError, IntegrityError) as e: logger.error(f"Failed to create category: {e}") raise e @@ -533,6 +533,15 @@ def __repr__(self): ) +class Parasha(Base): + __tablename__ = "parashot" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String, nullable=False) + hebrew_name = Column(String, nullable=False) + link = Column(String, nullable=False) + + class SharedListItem(Base): __tablename__ = "shared_list_item" @@ -600,8 +609,8 @@ class Task(Base): # insert language data - - +# Credit to adrihanu https://stackoverflow.com/users/9127249/adrihanu +# https://stackoverflow.com/questions/17461251 def insert_data(target, session: Session, **kw): """insert language data Credit to adrihanu https://stackoverflow.com/users/9127249/adrihanu diff --git a/app/internal/json_data_loader.py b/app/internal/json_data_loader.py index 779bfa17..9c5ee271 100644 --- a/app/internal/json_data_loader.py +++ b/app/internal/json_data_loader.py @@ -5,46 +5,64 @@ from loguru import logger from sqlalchemy.orm import Session -from app.database.models import Base, InternationalDays, Joke, Quote, Zodiac -from app.internal import daily_quotes, international_days, jokes, zodiac + +from app.database.models import ( + Base, InternationalDays, Joke, Quote, Parasha, Zodiac, +) +from app.config import RESOURCES_DIR +from app.internal import ( + daily_quotes, international_days, jokes, weekly_parasha, zodiac, +) def load_to_database(session: Session) -> None: """Loads data from JSON data files into the database. - On startup, data from the JSON files should be added to the database and - not be accessed from a network call for each request as it is costly. - + On startup, data from the JSON files should be added to the + database and not be accessed from a network call for each + request as it is costly. The quotes JSON file content is copied from the free API: 'https://type.fit/api/quotes'. + The parashot and hebrew_view JSON files content is copied + from the free API: + 'https://www.hebcal.com/hebcal?v=1&cfg=json&maj=on&min=on& + mod=on&nx=on&year=now&month=x&ss=on&mf=on&c=on&geo=geoname + &geonameid=293397&m=50&s=on&d=on&D=on'. Args: session: The database connection. """ _insert_into_database( session, - 'app/resources/zodiac.json', + RESOURCES_DIR / "zodiac.json", Zodiac, zodiac.get_zodiac, ) _insert_into_database( session, - 'app/resources/quotes.json', + RESOURCES_DIR / "quotes.json", Quote, daily_quotes.get_quote, ) _insert_into_database( session, - 'app/resources/international_days.json', + RESOURCES_DIR / "parashot.json", + Parasha, + weekly_parasha.create_parasha_object, + ) + + _insert_into_database( + session, + RESOURCES_DIR / "international_days.json", InternationalDays, international_days.get_international_day, ) _insert_into_database( session, - 'app/resources/jokes.json', + RESOURCES_DIR / "jokes.json", Joke, jokes.get_joke, ) @@ -57,13 +75,11 @@ def _insert_into_database( model_creator: Callable ) -> bool: """Inserts the extracted JSON data into the database. - Args: session: The database connection. path: The file path. table: A model entity table. model_creator: A model creation function. - Returns: True if the save was successful, otherwise returns False. """ @@ -104,7 +120,7 @@ def _get_data_from_json(path: str) -> List[Dict[str, Any]]: A list of dictionary objects. """ try: - with open(path, 'r') as json_file: + with open(path, 'r', encoding='utf-8') as json_file: json_content = json.load(json_file) except (IOError, ValueError): file_name = os.path.basename(path) diff --git a/app/internal/weekly_parasha.py b/app/internal/weekly_parasha.py new file mode 100644 index 00000000..fad1dc6a --- /dev/null +++ b/app/internal/weekly_parasha.py @@ -0,0 +1,51 @@ +from datetime import datetime +from typing import Dict, Optional + +from pyluach import dates, parshios +from sqlalchemy.orm import Session + +from app.database.models import Parasha + + +def create_parasha_object(parashot_fields: Dict[str, str]) -> Parasha: + """This function create a parasha object from given fields dictionary. + It is used for adding the data from the json into the db""" + return Parasha( + name=parashot_fields["name"], + hebrew_name=parashot_fields["hebrew"], + link=parashot_fields["link"], + ) + + +def get_parasha_only_to_saturday(date: datetime) -> Optional[str]: + """Returns the parasha name if the date is Saturday. + + Args: + date: The requested date. + + Returns: + If the date is Saturday, return the parasha name, + else return None. + """ + date_split = str(date).split("-") + new_date_format = [int(x) for x in date_split] + gregorian_date = dates.GregorianDate(*new_date_format) + if gregorian_date == gregorian_date.shabbos(): + return parshios.getparsha_string(gregorian_date) + + +def get_parasha_object(session: Session, date: datetime) -> Optional[Parasha]: + """Returns the parasha object for the specific day. + + Args: + session: The database connection. + date: The requested date. + + Returns: + A HebrewView object. + IF the specific day in not Saturday, it return None. + """ + parasha_name = get_parasha_only_to_saturday(date) + if parasha_name is None: + return None + return session.query(Parasha).filter_by(name=parasha_name).first() diff --git a/app/main.py b/app/main.py index ccf4c30c..d4dab59f 100644 --- a/app/main.py +++ b/app/main.py @@ -92,6 +92,7 @@ def create_tables(engine, psql_environment): whatsapp, ) + json_data_loader.load_to_database(next(get_db())) diff --git a/app/resources/parashot.json b/app/resources/parashot.json new file mode 100644 index 00000000..38e05d3f --- /dev/null +++ b/app/resources/parashot.json @@ -0,0 +1,252 @@ +[ + { + "name": "Parashat Vayechi", + "hebrew": "פרשת ויחי", + "link": "https://www.hebcal.com/sedrot/vayechi-20210102?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Shemot", + "hebrew": "פרשת שמות", + "link": "https://www.hebcal.com/sedrot/shemot-20210109?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Vaera", + "hebrew": "פרשת וארא", + "link": "https://www.hebcal.com/sedrot/vaera-20210116?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Bo", + "hebrew": "פרשת בא", + "link": "https://www.hebcal.com/sedrot/bo-20210123?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Beshalach", + "hebrew": "פרשת בשלח", + "link": "https://www.hebcal.com/sedrot/beshalach-20210130?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Yitro", + "hebrew": "פרשת יתרו", + "link": "https://www.hebcal.com/sedrot/yitro-20210206?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Mishpatim", + "hebrew": "פרשת משפטים", + "link": "https://www.hebcal.com/sedrot/mishpatim-20210213?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Terumah", + "hebrew": "פרשת תרומה", + "link": "https://www.hebcal.com/sedrot/terumah-20210220?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Tetzaveh", + "hebrew": "פרשת תצוה", + "link": "https://www.hebcal.com/sedrot/tetzaveh-20210227?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Ki Tisa", + "hebrew": "פרשת כי תשא", + "link": "https://www.hebcal.com/sedrot/ki-tisa-20210306?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Vayakhel-Pekudei", + "hebrew": "פרשת ויקהל־פקודי", + "link": "https://www.hebcal.com/sedrot/vayakhel-pekudei-20210313?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Vayikra", + "hebrew": "פרשת ויקרא", + "link": "https://www.hebcal.com/sedrot/vayikra-20210320?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Tzav", + "hebrew": "פרשת צו", + "link": "https://www.hebcal.com/sedrot/tzav-20210327?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Shmini", + "hebrew": "פרשת שמיני", + "link": "https://www.hebcal.com/sedrot/shmini-20210410?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Tazria-Metzora", + "hebrew": "פרשת תזריע־מצרע", + "link": "https://www.hebcal.com/sedrot/tazria-metzora-20210417?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Achrei Mot-Kedoshim", + "hebrew": "פרשת אחרי מות־קדשים", + "link": "https://www.hebcal.com/sedrot/achrei-mot-kedoshim-20210424?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Emor", + "hebrew": "פרשת אמור", + "link": "https://www.hebcal.com/sedrot/emor-20210501?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Behar-Bechukotai", + "hebrew": "פרשת בהר־בחקתי", + "link": "https://www.hebcal.com/sedrot/behar-bechukotai-20210508?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Bamidbar", + "hebrew": "פרשת במדבר", + "link": "https://www.hebcal.com/sedrot/bamidbar-20210515?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Nasso", + "hebrew": "פרשת נשא", + "link": "https://www.hebcal.com/sedrot/nasso-20210522?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Beha'alotcha", + "hebrew": "פרשת בהעלתך", + "link": "https://www.hebcal.com/sedrot/behaalotcha-20210529?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Sh'lach", + "hebrew": "פרשת שלח־לך", + "link": "https://www.hebcal.com/sedrot/shlach-20210605?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Korach", + "hebrew": "פרשת קורח", + "link": "https://www.hebcal.com/sedrot/korach-20210612?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Chukat", + "hebrew": "פרשת חקת", + "link": "https://www.hebcal.com/sedrot/chukat-20210619?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Balak", + "hebrew": "פרשת בלק", + "link": "https://www.hebcal.com/sedrot/balak-20210626?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Pinchas", + "hebrew": "פרשת פינחס", + "link": "https://www.hebcal.com/sedrot/pinchas-20210703?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Matot-Masei", + "hebrew":"פרשת מטות־מסעי" , + "link": "https://www.hebcal.com/sedrot/matot-masei-20210710?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Devarim", + "hebrew": "פרשת דברים", + "link": "https://www.hebcal.com/sedrot/devarim-20210717?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Vaetchanan", + "hebrew": "פרשת ואתחנן", + "link": "https://www.hebcal.com/sedrot/vaetchanan-20210724?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Eikev", + "hebrew": "פרשת עקב", + "link": "https://www.hebcal.com/sedrot/eikev-20210731?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Re'eh", + "hebrew": "פרשת ראה", + "link": "https://www.hebcal.com/sedrot/reeh-20210807?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Shoftim", + "hebrew": "פרשת שופטים", + "link": "https://www.hebcal.com/sedrot/shoftim-20210814?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Ki Teitzei", + "hebrew": "פרשת כי־תצא", + "link": "https://www.hebcal.com/sedrot/ki-teitzei-20210821?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Ki Tavo", + "hebrew": "פרשת כי־תבוא", + "link": "https://www.hebcal.com/sedrot/ki-tavo-20210828?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Nitzavim", + "hebrew": "פרשת נצבים", + "link": "https://www.hebcal.com/sedrot/nitzavim-20210904?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Vayeilech", + "hebrew": "פרשת וילך", + "link": "https://www.hebcal.com/sedrot/vayeilech-20210911?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Ha'Azinu", + "hebrew": "פרשת האזינו", + "link": "https://www.hebcal.com/sedrot/haazinu-20210918?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Bereshit", + "hebrew": "פרשת בראשית", + "link": "https://www.hebcal.com/sedrot/bereshit-20211002?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Noach", + "hebrew": "פרשת נח", + "link": "https://www.hebcal.com/sedrot/noach-20211009?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Lech-Lecha", + "hebrew": "פרשת לך־לך", + "link": "https://www.hebcal.com/sedrot/lech-lecha-20211016?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Vayera", + "hebrew": "פרשת וירא", + "link": "https://www.hebcal.com/sedrot/vayera-20211023?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Chayei Sara", + "hebrew": "פרשת חיי שרה", + "link": "https://www.hebcal.com/sedrot/chayei-sara-20211030?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Toldot", + "hebrew": "פרשת תולדות", + "link": "https://www.hebcal.com/sedrot/toldot-20211106?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Vayetzei", + "hebrew": "פרשת ויצא", + "link": "https://www.hebcal.com/sedrot/vayetzei-20211113?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Vayishlach", + "hebrew": "פרשת וישלח", + "link": "https://www.hebcal.com/sedrot/vayishlach-20211120?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Vayeshev", + "hebrew": "פרשת וישב", + "link": "https://www.hebcal.com/sedrot/vayeshev-20211127?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Miketz", + "hebrew": "פרשת מקץ", + "link": "https://www.hebcal.com/sedrot/miketz-20211204?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Vayigash", + "hebrew": "פרשת ויגש", + "link": "https://www.hebcal.com/sedrot/vayigash-20211211?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Vayechi", + "hebrew": "פרשת ויחי", + "link": "https://www.hebcal.com/sedrot/vayechi-20211218?i=on&utm_source=js&utm_medium=api" + }, + { + "name": "Parashat Shemot", + "hebrew": "פרשת שמות", + "link": "https://www.hebcal.com/sedrot/shemot-20211225?i=on&utm_source=js&utm_medium=api" + } +] \ No newline at end of file diff --git a/app/routers/audio.py b/app/routers/audio.py index c53ed9ca..3da3d9e7 100644 --- a/app/routers/audio.py +++ b/app/routers/audio.py @@ -23,6 +23,7 @@ ) from app.internal.security.dependencies import current_user + router = APIRouter( prefix="/audio", tags=["audio"], diff --git a/app/routers/calendar.py b/app/routers/calendar.py index dc1aeefa..02c37885 100644 --- a/app/routers/calendar.py +++ b/app/routers/calendar.py @@ -1,11 +1,12 @@ from http import HTTPStatus +from app.dependencies import templates +from app.routers import calendar_grid + from fastapi import APIRouter, Request from fastapi.responses import HTMLResponse from starlette.responses import Response -from app.dependencies import templates -from app.routers import calendar_grid as cg router = APIRouter( prefix="/calendar/month", @@ -17,15 +18,15 @@ @router.get("/") async def calendar(request: Request) -> Response: - user_local_time = cg.Day.get_user_local_time() - day = cg.create_day(user_local_time) + user_local_time = calendar_grid.Day.get_user_local_time() + day = calendar_grid.create_day(user_local_time) return templates.TemplateResponse( "calendar_monthly_view.html", { "request": request, "day": day, - "week_days": cg.Week.DAYS_OF_THE_WEEK, - "weeks_block": cg.get_month_block(day) + "week_days": calendar_grid.Week.DAYS_OF_THE_WEEK, + "weeks_block": calendar_grid.get_month_block(day), } ) @@ -34,8 +35,9 @@ async def calendar(request: Request) -> Response: async def update_calendar( request: Request, date: str, days: int ) -> HTMLResponse: - last_day = cg.Day.convert_str_to_date(date) - next_weeks = cg.create_weeks(cg.get_n_days(last_day, days)) + last_day = calendar_grid.Day.convert_str_to_date(date) + next_weeks = calendar_grid.create_weeks( + calendar_grid.get_n_days(last_day, days)) template = templates.get_template( 'partials/calendar/monthly_view/add_week.html') content = template.render(weeks_block=next_weeks) diff --git a/app/routers/calendar_grid.py b/app/routers/calendar_grid.py index 9ef5202c..627462c7 100644 --- a/app/routers/calendar_grid.py +++ b/app/routers/calendar_grid.py @@ -6,6 +6,7 @@ import pytz + MONTH_BLOCK: int = 6 locale.setlocale(locale.LC_ALL, "en_US.UTF-8") diff --git a/app/routers/dayview.py b/app/routers/dayview.py index 2894eaa2..31458827 100644 --- a/app/routers/dayview.py +++ b/app/routers/dayview.py @@ -6,7 +6,7 @@ from app.database.models import Event, Task, User from app.dependencies import get_db, templates -from app.internal import international_days, zodiac +from app.internal import international_days, weekly_parasha, zodiac from app.internal.security.dependencies import current_user from app.routers.user import get_all_user_events @@ -198,6 +198,7 @@ async def dayview( except ValueError as err: raise HTTPException(status_code=404, detail=f"{err}") zodiac_obj = zodiac.get_zodiac_of_day(session, day) + parasha_obj = weekly_parasha.get_parasha_object(session, day.date()) events_with_attrs = get_events_and_attributes( day=day, session=session, @@ -211,10 +212,8 @@ async def dayview( current_time_with_attrs = CurrentTimeAttributes(date=day) inter_day = international_days.get_international_day_per_day(session, day) tasks = ( - session.query(Task) - .filter(Task.owner_id == user.user_id) - .filter(Task.date == day.date()) - .order_by(Task.time) + session.query(Task).filter(Task.owner_id == user.user_id).filter( + Task.date == day.date()).order_by(Task.time) ) month = day.strftime("%B").upper() return templates.TemplateResponse( @@ -231,5 +230,6 @@ async def dayview( "view": view, "current_time": current_time_with_attrs, "tasks": tasks, + "parasha": parasha_obj, }, ) diff --git a/app/static/dayview.css b/app/static/dayview.css index 3cde2aa8..f1d63e90 100644 --- a/app/static/dayview.css +++ b/app/static/dayview.css @@ -286,4 +286,27 @@ body { background-color: var(--primary); color: white; box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19); +} +.parashot { + position: absolute; + right: 1.2rem; + top: 10rem; + padding-right: var(--space_xs); + padding-left: var(--space_xs); + padding-bottom: var(--space_xxs); + border: solid 0.1px var(--primary); + background-color: var(--surface-variant); + box-shadow: 1px 1px 2px #999; + line-height: 1.2; + height: 6rem; + width: 5.2rem; + align-items: center; + justify-content: center; + font-weight: bold; + font-size: 0.65rem; +} + +.parashot a { + background-color: powderblue; + font-size: 0.55rem; } \ No newline at end of file diff --git a/app/templates/calendar_day_view.html b/app/templates/calendar_day_view.html index 272e3558..b4e5045b 100644 --- a/app/templates/calendar_day_view.html +++ b/app/templates/calendar_day_view.html @@ -21,15 +21,24 @@ alt="zodiac sign" width="14em" height="13em"> {% endif %} + {% if parasha %} +
+ {{ gettext(" Weekly Torah Portion: %(hebrew)s", hebrew=parasha.name) }}
{{ gettext("%(name)s", + name=parasha.hebrew_name) }} + {{ gettext("Detailing in a separate link on %(parashah)s", + parashah=parasha.hebrew_name) }} +
+ {% endif %} {% else %} {{day}} / {{month}} {% endif %}
{% for event in all_day_events %} - {{ event.title }}    + {{ event.title + }}    {% endfor %} -
+ {% if international_day %}
The International days are: "{{ international_day.international_day }}" @@ -62,12 +71,12 @@ {% endfor %}
- {% if current_time.is_viewed %} + {% if current_time.is_viewed %}
- {% endif %} + {% endif %}
{% for event, attr in events_and_attrs %} diff --git a/app/templates/home.html b/app/templates/home.html index f7d8d0d2..a252d6c6 100644 --- a/app/templates/home.html +++ b/app/templates/home.html @@ -6,6 +6,7 @@
+
diff --git a/requirements.txt b/requirements.txt index 9612912e..5814f785 100644 --- a/requirements.txt +++ b/requirements.txt @@ -107,6 +107,7 @@ pycparser==2.20 pydantic==1.7.3 pyflakes==2.2.0 PyJWT==2.0.0 +pyluach==1.2.1 pyparsing==2.4.7 pytest==6.2.1 pytest-asyncio==0.12.0 diff --git a/tests/test_weekly_parasha.py b/tests/test_weekly_parasha.py new file mode 100644 index 00000000..8a45267b --- /dev/null +++ b/tests/test_weekly_parasha.py @@ -0,0 +1,12 @@ +from app.internal import weekly_parasha + + +def test_create_parasha_object(): + parashot_fields = { + "name": "Parashat Vayechi", + "hebrew": "פרשת ויחי", + "link": "https://www.hebcal.com/sedrot/vayechi-20210102?" + "i=on&utm_source=js&utm_medium=api", + } + result = weekly_parasha.create_parasha_object(parashot_fields) + assert result.name == "Parashat Vayechi"