mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-25 11:19:39 -05:00
Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84fd3055ea | ||
|
|
2bd60a6f13 | ||
|
|
fe75052baa | ||
|
|
0a3b750294 | ||
|
|
fcd3918b5f | ||
|
|
e7aac06ca7 | ||
|
|
67e1f57723 | ||
|
|
10581329e8 | ||
|
|
553c06f291 | ||
|
|
e0cbfd824c | ||
|
|
a5a522d378 | ||
|
|
8502bb235b | ||
|
|
42f2ad624f | ||
|
|
3b95bf40da | ||
|
|
81a6837b06 | ||
|
|
a95e352250 | ||
|
|
a4ca66d287 | ||
|
|
ec30b81ae5 | ||
|
|
92211b1f51 | ||
|
|
8eeea42057 | ||
|
|
7e76a71ccc | ||
|
|
b397c94f0a | ||
|
|
9552564e59 | ||
|
|
4ecf323e4f | ||
|
|
d5d5c2c52b | ||
|
|
7ffabfe711 | ||
|
|
49e0b5b962 | ||
|
|
a05f1ece24 | ||
|
|
748b91bb8a | ||
|
|
bd2e9cc3d9 | ||
|
|
c40bb20a7a | ||
|
|
b377d2cd35 | ||
|
|
dc0e91d0f9 | ||
|
|
5f12907544 | ||
|
|
889ddac7dc | ||
|
|
b369e2618a | ||
|
|
5a4e0204c9 | ||
|
|
bfc2e96b54 | ||
|
|
f065ef80aa | ||
|
|
61c14b8b05 | ||
|
|
35d5d64809 | ||
|
|
63c711d18c | ||
|
|
59e3ea70d1 | ||
|
|
6771662a9f | ||
|
|
9b792a1393 | ||
|
|
862957c121 | ||
|
|
bdcbafd52f | ||
|
|
5e454a5212 | ||
|
|
20bea63997 | ||
|
|
8a265772c0 | ||
|
|
6febb4e3e8 | ||
|
|
04f9167fd8 | ||
|
|
8f29e01daf | ||
|
|
e810363b22 | ||
|
|
b5a2120bdf | ||
|
|
643fcbad9b | ||
|
|
4a3b834463 | ||
|
|
003149133a | ||
|
|
a43de0ca4d | ||
|
|
e05aaed75c | ||
|
|
4984e3e31b | ||
|
|
11dce4c6ad | ||
|
|
8d0d338ea2 | ||
|
|
d22b5a4a39 | ||
|
|
d09e629415 | ||
|
|
53ef2ef99f | ||
|
|
d7b26d1b29 | ||
|
|
602f0a8bf0 | ||
|
|
673d12d233 | ||
|
|
6359245925 | ||
|
|
a7c4822322 | ||
|
|
e94419f320 | ||
|
|
01f46483ff | ||
|
|
d6da5688af | ||
|
|
680ae39201 | ||
|
|
2472ee9c26 | ||
|
|
4428b06d4a | ||
|
|
e9c38d7d5e | ||
|
|
6f28d58807 | ||
|
|
88db611f0a | ||
|
|
f3302b4014 | ||
|
|
d4bb161275 | ||
|
|
32f1538938 | ||
|
|
029baea4c7 | ||
|
|
38d1b7cef5 | ||
|
|
856f417d1b | ||
|
|
85821bcc94 | ||
|
|
2345af8fd6 | ||
|
|
51107c64ee | ||
|
|
81983c5ae2 | ||
|
|
f7713a43a7 | ||
|
|
ffd951a7f4 | ||
|
|
319ac8e191 | ||
|
|
e292b72e34 | ||
|
|
4e795ecf55 | ||
|
|
e3c2a66723 | ||
|
|
eec3e97f97 | ||
|
|
3f481d6922 | ||
|
|
0810ab7210 | ||
|
|
abd621145c | ||
|
|
7d218aa93d | ||
|
|
1b41bd9115 | ||
|
|
aea247b4a3 | ||
|
|
e2843bb02f | ||
|
|
e3aa3e1137 | ||
|
|
da1187b03a | ||
|
|
f9ed79978c | ||
|
|
920a3ed4a3 | ||
|
|
2077eae142 | ||
|
|
b1ef35e415 | ||
|
|
0a687d840c | ||
|
|
6a3034b966 | ||
|
|
3d7afbfe4f | ||
|
|
02e43730bd | ||
|
|
6adf077ee5 | ||
|
|
d73ffa46ff | ||
|
|
8572f338ad | ||
|
|
920ec8e74b | ||
|
|
2328bf2342 | ||
|
|
85620a1431 | ||
|
|
d4f654554b | ||
|
|
c8115545b8 | ||
|
|
6dbf0871ec | ||
|
|
f1c5c8bc43 | ||
|
|
22e0108992 | ||
|
|
e2e05c8d1d | ||
|
|
b02b36812d |
2
.github/workflows/build-docker.yml
vendored
2
.github/workflows/build-docker.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
fi
|
||||
|
||||
# Build Vue 3 frontend
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: yarn
|
||||
|
||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -13,14 +13,14 @@ jobs:
|
||||
node-version: ["22"]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: awalsh128/cache-apt-pkgs-action@v1.5.3
|
||||
- uses: awalsh128/cache-apt-pkgs-action@v1.6.0
|
||||
with:
|
||||
packages: libsasl2-dev python3-dev libxml2-dev libxmlsec1-dev libxslt-dev libxmlsec1-openssl libxslt-dev libldap2-dev libssl-dev gcc musl-dev postgresql-dev zlib-dev jpeg-dev libwebp-dev openssl-dev libffi-dev cargo openldap-dev python3-dev xmlsec-dev xmlsec build-base g++ curl
|
||||
version: 1.0
|
||||
|
||||
# Setup python & dependencies
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: "pip"
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
# Build Vue frontend & Dependencies
|
||||
- name: Set up Node ${{ matrix.node-version }}
|
||||
if: steps.django_cache.outputs.cache-hit != 'true'
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "yarn"
|
||||
|
||||
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
with:
|
||||
languages: python, javascript
|
||||
@@ -47,6 +47,6 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
languages: javascript, python
|
||||
|
||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: pip install mkdocs-material mkdocs-include-markdown-plugin
|
||||
|
||||
16
boot.sh
16
boot.sh
@@ -2,7 +2,7 @@
|
||||
source venv/bin/activate
|
||||
|
||||
# these are envsubst in the nginx config, make sure they default to something sensible when unset
|
||||
export TANDOOR_PORT="${TANDOOR_PORT:-8080}"
|
||||
export TANDOOR_PORT="${TANDOOR_PORT:-80}"
|
||||
export MEDIA_ROOT=${MEDIA_ROOT:-/opt/recipes/mediafiles};
|
||||
export STATIC_ROOT=${STATIC_ROOT:-/opt/recipes/staticfiles};
|
||||
|
||||
@@ -12,11 +12,6 @@ GUNICORN_LOG_LEVEL="${GUNICORN_LOG_LEVEL:-'info'}"
|
||||
|
||||
PLUGINS_BUILD="${PLUGINS_BUILD:-0}"
|
||||
|
||||
if [ "${TANDOOR_PORT}" -eq 80 ]; then
|
||||
echo "TANDOOR_PORT set to 8080 because 80 is now taken by the integrated nginx"
|
||||
TANDOOR_PORT=8080
|
||||
fi
|
||||
|
||||
display_warning() {
|
||||
echo "[WARNING]"
|
||||
echo -e "$1"
|
||||
@@ -29,7 +24,6 @@ envsubst '$MEDIA_ROOT $STATIC_ROOT $TANDOOR_PORT' < /opt/recipes/http.d/Recipes.
|
||||
echo "Starting nginx"
|
||||
nginx
|
||||
|
||||
|
||||
echo "Checking configuration..."
|
||||
|
||||
# SECRET_KEY (or a valid file at SECRET_KEY_FILE) must be set in .env file
|
||||
@@ -110,9 +104,5 @@ chmod -R 755 ${MEDIA_ROOT:-/opt/recipes/mediafiles}
|
||||
ipv6_disable=$(cat /sys/module/ipv6/parameters/disable)
|
||||
|
||||
echo "Starting gunicorn"
|
||||
# Check if IPv6 is enabled, only then run gunicorn with ipv6 support
|
||||
if [ "$ipv6_disable" -eq 0 ]; then
|
||||
exec gunicorn -b "[::]:$TANDOOR_PORT" --workers $GUNICORN_WORKERS --threads $GUNICORN_THREADS --access-logfile - --error-logfile - --log-level $GUNICORN_LOG_LEVEL recipes.wsgi
|
||||
else
|
||||
exec gunicorn -b ":$TANDOOR_PORT" --workers $GUNICORN_WORKERS --threads $GUNICORN_THREADS --access-logfile - --error-logfile - --log-level $GUNICORN_LOG_LEVEL recipes.wsgi
|
||||
fi
|
||||
exec gunicorn --bind unix:/run/tandoor.sock --workers $GUNICORN_WORKERS --threads $GUNICORN_THREADS --timeout ${GUNICORN_TIMEOUT:-30} --access-logfile - --error-logfile - --log-level $GUNICORN_LOG_LEVEL recipes.wsgi
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class FoodPropertyHelper:
|
||||
found_property = False
|
||||
# if food has a value for the given property type (no matter if conversion is possible)
|
||||
has_property_value = False
|
||||
if i.food.properties_food_amount == 0 or i.food.properties_food_unit is None and not (i.amount == 0 or i.no_amount): # if food is configured incorrectly
|
||||
if (i.food.properties_food_amount == 0 or i.food.properties_food_unit is None) and not (i.amount == 0 or i.no_amount): # if food is configured incorrectly
|
||||
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': None}
|
||||
computed_properties[pt.id]['missing_value'] = True
|
||||
else:
|
||||
@@ -63,8 +63,9 @@ class FoodPropertyHelper:
|
||||
computed_properties[p.property_type.id]['food_values'], c.food.id, (c.amount / i.food.properties_food_amount) * p.property_amount, c.food)
|
||||
if not found_property:
|
||||
# if no amount and food does not exist yet add it but don't count as missing
|
||||
if i.amount == 0 or i.no_amount and i.food.id not in computed_properties[pt.id]['food_values']:
|
||||
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': 0}
|
||||
if i.amount == 0 or i.no_amount:
|
||||
if i.food.id not in computed_properties[pt.id]['food_values']:
|
||||
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': 0}
|
||||
# if amount is present but unit is missing indicate it in the result
|
||||
elif i.unit is None:
|
||||
if i.food.id not in computed_properties[pt.id]['food_values']:
|
||||
@@ -72,7 +73,8 @@ class FoodPropertyHelper:
|
||||
computed_properties[pt.id]['food_values'][i.food.id]['missing_unit'] = True
|
||||
else:
|
||||
computed_properties[pt.id]['missing_value'] = True
|
||||
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': None}
|
||||
if i.food.id not in computed_properties[pt.id]['food_values']:
|
||||
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': {'id': i.food.id, 'name': i.food.name}, 'value': None}
|
||||
if has_property_value and i.unit is not None:
|
||||
computed_properties[pt.id]['food_values'][i.food.id]['missing_conversion'] = {'base_unit': {'id': i.unit.id, 'name': i.unit.name}, 'converted_unit': {'id': i.food.properties_food_unit.id, 'name': i.food.properties_food_unit.name}}
|
||||
|
||||
@@ -82,8 +84,12 @@ class FoodPropertyHelper:
|
||||
# TODO move to central helper ? --> use defaultdict
|
||||
@staticmethod
|
||||
def add_or_create(d, key, value, food):
|
||||
if key in d and d[key]['value']:
|
||||
d[key]['value'] += value
|
||||
if key in d:
|
||||
# value can be None if a previous instance of the same food was missing a conversion
|
||||
if d[key]['value']:
|
||||
d[key]['value'] += value
|
||||
else:
|
||||
d[key]['value'] = value
|
||||
else:
|
||||
d[key] = {'id': food.id, 'food': {'id': food.id, 'name': food.name}, 'value': value}
|
||||
return d
|
||||
|
||||
@@ -326,7 +326,7 @@ class RecipeSearch():
|
||||
def _favorite_recipes(self):
|
||||
if self._sort_includes('favorite') or self._timescooked or self._timescooked_gte or self._timescooked_lte:
|
||||
less_than = self._timescooked_lte and not self._sort_includes('-favorite')
|
||||
if less_than:
|
||||
if less_than or self._timescooked == 0:
|
||||
default = 1000
|
||||
else:
|
||||
default = 0
|
||||
@@ -339,7 +339,7 @@ class RecipeSearch():
|
||||
self._queryset = self._queryset.annotate(favorite=Coalesce(Subquery(favorite_recipes), default))
|
||||
|
||||
if self._timescooked:
|
||||
self._queryset = self._queryset.filter(favorite=0)
|
||||
self._queryset = self._queryset.filter(favorite=self._timescooked)
|
||||
elif self._timescooked_lte:
|
||||
self._queryset = self._queryset.filter(favorite__lte=int(self._timescooked_lte)).exclude(favorite=0)
|
||||
elif self._timescooked_gte:
|
||||
|
||||
@@ -69,15 +69,8 @@ def get_from_scraper(scrape, request):
|
||||
recipe_json['description'] = parse_description(description)
|
||||
recipe_json['description'] = automation_engine.apply_regex_replace_automation(recipe_json['description'], Automation.DESCRIPTION_REPLACE)
|
||||
|
||||
# assign servings attributes
|
||||
try:
|
||||
# dont use scrape.yields() as this will always return "x servings" or "x items", should be improved in scrapers directly
|
||||
servings = scrape.schema.data.get('recipeYield') or 1
|
||||
except Exception:
|
||||
servings = 1
|
||||
|
||||
recipe_json['servings'] = parse_servings(servings)
|
||||
recipe_json['servings_text'] = parse_servings_text(servings)
|
||||
recipe_json['servings'] = parse_servings(scrape.schema.data.get('recipeYield'))
|
||||
recipe_json['servings_text'] = parse_servings_text(scrape.schema.data.get('recipeYield'))
|
||||
|
||||
# assign time attributes
|
||||
try:
|
||||
@@ -406,7 +399,7 @@ def parse_servings(servings):
|
||||
def parse_servings_text(servings):
|
||||
if isinstance(servings, str):
|
||||
try:
|
||||
servings = re.sub("\\d+", '', servings).strip()
|
||||
servings = re.sub("\\d+", '', servings, 1).strip()
|
||||
except Exception:
|
||||
servings = ''
|
||||
if isinstance(servings, list):
|
||||
|
||||
@@ -135,8 +135,9 @@ class UnitConversionHelper:
|
||||
:param food: base food
|
||||
:return: converted ingredient object from base amount/unit/food
|
||||
"""
|
||||
if uc.food is None or uc.food == food:
|
||||
if (uc.food is None or uc.food == food) and uc.converted_amount > 0 and uc.base_amount > 0:
|
||||
if unit == uc.base_unit:
|
||||
return Ingredient(amount=amount * (uc.converted_amount / uc.base_amount), unit=uc.converted_unit, food=food, space=self.space)
|
||||
else:
|
||||
return Ingredient(amount=amount * (uc.base_amount / uc.converted_amount), unit=uc.base_unit, food=food, space=self.space)
|
||||
return None
|
||||
|
||||
@@ -128,6 +128,7 @@ class Mealie1(Integration):
|
||||
|
||||
steps_relation = []
|
||||
first_step_of_recipe_dict = {}
|
||||
step_id_dict = {}
|
||||
for s in mealie_database['recipe_instructions']:
|
||||
if s['recipe_id'] in recipes_dict:
|
||||
step = Step.objects.create(instruction=(s['text'] if s['text'] else "") + (f" \n {s['summary']}" if s['summary'] else ""),
|
||||
@@ -135,9 +136,20 @@ class Mealie1(Integration):
|
||||
name=s['title'],
|
||||
space=self.request.space)
|
||||
steps_relation.append(Recipe.steps.through(recipe_id=recipes_dict[s['recipe_id']], step_id=step.pk))
|
||||
step_id_dict[s["id"]] = step.pk
|
||||
if s['recipe_id'] not in first_step_of_recipe_dict:
|
||||
first_step_of_recipe_dict[s['recipe_id']] = step.pk
|
||||
|
||||
# it is possible for a recipe to not have steps but have ingredients, in that case create an empty step to add them to later
|
||||
for r in recipes_dict.keys():
|
||||
if r not in first_step_of_recipe_dict:
|
||||
step = Step.objects.create(instruction='',
|
||||
order=0,
|
||||
name='',
|
||||
space=self.request.space)
|
||||
steps_relation.append(Recipe.steps.through(recipe_id=recipes_dict[r], step_id=step.pk))
|
||||
first_step_of_recipe_dict[r] = step.pk
|
||||
|
||||
for n in mealie_database['notes']:
|
||||
if n['recipe_id'] in recipes_dict:
|
||||
step = Step.objects.create(instruction=n['text'],
|
||||
@@ -153,6 +165,11 @@ class Mealie1(Integration):
|
||||
self.import_log.msg += f"Importing {len(mealie_database["recipes_ingredients"])} ingredients...\n"
|
||||
self.import_log.save()
|
||||
|
||||
# mealie stores the reference to a step (instruction) from an ingredient (reference) in the recipe_ingredient_ref_link table
|
||||
recipe_ingredient_ref_link_dict = {}
|
||||
for ref in mealie_database['recipe_ingredient_ref_link']:
|
||||
recipe_ingredient_ref_link_dict[ref["reference_id"]] = ref["instruction_id"]
|
||||
|
||||
ingredients_relation = []
|
||||
for i in mealie_database['recipes_ingredients']:
|
||||
if i['recipe_id'] in recipes_dict:
|
||||
@@ -162,18 +179,18 @@ class Mealie1(Integration):
|
||||
is_header=True,
|
||||
space=self.request.space,
|
||||
)
|
||||
ingredients_relation.append(Step.ingredients.through(step_id=first_step_of_recipe_dict[i['recipe_id']], ingredient_id=title_ingredient.pk))
|
||||
ingredients_relation.append(Step.ingredients.through(step_id=get_step_id(i, first_step_of_recipe_dict, step_id_dict,recipe_ingredient_ref_link_dict), ingredient_id=title_ingredient.pk))
|
||||
if i['food_id']:
|
||||
ingredient = Ingredient.objects.create(
|
||||
food_id=foods_dict[i['food_id']] if i['food_id'] in foods_dict else None,
|
||||
unit_id=units_dict[i['unit_id']] if i['unit_id'] in units_dict else None,
|
||||
original_text=i['original_text'],
|
||||
order=i['position'],
|
||||
amount=i['quantity'],
|
||||
amount=i['quantity'] if i['quantity'] else 0,
|
||||
note=i['note'],
|
||||
space=self.request.space,
|
||||
)
|
||||
ingredients_relation.append(Step.ingredients.through(step_id=first_step_of_recipe_dict[i['recipe_id']], ingredient_id=ingredient.pk))
|
||||
ingredients_relation.append(Step.ingredients.through(step_id=get_step_id(i, first_step_of_recipe_dict, step_id_dict,recipe_ingredient_ref_link_dict), ingredient_id=ingredient.pk))
|
||||
elif i['note'].strip():
|
||||
amount, unit, food, note = ingredient_parser.parse(i['note'].strip())
|
||||
f = ingredient_parser.get_food(food)
|
||||
@@ -186,7 +203,7 @@ class Mealie1(Integration):
|
||||
original_text=i['original_text'],
|
||||
space=self.request.space,
|
||||
)
|
||||
ingredients_relation.append(Step.ingredients.through(step_id=first_step_of_recipe_dict[i['recipe_id']], ingredient_id=ingredient.pk))
|
||||
ingredients_relation.append(Step.ingredients.through(step_id=get_step_id(i, first_step_of_recipe_dict, step_id_dict,recipe_ingredient_ref_link_dict), ingredient_id=ingredient.pk))
|
||||
Step.ingredients.through.objects.bulk_create(ingredients_relation)
|
||||
|
||||
self.import_log.msg += f"Importing {len(mealie_database["recipes_to_categories"]) + len(mealie_database["recipes_to_tags"])} category and keyword relations...\n"
|
||||
@@ -340,3 +357,10 @@ class Mealie1(Integration):
|
||||
|
||||
def get_file_from_recipe(self, recipe):
|
||||
raise NotImplementedError('Method not implemented in storage integration')
|
||||
|
||||
|
||||
def get_step_id(i, first_step_of_recipe_dict, step_id_dict, recipe_ingredient_ref_link_dict):
|
||||
try:
|
||||
return step_id_dict[recipe_ingredient_ref_link_dict[i['reference_id']]]
|
||||
except KeyError:
|
||||
return first_step_of_recipe_dict[i['recipe_id']]
|
||||
@@ -12,7 +12,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
|
||||
"PO-Revision-Date: 2025-09-22 10:09+0000\n"
|
||||
"PO-Revision-Date: 2025-11-18 07:01+0000\n"
|
||||
"Last-Translator: Vincenzo Reale <smart2128vr@gmail.com>\n"
|
||||
"Language-Team: Italian <http://translate.tandoor.dev/projects/tandoor/"
|
||||
"recipes-backend/it/>\n"
|
||||
@@ -21,7 +21,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.13.1\n"
|
||||
"X-Generator: Weblate 5.13.3\n"
|
||||
|
||||
#: .\cookbook\forms.py:50
|
||||
msgid "Default"
|
||||
@@ -126,54 +126,52 @@ msgid "ferment"
|
||||
msgstr "fermentare"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:325
|
||||
#, fuzzy
|
||||
#| msgid "Last cooked"
|
||||
msgid "slow cook"
|
||||
msgstr "Cucinato di recente"
|
||||
msgstr "cottura lenta"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:326
|
||||
msgid "egg boiler"
|
||||
msgstr ""
|
||||
msgstr "bollitore per uova"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:327
|
||||
msgid "kettle"
|
||||
msgstr ""
|
||||
msgstr "teiera"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:328
|
||||
msgid "blend"
|
||||
msgstr ""
|
||||
msgstr "miscela"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:329
|
||||
msgid "pre-clean"
|
||||
msgstr ""
|
||||
msgstr "pre-pulizia"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:330
|
||||
msgid "high temperature"
|
||||
msgstr ""
|
||||
msgstr "alta temperatura"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:331
|
||||
msgid "rice cooker"
|
||||
msgstr ""
|
||||
msgstr "cuociriso"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:332
|
||||
msgid "caramelize"
|
||||
msgstr ""
|
||||
msgstr "caramellare"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:333
|
||||
msgid "peeler"
|
||||
msgstr ""
|
||||
msgstr "pelapatate"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:334
|
||||
msgid "slicer"
|
||||
msgstr ""
|
||||
msgstr "affettatrice"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:335
|
||||
msgid "grater"
|
||||
msgstr ""
|
||||
msgstr "grattugia"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:336
|
||||
msgid "spiralizer"
|
||||
msgstr ""
|
||||
msgstr "tagliaverdure a spirale"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:337
|
||||
msgid "sous-vide"
|
||||
@@ -235,7 +233,7 @@ msgstr "Carboidrati"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:212
|
||||
msgid "Cholesterol"
|
||||
msgstr ""
|
||||
msgstr "Colesterolo"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:213
|
||||
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
|
||||
@@ -244,33 +242,31 @@ msgstr "Grassi"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:214
|
||||
msgid "Fiber"
|
||||
msgstr ""
|
||||
msgstr "Fibra"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:215
|
||||
#, fuzzy
|
||||
#| msgid "Proteins"
|
||||
msgid "Protein"
|
||||
msgstr "Proteine"
|
||||
msgstr "Proteina"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:216
|
||||
msgid "Saturated Fat"
|
||||
msgstr ""
|
||||
msgstr "Grasso saturo"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:217
|
||||
msgid "Sodium"
|
||||
msgstr ""
|
||||
msgstr "Sodio"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:218
|
||||
msgid "Sugar"
|
||||
msgstr ""
|
||||
msgstr "Zucchero"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:219
|
||||
msgid "Trans Fat"
|
||||
msgstr ""
|
||||
msgstr "Grasso trans"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:220
|
||||
msgid "Unsaturated Fat"
|
||||
msgstr ""
|
||||
msgstr "Grasso insaturo"
|
||||
|
||||
#: .\cookbook\integration\openeats.py:28
|
||||
msgid "Recipe source:"
|
||||
@@ -484,7 +480,7 @@ msgstr "Hai raggiungo il limite per il caricamento dei file."
|
||||
|
||||
#: .\cookbook\serializer.py:281
|
||||
msgid "The given file type is not allowed."
|
||||
msgstr ""
|
||||
msgstr "Il tipo di filo specificato non è consentito."
|
||||
|
||||
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
|
||||
msgid ""
|
||||
@@ -493,7 +489,7 @@ msgstr "Hai raggiunto il numero massimo di istanze di tua proprietà."
|
||||
|
||||
#: .\cookbook\serializer.py:434
|
||||
msgid "Space Name must be unique."
|
||||
msgstr ""
|
||||
msgstr "Il nome dello spazio deve essere univoco."
|
||||
|
||||
#: .\cookbook\serializer.py:469
|
||||
msgid "Cannot modify Space owner permission."
|
||||
@@ -847,10 +843,8 @@ msgid "We are sorry, but the sign up is currently closed."
|
||||
msgstr "Spiacenti, al momento le iscrizioni sono chiuse."
|
||||
|
||||
#: .\cookbook\templates\frontend\tandoor.html:15
|
||||
#, fuzzy
|
||||
#| msgid "Tandoor Recipes Invite"
|
||||
msgid "Tandoor Recipe Manager"
|
||||
msgstr "Invito per Tandoor Recipes"
|
||||
msgstr "Gestore delle ricette Tandoor"
|
||||
|
||||
#: .\cookbook\templates\index.html:28
|
||||
msgid "Search recipe ..."
|
||||
@@ -1442,8 +1436,6 @@ msgstr ""
|
||||
" %(site_name)s. Per finire, completa il modulo seguente:"
|
||||
|
||||
#: .\cookbook\templates\socialaccount\signup.html:32
|
||||
#, fuzzy
|
||||
#| msgid "I accept the follwoing"
|
||||
msgid "I accept the following"
|
||||
msgstr "Accetto i seguenti"
|
||||
|
||||
@@ -1529,15 +1521,6 @@ msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: .\cookbook\templates\system.html:24
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "\n"
|
||||
#| " Django Recipes is an open source free software application. It "
|
||||
#| "can be found on\n"
|
||||
#| " <a href=\"https://github.com/vabene1111/recipes\">GitHub</a>.\n"
|
||||
#| " Changelogs can be found <a href=\"https://github.com/vabene1111/"
|
||||
#| "recipes/releases\">here</a>.\n"
|
||||
#| " "
|
||||
msgid ""
|
||||
"\n"
|
||||
" Tandoor Recipes is an open source free software application. It can "
|
||||
@@ -1548,11 +1531,11 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Django Recipes è una applicazione gratuita e open source. È "
|
||||
" Tandoor Recipes è una applicazione gratuita e open source. È "
|
||||
"disponibile su\n"
|
||||
" <a href=\"https://github.com/vabene1111/recipes\">GitHub</a>.\n"
|
||||
" Puoi consultare le ultime novità <a href=\"https://github.com/"
|
||||
"vabene1111/recipes/releases\">qui</a>.\n"
|
||||
" <a href=\"https://github.com/TandoorRecipes/recipes\">GitHub</a>.\n"
|
||||
" Puoi consultare le ultime novità <a href="
|
||||
"\"https://github.com/TandoorRecipes/recipes/releases\">qui</a>.\n"
|
||||
" "
|
||||
|
||||
#: .\cookbook\templates\system.html:30
|
||||
@@ -1574,7 +1557,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\templates\system.html:56
|
||||
msgid "Plugins"
|
||||
msgstr ""
|
||||
msgstr "Estensioni"
|
||||
|
||||
#: .\cookbook\templates\system.html:67
|
||||
msgid "Media Serving"
|
||||
@@ -1802,18 +1785,12 @@ msgid "{obj.name} was added to the shopping list."
|
||||
msgstr "{obj.name} è stato aggiunto alla lista della spesa."
|
||||
|
||||
#: .\cookbook\views\api.py:1239
|
||||
#, fuzzy
|
||||
#| msgid "Filter meal plans from date (inclusive) in the format of YYYY-MM-DD."
|
||||
msgid "Filter meal plans from date (inclusive)."
|
||||
msgstr ""
|
||||
"Filtra i piani alimentari in base alla data (inclusa) nel formato AAAA-MM-GG."
|
||||
msgstr "Filtra i piani alimentari dalla data (inclusa)."
|
||||
|
||||
#: .\cookbook\views\api.py:1241
|
||||
#, fuzzy
|
||||
#| msgid "Filter meal plans to date (inclusive) in the format of YYYY-MM-DD."
|
||||
msgid "Filter meal plans to date (inclusive)."
|
||||
msgstr ""
|
||||
"Filtra i piani alimentari fino alla data (inclusa) nel formato AAAA-MM-GG."
|
||||
msgstr "Filtra i piani alimentari fino alla data (inclusa)."
|
||||
|
||||
#: .\cookbook\views\api.py:1244
|
||||
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
|
||||
@@ -1940,115 +1917,70 @@ msgstr "ID dell'unità che una ricetta dovrebbe avere."
|
||||
|
||||
#: .\cookbook\views\api.py:1464
|
||||
msgid "Exact rating of recipe"
|
||||
msgstr ""
|
||||
msgstr "Valutazione precisa della ricetta"
|
||||
|
||||
#: .\cookbook\views\api.py:1465
|
||||
#, fuzzy
|
||||
#| msgid "ID of unit a recipe should have."
|
||||
msgid "Rating a recipe should have or greater."
|
||||
msgstr "ID dell'unità che una ricetta dovrebbe avere."
|
||||
msgstr "La valutazione che una ricetta dovrebbe avere o superiore."
|
||||
|
||||
#: .\cookbook\views\api.py:1466
|
||||
#, fuzzy
|
||||
#| msgid "ID of unit a recipe should have."
|
||||
msgid "Rating a recipe should have or smaller."
|
||||
msgstr "ID dell'unità che una ricetta dovrebbe avere."
|
||||
msgstr "La valutazione che una ricetta dovrebbe avere o inferiore."
|
||||
|
||||
#: .\cookbook\views\api.py:1468
|
||||
msgid "Filter recipes cooked X times."
|
||||
msgstr ""
|
||||
msgstr "Filtra le ricette cucinate N volte."
|
||||
|
||||
#: .\cookbook\views\api.py:1469
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Filter recipes cooked X times or more. Negative values returns cooked "
|
||||
#| "less than X times"
|
||||
msgid "Filter recipes cooked X times or more."
|
||||
msgstr ""
|
||||
"Filtra le ricette cucinate X volte o più. I valori negativi restituiscono "
|
||||
"ricette cucinate meno di X volte"
|
||||
msgstr "Filtra le ricette cucinate N volte o più."
|
||||
|
||||
#: .\cookbook\views\api.py:1470
|
||||
msgid "Filter recipes cooked X times or less."
|
||||
msgstr ""
|
||||
msgstr "Filtra le ricette cucinate N volte o meno."
|
||||
|
||||
#: .\cookbook\views\api.py:1472
|
||||
#, fuzzy
|
||||
#| msgid "Filter for entries with the given recipe"
|
||||
msgid "Filter recipes created on the given date."
|
||||
msgstr "Filtra le voci con la ricetta specificata"
|
||||
msgstr "Filtra create alla data specificata."
|
||||
|
||||
#: .\cookbook\views\api.py:1473
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
|
||||
#| "before date."
|
||||
msgid "Filter recipes created on the given date or after."
|
||||
msgstr ""
|
||||
"Filtra le ricette create il o dopo AAAA-MM-GG. Anteponendo: filtra alla data "
|
||||
"o prima della data."
|
||||
msgstr "Filtra le ricette create alla data specificata o dopo."
|
||||
|
||||
#: .\cookbook\views\api.py:1474
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
|
||||
#| "before date."
|
||||
msgid "Filter recipes created on the given date or before."
|
||||
msgstr ""
|
||||
"Filtra le ricette create il o dopo AAAA-MM-GG. Anteponendo: filtra alla data "
|
||||
"o prima della data."
|
||||
msgstr "Filtra le ricette create alla data specificata o prima."
|
||||
|
||||
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
|
||||
#: .\cookbook\views\api.py:1478
|
||||
#, fuzzy
|
||||
#| msgid "Filter for entries with the given recipe"
|
||||
msgid "Filter recipes updated on the given date."
|
||||
msgstr "Filtra le voci con la ricetta specificata"
|
||||
msgstr "Filtra le ricette aggiornate alla data specificata."
|
||||
|
||||
#: .\cookbook\views\api.py:1480
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
|
||||
#| "on or before date."
|
||||
msgid "Filter recipes last cooked on the given date or after."
|
||||
msgstr ""
|
||||
"Filtra le ricette cucinate l'ultima volta il o dopo AAAA-MM-GG. Anteponendo "
|
||||
"- filtra alla data o prima della data."
|
||||
msgstr "Filtra le ricette cucinate l'ultima volta alla data specificata o dopo."
|
||||
|
||||
#: .\cookbook\views\api.py:1481
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
|
||||
#| "on or before date."
|
||||
msgid "Filter recipes last cooked on the given date or before."
|
||||
msgstr ""
|
||||
"Filtra le ricette cucinate l'ultima volta il o dopo AAAA-MM-GG. Anteponendo "
|
||||
"- filtra alla data o prima della data."
|
||||
"Filtra le ricette cucinate l'ultima volta alla data specificata o prima."
|
||||
|
||||
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters "
|
||||
#| "on or before date."
|
||||
msgid "Filter recipes lasts viewed on the given date."
|
||||
msgstr ""
|
||||
"Filtra le ricette visualizzate per ultime il o dopo AAAA-MM-GG. Anteponendo "
|
||||
"- filtra alla data o prima della data."
|
||||
msgstr "Filtra le ricette visualizzate per ultime alla data specificata."
|
||||
|
||||
#: .\cookbook\views\api.py:1486
|
||||
#, fuzzy
|
||||
#| msgid "Filter for entries with the given recipe"
|
||||
msgid "Filter recipes for ones created by the given user ID"
|
||||
msgstr "Filtra le voci con la ricetta specificata"
|
||||
msgstr "Filtra le ricette create dall'ID utente specificato"
|
||||
|
||||
#: .\cookbook\views\api.py:1487
|
||||
msgid "If only internal recipes should be returned. [true/<b>false</b>]"
|
||||
msgstr ""
|
||||
"Se devono essere restituite solo le ricette interne. [vero/<b>falso</b>]"
|
||||
"Se devono essere restituite solo le ricette interne. [true/<b>false</b>]"
|
||||
|
||||
#: .\cookbook\views\api.py:1488
|
||||
msgid "Returns the results in randomized order. [true/<b>false</b>]"
|
||||
msgstr "Restituisce i risultati in ordine casuale. [vero/<b>falso</b>]"
|
||||
msgstr "Restituisce i risultati in ordine casuale. [true/<b>false</b>]"
|
||||
|
||||
#: .\cookbook\views\api.py:1490
|
||||
msgid ""
|
||||
@@ -2056,6 +1988,9 @@ msgid ""
|
||||
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
|
||||
"created_at,lastviewed,-lastviewed"
|
||||
msgstr ""
|
||||
"Determina l'ordine dei risultati. Le opzioni sono: "
|
||||
"score,-score,name,-name,lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-created_at,lastviewed,-lastviewed"
|
||||
""
|
||||
|
||||
#: .\cookbook\views\api.py:1492
|
||||
msgid "Returns new results first in search results. [true/<b>false</b>]"
|
||||
@@ -2068,10 +2003,14 @@ msgid ""
|
||||
"Returns the given number of recently viewed recipes before search results "
|
||||
"(if given)"
|
||||
msgstr ""
|
||||
"Restituisce il numero specificato di ricette visualizzate di recente prima "
|
||||
"dei risultati di ricerca (se indicati)"
|
||||
|
||||
#: .\cookbook\views\api.py:1494
|
||||
msgid "ID of a custom filter. Returns all recipes matched by that filter."
|
||||
msgstr ""
|
||||
"ID di un filtro personalizzato. Restituisce tutte le ricette verificate da "
|
||||
"quel filtro."
|
||||
|
||||
#: .\cookbook\views\api.py:1495
|
||||
msgid "Filter recipes that can be made with OnHand food. [true/<b>false</b>]"
|
||||
@@ -2084,48 +2023,47 @@ msgid ""
|
||||
"Return the PropertyTypes matching the property category. Repeat for "
|
||||
"multiple."
|
||||
msgstr ""
|
||||
"Restituisci i PropertyTypes corrispondenti alla categoria di proprietà. "
|
||||
"Ripeti per più di uno."
|
||||
|
||||
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
|
||||
#, fuzzy
|
||||
#| msgid "Filter for entries with the given recipe"
|
||||
msgid "Returns only entries associated with the given mealplan id"
|
||||
msgstr "Filtra le voci con la ricetta specificata"
|
||||
msgstr ""
|
||||
"Restituisce solo le voci associate all'ID del piano alimentare specificato"
|
||||
|
||||
#: .\cookbook\views\api.py:1858
|
||||
msgid ""
|
||||
"Returns only elements updated after the given timestamp in ISO 8601 format."
|
||||
msgstr ""
|
||||
"Restituisce solo gli elementi aggiornati dopo la marca temporale specificata "
|
||||
"nel formato ISO 8601."
|
||||
|
||||
#: .\cookbook\views\api.py:2031
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Return the Automations matching the automation type. Multiple values "
|
||||
#| "allowed."
|
||||
msgid ""
|
||||
"Return the Automations matching the automation type. Repeat for multiple."
|
||||
msgstr ""
|
||||
"Restituisce le automazioni corrispondenti al tipo di automazione. Sono "
|
||||
"consentiti più valori."
|
||||
"Restituisci le automazioni corrispondenti al tipo di automazione. Ripeti per "
|
||||
"più automazioni."
|
||||
|
||||
#: .\cookbook\views\api.py:2048
|
||||
msgid ""
|
||||
"Text field to store data that gets carried over to the UserSpace created "
|
||||
"from the InviteLink"
|
||||
msgstr ""
|
||||
"Campo di testo per memorizzare i dati che vengono trasferiti allo spazio "
|
||||
"utente creato dal collegamento di invito"
|
||||
|
||||
#: .\cookbook\views\api.py:2049
|
||||
msgid "Only return InviteLinks that have not been used yet."
|
||||
msgstr ""
|
||||
"Restituisci solo i collegamenti di invito che non sono ancora stati "
|
||||
"utilizzati."
|
||||
|
||||
#: .\cookbook\views\api.py:2076
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Return the Automations matching the automation type. Multiple values "
|
||||
#| "allowed."
|
||||
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
|
||||
msgstr ""
|
||||
"Restituisce le automazioni corrispondenti al tipo di automazione. Sono "
|
||||
"consentiti più valori."
|
||||
"Restituisci i CustomFilters corrispondenti al tipo di modello. Ripeti per "
|
||||
"più di un modello."
|
||||
|
||||
#: .\cookbook\views\api.py:2176
|
||||
msgid "Nothing to do."
|
||||
@@ -2149,13 +2087,15 @@ msgstr "Nessuna informazione utilizzabile è stata trovata."
|
||||
|
||||
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
|
||||
msgid "You must select an AI provider to perform your request."
|
||||
msgstr ""
|
||||
msgstr "Devi selezionare un fornitore AI per eseguire la tua richiesta."
|
||||
|
||||
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
|
||||
msgid ""
|
||||
"You don't have any credits remaining to use AI or AI features are not "
|
||||
"enabled for your space."
|
||||
msgstr ""
|
||||
"Non hai credito rimanente per utilizzare l'AI o le funzionalità di AI "
|
||||
"abilitate per il tuo spazio."
|
||||
|
||||
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
|
||||
msgid "File is above space limit"
|
||||
|
||||
2453
cookbook/locale/ko/LC_MESSAGES/django.po
Normal file
2453
cookbook/locale/ko/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-09-22 20:15+0200\n"
|
||||
"PO-Revision-Date: 2024-11-19 06:58+0000\n"
|
||||
"PO-Revision-Date: 2025-11-15 12:08+0000\n"
|
||||
"Last-Translator: \"Matjaž T.\" <matjaz@moj-svet.si>\n"
|
||||
"Language-Team: Slovenian <http://translate.tandoor.dev/projects/tandoor/"
|
||||
"recipes-backend/sl/>\n"
|
||||
@@ -16,9 +16,9 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n"
|
||||
"%100==4 ? 2 : 3;\n"
|
||||
"X-Generator: Weblate 5.6.2\n"
|
||||
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || "
|
||||
"n%100==4 ? 2 : 3;\n"
|
||||
"X-Generator: Weblate 5.13.3\n"
|
||||
|
||||
#: .\cookbook\forms.py:50
|
||||
msgid "Default"
|
||||
@@ -123,51 +123,51 @@ msgstr "fermentiramo"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:325
|
||||
msgid "slow cook"
|
||||
msgstr ""
|
||||
msgstr "počasno kuhanje"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:326
|
||||
msgid "egg boiler"
|
||||
msgstr ""
|
||||
msgstr "kuhalnik jajc"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:327
|
||||
msgid "kettle"
|
||||
msgstr ""
|
||||
msgstr "kotel"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:328
|
||||
msgid "blend"
|
||||
msgstr ""
|
||||
msgstr "mešanica"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:329
|
||||
msgid "pre-clean"
|
||||
msgstr ""
|
||||
msgstr "predhodno čiščenje"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:330
|
||||
msgid "high temperature"
|
||||
msgstr ""
|
||||
msgstr "visoka temperatura"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:331
|
||||
msgid "rice cooker"
|
||||
msgstr ""
|
||||
msgstr "kuhalnik riža"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:332
|
||||
msgid "caramelize"
|
||||
msgstr ""
|
||||
msgstr "karamelizirati"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:333
|
||||
msgid "peeler"
|
||||
msgstr ""
|
||||
msgstr "lupilec"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:334
|
||||
msgid "slicer"
|
||||
msgstr ""
|
||||
msgstr "rezalnik"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:335
|
||||
msgid "grater"
|
||||
msgstr ""
|
||||
msgstr "strgalo"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:336
|
||||
msgid "spiralizer"
|
||||
msgstr ""
|
||||
msgstr "spiralizator"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:337
|
||||
msgid "sous-vide"
|
||||
@@ -229,7 +229,7 @@ msgstr "Ogljikovi hidrati"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:212
|
||||
msgid "Cholesterol"
|
||||
msgstr ""
|
||||
msgstr "Holesterol"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:213
|
||||
#: .\cookbook\migrations\0190_auto_20230525_1506.py:17
|
||||
@@ -238,33 +238,31 @@ msgstr "Maščoba"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:214
|
||||
msgid "Fiber"
|
||||
msgstr ""
|
||||
msgstr "Vlaknine"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:215
|
||||
#, fuzzy
|
||||
#| msgid "Proteins"
|
||||
msgid "Protein"
|
||||
msgstr "Beljakovine"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:216
|
||||
msgid "Saturated Fat"
|
||||
msgstr ""
|
||||
msgstr "Nasičene maščobe"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:217
|
||||
msgid "Sodium"
|
||||
msgstr ""
|
||||
msgstr "Natrij"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:218
|
||||
msgid "Sugar"
|
||||
msgstr ""
|
||||
msgstr "Sladkor"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:219
|
||||
msgid "Trans Fat"
|
||||
msgstr ""
|
||||
msgstr "Trans maščobe"
|
||||
|
||||
#: .\cookbook\integration\mealie1.py:220
|
||||
msgid "Unsaturated Fat"
|
||||
msgstr ""
|
||||
msgstr "Nenasičene maščobe"
|
||||
|
||||
#: .\cookbook\integration\openeats.py:28
|
||||
msgid "Recipe source:"
|
||||
@@ -478,7 +476,7 @@ msgstr "Dosegli ste omejitev nalaganja datotek."
|
||||
|
||||
#: .\cookbook\serializer.py:281
|
||||
msgid "The given file type is not allowed."
|
||||
msgstr ""
|
||||
msgstr "Navedena vrsta datoteke ni dovoljena."
|
||||
|
||||
#: .\cookbook\serializer.py:421 .\cookbook\views\views.py:94
|
||||
msgid ""
|
||||
@@ -487,7 +485,7 @@ msgstr "Dosegli ste največje število prostorov, ki so lahko v vaši lasti."
|
||||
|
||||
#: .\cookbook\serializer.py:434
|
||||
msgid "Space Name must be unique."
|
||||
msgstr ""
|
||||
msgstr "Ime prostora mora biti edinstveno."
|
||||
|
||||
#: .\cookbook\serializer.py:469
|
||||
msgid "Cannot modify Space owner permission."
|
||||
@@ -838,10 +836,8 @@ msgid "We are sorry, but the sign up is currently closed."
|
||||
msgstr "Žal nam je, vendar so registracije trenutno zaprte."
|
||||
|
||||
#: .\cookbook\templates\frontend\tandoor.html:15
|
||||
#, fuzzy
|
||||
#| msgid "Tandoor Recipes Invite"
|
||||
msgid "Tandoor Recipe Manager"
|
||||
msgstr "Tandoor Recepti vabilo"
|
||||
msgstr "Urejevalnik Tandoor Recepti"
|
||||
|
||||
#: .\cookbook\templates\index.html:28
|
||||
msgid "Search recipe ..."
|
||||
@@ -1428,8 +1424,6 @@ msgstr ""
|
||||
" %(site_name)s. Kot zadnji korak izpolnite naslednji obrazec:"
|
||||
|
||||
#: .\cookbook\templates\socialaccount\signup.html:32
|
||||
#, fuzzy
|
||||
#| msgid "I accept the follwoing"
|
||||
msgid "I accept the following"
|
||||
msgstr "Sprejemam naslednje"
|
||||
|
||||
@@ -1514,15 +1508,6 @@ msgid "System"
|
||||
msgstr "Sistem"
|
||||
|
||||
#: .\cookbook\templates\system.html:24
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "\n"
|
||||
#| " Django Recipes is an open source free software application. It "
|
||||
#| "can be found on\n"
|
||||
#| " <a href=\"https://github.com/vabene1111/recipes\">GitHub</a>.\n"
|
||||
#| " Changelogs can be found <a href=\"https://github.com/vabene1111/"
|
||||
#| "recipes/releases\">here</a>.\n"
|
||||
#| " "
|
||||
msgid ""
|
||||
"\n"
|
||||
" Tandoor Recipes is an open source free software application. It can "
|
||||
@@ -1533,11 +1518,11 @@ msgid ""
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" Django Recipes je odprtokodna brezplačna programska aplikacija. "
|
||||
"Najdete ga na GitHub\n"
|
||||
" <a href=\"https://github.com/vabene1111/recipes\"></a>.\n"
|
||||
" Dnevnike sprememb lahko najdete tukaj <a href=\"https://github.com/"
|
||||
"vabene1111/recipes/releases\"></a>.\n"
|
||||
" Tandoor Recipes je odprtokodna brezplačna programska oprema. Najdete "
|
||||
"jo na\n"
|
||||
" <a href=\"https://github.com/TandoorRecipes/recipes\">GitHub</a>.\n"
|
||||
" Dnevnike sprememb najdete <a href="
|
||||
"\"https://github.com/TandoorRecipes/recipes/releases\">tukaj</a>.\n"
|
||||
" "
|
||||
|
||||
#: .\cookbook\templates\system.html:30
|
||||
@@ -1559,7 +1544,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\templates\system.html:56
|
||||
msgid "Plugins"
|
||||
msgstr ""
|
||||
msgstr "Vtičniki"
|
||||
|
||||
#: .\cookbook\templates\system.html:67
|
||||
msgid "Media Serving"
|
||||
@@ -1789,16 +1774,12 @@ msgid "{obj.name} was added to the shopping list."
|
||||
msgstr "{obj.name} je bil dodan na nakupovalni seznam."
|
||||
|
||||
#: .\cookbook\views\api.py:1239
|
||||
#, fuzzy
|
||||
#| msgid "Filter meal plans from date (inclusive) in the format of YYYY-MM-DD."
|
||||
msgid "Filter meal plans from date (inclusive)."
|
||||
msgstr "Filtrirajte načrte obrokov od datuma (vključno) v obliki LLLL-MM-DD."
|
||||
msgstr "Filtriraj načrte obrokov od datuma (vključno)."
|
||||
|
||||
#: .\cookbook\views\api.py:1241
|
||||
#, fuzzy
|
||||
#| msgid "Filter meal plans to date (inclusive) in the format of YYYY-MM-DD."
|
||||
msgid "Filter meal plans to date (inclusive)."
|
||||
msgstr "Filtrirajte dosedanje načrte obrokov (vključno) v obliki LLLL-MM-DD."
|
||||
msgstr "Filtriraj načrte obrokov do danes (vključno)."
|
||||
|
||||
#: .\cookbook\views\api.py:1244
|
||||
msgid "Filter meal plans with MealType ID. For multiple repeat parameter."
|
||||
@@ -1905,106 +1886,62 @@ msgstr "ID enote, ki bi jo moral imeti recept."
|
||||
|
||||
#: .\cookbook\views\api.py:1464
|
||||
msgid "Exact rating of recipe"
|
||||
msgstr ""
|
||||
msgstr "Natančna ocena recepta"
|
||||
|
||||
#: .\cookbook\views\api.py:1465
|
||||
#, fuzzy
|
||||
#| msgid "ID of unit a recipe should have."
|
||||
msgid "Rating a recipe should have or greater."
|
||||
msgstr "ID enote, ki bi jo moral imeti recept."
|
||||
msgstr "Ocena, ki bi jo moral imeti recept, ali višja."
|
||||
|
||||
#: .\cookbook\views\api.py:1466
|
||||
#, fuzzy
|
||||
#| msgid "ID of unit a recipe should have."
|
||||
msgid "Rating a recipe should have or smaller."
|
||||
msgstr "ID enote, ki bi jo moral imeti recept."
|
||||
msgstr "Ocena, ki jo mora imeti recept, je 1 ali manjša."
|
||||
|
||||
#: .\cookbook\views\api.py:1468
|
||||
msgid "Filter recipes cooked X times."
|
||||
msgstr ""
|
||||
msgstr "Filtriraj recepte, kuhane X-krat."
|
||||
|
||||
#: .\cookbook\views\api.py:1469
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Filter recipes cooked X times or more. Negative values returns cooked "
|
||||
#| "less than X times"
|
||||
msgid "Filter recipes cooked X times or more."
|
||||
msgstr ""
|
||||
"Filtrirajte recepte, kuhane X-krat ali večkrat. Negativne vrednosti se "
|
||||
"vrnejo kuhane manj kot X-krat"
|
||||
msgstr "Filtriraj recepte, kuhane X-krat ali večkrat."
|
||||
|
||||
#: .\cookbook\views\api.py:1470
|
||||
msgid "Filter recipes cooked X times or less."
|
||||
msgstr ""
|
||||
msgstr "Filtriraj recepte, kuhane X-krat ali manj."
|
||||
|
||||
#: .\cookbook\views\api.py:1472
|
||||
#, fuzzy
|
||||
#| msgid "Filter for entries with the given recipe"
|
||||
msgid "Filter recipes created on the given date."
|
||||
msgstr "Filter za vnose z danim receptom"
|
||||
msgstr "Filtriraj recepte, ustvarjene na določen datum."
|
||||
|
||||
#: .\cookbook\views\api.py:1473
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
|
||||
#| "before date."
|
||||
msgid "Filter recipes created on the given date or after."
|
||||
msgstr ""
|
||||
"Filtrirajte recepte, ustvarjene LLLL-MM-DD ali pozneje. Pred - filtrira na "
|
||||
"ali pred datumom."
|
||||
msgstr "Filtriraj recepte, ustvarjene na določen datum ali pozneje."
|
||||
|
||||
#: .\cookbook\views\api.py:1474
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Filter recipes created on or after YYYY-MM-DD. Prepending - filters on or "
|
||||
#| "before date."
|
||||
msgid "Filter recipes created on the given date or before."
|
||||
msgstr ""
|
||||
"Filtrirajte recepte, ustvarjene LLLL-MM-DD ali pozneje. Pred - filtrira na "
|
||||
"ali pred datumom."
|
||||
msgstr "Filtriraj recepte, ustvarjene na določen datum ali prej."
|
||||
|
||||
#: .\cookbook\views\api.py:1476 .\cookbook\views\api.py:1477
|
||||
#: .\cookbook\views\api.py:1478
|
||||
#, fuzzy
|
||||
#| msgid "Filter for entries with the given recipe"
|
||||
msgid "Filter recipes updated on the given date."
|
||||
msgstr "Filter za vnose z danim receptom"
|
||||
msgstr "Filtriraj recepte, posodobljene na navedeni datum."
|
||||
|
||||
#: .\cookbook\views\api.py:1480
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
|
||||
#| "on or before date."
|
||||
msgid "Filter recipes last cooked on the given date or after."
|
||||
msgstr ""
|
||||
"Filtriraj recepte, nazadnje kuhane LLLL-MM-DD ali pozneje. Pred - filtrira "
|
||||
"na ali pred datumom."
|
||||
"Filtriraj recepte, ki so bili nazadnje kuhani na določen datum ali pozneje."
|
||||
|
||||
#: .\cookbook\views\api.py:1481
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Filter recipes last cooked on or after YYYY-MM-DD. Prepending - filters "
|
||||
#| "on or before date."
|
||||
msgid "Filter recipes last cooked on the given date or before."
|
||||
msgstr ""
|
||||
"Filtriraj recepte, nazadnje kuhane LLLL-MM-DD ali pozneje. Pred - filtrira "
|
||||
"na ali pred datumom."
|
||||
"Filtriraj recepte, ki so bili nazadnje kuhani na določen datum ali prej."
|
||||
|
||||
#: .\cookbook\views\api.py:1483 .\cookbook\views\api.py:1484
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending - filters "
|
||||
#| "on or before date."
|
||||
msgid "Filter recipes lasts viewed on the given date."
|
||||
msgstr ""
|
||||
"Recepti filtrov so zadnjič prikazani LLLL-MM-DD ali pozneje. Pred - filtrira "
|
||||
"na ali pred datumom."
|
||||
msgstr "Filtriraj recepte, ki so bili nazadnje ogledani na določen datum."
|
||||
|
||||
#: .\cookbook\views\api.py:1486
|
||||
#, fuzzy
|
||||
#| msgid "Filter for entries with the given recipe"
|
||||
msgid "Filter recipes for ones created by the given user ID"
|
||||
msgstr "Filter za vnose z danim receptom"
|
||||
msgstr "Filtriraj recepte po tistih, ki jih je ustvaril dani uporabniški ID"
|
||||
|
||||
#: .\cookbook\views\api.py:1487
|
||||
msgid "If only internal recipes should be returned. [true/<b>false</b>]"
|
||||
@@ -2021,6 +1958,9 @@ msgid ""
|
||||
"lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-"
|
||||
"created_at,lastviewed,-lastviewed"
|
||||
msgstr ""
|
||||
"Določa vrstni red rezultatov. Možnosti so: "
|
||||
"score,-score,name,-name,lastcooked,-lastcooked,rating,-rating,times_cooked,-times_cooked,created_at,-created_at,lastviewed,-lastviewed"
|
||||
""
|
||||
|
||||
#: .\cookbook\views\api.py:1492
|
||||
msgid "Returns new results first in search results. [true/<b>false</b>]"
|
||||
@@ -2031,10 +1971,12 @@ msgid ""
|
||||
"Returns the given number of recently viewed recipes before search results "
|
||||
"(if given)"
|
||||
msgstr ""
|
||||
"Vrne podano število nedavno ogledanih receptov pred rezultati iskanja "
|
||||
"(če je podano)"
|
||||
|
||||
#: .\cookbook\views\api.py:1494
|
||||
msgid "ID of a custom filter. Returns all recipes matched by that filter."
|
||||
msgstr ""
|
||||
msgstr "ID filtra po meri. Vrne vse recepte, ki se ujemajo s tem filtrom."
|
||||
|
||||
#: .\cookbook\views\api.py:1495
|
||||
msgid "Filter recipes that can be made with OnHand food. [true/<b>false</b>]"
|
||||
@@ -2047,48 +1989,43 @@ msgid ""
|
||||
"Return the PropertyTypes matching the property category. Repeat for "
|
||||
"multiple."
|
||||
msgstr ""
|
||||
"Vrne vrste lastnosti (PropertyTypes), ki ustrezajo kategoriji lastnosti. "
|
||||
"Ponovite za več vrst lastnosti."
|
||||
|
||||
#: .\cookbook\views\api.py:1804 .\cookbook\views\api.py:1860
|
||||
#, fuzzy
|
||||
#| msgid "Filter for entries with the given recipe"
|
||||
msgid "Returns only entries associated with the given mealplan id"
|
||||
msgstr "Filter za vnose z danim receptom"
|
||||
msgstr "Vrne samo vnose, povezane z danim ID-jem načrta obrokov"
|
||||
|
||||
#: .\cookbook\views\api.py:1858
|
||||
msgid ""
|
||||
"Returns only elements updated after the given timestamp in ISO 8601 format."
|
||||
msgstr ""
|
||||
"Vrne samo elemente, posodobljene po danem časovnem žigu v formatu ISO 8601."
|
||||
|
||||
#: .\cookbook\views\api.py:2031
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Return the Automations matching the automation type. Multiple values "
|
||||
#| "allowed."
|
||||
msgid ""
|
||||
"Return the Automations matching the automation type. Repeat for multiple."
|
||||
msgstr ""
|
||||
"Vrnite avtomatizacije, ki ustrezajo vrsti avtomatizacije. Dovoljenih je "
|
||||
"več vrednosti."
|
||||
"Vrne avtomatizacije, ki ustrezajo vrsti avtomatizacije. Ponovite za več "
|
||||
"avtomatizacij."
|
||||
|
||||
#: .\cookbook\views\api.py:2048
|
||||
msgid ""
|
||||
"Text field to store data that gets carried over to the UserSpace created "
|
||||
"from the InviteLink"
|
||||
msgstr ""
|
||||
"Besedilno polje za shranjevanje podatkov, ki se prenesejo v uporabniški "
|
||||
"prostor, ustvarjen iz InviteLink"
|
||||
|
||||
#: .\cookbook\views\api.py:2049
|
||||
msgid "Only return InviteLinks that have not been used yet."
|
||||
msgstr ""
|
||||
msgstr "Vrni samo povezave InviteLink, ki še niso bile uporabljene."
|
||||
|
||||
#: .\cookbook\views\api.py:2076
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Return the Automations matching the automation type. Multiple values "
|
||||
#| "allowed."
|
||||
msgid "Return the CustomFilters matching the model type. Repeat for multiple."
|
||||
msgstr ""
|
||||
"Vrnite avtomatizacije, ki ustrezajo vrsti avtomatizacije. Dovoljenih je "
|
||||
"več vrednosti."
|
||||
"Vrne filtre CustomFilters, ki ustrezajo vrsti modela. Ponovite za več "
|
||||
"filtrov."
|
||||
|
||||
#: .\cookbook\views\api.py:2176
|
||||
msgid "Nothing to do."
|
||||
@@ -2112,13 +2049,15 @@ msgstr "Uporabnih podatkov ni bilo mogoče najti."
|
||||
|
||||
#: .\cookbook\views\api.py:2286 .\cookbook\views\api.py:2434
|
||||
msgid "You must select an AI provider to perform your request."
|
||||
msgstr ""
|
||||
msgstr "Za izvedbo vaše zahteve morate izbrati ponudnika umetne inteligence."
|
||||
|
||||
#: .\cookbook\views\api.py:2293 .\cookbook\views\api.py:2441
|
||||
msgid ""
|
||||
"You don't have any credits remaining to use AI or AI features are not "
|
||||
"enabled for your space."
|
||||
msgstr ""
|
||||
"Nimate več kreditov za uporabo umetne inteligence ali pa funkcije umetne "
|
||||
"inteligence niso omogočene za vaš prostor."
|
||||
|
||||
#: .\cookbook\views\api.py:2499 .\cookbook\views\api.py:2667
|
||||
msgid "File is above space limit"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from cookbook.models import SearchFields
|
||||
|
||||
from django.contrib.postgres.operations import TrigramExtension, UnaccentExtension
|
||||
|
||||
def allSearchFields():
|
||||
return list(SearchFields.objects.values_list('id', flat=True))
|
||||
@@ -141,6 +141,8 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
TrigramExtension(),
|
||||
UnaccentExtension(),
|
||||
migrations.RunPython(create_default_groups),
|
||||
migrations.CreateModel(
|
||||
name='AiProvider',
|
||||
|
||||
15
cookbook/migrations/0230_auto_20250925_2056.py
Normal file
15
cookbook/migrations/0230_auto_20250925_2056.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-25 18:56
|
||||
|
||||
from django.db import migrations
|
||||
from django.contrib.postgres.operations import TrigramExtension, UnaccentExtension
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0229_alter_ailog_options_alter_aiprovider_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
TrigramExtension(),
|
||||
UnaccentExtension(),
|
||||
]
|
||||
@@ -0,0 +1,141 @@
|
||||
# Generated by Django 5.2.6 on 2025-09-30 18:47
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0230_auto_20250925_2056'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='aiprovider',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='automation',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='bookmarkletimport',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='comment',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='connectorconfig',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='cooklog',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='customfilter',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='exportlog',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='food',
|
||||
options={'ordering': ('name',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='importlog',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='invitelink',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='keyword',
|
||||
options={'ordering': ('name',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='mealplan',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='mealtype',
|
||||
options={'ordering': ('name',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='recipe',
|
||||
options={'ordering': ('name',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='recipebook',
|
||||
options={'ordering': ('name',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='recipeimport',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='sharelink',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='shoppinglistentry',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='shoppinglistrecipe',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='space',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='storage',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='supermarket',
|
||||
options={'ordering': ('name',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='supermarketcategory',
|
||||
options={'ordering': ('name',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='sync',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='synclog',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='telegrambot',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='unit',
|
||||
options={'ordering': ('name',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='unitconversion',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='userfile',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='userspace',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='viewlog',
|
||||
options={'ordering': ('pk',)},
|
||||
),
|
||||
]
|
||||
@@ -402,6 +402,9 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class AiProvider(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
@@ -421,13 +424,14 @@ class AiProvider(models.Model):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
ordering = ('id',)
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class AiLog(models.Model, PermissionModelMixin):
|
||||
F_FILE_IMPORT = 'FILE_IMPORT'
|
||||
F_STEP_SORT = 'STEP_SORT'
|
||||
F_FOOD_PROPERTIES = 'FOOD_PROPERTIES'
|
||||
F_RECIPE_PROPERTIES = 'RECIPE_PROPERTIES'
|
||||
|
||||
ai_provider = models.ForeignKey(AiProvider, on_delete=models.SET_NULL, null=True)
|
||||
function = models.CharField(max_length=64)
|
||||
@@ -476,6 +480,9 @@ class ConnectorConfig(models.Model, PermissionModelMixin):
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
objects = ScopedManager(space='space')
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class UserPreference(models.Model, PermissionModelMixin):
|
||||
# Themes
|
||||
@@ -579,6 +586,9 @@ class UserSpace(models.Model, PermissionModelMixin):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class Storage(models.Model, PermissionModelMixin):
|
||||
DROPBOX = 'DB'
|
||||
@@ -603,6 +613,9 @@ class Storage(models.Model, PermissionModelMixin):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class Sync(models.Model, PermissionModelMixin):
|
||||
storage = models.ForeignKey(Storage, on_delete=models.PROTECT)
|
||||
@@ -618,6 +631,9 @@ class Sync(models.Model, PermissionModelMixin):
|
||||
def __str__(self):
|
||||
return self.path
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class SupermarketCategory(models.Model, PermissionModelMixin, MergeModelMixin):
|
||||
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
|
||||
@@ -643,6 +659,7 @@ class SupermarketCategory(models.Model, PermissionModelMixin, MergeModelMixin):
|
||||
models.UniqueConstraint(fields=['space', 'name'], name='smc_unique_name_per_space'),
|
||||
models.UniqueConstraint(fields=['space', 'open_data_slug'], name='supermarket_category_unique_open_data_slug_per_space')
|
||||
]
|
||||
ordering = ('name',)
|
||||
|
||||
|
||||
class Supermarket(models.Model, PermissionModelMixin):
|
||||
@@ -662,6 +679,7 @@ class Supermarket(models.Model, PermissionModelMixin):
|
||||
models.UniqueConstraint(fields=['space', 'name'], name='sm_unique_name_per_space'),
|
||||
models.UniqueConstraint(fields=['space', 'open_data_slug'], name='supermarket_unique_open_data_slug_per_space')
|
||||
]
|
||||
ordering = ('name',)
|
||||
|
||||
|
||||
class SupermarketCategoryRelation(models.Model, PermissionModelMixin):
|
||||
@@ -693,6 +711,9 @@ class SyncLog(models.Model, PermissionModelMixin):
|
||||
def __str__(self):
|
||||
return f"{self.created_at}:{self.sync} - {self.status}"
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class Keyword(ExportModelOperationsMixin('keyword'), TreeModel, PermissionModelMixin):
|
||||
if SORT_TREE_BY_NAME:
|
||||
@@ -710,6 +731,7 @@ class Keyword(ExportModelOperationsMixin('keyword'), TreeModel, PermissionModelM
|
||||
models.UniqueConstraint(fields=['space', 'name'], name='kw_unique_name_per_space')
|
||||
]
|
||||
indexes = (Index(fields=['id', 'name']),)
|
||||
ordering = ('name',)
|
||||
|
||||
|
||||
class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixin, MergeModelMixin):
|
||||
@@ -741,6 +763,7 @@ class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixi
|
||||
models.UniqueConstraint(fields=['space', 'name'], name='u_unique_name_per_space'),
|
||||
models.UniqueConstraint(fields=['space', 'open_data_slug'], name='unit_unique_open_data_slug_per_space')
|
||||
]
|
||||
ordering = ('name',)
|
||||
|
||||
|
||||
class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
||||
@@ -874,6 +897,7 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
||||
Index(fields=['id']),
|
||||
Index(fields=['name']),
|
||||
)
|
||||
ordering = ('name',)
|
||||
|
||||
|
||||
class UnitConversion(ExportModelOperationsMixin('unit_conversion'), models.Model, PermissionModelMixin):
|
||||
@@ -900,6 +924,7 @@ class UnitConversion(ExportModelOperationsMixin('unit_conversion'), models.Model
|
||||
models.UniqueConstraint(fields=['space', 'base_unit', 'converted_unit', 'food'], name='f_unique_conversion_per_space'),
|
||||
models.UniqueConstraint(fields=['space', 'open_data_slug'], name='unit_conversion_unique_open_data_slug_per_space')
|
||||
]
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, PermissionModelMixin):
|
||||
@@ -1104,13 +1129,14 @@ class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModel
|
||||
sub_food_recipes = Q(id__in=Food.objects.filter(ingredient__step__recipe__in=related_recipes).exclude(recipe=None).values_list('recipe'))
|
||||
return Recipe.objects.filter(Q(id__in=related_recipes.values_list('id')) | sub_step_recipes | sub_food_recipes)
|
||||
|
||||
class Meta():
|
||||
class Meta:
|
||||
indexes = (
|
||||
GinIndex(fields=["name_search_vector"]),
|
||||
GinIndex(fields=["desc_search_vector"]),
|
||||
Index(fields=['id']),
|
||||
Index(fields=['name']),
|
||||
)
|
||||
ordering = ('name',)
|
||||
|
||||
|
||||
class Comment(ExportModelOperationsMixin('comment'), models.Model, PermissionModelMixin):
|
||||
@@ -1131,6 +1157,9 @@ class Comment(ExportModelOperationsMixin('comment'), models.Model, PermissionMod
|
||||
|
||||
def __str__(self):
|
||||
return self.text
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class RecipeImport(models.Model, PermissionModelMixin):
|
||||
@@ -1159,6 +1188,9 @@ class RecipeImport(models.Model, PermissionModelMixin):
|
||||
self.delete()
|
||||
return recipe
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class RecipeBook(ExportModelOperationsMixin('book'), models.Model, PermissionModelMixin):
|
||||
name = models.CharField(max_length=128)
|
||||
@@ -1176,6 +1208,7 @@ class RecipeBook(ExportModelOperationsMixin('book'), models.Model, PermissionMod
|
||||
|
||||
class Meta():
|
||||
indexes = (Index(fields=['name']),)
|
||||
ordering = ('name',)
|
||||
|
||||
|
||||
class RecipeBookEntry(ExportModelOperationsMixin('book_entry'), models.Model, PermissionModelMixin):
|
||||
@@ -1221,6 +1254,7 @@ class MealType(models.Model, PermissionModelMixin):
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['space', 'name', 'created_by'], name='mt_unique_name_per_space'),
|
||||
]
|
||||
ordering = ('name',)
|
||||
|
||||
|
||||
class MealPlan(ExportModelOperationsMixin('meal_plan'), models.Model, PermissionModelMixin):
|
||||
@@ -1248,6 +1282,9 @@ class MealPlan(ExportModelOperationsMixin('meal_plan'), models.Model, Permission
|
||||
def __str__(self):
|
||||
return f'{self.get_label()} - {self.from_date} - {self.meal_type.name}'
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), models.Model, PermissionModelMixin):
|
||||
name = models.CharField(max_length=32, blank=True, default='')
|
||||
@@ -1263,6 +1300,9 @@ class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), mod
|
||||
def __str__(self):
|
||||
return f'Shopping list recipe {self.id} - {self.recipe}'
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin):
|
||||
list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries')
|
||||
@@ -1294,6 +1334,9 @@ class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), model
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class ShareLink(ExportModelOperationsMixin('share_link'), models.Model, PermissionModelMixin):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
@@ -1309,6 +1352,9 @@ class ShareLink(ExportModelOperationsMixin('share_link'), models.Model, Permissi
|
||||
def __str__(self):
|
||||
return f'{self.recipe} - {self.uuid}'
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
def default_valid_until():
|
||||
return date.today() + timedelta(days=14)
|
||||
@@ -1332,6 +1378,9 @@ class InviteLink(ExportModelOperationsMixin('invite_link'), models.Model, Permis
|
||||
def __str__(self):
|
||||
return f'{self.uuid}'
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class TelegramBot(models.Model, PermissionModelMixin):
|
||||
token = models.CharField(max_length=256)
|
||||
@@ -1346,6 +1395,9 @@ class TelegramBot(models.Model, PermissionModelMixin):
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class CookLog(ExportModelOperationsMixin('cook_log'), models.Model, PermissionModelMixin):
|
||||
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||
@@ -1363,7 +1415,7 @@ class CookLog(ExportModelOperationsMixin('cook_log'), models.Model, PermissionMo
|
||||
def __str__(self):
|
||||
return self.recipe.name
|
||||
|
||||
class Meta():
|
||||
class Meta:
|
||||
indexes = (
|
||||
Index(fields=['id']),
|
||||
Index(fields=['recipe']),
|
||||
@@ -1372,6 +1424,7 @@ class CookLog(ExportModelOperationsMixin('cook_log'), models.Model, PermissionMo
|
||||
Index(fields=['created_by']),
|
||||
Index(fields=['created_by', 'rating']),
|
||||
)
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class ViewLog(ExportModelOperationsMixin('view_log'), models.Model, PermissionModelMixin):
|
||||
@@ -1385,13 +1438,14 @@ class ViewLog(ExportModelOperationsMixin('view_log'), models.Model, PermissionMo
|
||||
def __str__(self):
|
||||
return self.recipe.name
|
||||
|
||||
class Meta():
|
||||
class Meta:
|
||||
indexes = (
|
||||
Index(fields=['recipe']),
|
||||
Index(fields=['-created_at']),
|
||||
Index(fields=['created_by']),
|
||||
Index(fields=['recipe', '-created_at', 'created_by']),
|
||||
)
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class ImportLog(models.Model, PermissionModelMixin):
|
||||
@@ -1412,6 +1466,9 @@ class ImportLog(models.Model, PermissionModelMixin):
|
||||
def __str__(self):
|
||||
return f"{self.created_at}:{self.type}"
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class ExportLog(models.Model, PermissionModelMixin):
|
||||
type = models.CharField(max_length=32)
|
||||
@@ -1432,6 +1489,9 @@ class ExportLog(models.Model, PermissionModelMixin):
|
||||
def __str__(self):
|
||||
return f"{self.created_at}:{self.type}"
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class BookmarkletImport(ExportModelOperationsMixin('bookmarklet_import'), models.Model, PermissionModelMixin):
|
||||
html = models.TextField()
|
||||
@@ -1442,6 +1502,9 @@ class BookmarkletImport(ExportModelOperationsMixin('bookmarklet_import'), models
|
||||
objects = ScopedManager(space='space')
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
# field names used to configure search behavior - all data populated during data migration
|
||||
# other option is to use a MultiSelectField from https://github.com/goinnn/django-multiselectfield
|
||||
@@ -1509,6 +1572,9 @@ class UserFile(ExportModelOperationsMixin('user_files'), models.Model, Permissio
|
||||
def __str__(self):
|
||||
return f'{self.name} (#{self.id})'
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class Automation(ExportModelOperationsMixin('automations'), models.Model, PermissionModelMixin):
|
||||
FOOD_ALIAS = 'FOOD_ALIAS'
|
||||
@@ -1555,6 +1621,9 @@ class Automation(ExportModelOperationsMixin('automations'), models.Model, Permis
|
||||
objects = ScopedManager(space='space')
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
|
||||
|
||||
class CustomFilter(models.Model, PermissionModelMixin):
|
||||
RECIPE = 'RECIPE'
|
||||
@@ -1585,3 +1654,4 @@ class CustomFilter(models.Model, PermissionModelMixin):
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['space', 'name'], name='cf_unique_name_per_space')
|
||||
]
|
||||
ordering = ('pk',)
|
||||
|
||||
@@ -99,19 +99,19 @@ def test_list_filter(obj_1, u1_s1):
|
||||
|
||||
response = json.loads(
|
||||
u1_s1.get(
|
||||
f'{reverse(LIST_URL)}?from_date={(timezone.now() + timedelta(days=2)).strftime("%Y-%m-%d")}'
|
||||
f'{reverse(LIST_URL)}?from_date={(timezone.localtime(timezone.now()) + timedelta(days=1)).strftime("%Y-%m-%d")}'
|
||||
).content)['results']
|
||||
assert len(response) == 0
|
||||
|
||||
response = json.loads(
|
||||
u1_s1.get(
|
||||
f'{reverse(LIST_URL)}?to_date={(timezone.now() - timedelta(days=2)).strftime("%Y-%m-%d")}'
|
||||
f'{reverse(LIST_URL)}?to_date={(timezone.localtime(timezone.now()) - timedelta(days=1)).strftime("%Y-%m-%d")}'
|
||||
).content)['results']
|
||||
assert len(response) == 0
|
||||
|
||||
response = json.loads(
|
||||
u1_s1.get(
|
||||
f'{reverse(LIST_URL)}?from_date={(timezone.now() - timedelta(days=2)).strftime("%Y-%m-%d")}&to_date={(timezone.now() + timedelta(days=2)).strftime("%Y-%m-%d")}'
|
||||
f'{reverse(LIST_URL)}?from_date={(timezone.localtime(timezone.now()) - timedelta(days=1)).strftime("%Y-%m-%d")}&to_date={(timezone.localtime(timezone.now()) + timedelta(days=1)).strftime("%Y-%m-%d")}'
|
||||
).content)['results']
|
||||
assert len(response) == 1
|
||||
|
||||
@@ -153,8 +153,8 @@ def test_add(arg, request, u1_s2, recipe_1_s1, meal_type):
|
||||
'id': meal_type.id,
|
||||
'name': meal_type.name
|
||||
},
|
||||
'from_date': (timezone.now()).strftime("%Y-%m-%d"),
|
||||
'to_date': (timezone.now()).strftime("%Y-%m-%d"),
|
||||
'from_date': (timezone.localtime(timezone.now())).strftime("%Y-%m-%d"),
|
||||
'to_date': (timezone.localtime(timezone.now())).strftime("%Y-%m-%d"),
|
||||
'servings': 1,
|
||||
'title': 'test',
|
||||
'shared': []
|
||||
@@ -196,8 +196,8 @@ def test_add_with_shopping(u1_s1, meal_type):
|
||||
'id': meal_type.id,
|
||||
'name': meal_type.name
|
||||
},
|
||||
'from_date': (timezone.now()).strftime("%Y-%m-%d"),
|
||||
'to_date': (timezone.now()).strftime("%Y-%m-%d"),
|
||||
'from_date': (timezone.localtime(timezone.now())).strftime("%Y-%m-%d"),
|
||||
'to_date': (timezone.localtime(timezone.now())).strftime("%Y-%m-%d"),
|
||||
'servings': 1,
|
||||
'title': 'test',
|
||||
'shared': [],
|
||||
@@ -212,13 +212,13 @@ def test_add_with_shopping(u1_s1, meal_type):
|
||||
|
||||
@pytest.mark.parametrize("arg", [
|
||||
['', 2],
|
||||
[f'?from_date={timezone.now().strftime("%Y-%m-%d")}', 1],
|
||||
[f'?from_date={timezone.localtime(timezone.now()).strftime("%Y-%m-%d")}', 1],
|
||||
[
|
||||
f'?to_date={(timezone.now() - timedelta(days=1)).strftime("%Y-%m-%d")}',
|
||||
f'?to_date={(timezone.localtime(timezone.now()) - timedelta(days=1)).strftime("%Y-%m-%d")}',
|
||||
1
|
||||
],
|
||||
[
|
||||
f'?from_date={(timezone.now() + timedelta(days=2)).strftime("%Y-%m-%d")}&to_date={(timezone.now() + timedelta(days=2)).strftime("%Y-%m-%d")}',
|
||||
f'?from_date={(timezone.localtime(timezone.now()) + timedelta(days=1)).strftime("%Y-%m-%d")}&to_date={(timezone.localtime(timezone.now()) + timedelta(days=1)).strftime("%Y-%m-%d")}',
|
||||
0
|
||||
],
|
||||
])
|
||||
|
||||
@@ -185,3 +185,32 @@ def test_unit_conversions(space_1, space_2, u1_s1):
|
||||
assert next(x for x in conversions if x.unit == unit_kg_space_2) is not None
|
||||
assert abs(next(x for x in conversions if x.unit == unit_kg_space_2).amount - Decimal(0.1)) < 0.0001
|
||||
print(conversions)
|
||||
|
||||
def test_conversion_with_zero(space_1, space_2, u1_s1):
|
||||
with scopes_disabled():
|
||||
uch = UnitConversionHelper(space_1)
|
||||
|
||||
unit_gram = Unit.objects.create(name='gram', base_unit='g', space=space_1)
|
||||
unit_fantasy = Unit.objects.create(name='Fantasy Unit', base_unit=None, space=space_1)
|
||||
|
||||
food_1 = Food.objects.create(name='Test Food 1', space=space_1)
|
||||
|
||||
ingredient_food_1_gram = Ingredient.objects.create(
|
||||
food=food_1,
|
||||
unit=unit_gram,
|
||||
amount=100,
|
||||
space=space_1,
|
||||
)
|
||||
|
||||
print('\n----------- TEST BASE CUSTOM CONVERSION - TO CUSTOM CONVERSION ---------------')
|
||||
UnitConversion.objects.create(
|
||||
base_amount=0,
|
||||
base_unit=unit_gram,
|
||||
converted_amount=0,
|
||||
converted_unit=unit_fantasy,
|
||||
space=space_1,
|
||||
created_by=auth.get_user(u1_s1),
|
||||
)
|
||||
conversions = uch.get_conversions(ingredient_food_1_gram)
|
||||
|
||||
assert len(conversions) == 1 # conversion always includes the ingredient, if count is 1 no other conversion was found
|
||||
|
||||
@@ -372,11 +372,16 @@ class MergeMixin(ViewSetMixin):
|
||||
isTree = False
|
||||
|
||||
try:
|
||||
# TODO these checks could be improved to merge existing properties and conversion in a smart way. For now it will just loose them to prevent duplicates
|
||||
if isinstance(source, Food):
|
||||
source.properties.all().delete()
|
||||
source.properties.clear()
|
||||
UnitConversion.objects.filter(food=source).delete()
|
||||
|
||||
if isinstance(source, Unit):
|
||||
UnitConversion.objects.filter(base_unit=source).delete()
|
||||
UnitConversion.objects.filter(converted_unit=source).delete()
|
||||
|
||||
for link in [field for field in source._meta.get_fields() if issubclass(type(field), ForeignObjectRel)]:
|
||||
linkManager = getattr(source, link.get_accessor_name())
|
||||
related = linkManager.all()
|
||||
@@ -1125,7 +1130,7 @@ class FoodViewSet(LoggingMixin, TreeMixin, DeleteRelationMixing):
|
||||
"type": "text",
|
||||
"text": "Given the following food and the following different types of properties please update the food so that the properties attribute contains a list with all property types in the following format [{property_amount: <the property value>, property_type: {id: <the ID of the property type>, name: <the name of the property type>}}]."
|
||||
"The property values should be in the unit given in the property type and for the amount specified in the properties_food_amount attribute of the food, which is given in the properties_food_unit."
|
||||
"property_amount is a decimal number. Please try to keep a percision of two decimal places if given in your source data."
|
||||
"property_amount is a decimal number. Please try to keep a precision of two decimal places if given in your source data."
|
||||
"Do not make up any data. If there is no data available for the given property type that is ok, just return null as a property_amount for that property type. Do not change anything else!"
|
||||
"Most property types are likely going to be nutritional values. Please do not make up any values, only return values you can find in the sources available to you."
|
||||
"Only return values if you are sure they are meant for the food given. Under no circumstance are you allowed to change any other value of the given food or change the structure in any way or form."
|
||||
@@ -1805,6 +1810,82 @@ class RecipeViewSet(LoggingMixin, viewsets.ModelViewSet, DeleteRelationMixing):
|
||||
|
||||
return Response(serializer.errors, 400)
|
||||
|
||||
@extend_schema(
|
||||
parameters=[
|
||||
OpenApiParameter(name='provider', description='ID of the AI provider that should be used for this AI request', type=int),
|
||||
]
|
||||
)
|
||||
@decorators.action(detail=True, methods=['POST'], )
|
||||
def aiproperties(self, request, pk):
|
||||
serializer = RecipeSerializer(data=request.data, partial=True, context={'request': request})
|
||||
if serializer.is_valid():
|
||||
|
||||
if not request.query_params.get('provider', None) or not re.match(r'^(\d)+$', request.query_params.get('provider', None)):
|
||||
response = {
|
||||
'error': True,
|
||||
'msg': _('You must select an AI provider to perform your request.'),
|
||||
}
|
||||
return Response(response, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if not can_perform_ai_request(request.space):
|
||||
response = {
|
||||
'error': True,
|
||||
'msg': _("You don't have any credits remaining to use AI or AI features are not enabled for your space."),
|
||||
}
|
||||
return Response(response, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
ai_provider = AiProvider.objects.filter(pk=request.query_params.get('provider')).filter(Q(space=request.space) | Q(space__isnull=True)).first()
|
||||
|
||||
litellm.callbacks = [AiCallbackHandler(request.space, request.user, ai_provider, AiLog.F_RECIPE_PROPERTIES)]
|
||||
|
||||
property_type_list = list(PropertyType.objects.filter(space=request.space).values('id', 'name', 'description', 'unit', 'category', 'fdc_id'))
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Given the following recipe and the following different types of properties please update the recipe so that the properties attribute contains a list with all property types in the following format [{property_amount: <the property value>, property_type: {id: <the ID of the property type>, name: <the name of the property type>}}]."
|
||||
"The property values should be in the unit given in the property type and calculated based on the total quantity of the foods used for the recipe."
|
||||
"property_amount is a decimal number. Please try to keep a precision of two decimal places if given in your source data."
|
||||
"Do not make up any data. If there is no data available for the given property type that is ok, just return null as a property_amount for that property type. Do not change anything else!"
|
||||
"Most property types are likely going to be nutritional values. Please do not make up any values, only return values you can find in the sources available to you."
|
||||
"Under no circumstance are you allowed to change any other value of the given food or change the structure in any way or form."
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": json.dumps(request.data)
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": json.dumps(property_type_list)
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
try:
|
||||
ai_request = {
|
||||
'api_key': ai_provider.api_key,
|
||||
'model': ai_provider.model_name,
|
||||
'response_format': {"type": "json_object"},
|
||||
'messages': messages,
|
||||
}
|
||||
if ai_provider.url:
|
||||
ai_request['api_base'] = ai_provider.url
|
||||
ai_response = completion(**ai_request)
|
||||
|
||||
response_text = ai_response.choices[0].message.content
|
||||
|
||||
return Response(json.loads(response_text), status=status.HTTP_200_OK)
|
||||
except BadRequestError as err:
|
||||
pass
|
||||
response = {
|
||||
'error': True,
|
||||
'msg': 'The AI could not process your request. \n\n' + err.message,
|
||||
}
|
||||
return Response(response, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@extend_schema(responses=RecipeSerializer(many=False))
|
||||
@decorators.action(detail=True, pagination_class=None, methods=['PATCH'], serializer_class=RecipeSerializer)
|
||||
def delete_external(self, request, pk):
|
||||
@@ -2481,6 +2562,13 @@ class AiImportView(APIView):
|
||||
'msg': "Error parsing AI results. Response Text:\n\n" + response_text
|
||||
}
|
||||
return Response(RecipeFromSourceResponseSerializer(context={'request': request}).to_representation(response), status=status.HTTP_400_BAD_REQUEST)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
response = {
|
||||
'error': True,
|
||||
'msg': "Error processing AI results. Response Text:\n\n" + response_text + "\n\n" + traceback.format_exc()
|
||||
}
|
||||
return Response(RecipeFromSourceResponseSerializer(context={'request': request}).to_representation(response), status=status.HTTP_400_BAD_REQUEST)
|
||||
else:
|
||||
response = {
|
||||
'error': True,
|
||||
|
||||
@@ -33,4 +33,4 @@ Convert pictures of recipes to a structure that can be imported to Tandoor with
|
||||
|
||||
Maintained by [smilerz](https://github.com/smilerz/tandoor-menu-generator)
|
||||
|
||||
Generate a mealplan tbased on complex criteria and optionally insert it into an SVG menu template.
|
||||
Generate a meal plan based on complex criteria and optionally insert it into an SVG menu template.
|
||||
|
||||
@@ -36,7 +36,7 @@ then make sure you have set [all required headers](install/docker.md#required-he
|
||||
If that doesn't fix it, you can also refer to the appropriate sub section in the [reverse proxy documentation](install/docker.md#reverse-proxy) and verify your general webserver configuration.
|
||||
|
||||
### Required Headers
|
||||
Navigate to `/system` and review the headers listed in the DEBUG section. At a minimum, if you are using a reverse proxy the headers must match the below conditions.
|
||||
Navigate to `/system/` and review the headers listed in the DEBUG section. At a minimum, if you are using a reverse proxy the headers must match the below conditions.
|
||||
|
||||
| Header | Requirement |
|
||||
| :--- | :---- |
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
<a href="https://discord.gg/RhzBrfWgtp" target="_blank" rel="noopener noreferrer"><img src="https://badgen.net/badge/icon/discord?icon=discord&label" ></a>
|
||||
<a href="https://hub.docker.com/r/vabene1111/recipes" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/docker/pulls/vabene1111/recipes" ></a>
|
||||
<a href="https://github.com/vabene1111/recipes/releases/latest" rel="noopener noreferrer"><img src="https://img.shields.io/github/v/release/vabene1111/recipes" ></a>
|
||||
<a href="https://app.tandoor.dev/accounts/login/?demo" rel="noopener noreferrer"><img src="https://img.shields.io/badge/demo-available-success" ></a>
|
||||
<a href="https://app.tandoor.dev/e/demo-auto-login/" rel="noopener noreferrer"><img src="https://img.shields.io/badge/demo-available-success" ></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://tandoor.dev" target="_blank" rel="noopener noreferrer">Website</a> •
|
||||
<a href="https://docs.tandoor.dev/install/docker/" target="_blank" rel="noopener noreferrer">Installation</a> •
|
||||
<a href="https://docs.tandoor.dev/" target="_blank" rel="noopener noreferrer">Docs</a> •
|
||||
<a href="https://app.tandoor.dev/accounts/login/?demo" target="_blank" rel="noopener noreferrer">Demo</a> •
|
||||
<a href="https://app.tandoor.dev/e/demo-auto-login/" target="_blank" rel="noopener noreferrer">Demo</a> •
|
||||
<a href="https://community.tandoor.dev" target="_blank" rel="noopener noreferrer">Community</a> •
|
||||
<a href="https://discord.gg/RhzBrfWgtp" target="_blank" rel="noopener noreferrer">Discord</a>
|
||||
</p>
|
||||
|
||||
@@ -69,8 +69,6 @@ wget https://raw.githubusercontent.com/vabene1111/recipes/develop/docs/install/d
|
||||
|
||||
Most deployments will likely use a reverse proxy.
|
||||
|
||||
If your reverse proxy is not listed below, please refer to chapter [Others](#others).
|
||||
|
||||
#### **Traefik**
|
||||
|
||||
If you use Traefik, this configuration is the one for you.
|
||||
@@ -115,6 +113,17 @@ wget https://raw.githubusercontent.com/vabene1111/recipes/develop/docs/install/d
|
||||
{% include "./docker/nginx-proxy/docker-compose.yml" %}
|
||||
~~~
|
||||
|
||||
|
||||
#### **Apache proxy**
|
||||
|
||||
If you use Apache as a reverse proxy, this configuration is the one for you.
|
||||
|
||||
~~~yaml
|
||||
{% include "./docker/apache-proxy/docker-compose.yml" %}
|
||||
~~~
|
||||
|
||||
Keep in mind, that the port configured for the service `web_recipes` should be the same as in chapter [Required Headers: Apache](#apache).
|
||||
|
||||
## **DockSTARTer**
|
||||
|
||||
The main goal of [DockSTARTer](https://dockstarter.com/) is to make it quick and easy to get up and running with Docker.
|
||||
@@ -139,7 +148,8 @@ if you manually change it/bind the folder as a volume.
|
||||
|
||||
Please be sure to supply all required headers in your nginx/Apache/Caddy/... configuration!
|
||||
|
||||
nginx:
|
||||
#### **nginx**
|
||||
|
||||
```nginx
|
||||
location / {
|
||||
proxy_set_header Host $http_host; # try $host instead if this doesn't work
|
||||
@@ -149,7 +159,8 @@ location / {
|
||||
}
|
||||
```
|
||||
|
||||
Apache:
|
||||
#### **Apache**
|
||||
|
||||
```apache
|
||||
RequestHeader set X-Forwarded-Proto "https"
|
||||
Header always set Access-Control-Allow-Origin "*"
|
||||
|
||||
24
docs/install/docker/apache-proxy/docker-compose.yml
Normal file
24
docs/install/docker/apache-proxy/docker-compose.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
services:
|
||||
db_recipes:
|
||||
restart: always
|
||||
image: postgres:16-alpine
|
||||
volumes:
|
||||
- ./postgresql:/var/lib/postgresql/data
|
||||
env_file:
|
||||
- ./.env
|
||||
|
||||
web_recipes:
|
||||
restart: always
|
||||
image: vabene1111/recipes
|
||||
ports:
|
||||
- 127.0.0.1:8080:80 # replace port
|
||||
env_file:
|
||||
- ./.env
|
||||
volumes:
|
||||
- staticfiles:/opt/recipes/staticfiles
|
||||
- ./mediafiles:/opt/recipes/mediafiles
|
||||
depends_on:
|
||||
- db_recipes
|
||||
|
||||
volumes:
|
||||
staticfiles:
|
||||
@@ -3,7 +3,7 @@
|
||||
These instructions are inspired from a standard django/gunicorn/postgresql instructions ([for example](https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu-16-04))
|
||||
|
||||
!!! warning
|
||||
Make sure to use at least Python 3.10 (although 3.12 is preferred) or higher, and ensure that `pip` is associated with Python 3. Depending on your system configuration, using `python` or `pip` might default to Python 2. Make sure your machine has at least 2048 MB of memory; otherwise, the `yarn build` process may fail with the error: `FATAL ERROR: Reached heap limit - Allocation failed: JavaScript heap out of memory`.
|
||||
Make sure to use at least Python 3.12 or higher, and ensure that `pip` is associated with Python 3. Depending on your system configuration, using `python` or `pip` might default to Python 2. Make sure your machine has at least 2048 MB of memory; otherwise, the `yarn build` process may fail with the error: `FATAL ERROR: Reached heap limit - Allocation failed: JavaScript heap out of memory`.
|
||||
|
||||
!!! warning
|
||||
These instructions are **not** regularly reviewed and might be outdated.
|
||||
@@ -77,10 +77,10 @@ Using binaries from the virtual env:
|
||||
/var/www/recipes/bin/pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
You will also need to install front end requirements and build them. For this navigate to the `./vue` folder and run
|
||||
You will also need to install front end requirements and build them. For this navigate to the `./vue3` folder and run
|
||||
|
||||
```shell
|
||||
cd ./vue
|
||||
cd ./vue3
|
||||
yarn install
|
||||
yarn build
|
||||
```
|
||||
@@ -224,7 +224,7 @@ bin/python3 manage.py migrate
|
||||
bin/python3 manage.py collectstatic --no-input
|
||||
bin/python3 manage.py collectstatic_js_reverse
|
||||
# change to frontend directory
|
||||
cd vue
|
||||
cd vue3
|
||||
# install and build frontend
|
||||
yarn install
|
||||
yarn build
|
||||
|
||||
@@ -96,12 +96,15 @@ Configuration options for serving related services.
|
||||
|
||||
#### Port
|
||||
|
||||
> default `8080` - options: `1-65535`
|
||||
> default `80` - options: `1-65535`
|
||||
|
||||
Port for gunicorn to bind to. Should not be changed if using docker stack with reverse proxy.
|
||||
!!! warning
|
||||
Changed in version 2.3 to no longer configure the port of gunicorn but the port of the internal nginx
|
||||
|
||||
Port where Tandoor exposes its internal web server.
|
||||
|
||||
```
|
||||
TANDOOR_PORT=8080
|
||||
TANDOOR_PORT=80
|
||||
```
|
||||
|
||||
|
||||
@@ -186,6 +189,19 @@ See [Gunicorn docs](https://docs.gunicorn.org/en/stable/design.html#how-many-wor
|
||||
GUNICORN_THREADS=2
|
||||
```
|
||||
|
||||
|
||||
#### Gunicorn Timeout
|
||||
|
||||
> default `30` - options `1-X`
|
||||
|
||||
Set the timeout in seconds of gunicorn when starting using `boot.sh` (all container installations).
|
||||
The default is likely appropriate for most installations. However, if you are using a LLM which high response times gunicornmight time out during the wait until the LLM finished, in such cases you might want to increase the timeout.
|
||||
See [Gunicorn docs]([https://docs.gunicorn.org/en/stable/design.html#how-many-workers](https://docs.gunicorn.org/en/stable/settings.html#timeout)) for default settings.
|
||||
|
||||
```
|
||||
GUNICORN_TIMEOUT=30
|
||||
```
|
||||
|
||||
#### Gunicorn Media
|
||||
|
||||
> default `0` - options `0`, `1`
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80 ipv6only=on;
|
||||
listen ${TANDOOR_PORT};
|
||||
listen [::]:${TANDOOR_PORT} ipv6only=on;
|
||||
server_name localhost;
|
||||
|
||||
client_max_body_size 128M;
|
||||
client_max_body_size 512M;
|
||||
|
||||
# serve media files
|
||||
location /media {
|
||||
@@ -19,7 +19,10 @@ server {
|
||||
# pass requests for dynamic content to gunicorn
|
||||
location / {
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_pass http://localhost:${TANDOOR_PORT};
|
||||
proxy_pass http://unix:/run/tandoor.sock;
|
||||
|
||||
# param needed by django allauth sessions to log IP
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# disabled for now because it redirects to the error page and not back, also not showing html
|
||||
#error_page 502 /errors/http502.html;
|
||||
|
||||
@@ -34,7 +34,7 @@ urlpatterns = [
|
||||
),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
if settings.DEBUG and settings.DEBUG_TOOLBAR:
|
||||
urlpatterns += path('__debug__/', include('debug_toolbar.urls')),
|
||||
|
||||
if settings.ENABLE_METRICS:
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
Django==5.2.6
|
||||
Django==5.2.8
|
||||
cryptography===45.0.5
|
||||
django-annoying==0.10.6
|
||||
django-cleanup==9.0.0
|
||||
django-crispy-forms==2.4
|
||||
crispy-bootstrap4==2025.6
|
||||
djangorestframework==3.16.1
|
||||
drf-spectacular==0.27.1
|
||||
drf-spectacular==0.28.0
|
||||
drf-spectacular-sidecar==2025.8.1
|
||||
drf-writable-nested==0.7.2
|
||||
django-oauth-toolkit==2.4.0
|
||||
django-debug-toolbar==4.3.0
|
||||
django-debug-toolbar==6.0.0
|
||||
bleach==6.2.0
|
||||
gunicorn==23.0.0
|
||||
lxml==5.3.1
|
||||
lxml==6.0.2
|
||||
Markdown==3.7
|
||||
Pillow==11.3.0
|
||||
psycopg2-binary==2.9.10
|
||||
@@ -22,14 +22,14 @@ six==1.17.0
|
||||
webdavclient3==3.14.6
|
||||
whitenoise==6.8.2
|
||||
icalendar==6.3.1
|
||||
pyyaml==6.0.2
|
||||
pyyaml==6.0.3
|
||||
uritemplate==4.1.1
|
||||
beautifulsoup4==4.12.3
|
||||
microdata==0.8.0
|
||||
mock==5.2.0
|
||||
Jinja2==3.1.6
|
||||
django-allauth[mfa,socialaccount]==65.9.0
|
||||
recipe-scrapers==15.8.0
|
||||
recipe-scrapers==15.10.0
|
||||
django-scopes==2.0.0
|
||||
django-treebeard==4.7.1
|
||||
django-cors-headers==4.6.0
|
||||
@@ -37,7 +37,7 @@ django-storages==1.14.6
|
||||
boto3==1.28.75
|
||||
django-prometheus==2.4.1
|
||||
django-hCaptcha==0.2.0
|
||||
python-ldap==3.4.4
|
||||
python-ldap==3.4.5
|
||||
django-auth-ldap==4.6.0
|
||||
pyppeteer==2.0.0
|
||||
pytubefix==9.2.2
|
||||
@@ -53,7 +53,7 @@ django-vite==3.1.0
|
||||
litellm==1.64.1
|
||||
|
||||
# Development
|
||||
pytest==8.4.1
|
||||
pytest==8.4.2
|
||||
pytest-django==4.11.0
|
||||
pytest-cov===6.2.1
|
||||
pytest-factoryboy==2.8.1
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"@vueform/multiselect": "^2.6.11",
|
||||
"@vueuse/core": "^13.6.0",
|
||||
"@vueuse/router": "^13.6.0",
|
||||
"@vueuse/router": "^13.9.0",
|
||||
"luxon": "^3.7.1",
|
||||
"mavon-editor": "^3.0.1",
|
||||
"pinia": "^3.0.2",
|
||||
@@ -23,7 +23,7 @@
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-simple-calendar": "7.1.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuetify": "^3.9.7"
|
||||
"vuetify": "^3.10.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
@@ -31,11 +31,11 @@
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "^24.0.8",
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"esbuild-register": "^3.6.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "7.1.5",
|
||||
"vite": "7.1.11",
|
||||
"vite-plugin-pwa": "^1.0.3",
|
||||
"vite-plugin-vuetify": "^2.1.1",
|
||||
"vue-tsc": "^3.0.6",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-app-bar color="tandoor" flat density="comfortable" v-if="!useUserPreferenceStore().isAuthenticated">
|
||||
<v-app-bar color="tandoor" flat density="comfortable" v-if="!useUserPreferenceStore().isAuthenticated && !useUserPreferenceStore().isPrintMode">
|
||||
|
||||
</v-app-bar>
|
||||
<v-app-bar :color="useUserPreferenceStore().activeSpace.navBgColor ? useUserPreferenceStore().activeSpace.navBgColor : useUserPreferenceStore().userSettings.navBgColor"
|
||||
flat density="comfortable" v-if="useUserPreferenceStore().isAuthenticated" :scroll-behavior="useUserPreferenceStore().userSettings.navSticky ? '' : 'hide'">
|
||||
flat density="comfortable" v-if="useUserPreferenceStore().isAuthenticated && !useUserPreferenceStore().isPrintMode" :scroll-behavior="useUserPreferenceStore().userSettings.navSticky ? '' : 'hide'">
|
||||
<router-link :to="{ name: 'StartPage', params: {} }">
|
||||
<v-img src="../../assets/brand_logo.svg" width="140px" class="ms-2"
|
||||
v-if="useUserPreferenceStore().userSettings.navShowLogo && !useUserPreferenceStore().activeSpace.navLogo"></v-img>
|
||||
@@ -58,7 +58,7 @@
|
||||
</p>
|
||||
</v-app-bar>
|
||||
|
||||
<v-app-bar color="info" density="compact" v-if="useUserPreferenceStore().isAuthenticated && useUserPreferenceStore().activeSpace.message != ''">
|
||||
<v-app-bar color="info" density="compact" v-if="useUserPreferenceStore().isAuthenticated && useUserPreferenceStore().activeSpace.message != '' && !useUserPreferenceStore().isPrintMode">
|
||||
<p class="text-center w-100">
|
||||
{{ useUserPreferenceStore().activeSpace.message }}
|
||||
</p>
|
||||
@@ -69,7 +69,7 @@
|
||||
</v-main>
|
||||
|
||||
<!-- completely hide in print mode because setting d-print-node keeps layout -->
|
||||
<v-navigation-drawer v-if="lgAndUp && useUserPreferenceStore().isAuthenticated && !isPrintMode">
|
||||
<v-navigation-drawer v-if="lgAndUp && useUserPreferenceStore().isAuthenticated && !useUserPreferenceStore().isPrintMode">
|
||||
<v-list nav>
|
||||
<v-list-item :to="{ name: 'SettingsPage', params: {} }">
|
||||
<template #prepend>
|
||||
@@ -96,7 +96,7 @@
|
||||
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-bottom-navigation grow v-if="useUserPreferenceStore().isAuthenticated && !lgAndUp">
|
||||
<v-bottom-navigation grow v-if="useUserPreferenceStore().isAuthenticated && !lgAndUp && !useUserPreferenceStore().isPrintMode">
|
||||
<v-btn value="recent" :to="{ name: 'StartPage', params: {} }">
|
||||
<v-icon icon="fa-fw fas fa-book "/>
|
||||
</v-btn>
|
||||
@@ -131,43 +131,44 @@
|
||||
<script lang="ts" setup>
|
||||
import GlobalSearchDialog from "@/components/inputs/GlobalSearchDialog.vue"
|
||||
|
||||
import {useDisplay} from "vuetify"
|
||||
import {useDisplay, useLocale} from "vuetify"
|
||||
import VSnackbarQueued from "@/components/display/VSnackbarQueued.vue";
|
||||
import MessageListDialog from "@/components/dialogs/MessageListDialog.vue";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||
import NavigationDrawerContextMenu from "@/components/display/NavigationDrawerContextMenu.vue";
|
||||
import {useDjangoUrls} from "@/composables/useDjangoUrls";
|
||||
import {nextTick, onMounted} from "vue";
|
||||
import {nextTick, onMounted, ref} from "vue";
|
||||
import {isSpaceAboveLimit} from "@/utils/logic_utils";
|
||||
import {useMediaQuery, useTitle} from "@vueuse/core";
|
||||
import {useTitle} from "@vueuse/core";
|
||||
import HelpDialog from "@/components/dialogs/HelpDialog.vue";
|
||||
import {NAVIGATION_DRAWER} from "@/utils/navigation.ts";
|
||||
import {useNavigation} from "@/composables/useNavigation.ts";
|
||||
import {useRouter} from "vue-router";
|
||||
import {useI18n} from "vue-i18n";
|
||||
|
||||
const {lgAndUp} = useDisplay()
|
||||
const {getDjangoUrl} = useDjangoUrls()
|
||||
const {t} = useI18n()
|
||||
|
||||
const title = useTitle()
|
||||
const router = useRouter()
|
||||
|
||||
const isPrintMode = useMediaQuery('print')
|
||||
|
||||
onMounted(() => {
|
||||
useUserPreferenceStore().init().then(() => {
|
||||
if (useUserPreferenceStore().activeSpace.spaceSetupCompleted != undefined && !useUserPreferenceStore().activeSpace.spaceSetupCompleted) {
|
||||
router.push({name: 'WelcomePage'})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const {current} = useLocale()
|
||||
let locale = document.querySelector('html')!.getAttribute('lang')
|
||||
if (locale != null) {
|
||||
current.value = locale
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* global title update handler, might be overridden by page specific handlers
|
||||
*/
|
||||
router.afterEach((to, from) => {
|
||||
if(to.name == 'StartPage' && useUserPreferenceStore().initCompleted && !useUserPreferenceStore().activeSpace.spaceSetupCompleted != undefined &&!useUserPreferenceStore().activeSpace.spaceSetupCompleted && useUserPreferenceStore().activeSpace.createdBy.id! == useUserPreferenceStore().userSettings.user.id!){
|
||||
if (to.name == 'StartPage' && useUserPreferenceStore().initCompleted && !useUserPreferenceStore().activeSpace.spaceSetupCompleted != undefined && !useUserPreferenceStore().activeSpace.spaceSetupCompleted && useUserPreferenceStore().activeSpace.createdBy.id! == useUserPreferenceStore().userSettings.user.id!) {
|
||||
router.push({name: 'WelcomePage'})
|
||||
}
|
||||
nextTick(() => {
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn @click="useMessageStore().deleteAllMessages()" color="error">{{$t('Delete_All')}}</v-btn>
|
||||
<v-btn @click="addTestMessage()" color="warning">{{$t('Add')}}</v-btn>
|
||||
<!-- <v-btn @click="addTestMessage()" color="warning">{{$t('Add')}}</v-btn>-->
|
||||
<v-btn @click="isActive.value = false">{{ $t('Close')}}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
|
||||
@@ -54,15 +54,13 @@
|
||||
</router-link>
|
||||
<a v-else-if="i.food.url" :href="i.food.url" target="_blank">{{ ingredientToFoodString(i, ingredientFactor) }}</a>
|
||||
<span v-else>{{ ingredientToFoodString(i, ingredientFactor) }}</span>
|
||||
<template v-if="i.note != '' && i.note != undefined">
|
||||
<span class="d-none d-print-block text-disabled font-italic"> {{ i.note }}</span>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
<td style="width: 1%; text-wrap: nowrap" class="d-print-none">
|
||||
<td v-if="useUserPreferenceStore().isPrintMode">
|
||||
<span class="text-disabled font-italic"> {{ i.note }}</span>
|
||||
</td>
|
||||
<td style="width: 1%; text-wrap: nowrap" v-if="!useUserPreferenceStore().isPrintMode">
|
||||
<v-icon class="far fa-comment float-right" v-if="i.note != '' && i.note != undefined">
|
||||
<v-tooltip activator="parent" open-on-click location="start">{{ i.note }}</v-tooltip>
|
||||
</v-icon>
|
||||
|
||||
@@ -157,6 +157,7 @@ function dropCalendarItemOnDate(undefinedItem: IMealPlanNormalizedCalendarItem,
|
||||
let new_entry = Object.assign({}, mealPlan)
|
||||
new_entry.fromDate = targetDate
|
||||
new_entry.toDate = DateTime.fromJSDate(targetDate).plus(fromToDiff).toJSDate()
|
||||
new_entry.addshopping = mealPlan.shopping
|
||||
useMealPlanStore().createObject(new_entry)
|
||||
} else {
|
||||
mealPlan.fromDate = targetDate
|
||||
|
||||
@@ -53,13 +53,13 @@
|
||||
{{ fv.food.name }}
|
||||
</span>
|
||||
<template #append>
|
||||
<v-chip v-if="fv.value != undefined">{{ $n(fv.value) }} {{ dialogProperty.unit }}</v-chip>
|
||||
<v-chip color="create" v-else-if="fv.missing_conversion" class="cursor-pointer" prepend-icon="$create">
|
||||
<v-chip color="create" v-if="fv.missing_conversion" class="cursor-pointer" prepend-icon="$create">
|
||||
{{ $t('Conversion') }}: {{ fv.missing_conversion.base_unit.name }} <i class="fa-solid fa-arrow-right me-1 ms-1"></i>
|
||||
{{ fv.missing_conversion.converted_unit.name }}
|
||||
<model-edit-dialog model="UnitConversion" @create="refreshRecipe()"
|
||||
:item-defaults="{baseAmount: 1, baseUnit: fv.missing_conversion.base_unit, convertedUnit: fv.missing_conversion.converted_unit, food: fv.food}"></model-edit-dialog>
|
||||
</v-chip>
|
||||
<v-chip v-else-if="fv.value != undefined">{{ $n(fv.value) }} {{ dialogProperty.unit }}</v-chip>
|
||||
<v-chip color="warning" prepend-icon="$edit" class="cursor-pointer" :to="{name: 'ModelEditPage', params: {model: 'Recipe', id: recipe.id}}" v-else-if="fv.missing_unit">
|
||||
{{ $t('NoUnit') }}
|
||||
</v-chip>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-card class="mt-1 d-print-none" v-if="useUserPreferenceStore().isAuthenticated" :loading="loading">
|
||||
<v-card class="mt-1" v-if="useUserPreferenceStore().isAuthenticated && !useUserPreferenceStore().isPrintMode" :loading="loading">
|
||||
<v-card-text>
|
||||
<v-textarea :label="$t('Comment')" rows="2" v-model="newCookLog.comment" auto-grow></v-textarea>
|
||||
<v-row dense>
|
||||
|
||||
@@ -121,10 +121,10 @@
|
||||
<template v-if="recipe.filePath">
|
||||
<external-recipe-viewer class="mt-2" :recipe="recipe"></external-recipe-viewer>
|
||||
|
||||
<v-card :title="$t('AI')" prepend-icon="$ai" :loading="fileApiLoading || loading" :disabled="fileApiLoading || loading || !useUserPreferenceStore().activeSpace.aiEnabled"
|
||||
<v-card :title="$t('AI')" prepend-icon="$ai" :loading="fileApiLoading || loading" :disabled="fileApiLoading || loading || !useUserPreferenceStore().activeSpace.aiEnabled"
|
||||
v-if="!recipe.internal">
|
||||
<v-card-text>
|
||||
{{$t('ConvertUsingAI')}}
|
||||
{{ $t('ConvertUsingAI') }}
|
||||
|
||||
<model-select model="AiProvider" v-model="selectedAiProvider">
|
||||
<template #append>
|
||||
@@ -135,7 +135,8 @@
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<v-card class="mt-1" v-if="(recipe.steps.length > 1 || (recipe.steps.length == 1 && !recipe.steps[0].showIngredientsTable)) && recipe.showIngredientOverview">
|
||||
<v-card class="mt-1"
|
||||
v-if="(recipe.steps.length > 1 || (recipe.steps.length == 1 && !recipe.steps[0].showIngredientsTable)) && recipe.showIngredientOverview && !useUserPreferenceStore().isPrintMode">
|
||||
<steps-overview :steps="recipe.steps" :ingredient-factor="ingredientFactor"></steps-overview>
|
||||
</v-card>
|
||||
|
||||
@@ -147,8 +148,8 @@
|
||||
|
||||
<v-card class="mt-2">
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="3">
|
||||
<v-row dense>
|
||||
<v-col cols="12" :sm="(recipe.sourceUrl) ? 3 : 4">
|
||||
<v-card
|
||||
variant="outlined"
|
||||
:title="$t('CreatedBy')"
|
||||
@@ -157,7 +158,7 @@
|
||||
:to="(useUserPreferenceStore().isAuthenticated) ? {name: 'SearchPage', query: {createdby: recipe.createdBy.id!}}: undefined">
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-col cols="12" :sm="(recipe.sourceUrl) ? 3 : 4">
|
||||
<v-card
|
||||
variant="outlined"
|
||||
:title="$t('Created')"
|
||||
@@ -166,7 +167,7 @@
|
||||
:to="(useUserPreferenceStore().isAuthenticated) ? {name: 'SearchPage', query: {createdon: DateTime.fromJSDate(recipe.createdAt).toISODate()}} : undefined">
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-col cols="12" :sm="(recipe.sourceUrl) ? 3 : 4">
|
||||
<v-card
|
||||
variant="outlined"
|
||||
:title="$t('Updated')"
|
||||
@@ -175,7 +176,7 @@
|
||||
:to="(useUserPreferenceStore().isAuthenticated) ? {name: 'SearchPage', query: {updatedon: DateTime.fromJSDate(recipe.updatedAt).toISODate()}}: undefined">
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3" v-if="recipe.sourceUrl">
|
||||
<v-col cols="12" :sm="(recipe.sourceUrl) ? 3 : 4" v-if="recipe.sourceUrl">
|
||||
<v-card
|
||||
variant="outlined"
|
||||
:title="$t('Imported_From')"
|
||||
@@ -229,7 +230,7 @@ const selectedAiProvider = ref<undefined | AiProvider>(useUserPreferenceStore().
|
||||
* factor for multiplying ingredient amounts based on recipe base servings and user selected servings
|
||||
*/
|
||||
const ingredientFactor = computed(() => {
|
||||
return servings.value / ((recipe.value.servings != undefined) ? recipe.value.servings : 1)
|
||||
return servings.value / ((recipe.value.servings != undefined) ? Math.max(recipe.value.servings, 1) : 1)
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -257,7 +258,7 @@ onBeforeUnmount(() => {
|
||||
function aiConvertRecipe() {
|
||||
let api = new ApiApi()
|
||||
|
||||
doAiImport(selectedAiProvider.value.id!,null, '', recipe.value.id!).then(r => {
|
||||
doAiImport(selectedAiProvider.value.id!, null, '', recipe.value.id!).then(r => {
|
||||
if (r.recipe) {
|
||||
recipe.value.internal = true
|
||||
recipe.value.steps = r.recipe.steps
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
<v-row>
|
||||
<v-col>
|
||||
<span v-if="step.name">{{ step.name }}</span>
|
||||
<span v-else-if="step.stepRecipe"><v-icon icon="$recipes"></v-icon> {{ step.stepRecipeData.name }}</span>
|
||||
<span v-else>{{ $t('Step') }} {{ props.stepNumber }}</span>
|
||||
</v-col>
|
||||
<v-col class="text-right">
|
||||
@@ -23,11 +22,12 @@
|
||||
<timer :seconds="step.time != undefined ? step.time*60 : 0" @stop="timerRunning = false" v-if="timerRunning"></timer>
|
||||
<v-card-text v-if="step.ingredients.length > 0 || step.instruction != ''">
|
||||
<v-row>
|
||||
<v-col cols="12" md="6" v-if="step.ingredients.length > 0 && step.showIngredientsTable">
|
||||
<v-col cols="12" md="6" v-if="step.ingredients.length > 0 && (step.showIngredientsTable || step.show_ingredients_table)">
|
||||
<ingredients-table v-model="step.ingredients" :ingredient-factor="ingredientFactor"></ingredients-table>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" class="markdown-body">
|
||||
<instructions :instructions_html="step.instructionsMarkdown" :ingredient_factor="ingredientFactor" v-if="step.instructionsMarkdown != undefined"></instructions>
|
||||
<instructions :instructions_html="step.instructionsMarkdown" :ingredient_factor="ingredientFactor"
|
||||
v-if="step.instructionsMarkdown != undefined"></instructions>
|
||||
<!-- sub recipes dont have a correct schema, thus they use different variable naming -->
|
||||
<instructions :instructions_html="step.instructions_markdown" :ingredient_factor="ingredientFactor" v-else></instructions>
|
||||
</v-col>
|
||||
@@ -35,7 +35,12 @@
|
||||
</v-card-text>
|
||||
|
||||
<template v-if="step.stepRecipe">
|
||||
<v-card class="ma-2 border-md" prepend-icon="$recipes" :title="step.stepRecipeData.name">
|
||||
<v-card class="ma-2 border-md">
|
||||
<v-card-title>
|
||||
<v-icon icon="$recipes"></v-icon>
|
||||
{{ step.stepRecipeData.name }}
|
||||
<v-btn icon="fa-solid fa-up-right-from-square" size="x-small" :to="{name: 'RecipeViewPage', params: {id: step.stepRecipeData.id}}" target="_blank" variant="plain"></v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text class="mt-1" v-for="(subRecipeStep, subRecipeStepIndex) in step.stepRecipeData.steps" :key="subRecipeStep.id">
|
||||
<step-view v-model="step.stepRecipeData.steps[subRecipeStepIndex]" :step-number="subRecipeStepIndex+1" :ingredientFactor="ingredientFactor"></step-view>
|
||||
</v-card-text>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<v-btn-group density="compact">
|
||||
<v-btn color="create" @click="food.properties.push({} as Property)" prepend-icon="$create">{{ $t('Add') }}</v-btn>
|
||||
<v-btn color="create" @click="editingObj.properties.push({} as Property); addPropertiesFoodUnit()" prepend-icon="$create">{{ $t('Add') }}</v-btn>
|
||||
<v-btn color="secondary" @click="addAllProperties" prepend-icon="fa-solid fa-list">{{ $t('AddAll') }}</v-btn>
|
||||
<ai-action-button color="info" @selected="propertiesFromAi" :loading="aiLoading" prepend-icon="$ai">{{ $t('AI') }}</ai-action-button>
|
||||
</v-btn-group>
|
||||
|
||||
<v-row class="d-none d-md-flex mt-2" v-for="p in food.properties" dense>
|
||||
<v-row class="d-none d-md-flex mt-2" v-for="p in editingObj.properties" dense>
|
||||
<v-col cols="0" md="6">
|
||||
<v-number-input :step="10" v-model="p.propertyAmount" control-variant="stacked" :precision="2">
|
||||
<template #append-inner v-if="p.propertyType">
|
||||
@@ -25,7 +25,7 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-list class="d-md-none">
|
||||
<v-list-item v-for="p in food.properties" border>
|
||||
<v-list-item v-for="p in editingObj.properties" border>
|
||||
<span v-if="p.propertyType">{{ p.propertyAmount }} {{ p.propertyType.unit }} {{ p.propertyType.name }} / {{ props.amountFor }}
|
||||
</span>
|
||||
<span v-else><i><{{ $t('New') }}></i></span>
|
||||
@@ -41,18 +41,23 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import {ApiApi, Food, Property} from "@/openapi";
|
||||
import {ApiApi, Food, Property, Recipe, Unit} from "@/openapi";
|
||||
import ModelEditDialog from "@/components/dialogs/ModelEditDialog.vue";
|
||||
import ModelSelect from "@/components/inputs/ModelSelect.vue";
|
||||
import {PropType, ref} from "vue";
|
||||
import {computed, nextTick, onMounted, ref} from "vue";
|
||||
import AiActionButton from "@/components/buttons/AiActionButton.vue";
|
||||
import {ErrorMessageType, useMessageStore} from "@/stores/MessageStore.ts";
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
|
||||
|
||||
const props = defineProps({
|
||||
amountFor: {type: String, required: true},
|
||||
})
|
||||
|
||||
const food = defineModel<Food>({required: true})
|
||||
const isFood = computed(() => {
|
||||
return !('steps' in editingObj.value)
|
||||
})
|
||||
|
||||
const editingObj = defineModel<Food | Recipe>({required: true})
|
||||
|
||||
const aiLoading = ref(false)
|
||||
|
||||
@@ -61,8 +66,8 @@ const aiLoading = ref(false)
|
||||
* @param property property to delete
|
||||
*/
|
||||
function deleteProperty(property: Property) {
|
||||
if (food.value.properties) {
|
||||
food.value.properties = food.value.properties.filter(p => p !== property)
|
||||
if (editingObj.value.properties) {
|
||||
editingObj.value.properties = editingObj.value.properties.filter(p => p !== property)
|
||||
// TODO delete from DB, needs endpoint for property relation to either recipe or food
|
||||
}
|
||||
}
|
||||
@@ -74,14 +79,16 @@ function deleteProperty(property: Property) {
|
||||
function addAllProperties() {
|
||||
const api = new ApiApi()
|
||||
|
||||
if (food.value.properties) {
|
||||
food.value.properties = []
|
||||
}
|
||||
// if (editingObj.value.properties) {
|
||||
// editingObj.value.properties = []
|
||||
// }
|
||||
|
||||
addPropertiesFoodUnit()
|
||||
|
||||
api.apiPropertyTypeList().then(r => {
|
||||
r.results.forEach(pt => {
|
||||
if (food.value.properties.findIndex(x => x.propertyType.name == pt.name) == -1) {
|
||||
food.value.properties.push({propertyAmount: 0, propertyType: pt} as Property)
|
||||
if (editingObj.value.properties.findIndex(x => x.propertyType.name == pt.name) == -1) {
|
||||
editingObj.value.properties.push({propertyAmount: 0, propertyType: pt} as Property)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -90,13 +97,39 @@ function addAllProperties() {
|
||||
function propertiesFromAi(providerId: number) {
|
||||
const api = new ApiApi()
|
||||
aiLoading.value = true
|
||||
api.apiFoodAipropertiesCreate({id: food.value.id!, food: food.value, provider: providerId}).then(r => {
|
||||
food.value = r
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}).finally(() => {
|
||||
aiLoading.value = false
|
||||
})
|
||||
|
||||
if (isFood.value) {
|
||||
api.apiFoodAipropertiesCreate({id: editingObj.value.id!, food: editingObj.value, provider: providerId}).then(r => {
|
||||
editingObj.value = r
|
||||
nextTick(() => {
|
||||
addPropertiesFoodUnit()
|
||||
})
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}).finally(() => {
|
||||
aiLoading.value = false
|
||||
})
|
||||
} else {
|
||||
api.apiRecipeAipropertiesCreate({id: editingObj.value.id!, recipe: editingObj.value, provider: providerId}).then(r => {
|
||||
editingObj.value = r
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}).finally(() => {
|
||||
aiLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* if its empty add the properties food unit
|
||||
*/
|
||||
function addPropertiesFoodUnit(){
|
||||
console.log('ADDING UNIT', !editingObj.value.propertiesFoodUnit)
|
||||
if (isFood.value && !editingObj.value.propertiesFoodUnit) {
|
||||
console.log('ADDING UNIT ACTUALLY')
|
||||
editingObj.value.propertiesFoodUnit = (useUserPreferenceStore().defaultUnitObj != null) ? useUserPreferenceStore().defaultUnitObj! : {name: 'g'} as Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -26,11 +26,9 @@
|
||||
<v-progress-circular v-if="duplicateLoading" indeterminate size="small"></v-progress-circular>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
<!-- TODO when calling print() some timing or whatever issue makes it so the useMediaQuery does not work and the sidebar is still shown -->
|
||||
<!-- <v-list-item prepend-icon="fa-solid fa-print" @click="openPrintView()">-->
|
||||
<!-- {{ $t('Print') }}-->
|
||||
<!-- </v-list-item>-->
|
||||
<v-list-item :to="{ name: 'RecipeViewPage', params: { id: recipe.id}, query: {print: 'true'} }" :active="false" target="_blank" prepend-icon="fa-solid fa-print">
|
||||
{{ $t('Print') }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-btn>
|
||||
|
||||
@@ -67,13 +67,13 @@
|
||||
</div>
|
||||
<div class="d-flex flex-nowrap">
|
||||
<div class="flex-col flex-grow-0 ma-1" style="min-width: 15%" v-if="!ingredient.isHeader">
|
||||
<v-text-field :id="`id_input_amount_${step.id}_${index}`" :label="$t('Amount')" type="number" v-model="ingredient.amount" density="compact"
|
||||
hide-details :disabled="ingredient.noAmount">
|
||||
<v-number-input :id="`id_input_amount_${props.stepIndex}_${index}`" :label="$t('Amount')" v-model="ingredient.amount" density="compact"
|
||||
hide-details control-variant="hidden" :disabled="ingredient.noAmount" :precision="useUserPreferenceStore().userSettings.ingredientDecimals">
|
||||
|
||||
<template #prepend>
|
||||
<v-icon icon="$dragHandle" class="drag-handle cursor-grab"></v-icon>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-number-input>
|
||||
</div>
|
||||
<div class="flex-col flex-grow-0 ma-1" style="min-width: 15%" v-if="!ingredient.isHeader ">
|
||||
<model-select model="Unit" v-model="ingredient.unit" density="compact" allow-create hide-details :disabled="ingredient.noAmount"></model-select>
|
||||
@@ -195,7 +195,7 @@
|
||||
<v-text-field :label="$t('Original_Text')" readonly v-model="step.ingredients[editingIngredientIndex].originalText"
|
||||
v-if="step.ingredients[editingIngredientIndex].originalText"></v-text-field>
|
||||
<v-number-input v-model="step.ingredients[editingIngredientIndex].amount" inset control-variant="stacked" autofocus :label="$t('Amount')"
|
||||
:min="0" :precision="2" v-if="!step.ingredients[editingIngredientIndex].isHeader"></v-number-input>
|
||||
:min="0" :precision="useUserPreferenceStore().userSettings.ingredientDecimals" v-if="!step.ingredients[editingIngredientIndex].isHeader"></v-number-input>
|
||||
<model-select model="Unit" v-model="step.ingredients[editingIngredientIndex].unit" :label="$t('Unit')" v-if="!step.ingredients[editingIngredientIndex].isHeader"
|
||||
allow-create></model-select>
|
||||
<model-select model="Food" v-model="step.ingredients[editingIngredientIndex].food" :label="$t('Food')" v-if="!step.ingredients[editingIngredientIndex].isHeader"
|
||||
@@ -261,24 +261,6 @@ const dialogIngredientSorter = ref(false)
|
||||
const editingIngredientIndex = ref(0)
|
||||
const ingredientTextInput = ref("")
|
||||
|
||||
const defaultUnit = ref<null | Unit>(null)
|
||||
|
||||
onMounted(() => {
|
||||
let api = new ApiApi()
|
||||
|
||||
if (useUserPreferenceStore().userSettings.defaultUnit) {
|
||||
api.apiUnitList({query: useUserPreferenceStore().userSettings.defaultUnit}).then(r => {
|
||||
r.results.forEach(u => {
|
||||
if (u.name == useUserPreferenceStore().userSettings.defaultUnit) {
|
||||
defaultUnit.value = u
|
||||
}
|
||||
})
|
||||
}).catch(err => {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* sort function called by draggable when ingredient table is sorted
|
||||
*/
|
||||
@@ -334,14 +316,10 @@ function handleIngredientNoteTab(event: KeyboardEvent, index: number) {
|
||||
function insertAndFocusIngredient() {
|
||||
let ingredient = {
|
||||
amount: 0,
|
||||
unit: null,
|
||||
unit: useUserPreferenceStore().defaultUnitObj,
|
||||
food: null,
|
||||
} as Ingredient
|
||||
|
||||
if (defaultUnit.value != null) {
|
||||
ingredient.unit = defaultUnit.value
|
||||
}
|
||||
|
||||
step.value.ingredients.push(ingredient)
|
||||
nextTick(() => {
|
||||
sortIngredients()
|
||||
@@ -349,7 +327,7 @@ function insertAndFocusIngredient() {
|
||||
editingIngredientIndex.value = step.value.ingredients.length - 1
|
||||
dialogIngredientEditor.value = true
|
||||
} else {
|
||||
document.getElementById(`id_input_amount_${step.value.id}_${step.value.ingredients.length - 1}`).select()
|
||||
document.getElementById(`id_input_amount_${props.stepIndex}_${step.value.ingredients.length - 1}`).select()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
<v-list density="compact">
|
||||
<v-list-subheader>{{$t('Ingredients')}}</v-list-subheader>
|
||||
<v-list-item
|
||||
v-for="template in templates"
|
||||
@click="insertTextAtPosition(template.template + ' ')"
|
||||
v-for="t in templates"
|
||||
@click="insertTextAtPosition(t.template + ' ')"
|
||||
>
|
||||
<ingredient-string :ingredient="template.ingredient"></ingredient-string>
|
||||
<ingredient-string :ingredient="t.ingredient"></ingredient-string>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
@@ -65,7 +65,7 @@ const templates = computed(() => {
|
||||
function insertTextAtPosition(text: string){
|
||||
let textarea = markdownEditor.value.getTextareaDom()
|
||||
let position = textarea.selectionStart
|
||||
if (step.value.instruction){
|
||||
if (step.value.instruction != undefined){
|
||||
step.value.instruction = step.value.instruction.slice(0, position) + text + step.value.instruction.slice(position)
|
||||
|
||||
nextTick(() => {
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
<v-rating v-model="editingObj.rating" clearable hover density="compact"></v-rating>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
|
||||
<v-number-input :label="$t('Servings')" v-model="editingObj.servings" :precision="2"></v-number-input>
|
||||
</v-col>
|
||||
<v-col cols="12" md="4">
|
||||
@@ -42,7 +41,7 @@ import {onMounted, PropType, watch} from "vue";
|
||||
import {CookLog} from "@/openapi";
|
||||
import ModelEditorBase from "@/components/model_editors/ModelEditorBase.vue";
|
||||
import {useModelEditorFunctions} from "@/composables/useModelEditorFunctions";
|
||||
|
||||
import {VDateInput} from 'vuetify/labs/VDateInput' //TODO remove once component is out of labs
|
||||
|
||||
const props = defineProps({
|
||||
item: {type: {} as PropType<CookLog>, required: false, default: null},
|
||||
|
||||
@@ -226,7 +226,7 @@ function initializeEditor() {
|
||||
setupState(props.item, props.itemId, {
|
||||
newItemFunction: () => {
|
||||
editingObj.value.propertiesFoodAmount = 100
|
||||
editingObj.value.propertiesFoodUnit = {name: (useUserPreferenceStore().userSettings.defaultUnit != undefined ? useUserPreferenceStore().userSettings.defaultUnit : 'g')} as Unit
|
||||
editingObj.value.propertiesFoodUnit = (useUserPreferenceStore().defaultUnitObj != null) ? useUserPreferenceStore().defaultUnitObj! : {name: 'g'} as Unit
|
||||
},
|
||||
itemDefaults: props.itemDefaults,
|
||||
})
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<v-tabs v-model="tab" :disabled="loading || fileApiLoading" grow>
|
||||
<v-tab value="recipe">{{ $t('Recipe') }}</v-tab>
|
||||
<v-tab value="steps">{{ $t('Steps') }}</v-tab>
|
||||
<v-tab value="properties">{{ $t('Properties') }}</v-tab>
|
||||
<v-tab value="settings">{{ $t('Miscellaneous') }}</v-tab>
|
||||
<v-tab value="properties" :disabled="!isUpdate()">{{ $t('Properties') }}</v-tab>
|
||||
<v-tab value="settings" :disabled="!isUpdate()">{{ $t('Miscellaneous') }}</v-tab>
|
||||
</v-tabs>
|
||||
</v-card-text>
|
||||
<v-card-text v-if="!isSpaceAtRecipeLimit(useUserPreferenceStore().activeSpace)">
|
||||
@@ -87,6 +87,12 @@
|
||||
</v-row>
|
||||
|
||||
<v-form :disabled="loading || fileApiLoading">
|
||||
<v-row v-if="editingObj.steps.length == 0">
|
||||
<v-col class="text-center">
|
||||
<v-btn icon="$create" variant="outlined" size="x-small" @click="addStep(i+1)"></v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row v-for="(s,i ) in editingObj.steps" :key="s.id" dense>
|
||||
<v-col>
|
||||
<step-editor v-model="editingObj.steps[i]" v-model:recipe="editingObj" :step-index="i" @delete="deleteStepAtIndex(i)" @move="dialogStepManager = true"></step-editor>
|
||||
@@ -106,7 +112,10 @@
|
||||
<v-tabs-window-item value="properties">
|
||||
<v-form :disabled="loading || fileApiLoading">
|
||||
<closable-help-alert :text="$t('PropertiesFoodHelp')"></closable-help-alert>
|
||||
<properties-editor v-model="editingObj.properties" :amount-for="$t('Serving')"></properties-editor>
|
||||
<properties-editor v-model="editingObj" :amount-for="$t('Serving')"></properties-editor>
|
||||
|
||||
<!-- TODO remove once append to body for model select is working properly -->
|
||||
<v-spacer style="margin-top: 100px;"></v-spacer>
|
||||
</v-form>
|
||||
</v-tabs-window-item>
|
||||
<v-tabs-window-item value="settings">
|
||||
@@ -226,7 +235,7 @@ function initializeEditor() {
|
||||
addStep()
|
||||
editingObj.value.steps[0].ingredients.push({
|
||||
food: null,
|
||||
unit: null,
|
||||
unit: useUserPreferenceStore().defaultUnitObj,
|
||||
amount: 0,
|
||||
} as Ingredient)
|
||||
editingObj.value.internal = true //TODO make database default after v2
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col md="6">
|
||||
<v-number-input :label="$t('Amount')" :step="10" v-model="editingObj.baseAmount" control-variant="stacked" :precision="3"></v-number-input>
|
||||
<v-number-input :label="$t('Amount')" :step="10" v-model="editingObj.baseAmount" control-variant="stacked" :precision="3" :min="0.001"></v-number-input>
|
||||
</v-col>
|
||||
<v-col md="6">
|
||||
<!-- TODO fix card overflow invisible, overflow-visible class is not working -->
|
||||
<model-select :label="$t('Unit')" v-model="editingObj.baseUnit" model="Unit"></model-select>
|
||||
<model-select v-model="editingObj.baseUnit" model="Unit"></model-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row class="mt-0">
|
||||
@@ -33,11 +33,11 @@
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col md="6">
|
||||
<v-number-input :label="$t('Amount')" :step="10" v-model="editingObj.convertedAmount" control-variant="stacked" :precision="3"></v-number-input>
|
||||
<v-number-input :label="$t('Amount')" :step="10" v-model="editingObj.convertedAmount" control-variant="stacked" :precision="3" :min="0.001"></v-number-input>
|
||||
</v-col>
|
||||
<v-col md="6">
|
||||
<!-- TODO fix card overflow invisible, overflow-visible class is not working -->
|
||||
<model-select :label="$t('Unit')" v-model="editingObj.convertedUnit" model="Unit"></model-select>
|
||||
<model-select v-model="editingObj.convertedUnit" model="Unit"></model-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
|
||||
@@ -29,7 +29,11 @@
|
||||
<v-checkbox v-model="useUserPreferenceStore().deviceSettings.start_showMealPlan" :label="$t('ShowMealPlanOnStartPage')"></v-checkbox>
|
||||
|
||||
<v-btn @click="useUserPreferenceStore().resetDeviceSettings()" color="warning">{{ $t('Reset') }}</v-btn> <br/>
|
||||
<v-btn @click="useUserPreferenceStore().deviceSettings.general_closedHelpAlerts = []" color="warning" class="mt-1">{{ $t('ResetHelp') }}</v-btn>
|
||||
<v-btn @click="useUserPreferenceStore().deviceSettings.general_closedHelpAlerts = []" color="warning" class="mt-1">{{ $t('ResetHelp') }}</v-btn> <br/>
|
||||
<v-btn color="info" class="mt-1">
|
||||
<message-list-dialog></message-list-dialog>
|
||||
{{ $t('Messages') }}
|
||||
</v-btn>
|
||||
|
||||
</v-form>
|
||||
</template>
|
||||
@@ -43,6 +47,7 @@ import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/Messa
|
||||
import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
||||
import {useDjangoUrls} from "@/composables/useDjangoUrls";
|
||||
import ThankYouNote from "@/components/display/ThankYouNote.vue";
|
||||
import MessageListDialog from "@/components/dialogs/MessageListDialog.vue";
|
||||
|
||||
const {getDjangoUrl} = useDjangoUrls()
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
Authentication works by proving the word <code>Bearer</code> followed by an API Token as a request Authorization
|
||||
header as shown below. <br/>
|
||||
<code>Authorization: Bearer TOKEN</code> -or-<br/>
|
||||
<code>curl -X GET http://your.domain.com/api/recipes/ -H 'Authorization:
|
||||
<code>curl -X GET http://your.domain.com/api/recipe/ -H 'Authorization:
|
||||
Bearer TOKEN'</code>
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -124,6 +124,8 @@ export function useFileApi() {
|
||||
* @returns Promise resolving to the import ID of the app import
|
||||
*/
|
||||
function doAppImport(files: File[], app: string, includeDuplicates: boolean, mealPlans: boolean = true, shoppingLists: boolean = true, nutritionPerServing: boolean = false,) {
|
||||
fileApiLoading.value = true
|
||||
|
||||
let formData = new FormData()
|
||||
formData.append('type', app);
|
||||
formData.append('duplicates', includeDuplicates ? 'true' : 'false')
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
"Fats": "",
|
||||
"File": "",
|
||||
"Files": "",
|
||||
"Finish": "",
|
||||
"Food": "",
|
||||
"FoodInherit": "",
|
||||
"FoodNotOnHand": "",
|
||||
|
||||
@@ -112,6 +112,7 @@
|
||||
"Fats": "Мазнини",
|
||||
"File": "Файл",
|
||||
"Files": "Файлове",
|
||||
"Finish": "",
|
||||
"Food": "Храна",
|
||||
"FoodInherit": "Хранителни наследствени полета",
|
||||
"FoodNotOnHand": "Нямате {храна} под ръка.",
|
||||
|
||||
@@ -154,6 +154,7 @@
|
||||
"Fats": "Greixos",
|
||||
"File": "Arxiu",
|
||||
"Files": "Arxius",
|
||||
"Finish": "",
|
||||
"First_name": "Nom",
|
||||
"Food": "Aliment",
|
||||
"FoodInherit": "Camps Heretats",
|
||||
|
||||
@@ -153,6 +153,7 @@
|
||||
"Fats": "Tuky",
|
||||
"File": "Soubor",
|
||||
"Files": "Soubory",
|
||||
"Finish": "",
|
||||
"First_name": "Jméno",
|
||||
"Food": "Potravina",
|
||||
"FoodInherit": "Propisovatelná pole potraviny",
|
||||
|
||||
@@ -154,6 +154,7 @@
|
||||
"Fats": "Fedtstoffer",
|
||||
"File": "Fil",
|
||||
"Files": "Filer",
|
||||
"Finish": "",
|
||||
"First_name": "Fornavn",
|
||||
"Food": "Mad",
|
||||
"FoodInherit": "Nedarvelige mad felter",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -154,6 +154,7 @@
|
||||
"Fats": "Λιπαρά",
|
||||
"File": "Αρχείο",
|
||||
"Files": "Αρχεία",
|
||||
"Finish": "",
|
||||
"First_name": "Όνομα",
|
||||
"Food": "Φαγητό",
|
||||
"FoodInherit": "Πεδία φαγητών που κληρονομούνται",
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"BaseUnit": "Base Unit",
|
||||
"BaseUnitHelp": "Standard unit for automatic unit conversion",
|
||||
"Basics": "Basics",
|
||||
"BatchDeleteConfirm": "Do you want to delete all shown items? This cannot be undone!",
|
||||
"BatchDeleteConfirm": "Do you want to delete all shown items? This cannot be undone! WARNING: It is possible that this deletes objects that are used elsewhere. ",
|
||||
"BatchDeleteHelp": "If an item cannot be deleted it is used somewhere. ",
|
||||
"BatchEdit": "Batch Edit",
|
||||
"BatchEditUpdatingItemsCount": "Editing {count} {type}",
|
||||
@@ -210,6 +210,7 @@
|
||||
"Fats": "Fats",
|
||||
"File": "File",
|
||||
"Files": "Files",
|
||||
"Finish": "Finish",
|
||||
"FinishedAt": "Finished at",
|
||||
"First": "First",
|
||||
"First_name": "First Name",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -151,6 +151,7 @@
|
||||
"Fats": "Rasvat",
|
||||
"File": "Tiedosto",
|
||||
"Files": "Tiedostot",
|
||||
"Finish": "",
|
||||
"First_name": "Etunimi",
|
||||
"Food": "Ruoka",
|
||||
"FoodInherit": "Ruoan perinnölliset kentät",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -154,6 +154,7 @@
|
||||
"Fats": "שומנים",
|
||||
"File": "קובץ",
|
||||
"Files": "קבצים",
|
||||
"Finish": "",
|
||||
"First_name": "שם פרטי",
|
||||
"Food": "אוכל",
|
||||
"FoodInherit": "ערכי מזון",
|
||||
|
||||
@@ -154,6 +154,7 @@
|
||||
"Fats": "Masti",
|
||||
"File": "Datoteka",
|
||||
"Files": "Datoteke",
|
||||
"Finish": "",
|
||||
"First_name": "Ime",
|
||||
"Food": "Namirnica",
|
||||
"FoodInherit": "Nasljedna polja namirnice",
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
"Fats": "Zsírok",
|
||||
"File": "Fájl",
|
||||
"Files": "Fájlok",
|
||||
"Finish": "",
|
||||
"First_name": "Keresztnév",
|
||||
"Food": "Alapanyag",
|
||||
"FoodInherit": "",
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"Fats": "",
|
||||
"File": "",
|
||||
"Files": "",
|
||||
"Finish": "",
|
||||
"Food": "Սննդամթերք",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
"Fats": "Lemak",
|
||||
"File": "Berkas",
|
||||
"Files": "File",
|
||||
"Finish": "",
|
||||
"First_name": "",
|
||||
"Food": "",
|
||||
"FoodInherit": "",
|
||||
|
||||
@@ -153,6 +153,7 @@
|
||||
"Fats": "",
|
||||
"File": "",
|
||||
"Files": "",
|
||||
"Finish": "",
|
||||
"First_name": "",
|
||||
"Food": "",
|
||||
"FoodInherit": "",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
866
vue3/src/locales/ko.json
Normal file
866
vue3/src/locales/ko.json
Normal file
@@ -0,0 +1,866 @@
|
||||
{
|
||||
"AI": "",
|
||||
"AIImportSubtitle": "",
|
||||
"AISettingsHostedHelp": "",
|
||||
"API": "",
|
||||
"APIKey": "",
|
||||
"API_Browser": "",
|
||||
"API_Documentation": "",
|
||||
"AccessTokenHelp": "",
|
||||
"Access_Token": "",
|
||||
"Account": "",
|
||||
"Actions": "",
|
||||
"Active": "",
|
||||
"Activity": "",
|
||||
"Add": "",
|
||||
"AddAll": "",
|
||||
"AddChild": "",
|
||||
"AddFilter": "",
|
||||
"AddFoodToShopping": "",
|
||||
"AddMany": "",
|
||||
"AddToShopping": "",
|
||||
"Add_Servings_to_Shopping": "",
|
||||
"Add_Step": "",
|
||||
"Add_nutrition_recipe": "",
|
||||
"Add_to_Plan": "",
|
||||
"Add_to_Shopping": "",
|
||||
"Added_To_Shopping_List": "",
|
||||
"Added_by": "",
|
||||
"Added_on": "",
|
||||
"Admin": "",
|
||||
"Advanced": "",
|
||||
"AiCreditsBalance": "",
|
||||
"AiLog": "",
|
||||
"AiLogHelp": "",
|
||||
"AiModelHelp": "",
|
||||
"AiProvider": "",
|
||||
"AiProviderHelp": "",
|
||||
"Alignment": "",
|
||||
"AllRecipes": "",
|
||||
"Amount": "",
|
||||
"App": "",
|
||||
"AppImportSubtitle": "",
|
||||
"Apply": "",
|
||||
"Are_You_Sure": "",
|
||||
"Auto_Planner": "",
|
||||
"Auto_Sort": "",
|
||||
"Auto_Sort_Help": "",
|
||||
"Automate": "",
|
||||
"Automation": "",
|
||||
"AutomationHelp": "",
|
||||
"Available": "",
|
||||
"AvailableCategories": "",
|
||||
"Back": "",
|
||||
"BaseUnit": "",
|
||||
"BaseUnitHelp": "",
|
||||
"Basics": "",
|
||||
"BatchDeleteConfirm": "",
|
||||
"BatchDeleteHelp": "",
|
||||
"BatchEdit": "",
|
||||
"BatchEditUpdatingItemsCount": "",
|
||||
"Blocking": "",
|
||||
"BlockingHelp": "",
|
||||
"Book": "",
|
||||
"Bookmarklet": "",
|
||||
"BookmarkletHelp1": "",
|
||||
"BookmarkletHelp2": "",
|
||||
"BookmarkletHelp3": "",
|
||||
"BookmarkletImportSubtitle": "",
|
||||
"Books": "",
|
||||
"CREATE_ERROR": "",
|
||||
"Calculator": "",
|
||||
"Calories": "",
|
||||
"Cancel": "",
|
||||
"Cannot_Add_Notes_To_Shopping": "",
|
||||
"Carbohydrates": "",
|
||||
"Cards": "",
|
||||
"Cascading": "",
|
||||
"CascadingHelp": "",
|
||||
"Categories": "",
|
||||
"Category": "",
|
||||
"CategoryInstruction": "",
|
||||
"CategoryName": "",
|
||||
"Change_Password": "",
|
||||
"Changing": "",
|
||||
"ChildInheritFields": "",
|
||||
"ChildInheritFields_help": "",
|
||||
"Choose_Category": "",
|
||||
"Clear": "",
|
||||
"Click_To_Edit": "",
|
||||
"Clone": "",
|
||||
"Close": "",
|
||||
"Color": "",
|
||||
"Combine_All_Steps": "",
|
||||
"Coming_Soon": "",
|
||||
"Comment": "",
|
||||
"Comments_setting": "",
|
||||
"Completed": "",
|
||||
"Confirm": "",
|
||||
"ConnectorConfig": "",
|
||||
"ConnectorConfigHelp": "",
|
||||
"Continue": "",
|
||||
"Conversion": "",
|
||||
"ConversionsHelp": "",
|
||||
"ConvertUsingAI": "",
|
||||
"CookLog": "",
|
||||
"CookLogHelp": "",
|
||||
"Cooked": "",
|
||||
"Copied": "",
|
||||
"Copy": "",
|
||||
"Copy Link": "",
|
||||
"Copy Token": "",
|
||||
"Copy_template_reference": "",
|
||||
"Cosmetic": "",
|
||||
"CountMore": "",
|
||||
"Create": "",
|
||||
"Create Food": "",
|
||||
"Create Recipe": "",
|
||||
"CreateFirstRecipe": "",
|
||||
"CreateInvitation": "",
|
||||
"Create_Meal_Plan_Entry": "",
|
||||
"Create_New_Food": "",
|
||||
"Create_New_Keyword": "",
|
||||
"Create_New_Meal_Type": "",
|
||||
"Create_New_Shopping Category": "",
|
||||
"Create_New_Shopping_Category": "",
|
||||
"Create_New_Unit": "",
|
||||
"Created": "",
|
||||
"CreatedBy": "",
|
||||
"Credits": "",
|
||||
"Ctrl+K": "",
|
||||
"Current_Period": "",
|
||||
"Custom Filter": "",
|
||||
"CustomImageHelp": "",
|
||||
"CustomLogoHelp": "",
|
||||
"CustomLogos": "",
|
||||
"CustomNavLogoHelp": "",
|
||||
"CustomTheme": "",
|
||||
"CustomThemeHelp": "",
|
||||
"DELETE_ERROR": "",
|
||||
"Data_Import_Info": "",
|
||||
"Database": "",
|
||||
"DatabaseHelp": "",
|
||||
"Datatype": "",
|
||||
"Date": "",
|
||||
"Day": "",
|
||||
"Days": "",
|
||||
"Decimals": "",
|
||||
"Default": "",
|
||||
"DefaultPage": "",
|
||||
"Default_Unit": "",
|
||||
"DelayFor": "",
|
||||
"DelayUntil": "",
|
||||
"Delete": "",
|
||||
"DeleteConfirmQuestion": "",
|
||||
"DeleteShoppingConfirm": "",
|
||||
"DeleteSomething": "",
|
||||
"Delete_All": "",
|
||||
"Delete_Food": "",
|
||||
"Delete_Keyword": "",
|
||||
"Deleted": "",
|
||||
"Description": "",
|
||||
"Description_Replace": "",
|
||||
"DeviceSettings": "",
|
||||
"DeviceSettingsHelp": "",
|
||||
"Disable": "",
|
||||
"Disable_Amount": "",
|
||||
"Disabled": "",
|
||||
"Documentation": "",
|
||||
"DontChange": "",
|
||||
"Down": "",
|
||||
"Download": "",
|
||||
"DragToUpload": "",
|
||||
"Drag_Here_To_Delete": "",
|
||||
"Duplicate": "",
|
||||
"DuplicateFoundInfo": "",
|
||||
"Edit": "",
|
||||
"Edit_Food": "",
|
||||
"Edit_Keyword": "",
|
||||
"Edit_Meal_Plan_Entry": "",
|
||||
"Edit_Recipe": "",
|
||||
"Email": "",
|
||||
"Empty": "",
|
||||
"Enable": "",
|
||||
"Enable_Amount": "",
|
||||
"Enabled": "",
|
||||
"EndDate": "",
|
||||
"Energy": "",
|
||||
"Entries": "",
|
||||
"Error": "",
|
||||
"ErrorUrlListImport": "",
|
||||
"Events": "",
|
||||
"Export": "",
|
||||
"Export_As_ICal": "",
|
||||
"Export_Not_Yet_Supported": "",
|
||||
"Export_Supported": "",
|
||||
"Export_To_ICal": "",
|
||||
"External": "",
|
||||
"ExternalRecipe": "",
|
||||
"ExternalRecipeImport": "",
|
||||
"ExternalRecipeImportHelp": "",
|
||||
"ExternalStorage": "",
|
||||
"External_Recipe_Image": "",
|
||||
"FDC_ID": "",
|
||||
"FDC_ID_help": "",
|
||||
"FDC_Search": "",
|
||||
"FETCH_ERROR": "",
|
||||
"Failure": "",
|
||||
"Fats": "",
|
||||
"File": "",
|
||||
"Files": "",
|
||||
"FinishedAt": "",
|
||||
"First": "",
|
||||
"First_name": "",
|
||||
"Food": "",
|
||||
"FoodHelp": "",
|
||||
"FoodInherit": "",
|
||||
"FoodNotOnHand": "",
|
||||
"FoodOnHand": "",
|
||||
"Food_Alias": "",
|
||||
"Food_Replace": "",
|
||||
"Foods": "",
|
||||
"Friday": "",
|
||||
"FromBalance": "",
|
||||
"Fulltext": "",
|
||||
"FulltextHelp": "",
|
||||
"Fuzzy": "",
|
||||
"FuzzySearchHelp": "",
|
||||
"GettingStarted": "",
|
||||
"Global": "",
|
||||
"GlobalHelp": "",
|
||||
"Group": "",
|
||||
"GroupBy": "",
|
||||
"HeaderWarning": "",
|
||||
"Headline": "",
|
||||
"Help": "",
|
||||
"Hide_External": "",
|
||||
"Hide_Food": "",
|
||||
"Hide_Keyword": "",
|
||||
"Hide_Keywords": "",
|
||||
"Hide_Recipes": "",
|
||||
"Hide_as_header": "",
|
||||
"Hierarchy": "",
|
||||
"History": "",
|
||||
"HostedFreeVersion": "",
|
||||
"Hour": "",
|
||||
"Hours": "",
|
||||
"Icon": "",
|
||||
"IgnoreAccents": "",
|
||||
"IgnoreAccentsHelp": "",
|
||||
"IgnoreThis": "",
|
||||
"Ignore_Shopping": "",
|
||||
"IgnoredFood": "",
|
||||
"Image": "",
|
||||
"Import": "",
|
||||
"Import Recipe": "",
|
||||
"ImportAll": "",
|
||||
"ImportFirstRecipe": "",
|
||||
"ImportIntoTandoor": "",
|
||||
"ImportMealPlans": "",
|
||||
"ImportShoppingList": "",
|
||||
"Import_Error": "",
|
||||
"Import_Not_Yet_Supported": "",
|
||||
"Import_Result_Info": "",
|
||||
"Import_Supported": "",
|
||||
"Import_finished": "",
|
||||
"Imported": "",
|
||||
"Imported_From": "",
|
||||
"Importer_Help": "",
|
||||
"Information": "",
|
||||
"Ingredient": "",
|
||||
"Ingredient Editor": "",
|
||||
"Ingredient Overview": "",
|
||||
"IngredientEditorHelp": "",
|
||||
"IngredientHelp": "",
|
||||
"IngredientInShopping": "",
|
||||
"Ingredients": "",
|
||||
"Inherit": "",
|
||||
"InheritFields": "",
|
||||
"InheritFields_help": "",
|
||||
"InheritWarning": "",
|
||||
"Input": "",
|
||||
"Instruction_Replace": "",
|
||||
"Instructions": "",
|
||||
"InstructionsEditHelp": "",
|
||||
"Internal": "",
|
||||
"InviteLinkHelp": "",
|
||||
"Invite_Link": "",
|
||||
"Invites": "",
|
||||
"Key_Ctrl": "",
|
||||
"Key_Shift": "",
|
||||
"Keyword": "",
|
||||
"KeywordHelp": "",
|
||||
"Keyword_Alias": "",
|
||||
"Keywords": "",
|
||||
"Language": "",
|
||||
"Last": "",
|
||||
"Last_name": "",
|
||||
"Learn_More": "",
|
||||
"LeaveSpace": "",
|
||||
"Link": "",
|
||||
"Load": "",
|
||||
"Load_More": "",
|
||||
"LogCredits": "",
|
||||
"LogCreditsHelp": "",
|
||||
"Log_Cooking": "",
|
||||
"Log_Recipe_Cooking": "",
|
||||
"Logo": "",
|
||||
"Logout": "",
|
||||
"Make_Header": "",
|
||||
"Make_Ingredient": "",
|
||||
"ManageSubscription": "",
|
||||
"Manage_Books": "",
|
||||
"Manage_Emails": "",
|
||||
"MealPlanHelp": "",
|
||||
"MealPlanShoppingHelp": "",
|
||||
"MealTypeHelp": "",
|
||||
"Meal_Plan": "",
|
||||
"Meal_Plan_Days": "",
|
||||
"Meal_Type": "",
|
||||
"Meal_Type_Required": "",
|
||||
"Meal_Types": "",
|
||||
"Merge": "",
|
||||
"MergeAutomateHelp": "",
|
||||
"MergeInsteadOfDelete": "",
|
||||
"Merge_Keyword": "",
|
||||
"Message": "",
|
||||
"Messages": "",
|
||||
"Miscellaneous": "",
|
||||
"MissingConversion": "",
|
||||
"MissingProperties": "",
|
||||
"Model": "",
|
||||
"ModelSelectResultsHelp": "",
|
||||
"Monday": "",
|
||||
"Month": "",
|
||||
"MonthlyCredits": "",
|
||||
"MonthlyCreditsUsed": "",
|
||||
"More": "",
|
||||
"Move": "",
|
||||
"MoveCategory": "",
|
||||
"MoveToStep": "",
|
||||
"Move_Down": "",
|
||||
"Move_Food": "",
|
||||
"Move_Keyword": "",
|
||||
"Move_Up": "",
|
||||
"Multiple": "",
|
||||
"Name": "",
|
||||
"Name_Replace": "",
|
||||
"Nav_Color": "",
|
||||
"Nav_Color_Help": "",
|
||||
"Nav_Text_Mode": "",
|
||||
"Nav_Text_Mode_Help": "",
|
||||
"Never_Unit": "",
|
||||
"New": "",
|
||||
"New_Cookbook": "",
|
||||
"New_Entry": "",
|
||||
"New_Food": "",
|
||||
"New_Keyword": "",
|
||||
"New_Meal_Type": "",
|
||||
"New_Recipe": "",
|
||||
"New_Supermarket": "",
|
||||
"New_Supermarket_Category": "",
|
||||
"New_Unit": "",
|
||||
"Next": "",
|
||||
"Next_Day": "",
|
||||
"Next_Period": "",
|
||||
"No": "",
|
||||
"NoCategory": "",
|
||||
"NoMoreUndo": "",
|
||||
"NoUnit": "",
|
||||
"No_ID": "",
|
||||
"No_Results": "",
|
||||
"NotFound": "",
|
||||
"NotFoundHelp": "",
|
||||
"NotInShopping": "",
|
||||
"Note": "",
|
||||
"NullingHelp": "",
|
||||
"Number of Objects": "",
|
||||
"Nutrition": "",
|
||||
"NutritionsPerServing": "",
|
||||
"NutritionsPerServingHelp": "",
|
||||
"OfflineAlert": "",
|
||||
"Ok": "",
|
||||
"OnHand": "",
|
||||
"OnHand_help": "",
|
||||
"Open": "",
|
||||
"Open_Data_Import": "",
|
||||
"Open_Data_Slug": "",
|
||||
"Options": "",
|
||||
"Order": "",
|
||||
"OrderInformation": "",
|
||||
"Original_Text": "",
|
||||
"Owner": "",
|
||||
"Page": "",
|
||||
"Parameter": "",
|
||||
"Parent": "",
|
||||
"PartialMatch": "",
|
||||
"PartialMatchHelp": "",
|
||||
"Password": "",
|
||||
"Path": "",
|
||||
"PerPage": "",
|
||||
"Period": "",
|
||||
"Periods": "",
|
||||
"Pin": "",
|
||||
"Pinned": "",
|
||||
"PinnedConfirmation": "",
|
||||
"Plan_Period_To_Show": "",
|
||||
"Plan_Show_How_Many_Periods": "",
|
||||
"Planned": "",
|
||||
"Planner": "",
|
||||
"Planner_Settings": "",
|
||||
"Planning&Shopping": "",
|
||||
"Plural": "",
|
||||
"Postpone": "",
|
||||
"PostponedUntil": "",
|
||||
"PrecisionSearchHelp": "",
|
||||
"Preferences": "",
|
||||
"Preparation": "",
|
||||
"Preview": "",
|
||||
"Previous_Day": "",
|
||||
"Previous_Period": "",
|
||||
"Print": "",
|
||||
"Private": "",
|
||||
"Private_Recipe": "",
|
||||
"Private_Recipe_Help": "",
|
||||
"Profile": "",
|
||||
"Properties": "",
|
||||
"PropertiesFoodHelp": "",
|
||||
"Properties_Food_Amount": "",
|
||||
"Properties_Food_Unit": "",
|
||||
"Property": "",
|
||||
"PropertyHelp": "",
|
||||
"PropertyType": "",
|
||||
"PropertyTypeHelp": "",
|
||||
"Property_Editor": "",
|
||||
"Protected": "",
|
||||
"Proteins": "",
|
||||
"Quick actions": "",
|
||||
"QuickEntry": "",
|
||||
"Random Recipes": "",
|
||||
"RandomOrder": "",
|
||||
"RateLimit": "",
|
||||
"RateLimitHelp": "",
|
||||
"Rating": "",
|
||||
"Ratings": "",
|
||||
"Recently_Viewed": "",
|
||||
"Recipe": "",
|
||||
"RecipeBookEntryHelp": "",
|
||||
"RecipeBookHelp": "",
|
||||
"RecipeHelp": "",
|
||||
"RecipeStepsHelp": "",
|
||||
"Recipe_Book": "",
|
||||
"Recipe_Image": "",
|
||||
"Recipes": "",
|
||||
"Recipes_In_Import": "",
|
||||
"Recipes_per_page": "",
|
||||
"Refresh": "",
|
||||
"Remove": "",
|
||||
"RemoveAllType": "",
|
||||
"RemoveFoodFromShopping": "",
|
||||
"RemoveParent": "",
|
||||
"Remove_nutrition_recipe": "",
|
||||
"Reset": "",
|
||||
"ResetHelp": "",
|
||||
"Reset_Search": "",
|
||||
"Reusable": "",
|
||||
"Role": "",
|
||||
"Root": "",
|
||||
"Saturday": "",
|
||||
"Save": "",
|
||||
"Save/Load": "",
|
||||
"Save_and_View": "",
|
||||
"SavedSearch": "",
|
||||
"SavedSearchHelp": "",
|
||||
"ScalableNumber": "",
|
||||
"Search": "",
|
||||
"Search Settings": "",
|
||||
"SearchMethod": "",
|
||||
"SearchSettingsOverview": "",
|
||||
"SearchSettingsWarning": "",
|
||||
"Second": "",
|
||||
"Seconds": "",
|
||||
"Select": "",
|
||||
"SelectAll": "",
|
||||
"SelectNone": "",
|
||||
"Select_App_To_Import": "",
|
||||
"Select_Book": "",
|
||||
"Select_File": "",
|
||||
"Selected": "",
|
||||
"SelectedCategories": "",
|
||||
"Serving": "",
|
||||
"Servings": "",
|
||||
"ServingsText": "",
|
||||
"Settings": "",
|
||||
"SettingsOnlySuperuser": "",
|
||||
"Share": "",
|
||||
"ShopLater": "",
|
||||
"ShopNow": "",
|
||||
"ShoppingBackgroundSyncWarning": "",
|
||||
"ShoppingListEntry": "",
|
||||
"ShoppingListEntryHelp": "",
|
||||
"ShoppingListRecipe": "",
|
||||
"Shopping_Categories": "",
|
||||
"Shopping_Category": "",
|
||||
"Shopping_List_Empty": "",
|
||||
"Shopping_input_placeholder": "",
|
||||
"Shopping_list": "",
|
||||
"ShowDelayed": "",
|
||||
"ShowIngredients": "",
|
||||
"ShowMealPlanOnStartPage": "",
|
||||
"ShowRecentlyCompleted": "",
|
||||
"ShowUncategorizedFood": "",
|
||||
"Show_Logo": "",
|
||||
"Show_Logo_Help": "",
|
||||
"Show_Week_Numbers": "",
|
||||
"Show_as_header": "",
|
||||
"Single": "",
|
||||
"Size": "",
|
||||
"Skip": "",
|
||||
"Social_Authentication": "",
|
||||
"Sort_by_new": "",
|
||||
"Source": "",
|
||||
"SourceImportHelp": "",
|
||||
"SourceImportSubtitle": "",
|
||||
"Space": "",
|
||||
"SpaceHelp": "",
|
||||
"SpaceLimitExceeded": "",
|
||||
"SpaceLimitReached": "",
|
||||
"SpaceMemberHelp": "",
|
||||
"SpaceMembers": "",
|
||||
"SpaceMembersHelp": "",
|
||||
"SpaceName": "",
|
||||
"SpacePrivateObjectsHelp": "",
|
||||
"SpaceSettings": "",
|
||||
"Space_Cosmetic_Settings": "",
|
||||
"Split": "",
|
||||
"Split_All_Steps": "",
|
||||
"StartDate": "",
|
||||
"Starting_Day": "",
|
||||
"StartsWith": "",
|
||||
"StartsWithHelp": "",
|
||||
"Step": "",
|
||||
"StepHelp": "",
|
||||
"Step_Name": "",
|
||||
"Step_Type": "",
|
||||
"Step_start_time": "",
|
||||
"Steps": "",
|
||||
"StepsOverview": "",
|
||||
"Sticky_Nav": "",
|
||||
"Sticky_Nav_Help": "",
|
||||
"Storage": "",
|
||||
"StorageHelp": "",
|
||||
"StoragePasswordTokenHelp": "",
|
||||
"Structured": "",
|
||||
"SubstituteOnHand": "",
|
||||
"Substitutes": "",
|
||||
"Success": "",
|
||||
"SuccessClipboard": "",
|
||||
"Summary": "",
|
||||
"Sunday": "",
|
||||
"Supermarket": "",
|
||||
"SupermarketCategoriesOnly": "",
|
||||
"SupermarketCategoryHelp": "",
|
||||
"SupermarketHelp": "",
|
||||
"SupermarketName": "",
|
||||
"Supermarkets": "",
|
||||
"SupportsDescriptionField": "",
|
||||
"SyncLog": "",
|
||||
"SyncLogHelp": "",
|
||||
"SyncedPath": "",
|
||||
"SyncedPathHelp": "",
|
||||
"System": "",
|
||||
"Table": "",
|
||||
"Table_of_Contents": "",
|
||||
"Text": "",
|
||||
"ThankYou": "",
|
||||
"ThanksTextHosted": "",
|
||||
"ThanksTextSelfhosted": "",
|
||||
"Theme": "",
|
||||
"Thursday": "",
|
||||
"Time": "",
|
||||
"Title": "",
|
||||
"Title_or_Recipe_Required": "",
|
||||
"Today": "",
|
||||
"Toggle": "",
|
||||
"Transpose_Words": "",
|
||||
"TrigramThreshold": "",
|
||||
"TrigramThresholdHelp": "",
|
||||
"Tuesday": "",
|
||||
"Type": "",
|
||||
"UPDATE_ERROR": "",
|
||||
"Unchanged": "",
|
||||
"Undefined": "",
|
||||
"Undo": "",
|
||||
"Unit": "",
|
||||
"UnitConversion": "",
|
||||
"UnitConversionHelp": "",
|
||||
"UnitHelp": "",
|
||||
"Unit_Alias": "",
|
||||
"Unit_Replace": "",
|
||||
"Units": "",
|
||||
"Unpin": "",
|
||||
"UnpinnedConfirmation": "",
|
||||
"Unrated": "",
|
||||
"Up": "",
|
||||
"Update": "",
|
||||
"Update_Existing_Data": "",
|
||||
"Updated": "",
|
||||
"UpgradeNow": "",
|
||||
"Url": "",
|
||||
"UrlImportSubtitle": "",
|
||||
"UrlList": "",
|
||||
"UrlListSubtitle": "",
|
||||
"Url_Import": "",
|
||||
"Use_Fractions": "",
|
||||
"Use_Fractions_Help": "",
|
||||
"Use_Kj": "",
|
||||
"Use_Metric": "",
|
||||
"Use_Plural_Food_Always": "",
|
||||
"Use_Plural_Food_Simple": "",
|
||||
"Use_Plural_Unit_Always": "",
|
||||
"Use_Plural_Unit_Simple": "",
|
||||
"User": "",
|
||||
"UserFileHelp": "",
|
||||
"UserHelp": "",
|
||||
"Username": "",
|
||||
"Users": "",
|
||||
"Valid Until": "",
|
||||
"View": "",
|
||||
"ViewLogHelp": "",
|
||||
"View_Recipes": "",
|
||||
"Viewed": "",
|
||||
"Visibility": "",
|
||||
"Waiting": "",
|
||||
"WaitingTime": "",
|
||||
"WarnPageLeave": "",
|
||||
"Warning": "",
|
||||
"WarningRecipeBookEntryDuplicate": "",
|
||||
"Warning_Delete_Supermarket_Category": "",
|
||||
"Website": "",
|
||||
"Wednesday": "",
|
||||
"Week": "",
|
||||
"Week_Numbers": "",
|
||||
"Welcome": "",
|
||||
"WelcomeSettingsHelp": "",
|
||||
"WelcometoTandoor": "",
|
||||
"WorkingTime": "",
|
||||
"Year": "",
|
||||
"Yes": "",
|
||||
"YourSpaces": "",
|
||||
"active": "",
|
||||
"add_keyword": "",
|
||||
"additional_options": "",
|
||||
"advanced": "",
|
||||
"advanced_search_settings": "",
|
||||
"after": "",
|
||||
"all": "",
|
||||
"all_fields_optional": "",
|
||||
"and": "",
|
||||
"and_down": "",
|
||||
"and_up": "",
|
||||
"any": "",
|
||||
"asc": "",
|
||||
"base_amount": "",
|
||||
"base_unit": "",
|
||||
"before": "",
|
||||
"book_filter_help": "",
|
||||
"click_image_import": "",
|
||||
"confirm_delete": "",
|
||||
"convert_internal": "",
|
||||
"converted_amount": "",
|
||||
"converted_unit": "",
|
||||
"copy_markdown_table": "",
|
||||
"copy_to_clipboard": "",
|
||||
"copy_to_new": "",
|
||||
"create_food_desc": "",
|
||||
"create_rule": "",
|
||||
"create_title": "",
|
||||
"created_by": "",
|
||||
"created_on": "",
|
||||
"csv_delim_help": "",
|
||||
"csv_delim_label": "",
|
||||
"csv_prefix_help": "",
|
||||
"csv_prefix_label": "",
|
||||
"date_created": "",
|
||||
"date_viewed": "",
|
||||
"default_delay": "",
|
||||
"default_delay_desc": "",
|
||||
"del_confirmation_tree": "",
|
||||
"delete_confirmation": "",
|
||||
"delete_title": "",
|
||||
"desc": "",
|
||||
"download_csv": "",
|
||||
"download_pdf": "",
|
||||
"edit_title": "",
|
||||
"empty_list": "",
|
||||
"enable_expert": "",
|
||||
"err_creating_resource": "",
|
||||
"err_deleting_protected_resource": "",
|
||||
"err_deleting_resource": "",
|
||||
"err_fetching_resource": "",
|
||||
"err_importing_recipe": "",
|
||||
"err_merge_self": "",
|
||||
"err_merging_resource": "",
|
||||
"err_move_self": "",
|
||||
"err_moving_resource": "",
|
||||
"err_updating_resource": "",
|
||||
"exact": "",
|
||||
"exclude": "",
|
||||
"expert_mode": "",
|
||||
"explain": "",
|
||||
"fields": "",
|
||||
"file_upload_disabled": "",
|
||||
"filter": "",
|
||||
"filter_name": "",
|
||||
"filter_to_supermarket": "",
|
||||
"filter_to_supermarket_desc": "",
|
||||
"fluid_ounce": "",
|
||||
"food_inherit_info": "",
|
||||
"food_recipe_help": "",
|
||||
"g": "",
|
||||
"gallon": "",
|
||||
"hide_step_ingredients": "",
|
||||
"hours": "",
|
||||
"ignore_shopping_help": "",
|
||||
"imperial_fluid_ounce": "",
|
||||
"imperial_gallon": "",
|
||||
"imperial_pint": "",
|
||||
"imperial_quart": "",
|
||||
"imperial_tbsp": "",
|
||||
"imperial_tsp": "",
|
||||
"import_duplicates": "",
|
||||
"import_running": "",
|
||||
"in_shopping": "",
|
||||
"ingredient_list": "",
|
||||
"kg": "",
|
||||
"l": "",
|
||||
"last_cooked": "",
|
||||
"last_viewed": "",
|
||||
"left_handed": "",
|
||||
"left_handed_help": "",
|
||||
"make_now": "",
|
||||
"make_now_count": "",
|
||||
"mark_complete": "",
|
||||
"mealplan_autoadd_shopping": "",
|
||||
"mealplan_autoadd_shopping_desc": "",
|
||||
"mealplan_autoexclude_onhand": "",
|
||||
"mealplan_autoexclude_onhand_desc": "",
|
||||
"mealplan_autoinclude_related": "",
|
||||
"mealplan_autoinclude_related_desc": "",
|
||||
"merge_confirmation": "",
|
||||
"merge_selection": "",
|
||||
"merge_title": "",
|
||||
"min": "",
|
||||
"ml": "",
|
||||
"move_confirmation": "",
|
||||
"move_selection": "",
|
||||
"move_title": "",
|
||||
"no_more_images_found": "",
|
||||
"no_pinned_recipes": "",
|
||||
"not": "",
|
||||
"nothing": "",
|
||||
"nothing_planned_today": "",
|
||||
"on": "",
|
||||
"one_url_per_line": "",
|
||||
"open_data_help_text": "",
|
||||
"or": "",
|
||||
"ounce": "",
|
||||
"parameter_count": "",
|
||||
"paste_ingredients": "",
|
||||
"paste_ingredients_placeholder": "",
|
||||
"paste_json": "",
|
||||
"per_serving": "",
|
||||
"pint": "",
|
||||
"plan_share_desc": "",
|
||||
"plural_short": "",
|
||||
"plural_usage_info": "",
|
||||
"pound": "",
|
||||
"property_type_fdc_hint": "",
|
||||
"quart": "",
|
||||
"recipe_filter": "",
|
||||
"recipe_name": "",
|
||||
"recipe_property_info": "",
|
||||
"related_recipes": "",
|
||||
"remember_hours": "",
|
||||
"remember_search": "",
|
||||
"remove_selection": "",
|
||||
"reset_children": "",
|
||||
"reset_children_help": "",
|
||||
"reset_food_inheritance": "",
|
||||
"reset_food_inheritance_info": "",
|
||||
"reusable_help_text": "",
|
||||
"review_shopping": "",
|
||||
"save_filter": "",
|
||||
"searchFilterCreatedByHelp": "",
|
||||
"searchFilterObjectsAndHelp": "",
|
||||
"searchFilterObjectsAndNotHelp": "",
|
||||
"searchFilterObjectsHelp": "",
|
||||
"searchFilterObjectsOrNotHelp": "",
|
||||
"search_create_help_text": "",
|
||||
"search_import_help_text": "",
|
||||
"search_no_recipes": "",
|
||||
"search_rank": "",
|
||||
"seconds": "",
|
||||
"select_file": "",
|
||||
"select_food": "",
|
||||
"select_keyword": "",
|
||||
"select_recipe": "",
|
||||
"select_unit": "",
|
||||
"shared_with": "",
|
||||
"shopping_add_onhand": "",
|
||||
"shopping_add_onhand_desc": "",
|
||||
"shopping_auto_sync": "",
|
||||
"shopping_auto_sync_desc": "",
|
||||
"shopping_category_help": "",
|
||||
"shopping_recent_days": "",
|
||||
"shopping_recent_days_desc": "",
|
||||
"shopping_share": "",
|
||||
"shopping_share_desc": "",
|
||||
"show_books": "",
|
||||
"show_filters": "",
|
||||
"show_foods": "",
|
||||
"show_ingredient_overview": "",
|
||||
"show_ingredients_table": "",
|
||||
"show_keywords": "",
|
||||
"show_only_internal": "",
|
||||
"show_rating": "",
|
||||
"show_sortby": "",
|
||||
"show_split_screen": "",
|
||||
"show_sql": "",
|
||||
"show_step_ingredients": "",
|
||||
"show_step_ingredients_setting": "",
|
||||
"show_step_ingredients_setting_help": "",
|
||||
"show_units": "",
|
||||
"simple_mode": "",
|
||||
"sort_by": "",
|
||||
"sql_debug": "",
|
||||
"step_time_minutes": "",
|
||||
"substitute_children": "",
|
||||
"substitute_children_help": "",
|
||||
"substitute_help": "",
|
||||
"substitute_siblings": "",
|
||||
"substitute_siblings_help": "",
|
||||
"success_creating_resource": "",
|
||||
"success_deleting_resource": "",
|
||||
"success_fetching_resource": "",
|
||||
"success_merging_resource": "",
|
||||
"success_moving_resource": "",
|
||||
"success_updating_resource": "",
|
||||
"tbsp": "",
|
||||
"theUsernameCannotBeChanged": "",
|
||||
"times_cooked": "",
|
||||
"to_close": "",
|
||||
"to_navigate": "",
|
||||
"to_select": "",
|
||||
"today_recipes": "",
|
||||
"total": "",
|
||||
"tree_root": "",
|
||||
"tree_select": "",
|
||||
"tsp": "",
|
||||
"unsaved": "",
|
||||
"updatedon": "",
|
||||
"view_recipe": "",
|
||||
"warning_duplicate_filter": "",
|
||||
"warning_feature_beta": "",
|
||||
"warning_space_delete": ""
|
||||
}
|
||||
@@ -139,6 +139,7 @@
|
||||
"Fats": "",
|
||||
"File": "",
|
||||
"Files": "",
|
||||
"Finish": "",
|
||||
"First_name": "",
|
||||
"Food": "",
|
||||
"FoodInherit": "",
|
||||
|
||||
@@ -154,6 +154,7 @@
|
||||
"Fats": "",
|
||||
"File": "",
|
||||
"Files": "",
|
||||
"Finish": "",
|
||||
"First_name": "",
|
||||
"Food": "",
|
||||
"FoodInherit": "",
|
||||
|
||||
@@ -146,6 +146,7 @@
|
||||
"Fats": "Fett",
|
||||
"File": "Fil",
|
||||
"Files": "Filer",
|
||||
"Finish": "",
|
||||
"First_name": "Fornavn",
|
||||
"Food": "Matretter",
|
||||
"FoodInherit": "Arvbare felt for matvarer",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -180,6 +180,7 @@
|
||||
"Fats": "Tłuszcze",
|
||||
"File": "Plik",
|
||||
"Files": "Pliki",
|
||||
"Finish": "",
|
||||
"First_name": "Imię",
|
||||
"Food": "Żywność",
|
||||
"FoodInherit": "Pola dziedziczone w żywności",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"Auto_Sort_Help": "Mover todos os ingredientes para o passo mais indicado.",
|
||||
"Automate": "Automatizar",
|
||||
"Automation": "Automação",
|
||||
"BatchDeleteConfirm": "",
|
||||
"Books": "Livros",
|
||||
"Calculator": "Calculadora",
|
||||
"Calories": "Calorias",
|
||||
@@ -103,6 +104,7 @@
|
||||
"Fats": "Gorduras",
|
||||
"File": "Ficheiro",
|
||||
"Files": "Ficheiros",
|
||||
"Finish": "",
|
||||
"Food": "Comida",
|
||||
"FoodInherit": "Campos herdados por comida",
|
||||
"FoodNotOnHand": "Não têm {food} disponível.",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -133,6 +133,7 @@
|
||||
"Fats": "Grăsimi",
|
||||
"File": "Fișier",
|
||||
"Files": "Fișiere",
|
||||
"Finish": "",
|
||||
"First_name": "Prenume",
|
||||
"Food": "Mâncare",
|
||||
"FoodInherit": "Câmpuri moștenite de alimente",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -191,6 +191,7 @@
|
||||
"Fats": "Fett",
|
||||
"File": "Fil",
|
||||
"Files": "Filer",
|
||||
"Finish": "",
|
||||
"First_name": "Förnamn",
|
||||
"Food": "Livsmedel",
|
||||
"FoodInherit": "Ärftliga livsmedels fält",
|
||||
|
||||
@@ -154,6 +154,7 @@
|
||||
"Fats": "Yağlar",
|
||||
"File": "Dosya",
|
||||
"Files": "Dosyalar",
|
||||
"Finish": "",
|
||||
"First_name": "İsim",
|
||||
"Food": "Yiyecek",
|
||||
"FoodInherit": "Yiyeceğin Devralınabileceği Alanlar",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -154,6 +154,7 @@
|
||||
"Fats": "脂肪",
|
||||
"File": "文件",
|
||||
"Files": "文件",
|
||||
"Finish": "",
|
||||
"First_name": "名",
|
||||
"Food": "食物",
|
||||
"FoodInherit": "食物可继承的字段",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -877,6 +877,12 @@ export interface ApiEnterpriseSocialKeywordUpdateRequest {
|
||||
keyword: Omit<Keyword, 'label'|'parent'|'numchild'|'createdAt'|'updatedAt'|'fullName'>;
|
||||
}
|
||||
|
||||
export interface ApiEnterpriseSocialRecipeAipropertiesCreateRequest {
|
||||
id: number;
|
||||
recipe: Omit<Recipe, 'image'|'createdBy'|'createdAt'|'updatedAt'|'foodProperties'|'rating'|'lastCooked'>;
|
||||
provider?: number;
|
||||
}
|
||||
|
||||
export interface ApiEnterpriseSocialRecipeBatchUpdateUpdateRequest {
|
||||
recipeBatchUpdate: RecipeBatchUpdate;
|
||||
}
|
||||
@@ -1689,6 +1695,12 @@ export interface ApiPropertyUpdateRequest {
|
||||
property: Property;
|
||||
}
|
||||
|
||||
export interface ApiRecipeAipropertiesCreateRequest {
|
||||
id: number;
|
||||
recipe: Omit<Recipe, 'image'|'createdBy'|'createdAt'|'updatedAt'|'foodProperties'|'rating'|'lastCooked'>;
|
||||
provider?: number;
|
||||
}
|
||||
|
||||
export interface ApiRecipeBatchUpdateUpdateRequest {
|
||||
recipeBatchUpdate: RecipeBatchUpdate;
|
||||
}
|
||||
@@ -5574,6 +5586,57 @@ export class ApiApi extends runtime.BaseAPI {
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiEnterpriseSocialRecipeAipropertiesCreateRaw(requestParameters: ApiEnterpriseSocialRecipeAipropertiesCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Recipe>> {
|
||||
if (requestParameters['id'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'id',
|
||||
'Required parameter "id" was null or undefined when calling apiEnterpriseSocialRecipeAipropertiesCreate().'
|
||||
);
|
||||
}
|
||||
|
||||
if (requestParameters['recipe'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'recipe',
|
||||
'Required parameter "recipe" was null or undefined when calling apiEnterpriseSocialRecipeAipropertiesCreate().'
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
if (requestParameters['provider'] != null) {
|
||||
queryParameters['provider'] = requestParameters['provider'];
|
||||
}
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
headerParameters['Content-Type'] = 'application/json';
|
||||
|
||||
if (this.configuration && this.configuration.apiKey) {
|
||||
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
|
||||
}
|
||||
|
||||
const response = await this.request({
|
||||
path: `/api/enterprise-social-recipe/{id}/aiproperties/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
|
||||
method: 'POST',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: RecipeToJSON(requestParameters['recipe']),
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => RecipeFromJSON(jsonValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiEnterpriseSocialRecipeAipropertiesCreate(requestParameters: ApiEnterpriseSocialRecipeAipropertiesCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Recipe> {
|
||||
const response = await this.apiEnterpriseSocialRecipeAipropertiesCreateRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
@@ -12351,6 +12414,57 @@ export class ApiApi extends runtime.BaseAPI {
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiRecipeAipropertiesCreateRaw(requestParameters: ApiRecipeAipropertiesCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Recipe>> {
|
||||
if (requestParameters['id'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'id',
|
||||
'Required parameter "id" was null or undefined when calling apiRecipeAipropertiesCreate().'
|
||||
);
|
||||
}
|
||||
|
||||
if (requestParameters['recipe'] == null) {
|
||||
throw new runtime.RequiredError(
|
||||
'recipe',
|
||||
'Required parameter "recipe" was null or undefined when calling apiRecipeAipropertiesCreate().'
|
||||
);
|
||||
}
|
||||
|
||||
const queryParameters: any = {};
|
||||
|
||||
if (requestParameters['provider'] != null) {
|
||||
queryParameters['provider'] = requestParameters['provider'];
|
||||
}
|
||||
|
||||
const headerParameters: runtime.HTTPHeaders = {};
|
||||
|
||||
headerParameters['Content-Type'] = 'application/json';
|
||||
|
||||
if (this.configuration && this.configuration.apiKey) {
|
||||
headerParameters["Authorization"] = await this.configuration.apiKey("Authorization"); // ApiKeyAuth authentication
|
||||
}
|
||||
|
||||
const response = await this.request({
|
||||
path: `/api/recipe/{id}/aiproperties/`.replace(`{${"id"}}`, encodeURIComponent(String(requestParameters['id']))),
|
||||
method: 'POST',
|
||||
headers: headerParameters,
|
||||
query: queryParameters,
|
||||
body: RecipeToJSON(requestParameters['recipe']),
|
||||
}, initOverrides);
|
||||
|
||||
return new runtime.JSONApiResponse(response, (jsonValue) => RecipeFromJSON(jsonValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
async apiRecipeAipropertiesCreate(requestParameters: ApiRecipeAipropertiesCreateRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Recipe> {
|
||||
const response = await this.apiRecipeAipropertiesCreateRaw(requestParameters, initOverrides);
|
||||
return await response.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* logs request counts to redis cache total/per user/
|
||||
*/
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</v-list-item>
|
||||
<v-list-item link prepend-icon="fa-solid fa-arrows-to-dot" :disabled="!selectedFood">
|
||||
{{ $t('Merge') }}
|
||||
<model-merge-dialog :source="selectedFood" model="Food"
|
||||
<model-merge-dialog :source="[selectedFood]" model="Food"
|
||||
@change="(obj: Food) => {selectedFood = obj;refreshPage()} "></model-merge-dialog>
|
||||
</v-list-item>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
</v-list-item>
|
||||
<v-list-item link prepend-icon="fa-solid fa-arrows-to-dot" :disabled="!selectedUnit">
|
||||
{{ $t('Merge') }}
|
||||
<model-merge-dialog :source="selectedUnit" model="Unit"
|
||||
<model-merge-dialog :source="[selectedUnit]" model="Unit"
|
||||
@change="(obj: Food) => {selectedUnit = obj;refreshPage()} "></model-merge-dialog>
|
||||
</v-list-item>
|
||||
<v-list-item link prepend-icon="$automation" :disabled="!selectedUnit">
|
||||
@@ -117,12 +117,12 @@
|
||||
@update:modelValue="item.changed = true" :precision="2"></v-number-input>
|
||||
</template>
|
||||
<template v-slot:item.unit="{ item }">
|
||||
<model-select model="Unit" v-model="item.unit" :label="$t('Unit')" density="compact" hide-details allow-create append-to-body
|
||||
<model-select model="Unit" v-model="item.unit" density="compact" hide-details allow-create append-to-body
|
||||
@update:modelValue="item.changed = true">
|
||||
</model-select>
|
||||
</template>
|
||||
<template v-slot:item.food="{ item }">
|
||||
<model-select model="Food" v-model="item.food" :label="$t('Food')" density="compact" hide-details allow-create append-to-body
|
||||
<model-select model="Food" v-model="item.food" density="compact" hide-details allow-create append-to-body
|
||||
@update:modelValue="item.changed = true"></model-select>
|
||||
</template>
|
||||
<template v-slot:item.note="{ item }">
|
||||
|
||||
@@ -49,10 +49,10 @@
|
||||
<td>
|
||||
{{ ingredient.food.name }}
|
||||
<!-- TODO weird mixture of using ingredients but not in the correct relation to the recipe not good, properly sort out and add easy unitconversion/food edit features -->
|
||||
<!-- <v-btn variant="outlined" block>-->
|
||||
<!-- {{ ingredient.food.name }}-->
|
||||
<!-- <model-edit-dialog model="Food" :item="ingredient.food!" @save="args => ingredient.food = args"></model-edit-dialog>-->
|
||||
<!-- </v-btn>-->
|
||||
<!-- <v-btn variant="outlined" block>-->
|
||||
<!-- {{ ingredient.food.name }}-->
|
||||
<!-- <model-edit-dialog model="Food" :item="ingredient.food!" @save="args => ingredient.food = args"></model-edit-dialog>-->
|
||||
<!-- </v-btn>-->
|
||||
<!-- <v-chip v-if="ingredient.unit && ingredient.food.propertiesFoodUnit && ingredient.unit.id == ingredient.food.propertiesFoodUnit.id" color="success"-->
|
||||
<!-- size="small">{{ ingredient.unit.name }}-->
|
||||
<!-- </v-chip>-->
|
||||
@@ -73,7 +73,8 @@
|
||||
@click="fdcSelectedIngredient = ingredient; fdcDialog = true"></v-btn>
|
||||
<v-btn @click="updateFoodFdcData(ingredient)" icon="fa-solid fa-arrows-rotate" size="small" density="compact" variant="plain"
|
||||
v-if="ingredient.food.fdcId"></v-btn>
|
||||
<v-btn @click="openFdcPage(ingredient.food.fdcId)" :href="`https://fdc.nal.usda.gov/food-details/${ingredient.food.fdcId}/nutrients`" target="_blank"
|
||||
<v-btn @click="openFdcPage(ingredient.food.fdcId)" :href="`https://fdc.nal.usda.gov/food-details/${ingredient.food.fdcId}/nutrients`"
|
||||
target="_blank"
|
||||
icon="fa-solid fa-arrow-up-right-from-square"
|
||||
size="small" variant="plain" v-if="ingredient.food.fdcId"></v-btn>
|
||||
</template>
|
||||
@@ -81,7 +82,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<v-number-input v-model="ingredient.food.propertiesFoodAmount" density="compact" hide-details @change="updateFood(ingredient)"
|
||||
:loading="ingredient.loading" style="min-width: 100px" control-variant="hidden" :precision="2">
|
||||
:loading="ingredient.loading" style="min-width: 100px" control-variant="hidden" :precision="2">
|
||||
|
||||
</v-number-input>
|
||||
</td>
|
||||
@@ -90,8 +91,10 @@
|
||||
:loading="ingredient.loading"></model-select>
|
||||
</td>
|
||||
<td v-for="p in ingredient.food.properties" v-bind:key="`${ingredient.food.id}_${p.propertyType.id}`">
|
||||
<v-number-input v-model="p.propertyAmount" density="compact" hide-details v-if="p.propertyAmount != null" @change="updateFood(ingredient)" :precision="2"
|
||||
:loading="ingredient.loading" @click:clear="deleteFoodProperty(p, ingredient)" style="min-width: 120px" control-variant="hidden" clearable>
|
||||
<v-number-input v-model="p.propertyAmount" density="compact" hide-details v-if="p.propertyAmount != null" @change="updateFood(ingredient)"
|
||||
:precision="2"
|
||||
:loading="ingredient.loading" @click:clear="deleteFoodProperty(p, ingredient)" style="min-width: 120px" control-variant="hidden"
|
||||
clearable>
|
||||
|
||||
</v-number-input>
|
||||
|
||||
@@ -104,11 +107,10 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<!-- TODO remove once append to body for model select is working properly -->
|
||||
<v-spacer style="margin-top: 120px;"></v-spacer>
|
||||
</v-table>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col>
|
||||
|
||||
<v-card prepend-icon="fa-solid fa-calculator" :title="$t('Calculator')">
|
||||
<v-card-text>
|
||||
<v-row dense>
|
||||
|
||||
@@ -837,7 +837,7 @@ function deleteStep(step: SourceImportStep) {
|
||||
|
||||
function handleMergeAllSteps(): void {
|
||||
if (importResponse.value.recipe && importResponse.value.recipe.steps) {
|
||||
mergeAllSteps(importResponse.value.recipe.steps)
|
||||
importResponse.value.recipe.steps = mergeAllSteps(importResponse.value.recipe.steps)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -931,7 +931,10 @@ function setAllKeywordsImportStatus(status: boolean) {
|
||||
* add a new (empty) step at the end of the step list
|
||||
*/
|
||||
function addStep() {
|
||||
importResponse.value.recipe?.steps.push({} as SourceImportStep)
|
||||
importResponse.value.recipe?.steps.push({
|
||||
ingredients: [],
|
||||
instruction: ''
|
||||
} as SourceImportStep)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<v-container :class="{'ps-0 pe-0 pt-0': mobile}">
|
||||
<recipe-view v-model="recipe"></recipe-view>
|
||||
<v-defaults-provider :defaults="(useUserPreferenceStore().isPrintMode ? {VCard: {variant: 'flat'}} : {})">
|
||||
|
||||
<div class="mt-2" v-if="isShared && Object.keys(recipe).length > 0">
|
||||
<import-tandoor-dialog></import-tandoor-dialog>
|
||||
</div>
|
||||
<recipe-view v-model="recipe"></recipe-view>
|
||||
|
||||
<div class="mt-2" v-if="isShared && Object.keys(recipe).length > 0">
|
||||
<import-tandoor-dialog></import-tandoor-dialog>
|
||||
</div>
|
||||
</v-defaults-provider>
|
||||
|
||||
</v-container>
|
||||
|
||||
@@ -56,6 +58,12 @@ function refreshData(recipeId: string) {
|
||||
recipe.value = r
|
||||
title.value = recipe.value.name
|
||||
|
||||
setTimeout(() => {
|
||||
if (useUserPreferenceStore().isPrintMode) {
|
||||
window.print()
|
||||
}
|
||||
}, 500)
|
||||
|
||||
if (useUserPreferenceStore().isAuthenticated) {
|
||||
api.apiViewLogCreate({viewLog: {recipe: Number(recipeId)} as ViewLog})
|
||||
}
|
||||
|
||||
@@ -788,7 +788,7 @@ const filters = ref({
|
||||
enabled: false,
|
||||
default: undefined,
|
||||
is: VNumberInput,
|
||||
modelValue: useRouteQuery('timescookedGte', undefined, {transform: Number}),
|
||||
modelValue: useRouteQuery('timescooked', undefined, {transform: Number}),
|
||||
},
|
||||
timescookedGte: {
|
||||
id: 'timescookedGte',
|
||||
|
||||
@@ -34,13 +34,13 @@
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<template v-if="totalRecipes > 0">
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="recent" v-if="totalRecipes > 5"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="new"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="recent" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="new" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="keyword" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="random" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="created_by" v-if="totalRecipes > 5"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="2" mode="rating" v-if="totalRecipes > 5"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="keyword" v-if="totalRecipes > 5"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="random" v-if="totalRecipes > 0"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="created_by" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="2" mode="rating" v-if="totalRecipes > 10"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="keyword" v-if="totalRecipes > 25"></horizontal-recipe-scroller>
|
||||
<horizontal-recipe-scroller :skeletons="4" mode="random" v-if="totalRecipes > 25"></horizontal-recipe-scroller>
|
||||
|
||||
<v-row>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import {acceptHMRUpdate, defineStore} from 'pinia'
|
||||
import {useStorage} from "@vueuse/core";
|
||||
import {ErrorMessageType, PreparedMessage, useMessageStore} from "@/stores/MessageStore";
|
||||
import {ApiApi, ServerSettings, Space, UserPreference, UserSpace} from "@/openapi";
|
||||
import {ApiApi, ServerSettings, Space, Unit, UserPreference, UserSpace} from "@/openapi";
|
||||
import {ShoppingGroupingOptions} from "@/types/Shopping";
|
||||
import {computed, ComputedRef, ref} from "vue";
|
||||
import {DeviceSettings} from "@/types/settings";
|
||||
import {useTheme} from "vuetify";
|
||||
import {useRouter} from "vue-router";
|
||||
import {useRouteQuery} from "@vueuse/router";
|
||||
|
||||
const DEVICE_SETTINGS_KEY = 'TANDOOR_DEVICE_SETTINGS'
|
||||
const USER_PREFERENCE_KEY = 'TANDOOR_USER_PREFERENCE'
|
||||
@@ -50,6 +51,16 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
|
||||
*/
|
||||
const initCompleted = ref(false)
|
||||
|
||||
/**
|
||||
* load the default unit to the store for easy use in editors and more
|
||||
*/
|
||||
const defaultUnitObj = ref<Unit | null>(null)
|
||||
|
||||
/**
|
||||
* detect if print mode is activated by checking for "print" query parameter
|
||||
*/
|
||||
const isPrintMode = useRouteQuery('print', false, {transform: Boolean})
|
||||
|
||||
const theme = useTheme()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -77,6 +88,7 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
|
||||
userSettings.value = r[0]
|
||||
isAuthenticated.value = true
|
||||
updateTheme()
|
||||
loadDefaultUnit()
|
||||
} else {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, r)
|
||||
}
|
||||
@@ -87,6 +99,28 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* load the default unit from the backend
|
||||
* TODO migrate to nested serializer but requires actually creating the unit as currently its possible the default unit does not exist yet
|
||||
*/
|
||||
function loadDefaultUnit() {
|
||||
let api = new ApiApi()
|
||||
|
||||
if (userSettings.value.defaultUnit) {
|
||||
api.apiUnitList({query: userSettings.value.defaultUnit}).then(r => {
|
||||
r.results.forEach(u => {
|
||||
if (u.name == userSettings.value.defaultUnit) {
|
||||
defaultUnitObj.value = u
|
||||
}
|
||||
})
|
||||
}).catch(err => {
|
||||
if (err.response.status != 403) {
|
||||
useMessageStore().addError(ErrorMessageType.FETCH_ERROR, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* persist changes to user settings to DB
|
||||
*/
|
||||
@@ -222,10 +256,10 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
|
||||
* applies user settings regarding themes/styling
|
||||
*/
|
||||
function updateTheme() {
|
||||
if (userSettings.value.theme == 'TANDOOR') {
|
||||
theme.change('light')
|
||||
} else if (userSettings.value.theme == 'TANDOOR_DARK') {
|
||||
if (userSettings.value.theme == 'TANDOOR_DARK' && !isPrintMode.value) {
|
||||
theme.change('dark')
|
||||
} else {
|
||||
theme.change('light')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +287,9 @@ export const useUserPreferenceStore = defineStore('user_preference_store', () =>
|
||||
spaces,
|
||||
activeUserSpace,
|
||||
isAuthenticated,
|
||||
isPrintMode,
|
||||
initCompleted,
|
||||
defaultUnitObj,
|
||||
loadUserSettings,
|
||||
loadServerSettings,
|
||||
updateUserSettings,
|
||||
|
||||
@@ -6,6 +6,9 @@ import {useUserPreferenceStore} from "@/stores/UserPreferenceStore.ts";
|
||||
*/
|
||||
export function roundDecimals(num: number) {
|
||||
let decimals = useUserPreferenceStore().userSettings.ingredientDecimals
|
||||
if (decimals === undefined) {
|
||||
decimals = 2
|
||||
}
|
||||
return Number(num.toFixed(decimals))
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {aliases, fa} from 'vuetify/iconsets/fa'
|
||||
// Composables
|
||||
import {createVuetify} from 'vuetify'
|
||||
import {DateTime} from "luxon";
|
||||
import {af, ar, az, bg, ca, ckb, cs, da, de, el, en, es, et, fi, fr, he, hr, hu, id, it, ja, km, ko, lt, lv, nl, no, pl, pt, ro, ru, sk, sl, srCyrl, srLatn, sv, th, tr, uk, vi, zhHans, zhHant} from "vuetify/locale";
|
||||
|
||||
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
||||
export default createVuetify({
|
||||
@@ -22,17 +23,23 @@ export default createVuetify({
|
||||
maxWidth: '1400px'
|
||||
},
|
||||
// always localize the date display of DateInputs
|
||||
VDateInput: {
|
||||
displayFormat : (date: Date) => DateTime.fromJSDate(date).toLocaleString()
|
||||
},
|
||||
// VDateInput: {
|
||||
// displayFormat: (date: Date) => DateTime.fromJSDate(date).toLocaleString()
|
||||
// },
|
||||
// always use color for switches to properly see if enabled or not
|
||||
VSwitch: {
|
||||
color: 'primary'
|
||||
},
|
||||
// globally set the correct decimal seperator
|
||||
VNumberInput: {
|
||||
decimalSeparator: 0.1.toLocaleString().replace(/\d/g, '')
|
||||
}
|
||||
// VNumberInput: {
|
||||
// decimalSeparator: 0.1.toLocaleString().replace(/\d/g, '')
|
||||
// }
|
||||
},
|
||||
locale: {
|
||||
locale: 'en',
|
||||
fallback: 'en',
|
||||
messages: {af, ar, az, bg, ca, ckb, cs, da, de, el, en, es, et, fi, fr, he, hr, hu, id, it, ja, km, ko, lt, lv, nl, no, pl, pt, ro, ru, sk, sl, srCyrl, srLatn, sv, th, tr, uk, vi, zhHans, zhHant},
|
||||
decimalSeparator: 0.1.toLocaleString().replace(/\d/g, '')
|
||||
},
|
||||
theme: {
|
||||
defaultTheme: 'light',
|
||||
|
||||
@@ -1417,10 +1417,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.17.tgz#e8b3a41f0be76499882a89e8ed40d86a70fa4b70"
|
||||
integrity sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==
|
||||
|
||||
"@vue/tsconfig@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.7.0.tgz#67044c847b7a137b8cbfd6b23104c36dbaf80d1d"
|
||||
integrity sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==
|
||||
"@vue/tsconfig@^0.8.1":
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.8.1.tgz#4732251fa58945024424385cf3be0b1708fad5fe"
|
||||
integrity sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==
|
||||
|
||||
"@vueform/multiselect@^2.6.11":
|
||||
version "2.6.11"
|
||||
@@ -1448,18 +1448,23 @@
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-13.6.0.tgz#49196025c96c7daeb591c20a54b61cc336af99b6"
|
||||
integrity sha512-rnIH7JvU7NjrpexTsl2Iwv0V0yAx9cw7+clymjKuLSXG0QMcLD0LDgdNmXic+qL0SGvgSVPEpM9IDO/wqo1vkQ==
|
||||
|
||||
"@vueuse/router@^13.6.0":
|
||||
version "13.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/router/-/router-13.6.0.tgz#29456dab42eb75a0dc5fe4c62f59dd3f7c21a6ab"
|
||||
integrity sha512-iXRwR4K7nz4PReW0QudhnM9NtYGvN4KrskFgF9G7NouM43big3bpSNRRocJKFWK7iu97ww5y82B3QA2zz3S/vw==
|
||||
"@vueuse/router@^13.9.0":
|
||||
version "13.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/router/-/router-13.9.0.tgz#44235e6732a30b53d1c8e2ef13ce783fdd189ca6"
|
||||
integrity sha512-7AYay8Pv/0fC4D0eygbIyZuLyVs+9D7dsnO5D8aqat9qcOz91v/XFWR667WE1+p+OkU0ib+FjQUdnTVBNoIw8g==
|
||||
dependencies:
|
||||
"@vueuse/shared" "13.6.0"
|
||||
"@vueuse/shared" "13.9.0"
|
||||
|
||||
"@vueuse/shared@13.6.0":
|
||||
version "13.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-13.6.0.tgz#872fdbd725fb4e3a12bd5aab85af9a5db0b1e481"
|
||||
integrity sha512-pDykCSoS2T3fsQrYqf9SyF0QXWHmcGPQ+qiOVjlYSzlWd9dgppB2bFSM1GgKKkt7uzn0BBMV3IbJsUfHG2+BCg==
|
||||
|
||||
"@vueuse/shared@13.9.0":
|
||||
version "13.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-13.9.0.tgz#7168b4ed647e625b05eb4e7e80fe8aabd00e3923"
|
||||
integrity sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==
|
||||
|
||||
acorn@^8.14.0:
|
||||
version "8.15.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
|
||||
@@ -3343,10 +3348,10 @@ vite-plugin-vuetify@^2.1.1:
|
||||
debug "^4.3.3"
|
||||
upath "^2.0.1"
|
||||
|
||||
vite@7.1.5:
|
||||
version "7.1.5"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.5.tgz#4dbcb48c6313116689be540466fc80faa377be38"
|
||||
integrity sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==
|
||||
vite@7.1.11:
|
||||
version "7.1.11"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.11.tgz#4d006746112fee056df64985191e846ebfb6007e"
|
||||
integrity sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==
|
||||
dependencies:
|
||||
esbuild "^0.25.0"
|
||||
fdir "^6.5.0"
|
||||
@@ -3418,10 +3423,10 @@ vuedraggable@^4.1.0:
|
||||
dependencies:
|
||||
sortablejs "1.14.0"
|
||||
|
||||
vuetify@^3.9.7:
|
||||
version "3.9.7"
|
||||
resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-3.9.7.tgz#aea996f35111f25dd7e31ab956fbb40911841c24"
|
||||
integrity sha512-Ib8PB3ItcguCol8f0DXLpoGyy7FvoOYW23SEWqXX+in1CSItJZHxUXXGSus94m5JWqYqQrFiwCykbHm7UWPi4Q==
|
||||
vuetify@^3.10.3:
|
||||
version "3.10.3"
|
||||
resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-3.10.3.tgz#f04e507bb5efee6b52f11b2fd60a20dced1a8831"
|
||||
integrity sha512-psc7oZfjz3LwH96ZRzSm4iGcOKKoeoVZIyO5Q5xO4vcUfWYxobL7TvMQv53jv1PnNvaMIXWeVIrQmiyce5dpTg==
|
||||
|
||||
w3c-xmlserializer@^5.0.0:
|
||||
version "5.0.0"
|
||||
|
||||
Reference in New Issue
Block a user