Skip to content
This repository has been archived by the owner on Feb 5, 2024. It is now read-only.

Commit

Permalink
feat: shift meals (#36)
Browse files Browse the repository at this point in the history
* feat: implement meal shifting

* chore: enable simplify in shift endpoint

* feat: do not update notion meals in dev

* chore: bump version to 1.7.0

* chore: apply pylint recommendations
  • Loading branch information
sralloza authored Mar 7, 2022
1 parent b49f1a0 commit 31ed26f
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 23 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ You need to supply the following environment variables (required ones are marked

### Other

- **LOCALE_WEEKDAY_NAMES** (`list(str)`): weekday names, starting with Sunday and ending with Saturday. Must contain 7 elements (one for each week day).
- **LOCALE_WEEKDAY_NAMES** (`list(str)`): weekday names, starting with Monday and ending with Sunday. Must contain 7 elements (one for each week day).
- **NULL_STR** (`str`): string to represent an empty `lunch1` or `dinner`. Defaults to `N/A`.

## Future

Expand Down
54 changes: 42 additions & 12 deletions app/api/meals.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
**gen_responses({401: "Missing Token", 403: "Invalid token"}),
)

_CustomMeal = Union[Meal, SimpleMeal]
_CustomMealList = List[_CustomMeal]


@router.get(
"",
response_model_exclude_unset=True,
response_model=List[Union[Meal, SimpleMeal]],
response_model=_CustomMealList,
summary="Get all meals",
)
def get_meals(
Expand All @@ -37,7 +40,7 @@ def get_meals(
@router.get(
"/week/current",
response_model_exclude_unset=True,
response_model=List[Union[Meal, SimpleMeal]],
response_model=_CustomMealList,
summary="Get meals of current week",
)
def get_meals_of_current_week(*, db=Depends(get_db), simple=Depends(simplify_asked)):
Expand All @@ -48,7 +51,7 @@ def get_meals_of_current_week(*, db=Depends(get_db), simple=Depends(simplify_ask
@router.get(
"/week/next",
response_model_exclude_unset=True,
response_model=List[Union[Meal, SimpleMeal]],
response_model=_CustomMealList,
summary="Get meals of next week",
)
def get_meals_of_next_week(*, db=Depends(get_db), simple=Depends(simplify_asked)):
Expand All @@ -61,7 +64,7 @@ def get_meals_of_next_week(*, db=Depends(get_db), simple=Depends(simplify_asked)
@router.get(
"/week/{week}",
response_model_exclude_unset=True,
response_model=List[Union[Meal, SimpleMeal]],
response_model=_CustomMealList,
summary="Get meals of week",
)
def get_meals_of_week(*, week: int, db=Depends(get_db), simple=Depends(simplify_asked)):
Expand All @@ -72,7 +75,7 @@ def get_meals_of_week(*, week: int, db=Depends(get_db), simple=Depends(simplify_
@router.get(
"/today",
response_model_exclude_unset=True,
response_model=Union[Meal, SimpleMeal],
response_model=_CustomMeal,
summary="Get today meals",
)
def get_today_meals(*, db=Depends(get_db), simple=Depends(simplify_asked)):
Expand All @@ -83,7 +86,7 @@ def get_today_meals(*, db=Depends(get_db), simple=Depends(simplify_asked)):
@router.get(
"/tomorrow",
response_model_exclude_unset=True,
response_model=Union[Meal, SimpleMeal],
response_model=_CustomMeal,
summary="Get tomorrow meals",
)
def get_tomorrow_meals(*, db=Depends(get_db), simple=Depends(simplify_asked)):
Expand All @@ -94,7 +97,7 @@ def get_tomorrow_meals(*, db=Depends(get_db), simple=Depends(simplify_asked)):
@router.get(
"/{date}",
response_model_exclude_unset=True,
response_model=Union[Meal, SimpleMeal],
response_model=_CustomMeal,
summary="Get meal by its date",
**gen_responses({404: "Not Found"}),
)
Expand All @@ -107,7 +110,7 @@ def get_single_meal(

@router.post(
"",
response_model=Union[Meal, SimpleMeal],
response_model=_CustomMeal,
status_code=201,
summary="Create single meal",
**gen_responses({409: "Date conflict"}),
Expand All @@ -128,7 +131,7 @@ def create_single_meal(
@router.post(
"/bulk",
response_model_exclude_unset=True,
response_model=List[Union[Meal, SimpleMeal]],
response_model=_CustomMealList,
status_code=201,
summary="Create multiple meals",
**gen_responses({409: "Date conflict"}),
Expand All @@ -150,7 +153,7 @@ def create_multiple_meals(

@router.put(
"/swap",
response_model=List[Union[Meal, SimpleMeal]],
response_model=_CustomMealList,
response_model_exclude_unset=True,
summary="Swap meals",
)
Expand All @@ -160,17 +163,44 @@ def swap_meals(
meal_1: datetime.date,
meal_2: datetime.date,
mode: SwapMode,
simple=Depends(simplify_asked),
background_tasks: BackgroundTasks,
):
"""Swaps meal attributes."""
result = crud.meal.swap(db, date_1=meal_1, date_2=meal_2, mode=mode)
result = simplify(
crud.meal.swap(db, date_1=meal_1, date_2=meal_2, mode=mode),
List[SimpleMeal],
simple,
)
background_tasks.add_task(update_notion_meals)
return result


@router.put(
"/shift/{date}",
response_model=_CustomMealList,
response_model_exclude_unset=True,
summary="Shift meals",
)
def shift_meals(
*,
db=Depends(get_db),
date: datetime.date,
mode: SwapMode,
simple=Depends(simplify_asked),
background_tasks: BackgroundTasks,
):
"""Shifts meals X days to the future."""
result = simplify(
crud.meal.shift(db, date=date, mode=mode), List[SimpleMeal], simple
)
background_tasks.add_task(update_notion_meals)
return result


@router.put(
"/{date}",
response_model=Union[Meal, SimpleMeal],
response_model=_CustomMeal,
response_model_exclude_unset=True,
summary="Update meal",
**gen_responses({404: "Not Found"}),
Expand Down
1 change: 1 addition & 0 deletions app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class Settings(BaseSettings):

# Other
LOCALE_WEEKDAY_NAMES: Optional[List[str]]
NULL_STR = "N/A"

@validator("DATABASE_URI", pre=True)
def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
Expand Down
7 changes: 6 additions & 1 deletion app/core/meals.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


from enum import Enum
from typing import Any
from typing import Any, List

from fastapi import Query
from pydantic import parse_obj_as
Expand Down Expand Up @@ -48,3 +48,8 @@ def swap_attrs(obj1: Any, obj2: Any, attrname: str):

setattr(obj1, attrname, attr2)
setattr(obj2, attrname, attr1)


def set_attrs(old: Any, new: Any, attrnames: List[str]):
for attr in attrnames:
setattr(new, attr, getattr(old, attr))
4 changes: 4 additions & 0 deletions app/cron/update_notion_meals.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def get_weekday(delta_days: int = 0) -> str:
@scheduler.scheduled_job("cron", id="update-notion-meals", hour="5", minute="0")
def update_notion_meals():
"""Update meals in notion page."""
if not settings.PRODUCTION:
print("Skipping update-cron-meals (dev)")
return

with manual_db() as db:
today_meal = crud.meal.get_today(db)
tomorrow_meal = crud.meal.get_tomorrow(db)
Expand Down
76 changes: 68 additions & 8 deletions app/crud/crud_meal.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,22 @@
from sqlalchemy import func
from sqlalchemy.orm.session import Session

from ..core.meals import SwapMode, swap_attrs
from ..core.config import settings
from ..core.meals import SwapMode, set_attrs, swap_attrs
from ..crud.base import CRUDBase
from ..models import Meal
from ..schemas.meal import MealCreate, MealUpdate
from ..utils.misc import get_current_week

NULL_MAP = {
"lunch1": settings.NULL_STR,
"lunch1_frozen": False,
"lunch2": None,
"lunch2_frozen": False,
"dinner": settings.NULL_STR,
"dinner_frozen": False,
}


class CRUDMeal(CRUDBase[Meal, MealCreate, MealUpdate]):
"""Meal CRUD operations."""
Expand Down Expand Up @@ -83,12 +93,27 @@ def swap(
*,
date_1: datetime.date,
date_2: datetime.date,
mode: SwapMode
mode: SwapMode,
) -> List[Meal]:
"""Swaps two meals data."""
obj1 = self.get_or_404(db, id=date_1)
obj2 = self.get_or_404(db, id=date_2)

attrnames = self.get_attrnames_from_swapmode(mode)
for attr in attrnames:
swap_attrs(obj1, obj2, attr)

db.add(obj1)
db.add(obj2)
db.commit()
db.refresh(obj1)
db.refresh(obj2)

return [obj1, obj2]

@staticmethod
def get_attrnames_from_swapmode(mode: SwapMode) -> List[str]:
"""Returns the attributes that must be edited for each SwapMode."""
attrnames = []
if mode == SwapMode.ALL:
attrnames += ["lunch1", "lunch2", "dinner"]
Expand All @@ -108,16 +133,51 @@ def swap(
if "dinner" in attrnames:
attrnames.append("dinner_frozen")

return attrnames

def shift(self, db: Session, *, date: datetime.date, mode: SwapMode):
"""Shifts all meals x days to the future."""
meal_to_move = self.get(db, id=date)
# If there is no meal for that date we don't have to do anything
if meal_to_move is None:
return []

attrnames = self.get_attrnames_from_swapmode(mode)
meals_to_edit = self.get_days_to_shift(db, meal_to_move, attrnames)

for meal_to_edit in meals_to_edit:
db.refresh(meal_to_edit)

# Shift all meals
meals_to_edit.sort(key=lambda x: x.id, reverse=True)
for idx, meal_to_move in enumerate(meals_to_edit):
if idx:
set_attrs(meals_to_edit[idx], meals_to_edit[idx - 1], attrnames)

# Remove attributes from first meal
for attr in attrnames:
swap_attrs(obj1, obj2, attr)
setattr(meal_to_move, attr, NULL_MAP[attr])

db.add(obj1)
db.add(obj2)
db.add_all(meals_to_edit)
db.commit()
db.refresh(obj1)
db.refresh(obj2)

return [obj1, obj2]
meals_to_edit.sort(key=lambda x: x.id)
return meals_to_edit

def get_days_to_shift(
self, db: Session, first_meal: Meal, attrnames: List[str]
) -> List[Meal]:
"""Returns a list of meals to edit after shifting."""
next_day_id = first_meal.id + datetime.timedelta(days=1)
next_day_meal = self.get(db, id=next_day_id)
if next_day_meal is None:
new_meal = MealCreate(
date=next_day_id, lunch1=settings.NULL_STR, dinner=settings.NULL_STR
)
next_day_meal = self.create(db, obj_in=new_meal)
return [first_meal, next_day_meal]

return self.get_days_to_shift(db, next_day_meal, attrnames) + [first_meal]

def remove_week(self, db: Session, *, week: int):
"""Remove meals for the entire week."""
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "meal-planner"
version = "1.6.1"
version = "1.7.0"
description = ""
authors = ["Diego Alloza González <[email protected]>"]
packages = [
Expand Down

0 comments on commit 31ed26f

Please sign in to comment.