mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-04 21:58:54 -05:00
WIP
This commit is contained in:
@@ -14,7 +14,6 @@ from zipfile import ZipFile
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
import validators
|
import validators
|
||||||
from PIL import UnidentifiedImageError
|
|
||||||
from annoying.decorators import ajax_request
|
from annoying.decorators import ajax_request
|
||||||
from annoying.functions import get_object_or_None
|
from annoying.functions import get_object_or_None
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
@@ -34,9 +33,10 @@ from django.utils import timezone
|
|||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django_scopes import scopes_disabled
|
from django_scopes import scopes_disabled
|
||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import extend_schema, OpenApiParameter, extend_schema_view
|
from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view
|
||||||
from icalendar import Calendar, Event
|
from icalendar import Calendar, Event
|
||||||
from oauth2_provider.models import AccessToken
|
from oauth2_provider.models import AccessToken
|
||||||
|
from PIL import UnidentifiedImageError
|
||||||
from recipe_scrapers import scrape_me
|
from recipe_scrapers import scrape_me
|
||||||
from recipe_scrapers._exceptions import NoSchemaFoundInWildMode
|
from recipe_scrapers._exceptions import NoSchemaFoundInWildMode
|
||||||
from requests.exceptions import MissingSchema
|
from requests.exceptions import MissingSchema
|
||||||
@@ -59,61 +59,43 @@ from cookbook.helper.HelperFunctions import str2bool
|
|||||||
from cookbook.helper.image_processing import handle_image
|
from cookbook.helper.image_processing import handle_image
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.helper.open_data_importer import OpenDataImporter
|
from cookbook.helper.open_data_importer import OpenDataImporter
|
||||||
from cookbook.helper.permission_helper import (
|
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsOwner, CustomIsOwnerReadOnly, CustomIsShared, CustomIsSpaceOwner, CustomIsUser, CustomRecipePermission,
|
||||||
CustomIsAdmin, CustomIsOwner, CustomIsOwnerReadOnly, CustomIsShared, CustomIsSpaceOwner, CustomIsUser, CustomRecipePermission, CustomTokenHasReadWriteScope,
|
CustomTokenHasReadWriteScope, CustomTokenHasScope, CustomUserPermission, IsReadOnlyDRF, above_space_limit, group_required,
|
||||||
CustomTokenHasScope, CustomUserPermission, IsReadOnlyDRF, above_space_limit, group_required, has_group_permission, is_space_owner, switch_user_active_space,
|
has_group_permission, is_space_owner, switch_user_active_space,
|
||||||
)
|
)
|
||||||
from cookbook.helper.recipe_search import RecipeSearch
|
from cookbook.helper.recipe_search import RecipeSearch
|
||||||
from cookbook.helper.recipe_url_import import clean_dict, get_from_youtube_scraper, get_images_from_soup
|
from cookbook.helper.recipe_url_import import clean_dict, get_from_youtube_scraper, get_images_from_soup
|
||||||
from cookbook.helper.scrapers.scrapers import text_scraper
|
from cookbook.helper.scrapers.scrapers import text_scraper
|
||||||
from cookbook.helper.shopping_helper import RecipeShoppingEditor, shopping_helper
|
from cookbook.helper.shopping_helper import RecipeShoppingEditor, shopping_helper
|
||||||
from cookbook.models import (Automation, BookmarkletImport, CookLog, CustomFilter, ExportLog, Food,
|
from cookbook.models import (Automation, BookmarkletImport, ConnectorConfig, CookLog, CustomFilter, ExportLog, Food, FoodInheritField, FoodProperty, ImportLog, Ingredient,
|
||||||
FoodInheritField, FoodProperty, ImportLog, Ingredient, InviteLink,
|
InviteLink, Keyword, MealPlan, MealType, Property, PropertyType, Recipe, RecipeBook, RecipeBookEntry, ShareLink, ShoppingListEntry,
|
||||||
Keyword, MealPlan, MealType, Property, PropertyType, Recipe,
|
ShoppingListRecipe, Space, Step, Storage, Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
|
||||||
RecipeBook, RecipeBookEntry, ShareLink, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
|
UserFile, UserPreference, UserSpace, ViewLog,
|
||||||
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync,
|
)
|
||||||
SyncLog, Unit, UnitConversion, UserFile, UserPreference, UserSpace,
|
|
||||||
ViewLog, ConnectorConfig)
|
|
||||||
from cookbook.provider.dropbox import Dropbox
|
from cookbook.provider.dropbox import Dropbox
|
||||||
from cookbook.provider.local import Local
|
from cookbook.provider.local import Local
|
||||||
from cookbook.provider.nextcloud import Nextcloud
|
from cookbook.provider.nextcloud import Nextcloud
|
||||||
from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer,
|
from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer, AutoMealPlanSerializer, BookmarkletImportListSerializer, BookmarkletImportSerializer,
|
||||||
AutoMealPlanSerializer, BookmarkletImportListSerializer,
|
ConnectorConfigConfigSerializer, CookLogSerializer, CustomFilterSerializer, ExportLogSerializer, FoodInheritFieldSerializer, FoodSerializer,
|
||||||
BookmarkletImportSerializer, CookLogSerializer,
|
FoodShoppingUpdateSerializer, FoodSimpleSerializer, GroupSerializer, ImportLogSerializer, IngredientSerializer, IngredientSimpleSerializer,
|
||||||
CustomFilterSerializer, ExportLogSerializer,
|
InviteLinkSerializer, KeywordSerializer, MealPlanSerializer, MealTypeSerializer, PropertySerializer, PropertyTypeSerializer,
|
||||||
FoodInheritFieldSerializer, FoodSerializer,
|
RecipeBookEntrySerializer, RecipeBookSerializer, RecipeExportSerializer, RecipeFlatSerializer, RecipeFromSourceSerializer, RecipeImageSerializer,
|
||||||
FoodShoppingUpdateSerializer, FoodSimpleSerializer,
|
RecipeOverviewSerializer, RecipeSerializer, RecipeShoppingUpdateSerializer, RecipeSimpleSerializer, ShoppingListEntryBulkSerializer,
|
||||||
GroupSerializer, ImportLogSerializer, IngredientSerializer,
|
ShoppingListEntrySerializer, ShoppingListRecipeSerializer, SpaceSerializer, StepSerializer, StorageSerializer,
|
||||||
IngredientSimpleSerializer, InviteLinkSerializer,
|
SupermarketCategoryRelationSerializer, SupermarketCategorySerializer, SupermarketSerializer, SyncLogSerializer, SyncSerializer,
|
||||||
KeywordSerializer, MealPlanSerializer, MealTypeSerializer,
|
UnitConversionSerializer, UnitSerializer, UserFileSerializer, UserPreferenceSerializer, UserSerializer, UserSpaceSerializer, ViewLogSerializer,
|
||||||
PropertySerializer, PropertyTypeSerializer,
|
)
|
||||||
RecipeBookEntrySerializer, RecipeBookSerializer,
|
|
||||||
RecipeExportSerializer, RecipeFromSourceSerializer,
|
|
||||||
RecipeImageSerializer, RecipeOverviewSerializer, RecipeSerializer,
|
|
||||||
RecipeShoppingUpdateSerializer, RecipeSimpleSerializer,
|
|
||||||
ShoppingListEntrySerializer,
|
|
||||||
ShoppingListRecipeSerializer, SpaceSerializer, StepSerializer, StorageSerializer,
|
|
||||||
SupermarketCategoryRelationSerializer,
|
|
||||||
SupermarketCategorySerializer, SupermarketSerializer,
|
|
||||||
SyncLogSerializer, SyncSerializer, UnitConversionSerializer,
|
|
||||||
UnitSerializer, UserFileSerializer, UserPreferenceSerializer,
|
|
||||||
UserSerializer, UserSpaceSerializer, ViewLogSerializer,
|
|
||||||
ShoppingListEntryBulkSerializer, ConnectorConfigConfigSerializer, RecipeFlatSerializer)
|
|
||||||
from cookbook.views.import_export import get_integration
|
from cookbook.views.import_export import get_integration
|
||||||
from recipes import settings
|
from recipes import settings
|
||||||
from recipes.settings import DRF_THROTTLE_RECIPE_URL_IMPORT, FDC_API_KEY
|
from recipes.settings import DRF_THROTTLE_RECIPE_URL_IMPORT, FDC_API_KEY
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(
|
@extend_schema_view(list=extend_schema(parameters=[
|
||||||
list=extend_schema(
|
OpenApiParameter(name='query', description='lookup if query string is contained within the name, case insensitive', type=str),
|
||||||
parameters=[
|
OpenApiParameter(name='updated_at', description='if model has an updated_at timestamp, filter only models updated at or after datetime', type=str), # TODO format hint
|
||||||
OpenApiParameter(name='query', description='lookup if query string is contained within the name, case insensitive', type=str),
|
OpenApiParameter(name='limit', description='limit number of entries to return', type=str),
|
||||||
OpenApiParameter(name='updated_at', description='if model has an updated_at timestamp, filter only models updated at or after datetime', type=str), # TODO format hint
|
OpenApiParameter(name='random', description='randomly orders entries (only works together with limit)', type=str),
|
||||||
OpenApiParameter(name='limit', description='limit number of entries to return', type=str),
|
]))
|
||||||
OpenApiParameter(name='random', description='randomly orders entries (only works together with limit)', type=str),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
class StandardFilterModelViewSet(viewsets.ModelViewSet):
|
class StandardFilterModelViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@@ -203,8 +185,7 @@ class FuzzyFilterMixin(ViewSetMixin, ExtendedRecipeMixin):
|
|||||||
if query is not None and query not in ["''", '']:
|
if query is not None and query not in ["''", '']:
|
||||||
if fuzzy and (settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql'):
|
if fuzzy and (settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql'):
|
||||||
if self.request.user.is_authenticated and any(
|
if self.request.user.is_authenticated and any(
|
||||||
[self.model.__name__.lower() in x for x in self.request.user.searchpreference.unaccent.values_list('field', flat=True)]
|
[self.model.__name__.lower() in x for x in self.request.user.searchpreference.unaccent.values_list('field', flat=True)]):
|
||||||
):
|
|
||||||
self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name__unaccent', query))
|
self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name__unaccent', query))
|
||||||
else:
|
else:
|
||||||
self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name', query))
|
self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name', query))
|
||||||
@@ -535,16 +516,12 @@ class SupermarketCategoryRelationViewSet(StandardFilterModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
# TODO make TreeMixin a view type and move schema to view type
|
# TODO make TreeMixin a view type and move schema to view type
|
||||||
@extend_schema_view(
|
@extend_schema_view(list=extend_schema(parameters=[
|
||||||
list=extend_schema(
|
OpenApiParameter(name='query', description='lookup if query string is contained within the name, case insensitive', type=str),
|
||||||
parameters=[
|
OpenApiParameter(name='updated_at', description='if model has an updated_at timestamp, filter only models updated at or after datetime', type=str), # TODO format hint
|
||||||
OpenApiParameter(name='query', description='lookup if query string is contained within the name, case insensitive', type=str),
|
OpenApiParameter(name='limit', description='limit number of entries to return', type=str),
|
||||||
OpenApiParameter(name='updated_at', description='if model has an updated_at timestamp, filter only models updated at or after datetime', type=str), # TODO format hint
|
OpenApiParameter(name='random', description='randomly orders entries (only works together with limit)', type=str),
|
||||||
OpenApiParameter(name='limit', description='limit number of entries to return', type=str),
|
]))
|
||||||
OpenApiParameter(name='random', description='randomly orders entries (only works together with limit)', type=str),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
class KeywordViewSet(viewsets.ModelViewSet, TreeMixin):
|
class KeywordViewSet(viewsets.ModelViewSet, TreeMixin):
|
||||||
queryset = Keyword.objects
|
queryset = Keyword.objects
|
||||||
model = Keyword
|
model = Keyword
|
||||||
@@ -560,6 +537,9 @@ class UnitViewSet(StandardFilterModelViewSet, MergeMixin):
|
|||||||
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
||||||
pagination_class = DefaultPagination
|
pagination_class = DefaultPagination
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset()
|
||||||
|
|
||||||
|
|
||||||
class FoodInheritFieldViewSet(viewsets.ReadOnlyModelViewSet):
|
class FoodInheritFieldViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
queryset = FoodInheritField.objects
|
queryset = FoodInheritField.objects
|
||||||
@@ -573,16 +553,12 @@ class FoodInheritFieldViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
# TODO make TreeMixin a view type and move schema to view type
|
# TODO make TreeMixin a view type and move schema to view type
|
||||||
@extend_schema_view(
|
@extend_schema_view(list=extend_schema(parameters=[
|
||||||
list=extend_schema(
|
OpenApiParameter(name='query', description='lookup if query string is contained within the name, case insensitive', type=str),
|
||||||
parameters=[
|
OpenApiParameter(name='updated_at', description='if model has an updated_at timestamp, filter only models updated at or after datetime', type=str), # TODO format hint
|
||||||
OpenApiParameter(name='query', description='lookup if query string is contained within the name, case insensitive', type=str),
|
OpenApiParameter(name='limit', description='limit number of entries to return', type=str),
|
||||||
OpenApiParameter(name='updated_at', description='if model has an updated_at timestamp, filter only models updated at or after datetime', type=str), # TODO format hint
|
OpenApiParameter(name='random', description='randomly orders entries (only works together with limit)', type=str),
|
||||||
OpenApiParameter(name='limit', description='limit number of entries to return', type=str),
|
]))
|
||||||
OpenApiParameter(name='random', description='randomly orders entries (only works together with limit)', type=str),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
|
class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
|
||||||
queryset = Food.objects
|
queryset = Food.objects
|
||||||
model = Food
|
model = Food
|
||||||
@@ -652,7 +628,7 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
|
|||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{
|
{
|
||||||
'msg':
|
'msg':
|
||||||
'API Key Rate Limit reached/exceeded, see https://api.data.gov/docs/rate-limits/ for more information. \
|
'API Key Rate Limit reached/exceeded, see https://api.data.gov/docs/rate-limits/ for more information. \
|
||||||
Configure your key in Tandoor using environment FDC_API_KEY variable.'
|
Configure your key in Tandoor using environment FDC_API_KEY variable.'
|
||||||
},
|
},
|
||||||
status=429,
|
status=429,
|
||||||
@@ -663,11 +639,13 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
|
|||||||
json_dumps_params={'indent': 4})
|
json_dumps_params={'indent': 4})
|
||||||
|
|
||||||
food.properties_food_amount = 100
|
food.properties_food_amount = 100
|
||||||
food.properties_food_unit = Unit.objects.get_or_create(
|
food.properties_food_unit = Unit.objects.get_or_create(base_unit__iexact='g',
|
||||||
base_unit__iexact='g',
|
space=self.request.space,
|
||||||
space=self.request.space,
|
defaults={
|
||||||
defaults={ 'name': 'g', 'base_unit': 'g', 'space': self.request.space}
|
'name': 'g',
|
||||||
)[0]
|
'base_unit': 'g',
|
||||||
|
'space': self.request.space
|
||||||
|
})[0]
|
||||||
|
|
||||||
food.save()
|
food.save()
|
||||||
|
|
||||||
@@ -687,25 +665,24 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
|
|||||||
for fn in data['foodNutrients']:
|
for fn in data['foodNutrients']:
|
||||||
if fn['nutrient']['id'] == pt.fdc_id:
|
if fn['nutrient']['id'] == pt.fdc_id:
|
||||||
property_found = True
|
property_found = True
|
||||||
food_property_list.append(Property(
|
food_property_list.append(
|
||||||
property_type_id=pt.id,
|
Property(property_type_id=pt.id,
|
||||||
property_amount=max(0, round(fn['amount'], 2)), # sometimes FDC might return negative values which make no sense, set to 0
|
property_amount=max(0, round(fn['amount'], 2)), # sometimes FDC might return negative values which make no sense, set to 0
|
||||||
space=self.request.space,
|
space=self.request.space,
|
||||||
))
|
))
|
||||||
if not property_found:
|
if not property_found:
|
||||||
food_property_list.append(Property(
|
food_property_list.append(
|
||||||
property_type_id=pt.id,
|
Property(property_type_id=pt.id, property_amount=0, # if field not in FDC data the food does not have that property
|
||||||
property_amount=0, # if field not in FDC data the food does not have that property
|
space=self.request.space,
|
||||||
space=self.request.space,
|
))
|
||||||
))
|
|
||||||
|
|
||||||
properties = Property.objects.bulk_create(food_property_list, unique_fields=('space', 'property_type',))
|
properties = Property.objects.bulk_create(food_property_list, unique_fields=('space', 'property_type', ))
|
||||||
|
|
||||||
property_food_relation_list = []
|
property_food_relation_list = []
|
||||||
for p in properties:
|
for p in properties:
|
||||||
property_food_relation_list.append(Food.properties.through(food_id=food.id, property_id=p.pk))
|
property_food_relation_list.append(Food.properties.through(food_id=food.id, property_id=p.pk))
|
||||||
|
|
||||||
FoodProperty.objects.bulk_create(property_food_relation_list, ignore_conflicts=True, unique_fields=('food_id', 'property_id',))
|
FoodProperty.objects.bulk_create(property_food_relation_list, ignore_conflicts=True, unique_fields=('food_id', 'property_id', ))
|
||||||
|
|
||||||
return self.retrieve(request, pk)
|
return self.retrieve(request, pk)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -771,15 +748,8 @@ MealPlanViewQueryParameters = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(
|
@extend_schema_view(list=extend_schema(parameters=MealPlanViewQueryParameters),
|
||||||
list=extend_schema(
|
ical=extend_schema(parameters=MealPlanViewQueryParameters, responses={(200, 'text/calendar'): OpenApiTypes.STR}))
|
||||||
parameters=MealPlanViewQueryParameters
|
|
||||||
),
|
|
||||||
ical=extend_schema(
|
|
||||||
parameters=MealPlanViewQueryParameters,
|
|
||||||
responses={(200, 'text/calendar'): OpenApiTypes.STR}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
class MealPlanViewSet(viewsets.ModelViewSet):
|
class MealPlanViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
list:
|
list:
|
||||||
@@ -918,14 +888,10 @@ class IngredientViewSet(viewsets.ModelViewSet):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(
|
@extend_schema_view(list=extend_schema(parameters=[
|
||||||
list=extend_schema(
|
OpenApiParameter(name='recipe', description=_('ID of recipe a step is part of. For multiple repeat parameter.'), type=int),
|
||||||
parameters=[
|
OpenApiParameter(name='query', description=_('Query string matched (fuzzy) against object name.'), type=str),
|
||||||
OpenApiParameter(name='recipe', description=_('ID of recipe a step is part of. For multiple repeat parameter.'), type=int),
|
]))
|
||||||
OpenApiParameter(name='query', description=_('Query string matched (fuzzy) against object name.'), type=str),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
class StepViewSet(viewsets.ModelViewSet):
|
class StepViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Step.objects
|
queryset = Step.objects
|
||||||
serializer_class = StepSerializer
|
serializer_class = StepSerializer
|
||||||
@@ -956,39 +922,59 @@ class RecipePagination(PageNumberPagination):
|
|||||||
return Response(OrderedDict([('count', self.page.paginator.count), ('next', self.get_next_link()), ('previous', self.get_previous_link()), ('results', data), ]))
|
return Response(OrderedDict([('count', self.page.paginator.count), ('next', self.get_next_link()), ('previous', self.get_previous_link()), ('results', data), ]))
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(
|
@extend_schema_view(list=extend_schema(parameters=[
|
||||||
list=extend_schema(
|
OpenApiParameter(name='query', description=_('Query string matched (fuzzy) against recipe name. In the future also fulltext search.')),
|
||||||
parameters=[
|
OpenApiParameter(name='keywords', description=_('ID of keyword a recipe should have. For multiple repeat parameter. Equivalent to keywords_or'), type=int),
|
||||||
OpenApiParameter(name='query', description=_('Query string matched (fuzzy) against recipe name. In the future also fulltext search.')),
|
OpenApiParameter(name='keywords_or', description=_('Keyword IDs, repeat for multiple. Return recipes with any of the keywords'), type=int),
|
||||||
OpenApiParameter(name='keywords', description=_('ID of keyword a recipe should have. For multiple repeat parameter. Equivalent to keywords_or'), type=int),
|
OpenApiParameter(name='keywords_and', description=_('Keyword IDs, repeat for multiple. Return recipes with all of the keywords.'), type=int),
|
||||||
OpenApiParameter(name='keywords_or', description=_('Keyword IDs, repeat for multiple. Return recipes with any of the keywords'), type=int),
|
OpenApiParameter(name='keywords_or_not', description=_('Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords.'), type=int),
|
||||||
OpenApiParameter(name='keywords_and', description=_('Keyword IDs, repeat for multiple. Return recipes with all of the keywords.'), type=int),
|
OpenApiParameter(name='keywords_and_not', description=_('Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords.'), type=int),
|
||||||
OpenApiParameter(name='keywords_or_not', description=_('Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords.'), type=int),
|
OpenApiParameter(name='foods', description=_('ID of food a recipe should have. For multiple repeat parameter.'), type=int),
|
||||||
OpenApiParameter(name='keywords_and_not', description=_('Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords.'), type=int),
|
OpenApiParameter(name='foods_or', description=_('Food IDs, repeat for multiple. Return recipes with any of the foods'), type=int),
|
||||||
OpenApiParameter(name='foods', description=_('ID of food a recipe should have. For multiple repeat parameter.'), type=int),
|
OpenApiParameter(name='foods_and', description=_('Food IDs, repeat for multiple. Return recipes with all of the foods.'), type=int),
|
||||||
OpenApiParameter(name='foods_or', description=_('Food IDs, repeat for multiple. Return recipes with any of the foods'), type=int),
|
OpenApiParameter(name='foods_or_not', description=_('Food IDs, repeat for multiple. Exclude recipes with any of the foods.'), type=int),
|
||||||
OpenApiParameter(name='foods_and', description=_('Food IDs, repeat for multiple. Return recipes with all of the foods.'), type=int),
|
OpenApiParameter(name='foods_and_not', description=_('Food IDs, repeat for multiple. Exclude recipes with all of the foods.'), type=int),
|
||||||
OpenApiParameter(name='foods_or_not', description=_('Food IDs, repeat for multiple. Exclude recipes with any of the foods.'), type=int),
|
OpenApiParameter(name='units', description=_('ID of unit a recipe should have.'), type=int),
|
||||||
OpenApiParameter(name='foods_and_not', description=_('Food IDs, repeat for multiple. Exclude recipes with all of the foods.'), type=int),
|
OpenApiParameter(name='rating', description=_('Rating a recipe should have or greater. [0 - 5] Negative value filters rating less than.'), type=int),
|
||||||
OpenApiParameter(name='units', description=_('ID of unit a recipe should have.'), type=int),
|
OpenApiParameter(name='books', description=_('ID of book a recipe should be in. For multiple repeat parameter.')),
|
||||||
OpenApiParameter(name='rating', description=_('Rating a recipe should have or greater. [0 - 5] Negative value filters rating less than.'), type=int),
|
OpenApiParameter(name='books_or', description=_('Book IDs, repeat for multiple. Return recipes with any of the books'), type=int),
|
||||||
OpenApiParameter(name='books', description=_('ID of book a recipe should be in. For multiple repeat parameter.')),
|
OpenApiParameter(name='books_and', description=_('Book IDs, repeat for multiple. Return recipes with all of the books.'), type=int),
|
||||||
OpenApiParameter(name='books_or', description=_('Book IDs, repeat for multiple. Return recipes with any of the books'), type=int),
|
OpenApiParameter(name='books_or_not', description=_('Book IDs, repeat for multiple. Exclude recipes with any of the books.'), type=int),
|
||||||
OpenApiParameter(name='books_and', description=_('Book IDs, repeat for multiple. Return recipes with all of the books.'), type=int),
|
OpenApiParameter(name='books_and_not', description=_('Book IDs, repeat for multiple. Exclude recipes with all of the books.'), type=int),
|
||||||
OpenApiParameter(name='books_or_not', description=_('Book IDs, repeat for multiple. Exclude recipes with any of the books.'), type=int),
|
OpenApiParameter(name='internal', description=_('If only internal recipes should be returned. ['
|
||||||
OpenApiParameter(name='books_and_not', description=_('Book IDs, repeat for multiple. Exclude recipes with all of the books.'), type=int),
|
'true'
|
||||||
OpenApiParameter(name='internal', description=_('If only internal recipes should be returned. [''true''/''<b>false</b>'']')),
|
'/'
|
||||||
OpenApiParameter(name='random', description=_('Returns the results in randomized order. [''true''/''<b>false</b>'']')),
|
'<b>false</b>'
|
||||||
OpenApiParameter(name='new', description=_('Returns new results first in search results. [''true''/''<b>false</b>'']')),
|
']')),
|
||||||
OpenApiParameter(name='timescooked', description=_('Filter recipes cooked X times or more. Negative values returns cooked less than X times'), type=int),
|
OpenApiParameter(name='random', description=_('Returns the results in randomized order. ['
|
||||||
OpenApiParameter(name='cookedon', description=_('Filter recipes last cooked on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
|
'true'
|
||||||
OpenApiParameter(name='createdon', description=_('Filter recipes created on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
|
'/'
|
||||||
OpenApiParameter(name='updatedon', description=_('Filter recipes updated on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
|
'<b>false</b>'
|
||||||
OpenApiParameter(name='viewedon', description=_('Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
|
']')),
|
||||||
OpenApiParameter(name='makenow', description=_('Filter recipes that can be made with OnHand food. [''true''/''<b>false</b>'']')),
|
OpenApiParameter(name='new', description=_('Returns new results first in search results. ['
|
||||||
]
|
'true'
|
||||||
)
|
'/'
|
||||||
)
|
'<b>false</b>'
|
||||||
|
']')),
|
||||||
|
OpenApiParameter(name='timescooked', description=_('Filter recipes cooked X times or more. Negative values returns cooked less than X times'), type=int),
|
||||||
|
OpenApiParameter(name='cookedon', description=_('Filter recipes last cooked on or after YYYY-MM-DD. Prepending '
|
||||||
|
'-'
|
||||||
|
' filters on or before date.')),
|
||||||
|
OpenApiParameter(name='createdon', description=_('Filter recipes created on or after YYYY-MM-DD. Prepending '
|
||||||
|
'-'
|
||||||
|
' filters on or before date.')),
|
||||||
|
OpenApiParameter(name='updatedon', description=_('Filter recipes updated on or after YYYY-MM-DD. Prepending '
|
||||||
|
'-'
|
||||||
|
' filters on or before date.')),
|
||||||
|
OpenApiParameter(name='viewedon', description=_('Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending '
|
||||||
|
'-'
|
||||||
|
' filters on or before date.')),
|
||||||
|
OpenApiParameter(name='makenow', description=_('Filter recipes that can be made with OnHand food. ['
|
||||||
|
'true'
|
||||||
|
'/'
|
||||||
|
'<b>false</b>'
|
||||||
|
']')),
|
||||||
|
]))
|
||||||
class RecipeViewSet(viewsets.ModelViewSet):
|
class RecipeViewSet(viewsets.ModelViewSet):
|
||||||
queryset = Recipe.objects
|
queryset = Recipe.objects
|
||||||
serializer_class = RecipeSerializer
|
serializer_class = RecipeSerializer
|
||||||
@@ -1124,15 +1110,8 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
|||||||
qs = obj.get_related_recipes(levels=levels) # TODO: make levels a user setting, included in request data?, keep solely in the backend?
|
qs = obj.get_related_recipes(levels=levels) # TODO: make levels a user setting, included in request data?, keep solely in the backend?
|
||||||
return Response(self.serializer_class(qs, many=True).data)
|
return Response(self.serializer_class(qs, many=True).data)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(responses=RecipeFlatSerializer(many=True))
|
||||||
responses=RecipeFlatSerializer(many=True)
|
@decorators.action(detail=False, pagination_class=None, methods=['GET'], serializer_class=RecipeFlatSerializer, )
|
||||||
)
|
|
||||||
@decorators.action(
|
|
||||||
detail=False,
|
|
||||||
pagination_class=None,
|
|
||||||
methods=['GET'],
|
|
||||||
serializer_class=RecipeFlatSerializer,
|
|
||||||
)
|
|
||||||
def flat(self, request):
|
def flat(self, request):
|
||||||
# TODO limit fields retrieved but .values() kills image
|
# TODO limit fields retrieved but .values() kills image
|
||||||
qs = Recipe.objects.filter(space=request.space).filter(Q(private=False) | (Q(private=True) & (Q(created_by=self.request.user) | Q(shared=self.request.user)))).all()
|
qs = Recipe.objects.filter(space=request.space).filter(Q(private=False) | (Q(private=True) & (Q(created_by=self.request.user) | Q(shared=self.request.user)))).all()
|
||||||
@@ -1140,13 +1119,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
|||||||
return Response(self.serializer_class(qs, many=True).data)
|
return Response(self.serializer_class(qs, many=True).data)
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(
|
@extend_schema_view(list=extend_schema(parameters=[OpenApiParameter(name='food_id', description='ID of food to filter for', type=int), ]))
|
||||||
list=extend_schema(
|
|
||||||
parameters=[
|
|
||||||
OpenApiParameter(name='food_id', description='ID of food to filter for', type=int),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
class UnitConversionViewSet(viewsets.ModelViewSet):
|
class UnitConversionViewSet(viewsets.ModelViewSet):
|
||||||
queryset = UnitConversion.objects
|
queryset = UnitConversion.objects
|
||||||
serializer_class = UnitConversionSerializer
|
serializer_class = UnitConversionSerializer
|
||||||
@@ -1185,26 +1158,28 @@ class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.queryset = self.queryset.filter(Q(entries__space=self.request.space) | Q(recipe__space=self.request.space))
|
self.queryset = self.queryset.filter(Q(entries__space=self.request.space) | Q(recipe__space=self.request.space))
|
||||||
return self.queryset.filter(
|
return self.queryset.filter(Q(entries__isnull=True)
|
||||||
Q(entries__isnull=True)
|
| Q(entries__created_by=self.request.user)
|
||||||
| Q(entries__created_by=self.request.user)
|
| Q(entries__created_by__in=list(self.request.user.get_shopping_share()))).distinct().all()
|
||||||
| Q(entries__created_by__in=list(self.request.user.get_shopping_share()))
|
|
||||||
).distinct().all()
|
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(
|
@extend_schema_view(list=extend_schema(parameters=[
|
||||||
list=extend_schema(
|
OpenApiParameter(name='id', description=_('Returns the shopping list entry with a primary key of id. Multiple values allowed.'), type=int),
|
||||||
parameters=[
|
OpenApiParameter(name='checked',
|
||||||
OpenApiParameter(name='id', description=_('Returns the shopping list entry with a primary key of id. Multiple values allowed.'), type=int),
|
description=_('Filter shopping list entries on checked. ['
|
||||||
OpenApiParameter(
|
'true'
|
||||||
name='checked',
|
', '
|
||||||
description=_('Filter shopping list entries on checked. [''true'', ''false'', ''both'', ''<b>recent</b>'']<br> \
|
'false'
|
||||||
- ''recent'' includes unchecked items and recently completed items.')
|
', '
|
||||||
),
|
'both'
|
||||||
OpenApiParameter(name='supermarket', description=_('Returns the shopping list entries sorted by supermarket category order.'), type=int),
|
', '
|
||||||
]
|
'<b>recent</b>'
|
||||||
)
|
']<br> \
|
||||||
)
|
- '
|
||||||
|
'recent'
|
||||||
|
' includes unchecked items and recently completed items.')),
|
||||||
|
OpenApiParameter(name='supermarket', description=_('Returns the shopping list entries sorted by supermarket category order.'), type=int),
|
||||||
|
]))
|
||||||
class ShoppingListEntryViewSet(viewsets.ModelViewSet):
|
class ShoppingListEntryViewSet(viewsets.ModelViewSet):
|
||||||
queryset = ShoppingListEntry.objects
|
queryset = ShoppingListEntry.objects
|
||||||
serializer_class = ShoppingListEntrySerializer
|
serializer_class = ShoppingListEntrySerializer
|
||||||
@@ -1251,9 +1226,9 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
|
|||||||
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
print(serializer.validated_data)
|
print(serializer.validated_data)
|
||||||
bulk_entries = ShoppingListEntry.objects.filter(
|
bulk_entries = ShoppingListEntry.objects.filter(Q(created_by=self.request.user)
|
||||||
Q(created_by=self.request.user) | Q(created_by__in=list(self.request.user.get_shopping_share()))
|
| Q(created_by__in=list(self.request.user.get_shopping_share()))).filter(space=request.space,
|
||||||
).filter(space=request.space, id__in=serializer.validated_data['ids'])
|
id__in=serializer.validated_data['ids'])
|
||||||
bulk_entries.update(checked=(checked := serializer.validated_data['checked']), updated_at=timezone.now(), )
|
bulk_entries.update(checked=(checked := serializer.validated_data['checked']), updated_at=timezone.now(), )
|
||||||
|
|
||||||
# update the onhand for food if shopping_add_onhand is True
|
# update the onhand for food if shopping_add_onhand is True
|
||||||
@@ -1282,13 +1257,7 @@ class ViewLogViewSet(viewsets.ModelViewSet):
|
|||||||
return self.queryset.filter(created_by=self.request.user).filter(space=self.request.space)
|
return self.queryset.filter(created_by=self.request.user).filter(space=self.request.space)
|
||||||
|
|
||||||
|
|
||||||
@extend_schema_view(
|
@extend_schema_view(list=extend_schema(parameters=[OpenApiParameter(name='recipe', description='Filter for entries with the given recipe', type=int), ]))
|
||||||
list=extend_schema(
|
|
||||||
parameters=[
|
|
||||||
OpenApiParameter(name='recipe', description='Filter for entries with the given recipe', type=int),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
class CookLogViewSet(viewsets.ModelViewSet):
|
class CookLogViewSet(viewsets.ModelViewSet):
|
||||||
queryset = CookLog.objects
|
queryset = CookLog.objects
|
||||||
serializer_class = CookLogSerializer
|
serializer_class = CookLogSerializer
|
||||||
@@ -1386,10 +1355,7 @@ class AutomationViewSet(StandardFilterModelViewSet):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
parameters=[
|
parameters=[OpenApiParameter(name='automation_type', description=_('Return the Automations matching the automation type. Multiple values allowed.'), type=str)])
|
||||||
OpenApiParameter(name='automation_type', description=_('Return the Automations matching the automation type. Multiple values allowed.'), type=str)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
def list(self, request, *args, **kwargs):
|
def list(self, request, *args, **kwargs):
|
||||||
return super().list(request, *args, **kwargs)
|
return super().list(request, *args, **kwargs)
|
||||||
|
|
||||||
@@ -1552,7 +1518,7 @@ class RecipeUrlImportView(APIView):
|
|||||||
'recipe_json': helper.get_from_scraper(scrape, request),
|
'recipe_json': helper.get_from_scraper(scrape, request),
|
||||||
'recipe_images': list(dict.fromkeys(get_images_from_soup(scrape.soup, url))),
|
'recipe_images': list(dict.fromkeys(get_images_from_soup(scrape.soup, url))),
|
||||||
},
|
},
|
||||||
status=status.HTTP_200_OK)
|
status=status.HTTP_200_OK)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return Response({'error': True, 'msg': _('No usable data could be found.')}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'error': True, 'msg': _('No usable data could be found.')}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
@@ -1763,7 +1729,7 @@ def sync_all(request):
|
|||||||
# @schema(AutoSchema()) #TODO add proper schema
|
# @schema(AutoSchema()) #TODO add proper schema
|
||||||
@permission_classes([CustomIsUser & CustomTokenHasReadWriteScope])
|
@permission_classes([CustomIsUser & CustomTokenHasReadWriteScope])
|
||||||
def share_link(request, pk):
|
def share_link(request, pk):
|
||||||
if request.space.allow_sharing and has_group_permission(request.user, ('user',)):
|
if request.space.allow_sharing and has_group_permission(request.user, ('user', )):
|
||||||
recipe = get_object_or_404(Recipe, pk=pk, space=request.space)
|
recipe = get_object_or_404(Recipe, pk=pk, space=request.space)
|
||||||
link = ShareLink.objects.create(recipe=recipe, created_by=request.user, space=request.space)
|
link = ShareLink.objects.create(recipe=recipe, created_by=request.user, space=request.space)
|
||||||
return JsonResponse({'pk': pk, 'share': link.uuid, 'link': request.build_absolute_uri(reverse('view_recipe', args=[pk, link.uuid]))})
|
return JsonResponse({'pk': pk, 'share': link.uuid, 'link': request.build_absolute_uri(reverse('view_recipe', args=[pk, link.uuid]))})
|
||||||
|
|||||||
Reference in New Issue
Block a user