Introduce ical action on MealPlanViewSet to expose ical format for listing meal plans.

This commit is contained in:
Anand Patel
2024-03-16 05:28:16 +00:00
committed by smilerz
parent 77a46a4ef6
commit bdd9ff796a
4 changed files with 107 additions and 91 deletions

View File

@@ -14,8 +14,11 @@ class QueryParam(object):
class QueryParamAutoSchema(AutoSchema): class QueryParamAutoSchema(AutoSchema):
def is_query(self, path, method):
return is_list_view(path, method, self.view)
def get_path_parameters(self, path, method): def get_path_parameters(self, path, method):
if not is_list_view(path, method, self.view): if not self.is_query(path, method):
return super().get_path_parameters(path, method) return super().get_path_parameters(path, method)
parameters = super().get_path_parameters(path, method) parameters = super().get_path_parameters(path, method)
for q in self.view.query_params: for q in self.view.query_params:

View File

@@ -5,12 +5,14 @@ import pytest
from django.contrib import auth from django.contrib import auth
from django.urls import reverse from django.urls import reverse
from django_scopes import scope, scopes_disabled from django_scopes import scope, scopes_disabled
from icalendar import Calendar
from cookbook.models import MealPlan, MealType from cookbook.models import MealPlan, MealType
from cookbook.tests.factories import RecipeFactory from cookbook.tests.factories import RecipeFactory
LIST_URL = 'api:mealplan-list' LIST_URL = 'api:mealplan-list'
DETAIL_URL = 'api:mealplan-detail' DETAIL_URL = 'api:mealplan-detail'
ICAL_URL = 'api:mealplan-ical'
# NOTE: auto adding shopping list from meal plan is tested in test_shopping_recipe as tests are identical # NOTE: auto adding shopping list from meal plan is tested in test_shopping_recipe as tests are identical
@@ -32,6 +34,11 @@ def obj_2(space_1, recipe_1_s1, meal_type, u1_s1):
return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, from_date=datetime.now(), to_date=datetime.now(), return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, from_date=datetime.now(), to_date=datetime.now(),
created_by=auth.get_user(u1_s1)) created_by=auth.get_user(u1_s1))
@pytest.fixture
def obj_3(space_1, recipe_1_s1, meal_type, u1_s1):
return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, from_date=datetime.now() - timedelta(days=30), to_date=datetime.now() - timedelta(days=1),
created_by=auth.get_user(u1_s1))
@pytest.mark.parametrize("arg", [ @pytest.mark.parametrize("arg", [
['a_u', 403], ['a_u', 403],
@@ -163,3 +170,32 @@ def test_add_with_shopping(u1_s1, meal_type):
) )
assert len(json.loads(u1_s1.get(reverse('api:shoppinglistentry-list')).content)) == 10 assert len(json.loads(u1_s1.get(reverse('api:shoppinglistentry-list')).content)) == 10
@pytest.mark.parametrize("arg", [
[f'', 2],
[f'?from_date={datetime.now().strftime("%Y-%m-%d")}', 1],
[f'?to_date={(datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")}', 1],
[f'?from_date={(datetime.now() + timedelta(days=2)).strftime("%Y-%m-%d")}&to_date={(datetime.now() + timedelta(days=2)).strftime("%Y-%m-%d")}', 0],
])
def test_ical(arg, request, obj_1, obj_3, u1_s1):
r = u1_s1.get(f'{reverse(ICAL_URL)}{arg[0]}')
assert r.status_code == 200
cal = Calendar.from_ical(r.getvalue().decode('UTF-8'))
events = cal.walk('VEVENT')
assert len(events) == arg[1]
def test_ical_event(obj_1, u1_s1):
r = u1_s1.get(f'{reverse(ICAL_URL)}')
cal = Calendar.from_ical(r.getvalue().decode('UTF-8'))
events = cal.walk('VEVENT')
assert len(events) == 1
event = events[0]
assert int(event['uid']) == obj_1.id
assert event['summary'] == f'{obj_1.meal_type.name}: {obj_1.get_label()}'
assert event['description'] == obj_1.note
assert event.decoded('dtstart') == datetime.now().date()
assert event.decoded('dtend') == datetime.now().date()

View File

@@ -1,88 +1,55 @@
# from datetime import datetime, timedelta from datetime import datetime, timedelta
#
# import pytest import pytest
# from django.contrib import auth from django.contrib import auth
# from django.urls import reverse from django.urls import reverse
# from icalendar import Calendar from icalendar import Calendar
#
# from cookbook.models import MealPlan, MealType from cookbook.models import MealPlan, MealType
#
# BOUND_URL = 'api_get_plan_ical' BOUND_URL = 'api_get_plan_ical'
# FROM_URL = 'api_get_plan_ical'
# FUTURE_URL = 'api_get_plan_ical'
# @pytest.fixture()
# def meal_type(space_1, u1_s1):
# @pytest.fixture() return MealType.objects.get_or_create(name='test', space=space_1, created_by=auth.get_user(u1_s1))[0]
# def meal_type(space_1, u1_s1):
# return MealType.objects.get_or_create(name='test', space=space_1, created_by=auth.get_user(u1_s1))[0]
# @pytest.fixture()
# def obj_1(space_1, recipe_1_s1, meal_type, u1_s1):
# @pytest.fixture() return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, from_date=datetime.now(), to_date=datetime.now(),
# def obj_1(space_1, recipe_1_s1, meal_type, u1_s1): created_by=auth.get_user(u1_s1))
# return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, from_date=datetime.now(), to_date=datetime.now(),
# created_by=auth.get_user(u1_s1))
# @pytest.fixture
# def obj_2(space_1, recipe_1_s1, meal_type, u1_s1):
# @pytest.fixture return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, from_date=datetime.now()+timedelta(days=30), to_date=datetime.now()+timedelta(days=30),
# def obj_2(space_1, recipe_1_s1, meal_type, u1_s1): created_by=auth.get_user(u1_s1))
# return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, from_date=datetime.now()+timedelta(days=30), to_date=datetime.now()+timedelta(days=30),
# created_by=auth.get_user(u1_s1)) @pytest.fixture
# def obj_3(space_1, recipe_1_s1, meal_type, u1_s1):
# @pytest.fixture return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, from_date=datetime.now()+timedelta(days=-30), to_date=datetime.now()+timedelta(days=-1),
# def obj_3(space_1, recipe_1_s1, meal_type, u1_s1): created_by=auth.get_user(u1_s1))
# return MealPlan.objects.create(recipe=recipe_1_s1, space=space_1, meal_type=meal_type, from_date=datetime.now()+timedelta(days=-30), to_date=datetime.now()+timedelta(days=-1),
# created_by=auth.get_user(u1_s1))
# @pytest.mark.parametrize("arg", [
# ['a_u', 403],
# @pytest.mark.parametrize("arg", [ ['g1_s1', 403],
# ['a_u', 403], ['u1_s1', 200],
# ['g1_s1', 403], ['a1_s1', 200],
# ['u1_s1', 200], ])
# ['a1_s1', 200], def test_permissions(arg, request):
# ]) c = request.getfixturevalue(arg[0])
# def test_permissions(arg, request): from_date_slug = (datetime.now()+timedelta(days=-1)).strftime("%Y-%m-%d")
# c = request.getfixturevalue(arg[0]) to_date_slug = (datetime.now()+timedelta(days=1)).strftime("%Y-%m-%d")
# assert c.get(reverse(FUTURE_URL)).status_code == arg[1] assert c.get(reverse(BOUND_URL, kwargs={'from_date': from_date_slug, 'to_date': to_date_slug})).status_code == arg[1]
#
# def test_future(obj_1, obj_2, obj_3, u1_s1): def test_bound(obj_1, obj_2, obj_3, u1_s1):
# r = u1_s1.get(reverse(FUTURE_URL)) from_date_slug = (datetime.now()+timedelta(days=-1)).strftime("%Y-%m-%d")
# assert r.status_code == 200 to_date_slug = (datetime.now()+timedelta(days=1)).strftime("%Y-%m-%d")
# r = u1_s1.get(reverse(BOUND_URL, kwargs={'from_date': from_date_slug, 'to_date': to_date_slug}))
# cal = Calendar.from_ical(r.getvalue().decode('UTF-8')) assert r.status_code == 200
# events = cal.walk('VEVENT')
# assert len(events) == 2 cal = Calendar.from_ical(r.getvalue().decode('UTF-8'))
# events = cal.walk('VEVENT')
# def test_from(obj_1, obj_2, obj_3, u1_s1): assert len(events) == 1
# from_date_slug = (datetime.now()+timedelta(days=1)).strftime("%Y-%m-%d")
# r = u1_s1.get(reverse(FROM_URL, kwargs={'from_date': from_date_slug}))
# assert r.status_code == 200
#
# cal = Calendar.from_ical(r.getvalue().decode('UTF-8'))
# events = cal.walk('VEVENT')
# assert len(events) == 1
#
# def test_bound(obj_1, obj_2, obj_3, u1_s1):
# from_date_slug = (datetime.now()+timedelta(days=-1)).strftime("%Y-%m-%d")
# to_date_slug = (datetime.now()+timedelta(days=1)).strftime("%Y-%m-%d")
# r = u1_s1.get(reverse(BOUND_URL, kwargs={'from_date': from_date_slug, 'to_date': to_date_slug}))
# assert r.status_code == 200
#
# cal = Calendar.from_ical(r.getvalue().decode('UTF-8'))
# events = cal.walk('VEVENT')
# assert len(events) == 1
#
# def test_event(obj_1, u1_s1):
# from_date_slug = (datetime.now()+timedelta(days=-1)).strftime("%Y-%m-%d")
# to_date_slug = (datetime.now()+timedelta(days=1)).strftime("%Y-%m-%d")
# r = u1_s1.get(reverse(BOUND_URL, kwargs={'from_date': from_date_slug, 'to_date': to_date_slug}))
#
# cal = Calendar.from_ical(r.getvalue().decode('UTF-8'))
# events = cal.walk('VEVENT')
# assert len(events) == 1
#
# event = events[0]
# assert int(event['uid']) == obj_1.id
# assert event['summary'] == f'{obj_1.meal_type.name}: {obj_1.get_label()}'
# assert event['description'] == obj_1.note
# assert event.decoded('dtstart') == datetime.now().date()
# assert event.decoded('dtend') == datetime.now().date()

View File

@@ -41,7 +41,7 @@ from recipe_scrapers._exceptions import NoSchemaFoundInWildMode
from requests.exceptions import MissingSchema from requests.exceptions import MissingSchema
from rest_framework import decorators, status, viewsets from rest_framework import decorators, status, viewsets
from rest_framework.authtoken.views import ObtainAuthToken from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.decorators import api_view, permission_classes from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.exceptions import APIException, PermissionDenied from rest_framework.exceptions import APIException, PermissionDenied
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
from rest_framework.parsers import MultiPartParser from rest_framework.parsers import MultiPartParser
@@ -804,6 +804,13 @@ class MealPlanViewSet(viewsets.ModelViewSet):
queryset = queryset.filter(meal_type__in=meal_type) queryset = queryset.filter(meal_type__in=meal_type)
return queryset return queryset
@action(detail=False)
def ical(self, request):
from_date = self.request.query_params.get('from_date', None)
to_date = self.request.query_params.get('to_date', None)
return meal_plans_to_ical(self.get_queryset(), f'meal_plan_{from_date}-{to_date}.ics')
class AutoPlanViewSet(viewsets.ViewSet): class AutoPlanViewSet(viewsets.ViewSet):
@@ -1786,6 +1793,9 @@ def get_plan_ical(request, from_date=datetime.date.today(), to_date=None):
if to_date is not None: if to_date is not None:
queryset = queryset.filter(to_date__lte=to_date) queryset = queryset.filter(to_date__lte=to_date)
return meal_plans_to_ical(queryset, f'meal_plan_{from_date}-{to_date}.ics')
def meal_plans_to_ical(queryset, filename):
cal = Calendar() cal = Calendar()
for p in queryset: for p in queryset:
@@ -1801,7 +1811,7 @@ def get_plan_ical(request, from_date=datetime.date.today(), to_date=None):
cal.add_component(event) cal.add_component(event)
response = FileResponse(io.BytesIO(cal.to_ical())) response = FileResponse(io.BytesIO(cal.to_ical()))
response["Content-Disposition"] = f'attachment; filename=meal_plan_{from_date}-{to_date}.ics' # noqa: E501 response["Content-Disposition"] = f'attachment; filename={filename}' # noqa: E501
return response return response