drf spectacular

This commit is contained in:
vabene1111
2024-02-29 16:34:13 +01:00
parent 521c71733a
commit e47bdd043e
4 changed files with 154 additions and 95 deletions

View File

@@ -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('_', '-')

View File

@@ -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]

View File

@@ -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'

View File

@@ -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