added permissions to docs/api

added swagger api view
added authentication method to openapi schema
added logo to docs/api
fixed tree and merge schemas
This commit is contained in:
smilerz
2024-03-27 15:45:51 -05:00
parent daf343c5fd
commit 2c12ce3edf
14 changed files with 970 additions and 612 deletions

View File

@@ -160,18 +160,15 @@ class GroupRequiredMixin(object):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if not has_group_permission(request.user, self.groups_required): if not has_group_permission(request.user, self.groups_required):
if not request.user.is_authenticated: if not request.user.is_authenticated:
messages.add_message(request, messages.ERROR, messages.add_message(request, messages.ERROR, _('You are not logged in and therefore cannot view this page!'))
_('You are not logged in and therefore cannot view this page!'))
return HttpResponseRedirect(reverse_lazy('account_login') + '?next=' + request.path) return HttpResponseRedirect(reverse_lazy('account_login') + '?next=' + request.path)
else: else:
messages.add_message(request, messages.ERROR, messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
_('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse_lazy('index')) return HttpResponseRedirect(reverse_lazy('index'))
try: try:
obj = self.get_object() obj = self.get_object()
if obj.get_space() != request.space: if obj.get_space() != request.space:
messages.add_message(request, messages.ERROR, messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
_('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse_lazy('index')) return HttpResponseRedirect(reverse_lazy('index'))
except AttributeError: except AttributeError:
pass pass

View File

@@ -417,7 +417,6 @@ class ConnectorConfig(models.Model, PermissionModelMixin):
objects = ScopedManager(space='space') objects = ScopedManager(space='space')
class UserPreference(models.Model, PermissionModelMixin): class UserPreference(models.Model, PermissionModelMixin):
# Themes # Themes
BOOTSTRAP = 'BOOTSTRAP' BOOTSTRAP = 'BOOTSTRAP'

File diff suppressed because one or more lines are too long

View File

@@ -1,16 +0,0 @@
{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% block title %}{% trans "API Documentation" %}{% endblock %}
{% block extra_head %}
{% endblock %}
{% block content_fluid %}
<redoc spec-url='{% url 'openapi-schema' %}'></redoc>
<script src="{% static 'js/redoc.standalone.js' %}"></script>
{% endblock %}

View File

@@ -370,17 +370,13 @@
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'docs_markdown' %}"><i <a class="dropdown-item" href="{% url 'docs_markdown' %}"><i
class="fab fa-markdown fa-fw"></i> {% trans 'Markdown Guide' %}</a> class="fab fa-markdown fa-fw"></i> {% trans 'Markdown Guide' %}</a>
<a class="dropdown-item" href="https://github.com/vabene1111/recipes"><i <a class="dropdown-item" href="https://github.com/vabene1111/recipes"><i class="fab fa-github fa-fw"></i> {% trans 'GitHub' %}</a>
class="fab fa-github fa-fw"></i> {% trans 'GitHub' %}</a> <a class="dropdown-item" href="https://translate.tandoor.dev/projects/tandoor/"><i class="fas fa-language fa-fw"></i> {% trans 'Translate Tandoor' %}</a>
<a class="dropdown-item" href="https://translate.tandoor.dev/projects/tandoor/"><i <a class="dropdown-item" href="{% url 'docs_api' %}"><i class="fas fa-passport fa-fw"></i> {% trans 'API ReDoc Documentation' %}</a>
class="fas fa-language fa-fw"></i> {% trans 'Translate Tandoor' %}</a> <a class="dropdown-item" href="{% url 'docs_swagger' %}"><i class="fas fa-passport fa-fw"></i> {% trans 'API Swagger Documentation' %}</a>
<a class="dropdown-item" href="{% url 'docs_api' %}"><i <a class="dropdown-item" href="{% url 'api:api-root' %}"><i class="fas fa-file-code fa-fw"></i> {% trans 'API Browser' %}</a>
class="fas fa-passport fa-fw"></i> {% trans 'API Documentation' %}</a>
<a class="dropdown-item" href="{% url 'api:api-root' %}"><i
class="fas fa-file-code fa-fw"></i> {% trans 'API Browser' %}</a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'account_logout' %}"><i <a class="dropdown-item" href="{% url 'account_logout' %}"><i class="fas fa-sign-out-alt fa-fw"></i> {% trans 'Log out' %}</a>
class="fas fa-sign-out-alt fa-fw"></i> {% trans 'Log out' %}</a>
</div> </div>
</li> </li>
{% else %} {% else %}

View File

@@ -2,8 +2,8 @@ from django import template
from django.templatetags.static import static from django.templatetags.static import static
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from cookbook.models import UserPreference, UserFile, Space from cookbook.models import UserPreference, Space
from recipes.settings import STICKY_NAV_PREF_DEFAULT, UNAUTHENTICATED_THEME_FROM_SPACE, FORCE_THEME_FROM_SPACE from recipes.settings import UNAUTHENTICATED_THEME_FROM_SPACE, FORCE_THEME_FROM_SPACE
register = template.Library() register = template.Library()

View File

@@ -119,3 +119,13 @@ def test_markdown_doc(arg, request, ext_recipe_1_s1):
def test_api_info(arg, request, ext_recipe_1_s1): def test_api_info(arg, request, ext_recipe_1_s1):
c = request.getfixturevalue(arg[0]) c = request.getfixturevalue(arg[0])
assert c.get(reverse('docs_api')).status_code == arg[1] assert c.get(reverse('docs_api')).status_code == arg[1]
@pytest.mark.parametrize("arg", [
['a_u', 302],
['g1_s1', 200],
['u1_s1', 200],
['a1_s1', 200],
])
def test_api_swagger(arg, request, ext_recipe_1_s1):
c = request.getfixturevalue(arg[0])
assert c.get(reverse('docs_api')).status_code == arg[1]

View File

@@ -2,9 +2,8 @@ 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 drf_spectacular.views import SpectacularAPIView
from rest_framework import permissions, routers from rest_framework import routers
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
@@ -16,7 +15,7 @@ from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keywor
from .views import api, data, delete, edit, import_export, lists, new, telegram, views from .views import api, data, delete, edit, import_export, lists, new, telegram, views
from .views.api import CustomAuthToken, ImportOpenData from .views.api import CustomAuthToken, ImportOpenData
import datetime
# extend DRF default router class to allow including additional routers # extend DRF default router class to allow including additional routers
class DefaultRouter(routers.DefaultRouter): class DefaultRouter(routers.DefaultRouter):
@@ -137,15 +136,15 @@ 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/', SpectacularRedocView.as_view(url_name='openapi-schema'), name='docs_api'), path('docs/api/', views.Redoc.as_view(url_name='openapi-schema'), name='docs_api'),
path('docs/swagger/', views.Swagger.as_view(url_name='openapi-schema'), name='docs_swagger'),
path('openapi/', SpectacularAPIView.as_view(api_version=TANDOOR_VERSION), 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()),
path('api-import-open-data/', ImportOpenData.as_view(), name='api_import_open_data'), path('api-import-open-data/', ImportOpenData.as_view(), name='api_import_open_data'),
path('offline/', views.offline, name='view_offline'), path('offline/', views.offline, name='view_offline'),
path('service-worker.js', (TemplateView.as_view(template_name="sw.js", content_type='application/javascript', path('service-worker.js', (TemplateView.as_view(template_name="sw.js", content_type='application/javascript', )), name='service_worker'),
)), name='service_worker'),
path('manifest.json', views.web_manifest, name='web_manifest'), path('manifest.json', views.web_manifest, name='web_manifest'),
] ]

