Merge branch 'develop' into feature/unit-conversion

# Conflicts:
#	vue/src/components/Modals/GenericModalForm.vue
This commit is contained in:
vabene1111
2023-05-24 08:53:49 +02:00
11 changed files with 177 additions and 12 deletions

View File

@ -3,6 +3,9 @@
DEBUG=0 DEBUG=0
SQL_DEBUG=0 SQL_DEBUG=0
DEBUG_TOOLBAR=0 DEBUG_TOOLBAR=0
# Gunicorn log level for debugging (default value is "info" when unset)
# (see https://docs.gunicorn.org/en/stable/settings.html#loglevel for available settings)
# GUNICORN_LOG_LEVEL="debug"
# HTTP port to bind to # HTTP port to bind to
# TANDOOR_PORT=8080 # TANDOOR_PORT=8080

View File

@ -0,0 +1,120 @@
name: Build Docker Container with open data plugin installed
on: push
jobs:
build-container:
name: Build ${{ matrix.name }} Container
runs-on: ubuntu-latest
if: github.repository_owner == 'TandoorRecipes'
continue-on-error: ${{ matrix.continue-on-error }}
permissions:
contents: read
packages: write
strategy:
matrix:
include:
# Standard build config
- name: Standard
dockerfile: Dockerfile
platforms: linux/amd64,linux/arm64
suffix: ""
continue-on-error: false
steps:
- uses: actions/checkout@v3
- name: Get version number
id: get_version
run: |
if [[ "$GITHUB_REF" = refs/tags/* ]]; then
echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT
elif [[ "$GITHUB_REF" = refs/heads/beta ]]; then
echo VERSION=beta >> $GITHUB_OUTPUT
else
echo VERSION=develop >> $GITHUB_OUTPUT
fi
# Update Version number
- name: Update version file
uses: DamianReeves/write-file-action@v1.2
with:
path: recipes/version.py
contents: |
VERSION_NUMBER = '${{ steps.get_version.outputs.VERSION }}-open-data'
BUILD_REF = '${{ github.sha }}'
write-mode: overwrite
# clone open data plugin
- name: clone open data plugin repo
uses: actions/checkout@master
with:
repository: TandoorRecipes/open_data_plugin
ref: master
path: ./recipes/plugins/open_data_plugin
# Build Vue frontend
- uses: actions/setup-node@v3
with:
node-version: '14'
cache: yarn
cache-dependency-path: vue/yarn.lock
- name: Install dependencies
working-directory: ./vue
run: yarn install --frozen-lockfile
- name: Build dependencies
working-directory: ./vue
run: yarn build
- name: Setup Open Data Plugin Links
working-directory: ./recipes/plugins/open_data_plugin
run: python setup_repo.py
- name: Build Open Data Frontend
working-directory: ./recipes/plugins/open_data_plugin/vue
run: yarn build
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
if: github.secret_source == 'Actions'
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
if: github.secret_source == 'Actions'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
vabene1111/recipes
ghcr.io/TandoorRecipes/recipes
flavor: |
latest=false
suffix=${{ matrix.suffix }}
tags: |
type=raw,value=latest,suffix=-open-data-plugin,enable=${{ startsWith(github.ref, 'refs/tags/') }}
type=semver,suffix=-open-data-plugin,pattern={{version}}
type=semver,suffix=-open-data-plugin,pattern={{major}}.{{minor}}
type=semver,suffix=-open-data-plugin,pattern={{major}}
type=ref,suffix=-open-data-plugin,event=branch
- name: Build and Push
uses: docker/build-push-action@v4
with:
context: .
file: ${{ matrix.dockerfile }}
pull: true
push: ${{ github.secret_source == 'Actions' }}
platforms: ${{ matrix.platforms }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -4,6 +4,7 @@ source venv/bin/activate
TANDOOR_PORT="${TANDOOR_PORT:-8080}" TANDOOR_PORT="${TANDOOR_PORT:-8080}"
GUNICORN_WORKERS="${GUNICORN_WORKERS:-3}" GUNICORN_WORKERS="${GUNICORN_WORKERS:-3}"
GUNICORN_THREADS="${GUNICORN_THREADS:-2}" GUNICORN_THREADS="${GUNICORN_THREADS:-2}"
GUNICORN_LOG_LEVEL="${GUNICORN_LOG_LEVEL:-'info'}"
NGINX_CONF_FILE=/opt/recipes/nginx/conf.d/Recipes.conf NGINX_CONF_FILE=/opt/recipes/nginx/conf.d/Recipes.conf
display_warning() { display_warning() {
@ -65,4 +66,4 @@ echo "Done"
chmod -R 755 /opt/recipes/mediafiles chmod -R 755 /opt/recipes/mediafiles
exec gunicorn -b :$TANDOOR_PORT --workers $GUNICORN_WORKERS --threads $GUNICORN_THREADS --access-logfile - --error-logfile - --log-level INFO recipes.wsgi exec gunicorn -b :$TANDOOR_PORT --workers $GUNICORN_WORKERS --threads $GUNICORN_THREADS --access-logfile - --error-logfile - --log-level $GUNICORN_LOG_LEVEL recipes.wsgi

View File

@ -9,6 +9,7 @@
{% endblock %}</title> {% endblock %}</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="noindex,nofollow"/>
<link rel="shortcut icon" type="image/x-icon" href="{% static 'assets/favicon.svg' %}"> <link rel="shortcut icon" type="image/x-icon" href="{% static 'assets/favicon.svg' %}">
@ -49,7 +50,7 @@
<script type="text/javascript"> <script type="text/javascript">
$.fn.select2.defaults.set("theme", "bootstrap"); $.fn.select2.defaults.set("theme", "bootstrap");
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
window.ACTIVE_SPACE_ID = '{{request.space.id}}'; window.ACTIVE_SPACE_ID = '{{request.space.id}}';
{% endif %} {% endif %}
</script> </script>
@ -349,6 +350,12 @@
<a class="dropdown-item" href="{% url 'view_space_overview' %}"><i <a class="dropdown-item" href="{% url 'view_space_overview' %}"><i
class="fas fa-list"></i> {% trans 'Overview' %}</a> class="fas fa-list"></i> {% trans 'Overview' %}</a>
{% endif %} {% endif %}
{% plugin_dropdown_nav_templates as plugin_dropdown_nav_templates %}
{% for pn in plugin_dropdown_nav_templates %}
<div class="dropdown-divider"></div>
{% include pn %}
{% endfor %}
<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>
@ -375,6 +382,7 @@
</div> </div>
</nav> </nav>
{% message_of_the_day request as message_of_the_day %} {% message_of_the_day request as message_of_the_day %}
{% if message_of_the_day %} {% if message_of_the_day %}
<div class="bg-info" style=" width: 100%; text-align: center!important; color: #ffffff; padding: 8px"> <div class="bg-info" style=" width: 100%; text-align: center!important; color: #ffffff; padding: 8px">

View File

@ -16,7 +16,7 @@ from cookbook.helper.mdx_attributes import MarkdownFormatExtension
from cookbook.helper.mdx_urlize import UrlizeExtension from cookbook.helper.mdx_urlize import UrlizeExtension
from cookbook.models import Space, get_model_name from cookbook.models import Space, get_model_name
from recipes import settings from recipes import settings
from recipes.settings import STATIC_URL from recipes.settings import STATIC_URL, PLUGINS
register = template.Library() register = template.Library()
@ -132,6 +132,14 @@ def is_debug():
def markdown_link(): def markdown_link():
return f"{_('You can use markdown to format this field. See the ')}<a target='_blank' href='{reverse('docs_markdown')}'>{_('docs here')}</a>" return f"{_('You can use markdown to format this field. See the ')}<a target='_blank' href='{reverse('docs_markdown')}'>{_('docs here')}</a>"
@register.simple_tag
def plugin_dropdown_nav_templates():
templates = []
for p in PLUGINS:
if p['nav_dropdown']:
templates.append(p['nav_dropdown'])
return templates
@register.simple_tag @register.simple_tag
def bookmarklet(request): def bookmarklet(request):

View File

@ -6,7 +6,7 @@ from rest_framework import permissions, routers
from rest_framework.schemas import get_schema_view from rest_framework.schemas import get_schema_view
from cookbook.helper import dal from cookbook.helper import dal
from recipes.settings import DEBUG from recipes.settings import DEBUG, PLUGINS
from recipes.version import VERSION_NUMBER from recipes.version import VERSION_NUMBER
from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, MealPlan, Recipe, from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, MealPlan, Recipe,
@ -16,7 +16,13 @@ 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
router = routers.DefaultRouter() # extend DRF default router class to allow including additional routers
class DefaultRouter(routers.DefaultRouter):
def extend(self, r):
self.registry.extend(r.registry)
router = DefaultRouter()
router.register(r'automation', api.AutomationViewSet) router.register(r'automation', api.AutomationViewSet)
router.register(r'bookmarklet-import', api.BookmarkletImportViewSet) router.register(r'bookmarklet-import', api.BookmarkletImportViewSet)
router.register(r'cook-log', api.CookLogViewSet) router.register(r'cook-log', api.CookLogViewSet)
@ -56,6 +62,13 @@ router.register(r'user-space', api.UserSpaceViewSet)
router.register(r'view-log', api.ViewLogViewSet) router.register(r'view-log', api.ViewLogViewSet)
router.register(r'access-token', api.AccessTokenViewSet) router.register(r'access-token', api.AccessTokenViewSet)
for p in PLUGINS:
if c := locate(f'{p["module"]}.urls.{p["api_router_name"]}'):
try:
router.extend(c)
except AttributeError:
pass
urlpatterns = [ urlpatterns = [
path('', views.index, name='index'), path('', views.index, name='index'),
path('setup/', views.setup, name='view_setup'), path('setup/', views.setup, name='view_setup'),
@ -122,7 +135,6 @@ urlpatterns = [
path('api/switch-active-space/<int:space_id>/', api.switch_active_space, name='api_switch_active_space'), path('api/switch-active-space/<int:space_id>/', api.switch_active_space, name='api_switch_active_space'),
path('api/download-file/<int:file_id>/', api.download_file, name='api_download_file'), path('api/download-file/<int:file_id>/', api.download_file, name='api_download_file'),
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'), path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
# TODO is this deprecated? not yet, some old forms remain, could likely be changed to generic API endpoints # TODO is this deprecated? not yet, some old forms remain, could likely be changed to generic API endpoints
path('dal/food/', dal.IngredientsAutocomplete.as_view(), name='dal_food'), # TODO is this deprecated? path('dal/food/', dal.IngredientsAutocomplete.as_view(), name='dal_food'), # TODO is this deprecated?

View File

@ -80,6 +80,8 @@ DJANGO_TABLES2_PAGE_RANGE = 8
HCAPTCHA_SITEKEY = os.getenv('HCAPTCHA_SITEKEY', '') HCAPTCHA_SITEKEY = os.getenv('HCAPTCHA_SITEKEY', '')
HCAPTCHA_SECRET = os.getenv('HCAPTCHA_SECRET', '') HCAPTCHA_SECRET = os.getenv('HCAPTCHA_SECRET', '')
FDA_API_KEY = os.getenv('FDA_API_KEY', 'DEMO_KEY')
SHARING_ABUSE = bool(int(os.getenv('SHARING_ABUSE', False))) SHARING_ABUSE = bool(int(os.getenv('SHARING_ABUSE', False)))
SHARING_LIMIT = int(os.getenv('SHARING_LIMIT', 0)) SHARING_LIMIT = int(os.getenv('SHARING_LIMIT', 0))
@ -144,6 +146,9 @@ try:
'base_path': os.path.join(BASE_DIR, 'recipes', 'plugins', d), 'base_path': os.path.join(BASE_DIR, 'recipes', 'plugins', d),
'base_url': plugin_class.base_url, 'base_url': plugin_class.base_url,
'bundle_name': plugin_class.bundle_name if hasattr(plugin_class, 'bundle_name') else '', 'bundle_name': plugin_class.bundle_name if hasattr(plugin_class, 'bundle_name') else '',
'api_router_name': plugin_class.api_router_name if hasattr(plugin_class, 'api_router_name') else '',
'nav_main': plugin_class.nav_main if hasattr(plugin_class, 'nav_main') else '',
'nav_dropdown': plugin_class.nav_dropdown if hasattr(plugin_class, 'nav_dropdown') else '',
} }
PLUGINS.append(plugin_config) PLUGINS.append(plugin_config)
except Exception: except Exception:
@ -412,7 +417,7 @@ for p in PLUGINS:
if p['bundle_name'] != '': if p['bundle_name'] != '':
WEBPACK_LOADER[p['bundle_name']] = { WEBPACK_LOADER[p['bundle_name']] = {
'CACHE': not DEBUG, 'CACHE': not DEBUG,
'BUNDLE_DIR_NAME': f'{p["base_path"]}/vue/', # must end with slash 'BUNDLE_DIR_NAME': f'vue/', # must end with slash
'STATS_FILE': os.path.join(p["base_path"], 'vue', 'webpack-stats.json'), 'STATS_FILE': os.path.join(p["base_path"], 'vue', 'webpack-stats.json'),
'POLL_INTERVAL': 0.1, 'POLL_INTERVAL': 0.1,
'TIMEOUT': None, 'TIMEOUT': None,

View File

@ -17,7 +17,7 @@ Markdown==3.4.3
Pillow==9.4.0 Pillow==9.4.0
psycopg2-binary==2.9.5 psycopg2-binary==2.9.5
python-dotenv==0.21.0 python-dotenv==0.21.0
requests==2.28.2 requests==2.31.0
six==1.16.0 six==1.16.0
webdavclient3==3.14.6 webdavclient3==3.14.6
whitenoise==6.2.0 whitenoise==6.2.0

View File

@ -109,6 +109,10 @@ export default {
mounted() { mounted() {
this.id = Math.random() this.id = Math.random()
this.$root.$on("change", this.storeValue) // bootstrap modal placed at document so have to listen at root of component this.$root.$on("change", this.storeValue) // bootstrap modal placed at document so have to listen at root of component
if (this.models !== null){
this.Models = this.models // override models definition file with prop
}
}, },
computed: { computed: {
advancedForm() { advancedForm() {
@ -179,6 +183,7 @@ export default {
if (this.dirty) { if (this.dirty) {
this.dirty = false this.dirty = false
this.$emit("finish-action", "cancel") this.$emit("finish-action", "cancel")
this.$emit("hidden")
} }
}, },
storeValue: function (field, value) { storeValue: function (field, value) {

View File

View File

@ -50,7 +50,7 @@ export class StandardToasts {
static FAIL_MOVE = "FAIL_MOVE" static FAIL_MOVE = "FAIL_MOVE"
static FAIL_MERGE = "FAIL_MERGE" static FAIL_MERGE = "FAIL_MERGE"
static makeStandardToast(context, toast, err) { static makeStandardToast(context, toast, err = undefined, always_show_errors = false) {
let title = '' let title = ''
let msg = '' let msg = ''
let variant = '' let variant = ''
@ -124,7 +124,7 @@ export class StandardToasts {
} }
let DEBUG = localStorage.getItem("DEBUG") === "True" || false let DEBUG = localStorage.getItem("DEBUG") === "True" || always_show_errors
if (err !== undefined && 'response' in err && 'headers' in err.response) { if (err !== undefined && 'response' in err && 'headers' in err.response) {
if (DEBUG && err.response.headers['content-type'] === 'application/json' && err.response.status < 500) { if (DEBUG && err.response.headers['content-type'] === 'application/json' && err.response.status < 500) {
@ -311,7 +311,7 @@ export function calculateHourMinuteSplit(amount) {
let minutes = amount - hours * 60 let minutes = amount - hours * 60
let output_text = hours + " h" let output_text = hours + " h"
if (minutes > 0){ if (minutes > 0) {
output_text += " " + minutes + " min" output_text += " " + minutes + " min"
} }
@ -368,6 +368,9 @@ export const ApiMixin = {
let func = setup.function let func = setup.function
let parameters = buildParams(options, setup) let parameters = buildParams(options, setup)
let apiClient = new ApiApiFactory() let apiClient = new ApiApiFactory()
if (model.apiClient !== undefined) {
apiClient = model.apiClient
}
return apiClient[func](...parameters) return apiClient[func](...parameters)
}, },
genericGetAPI: function (url, options) { genericGetAPI: function (url, options) {