fix: mealplan pagination (#1464)
* added pagination to get_slice route * updated mealplan tests * renamed vars to match pagination query
This commit is contained in:
parent
2f7ff6d178
commit
2809cef3b1
5 changed files with 102 additions and 29 deletions
|
@ -26,11 +26,11 @@ export const useMealplans = function (range: Ref<DateRange>) {
|
|||
loading.value = true;
|
||||
const units = useAsync(async () => {
|
||||
const query = {
|
||||
start: format(range.value.start, "yyyy-MM-dd"),
|
||||
limit: format(range.value.end, "yyyy-MM-dd"),
|
||||
start_date: format(range.value.start, "yyyy-MM-dd"),
|
||||
end_date: format(range.value.end, "yyyy-MM-dd"),
|
||||
};
|
||||
// @ts-ignore TODO Modify typing to allow for string start+limit for mealplans
|
||||
const { data } = await api.mealplans.getAll(1, -1, { start: query.start, limit: query.limit });
|
||||
const { data } = await api.mealplans.getAll(1, -1, { start_date: query.start_date, end_date: query.end_date });
|
||||
|
||||
if (data) {
|
||||
return data.items;
|
||||
|
@ -45,11 +45,11 @@ export const useMealplans = function (range: Ref<DateRange>) {
|
|||
async refreshAll(this: void) {
|
||||
loading.value = true;
|
||||
const query = {
|
||||
start: format(range.value.start, "yyyy-MM-dd"),
|
||||
limit: format(range.value.end, "yyyy-MM-dd"),
|
||||
start_date: format(range.value.start, "yyyy-MM-dd"),
|
||||
end_date: format(range.value.end, "yyyy-MM-dd"),
|
||||
};
|
||||
// @ts-ignore TODO Modify typing to allow for string start+limit for mealplans
|
||||
const { data } = await api.mealplans.getAll(1, -1, { start: query.start, limit: query.limit });
|
||||
const { data } = await api.mealplans.getAll(1, -1, { start_date: query.start_date, end_date: query.end_date });
|
||||
|
||||
if (data && data.items) {
|
||||
mealplans.value = data.items;
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
from datetime import date
|
||||
from math import ceil
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.sql import sqltypes
|
||||
|
||||
from mealie.db.models.group import GroupMealPlan
|
||||
from mealie.schema.meal_plan.new_meal import ReadPlanEntry
|
||||
from mealie.schema.meal_plan.new_meal import PlanEntryPagination, ReadPlanEntry
|
||||
from mealie.schema.response.pagination import OrderDirection, PaginationQuery
|
||||
|
||||
from .repository_generic import RepositoryGeneric
|
||||
|
||||
|
@ -11,15 +16,67 @@ class RepositoryMeals(RepositoryGeneric[ReadPlanEntry, GroupMealPlan]):
|
|||
def by_group(self, group_id: UUID) -> "RepositoryMeals":
|
||||
return super().by_group(group_id) # type: ignore
|
||||
|
||||
def get_slice(self, start: date, end: date, group_id: UUID) -> list[ReadPlanEntry]:
|
||||
start_str = start.strftime("%Y-%m-%d")
|
||||
end_str = end.strftime("%Y-%m-%d")
|
||||
qry = self.session.query(GroupMealPlan).filter(
|
||||
def get_slice(
|
||||
self, pagination: PaginationQuery, start_date: date, end_date: date, group_id: UUID
|
||||
) -> PlanEntryPagination:
|
||||
start_str = start_date.strftime("%Y-%m-%d")
|
||||
end_str = end_date.strftime("%Y-%m-%d")
|
||||
|
||||
# get the total number of documents
|
||||
q = self.session.query(GroupMealPlan).filter(
|
||||
GroupMealPlan.date.between(start_str, end_str),
|
||||
GroupMealPlan.group_id == group_id,
|
||||
)
|
||||
|
||||
return [self.schema.from_orm(x) for x in qry.all()]
|
||||
count = q.count()
|
||||
|
||||
# interpret -1 as "get_all"
|
||||
if pagination.per_page == -1:
|
||||
pagination.per_page = count
|
||||
|
||||
try:
|
||||
total_pages = ceil(count / pagination.per_page)
|
||||
|
||||
except ZeroDivisionError:
|
||||
total_pages = 0
|
||||
|
||||
# interpret -1 as "last page"
|
||||
if pagination.page == -1:
|
||||
pagination.page = total_pages
|
||||
|
||||
# failsafe for user input error
|
||||
if pagination.page < 1:
|
||||
pagination.page = 1
|
||||
|
||||
if pagination.order_by:
|
||||
if order_attr := getattr(self.model, pagination.order_by, None):
|
||||
# queries handle uppercase and lowercase differently, which is undesirable
|
||||
if isinstance(order_attr.type, sqltypes.String):
|
||||
order_attr = func.lower(order_attr)
|
||||
|
||||
if pagination.order_direction == OrderDirection.asc:
|
||||
order_attr = order_attr.asc()
|
||||
elif pagination.order_direction == OrderDirection.desc:
|
||||
order_attr = order_attr.desc()
|
||||
|
||||
q = q.order_by(order_attr)
|
||||
|
||||
q = q.limit(pagination.per_page).offset((pagination.page - 1) * pagination.per_page)
|
||||
|
||||
try:
|
||||
data = [self.schema.from_orm(x) for x in q.all()]
|
||||
except Exception as e:
|
||||
self._log_exception(e)
|
||||
self.session.rollback()
|
||||
raise e
|
||||
|
||||
return PlanEntryPagination(
|
||||
page=pagination.page,
|
||||
per_page=pagination.per_page,
|
||||
total=count,
|
||||
total_pages=total_pages,
|
||||
items=data,
|
||||
)
|
||||
|
||||
def get_today(self, group_id: UUID) -> list[ReadPlanEntry]:
|
||||
today = date.today()
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from datetime import date, timedelta
|
||||
from functools import cached_property
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from mealie.core.exceptions import mealie_registered_exceptions
|
||||
from mealie.repos.repository_meals import RepositoryMeals
|
||||
|
@ -9,9 +10,10 @@ from mealie.routes._base import BaseUserController, controller
|
|||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.schema import mapper
|
||||
from mealie.schema.meal_plan import CreatePlanEntry, ReadPlanEntry, SavePlanEntry, UpdatePlanEntry
|
||||
from mealie.schema.meal_plan.new_meal import CreateRandomEntry
|
||||
from mealie.schema.meal_plan.new_meal import CreateRandomEntry, PlanEntryPagination
|
||||
from mealie.schema.meal_plan.plan_rules import PlanRulesDay
|
||||
from mealie.schema.recipe.recipe import Recipe
|
||||
from mealie.schema.response.pagination import PaginationQuery
|
||||
from mealie.schema.response.responses import ErrorResponse
|
||||
|
||||
router = APIRouter(prefix="/groups/mealplans", tags=["Groups: Mealplans"])
|
||||
|
@ -85,11 +87,17 @@ class GroupMealplanController(BaseUserController):
|
|||
except IndexError:
|
||||
raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No recipes match your rules"))
|
||||
|
||||
@router.get("", response_model=list[ReadPlanEntry])
|
||||
def get_all(self, start: date = None, limit: date = None):
|
||||
start = start or date.today() - timedelta(days=999)
|
||||
limit = limit or date.today() + timedelta(days=999)
|
||||
return self.repo.get_slice(start, limit, group_id=self.group.id)
|
||||
@router.get("", response_model=PlanEntryPagination)
|
||||
def get_all(
|
||||
self,
|
||||
q: PaginationQuery = Depends(PaginationQuery),
|
||||
start_date: Optional[date] = None,
|
||||
end_date: Optional[date] = None,
|
||||
):
|
||||
start_date = start_date or date.today() - timedelta(days=999)
|
||||
end_date = end_date or date.today() + timedelta(days=999)
|
||||
|
||||
return self.repo.get_slice(pagination=q, start_date=start_date, end_date=end_date, group_id=self.group.id)
|
||||
|
||||
@router.post("", response_model=ReadPlanEntry, status_code=201)
|
||||
def create_one(self, data: CreatePlanEntry):
|
||||
|
|
|
@ -7,6 +7,7 @@ from pydantic import validator
|
|||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema.recipe.recipe import RecipeSummary
|
||||
from mealie.schema.response.pagination import PaginationBase
|
||||
|
||||
|
||||
class PlanEntryType(str, Enum):
|
||||
|
@ -54,3 +55,7 @@ class ReadPlanEntry(UpdatePlanEntry):
|
|||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class PlanEntryPagination(PaginationBase):
|
||||
items: list[ReadPlanEntry]
|
||||
|
|
|
@ -12,14 +12,17 @@ class Routes:
|
|||
recipe = "/api/recipes"
|
||||
today = "/api/groups/mealplans/today"
|
||||
|
||||
def all_slice(start: str, end: str):
|
||||
return f"{Routes.base}?start={start}&limit={end}"
|
||||
@staticmethod
|
||||
def all_slice(page: int, perPage: int, start_date: str, end_date: str):
|
||||
return f"{Routes.base}?page={page}&perPage={perPage}&start_date={start_date}&end_date={end_date}"
|
||||
|
||||
@staticmethod
|
||||
def item(item_id: int) -> str:
|
||||
return f"{Routes.base}/{item_id}"
|
||||
|
||||
def recipe_slug(recipe_id: int) -> str:
|
||||
return f"{Routes.recipe}/{recipe_id}"
|
||||
@staticmethod
|
||||
def recipe_slug(recipe_name: str) -> str:
|
||||
return f"{Routes.recipe}/{recipe_name}"
|
||||
|
||||
|
||||
def test_create_mealplan_no_recipe(api_client: TestClient, unique_user: TestUser):
|
||||
|
@ -106,10 +109,10 @@ def test_get_all_mealplans(api_client: TestClient, unique_user: TestUser):
|
|||
response = api_client.post(Routes.base, json=new_plan, headers=unique_user.token)
|
||||
assert response.status_code == 201
|
||||
|
||||
response = api_client.get(Routes.base, headers=unique_user.token)
|
||||
response = api_client.get(Routes.base, headers=unique_user.token, params={"page": 1, "perPage": -1})
|
||||
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()) >= 3
|
||||
assert len(response.json()["items"]) >= 3
|
||||
|
||||
|
||||
def test_get_slice_mealplans(api_client: TestClient, unique_user: TestUser):
|
||||
|
@ -132,15 +135,15 @@ def test_get_slice_mealplans(api_client: TestClient, unique_user: TestUser):
|
|||
slices = [dates, dates[1:2], dates[2:3], dates[3:4], dates[4:5]]
|
||||
|
||||
for date_range in slices:
|
||||
start = date_range[0].strftime("%Y-%m-%d")
|
||||
end = date_range[-1].strftime("%Y-%m-%d")
|
||||
start_date = date_range[0].strftime("%Y-%m-%d")
|
||||
end_date = date_range[-1].strftime("%Y-%m-%d")
|
||||
|
||||
response = api_client.get(Routes.all_slice(start, end), headers=unique_user.token)
|
||||
response = api_client.get(Routes.all_slice(1, -1, start_date, end_date), headers=unique_user.token)
|
||||
|
||||
assert response.status_code == 200
|
||||
response_json = response.json()
|
||||
|
||||
for meal_plan in response_json:
|
||||
for meal_plan in response_json["items"]:
|
||||
assert meal_plan["date"] in [date.strftime("%Y-%m-%d") for date in date_range]
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue