mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-07 23:28:16 -05:00
drf spectacular
This commit is contained in:
@@ -2,13 +2,13 @@ from pydoc import locate
|
|||||||
|
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView
|
||||||
from rest_framework import permissions, routers
|
from rest_framework import permissions, routers
|
||||||
from rest_framework.schemas import get_schema_view
|
from rest_framework.schemas import get_schema_view
|
||||||
|
|
||||||
from cookbook.version_info import TANDOOR_VERSION
|
from cookbook.version_info import TANDOOR_VERSION
|
||||||
from recipes.settings import DEBUG, PLUGINS
|
from recipes.settings import DEBUG, PLUGINS
|
||||||
|
|
||||||
|
|
||||||
from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, PropertyType,
|
from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, PropertyType,
|
||||||
Recipe, RecipeBook, RecipeBookEntry, RecipeImport, Space, Step,
|
Recipe, RecipeBook, RecipeBookEntry, RecipeImport, Space, Step,
|
||||||
Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, UnitConversion,
|
Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, UnitConversion,
|
||||||
@@ -135,8 +135,10 @@ urlpatterns = [
|
|||||||
path('telegram/hook/<slug:token>/', telegram.hook, name='telegram_hook'),
|
path('telegram/hook/<slug:token>/', telegram.hook, name='telegram_hook'),
|
||||||
path('docs/markdown/', views.markdown_info, name='docs_markdown'),
|
path('docs/markdown/', views.markdown_info, name='docs_markdown'),
|
||||||
path('docs/search/', views.search_info, name='docs_search'),
|
path('docs/search/', views.search_info, name='docs_search'),
|
||||||
path('docs/api/', views.api_info, name='docs_api'),
|
path('docs/api/', SpectacularRedocView.as_view(url_name='openapi-schema'), name='docs_api'),
|
||||||
path('openapi/', get_schema_view(title="Django Recipes", version=TANDOOR_VERSION, public=True, permission_classes=(permissions.AllowAny, )), name='openapi-schema'),
|
|
||||||
|
path('openapi/', SpectacularAPIView.as_view(api_version=TANDOOR_VERSION), name='openapi-schema'),
|
||||||
|
|
||||||
path('api/', include((router.urls, 'api'))),
|
path('api/', include((router.urls, 'api'))),
|
||||||
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||||
path('api-token-auth/', CustomAuthToken.as_view()),
|
path('api-token-auth/', CustomAuthToken.as_view()),
|
||||||
@@ -147,13 +149,11 @@ urlpatterns = [
|
|||||||
path('manifest.json', views.web_manifest, name='web_manifest'),
|
path('manifest.json', views.web_manifest, name='web_manifest'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
generic_models = (
|
generic_models = (
|
||||||
Recipe, RecipeImport, Storage, ConnectorConfig, RecipeBook, SyncLog, Sync,
|
Recipe, RecipeImport, Storage, ConnectorConfig, RecipeBook, SyncLog, Sync,
|
||||||
Comment, RecipeBookEntry, InviteLink, UserSpace, Space
|
Comment, RecipeBookEntry, InviteLink, UserSpace, Space
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
for m in generic_models:
|
for m in generic_models:
|
||||||
py_name = get_model_name(m)
|
py_name = get_model_name(m)
|
||||||
url_name = py_name.replace('_', '-')
|
url_name = py_name.replace('_', '-')
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ from django.urls import reverse
|
|||||||
from django.utils import timezone
|
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.utils import extend_schema, OpenApiParameter, 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 recipe_scrapers import scrape_me
|
from recipe_scrapers import scrape_me
|
||||||
@@ -103,7 +104,17 @@ 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
|
||||||
|
|
||||||
|
|
||||||
class StandardFilterMixin(ViewSetMixin):
|
@extend_schema_view(
|
||||||
|
list=extend_schema(
|
||||||
|
parameters=[
|
||||||
|
OpenApiParameter(name='query', description='lookup if query string is contained within the name, case insensitive', 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='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):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = self.queryset
|
queryset = self.queryset
|
||||||
@@ -171,8 +182,14 @@ class ExtendedRecipeMixin():
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
# @extend_schema_view(
|
||||||
|
# list=extend_schema(
|
||||||
|
# parameters=[
|
||||||
|
# OpenApiParameter(name='query', description='Match name field against against parameter', type=str)
|
||||||
|
# ]
|
||||||
|
# )
|
||||||
|
# )
|
||||||
class FuzzyFilterMixin(ViewSetMixin, ExtendedRecipeMixin):
|
class FuzzyFilterMixin(ViewSetMixin, ExtendedRecipeMixin):
|
||||||
schema = FilterSchema()
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.queryset = self.queryset.filter(space=self.request.space).order_by(Lower('name').asc())
|
self.queryset = self.queryset.filter(space=self.request.space).order_by(Lower('name').asc())
|
||||||
@@ -296,7 +313,7 @@ class MergeMixin(ViewSetMixin):
|
|||||||
|
|
||||||
|
|
||||||
class TreeMixin(MergeMixin, FuzzyFilterMixin, ExtendedRecipeMixin):
|
class TreeMixin(MergeMixin, FuzzyFilterMixin, ExtendedRecipeMixin):
|
||||||
schema = TreeSchema()
|
# schema = TreeSchema()
|
||||||
model = None
|
model = None
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@@ -485,8 +502,7 @@ class SyncLogViewSet(viewsets.ReadOnlyModelViewSet):
|
|||||||
return self.queryset.filter(sync__space=self.request.space)
|
return self.queryset.filter(sync__space=self.request.space)
|
||||||
|
|
||||||
|
|
||||||
class SupermarketViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
class SupermarketViewSet(StandardFilterModelViewSet):
|
||||||
schema = FilterSchema()
|
|
||||||
queryset = Supermarket.objects
|
queryset = Supermarket.objects
|
||||||
serializer_class = SupermarketSerializer
|
serializer_class = SupermarketSerializer
|
||||||
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
||||||
@@ -507,7 +523,7 @@ class SupermarketCategoryViewSet(viewsets.ModelViewSet, FuzzyFilterMixin, MergeM
|
|||||||
return super().get_queryset()
|
return super().get_queryset()
|
||||||
|
|
||||||
|
|
||||||
class SupermarketCategoryRelationViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
class SupermarketCategoryRelationViewSet(StandardFilterModelViewSet):
|
||||||
queryset = SupermarketCategoryRelation.objects
|
queryset = SupermarketCategoryRelation.objects
|
||||||
serializer_class = SupermarketCategoryRelationSerializer
|
serializer_class = SupermarketCategoryRelationSerializer
|
||||||
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
||||||
@@ -682,7 +698,7 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
|
|||||||
return Response(content, status=status.HTTP_403_FORBIDDEN)
|
return Response(content, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
|
||||||
class RecipeBookViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
class RecipeBookViewSet(StandardFilterModelViewSet):
|
||||||
queryset = RecipeBook.objects
|
queryset = RecipeBook.objects
|
||||||
serializer_class = RecipeBookSerializer
|
serializer_class = RecipeBookSerializer
|
||||||
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
|
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
|
||||||
@@ -726,6 +742,15 @@ class RecipeBookEntryViewSet(viewsets.ModelViewSet, viewsets.GenericViewSet):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
list=extend_schema(
|
||||||
|
parameters=[
|
||||||
|
OpenApiParameter(name='from_date', description=_('Filter meal plans from date (inclusive) in the format of YYYY-MM-DD.'), type=str),
|
||||||
|
OpenApiParameter(name='to_date', description=_('Filter meal plans to date (inclusive) in the format of YYYY-MM-DD.'), type=str),
|
||||||
|
OpenApiParameter(name='meal_type', description=_('Filter meal plans with MealType ID. For multiple repeat parameter.'), type=str),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
class MealPlanViewSet(viewsets.ModelViewSet):
|
class MealPlanViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
list:
|
list:
|
||||||
@@ -739,12 +764,6 @@ class MealPlanViewSet(viewsets.ModelViewSet):
|
|||||||
queryset = MealPlan.objects
|
queryset = MealPlan.objects
|
||||||
serializer_class = MealPlanSerializer
|
serializer_class = MealPlanSerializer
|
||||||
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
|
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
|
||||||
query_params = [
|
|
||||||
QueryParam(name='from_date', description=_('Filter meal plans from date (inclusive) in the format of YYYY-MM-DD.'), qtype='string'),
|
|
||||||
QueryParam(name='to_date', description=_('Filter meal plans to date (inclusive) in the format of YYYY-MM-DD.'), qtype='string'),
|
|
||||||
QueryParam(name='meal_type', description=_('Filter meal plans with MealType ID. For multiple repeat parameter.'), qtype='integer'),
|
|
||||||
]
|
|
||||||
schema = QueryParamAutoSchema()
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(space=self.request.space).distinct().all()
|
queryset = self.queryset.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).filter(space=self.request.space).distinct().all()
|
||||||
@@ -864,16 +883,19 @@ class IngredientViewSet(viewsets.ModelViewSet):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema_view(
|
||||||
|
list=extend_schema(
|
||||||
|
parameters=[
|
||||||
|
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
|
||||||
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
||||||
pagination_class = DefaultPagination
|
pagination_class = DefaultPagination
|
||||||
query_params = [
|
|
||||||
QueryParam(name='recipe', description=_('ID of recipe a step is part of. For multiple repeat parameter.'), qtype='integer'),
|
|
||||||
QueryParam(name='query', description=_('Query string matched (fuzzy) against object name.'), qtype='string'),
|
|
||||||
]
|
|
||||||
schema = QueryParamAutoSchema()
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
recipes = self.request.query_params.getlist('recipe', [])
|
recipes = self.request.query_params.getlist('recipe', [])
|
||||||
@@ -899,6 +921,39 @@ 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(
|
||||||
|
list=extend_schema(
|
||||||
|
parameters=[
|
||||||
|
OpenApiParameter(name='query', description=_('Query string matched (fuzzy) against recipe name. In the future also fulltext search.')),
|
||||||
|
OpenApiParameter(name='keywords', description=_('ID of keyword a recipe should have. For multiple repeat parameter. Equivalent to keywords_or'), type=int),
|
||||||
|
OpenApiParameter(name='keywords_or', description=_('Keyword IDs, repeat for multiple. Return 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_or_not', description=_('Keyword IDs, repeat for multiple. Exclude recipes with any 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='foods', description=_('ID of food a recipe should have. For multiple repeat parameter.'), type=int),
|
||||||
|
OpenApiParameter(name='foods_or', description=_('Food IDs, repeat for multiple. Return 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_or_not', description=_('Food IDs, repeat for multiple. Exclude recipes with any 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='units', description=_('ID of unit a recipe should have.'), 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='books', description=_('ID of book a recipe should be in. For multiple repeat parameter.')),
|
||||||
|
OpenApiParameter(name='books_or', description=_('Book IDs, repeat for multiple. Return 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_or_not', description=_('Book IDs, repeat for multiple. Exclude recipes with any 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='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>'']')),
|
||||||
|
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
|
||||||
@@ -906,37 +961,6 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
|||||||
permission_classes = [CustomRecipePermission & CustomTokenHasReadWriteScope]
|
permission_classes = [CustomRecipePermission & CustomTokenHasReadWriteScope]
|
||||||
pagination_class = RecipePagination
|
pagination_class = RecipePagination
|
||||||
|
|
||||||
query_params = [
|
|
||||||
QueryParam(name='query', description=_('Query string matched (fuzzy) against recipe name. In the future also fulltext search.')),
|
|
||||||
QueryParam(name='keywords', description=_('ID of keyword a recipe should have. For multiple repeat parameter. Equivalent to keywords_or'), qtype='integer'),
|
|
||||||
QueryParam(name='keywords_or', description=_('Keyword IDs, repeat for multiple. Return recipes with any of the keywords'), qtype='integer'),
|
|
||||||
QueryParam(name='keywords_and', description=_('Keyword IDs, repeat for multiple. Return recipes with all of the keywords.'), qtype='integer'),
|
|
||||||
QueryParam(name='keywords_or_not', description=_('Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords.'), qtype='integer'),
|
|
||||||
QueryParam(name='keywords_and_not', description=_('Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords.'), qtype='integer'),
|
|
||||||
QueryParam(name='foods', description=_('ID of food a recipe should have. For multiple repeat parameter.'), qtype='integer'),
|
|
||||||
QueryParam(name='foods_or', description=_('Food IDs, repeat for multiple. Return recipes with any of the foods'), qtype='integer'),
|
|
||||||
QueryParam(name='foods_and', description=_('Food IDs, repeat for multiple. Return recipes with all of the foods.'), qtype='integer'),
|
|
||||||
QueryParam(name='foods_or_not', description=_('Food IDs, repeat for multiple. Exclude recipes with any of the foods.'), qtype='integer'),
|
|
||||||
QueryParam(name='foods_and_not', description=_('Food IDs, repeat for multiple. Exclude recipes with all of the foods.'), qtype='integer'),
|
|
||||||
QueryParam(name='units', description=_('ID of unit a recipe should have.'), qtype='integer'),
|
|
||||||
QueryParam(name='rating', description=_('Rating a recipe should have or greater. [0 - 5] Negative value filters rating less than.'), qtype='integer'),
|
|
||||||
QueryParam(name='books', description=_('ID of book a recipe should be in. For multiple repeat parameter.')),
|
|
||||||
QueryParam(name='books_or', description=_('Book IDs, repeat for multiple. Return recipes with any of the books'), qtype='integer'),
|
|
||||||
QueryParam(name='books_and', description=_('Book IDs, repeat for multiple. Return recipes with all of the books.'), qtype='integer'),
|
|
||||||
QueryParam(name='books_or_not', description=_('Book IDs, repeat for multiple. Exclude recipes with any of the books.'), qtype='integer'),
|
|
||||||
QueryParam(name='books_and_not', description=_('Book IDs, repeat for multiple. Exclude recipes with all of the books.'), qtype='integer'),
|
|
||||||
QueryParam(name='internal', description=_('If only internal recipes should be returned. [''true''/''<b>false</b>'']')),
|
|
||||||
QueryParam(name='random', description=_('Returns the results in randomized order. [''true''/''<b>false</b>'']')),
|
|
||||||
QueryParam(name='new', description=_('Returns new results first in search results. [''true''/''<b>false</b>'']')),
|
|
||||||
QueryParam(name='timescooked', description=_('Filter recipes cooked X times or more. Negative values returns cooked less than X times'), qtype='integer'),
|
|
||||||
QueryParam(name='cookedon', description=_('Filter recipes last cooked on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
|
|
||||||
QueryParam(name='createdon', description=_('Filter recipes created on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
|
|
||||||
QueryParam(name='updatedon', description=_('Filter recipes updated on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
|
|
||||||
QueryParam(name='viewedon', description=_('Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')),
|
|
||||||
QueryParam(name='makenow', description=_('Filter recipes that can be made with OnHand food. [''true''/''<b>false</b>'']')),
|
|
||||||
]
|
|
||||||
schema = QueryParamAutoSchema()
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
share = self.request.query_params.get('share', None)
|
share = self.request.query_params.get('share', None)
|
||||||
|
|
||||||
@@ -1077,14 +1101,17 @@ 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(
|
||||||
|
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
|
||||||
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
||||||
query_params = [
|
|
||||||
QueryParam(name='food_id', description='ID of food to filter for', qtype='integer'),
|
|
||||||
]
|
|
||||||
schema = QueryParamAutoSchema()
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
food_id = self.request.query_params.get('food_id', None)
|
food_id = self.request.query_params.get('food_id', None)
|
||||||
@@ -1126,20 +1153,23 @@ class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
|
|||||||
).distinct().all()
|
).distinct().all()
|
||||||
|
|
||||||
|
|
||||||
class ShoppingListEntryViewSet(viewsets.ModelViewSet):
|
@extend_schema_view(
|
||||||
queryset = ShoppingListEntry.objects
|
list=extend_schema(
|
||||||
serializer_class = ShoppingListEntrySerializer
|
parameters=[
|
||||||
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
|
OpenApiParameter(name='id', description=_('Returns the shopping list entry with a primary key of id. Multiple values allowed.'), type=int),
|
||||||
query_params = [
|
OpenApiParameter(
|
||||||
QueryParam(name='id', description=_('Returns the shopping list entry with a primary key of id. Multiple values allowed.'), qtype='integer'),
|
|
||||||
QueryParam(
|
|
||||||
name='checked',
|
name='checked',
|
||||||
description=_('Filter shopping list entries on checked. [''true'', ''false'', ''both'', ''<b>recent</b>'']<br> \
|
description=_('Filter shopping list entries on checked. [''true'', ''false'', ''both'', ''<b>recent</b>'']<br> \
|
||||||
- ''recent'' includes unchecked items and recently completed items.')
|
- ''recent'' includes unchecked items and recently completed items.')
|
||||||
),
|
),
|
||||||
QueryParam(name='supermarket', description=_('Returns the shopping list entries sorted by supermarket category order.'), qtype='integer'),
|
OpenApiParameter(name='supermarket', description=_('Returns the shopping list entries sorted by supermarket category order.'), type=int),
|
||||||
]
|
]
|
||||||
schema = QueryParamAutoSchema()
|
)
|
||||||
|
)
|
||||||
|
class ShoppingListEntryViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = ShoppingListEntry.objects
|
||||||
|
serializer_class = ShoppingListEntrySerializer
|
||||||
|
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
self.queryset = self.queryset.filter(space=self.request.space)
|
self.queryset = self.queryset.filter(space=self.request.space)
|
||||||
@@ -1251,8 +1281,7 @@ class BookmarkletImportViewSet(viewsets.ModelViewSet):
|
|||||||
return self.queryset.filter(space=self.request.space).all()
|
return self.queryset.filter(space=self.request.space).all()
|
||||||
|
|
||||||
|
|
||||||
class UserFileViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
class UserFileViewSet(StandardFilterModelViewSet):
|
||||||
schema = FilterSchema()
|
|
||||||
queryset = UserFile.objects
|
queryset = UserFile.objects
|
||||||
serializer_class = UserFileSerializer
|
serializer_class = UserFileSerializer
|
||||||
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
||||||
@@ -1263,7 +1292,7 @@ class UserFileViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
|||||||
return super().get_queryset()
|
return super().get_queryset()
|
||||||
|
|
||||||
|
|
||||||
class AutomationViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
class AutomationViewSet(StandardFilterModelViewSet):
|
||||||
"""
|
"""
|
||||||
list:
|
list:
|
||||||
optional parameters
|
optional parameters
|
||||||
@@ -1288,11 +1317,6 @@ class AutomationViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
|||||||
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
||||||
pagination_class = DefaultPagination
|
pagination_class = DefaultPagination
|
||||||
|
|
||||||
query_params = [
|
|
||||||
QueryParam(name='automation_type', description=_('Return the Automations matching the automation type. Multiple values allowed.'), qtype='string'),
|
|
||||||
]
|
|
||||||
schema = QueryParamAutoSchema()
|
|
||||||
|
|
||||||
auto_type = {
|
auto_type = {
|
||||||
'FS': 'FOOD_ALIAS',
|
'FS': 'FOOD_ALIAS',
|
||||||
'UA': 'UNIT_ALIAS',
|
'UA': 'UNIT_ALIAS',
|
||||||
@@ -1306,6 +1330,14 @@ class AutomationViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
|||||||
'NR': 'NAME_REPLACE'
|
'NR': 'NAME_REPLACE'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
parameters=[
|
||||||
|
OpenApiParameter(name='automation_type', description=_('Return the Automations matching the automation type. Multiple values allowed.'), type=str)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
return super().list(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
automation_type = self.request.query_params.getlist('automation_type', [])
|
automation_type = self.request.query_params.getlist('automation_type', [])
|
||||||
if automation_type:
|
if automation_type:
|
||||||
@@ -1314,7 +1346,7 @@ class AutomationViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
|||||||
return super().get_queryset()
|
return super().get_queryset()
|
||||||
|
|
||||||
|
|
||||||
class InviteLinkViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
class InviteLinkViewSet(StandardFilterModelViewSet):
|
||||||
queryset = InviteLink.objects
|
queryset = InviteLink.objects
|
||||||
serializer_class = InviteLinkSerializer
|
serializer_class = InviteLinkSerializer
|
||||||
permission_classes = [CustomIsSpaceOwner & CustomIsAdmin & CustomTokenHasReadWriteScope]
|
permission_classes = [CustomIsSpaceOwner & CustomIsAdmin & CustomTokenHasReadWriteScope]
|
||||||
@@ -1332,7 +1364,7 @@ class InviteLinkViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class CustomFilterViewSet(viewsets.ModelViewSet, StandardFilterMixin):
|
class CustomFilterViewSet(StandardFilterModelViewSet):
|
||||||
queryset = CustomFilter.objects
|
queryset = CustomFilter.objects
|
||||||
serializer_class = CustomFilterSerializer
|
serializer_class = CustomFilterSerializer
|
||||||
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
|
permission_classes = [CustomIsOwner & CustomTokenHasReadWriteScope]
|
||||||
|
|||||||
@@ -105,10 +105,34 @@ MESSAGE_TAGS = {messages.ERROR: 'danger'}
|
|||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages',
|
'django.contrib.admin',
|
||||||
'django.contrib.sites', 'django.contrib.staticfiles', 'django.contrib.postgres', 'oauth2_provider', 'django_prometheus', 'django_tables2', 'corsheaders', 'crispy_forms',
|
'django.contrib.auth',
|
||||||
'crispy_bootstrap4', 'rest_framework', 'rest_framework.authtoken', 'django_cleanup.apps.CleanupConfig', 'webpack_loader', 'django_vite', 'django_js_reverse', 'hcaptcha', 'allauth',
|
'django.contrib.contenttypes',
|
||||||
'allauth.account', 'allauth.socialaccount', 'cookbook.apps.CookbookConfig', 'treebeard',
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.sites',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'django.contrib.postgres',
|
||||||
|
'oauth2_provider',
|
||||||
|
'django_prometheus',
|
||||||
|
'django_tables2',
|
||||||
|
'corsheaders',
|
||||||
|
'crispy_forms',
|
||||||
|
'crispy_bootstrap4',
|
||||||
|
'rest_framework',
|
||||||
|
'rest_framework.authtoken',
|
||||||
|
'drf_spectacular',
|
||||||
|
'drf_spectacular_sidecar',
|
||||||
|
'django_cleanup.apps.CleanupConfig',
|
||||||
|
'webpack_loader',
|
||||||
|
'django_vite',
|
||||||
|
'django_js_reverse',
|
||||||
|
'hcaptcha',
|
||||||
|
'allauth',
|
||||||
|
'allauth.account',
|
||||||
|
'allauth.socialaccount',
|
||||||
|
'cookbook.apps.CookbookConfig',
|
||||||
|
'treebeard',
|
||||||
]
|
]
|
||||||
|
|
||||||
PLUGINS_DIRECTORY = os.path.join(BASE_DIR, 'recipes', 'plugins')
|
PLUGINS_DIRECTORY = os.path.join(BASE_DIR, 'recipes', 'plugins')
|
||||||
@@ -273,6 +297,7 @@ REST_FRAMEWORK = {
|
|||||||
('rest_framework.authentication.SessionAuthentication', 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', 'rest_framework.authentication.BasicAuthentication',
|
('rest_framework.authentication.SessionAuthentication', 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', 'rest_framework.authentication.BasicAuthentication',
|
||||||
),
|
),
|
||||||
'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated', ],
|
'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated', ],
|
||||||
|
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
|
||||||
}
|
}
|
||||||
|
|
||||||
ROOT_URLCONF = 'recipes.urls'
|
ROOT_URLCONF = 'recipes.urls'
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ crispy-bootstrap4==2022.1
|
|||||||
django-tables2==2.7.0
|
django-tables2==2.7.0
|
||||||
djangorestframework==3.14.0
|
djangorestframework==3.14.0
|
||||||
drf-writable-nested==0.7.0
|
drf-writable-nested==0.7.0
|
||||||
|
drf-spectacular==0.27.1
|
||||||
|
drf-spectacular-sidecar==2024.2.1
|
||||||
django-oauth-toolkit==2.3.0
|
django-oauth-toolkit==2.3.0
|
||||||
django-debug-toolbar==4.2.0
|
django-debug-toolbar==4.2.0
|
||||||
bleach==6.0.0
|
bleach==6.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user