View File

@@ -183,8 +183,10 @@ class FuzzyFilterMixin(viewsets.ModelViewSet, 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.model.__name__.lower() in x for x in self.request.user.searchpreference.unaccent.values_list('field', flat=True)]): 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.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))

View File

@@ -19,10 +19,11 @@ from django.urls import reverse, reverse_lazy
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.views import SpectacularRedocView, SpectacularSwaggerView
from cookbook.forms import CommentForm, Recipe, SearchPreferenceForm, SpaceCreateForm, SpaceJoinForm, User, UserCreateForm, UserPreference from cookbook.forms import CommentForm, Recipe, SearchPreferenceForm, SpaceCreateForm, SpaceJoinForm, User, UserCreateForm, UserPreference
from cookbook.helper.HelperFunctions import str2bool from cookbook.helper.HelperFunctions import str2bool
from cookbook.helper.permission_helper import group_required, has_group_permission, share_link_valid, switch_user_active_space from cookbook.helper.permission_helper import CustomIsGuest, GroupRequiredMixin, group_required, has_group_permission, share_link_valid, switch_user_active_space
from cookbook.models import Comment, CookLog, InviteLink, SearchFields, SearchPreference, ShareLink, Space, UserSpace, ViewLog from cookbook.models import Comment, CookLog, InviteLink, SearchFields, SearchPreference, ShareLink, Space, UserSpace, ViewLog
from cookbook.tables import CookLogTable, ViewLogTable from cookbook.tables import CookLogTable, ViewLogTable
from cookbook.templatetags.theming_tags import get_theming_values from cookbook.templatetags.theming_tags import get_theming_values
@@ -38,16 +39,20 @@ def index(request):
return HttpResponseRedirect(reverse_lazy('view_search')) return HttpResponseRedirect(reverse_lazy('view_search'))
try: try:
page_map = {UserPreference.SEARCH: reverse_lazy('view_search'), UserPreference.PLAN: reverse_lazy('view_plan'), UserPreference.BOOKS: reverse_lazy('view_books'), UserPreference.SHOPPING: reverse_lazy('view_shopping'),} page_map = {
UserPreference.SEARCH: reverse_lazy('view_search'),
UserPreference.PLAN: reverse_lazy('view_plan'),
UserPreference.BOOKS: reverse_lazy('view_books'),
UserPreference.SHOPPING: reverse_lazy('view_shopping'),
}
return HttpResponseRedirect(page_map.get(request.user.userpreference.default_page)) return HttpResponseRedirect(page_map.get(request.user.userpreference.default_page))
except UserPreference.DoesNotExist: except UserPreference.DoesNotExist:
return HttpResponseRedirect(reverse('view_search')) return HttpResponseRedirect(reverse('view_search'))
# TODO need to deprecate
def search(request): def search(request):
if has_group_permission(request.user, ('guest',)): if has_group_permission(request.user, ('guest', )):
return render(request, 'search.html', {}) return render(request, 'search.html', {})
else: else:
if request.user.is_authenticated: if request.user.is_authenticated:
@@ -127,7 +132,7 @@ def recipe_view(request, pk, share=None):
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!')) messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse('account_login') + '?next=' + request.path) return HttpResponseRedirect(reverse('account_login') + '?next=' + request.path)
if not (has_group_permission(request.user, ('guest',)) and recipe.space == request.space) and not share_link_valid(recipe, share): if not (has_group_permission(request.user, ('guest', )) and recipe.space == request.space) and not share_link_valid(recipe, share):
messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!')) messages.add_message(request, messages.ERROR, _('You do not have the required permissions to view this page!'))
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
@@ -160,7 +165,6 @@ def recipe_view(request, pk, share=None):
return render(request, 'recipe_view.html', {'recipe': recipe, 'comments': comments, 'comment_form': comment_form, 'share': share, 'servings': servings}) return render(request, 'recipe_view.html', {'recipe': recipe, 'comments': comments, 'comment_form': comment_form, 'share': share, 'servings': servings})
@group_required('user') @group_required('user')
def books(request): def books(request):
return render(request, 'books.html', {}) return render(request, 'books.html', {})
@@ -346,8 +350,17 @@ def system(request):
return render( return render(
request, 'system.html', { request, 'system.html', {
'gunicorn_media': settings.GUNICORN_MEDIA, 'debug': settings.DEBUG, 'postgres': postgres, 'postgres_version': postgres_ver, 'postgres_status': database_status, 'gunicorn_media': settings.GUNICORN_MEDIA,
'postgres_message': database_message, 'version_info': VERSION_INFO, 'plugins': PLUGINS, 'secret_key': secret_key, 'orphans': orphans, 'migration_info': migration_info, 'debug': settings.DEBUG,
'postgres': postgres,
'postgres_version': postgres_ver,
'postgres_status': database_status,
'postgres_message': database_message,
'version_info': VERSION_INFO,
'plugins': PLUGINS,
'secret_key': secret_key,
'orphans': orphans,
'migration_info': migration_info,
'missing_migration': missing_migration, 'missing_migration': missing_migration,
}) })
@@ -358,8 +371,7 @@ def setup(request):
messages.add_message( messages.add_message(
request, messages.ERROR, request, messages.ERROR,
_('The setup page can only be used to create the first user! \ _('The setup page can only be used to create the first user! \
If you have forgotten your superuser credentials please consult the django documentation on how to reset passwords.' If you have forgotten your superuser credentials please consult the django documentation on how to reset passwords.'))
))
return HttpResponseRedirect(reverse('account_login')) return HttpResponseRedirect(reverse('account_login'))
if request.method == 'POST': if request.method == 'POST':
@@ -441,24 +453,63 @@ def report_share_abuse(request, token):
def web_manifest(request): def web_manifest(request):
theme_values = get_theming_values(request) theme_values = get_theming_values(request)
icons = [{"src": theme_values['logo_color_svg'], "sizes": "any"}, {"src": theme_values['logo_color_144'], "type": "image/png", "sizes": "144x144"}, icons = [{
{"src": theme_values['logo_color_512'], "type": "image/png", "sizes": "512x512"}] "src": theme_values['logo_color_svg'],
"sizes": "any"
}, {
"src": theme_values['logo_color_144'],
"type": "image/png",
"sizes": "144x144"
}, {
"src": theme_values['logo_color_512'],
"type": "image/png",
"sizes": "512x512"
}]
manifest_info = { manifest_info = {
"name": "name":
theme_values['app_name'], "short_name": theme_values['app_name'],
theme_values['app_name'], "description": "short_name":
_("Manage recipes, shopping list, meal plans and more."), "icons": theme_values['app_name'],
icons, "start_url": "description":
"./", "background_color": _("Manage recipes, shopping list, meal plans and more."),
theme_values['nav_bg_color'], "display": "icons":
"standalone", "scope": icons,
".", "theme_color": "start_url":
theme_values['nav_bg_color'], "shortcuts": "./",
[{"name": _("Plan"), "short_name": _("Plan"), "description": _("View your meal Plan"), "url": "background_color":
"./plan"}, {"name": _("Books"), "short_name": _("Books"), "description": _("View your cookbooks"), "url": "./books"}, theme_values['nav_bg_color'],
{"name": _("Shopping"), "short_name": _("Shopping"), "description": _("View your shopping lists"), "url": "display":
"./shopping/"}], "share_target": {"action": "/data/import/url", "method": "GET", "params": {"title": "title", "url": "url", "text": "text"}} "standalone",
"scope":
".",
"theme_color":
theme_values['nav_bg_color'],
"shortcuts": [{
"name": _("Plan"),
"short_name": _("Plan"),
"description": _("View your meal Plan"),
"url": "./plan"
}, {
"name": _("Books"),
"short_name": _("Books"),
"description": _("View your cookbooks"),
"url": "./books"
}, {
"name": _("Shopping"),
"short_name": _("Shopping"),
"description": _("View your shopping lists"),
"url": "./shopping/"
}],
"share_target": {
"action": "/data/import/url",
"method": "GET",
"params": {
"title": "title",
"url": "url",
"text": "text"
}
}
} }
return JsonResponse(manifest_info, json_dumps_params={'indent': 4}) return JsonResponse(manifest_info, json_dumps_params={'indent': 4})
@@ -472,9 +523,14 @@ def search_info(request):
return render(request, 'search_info.html', {}) return render(request, 'search_info.html', {})
@group_required('guest') class Redoc(GroupRequiredMixin, SpectacularRedocView):
def api_info(request): permission_classes = [CustomIsGuest]
return render(request, 'api_info.html', {}) groups_required = ['guest']
class Swagger(GroupRequiredMixin, SpectacularSwaggerView):
permission_classes = [CustomIsGuest]
groups_required = ['guest']
def offline(request): def offline(request):

