Merge branch 'develop' into feature/importer_to_vue
# Conflicts: # cookbook/helper/recipe_url_import.py
This commit is contained in:
4
.github/workflows/docker-publish-beta.yml
vendored
4
.github/workflows/docker-publish-beta.yml
vendored
@ -35,8 +35,8 @@ jobs:
|
|||||||
publish: true
|
publish: true
|
||||||
imageName: vabene1111/recipes
|
imageName: vabene1111/recipes
|
||||||
tag: beta
|
tag: beta
|
||||||
dockerHubUser: ${{ secrets.DOCKER_USERNAME }}
|
dockerUser: ${{ secrets.DOCKER_USERNAME }}
|
||||||
dockerHubPassword: ${{ secrets.DOCKER_PASSWORD }}
|
dockerPassword: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
# Send discord notification
|
# Send discord notification
|
||||||
- name: Discord notification
|
- name: Discord notification
|
||||||
env:
|
env:
|
||||||
|
4
.github/workflows/docker-publish-latest.yml
vendored
4
.github/workflows/docker-publish-latest.yml
vendored
@ -39,5 +39,5 @@ jobs:
|
|||||||
publish: true
|
publish: true
|
||||||
imageName: vabene1111/recipes
|
imageName: vabene1111/recipes
|
||||||
tag: latest
|
tag: latest
|
||||||
dockerHubUser: ${{ secrets.DOCKER_USERNAME }}
|
dockerUser: ${{ secrets.DOCKER_USERNAME }}
|
||||||
dockerHubPassword: ${{ secrets.DOCKER_PASSWORD }}
|
dockerPassword: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
4
.github/workflows/docker-publish-release.yml
vendored
4
.github/workflows/docker-publish-release.yml
vendored
@ -41,8 +41,8 @@ jobs:
|
|||||||
publish: true
|
publish: true
|
||||||
imageName: vabene1111/recipes
|
imageName: vabene1111/recipes
|
||||||
tag: ${{ steps.get_version.outputs.VERSION }}
|
tag: ${{ steps.get_version.outputs.VERSION }}
|
||||||
dockerHubUser: ${{ secrets.DOCKER_USERNAME }}
|
dockerUser: ${{ secrets.DOCKER_USERNAME }}
|
||||||
dockerHubPassword: ${{ secrets.DOCKER_PASSWORD }}
|
dockerPassword: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
# Send discord notification
|
# Send discord notification
|
||||||
- name: Discord notification
|
- name: Discord notification
|
||||||
env:
|
env:
|
||||||
|
@ -6,11 +6,17 @@ Please have a look at the [list of pull requests](https://github.com/vabene1111/
|
|||||||
a complete list of contributions.
|
a complete list of contributions.
|
||||||
Below are some of the larger contributions made yet.
|
Below are some of the larger contributions made yet.
|
||||||
|
|
||||||
|
- [vabene1111]
|
||||||
- @tourn provided the serving feature and **several** other improvements!
|
- [Kaibu]
|
||||||
- @l0c4lh057 provided a much improved ingredient text parser in [#277](https://github.com/vabene1111/recipes/pull/277)
|
- [smilerz]
|
||||||
- @sebimarkgraf added nutritional information [#199](https://github.com/vabene1111/recipes/pull/199)
|
- [MaxJa4] Docker builds and other improvements
|
||||||
- @cazier added reverse proxy authentication [#88](https://github.com/vabene1111/recipes/pull/88)
|
- [tourn] provided the serving feature and **several** other improvements!
|
||||||
|
- [l0c4lh057] provided a much improved ingredient text parser in [#277](https://github.com/vabene1111/recipes/pull/277)
|
||||||
|
- [sebimarkgraf] added nutritional information [#199](https://github.com/vabene1111/recipes/pull/199)
|
||||||
|
- [cazier] added reverse proxy authentication [#88](https://github.com/vabene1111/recipes/pull/88)
|
||||||
|
- [murphy83] added support for IPv6 #1490
|
||||||
|
- [TheHaf] added custom serving size component #1411
|
||||||
|
- [lostlont] added LDAP support #960
|
||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
@ -30,6 +36,7 @@ Below are some of the larger contributions made yet.
|
|||||||
### German
|
### German
|
||||||
[eTaurus](https://www.transifex.com/user/profile/eTaurus/)
|
[eTaurus](https://www.transifex.com/user/profile/eTaurus/)
|
||||||
[l0c4lh057](https://www.transifex.com/user/profile/l0c4lh057/)
|
[l0c4lh057](https://www.transifex.com/user/profile/l0c4lh057/)
|
||||||
|
[hyperbit00]
|
||||||
|
|
||||||
### Hungarian
|
### Hungarian
|
||||||
[igazka](https://www.transifex.com/user/profile/igazka/)
|
[igazka](https://www.transifex.com/user/profile/igazka/)
|
||||||
@ -60,4 +67,4 @@ Below are some of the larger contributions made yet.
|
|||||||
|
|
||||||
### Vietnamese
|
### Vietnamese
|
||||||
|
|
||||||
[vuongtrunghieu](https://www.transifex.com/user/profile/vuongtrunghieu/)
|
[vuongtrunghieu](https://www.transifex.com/user/profile/vuongtrunghieu/)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
FROM python:3.10-alpine3.15
|
FROM python:3.10-alpine3.15
|
||||||
|
|
||||||
#Install all dependencies.
|
#Install all dependencies.
|
||||||
RUN apk add --no-cache postgresql-libs postgresql-client gettext zlib libjpeg libwebp libxml2-dev libxslt-dev py-cryptography
|
RUN apk add --no-cache postgresql-libs postgresql-client gettext zlib libjpeg libwebp libxml2-dev libxslt-dev py-cryptography openldap
|
||||||
|
|
||||||
#Print all logs without buffering it.
|
#Print all logs without buffering it.
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
@ -15,11 +15,12 @@ WORKDIR /opt/recipes
|
|||||||
|
|
||||||
COPY requirements.txt ./
|
COPY requirements.txt ./
|
||||||
|
|
||||||
RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-dev jpeg-dev libwebp-dev libressl-dev libffi-dev cargo openssl-dev openldap-dev && \
|
RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-dev jpeg-dev libwebp-dev libressl-dev libffi-dev cargo openssl-dev openldap-dev python3-dev && \
|
||||||
echo -n "INPUT ( libldap.so )" > /usr/lib/libldap_r.so && \
|
echo -n "INPUT ( libldap.so )" > /usr/lib/libldap_r.so && \
|
||||||
python -m venv venv && \
|
python -m venv venv && \
|
||||||
/opt/recipes/venv/bin/python -m pip install --upgrade pip && \
|
/opt/recipes/venv/bin/python -m pip install --upgrade pip && \
|
||||||
venv/bin/pip install wheel==0.36.2 && \
|
venv/bin/pip install wheel==0.37.1 && \
|
||||||
|
venv/bin/pip install setuptools_rust==1.1.2 && \
|
||||||
venv/bin/pip install -r requirements.txt --no-cache-dir &&\
|
venv/bin/pip install -r requirements.txt --no-cache-dir &&\
|
||||||
apk --purge del .build-deps
|
apk --purge del .build-deps
|
||||||
|
|
||||||
|
21
boot.sh
21
boot.sh
@ -21,18 +21,23 @@ if [ -z "${SECRET_KEY}" ]; then
|
|||||||
display_warning "The environment variable 'SECRET_KEY' is not set but REQUIRED for running Tandoor!"
|
display_warning "The environment variable 'SECRET_KEY' is not set but REQUIRED for running Tandoor!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# POSTGRES_PASSWORD must be set in .env file
|
|
||||||
if [ -z "${POSTGRES_PASSWORD}" ]; then
|
|
||||||
display_warning "The environment variable 'POSTGRES_PASSWORD' is not set but REQUIRED for running Tandoor!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Waiting for database to be ready..."
|
echo "Waiting for database to be ready..."
|
||||||
|
|
||||||
attempt=0
|
attempt=0
|
||||||
max_attempts=20
|
max_attempts=20
|
||||||
while pg_isready --host=${POSTGRES_HOST} -q; status=$?; attempt=$((attempt+1)); [ $status -ne 0 ] && [ $attempt -le $max_attempts ]; do
|
|
||||||
sleep 5
|
if [ "${DB_ENGINE}" != 'django.db.backends.sqlite3' ]; then
|
||||||
done
|
|
||||||
|
# POSTGRES_PASSWORD must be set in .env file
|
||||||
|
if [ -z "${POSTGRES_PASSWORD}" ]; then
|
||||||
|
display_warning "The environment variable 'POSTGRES_PASSWORD' is not set but REQUIRED for running Tandoor!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
while pg_isready --host=${POSTGRES_HOST} --port=${POSTGRES_PORT} -q; status=$?; attempt=$((attempt+1)); [ $status -ne 0 ] && [ $attempt -le $max_attempts ]; do
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
if [ $attempt -gt $max_attempts ]; then
|
if [ $attempt -gt $max_attempts ]; then
|
||||||
echo -e "\nDatabase not reachable. Maximum attempts exceeded."
|
echo -e "\nDatabase not reachable. Maximum attempts exceeded."
|
||||||
@ -58,4 +63,4 @@ echo "Done"
|
|||||||
|
|
||||||
chmod -R 755 /opt/recipes/mediafiles
|
chmod -R 755 /opt/recipes/mediafiles
|
||||||
|
|
||||||
exec gunicorn -b :$TANDOOR_PORT --access-logfile - --error-logfile - --log-level INFO recipes.wsgi
|
exec gunicorn -b :$TANDOOR_PORT --access-logfile - --error-logfile - --log-level INFO recipes.wsgi
|
||||||
|
@ -179,6 +179,7 @@ class ImportForm(ImportExportBase):
|
|||||||
class ExportForm(ImportExportBase):
|
class ExportForm(ImportExportBase):
|
||||||
recipes = forms.ModelMultipleChoiceField(widget=MultiSelectWidget, queryset=Recipe.objects.none(), required=False)
|
recipes = forms.ModelMultipleChoiceField(widget=MultiSelectWidget, queryset=Recipe.objects.none(), required=False)
|
||||||
all = forms.BooleanField(required=False)
|
all = forms.BooleanField(required=False)
|
||||||
|
custom_filter = forms.IntegerField(required=False)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
space = kwargs.pop('space')
|
space = kwargs.pop('space')
|
||||||
|
@ -13,8 +13,8 @@ from cookbook.filters import RecipeFilter
|
|||||||
from cookbook.helper.HelperFunctions import Round, str2bool
|
from cookbook.helper.HelperFunctions import Round, str2bool
|
||||||
from cookbook.helper.permission_helper import has_group_permission
|
from cookbook.helper.permission_helper import has_group_permission
|
||||||
from cookbook.managers import DICTIONARY
|
from cookbook.managers import DICTIONARY
|
||||||
from cookbook.models import (CookLog, CustomFilter, Food, Keyword, Recipe, SearchFields,
|
from cookbook.models import (CookLog, CustomFilter, Food, Keyword, Recipe, RecipeBook, SearchFields,
|
||||||
SearchPreference, ViewLog, RecipeBook)
|
SearchPreference, ViewLog)
|
||||||
from recipes import settings
|
from recipes import settings
|
||||||
|
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ class RecipeSearch():
|
|||||||
self._queryset = None
|
self._queryset = None
|
||||||
if f := params.get('filter', None):
|
if f := params.get('filter', None):
|
||||||
custom_filter = CustomFilter.objects.filter(id=f, space=self._request.space).filter(Q(created_by=self._request.user) |
|
custom_filter = CustomFilter.objects.filter(id=f, space=self._request.space).filter(Q(created_by=self._request.user) |
|
||||||
Q(shared=self._request.user) | Q(recipebook__shared=self._request.user)).first()
|
Q(shared=self._request.user) | Q(recipebook__shared=self._request.user)).first()
|
||||||
if custom_filter:
|
if custom_filter:
|
||||||
self._params = {**json.loads(custom_filter.search)}
|
self._params = {**json.loads(custom_filter.search)}
|
||||||
self._original_params = {**(params or {})}
|
self._original_params = {**(params or {})}
|
||||||
@ -40,7 +40,7 @@ class RecipeSearch():
|
|||||||
self._search_prefs = request.user.searchpreference
|
self._search_prefs = request.user.searchpreference
|
||||||
else:
|
else:
|
||||||
self._search_prefs = SearchPreference()
|
self._search_prefs = SearchPreference()
|
||||||
self._string = params.get('query').strip() if params.get('query', None) else None
|
self._string = self._params.get('query').strip() if self._params.get('query', None) else None
|
||||||
self._rating = self._params.get('rating', None)
|
self._rating = self._params.get('rating', None)
|
||||||
self._keywords = {
|
self._keywords = {
|
||||||
'or': self._params.get('keywords_or', None) or self._params.get('keywords', None),
|
'or': self._params.get('keywords_or', None) or self._params.get('keywords', None),
|
||||||
@ -89,7 +89,10 @@ class RecipeSearch():
|
|||||||
|
|
||||||
self._search_type = self._search_prefs.search or 'plain'
|
self._search_type = self._search_prefs.search or 'plain'
|
||||||
if self._string:
|
if self._string:
|
||||||
self._unaccent_include = self._search_prefs.unaccent.values_list('field', flat=True)
|
if self._postgres:
|
||||||
|
self._unaccent_include = self._search_prefs.unaccent.values_list('field', flat=True)
|
||||||
|
else:
|
||||||
|
self._unaccent_include = []
|
||||||
self._icontains_include = [x + '__unaccent' if x in self._unaccent_include else x for x in self._search_prefs.icontains.values_list('field', flat=True)]
|
self._icontains_include = [x + '__unaccent' if x in self._unaccent_include else x for x in self._search_prefs.icontains.values_list('field', flat=True)]
|
||||||
self._istartswith_include = [x + '__unaccent' if x in self._unaccent_include else x for x in self._search_prefs.istartswith.values_list('field', flat=True)]
|
self._istartswith_include = [x + '__unaccent' if x in self._unaccent_include else x for x in self._search_prefs.istartswith.values_list('field', flat=True)]
|
||||||
self._trigram_include = None
|
self._trigram_include = None
|
||||||
@ -205,7 +208,7 @@ class RecipeSearch():
|
|||||||
else:
|
else:
|
||||||
self._queryset = self._queryset.annotate(simularity=Coalesce(Subquery(simularity), 0.0))
|
self._queryset = self._queryset.annotate(simularity=Coalesce(Subquery(simularity), 0.0))
|
||||||
if self._sort_includes('score') and self._fulltext_include and self._fuzzy_match is not None:
|
if self._sort_includes('score') and self._fulltext_include and self._fuzzy_match is not None:
|
||||||
self._queryset = self._queryset.annotate(score=Sum(F('rank')+F('simularity')))
|
self._queryset = self._queryset.annotate(score=F('rank')+F('simularity'))
|
||||||
else:
|
else:
|
||||||
query_filter = Q()
|
query_filter = Q()
|
||||||
for f in [x + '__unaccent__iexact' if x in self._unaccent_include else x + '__iexact' for x in SearchFields.objects.all().values_list('field', flat=True)]:
|
for f in [x + '__unaccent__iexact' if x in self._unaccent_include else x + '__iexact' for x in SearchFields.objects.all().values_list('field', flat=True)]:
|
||||||
@ -726,9 +729,8 @@ class RecipeFacet():
|
|||||||
return self.get_facets()
|
return self.get_facets()
|
||||||
|
|
||||||
def _recipe_count_queryset(self, field, depth=1, steplen=4):
|
def _recipe_count_queryset(self, field, depth=1, steplen=4):
|
||||||
return Recipe.objects.filter(**{f'{field}__path__startswith': OuterRef('path')}, id__in=self._recipe_list, space=self._request.space
|
return Recipe.objects.filter(**{f'{field}__path__startswith': OuterRef('path'), f'{field}__depth__gte': depth}, id__in=self._recipe_list, space=self._request.space
|
||||||
).values(child=Substr(f'{field}__path', 1, steplen*depth)
|
).annotate(count=Coalesce(Func('pk', function='Count'), 0)).values('count')
|
||||||
).annotate(count=Count('pk', distinct=True)).values('count')
|
|
||||||
|
|
||||||
def _keyword_queryset(self, queryset, keyword=None):
|
def _keyword_queryset(self, queryset, keyword=None):
|
||||||
depth = getattr(keyword, 'depth', 0) + 1
|
depth = getattr(keyword, 'depth', 0) + 1
|
||||||
|
@ -4,8 +4,10 @@ from html import unescape
|
|||||||
from unicodedata import decomposition
|
from unicodedata import decomposition
|
||||||
|
|
||||||
from django.utils.dateparse import parse_duration
|
from django.utils.dateparse import parse_duration
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from isodate import parse_duration as iso_parse_duration
|
from isodate import parse_duration as iso_parse_duration
|
||||||
from isodate.isoerror import ISO8601Error
|
from isodate.isoerror import ISO8601Error
|
||||||
|
from recipe_scrapers._utils import get_minutes
|
||||||
|
|
||||||
from cookbook.helper import recipe_url_import as helper
|
from cookbook.helper import recipe_url_import as helper
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
@ -29,9 +31,14 @@ def get_from_scraper(scrape, request):
|
|||||||
recipe_json['name'] = ''
|
recipe_json['name'] = ''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
description = scrape.schema.data.get("description") or ''
|
description = scrape.description() or None
|
||||||
except Exception:
|
except Exception:
|
||||||
description = ''
|
description = None
|
||||||
|
if not description:
|
||||||
|
try:
|
||||||
|
description = scrape.schema.data.get("description") or ''
|
||||||
|
except Exception:
|
||||||
|
description = ''
|
||||||
|
|
||||||
recipe_json['description'] = parse_description(description)[:512]
|
recipe_json['description'] = parse_description(description)[:512]
|
||||||
recipe_json['internal'] = True
|
recipe_json['internal'] = True
|
||||||
@ -53,13 +60,19 @@ def get_from_scraper(scrape, request):
|
|||||||
recipe_json['servings'] = max(servings, 1)
|
recipe_json['servings'] = max(servings, 1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
recipe_json['working_time'] = get_minutes(scrape.schema.data.get("prepTime")) or 0
|
recipe_json['working_time'] = get_minutes(scrape.prep_time()) or 0
|
||||||
except Exception:
|
except Exception:
|
||||||
recipe_json['working_time'] = 0
|
try:
|
||||||
|
recipe_json['working_time'] = get_minutes(scrape.schema.data.get("prepTime")) or 0
|
||||||
|
except Exception:
|
||||||
|
recipe_json['working_time'] = 0
|
||||||
try:
|
try:
|
||||||
recipe_json['waiting_time'] = get_minutes(scrape.schema.data.get("cookTime")) or 0
|
recipe_json['waiting_time'] = get_minutes(scrape.cook_time()) or 0
|
||||||
except Exception:
|
except Exception:
|
||||||
recipe_json['waiting_time'] = 0
|
try:
|
||||||
|
recipe_json['waiting_time'] = get_minutes(scrape.schema.data.get("cookTime")) or 0
|
||||||
|
except Exception:
|
||||||
|
recipe_json['waiting_time'] = 0
|
||||||
|
|
||||||
if recipe_json['working_time'] + recipe_json['waiting_time'] == 0:
|
if recipe_json['working_time'] + recipe_json['waiting_time'] == 0:
|
||||||
try:
|
try:
|
||||||
@ -87,15 +100,23 @@ def get_from_scraper(scrape, request):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
if scrape.schema.data.get('recipeCategory'):
|
if scrape.category():
|
||||||
keywords += listify_keywords(scrape.schema.data.get("recipeCategory"))
|
keywords += listify_keywords(scrape.category())
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
try:
|
||||||
|
if scrape.schema.data.get('recipeCategory'):
|
||||||
|
keywords += listify_keywords(scrape.schema.data.get("recipeCategory"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
if scrape.schema.data.get('recipeCuisine'):
|
if scrape.cuisine():
|
||||||
keywords += listify_keywords(scrape.schema.data.get("recipeCuisine"))
|
keywords += listify_keywords(scrape.cuisine())
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
try:
|
||||||
|
if scrape.schema.data.get('recipeCuisine'):
|
||||||
|
keywords += listify_keywords(scrape.schema.data.get("recipeCuisine"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
try:
|
try:
|
||||||
recipe_json['keywords'] = parse_keywords(list(set(map(str.casefold, keywords))), request.space)
|
recipe_json['keywords'] = parse_keywords(list(set(map(str.casefold, keywords))), request.space)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@ -142,8 +163,8 @@ def get_from_scraper(scrape, request):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if scrape.url:
|
if scrape.canonical_url():
|
||||||
recipe_json['source_url'] = scrape.url
|
recipe_json['source_url'] = scrape.canonical_url()
|
||||||
return recipe_json
|
return recipe_json
|
||||||
|
|
||||||
|
|
||||||
@ -307,56 +328,6 @@ def normalize_string(string):
|
|||||||
return unescaped_string
|
return unescaped_string
|
||||||
|
|
||||||
|
|
||||||
# TODO deprecate when merged into recipe_scapers
|
|
||||||
|
|
||||||
|
|
||||||
def get_minutes(time_text):
|
|
||||||
if time_text is None:
|
|
||||||
return 0
|
|
||||||
TIME_REGEX = re.compile(
|
|
||||||
r"(\D*(?P<hours>\d*.?(\s\d)?\/?\d+)\s*(hours|hrs|hr|h|óra))?(\D*(?P<minutes>\d+)\s*(minutes|mins|min|m|perc))?",
|
|
||||||
re.IGNORECASE,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
return int(time_text)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if time_text.startswith("P") and "T" in time_text:
|
|
||||||
time_text = time_text.split("T", 2)[1]
|
|
||||||
if "-" in time_text:
|
|
||||||
time_text = time_text.split("-", 2)[
|
|
||||||
1
|
|
||||||
] # sometimes formats are like this: '12-15 minutes'
|
|
||||||
if " to " in time_text:
|
|
||||||
time_text = time_text.split("to", 2)[
|
|
||||||
1
|
|
||||||
] # sometimes formats are like this: '12 to 15 minutes'
|
|
||||||
|
|
||||||
empty = ''
|
|
||||||
for x in time_text:
|
|
||||||
if 'fraction' in decomposition(x):
|
|
||||||
f = decomposition(x[-1:]).split()
|
|
||||||
empty += f" {f[1].replace('003', '')}/{f[3].replace('003', '')}"
|
|
||||||
else:
|
|
||||||
empty += x
|
|
||||||
time_text = empty
|
|
||||||
matched = TIME_REGEX.search(time_text)
|
|
||||||
|
|
||||||
minutes = int(matched.groupdict().get("minutes") or 0)
|
|
||||||
|
|
||||||
if "/" in (hours := matched.groupdict().get("hours") or ''):
|
|
||||||
number = hours.split(" ")
|
|
||||||
if len(number) == 2:
|
|
||||||
minutes += 60 * int(number[0])
|
|
||||||
fraction = number[-1:][0].split("/")
|
|
||||||
minutes += 60 * float(int(fraction[0]) / int(fraction[1]))
|
|
||||||
else:
|
|
||||||
minutes += 60 * float(hours)
|
|
||||||
|
|
||||||
return int(minutes)
|
|
||||||
|
|
||||||
|
|
||||||
def iso_duration_to_minutes(string):
|
def iso_duration_to_minutes(string):
|
||||||
match = re.match(
|
match = re.match(
|
||||||
r'P((?P<years>\d+)Y)?((?P<months>\d+)M)?((?P<weeks>\d+)W)?((?P<days>\d+)D)?T((?P<hours>\d+)H)?((?P<minutes>\d+)M)?((?P<seconds>\d+)S)?',
|
r'P((?P<years>\d+)Y)?((?P<months>\d+)M)?((?P<weeks>\d+)W)?((?P<days>\d+)D)?T((?P<hours>\d+)H)?((?P<minutes>\d+)M)?((?P<seconds>\d+)S)?',
|
||||||
|
@ -35,7 +35,7 @@ def shopping_helper(qs, request):
|
|||||||
qs = qs.filter(Q(checked=False) | Q(completed_at__gte=week_ago))
|
qs = qs.filter(Q(checked=False) | Q(completed_at__gte=week_ago))
|
||||||
supermarket_order = ['checked'] + supermarket_order
|
supermarket_order = ['checked'] + supermarket_order
|
||||||
|
|
||||||
return qs.order_by(*supermarket_order).select_related('unit', 'food', 'ingredient', 'created_by', 'list_recipe', 'list_recipe__mealplan', 'list_recipe__recipe')
|
return qs.distinct().order_by(*supermarket_order).select_related('unit', 'food', 'ingredient', 'created_by', 'list_recipe', 'list_recipe__mealplan', 'list_recipe__recipe')
|
||||||
|
|
||||||
|
|
||||||
class RecipeShoppingEditor():
|
class RecipeShoppingEditor():
|
||||||
|
@ -2,14 +2,14 @@ import re
|
|||||||
|
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient
|
from cookbook.models import Ingredient, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class ChefTap(Integration):
|
class ChefTap(Integration):
|
||||||
|
|
||||||
def import_file_name_filter(self, zip_info_object):
|
def import_file_name_filter(self, zip_info_object):
|
||||||
print("testing", zip_info_object.filename)
|
print("testing", zip_info_object.filename)
|
||||||
return re.match(r'^cheftap_export/([A-Za-z\d\w\s-])+.txt$', zip_info_object.filename) or re.match(r'^([A-Za-z\d\w\s-])+.txt$', zip_info_object.filename)
|
return re.match(r'^cheftap_export/([A-Za-z\d\s\-_()\[\]\u00C0-\u017F])+.txt$', zip_info_object.filename) or re.match(r'^([A-Za-z\d\s\-_()\[\]\u00C0-\u017F])+.txt$', zip_info_object.filename)
|
||||||
|
|
||||||
def get_recipe_from_file(self, file):
|
def get_recipe_from_file(self, file):
|
||||||
source_url = ''
|
source_url = ''
|
||||||
@ -45,11 +45,11 @@ class ChefTap(Integration):
|
|||||||
ingredient_parser = IngredientParser(self.request, True)
|
ingredient_parser = IngredientParser(self.request, True)
|
||||||
for ingredient in ingredients:
|
for ingredient in ingredients:
|
||||||
if len(ingredient.strip()) > 0:
|
if len(ingredient.strip()) > 0:
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
|
amount, unit, food, note = ingredient_parser.parse(ingredient)
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(food)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space,
|
||||||
))
|
))
|
||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
|
||||||
|
@ -5,14 +5,14 @@ from zipfile import ZipFile
|
|||||||
from cookbook.helper.image_processing import get_filetype
|
from cookbook.helper.image_processing import get_filetype
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
from cookbook.models import Ingredient, Keyword, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class Chowdown(Integration):
|
class Chowdown(Integration):
|
||||||
|
|
||||||
def import_file_name_filter(self, zip_info_object):
|
def import_file_name_filter(self, zip_info_object):
|
||||||
print("testing", zip_info_object.filename)
|
print("testing", zip_info_object.filename)
|
||||||
return re.match(r'^(_)*recipes/([A-Za-z\d\s-])+.md$', zip_info_object.filename)
|
return re.match(r'^(_)*recipes/([A-Za-z\d\s\-_()\[\]\u00C0-\u017F])+.md$', zip_info_object.filename)
|
||||||
|
|
||||||
def get_recipe_from_file(self, file):
|
def get_recipe_from_file(self, file):
|
||||||
ingredient_mode = False
|
ingredient_mode = False
|
||||||
@ -60,12 +60,13 @@ class Chowdown(Integration):
|
|||||||
|
|
||||||
ingredient_parser = IngredientParser(self.request, True)
|
ingredient_parser = IngredientParser(self.request, True)
|
||||||
for ingredient in ingredients:
|
for ingredient in ingredients:
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
|
if len(ingredient.strip()) > 0:
|
||||||
f = ingredient_parser.get_food(ingredient)
|
amount, unit, food, note = ingredient_parser.parse(ingredient)
|
||||||
u = ingredient_parser.get_unit(unit)
|
f = ingredient_parser.get_food(food)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
u = ingredient_parser.get_unit(unit)
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
))
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space,
|
||||||
|
))
|
||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
|
||||||
for f in self.files:
|
for f in self.files:
|
||||||
|
@ -2,6 +2,7 @@ import base64
|
|||||||
import gzip
|
import gzip
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
from gettext import gettext as _
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
@ -11,8 +12,7 @@ from cookbook.helper.ingredient_parser import IngredientParser
|
|||||||
from cookbook.helper.recipe_html_import import get_recipe_from_source
|
from cookbook.helper.recipe_html_import import get_recipe_from_source
|
||||||
from cookbook.helper.recipe_url_import import iso_duration_to_minutes
|
from cookbook.helper.recipe_url_import import iso_duration_to_minutes
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
from cookbook.models import Ingredient, Keyword, Recipe, Step
|
||||||
from gettext import gettext as _
|
|
||||||
|
|
||||||
|
|
||||||
class CookBookApp(Integration):
|
class CookBookApp(Integration):
|
||||||
@ -51,11 +51,11 @@ class CookBookApp(Integration):
|
|||||||
|
|
||||||
ingredient_parser = IngredientParser(self.request, True)
|
ingredient_parser = IngredientParser(self.request, True)
|
||||||
for ingredient in recipe_json['recipeIngredient']:
|
for ingredient in recipe_json['recipeIngredient']:
|
||||||
f = ingredient_parser.get_food(ingredient['ingredient']['text'])
|
f = ingredient_parser.get_food(ingredient['ingredient']['text'])
|
||||||
u = ingredient_parser.get_unit(ingredient['unit']['text'])
|
u = ingredient_parser.get_unit(ingredient['unit']['text'])
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=ingredient['amount'], note=ingredient['note'], space=self.request.space,
|
food=f, unit=u, amount=ingredient['amount'], note=ingredient['note'], space=self.request.space,
|
||||||
))
|
))
|
||||||
|
|
||||||
if len(images) > 0:
|
if len(images) > 0:
|
||||||
try:
|
try:
|
||||||
|
@ -4,11 +4,12 @@ from zipfile import ZipFile
|
|||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.helper.recipe_html_import import get_recipe_from_source
|
from cookbook.helper.recipe_html_import import get_recipe_from_source
|
||||||
from cookbook.helper.recipe_url_import import iso_duration_to_minutes, parse_servings
|
from cookbook.helper.recipe_url_import import iso_duration_to_minutes, parse_servings
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
from cookbook.models import Ingredient, Keyword, Recipe, Step
|
||||||
from recipes.settings import DEBUG
|
from recipes.settings import DEBUG
|
||||||
|
|
||||||
|
|
||||||
@ -41,11 +42,11 @@ class CopyMeThat(Integration):
|
|||||||
for ingredient in file.find_all("li", {"class": "recipeIngredient"}):
|
for ingredient in file.find_all("li", {"class": "recipeIngredient"}):
|
||||||
if ingredient.text == "":
|
if ingredient.text == "":
|
||||||
continue
|
continue
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient.text.strip())
|
amount, unit, food, note = ingredient_parser.parse(ingredient.text.strip())
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(food)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient.text.strip(), space=self.request.space,
|
||||||
))
|
))
|
||||||
|
|
||||||
for s in file.find_all("li", {"class": "instruction"}):
|
for s in file.find_all("li", {"class": "instruction"}):
|
||||||
@ -60,7 +61,7 @@ class CopyMeThat(Integration):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if file.find("a", {"id": "original_link"}).text != '':
|
if file.find("a", {"id": "original_link"}).text != '':
|
||||||
step.instruction += "\n\nImported from: " + file.find("a", {"id": "original_link"}).text
|
step.instruction += "\n\n" + _("Imported from") + ": " + file.find("a", {"id": "original_link"}).text
|
||||||
step.save()
|
step.save()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
@ -4,7 +4,7 @@ from io import BytesIO
|
|||||||
|
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient
|
from cookbook.models import Ingredient, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class Domestica(Integration):
|
class Domestica(Integration):
|
||||||
@ -37,11 +37,11 @@ class Domestica(Integration):
|
|||||||
ingredient_parser = IngredientParser(self.request, True)
|
ingredient_parser = IngredientParser(self.request, True)
|
||||||
for ingredient in file['ingredients'].split('\n'):
|
for ingredient in file['ingredients'].split('\n'):
|
||||||
if len(ingredient.strip()) > 0:
|
if len(ingredient.strip()) > 0:
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
|
amount, unit, food, note = ingredient_parser.parse(ingredient)
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(food)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space,
|
||||||
))
|
))
|
||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ class Integration:
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
self.handle_exception(e, log=il, message=f'-------------------- \nERROR \n{e}\n--------------------\n')
|
self.handle_exception(e, log=il, message=f'-------------------- \nERROR \n{e}\n--------------------\n')
|
||||||
import_zip.close()
|
import_zip.close()
|
||||||
elif '.json' in f['name'] or '.txt' in f['name'] or '.mmf' in f['name']:
|
elif '.json' in f['name'] or '.txt' in f['name'] or '.mmf' in f['name'] or '.rk' in f['name']:
|
||||||
data_list = self.split_recipe_file(f['file'])
|
data_list = self.split_recipe_file(f['file'])
|
||||||
il.total_recipes += len(data_list)
|
il.total_recipes += len(data_list)
|
||||||
for d in data_list:
|
for d in data_list:
|
||||||
|
@ -6,13 +6,13 @@ from zipfile import ZipFile
|
|||||||
from cookbook.helper.image_processing import get_filetype
|
from cookbook.helper.image_processing import get_filetype
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient
|
from cookbook.models import Ingredient, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class Mealie(Integration):
|
class Mealie(Integration):
|
||||||
|
|
||||||
def import_file_name_filter(self, zip_info_object):
|
def import_file_name_filter(self, zip_info_object):
|
||||||
return re.match(r'^recipes/([A-Za-z\d-])+/([A-Za-z\d-])+.json$', zip_info_object.filename)
|
return re.match(r'^recipes/([A-Za-z\d\s\-_()\[\]\u00C0-\u017F])+/([A-Za-z\d\s\-_()\[\]\u00C0-\u017F])+.json$', zip_info_object.filename)
|
||||||
|
|
||||||
def get_recipe_from_file(self, file):
|
def get_recipe_from_file(self, file):
|
||||||
recipe_json = json.loads(file.getvalue().decode("utf-8"))
|
recipe_json = json.loads(file.getvalue().decode("utf-8"))
|
||||||
@ -45,12 +45,14 @@ class Mealie(Integration):
|
|||||||
u = ingredient_parser.get_unit(ingredient['unit'])
|
u = ingredient_parser.get_unit(ingredient['unit'])
|
||||||
amount = ingredient['quantity']
|
amount = ingredient['quantity']
|
||||||
note = ingredient['note']
|
note = ingredient['note']
|
||||||
|
original_text = None
|
||||||
else:
|
else:
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient['note'])
|
amount, unit, food, note = ingredient_parser.parse(ingredient['note'])
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(food)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
|
original_text = ingredient['note']
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=original_text, space=self.request.space,
|
||||||
))
|
))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@ -60,7 +62,8 @@ class Mealie(Integration):
|
|||||||
if '.zip' in f['name']:
|
if '.zip' in f['name']:
|
||||||
import_zip = ZipFile(f['file'])
|
import_zip = ZipFile(f['file'])
|
||||||
try:
|
try:
|
||||||
self.import_recipe_image(recipe, BytesIO(import_zip.read(f'recipes/{recipe_json["slug"]}/images/min-original.webp')), filetype=get_filetype(f'recipes/{recipe_json["slug"]}/images/original'))
|
self.import_recipe_image(recipe, BytesIO(import_zip.read(f'recipes/{recipe_json["slug"]}/images/min-original.webp')),
|
||||||
|
filetype=get_filetype(f'recipes/{recipe_json["slug"]}/images/original'))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import re
|
|||||||
|
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
from cookbook.models import Ingredient, Keyword, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class MealMaster(Integration):
|
class MealMaster(Integration):
|
||||||
@ -45,11 +45,11 @@ class MealMaster(Integration):
|
|||||||
ingredient_parser = IngredientParser(self.request, True)
|
ingredient_parser = IngredientParser(self.request, True)
|
||||||
for ingredient in ingredients:
|
for ingredient in ingredients:
|
||||||
if len(ingredient.strip()) > 0:
|
if len(ingredient.strip()) > 0:
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
|
amount, unit, food, note = ingredient_parser.parse(ingredient)
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(ingredient)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space,
|
||||||
))
|
))
|
||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from cookbook.helper.image_processing import get_filetype
|
|||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.helper.recipe_url_import import iso_duration_to_minutes
|
from cookbook.helper.recipe_url_import import iso_duration_to_minutes
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
from cookbook.models import Ingredient, Keyword, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class NextcloudCookbook(Integration):
|
class NextcloudCookbook(Integration):
|
||||||
@ -57,11 +57,11 @@ class NextcloudCookbook(Integration):
|
|||||||
|
|
||||||
ingredient_parser = IngredientParser(self.request, True)
|
ingredient_parser = IngredientParser(self.request, True)
|
||||||
for ingredient in recipe_json['recipeIngredient']:
|
for ingredient in recipe_json['recipeIngredient']:
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
|
amount, unit, food, note = ingredient_parser.parse(ingredient)
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(food)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space,
|
||||||
))
|
))
|
||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import json
|
|||||||
|
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient
|
from cookbook.models import Ingredient, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class OpenEats(Integration):
|
class OpenEats(Integration):
|
||||||
|
@ -2,12 +2,12 @@ import base64
|
|||||||
import gzip
|
import gzip
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
from gettext import gettext as _
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
from cookbook.models import Ingredient, Keyword, Recipe, Step
|
||||||
from gettext import gettext as _
|
|
||||||
|
|
||||||
|
|
||||||
class Paprika(Integration):
|
class Paprika(Integration):
|
||||||
@ -70,11 +70,11 @@ class Paprika(Integration):
|
|||||||
try:
|
try:
|
||||||
for ingredient in recipe_json['ingredients'].split('\n'):
|
for ingredient in recipe_json['ingredients'].split('\n'):
|
||||||
if len(ingredient.strip()) > 0:
|
if len(ingredient.strip()) > 0:
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
|
amount, unit, food, note = ingredient_parser.parse(ingredient)
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(food)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space,
|
||||||
))
|
))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient
|
from cookbook.models import Ingredient, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class Pepperplate(Integration):
|
class Pepperplate(Integration):
|
||||||
@ -41,11 +41,11 @@ class Pepperplate(Integration):
|
|||||||
ingredient_parser = IngredientParser(self.request, True)
|
ingredient_parser = IngredientParser(self.request, True)
|
||||||
for ingredient in ingredients:
|
for ingredient in ingredients:
|
||||||
if len(ingredient.strip()) > 0:
|
if len(ingredient.strip()) > 0:
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
|
amount, unit, food, note = ingredient_parser.parse(ingredient)
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(food)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space,
|
||||||
))
|
))
|
||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import requests
|
|||||||
|
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
from cookbook.models import Ingredient, Keyword, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class Plantoeat(Integration):
|
class Plantoeat(Integration):
|
||||||
@ -56,11 +56,11 @@ class Plantoeat(Integration):
|
|||||||
ingredient_parser = IngredientParser(self.request, True)
|
ingredient_parser = IngredientParser(self.request, True)
|
||||||
for ingredient in ingredients:
|
for ingredient in ingredients:
|
||||||
if len(ingredient.strip()) > 0:
|
if len(ingredient.strip()) > 0:
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
|
amount, unit, food, note = ingredient_parser.parse(ingredient)
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(food)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space,
|
||||||
))
|
))
|
||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import re
|
import imghdr
|
||||||
import json
|
import json
|
||||||
import requests
|
import re
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
import imghdr
|
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from cookbook.helper.image_processing import get_filetype
|
from cookbook.helper.image_processing import get_filetype
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
from cookbook.models import Ingredient, Keyword, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class RecetteTek(Integration):
|
class RecetteTek(Integration):
|
||||||
@ -48,7 +50,7 @@ class RecetteTek(Integration):
|
|||||||
# Append the original import url to the step (if it exists)
|
# Append the original import url to the step (if it exists)
|
||||||
try:
|
try:
|
||||||
if file['url'] != '':
|
if file['url'] != '':
|
||||||
step.instruction += '\n\nImported from: ' + file['url']
|
step.instruction += '\n\n' + _('Imported from') + ': ' + file['url']
|
||||||
step.save()
|
step.save()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(recipe.name, ': failed to import source url ', str(e))
|
print(recipe.name, ': failed to import source url ', str(e))
|
||||||
@ -58,11 +60,11 @@ class RecetteTek(Integration):
|
|||||||
ingredient_parser = IngredientParser(self.request, True)
|
ingredient_parser = IngredientParser(self.request, True)
|
||||||
for ingredient in file['ingredients'].split('\n'):
|
for ingredient in file['ingredients'].split('\n'):
|
||||||
if len(ingredient.strip()) > 0:
|
if len(ingredient.strip()) > 0:
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
|
amount, unit, food, note = ingredient_parser.parse(food)
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(ingredient)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space,
|
||||||
))
|
))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(recipe.name, ': failed to parse recipe ingredients ', str(e))
|
print(recipe.name, ': failed to parse recipe ingredients ', str(e))
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import re
|
import re
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.helper.recipe_url_import import parse_servings, iso_duration_to_minutes
|
from cookbook.helper.recipe_url_import import iso_duration_to_minutes, parse_servings
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
from cookbook.models import Ingredient, Keyword, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class RecipeKeeper(Integration):
|
class RecipeKeeper(Integration):
|
||||||
@ -45,11 +47,11 @@ class RecipeKeeper(Integration):
|
|||||||
for ingredient in file.find("div", {"itemprop": "recipeIngredients"}).findChildren("p"):
|
for ingredient in file.find("div", {"itemprop": "recipeIngredients"}).findChildren("p"):
|
||||||
if ingredient.text == "":
|
if ingredient.text == "":
|
||||||
continue
|
continue
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient.text.strip())
|
amount, unit, food, note = ingredient_parser.parse(ingredient.text.strip())
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(food)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space,
|
||||||
))
|
))
|
||||||
|
|
||||||
for s in file.find("div", {"itemprop": "recipeDirections"}).find_all("p"):
|
for s in file.find("div", {"itemprop": "recipeDirections"}).find_all("p"):
|
||||||
@ -58,7 +60,7 @@ class RecipeKeeper(Integration):
|
|||||||
step.instruction += s.text + ' \n'
|
step.instruction += s.text + ' \n'
|
||||||
|
|
||||||
if file.find("span", {"itemprop": "recipeSource"}).text != '':
|
if file.find("span", {"itemprop": "recipeSource"}).text != '':
|
||||||
step.instruction += "\n\nImported from: " + file.find("span", {"itemprop": "recipeSource"}).text
|
step.instruction += "\n\n" + _("Imported from") + ": " + file.find("span", {"itemprop": "recipeSource"}).text
|
||||||
step.save()
|
step.save()
|
||||||
|
|
||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
@ -5,7 +5,7 @@ import requests
|
|||||||
|
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient
|
from cookbook.models import Ingredient, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class RecipeSage(Integration):
|
class RecipeSage(Integration):
|
||||||
@ -31,7 +31,7 @@ class RecipeSage(Integration):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print('failed to parse yield or time ', str(e))
|
print('failed to parse yield or time ', str(e))
|
||||||
|
|
||||||
ingredient_parser = IngredientParser(self.request,True)
|
ingredient_parser = IngredientParser(self.request, True)
|
||||||
ingredients_added = False
|
ingredients_added = False
|
||||||
for s in file['recipeInstructions']:
|
for s in file['recipeInstructions']:
|
||||||
step = Step.objects.create(
|
step = Step.objects.create(
|
||||||
@ -41,11 +41,11 @@ class RecipeSage(Integration):
|
|||||||
ingredients_added = True
|
ingredients_added = True
|
||||||
|
|
||||||
for ingredient in file['recipeIngredient']:
|
for ingredient in file['recipeIngredient']:
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
|
amount, unit, food, note = ingredient_parser.parse(ingredient)
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(food)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space,
|
||||||
))
|
))
|
||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient, Keyword
|
from cookbook.models import Ingredient, Keyword, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class RezKonv(Integration):
|
class RezKonv(Integration):
|
||||||
@ -44,11 +44,11 @@ class RezKonv(Integration):
|
|||||||
ingredient_parser = IngredientParser(self.request, True)
|
ingredient_parser = IngredientParser(self.request, True)
|
||||||
for ingredient in ingredients:
|
for ingredient in ingredients:
|
||||||
if len(ingredient.strip()) > 0:
|
if len(ingredient.strip()) > 0:
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
|
amount, unit, food, note = ingredient_parser.parse(ingredient)
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(food)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space,
|
||||||
))
|
))
|
||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
|
||||||
@ -60,9 +60,14 @@ class RezKonv(Integration):
|
|||||||
def split_recipe_file(self, file):
|
def split_recipe_file(self, file):
|
||||||
recipe_list = []
|
recipe_list = []
|
||||||
current_recipe = ''
|
current_recipe = ''
|
||||||
|
encoding_list = ['windows-1250', 'latin-1'] #TODO build algorithm to try trough encodings and fail if none work, use for all importers
|
||||||
|
encoding = 'windows-1250'
|
||||||
for fl in file.readlines():
|
for fl in file.readlines():
|
||||||
line = fl.decode("windows-1250")
|
try:
|
||||||
|
line = fl.decode(encoding)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
encoding = 'latin-1'
|
||||||
|
line = fl.decode(encoding)
|
||||||
if line.startswith('=====') and 'rezkonv' in line.lower():
|
if line.startswith('=====') and 'rezkonv' in line.lower():
|
||||||
if current_recipe != '':
|
if current_recipe != '':
|
||||||
recipe_list.append(current_recipe)
|
recipe_list.append(current_recipe)
|
||||||
|
@ -2,7 +2,7 @@ from django.utils.translation import gettext as _
|
|||||||
|
|
||||||
from cookbook.helper.ingredient_parser import IngredientParser
|
from cookbook.helper.ingredient_parser import IngredientParser
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
from cookbook.models import Recipe, Step, Ingredient
|
from cookbook.models import Ingredient, Recipe, Step
|
||||||
|
|
||||||
|
|
||||||
class Saffron(Integration):
|
class Saffron(Integration):
|
||||||
@ -47,11 +47,11 @@ class Saffron(Integration):
|
|||||||
|
|
||||||
ingredient_parser = IngredientParser(self.request, True)
|
ingredient_parser = IngredientParser(self.request, True)
|
||||||
for ingredient in ingredients:
|
for ingredient in ingredients:
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
|
amount, unit, food, note = ingredient_parser.parse(ingredient)
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(food)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
step.ingredients.add(Ingredient.objects.create(
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
food=f, unit=u, amount=amount, note=note, space=self.request.space,
|
food=f, unit=u, amount=amount, note=note, original_text=ingredient, space=self.request.space,
|
||||||
))
|
))
|
||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ class Saffron(Integration):
|
|||||||
|
|
||||||
for i in s.ingredients.all():
|
for i in s.ingredients.all():
|
||||||
recipeIngredient.append(f'{float(i.amount)} {i.unit} {i.food}')
|
recipeIngredient.append(f'{float(i.amount)} {i.unit} {i.food}')
|
||||||
|
|
||||||
data += "Ingredients: \n"
|
data += "Ingredients: \n"
|
||||||
for ingredient in recipeIngredient:
|
for ingredient in recipeIngredient:
|
||||||
data += ingredient+"\n"
|
data += ingredient+"\n"
|
||||||
@ -91,10 +91,10 @@ class Saffron(Integration):
|
|||||||
files = []
|
files = []
|
||||||
for r in recipes:
|
for r in recipes:
|
||||||
filename, data = self.get_file_from_recipe(r)
|
filename, data = self.get_file_from_recipe(r)
|
||||||
files.append([ filename, data ])
|
files.append([filename, data])
|
||||||
|
|
||||||
el.exported_recipes += 1
|
el.exported_recipes += 1
|
||||||
el.msg += self.get_recipe_processed_msg(r)
|
el.msg += self.get_recipe_processed_msg(r)
|
||||||
el.save()
|
el.save()
|
||||||
|
|
||||||
return files
|
return files
|
||||||
|
18
cookbook/migrations/0172_ingredient_original_text.py
Normal file
18
cookbook/migrations/0172_ingredient_original_text.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.12 on 2022-02-25 15:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cookbook', '0171_alter_searchpreference_trigram_threshold'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ingredient',
|
||||||
|
name='original_text',
|
||||||
|
field=models.CharField(blank=True, default=None, max_length=512, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -62,9 +62,10 @@ class TreeManager(MP_NodeManager):
|
|||||||
# model.Manager get_or_create() is not compatible with MP_Tree
|
# model.Manager get_or_create() is not compatible with MP_Tree
|
||||||
def get_or_create(self, *args, **kwargs):
|
def get_or_create(self, *args, **kwargs):
|
||||||
kwargs['name'] = kwargs['name'].strip()
|
kwargs['name'] = kwargs['name'].strip()
|
||||||
try:
|
|
||||||
return self.get(name__iexact=kwargs['name'], space=kwargs['space']), False
|
if obj := self.filter(name__iexact=kwargs['name'], space=kwargs['space']).first():
|
||||||
except self.model.DoesNotExist:
|
return obj, False
|
||||||
|
else:
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
try:
|
try:
|
||||||
defaults = kwargs.pop('defaults', None)
|
defaults = kwargs.pop('defaults', None)
|
||||||
@ -590,6 +591,7 @@ class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, Permiss
|
|||||||
is_header = models.BooleanField(default=False)
|
is_header = models.BooleanField(default=False)
|
||||||
no_amount = models.BooleanField(default=False)
|
no_amount = models.BooleanField(default=False)
|
||||||
order = models.IntegerField(default=0)
|
order = models.IntegerField(default=0)
|
||||||
|
original_text = models.CharField(max_length=512, null=True, blank=True, default=None)
|
||||||
|
|
||||||
original_text = models.CharField(max_length=512, null=True, blank=True, default=None)
|
original_text = models.CharField(max_length=512, null=True, blank=True, default=None)
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ from cookbook.models import Recipe, RecipeImport, SyncLog
|
|||||||
from cookbook.provider.provider import Provider
|
from cookbook.provider.provider import Provider
|
||||||
from requests.auth import HTTPBasicAuth
|
from requests.auth import HTTPBasicAuth
|
||||||
|
|
||||||
|
from recipes.settings import DEBUG
|
||||||
|
|
||||||
|
|
||||||
class Nextcloud(Provider):
|
class Nextcloud(Provider):
|
||||||
|
|
||||||
@ -28,15 +30,18 @@ class Nextcloud(Provider):
|
|||||||
def import_all(monitor):
|
def import_all(monitor):
|
||||||
client = Nextcloud.get_client(monitor.storage)
|
client = Nextcloud.get_client(monitor.storage)
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
print(f'TANDOOR_PROVIDER_DEBUG checking path {monitor.path} with client {client}')
|
||||||
|
|
||||||
files = client.list(monitor.path)
|
files = client.list(monitor.path)
|
||||||
|
|
||||||
try:
|
if DEBUG:
|
||||||
files.pop(0) # remove first element because its the folder itself
|
print(f'TANDOOR_PROVIDER_DEBUG file list {files}')
|
||||||
except IndexError:
|
|
||||||
pass # folder is empty, no recipes will be imported
|
|
||||||
|
|
||||||
import_count = 0
|
import_count = 0
|
||||||
for file in files:
|
for file in files:
|
||||||
|
if DEBUG:
|
||||||
|
print(f'TANDOOR_PROVIDER_DEBUG importing file {file}')
|
||||||
path = monitor.path + '/' + file
|
path = monitor.path + '/' + file
|
||||||
if not Recipe.objects.filter(file_path__iexact=path, space=monitor.space).exists() and not RecipeImport.objects.filter(file_path=path, space=monitor.space).exists():
|
if not Recipe.objects.filter(file_path__iexact=path, space=monitor.space).exists() and not RecipeImport.objects.filter(file_path=path, space=monitor.space).exists():
|
||||||
name = os.path.splitext(file)[0]
|
name = os.path.splitext(file)[0]
|
||||||
|
@ -337,7 +337,7 @@ class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerial
|
|||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
name = validated_data.pop('name').strip()
|
name = validated_data.pop('name').strip()
|
||||||
space = validated_data.pop('space', self.context['request'].space)
|
space = validated_data.pop('space', self.context['request'].space)
|
||||||
obj, created = SupermarketCategory.objects.get_or_create(name__iexact=name, space=space, defaults=validated_data)
|
obj, created = SupermarketCategory.objects.get_or_create(name=name, space=space)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
@ -421,9 +421,11 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
|
|||||||
space = validated_data.pop('space', self.context['request'].space)
|
space = validated_data.pop('space', self.context['request'].space)
|
||||||
# supermarket category needs to be handled manually as food.get or create does not create nested serializers unlike a super.create of serializer
|
# supermarket category needs to be handled manually as food.get or create does not create nested serializers unlike a super.create of serializer
|
||||||
if 'supermarket_category' in validated_data and validated_data['supermarket_category']:
|
if 'supermarket_category' in validated_data and validated_data['supermarket_category']:
|
||||||
|
sm_category = validated_data['supermarket_category']
|
||||||
|
sc_name = sm_category.pop('name', None)
|
||||||
validated_data['supermarket_category'], sc_created = SupermarketCategory.objects.get_or_create(
|
validated_data['supermarket_category'], sc_created = SupermarketCategory.objects.get_or_create(
|
||||||
name__iexact=validated_data.pop('supermarket_category')['name'],
|
name=sc_name,
|
||||||
space=self.context['request'].space)
|
space=space, defaults=sm_category)
|
||||||
onhand = validated_data.pop('food_onhand', None)
|
onhand = validated_data.pop('food_onhand', None)
|
||||||
|
|
||||||
# assuming if on hand for user also onhand for shopping_share users
|
# assuming if on hand for user also onhand for shopping_share users
|
||||||
@ -479,6 +481,10 @@ class IngredientSerializer(WritableNestedModelSerializer):
|
|||||||
validated_data['space'] = self.context['request'].space
|
validated_data['space'] = self.context['request'].space
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
validated_data.pop('original_text', None)
|
||||||
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ingredient
|
model = Ingredient
|
||||||
fields = (
|
fields = (
|
||||||
@ -681,7 +687,7 @@ class RecipeBookEntrySerializer(serializers.ModelSerializer):
|
|||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
book = validated_data['book']
|
book = validated_data['book']
|
||||||
recipe = validated_data['recipe']
|
recipe = validated_data['recipe']
|
||||||
if not book.get_owner() == self.context['request'].user and not self.context['request'].user in book.get_shared():
|
if not book.get_owner() == self.context['request'].user and not self.context['request'].user in book.get_shared():
|
||||||
raise NotFound(detail=None, code=None)
|
raise NotFound(detail=None, code=None)
|
||||||
obj, created = RecipeBookEntry.objects.get_or_create(book=book, recipe=recipe)
|
obj, created = RecipeBookEntry.objects.get_or_create(book=book, recipe=recipe)
|
||||||
return obj
|
return obj
|
||||||
@ -736,11 +742,11 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
|
|||||||
value = Decimal(value)
|
value = Decimal(value)
|
||||||
value = value.quantize(Decimal(1)) if value == value.to_integral() else value.normalize() # strips trailing zero
|
value = value.quantize(Decimal(1)) if value == value.to_integral() else value.normalize() # strips trailing zero
|
||||||
return (
|
return (
|
||||||
obj.name
|
obj.name
|
||||||
or getattr(obj.mealplan, 'title', None)
|
or getattr(obj.mealplan, 'title', None)
|
||||||
or (d := getattr(obj.mealplan, 'date', None)) and ': '.join([obj.mealplan.recipe.name, str(d)])
|
or (d := getattr(obj.mealplan, 'date', None)) and ': '.join([obj.mealplan.recipe.name, str(d)])
|
||||||
or obj.recipe.name
|
or obj.recipe.name
|
||||||
) + f' ({value:.2g})'
|
) + f' ({value:.2g})'
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
# TODO remove once old shopping list
|
# TODO remove once old shopping list
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
window.IMAGE_PLACEHOLDER = "{% static 'assets/recipe_no_image.svg' %}"
|
window.IMAGE_PLACEHOLDER = "{% static 'assets/recipe_no_image.svg' %}"
|
||||||
|
window.CUSTOM_LOCALE = '{{ request.LANGUAGE_CODE }}'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% render_bundle 'shopping_list_view' %} {% endblock %}
|
{% render_bundle 'shopping_list_view' %} {% endblock %}
|
||||||
|
@ -88,9 +88,8 @@
|
|||||||
<h4>
|
<h4>
|
||||||
{% trans 'Members' %}
|
{% trans 'Members' %}
|
||||||
<small class="text-muted"
|
<small class="text-muted"
|
||||||
>{{ space_users|length }}/ {% if request.space.max_users > 0 %} {{ request.space.max_users }}{% else
|
>{{ space_users|length }}/{% if request.space.max_users > 0 %} {{ request.space.max_users }}{% else %}∞{% endif %}
|
||||||
%}∞{% endif %}</small
|
</small>
|
||||||
>
|
|
||||||
<a class="btn btn-success float-right" href="{% url 'new_invite_link' %}"
|
<a class="btn btn-success float-right" href="{% url 'new_invite_link' %}"
|
||||||
><i class="fas fa-plus-circle"></i> {% trans 'Invite User' %}</a
|
><i class="fas fa-plus-circle"></i> {% trans 'Invite User' %}</a
|
||||||
>
|
>
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
style="height:50%"
|
style="height:50%"
|
||||||
href="{% bookmarklet request %}"
|
href="{% bookmarklet request %}"
|
||||||
title="{% trans 'Drag me to your bookmarks to import recipes from anywhere' %}">
|
title="{% trans 'Drag me to your bookmarks to import recipes from anywhere' %}">
|
||||||
<img src="{% static 'assets/favicon-16x16.png' %}">{% trans 'Bookmark Me!' %} </a>
|
<img src="{% static 'assets/favicon-16x16.png' %}" style="margin-right: 1em;">{% trans 'Bookmark Me!' %} </a>
|
||||||
</div>
|
</div>
|
||||||
<nav class="nav nav-pills flex-sm-row mb-2">
|
<nav class="nav nav-pills flex-sm-row mb-2">
|
||||||
<a class="nav-link active" href="#nav-url" data-toggle="tab" role="tab" aria-controls="nav-url"
|
<a class="nav-link active" href="#nav-url" data-toggle="tab" role="tab" aria-controls="nav-url"
|
||||||
@ -50,11 +50,11 @@
|
|||||||
<div class="tab-pane fade show active" id="nav-url" role="tabpanel">
|
<div class="tab-pane fade show active" id="nav-url" role="tabpanel">
|
||||||
<div class="btn-group btn-group-toggle mt-2" data-toggle="buttons">
|
<div class="btn-group btn-group-toggle mt-2" data-toggle="buttons">
|
||||||
<label class="btn btn-outline-info btn-sm active" @click="automatic=true">
|
<label class="btn btn-outline-info btn-sm active" @click="automatic=true">
|
||||||
<input type="radio" autocomplete="off" checked> Automatic
|
<input type="radio" autocomplete="off" checked> {% trans 'Automatic' %}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="btn btn-outline-info btn-sm" @click="automatic=false">
|
<label class="btn btn-outline-info btn-sm" @click="automatic=false">
|
||||||
<input type="radio" autocomplete="off"> Manual
|
<input type="radio" autocomplete="off"> {% trans 'Manual' %}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div role="group" class="input-group mt-4">
|
<div role="group" class="input-group mt-4">
|
||||||
@ -473,9 +473,9 @@
|
|||||||
|
|
||||||
<div class="card" style="margin-top: 4px">
|
<div class="card" style="margin-top: 4px">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row" v-if="i.original">
|
<div class="row" v-if="i.original_text">
|
||||||
<div class="col-md-12" style="margin-bottom: 4px">
|
<div class="col-md-12" style="margin-bottom: 4px">
|
||||||
<span class="text-muted"><i class="fas fa-globe"></i> [[i.original]]</span>
|
<span class="text-muted"><i class="fas fa-globe"></i> [[i.original_text]]</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -1024,7 +1024,7 @@
|
|||||||
amount: String(response.body.amount),
|
amount: String(response.body.amount),
|
||||||
ingredient: {id: Math.random() * 1000, text: response.body.food},
|
ingredient: {id: Math.random() * 1000, text: response.body.food},
|
||||||
note: response.body.note,
|
note: response.body.note,
|
||||||
original: v
|
original_text: v
|
||||||
}
|
}
|
||||||
this.recipe_json.recipeIngredient.push(new_ingredient)
|
this.recipe_json.recipeIngredient.push(new_ingredient)
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -156,7 +156,7 @@ def import_url(request):
|
|||||||
recipe.steps.add(step)
|
recipe.steps.add(step)
|
||||||
|
|
||||||
for kw in data['keywords']:
|
for kw in data['keywords']:
|
||||||
if data['all_keywords']: # do not remove this check :) https://github.com/vabene1111/recipes/issues/645
|
if data['all_keywords']: # do not remove this check :) https://github.com/vabene1111/recipes/issues/645
|
||||||
k, created = Keyword.objects.get_or_create(name=kw['text'], space=request.space)
|
k, created = Keyword.objects.get_or_create(name=kw['text'], space=request.space)
|
||||||
recipe.keywords.add(k)
|
recipe.keywords.add(k)
|
||||||
else:
|
else:
|
||||||
@ -168,7 +168,8 @@ def import_url(request):
|
|||||||
|
|
||||||
ingredient_parser = IngredientParser(request, True)
|
ingredient_parser = IngredientParser(request, True)
|
||||||
for ing in data['recipeIngredient']:
|
for ing in data['recipeIngredient']:
|
||||||
ingredient = Ingredient(space=request.space, )
|
original = ing.pop('original', None) or ing.pop('original_text', None)
|
||||||
|
ingredient = Ingredient(original_text=original, space=request.space, )
|
||||||
|
|
||||||
if food_text := ing['ingredient']['text'].strip():
|
if food_text := ing['ingredient']['text'].strip():
|
||||||
ingredient.food = ingredient_parser.get_food(food_text)
|
ingredient.food = ingredient_parser.get_food(food_text)
|
||||||
|
@ -11,6 +11,7 @@ from django.utils.translation import gettext as _
|
|||||||
|
|
||||||
from cookbook.forms import ExportForm, ImportExportBase, ImportForm
|
from cookbook.forms import ExportForm, ImportExportBase, ImportForm
|
||||||
from cookbook.helper.permission_helper import group_required
|
from cookbook.helper.permission_helper import group_required
|
||||||
|
from cookbook.helper.recipe_search import RecipeSearch
|
||||||
from cookbook.integration.cheftap import ChefTap
|
from cookbook.integration.cheftap import ChefTap
|
||||||
from cookbook.integration.chowdown import Chowdown
|
from cookbook.integration.chowdown import Chowdown
|
||||||
from cookbook.integration.cookbookapp import CookBookApp
|
from cookbook.integration.cookbookapp import CookBookApp
|
||||||
@ -123,6 +124,9 @@ def export_recipe(request):
|
|||||||
recipes = form.cleaned_data['recipes']
|
recipes = form.cleaned_data['recipes']
|
||||||
if form.cleaned_data['all']:
|
if form.cleaned_data['all']:
|
||||||
recipes = Recipe.objects.filter(space=request.space, internal=True).all()
|
recipes = Recipe.objects.filter(space=request.space, internal=True).all()
|
||||||
|
elif custom_filter := form.cleaned_data['custom_filter']:
|
||||||
|
search = RecipeSearch(request, filter=custom_filter)
|
||||||
|
recipes = search.get_queryset(Recipe.objects.filter(space=request.space, internal=True))
|
||||||
|
|
||||||
integration = get_integration(request, form.cleaned_data['type'])
|
integration = get_integration(request, form.cleaned_data['type'])
|
||||||
|
|
||||||
|
@ -48,11 +48,11 @@ def hook(request, token):
|
|||||||
request.space = tb.space # TODO this is likely a bad idea. Verify and test
|
request.space = tb.space # TODO this is likely a bad idea. Verify and test
|
||||||
request.user = tb.created_by
|
request.user = tb.created_by
|
||||||
ingredient_parser = IngredientParser(request, False)
|
ingredient_parser = IngredientParser(request, False)
|
||||||
amount, unit, ingredient, note = ingredient_parser.parse(data['message']['text'])
|
amount, unit, food, note = ingredient_parser.parse(data['message']['text'])
|
||||||
f = ingredient_parser.get_food(ingredient)
|
f = ingredient_parser.get_food(food)
|
||||||
u = ingredient_parser.get_unit(unit)
|
u = ingredient_parser.get_unit(unit)
|
||||||
|
|
||||||
ShoppingListEntry.objects.create(food=f, unit=u, amount=amount, created_by=request.user, space=request.space)
|
ShoppingListEntry.objects.create(food=f, unit=u, amount=amount, original_text=ingredient, created_by=request.user, space=request.space)
|
||||||
|
|
||||||
return JsonResponse({'data': data['message']['text']})
|
return JsonResponse({'data': data['message']['text']})
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -57,6 +57,7 @@ CORS_ORIGIN_ALLOW_ALL = True
|
|||||||
LOGIN_REDIRECT_URL = "index"
|
LOGIN_REDIRECT_URL = "index"
|
||||||
LOGOUT_REDIRECT_URL = "index"
|
LOGOUT_REDIRECT_URL = "index"
|
||||||
ACCOUNT_LOGOUT_REDIRECT_URL = "index"
|
ACCOUNT_LOGOUT_REDIRECT_URL = "index"
|
||||||
|
ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = "index"
|
||||||
|
|
||||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
|
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
|
||||||
SESSION_COOKIE_AGE = 365 * 60 * 24 * 60
|
SESSION_COOKIE_AGE = 365 * 60 * 24 * 60
|
||||||
|
@ -17,7 +17,6 @@ Pillow==9.0.1
|
|||||||
psycopg2-binary==2.9.3
|
psycopg2-binary==2.9.3
|
||||||
python-dotenv==0.19.2
|
python-dotenv==0.19.2
|
||||||
requests==2.27.1
|
requests==2.27.1
|
||||||
simplejson==3.17.6
|
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
webdavclient3==3.14.6
|
webdavclient3==3.14.6
|
||||||
whitenoise==5.3.0
|
whitenoise==5.3.0
|
||||||
@ -30,7 +29,7 @@ Jinja2==3.0.3
|
|||||||
django-webpack-loader==1.4.1
|
django-webpack-loader==1.4.1
|
||||||
django-js-reverse==0.9.1
|
django-js-reverse==0.9.1
|
||||||
django-allauth==0.47.0
|
django-allauth==0.47.0
|
||||||
recipe-scrapers==13.16.0
|
recipe-scrapers==13.19.0
|
||||||
django-scopes==1.2.0
|
django-scopes==1.2.0
|
||||||
pytest==6.2.5
|
pytest==6.2.5
|
||||||
pytest-django==4.5.2
|
pytest-django==4.5.2
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"html2pdf.js": "^0.10.1",
|
"html2pdf.js": "^0.10.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"prismjs": "^1.25.0",
|
"prismjs": "^1.27.0",
|
||||||
"vue": "^2.6.14",
|
"vue": "^2.6.14",
|
||||||
"vue-class-component": "^7.2.3",
|
"vue-class-component": "^7.2.3",
|
||||||
"vue-click-outside": "^1.1.0",
|
"vue-click-outside": "^1.1.0",
|
||||||
|
@ -1,145 +1,126 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<br/>
|
<br />
|
||||||
|
|
||||||
<template v-if="export_info !== undefined">
|
<template v-if="export_info !== undefined">
|
||||||
|
<template v-if="export_info.running">
|
||||||
|
<h5 style="text-align: center">{{ $t("Exporting") }}...</h5>
|
||||||
|
|
||||||
<template v-if="export_info.running">
|
<b-progress :max="export_info.total_recipes">
|
||||||
<h5 style="text-align: center">{{ $t('Exporting') }}...</h5>
|
<b-progress-bar :value="export_info.exported_recipes" :label="`${export_info.exported_recipes}/${export_info.total_recipes}`"></b-progress-bar>
|
||||||
|
</b-progress>
|
||||||
|
|
||||||
<b-progress :max="export_info.total_recipes">
|
<loading-spinner :size="25"></loading-spinner>
|
||||||
<b-progress-bar :value="export_info.exported_recipes" :label="`${export_info.exported_recipes}/${export_info.total_recipes}`"></b-progress-bar>
|
</template>
|
||||||
</b-progress>
|
|
||||||
|
|
||||||
<loading-spinner :size="25"></loading-spinner>
|
<div class="row">
|
||||||
</template>
|
<div class="col col-md-12" v-if="!export_info.running">
|
||||||
|
<span>{{ $t("Export_finished") }}! </span> <a :href="`${resolveDjangoUrl('viewExport')}`">{{ $t("Return to export") }} </a><br /><br />
|
||||||
|
|
||||||
<div class="row">
|
{{ $t("If download did not start automatically: ") }}
|
||||||
<div class="col col-md-12" v-if="!export_info.running">
|
|
||||||
<span>{{ $t('Export_finished') }}! </span> <a :href="`${resolveDjangoUrl('viewExport') }`">{{ $t('Return to export') }} </a><br><br>
|
|
||||||
|
|
||||||
{{ $t('If download did not start automatically: ') }}
|
<template v-if="export_info.expired">
|
||||||
|
<a disabled
|
||||||
<template v-if="export_info.expired">
|
><del>{{ $t("Download") }}</del></a
|
||||||
<a disabled><del>{{ $t('Download') }}</del></a> ({{ $t('Expired') }})
|
>
|
||||||
</template>
|
({{ $t("Expired") }})
|
||||||
<a v-else :href="`/export-file/${export_id}/`" ref="downloadAnchor" >{{ $t('Download') }}</a>
|
</template>
|
||||||
|
<a v-else :href="`${resolveDjangoUrl('view_export_file', export_id)}`" ref="downloadAnchor">{{ $t("Download") }}</a>
|
||||||
|
|
||||||
<br>
|
<br />
|
||||||
{{ $t('The link will remain active for') }}
|
{{ $t("The link will remain active for") }}
|
||||||
|
|
||||||
<template v-if="export_info.cache_duration > 3600">
|
|
||||||
{{ export_info.cache_duration/3600 }}{{ $t('hr') }}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="export_info.cache_duration > 60">
|
|
||||||
{{ export_info.cache_duration/60 }}{{ $t('min') }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ export_info.cache_duration }}{{ $t('sec') }}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
<template v-if="export_info.cache_duration > 3600"> {{ export_info.cache_duration / 3600 }}{{ $t("hr") }} </template>
|
||||||
|
<template v-else-if="export_info.cache_duration > 60"> {{ export_info.cache_duration / 60 }}{{ $t("min") }} </template>
|
||||||
|
<template v-else> {{ export_info.cache_duration }}{{ $t("sec") }} </template>
|
||||||
|
|
||||||
<br>
|
<br />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col col-md-12">
|
|
||||||
<label for="id_textarea">{{ $t('Information') }}</label>
|
|
||||||
<textarea id="id_textarea" ref="output_text" class="form-control" style="height: 50vh"
|
|
||||||
v-html="export_info.msg"
|
|
||||||
disabled></textarea>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
<label for="id_textarea">{{ $t("Information") }}</label>
|
||||||
|
<textarea id="id_textarea" ref="output_text" class="form-control" style="height: 50vh" v-html="export_info.msg" disabled></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Vue from 'vue'
|
import Vue from "vue"
|
||||||
import {BootstrapVue} from 'bootstrap-vue'
|
import { BootstrapVue } from "bootstrap-vue"
|
||||||
|
|
||||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||||
|
|
||||||
import {ResolveUrlMixin, makeToast, ToastMixin} from "@/utils/utils";
|
import { ResolveUrlMixin, makeToast, ToastMixin } from "@/utils/utils"
|
||||||
|
|
||||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
|
|
||||||
import {ApiApiFactory} from "@/utils/openapi/api.ts";
|
import { ApiApiFactory } from "@/utils/openapi/api.ts"
|
||||||
|
|
||||||
Vue.use(BootstrapVue)
|
Vue.use(BootstrapVue)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ExportResponseView',
|
name: "ExportResponseView",
|
||||||
mixins: [
|
mixins: [ResolveUrlMixin, ToastMixin],
|
||||||
ResolveUrlMixin,
|
components: {
|
||||||
ToastMixin,
|
LoadingSpinner,
|
||||||
],
|
},
|
||||||
components: {
|
data() {
|
||||||
LoadingSpinner
|
return {
|
||||||
},
|
export_id: window.EXPORT_ID,
|
||||||
data() {
|
export_info: undefined,
|
||||||
return {
|
}
|
||||||
export_id: window.EXPORT_ID,
|
},
|
||||||
export_info: undefined,
|
mounted() {
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.refreshData()
|
|
||||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
|
||||||
|
|
||||||
this.dynamicIntervalTimeout = 250 //initial refresh rate
|
|
||||||
this.run = setTimeout(this.dynamicInterval.bind(this), this.dynamicIntervalTimeout)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
dynamicInterval: function(){
|
|
||||||
|
|
||||||
//update frequently at start but slowdown as it takes longer
|
|
||||||
this.dynamicIntervalTimeout = Math.round(this.dynamicIntervalTimeout*((1+Math.sqrt(5))/2))
|
|
||||||
if(this.dynamicIntervalTimeout > 5000) this.dynamicIntervalTimeout = 5000
|
|
||||||
clearInterval(this.run);
|
|
||||||
this.run = setInterval(this.dynamicInterval.bind(this), this.dynamicIntervalTimeout);
|
|
||||||
|
|
||||||
if ((this.export_id !== null) && window.navigator.onLine && this.export_info.running) {
|
|
||||||
this.refreshData()
|
this.refreshData()
|
||||||
let el = this.$refs.output_text
|
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||||
el.scrollTop = el.scrollHeight;
|
|
||||||
|
|
||||||
if(this.export_info.expired)
|
this.dynamicIntervalTimeout = 250 //initial refresh rate
|
||||||
makeToast(this.$t("Error"), this.$t("The download link is expired!"), "danger")
|
this.run = setTimeout(this.dynamicInterval.bind(this), this.dynamicIntervalTimeout)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
dynamicInterval: function () {
|
||||||
|
//update frequently at start but slowdown as it takes longer
|
||||||
|
this.dynamicIntervalTimeout = Math.round(this.dynamicIntervalTimeout * ((1 + Math.sqrt(5)) / 2))
|
||||||
|
if (this.dynamicIntervalTimeout > 5000) this.dynamicIntervalTimeout = 5000
|
||||||
|
clearInterval(this.run)
|
||||||
|
this.run = setInterval(this.dynamicInterval.bind(this), this.dynamicIntervalTimeout)
|
||||||
|
|
||||||
startDownload: function(){
|
if (this.export_id !== null && window.navigator.onLine && this.export_info.running) {
|
||||||
this.$refs['downloadAnchor'].click()
|
this.refreshData()
|
||||||
|
let el = this.$refs.output_text
|
||||||
|
el.scrollTop = el.scrollHeight
|
||||||
|
|
||||||
|
if (this.export_info.expired) makeToast(this.$t("Error"), this.$t("The download link is expired!"), "danger")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startDownload: function () {
|
||||||
|
this.$refs["downloadAnchor"].click()
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshData: function () {
|
||||||
|
let apiClient = new ApiApiFactory()
|
||||||
|
|
||||||
|
apiClient.retrieveExportLog(this.export_id).then((result) => {
|
||||||
|
this.export_info = result.data
|
||||||
|
this.export_info.expired = !this.export_info.possibly_not_expired
|
||||||
|
|
||||||
|
if (!this.export_info.running)
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.startDownload()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
refreshData: function () {
|
|
||||||
let apiClient = new ApiApiFactory()
|
|
||||||
|
|
||||||
apiClient.retrieveExportLog(this.export_id).then(result => {
|
|
||||||
this.export_info = result.data
|
|
||||||
this.export_info.expired = !this.export_info.possibly_not_expired
|
|
||||||
|
|
||||||
if(!this.export_info.running)
|
|
||||||
this.$nextTick(()=>{ this.startDownload(); } )
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style></style>
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
@ -1,174 +1,180 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
|
<h2>{{ $t("Export") }}</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col col-md-12">
|
||||||
|
<br />
|
||||||
|
<!-- TODO get option dynamicaly -->
|
||||||
|
<select class="form-control" v-model="recipe_app">
|
||||||
|
<option value="DEFAULT">Default</option>
|
||||||
|
<option value="SAFFRON">Saffron</option>
|
||||||
|
<option value="RECIPESAGE">Recipe Sage</option>
|
||||||
|
<option value="PDF">PDF (experimental)</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<h2>{{ $t('Export') }}</h2>
|
<br />
|
||||||
<div class="row">
|
<b-form-checkbox v-model="export_all" @change="disabled_multiselect = $event" name="check-button" switch style="margin-top: 1vh">
|
||||||
<div class="col col-md-12">
|
{{ $t("All recipes") }}
|
||||||
|
</b-form-checkbox>
|
||||||
|
|
||||||
<br/>
|
<!-- <multiselect
|
||||||
<!-- TODO get option dynamicaly -->
|
:searchable="true"
|
||||||
<select class="form-control" v-model="recipe_app">
|
:disabled="disabled_multiselect"
|
||||||
<option value="DEFAULT">Default</option>
|
v-model="recipe_list"
|
||||||
<option value="SAFFRON">Saffron</option>
|
:options="recipes"
|
||||||
<option value="RECIPESAGE">Recipe Sage</option>
|
:close-on-select="false"
|
||||||
<option value="PDF">PDF (experimental)</option>
|
:clear-on-select="true"
|
||||||
</select>
|
:hide-selected="true"
|
||||||
|
:preserve-search="true"
|
||||||
|
placeholder="Select Recipes"
|
||||||
|
:taggable="false"
|
||||||
|
label="name"
|
||||||
|
track-by="id"
|
||||||
|
id="id_recipes"
|
||||||
|
:multiple="true"
|
||||||
|
:loading="recipes_loading"
|
||||||
|
@search-change="searchRecipes"
|
||||||
|
>
|
||||||
|
</multiselect> -->
|
||||||
|
<generic-multiselect
|
||||||
|
class="input-group-text m-0 p-0"
|
||||||
|
@change="recipe_list = $event.val"
|
||||||
|
label="name"
|
||||||
|
:model="Models.RECIPE"
|
||||||
|
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||||
|
v-bind:placeholder="$t('Recipe')"
|
||||||
|
:limit="20"
|
||||||
|
:multiple="true"
|
||||||
|
/>
|
||||||
|
<generic-multiselect
|
||||||
|
@change="filter = $event.val"
|
||||||
|
:model="Models.CUSTOM_FILTER"
|
||||||
|
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||||
|
:placeholder="$t('Custom Filter')"
|
||||||
|
:multiple="false"
|
||||||
|
:limit="50"
|
||||||
|
/>
|
||||||
|
|
||||||
<br/>
|
<br />
|
||||||
<b-form-checkbox v-model="export_all" @change="disabled_multiselect=$event" name="check-button" switch style="margin-top: 1vh">
|
<button @click="exportRecipe()" class="btn btn-primary shadow-none"><i class="fas fa-file-export"></i> {{ $t("Export") }}</button>
|
||||||
{{ $t('All recipes') }}
|
</div>
|
||||||
</b-form-checkbox>
|
|
||||||
|
|
||||||
<multiselect
|
|
||||||
:searchable="true"
|
|
||||||
:disabled="disabled_multiselect"
|
|
||||||
v-model="recipe_list"
|
|
||||||
:options="recipes"
|
|
||||||
:close-on-select="false"
|
|
||||||
:clear-on-select="true"
|
|
||||||
:hide-selected="true"
|
|
||||||
:preserve-search="true"
|
|
||||||
placeholder="Select Recipes"
|
|
||||||
:taggable="false"
|
|
||||||
label="name"
|
|
||||||
track-by="id"
|
|
||||||
id="id_recipes"
|
|
||||||
:multiple="true"
|
|
||||||
:loading="recipes_loading"
|
|
||||||
@search-change="searchRecipes">
|
|
||||||
</multiselect>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<button @click="exportRecipe()" class="btn btn-primary shadow-none"><i class="fas fa-file-export"></i> {{ $t('Export') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Vue from 'vue'
|
import Vue from "vue"
|
||||||
import {BootstrapVue} from 'bootstrap-vue'
|
import { BootstrapVue } from "bootstrap-vue"
|
||||||
|
|
||||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||||
|
|
||||||
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
|
|
||||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
import { StandardToasts, makeToast, resolveDjangoUrl, ApiMixin } from "@/utils/utils"
|
||||||
|
// import Multiselect from "vue-multiselect"
|
||||||
import {StandardToasts, makeToast, resolveDjangoUrl} from "@/utils/utils";
|
import GenericMultiselect from "@/components/GenericMultiselect"
|
||||||
import Multiselect from "vue-multiselect";
|
import { ApiApiFactory } from "@/utils/openapi/api.ts"
|
||||||
import {ApiApiFactory} from "@/utils/openapi/api.ts";
|
import axios from "axios"
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
|
|
||||||
Vue.use(BootstrapVue)
|
Vue.use(BootstrapVue)
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ExportView',
|
name: "ExportView",
|
||||||
/*mixins: [
|
/*mixins: [
|
||||||
ResolveUrlMixin,
|
ResolveUrlMixin,
|
||||||
ToastMixin,
|
ToastMixin,
|
||||||
],*/
|
],*/
|
||||||
components: {Multiselect},
|
components: { GenericMultiselect },
|
||||||
data() {
|
mixins: [ApiMixin],
|
||||||
return {
|
data() {
|
||||||
export_id: window.EXPORT_ID,
|
return {
|
||||||
loading: false,
|
export_id: window.EXPORT_ID,
|
||||||
disabled_multiselect: false,
|
loading: false,
|
||||||
|
disabled_multiselect: false,
|
||||||
|
|
||||||
recipe_app: 'DEFAULT',
|
recipe_app: "DEFAULT",
|
||||||
recipe_list: [],
|
recipe_list: [],
|
||||||
recipes_loading: false,
|
recipes_loading: false,
|
||||||
recipes: [],
|
recipes: [],
|
||||||
export_all: false,
|
export_all: false,
|
||||||
}
|
filter: undefined,
|
||||||
},
|
}
|
||||||
mounted() {
|
|
||||||
if(this.export_id)
|
|
||||||
this.insertRequested()
|
|
||||||
else
|
|
||||||
this.searchRecipes('')
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
insertRequested: function(){
|
|
||||||
|
|
||||||
let apiFactory = new ApiApiFactory()
|
|
||||||
|
|
||||||
this.recipes_loading = true
|
|
||||||
|
|
||||||
apiFactory.retrieveRecipe(this.export_id).then((response) => {
|
|
||||||
this.recipes_loading = false
|
|
||||||
this.recipe_list.push(response.data)
|
|
||||||
|
|
||||||
}).catch((err) => {
|
|
||||||
console.log(err)
|
|
||||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH)
|
|
||||||
}).then(e => this.searchRecipes(''))
|
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
searchRecipes: function (query) {
|
if (this.export_id) this.insertRequested()
|
||||||
|
// else this.searchRecipes("")
|
||||||
let apiFactory = new ApiApiFactory()
|
|
||||||
|
|
||||||
this.recipes_loading = true
|
|
||||||
|
|
||||||
let maxResultLenght = 1000
|
|
||||||
apiFactory.listRecipes(query, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 1, maxResultLenght).then((response) => {
|
|
||||||
this.recipes = response.data.results;
|
|
||||||
this.recipes_loading = false
|
|
||||||
|
|
||||||
}).catch((err) => {
|
|
||||||
console.log(err)
|
|
||||||
StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
insertRequested: function () {
|
||||||
|
let apiFactory = new ApiApiFactory()
|
||||||
|
|
||||||
exportRecipe: function () {
|
this.recipes_loading = true
|
||||||
|
|
||||||
if (this.recipe_list.length < 1 && this.export_all == false) {
|
apiFactory
|
||||||
makeToast(this.$t("Error"), this.$t("Select at least one recipe"), "danger")
|
.retrieveRecipe(this.export_id)
|
||||||
return;
|
.then((response) => {
|
||||||
}
|
this.recipes_loading = false
|
||||||
|
this.recipe_list.push(response.data)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err)
|
||||||
|
StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH)
|
||||||
|
})
|
||||||
|
// .then((e) => this.searchRecipes(""))
|
||||||
|
},
|
||||||
|
|
||||||
this.error = undefined
|
// searchRecipes: function (query) {
|
||||||
this.loading = true
|
// this.recipes_loading = true
|
||||||
let formData = new FormData();
|
|
||||||
formData.append('type', this.recipe_app);
|
|
||||||
formData.append('all', this.export_all)
|
|
||||||
|
|
||||||
for (var i = 0; i < this.recipe_list.length; i++) {
|
// this.genericAPI(this.Models.RECIPE, this.Actions.LIST, { query: query })
|
||||||
formData.append('recipes', this.recipe_list[i].id);
|
// .then((response) => {
|
||||||
}
|
// this.recipes = response.data.results
|
||||||
|
// this.recipes_loading = false
|
||||||
|
// })
|
||||||
|
// .catch((err) => {
|
||||||
|
// console.log(err)
|
||||||
|
// StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH)
|
||||||
|
// })
|
||||||
|
// },
|
||||||
|
|
||||||
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
|
exportRecipe: function () {
|
||||||
axios.post(resolveDjangoUrl('view_export',), formData).then((response) => {
|
if (this.recipe_list.length < 1 && this.export_all == false && this.filter === undefined) {
|
||||||
if (response.data['error'] !== undefined){
|
makeToast(this.$t("Error"), this.$t("Select at least one recipe"), "danger")
|
||||||
makeToast(this.$t("Error"), response.data['error'],"warning")
|
return
|
||||||
}else{
|
}
|
||||||
window.location.href = resolveDjangoUrl('view_export_response', response.data['export_id'])
|
|
||||||
}
|
|
||||||
|
|
||||||
}).catch((err) => {
|
this.error = undefined
|
||||||
this.error = err.data
|
this.loading = true
|
||||||
this.loading = false
|
let formData = new FormData()
|
||||||
console.log(err)
|
formData.append("type", this.recipe_app)
|
||||||
makeToast(this.$t("Error"), this.$t("There was an error loading a resource!"), "warning")
|
formData.append("all", this.export_all)
|
||||||
})
|
formData.append("filter", this.filter?.id ?? null)
|
||||||
|
|
||||||
|
for (var i = 0; i < this.recipe_list.length; i++) {
|
||||||
|
formData.append("recipes", this.recipe_list[i].id)
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded"
|
||||||
|
axios
|
||||||
|
.post(resolveDjangoUrl("view_export"), formData)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.data["error"] !== undefined) {
|
||||||
|
makeToast(this.$t("Error"), response.data["error"], "warning")
|
||||||
|
} else {
|
||||||
|
window.location.href = resolveDjangoUrl("view_export_response", response.data["export_id"])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.error = err.data
|
||||||
|
this.loading = false
|
||||||
|
console.log(err)
|
||||||
|
makeToast(this.$t("Error"), this.$t("There was an error loading a resource!"), "warning")
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
|
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
|
||||||
|
|
||||||
<style>
|
<style></style>
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
@ -65,8 +65,11 @@
|
|||||||
:preserve-search="true"
|
:preserve-search="true"
|
||||||
:internal-search="false"
|
:internal-search="false"
|
||||||
:limit="options_limit"
|
:limit="options_limit"
|
||||||
placeholder="Select Keyword"
|
:placeholder="$t('select_keyword')"
|
||||||
tag-placeholder="Add Keyword"
|
:tag-placeholder="$t('add_keyword')"
|
||||||
|
:select-label="$t('Select')"
|
||||||
|
:selected-label="$t('Selected')"
|
||||||
|
:deselect-label="$t('remove_selection')"
|
||||||
:taggable="true"
|
:taggable="true"
|
||||||
@tag="addKeyword"
|
@tag="addKeyword"
|
||||||
label="label"
|
label="label"
|
||||||
@ -76,6 +79,7 @@
|
|||||||
:loading="keywords_loading"
|
:loading="keywords_loading"
|
||||||
@search-change="searchKeywords"
|
@search-change="searchKeywords"
|
||||||
>
|
>
|
||||||
|
<template v-slot:noOptions>{{ $t("empty_list") }}</template>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -244,8 +248,10 @@
|
|||||||
:clear-on-select="true"
|
:clear-on-select="true"
|
||||||
:allow-empty="true"
|
:allow-empty="true"
|
||||||
:preserve-search="true"
|
:preserve-search="true"
|
||||||
placeholder="Select File"
|
:placeholder="$t('select_file')"
|
||||||
select-label="Select"
|
:select-label="$t('Select')"
|
||||||
|
:selected-label="$t('Selected')"
|
||||||
|
:deselect-label="$t('remove_selection')"
|
||||||
:id="'id_step_' + step.id + '_file'"
|
:id="'id_step_' + step.id + '_file'"
|
||||||
label="name"
|
label="name"
|
||||||
track-by="name"
|
track-by="name"
|
||||||
@ -254,6 +260,7 @@
|
|||||||
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
style="flex-grow: 1; flex-shrink: 1; flex-basis: 0"
|
||||||
@search-change="searchFiles"
|
@search-change="searchFiles"
|
||||||
>
|
>
|
||||||
|
<template v-slot:noOptions>{{ $t("empty_list") }}</template>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
<b-input-group-append>
|
<b-input-group-append>
|
||||||
<b-button
|
<b-button
|
||||||
@ -283,14 +290,17 @@
|
|||||||
:preserve-search="true"
|
:preserve-search="true"
|
||||||
:internal-search="false"
|
:internal-search="false"
|
||||||
:limit="options_limit"
|
:limit="options_limit"
|
||||||
placeholder="Select Recipe"
|
:placeholder="$t('select_recipe')"
|
||||||
select-label="Select"
|
:select-label="$t('Select')"
|
||||||
|
:selected-label="$t('Selected')"
|
||||||
|
:deselect-label="$t('remove_selection')"
|
||||||
:id="'id_step_' + step.id + '_recipe'"
|
:id="'id_step_' + step.id + '_recipe'"
|
||||||
:custom-label="(opt) => recipes.find((x) => x.id === opt).name"
|
:custom-label="(opt) => recipes.find((x) => x.id === opt).name"
|
||||||
:multiple="false"
|
:multiple="false"
|
||||||
:loading="recipes_loading"
|
:loading="recipes_loading"
|
||||||
@search-change="searchRecipes"
|
@search-change="searchRecipes"
|
||||||
>
|
>
|
||||||
|
<template v-slot:noOptions>{{ $t("empty_list") }}</template>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -340,9 +350,11 @@
|
|||||||
:preserve-search="true"
|
:preserve-search="true"
|
||||||
:internal-search="false"
|
:internal-search="false"
|
||||||
:limit="options_limit"
|
:limit="options_limit"
|
||||||
placeholder="Select Unit"
|
:placeholder="$t('select_unit')"
|
||||||
tag-placeholder="Create"
|
:tag-placeholder="$t('Create')"
|
||||||
select-label="Select"
|
:select-label="$t('Select')"
|
||||||
|
:selected-label="$t('Selected')"
|
||||||
|
:deselect-label="$t('remove_selection')"
|
||||||
:taggable="true"
|
:taggable="true"
|
||||||
@tag="addUnitType"
|
@tag="addUnitType"
|
||||||
:id="`unit_${step_index}_${index}`"
|
:id="`unit_${step_index}_${index}`"
|
||||||
@ -352,6 +364,7 @@
|
|||||||
:loading="units_loading"
|
:loading="units_loading"
|
||||||
@search-change="searchUnits"
|
@search-change="searchUnits"
|
||||||
>
|
>
|
||||||
|
<template v-slot:noOptions>{{ $t("empty_list") }}</template>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-4 col-md-6 small-padding" v-if="!ingredient.is_header">
|
<div class="col-lg-4 col-md-6 small-padding" v-if="!ingredient.is_header">
|
||||||
@ -367,9 +380,11 @@
|
|||||||
:preserve-search="true"
|
:preserve-search="true"
|
||||||
:internal-search="false"
|
:internal-search="false"
|
||||||
:limit="options_limit"
|
:limit="options_limit"
|
||||||
placeholder="Select Food"
|
:placeholder="$t('select_food')"
|
||||||
tag-placeholder="Create"
|
:tag-placeholder="$t('Create')"
|
||||||
select-label="Select"
|
:select-label="$t('Select')"
|
||||||
|
:selected-label="$t('Selected')"
|
||||||
|
:deselect-label="$t('remove_selection')"
|
||||||
:taggable="true"
|
:taggable="true"
|
||||||
@tag="addFoodType"
|
@tag="addFoodType"
|
||||||
:id="`ingredient_${step_index}_${index}`"
|
:id="`ingredient_${step_index}_${index}`"
|
||||||
@ -379,6 +394,7 @@
|
|||||||
:loading="foods_loading"
|
:loading="foods_loading"
|
||||||
@search-change="searchFoods"
|
@search-change="searchFoods"
|
||||||
>
|
>
|
||||||
|
<template v-slot:noOptions>{{ $t("empty_list") }}</template>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
</div>
|
</div>
|
||||||
<div class="small-padding" v-bind:class="{ 'col-lg-4 col-md-6': !ingredient.is_header, 'col-lg-12 col-md-12': ingredient.is_header }">
|
<div class="small-padding" v-bind:class="{ 'col-lg-4 col-md-6': !ingredient.is_header, 'col-lg-12 col-md-12': ingredient.is_header }">
|
||||||
@ -804,7 +820,7 @@ export default {
|
|||||||
no_amount: false,
|
no_amount: false,
|
||||||
})
|
})
|
||||||
this.sortIngredients(step)
|
this.sortIngredients(step)
|
||||||
this.$nextTick(() => document.getElementById(`amount_${this.recipe.steps.indexOf(step)}_${step.ingredients.length - 1}`).focus())
|
this.$nextTick(() => document.getElementById(`amount_${this.recipe.steps.indexOf(step)}_${step.ingredients.length - 1}`).select())
|
||||||
},
|
},
|
||||||
removeIngredient: function (step, ingredient) {
|
removeIngredient: function (step, ingredient) {
|
||||||
if (confirm(this.$t("confirm_delete", { object: this.$t("Ingredient") }))) {
|
if (confirm(this.$t("confirm_delete", { object: this.$t("Ingredient") }))) {
|
||||||
@ -985,6 +1001,7 @@ export default {
|
|||||||
unit: unit,
|
unit: unit,
|
||||||
food: { name: result.data.food },
|
food: { name: result.data.food },
|
||||||
note: result.data.note,
|
note: result.data.note,
|
||||||
|
original_text: ing,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
order++
|
order++
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -934,6 +934,8 @@ export default {
|
|||||||
this.ui = Object.assign({}, this.ui, this.$cookies.get(SETTINGS_COOKIE_NAME))
|
this.ui = Object.assign({}, this.ui, this.$cookies.get(SETTINGS_COOKIE_NAME))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||||
|
console.log(window.CUSTOM_LOCALE)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// this.genericAPI inherited from ApiMixin
|
// this.genericAPI inherited from ApiMixin
|
||||||
@ -1491,7 +1493,7 @@ export default {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
height: 6vh;
|
height: 60vh; /* TODO use proper fill height here to not render list underneath bottom buttons */
|
||||||
padding-right: 8px !important;
|
padding-right: 8px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<b-sidebar id="related-recipes" backdrop right bottom no-header shadow="sm" style="z-index: 10000" @shown="updatePinnedRecipes()">
|
<b-sidebar id="related-recipes" backdrop right bottom no-header shadow="sm" style="z-index: 10000" @shown="updatePinnedRecipes()">
|
||||||
<template #default="{ hide }">
|
<template #default="{ hide }">
|
||||||
<div class="d-flex flex-column justify-content-end h-100 p-3 align-items-end">
|
<div class="d-flex flex-column justify-content-end h-100 p-3 align-items-end">
|
||||||
<h5>Planned <i class="fas fa-calendar fa-fw"></i></h5>
|
<h5>{{$t("Planned")}} <i class="fas fa-calendar fa-fw"></i></h5>
|
||||||
|
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<template v-if="planned_recipes.length > 0">
|
<template v-if="planned_recipes.length > 0">
|
||||||
@ -24,11 +24,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span class="text-muted">You have nothing planned for today!</span>
|
<span class="text-muted">{{$t("nothing_planned_today")}}</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h5>Pinned <i class="fas fa-thumbtack fa-fw"></i></h5>
|
<h5>{{$t("Pinned")}} <i class="fas fa-thumbtack fa-fw"></i></h5>
|
||||||
|
|
||||||
<template v-if="pinned_recipes.length > 0">
|
<template v-if="pinned_recipes.length > 0">
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
@ -53,7 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span class="text-muted">You have no pinned recipes!</span>
|
<span class="text-muted">{{$t("no_pinned_recipes")}}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="related_recipes.length > 0">
|
<template v-if="related_recipes.length > 0">
|
||||||
@ -77,8 +77,8 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #footer="{ hide }">
|
<template #footer="{ hide }">
|
||||||
<div class="d-flex bg-dark text-light align-items-center px-3 py-2">
|
<div class="d-flex bg-dark text-light align-items-center px-3 py-2">
|
||||||
<strong class="mr-auto">Quick actions</strong>
|
<strong class="mr-auto">{{$t("Quick actions")}}</strong>
|
||||||
<b-button size="sm" @click="hide">Close</b-button>
|
<b-button size="sm" @click="hide">{{$t("Close")}}</b-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</b-sidebar>
|
</b-sidebar>
|
||||||
|
@ -52,10 +52,12 @@ export default {
|
|||||||
page_count: function () {
|
page_count: function () {
|
||||||
return Math.ceil(this.page_count_pagination / this.per_page_count)
|
return Math.ceil(this.page_count_pagination / this.per_page_count)
|
||||||
},
|
},
|
||||||
|
display_recipes: function() {
|
||||||
|
return this.recipes.slice((this.current_page - 1 - 1) * 2, (this.current_page - 1) * 2)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
display_recipes: [],
|
|
||||||
current_page: 1,
|
current_page: 1,
|
||||||
per_page_count: 2,
|
per_page_count: 2,
|
||||||
bounce_left: false,
|
bounce_left: false,
|
||||||
@ -66,18 +68,23 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
pageChange: function (page) {
|
pageChange: function (page) {
|
||||||
this.current_page = page
|
this.current_page = page
|
||||||
this.display_recipes = this.recipes.slice((this.current_page - 1 - 1) * 2, (this.current_page - 1) * 2)
|
|
||||||
this.loadRecipeDetails(page)
|
this.loadRecipeDetails(page)
|
||||||
},
|
},
|
||||||
loadRecipeDetails: function (page) {
|
loadRecipeDetails: function (page) {
|
||||||
this.display_recipes.forEach((recipe, index) => {
|
this.display_recipes.forEach((recipe, index) => {
|
||||||
|
if (recipe.recipe_content.steps === undefined) {
|
||||||
let apiClient = new ApiApiFactory()
|
let apiClient = new ApiApiFactory()
|
||||||
|
|
||||||
apiClient.retrieveRecipe(recipe.recipe).then((result) => {
|
apiClient.retrieveRecipe(recipe.recipe).then((result) => {
|
||||||
let new_entry = Object.assign({}, recipe)
|
let new_entry = Object.assign({}, recipe)
|
||||||
new_entry.recipe_content = result.data
|
new_entry.recipe_content = result.data
|
||||||
this.$set(this.display_recipes, index, new_entry)
|
this.recipes.forEach((rec, i) => {
|
||||||
|
if (rec.recipe === new_entry.recipe) {
|
||||||
|
this.$set(this.recipes, i, new_entry)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
swipeLeft: function () {
|
swipeLeft: function () {
|
||||||
|
@ -21,11 +21,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="actionArea pt-1 pb-1 d-none d-lg-flex">
|
<div class="actionArea pt-1 pb-1 d-none d-lg-flex">
|
||||||
<span class="period-span-1 pt-1 pb-1 pl-1 pr-1 d-none d-xl-inline-flex text-body align-items-center">
|
<span class="period-span-1 pt-1 pb-1 pl-1 pr-1 d-none d-xl-inline-flex text-body align-items-center">
|
||||||
<small>Period:</small>
|
<small>{{ $t('Period') }}:</small>
|
||||||
<b-form-select class="ml-1" id="UomInput" v-model="settings.displayPeriodUom" :options="options.displayPeriodUom"></b-form-select>
|
<b-form-select class="ml-1" id="UomInput" v-model="settings.displayPeriodUom" :options="options.displayPeriodUom"></b-form-select>
|
||||||
</span>
|
</span>
|
||||||
<span class="period-span-2 pt-1 pb-1 pl-1 pr-1 mr-1 ml-1 d-none d-xl-inline-flex text-body align-items-center">
|
<span class="period-span-2 pt-1 pb-1 pl-1 pr-1 mr-1 ml-1 d-none d-xl-inline-flex text-body align-items-center">
|
||||||
<small>Periods:</small>
|
<small>{{ $t('Periods') }}:</small>
|
||||||
<b-form-select class="ml-1" id="UomInput" v-model="settings.displayPeriodCount" :options="options.displayPeriodCount"></b-form-select>
|
<b-form-select class="ml-1" id="UomInput" v-model="settings.displayPeriodCount" :options="options.displayPeriodCount"></b-form-select>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
@ -206,7 +206,7 @@ export default {
|
|||||||
}
|
}
|
||||||
if (!cancel) {
|
if (!cancel) {
|
||||||
this.$bvModal.hide(`edit-modal`)
|
this.$bvModal.hide(`edit-modal`)
|
||||||
this.$emit("save-entry", { ...this.mealplan_settings, ...this.entryEditing, ...{ addshopping: this.entryEditing.addshopping && !this.autoMealPlan } })
|
this.$emit("save-entry", { ...this.mealplan_settings, ...this.entryEditing, ...{ addshopping: this.mealplan_settings.addshopping && !this.autoMealPlan } })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteEntry() {
|
deleteEntry() {
|
||||||
|
@ -1,39 +1,42 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<b-form-group
|
<b-form-group v-bind:label="label" class="mb-3">
|
||||||
v-bind:label="label"
|
<b-form-select v-model="new_value" :placeholder="placeholder" :options="translatedOptions"></b-form-select>
|
||||||
class="mb-3">
|
|
||||||
<b-form-select v-model="new_value" :placeholder="placeholder" :options="options"></b-form-select>
|
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ChoiceInput',
|
name: "ChoiceInput",
|
||||||
props: {
|
props: {
|
||||||
field: {type: String, default: 'You Forgot To Set Field Name'},
|
field: { type: String, default: "You Forgot To Set Field Name" },
|
||||||
label: {type: String, default: 'Text Field'},
|
label: { type: String, default: "Text Field" },
|
||||||
value: {type: String, default: ''},
|
value: { type: String, default: "" },
|
||||||
options: [],
|
options: [],
|
||||||
placeholder: {type: String, default: 'You Should Add Placeholder Text'},
|
placeholder: { type: String, default: "You Should Add Placeholder Text" },
|
||||||
show_merge: {type: Boolean, default: false},
|
show_merge: { type: Boolean, default: false },
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
new_value: undefined,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.new_value = this.value
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'new_value': function () {
|
|
||||||
this.$root.$emit('change', this.field, this.new_value)
|
|
||||||
},
|
},
|
||||||
},
|
data() {
|
||||||
methods: {
|
return {
|
||||||
}
|
new_value: undefined,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.new_value = this.value
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
new_value: function () {
|
||||||
|
this.$root.$emit("change", this.field, this.new_value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
translatedOptions() {
|
||||||
|
return this.options.map((x) => {
|
||||||
|
return { ...x, text: this.$t(x.text) }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
:initialContent="value"
|
:initialContent="value"
|
||||||
:emojiData="emojiDataAll"
|
:emojiData="emojiDataAll"
|
||||||
:emojiGroups="emojiGroups"
|
:emojiGroups="emojiGroups"
|
||||||
triggerType="hover"
|
triggerType="click"
|
||||||
:recentEmojisFeat="true"
|
:recentEmojisFeat="true"
|
||||||
recentEmojisStorage="local"
|
recentEmojisStorage="local"
|
||||||
@contentChanged="setIcon"
|
@contentChanged="setIcon"
|
||||||
|
@ -67,6 +67,9 @@ export default {
|
|||||||
this.field = this.form?.field ?? "You Forgot To Set Field Name"
|
this.field = this.form?.field ?? "You Forgot To Set Field Name"
|
||||||
this.label = this.form?.label ?? ""
|
this.label = this.form?.label ?? ""
|
||||||
this.sticky_options = this.form?.sticky_options ?? []
|
this.sticky_options = this.form?.sticky_options ?? []
|
||||||
|
this.sticky_options = this.sticky_options.map((x) => {
|
||||||
|
return { ...x, name: this.$t(x.name) }
|
||||||
|
})
|
||||||
this.list_label = this.form?.list_label ?? undefined
|
this.list_label = this.form?.list_label ?? undefined
|
||||||
if (this.list_label?.includes("::")) {
|
if (this.list_label?.includes("::")) {
|
||||||
this.list_label = this.list_label.split("::")[1]
|
this.list_label = this.list_label.split("::")[1]
|
||||||
@ -74,7 +77,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
modelName() {
|
modelName() {
|
||||||
return this?.model?.name ?? this.$t("Search")
|
return this.$t(this?.model?.name) ?? this.$t("Search")
|
||||||
},
|
},
|
||||||
useMultiple() {
|
useMultiple() {
|
||||||
return this.form?.multiple || this.form?.ordered || false
|
return this.form?.multiple || this.form?.ordered || false
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<div class="text-nowrap"><i class="fa fa-chevron-right rotate" :class="showDetails ? 'rotated' : ''"></i></div>
|
<div class="text-nowrap"><i class="fa fa-chevron-right rotate" :class="showDetails ? 'rotated' : ''"></i></div>
|
||||||
</b-button>
|
</b-button>
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col cols="1" class="align-items-center d-flex">
|
<b-col cols="2" md="1" class="align-items-center d-flex">
|
||||||
<div class="dropdown b-dropdown position-static inline-block" data-html2canvas-ignore="true" @click.stop="$emit('open-context-menu', $event, entries)">
|
<div class="dropdown b-dropdown position-static inline-block" data-html2canvas-ignore="true" @click.stop="$emit('open-context-menu', $event, entries)">
|
||||||
<button
|
<button
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
@ -23,7 +23,7 @@
|
|||||||
<b-col cols="1" class="px-1 justify-content-center align-items-center d-none d-md-flex">
|
<b-col cols="1" class="px-1 justify-content-center align-items-center d-none d-md-flex">
|
||||||
<input type="checkbox" class="form-control form-control-sm checkbox-control" :checked="formatChecked" @change="updateChecked" :key="entries[0].id" />
|
<input type="checkbox" class="form-control form-control-sm checkbox-control" :checked="formatChecked" @change="updateChecked" :key="entries[0].id" />
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col cols="8" md="9">
|
<b-col cols="7" md="9">
|
||||||
<b-row class="d-flex h-100">
|
<b-row class="d-flex h-100">
|
||||||
<b-col cols="5" md="3" class="d-flex align-items-center" v-if="Object.entries(formatAmount).length == 1">
|
<b-col cols="5" md="3" class="d-flex align-items-center" v-if="Object.entries(formatAmount).length == 1">
|
||||||
<strong class="mr-1">{{ Object.entries(formatAmount)[0][1] }}</strong> {{ Object.entries(formatAmount)[0][0] }}
|
<strong class="mr-1">{{ Object.entries(formatAmount)[0][1] }}</strong> {{ Object.entries(formatAmount)[0][0] }}
|
||||||
@ -86,7 +86,7 @@
|
|||||||
<b-col cols="3" md="2" class="justify-content-start align-items-center d-flex d-md-none pr-0" v-if="settings.left_handed">
|
<b-col cols="3" md="2" class="justify-content-start align-items-center d-flex d-md-none pr-0" v-if="settings.left_handed">
|
||||||
<input type="checkbox" class="form-control form-control-sm checkbox-control-mobile" :checked="formatChecked" @change="updateChecked" :key="entries[0].id" />
|
<input type="checkbox" class="form-control form-control-sm checkbox-control-mobile" :checked="formatChecked" @change="updateChecked" :key="entries[0].id" />
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col cols="1" class="align-items-center d-flex">
|
<b-col cols="2" md="1" class="align-items-center d-flex">
|
||||||
<div class="dropdown b-dropdown position-static inline-block" data-html2canvas-ignore="true" @click.stop="$emit('open-context-menu', $event, e)">
|
<div class="dropdown b-dropdown position-static inline-block" data-html2canvas-ignore="true" @click.stop="$emit('open-context-menu', $event, e)">
|
||||||
<button
|
<button
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
@ -102,7 +102,7 @@
|
|||||||
<b-col cols="1" class="justify-content-center align-items-center d-none d-md-flex">
|
<b-col cols="1" class="justify-content-center align-items-center d-none d-md-flex">
|
||||||
<input type="checkbox" class="form-control form-control-sm checkbox-control" :checked="formatChecked" @change="updateChecked" :key="entries[0].id" />
|
<input type="checkbox" class="form-control form-control-sm checkbox-control" :checked="formatChecked" @change="updateChecked" :key="entries[0].id" />
|
||||||
</b-col>
|
</b-col>
|
||||||
<b-col cols="8" md="9">
|
<b-col cols="7" md="9">
|
||||||
<b-row class="d-flex align-items-center h-100">
|
<b-row class="d-flex align-items-center h-100">
|
||||||
<b-col cols="5" md="3" class="d-flex align-items-center">
|
<b-col cols="5" md="3" class="d-flex align-items-center">
|
||||||
<strong class="mr-1">{{ formatOneAmount(e) }}</strong> {{ formatOneUnit(e) }}
|
<strong class="mr-1">{{ formatOneAmount(e) }}</strong> {{ formatOneUnit(e) }}
|
||||||
|
@ -137,7 +137,7 @@
|
|||||||
"Move_Down": "Runter",
|
"Move_Down": "Runter",
|
||||||
"Step_Name": "Schritt Name",
|
"Step_Name": "Schritt Name",
|
||||||
"Create": "Erstellen",
|
"Create": "Erstellen",
|
||||||
"Advanced Search Settings": "Erweiterte Sucheinstellungen",
|
"advanced_search_settings": "Erweiterte Sucheinstellungen",
|
||||||
"View": "Ansicht",
|
"View": "Ansicht",
|
||||||
"Recipes": "Rezepte",
|
"Recipes": "Rezepte",
|
||||||
"Move": "Verschieben",
|
"Move": "Verschieben",
|
||||||
@ -249,7 +249,7 @@
|
|||||||
"shopping_auto_sync_desc": "Bei 0 wird Auto-Sync deaktiviert. Beim Betrachten einer Einkaufsliste wird die Liste alle gesetzten Sekunden aktualisiert, um mögliche Änderungen anderer zu zeigen. Nützlich, wenn mehrere Personen einkaufen und mobile Daten nutzen.",
|
"shopping_auto_sync_desc": "Bei 0 wird Auto-Sync deaktiviert. Beim Betrachten einer Einkaufsliste wird die Liste alle gesetzten Sekunden aktualisiert, um mögliche Änderungen anderer zu zeigen. Nützlich, wenn mehrere Personen einkaufen und mobile Daten nutzen.",
|
||||||
"MoveCategory": "Verschieben nach: ",
|
"MoveCategory": "Verschieben nach: ",
|
||||||
"mealplan_autoadd_shopping_desc": "Essensplan-Zutaten automatisch zur Einkaufsliste hinzufügen.",
|
"mealplan_autoadd_shopping_desc": "Essensplan-Zutaten automatisch zur Einkaufsliste hinzufügen.",
|
||||||
"Pin": "Pin",
|
"Pin": "Anheften",
|
||||||
"mark_complete": "Vollständig markieren",
|
"mark_complete": "Vollständig markieren",
|
||||||
"shopping_add_onhand_desc": "Markiere Lebensmittel als \"Vorrätig\", wenn von der Einkaufsliste abgehakt wurden.",
|
"shopping_add_onhand_desc": "Markiere Lebensmittel als \"Vorrätig\", wenn von der Einkaufsliste abgehakt wurden.",
|
||||||
"left_handed": "Linkshänder-Modus",
|
"left_handed": "Linkshänder-Modus",
|
||||||
@ -298,5 +298,61 @@
|
|||||||
"Foods": "Lebensmittel",
|
"Foods": "Lebensmittel",
|
||||||
"food_recipe_help": "Wird ein Rezept hier verknüpft, wird diese Verknüpfung in allen anderen Rezepten übernommen, die dieses Lebensmittel beinhaltet",
|
"food_recipe_help": "Wird ein Rezept hier verknüpft, wird diese Verknüpfung in allen anderen Rezepten übernommen, die dieses Lebensmittel beinhaltet",
|
||||||
"review_shopping": "Überprüfe die Einkaufsliste vor dem Speichern",
|
"review_shopping": "Überprüfe die Einkaufsliste vor dem Speichern",
|
||||||
"view_recipe": "Rezept anschauen"
|
"view_recipe": "Rezept anschauen",
|
||||||
|
"Planned": "Geplant",
|
||||||
|
"Pinned": "Angeheftet",
|
||||||
|
"nothing_planned_today": "Sie haben für heute nichts geplant!",
|
||||||
|
"no_pinned_recipes": "Sie haben nichts angeheftet!",
|
||||||
|
"Quick actions": "Schnellbefehle",
|
||||||
|
"search_no_recipes": "Keine Rezepte gefunden!",
|
||||||
|
"search_import_help_text": "Importiere ein Rezept von einer externen Webseite oder Anwendung.",
|
||||||
|
"search_create_help_text": "Erstelle ein neues Rezept direkt in Tandoor.",
|
||||||
|
"Ratings": "Bewertungen",
|
||||||
|
"Custom Filter": "Benutzerdefinierter Filter",
|
||||||
|
"expert_mode": "Experten-Modus",
|
||||||
|
"simple_mode": "Einfacher Modus",
|
||||||
|
"explain": "Erklären",
|
||||||
|
"save_filter": "Filter speichern",
|
||||||
|
"Internal": "Intern",
|
||||||
|
"advanced": "Erweitert",
|
||||||
|
"fields": "Felder",
|
||||||
|
"show_keywords": "Schlüsselwörter anzeigen",
|
||||||
|
"show_foods": "Zutaten anzeigen",
|
||||||
|
"show_books": "Bücher anzeigen",
|
||||||
|
"show_rating": "Bewertungen anzeigen",
|
||||||
|
"show_units": "Einheiten anzeigen",
|
||||||
|
"show_filters": "Filter anzeigen",
|
||||||
|
"times_cooked": "Wie oft gekocht",
|
||||||
|
"show_sortby": "Zeige 'Sortiere nach'",
|
||||||
|
"make_now": "Jetzt machen",
|
||||||
|
"date_viewed": "Letztens besucht",
|
||||||
|
"last_cooked": "Letztens gekocht",
|
||||||
|
"created_on": "Erstellt am",
|
||||||
|
"updatedon": "Geändert am",
|
||||||
|
"date_created": "Erstellungsdatum",
|
||||||
|
"Units": "Einheiten",
|
||||||
|
"last_viewed": "Letztens besucht",
|
||||||
|
"sort_by": "Sortiere nach",
|
||||||
|
"Random Recipes": "Zufällige Rezepte",
|
||||||
|
"recipe_filter": "Rezept-Filter",
|
||||||
|
"parameter_count": "Parameter {count}",
|
||||||
|
"select_keyword": "Stichwort auswählen",
|
||||||
|
"add_keyword": "Stichwort hinzufügen",
|
||||||
|
"select_file": "Datei auswählen",
|
||||||
|
"select_recipe": "Rezept auswählen",
|
||||||
|
"select_unit": "Einheit wählen",
|
||||||
|
"select_food": "Zutat auswählen",
|
||||||
|
"remove_selection": "Abwählen",
|
||||||
|
"empty_list": "Liste ist leer.",
|
||||||
|
"Select": "Auswählen",
|
||||||
|
"Supermarkets": "Supermärkte",
|
||||||
|
"User": "Benutzer",
|
||||||
|
"Keyword": "Schlüsselwort",
|
||||||
|
"Advanced": "Erweitert",
|
||||||
|
"Substitutes": "Zusätze",
|
||||||
|
"copy_to_new": "Kopiere zu neuem Rezept",
|
||||||
|
"Page": "Seite",
|
||||||
|
"Reset": "Zurücksetzen",
|
||||||
|
"search_rank": "Such-Rang",
|
||||||
|
"paste_ingredients": "Zutaten einfügen"
|
||||||
}
|
}
|
||||||
|
@ -355,5 +355,31 @@
|
|||||||
"InheritFields_help": "The values of these fields will be inheritted from parent (Exception: blank shopping categories are not inheritted)",
|
"InheritFields_help": "The values of these fields will be inheritted from parent (Exception: blank shopping categories are not inheritted)",
|
||||||
"last_viewed": "Last Viewed",
|
"last_viewed": "Last Viewed",
|
||||||
"created_on": "Created On",
|
"created_on": "Created On",
|
||||||
"updatedon": "Updated On"
|
"updatedon": "Updated On",
|
||||||
|
"advanced_search_settings": "Advanced Search Settings",
|
||||||
|
"nothing_planned_today": "You have nothing planned for today!",
|
||||||
|
"no_pinned_recipes": "You have no pinned recipes!",
|
||||||
|
"Planned": "Planned",
|
||||||
|
"Pinned": "Pinned",
|
||||||
|
"Quick actions": "Quick actions",
|
||||||
|
"Ratings": "Ratings",
|
||||||
|
"Internal": "Internal",
|
||||||
|
"Units": "Units",
|
||||||
|
"Random Recipes": "Random Recipes",
|
||||||
|
"parameter_count": "Parameter {count}",
|
||||||
|
"select_keyword": "Select Keyword",
|
||||||
|
"add_keyword": "Add Keyword",
|
||||||
|
"select_file": "Select File",
|
||||||
|
"select_recipe": "Select Recipe",
|
||||||
|
"select_unit": "Select Unit",
|
||||||
|
"select_food": "Select Food",
|
||||||
|
"remove_selection": "Deselect",
|
||||||
|
"empty_list": "List is empty.",
|
||||||
|
"Select": "Select",
|
||||||
|
"Supermarkets": "Supermarkets",
|
||||||
|
"User": "User",
|
||||||
|
"Keyword": "Keyword",
|
||||||
|
"Advanced": "Advanced",
|
||||||
|
"Page": "Page",
|
||||||
|
"Reset": "Reset"
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Utility CLASS to define model configurations
|
* Utility CLASS to define model configurations
|
||||||
* */
|
* */
|
||||||
import i18n from "@/i18n"
|
|
||||||
|
|
||||||
// TODO this needs rethought and simplified
|
// TODO this needs rethought and simplified
|
||||||
// maybe a function that returns a single dictionary based on action?
|
// maybe a function that returns a single dictionary based on action?
|
||||||
@ -51,7 +50,7 @@ export class Models {
|
|||||||
type: "lookup",
|
type: "lookup",
|
||||||
field: "target",
|
field: "target",
|
||||||
list: "self",
|
list: "self",
|
||||||
sticky_options: [{ id: 0, name: i18n.t("tree_root") }],
|
sticky_options: [{ id: 0, name: "tree_root" }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -59,7 +58,7 @@ export class Models {
|
|||||||
|
|
||||||
// MODELS - inherits and takes precedence over MODEL_TYPES and ACTIONS
|
// MODELS - inherits and takes precedence over MODEL_TYPES and ACTIONS
|
||||||
static FOOD = {
|
static FOOD = {
|
||||||
name: i18n.t("Food"), // *OPTIONAL* : parameters will be built model -> model_type -> default
|
name: "Food", // *OPTIONAL* : parameters will be built model -> model_type -> default
|
||||||
apiName: "Food", // *REQUIRED* : the name that is used in api.ts for this model
|
apiName: "Food", // *REQUIRED* : the name that is used in api.ts for this model
|
||||||
model_type: this.TREE, // *OPTIONAL* : model specific params for api, if not present will attempt modeltype_create then default_create
|
model_type: this.TREE, // *OPTIONAL* : model specific params for api, if not present will attempt modeltype_create then default_create
|
||||||
paginated: true,
|
paginated: true,
|
||||||
@ -100,15 +99,15 @@ export class Models {
|
|||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "name",
|
field: "name",
|
||||||
label: i18n.t("Name"),
|
label: "Name", // form.label always translated in utils.getForm()
|
||||||
placeholder: "",
|
placeholder: "", // form.placeholder always translated
|
||||||
subtitle_field: "full_name",
|
subtitle_field: "full_name",
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "description",
|
field: "description",
|
||||||
label: i18n.t("Description"),
|
label: "Description", // form.label always translated in utils.getForm()
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
recipe: {
|
recipe: {
|
||||||
@ -116,31 +115,31 @@ export class Models {
|
|||||||
type: "lookup",
|
type: "lookup",
|
||||||
field: "recipe",
|
field: "recipe",
|
||||||
list: "RECIPE",
|
list: "RECIPE",
|
||||||
label: i18n.t("Recipe"),
|
label: "Recipe", // form.label always translated in utils.getForm()
|
||||||
help_text: i18n.t("food_recipe_help"),
|
help_text: "food_recipe_help", // form.help_text always translated
|
||||||
},
|
},
|
||||||
onhand: {
|
onhand: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
field: "food_onhand",
|
field: "food_onhand",
|
||||||
label: i18n.t("OnHand"),
|
label: "OnHand",
|
||||||
help_text: i18n.t("OnHand_help"),
|
help_text: "OnHand_help",
|
||||||
},
|
},
|
||||||
ignore_shopping: {
|
ignore_shopping: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
field: "ignore_shopping",
|
field: "ignore_shopping",
|
||||||
label: i18n.t("Ignore_Shopping"),
|
label: "Ignore_Shopping",
|
||||||
help_text: i18n.t("ignore_shopping_help"),
|
help_text: "ignore_shopping_help",
|
||||||
},
|
},
|
||||||
shopping_category: {
|
shopping_category: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "lookup",
|
type: "lookup",
|
||||||
field: "supermarket_category",
|
field: "supermarket_category",
|
||||||
list: "SHOPPING_CATEGORY",
|
list: "SHOPPING_CATEGORY",
|
||||||
label: i18n.t("Shopping_Category"),
|
label: "Shopping_Category",
|
||||||
allow_create: true,
|
allow_create: true,
|
||||||
help_text: i18n.t("shopping_category_help"),
|
help_text: "shopping_category_help", // form.help_text always translated
|
||||||
},
|
},
|
||||||
substitute: {
|
substitute: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
@ -149,17 +148,17 @@ export class Models {
|
|||||||
multiple: true,
|
multiple: true,
|
||||||
field: "substitute",
|
field: "substitute",
|
||||||
list: "FOOD",
|
list: "FOOD",
|
||||||
label: i18n.t("Substitutes"),
|
label: "Substitutes",
|
||||||
allow_create: false,
|
allow_create: false,
|
||||||
help_text: i18n.t("substitute_help"),
|
help_text: "substitute_help",
|
||||||
},
|
},
|
||||||
substitute_siblings: {
|
substitute_siblings: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
advanced: true,
|
advanced: true,
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
field: "substitute_siblings",
|
field: "substitute_siblings",
|
||||||
label: i18n.t("substitute_siblings"),
|
label: "substitute_siblings", // form.label always translated in utils.getForm()
|
||||||
help_text: i18n.t("substitute_siblings_help"),
|
help_text: "substitute_siblings_help", // form.help_text always translated
|
||||||
condition: { field: "parent", value: true, condition: "field_exists" },
|
condition: { field: "parent", value: true, condition: "field_exists" },
|
||||||
},
|
},
|
||||||
substitute_children: {
|
substitute_children: {
|
||||||
@ -167,8 +166,8 @@ export class Models {
|
|||||||
advanced: true,
|
advanced: true,
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
field: "substitute_children",
|
field: "substitute_children",
|
||||||
label: i18n.t("substitute_children"),
|
label: "substitute_children",
|
||||||
help_text: i18n.t("substitute_children_help"),
|
help_text: "substitute_children_help",
|
||||||
condition: { field: "numchild", value: 0, condition: "gt" },
|
condition: { field: "numchild", value: 0, condition: "gt" },
|
||||||
},
|
},
|
||||||
inherit_fields: {
|
inherit_fields: {
|
||||||
@ -178,9 +177,9 @@ export class Models {
|
|||||||
multiple: true,
|
multiple: true,
|
||||||
field: "inherit_fields",
|
field: "inherit_fields",
|
||||||
list: "FOOD_INHERIT_FIELDS",
|
list: "FOOD_INHERIT_FIELDS",
|
||||||
label: i18n.t("InheritFields"),
|
label: "InheritFields",
|
||||||
condition: { field: "food_children_exist", value: true, condition: "preference_equals" },
|
condition: { field: "food_children_exist", value: true, condition: "preference_equals" },
|
||||||
help_text: i18n.t("InheritFields_help"),
|
help_text: "InheritFields_help",
|
||||||
},
|
},
|
||||||
child_inherit_fields: {
|
child_inherit_fields: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
@ -189,17 +188,17 @@ export class Models {
|
|||||||
multiple: true,
|
multiple: true,
|
||||||
field: "child_inherit_fields",
|
field: "child_inherit_fields",
|
||||||
list: "FOOD_INHERIT_FIELDS",
|
list: "FOOD_INHERIT_FIELDS",
|
||||||
label: i18n.t("ChildInheritFields"),
|
label: "ChildInheritFields", // form.label always translated in utils.getForm()
|
||||||
condition: { field: "numchild", value: 0, condition: "gt" },
|
condition: { field: "numchild", value: 0, condition: "gt" },
|
||||||
help_text: i18n.t("ChildInheritFields_help"),
|
help_text: "ChildInheritFields_help", // form.help_text always translated
|
||||||
},
|
},
|
||||||
reset_inherit: {
|
reset_inherit: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
advanced: true,
|
advanced: true,
|
||||||
type: "checkbox",
|
type: "checkbox",
|
||||||
field: "reset_inherit",
|
field: "reset_inherit",
|
||||||
label: i18n.t("reset_children"),
|
label: "reset_children",
|
||||||
help_text: i18n.t("reset_children_help"),
|
help_text: "reset_children_help",
|
||||||
condition: { field: "numchild", value: 0, condition: "gt" },
|
condition: { field: "numchild", value: 0, condition: "gt" },
|
||||||
},
|
},
|
||||||
form_function: "FoodCreateDefault",
|
form_function: "FoodCreateDefault",
|
||||||
@ -215,7 +214,7 @@ export class Models {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static KEYWORD = {
|
static KEYWORD = {
|
||||||
name: i18n.t("Keyword"), // *OPTIONAL: parameters will be built model -> model_type -> default
|
name: "Keyword", // *OPTIONAL: parameters will be built model -> model_type -> default
|
||||||
apiName: "Keyword",
|
apiName: "Keyword",
|
||||||
model_type: this.TREE,
|
model_type: this.TREE,
|
||||||
paginated: true,
|
paginated: true,
|
||||||
@ -232,21 +231,21 @@ export class Models {
|
|||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "name",
|
field: "name",
|
||||||
label: i18n.t("Name"),
|
label: "Name",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "description",
|
field: "description",
|
||||||
label: i18n.t("Description"),
|
label: "Description",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "emoji",
|
type: "emoji",
|
||||||
field: "icon",
|
field: "icon",
|
||||||
label: i18n.t("Icon"),
|
label: "Icon",
|
||||||
},
|
},
|
||||||
full_name: {
|
full_name: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
@ -258,7 +257,7 @@ export class Models {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static UNIT = {
|
static UNIT = {
|
||||||
name: i18n.t("Unit"),
|
name: "Unit",
|
||||||
apiName: "Unit",
|
apiName: "Unit",
|
||||||
paginated: true,
|
paginated: true,
|
||||||
create: {
|
create: {
|
||||||
@ -268,14 +267,14 @@ export class Models {
|
|||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "name",
|
field: "name",
|
||||||
label: i18n.t("Name"),
|
label: "Name",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "description",
|
field: "description",
|
||||||
label: i18n.t("Description"),
|
label: "Description",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -284,7 +283,7 @@ export class Models {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static SHOPPING_LIST = {
|
static SHOPPING_LIST = {
|
||||||
name: i18n.t("Shopping_list"),
|
name: "Shopping_list",
|
||||||
apiName: "ShoppingListEntry",
|
apiName: "ShoppingListEntry",
|
||||||
list: {
|
list: {
|
||||||
params: ["id", "checked", "supermarket", "options"],
|
params: ["id", "checked", "supermarket", "options"],
|
||||||
@ -297,7 +296,7 @@ export class Models {
|
|||||||
type: "lookup",
|
type: "lookup",
|
||||||
field: "unit",
|
field: "unit",
|
||||||
list: "UNIT",
|
list: "UNIT",
|
||||||
label: i18n.t("Unit"),
|
label: "Unit",
|
||||||
allow_create: true,
|
allow_create: true,
|
||||||
},
|
},
|
||||||
food: {
|
food: {
|
||||||
@ -305,7 +304,7 @@ export class Models {
|
|||||||
type: "lookup",
|
type: "lookup",
|
||||||
field: "food",
|
field: "food",
|
||||||
list: "FOOD",
|
list: "FOOD",
|
||||||
label: i18n.t("Food"),
|
label: "Food", // form.label always translated in utils.getForm()
|
||||||
allow_create: true,
|
allow_create: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -313,7 +312,7 @@ export class Models {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static RECIPE_BOOK = {
|
static RECIPE_BOOK = {
|
||||||
name: i18n.t("Recipe_Book"),
|
name: "Recipe_Book",
|
||||||
apiName: "RecipeBook",
|
apiName: "RecipeBook",
|
||||||
create: {
|
create: {
|
||||||
params: [["name", "description", "icon", "filter"]],
|
params: [["name", "description", "icon", "filter"]],
|
||||||
@ -322,27 +321,27 @@ export class Models {
|
|||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "name",
|
field: "name",
|
||||||
label: i18n.t("Name"),
|
label: "Name",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "description",
|
field: "description",
|
||||||
label: i18n.t("Description"),
|
label: "Description",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "emoji",
|
type: "emoji",
|
||||||
field: "icon",
|
field: "icon",
|
||||||
label: i18n.t("Icon"),
|
label: "Icon",
|
||||||
},
|
},
|
||||||
filter: {
|
filter: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "lookup",
|
type: "lookup",
|
||||||
field: "filter",
|
field: "filter",
|
||||||
label: i18n.t("Custom Filter"),
|
label: "Custom Filter",
|
||||||
list: "CUSTOM_FILTER",
|
list: "CUSTOM_FILTER",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -350,7 +349,7 @@ export class Models {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static SHOPPING_CATEGORY = {
|
static SHOPPING_CATEGORY = {
|
||||||
name: i18n.t("Shopping_Category"),
|
name: "Shopping_Category",
|
||||||
apiName: "SupermarketCategory",
|
apiName: "SupermarketCategory",
|
||||||
create: {
|
create: {
|
||||||
params: [["name", "description"]],
|
params: [["name", "description"]],
|
||||||
@ -359,14 +358,14 @@ export class Models {
|
|||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "name",
|
field: "name",
|
||||||
label: i18n.t("Name"),
|
label: "Name", // form.label always translated in utils.getForm()
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "description",
|
field: "description",
|
||||||
label: i18n.t("Description"),
|
label: "Description",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -374,7 +373,7 @@ export class Models {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static SHOPPING_CATEGORY_RELATION = {
|
static SHOPPING_CATEGORY_RELATION = {
|
||||||
name: i18n.t("Shopping_Category_Relation"),
|
name: "Shopping_Category_Relation",
|
||||||
apiName: "SupermarketCategoryRelation",
|
apiName: "SupermarketCategoryRelation",
|
||||||
create: {
|
create: {
|
||||||
params: [["category", "supermarket", "order"]],
|
params: [["category", "supermarket", "order"]],
|
||||||
@ -383,14 +382,14 @@ export class Models {
|
|||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "name",
|
field: "name",
|
||||||
label: i18n.t("Name"),
|
label: "Name",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "description",
|
field: "description",
|
||||||
label: i18n.t("Description"),
|
label: "Description",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -398,7 +397,7 @@ export class Models {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static SUPERMARKET = {
|
static SUPERMARKET = {
|
||||||
name: i18n.t("Supermarket"),
|
name: "Supermarket",
|
||||||
apiName: "Supermarket",
|
apiName: "Supermarket",
|
||||||
ordered_tags: [{ field: "category_to_supermarket", label: "category::name", color: "info" }],
|
ordered_tags: [{ field: "category_to_supermarket", label: "category::name", color: "info" }],
|
||||||
create: {
|
create: {
|
||||||
@ -408,14 +407,14 @@ export class Models {
|
|||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "name",
|
field: "name",
|
||||||
label: i18n.t("Name"),
|
label: "Name",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "description",
|
field: "description",
|
||||||
label: i18n.t("Description"),
|
label: "Description",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
categories: {
|
categories: {
|
||||||
@ -425,7 +424,7 @@ export class Models {
|
|||||||
list_label: "category::name",
|
list_label: "category::name",
|
||||||
ordered: true, // ordered lookups assume working with relation field
|
ordered: true, // ordered lookups assume working with relation field
|
||||||
field: "category_to_supermarket",
|
field: "category_to_supermarket",
|
||||||
label: i18n.t("Categories"),
|
label: "Categories", // form.label always translated in utils.getForm()
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -441,7 +440,7 @@ export class Models {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static AUTOMATION = {
|
static AUTOMATION = {
|
||||||
name: i18n.t("Automation"),
|
name: "Automation",
|
||||||
apiName: "Automation",
|
apiName: "Automation",
|
||||||
paginated: true,
|
paginated: true,
|
||||||
list: {
|
list: {
|
||||||
@ -456,47 +455,74 @@ export class Models {
|
|||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "name",
|
field: "name",
|
||||||
label: i18n.t("Name"),
|
label: "Name",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "description",
|
field: "description",
|
||||||
label: i18n.t("Description"),
|
label: "Description",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "choice",
|
type: "choice",
|
||||||
options: [
|
options: [
|
||||||
{ value: "FOOD_ALIAS", text: i18n.t("Food_Alias") },
|
{ value: "FOOD_ALIAS", text: "Food_Alias" },
|
||||||
{ value: "UNIT_ALIAS", text: i18n.t("Unit_Alias") },
|
{ value: "UNIT_ALIAS", text: "Unit_Alias" },
|
||||||
{ value: "KEYWORD_ALIAS", text: i18n.t("Keyword_Alias") },
|
{ value: "KEYWORD_ALIAS", text: "Keyword_Alias" },
|
||||||
],
|
],
|
||||||
field: "type",
|
field: "type",
|
||||||
label: i18n.t("Type"),
|
label: "Type",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
param_1: {
|
param_1: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "param_1",
|
field: "param_1",
|
||||||
label: i18n.t("Parameter") + " 1",
|
label: {
|
||||||
|
function: "translate",
|
||||||
|
phrase: "parameter_count",
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
token: "count",
|
||||||
|
attribute: "1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
param_2: {
|
param_2: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "param_2",
|
field: "param_2",
|
||||||
label: i18n.t("Parameter") + " 2",
|
label: {
|
||||||
|
function: "translate",
|
||||||
|
phrase: "parameter_count",
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
token: "count",
|
||||||
|
attribute: "2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
param_3: {
|
param_3: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "param_3",
|
field: "param_3",
|
||||||
label: i18n.t("Parameter") + " 3",
|
label: {
|
||||||
|
function: "translate",
|
||||||
|
phrase: "parameter_count",
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
token: "count",
|
||||||
|
attribute: "3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -504,7 +530,7 @@ export class Models {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static RECIPE = {
|
static RECIPE = {
|
||||||
name: i18n.t("Recipe"),
|
name: "Recipe",
|
||||||
apiName: "Recipe",
|
apiName: "Recipe",
|
||||||
list: {
|
list: {
|
||||||
params: [
|
params: [
|
||||||
@ -546,7 +572,7 @@ export class Models {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static CUSTOM_FILTER = {
|
static CUSTOM_FILTER = {
|
||||||
name: i18n.t("Custom Filter"),
|
name: "Custom Filter",
|
||||||
apiName: "CustomFilter",
|
apiName: "CustomFilter",
|
||||||
|
|
||||||
create: {
|
create: {
|
||||||
@ -556,7 +582,7 @@ export class Models {
|
|||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "name",
|
field: "name",
|
||||||
label: i18n.t("Name"),
|
label: "Name", // form.label always translated in utils.getForm()
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -566,14 +592,14 @@ export class Models {
|
|||||||
field: "shared",
|
field: "shared",
|
||||||
list: "USER",
|
list: "USER",
|
||||||
list_label: "username",
|
list_label: "username",
|
||||||
label: i18n.t("shared_with"),
|
label: "shared_with",
|
||||||
multiple: true,
|
multiple: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
static USER_NAME = {
|
static USER_NAME = {
|
||||||
name: i18n.t("User"),
|
name: "User",
|
||||||
apiName: "User",
|
apiName: "User",
|
||||||
list: {
|
list: {
|
||||||
params: ["filter_list"],
|
params: ["filter_list"],
|
||||||
@ -581,7 +607,7 @@ export class Models {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static MEAL_TYPE = {
|
static MEAL_TYPE = {
|
||||||
name: i18n.t("Meal_Type"),
|
name: "Meal_Type",
|
||||||
apiName: "MealType",
|
apiName: "MealType",
|
||||||
list: {
|
list: {
|
||||||
params: ["filter_list"],
|
params: ["filter_list"],
|
||||||
@ -589,7 +615,7 @@ export class Models {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static MEAL_PLAN = {
|
static MEAL_PLAN = {
|
||||||
name: i18n.t("Meal_Plan"),
|
name: "Meal_Plan",
|
||||||
apiName: "MealPlan",
|
apiName: "MealPlan",
|
||||||
list: {
|
list: {
|
||||||
params: ["options"],
|
params: ["options"],
|
||||||
@ -597,7 +623,7 @@ export class Models {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static USERFILE = {
|
static USERFILE = {
|
||||||
name: i18n.t("File"),
|
name: "File",
|
||||||
apiName: "UserFile",
|
apiName: "UserFile",
|
||||||
paginated: false,
|
paginated: false,
|
||||||
list: {
|
list: {
|
||||||
@ -612,27 +638,27 @@ export class Models {
|
|||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "text",
|
||||||
field: "name",
|
field: "name",
|
||||||
label: i18n.t("Name"),
|
label: "Name",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
file: {
|
file: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "file",
|
type: "file",
|
||||||
field: "file",
|
field: "file",
|
||||||
label: i18n.t("File"),
|
label: "File", // form.label always translated in utils.getForm()
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
static USER = {
|
static USER = {
|
||||||
name: i18n.t("User"),
|
name: "User",
|
||||||
apiName: "User",
|
apiName: "User",
|
||||||
paginated: false,
|
paginated: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
static STEP = {
|
static STEP = {
|
||||||
name: i18n.t("Step"),
|
name: "Step",
|
||||||
apiName: "Step",
|
apiName: "Step",
|
||||||
list: {
|
list: {
|
||||||
params: ["recipe", "query", "page", "pageSize", "options"],
|
params: ["recipe", "query", "page", "pageSize", "options"],
|
||||||
@ -652,10 +678,11 @@ export class Actions {
|
|||||||
token: "type",
|
token: "type",
|
||||||
from: "model",
|
from: "model",
|
||||||
attribute: "name",
|
attribute: "name",
|
||||||
|
translate: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
ok_label: i18n.t("Save"),
|
ok_label: { function: "translate", phrase: "Save" },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
static UPDATE = {
|
static UPDATE = {
|
||||||
@ -669,6 +696,7 @@ export class Actions {
|
|||||||
token: "type",
|
token: "type",
|
||||||
from: "model",
|
from: "model",
|
||||||
attribute: "name",
|
attribute: "name",
|
||||||
|
translate: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -685,10 +713,11 @@ export class Actions {
|
|||||||
token: "type",
|
token: "type",
|
||||||
from: "model",
|
from: "model",
|
||||||
attribute: "name",
|
attribute: "name",
|
||||||
|
translate: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
ok_label: i18n.t("Delete"),
|
ok_label: { function: "translate", phrase: "Delete" },
|
||||||
instruction: {
|
instruction: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "instruction",
|
type: "instruction",
|
||||||
@ -736,10 +765,11 @@ export class Actions {
|
|||||||
token: "type",
|
token: "type",
|
||||||
from: "model",
|
from: "model",
|
||||||
attribute: "name",
|
attribute: "name",
|
||||||
|
translate: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
ok_label: i18n.t("Merge"),
|
ok_label: { function: "translate", phrase: "Merge" },
|
||||||
instruction: {
|
instruction: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "instruction",
|
type: "instruction",
|
||||||
@ -756,6 +786,7 @@ export class Actions {
|
|||||||
token: "type",
|
token: "type",
|
||||||
from: "model",
|
from: "model",
|
||||||
attribute: "name",
|
attribute: "name",
|
||||||
|
translate: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@ -784,10 +815,11 @@ export class Actions {
|
|||||||
token: "type",
|
token: "type",
|
||||||
from: "model",
|
from: "model",
|
||||||
attribute: "name",
|
attribute: "name",
|
||||||
|
translate: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
ok_label: i18n.t("Move"),
|
ok_label: { function: "translate", phrase: "Move" },
|
||||||
instruction: {
|
instruction: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "instruction",
|
type: "instruction",
|
||||||
@ -804,6 +836,7 @@ export class Actions {
|
|||||||
token: "type",
|
token: "type",
|
||||||
from: "model",
|
from: "model",
|
||||||
attribute: "name",
|
attribute: "name",
|
||||||
|
translate: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -349,7 +349,7 @@ export function getConfig(model, action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let config = {
|
let config = {
|
||||||
name: model.name,
|
name: i18n.t(model.name),
|
||||||
apiName: model.apiName,
|
apiName: model.apiName,
|
||||||
}
|
}
|
||||||
// spread operator merges dictionaries - last item in list takes precedence
|
// spread operator merges dictionaries - last item in list takes precedence
|
||||||
@ -391,8 +391,11 @@ export function getForm(model, action, item1, item2) {
|
|||||||
value = v
|
value = v
|
||||||
}
|
}
|
||||||
if (value?.form_field) {
|
if (value?.form_field) {
|
||||||
|
for (const [i, h] of Object.entries(value)) {
|
||||||
|
// console.log("formfield", i)
|
||||||
|
}
|
||||||
value["value"] = item1?.[value?.field] ?? undefined
|
value["value"] = item1?.[value?.field] ?? undefined
|
||||||
value["help"] = item1?.[value?.help_text_field] ?? value?.help_text ?? undefined
|
value["help"] = item1?.[value?.help_text_field] ?? formTranslate(value?.help_text) ?? undefined
|
||||||
value["subtitle"] = item1?.[value?.subtitle_field] ?? value?.subtitle ?? undefined
|
value["subtitle"] = item1?.[value?.subtitle_field] ?? value?.subtitle ?? undefined
|
||||||
form.fields.push({
|
form.fields.push({
|
||||||
...value,
|
...value,
|
||||||
@ -410,23 +413,31 @@ export function getForm(model, action, item1, item2) {
|
|||||||
|
|
||||||
function formTranslate(translate, model, item1, item2) {
|
function formTranslate(translate, model, item1, item2) {
|
||||||
if (typeof translate !== "object") {
|
if (typeof translate !== "object") {
|
||||||
return translate
|
return i18n.t(translate)
|
||||||
}
|
}
|
||||||
let phrase = translate.phrase
|
let phrase = translate.phrase
|
||||||
let options = {}
|
let options = {}
|
||||||
let obj = undefined
|
let value = undefined
|
||||||
translate?.params.forEach(function (x, index) {
|
translate?.params?.forEach(function (x, index) {
|
||||||
switch (x.from) {
|
switch (x?.from) {
|
||||||
case "item1":
|
case "item1":
|
||||||
obj = item1
|
value = item1[x.attribute]
|
||||||
break
|
break
|
||||||
case "item2":
|
case "item2":
|
||||||
obj = item2
|
value = item2[x.attribute]
|
||||||
break
|
break
|
||||||
case "model":
|
case "model":
|
||||||
obj = model
|
value = model[x.attribute]
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
value = x.attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x.translate) {
|
||||||
|
options[x.token] = i18n.t(value)
|
||||||
|
} else {
|
||||||
|
options[x.token] = value
|
||||||
}
|
}
|
||||||
options[x.token] = obj[x.attribute]
|
|
||||||
})
|
})
|
||||||
return i18n.t(phrase, options)
|
return i18n.t(phrase, options)
|
||||||
}
|
}
|
||||||
|
@ -8548,10 +8548,10 @@ pretty-error@^2.0.2:
|
|||||||
lodash "^4.17.20"
|
lodash "^4.17.20"
|
||||||
renderkid "^2.0.4"
|
renderkid "^2.0.4"
|
||||||
|
|
||||||
prismjs@^1.13.0, prismjs@^1.23.0, prismjs@^1.25.0:
|
prismjs@^1.13.0, prismjs@^1.23.0, prismjs@^1.27.0:
|
||||||
version "1.25.0"
|
version "1.27.0"
|
||||||
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756"
|
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057"
|
||||||
integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg==
|
integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==
|
||||||
|
|
||||||
process-nextick-args@~2.0.0:
|
process-nextick-args@~2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
@ -10410,9 +10410,9 @@ url-loader@^2.2.0:
|
|||||||
schema-utils "^2.5.0"
|
schema-utils "^2.5.0"
|
||||||
|
|
||||||
url-parse@^1.4.3, url-parse@^1.5.3:
|
url-parse@^1.4.3, url-parse@^1.5.3:
|
||||||
version "1.5.7"
|
version "1.5.10"
|
||||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.7.tgz#00780f60dbdae90181f51ed85fb24109422c932a"
|
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
|
||||||
integrity sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==
|
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
querystringify "^2.1.1"
|
querystringify "^2.1.1"
|
||||||
requires-port "^1.0.0"
|
requires-port "^1.0.0"
|
||||||
|
Reference in New Issue
Block a user