View File

@@ -14,9 +14,9 @@ import json
import mimetypes import mimetypes
import os import os
import re import re
import socket
import sys import sys
import traceback import traceback
import socket
from django.contrib import messages from django.contrib import messages
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@@ -24,6 +24,13 @@ from dotenv import load_dotenv
load_dotenv() load_dotenv()
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SCRIPT_NAME = os.getenv('SCRIPT_NAME', '')
# path for django_js_reverse to generate the javascript file containing all urls. Only done because the default command (collectstatic_js_reverse) fails to update the manifest
JS_REVERSE_OUTPUT_PATH = os.path.join(BASE_DIR, "cookbook/static/django_js_reverse")
JS_REVERSE_SCRIPT_PREFIX = os.getenv('JS_REVERSE_SCRIPT_PREFIX', SCRIPT_NAME)
STATIC_URL = os.getenv('STATIC_URL', '/static/')
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
# Get vars from .env files # Get vars from .env files
SECRET_KEY = os.getenv('SECRET_KEY') if os.getenv('SECRET_KEY') else 'INSECURE_STANDARD_KEY_SET_IN_ENV' SECRET_KEY = os.getenv('SECRET_KEY') if os.getenv('SECRET_KEY') else 'INSECURE_STANDARD_KEY_SET_IN_ENV'
@@ -105,34 +112,10 @@ MESSAGE_TAGS = {messages.ERROR: 'danger'}
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.sites',
'django.contrib.auth', 'django.contrib.staticfiles', 'django.contrib.postgres', 'oauth2_provider', 'django_prometheus', 'django_tables2', 'corsheaders', 'crispy_forms', 'crispy_bootstrap4',
'django.contrib.contenttypes', 'rest_framework', 'rest_framework.authtoken', 'drf_spectacular', 'drf_spectacular_sidecar', 'django_cleanup.apps.CleanupConfig', 'webpack_loader', 'django_vite',
'django.contrib.sessions', 'django_js_reverse', 'hcaptcha', 'allauth', 'allauth.account', 'allauth.socialaccount', 'cookbook.apps.CookbookConfig', 'treebeard',
'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')
@@ -209,14 +192,14 @@ MIDDLEWARE = [
] ]
if DEBUG_TOOLBAR: if DEBUG_TOOLBAR:
MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware',) MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware', )
INSTALLED_APPS += ('debug_toolbar',) INSTALLED_APPS += ('debug_toolbar', )
SORT_TREE_BY_NAME = bool(int(os.getenv('SORT_TREE_BY_NAME', False))) SORT_TREE_BY_NAME = bool(int(os.getenv('SORT_TREE_BY_NAME', False)))
DISABLE_TREE_FIX_STARTUP = bool(int(os.getenv('DISABLE_TREE_FIX_STARTUP', False))) DISABLE_TREE_FIX_STARTUP = bool(int(os.getenv('DISABLE_TREE_FIX_STARTUP', False)))
if bool(int(os.getenv('SQL_DEBUG', False))): if bool(int(os.getenv('SQL_DEBUG', False))):
MIDDLEWARE += ('recipes.middleware.SqlPrintingMiddleware',) MIDDLEWARE += ('recipes.middleware.SqlPrintingMiddleware', )
if ENABLE_METRICS: if ENABLE_METRICS:
MIDDLEWARE += 'django_prometheus.middleware.PrometheusAfterMiddleware', MIDDLEWARE += 'django_prometheus.middleware.PrometheusAfterMiddleware',
@@ -295,11 +278,13 @@ WRITE_SCOPE = 'write'
REST_FRAMEWORK = { REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': 'DEFAULT_AUTHENTICATION_CLASSES':
('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', 'DEFAULT_SCHEMA_CLASS':
'COERCE_DECIMAL_TO_STRING': False, 'drf_spectacular.openapi.AutoSchema',
'COERCE_DECIMAL_TO_STRING':
False,
} }
SPECTACULAR_SETTINGS = { SPECTACULAR_SETTINGS = {
@@ -307,6 +292,38 @@ SPECTACULAR_SETTINGS = {
'DESCRIPTION': 'Tandoor API Docs', 'DESCRIPTION': 'Tandoor API Docs',
'SERVE_INCLUDE_SCHEMA': False, 'SERVE_INCLUDE_SCHEMA': False,
'ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE': False, 'ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE': False,
"AUTHENTICATION_WHITELIST": [],
"APPEND_COMPONENTS": {
"securitySchemes": {
"ApiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "Authorization",
}
}
},
"SECURITY": [{
"ApiKeyAuth": []
}],
'SWAGGER_UI_DIST': 'SIDECAR',
'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',
'REDOC_DIST': 'SIDECAR',
'EXTENSIONS_INFO': {
"x-logo": {
"url": f"{STATIC_URL}assets/brand_logo.svg",
"backgroundColor": "#FFFFFF",
"altText": "Tandoor logo",
'href': '/'
}
},
'CAMELIZE_NAMES': True,
"SWAGGER_UI_SETTINGS": {
"deepLinking": True,
"persistAuthorization": True,
"hideDownloadButton": False,
'schemaExpansionLevel': 'all',
'showExtensions': True
},
} }
ROOT_URLCONF = 'recipes.urls' ROOT_URLCONF = 'recipes.urls'
@@ -419,14 +436,7 @@ for p in PLUGINS:
'IGNORE': [r'.+\.hot-update.js', r'.+\.map'], 'IGNORE': [r'.+\.hot-update.js', r'.+\.map'],
} }
DJANGO_VITE = { DJANGO_VITE = {"default": {"dev_mode": False, "static_url_prefix": 'vue3', "dev_server_port": 5173, "dev_server_host": os.getenv('DJANGO_VITE_DEV_SERVER_HOST', 'localhost'), }, }
"default": {
"dev_mode": False,
"static_url_prefix": 'vue3',
"dev_server_port": 5173,
"dev_server_host": os.getenv('DJANGO_VITE_DEV_SERVER_HOST', 'localhost'),
},
}
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(0.001) s.settimeout(0.001)
@@ -462,14 +472,6 @@ LANGUAGES = [('hy', _('Armenian ')), ('bg', _('Bulgarian')), ('ca', _('Catalan')
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/ # https://docs.djangoproject.com/en/2.0/howto/static-files/
SCRIPT_NAME = os.getenv('SCRIPT_NAME', '')
# path for django_js_reverse to generate the javascript file containing all urls. Only done because the default command (collectstatic_js_reverse) fails to update the manifest
JS_REVERSE_OUTPUT_PATH = os.path.join(BASE_DIR, "cookbook/static/django_js_reverse")
JS_REVERSE_SCRIPT_PREFIX = os.getenv('JS_REVERSE_SCRIPT_PREFIX', SCRIPT_NAME)
STATIC_URL = os.getenv('STATIC_URL', '/static/')
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
AWS_ENABLED = True if os.getenv('S3_ACCESS_KEY', False) else False AWS_ENABLED = True if os.getenv('S3_ACCESS_KEY', False) else False
if os.getenv('S3_ACCESS_KEY', ''): if os.getenv('S3_ACCESS_KEY', ''):
@@ -529,18 +531,9 @@ DISABLE_EXTERNAL_CONNECTORS = bool(int(os.getenv('DISABLE_EXTERNAL_CONNECTORS',
EXTERNAL_CONNECTORS_QUEUE_SIZE = int(os.getenv('EXTERNAL_CONNECTORS_QUEUE_SIZE', 100)) EXTERNAL_CONNECTORS_QUEUE_SIZE = int(os.getenv('EXTERNAL_CONNECTORS_QUEUE_SIZE', 100))
# ACCOUNT_SIGNUP_FORM_CLASS = 'cookbook.forms.AllAuthSignupForm' # ACCOUNT_SIGNUP_FORM_CLASS = 'cookbook.forms.AllAuthSignupForm'
ACCOUNT_FORMS = { ACCOUNT_FORMS = {'signup': 'cookbook.forms.AllAuthSignupForm', 'reset_password': 'cookbook.forms.CustomPasswordResetForm'}
'signup': 'cookbook.forms.AllAuthSignupForm',
'reset_password': 'cookbook.forms.CustomPasswordResetForm'
}
ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS = False ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS = False
ACCOUNT_RATE_LIMITS = { ACCOUNT_RATE_LIMITS = {"change_password": "1/m/user", "reset_password": "1/m/ip,1/m/key", "reset_password_from_key": "1/m/ip", "signup": "5/m/ip", "login": "5/m/ip", }
"change_password": "1/m/user",
"reset_password": "1/m/ip,1/m/key",
"reset_password_from_key": "1/m/ip",
"signup": "5/m/ip",
"login": "5/m/ip",
}
mimetypes.add_type("text/javascript", ".js", True) mimetypes.add_type("text/javascript", ".js", True)

File diff suppressed because it is too large Load Diff

View File

@@ -27,9 +27,10 @@ export class ApiImportOpenDataApi extends runtime.BaseAPI {
const headerParameters: runtime.HTTPHeaders = {}; const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && (this.configuration.username !== undefined || this.configuration.password !== undefined)) { if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = "Basic " + btoa(this.configuration.username + ":" + this.configuration.password); headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
} }
const response = await this.request({ const response = await this.request({
path: `/api-import-open-data/`, path: `/api-import-open-data/`,
method: 'POST', method: 'POST',
@@ -53,9 +54,10 @@ export class ApiImportOpenDataApi extends runtime.BaseAPI {
const headerParameters: runtime.HTTPHeaders = {}; const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && (this.configuration.username !== undefined || this.configuration.password !== undefined)) { if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = "Basic " + btoa(this.configuration.username + ":" + this.configuration.password); headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
} }
const response = await this.request({ const response = await this.request({
path: `/api-import-open-data/`, path: `/api-import-open-data/`,
method: 'GET', method: 'GET',

View File

@@ -61,9 +61,10 @@ export class ApiTokenAuthApi extends runtime.BaseAPI {
const headerParameters: runtime.HTTPHeaders = {}; const headerParameters: runtime.HTTPHeaders = {};
if (this.configuration && (this.configuration.username !== undefined || this.configuration.password !== undefined)) { if (this.configuration && this.configuration.apiKey) {
headerParameters["Authorization"] = "Basic " + btoa(this.configuration.username + ":" + this.configuration.password); headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
} }
const consumes: runtime.Consume[] = [ const consumes: runtime.Consume[] = [
{ contentType: 'application/x-www-form-urlencoded' }, { contentType: 'application/x-www-form-urlencoded' },
{ contentType: 'multipart/form-data' }, { contentType: 'multipart/form-data' },