Merge branch 'TandoorRecipes:develop' into Auto-Planner

This commit is contained in:
AquaticLava
2023-06-21 19:16:49 -06:00
committed by GitHub
86 changed files with 24920 additions and 15094 deletions

View File

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

View File

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

View File

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

View File

@@ -10,12 +10,13 @@ from treebeard.forms import movenodeform_factory
from cookbook.managers import DICTIONARY from cookbook.managers import DICTIONARY
from .models import (BookmarkletImport, Comment, CookLog, Food, FoodInheritField, ImportLog, from .models import (Automation, BookmarkletImport, Comment, CookLog, Food, FoodInheritField,
Ingredient, InviteLink, Keyword, MealPlan, MealType, NutritionInformation, ImportLog, Ingredient, InviteLink, Keyword, MealPlan, MealType,
Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchPreference, ShareLink, NutritionInformation, Property, PropertyType, Recipe, RecipeBook,
ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage, RecipeBookEntry, RecipeImport, SearchPreference, ShareLink, ShoppingList,
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage, Supermarket,
TelegramBot, Unit, UserFile, UserPreference, ViewLog, Automation, UserSpace) SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog, TelegramBot,
Unit, UnitConversion, UserFile, UserPreference, UserSpace, ViewLog)
class CustomUserAdmin(UserAdmin): class CustomUserAdmin(UserAdmin):
@@ -150,9 +151,16 @@ class KeywordAdmin(TreeAdmin):
admin.site.register(Keyword, KeywordAdmin) admin.site.register(Keyword, KeywordAdmin)
@admin.action(description='Delete Steps not part of a Recipe.')
def delete_unattached_steps(modeladmin, request, queryset):
with scopes_disabled():
Step.objects.filter(recipe=None).delete()
class StepAdmin(admin.ModelAdmin): class StepAdmin(admin.ModelAdmin):
list_display = ('name', 'order',) list_display = ('name', 'order',)
search_fields = ('name',) search_fields = ('name',)
actions = [delete_unattached_steps]
admin.site.register(Step, StepAdmin) admin.site.register(Step, StepAdmin)
@@ -201,9 +209,24 @@ class FoodAdmin(TreeAdmin):
admin.site.register(Food, FoodAdmin) admin.site.register(Food, FoodAdmin)
class UnitConversionAdmin(admin.ModelAdmin):
list_display = ('base_amount', 'base_unit', 'food', 'converted_amount', 'converted_unit')
search_fields = ('food__name', 'unit__name')
admin.site.register(UnitConversion, UnitConversionAdmin)
@admin.action(description='Delete Ingredients not part of a Recipe.')
def delete_unattached_ingredients(modeladmin, request, queryset):
with scopes_disabled():
Ingredient.objects.filter(step__recipe=None).delete()
class IngredientAdmin(admin.ModelAdmin): class IngredientAdmin(admin.ModelAdmin):
list_display = ('food', 'amount', 'unit') list_display = ('food', 'amount', 'unit')
search_fields = ('food__name', 'unit__name') search_fields = ('food__name', 'unit__name')
actions = [delete_unattached_ingredients]
admin.site.register(Ingredient, IngredientAdmin) admin.site.register(Ingredient, IngredientAdmin)
@@ -319,6 +342,20 @@ class ShareLinkAdmin(admin.ModelAdmin):
admin.site.register(ShareLink, ShareLinkAdmin) admin.site.register(ShareLink, ShareLinkAdmin)
class PropertyTypeAdmin(admin.ModelAdmin):
list_display = ('id', 'name')
admin.site.register(PropertyType, PropertyTypeAdmin)
class PropertyAdmin(admin.ModelAdmin):
list_display = ('property_amount', 'property_type')
admin.site.register(Property, PropertyAdmin)
class NutritionInformationAdmin(admin.ModelAdmin): class NutritionInformationAdmin(admin.ModelAdmin):
list_display = ('id',) list_display = ('id',)

View File

@@ -167,8 +167,25 @@ class ImportExportBase(forms.Form):
)) ))
class MultipleFileInput(forms.ClearableFileInput):
allow_multiple_selected = True
class MultipleFileField(forms.FileField):
def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", MultipleFileInput())
super().__init__(*args, **kwargs)
def clean(self, data, initial=None):
single_file_clean = super().clean
if isinstance(data, (list, tuple)):
result = [single_file_clean(d, initial) for d in data]
else:
result = single_file_clean(data, initial)
return result
class ImportForm(ImportExportBase): class ImportForm(ImportExportBase):
files = forms.FileField(required=True, widget=forms.ClearableFileInput(attrs={'multiple': True})) files = MultipleFileField(required=True)
duplicates = forms.BooleanField(help_text=_( duplicates = forms.BooleanField(help_text=_(
'To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'), 'To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'),
required=False) required=False)

View File

@@ -0,0 +1,11 @@
class CacheHelper:
space = None
BASE_UNITS_CACHE_KEY = None
PROPERTY_TYPE_CACHE_KEY = None
def __init__(self, space):
self.space = space
self.BASE_UNITS_CACHE_KEY = f'SPACE_{space.id}_BASE_UNITS'
self.PROPERTY_TYPE_CACHE_KEY = f'SPACE_{space.id}_PROPERTY_TYPES'

View File

@@ -41,6 +41,11 @@ def get_filetype(name):
# filetype argument can not be optional, otherwise this function will treat all images as if they were a jpeg # filetype argument can not be optional, otherwise this function will treat all images as if they were a jpeg
# Because it's no longer optional, no reason to return it # Because it's no longer optional, no reason to return it
def handle_image(request, image_object, filetype): def handle_image(request, image_object, filetype):
try:
Image.open(image_object).verify()
except Exception:
return None
if (image_object.size / 1000) > 500: # if larger than 500 kb compress if (image_object.size / 1000) > 500: # if larger than 500 kb compress
if filetype == '.jpeg' or filetype == '.jpg': if filetype == '.jpeg' or filetype == '.jpg':
return rescale_image_jpeg(image_object) return rescale_image_jpeg(image_object)

View File

@@ -0,0 +1,214 @@
from django.db.models import Q
from cookbook.models import Unit, SupermarketCategory, Property, PropertyType, Supermarket, SupermarketCategoryRelation, Food, Automation, UnitConversion, FoodProperty
class OpenDataImporter:
request = None
data = {}
slug_id_cache = {}
update_existing = False
use_metric = True
def __init__(self, request, data, update_existing=False, use_metric=True):
self.request = request
self.data = data
self.update_existing = update_existing
self.use_metric = use_metric
def _update_slug_cache(self, object_class, datatype):
self.slug_id_cache[datatype] = dict(object_class.objects.filter(space=self.request.space, open_data_slug__isnull=False).values_list('open_data_slug', 'id', ))
def import_units(self):
datatype = 'unit'
insert_list = []
for u in list(self.data[datatype].keys()):
insert_list.append(Unit(
name=self.data[datatype][u]['name'],
plural_name=self.data[datatype][u]['plural_name'],
base_unit=self.data[datatype][u]['base_unit'] if self.data[datatype][u]['base_unit'] != '' else None,
open_data_slug=u,
space=self.request.space
))
if self.update_existing:
return Unit.objects.bulk_create(insert_list, update_conflicts=True, update_fields=('name', 'plural_name', 'base_unit', 'open_data_slug'), unique_fields=('space', 'name',))
else:
return Unit.objects.bulk_create(insert_list, update_conflicts=True, update_fields=('open_data_slug',), unique_fields=('space', 'name',))
def import_category(self):
datatype = 'category'
insert_list = []
for k in list(self.data[datatype].keys()):
insert_list.append(SupermarketCategory(
name=self.data[datatype][k]['name'],
open_data_slug=k,
space=self.request.space
))
return SupermarketCategory.objects.bulk_create(insert_list, update_conflicts=True, update_fields=('open_data_slug',), unique_fields=('space', 'name',))
def import_property(self):
datatype = 'property'
insert_list = []
for k in list(self.data[datatype].keys()):
insert_list.append(PropertyType(
name=self.data[datatype][k]['name'],
unit=self.data[datatype][k]['unit'],
open_data_slug=k,
space=self.request.space
))
return PropertyType.objects.bulk_create(insert_list, update_conflicts=True, update_fields=('open_data_slug',), unique_fields=('space', 'name',))
def import_supermarket(self):
datatype = 'store'
self._update_slug_cache(SupermarketCategory, 'category')
insert_list = []
for k in list(self.data[datatype].keys()):
insert_list.append(Supermarket(
name=self.data[datatype][k]['name'],
open_data_slug=k,
space=self.request.space
))
# always add open data slug if matching supermarket is found, otherwise relation might fail
supermarkets = Supermarket.objects.bulk_create(insert_list, unique_fields=('space', 'name',), update_conflicts=True, update_fields=('open_data_slug',))
self._update_slug_cache(Supermarket, 'store')
insert_list = []
for k in list(self.data[datatype].keys()):
relations = []
order = 0
for c in self.data[datatype][k]['categories']:
relations.append(
SupermarketCategoryRelation(
supermarket_id=self.slug_id_cache[datatype][k],
category_id=self.slug_id_cache['category'][c],
order=order,
)
)
order += 1
SupermarketCategoryRelation.objects.bulk_create(relations, ignore_conflicts=True, unique_fields=('supermarket', 'category',))
return supermarkets
def import_food(self):
identifier_list = []
datatype = 'food'
for k in list(self.data[datatype].keys()):
identifier_list.append(self.data[datatype][k]['name'])
identifier_list.append(self.data[datatype][k]['plural_name'])
existing_objects_flat = []
existing_objects = {}
for f in Food.objects.filter(space=self.request.space).filter(name__in=identifier_list).values_list('id', 'name', 'plural_name'):
existing_objects_flat.append(f[1])
existing_objects_flat.append(f[2])
existing_objects[f[1]] = f
existing_objects[f[2]] = f
self._update_slug_cache(Unit, 'unit')
self._update_slug_cache(PropertyType, 'property')
# pref_unit_key = 'preferred_unit_metric'
# pref_shopping_unit_key = 'preferred_packaging_unit_metric'
# if not self.use_metric:
# pref_unit_key = 'preferred_unit_imperial'
# pref_shopping_unit_key = 'preferred_packaging_unit_imperial'
insert_list = []
update_list = []
update_field_list = []
for k in list(self.data[datatype].keys()):
if not (self.data[datatype][k]['name'] in existing_objects_flat or self.data[datatype][k]['plural_name'] in existing_objects_flat):
insert_list.append({'data': {
'name': self.data[datatype][k]['name'],
'plural_name': self.data[datatype][k]['plural_name'] if self.data[datatype][k]['plural_name'] != '' else None,
# 'preferred_unit_id': self.slug_id_cache['unit'][self.data[datatype][k][pref_unit_key]],
# 'preferred_shopping_unit_id': self.slug_id_cache['unit'][self.data[datatype][k][pref_shopping_unit_key]],
'supermarket_category_id': self.slug_id_cache['category'][self.data[datatype][k]['store_category']],
'fdc_id': self.data[datatype][k]['fdc_id'] if self.data[datatype][k]['fdc_id'] != '' else None,
'open_data_slug': k,
'space': self.request.space.id,
}})
else:
if self.data[datatype][k]['name'] in existing_objects:
existing_food_id = existing_objects[self.data[datatype][k]['name']][0]
else:
existing_food_id = existing_objects[self.data[datatype][k]['plural_name']][0]
if self.update_existing:
update_field_list = ['name', 'plural_name', 'preferred_unit_id', 'preferred_shopping_unit_id', 'supermarket_category_id', 'fdc_id', 'open_data_slug', ]
update_list.append(Food(
id=existing_food_id,
name=self.data[datatype][k]['name'],
plural_name=self.data[datatype][k]['plural_name'] if self.data[datatype][k]['plural_name'] != '' else None,
# preferred_unit_id=self.slug_id_cache['unit'][self.data[datatype][k][pref_unit_key]],
# preferred_shopping_unit_id=self.slug_id_cache['unit'][self.data[datatype][k][pref_shopping_unit_key]],
supermarket_category_id=self.slug_id_cache['category'][self.data[datatype][k]['store_category']],
fdc_id=self.data[datatype][k]['fdc_id'] if self.data[datatype][k]['fdc_id'] != '' else None,
open_data_slug=k,
))
else:
update_field_list = ['open_data_slug', ]
update_list.append(Food(id=existing_food_id, open_data_slug=k, ))
Food.load_bulk(insert_list, None)
if len(update_list) > 0:
Food.objects.bulk_update(update_list, update_field_list)
self._update_slug_cache(Food, 'food')
food_property_list = []
alias_list = []
for k in list(self.data[datatype].keys()):
for fp in self.data[datatype][k]['properties']['type_values']:
food_property_list.append(Property(
property_type_id=self.slug_id_cache['property'][fp['property_type']],
property_amount=fp['property_value'],
import_food_id=self.slug_id_cache['food'][k],
space=self.request.space,
))
# for a in self.data[datatype][k]['alias']:
# alias_list.append(Automation(
# param_1=a,
# param_2=self.data[datatype][k]['name'],
# space=self.request.space,
# created_by=self.request.user,
# ))
Property.objects.bulk_create(food_property_list, ignore_conflicts=True, unique_fields=('space', 'import_food_id', 'property_type',))
property_food_relation_list = []
for p in Property.objects.filter(space=self.request.space, import_food_id__isnull=False).values_list('import_food_id', 'id', ):
property_food_relation_list.append(Food.properties.through(food_id=p[0], property_id=p[1]))
FoodProperty.objects.bulk_create(property_food_relation_list, ignore_conflicts=True, unique_fields=('food_id', 'property_id',))
# Automation.objects.bulk_create(alias_list, ignore_conflicts=True, unique_fields=('space', 'param_1', 'param_2',))
return insert_list + update_list
def import_conversion(self):
datatype = 'conversion'
insert_list = []
for k in list(self.data[datatype].keys()):
insert_list.append(UnitConversion(
base_amount=self.data[datatype][k]['base_amount'],
base_unit_id=self.slug_id_cache['unit'][self.data[datatype][k]['base_unit']],
converted_amount=self.data[datatype][k]['converted_amount'],
converted_unit_id=self.slug_id_cache['unit'][self.data[datatype][k]['converted_unit']],
food_id=self.slug_id_cache['food'][self.data[datatype][k]['food']],
open_data_slug=k,
space=self.request.space,
created_by=self.request.user,
))
return UnitConversion.objects.bulk_create(insert_list, ignore_conflicts=True, unique_fields=('space', 'base_unit', 'converted_unit', 'food', 'open_data_slug'))

View File

@@ -0,0 +1,71 @@
from django.core.cache import caches
from cookbook.helper.cache_helper import CacheHelper
from cookbook.helper.unit_conversion_helper import UnitConversionHelper
from cookbook.models import PropertyType, Unit, Food, Property, Recipe, Step
class FoodPropertyHelper:
space = None
def __init__(self, space):
"""
Helper to perform food property calculations
:param space: space to limit scope to
"""
self.space = space
def calculate_recipe_properties(self, recipe):
"""
Calculate all food properties for a given recipe.
:param recipe: recipe to calculate properties for
:return: dict of with property keys and total/food values for each property available
"""
ingredients = []
computed_properties = {}
for s in recipe.steps.all():
ingredients += s.ingredients.all()
property_types = caches['default'].get(CacheHelper(self.space).PROPERTY_TYPE_CACHE_KEY, None)
if not property_types:
property_types = PropertyType.objects.filter(space=self.space).all()
caches['default'].set(CacheHelper(self.space).PROPERTY_TYPE_CACHE_KEY, property_types, 60 * 60) # cache is cleared on property type save signal so long duration is fine
for fpt in property_types:
computed_properties[fpt.id] = {'id': fpt.id, 'name': fpt.name, 'icon': fpt.icon, 'description': fpt.description, 'unit': fpt.unit, 'food_values': {}, 'total_value': 0, 'missing_value': False}
uch = UnitConversionHelper(self.space)
for i in ingredients:
if i.food is not None:
conversions = uch.get_conversions(i)
for pt in property_types:
found_property = False
if i.food.properties_food_amount == 0 or i.food.properties_food_unit is None:
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': i.food.name, 'value': 0}
computed_properties[pt.id]['missing_value'] = i.food.properties_food_unit is None
else:
for p in i.food.properties.all():
if p.property_type == pt:
for c in conversions:
if c.unit == i.food.properties_food_unit:
found_property = True
computed_properties[pt.id]['total_value'] += (c.amount / i.food.properties_food_amount) * p.property_amount
computed_properties[pt.id]['food_values'] = self.add_or_create(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:
computed_properties[pt.id]['missing_value'] = True
computed_properties[pt.id]['food_values'][i.food.id] = {'id': i.food.id, 'food': i.food.name, 'value': 0}
return computed_properties
# small dict helper to add to existing key or create new, probably a better way of doing this
# TODO move to central helper ?
@staticmethod
def add_or_create(d, key, value, food):
if key in d:
d[key]['value'] += value
else:
d[key] = {'id': food.id, 'food': food.name, 'value': value}
return d

View File

@@ -1,5 +1,6 @@
# import random # import random
import re import re
import traceback
from html import unescape from html import unescape
from django.core.cache import caches from django.core.cache import caches
@@ -12,7 +13,8 @@ from recipe_scrapers._utils import get_host_name, get_minutes
# from cookbook.helper import recipe_url_import as helper # from cookbook.helper import recipe_url_import as helper
from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.models import Automation, Keyword from cookbook.models import Automation, Keyword, PropertyType
# from unicodedata import decomposition # from unicodedata import decomposition
@@ -33,6 +35,9 @@ def get_from_scraper(scrape, request):
except Exception: except Exception:
recipe_json['name'] = '' recipe_json['name'] = ''
if isinstance(recipe_json['name'], list) and len(recipe_json['name']) > 0:
recipe_json['name'] = recipe_json['name'][0]
try: try:
description = scrape.description() or None description = scrape.description() or None
except Exception: except Exception:
@@ -193,7 +198,14 @@ def get_from_scraper(scrape, request):
except Exception: except Exception:
pass pass
if recipe_json['source_url']: try:
recipe_json['properties'] = get_recipe_properties(request.space, scrape.schema.nutrients())
print(recipe_json['properties'])
except Exception:
traceback.print_exc()
pass
if 'source_url' in recipe_json and recipe_json['source_url']:
automations = Automation.objects.filter(type=Automation.INSTRUCTION_REPLACE, space=request.space, disabled=False).only('param_1', 'param_2', 'param_3').order_by('order').all()[:512] automations = Automation.objects.filter(type=Automation.INSTRUCTION_REPLACE, space=request.space, disabled=False).only('param_1', 'param_2', 'param_3').order_by('order').all()[:512]
for a in automations: for a in automations:
if re.match(a.param_1, (recipe_json['source_url'])[:512]): if re.match(a.param_1, (recipe_json['source_url'])[:512]):
@@ -203,6 +215,30 @@ def get_from_scraper(scrape, request):
return recipe_json return recipe_json
def get_recipe_properties(space, property_data):
# {'servingSize': '1', 'calories': '302 kcal', 'proteinContent': '7,66g', 'fatContent': '11,56g', 'carbohydrateContent': '41,33g'}
properties = {
"property-calories": "calories",
"property-carbohydrates": "carbohydrateContent",
"property-proteins": "proteinContent",
"property-fats": "fatContent",
}
recipe_properties = []
for pt in PropertyType.objects.filter(space=space, open_data_slug__in=list(properties.keys())).all():
for p in list(properties.keys()):
if pt.open_data_slug == p:
if properties[p] in property_data:
recipe_properties.append({
'property_type': {
'id': pt.id,
'name': pt.name,
},
'property_amount': parse_servings(property_data[properties[p]]) / float(property_data['servingSize']),
})
return recipe_properties
def get_from_youtube_scraper(url, request): def get_from_youtube_scraper(url, request):
"""A YouTube Information Scraper.""" """A YouTube Information Scraper."""
kw, created = Keyword.objects.get_or_create(name='YouTube', space=request.space) kw, created = Keyword.objects.get_or_create(name='YouTube', space=request.space)

View File

@@ -0,0 +1,141 @@
from django.core.cache import caches
from decimal import Decimal
from cookbook.helper.cache_helper import CacheHelper
from cookbook.models import Ingredient, Unit
CONVERSION_TABLE = {
'weight': {
'g': 1000,
'kg': 1,
'ounce': 35.274,
'pound': 2.20462
},
'volume': {
'ml': 1000,
'l': 1,
'fluid_ounce': 33.814,
'pint': 2.11338,
'quart': 1.05669,
'gallon': 0.264172,
'tbsp': 67.628,
'tsp': 202.884,
'imperial_fluid_ounce': 35.1951,
'imperial_pint': 1.75975,
'imperial_quart': 0.879877,
'imperial_gallon': 0.219969,
'imperial_tbsp': 56.3121,
'imperial_tsp': 168.936,
},
}
BASE_UNITS_WEIGHT = list(CONVERSION_TABLE['weight'].keys())
BASE_UNITS_VOLUME = list(CONVERSION_TABLE['volume'].keys())
class ConversionException(Exception):
pass
class UnitConversionHelper:
space = None
def __init__(self, space):
"""
Initializes unit conversion helper
:param space: space to perform conversions on
"""
self.space = space
@staticmethod
def convert_from_to(from_unit, to_unit, amount):
"""
Convert from one base unit to another. Throws ConversionException if trying to convert between different systems (weight/volume) or if units are not supported.
:param from_unit: str unit to convert from
:param to_unit: str unit to convert to
:param amount: amount to convert
:return: Decimal converted amount
"""
system = None
if from_unit in BASE_UNITS_WEIGHT and to_unit in BASE_UNITS_WEIGHT:
system = 'weight'
if from_unit in BASE_UNITS_VOLUME and to_unit in BASE_UNITS_VOLUME:
system = 'volume'
if not system:
raise ConversionException('Trying to convert units not existing or not in one unit system (weight/volume)')
return Decimal(amount / Decimal(CONVERSION_TABLE[system][from_unit] / CONVERSION_TABLE[system][to_unit]))
def base_conversions(self, ingredient_list):
"""
Calculates all possible base unit conversions for each ingredient give.
Converts to all common base units IF they exist in the unit database of the space.
For useful results all ingredients passed should be of the same food, otherwise filtering afterwards might be required.
:param ingredient_list: list of ingredients to convert
:return: ingredient list with appended conversions
"""
base_conversion_ingredient_list = ingredient_list.copy()
for i in ingredient_list:
try:
conversion_unit = i.unit.name
if i.unit.base_unit:
conversion_unit = i.unit.base_unit
# TODO allow setting which units to convert to? possibly only once conversions become visible
units = caches['default'].get(CacheHelper(self.space).BASE_UNITS_CACHE_KEY, None)
if not units:
units = Unit.objects.filter(space=self.space, base_unit__in=(BASE_UNITS_VOLUME + BASE_UNITS_WEIGHT)).all()
caches['default'].set(CacheHelper(self.space).BASE_UNITS_CACHE_KEY, units, 60 * 60) # cache is cleared on unit save signal so long duration is fine
for u in units:
try:
ingredient = Ingredient(amount=self.convert_from_to(conversion_unit, u.base_unit, i.amount), unit=u, food=ingredient_list[0].food, )
if not any((x.unit.name == ingredient.unit.name or x.unit.base_unit == ingredient.unit.name) for x in base_conversion_ingredient_list):
base_conversion_ingredient_list.append(ingredient)
except ConversionException:
pass
except Exception:
pass
return base_conversion_ingredient_list
def get_conversions(self, ingredient):
"""
Converts an ingredient to all possible conversions based on the custom unit conversion database.
After that passes conversion to UnitConversionHelper.base_conversions() to get all base conversions possible.
:param ingredient: Ingredient object
:return: list of ingredients with all possible custom and base conversions
"""
conversions = [ingredient]
if ingredient.unit:
for c in ingredient.unit.unit_conversion_base_relation.all():
if c.space == self.space:
r = self._uc_convert(c, ingredient.amount, ingredient.unit, ingredient.food)
if r and r not in conversions:
conversions.append(r)
for c in ingredient.unit.unit_conversion_converted_relation.all():
if c.space == self.space:
r = self._uc_convert(c, ingredient.amount, ingredient.unit, ingredient.food)
if r and r not in conversions:
conversions.append(r)
conversions = self.base_conversions(conversions)
return conversions
def _uc_convert(self, uc, amount, unit, food):
"""
Helper to calculate values for custom unit conversions.
Converts given base values using the passed UnitConversion object into a converted Ingredient
:param uc: UnitConversion object
:param amount: base amount
:param unit: base unit
:param food: base food
:return: converted ingredient object from base amount/unit/food
"""
if uc.food is None or uc.food == food:
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)

View File

@@ -51,6 +51,12 @@ class NextcloudCookbook(Integration):
ingredients_added = False ingredients_added = False
for s in recipe_json['recipeInstructions']: for s in recipe_json['recipeInstructions']:
instruction_text = ''
if 'text' in s:
step = Step.objects.create(
instruction=s['text'], name=s['name'], space=self.request.space,
)
else:
step = Step.objects.create( step = Step.objects.create(
instruction=s, space=self.request.space, instruction=s, space=self.request.space,
) )
@@ -102,7 +108,6 @@ class NextcloudCookbook(Integration):
m = min % 60 m = min % 60
return f'PT{h}H{m}M0S' return f'PT{h}H{m}M0S'
def get_file_from_recipe(self, recipe): def get_file_from_recipe(self, recipe):
export = {} export = {}
@@ -133,7 +138,6 @@ class NextcloudCookbook(Integration):
export['recipeIngredient'] = recipeIngredient export['recipeIngredient'] = recipeIngredient
export['recipeInstructions'] = recipeInstructions export['recipeInstructions'] = recipeInstructions
return "recipe.json", json.dumps(export) return "recipe.json", json.dumps(export)
def get_files_from_recipes(self, recipes, el, cookie): def get_files_from_recipes(self, recipes, el, cookie):

View File

@@ -1,6 +1,7 @@
from io import BytesIO from io import BytesIO
import requests import requests
import validators
from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
@@ -67,6 +68,7 @@ class Plantoeat(Integration):
if image_url: if image_url:
try: try:
if validators.url(image_url, public=True):
response = requests.get(image_url) response = requests.get(image_url)
self.import_recipe_image(recipe, BytesIO(response.content)) self.import_recipe_image(recipe, BytesIO(response.content))
except Exception as e: except Exception as e:

View File

@@ -5,6 +5,7 @@ import requests
import validators import validators
from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.recipe_url_import import parse_servings, parse_servings_text, parse_time
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Ingredient, Recipe, Step from cookbook.models import Ingredient, Recipe, Step
@@ -18,19 +19,21 @@ class RecipeSage(Integration):
created_by=self.request.user, internal=True, created_by=self.request.user, internal=True,
space=self.request.space) space=self.request.space)
try:
if file['recipeYield'] != '': if file['recipeYield'] != '':
recipe.servings = int(file['recipeYield']) recipe.servings = parse_servings(file['recipeYield'])
recipe.servings_text = parse_servings_text(file['recipeYield'])
if file['totalTime'] != '': try:
recipe.waiting_time = int(file['totalTime']) - int(file['timePrep']) if 'totalTime' in file and file['totalTime'] != '':
recipe.working_time = parse_time(file['totalTime'])
if file['prepTime'] != '': if 'timePrep' in file and file['prepTime'] != '':
recipe.working_time = int(file['timePrep']) recipe.working_time = parse_time(file['timePrep'])
recipe.waiting_time = parse_time(file['totalTime']) - parse_time(file['timePrep'])
except Exception as e:
print('failed to parse time ', str(e))
recipe.save() recipe.save()
except Exception as e:
print('failed to parse yield or time ', str(e))
ingredient_parser = IngredientParser(self.request, True) ingredient_parser = IngredientParser(self.request, True)
ingredients_added = False ingredients_added = False

View File

@@ -22,9 +22,12 @@ class Rezeptsuitede(Integration):
name=recipe_xml.find('head').attrib['title'].strip(), name=recipe_xml.find('head').attrib['title'].strip(),
created_by=self.request.user, internal=True, space=self.request.space) created_by=self.request.user, internal=True, space=self.request.space)
try:
if recipe_xml.find('head').attrib['servingtype']: if recipe_xml.find('head').attrib['servingtype']:
recipe.servings = parse_servings(recipe_xml.find('head').attrib['servingtype'].strip()) recipe.servings = parse_servings(recipe_xml.find('head').attrib['servingtype'].strip())
recipe.servings_text = parse_servings_text(recipe_xml.find('head').attrib['servingtype'].strip()) recipe.servings_text = parse_servings_text(recipe_xml.find('head').attrib['servingtype'].strip())
except KeyError:
pass
if recipe_xml.find('remark') is not None: # description is a list of <li>'s with text if recipe_xml.find('remark') is not None: # description is a list of <li>'s with text
if recipe_xml.find('remark').find('line') is not None: if recipe_xml.find('remark').find('line') is not None:
@@ -50,6 +53,8 @@ class Rezeptsuitede(Integration):
for ingredient in recipe_xml.find('part').findall('ingredient'): for ingredient in recipe_xml.find('part').findall('ingredient'):
f = ingredient_parser.get_food(ingredient.attrib['item']) f = ingredient_parser.get_food(ingredient.attrib['item'])
u = ingredient_parser.get_unit(ingredient.attrib['unit']) u = ingredient_parser.get_unit(ingredient.attrib['unit'])
amount = 0
if ingredient.attrib['qty'].strip() != '':
amount, unit, note = ingredient_parser.parse_amount(ingredient.attrib['qty']) amount, unit, note = ingredient_parser.parse_amount(ingredient.attrib['qty'])
ingredient_step.ingredients.add(Ingredient.objects.create(food=f, unit=u, amount=amount, space=self.request.space, )) ingredient_step.ingredients.add(Ingredient.objects.create(food=f, unit=u, amount=amount, space=self.request.space, ))

View File

@@ -12,7 +12,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-26 07:46+0200\n" "POT-Creation-Date: 2023-05-18 14:28+0200\n"
"PO-Revision-Date: 2023-04-12 11:55+0000\n" "PO-Revision-Date: 2023-04-12 11:55+0000\n"
"Last-Translator: noxonad <noxonad@proton.me>\n" "Last-Translator: noxonad <noxonad@proton.me>\n"
"Language-Team: Catalan <http://translate.tandoor.dev/projects/tandoor/" "Language-Team: Catalan <http://translate.tandoor.dev/projects/tandoor/"
@@ -116,7 +116,7 @@ msgstr "Nombre de decimals dels ingredients."
msgid "If you want to be able to create and see comments underneath recipes." msgid "If you want to be able to create and see comments underneath recipes."
msgstr "Si vols poder crear i veure comentaris a sota de les receptes." msgstr "Si vols poder crear i veure comentaris a sota de les receptes."
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492 #: .\cookbook\forms.py:79 .\cookbook\forms.py:509
msgid "" msgid ""
"Setting to 0 will disable auto sync. When viewing a shopping list the list " "Setting to 0 will disable auto sync. When viewing a shopping list the list "
"is updated every set seconds to sync changes someone else might have made. " "is updated every set seconds to sync changes someone else might have made. "
@@ -133,7 +133,7 @@ msgstr ""
msgid "Makes the navbar stick to the top of the page." msgid "Makes the navbar stick to the top of the page."
msgstr "Barra de navegació s'enganxi a la part superior de la pàgina." msgstr "Barra de navegació s'enganxi a la part superior de la pàgina."
#: .\cookbook\forms.py:83 .\cookbook\forms.py:495 #: .\cookbook\forms.py:83 .\cookbook\forms.py:512
msgid "Automatically add meal plan ingredients to shopping list." msgid "Automatically add meal plan ingredients to shopping list."
msgstr "" msgstr ""
"Afegeix automàticament els ingredients del pla d'àpats a la llista de la " "Afegeix automàticament els ingredients del pla d'àpats a la llista de la "
@@ -155,11 +155,11 @@ msgstr ""
"Tots dos camps són opcionals. Si no se'n dóna cap, es mostrarà el nom " "Tots dos camps són opcionals. Si no se'n dóna cap, es mostrarà el nom "
"d'usuari" "d'usuari"
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297 #: .\cookbook\forms.py:123 .\cookbook\forms.py:314
msgid "Name" msgid "Name"
msgstr "Nom" msgstr "Nom"
#: .\cookbook\forms.py:124 .\cookbook\forms.py:298 .\cookbook\views\lists.py:88 #: .\cookbook\forms.py:124 .\cookbook\forms.py:315 .\cookbook\views\lists.py:88
msgid "Keywords" msgid "Keywords"
msgstr "Paraules clau" msgstr "Paraules clau"
@@ -171,7 +171,7 @@ msgstr "Temps de preparació en minuts"
msgid "Waiting time (cooking/baking) in minutes" msgid "Waiting time (cooking/baking) in minutes"
msgstr "Temps d'espera (cocció/fornejat) en minuts" msgstr "Temps d'espera (cocció/fornejat) en minuts"
#: .\cookbook\forms.py:127 .\cookbook\forms.py:266 .\cookbook\forms.py:299 #: .\cookbook\forms.py:127 .\cookbook\forms.py:283 .\cookbook\forms.py:316
msgid "Path" msgid "Path"
msgstr "Ruta" msgstr "Ruta"
@@ -183,7 +183,7 @@ msgstr "UID Emmagatzematge"
msgid "Default" msgid "Default"
msgstr "Per defecte" msgstr "Per defecte"
#: .\cookbook\forms.py:173 #: .\cookbook\forms.py:190
msgid "" msgid ""
"To prevent duplicates recipes with the same name as existing ones are " "To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything." "ignored. Check this box to import everything."
@@ -191,21 +191,21 @@ msgstr ""
"Per evitar duplicats, s'ignoren les receptes amb el mateix nom que les " "Per evitar duplicats, s'ignoren les receptes amb el mateix nom que les "
"existents. Marqueu aquesta casella per importar-ho tot." "existents. Marqueu aquesta casella per importar-ho tot."
#: .\cookbook\forms.py:196 #: .\cookbook\forms.py:213
msgid "Add your comment: " msgid "Add your comment: "
msgstr "Afegir el teu comentari: " msgstr "Afegir el teu comentari: "
#: .\cookbook\forms.py:211 #: .\cookbook\forms.py:228
msgid "Leave empty for dropbox and enter app password for nextcloud." msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "" msgstr ""
"Deixeu-lo buit per a Dropbox i introduïu la contrasenya de l'aplicació per a " "Deixeu-lo buit per a Dropbox i introduïu la contrasenya de l'aplicació per a "
"nextcloud." "nextcloud."
#: .\cookbook\forms.py:218 #: .\cookbook\forms.py:235
msgid "Leave empty for nextcloud and enter api token for dropbox." msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "Deixeu-lo buit per a nextcloud i introduïu el token API per a Dropbox." msgstr "Deixeu-lo buit per a nextcloud i introduïu el token API per a Dropbox."
#: .\cookbook\forms.py:227 #: .\cookbook\forms.py:244
msgid "" msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote." "Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
"php/webdav/</code> is added automatically)" "php/webdav/</code> is added automatically)"
@@ -213,33 +213,33 @@ msgstr ""
"Deixeu-lo buit per a Dropbox i introduïu només l'URL base per a Nextcloud " "Deixeu-lo buit per a Dropbox i introduïu només l'URL base per a Nextcloud "
"(<code>/remote.php/webdav/</code> s'afegeix automàticament)" "(<code>/remote.php/webdav/</code> s'afegeix automàticament)"
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157 #: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
msgid "Storage" msgid "Storage"
msgstr "Emmagatzematge" msgstr "Emmagatzematge"
#: .\cookbook\forms.py:267 #: .\cookbook\forms.py:284
msgid "Active" msgid "Active"
msgstr "Actiu" msgstr "Actiu"
#: .\cookbook\forms.py:273 #: .\cookbook\forms.py:290
msgid "Search String" msgid "Search String"
msgstr "Cerca Cadena" msgstr "Cerca Cadena"
#: .\cookbook\forms.py:300 #: .\cookbook\forms.py:317
msgid "File ID" msgid "File ID"
msgstr "ID d'Arxiu" msgstr "ID d'Arxiu"
#: .\cookbook\forms.py:322 #: .\cookbook\forms.py:339
msgid "You must provide at least a recipe or a title." msgid "You must provide at least a recipe or a title."
msgstr "Has de proporcionar com a mínim una recepta o un títol." msgstr "Has de proporcionar com a mínim una recepta o un títol."
#: .\cookbook\forms.py:335 #: .\cookbook\forms.py:352
msgid "You can list default users to share recipes with in the settings." msgid "You can list default users to share recipes with in the settings."
msgstr "" msgstr ""
"Podeu llistar els usuaris predeterminats amb els quals voleu compartir " "Podeu llistar els usuaris predeterminats amb els quals voleu compartir "
"receptes a la configuració." "receptes a la configuració."
#: .\cookbook\forms.py:336 #: .\cookbook\forms.py:353
msgid "" msgid ""
"You can use markdown to format this field. See the <a href=\"/docs/markdown/" "You can use markdown to format this field. See the <a href=\"/docs/markdown/"
"\">docs here</a>" "\">docs here</a>"
@@ -247,15 +247,15 @@ msgstr ""
"Podeu utilitzar el marcador per donar format a aquest camp. Consulteu els <a " "Podeu utilitzar el marcador per donar format a aquest camp. Consulteu els <a "
"href=\"/docs/markdown/\">documents aquí </a>" "href=\"/docs/markdown/\">documents aquí </a>"
#: .\cookbook\forms.py:362 #: .\cookbook\forms.py:379
msgid "Maximum number of users for this space reached." msgid "Maximum number of users for this space reached."
msgstr "Nombre màxim d'usuaris assolit per a aquest espai." msgstr "Nombre màxim d'usuaris assolit per a aquest espai."
#: .\cookbook\forms.py:368 #: .\cookbook\forms.py:385
msgid "Email address already taken!" msgid "Email address already taken!"
msgstr "Adreça de correu electrònic existent!" msgstr "Adreça de correu electrònic existent!"
#: .\cookbook\forms.py:376 #: .\cookbook\forms.py:393
msgid "" msgid ""
"An email address is not required but if present the invite link will be sent " "An email address is not required but if present the invite link will be sent "
"to the user." "to the user."
@@ -263,15 +263,15 @@ msgstr ""
"No cal una adreça de correu electrònic, però si està present, s'enviarà " "No cal una adreça de correu electrònic, però si està present, s'enviarà "
"l'enllaç d'invitació a l'usuari." "l'enllaç d'invitació a l'usuari."
#: .\cookbook\forms.py:391 #: .\cookbook\forms.py:408
msgid "Name already taken." msgid "Name already taken."
msgstr "Nom agafat." msgstr "Nom agafat."
#: .\cookbook\forms.py:402 #: .\cookbook\forms.py:419
msgid "Accept Terms and Privacy" msgid "Accept Terms and Privacy"
msgstr "Accepteu les condicions i la privadesa" msgstr "Accepteu les condicions i la privadesa"
#: .\cookbook\forms.py:434 #: .\cookbook\forms.py:451
msgid "" msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e." "Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)." "g. low values mean more typos are ignored)."
@@ -280,7 +280,7 @@ msgstr ""
"de trigrama (p. ex., els valors baixos signifiquen que s'ignoren més errors " "de trigrama (p. ex., els valors baixos signifiquen que s'ignoren més errors "
"ortogràfics)." "ortogràfics)."
#: .\cookbook\forms.py:444 #: .\cookbook\forms.py:461
msgid "" msgid ""
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for " "Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
"full description of choices." "full description of choices."
@@ -288,7 +288,7 @@ msgstr ""
"Seleccioneu el tipus de mètode de cerca. Feu clic <a href=\"/docs/search/" "Seleccioneu el tipus de mètode de cerca. Feu clic <a href=\"/docs/search/"
"\">aquí</a> per obtenir una descripció completa de les opcions." "\">aquí</a> per obtenir una descripció completa de les opcions."
#: .\cookbook\forms.py:445 #: .\cookbook\forms.py:462
msgid "" msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and " "Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes." "importing recipes."
@@ -296,7 +296,7 @@ msgstr ""
"Utilitzeu la concordança difusa en unitats, paraules clau i ingredients quan " "Utilitzeu la concordança difusa en unitats, paraules clau i ingredients quan "
"editeu i importeu receptes." "editeu i importeu receptes."
#: .\cookbook\forms.py:447 #: .\cookbook\forms.py:464
msgid "" msgid ""
"Fields to search ignoring accents. Selecting this option can improve or " "Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language" "degrade search quality depending on language"
@@ -304,7 +304,7 @@ msgstr ""
"Camps per cercar ignorant els accents. La selecció d'aquesta opció pot " "Camps per cercar ignorant els accents. La selecció d'aquesta opció pot "
"millorar o degradar la qualitat de la cerca en funció de l'idioma" "millorar o degradar la qualitat de la cerca en funció de l'idioma"
#: .\cookbook\forms.py:449 #: .\cookbook\forms.py:466
msgid "" msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return " "Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')" "'pie' and 'piece' and 'soapie')"
@@ -312,7 +312,7 @@ msgstr ""
"Camps per cercar coincidències parcials. (p. ex., en cercar \"Pastís\" " "Camps per cercar coincidències parcials. (p. ex., en cercar \"Pastís\" "
"tornarà \"pastís\" i \"peça\" i \"sabó\")" "tornarà \"pastís\" i \"peça\" i \"sabó\")"
#: .\cookbook\forms.py:451 #: .\cookbook\forms.py:468
msgid "" msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' " "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')" "will return 'salad' and 'sandwich')"
@@ -320,7 +320,7 @@ msgstr ""
"Camps per cercar l'inici de les coincidències de paraula. (p. ex., en cercar " "Camps per cercar l'inici de les coincidències de paraula. (p. ex., en cercar "
"\"sa\" es tornarà \"amanida\" i \"entrepà\")" "\"sa\" es tornarà \"amanida\" i \"entrepà\")"
#: .\cookbook\forms.py:453 #: .\cookbook\forms.py:470
msgid "" msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) " "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search." "Note: this option will conflict with 'web' and 'raw' methods of search."
@@ -329,7 +329,7 @@ msgstr ""
"trobareu \"recepta\".) Nota: aquesta opció entrarà en conflicte amb els " "trobareu \"recepta\".) Nota: aquesta opció entrarà en conflicte amb els "
"mètodes de cerca \"web\" i \"cru\"." "mètodes de cerca \"web\" i \"cru\"."
#: .\cookbook\forms.py:455 #: .\cookbook\forms.py:472
msgid "" msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods " "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields." "only function with fulltext fields."
@@ -337,35 +337,35 @@ msgstr ""
"Camps per a la cerca de text complet. Nota: els mètodes de cerca \"web\", " "Camps per a la cerca de text complet. Nota: els mètodes de cerca \"web\", "
"\"frase\" i \"en brut\" només funcionen amb camps de text complet." "\"frase\" i \"en brut\" només funcionen amb camps de text complet."
#: .\cookbook\forms.py:459 #: .\cookbook\forms.py:476
msgid "Search Method" msgid "Search Method"
msgstr "Mètode de cerca" msgstr "Mètode de cerca"
#: .\cookbook\forms.py:460 #: .\cookbook\forms.py:477
msgid "Fuzzy Lookups" msgid "Fuzzy Lookups"
msgstr "Cerques difuses" msgstr "Cerques difuses"
#: .\cookbook\forms.py:461 #: .\cookbook\forms.py:478
msgid "Ignore Accent" msgid "Ignore Accent"
msgstr "Ignora Accents" msgstr "Ignora Accents"
#: .\cookbook\forms.py:462 #: .\cookbook\forms.py:479
msgid "Partial Match" msgid "Partial Match"
msgstr "Cerca Parcial" msgstr "Cerca Parcial"
#: .\cookbook\forms.py:463 #: .\cookbook\forms.py:480
msgid "Starts With" msgid "Starts With"
msgstr "Comença amb" msgstr "Comença amb"
#: .\cookbook\forms.py:464 #: .\cookbook\forms.py:481
msgid "Fuzzy Search" msgid "Fuzzy Search"
msgstr "Cerca Difusa" msgstr "Cerca Difusa"
#: .\cookbook\forms.py:465 #: .\cookbook\forms.py:482
msgid "Full Text" msgid "Full Text"
msgstr "Text Sencer" msgstr "Text Sencer"
#: .\cookbook\forms.py:490 #: .\cookbook\forms.py:507
msgid "" msgid ""
"Users will see all items you add to your shopping list. They must add you " "Users will see all items you add to your shopping list. They must add you "
"to see items on their list." "to see items on their list."
@@ -373,7 +373,7 @@ msgstr ""
"Els usuaris veuran tots els articles que afegiu a la vostra llista de la " "Els usuaris veuran tots els articles que afegiu a la vostra llista de la "
"compra. Us han d'afegir per veure els elements de la seva llista." "compra. Us han d'afegir per veure els elements de la seva llista."
#: .\cookbook\forms.py:496 #: .\cookbook\forms.py:513
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes." "include all related recipes."
@@ -381,7 +381,7 @@ msgstr ""
"Quan afegiu un pla d'àpats a la llista de la compra (de manera manual o " "Quan afegiu un pla d'àpats a la llista de la compra (de manera manual o "
"automàtica), inclou totes les receptes relacionades." "automàtica), inclou totes les receptes relacionades."
#: .\cookbook\forms.py:497 #: .\cookbook\forms.py:514
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"exclude ingredients that are on hand." "exclude ingredients that are on hand."
@@ -389,93 +389,93 @@ msgstr ""
"Quan afegiu un pla d'àpats a la llista de la compra (manual o " "Quan afegiu un pla d'àpats a la llista de la compra (manual o "
"automàticament), excloeu els ingredients que teniu a mà." "automàticament), excloeu els ingredients que teniu a mà."
#: .\cookbook\forms.py:498 #: .\cookbook\forms.py:515
msgid "Default number of hours to delay a shopping list entry." msgid "Default number of hours to delay a shopping list entry."
msgstr "" msgstr ""
"Nombre d'hores per defecte per retardar l'entrada d'una llista de la compra." "Nombre d'hores per defecte per retardar l'entrada d'una llista de la compra."
#: .\cookbook\forms.py:499 #: .\cookbook\forms.py:516
msgid "Filter shopping list to only include supermarket categories." msgid "Filter shopping list to only include supermarket categories."
msgstr "" msgstr ""
"Filtreu la llista de compres per incloure només categories de supermercats." "Filtreu la llista de compres per incloure només categories de supermercats."
#: .\cookbook\forms.py:500 #: .\cookbook\forms.py:517
msgid "Days of recent shopping list entries to display." msgid "Days of recent shopping list entries to display."
msgstr "Dies de les entrades recents de la llista de la compra per mostrar." msgstr "Dies de les entrades recents de la llista de la compra per mostrar."
#: .\cookbook\forms.py:501 #: .\cookbook\forms.py:518
msgid "Mark food 'On Hand' when checked off shopping list." msgid "Mark food 'On Hand' when checked off shopping list."
msgstr "Marca el menjar com a \"A mà\" quan marqueu la llista de la compra." msgstr "Marca el menjar com a \"A mà\" quan marqueu la llista de la compra."
#: .\cookbook\forms.py:502 #: .\cookbook\forms.py:519
msgid "Delimiter to use for CSV exports." msgid "Delimiter to use for CSV exports."
msgstr "Delimitador per a les exportacions CSV." msgstr "Delimitador per a les exportacions CSV."
#: .\cookbook\forms.py:503 #: .\cookbook\forms.py:520
msgid "Prefix to add when copying list to the clipboard." msgid "Prefix to add when copying list to the clipboard."
msgstr "Prefix per afegir en copiar la llista al porta-retalls." msgstr "Prefix per afegir en copiar la llista al porta-retalls."
#: .\cookbook\forms.py:507 #: .\cookbook\forms.py:524
msgid "Share Shopping List" msgid "Share Shopping List"
msgstr "Compartir Llista de la Compra" msgstr "Compartir Llista de la Compra"
#: .\cookbook\forms.py:508 #: .\cookbook\forms.py:525
msgid "Autosync" msgid "Autosync"
msgstr "Autosync" msgstr "Autosync"
#: .\cookbook\forms.py:509 #: .\cookbook\forms.py:526
msgid "Auto Add Meal Plan" msgid "Auto Add Meal Plan"
msgstr "Afegeix automàticament un pla d'àpats" msgstr "Afegeix automàticament un pla d'àpats"
#: .\cookbook\forms.py:510 #: .\cookbook\forms.py:527
msgid "Exclude On Hand" msgid "Exclude On Hand"
msgstr "Exclou a mà" msgstr "Exclou a mà"
#: .\cookbook\forms.py:511 #: .\cookbook\forms.py:528
msgid "Include Related" msgid "Include Related"
msgstr "Incloure Relacionats" msgstr "Incloure Relacionats"
#: .\cookbook\forms.py:512 #: .\cookbook\forms.py:529
msgid "Default Delay Hours" msgid "Default Delay Hours"
msgstr "Hores de retard per defecte" msgstr "Hores de retard per defecte"
#: .\cookbook\forms.py:513 #: .\cookbook\forms.py:530
msgid "Filter to Supermarket" msgid "Filter to Supermarket"
msgstr "Filtrar a supermercat" msgstr "Filtrar a supermercat"
#: .\cookbook\forms.py:514 #: .\cookbook\forms.py:531
msgid "Recent Days" msgid "Recent Days"
msgstr "Dies recents" msgstr "Dies recents"
#: .\cookbook\forms.py:515 #: .\cookbook\forms.py:532
msgid "CSV Delimiter" msgid "CSV Delimiter"
msgstr "Delimitador CSV" msgstr "Delimitador CSV"
#: .\cookbook\forms.py:516 #: .\cookbook\forms.py:533
msgid "List Prefix" msgid "List Prefix"
msgstr "Prefix de Llista" msgstr "Prefix de Llista"
#: .\cookbook\forms.py:517 #: .\cookbook\forms.py:534
msgid "Auto On Hand" msgid "Auto On Hand"
msgstr "Auto a mà" msgstr "Auto a mà"
#: .\cookbook\forms.py:527 #: .\cookbook\forms.py:544
msgid "Reset Food Inheritance" msgid "Reset Food Inheritance"
msgstr "Restablir Herència Alimentària" msgstr "Restablir Herència Alimentària"
#: .\cookbook\forms.py:528 #: .\cookbook\forms.py:545
msgid "Reset all food to inherit the fields configured." msgid "Reset all food to inherit the fields configured."
msgstr "Restableix tots els aliments per heretar els camps configurats." msgstr "Restableix tots els aliments per heretar els camps configurats."
#: .\cookbook\forms.py:540 #: .\cookbook\forms.py:557
msgid "Fields on food that should be inherited by default." msgid "Fields on food that should be inherited by default."
msgstr "Camps dels aliments que s'han d'heretar per defecte." msgstr "Camps dels aliments que s'han d'heretar per defecte."
#: .\cookbook\forms.py:541 #: .\cookbook\forms.py:558
msgid "Show recipe counts on search filters" msgid "Show recipe counts on search filters"
msgstr "Mostra el recompte de receptes als filtres de cerca" msgstr "Mostra el recompte de receptes als filtres de cerca"
#: .\cookbook\forms.py:542 #: .\cookbook\forms.py:559
msgid "Use the plural form for units and food inside this space." msgid "Use the plural form for units and food inside this space."
msgstr "" msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-07-12 19:20+0200\n" "POT-Creation-Date: 2022-07-12 19:20+0200\n"
"PO-Revision-Date: 2022-10-17 11:33+0000\n" "PO-Revision-Date: 2023-05-31 17:19+0000\n"
"Last-Translator: Sokratis Potamias <sokratespot@gmail.com>\n" "Last-Translator: sweeney <sweeneytodd91@protonmail.com>\n"
"Language-Team: Greek <http://translate.tandoor.dev/projects/tandoor/" "Language-Team: Greek <http://translate.tandoor.dev/projects/tandoor/"
"recipes-backend/el/>\n" "recipes-backend/el/>\n"
"Language: el\n" "Language: el\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.10.1\n" "X-Generator: Weblate 4.15\n"
#: .\cookbook\filters.py:23 .\cookbook\templates\forms\ingredients.html:34 #: .\cookbook\filters.py:23 .\cookbook\templates\forms\ingredients.html:34
#: .\cookbook\templates\stats.html:28 #: .\cookbook\templates\stats.html:28
@@ -26,7 +26,7 @@ msgstr "Συστατικά"
#: .\cookbook\forms.py:53 #: .\cookbook\forms.py:53
msgid "Default unit" msgid "Default unit"
msgstr "" msgstr "Προεπιλεγμένη μονάδα μέτρησης"
#: .\cookbook\forms.py:54 #: .\cookbook\forms.py:54
msgid "Use fractions" msgid "Use fractions"
@@ -34,7 +34,7 @@ msgstr ""
#: .\cookbook\forms.py:55 #: .\cookbook\forms.py:55
msgid "Use KJ" msgid "Use KJ"
msgstr "" msgstr "Χρήση KiloJoule(KJ)"
#: .\cookbook\forms.py:56 #: .\cookbook\forms.py:56
msgid "Theme" msgid "Theme"
@@ -42,7 +42,7 @@ msgstr "Θέμα"
#: .\cookbook\forms.py:57 #: .\cookbook\forms.py:57
msgid "Navbar color" msgid "Navbar color"
msgstr "" msgstr "Χρώμα μπάρας πλοήγησης"
#: .\cookbook\forms.py:58 #: .\cookbook\forms.py:58
msgid "Sticky navbar" msgid "Sticky navbar"
@@ -50,19 +50,19 @@ msgstr ""
#: .\cookbook\forms.py:59 #: .\cookbook\forms.py:59
msgid "Default page" msgid "Default page"
msgstr "" msgstr "Προεπιλεγμένη σελίδα"
#: .\cookbook\forms.py:60 #: .\cookbook\forms.py:60
msgid "Show recent recipes" msgid "Show recent recipes"
msgstr "" msgstr "Προβολή πρόσφατων συνταγών"
#: .\cookbook\forms.py:61 #: .\cookbook\forms.py:61
msgid "Search style" msgid "Search style"
msgstr "" msgstr "Τρόπος αναζήτησης"
#: .\cookbook\forms.py:62 #: .\cookbook\forms.py:62
msgid "Plan sharing" msgid "Plan sharing"
msgstr "" msgstr "Κοινοποίηση προγράμματος"
#: .\cookbook\forms.py:63 #: .\cookbook\forms.py:63
msgid "Ingredient decimal places" msgid "Ingredient decimal places"
@@ -70,7 +70,7 @@ msgstr ""
#: .\cookbook\forms.py:64 #: .\cookbook\forms.py:64
msgid "Shopping list auto sync period" msgid "Shopping list auto sync period"
msgstr "" msgstr "Χρονική περίοδος αυτόματου συγχρονισμού λίστας αγορών"
#: .\cookbook\forms.py:65 .\cookbook\templates\recipe_view.html:21 #: .\cookbook\forms.py:65 .\cookbook\templates\recipe_view.html:21
#: .\cookbook\templates\stats.html:47 #: .\cookbook\templates\stats.html:47
@@ -79,17 +79,21 @@ msgstr "Σχόλια"
#: .\cookbook\forms.py:66 #: .\cookbook\forms.py:66
msgid "Left-handed mode" msgid "Left-handed mode"
msgstr "" msgstr "Έκδοση για αριστερόχειρες"
#: .\cookbook\forms.py:70 #: .\cookbook\forms.py:70
msgid "" msgid ""
"Color of the top navigation bar. Not all colors work with all themes, just " "Color of the top navigation bar. Not all colors work with all themes, just "
"try them out!" "try them out!"
msgstr "" msgstr ""
"Χρώμα της πάνω μπάρας πλοήγησης. Δεν δουλεύουν όλα τα χρώματα με όλα τα "
"θέματα, απλά δοκιμάστε τα!"
#: .\cookbook\forms.py:72 #: .\cookbook\forms.py:72
msgid "Default Unit to be used when inserting a new ingredient into a recipe." msgid "Default Unit to be used when inserting a new ingredient into a recipe."
msgstr "" msgstr ""
"Προεπιλεγμένη μονάδα μέτρησης που θα χρησιμοποιείται όταν προστίθεται ένα "
"υλικό σε μια συνταγή."
#: .\cookbook\forms.py:74 #: .\cookbook\forms.py:74
msgid "" msgid ""
@@ -104,18 +108,20 @@ msgstr ""
#: .\cookbook\forms.py:77 #: .\cookbook\forms.py:77
msgid "Users with whom newly created meal plans should be shared by default." msgid "Users with whom newly created meal plans should be shared by default."
msgstr "" msgstr ""
"Χρήστες με του οποίους η κοινοποίηση του προγραμματισμού των γευμάτων θα "
"γίνεται από προεπιλογή."
#: .\cookbook\forms.py:78 #: .\cookbook\forms.py:78
msgid "Users with whom to share shopping lists." msgid "Users with whom to share shopping lists."
msgstr "" msgstr "Χρήστες με του οποίους θα γίνει κοινοποίηση των λιστών αγορών."
#: .\cookbook\forms.py:80 #: .\cookbook\forms.py:80
msgid "Show recently viewed recipes on search page." msgid "Show recently viewed recipes on search page."
msgstr "" msgstr "Προβολή των προσφάτως προβεβλημένων συνταγών στη σελίδα αναζήτησης."
#: .\cookbook\forms.py:81 #: .\cookbook\forms.py:81
msgid "Number of decimals to round ingredients." msgid "Number of decimals to round ingredients."
msgstr "" msgstr "Αριθμός των δεκαδικών στα οποία θα γίνεται στρογγυλοποίηση."
#: .\cookbook\forms.py:82 #: .\cookbook\forms.py:82
msgid "If you want to be able to create and see comments underneath recipes." msgid "If you want to be able to create and see comments underneath recipes."
@@ -153,20 +159,20 @@ msgstr ""
#: .\cookbook\forms.py:128 .\cookbook\forms.py:301 #: .\cookbook\forms.py:128 .\cookbook\forms.py:301
msgid "Name" msgid "Name"
msgstr "" msgstr "Όνομα"
#: .\cookbook\forms.py:129 .\cookbook\forms.py:302 #: .\cookbook\forms.py:129 .\cookbook\forms.py:302
#: .\cookbook\templates\stats.html:24 .\cookbook\views\lists.py:88 #: .\cookbook\templates\stats.html:24 .\cookbook\views\lists.py:88
msgid "Keywords" msgid "Keywords"
msgstr "" msgstr "Λέξεις κλειδιά"
#: .\cookbook\forms.py:130 #: .\cookbook\forms.py:130
msgid "Preparation time in minutes" msgid "Preparation time in minutes"
msgstr "" msgstr "Χρόνος προετοιμασίας σε λεπτά"
#: .\cookbook\forms.py:131 #: .\cookbook\forms.py:131
msgid "Waiting time (cooking/baking) in minutes" msgid "Waiting time (cooking/baking) in minutes"
msgstr "" msgstr "Χρόνος αναμονής (μαγείρεμα/ ψήσιμο) σε λεπτά"
#: .\cookbook\forms.py:132 .\cookbook\forms.py:270 .\cookbook\forms.py:303 #: .\cookbook\forms.py:132 .\cookbook\forms.py:270 .\cookbook\forms.py:303
msgid "Path" msgid "Path"
@@ -188,15 +194,18 @@ msgstr ""
#: .\cookbook\forms.py:200 #: .\cookbook\forms.py:200
msgid "Add your comment: " msgid "Add your comment: "
msgstr "" msgstr "Προσθήκη σχολίου: "
#: .\cookbook\forms.py:215 #: .\cookbook\forms.py:215
msgid "Leave empty for dropbox and enter app password for nextcloud." msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "" msgstr ""
"Για dropbox παρακαλώ αφήστε το κενό και πληκτρολογήστε το password για "
"nextcloud."
#: .\cookbook\forms.py:222 #: .\cookbook\forms.py:222
msgid "Leave empty for nextcloud and enter api token for dropbox." msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "" msgstr ""
"Για nextcloud αφήστε το κενό και για dropbox πληκτρολογήστε το api token."
#: .\cookbook\forms.py:231 #: .\cookbook\forms.py:231
msgid "" msgid ""
@@ -240,7 +249,7 @@ msgstr ""
#: .\cookbook\forms.py:372 #: .\cookbook\forms.py:372
msgid "Email address already taken!" msgid "Email address already taken!"
msgstr "" msgstr "Αυτή η διεύθυνση email δεν είναι διαθέσιμη!"
#: .\cookbook\forms.py:380 #: .\cookbook\forms.py:380
msgid "" msgid ""
@@ -306,7 +315,7 @@ msgstr ""
#: .\cookbook\forms.py:463 #: .\cookbook\forms.py:463
msgid "Search Method" msgid "Search Method"
msgstr "" msgstr "Μέθοδος αναζήτησης"
#: .\cookbook\forms.py:464 #: .\cookbook\forms.py:464
msgid "Fuzzy Lookups" msgid "Fuzzy Lookups"
@@ -330,19 +339,24 @@ msgstr ""
#: .\cookbook\forms.py:469 #: .\cookbook\forms.py:469
msgid "Full Text" msgid "Full Text"
msgstr "" msgstr "Πλήρες κείμενο"
#: .\cookbook\forms.py:494 #: .\cookbook\forms.py:494
msgid "" msgid ""
"Users will see all items you add to your shopping list. They must add you " "Users will see all items you add to your shopping list. They must add you "
"to see items on their list." "to see items on their list."
msgstr "" msgstr ""
"Οι χρήστες θα μπορούν να δουν όλα τα αντικείμενα που προστίθενται στην λίστα "
"αγορών σας. Για να δείτε τα αντικείμενα στις λίστα αυτών θα πρέπει να σας "
"προστέσουν."
#: .\cookbook\forms.py:500 #: .\cookbook\forms.py:500
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes." "include all related recipes."
msgstr "" msgstr ""
"Όταν προστίθεται ένα πρόγραμμα γευμάτων στη λίστα αγορών (χειροκίνητα ή "
"αυτόματα), να συμπεριλαμβάνονται όλες οι σχετικές συνταγές."
#: .\cookbook\forms.py:501 #: .\cookbook\forms.py:501
msgid "" msgid ""
@@ -353,18 +367,23 @@ msgstr ""
#: .\cookbook\forms.py:502 #: .\cookbook\forms.py:502
msgid "Default number of hours to delay a shopping list entry." msgid "Default number of hours to delay a shopping list entry."
msgstr "" msgstr ""
"Προεπιλεγμένος αριθμός ωρών για την καθυστέρηση μιας εγγραφής στη λίστα "
"αγορών."
#: .\cookbook\forms.py:503 #: .\cookbook\forms.py:503
msgid "Filter shopping list to only include supermarket categories." msgid "Filter shopping list to only include supermarket categories."
msgstr "" msgstr ""
"Φιλτράρισμα λίστας αγορών ώστε να περιλαμβάνει μόνο τις κατηγορίες του "
"supermarker."
#: .\cookbook\forms.py:504 #: .\cookbook\forms.py:504
msgid "Days of recent shopping list entries to display." msgid "Days of recent shopping list entries to display."
msgstr "" msgstr "Αριθμός ημερών για τη προβολή των πρόσφατων εγγραφών της λίστας αγορών."
#: .\cookbook\forms.py:505 #: .\cookbook\forms.py:505
msgid "Mark food 'On Hand' when checked off shopping list." msgid "Mark food 'On Hand' when checked off shopping list."
msgstr "" msgstr ""
"Χαρακτηρισμός ενός τροφίμου ως 'Διαθέσιμο' όταν τσεκαριστεί στη λίστα αγορών."
#: .\cookbook\forms.py:506 #: .\cookbook\forms.py:506
msgid "Delimiter to use for CSV exports." msgid "Delimiter to use for CSV exports."
@@ -376,27 +395,27 @@ msgstr ""
#: .\cookbook\forms.py:511 #: .\cookbook\forms.py:511
msgid "Share Shopping List" msgid "Share Shopping List"
msgstr "" msgstr "Κοινοποίηση λίστας αγορών"
#: .\cookbook\forms.py:512 #: .\cookbook\forms.py:512
msgid "Autosync" msgid "Autosync"
msgstr "" msgstr "Αυτόματος συγχρονισμός"
#: .\cookbook\forms.py:513 #: .\cookbook\forms.py:513
msgid "Auto Add Meal Plan" msgid "Auto Add Meal Plan"
msgstr "" msgstr "Αυτόματη προσθήκη προγραμματισμού γευμάτων"
#: .\cookbook\forms.py:514 #: .\cookbook\forms.py:514
msgid "Exclude On Hand" msgid "Exclude On Hand"
msgstr "" msgstr "Αποκλεισμός διαθέσιμων"
#: .\cookbook\forms.py:515 #: .\cookbook\forms.py:515
msgid "Include Related" msgid "Include Related"
msgstr "" msgstr "Συμπερίληψη σχετικών"
#: .\cookbook\forms.py:516 #: .\cookbook\forms.py:516
msgid "Default Delay Hours" msgid "Default Delay Hours"
msgstr "" msgstr "Προεπιλεγμένες ώρες καθυστέρησης"
#: .\cookbook\forms.py:517 #: .\cookbook\forms.py:517
msgid "Filter to Supermarket" msgid "Filter to Supermarket"
@@ -404,7 +423,7 @@ msgstr ""
#: .\cookbook\forms.py:518 #: .\cookbook\forms.py:518
msgid "Recent Days" msgid "Recent Days"
msgstr "" msgstr "Πρόσφατες ημέρες"
#: .\cookbook\forms.py:519 #: .\cookbook\forms.py:519
msgid "CSV Delimiter" msgid "CSV Delimiter"
@@ -439,11 +458,13 @@ msgid ""
"In order to prevent spam, the requested email was not send. Please wait a " "In order to prevent spam, the requested email was not send. Please wait a "
"few minutes and try again." "few minutes and try again."
msgstr "" msgstr ""
"Για να αποκλείσουμε πιθανά spam, το email που ζητήθηκε δεν στάλθηκε. "
"Παρακαλώ περιμένετε λίγα λεπτά και δοκιμάστε ξανά."
#: .\cookbook\helper\permission_helper.py:149 #: .\cookbook\helper\permission_helper.py:149
#: .\cookbook\helper\permission_helper.py:172 .\cookbook\views\views.py:152 #: .\cookbook\helper\permission_helper.py:172 .\cookbook\views\views.py:152
msgid "You are not logged in and therefore cannot view this page!" msgid "You are not logged in and therefore cannot view this page!"
msgstr "" msgstr "Δεν μπορείτε να δείτε αυτή τη σελίδα γιατί δεν είστε συνδεδεμένος!"
#: .\cookbook\helper\permission_helper.py:153 #: .\cookbook\helper\permission_helper.py:153
#: .\cookbook\helper\permission_helper.py:159 #: .\cookbook\helper\permission_helper.py:159
@@ -455,7 +476,7 @@ msgstr ""
#: .\cookbook\views\views.py:163 .\cookbook\views\views.py:170 #: .\cookbook\views\views.py:163 .\cookbook\views\views.py:170
#: .\cookbook\views\views.py:249 #: .\cookbook\views\views.py:249
msgid "You do not have the required permissions to view this page!" msgid "You do not have the required permissions to view this page!"
msgstr "" msgstr "Δεν έχετε τα απαιτούμενα δικαιώματα να δείτε αυτή τη σελίδα!"
#: .\cookbook\helper\permission_helper.py:177 #: .\cookbook\helper\permission_helper.py:177
#: .\cookbook\helper\permission_helper.py:200 #: .\cookbook\helper\permission_helper.py:200
@@ -463,14 +484,15 @@ msgstr ""
#: .\cookbook\helper\permission_helper.py:237 #: .\cookbook\helper\permission_helper.py:237
msgid "You cannot interact with this object as it is not owned by you!" msgid "You cannot interact with this object as it is not owned by you!"
msgstr "" msgstr ""
"Δεν μπορείτε να αλληλεπιδράστε με αυτό το αντικείμενο γιατί δεν σας ανήκει!"
#: .\cookbook\helper\permission_helper.py:321 #: .\cookbook\helper\permission_helper.py:321
msgid "You have reached the maximum number of recipes for your space." msgid "You have reached the maximum number of recipes for your space."
msgstr "" msgstr "Έχετε υπερβεί τον μέγιστο αριθμό συνταγών για τον χώρο σας."
#: .\cookbook\helper\permission_helper.py:333 #: .\cookbook\helper\permission_helper.py:333
msgid "You have more users than allowed in your space." msgid "You have more users than allowed in your space."
msgstr "" msgstr "Έχετε περισσότερους χρήστες από το επιτρεπόμενο στον χώρο σας."
#: .\cookbook\helper\recipe_search.py:565 #: .\cookbook\helper\recipe_search.py:565
msgid "One of queryset or hash_key must be provided" msgid "One of queryset or hash_key must be provided"
@@ -519,33 +541,33 @@ msgstr ""
#: .\cookbook\integration\paprika.py:46 #: .\cookbook\integration\paprika.py:46
msgid "Notes" msgid "Notes"
msgstr "" msgstr "Σημειώσεις"
#: .\cookbook\integration\paprika.py:49 #: .\cookbook\integration\paprika.py:49
msgid "Nutritional Information" msgid "Nutritional Information"
msgstr "" msgstr "Διατροφικές πληροφορίες"
#: .\cookbook\integration\paprika.py:53 #: .\cookbook\integration\paprika.py:53
msgid "Source" msgid "Source"
msgstr "" msgstr "Πηγή"
#: .\cookbook\integration\saffron.py:23 #: .\cookbook\integration\saffron.py:23
msgid "Servings" msgid "Servings"
msgstr "" msgstr "Μερίδες"
#: .\cookbook\integration\saffron.py:25 #: .\cookbook\integration\saffron.py:25
msgid "Waiting time" msgid "Waiting time"
msgstr "" msgstr "Χρόνος αναμονής"
#: .\cookbook\integration\saffron.py:27 #: .\cookbook\integration\saffron.py:27
msgid "Preparation Time" msgid "Preparation Time"
msgstr "" msgstr "Χρόνος προετοιμασίας"
#: .\cookbook\integration\saffron.py:29 #: .\cookbook\integration\saffron.py:29
#: .\cookbook\templates\forms\ingredients.html:7 #: .\cookbook\templates\forms\ingredients.html:7
#: .\cookbook\templates\index.html:7 #: .\cookbook\templates\index.html:7
msgid "Cookbook" msgid "Cookbook"
msgstr "" msgstr "Βιβλίο συνταγών"
#: .\cookbook\integration\saffron.py:31 #: .\cookbook\integration\saffron.py:31
msgid "Section" msgid "Section"
@@ -569,19 +591,19 @@ msgstr ""
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14 #: .\cookbook\migrations\0047_auto_20200602_1133.py:14
msgid "Breakfast" msgid "Breakfast"
msgstr "" msgstr "Πρωινό"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19 #: .\cookbook\migrations\0047_auto_20200602_1133.py:19
msgid "Lunch" msgid "Lunch"
msgstr "" msgstr "Μεσημεριανό"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24 #: .\cookbook\migrations\0047_auto_20200602_1133.py:24
msgid "Dinner" msgid "Dinner"
msgstr "" msgstr "Βραδινό"
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29 #: .\cookbook\migrations\0047_auto_20200602_1133.py:29
msgid "Other" msgid "Other"
msgstr "" msgstr "Άλλο"
#: .\cookbook\models.py:251 #: .\cookbook\models.py:251
msgid "" msgid ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-26 07:46+0200\n" "POT-Creation-Date: 2023-05-18 14:28+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -102,7 +102,7 @@ msgstr ""
msgid "If you want to be able to create and see comments underneath recipes." msgid "If you want to be able to create and see comments underneath recipes."
msgstr "" msgstr ""
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492 #: .\cookbook\forms.py:79 .\cookbook\forms.py:509
msgid "" msgid ""
"Setting to 0 will disable auto sync. When viewing a shopping list the list " "Setting to 0 will disable auto sync. When viewing a shopping list the list "
"is updated every set seconds to sync changes someone else might have made. " "is updated every set seconds to sync changes someone else might have made. "
@@ -114,7 +114,7 @@ msgstr ""
msgid "Makes the navbar stick to the top of the page." msgid "Makes the navbar stick to the top of the page."
msgstr "" msgstr ""
#: .\cookbook\forms.py:83 .\cookbook\forms.py:495 #: .\cookbook\forms.py:83 .\cookbook\forms.py:512
msgid "Automatically add meal plan ingredients to shopping list." msgid "Automatically add meal plan ingredients to shopping list."
msgstr "" msgstr ""
@@ -132,11 +132,11 @@ msgid ""
"instead" "instead"
msgstr "" msgstr ""
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297 #: .\cookbook\forms.py:123 .\cookbook\forms.py:314
msgid "Name" msgid "Name"
msgstr "" msgstr ""
#: .\cookbook\forms.py:124 .\cookbook\forms.py:298 .\cookbook\views\lists.py:88 #: .\cookbook\forms.py:124 .\cookbook\forms.py:315 .\cookbook\views\lists.py:88
msgid "Keywords" msgid "Keywords"
msgstr "" msgstr ""
@@ -148,7 +148,7 @@ msgstr ""
msgid "Waiting time (cooking/baking) in minutes" msgid "Waiting time (cooking/baking) in minutes"
msgstr "" msgstr ""
#: .\cookbook\forms.py:127 .\cookbook\forms.py:266 .\cookbook\forms.py:299 #: .\cookbook\forms.py:127 .\cookbook\forms.py:283 .\cookbook\forms.py:316
msgid "Path" msgid "Path"
msgstr "" msgstr ""
@@ -160,261 +160,261 @@ msgstr ""
msgid "Default" msgid "Default"
msgstr "" msgstr ""
#: .\cookbook\forms.py:173 #: .\cookbook\forms.py:190
msgid "" msgid ""
"To prevent duplicates recipes with the same name as existing ones are " "To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything." "ignored. Check this box to import everything."
msgstr "" msgstr ""
#: .\cookbook\forms.py:196 #: .\cookbook\forms.py:213
msgid "Add your comment: " msgid "Add your comment: "
msgstr "" msgstr ""
#: .\cookbook\forms.py:211 #: .\cookbook\forms.py:228
msgid "Leave empty for dropbox and enter app password for nextcloud." msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "" msgstr ""
#: .\cookbook\forms.py:218 #: .\cookbook\forms.py:235
msgid "Leave empty for nextcloud and enter api token for dropbox." msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "" msgstr ""
#: .\cookbook\forms.py:227 #: .\cookbook\forms.py:244
msgid "" msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote." "Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
"php/webdav/</code> is added automatically)" "php/webdav/</code> is added automatically)"
msgstr "" msgstr ""
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157 #: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
msgid "Storage" msgid "Storage"
msgstr "" msgstr ""
#: .\cookbook\forms.py:267 #: .\cookbook\forms.py:284
msgid "Active" msgid "Active"
msgstr "" msgstr ""
#: .\cookbook\forms.py:273 #: .\cookbook\forms.py:290
msgid "Search String" msgid "Search String"
msgstr "" msgstr ""
#: .\cookbook\forms.py:300 #: .\cookbook\forms.py:317
msgid "File ID" msgid "File ID"
msgstr "" msgstr ""
#: .\cookbook\forms.py:322 #: .\cookbook\forms.py:339
msgid "You must provide at least a recipe or a title." msgid "You must provide at least a recipe or a title."
msgstr "" msgstr ""
#: .\cookbook\forms.py:335 #: .\cookbook\forms.py:352
msgid "You can list default users to share recipes with in the settings." msgid "You can list default users to share recipes with in the settings."
msgstr "" msgstr ""
#: .\cookbook\forms.py:336 #: .\cookbook\forms.py:353
msgid "" msgid ""
"You can use markdown to format this field. See the <a href=\"/docs/markdown/" "You can use markdown to format this field. See the <a href=\"/docs/markdown/"
"\">docs here</a>" "\">docs here</a>"
msgstr "" msgstr ""
#: .\cookbook\forms.py:362 #: .\cookbook\forms.py:379
msgid "Maximum number of users for this space reached." msgid "Maximum number of users for this space reached."
msgstr "" msgstr ""
#: .\cookbook\forms.py:368 #: .\cookbook\forms.py:385
msgid "Email address already taken!" msgid "Email address already taken!"
msgstr "" msgstr ""
#: .\cookbook\forms.py:376 #: .\cookbook\forms.py:393
msgid "" msgid ""
"An email address is not required but if present the invite link will be sent " "An email address is not required but if present the invite link will be sent "
"to the user." "to the user."
msgstr "" msgstr ""
#: .\cookbook\forms.py:391 #: .\cookbook\forms.py:408
msgid "Name already taken." msgid "Name already taken."
msgstr "" msgstr ""
#: .\cookbook\forms.py:402 #: .\cookbook\forms.py:419
msgid "Accept Terms and Privacy" msgid "Accept Terms and Privacy"
msgstr "" msgstr ""
#: .\cookbook\forms.py:434 #: .\cookbook\forms.py:451
msgid "" msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e." "Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)." "g. low values mean more typos are ignored)."
msgstr "" msgstr ""
#: .\cookbook\forms.py:444 #: .\cookbook\forms.py:461
msgid "" msgid ""
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for " "Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
"full description of choices." "full description of choices."
msgstr "" msgstr ""
#: .\cookbook\forms.py:445 #: .\cookbook\forms.py:462
msgid "" msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and " "Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes." "importing recipes."
msgstr "" msgstr ""
#: .\cookbook\forms.py:447 #: .\cookbook\forms.py:464
msgid "" msgid ""
"Fields to search ignoring accents. Selecting this option can improve or " "Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language" "degrade search quality depending on language"
msgstr "" msgstr ""
#: .\cookbook\forms.py:449 #: .\cookbook\forms.py:466
msgid "" msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return " "Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')" "'pie' and 'piece' and 'soapie')"
msgstr "" msgstr ""
#: .\cookbook\forms.py:451 #: .\cookbook\forms.py:468
msgid "" msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' " "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')" "will return 'salad' and 'sandwich')"
msgstr "" msgstr ""
#: .\cookbook\forms.py:453 #: .\cookbook\forms.py:470
msgid "" msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) " "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search." "Note: this option will conflict with 'web' and 'raw' methods of search."
msgstr "" msgstr ""
#: .\cookbook\forms.py:455 #: .\cookbook\forms.py:472
msgid "" msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods " "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields." "only function with fulltext fields."
msgstr "" msgstr ""
#: .\cookbook\forms.py:459 #: .\cookbook\forms.py:476
msgid "Search Method" msgid "Search Method"
msgstr "" msgstr ""
#: .\cookbook\forms.py:460 #: .\cookbook\forms.py:477
msgid "Fuzzy Lookups" msgid "Fuzzy Lookups"
msgstr "" msgstr ""
#: .\cookbook\forms.py:461 #: .\cookbook\forms.py:478
msgid "Ignore Accent" msgid "Ignore Accent"
msgstr "" msgstr ""
#: .\cookbook\forms.py:462 #: .\cookbook\forms.py:479
msgid "Partial Match" msgid "Partial Match"
msgstr "" msgstr ""
#: .\cookbook\forms.py:463 #: .\cookbook\forms.py:480
msgid "Starts With" msgid "Starts With"
msgstr "" msgstr ""
#: .\cookbook\forms.py:464 #: .\cookbook\forms.py:481
msgid "Fuzzy Search" msgid "Fuzzy Search"
msgstr "" msgstr ""
#: .\cookbook\forms.py:465 #: .\cookbook\forms.py:482
msgid "Full Text" msgid "Full Text"
msgstr "" msgstr ""
#: .\cookbook\forms.py:490 #: .\cookbook\forms.py:507
msgid "" msgid ""
"Users will see all items you add to your shopping list. They must add you " "Users will see all items you add to your shopping list. They must add you "
"to see items on their list." "to see items on their list."
msgstr "" msgstr ""
#: .\cookbook\forms.py:496 #: .\cookbook\forms.py:513
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes." "include all related recipes."
msgstr "" msgstr ""
#: .\cookbook\forms.py:497 #: .\cookbook\forms.py:514
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"exclude ingredients that are on hand." "exclude ingredients that are on hand."
msgstr "" msgstr ""
#: .\cookbook\forms.py:498 #: .\cookbook\forms.py:515
msgid "Default number of hours to delay a shopping list entry." msgid "Default number of hours to delay a shopping list entry."
msgstr "" msgstr ""
#: .\cookbook\forms.py:499 #: .\cookbook\forms.py:516
msgid "Filter shopping list to only include supermarket categories." msgid "Filter shopping list to only include supermarket categories."
msgstr "" msgstr ""
#: .\cookbook\forms.py:500 #: .\cookbook\forms.py:517
msgid "Days of recent shopping list entries to display." msgid "Days of recent shopping list entries to display."
msgstr "" msgstr ""
#: .\cookbook\forms.py:501 #: .\cookbook\forms.py:518
msgid "Mark food 'On Hand' when checked off shopping list." msgid "Mark food 'On Hand' when checked off shopping list."
msgstr "" msgstr ""
#: .\cookbook\forms.py:502 #: .\cookbook\forms.py:519
msgid "Delimiter to use for CSV exports." msgid "Delimiter to use for CSV exports."
msgstr "" msgstr ""
#: .\cookbook\forms.py:503 #: .\cookbook\forms.py:520
msgid "Prefix to add when copying list to the clipboard." msgid "Prefix to add when copying list to the clipboard."
msgstr "" msgstr ""
#: .\cookbook\forms.py:507 #: .\cookbook\forms.py:524
msgid "Share Shopping List" msgid "Share Shopping List"
msgstr "" msgstr ""
#: .\cookbook\forms.py:508 #: .\cookbook\forms.py:525
msgid "Autosync" msgid "Autosync"
msgstr "" msgstr ""
#: .\cookbook\forms.py:509 #: .\cookbook\forms.py:526
msgid "Auto Add Meal Plan" msgid "Auto Add Meal Plan"
msgstr "" msgstr ""
#: .\cookbook\forms.py:510 #: .\cookbook\forms.py:527
msgid "Exclude On Hand" msgid "Exclude On Hand"
msgstr "" msgstr ""
#: .\cookbook\forms.py:511 #: .\cookbook\forms.py:528
msgid "Include Related" msgid "Include Related"
msgstr "" msgstr ""
#: .\cookbook\forms.py:512 #: .\cookbook\forms.py:529
msgid "Default Delay Hours" msgid "Default Delay Hours"
msgstr "" msgstr ""
#: .\cookbook\forms.py:513 #: .\cookbook\forms.py:530
msgid "Filter to Supermarket" msgid "Filter to Supermarket"
msgstr "" msgstr ""
#: .\cookbook\forms.py:514 #: .\cookbook\forms.py:531
msgid "Recent Days" msgid "Recent Days"
msgstr "" msgstr ""
#: .\cookbook\forms.py:515 #: .\cookbook\forms.py:532
msgid "CSV Delimiter" msgid "CSV Delimiter"
msgstr "" msgstr ""
#: .\cookbook\forms.py:516 #: .\cookbook\forms.py:533
msgid "List Prefix" msgid "List Prefix"
msgstr "" msgstr ""
#: .\cookbook\forms.py:517 #: .\cookbook\forms.py:534
msgid "Auto On Hand" msgid "Auto On Hand"
msgstr "" msgstr ""
#: .\cookbook\forms.py:527 #: .\cookbook\forms.py:544
msgid "Reset Food Inheritance" msgid "Reset Food Inheritance"
msgstr "" msgstr ""
#: .\cookbook\forms.py:528 #: .\cookbook\forms.py:545
msgid "Reset all food to inherit the fields configured." msgid "Reset all food to inherit the fields configured."
msgstr "" msgstr ""
#: .\cookbook\forms.py:540 #: .\cookbook\forms.py:557
msgid "Fields on food that should be inherited by default." msgid "Fields on food that should be inherited by default."
msgstr "" msgstr ""
#: .\cookbook\forms.py:541 #: .\cookbook\forms.py:558
msgid "Show recipe counts on search filters" msgid "Show recipe counts on search filters"
msgstr "" msgstr ""
#: .\cookbook\forms.py:542 #: .\cookbook\forms.py:559
msgid "Use the plural form for units and food inside this space." msgid "Use the plural form for units and food inside this space."
msgstr "" msgstr ""

View File

@@ -13,9 +13,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-26 07:46+0200\n" "POT-Creation-Date: 2023-05-18 14:28+0200\n"
"PO-Revision-Date: 2023-03-13 06:55+0000\n" "PO-Revision-Date: 2023-05-26 16:19+0000\n"
"Last-Translator: Amara Ude <apu24@drexel.edu>\n" "Last-Translator: Luis Cacho <luiscachog@gmail.com>\n"
"Language-Team: Spanish <http://translate.tandoor.dev/projects/tandoor/" "Language-Team: Spanish <http://translate.tandoor.dev/projects/tandoor/"
"recipes-backend/es/>\n" "recipes-backend/es/>\n"
"Language: es\n" "Language: es\n"
@@ -117,7 +117,7 @@ msgstr "Número de decimales para redondear los ingredientes."
msgid "If you want to be able to create and see comments underneath recipes." msgid "If you want to be able to create and see comments underneath recipes."
msgstr "Si desea poder crear y ver comentarios debajo de las recetas." msgstr "Si desea poder crear y ver comentarios debajo de las recetas."
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492 #: .\cookbook\forms.py:79 .\cookbook\forms.py:509
msgid "" msgid ""
"Setting to 0 will disable auto sync. When viewing a shopping list the list " "Setting to 0 will disable auto sync. When viewing a shopping list the list "
"is updated every set seconds to sync changes someone else might have made. " "is updated every set seconds to sync changes someone else might have made. "
@@ -135,7 +135,7 @@ msgstr ""
msgid "Makes the navbar stick to the top of the page." msgid "Makes the navbar stick to the top of the page."
msgstr "Hace la barra de navegación fija en la parte superior de la página." msgstr "Hace la barra de navegación fija en la parte superior de la página."
#: .\cookbook\forms.py:83 .\cookbook\forms.py:495 #: .\cookbook\forms.py:83 .\cookbook\forms.py:512
msgid "Automatically add meal plan ingredients to shopping list." msgid "Automatically add meal plan ingredients to shopping list."
msgstr "" msgstr ""
"Añadir de manera automática los ingredientes del plan a la lista de la " "Añadir de manera automática los ingredientes del plan a la lista de la "
@@ -157,11 +157,11 @@ msgstr ""
"Ambos campos son opcionales. Si no se proporciona ninguno, se mostrará el " "Ambos campos son opcionales. Si no se proporciona ninguno, se mostrará el "
"nombre de usuario en su lugar" "nombre de usuario en su lugar"
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297 #: .\cookbook\forms.py:123 .\cookbook\forms.py:314
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
#: .\cookbook\forms.py:124 .\cookbook\forms.py:298 .\cookbook\views\lists.py:88 #: .\cookbook\forms.py:124 .\cookbook\forms.py:315 .\cookbook\views\lists.py:88
msgid "Keywords" msgid "Keywords"
msgstr "Palabras clave" msgstr "Palabras clave"
@@ -173,7 +173,7 @@ msgstr "Tiempo de preparación en minutos"
msgid "Waiting time (cooking/baking) in minutes" msgid "Waiting time (cooking/baking) in minutes"
msgstr "Tiempo de espera (cocinar/hornear) en minutos" msgstr "Tiempo de espera (cocinar/hornear) en minutos"
#: .\cookbook\forms.py:127 .\cookbook\forms.py:266 .\cookbook\forms.py:299 #: .\cookbook\forms.py:127 .\cookbook\forms.py:283 .\cookbook\forms.py:316
msgid "Path" msgid "Path"
msgstr "Ruta" msgstr "Ruta"
@@ -185,7 +185,7 @@ msgstr "UID de almacenamiento"
msgid "Default" msgid "Default"
msgstr "Por defecto" msgstr "Por defecto"
#: .\cookbook\forms.py:173 #: .\cookbook\forms.py:190
msgid "" msgid ""
"To prevent duplicates recipes with the same name as existing ones are " "To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything." "ignored. Check this box to import everything."
@@ -193,22 +193,22 @@ msgstr ""
"Para evitar duplicados, las recetas con el mismo nombre serán ignoradas. " "Para evitar duplicados, las recetas con el mismo nombre serán ignoradas. "
"Marca esta opción para importar todas las recetas." "Marca esta opción para importar todas las recetas."
#: .\cookbook\forms.py:196 #: .\cookbook\forms.py:213
msgid "Add your comment: " msgid "Add your comment: "
msgstr "Añada su comentario: " msgstr "Añada su comentario: "
#: .\cookbook\forms.py:211 #: .\cookbook\forms.py:228
msgid "Leave empty for dropbox and enter app password for nextcloud." msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "" msgstr ""
"Déjelo vacío para Dropbox e ingrese la contraseña de la aplicación para " "Déjelo vacío para Dropbox e ingrese la contraseña de la aplicación para "
"nextcloud." "nextcloud."
#: .\cookbook\forms.py:218 #: .\cookbook\forms.py:235
msgid "Leave empty for nextcloud and enter api token for dropbox." msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "" msgstr ""
"Déjelo en blanco para nextcloud e ingrese el token de api para dropbox." "Déjelo en blanco para nextcloud e ingrese el token de api para dropbox."
#: .\cookbook\forms.py:227 #: .\cookbook\forms.py:244
msgid "" msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote." "Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
"php/webdav/</code> is added automatically)" "php/webdav/</code> is added automatically)"
@@ -216,33 +216,33 @@ msgstr ""
"Dejar vació para Dropbox e introducir sólo la URL base para Nextcloud " "Dejar vació para Dropbox e introducir sólo la URL base para Nextcloud "
"(<code>/remote.php/webdav/</code> se añade automáticamente)" "(<code>/remote.php/webdav/</code> se añade automáticamente)"
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157 #: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
msgid "Storage" msgid "Storage"
msgstr "Almacenamiento" msgstr "Almacenamiento"
#: .\cookbook\forms.py:267 #: .\cookbook\forms.py:284
msgid "Active" msgid "Active"
msgstr "Activo" msgstr "Activo"
#: .\cookbook\forms.py:273 #: .\cookbook\forms.py:290
msgid "Search String" msgid "Search String"
msgstr "Cadena de búsqueda" msgstr "Cadena de búsqueda"
#: .\cookbook\forms.py:300 #: .\cookbook\forms.py:317
msgid "File ID" msgid "File ID"
msgstr "ID de Fichero" msgstr "ID de Fichero"
#: .\cookbook\forms.py:322 #: .\cookbook\forms.py:339
msgid "You must provide at least a recipe or a title." msgid "You must provide at least a recipe or a title."
msgstr "Debe proporcionar al menos una receta o un título." msgstr "Debe proporcionar al menos una receta o un título."
#: .\cookbook\forms.py:335 #: .\cookbook\forms.py:352
msgid "You can list default users to share recipes with in the settings." msgid "You can list default users to share recipes with in the settings."
msgstr "" msgstr ""
"Puede enumerar los usuarios predeterminados con los que compartir recetas en " "Puede enumerar los usuarios predeterminados con los que compartir recetas en "
"la configuración." "la configuración."
#: .\cookbook\forms.py:336 #: .\cookbook\forms.py:353
msgid "" msgid ""
"You can use markdown to format this field. See the <a href=\"/docs/markdown/" "You can use markdown to format this field. See the <a href=\"/docs/markdown/"
"\">docs here</a>" "\">docs here</a>"
@@ -250,15 +250,15 @@ msgstr ""
"Puede utilizar Markdown para formatear este campo. Vea la <a href=\"/docs/" "Puede utilizar Markdown para formatear este campo. Vea la <a href=\"/docs/"
"markdown/\">documentación aqui</a>" "markdown/\">documentación aqui</a>"
#: .\cookbook\forms.py:362 #: .\cookbook\forms.py:379
msgid "Maximum number of users for this space reached." msgid "Maximum number of users for this space reached."
msgstr "Se ha alcanzado el número máximo de usuarios en este espacio." msgstr "Se ha alcanzado el número máximo de usuarios en este espacio."
#: .\cookbook\forms.py:368 #: .\cookbook\forms.py:385
msgid "Email address already taken!" msgid "Email address already taken!"
msgstr "¡El correo electrónico ya existe!" msgstr "¡El correo electrónico ya existe!"
#: .\cookbook\forms.py:376 #: .\cookbook\forms.py:393
msgid "" msgid ""
"An email address is not required but if present the invite link will be sent " "An email address is not required but if present the invite link will be sent "
"to the user." "to the user."
@@ -266,15 +266,15 @@ msgstr ""
"El correo electrónico es opcional. Si se añade uno se mandará un link de " "El correo electrónico es opcional. Si se añade uno se mandará un link de "
"invitación." "invitación."
#: .\cookbook\forms.py:391 #: .\cookbook\forms.py:408
msgid "Name already taken." msgid "Name already taken."
msgstr "El nombre ya existe." msgstr "El nombre ya existe."
#: .\cookbook\forms.py:402 #: .\cookbook\forms.py:419
msgid "Accept Terms and Privacy" msgid "Accept Terms and Privacy"
msgstr "Aceptar términos y condiciones" msgstr "Aceptar términos y condiciones"
#: .\cookbook\forms.py:434 #: .\cookbook\forms.py:451
msgid "" msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e." "Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)." "g. low values mean more typos are ignored)."
@@ -283,7 +283,7 @@ msgstr ""
"similitud de trigramas(Ej. Valores más pequeños indican que más fallos se " "similitud de trigramas(Ej. Valores más pequeños indican que más fallos se "
"van a ignorar)." "van a ignorar)."
#: .\cookbook\forms.py:444 #: .\cookbook\forms.py:461
msgid "" msgid ""
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for " "Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
"full description of choices." "full description of choices."
@@ -291,7 +291,7 @@ msgstr ""
"Selecciona el tipo de búsqueda. Haz click <a href=\"/docs/search/\">aquí</" "Selecciona el tipo de búsqueda. Haz click <a href=\"/docs/search/\">aquí</"
"a> para una descripción completa de las opciones." "a> para una descripción completa de las opciones."
#: .\cookbook\forms.py:445 #: .\cookbook\forms.py:462
msgid "" msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and " "Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes." "importing recipes."
@@ -299,7 +299,7 @@ msgstr ""
"Utilizar comparación difusa en unidades, palabras clave e ingredientes al " "Utilizar comparación difusa en unidades, palabras clave e ingredientes al "
"editar e importar recetas." "editar e importar recetas."
#: .\cookbook\forms.py:447 #: .\cookbook\forms.py:464
msgid "" msgid ""
"Fields to search ignoring accents. Selecting this option can improve or " "Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language" "degrade search quality depending on language"
@@ -307,7 +307,7 @@ msgstr ""
"Campos de búsqueda ignorando acentos.  La selección de esta opción puede " "Campos de búsqueda ignorando acentos.  La selección de esta opción puede "
"mejorar o degradar la calidad de la búsqueda dependiendo del idioma" "mejorar o degradar la calidad de la búsqueda dependiendo del idioma"
#: .\cookbook\forms.py:449 #: .\cookbook\forms.py:466
msgid "" msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return " "Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')" "'pie' and 'piece' and 'soapie')"
@@ -315,7 +315,7 @@ msgstr ""
"Campos de búsqueda para coincidencias parciales. (por ejemplo, buscar 'Pie' " "Campos de búsqueda para coincidencias parciales. (por ejemplo, buscar 'Pie' "
"devolverá 'pie' y 'piece' y 'soapie')" "devolverá 'pie' y 'piece' y 'soapie')"
#: .\cookbook\forms.py:451 #: .\cookbook\forms.py:468
msgid "" msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' " "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')" "will return 'salad' and 'sandwich')"
@@ -323,7 +323,7 @@ msgstr ""
"Campos de búsqueda para coincidencias al principio de la palabra. (por " "Campos de búsqueda para coincidencias al principio de la palabra. (por "
"ejemplo, buscar 'sa' devolverá 'ensalada' y 'sándwich')" "ejemplo, buscar 'sa' devolverá 'ensalada' y 'sándwich')"
#: .\cookbook\forms.py:453 #: .\cookbook\forms.py:470
msgid "" msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) " "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search." "Note: this option will conflict with 'web' and 'raw' methods of search."
@@ -332,7 +332,7 @@ msgstr ""
"'receta'). Nota: esta opción entrará en conflicto con los métodos de " "'receta'). Nota: esta opción entrará en conflicto con los métodos de "
"búsqueda 'web' y 'raw'." "búsqueda 'web' y 'raw'."
#: .\cookbook\forms.py:455 #: .\cookbook\forms.py:472
msgid "" msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods " "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields." "only function with fulltext fields."
@@ -340,35 +340,35 @@ msgstr ""
"Campos para búsqueda de texto completo. Nota: los métodos de búsqueda 'web', " "Campos para búsqueda de texto completo. Nota: los métodos de búsqueda 'web', "
"'phrase' y 'raw' solo funcionan con campos de texto completo." "'phrase' y 'raw' solo funcionan con campos de texto completo."
#: .\cookbook\forms.py:459 #: .\cookbook\forms.py:476
msgid "Search Method" msgid "Search Method"
msgstr "Método de Búsqueda" msgstr "Método de Búsqueda"
#: .\cookbook\forms.py:460 #: .\cookbook\forms.py:477
msgid "Fuzzy Lookups" msgid "Fuzzy Lookups"
msgstr "Búsquedas difusas" msgstr "Búsquedas difusas"
#: .\cookbook\forms.py:461 #: .\cookbook\forms.py:478
msgid "Ignore Accent" msgid "Ignore Accent"
msgstr "Ignorar Acento" msgstr "Ignorar Acento"
#: .\cookbook\forms.py:462 #: .\cookbook\forms.py:479
msgid "Partial Match" msgid "Partial Match"
msgstr "Coincidencia Parcial" msgstr "Coincidencia Parcial"
#: .\cookbook\forms.py:463 #: .\cookbook\forms.py:480
msgid "Starts With" msgid "Starts With"
msgstr "Comienza Con" msgstr "Comienza Con"
#: .\cookbook\forms.py:464 #: .\cookbook\forms.py:481
msgid "Fuzzy Search" msgid "Fuzzy Search"
msgstr "Búsqueda Difusa" msgstr "Búsqueda Difusa"
#: .\cookbook\forms.py:465 #: .\cookbook\forms.py:482
msgid "Full Text" msgid "Full Text"
msgstr "Texto Completo" msgstr "Texto Completo"
#: .\cookbook\forms.py:490 #: .\cookbook\forms.py:507
msgid "" msgid ""
"Users will see all items you add to your shopping list. They must add you " "Users will see all items you add to your shopping list. They must add you "
"to see items on their list." "to see items on their list."
@@ -376,7 +376,7 @@ msgstr ""
"Los usuarios verán todos los elementos que agregues a tu lista de compras. " "Los usuarios verán todos los elementos que agregues a tu lista de compras. "
"Deben agregarte para ver los elementos en su lista." "Deben agregarte para ver los elementos en su lista."
#: .\cookbook\forms.py:496 #: .\cookbook\forms.py:513
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes." "include all related recipes."
@@ -384,7 +384,7 @@ msgstr ""
"Al agregar un plan de comidas a la lista de compras (manualmente o " "Al agregar un plan de comidas a la lista de compras (manualmente o "
"automáticamente), incluir todas las recetas relacionadas." "automáticamente), incluir todas las recetas relacionadas."
#: .\cookbook\forms.py:497 #: .\cookbook\forms.py:514
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"exclude ingredients that are on hand." "exclude ingredients that are on hand."
@@ -392,96 +392,96 @@ msgstr ""
"Al agregar un plan de comidas a la lista de compras (manualmente o " "Al agregar un plan de comidas a la lista de compras (manualmente o "
"automáticamente), excluir los ingredientes que están disponibles." "automáticamente), excluir los ingredientes que están disponibles."
#: .\cookbook\forms.py:498 #: .\cookbook\forms.py:515
msgid "Default number of hours to delay a shopping list entry." msgid "Default number of hours to delay a shopping list entry."
msgstr "" msgstr ""
"Número predeterminado de horas para retrasar una entrada en la lista de " "Número predeterminado de horas para retrasar una entrada en la lista de "
"compras." "compras."
#: .\cookbook\forms.py:499 #: .\cookbook\forms.py:516
msgid "Filter shopping list to only include supermarket categories." msgid "Filter shopping list to only include supermarket categories."
msgstr "" msgstr ""
"Filtrar la lista de compras para incluir solo categorías de supermercados." "Filtrar la lista de compras para incluir solo categorías de supermercados."
#: .\cookbook\forms.py:500 #: .\cookbook\forms.py:517
msgid "Days of recent shopping list entries to display." msgid "Days of recent shopping list entries to display."
msgstr "Días de entradas recientes en la lista de compras a mostrar." msgstr "Días de entradas recientes en la lista de compras a mostrar."
#: .\cookbook\forms.py:501 #: .\cookbook\forms.py:518
msgid "Mark food 'On Hand' when checked off shopping list." msgid "Mark food 'On Hand' when checked off shopping list."
msgstr "" msgstr ""
"Marcar los alimentos como 'Disponible' cuando se marca en la lista de " "Marcar los alimentos como 'Disponible' cuando se marca en la lista de "
"compras." "compras."
#: .\cookbook\forms.py:502 #: .\cookbook\forms.py:519
msgid "Delimiter to use for CSV exports." msgid "Delimiter to use for CSV exports."
msgstr "Delimitador a utilizar para exportaciones CSV." msgstr "Delimitador a utilizar para exportaciones CSV."
#: .\cookbook\forms.py:503 #: .\cookbook\forms.py:520
msgid "Prefix to add when copying list to the clipboard." msgid "Prefix to add when copying list to the clipboard."
msgstr "Prefijo a agregar al copiar la lista al portapapeles." msgstr "Prefijo a agregar al copiar la lista al portapapeles."
#: .\cookbook\forms.py:507 #: .\cookbook\forms.py:524
msgid "Share Shopping List" msgid "Share Shopping List"
msgstr "Compartir Lista de la Compra" msgstr "Compartir Lista de la Compra"
#: .\cookbook\forms.py:508 #: .\cookbook\forms.py:525
msgid "Autosync" msgid "Autosync"
msgstr "Autosincronización" msgstr "Autosincronización"
#: .\cookbook\forms.py:509 #: .\cookbook\forms.py:526
msgid "Auto Add Meal Plan" msgid "Auto Add Meal Plan"
msgstr "Agregar Plan de Comidas automáticamente" msgstr "Agregar Plan de Comidas automáticamente"
#: .\cookbook\forms.py:510 #: .\cookbook\forms.py:527
msgid "Exclude On Hand" msgid "Exclude On Hand"
msgstr "Excluir Disponible" msgstr "Excluir Disponible"
#: .\cookbook\forms.py:511 #: .\cookbook\forms.py:528
msgid "Include Related" msgid "Include Related"
msgstr "Incluir Relacionados" msgstr "Incluir Relacionados"
#: .\cookbook\forms.py:512 #: .\cookbook\forms.py:529
msgid "Default Delay Hours" msgid "Default Delay Hours"
msgstr "Horas de Retraso Predeterminadas" msgstr "Horas de Retraso Predeterminadas"
#: .\cookbook\forms.py:513 #: .\cookbook\forms.py:530
msgid "Filter to Supermarket" msgid "Filter to Supermarket"
msgstr "Filtrar según Supermercado" msgstr "Filtrar según Supermercado"
#: .\cookbook\forms.py:514 #: .\cookbook\forms.py:531
msgid "Recent Days" msgid "Recent Days"
msgstr "Días Recientes" msgstr "Días Recientes"
#: .\cookbook\forms.py:515 #: .\cookbook\forms.py:532
msgid "CSV Delimiter" msgid "CSV Delimiter"
msgstr "Delimitador CSV" msgstr "Delimitador CSV"
#: .\cookbook\forms.py:516 #: .\cookbook\forms.py:533
msgid "List Prefix" msgid "List Prefix"
msgstr "Prefijo de la lista" msgstr "Prefijo de la lista"
#: .\cookbook\forms.py:517 #: .\cookbook\forms.py:534
msgid "Auto On Hand" msgid "Auto On Hand"
msgstr "Auto en existencia" msgstr "Auto en existencia"
#: .\cookbook\forms.py:527 #: .\cookbook\forms.py:544
msgid "Reset Food Inheritance" msgid "Reset Food Inheritance"
msgstr "Restablecer la herencia de alimentos" msgstr "Restablecer la herencia de alimentos"
#: .\cookbook\forms.py:528 #: .\cookbook\forms.py:545
msgid "Reset all food to inherit the fields configured." msgid "Reset all food to inherit the fields configured."
msgstr "Reiniciar todos los alimentos para heredar los campos configurados." msgstr "Reiniciar todos los alimentos para heredar los campos configurados."
#: .\cookbook\forms.py:540 #: .\cookbook\forms.py:557
msgid "Fields on food that should be inherited by default." msgid "Fields on food that should be inherited by default."
msgstr "Campos en los alimentos que deben ser heredados por defecto." msgstr "Campos en los alimentos que deben ser heredados por defecto."
#: .\cookbook\forms.py:541 #: .\cookbook\forms.py:558
msgid "Show recipe counts on search filters" msgid "Show recipe counts on search filters"
msgstr "Mostrar cantidad de recetas en los filtros de búsquedas" msgstr "Mostrar cantidad de recetas en los filtros de búsquedas"
#: .\cookbook\forms.py:542 #: .\cookbook\forms.py:559
msgid "Use the plural form for units and food inside this space." msgid "Use the plural form for units and food inside this space."
msgstr "" msgstr ""
"Utilice la forma plural para las unidades y alimentos dentro de este espacio." "Utilice la forma plural para las unidades y alimentos dentro de este espacio."
@@ -603,10 +603,8 @@ msgid "Imported %s recipes."
msgstr "Se importaron %s recetas." msgstr "Se importaron %s recetas."
#: .\cookbook\integration\openeats.py:26 #: .\cookbook\integration\openeats.py:26
#, fuzzy
#| msgid "Recipe Home"
msgid "Recipe source:" msgid "Recipe source:"
msgstr "Página de inicio" msgstr "Recipe source:"
#: .\cookbook\integration\paprika.py:49 #: .\cookbook\integration\paprika.py:49
msgid "Notes" msgid "Notes"

View File

@@ -13,7 +13,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-26 07:46+0200\n" "POT-Creation-Date: 2023-05-18 14:28+0200\n"
"PO-Revision-Date: 2023-04-12 11:55+0000\n" "PO-Revision-Date: 2023-04-12 11:55+0000\n"
"Last-Translator: noxonad <noxonad@proton.me>\n" "Last-Translator: noxonad <noxonad@proton.me>\n"
"Language-Team: French <http://translate.tandoor.dev/projects/tandoor/recipes-" "Language-Team: French <http://translate.tandoor.dev/projects/tandoor/recipes-"
@@ -121,7 +121,7 @@ msgstr ""
"Si vous souhaitez pouvoir créer et consulter des commentaires en dessous des " "Si vous souhaitez pouvoir créer et consulter des commentaires en dessous des "
"recettes." "recettes."
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492 #: .\cookbook\forms.py:79 .\cookbook\forms.py:509
msgid "" msgid ""
"Setting to 0 will disable auto sync. When viewing a shopping list the list " "Setting to 0 will disable auto sync. When viewing a shopping list the list "
"is updated every set seconds to sync changes someone else might have made. " "is updated every set seconds to sync changes someone else might have made. "
@@ -139,7 +139,7 @@ msgstr ""
msgid "Makes the navbar stick to the top of the page." msgid "Makes the navbar stick to the top of the page."
msgstr "Épingler la barre de navigation en haut de la page." msgstr "Épingler la barre de navigation en haut de la page."
#: .\cookbook\forms.py:83 .\cookbook\forms.py:495 #: .\cookbook\forms.py:83 .\cookbook\forms.py:512
msgid "Automatically add meal plan ingredients to shopping list." msgid "Automatically add meal plan ingredients to shopping list."
msgstr "" msgstr ""
"Ajouter les ingrédients du menu de la semaine à la liste de courses " "Ajouter les ingrédients du menu de la semaine à la liste de courses "
@@ -161,11 +161,11 @@ msgstr ""
"Les deux champs sont facultatifs. Si aucun nest rempli, le nom " "Les deux champs sont facultatifs. Si aucun nest rempli, le nom "
"dutilisateur sera affiché à la place" "dutilisateur sera affiché à la place"
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297 #: .\cookbook\forms.py:123 .\cookbook\forms.py:314
msgid "Name" msgid "Name"
msgstr "Nom" msgstr "Nom"
#: .\cookbook\forms.py:124 .\cookbook\forms.py:298 .\cookbook\views\lists.py:88 #: .\cookbook\forms.py:124 .\cookbook\forms.py:315 .\cookbook\views\lists.py:88
msgid "Keywords" msgid "Keywords"
msgstr "Mots-clés" msgstr "Mots-clés"
@@ -177,7 +177,7 @@ msgstr "Temps de préparation en minutes"
msgid "Waiting time (cooking/baking) in minutes" msgid "Waiting time (cooking/baking) in minutes"
msgstr "Temps dattente (cuisson) en minutes" msgstr "Temps dattente (cuisson) en minutes"
#: .\cookbook\forms.py:127 .\cookbook\forms.py:266 .\cookbook\forms.py:299 #: .\cookbook\forms.py:127 .\cookbook\forms.py:283 .\cookbook\forms.py:316
msgid "Path" msgid "Path"
msgstr "Chemin" msgstr "Chemin"
@@ -189,7 +189,7 @@ msgstr "UID de stockage"
msgid "Default" msgid "Default"
msgstr "Par défaut" msgstr "Par défaut"
#: .\cookbook\forms.py:173 #: .\cookbook\forms.py:190
msgid "" msgid ""
"To prevent duplicates recipes with the same name as existing ones are " "To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything." "ignored. Check this box to import everything."
@@ -197,22 +197,22 @@ msgstr ""
"Pour éviter les doublons, les recettes de même nom seront ignorées. Cocher " "Pour éviter les doublons, les recettes de même nom seront ignorées. Cocher "
"cette case pour tout importer." "cette case pour tout importer."
#: .\cookbook\forms.py:196 #: .\cookbook\forms.py:213
msgid "Add your comment: " msgid "Add your comment: "
msgstr "Ajoutez votre commentaire : " msgstr "Ajoutez votre commentaire : "
#: .\cookbook\forms.py:211 #: .\cookbook\forms.py:228
msgid "Leave empty for dropbox and enter app password for nextcloud." msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "" msgstr ""
"Laissez vide pour Dropbox et renseignez votre mot de passe dapplication " "Laissez vide pour Dropbox et renseignez votre mot de passe dapplication "
"pour Nextcloud." "pour Nextcloud."
#: .\cookbook\forms.py:218 #: .\cookbook\forms.py:235
msgid "Leave empty for nextcloud and enter api token for dropbox." msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "" msgstr ""
"Laissez vide pour Nextcloud et renseignez votre jeton dAPI pour Dropbox." "Laissez vide pour Nextcloud et renseignez votre jeton dAPI pour Dropbox."
#: .\cookbook\forms.py:227 #: .\cookbook\forms.py:244
msgid "" msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote." "Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
"php/webdav/</code> is added automatically)" "php/webdav/</code> is added automatically)"
@@ -220,33 +220,33 @@ msgstr ""
"Laisser vide pour Dropbox et saisissez seulement lURL de base pour " "Laisser vide pour Dropbox et saisissez seulement lURL de base pour "
"Nextcloud (<code>/remote.php/webdav/</code> est ajouté automatiquement)" "Nextcloud (<code>/remote.php/webdav/</code> est ajouté automatiquement)"
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157 #: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
msgid "Storage" msgid "Storage"
msgstr "Stockage" msgstr "Stockage"
#: .\cookbook\forms.py:267 #: .\cookbook\forms.py:284
msgid "Active" msgid "Active"
msgstr "Actif" msgstr "Actif"
#: .\cookbook\forms.py:273 #: .\cookbook\forms.py:290
msgid "Search String" msgid "Search String"
msgstr "Texte recherché" msgstr "Texte recherché"
#: .\cookbook\forms.py:300 #: .\cookbook\forms.py:317
msgid "File ID" msgid "File ID"
msgstr "ID du fichier" msgstr "ID du fichier"
#: .\cookbook\forms.py:322 #: .\cookbook\forms.py:339
msgid "You must provide at least a recipe or a title." msgid "You must provide at least a recipe or a title."
msgstr "Vous devez au moins fournir une recette ou un titre." msgstr "Vous devez au moins fournir une recette ou un titre."
#: .\cookbook\forms.py:335 #: .\cookbook\forms.py:352
msgid "You can list default users to share recipes with in the settings." msgid "You can list default users to share recipes with in the settings."
msgstr "" msgstr ""
"Vous pouvez lister les utilisateurs par défaut avec qui partager des " "Vous pouvez lister les utilisateurs par défaut avec qui partager des "
"recettes dans les paramètres." "recettes dans les paramètres."
#: .\cookbook\forms.py:336 #: .\cookbook\forms.py:353
msgid "" msgid ""
"You can use markdown to format this field. See the <a href=\"/docs/markdown/" "You can use markdown to format this field. See the <a href=\"/docs/markdown/"
"\">docs here</a>" "\">docs here</a>"
@@ -254,15 +254,15 @@ msgstr ""
"Vous pouvez utiliser du markdown pour mettre en forme ce champ. Voir la <a " "Vous pouvez utiliser du markdown pour mettre en forme ce champ. Voir la <a "
"href=\"/docs/markdown/\">documentation ici</a>" "href=\"/docs/markdown/\">documentation ici</a>"
#: .\cookbook\forms.py:362 #: .\cookbook\forms.py:379
msgid "Maximum number of users for this space reached." msgid "Maximum number of users for this space reached."
msgstr "Nombre maximum dutilisateurs atteint pour ce groupe." msgstr "Nombre maximum dutilisateurs atteint pour ce groupe."
#: .\cookbook\forms.py:368 #: .\cookbook\forms.py:385
msgid "Email address already taken!" msgid "Email address already taken!"
msgstr "Adresse mail déjà utilisée !" msgstr "Adresse mail déjà utilisée !"
#: .\cookbook\forms.py:376 #: .\cookbook\forms.py:393
msgid "" msgid ""
"An email address is not required but if present the invite link will be sent " "An email address is not required but if present the invite link will be sent "
"to the user." "to the user."
@@ -270,15 +270,15 @@ msgstr ""
"Une adresse mail nest pas requise mais si elle est renseignée, le lien " "Une adresse mail nest pas requise mais si elle est renseignée, le lien "
"dinvitation sera envoyé à lutilisateur." "dinvitation sera envoyé à lutilisateur."
#: .\cookbook\forms.py:391 #: .\cookbook\forms.py:408
msgid "Name already taken." msgid "Name already taken."
msgstr "Nom déjà utilisé." msgstr "Nom déjà utilisé."
#: .\cookbook\forms.py:402 #: .\cookbook\forms.py:419
msgid "Accept Terms and Privacy" msgid "Accept Terms and Privacy"
msgstr "Accepter les conditions dutilisation" msgstr "Accepter les conditions dutilisation"
#: .\cookbook\forms.py:434 #: .\cookbook\forms.py:451
msgid "" msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e." "Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)." "g. low values mean more typos are ignored)."
@@ -287,7 +287,7 @@ msgstr ""
"par similarité de trigrammes (par exemple, des valeurs faibles signifient " "par similarité de trigrammes (par exemple, des valeurs faibles signifient "
"que davantage de fautes de frappe sont ignorées)." "que davantage de fautes de frappe sont ignorées)."
#: .\cookbook\forms.py:444 #: .\cookbook\forms.py:461
msgid "" msgid ""
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for " "Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
"full description of choices." "full description of choices."
@@ -295,7 +295,7 @@ msgstr ""
"Sélectionner la méthode de recherche. Cliquer <a href=\"/docs/search/" "Sélectionner la méthode de recherche. Cliquer <a href=\"/docs/search/"
"\">ici</a> pour une description complète des choix." "\">ici</a> pour une description complète des choix."
#: .\cookbook\forms.py:445 #: .\cookbook\forms.py:462
msgid "" msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and " "Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes." "importing recipes."
@@ -303,7 +303,7 @@ msgstr ""
"Utilisez la correspondance floue sur les unités, les mots-clés et les " "Utilisez la correspondance floue sur les unités, les mots-clés et les "
"ingrédients lors de lédition et de limportation de recettes." "ingrédients lors de lédition et de limportation de recettes."
#: .\cookbook\forms.py:447 #: .\cookbook\forms.py:464
msgid "" msgid ""
"Fields to search ignoring accents. Selecting this option can improve or " "Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language" "degrade search quality depending on language"
@@ -312,7 +312,7 @@ msgstr ""
"peut améliorer ou dégrader la qualité de la recherche en fonction de la " "peut améliorer ou dégrader la qualité de la recherche en fonction de la "
"langue." "langue."
#: .\cookbook\forms.py:449 #: .\cookbook\forms.py:466
msgid "" msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return " "Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')" "'pie' and 'piece' and 'soapie')"
@@ -320,7 +320,7 @@ msgstr ""
"Champs à rechercher pour les correspondances partielles. (par exemple, la " "Champs à rechercher pour les correspondances partielles. (par exemple, la "
"recherche de « Tarte » renverra « tarte », « tartelette » et « tartes »)" "recherche de « Tarte » renverra « tarte », « tartelette » et « tartes »)"
#: .\cookbook\forms.py:451 #: .\cookbook\forms.py:468
msgid "" msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' " "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')" "will return 'salad' and 'sandwich')"
@@ -329,7 +329,7 @@ msgstr ""
"exemple, si vous recherchez « sa », vous obtiendrez « salade » et " "exemple, si vous recherchez « sa », vous obtiendrez « salade » et "
 sandwich»)."  sandwich»)."
#: .\cookbook\forms.py:453 #: .\cookbook\forms.py:470
msgid "" msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) " "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search." "Note: this option will conflict with 'web' and 'raw' methods of search."
@@ -338,7 +338,7 @@ msgstr ""
 rectte», vous trouverez « recette ».) Remarque : cette option est "  rectte», vous trouverez « recette ».) Remarque : cette option est "
"incompatible avec les méthodes de recherche « web » et « brute »." "incompatible avec les méthodes de recherche « web » et « brute »."
#: .\cookbook\forms.py:455 #: .\cookbook\forms.py:472
msgid "" msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods " "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields." "only function with fulltext fields."
@@ -347,35 +347,35 @@ msgstr ""
 web », « phrase » et « brute » ne fonctionnent quavec des champs en texte "  web », « phrase » et « brute » ne fonctionnent quavec des champs en texte "
"intégral." "intégral."
#: .\cookbook\forms.py:459 #: .\cookbook\forms.py:476
msgid "Search Method" msgid "Search Method"
msgstr "Méthode de recherche" msgstr "Méthode de recherche"
#: .\cookbook\forms.py:460 #: .\cookbook\forms.py:477
msgid "Fuzzy Lookups" msgid "Fuzzy Lookups"
msgstr "Recherches floues" msgstr "Recherches floues"
#: .\cookbook\forms.py:461 #: .\cookbook\forms.py:478
msgid "Ignore Accent" msgid "Ignore Accent"
msgstr "Ignorer les accents" msgstr "Ignorer les accents"
#: .\cookbook\forms.py:462 #: .\cookbook\forms.py:479
msgid "Partial Match" msgid "Partial Match"
msgstr "correspondance partielle" msgstr "correspondance partielle"
#: .\cookbook\forms.py:463 #: .\cookbook\forms.py:480
msgid "Starts With" msgid "Starts With"
msgstr "Commence par" msgstr "Commence par"
#: .\cookbook\forms.py:464 #: .\cookbook\forms.py:481
msgid "Fuzzy Search" msgid "Fuzzy Search"
msgstr "Recherche floue" msgstr "Recherche floue"
#: .\cookbook\forms.py:465 #: .\cookbook\forms.py:482
msgid "Full Text" msgid "Full Text"
msgstr "Texte intégral" msgstr "Texte intégral"
#: .\cookbook\forms.py:490 #: .\cookbook\forms.py:507
msgid "" msgid ""
"Users will see all items you add to your shopping list. They must add you " "Users will see all items you add to your shopping list. They must add you "
"to see items on their list." "to see items on their list."
@@ -384,7 +384,7 @@ msgstr ""
"courses. Ils doivent vous ajouter pour que vous puissiez voir les éléments " "courses. Ils doivent vous ajouter pour que vous puissiez voir les éléments "
"de leur liste." "de leur liste."
#: .\cookbook\forms.py:496 #: .\cookbook\forms.py:513
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes." "include all related recipes."
@@ -392,7 +392,7 @@ msgstr ""
"Lors de lajout dun menu de la semaine à la liste de courses (manuel ou " "Lors de lajout dun menu de la semaine à la liste de courses (manuel ou "
"automatique), inclure toutes les recettes connexes." "automatique), inclure toutes les recettes connexes."
#: .\cookbook\forms.py:497 #: .\cookbook\forms.py:514
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"exclude ingredients that are on hand." "exclude ingredients that are on hand."
@@ -400,97 +400,97 @@ msgstr ""
"Lors de lajout dun menu de la semaine à la liste de courses (manuel ou " "Lors de lajout dun menu de la semaine à la liste de courses (manuel ou "
"automatique), exclure les ingrédients disponibles." "automatique), exclure les ingrédients disponibles."
#: .\cookbook\forms.py:498 #: .\cookbook\forms.py:515
msgid "Default number of hours to delay a shopping list entry." msgid "Default number of hours to delay a shopping list entry."
msgstr "" msgstr ""
"Nombre d'heures par défaut pour retarder l'ajoût d'un article à la liste de " "Nombre d'heures par défaut pour retarder l'ajoût d'un article à la liste de "
"courses." "courses."
#: .\cookbook\forms.py:499 #: .\cookbook\forms.py:516
msgid "Filter shopping list to only include supermarket categories." msgid "Filter shopping list to only include supermarket categories."
msgstr "" msgstr ""
"Filtrer la liste de courses pour ninclure que des catégories de " "Filtrer la liste de courses pour ninclure que des catégories de "
"supermarchés." "supermarchés."
#: .\cookbook\forms.py:500 #: .\cookbook\forms.py:517
msgid "Days of recent shopping list entries to display." msgid "Days of recent shopping list entries to display."
msgstr "Jours des entrées récentes de la liste de courses à afficher." msgstr "Jours des entrées récentes de la liste de courses à afficher."
#: .\cookbook\forms.py:501 #: .\cookbook\forms.py:518
msgid "Mark food 'On Hand' when checked off shopping list." msgid "Mark food 'On Hand' when checked off shopping list."
msgstr "" msgstr ""
"Marquer laliment comme disponible lorsquil est rayé de la liste de courses." "Marquer laliment comme disponible lorsquil est rayé de la liste de courses."
#: .\cookbook\forms.py:502 #: .\cookbook\forms.py:519
msgid "Delimiter to use for CSV exports." msgid "Delimiter to use for CSV exports."
msgstr "Caractère de séparation à utiliser pour les exportations CSV." msgstr "Caractère de séparation à utiliser pour les exportations CSV."
#: .\cookbook\forms.py:503 #: .\cookbook\forms.py:520
msgid "Prefix to add when copying list to the clipboard." msgid "Prefix to add when copying list to the clipboard."
msgstr "Préfixe à ajouter lors de la copie de la liste dans le presse-papiers." msgstr "Préfixe à ajouter lors de la copie de la liste dans le presse-papiers."
#: .\cookbook\forms.py:507 #: .\cookbook\forms.py:524
msgid "Share Shopping List" msgid "Share Shopping List"
msgstr "Partager la liste de courses" msgstr "Partager la liste de courses"
#: .\cookbook\forms.py:508 #: .\cookbook\forms.py:525
msgid "Autosync" msgid "Autosync"
msgstr "Synchronisation automatique" msgstr "Synchronisation automatique"
#: .\cookbook\forms.py:509 #: .\cookbook\forms.py:526
msgid "Auto Add Meal Plan" msgid "Auto Add Meal Plan"
msgstr "Ajouter le menu de la semaine automatiquement" msgstr "Ajouter le menu de la semaine automatiquement"
#: .\cookbook\forms.py:510 #: .\cookbook\forms.py:527
msgid "Exclude On Hand" msgid "Exclude On Hand"
msgstr "Exclure ingrédients disponibles" msgstr "Exclure ingrédients disponibles"
#: .\cookbook\forms.py:511 #: .\cookbook\forms.py:528
msgid "Include Related" msgid "Include Related"
msgstr "Inclure recettes connexes" msgstr "Inclure recettes connexes"
#: .\cookbook\forms.py:512 #: .\cookbook\forms.py:529
msgid "Default Delay Hours" msgid "Default Delay Hours"
msgstr "Heures de retard par défaut" msgstr "Heures de retard par défaut"
#: .\cookbook\forms.py:513 #: .\cookbook\forms.py:530
msgid "Filter to Supermarket" msgid "Filter to Supermarket"
msgstr "Filtrer par supermarché" msgstr "Filtrer par supermarché"
#: .\cookbook\forms.py:514 #: .\cookbook\forms.py:531
msgid "Recent Days" msgid "Recent Days"
msgstr "Jours récents" msgstr "Jours récents"
#: .\cookbook\forms.py:515 #: .\cookbook\forms.py:532
msgid "CSV Delimiter" msgid "CSV Delimiter"
msgstr "Caractère de séparation CSV" msgstr "Caractère de séparation CSV"
#: .\cookbook\forms.py:516 #: .\cookbook\forms.py:533
msgid "List Prefix" msgid "List Prefix"
msgstr "Préfixe de la liste" msgstr "Préfixe de la liste"
#: .\cookbook\forms.py:517 #: .\cookbook\forms.py:534
msgid "Auto On Hand" msgid "Auto On Hand"
msgstr "Disponible automatique" msgstr "Disponible automatique"
#: .\cookbook\forms.py:527 #: .\cookbook\forms.py:544
msgid "Reset Food Inheritance" msgid "Reset Food Inheritance"
msgstr "Réinitialiser l'héritage alimentaire" msgstr "Réinitialiser l'héritage alimentaire"
#: .\cookbook\forms.py:528 #: .\cookbook\forms.py:545
msgid "Reset all food to inherit the fields configured." msgid "Reset all food to inherit the fields configured."
msgstr "Réinitialiser tous les aliments pour hériter les champs configurés." msgstr "Réinitialiser tous les aliments pour hériter les champs configurés."
#: .\cookbook\forms.py:540 #: .\cookbook\forms.py:557
msgid "Fields on food that should be inherited by default." msgid "Fields on food that should be inherited by default."
msgstr "Champs sur les aliments à hériter par défaut." msgstr "Champs sur les aliments à hériter par défaut."
#: .\cookbook\forms.py:541 #: .\cookbook\forms.py:558
msgid "Show recipe counts on search filters" msgid "Show recipe counts on search filters"
msgstr "" msgstr ""
"Afficher le nombre de consultations par recette sur les filtres de recherche" "Afficher le nombre de consultations par recette sur les filtres de recherche"
#: .\cookbook\forms.py:542 #: .\cookbook\forms.py:559
msgid "Use the plural form for units and food inside this space." msgid "Use the plural form for units and food inside this space."
msgstr "" msgstr ""
"Utiliser la forme plurielle pour les unités et les aliments dans ce groupe." "Utiliser la forme plurielle pour les unités et les aliments dans ce groupe."

View File

@@ -10,7 +10,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-26 07:46+0200\n" "POT-Creation-Date: 2023-05-18 14:28+0200\n"
"PO-Revision-Date: 2023-04-12 11:55+0000\n" "PO-Revision-Date: 2023-04-12 11:55+0000\n"
"Last-Translator: noxonad <noxonad@proton.me>\n" "Last-Translator: noxonad <noxonad@proton.me>\n"
"Language-Team: Hungarian <http://translate.tandoor.dev/projects/tandoor/" "Language-Team: Hungarian <http://translate.tandoor.dev/projects/tandoor/"
@@ -116,7 +116,7 @@ msgstr ""
"Ha azt szeretné, hogy hozzászólásokat tudjon létrehozni és látni a receptek " "Ha azt szeretné, hogy hozzászólásokat tudjon létrehozni és látni a receptek "
"alatt." "alatt."
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492 #: .\cookbook\forms.py:79 .\cookbook\forms.py:509
msgid "" msgid ""
"Setting to 0 will disable auto sync. When viewing a shopping list the list " "Setting to 0 will disable auto sync. When viewing a shopping list the list "
"is updated every set seconds to sync changes someone else might have made. " "is updated every set seconds to sync changes someone else might have made. "
@@ -133,7 +133,7 @@ msgstr ""
msgid "Makes the navbar stick to the top of the page." msgid "Makes the navbar stick to the top of the page."
msgstr "A navigációs sávot az oldal tetejére rögzíti." msgstr "A navigációs sávot az oldal tetejére rögzíti."
#: .\cookbook\forms.py:83 .\cookbook\forms.py:495 #: .\cookbook\forms.py:83 .\cookbook\forms.py:512
msgid "Automatically add meal plan ingredients to shopping list." msgid "Automatically add meal plan ingredients to shopping list."
msgstr "" msgstr ""
"Automatikusan hozzáadja az étkezési terv hozzávalóit a bevásárlólistához." "Automatikusan hozzáadja az étkezési terv hozzávalóit a bevásárlólistához."
@@ -154,11 +154,11 @@ msgstr ""
"Mindkét mező opcionális. Ha egyiket sem adjuk meg, akkor a felhasználónév " "Mindkét mező opcionális. Ha egyiket sem adjuk meg, akkor a felhasználónév "
"jelenik meg helyette" "jelenik meg helyette"
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297 #: .\cookbook\forms.py:123 .\cookbook\forms.py:314
msgid "Name" msgid "Name"
msgstr "Név" msgstr "Név"
#: .\cookbook\forms.py:124 .\cookbook\forms.py:298 .\cookbook\views\lists.py:88 #: .\cookbook\forms.py:124 .\cookbook\forms.py:315 .\cookbook\views\lists.py:88
msgid "Keywords" msgid "Keywords"
msgstr "Kulcsszavak" msgstr "Kulcsszavak"
@@ -170,7 +170,7 @@ msgstr "Előkészítési idő percben"
msgid "Waiting time (cooking/baking) in minutes" msgid "Waiting time (cooking/baking) in minutes"
msgstr "Várakozási idő (sütés/főzés) percben" msgstr "Várakozási idő (sütés/főzés) percben"
#: .\cookbook\forms.py:127 .\cookbook\forms.py:266 .\cookbook\forms.py:299 #: .\cookbook\forms.py:127 .\cookbook\forms.py:283 .\cookbook\forms.py:316
msgid "Path" msgid "Path"
msgstr "Elérési útvonal" msgstr "Elérési útvonal"
@@ -182,7 +182,7 @@ msgstr "Tárhely UID"
msgid "Default" msgid "Default"
msgstr "Alapértelmezett" msgstr "Alapértelmezett"
#: .\cookbook\forms.py:173 #: .\cookbook\forms.py:190
msgid "" msgid ""
"To prevent duplicates recipes with the same name as existing ones are " "To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything." "ignored. Check this box to import everything."
@@ -191,23 +191,23 @@ msgstr ""
"recepteket a rendszer figyelmen kívül hagyja. Jelölje be ezt a négyzetet, ha " "recepteket a rendszer figyelmen kívül hagyja. Jelölje be ezt a négyzetet, ha "
"mindent importálni szeretne." "mindent importálni szeretne."
#: .\cookbook\forms.py:196 #: .\cookbook\forms.py:213
msgid "Add your comment: " msgid "Add your comment: "
msgstr "Add hozzá a kommented: " msgstr "Add hozzá a kommented: "
#: .\cookbook\forms.py:211 #: .\cookbook\forms.py:228
msgid "Leave empty for dropbox and enter app password for nextcloud." msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "" msgstr ""
"A dropbox esetében hagyja üresen, a nextcloud esetében pedig adja meg az " "A dropbox esetében hagyja üresen, a nextcloud esetében pedig adja meg az "
"alkalmazás jelszavát." "alkalmazás jelszavát."
#: .\cookbook\forms.py:218 #: .\cookbook\forms.py:235
msgid "Leave empty for nextcloud and enter api token for dropbox." msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "" msgstr ""
"A nextcloud esetében hagyja üresen, a dropbox esetében pedig adja meg az api " "A nextcloud esetében hagyja üresen, a dropbox esetében pedig adja meg az api "
"tokent." "tokent."
#: .\cookbook\forms.py:227 #: .\cookbook\forms.py:244
msgid "" msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote." "Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
"php/webdav/</code> is added automatically)" "php/webdav/</code> is added automatically)"
@@ -215,33 +215,33 @@ msgstr ""
"Hagyja üresen a dropbox esetén, és csak a nextcloud alap url-jét adja meg " "Hagyja üresen a dropbox esetén, és csak a nextcloud alap url-jét adja meg "
"(<code>/remote.php/webdav/</code> automatikusan hozzáadódik)" "(<code>/remote.php/webdav/</code> automatikusan hozzáadódik)"
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157 #: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
msgid "Storage" msgid "Storage"
msgstr "Tárhely" msgstr "Tárhely"
#: .\cookbook\forms.py:267 #: .\cookbook\forms.py:284
msgid "Active" msgid "Active"
msgstr "Aktív" msgstr "Aktív"
#: .\cookbook\forms.py:273 #: .\cookbook\forms.py:290
msgid "Search String" msgid "Search String"
msgstr "Keresési kifejezés" msgstr "Keresési kifejezés"
#: .\cookbook\forms.py:300 #: .\cookbook\forms.py:317
msgid "File ID" msgid "File ID"
msgstr "Fájl ID" msgstr "Fájl ID"
#: .\cookbook\forms.py:322 #: .\cookbook\forms.py:339
msgid "You must provide at least a recipe or a title." msgid "You must provide at least a recipe or a title."
msgstr "Legalább egy receptet vagy címet kell megadnia." msgstr "Legalább egy receptet vagy címet kell megadnia."
#: .\cookbook\forms.py:335 #: .\cookbook\forms.py:352
msgid "You can list default users to share recipes with in the settings." msgid "You can list default users to share recipes with in the settings."
msgstr "" msgstr ""
"A beállításokban megadhatja a receptek megosztására szolgáló alapértelmezett " "A beállításokban megadhatja a receptek megosztására szolgáló alapértelmezett "
"felhasználókat." "felhasználókat."
#: .\cookbook\forms.py:336 #: .\cookbook\forms.py:353
msgid "" msgid ""
"You can use markdown to format this field. See the <a href=\"/docs/markdown/" "You can use markdown to format this field. See the <a href=\"/docs/markdown/"
"\">docs here</a>" "\">docs here</a>"
@@ -249,15 +249,15 @@ msgstr ""
"A mező formázásához használhatja a markdown formátumot. Lásd a <a href=\"/" "A mező formázásához használhatja a markdown formátumot. Lásd a <a href=\"/"
"docs/markdown/\">dokumentációt itt</a>" "docs/markdown/\">dokumentációt itt</a>"
#: .\cookbook\forms.py:362 #: .\cookbook\forms.py:379
msgid "Maximum number of users for this space reached." msgid "Maximum number of users for this space reached."
msgstr "Elérte a felhasználók maximális számát ezen a területen." msgstr "Elérte a felhasználók maximális számát ezen a területen."
#: .\cookbook\forms.py:368 #: .\cookbook\forms.py:385
msgid "Email address already taken!" msgid "Email address already taken!"
msgstr "Az e-mail cím már foglalt!" msgstr "Az e-mail cím már foglalt!"
#: .\cookbook\forms.py:376 #: .\cookbook\forms.py:393
msgid "" msgid ""
"An email address is not required but if present the invite link will be sent " "An email address is not required but if present the invite link will be sent "
"to the user." "to the user."
@@ -265,15 +265,15 @@ msgstr ""
"Az e-mail cím megadása nem kötelező, de ha van, a meghívó linket elküldi a " "Az e-mail cím megadása nem kötelező, de ha van, a meghívó linket elküldi a "
"felhasználónak." "felhasználónak."
#: .\cookbook\forms.py:391 #: .\cookbook\forms.py:408
msgid "Name already taken." msgid "Name already taken."
msgstr "A név már foglalt." msgstr "A név már foglalt."
#: .\cookbook\forms.py:402 #: .\cookbook\forms.py:419
msgid "Accept Terms and Privacy" msgid "Accept Terms and Privacy"
msgstr "Feltételek és adatvédelem elfogadása" msgstr "Feltételek és adatvédelem elfogadása"
#: .\cookbook\forms.py:434 #: .\cookbook\forms.py:451
msgid "" msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e." "Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)." "g. low values mean more typos are ignored)."
@@ -282,7 +282,7 @@ msgstr ""
"párosítást használ (pl. az alacsony értékek azt jelentik, hogy több gépelési " "párosítást használ (pl. az alacsony értékek azt jelentik, hogy több gépelési "
"hibát figyelmen kívül hagynak)." "hibát figyelmen kívül hagynak)."
#: .\cookbook\forms.py:444 #: .\cookbook\forms.py:461
#, fuzzy #, fuzzy
#| msgid "" #| msgid ""
#| "Select type method of search. Click <a href=\"/docs/search/\">here</a> " #| "Select type method of search. Click <a href=\"/docs/search/\">here</a> "
@@ -294,7 +294,7 @@ msgstr ""
"Válassza ki a keresés típusát. Kattintson <a href=\"/docs/search/\">ide</a> " "Válassza ki a keresés típusát. Kattintson <a href=\"/docs/search/\">ide</a> "
"a lehetőségek teljes leírásáért." "a lehetőségek teljes leírásáért."
#: .\cookbook\forms.py:445 #: .\cookbook\forms.py:462
msgid "" msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and " "Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes." "importing recipes."
@@ -302,7 +302,7 @@ msgstr ""
"A receptek szerkesztése és importálása során az egységek, kulcsszavak és " "A receptek szerkesztése és importálása során az egységek, kulcsszavak és "
"összetevők bizonytalan megfeleltetése." "összetevők bizonytalan megfeleltetése."
#: .\cookbook\forms.py:447 #: .\cookbook\forms.py:464
msgid "" msgid ""
"Fields to search ignoring accents. Selecting this option can improve or " "Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language" "degrade search quality depending on language"
@@ -310,7 +310,7 @@ msgstr ""
"Az ékezetek figyelmen kívül hagyásával keresendő mezők. Ennek az opciónak a " "Az ékezetek figyelmen kívül hagyásával keresendő mezők. Ennek az opciónak a "
"kiválasztása javíthatja vagy ronthatja a keresés minőségét a nyelvtől függően" "kiválasztása javíthatja vagy ronthatja a keresés minőségét a nyelvtől függően"
#: .\cookbook\forms.py:449 #: .\cookbook\forms.py:466
msgid "" msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return " "Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')" "'pie' and 'piece' and 'soapie')"
@@ -318,7 +318,7 @@ msgstr ""
"Részleges egyezések keresésére szolgáló mezők. (pl. a 'Pie' keresése a " "Részleges egyezések keresésére szolgáló mezők. (pl. a 'Pie' keresése a "
"'pie' és a 'piece' és a 'soapie' kifejezéseket adja vissza.)" "'pie' és a 'piece' és a 'soapie' kifejezéseket adja vissza.)"
#: .\cookbook\forms.py:451 #: .\cookbook\forms.py:468
msgid "" msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' " "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')" "will return 'salad' and 'sandwich')"
@@ -326,7 +326,7 @@ msgstr ""
"Mezők a szó eleji egyezések kereséséhez. (pl. a 'sa' keresés a 'salad' és a " "Mezők a szó eleji egyezések kereséséhez. (pl. a 'sa' keresés a 'salad' és a "
"'sandwich' kifejezéseket adja vissza)" "'sandwich' kifejezéseket adja vissza)"
#: .\cookbook\forms.py:453 #: .\cookbook\forms.py:470
msgid "" msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) " "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search." "Note: this option will conflict with 'web' and 'raw' methods of search."
@@ -335,7 +335,7 @@ msgstr ""
"'recipe' szót.) Megjegyzés: ez az opció ütközik a 'web' és a 'raw' keresési " "'recipe' szót.) Megjegyzés: ez az opció ütközik a 'web' és a 'raw' keresési "
"módszerekkel." "módszerekkel."
#: .\cookbook\forms.py:455 #: .\cookbook\forms.py:472
msgid "" msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods " "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields." "only function with fulltext fields."
@@ -343,37 +343,37 @@ msgstr ""
"Mezők a teljes szöveges kereséshez. Megjegyzés: A 'web', 'phrase' és 'raw' " "Mezők a teljes szöveges kereséshez. Megjegyzés: A 'web', 'phrase' és 'raw' "
"keresési módszerek csak teljes szöveges mezőkkel működnek." "keresési módszerek csak teljes szöveges mezőkkel működnek."
#: .\cookbook\forms.py:459 #: .\cookbook\forms.py:476
msgid "Search Method" msgid "Search Method"
msgstr "Keresési módszer" msgstr "Keresési módszer"
#: .\cookbook\forms.py:460 #: .\cookbook\forms.py:477
msgid "Fuzzy Lookups" msgid "Fuzzy Lookups"
msgstr "Bizonytalan keresések" msgstr "Bizonytalan keresések"
#: .\cookbook\forms.py:461 #: .\cookbook\forms.py:478
msgid "Ignore Accent" msgid "Ignore Accent"
msgstr "Ékezetek ignorálása" msgstr "Ékezetek ignorálása"
#: .\cookbook\forms.py:462 #: .\cookbook\forms.py:479
msgid "Partial Match" msgid "Partial Match"
msgstr "Részleges találat" msgstr "Részleges találat"
#: .\cookbook\forms.py:463 #: .\cookbook\forms.py:480
#, fuzzy #, fuzzy
#| msgid "Starts Wtih" #| msgid "Starts Wtih"
msgid "Starts With" msgid "Starts With"
msgstr "Kezdődik a következővel" msgstr "Kezdődik a következővel"
#: .\cookbook\forms.py:464 #: .\cookbook\forms.py:481
msgid "Fuzzy Search" msgid "Fuzzy Search"
msgstr "Bizonytalan keresés" msgstr "Bizonytalan keresés"
#: .\cookbook\forms.py:465 #: .\cookbook\forms.py:482
msgid "Full Text" msgid "Full Text"
msgstr "Teljes szöveg" msgstr "Teljes szöveg"
#: .\cookbook\forms.py:490 #: .\cookbook\forms.py:507
msgid "" msgid ""
"Users will see all items you add to your shopping list. They must add you " "Users will see all items you add to your shopping list. They must add you "
"to see items on their list." "to see items on their list."
@@ -382,7 +382,7 @@ msgstr ""
"Ahhoz, hogy láthassák a saját listájukon szereplő tételeket, hozzá kell " "Ahhoz, hogy láthassák a saját listájukon szereplő tételeket, hozzá kell "
"adniuk téged." "adniuk téged."
#: .\cookbook\forms.py:496 #: .\cookbook\forms.py:513
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes." "include all related recipes."
@@ -390,7 +390,7 @@ msgstr ""
"Amikor étkezési tervet ad hozzá a bevásárlólistához (kézzel vagy " "Amikor étkezési tervet ad hozzá a bevásárlólistához (kézzel vagy "
"automatikusan), vegye fel az összes kapcsolódó receptet." "automatikusan), vegye fel az összes kapcsolódó receptet."
#: .\cookbook\forms.py:497 #: .\cookbook\forms.py:514
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"exclude ingredients that are on hand." "exclude ingredients that are on hand."
@@ -398,96 +398,96 @@ msgstr ""
"Amikor étkezési tervet ad hozzá a bevásárlólistához (kézzel vagy " "Amikor étkezési tervet ad hozzá a bevásárlólistához (kézzel vagy "
"automatikusan), zárja ki a kéznél lévő összetevőket." "automatikusan), zárja ki a kéznél lévő összetevőket."
#: .\cookbook\forms.py:498 #: .\cookbook\forms.py:515
msgid "Default number of hours to delay a shopping list entry." msgid "Default number of hours to delay a shopping list entry."
msgstr "A bevásárlólista bejegyzés késleltetésének alapértelmezett ideje." msgstr "A bevásárlólista bejegyzés késleltetésének alapértelmezett ideje."
#: .\cookbook\forms.py:499 #: .\cookbook\forms.py:516
msgid "Filter shopping list to only include supermarket categories." msgid "Filter shopping list to only include supermarket categories."
msgstr "" msgstr ""
"Szűrje a bevásárlólistát úgy, hogy csak a szupermarket kategóriákat " "Szűrje a bevásárlólistát úgy, hogy csak a szupermarket kategóriákat "
"tartalmazza." "tartalmazza."
#: .\cookbook\forms.py:500 #: .\cookbook\forms.py:517
msgid "Days of recent shopping list entries to display." msgid "Days of recent shopping list entries to display."
msgstr "A legutóbbi bevásárlólista bejegyzések megjelenítendő napjai." msgstr "A legutóbbi bevásárlólista bejegyzések megjelenítendő napjai."
#: .\cookbook\forms.py:501 #: .\cookbook\forms.py:518
msgid "Mark food 'On Hand' when checked off shopping list." msgid "Mark food 'On Hand' when checked off shopping list."
msgstr "" msgstr ""
"Jelölje meg a \" Kéznél van\" jelölést, ha a bevásárlólistáról kipipálta az " "Jelölje meg a \" Kéznél van\" jelölést, ha a bevásárlólistáról kipipálta az "
"élelmiszert." "élelmiszert."
#: .\cookbook\forms.py:502 #: .\cookbook\forms.py:519
msgid "Delimiter to use for CSV exports." msgid "Delimiter to use for CSV exports."
msgstr "A CSV exportáláshoz használandó elválasztójel." msgstr "A CSV exportáláshoz használandó elválasztójel."
#: .\cookbook\forms.py:503 #: .\cookbook\forms.py:520
msgid "Prefix to add when copying list to the clipboard." msgid "Prefix to add when copying list to the clipboard."
msgstr "A lista vágólapra másolásakor hozzáadandó előtag." msgstr "A lista vágólapra másolásakor hozzáadandó előtag."
#: .\cookbook\forms.py:507 #: .\cookbook\forms.py:524
msgid "Share Shopping List" msgid "Share Shopping List"
msgstr "Bevásárlólista megosztása" msgstr "Bevásárlólista megosztása"
#: .\cookbook\forms.py:508 #: .\cookbook\forms.py:525
msgid "Autosync" msgid "Autosync"
msgstr "Automatikus szinkronizálás" msgstr "Automatikus szinkronizálás"
#: .\cookbook\forms.py:509 #: .\cookbook\forms.py:526
msgid "Auto Add Meal Plan" msgid "Auto Add Meal Plan"
msgstr "Automatikus étkezési terv hozzáadása" msgstr "Automatikus étkezési terv hozzáadása"
#: .\cookbook\forms.py:510 #: .\cookbook\forms.py:527
msgid "Exclude On Hand" msgid "Exclude On Hand"
msgstr "Kéznél levő kihagyása" msgstr "Kéznél levő kihagyása"
#: .\cookbook\forms.py:511 #: .\cookbook\forms.py:528
msgid "Include Related" msgid "Include Related"
msgstr "Tartalmazza a kapcsolódókat" msgstr "Tartalmazza a kapcsolódókat"
#: .\cookbook\forms.py:512 #: .\cookbook\forms.py:529
msgid "Default Delay Hours" msgid "Default Delay Hours"
msgstr "Alapértelmezett késleltetési órák" msgstr "Alapértelmezett késleltetési órák"
#: .\cookbook\forms.py:513 #: .\cookbook\forms.py:530
msgid "Filter to Supermarket" msgid "Filter to Supermarket"
msgstr "Szűrő a szupermarkethez" msgstr "Szűrő a szupermarkethez"
#: .\cookbook\forms.py:514 #: .\cookbook\forms.py:531
msgid "Recent Days" msgid "Recent Days"
msgstr "Legutóbbi napok" msgstr "Legutóbbi napok"
#: .\cookbook\forms.py:515 #: .\cookbook\forms.py:532
msgid "CSV Delimiter" msgid "CSV Delimiter"
msgstr "CSV elválasztó" msgstr "CSV elválasztó"
#: .\cookbook\forms.py:516 #: .\cookbook\forms.py:533
msgid "List Prefix" msgid "List Prefix"
msgstr "Lista előtagja" msgstr "Lista előtagja"
#: .\cookbook\forms.py:517 #: .\cookbook\forms.py:534
msgid "Auto On Hand" msgid "Auto On Hand"
msgstr "Automatikus Kéznél lévő" msgstr "Automatikus Kéznél lévő"
#: .\cookbook\forms.py:527 #: .\cookbook\forms.py:544
msgid "Reset Food Inheritance" msgid "Reset Food Inheritance"
msgstr "Élelmiszer-öröklés visszaállítása" msgstr "Élelmiszer-öröklés visszaállítása"
#: .\cookbook\forms.py:528 #: .\cookbook\forms.py:545
msgid "Reset all food to inherit the fields configured." msgid "Reset all food to inherit the fields configured."
msgstr "Állítsa vissza az összes ételt, hogy örökölje a konfigurált mezőket." msgstr "Állítsa vissza az összes ételt, hogy örökölje a konfigurált mezőket."
#: .\cookbook\forms.py:540 #: .\cookbook\forms.py:557
msgid "Fields on food that should be inherited by default." msgid "Fields on food that should be inherited by default."
msgstr "" msgstr ""
"Az élelmiszerek azon mezői, amelyeket alapértelmezés szerint örökölni kell." "Az élelmiszerek azon mezői, amelyeket alapértelmezés szerint örökölni kell."
#: .\cookbook\forms.py:541 #: .\cookbook\forms.py:558
msgid "Show recipe counts on search filters" msgid "Show recipe counts on search filters"
msgstr "A receptek számának megjelenítése a keresési szűrőkön" msgstr "A receptek számának megjelenítése a keresési szűrőkön"
#: .\cookbook\forms.py:542 #: .\cookbook\forms.py:559
msgid "Use the plural form for units and food inside this space." msgid "Use the plural form for units and food inside this space."
msgstr "" msgstr ""

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-26 07:46+0200\n" "POT-Creation-Date: 2023-05-18 14:28+0200\n"
"PO-Revision-Date: 2023-04-29 07:55+0000\n" "PO-Revision-Date: 2023-04-29 07:55+0000\n"
"Last-Translator: Oliver Cervera <olivercervera@yahoo.it>\n" "Last-Translator: Oliver Cervera <olivercervera@yahoo.it>\n"
"Language-Team: Italian <http://translate.tandoor.dev/projects/tandoor/" "Language-Team: Italian <http://translate.tandoor.dev/projects/tandoor/"
@@ -116,7 +116,7 @@ msgid "If you want to be able to create and see comments underneath recipes."
msgstr "" msgstr ""
"Se vuoi essere in grado di creare e vedere i commenti sotto le ricette." "Se vuoi essere in grado di creare e vedere i commenti sotto le ricette."
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492 #: .\cookbook\forms.py:79 .\cookbook\forms.py:509
msgid "" msgid ""
"Setting to 0 will disable auto sync. When viewing a shopping list the list " "Setting to 0 will disable auto sync. When viewing a shopping list the list "
"is updated every set seconds to sync changes someone else might have made. " "is updated every set seconds to sync changes someone else might have made. "
@@ -134,7 +134,7 @@ msgstr ""
msgid "Makes the navbar stick to the top of the page." msgid "Makes the navbar stick to the top of the page."
msgstr "Fissa la barra di navigazione nella parte superiore della pagina." msgstr "Fissa la barra di navigazione nella parte superiore della pagina."
#: .\cookbook\forms.py:83 .\cookbook\forms.py:495 #: .\cookbook\forms.py:83 .\cookbook\forms.py:512
msgid "Automatically add meal plan ingredients to shopping list." msgid "Automatically add meal plan ingredients to shopping list."
msgstr "" msgstr ""
"Aggiungi automaticamente gli ingredienti del piano alimentare alla lista " "Aggiungi automaticamente gli ingredienti del piano alimentare alla lista "
@@ -156,11 +156,11 @@ msgstr ""
"Entrambi i campi sono facoltativi. Se non viene fornito, verrà visualizzato " "Entrambi i campi sono facoltativi. Se non viene fornito, verrà visualizzato "
"il nome utente" "il nome utente"
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297 #: .\cookbook\forms.py:123 .\cookbook\forms.py:314
msgid "Name" msgid "Name"
msgstr "Nome" msgstr "Nome"
#: .\cookbook\forms.py:124 .\cookbook\forms.py:298 .\cookbook\views\lists.py:88 #: .\cookbook\forms.py:124 .\cookbook\forms.py:315 .\cookbook\views\lists.py:88
msgid "Keywords" msgid "Keywords"
msgstr "Parole chiave" msgstr "Parole chiave"
@@ -172,7 +172,7 @@ msgstr "Tempo di preparazione in minuti"
msgid "Waiting time (cooking/baking) in minutes" msgid "Waiting time (cooking/baking) in minutes"
msgstr "Tempo di attesa (cottura) in minuti" msgstr "Tempo di attesa (cottura) in minuti"
#: .\cookbook\forms.py:127 .\cookbook\forms.py:266 .\cookbook\forms.py:299 #: .\cookbook\forms.py:127 .\cookbook\forms.py:283 .\cookbook\forms.py:316
msgid "Path" msgid "Path"
msgstr "Percorso" msgstr "Percorso"
@@ -184,7 +184,7 @@ msgstr "UID di archiviazione"
msgid "Default" msgid "Default"
msgstr "Predefinito" msgstr "Predefinito"
#: .\cookbook\forms.py:173 #: .\cookbook\forms.py:190
msgid "" msgid ""
"To prevent duplicates recipes with the same name as existing ones are " "To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything." "ignored. Check this box to import everything."
@@ -192,20 +192,20 @@ msgstr ""
"Per prevenire duplicati, vengono ignorate le ricette che hanno lo stesso " "Per prevenire duplicati, vengono ignorate le ricette che hanno lo stesso "
"nome di quelle esistenti. Metti la spunta per importare tutto." "nome di quelle esistenti. Metti la spunta per importare tutto."
#: .\cookbook\forms.py:196 #: .\cookbook\forms.py:213
msgid "Add your comment: " msgid "Add your comment: "
msgstr "Aggiungi il tuo commento: " msgstr "Aggiungi il tuo commento: "
#: .\cookbook\forms.py:211 #: .\cookbook\forms.py:228
msgid "Leave empty for dropbox and enter app password for nextcloud." msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "" msgstr ""
"Lascia vuoto per dropbox e inserisci la password dell'app per nextcloud." "Lascia vuoto per dropbox e inserisci la password dell'app per nextcloud."
#: .\cookbook\forms.py:218 #: .\cookbook\forms.py:235
msgid "Leave empty for nextcloud and enter api token for dropbox." msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "Lascia vuoto per nextcloud e inserisci l'api token per dropbox." msgstr "Lascia vuoto per nextcloud e inserisci l'api token per dropbox."
#: .\cookbook\forms.py:227 #: .\cookbook\forms.py:244
msgid "" msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote." "Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
"php/webdav/</code> is added automatically)" "php/webdav/</code> is added automatically)"
@@ -213,33 +213,33 @@ msgstr ""
"Lascia vuoto per dropbox e inserisci solo l'url base per nextcloud (<code>/" "Lascia vuoto per dropbox e inserisci solo l'url base per nextcloud (<code>/"
"remote.php/webdav/</code> è aggiunto automaticamente)" "remote.php/webdav/</code> è aggiunto automaticamente)"
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157 #: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
msgid "Storage" msgid "Storage"
msgstr "Archiviazione" msgstr "Archiviazione"
#: .\cookbook\forms.py:267 #: .\cookbook\forms.py:284
msgid "Active" msgid "Active"
msgstr "Attivo" msgstr "Attivo"
#: .\cookbook\forms.py:273 #: .\cookbook\forms.py:290
msgid "Search String" msgid "Search String"
msgstr "Stringa di Ricerca" msgstr "Stringa di Ricerca"
#: .\cookbook\forms.py:300 #: .\cookbook\forms.py:317
msgid "File ID" msgid "File ID"
msgstr "ID del File" msgstr "ID del File"
#: .\cookbook\forms.py:322 #: .\cookbook\forms.py:339
msgid "You must provide at least a recipe or a title." msgid "You must provide at least a recipe or a title."
msgstr "Devi fornire almeno una ricetta o un titolo." msgstr "Devi fornire almeno una ricetta o un titolo."
#: .\cookbook\forms.py:335 #: .\cookbook\forms.py:352
msgid "You can list default users to share recipes with in the settings." msgid "You can list default users to share recipes with in the settings."
msgstr "" msgstr ""
"È possibile visualizzare l'elenco degli utenti predefiniti con cui " "È possibile visualizzare l'elenco degli utenti predefiniti con cui "
"condividere le ricette nelle impostazioni." "condividere le ricette nelle impostazioni."
#: .\cookbook\forms.py:336 #: .\cookbook\forms.py:353
msgid "" msgid ""
"You can use markdown to format this field. See the <a href=\"/docs/markdown/" "You can use markdown to format this field. See the <a href=\"/docs/markdown/"
"\">docs here</a>" "\">docs here</a>"
@@ -247,15 +247,15 @@ msgstr ""
"Puoi usare markdown per formattare questo campo. Guarda la <a href=\"/docs/" "Puoi usare markdown per formattare questo campo. Guarda la <a href=\"/docs/"
"markdown/\">documentazione qui</a>" "markdown/\">documentazione qui</a>"
#: .\cookbook\forms.py:362 #: .\cookbook\forms.py:379
msgid "Maximum number of users for this space reached." msgid "Maximum number of users for this space reached."
msgstr "È stato raggiunto il numero massimo di utenti per questa istanza." msgstr "È stato raggiunto il numero massimo di utenti per questa istanza."
#: .\cookbook\forms.py:368 #: .\cookbook\forms.py:385
msgid "Email address already taken!" msgid "Email address already taken!"
msgstr "Questo indirizzo email è già in uso!" msgstr "Questo indirizzo email è già in uso!"
#: .\cookbook\forms.py:376 #: .\cookbook\forms.py:393
msgid "" msgid ""
"An email address is not required but if present the invite link will be sent " "An email address is not required but if present the invite link will be sent "
"to the user." "to the user."
@@ -263,15 +263,15 @@ msgstr ""
"Non è obbligatorio specificare l'indirizzo email, ma se presente verrà " "Non è obbligatorio specificare l'indirizzo email, ma se presente verrà "
"utilizzato per mandare all'utente un link di invito." "utilizzato per mandare all'utente un link di invito."
#: .\cookbook\forms.py:391 #: .\cookbook\forms.py:408
msgid "Name already taken." msgid "Name already taken."
msgstr "Nome già in uso." msgstr "Nome già in uso."
#: .\cookbook\forms.py:402 #: .\cookbook\forms.py:419
msgid "Accept Terms and Privacy" msgid "Accept Terms and Privacy"
msgstr "Accetta i Termini d'uso e Privacy" msgstr "Accetta i Termini d'uso e Privacy"
#: .\cookbook\forms.py:434 #: .\cookbook\forms.py:451
msgid "" msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e." "Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)." "g. low values mean more typos are ignored)."
@@ -280,7 +280,7 @@ msgstr ""
"trigrammi (ad esempio, valori bassi significano che vengono ignorati più " "trigrammi (ad esempio, valori bassi significano che vengono ignorati più "
"errori di battitura)." "errori di battitura)."
#: .\cookbook\forms.py:444 #: .\cookbook\forms.py:461
msgid "" msgid ""
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for " "Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
"full description of choices." "full description of choices."
@@ -288,7 +288,7 @@ msgstr ""
"Seleziona il metodo di ricerca. Clicca <a href=\"/docs/search/\">qui</a> " "Seleziona il metodo di ricerca. Clicca <a href=\"/docs/search/\">qui</a> "
"per avere maggiori informazioni." "per avere maggiori informazioni."
#: .\cookbook\forms.py:445 #: .\cookbook\forms.py:462
msgid "" msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and " "Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes." "importing recipes."
@@ -296,7 +296,7 @@ msgstr ""
"Usa la corrispondenza vaga per unità, parole chiave e ingredienti durante la " "Usa la corrispondenza vaga per unità, parole chiave e ingredienti durante la "
"modifica e l'importazione di ricette." "modifica e l'importazione di ricette."
#: .\cookbook\forms.py:447 #: .\cookbook\forms.py:464
msgid "" msgid ""
"Fields to search ignoring accents. Selecting this option can improve or " "Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language" "degrade search quality depending on language"
@@ -304,7 +304,7 @@ msgstr ""
"Campi da cercare ignorando gli accenti. A seconda alla lingua utilizzata, " "Campi da cercare ignorando gli accenti. A seconda alla lingua utilizzata, "
"questa opzione può migliorare o peggiorare la ricerca" "questa opzione può migliorare o peggiorare la ricerca"
#: .\cookbook\forms.py:449 #: .\cookbook\forms.py:466
msgid "" msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return " "Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')" "'pie' and 'piece' and 'soapie')"
@@ -312,7 +312,7 @@ msgstr ""
"Campi da cercare con corrispondenza parziale. (ad esempio, cercando 'Torta' " "Campi da cercare con corrispondenza parziale. (ad esempio, cercando 'Torta' "
"verranno mostrati 'torta', 'tortino' e 'contorta')" "verranno mostrati 'torta', 'tortino' e 'contorta')"
#: .\cookbook\forms.py:451 #: .\cookbook\forms.py:468
msgid "" msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' " "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')" "will return 'salad' and 'sandwich')"
@@ -320,7 +320,7 @@ msgstr ""
"Campi da cercare all'inizio di parole corrispondenti (es. cercando per 'ins' " "Campi da cercare all'inizio di parole corrispondenti (es. cercando per 'ins' "
"mostrerà 'insalata' e 'insaccati')" "mostrerà 'insalata' e 'insaccati')"
#: .\cookbook\forms.py:453 #: .\cookbook\forms.py:470
msgid "" msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) " "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search." "Note: this option will conflict with 'web' and 'raw' methods of search."
@@ -329,7 +329,7 @@ msgstr ""
"verrà mostrato 'ricetta'). Nota: questa opzione non è compatibile con la " "verrà mostrato 'ricetta'). Nota: questa opzione non è compatibile con la "
"ricerca 'web' o 'raw'." "ricerca 'web' o 'raw'."
#: .\cookbook\forms.py:455 #: .\cookbook\forms.py:472
msgid "" msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods " "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields." "only function with fulltext fields."
@@ -337,35 +337,35 @@ msgstr ""
"Campi per la ricerca full-text. Nota: i metodi di ricerca 'web', 'frase' e " "Campi per la ricerca full-text. Nota: i metodi di ricerca 'web', 'frase' e "
"'raw' funzionano solo con i campi full-text." "'raw' funzionano solo con i campi full-text."
#: .\cookbook\forms.py:459 #: .\cookbook\forms.py:476
msgid "Search Method" msgid "Search Method"
msgstr "Metodo di ricerca" msgstr "Metodo di ricerca"
#: .\cookbook\forms.py:460 #: .\cookbook\forms.py:477
msgid "Fuzzy Lookups" msgid "Fuzzy Lookups"
msgstr "Ricerche vaghe" msgstr "Ricerche vaghe"
#: .\cookbook\forms.py:461 #: .\cookbook\forms.py:478
msgid "Ignore Accent" msgid "Ignore Accent"
msgstr "Ignora accento" msgstr "Ignora accento"
#: .\cookbook\forms.py:462 #: .\cookbook\forms.py:479
msgid "Partial Match" msgid "Partial Match"
msgstr "Corrispondenza parziale" msgstr "Corrispondenza parziale"
#: .\cookbook\forms.py:463 #: .\cookbook\forms.py:480
msgid "Starts With" msgid "Starts With"
msgstr "Inizia con" msgstr "Inizia con"
#: .\cookbook\forms.py:464 #: .\cookbook\forms.py:481
msgid "Fuzzy Search" msgid "Fuzzy Search"
msgstr "Ricerca vaga" msgstr "Ricerca vaga"
#: .\cookbook\forms.py:465 #: .\cookbook\forms.py:482
msgid "Full Text" msgid "Full Text"
msgstr "Full Text" msgstr "Full Text"
#: .\cookbook\forms.py:490 #: .\cookbook\forms.py:507
msgid "" msgid ""
"Users will see all items you add to your shopping list. They must add you " "Users will see all items you add to your shopping list. They must add you "
"to see items on their list." "to see items on their list."
@@ -373,7 +373,7 @@ msgstr ""
"Gli utenti potranno vedere tutti gli elementi che aggiungi alla tua lista " "Gli utenti potranno vedere tutti gli elementi che aggiungi alla tua lista "
"della spesa. Devono aggiungerti per vedere gli elementi nella loro lista." "della spesa. Devono aggiungerti per vedere gli elementi nella loro lista."
#: .\cookbook\forms.py:496 #: .\cookbook\forms.py:513
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes." "include all related recipes."
@@ -381,7 +381,7 @@ msgstr ""
"Quando si aggiunge un piano alimentare alla lista della spesa (manualmente o " "Quando si aggiunge un piano alimentare alla lista della spesa (manualmente o "
"automaticamente), includi tutte le ricette correlate." "automaticamente), includi tutte le ricette correlate."
#: .\cookbook\forms.py:497 #: .\cookbook\forms.py:514
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"exclude ingredients that are on hand." "exclude ingredients that are on hand."
@@ -389,97 +389,97 @@ msgstr ""
"Quando si aggiunge un piano alimentare alla lista della spesa (manualmente o " "Quando si aggiunge un piano alimentare alla lista della spesa (manualmente o "
"automaticamente), escludi gli ingredienti già disponibili." "automaticamente), escludi gli ingredienti già disponibili."
#: .\cookbook\forms.py:498 #: .\cookbook\forms.py:515
msgid "Default number of hours to delay a shopping list entry." msgid "Default number of hours to delay a shopping list entry."
msgstr "" msgstr ""
"Il numero predefinito di ore per ritardare l'inserimento di una lista della " "Il numero predefinito di ore per ritardare l'inserimento di una lista della "
"spesa." "spesa."
#: .\cookbook\forms.py:499 #: .\cookbook\forms.py:516
msgid "Filter shopping list to only include supermarket categories." msgid "Filter shopping list to only include supermarket categories."
msgstr "" msgstr ""
"Filtra la lista della spesa per includere solo categorie dei supermercati." "Filtra la lista della spesa per includere solo categorie dei supermercati."
#: .\cookbook\forms.py:500 #: .\cookbook\forms.py:517
msgid "Days of recent shopping list entries to display." msgid "Days of recent shopping list entries to display."
msgstr "Giorni di visualizzazione di voci recenti della lista della spesa." msgstr "Giorni di visualizzazione di voci recenti della lista della spesa."
#: .\cookbook\forms.py:501 #: .\cookbook\forms.py:518
msgid "Mark food 'On Hand' when checked off shopping list." msgid "Mark food 'On Hand' when checked off shopping list."
msgstr "" msgstr ""
"Contrassegna gli alimenti come 'Disponibili' quando spuntati dalla lista " "Contrassegna gli alimenti come 'Disponibili' quando spuntati dalla lista "
"della spesa." "della spesa."
#: .\cookbook\forms.py:502 #: .\cookbook\forms.py:519
msgid "Delimiter to use for CSV exports." msgid "Delimiter to use for CSV exports."
msgstr "Delimitatore usato per le esportazioni CSV." msgstr "Delimitatore usato per le esportazioni CSV."
#: .\cookbook\forms.py:503 #: .\cookbook\forms.py:520
msgid "Prefix to add when copying list to the clipboard." msgid "Prefix to add when copying list to the clipboard."
msgstr "Prefisso da aggiungere quando si copia una lista negli appunti." msgstr "Prefisso da aggiungere quando si copia una lista negli appunti."
#: .\cookbook\forms.py:507 #: .\cookbook\forms.py:524
msgid "Share Shopping List" msgid "Share Shopping List"
msgstr "Condividi lista della spesa" msgstr "Condividi lista della spesa"
#: .\cookbook\forms.py:508 #: .\cookbook\forms.py:525
msgid "Autosync" msgid "Autosync"
msgstr "Sincronizzazione automatica" msgstr "Sincronizzazione automatica"
#: .\cookbook\forms.py:509 #: .\cookbook\forms.py:526
msgid "Auto Add Meal Plan" msgid "Auto Add Meal Plan"
msgstr "Aggiungi automaticamente al piano alimentare" msgstr "Aggiungi automaticamente al piano alimentare"
#: .\cookbook\forms.py:510 #: .\cookbook\forms.py:527
msgid "Exclude On Hand" msgid "Exclude On Hand"
msgstr "Escludi Disponibile" msgstr "Escludi Disponibile"
#: .\cookbook\forms.py:511 #: .\cookbook\forms.py:528
msgid "Include Related" msgid "Include Related"
msgstr "Includi correlati" msgstr "Includi correlati"
#: .\cookbook\forms.py:512 #: .\cookbook\forms.py:529
msgid "Default Delay Hours" msgid "Default Delay Hours"
msgstr "Ore di ritardo predefinite" msgstr "Ore di ritardo predefinite"
#: .\cookbook\forms.py:513 #: .\cookbook\forms.py:530
msgid "Filter to Supermarket" msgid "Filter to Supermarket"
msgstr "Filtra per supermercato" msgstr "Filtra per supermercato"
#: .\cookbook\forms.py:514 #: .\cookbook\forms.py:531
msgid "Recent Days" msgid "Recent Days"
msgstr "Giorni recenti" msgstr "Giorni recenti"
#: .\cookbook\forms.py:515 #: .\cookbook\forms.py:532
msgid "CSV Delimiter" msgid "CSV Delimiter"
msgstr "Delimitatore CSV" msgstr "Delimitatore CSV"
#: .\cookbook\forms.py:516 #: .\cookbook\forms.py:533
msgid "List Prefix" msgid "List Prefix"
msgstr "Prefisso lista" msgstr "Prefisso lista"
#: .\cookbook\forms.py:517 #: .\cookbook\forms.py:534
msgid "Auto On Hand" msgid "Auto On Hand"
msgstr "Disponibilità automatica" msgstr "Disponibilità automatica"
#: .\cookbook\forms.py:527 #: .\cookbook\forms.py:544
msgid "Reset Food Inheritance" msgid "Reset Food Inheritance"
msgstr "Ripristina Eredità Alimenti" msgstr "Ripristina Eredità Alimenti"
#: .\cookbook\forms.py:528 #: .\cookbook\forms.py:545
msgid "Reset all food to inherit the fields configured." msgid "Reset all food to inherit the fields configured."
msgstr "Ripristina tutti gli alimenti per ereditare i campi configurati." msgstr "Ripristina tutti gli alimenti per ereditare i campi configurati."
#: .\cookbook\forms.py:540 #: .\cookbook\forms.py:557
msgid "Fields on food that should be inherited by default." msgid "Fields on food that should be inherited by default."
msgstr "" msgstr ""
"Campi su alimenti che devono essere ereditati per impostazione predefinita." "Campi su alimenti che devono essere ereditati per impostazione predefinita."
#: .\cookbook\forms.py:541 #: .\cookbook\forms.py:558
msgid "Show recipe counts on search filters" msgid "Show recipe counts on search filters"
msgstr "Mostra il conteggio delle ricette nei filtri di ricerca" msgstr "Mostra il conteggio delle ricette nei filtri di ricerca"
#: .\cookbook\forms.py:542 #: .\cookbook\forms.py:559
msgid "Use the plural form for units and food inside this space." msgid "Use the plural form for units and food inside this space."
msgstr "" msgstr ""
"Usare la forma plurale per le unità e gli alimenti all'interno di questo " "Usare la forma plurale per le unità e gli alimenti all'interno di questo "

View File

@@ -10,7 +10,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-26 07:46+0200\n" "POT-Creation-Date: 2023-05-18 14:28+0200\n"
"PO-Revision-Date: 2023-01-08 17:55+0000\n" "PO-Revision-Date: 2023-01-08 17:55+0000\n"
"Last-Translator: Joachim Weber <joachim.weber@gmx.de>\n" "Last-Translator: Joachim Weber <joachim.weber@gmx.de>\n"
"Language-Team: Latvian <http://translate.tandoor.dev/projects/tandoor/" "Language-Team: Latvian <http://translate.tandoor.dev/projects/tandoor/"
@@ -125,7 +125,7 @@ msgid "If you want to be able to create and see comments underneath recipes."
msgstr "" msgstr ""
"Ja vēlaties, lai jūs varētu izveidot un redzēt komentārus zem receptēm." "Ja vēlaties, lai jūs varētu izveidot un redzēt komentārus zem receptēm."
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492 #: .\cookbook\forms.py:79 .\cookbook\forms.py:509
msgid "" msgid ""
"Setting to 0 will disable auto sync. When viewing a shopping list the list " "Setting to 0 will disable auto sync. When viewing a shopping list the list "
"is updated every set seconds to sync changes someone else might have made. " "is updated every set seconds to sync changes someone else might have made. "
@@ -143,7 +143,7 @@ msgstr ""
msgid "Makes the navbar stick to the top of the page." msgid "Makes the navbar stick to the top of the page."
msgstr "" msgstr ""
#: .\cookbook\forms.py:83 .\cookbook\forms.py:495 #: .\cookbook\forms.py:83 .\cookbook\forms.py:512
msgid "Automatically add meal plan ingredients to shopping list." msgid "Automatically add meal plan ingredients to shopping list."
msgstr "" msgstr ""
@@ -163,11 +163,11 @@ msgstr ""
"Abi lauki nav obligāti. Ja neviens nav norādīts, tā vietā tiks parādīts " "Abi lauki nav obligāti. Ja neviens nav norādīts, tā vietā tiks parādīts "
"lietotājvārds" "lietotājvārds"
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297 #: .\cookbook\forms.py:123 .\cookbook\forms.py:314
msgid "Name" msgid "Name"
msgstr "Vārds" msgstr "Vārds"
#: .\cookbook\forms.py:124 .\cookbook\forms.py:298 .\cookbook\views\lists.py:88 #: .\cookbook\forms.py:124 .\cookbook\forms.py:315 .\cookbook\views\lists.py:88
msgid "Keywords" msgid "Keywords"
msgstr "Atslēgvārdi" msgstr "Atslēgvārdi"
@@ -179,7 +179,7 @@ msgstr "Pagatavošanas laiks minūtēs"
msgid "Waiting time (cooking/baking) in minutes" msgid "Waiting time (cooking/baking) in minutes"
msgstr "Gaidīšanas laiks (vārīšana / cepšana) minūtēs" msgstr "Gaidīšanas laiks (vārīšana / cepšana) minūtēs"
#: .\cookbook\forms.py:127 .\cookbook\forms.py:266 .\cookbook\forms.py:299 #: .\cookbook\forms.py:127 .\cookbook\forms.py:283 .\cookbook\forms.py:316
msgid "Path" msgid "Path"
msgstr "Ceļš" msgstr "Ceļš"
@@ -191,25 +191,25 @@ msgstr "Krātuves UID"
msgid "Default" msgid "Default"
msgstr "" msgstr ""
#: .\cookbook\forms.py:173 #: .\cookbook\forms.py:190
msgid "" msgid ""
"To prevent duplicates recipes with the same name as existing ones are " "To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything." "ignored. Check this box to import everything."
msgstr "" msgstr ""
#: .\cookbook\forms.py:196 #: .\cookbook\forms.py:213
msgid "Add your comment: " msgid "Add your comment: "
msgstr "Pievienot komentāru: " msgstr "Pievienot komentāru: "
#: .\cookbook\forms.py:211 #: .\cookbook\forms.py:228
msgid "Leave empty for dropbox and enter app password for nextcloud." msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "Atstājiet tukšu Dropbox un ievadiet lietotnes paroli Nextcloud." msgstr "Atstājiet tukšu Dropbox un ievadiet lietotnes paroli Nextcloud."
#: .\cookbook\forms.py:218 #: .\cookbook\forms.py:235
msgid "Leave empty for nextcloud and enter api token for dropbox." msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "Atstājiet tukšu Nextcloud un ievadiet API tokenu Dropbox." msgstr "Atstājiet tukšu Nextcloud un ievadiet API tokenu Dropbox."
#: .\cookbook\forms.py:227 #: .\cookbook\forms.py:244
msgid "" msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote." "Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
"php/webdav/</code> is added automatically)" "php/webdav/</code> is added automatically)"
@@ -217,33 +217,33 @@ msgstr ""
"Atstājiet tukšu Dropbox un ievadiet tikai Nextcloud bāzes URL (<kods> /" "Atstājiet tukšu Dropbox un ievadiet tikai Nextcloud bāzes URL (<kods> /"
"remote.php/webdav/ </code> tiek pievienots automātiski)" "remote.php/webdav/ </code> tiek pievienots automātiski)"
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157 #: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
msgid "Storage" msgid "Storage"
msgstr "Krātuve" msgstr "Krātuve"
#: .\cookbook\forms.py:267 #: .\cookbook\forms.py:284
msgid "Active" msgid "Active"
msgstr "" msgstr ""
#: .\cookbook\forms.py:273 #: .\cookbook\forms.py:290
msgid "Search String" msgid "Search String"
msgstr "Meklēšanas virkne" msgstr "Meklēšanas virkne"
#: .\cookbook\forms.py:300 #: .\cookbook\forms.py:317
msgid "File ID" msgid "File ID"
msgstr "Faila ID" msgstr "Faila ID"
#: .\cookbook\forms.py:322 #: .\cookbook\forms.py:339
msgid "You must provide at least a recipe or a title." msgid "You must provide at least a recipe or a title."
msgstr "Jums jānorāda vismaz recepte vai nosaukums." msgstr "Jums jānorāda vismaz recepte vai nosaukums."
#: .\cookbook\forms.py:335 #: .\cookbook\forms.py:352
msgid "You can list default users to share recipes with in the settings." msgid "You can list default users to share recipes with in the settings."
msgstr "" msgstr ""
"Iestatījumos varat uzskaitīt noklusējuma lietotājus, ar kuriem koplietot " "Iestatījumos varat uzskaitīt noklusējuma lietotājus, ar kuriem koplietot "
"receptes." "receptes."
#: .\cookbook\forms.py:336 #: .\cookbook\forms.py:353
msgid "" msgid ""
"You can use markdown to format this field. See the <a href=\"/docs/markdown/" "You can use markdown to format this field. See the <a href=\"/docs/markdown/"
"\">docs here</a>" "\">docs here</a>"
@@ -251,219 +251,219 @@ msgstr ""
"Lai formatētu šo lauku, varat izmantot Markdown. Skatiet <a href=\"/docs/" "Lai formatētu šo lauku, varat izmantot Markdown. Skatiet <a href=\"/docs/"
"markdown/\"> dokumentus šeit </a>" "markdown/\"> dokumentus šeit </a>"
#: .\cookbook\forms.py:362 #: .\cookbook\forms.py:379
msgid "Maximum number of users for this space reached." msgid "Maximum number of users for this space reached."
msgstr "" msgstr ""
#: .\cookbook\forms.py:368 #: .\cookbook\forms.py:385
msgid "Email address already taken!" msgid "Email address already taken!"
msgstr "" msgstr ""
#: .\cookbook\forms.py:376 #: .\cookbook\forms.py:393
msgid "" msgid ""
"An email address is not required but if present the invite link will be sent " "An email address is not required but if present the invite link will be sent "
"to the user." "to the user."
msgstr "" msgstr ""
#: .\cookbook\forms.py:391 #: .\cookbook\forms.py:408
msgid "Name already taken." msgid "Name already taken."
msgstr "" msgstr ""
#: .\cookbook\forms.py:402 #: .\cookbook\forms.py:419
msgid "Accept Terms and Privacy" msgid "Accept Terms and Privacy"
msgstr "" msgstr ""
#: .\cookbook\forms.py:434 #: .\cookbook\forms.py:451
msgid "" msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e." "Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)." "g. low values mean more typos are ignored)."
msgstr "" msgstr ""
#: .\cookbook\forms.py:444 #: .\cookbook\forms.py:461
msgid "" msgid ""
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for " "Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
"full description of choices." "full description of choices."
msgstr "" msgstr ""
#: .\cookbook\forms.py:445 #: .\cookbook\forms.py:462
msgid "" msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and " "Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes." "importing recipes."
msgstr "" msgstr ""
#: .\cookbook\forms.py:447 #: .\cookbook\forms.py:464
msgid "" msgid ""
"Fields to search ignoring accents. Selecting this option can improve or " "Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language" "degrade search quality depending on language"
msgstr "" msgstr ""
#: .\cookbook\forms.py:449 #: .\cookbook\forms.py:466
msgid "" msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return " "Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')" "'pie' and 'piece' and 'soapie')"
msgstr "" msgstr ""
#: .\cookbook\forms.py:451 #: .\cookbook\forms.py:468
msgid "" msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' " "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')" "will return 'salad' and 'sandwich')"
msgstr "" msgstr ""
#: .\cookbook\forms.py:453 #: .\cookbook\forms.py:470
msgid "" msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) " "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search." "Note: this option will conflict with 'web' and 'raw' methods of search."
msgstr "" msgstr ""
#: .\cookbook\forms.py:455 #: .\cookbook\forms.py:472
msgid "" msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods " "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields." "only function with fulltext fields."
msgstr "" msgstr ""
#: .\cookbook\forms.py:459 #: .\cookbook\forms.py:476
#, fuzzy #, fuzzy
#| msgid "Search" #| msgid "Search"
msgid "Search Method" msgid "Search Method"
msgstr "Meklēt" msgstr "Meklēt"
#: .\cookbook\forms.py:460 #: .\cookbook\forms.py:477
msgid "Fuzzy Lookups" msgid "Fuzzy Lookups"
msgstr "" msgstr ""
#: .\cookbook\forms.py:461 #: .\cookbook\forms.py:478
msgid "Ignore Accent" msgid "Ignore Accent"
msgstr "" msgstr ""
#: .\cookbook\forms.py:462 #: .\cookbook\forms.py:479
msgid "Partial Match" msgid "Partial Match"
msgstr "" msgstr ""
#: .\cookbook\forms.py:463 #: .\cookbook\forms.py:480
msgid "Starts With" msgid "Starts With"
msgstr "" msgstr ""
#: .\cookbook\forms.py:464 #: .\cookbook\forms.py:481
#, fuzzy #, fuzzy
#| msgid "Search" #| msgid "Search"
msgid "Fuzzy Search" msgid "Fuzzy Search"
msgstr "Meklēt" msgstr "Meklēt"
#: .\cookbook\forms.py:465 #: .\cookbook\forms.py:482
#, fuzzy #, fuzzy
#| msgid "Text" #| msgid "Text"
msgid "Full Text" msgid "Full Text"
msgstr "Teskts" msgstr "Teskts"
#: .\cookbook\forms.py:490 #: .\cookbook\forms.py:507
msgid "" msgid ""
"Users will see all items you add to your shopping list. They must add you " "Users will see all items you add to your shopping list. They must add you "
"to see items on their list." "to see items on their list."
msgstr "" msgstr ""
#: .\cookbook\forms.py:496 #: .\cookbook\forms.py:513
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes." "include all related recipes."
msgstr "" msgstr ""
#: .\cookbook\forms.py:497 #: .\cookbook\forms.py:514
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"exclude ingredients that are on hand." "exclude ingredients that are on hand."
msgstr "" msgstr ""
#: .\cookbook\forms.py:498 #: .\cookbook\forms.py:515
msgid "Default number of hours to delay a shopping list entry." msgid "Default number of hours to delay a shopping list entry."
msgstr "" msgstr ""
#: .\cookbook\forms.py:499 #: .\cookbook\forms.py:516
msgid "Filter shopping list to only include supermarket categories." msgid "Filter shopping list to only include supermarket categories."
msgstr "" msgstr ""
#: .\cookbook\forms.py:500 #: .\cookbook\forms.py:517
msgid "Days of recent shopping list entries to display." msgid "Days of recent shopping list entries to display."
msgstr "" msgstr ""
#: .\cookbook\forms.py:501 #: .\cookbook\forms.py:518
msgid "Mark food 'On Hand' when checked off shopping list." msgid "Mark food 'On Hand' when checked off shopping list."
msgstr "" msgstr ""
#: .\cookbook\forms.py:502 #: .\cookbook\forms.py:519
msgid "Delimiter to use for CSV exports." msgid "Delimiter to use for CSV exports."
msgstr "" msgstr ""
#: .\cookbook\forms.py:503 #: .\cookbook\forms.py:520
msgid "Prefix to add when copying list to the clipboard." msgid "Prefix to add when copying list to the clipboard."
msgstr "" msgstr ""
#: .\cookbook\forms.py:507 #: .\cookbook\forms.py:524
#, fuzzy #, fuzzy
#| msgid "Shopping List" #| msgid "Shopping List"
msgid "Share Shopping List" msgid "Share Shopping List"
msgstr "Iepirkumu saraksts" msgstr "Iepirkumu saraksts"
#: .\cookbook\forms.py:508 #: .\cookbook\forms.py:525
msgid "Autosync" msgid "Autosync"
msgstr "" msgstr ""
#: .\cookbook\forms.py:509 #: .\cookbook\forms.py:526
msgid "Auto Add Meal Plan" msgid "Auto Add Meal Plan"
msgstr "" msgstr ""
#: .\cookbook\forms.py:510 #: .\cookbook\forms.py:527
msgid "Exclude On Hand" msgid "Exclude On Hand"
msgstr "" msgstr ""
#: .\cookbook\forms.py:511 #: .\cookbook\forms.py:528
msgid "Include Related" msgid "Include Related"
msgstr "" msgstr ""
#: .\cookbook\forms.py:512 #: .\cookbook\forms.py:529
msgid "Default Delay Hours" msgid "Default Delay Hours"
msgstr "" msgstr ""
#: .\cookbook\forms.py:513 #: .\cookbook\forms.py:530
msgid "Filter to Supermarket" msgid "Filter to Supermarket"
msgstr "" msgstr ""
#: .\cookbook\forms.py:514 #: .\cookbook\forms.py:531
msgid "Recent Days" msgid "Recent Days"
msgstr "" msgstr ""
#: .\cookbook\forms.py:515 #: .\cookbook\forms.py:532
msgid "CSV Delimiter" msgid "CSV Delimiter"
msgstr "" msgstr ""
#: .\cookbook\forms.py:516 #: .\cookbook\forms.py:533
msgid "List Prefix" msgid "List Prefix"
msgstr "Saraksta prefikss" msgstr "Saraksta prefikss"
#: .\cookbook\forms.py:517 #: .\cookbook\forms.py:534
msgid "Auto On Hand" msgid "Auto On Hand"
msgstr "" msgstr ""
#: .\cookbook\forms.py:527 #: .\cookbook\forms.py:544
msgid "Reset Food Inheritance" msgid "Reset Food Inheritance"
msgstr "" msgstr ""
#: .\cookbook\forms.py:528 #: .\cookbook\forms.py:545
msgid "Reset all food to inherit the fields configured." msgid "Reset all food to inherit the fields configured."
msgstr "" msgstr ""
#: .\cookbook\forms.py:540 #: .\cookbook\forms.py:557
#, fuzzy #, fuzzy
#| msgid "Food that should be replaced." #| msgid "Food that should be replaced."
msgid "Fields on food that should be inherited by default." msgid "Fields on food that should be inherited by default."
msgstr "Ēdiens, kas būtu jāaizstāj." msgstr "Ēdiens, kas būtu jāaizstāj."
#: .\cookbook\forms.py:541 #: .\cookbook\forms.py:558
#, fuzzy #, fuzzy
#| msgid "Show recently viewed recipes on search page." #| msgid "Show recently viewed recipes on search page."
msgid "Show recipe counts on search filters" msgid "Show recipe counts on search filters"
msgstr "Parādīt nesen skatītās receptes meklēšanas lapā." msgstr "Parādīt nesen skatītās receptes meklēšanas lapā."
#: .\cookbook\forms.py:542 #: .\cookbook\forms.py:559
msgid "Use the plural form for units and food inside this space." msgid "Use the plural form for units and food inside this space."
msgstr "" msgstr ""

View File

@@ -12,7 +12,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-26 07:46+0200\n" "POT-Creation-Date: 2023-05-18 14:28+0200\n"
"PO-Revision-Date: 2023-02-27 13:55+0000\n" "PO-Revision-Date: 2023-02-27 13:55+0000\n"
"Last-Translator: Jesse <jesse.kamps@pm.me>\n" "Last-Translator: Jesse <jesse.kamps@pm.me>\n"
"Language-Team: Dutch <http://translate.tandoor.dev/projects/tandoor/recipes-" "Language-Team: Dutch <http://translate.tandoor.dev/projects/tandoor/recipes-"
@@ -116,7 +116,7 @@ msgstr "Aantal decimalen om ingrediënten op af te ronden."
msgid "If you want to be able to create and see comments underneath recipes." msgid "If you want to be able to create and see comments underneath recipes."
msgstr "Als je opmerkingen onder recepten wil kunnen maken en zien." msgstr "Als je opmerkingen onder recepten wil kunnen maken en zien."
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492 #: .\cookbook\forms.py:79 .\cookbook\forms.py:509
msgid "" msgid ""
"Setting to 0 will disable auto sync. When viewing a shopping list the list " "Setting to 0 will disable auto sync. When viewing a shopping list the list "
"is updated every set seconds to sync changes someone else might have made. " "is updated every set seconds to sync changes someone else might have made. "
@@ -133,7 +133,7 @@ msgstr ""
msgid "Makes the navbar stick to the top of the page." msgid "Makes the navbar stick to the top of the page."
msgstr "Zet de navbar vast aan de bovenkant van de pagina." msgstr "Zet de navbar vast aan de bovenkant van de pagina."
#: .\cookbook\forms.py:83 .\cookbook\forms.py:495 #: .\cookbook\forms.py:83 .\cookbook\forms.py:512
msgid "Automatically add meal plan ingredients to shopping list." msgid "Automatically add meal plan ingredients to shopping list."
msgstr "Zet maaltijdplan ingrediënten automatisch op boodschappenlijst." msgstr "Zet maaltijdplan ingrediënten automatisch op boodschappenlijst."
@@ -153,11 +153,11 @@ msgstr ""
"Beide velden zijn optioneel. Indien niks is opgegeven wordt de " "Beide velden zijn optioneel. Indien niks is opgegeven wordt de "
"gebruikersnaam weergegeven" "gebruikersnaam weergegeven"
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297 #: .\cookbook\forms.py:123 .\cookbook\forms.py:314
msgid "Name" msgid "Name"
msgstr "Naam" msgstr "Naam"
#: .\cookbook\forms.py:124 .\cookbook\forms.py:298 .\cookbook\views\lists.py:88 #: .\cookbook\forms.py:124 .\cookbook\forms.py:315 .\cookbook\views\lists.py:88
msgid "Keywords" msgid "Keywords"
msgstr "Etiketten" msgstr "Etiketten"
@@ -169,7 +169,7 @@ msgstr "Voorbereidingstijd in minuten"
msgid "Waiting time (cooking/baking) in minutes" msgid "Waiting time (cooking/baking) in minutes"
msgstr "Wacht tijd in minuten (koken en bakken)" msgstr "Wacht tijd in minuten (koken en bakken)"
#: .\cookbook\forms.py:127 .\cookbook\forms.py:266 .\cookbook\forms.py:299 #: .\cookbook\forms.py:127 .\cookbook\forms.py:283 .\cookbook\forms.py:316
msgid "Path" msgid "Path"
msgstr "Pad" msgstr "Pad"
@@ -181,7 +181,7 @@ msgstr "Opslag UID"
msgid "Default" msgid "Default"
msgstr "Standaard waarde" msgstr "Standaard waarde"
#: .\cookbook\forms.py:173 #: .\cookbook\forms.py:190
msgid "" msgid ""
"To prevent duplicates recipes with the same name as existing ones are " "To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything." "ignored. Check this box to import everything."
@@ -189,19 +189,19 @@ msgstr ""
"Om dubbelingen te voorkomen worden recepten met dezelfde naam als een " "Om dubbelingen te voorkomen worden recepten met dezelfde naam als een "
"bestaand recept genegeerd. Vink aan om alles te importeren." "bestaand recept genegeerd. Vink aan om alles te importeren."
#: .\cookbook\forms.py:196 #: .\cookbook\forms.py:213
msgid "Add your comment: " msgid "Add your comment: "
msgstr "Voeg een opmerking toe: " msgstr "Voeg een opmerking toe: "
#: .\cookbook\forms.py:211 #: .\cookbook\forms.py:228
msgid "Leave empty for dropbox and enter app password for nextcloud." msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "Laat leeg voor dropbox en vul het app wachtwoord in voor nextcloud." msgstr "Laat leeg voor dropbox en vul het app wachtwoord in voor nextcloud."
#: .\cookbook\forms.py:218 #: .\cookbook\forms.py:235
msgid "Leave empty for nextcloud and enter api token for dropbox." msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "Laat leeg voor nextcloud en vul de api token in voor dropbox." msgstr "Laat leeg voor nextcloud en vul de api token in voor dropbox."
#: .\cookbook\forms.py:227 #: .\cookbook\forms.py:244
msgid "" msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote." "Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
"php/webdav/</code> is added automatically)" "php/webdav/</code> is added automatically)"
@@ -209,33 +209,33 @@ msgstr ""
"Laat leeg voor dropbox en vul enkel de base url voor nextcloud in. (<code>/" "Laat leeg voor dropbox en vul enkel de base url voor nextcloud in. (<code>/"
"remote.php/webdav/</code> wordt automatisch toegevoegd.)" "remote.php/webdav/</code> wordt automatisch toegevoegd.)"
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157 #: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
msgid "Storage" msgid "Storage"
msgstr "Opslag" msgstr "Opslag"
#: .\cookbook\forms.py:267 #: .\cookbook\forms.py:284
msgid "Active" msgid "Active"
msgstr "Actief" msgstr "Actief"
#: .\cookbook\forms.py:273 #: .\cookbook\forms.py:290
msgid "Search String" msgid "Search String"
msgstr "Zoekopdracht" msgstr "Zoekopdracht"
#: .\cookbook\forms.py:300 #: .\cookbook\forms.py:317
msgid "File ID" msgid "File ID"
msgstr "Bestands ID" msgstr "Bestands ID"
#: .\cookbook\forms.py:322 #: .\cookbook\forms.py:339
msgid "You must provide at least a recipe or a title." msgid "You must provide at least a recipe or a title."
msgstr "Je moet minimaal één recept of titel te specificeren." msgstr "Je moet minimaal één recept of titel te specificeren."
#: .\cookbook\forms.py:335 #: .\cookbook\forms.py:352
msgid "You can list default users to share recipes with in the settings." msgid "You can list default users to share recipes with in the settings."
msgstr "" msgstr ""
"Je kan in de instellingen standaard gebruikers in stellen om de recepten met " "Je kan in de instellingen standaard gebruikers in stellen om de recepten met "
"te delen." "te delen."
#: .\cookbook\forms.py:336 #: .\cookbook\forms.py:353
msgid "" msgid ""
"You can use markdown to format this field. See the <a href=\"/docs/markdown/" "You can use markdown to format this field. See the <a href=\"/docs/markdown/"
"\">docs here</a>" "\">docs here</a>"
@@ -243,15 +243,15 @@ msgstr ""
"Je kunt markdown gebruiken om dit veld te op te maken. Bekijk de <a href=\"/" "Je kunt markdown gebruiken om dit veld te op te maken. Bekijk de <a href=\"/"
"docs/markdown/\">documentatie hier</a>" "docs/markdown/\">documentatie hier</a>"
#: .\cookbook\forms.py:362 #: .\cookbook\forms.py:379
msgid "Maximum number of users for this space reached." msgid "Maximum number of users for this space reached."
msgstr "Maximum aantal gebruikers voor deze ruimte bereikt." msgstr "Maximum aantal gebruikers voor deze ruimte bereikt."
#: .\cookbook\forms.py:368 #: .\cookbook\forms.py:385
msgid "Email address already taken!" msgid "Email address already taken!"
msgstr "E-mailadres reeds in gebruik!" msgstr "E-mailadres reeds in gebruik!"
#: .\cookbook\forms.py:376 #: .\cookbook\forms.py:393
msgid "" msgid ""
"An email address is not required but if present the invite link will be sent " "An email address is not required but if present the invite link will be sent "
"to the user." "to the user."
@@ -259,15 +259,15 @@ msgstr ""
"Een e-mailadres is niet vereist, maar indien aanwezig zal de " "Een e-mailadres is niet vereist, maar indien aanwezig zal de "
"uitnodigingslink naar de gebruiker worden gestuurd." "uitnodigingslink naar de gebruiker worden gestuurd."
#: .\cookbook\forms.py:391 #: .\cookbook\forms.py:408
msgid "Name already taken." msgid "Name already taken."
msgstr "Naam reeds in gebruik." msgstr "Naam reeds in gebruik."
#: .\cookbook\forms.py:402 #: .\cookbook\forms.py:419
msgid "Accept Terms and Privacy" msgid "Accept Terms and Privacy"
msgstr "Accepteer voorwaarden" msgstr "Accepteer voorwaarden"
#: .\cookbook\forms.py:434 #: .\cookbook\forms.py:451
msgid "" msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e." "Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)." "g. low values mean more typos are ignored)."
@@ -275,7 +275,7 @@ msgstr ""
"Bepaalt hoe 'fuzzy' een zoekopdracht is als het trigram vergelijken gebruikt " "Bepaalt hoe 'fuzzy' een zoekopdracht is als het trigram vergelijken gebruikt "
"(lage waarden betekenen bijvoorbeeld dat meer typefouten genegeerd worden)." "(lage waarden betekenen bijvoorbeeld dat meer typefouten genegeerd worden)."
#: .\cookbook\forms.py:444 #: .\cookbook\forms.py:461
msgid "" msgid ""
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for " "Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
"full description of choices." "full description of choices."
@@ -283,7 +283,7 @@ msgstr ""
"Selecteer zoekmethode. Klik <a href=\"/docs/search/\">hier</a> voor een " "Selecteer zoekmethode. Klik <a href=\"/docs/search/\">hier</a> voor een "
"beschrijving van de keuzes." "beschrijving van de keuzes."
#: .\cookbook\forms.py:445 #: .\cookbook\forms.py:462
msgid "" msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and " "Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes." "importing recipes."
@@ -291,7 +291,7 @@ msgstr ""
"Gebruik 'fuzzy' koppelen bij eenheden, etiketten en ingrediënten bij " "Gebruik 'fuzzy' koppelen bij eenheden, etiketten en ingrediënten bij "
"bewerken en importeren van recepten." "bewerken en importeren van recepten."
#: .\cookbook\forms.py:447 #: .\cookbook\forms.py:464
msgid "" msgid ""
"Fields to search ignoring accents. Selecting this option can improve or " "Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language" "degrade search quality depending on language"
@@ -300,7 +300,7 @@ msgstr ""
"deze optie kan de zoekkwaliteit afhankelijk van de taal, zowel verbeteren " "deze optie kan de zoekkwaliteit afhankelijk van de taal, zowel verbeteren "
"als verslechteren" "als verslechteren"
#: .\cookbook\forms.py:449 #: .\cookbook\forms.py:466
msgid "" msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return " "Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')" "'pie' and 'piece' and 'soapie')"
@@ -308,7 +308,7 @@ msgstr ""
"Velden doorzoeken op gedeelde overeenkomsten. (zoeken op 'Appel' vindt " "Velden doorzoeken op gedeelde overeenkomsten. (zoeken op 'Appel' vindt "
"'appel', 'aardappel' en 'appelsap')" "'appel', 'aardappel' en 'appelsap')"
#: .\cookbook\forms.py:451 #: .\cookbook\forms.py:468
msgid "" msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' " "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')" "will return 'salad' and 'sandwich')"
@@ -316,7 +316,7 @@ msgstr ""
"Velden doorzoeken op overeenkomsten aan het begin van het woord. (zoeken op " "Velden doorzoeken op overeenkomsten aan het begin van het woord. (zoeken op "
"'sa' vindt 'salade' en 'sandwich')" "'sa' vindt 'salade' en 'sandwich')"
#: .\cookbook\forms.py:453 #: .\cookbook\forms.py:470
msgid "" msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) " "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search." "Note: this option will conflict with 'web' and 'raw' methods of search."
@@ -324,7 +324,7 @@ msgstr ""
"Velden 'fuzzy' doorzoeken. (zoeken op 'recetp' vindt ook 'recept') Noot: " "Velden 'fuzzy' doorzoeken. (zoeken op 'recetp' vindt ook 'recept') Noot: "
"deze optie conflicteert met de zoekmethoden 'web' en 'raw'." "deze optie conflicteert met de zoekmethoden 'web' en 'raw'."
#: .\cookbook\forms.py:455 #: .\cookbook\forms.py:472
msgid "" msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods " "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields." "only function with fulltext fields."
@@ -332,35 +332,35 @@ msgstr ""
"Velden doorzoeken op volledige tekst. Noot: Web, Zin en Raw zoekmethoden " "Velden doorzoeken op volledige tekst. Noot: Web, Zin en Raw zoekmethoden "
"werken alleen met volledige tekstvelden." "werken alleen met volledige tekstvelden."
#: .\cookbook\forms.py:459 #: .\cookbook\forms.py:476
msgid "Search Method" msgid "Search Method"
msgstr "Zoekmethode" msgstr "Zoekmethode"
#: .\cookbook\forms.py:460 #: .\cookbook\forms.py:477
msgid "Fuzzy Lookups" msgid "Fuzzy Lookups"
msgstr "'Fuzzy' zoekopdrachten" msgstr "'Fuzzy' zoekopdrachten"
#: .\cookbook\forms.py:461 #: .\cookbook\forms.py:478
msgid "Ignore Accent" msgid "Ignore Accent"
msgstr "Negeer accent" msgstr "Negeer accent"
#: .\cookbook\forms.py:462 #: .\cookbook\forms.py:479
msgid "Partial Match" msgid "Partial Match"
msgstr "Gedeeltelijke overeenkomst" msgstr "Gedeeltelijke overeenkomst"
#: .\cookbook\forms.py:463 #: .\cookbook\forms.py:480
msgid "Starts With" msgid "Starts With"
msgstr "Begint met" msgstr "Begint met"
#: .\cookbook\forms.py:464 #: .\cookbook\forms.py:481
msgid "Fuzzy Search" msgid "Fuzzy Search"
msgstr "'Fuzzy' zoeken" msgstr "'Fuzzy' zoeken"
#: .\cookbook\forms.py:465 #: .\cookbook\forms.py:482
msgid "Full Text" msgid "Full Text"
msgstr "Volledige tekst" msgstr "Volledige tekst"
#: .\cookbook\forms.py:490 #: .\cookbook\forms.py:507
msgid "" msgid ""
"Users will see all items you add to your shopping list. They must add you " "Users will see all items you add to your shopping list. They must add you "
"to see items on their list." "to see items on their list."
@@ -368,7 +368,7 @@ msgstr ""
"Gebruikers zien alle items die je op je boodschappenlijst zet. Ze moeten " "Gebruikers zien alle items die je op je boodschappenlijst zet. Ze moeten "
"jou toevoegen om items op hun lijst te zien." "jou toevoegen om items op hun lijst te zien."
#: .\cookbook\forms.py:496 #: .\cookbook\forms.py:513
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes." "include all related recipes."
@@ -376,7 +376,7 @@ msgstr ""
"Als een maaltijdplan aan de boodschappenlijst toegevoegd wordt (handmatig of " "Als een maaltijdplan aan de boodschappenlijst toegevoegd wordt (handmatig of "
"automatisch), neem dan alle recepten op." "automatisch), neem dan alle recepten op."
#: .\cookbook\forms.py:497 #: .\cookbook\forms.py:514
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"exclude ingredients that are on hand." "exclude ingredients that are on hand."
@@ -384,94 +384,94 @@ msgstr ""
"Als een maaltijdplan aan de boodschappenlijst toegevoegd wordt (handmatig of " "Als een maaltijdplan aan de boodschappenlijst toegevoegd wordt (handmatig of "
"automatisch), sluit ingrediënten die op voorraad zijn dan uit." "automatisch), sluit ingrediënten die op voorraad zijn dan uit."
#: .\cookbook\forms.py:498 #: .\cookbook\forms.py:515
msgid "Default number of hours to delay a shopping list entry." msgid "Default number of hours to delay a shopping list entry."
msgstr "Standaard aantal uren om een boodschappenlijst item te vertragen." msgstr "Standaard aantal uren om een boodschappenlijst item te vertragen."
#: .\cookbook\forms.py:499 #: .\cookbook\forms.py:516
msgid "Filter shopping list to only include supermarket categories." msgid "Filter shopping list to only include supermarket categories."
msgstr "Filter boodschappenlijst om alleen supermarktcategorieën te bevatten." msgstr "Filter boodschappenlijst om alleen supermarktcategorieën te bevatten."
#: .\cookbook\forms.py:500 #: .\cookbook\forms.py:517
msgid "Days of recent shopping list entries to display." msgid "Days of recent shopping list entries to display."
msgstr "Dagen van recente boodschappenlijst items weer te geven." msgstr "Dagen van recente boodschappenlijst items weer te geven."
#: .\cookbook\forms.py:501 #: .\cookbook\forms.py:518
msgid "Mark food 'On Hand' when checked off shopping list." msgid "Mark food 'On Hand' when checked off shopping list."
msgstr "" msgstr ""
"Markeer eten 'Op voorraad' wanneer het van het boodschappenlijstje is " "Markeer eten 'Op voorraad' wanneer het van het boodschappenlijstje is "
"afgevinkt." "afgevinkt."
#: .\cookbook\forms.py:502 #: .\cookbook\forms.py:519
msgid "Delimiter to use for CSV exports." msgid "Delimiter to use for CSV exports."
msgstr "Scheidingsteken te gebruiken voor CSV exports." msgstr "Scheidingsteken te gebruiken voor CSV exports."
#: .\cookbook\forms.py:503 #: .\cookbook\forms.py:520
msgid "Prefix to add when copying list to the clipboard." msgid "Prefix to add when copying list to the clipboard."
msgstr "" msgstr ""
"Toe te voegen Voorvoegsel bij het kopiëren van een lijst naar het klembord." "Toe te voegen Voorvoegsel bij het kopiëren van een lijst naar het klembord."
#: .\cookbook\forms.py:507 #: .\cookbook\forms.py:524
msgid "Share Shopping List" msgid "Share Shopping List"
msgstr "Deel boodschappenlijst" msgstr "Deel boodschappenlijst"
#: .\cookbook\forms.py:508 #: .\cookbook\forms.py:525
msgid "Autosync" msgid "Autosync"
msgstr "Autosync" msgstr "Autosync"
#: .\cookbook\forms.py:509 #: .\cookbook\forms.py:526
msgid "Auto Add Meal Plan" msgid "Auto Add Meal Plan"
msgstr "Voeg maaltijdplan automatisch toe" msgstr "Voeg maaltijdplan automatisch toe"
#: .\cookbook\forms.py:510 #: .\cookbook\forms.py:527
msgid "Exclude On Hand" msgid "Exclude On Hand"
msgstr "Sluit op voorraad uit" msgstr "Sluit op voorraad uit"
#: .\cookbook\forms.py:511 #: .\cookbook\forms.py:528
msgid "Include Related" msgid "Include Related"
msgstr "Neem gerelateerde op" msgstr "Neem gerelateerde op"
#: .\cookbook\forms.py:512 #: .\cookbook\forms.py:529
msgid "Default Delay Hours" msgid "Default Delay Hours"
msgstr "Standaard vertraging in uren" msgstr "Standaard vertraging in uren"
#: .\cookbook\forms.py:513 #: .\cookbook\forms.py:530
msgid "Filter to Supermarket" msgid "Filter to Supermarket"
msgstr "Filter op supermarkt" msgstr "Filter op supermarkt"
#: .\cookbook\forms.py:514 #: .\cookbook\forms.py:531
msgid "Recent Days" msgid "Recent Days"
msgstr "Afgelopen dagen" msgstr "Afgelopen dagen"
#: .\cookbook\forms.py:515 #: .\cookbook\forms.py:532
msgid "CSV Delimiter" msgid "CSV Delimiter"
msgstr "CSV scheidingsteken" msgstr "CSV scheidingsteken"
#: .\cookbook\forms.py:516 #: .\cookbook\forms.py:533
msgid "List Prefix" msgid "List Prefix"
msgstr "Lijst voorvoegsel" msgstr "Lijst voorvoegsel"
#: .\cookbook\forms.py:517 #: .\cookbook\forms.py:534
msgid "Auto On Hand" msgid "Auto On Hand"
msgstr "Auto op voorraad" msgstr "Auto op voorraad"
#: .\cookbook\forms.py:527 #: .\cookbook\forms.py:544
msgid "Reset Food Inheritance" msgid "Reset Food Inheritance"
msgstr "Herstel Ingrediënt overname" msgstr "Herstel Ingrediënt overname"
#: .\cookbook\forms.py:528 #: .\cookbook\forms.py:545
msgid "Reset all food to inherit the fields configured." msgid "Reset all food to inherit the fields configured."
msgstr "Herstel alle ingrediënten om de geconfigureerde velden over te nemen." msgstr "Herstel alle ingrediënten om de geconfigureerde velden over te nemen."
#: .\cookbook\forms.py:540 #: .\cookbook\forms.py:557
msgid "Fields on food that should be inherited by default." msgid "Fields on food that should be inherited by default."
msgstr "Velden van ingrediënten die standaard overgenomen moeten worden." msgstr "Velden van ingrediënten die standaard overgenomen moeten worden."
#: .\cookbook\forms.py:541 #: .\cookbook\forms.py:558
msgid "Show recipe counts on search filters" msgid "Show recipe counts on search filters"
msgstr "Toon recepten teller bij zoekfilters" msgstr "Toon recepten teller bij zoekfilters"
#: .\cookbook\forms.py:542 #: .\cookbook\forms.py:559
msgid "Use the plural form for units and food inside this space." msgid "Use the plural form for units and food inside this space."
msgstr "Gebruik de meervoudsvorm voor eenheden en voedsel in deze ruimte." msgstr "Gebruik de meervoudsvorm voor eenheden en voedsel in deze ruimte."

View File

@@ -11,7 +11,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-26 07:46+0200\n" "POT-Creation-Date: 2023-05-18 14:28+0200\n"
"PO-Revision-Date: 2023-01-08 17:55+0000\n" "PO-Revision-Date: 2023-01-08 17:55+0000\n"
"Last-Translator: Joachim Weber <joachim.weber@gmx.de>\n" "Last-Translator: Joachim Weber <joachim.weber@gmx.de>\n"
"Language-Team: Portuguese <http://translate.tandoor.dev/projects/tandoor/" "Language-Team: Portuguese <http://translate.tandoor.dev/projects/tandoor/"
@@ -114,7 +114,7 @@ msgstr "Número de casas decimais para arredondamentos."
msgid "If you want to be able to create and see comments underneath recipes." msgid "If you want to be able to create and see comments underneath recipes."
msgstr "Ativar a funcionalidade comentar receitas." msgstr "Ativar a funcionalidade comentar receitas."
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492 #: .\cookbook\forms.py:79 .\cookbook\forms.py:509
msgid "" msgid ""
"Setting to 0 will disable auto sync. When viewing a shopping list the list " "Setting to 0 will disable auto sync. When viewing a shopping list the list "
"is updated every set seconds to sync changes someone else might have made. " "is updated every set seconds to sync changes someone else might have made. "
@@ -131,7 +131,7 @@ msgstr ""
msgid "Makes the navbar stick to the top of the page." msgid "Makes the navbar stick to the top of the page."
msgstr "Mantém a barra de navegação no topo da página." msgstr "Mantém a barra de navegação no topo da página."
#: .\cookbook\forms.py:83 .\cookbook\forms.py:495 #: .\cookbook\forms.py:83 .\cookbook\forms.py:512
msgid "Automatically add meal plan ingredients to shopping list." msgid "Automatically add meal plan ingredients to shopping list."
msgstr "" msgstr ""
@@ -151,11 +151,11 @@ msgstr ""
"Ambos os campos são opcionais. Se nenhum for preenchido o nome de utilizador " "Ambos os campos são opcionais. Se nenhum for preenchido o nome de utilizador "
"será apresentado" "será apresentado"
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297 #: .\cookbook\forms.py:123 .\cookbook\forms.py:314
msgid "Name" msgid "Name"
msgstr "Nome" msgstr "Nome"
#: .\cookbook\forms.py:124 .\cookbook\forms.py:298 .\cookbook\views\lists.py:88 #: .\cookbook\forms.py:124 .\cookbook\forms.py:315 .\cookbook\views\lists.py:88
msgid "Keywords" msgid "Keywords"
msgstr "Palavras-chave" msgstr "Palavras-chave"
@@ -167,7 +167,7 @@ msgstr "Tempo de preparação em minutos"
msgid "Waiting time (cooking/baking) in minutes" msgid "Waiting time (cooking/baking) in minutes"
msgstr "Tempo de espera (cozedura) em minutos" msgstr "Tempo de espera (cozedura) em minutos"
#: .\cookbook\forms.py:127 .\cookbook\forms.py:266 .\cookbook\forms.py:299 #: .\cookbook\forms.py:127 .\cookbook\forms.py:283 .\cookbook\forms.py:316
msgid "Path" msgid "Path"
msgstr "Caminho" msgstr "Caminho"
@@ -179,7 +179,7 @@ msgstr "UID de armazenamento"
msgid "Default" msgid "Default"
msgstr "Predefinição" msgstr "Predefinição"
#: .\cookbook\forms.py:173 #: .\cookbook\forms.py:190
msgid "" msgid ""
"To prevent duplicates recipes with the same name as existing ones are " "To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything." "ignored. Check this box to import everything."
@@ -187,21 +187,21 @@ msgstr ""
"Para evitar repetições, receitas com o mesmo nome de receitas já existentes " "Para evitar repetições, receitas com o mesmo nome de receitas já existentes "
"são ignoradas. Marque esta caixa para importar tudo." "são ignoradas. Marque esta caixa para importar tudo."
#: .\cookbook\forms.py:196 #: .\cookbook\forms.py:213
msgid "Add your comment: " msgid "Add your comment: "
msgstr "Adicionar comentário: " msgstr "Adicionar comentário: "
#: .\cookbook\forms.py:211 #: .\cookbook\forms.py:228
msgid "Leave empty for dropbox and enter app password for nextcloud." msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "" msgstr ""
"Deixar vazio para Dropbox e inserir palavra-passe de aplicação para " "Deixar vazio para Dropbox e inserir palavra-passe de aplicação para "
"Nextcloud." "Nextcloud."
#: .\cookbook\forms.py:218 #: .\cookbook\forms.py:235
msgid "Leave empty for nextcloud and enter api token for dropbox." msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "Deixar vazio para Nextcloud e inserir token api para Dropbox." msgstr "Deixar vazio para Nextcloud e inserir token api para Dropbox."
#: .\cookbook\forms.py:227 #: .\cookbook\forms.py:244
msgid "" msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote." "Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
"php/webdav/</code> is added automatically)" "php/webdav/</code> is added automatically)"
@@ -209,33 +209,33 @@ msgstr ""
"Deixar vazio para Dropbox e inserir apenas url base para Nextcloud (<code>/" "Deixar vazio para Dropbox e inserir apenas url base para Nextcloud (<code>/"
"remote.php/webdav/</code>é adicionado automaticamente). " "remote.php/webdav/</code>é adicionado automaticamente). "
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157 #: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
msgid "Storage" msgid "Storage"
msgstr "Armazenamento" msgstr "Armazenamento"
#: .\cookbook\forms.py:267 #: .\cookbook\forms.py:284
msgid "Active" msgid "Active"
msgstr "Ativo" msgstr "Ativo"
#: .\cookbook\forms.py:273 #: .\cookbook\forms.py:290
msgid "Search String" msgid "Search String"
msgstr "Procurar" msgstr "Procurar"
#: .\cookbook\forms.py:300 #: .\cookbook\forms.py:317
msgid "File ID" msgid "File ID"
msgstr "ID the ficheiro" msgstr "ID the ficheiro"
#: .\cookbook\forms.py:322 #: .\cookbook\forms.py:339
msgid "You must provide at least a recipe or a title." msgid "You must provide at least a recipe or a title."
msgstr "É necessário inserir uma receita ou um título." msgstr "É necessário inserir uma receita ou um título."
#: .\cookbook\forms.py:335 #: .\cookbook\forms.py:352
msgid "You can list default users to share recipes with in the settings." msgid "You can list default users to share recipes with in the settings."
msgstr "" msgstr ""
"É possível escolher os utilizadores com quem partilhar receitas por defeitos " "É possível escolher os utilizadores com quem partilhar receitas por defeitos "
"nas definições." "nas definições."
#: .\cookbook\forms.py:336 #: .\cookbook\forms.py:353
msgid "" msgid ""
"You can use markdown to format this field. See the <a href=\"/docs/markdown/" "You can use markdown to format this field. See the <a href=\"/docs/markdown/"
"\">docs here</a>" "\">docs here</a>"
@@ -243,15 +243,15 @@ msgstr ""
"É possível utilizar markdown para editar este campo. Documentação <a href=\"/" "É possível utilizar markdown para editar este campo. Documentação <a href=\"/"
"docs/markdown/\">disponível aqui</a>" "docs/markdown/\">disponível aqui</a>"
#: .\cookbook\forms.py:362 #: .\cookbook\forms.py:379
msgid "Maximum number of users for this space reached." msgid "Maximum number of users for this space reached."
msgstr "Número máximo de utilizadores alcançado." msgstr "Número máximo de utilizadores alcançado."
#: .\cookbook\forms.py:368 #: .\cookbook\forms.py:385
msgid "Email address already taken!" msgid "Email address already taken!"
msgstr "Endereço email já utilizado!" msgstr "Endereço email já utilizado!"
#: .\cookbook\forms.py:376 #: .\cookbook\forms.py:393
msgid "" msgid ""
"An email address is not required but if present the invite link will be sent " "An email address is not required but if present the invite link will be sent "
"to the user." "to the user."
@@ -259,15 +259,15 @@ msgstr ""
"Um endereço de email não é obrigatório mas se fornecido será enviada uma " "Um endereço de email não é obrigatório mas se fornecido será enviada uma "
"mensagem ao utilizador." "mensagem ao utilizador."
#: .\cookbook\forms.py:391 #: .\cookbook\forms.py:408
msgid "Name already taken." msgid "Name already taken."
msgstr "Nome já existente." msgstr "Nome já existente."
#: .\cookbook\forms.py:402 #: .\cookbook\forms.py:419
msgid "Accept Terms and Privacy" msgid "Accept Terms and Privacy"
msgstr "Aceitar Termos e Condições" msgstr "Aceitar Termos e Condições"
#: .\cookbook\forms.py:434 #: .\cookbook\forms.py:451
msgid "" msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e." "Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)." "g. low values mean more typos are ignored)."
@@ -276,7 +276,7 @@ msgstr ""
"de semelhança de trigrama (valores mais baixos significam que mais erros são " "de semelhança de trigrama (valores mais baixos significam que mais erros são "
"ignorados)." "ignorados)."
#: .\cookbook\forms.py:444 #: .\cookbook\forms.py:461
#, fuzzy #, fuzzy
#| msgid "" #| msgid ""
#| "Select type method of search. Click <a href=\"/docs/search/\">here</a> " #| "Select type method of search. Click <a href=\"/docs/search/\">here</a> "
@@ -288,7 +288,7 @@ msgstr ""
"Selecionar o método de pesquisa. Uma descrição completa das opções pode ser " "Selecionar o método de pesquisa. Uma descrição completa das opções pode ser "
"encontrada <a href=\"/docs/search/\">aqui</a>." "encontrada <a href=\"/docs/search/\">aqui</a>."
#: .\cookbook\forms.py:445 #: .\cookbook\forms.py:462
msgid "" msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and " "Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes." "importing recipes."
@@ -296,7 +296,7 @@ msgstr ""
"Utilizar correspondência difusa em unidades, palavras-chave e ingredientes " "Utilizar correspondência difusa em unidades, palavras-chave e ingredientes "
"ao editar e importar receitas." "ao editar e importar receitas."
#: .\cookbook\forms.py:447 #: .\cookbook\forms.py:464
msgid "" msgid ""
"Fields to search ignoring accents. Selecting this option can improve or " "Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language" "degrade search quality depending on language"
@@ -304,171 +304,171 @@ msgstr ""
"Campos de pesquisa que ignoram pontuação. Esta opção pode aumentar ou " "Campos de pesquisa que ignoram pontuação. Esta opção pode aumentar ou "
"diminuir a qualidade de pesquisa dependendo da língua em uso" "diminuir a qualidade de pesquisa dependendo da língua em uso"
#: .\cookbook\forms.py:449 #: .\cookbook\forms.py:466
msgid "" msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return " "Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')" "'pie' and 'piece' and 'soapie')"
msgstr "" msgstr ""
#: .\cookbook\forms.py:451 #: .\cookbook\forms.py:468
msgid "" msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' " "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')" "will return 'salad' and 'sandwich')"
msgstr "" msgstr ""
#: .\cookbook\forms.py:453 #: .\cookbook\forms.py:470
msgid "" msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) " "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search." "Note: this option will conflict with 'web' and 'raw' methods of search."
msgstr "" msgstr ""
#: .\cookbook\forms.py:455 #: .\cookbook\forms.py:472
msgid "" msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods " "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields." "only function with fulltext fields."
msgstr "" msgstr ""
#: .\cookbook\forms.py:459 #: .\cookbook\forms.py:476
#, fuzzy #, fuzzy
#| msgid "Search" #| msgid "Search"
msgid "Search Method" msgid "Search Method"
msgstr "Procurar" msgstr "Procurar"
#: .\cookbook\forms.py:460 #: .\cookbook\forms.py:477
msgid "Fuzzy Lookups" msgid "Fuzzy Lookups"
msgstr "" msgstr ""
#: .\cookbook\forms.py:461 #: .\cookbook\forms.py:478
msgid "Ignore Accent" msgid "Ignore Accent"
msgstr "" msgstr ""
#: .\cookbook\forms.py:462 #: .\cookbook\forms.py:479
msgid "Partial Match" msgid "Partial Match"
msgstr "" msgstr ""
#: .\cookbook\forms.py:463 #: .\cookbook\forms.py:480
msgid "Starts With" msgid "Starts With"
msgstr "" msgstr ""
#: .\cookbook\forms.py:464 #: .\cookbook\forms.py:481
#, fuzzy #, fuzzy
#| msgid "Search" #| msgid "Search"
msgid "Fuzzy Search" msgid "Fuzzy Search"
msgstr "Procurar" msgstr "Procurar"
#: .\cookbook\forms.py:465 #: .\cookbook\forms.py:482
#, fuzzy #, fuzzy
#| msgid "Text" #| msgid "Text"
msgid "Full Text" msgid "Full Text"
msgstr "Texto" msgstr "Texto"
#: .\cookbook\forms.py:490 #: .\cookbook\forms.py:507
msgid "" msgid ""
"Users will see all items you add to your shopping list. They must add you " "Users will see all items you add to your shopping list. They must add you "
"to see items on their list." "to see items on their list."
msgstr "" msgstr ""
#: .\cookbook\forms.py:496 #: .\cookbook\forms.py:513
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes." "include all related recipes."
msgstr "" msgstr ""
#: .\cookbook\forms.py:497 #: .\cookbook\forms.py:514
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"exclude ingredients that are on hand." "exclude ingredients that are on hand."
msgstr "" msgstr ""
#: .\cookbook\forms.py:498 #: .\cookbook\forms.py:515
msgid "Default number of hours to delay a shopping list entry." msgid "Default number of hours to delay a shopping list entry."
msgstr "" msgstr ""
#: .\cookbook\forms.py:499 #: .\cookbook\forms.py:516
msgid "Filter shopping list to only include supermarket categories." msgid "Filter shopping list to only include supermarket categories."
msgstr "" msgstr ""
#: .\cookbook\forms.py:500 #: .\cookbook\forms.py:517
msgid "Days of recent shopping list entries to display." msgid "Days of recent shopping list entries to display."
msgstr "" msgstr ""
#: .\cookbook\forms.py:501 #: .\cookbook\forms.py:518
msgid "Mark food 'On Hand' when checked off shopping list." msgid "Mark food 'On Hand' when checked off shopping list."
msgstr "" msgstr ""
#: .\cookbook\forms.py:502 #: .\cookbook\forms.py:519
msgid "Delimiter to use for CSV exports." msgid "Delimiter to use for CSV exports."
msgstr "" msgstr ""
#: .\cookbook\forms.py:503 #: .\cookbook\forms.py:520
msgid "Prefix to add when copying list to the clipboard." msgid "Prefix to add when copying list to the clipboard."
msgstr "" msgstr ""
#: .\cookbook\forms.py:507 #: .\cookbook\forms.py:524
#, fuzzy #, fuzzy
#| msgid "Shopping" #| msgid "Shopping"
msgid "Share Shopping List" msgid "Share Shopping List"
msgstr "Compras" msgstr "Compras"
#: .\cookbook\forms.py:508 #: .\cookbook\forms.py:525
msgid "Autosync" msgid "Autosync"
msgstr "" msgstr ""
#: .\cookbook\forms.py:509 #: .\cookbook\forms.py:526
msgid "Auto Add Meal Plan" msgid "Auto Add Meal Plan"
msgstr "" msgstr ""
#: .\cookbook\forms.py:510 #: .\cookbook\forms.py:527
msgid "Exclude On Hand" msgid "Exclude On Hand"
msgstr "" msgstr ""
#: .\cookbook\forms.py:511 #: .\cookbook\forms.py:528
msgid "Include Related" msgid "Include Related"
msgstr "" msgstr ""
#: .\cookbook\forms.py:512 #: .\cookbook\forms.py:529
msgid "Default Delay Hours" msgid "Default Delay Hours"
msgstr "" msgstr ""
#: .\cookbook\forms.py:513 #: .\cookbook\forms.py:530
msgid "Filter to Supermarket" msgid "Filter to Supermarket"
msgstr "" msgstr ""
#: .\cookbook\forms.py:514 #: .\cookbook\forms.py:531
msgid "Recent Days" msgid "Recent Days"
msgstr "" msgstr ""
#: .\cookbook\forms.py:515 #: .\cookbook\forms.py:532
msgid "CSV Delimiter" msgid "CSV Delimiter"
msgstr "" msgstr ""
#: .\cookbook\forms.py:516 #: .\cookbook\forms.py:533
msgid "List Prefix" msgid "List Prefix"
msgstr "" msgstr ""
#: .\cookbook\forms.py:517 #: .\cookbook\forms.py:534
msgid "Auto On Hand" msgid "Auto On Hand"
msgstr "" msgstr ""
#: .\cookbook\forms.py:527 #: .\cookbook\forms.py:544
msgid "Reset Food Inheritance" msgid "Reset Food Inheritance"
msgstr "" msgstr ""
#: .\cookbook\forms.py:528 #: .\cookbook\forms.py:545
msgid "Reset all food to inherit the fields configured." msgid "Reset all food to inherit the fields configured."
msgstr "" msgstr ""
#: .\cookbook\forms.py:540 #: .\cookbook\forms.py:557
#, fuzzy #, fuzzy
#| msgid "Food that should be replaced." #| msgid "Food that should be replaced."
msgid "Fields on food that should be inherited by default." msgid "Fields on food that should be inherited by default."
msgstr "Prato a ser alterado." msgstr "Prato a ser alterado."
#: .\cookbook\forms.py:541 #: .\cookbook\forms.py:558
msgid "Show recipe counts on search filters" msgid "Show recipe counts on search filters"
msgstr "Mostrar receitas recentes na página de pesquisa" msgstr "Mostrar receitas recentes na página de pesquisa"
#: .\cookbook\forms.py:542 #: .\cookbook\forms.py:559
msgid "Use the plural form for units and food inside this space." msgid "Use the plural form for units and food inside this space."
msgstr "" msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-26 07:46+0200\n" "POT-Creation-Date: 2023-05-18 14:28+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -102,7 +102,7 @@ msgstr ""
msgid "If you want to be able to create and see comments underneath recipes." msgid "If you want to be able to create and see comments underneath recipes."
msgstr "" msgstr ""
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492 #: .\cookbook\forms.py:79 .\cookbook\forms.py:509
msgid "" msgid ""
"Setting to 0 will disable auto sync. When viewing a shopping list the list " "Setting to 0 will disable auto sync. When viewing a shopping list the list "
"is updated every set seconds to sync changes someone else might have made. " "is updated every set seconds to sync changes someone else might have made. "
@@ -114,7 +114,7 @@ msgstr ""
msgid "Makes the navbar stick to the top of the page." msgid "Makes the navbar stick to the top of the page."
msgstr "" msgstr ""
#: .\cookbook\forms.py:83 .\cookbook\forms.py:495 #: .\cookbook\forms.py:83 .\cookbook\forms.py:512
msgid "Automatically add meal plan ingredients to shopping list." msgid "Automatically add meal plan ingredients to shopping list."
msgstr "" msgstr ""
@@ -132,11 +132,11 @@ msgid ""
"instead" "instead"
msgstr "" msgstr ""
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297 #: .\cookbook\forms.py:123 .\cookbook\forms.py:314
msgid "Name" msgid "Name"
msgstr "" msgstr ""
#: .\cookbook\forms.py:124 .\cookbook\forms.py:298 .\cookbook\views\lists.py:88 #: .\cookbook\forms.py:124 .\cookbook\forms.py:315 .\cookbook\views\lists.py:88
msgid "Keywords" msgid "Keywords"
msgstr "" msgstr ""
@@ -148,7 +148,7 @@ msgstr ""
msgid "Waiting time (cooking/baking) in minutes" msgid "Waiting time (cooking/baking) in minutes"
msgstr "" msgstr ""
#: .\cookbook\forms.py:127 .\cookbook\forms.py:266 .\cookbook\forms.py:299 #: .\cookbook\forms.py:127 .\cookbook\forms.py:283 .\cookbook\forms.py:316
msgid "Path" msgid "Path"
msgstr "" msgstr ""
@@ -160,261 +160,261 @@ msgstr ""
msgid "Default" msgid "Default"
msgstr "" msgstr ""
#: .\cookbook\forms.py:173 #: .\cookbook\forms.py:190
msgid "" msgid ""
"To prevent duplicates recipes with the same name as existing ones are " "To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything." "ignored. Check this box to import everything."
msgstr "" msgstr ""
#: .\cookbook\forms.py:196 #: .\cookbook\forms.py:213
msgid "Add your comment: " msgid "Add your comment: "
msgstr "" msgstr ""
#: .\cookbook\forms.py:211 #: .\cookbook\forms.py:228
msgid "Leave empty for dropbox and enter app password for nextcloud." msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "" msgstr ""
#: .\cookbook\forms.py:218 #: .\cookbook\forms.py:235
msgid "Leave empty for nextcloud and enter api token for dropbox." msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "" msgstr ""
#: .\cookbook\forms.py:227 #: .\cookbook\forms.py:244
msgid "" msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote." "Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
"php/webdav/</code> is added automatically)" "php/webdav/</code> is added automatically)"
msgstr "" msgstr ""
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157 #: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
msgid "Storage" msgid "Storage"
msgstr "" msgstr ""
#: .\cookbook\forms.py:267 #: .\cookbook\forms.py:284
msgid "Active" msgid "Active"
msgstr "" msgstr ""
#: .\cookbook\forms.py:273 #: .\cookbook\forms.py:290
msgid "Search String" msgid "Search String"
msgstr "" msgstr ""
#: .\cookbook\forms.py:300 #: .\cookbook\forms.py:317
msgid "File ID" msgid "File ID"
msgstr "" msgstr ""
#: .\cookbook\forms.py:322 #: .\cookbook\forms.py:339
msgid "You must provide at least a recipe or a title." msgid "You must provide at least a recipe or a title."
msgstr "" msgstr ""
#: .\cookbook\forms.py:335 #: .\cookbook\forms.py:352
msgid "You can list default users to share recipes with in the settings." msgid "You can list default users to share recipes with in the settings."
msgstr "" msgstr ""
#: .\cookbook\forms.py:336 #: .\cookbook\forms.py:353
msgid "" msgid ""
"You can use markdown to format this field. See the <a href=\"/docs/markdown/" "You can use markdown to format this field. See the <a href=\"/docs/markdown/"
"\">docs here</a>" "\">docs here</a>"
msgstr "" msgstr ""
#: .\cookbook\forms.py:362 #: .\cookbook\forms.py:379
msgid "Maximum number of users for this space reached." msgid "Maximum number of users for this space reached."
msgstr "" msgstr ""
#: .\cookbook\forms.py:368 #: .\cookbook\forms.py:385
msgid "Email address already taken!" msgid "Email address already taken!"
msgstr "" msgstr ""
#: .\cookbook\forms.py:376 #: .\cookbook\forms.py:393
msgid "" msgid ""
"An email address is not required but if present the invite link will be sent " "An email address is not required but if present the invite link will be sent "
"to the user." "to the user."
msgstr "" msgstr ""
#: .\cookbook\forms.py:391 #: .\cookbook\forms.py:408
msgid "Name already taken." msgid "Name already taken."
msgstr "" msgstr ""
#: .\cookbook\forms.py:402 #: .\cookbook\forms.py:419
msgid "Accept Terms and Privacy" msgid "Accept Terms and Privacy"
msgstr "" msgstr ""
#: .\cookbook\forms.py:434 #: .\cookbook\forms.py:451
msgid "" msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e." "Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)." "g. low values mean more typos are ignored)."
msgstr "" msgstr ""
#: .\cookbook\forms.py:444 #: .\cookbook\forms.py:461
msgid "" msgid ""
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for " "Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
"full description of choices." "full description of choices."
msgstr "" msgstr ""
#: .\cookbook\forms.py:445 #: .\cookbook\forms.py:462
msgid "" msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and " "Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes." "importing recipes."
msgstr "" msgstr ""
#: .\cookbook\forms.py:447 #: .\cookbook\forms.py:464
msgid "" msgid ""
"Fields to search ignoring accents. Selecting this option can improve or " "Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language" "degrade search quality depending on language"
msgstr "" msgstr ""
#: .\cookbook\forms.py:449 #: .\cookbook\forms.py:466
msgid "" msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return " "Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')" "'pie' and 'piece' and 'soapie')"
msgstr "" msgstr ""
#: .\cookbook\forms.py:451 #: .\cookbook\forms.py:468
msgid "" msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' " "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')" "will return 'salad' and 'sandwich')"
msgstr "" msgstr ""
#: .\cookbook\forms.py:453 #: .\cookbook\forms.py:470
msgid "" msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) " "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search." "Note: this option will conflict with 'web' and 'raw' methods of search."
msgstr "" msgstr ""
#: .\cookbook\forms.py:455 #: .\cookbook\forms.py:472
msgid "" msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods " "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields." "only function with fulltext fields."
msgstr "" msgstr ""
#: .\cookbook\forms.py:459 #: .\cookbook\forms.py:476
msgid "Search Method" msgid "Search Method"
msgstr "" msgstr ""
#: .\cookbook\forms.py:460 #: .\cookbook\forms.py:477
msgid "Fuzzy Lookups" msgid "Fuzzy Lookups"
msgstr "" msgstr ""
#: .\cookbook\forms.py:461 #: .\cookbook\forms.py:478
msgid "Ignore Accent" msgid "Ignore Accent"
msgstr "" msgstr ""
#: .\cookbook\forms.py:462 #: .\cookbook\forms.py:479
msgid "Partial Match" msgid "Partial Match"
msgstr "" msgstr ""
#: .\cookbook\forms.py:463 #: .\cookbook\forms.py:480
msgid "Starts With" msgid "Starts With"
msgstr "" msgstr ""
#: .\cookbook\forms.py:464 #: .\cookbook\forms.py:481
msgid "Fuzzy Search" msgid "Fuzzy Search"
msgstr "" msgstr ""
#: .\cookbook\forms.py:465 #: .\cookbook\forms.py:482
msgid "Full Text" msgid "Full Text"
msgstr "" msgstr ""
#: .\cookbook\forms.py:490 #: .\cookbook\forms.py:507
msgid "" msgid ""
"Users will see all items you add to your shopping list. They must add you " "Users will see all items you add to your shopping list. They must add you "
"to see items on their list." "to see items on their list."
msgstr "" msgstr ""
#: .\cookbook\forms.py:496 #: .\cookbook\forms.py:513
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes." "include all related recipes."
msgstr "" msgstr ""
#: .\cookbook\forms.py:497 #: .\cookbook\forms.py:514
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"exclude ingredients that are on hand." "exclude ingredients that are on hand."
msgstr "" msgstr ""
#: .\cookbook\forms.py:498 #: .\cookbook\forms.py:515
msgid "Default number of hours to delay a shopping list entry." msgid "Default number of hours to delay a shopping list entry."
msgstr "" msgstr ""
#: .\cookbook\forms.py:499 #: .\cookbook\forms.py:516
msgid "Filter shopping list to only include supermarket categories." msgid "Filter shopping list to only include supermarket categories."
msgstr "" msgstr ""
#: .\cookbook\forms.py:500 #: .\cookbook\forms.py:517
msgid "Days of recent shopping list entries to display." msgid "Days of recent shopping list entries to display."
msgstr "" msgstr ""
#: .\cookbook\forms.py:501 #: .\cookbook\forms.py:518
msgid "Mark food 'On Hand' when checked off shopping list." msgid "Mark food 'On Hand' when checked off shopping list."
msgstr "" msgstr ""
#: .\cookbook\forms.py:502 #: .\cookbook\forms.py:519
msgid "Delimiter to use for CSV exports." msgid "Delimiter to use for CSV exports."
msgstr "" msgstr ""
#: .\cookbook\forms.py:503 #: .\cookbook\forms.py:520
msgid "Prefix to add when copying list to the clipboard." msgid "Prefix to add when copying list to the clipboard."
msgstr "" msgstr ""
#: .\cookbook\forms.py:507 #: .\cookbook\forms.py:524
msgid "Share Shopping List" msgid "Share Shopping List"
msgstr "" msgstr ""
#: .\cookbook\forms.py:508 #: .\cookbook\forms.py:525
msgid "Autosync" msgid "Autosync"
msgstr "" msgstr ""
#: .\cookbook\forms.py:509 #: .\cookbook\forms.py:526
msgid "Auto Add Meal Plan" msgid "Auto Add Meal Plan"
msgstr "" msgstr ""
#: .\cookbook\forms.py:510 #: .\cookbook\forms.py:527
msgid "Exclude On Hand" msgid "Exclude On Hand"
msgstr "" msgstr ""
#: .\cookbook\forms.py:511 #: .\cookbook\forms.py:528
msgid "Include Related" msgid "Include Related"
msgstr "" msgstr ""
#: .\cookbook\forms.py:512 #: .\cookbook\forms.py:529
msgid "Default Delay Hours" msgid "Default Delay Hours"
msgstr "" msgstr ""
#: .\cookbook\forms.py:513 #: .\cookbook\forms.py:530
msgid "Filter to Supermarket" msgid "Filter to Supermarket"
msgstr "" msgstr ""
#: .\cookbook\forms.py:514 #: .\cookbook\forms.py:531
msgid "Recent Days" msgid "Recent Days"
msgstr "" msgstr ""
#: .\cookbook\forms.py:515 #: .\cookbook\forms.py:532
msgid "CSV Delimiter" msgid "CSV Delimiter"
msgstr "" msgstr ""
#: .\cookbook\forms.py:516 #: .\cookbook\forms.py:533
msgid "List Prefix" msgid "List Prefix"
msgstr "" msgstr ""
#: .\cookbook\forms.py:517 #: .\cookbook\forms.py:534
msgid "Auto On Hand" msgid "Auto On Hand"
msgstr "" msgstr ""
#: .\cookbook\forms.py:527 #: .\cookbook\forms.py:544
msgid "Reset Food Inheritance" msgid "Reset Food Inheritance"
msgstr "" msgstr ""
#: .\cookbook\forms.py:528 #: .\cookbook\forms.py:545
msgid "Reset all food to inherit the fields configured." msgid "Reset all food to inherit the fields configured."
msgstr "" msgstr ""
#: .\cookbook\forms.py:540 #: .\cookbook\forms.py:557
msgid "Fields on food that should be inherited by default." msgid "Fields on food that should be inherited by default."
msgstr "" msgstr ""
#: .\cookbook\forms.py:541 #: .\cookbook\forms.py:558
msgid "Show recipe counts on search filters" msgid "Show recipe counts on search filters"
msgstr "" msgstr ""
#: .\cookbook\forms.py:542 #: .\cookbook\forms.py:559
msgid "Use the plural form for units and food inside this space." msgid "Use the plural form for units and food inside this space."
msgstr "" msgstr ""

View File

@@ -10,7 +10,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-26 07:46+0200\n" "POT-Creation-Date: 2023-05-18 14:28+0200\n"
"PO-Revision-Date: 2022-11-06 22:09+0000\n" "PO-Revision-Date: 2022-11-06 22:09+0000\n"
"Last-Translator: Gorkem <g.kalipcilar@gmail.com>\n" "Last-Translator: Gorkem <g.kalipcilar@gmail.com>\n"
"Language-Team: Turkish <http://translate.tandoor.dev/projects/tandoor/" "Language-Team: Turkish <http://translate.tandoor.dev/projects/tandoor/"
@@ -110,7 +110,7 @@ msgstr "Malzeme birimleri için yuvarlanma basamağı."
msgid "If you want to be able to create and see comments underneath recipes." msgid "If you want to be able to create and see comments underneath recipes."
msgstr "Tariflerin altında yorumlar oluşturup görebilmek istiyorsanız." msgstr "Tariflerin altında yorumlar oluşturup görebilmek istiyorsanız."
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492 #: .\cookbook\forms.py:79 .\cookbook\forms.py:509
msgid "" msgid ""
"Setting to 0 will disable auto sync. When viewing a shopping list the list " "Setting to 0 will disable auto sync. When viewing a shopping list the list "
"is updated every set seconds to sync changes someone else might have made. " "is updated every set seconds to sync changes someone else might have made. "
@@ -127,7 +127,7 @@ msgstr ""
msgid "Makes the navbar stick to the top of the page." msgid "Makes the navbar stick to the top of the page."
msgstr "" msgstr ""
#: .\cookbook\forms.py:83 .\cookbook\forms.py:495 #: .\cookbook\forms.py:83 .\cookbook\forms.py:512
msgid "Automatically add meal plan ingredients to shopping list." msgid "Automatically add meal plan ingredients to shopping list."
msgstr "" msgstr ""
@@ -145,11 +145,11 @@ msgid ""
"instead" "instead"
msgstr "" msgstr ""
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297 #: .\cookbook\forms.py:123 .\cookbook\forms.py:314
msgid "Name" msgid "Name"
msgstr "İsim" msgstr "İsim"
#: .\cookbook\forms.py:124 .\cookbook\forms.py:298 .\cookbook\views\lists.py:88 #: .\cookbook\forms.py:124 .\cookbook\forms.py:315 .\cookbook\views\lists.py:88
msgid "Keywords" msgid "Keywords"
msgstr "" msgstr ""
@@ -161,7 +161,7 @@ msgstr ""
msgid "Waiting time (cooking/baking) in minutes" msgid "Waiting time (cooking/baking) in minutes"
msgstr "" msgstr ""
#: .\cookbook\forms.py:127 .\cookbook\forms.py:266 .\cookbook\forms.py:299 #: .\cookbook\forms.py:127 .\cookbook\forms.py:283 .\cookbook\forms.py:316
msgid "Path" msgid "Path"
msgstr "" msgstr ""
@@ -173,263 +173,263 @@ msgstr ""
msgid "Default" msgid "Default"
msgstr "Varsayılan" msgstr "Varsayılan"
#: .\cookbook\forms.py:173 #: .\cookbook\forms.py:190
msgid "" msgid ""
"To prevent duplicates recipes with the same name as existing ones are " "To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything." "ignored. Check this box to import everything."
msgstr "" msgstr ""
#: .\cookbook\forms.py:196 #: .\cookbook\forms.py:213
msgid "Add your comment: " msgid "Add your comment: "
msgstr "" msgstr ""
#: .\cookbook\forms.py:211 #: .\cookbook\forms.py:228
msgid "Leave empty for dropbox and enter app password for nextcloud." msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "" msgstr ""
#: .\cookbook\forms.py:218 #: .\cookbook\forms.py:235
msgid "Leave empty for nextcloud and enter api token for dropbox." msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "" msgstr ""
#: .\cookbook\forms.py:227 #: .\cookbook\forms.py:244
msgid "" msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote." "Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
"php/webdav/</code> is added automatically)" "php/webdav/</code> is added automatically)"
msgstr "" msgstr ""
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157 #: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
msgid "Storage" msgid "Storage"
msgstr "" msgstr ""
#: .\cookbook\forms.py:267 #: .\cookbook\forms.py:284
msgid "Active" msgid "Active"
msgstr "Aktif" msgstr "Aktif"
#: .\cookbook\forms.py:273 #: .\cookbook\forms.py:290
msgid "Search String" msgid "Search String"
msgstr "" msgstr ""
#: .\cookbook\forms.py:300 #: .\cookbook\forms.py:317
msgid "File ID" msgid "File ID"
msgstr "" msgstr ""
#: .\cookbook\forms.py:322 #: .\cookbook\forms.py:339
msgid "You must provide at least a recipe or a title." msgid "You must provide at least a recipe or a title."
msgstr "" msgstr ""
#: .\cookbook\forms.py:335 #: .\cookbook\forms.py:352
msgid "You can list default users to share recipes with in the settings." msgid "You can list default users to share recipes with in the settings."
msgstr "" msgstr ""
#: .\cookbook\forms.py:336 #: .\cookbook\forms.py:353
msgid "" msgid ""
"You can use markdown to format this field. See the <a href=\"/docs/markdown/" "You can use markdown to format this field. See the <a href=\"/docs/markdown/"
"\">docs here</a>" "\">docs here</a>"
msgstr "" msgstr ""
#: .\cookbook\forms.py:362 #: .\cookbook\forms.py:379
msgid "Maximum number of users for this space reached." msgid "Maximum number of users for this space reached."
msgstr "" msgstr ""
#: .\cookbook\forms.py:368 #: .\cookbook\forms.py:385
msgid "Email address already taken!" msgid "Email address already taken!"
msgstr "" msgstr ""
#: .\cookbook\forms.py:376 #: .\cookbook\forms.py:393
msgid "" msgid ""
"An email address is not required but if present the invite link will be sent " "An email address is not required but if present the invite link will be sent "
"to the user." "to the user."
msgstr "" msgstr ""
#: .\cookbook\forms.py:391 #: .\cookbook\forms.py:408
msgid "Name already taken." msgid "Name already taken."
msgstr "" msgstr ""
#: .\cookbook\forms.py:402 #: .\cookbook\forms.py:419
msgid "Accept Terms and Privacy" msgid "Accept Terms and Privacy"
msgstr "" msgstr ""
#: .\cookbook\forms.py:434 #: .\cookbook\forms.py:451
msgid "" msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e." "Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)." "g. low values mean more typos are ignored)."
msgstr "" msgstr ""
#: .\cookbook\forms.py:444 #: .\cookbook\forms.py:461
msgid "" msgid ""
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for " "Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
"full description of choices." "full description of choices."
msgstr "" msgstr ""
#: .\cookbook\forms.py:445 #: .\cookbook\forms.py:462
msgid "" msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and " "Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes." "importing recipes."
msgstr "" msgstr ""
#: .\cookbook\forms.py:447 #: .\cookbook\forms.py:464
msgid "" msgid ""
"Fields to search ignoring accents. Selecting this option can improve or " "Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language" "degrade search quality depending on language"
msgstr "" msgstr ""
#: .\cookbook\forms.py:449 #: .\cookbook\forms.py:466
msgid "" msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return " "Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')" "'pie' and 'piece' and 'soapie')"
msgstr "" msgstr ""
#: .\cookbook\forms.py:451 #: .\cookbook\forms.py:468
msgid "" msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' " "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')" "will return 'salad' and 'sandwich')"
msgstr "" msgstr ""
#: .\cookbook\forms.py:453 #: .\cookbook\forms.py:470
msgid "" msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) " "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search." "Note: this option will conflict with 'web' and 'raw' methods of search."
msgstr "" msgstr ""
#: .\cookbook\forms.py:455 #: .\cookbook\forms.py:472
msgid "" msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods " "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields." "only function with fulltext fields."
msgstr "" msgstr ""
#: .\cookbook\forms.py:459 #: .\cookbook\forms.py:476
msgid "Search Method" msgid "Search Method"
msgstr "" msgstr ""
#: .\cookbook\forms.py:460 #: .\cookbook\forms.py:477
msgid "Fuzzy Lookups" msgid "Fuzzy Lookups"
msgstr "" msgstr ""
#: .\cookbook\forms.py:461 #: .\cookbook\forms.py:478
msgid "Ignore Accent" msgid "Ignore Accent"
msgstr "" msgstr ""
#: .\cookbook\forms.py:462 #: .\cookbook\forms.py:479
msgid "Partial Match" msgid "Partial Match"
msgstr "" msgstr ""
#: .\cookbook\forms.py:463 #: .\cookbook\forms.py:480
msgid "Starts With" msgid "Starts With"
msgstr "" msgstr ""
#: .\cookbook\forms.py:464 #: .\cookbook\forms.py:481
msgid "Fuzzy Search" msgid "Fuzzy Search"
msgstr "" msgstr ""
#: .\cookbook\forms.py:465 #: .\cookbook\forms.py:482
msgid "Full Text" msgid "Full Text"
msgstr "" msgstr ""
#: .\cookbook\forms.py:490 #: .\cookbook\forms.py:507
msgid "" msgid ""
"Users will see all items you add to your shopping list. They must add you " "Users will see all items you add to your shopping list. They must add you "
"to see items on their list." "to see items on their list."
msgstr "" msgstr ""
#: .\cookbook\forms.py:496 #: .\cookbook\forms.py:513
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes." "include all related recipes."
msgstr "" msgstr ""
#: .\cookbook\forms.py:497 #: .\cookbook\forms.py:514
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"exclude ingredients that are on hand." "exclude ingredients that are on hand."
msgstr "" msgstr ""
#: .\cookbook\forms.py:498 #: .\cookbook\forms.py:515
msgid "Default number of hours to delay a shopping list entry." msgid "Default number of hours to delay a shopping list entry."
msgstr "" msgstr ""
#: .\cookbook\forms.py:499 #: .\cookbook\forms.py:516
msgid "Filter shopping list to only include supermarket categories." msgid "Filter shopping list to only include supermarket categories."
msgstr "" msgstr ""
#: .\cookbook\forms.py:500 #: .\cookbook\forms.py:517
msgid "Days of recent shopping list entries to display." msgid "Days of recent shopping list entries to display."
msgstr "" msgstr ""
#: .\cookbook\forms.py:501 #: .\cookbook\forms.py:518
msgid "Mark food 'On Hand' when checked off shopping list." msgid "Mark food 'On Hand' when checked off shopping list."
msgstr "" msgstr ""
#: .\cookbook\forms.py:502 #: .\cookbook\forms.py:519
msgid "Delimiter to use for CSV exports." msgid "Delimiter to use for CSV exports."
msgstr "" msgstr ""
#: .\cookbook\forms.py:503 #: .\cookbook\forms.py:520
msgid "Prefix to add when copying list to the clipboard." msgid "Prefix to add when copying list to the clipboard."
msgstr "" msgstr ""
#: .\cookbook\forms.py:507 #: .\cookbook\forms.py:524
msgid "Share Shopping List" msgid "Share Shopping List"
msgstr "" msgstr ""
#: .\cookbook\forms.py:508 #: .\cookbook\forms.py:525
msgid "Autosync" msgid "Autosync"
msgstr "" msgstr ""
#: .\cookbook\forms.py:509 #: .\cookbook\forms.py:526
msgid "Auto Add Meal Plan" msgid "Auto Add Meal Plan"
msgstr "" msgstr ""
#: .\cookbook\forms.py:510 #: .\cookbook\forms.py:527
msgid "Exclude On Hand" msgid "Exclude On Hand"
msgstr "" msgstr ""
#: .\cookbook\forms.py:511 #: .\cookbook\forms.py:528
msgid "Include Related" msgid "Include Related"
msgstr "" msgstr ""
#: .\cookbook\forms.py:512 #: .\cookbook\forms.py:529
msgid "Default Delay Hours" msgid "Default Delay Hours"
msgstr "" msgstr ""
#: .\cookbook\forms.py:513 #: .\cookbook\forms.py:530
msgid "Filter to Supermarket" msgid "Filter to Supermarket"
msgstr "" msgstr ""
#: .\cookbook\forms.py:514 #: .\cookbook\forms.py:531
msgid "Recent Days" msgid "Recent Days"
msgstr "" msgstr ""
#: .\cookbook\forms.py:515 #: .\cookbook\forms.py:532
msgid "CSV Delimiter" msgid "CSV Delimiter"
msgstr "" msgstr ""
#: .\cookbook\forms.py:516 #: .\cookbook\forms.py:533
msgid "List Prefix" msgid "List Prefix"
msgstr "" msgstr ""
#: .\cookbook\forms.py:517 #: .\cookbook\forms.py:534
msgid "Auto On Hand" msgid "Auto On Hand"
msgstr "" msgstr ""
#: .\cookbook\forms.py:527 #: .\cookbook\forms.py:544
msgid "Reset Food Inheritance" msgid "Reset Food Inheritance"
msgstr "" msgstr ""
#: .\cookbook\forms.py:528 #: .\cookbook\forms.py:545
msgid "Reset all food to inherit the fields configured." msgid "Reset all food to inherit the fields configured."
msgstr "" msgstr ""
#: .\cookbook\forms.py:540 #: .\cookbook\forms.py:557
msgid "Fields on food that should be inherited by default." msgid "Fields on food that should be inherited by default."
msgstr "" msgstr ""
#: .\cookbook\forms.py:541 #: .\cookbook\forms.py:558
#, fuzzy #, fuzzy
#| msgid "Show recently viewed recipes on search page." #| msgid "Show recently viewed recipes on search page."
msgid "Show recipe counts on search filters" msgid "Show recipe counts on search filters"
msgstr "Son görüntülenen tarifleri arama sayfasında göster." msgstr "Son görüntülenen tarifleri arama sayfasında göster."
#: .\cookbook\forms.py:542 #: .\cookbook\forms.py:559
msgid "Use the plural form for units and food inside this space." msgid "Use the plural form for units and food inside this space."
msgstr "" msgstr ""

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-04-26 07:46+0200\n" "POT-Creation-Date: 2023-05-18 14:28+0200\n"
"PO-Revision-Date: 2023-02-26 13:15+0000\n" "PO-Revision-Date: 2023-02-26 13:15+0000\n"
"Last-Translator: 吕楪 <thy@irithys.com>\n" "Last-Translator: 吕楪 <thy@irithys.com>\n"
"Language-Team: Chinese (Simplified) <http://translate.tandoor.dev/projects/" "Language-Team: Chinese (Simplified) <http://translate.tandoor.dev/projects/"
@@ -104,7 +104,7 @@ msgstr "四舍五入食材的小数点数量。"
msgid "If you want to be able to create and see comments underneath recipes." msgid "If you want to be able to create and see comments underneath recipes."
msgstr "如果你希望能够在菜谱下面创建并看到评论。" msgstr "如果你希望能够在菜谱下面创建并看到评论。"
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492 #: .\cookbook\forms.py:79 .\cookbook\forms.py:509
msgid "" msgid ""
"Setting to 0 will disable auto sync. When viewing a shopping list the list " "Setting to 0 will disable auto sync. When viewing a shopping list the list "
"is updated every set seconds to sync changes someone else might have made. " "is updated every set seconds to sync changes someone else might have made. "
@@ -119,7 +119,7 @@ msgstr ""
msgid "Makes the navbar stick to the top of the page." msgid "Makes the navbar stick to the top of the page."
msgstr "使导航栏悬浮在页面的顶部。" msgstr "使导航栏悬浮在页面的顶部。"
#: .\cookbook\forms.py:83 .\cookbook\forms.py:495 #: .\cookbook\forms.py:83 .\cookbook\forms.py:512
msgid "Automatically add meal plan ingredients to shopping list." msgid "Automatically add meal plan ingredients to shopping list."
msgstr "自动将膳食计划食材添加到购物清单中。" msgstr "自动将膳食计划食材添加到购物清单中。"
@@ -137,11 +137,11 @@ msgid ""
"instead" "instead"
msgstr "这两个字段都是可选的。如果没有给出,将显示用户名" msgstr "这两个字段都是可选的。如果没有给出,将显示用户名"
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297 #: .\cookbook\forms.py:123 .\cookbook\forms.py:314
msgid "Name" msgid "Name"
msgstr "名字" msgstr "名字"
#: .\cookbook\forms.py:124 .\cookbook\forms.py:298 .\cookbook\views\lists.py:88 #: .\cookbook\forms.py:124 .\cookbook\forms.py:315 .\cookbook\views\lists.py:88
msgid "Keywords" msgid "Keywords"
msgstr "关键词" msgstr "关键词"
@@ -153,7 +153,7 @@ msgstr "准备时间(分钟)"
msgid "Waiting time (cooking/baking) in minutes" msgid "Waiting time (cooking/baking) in minutes"
msgstr "等候(烹饪、烘焙等)时间(分钟)" msgstr "等候(烹饪、烘焙等)时间(分钟)"
#: .\cookbook\forms.py:127 .\cookbook\forms.py:266 .\cookbook\forms.py:299 #: .\cookbook\forms.py:127 .\cookbook\forms.py:283 .\cookbook\forms.py:316
msgid "Path" msgid "Path"
msgstr "路径" msgstr "路径"
@@ -165,7 +165,7 @@ msgstr "存储 UID"
msgid "Default" msgid "Default"
msgstr "默认" msgstr "默认"
#: .\cookbook\forms.py:173 #: .\cookbook\forms.py:190
msgid "" msgid ""
"To prevent duplicates recipes with the same name as existing ones are " "To prevent duplicates recipes with the same name as existing ones are "
"ignored. Check this box to import everything." "ignored. Check this box to import everything."
@@ -173,19 +173,19 @@ msgstr ""
"为防止重复,忽略与现有同名的菜谱。选中此框可导入所有内容(危险操作,请先备" "为防止重复,忽略与现有同名的菜谱。选中此框可导入所有内容(危险操作,请先备"
"份)。" "份)。"
#: .\cookbook\forms.py:196 #: .\cookbook\forms.py:213
msgid "Add your comment: " msgid "Add your comment: "
msgstr "发表评论: " msgstr "发表评论: "
#: .\cookbook\forms.py:211 #: .\cookbook\forms.py:228
msgid "Leave empty for dropbox and enter app password for nextcloud." msgid "Leave empty for dropbox and enter app password for nextcloud."
msgstr "Dropbox 留空并输入 Nextcloud 应用密码。" msgstr "Dropbox 留空并输入 Nextcloud 应用密码。"
#: .\cookbook\forms.py:218 #: .\cookbook\forms.py:235
msgid "Leave empty for nextcloud and enter api token for dropbox." msgid "Leave empty for nextcloud and enter api token for dropbox."
msgstr "Nextcloud 留空并输入 Dropbox API 令牌。" msgstr "Nextcloud 留空并输入 Dropbox API 令牌。"
#: .\cookbook\forms.py:227 #: .\cookbook\forms.py:244
msgid "" msgid ""
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote." "Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
"php/webdav/</code> is added automatically)" "php/webdav/</code> is added automatically)"
@@ -193,60 +193,60 @@ msgstr ""
"Dropbox 留空并输入基础 Nextcloud 网址(<code>/remote.php/webdav/</code> 会自" "Dropbox 留空并输入基础 Nextcloud 网址(<code>/remote.php/webdav/</code> 会自"
"动添加)" "动添加)"
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157 #: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
msgid "Storage" msgid "Storage"
msgstr "存储" msgstr "存储"
#: .\cookbook\forms.py:267 #: .\cookbook\forms.py:284
msgid "Active" msgid "Active"
msgstr "活跃" msgstr "活跃"
#: .\cookbook\forms.py:273 #: .\cookbook\forms.py:290
msgid "Search String" msgid "Search String"
msgstr "搜索字符串" msgstr "搜索字符串"
#: .\cookbook\forms.py:300 #: .\cookbook\forms.py:317
msgid "File ID" msgid "File ID"
msgstr "文件编号" msgstr "文件编号"
#: .\cookbook\forms.py:322 #: .\cookbook\forms.py:339
msgid "You must provide at least a recipe or a title." msgid "You must provide at least a recipe or a title."
msgstr "你必须至少提供一份菜谱或一个标题。" msgstr "你必须至少提供一份菜谱或一个标题。"
#: .\cookbook\forms.py:335 #: .\cookbook\forms.py:352
msgid "You can list default users to share recipes with in the settings." msgid "You can list default users to share recipes with in the settings."
msgstr "你可以在设置中列出默认用户来分享菜谱。" msgstr "你可以在设置中列出默认用户来分享菜谱。"
#: .\cookbook\forms.py:336 #: .\cookbook\forms.py:353
msgid "" msgid ""
"You can use markdown to format this field. See the <a href=\"/docs/markdown/" "You can use markdown to format this field. See the <a href=\"/docs/markdown/"
"\">docs here</a>" "\">docs here</a>"
msgstr "" msgstr ""
"可以使用 Markdown 设置此字段格式。<a href=\"/docs/markdown/\">查看文档</a>" "可以使用 Markdown 设置此字段格式。<a href=\"/docs/markdown/\">查看文档</a>"
#: .\cookbook\forms.py:362 #: .\cookbook\forms.py:379
msgid "Maximum number of users for this space reached." msgid "Maximum number of users for this space reached."
msgstr "已达到该空间的最大用户数。" msgstr "已达到该空间的最大用户数。"
#: .\cookbook\forms.py:368 #: .\cookbook\forms.py:385
msgid "Email address already taken!" msgid "Email address already taken!"
msgstr "电子邮件地址已被注册!" msgstr "电子邮件地址已被注册!"
#: .\cookbook\forms.py:376 #: .\cookbook\forms.py:393
msgid "" msgid ""
"An email address is not required but if present the invite link will be sent " "An email address is not required but if present the invite link will be sent "
"to the user." "to the user."
msgstr "电子邮件地址不是必需的,但如果存在,邀请链接将被发送给用户。" msgstr "电子邮件地址不是必需的,但如果存在,邀请链接将被发送给用户。"
#: .\cookbook\forms.py:391 #: .\cookbook\forms.py:408
msgid "Name already taken." msgid "Name already taken."
msgstr "名字已被占用。" msgstr "名字已被占用。"
#: .\cookbook\forms.py:402 #: .\cookbook\forms.py:419
msgid "Accept Terms and Privacy" msgid "Accept Terms and Privacy"
msgstr "接受条款及隐私政策" msgstr "接受条款及隐私政策"
#: .\cookbook\forms.py:434 #: .\cookbook\forms.py:451
msgid "" msgid ""
"Determines how fuzzy a search is if it uses trigram similarity matching (e." "Determines how fuzzy a search is if it uses trigram similarity matching (e."
"g. low values mean more typos are ignored)." "g. low values mean more typos are ignored)."
@@ -254,7 +254,7 @@ msgstr ""
"确定使用三元图相似性匹配时搜索的模糊程度(例如,较低的值意味着忽略更多的打字" "确定使用三元图相似性匹配时搜索的模糊程度(例如,较低的值意味着忽略更多的打字"
"错误)。" "错误)。"
#: .\cookbook\forms.py:444 #: .\cookbook\forms.py:461
msgid "" msgid ""
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for " "Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
"full description of choices." "full description of choices."
@@ -262,31 +262,31 @@ msgstr ""
"选择搜索类型方法。 <a href=\"/docs/search/\">点击此处</a> 查看选项的完整说" "选择搜索类型方法。 <a href=\"/docs/search/\">点击此处</a> 查看选项的完整说"
"明。" "明。"
#: .\cookbook\forms.py:445 #: .\cookbook\forms.py:462
msgid "" msgid ""
"Use fuzzy matching on units, keywords and ingredients when editing and " "Use fuzzy matching on units, keywords and ingredients when editing and "
"importing recipes." "importing recipes."
msgstr "编辑和导入菜谱时,对单位、关键词和食材使用模糊匹配。" msgstr "编辑和导入菜谱时,对单位、关键词和食材使用模糊匹配。"
#: .\cookbook\forms.py:447 #: .\cookbook\forms.py:464
msgid "" msgid ""
"Fields to search ignoring accents. Selecting this option can improve or " "Fields to search ignoring accents. Selecting this option can improve or "
"degrade search quality depending on language" "degrade search quality depending on language"
msgstr "忽略搜索字段的重音。此选项会因语言差异导致搜索质量产生变化" msgstr "忽略搜索字段的重音。此选项会因语言差异导致搜索质量产生变化"
#: .\cookbook\forms.py:449 #: .\cookbook\forms.py:466
msgid "" msgid ""
"Fields to search for partial matches. (e.g. searching for 'Pie' will return " "Fields to search for partial matches. (e.g. searching for 'Pie' will return "
"'pie' and 'piece' and 'soapie')" "'pie' and 'piece' and 'soapie')"
msgstr "用于搜索部分匹配的字段。如搜索“Pie”会返回“pie”、“piece”和“soapie”" msgstr "用于搜索部分匹配的字段。如搜索“Pie”会返回“pie”、“piece”和“soapie”"
#: .\cookbook\forms.py:451 #: .\cookbook\forms.py:468
msgid "" msgid ""
"Fields to search for beginning of word matches. (e.g. searching for 'sa' " "Fields to search for beginning of word matches. (e.g. searching for 'sa' "
"will return 'salad' and 'sandwich')" "will return 'salad' and 'sandwich')"
msgstr "用于搜索开头匹配的字段。如搜索“sa”会返回“salad”和“sandwich”" msgstr "用于搜索开头匹配的字段。如搜索“sa”会返回“salad”和“sandwich”"
#: .\cookbook\forms.py:453 #: .\cookbook\forms.py:470
msgid "" msgid ""
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) " "Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
"Note: this option will conflict with 'web' and 'raw' methods of search." "Note: this option will conflict with 'web' and 'raw' methods of search."
@@ -294,41 +294,41 @@ msgstr ""
"“模糊”搜索字段。例如搜索“recpie”将会找到“recipe”。注意此选项将" "“模糊”搜索字段。例如搜索“recpie”将会找到“recipe”。注意此选项将"
"与“web”和“raw”搜索方法冲突。" "与“web”和“raw”搜索方法冲突。"
#: .\cookbook\forms.py:455 #: .\cookbook\forms.py:472
msgid "" msgid ""
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods " "Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
"only function with fulltext fields." "only function with fulltext fields."
msgstr "全文搜索字段。“web”、“phrase”和“raw”搜索方法仅适用于全文字段。" msgstr "全文搜索字段。“web”、“phrase”和“raw”搜索方法仅适用于全文字段。"
#: .\cookbook\forms.py:459 #: .\cookbook\forms.py:476
msgid "Search Method" msgid "Search Method"
msgstr "搜索方法" msgstr "搜索方法"
#: .\cookbook\forms.py:460 #: .\cookbook\forms.py:477
msgid "Fuzzy Lookups" msgid "Fuzzy Lookups"
msgstr "模糊查找" msgstr "模糊查找"
#: .\cookbook\forms.py:461 #: .\cookbook\forms.py:478
msgid "Ignore Accent" msgid "Ignore Accent"
msgstr "忽略重音" msgstr "忽略重音"
#: .\cookbook\forms.py:462 #: .\cookbook\forms.py:479
msgid "Partial Match" msgid "Partial Match"
msgstr "部分匹配" msgstr "部分匹配"
#: .\cookbook\forms.py:463 #: .\cookbook\forms.py:480
msgid "Starts With" msgid "Starts With"
msgstr "起始于" msgstr "起始于"
#: .\cookbook\forms.py:464 #: .\cookbook\forms.py:481
msgid "Fuzzy Search" msgid "Fuzzy Search"
msgstr "模糊搜索" msgstr "模糊搜索"
#: .\cookbook\forms.py:465 #: .\cookbook\forms.py:482
msgid "Full Text" msgid "Full Text"
msgstr "全文" msgstr "全文"
#: .\cookbook\forms.py:490 #: .\cookbook\forms.py:507
msgid "" msgid ""
"Users will see all items you add to your shopping list. They must add you " "Users will see all items you add to your shopping list. They must add you "
"to see items on their list." "to see items on their list."
@@ -336,103 +336,103 @@ msgstr ""
"用户将看到你添加到购物清单中的所有商品。他们必须将你添加到列表才能看到他们清" "用户将看到你添加到购物清单中的所有商品。他们必须将你添加到列表才能看到他们清"
"单上的项目。" "单上的项目。"
#: .\cookbook\forms.py:496 #: .\cookbook\forms.py:513
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"include all related recipes." "include all related recipes."
msgstr "将膳食计划(手动或自动)添加到购物清单时,包括所有相关食谱。" msgstr "将膳食计划(手动或自动)添加到购物清单时,包括所有相关食谱。"
#: .\cookbook\forms.py:497 #: .\cookbook\forms.py:514
msgid "" msgid ""
"When adding a meal plan to the shopping list (manually or automatically), " "When adding a meal plan to the shopping list (manually or automatically), "
"exclude ingredients that are on hand." "exclude ingredients that are on hand."
msgstr "将膳食计划(手动或自动)添加到购物清单时,排除现有食材。" msgstr "将膳食计划(手动或自动)添加到购物清单时,排除现有食材。"
#: .\cookbook\forms.py:498 #: .\cookbook\forms.py:515
msgid "Default number of hours to delay a shopping list entry." msgid "Default number of hours to delay a shopping list entry."
msgstr "延迟购物清单条目的默认小时数。" msgstr "延迟购物清单条目的默认小时数。"
#: .\cookbook\forms.py:499 #: .\cookbook\forms.py:516
msgid "Filter shopping list to only include supermarket categories." msgid "Filter shopping list to only include supermarket categories."
msgstr "筛选购物清单仅包含超市分类。" msgstr "筛选购物清单仅包含超市分类。"
#: .\cookbook\forms.py:500 #: .\cookbook\forms.py:517
msgid "Days of recent shopping list entries to display." msgid "Days of recent shopping list entries to display."
msgstr "显示最近几天的购物清单列表。" msgstr "显示最近几天的购物清单列表。"
#: .\cookbook\forms.py:501 #: .\cookbook\forms.py:518
msgid "Mark food 'On Hand' when checked off shopping list." msgid "Mark food 'On Hand' when checked off shopping list."
msgstr "在核对购物清单时,将食物标记为“入手”。" msgstr "在核对购物清单时,将食物标记为“入手”。"
#: .\cookbook\forms.py:502 #: .\cookbook\forms.py:519
msgid "Delimiter to use for CSV exports." msgid "Delimiter to use for CSV exports."
msgstr "用于 CSV 导出的分隔符。" msgstr "用于 CSV 导出的分隔符。"
#: .\cookbook\forms.py:503 #: .\cookbook\forms.py:520
msgid "Prefix to add when copying list to the clipboard." msgid "Prefix to add when copying list to the clipboard."
msgstr "将清单复制到剪贴板时要添加的前缀。" msgstr "将清单复制到剪贴板时要添加的前缀。"
#: .\cookbook\forms.py:507 #: .\cookbook\forms.py:524
msgid "Share Shopping List" msgid "Share Shopping List"
msgstr "分享购物清单" msgstr "分享购物清单"
#: .\cookbook\forms.py:508 #: .\cookbook\forms.py:525
msgid "Autosync" msgid "Autosync"
msgstr "自动同步" msgstr "自动同步"
#: .\cookbook\forms.py:509 #: .\cookbook\forms.py:526
msgid "Auto Add Meal Plan" msgid "Auto Add Meal Plan"
msgstr "自动添加膳食计划" msgstr "自动添加膳食计划"
#: .\cookbook\forms.py:510 #: .\cookbook\forms.py:527
msgid "Exclude On Hand" msgid "Exclude On Hand"
msgstr "排除现有" msgstr "排除现有"
#: .\cookbook\forms.py:511 #: .\cookbook\forms.py:528
msgid "Include Related" msgid "Include Related"
msgstr "包括相关" msgstr "包括相关"
#: .\cookbook\forms.py:512 #: .\cookbook\forms.py:529
msgid "Default Delay Hours" msgid "Default Delay Hours"
msgstr "默认延迟时间" msgstr "默认延迟时间"
#: .\cookbook\forms.py:513 #: .\cookbook\forms.py:530
msgid "Filter to Supermarket" msgid "Filter to Supermarket"
msgstr "按超市筛选" msgstr "按超市筛选"
#: .\cookbook\forms.py:514 #: .\cookbook\forms.py:531
msgid "Recent Days" msgid "Recent Days"
msgstr "最近几天" msgstr "最近几天"
#: .\cookbook\forms.py:515 #: .\cookbook\forms.py:532
msgid "CSV Delimiter" msgid "CSV Delimiter"
msgstr "CSV 分隔符" msgstr "CSV 分隔符"
#: .\cookbook\forms.py:516 #: .\cookbook\forms.py:533
msgid "List Prefix" msgid "List Prefix"
msgstr "清单前缀" msgstr "清单前缀"
#: .\cookbook\forms.py:517 #: .\cookbook\forms.py:534
msgid "Auto On Hand" msgid "Auto On Hand"
msgstr "自动入手" msgstr "自动入手"
#: .\cookbook\forms.py:527 #: .\cookbook\forms.py:544
msgid "Reset Food Inheritance" msgid "Reset Food Inheritance"
msgstr "重置食物材料" msgstr "重置食物材料"
#: .\cookbook\forms.py:528 #: .\cookbook\forms.py:545
msgid "Reset all food to inherit the fields configured." msgid "Reset all food to inherit the fields configured."
msgstr "重置所有食物以继承配置的字段。" msgstr "重置所有食物以继承配置的字段。"
#: .\cookbook\forms.py:540 #: .\cookbook\forms.py:557
msgid "Fields on food that should be inherited by default." msgid "Fields on food that should be inherited by default."
msgstr "默认情况下应继承的食物上的字段。" msgstr "默认情况下应继承的食物上的字段。"
#: .\cookbook\forms.py:541 #: .\cookbook\forms.py:558
msgid "Show recipe counts on search filters" msgid "Show recipe counts on search filters"
msgstr "显示搜索筛选器上的食谱计数" msgstr "显示搜索筛选器上的食谱计数"
#: .\cookbook\forms.py:542 #: .\cookbook\forms.py:559
msgid "Use the plural form for units and food inside this space." msgid "Use the plural form for units and food inside this space."
msgstr "在此空间内使用复数形式表示单位和食物。" msgstr "在此空间内使用复数形式表示单位和食物。"

View File

@@ -0,0 +1,163 @@
# Generated by Django 4.1.9 on 2023-05-25 13:05
import cookbook.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django_prometheus.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0188_space_no_sharing_limit'),
]
operations = [
migrations.CreateModel(
name='Property',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('property_amount', models.DecimalField(decimal_places=4, default=0, max_digits=32)),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='PropertyType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128)),
('unit', models.CharField(blank=True, max_length=64, null=True)),
('icon', models.CharField(blank=True, max_length=16, null=True)),
('description', models.CharField(blank=True, max_length=512, null=True)),
('category', models.CharField(blank=True, choices=[('NUTRITION', 'Nutrition'), ('ALLERGEN', 'Allergen'), ('PRICE', 'Price'), ('GOAL', 'Goal'), ('OTHER', 'Other')], max_length=64, null=True)),
('open_data_slug', models.CharField(blank=True, default=None, max_length=128, null=True)),
],
bases=(models.Model, cookbook.models.PermissionModelMixin),
),
migrations.CreateModel(
name='UnitConversion',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('base_amount', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('converted_amount', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('open_data_slug', models.CharField(blank=True, default=None, max_length=128, null=True)),
],
bases=(django_prometheus.models.ExportModelOperationsMixin('unit_conversion'), models.Model, cookbook.models.PermissionModelMixin),
),
migrations.AddField(
model_name='food',
name='fdc_id',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
migrations.AddField(
model_name='food',
name='open_data_slug',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
migrations.AddField(
model_name='food',
name='preferred_shopping_unit',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='preferred_shopping_unit', to='cookbook.unit'),
),
migrations.AddField(
model_name='food',
name='preferred_unit',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='preferred_unit', to='cookbook.unit'),
),
migrations.AddField(
model_name='food',
name='properties_food_amount',
field=models.IntegerField(blank=True, default=100),
),
migrations.AddField(
model_name='food',
name='properties_food_unit',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.unit'),
),
migrations.AddField(
model_name='supermarket',
name='open_data_slug',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
migrations.AddField(
model_name='supermarketcategory',
name='open_data_slug',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
migrations.AddField(
model_name='unit',
name='base_unit',
field=models.TextField(blank=True, default=None, max_length=256, null=True),
),
migrations.AddField(
model_name='unit',
name='open_data_slug',
field=models.CharField(blank=True, default=None, max_length=128, null=True),
),
migrations.AddConstraint(
model_name='supermarketcategoryrelation',
constraint=models.UniqueConstraint(fields=('supermarket', 'category'), name='unique_sm_category_relation'),
),
migrations.AddField(
model_name='unitconversion',
name='base_unit',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_conversion_base_relation', to='cookbook.unit'),
),
migrations.AddField(
model_name='unitconversion',
name='converted_unit',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_conversion_converted_relation', to='cookbook.unit'),
),
migrations.AddField(
model_name='unitconversion',
name='created_by',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='unitconversion',
name='food',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.food'),
),
migrations.AddField(
model_name='unitconversion',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='propertytype',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='property',
name='property_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.propertytype'),
),
migrations.AddField(
model_name='property',
name='space',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space'),
),
migrations.AddField(
model_name='food',
name='properties',
field=models.ManyToManyField(blank=True, to='cookbook.property'),
),
migrations.AddField(
model_name='recipe',
name='properties',
field=models.ManyToManyField(blank=True, to='cookbook.property'),
),
migrations.AddConstraint(
model_name='unitconversion',
constraint=models.UniqueConstraint(fields=('space', 'base_unit', 'converted_unit', 'food'), name='f_unique_conversion_per_space'),
),
migrations.AddConstraint(
model_name='propertytype',
constraint=models.UniqueConstraint(fields=('space', 'name'), name='property_type_unique_name_per_space'),
),
]

View File

@@ -0,0 +1,38 @@
# Generated by Django 4.1.9 on 2023-05-25 13:06
from django.db import migrations
from django_scopes import scopes_disabled
from gettext import gettext as _
def migrate_old_nutrition_data(apps, schema_editor):
print('Transforming nutrition information, this might take a while on large databases')
with scopes_disabled():
PropertyType = apps.get_model('cookbook', 'PropertyType')
RecipeProperty = apps.get_model('cookbook', 'Property')
Recipe = apps.get_model('cookbook', 'Recipe')
Space = apps.get_model('cookbook', 'Space')
# TODO respect space
for s in Space.objects.all():
property_fat = PropertyType.objects.get_or_create(name=_('Fat'), unit=_('g'), space=s, )[0]
property_carbohydrates = PropertyType.objects.get_or_create(name=_('Carbohydrates'), unit=_('g'), space=s, )[0]
property_proteins = PropertyType.objects.get_or_create(name=_('Proteins'), unit=_('g'), space=s, )[0]
property_calories = PropertyType.objects.get_or_create(name=_('Calories'), unit=_('kcal'), space=s, )[0]
for r in Recipe.objects.filter(nutrition__isnull=False, space=s).all():
rp_fat = RecipeProperty.objects.create(property_type=property_fat, property_amount=r.nutrition.fats, space=s)
rp_carbohydrates = RecipeProperty.objects.create(property_type=property_carbohydrates, property_amount=r.nutrition.carbohydrates, space=s)
rp_proteins = RecipeProperty.objects.create(property_type=property_proteins, property_amount=r.nutrition.proteins, space=s)
rp_calories = RecipeProperty.objects.create(property_type=property_calories, property_amount=r.nutrition.calories, space=s)
r.properties.add(rp_fat, rp_carbohydrates, rp_proteins, rp_calories)
r.nutrition = None
r.save()
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0189_property_propertytype_unitconversion_food_fdc_id_and_more'),
]
operations = [
migrations.RunPython(migrate_old_nutrition_data)
]

View File

@@ -0,0 +1,49 @@
# Generated by Django 4.1.9 on 2023-06-20 13:07
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0190_auto_20230525_1506'),
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=[
migrations.RunSQL(
sql="ALTER TABLE cookbook_food_properties RENAME TO cookbook_foodproperty",
reverse_sql="ALTER TABLE cookbook_foodproperty RENAME TO cookbook_food_properties",
),
],
state_operations=[
migrations.CreateModel(
name='FoodProperty',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('food', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.food')),
('property', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.property')),
],
),
migrations.AlterField(
model_name='food',
name='properties',
field=models.ManyToManyField(blank=True, through='cookbook.FoodProperty', to='cookbook.property'),
),
]
),
migrations.AddConstraint(
model_name='foodproperty',
constraint=models.UniqueConstraint(fields=('food', 'property'), name='property_unique_food'),
),
migrations.AddField(
model_name='property',
name='import_food_id',
field=models.IntegerField(blank=True, null=True),
),
migrations.AddConstraint(
model_name='property',
constraint=models.UniqueConstraint(fields=('space', 'property_type', 'import_food_id'), name='property_unique_import_food_per_space'),
),
]

View File

@@ -0,0 +1,37 @@
# Generated by Django 4.1.9 on 2023-06-20 13:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0191_foodproperty_property_import_food_id_and_more'),
]
operations = [
migrations.AddConstraint(
model_name='food',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='food_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='propertytype',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='property_type_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='supermarket',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='supermarket_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='supermarketcategory',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='supermarket_category_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='unit',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='unit_unique_open_data_slug_per_space'),
),
migrations.AddConstraint(
model_name='unitconversion',
constraint=models.UniqueConstraint(fields=('space', 'open_data_slug'), name='unit_conversion_unique_open_data_slug_per_space'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.1.9 on 2023-06-21 13:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0192_food_food_unique_open_data_slug_per_space_and_more'),
]
operations = [
migrations.AddField(
model_name='space',
name='internal_note',
field=models.TextField(blank=True, null=True),
),
]

View File

@@ -82,10 +82,13 @@ class TreeManager(MP_NodeManager):
# model.Manager get_or_create() is not compatible with MP_Tree # model.Manager get_or_create() is not compatible with MP_Tree
def get_or_create(self, *args, **kwargs): def get_or_create(self, *args, **kwargs):
kwargs['name'] = kwargs['name'].strip() kwargs['name'] = kwargs['name'].strip()
if hasattr(self, 'space'):
if obj := self.filter(name__iexact=kwargs['name'], space=kwargs['space']).first(): if obj := self.filter(name__iexact=kwargs['name'], space=kwargs['space']).first():
return obj, False return obj, False
else: else:
if obj := self.filter(name__iexact=kwargs['name']).first():
return obj, False
with scopes_disabled(): with scopes_disabled():
try: try:
defaults = kwargs.pop('defaults', None) defaults = kwargs.pop('defaults', None)
@@ -267,6 +270,8 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
food_inherit = models.ManyToManyField(FoodInheritField, blank=True) food_inherit = models.ManyToManyField(FoodInheritField, blank=True)
show_facet_count = models.BooleanField(default=False) show_facet_count = models.BooleanField(default=False)
internal_note = models.TextField(blank=True, null=True)
def safe_delete(self): def safe_delete(self):
""" """
Safely deletes a space by deleting all objects belonging to the space first and then deleting the space itself Safely deletes a space by deleting all objects belonging to the space first and then deleting the space itself
@@ -454,6 +459,7 @@ class Sync(models.Model, PermissionModelMixin):
class SupermarketCategory(models.Model, PermissionModelMixin): class SupermarketCategory(models.Model, PermissionModelMixin):
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)]) name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE) space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space') objects = ScopedManager(space='space')
@@ -463,7 +469,8 @@ class SupermarketCategory(models.Model, PermissionModelMixin):
class Meta: class Meta:
constraints = [ constraints = [
models.UniqueConstraint(fields=['space', 'name'], name='smc_unique_name_per_space') 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')
] ]
@@ -471,6 +478,7 @@ class Supermarket(models.Model, PermissionModelMixin):
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)]) name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
categories = models.ManyToManyField(SupermarketCategory, through='SupermarketCategoryRelation') categories = models.ManyToManyField(SupermarketCategory, through='SupermarketCategoryRelation')
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE) space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space') objects = ScopedManager(space='space')
@@ -480,7 +488,8 @@ class Supermarket(models.Model, PermissionModelMixin):
class Meta: class Meta:
constraints = [ constraints = [
models.UniqueConstraint(fields=['space', 'name'], name='sm_unique_name_per_space') 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')
] ]
@@ -496,6 +505,9 @@ class SupermarketCategoryRelation(models.Model, PermissionModelMixin):
return 'supermarket', 'space' return 'supermarket', 'space'
class Meta: class Meta:
constraints = [
models.UniqueConstraint(fields=['supermarket', 'category'], name='unique_sm_category_relation')
]
ordering = ('order',) ordering = ('order',)
@@ -534,6 +546,8 @@ class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixi
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)]) name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
plural_name = models.CharField(max_length=128, null=True, blank=True, default=None) plural_name = models.CharField(max_length=128, null=True, blank=True, default=None)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
base_unit = models.TextField(max_length=256, null=True, blank=True, default=None)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE) space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space') objects = ScopedManager(space='space')
@@ -543,7 +557,8 @@ class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixi
class Meta: class Meta:
constraints = [ constraints = [
models.UniqueConstraint(fields=['space', 'name'], name='u_unique_name_per_space') 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')
] ]
@@ -569,6 +584,15 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
substitute_children = models.BooleanField(default=False) substitute_children = models.BooleanField(default=False)
child_inherit_fields = models.ManyToManyField(FoodInheritField, blank=True, related_name='child_inherit') child_inherit_fields = models.ManyToManyField(FoodInheritField, blank=True, related_name='child_inherit')
properties = models.ManyToManyField("Property", blank=True, through='FoodProperty')
properties_food_amount = models.IntegerField(default=100, blank=True)
properties_food_unit = models.ForeignKey(Unit, on_delete=models.PROTECT, blank=True, null=True)
preferred_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_unit')
preferred_shopping_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_shopping_unit')
fdc_id = models.CharField(max_length=128, null=True, blank=True, default=None)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE) space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space', _manager_class=TreeManager) objects = ScopedManager(space='space', _manager_class=TreeManager)
@@ -642,7 +666,8 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
class Meta: class Meta:
constraints = [ constraints = [
models.UniqueConstraint(fields=['space', 'name'], name='f_unique_name_per_space') models.UniqueConstraint(fields=['space', 'name'], name='f_unique_name_per_space'),
models.UniqueConstraint(fields=['space', 'open_data_slug'], name='food_unique_open_data_slug_per_space')
] ]
indexes = ( indexes = (
Index(fields=['id']), Index(fields=['id']),
@@ -650,6 +675,32 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
) )
class UnitConversion(ExportModelOperationsMixin('unit_conversion'), models.Model, PermissionModelMixin):
base_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
base_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, related_name='unit_conversion_base_relation')
converted_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
converted_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, related_name='unit_conversion_converted_relation')
food = models.ForeignKey('Food', on_delete=models.CASCADE, null=True, blank=True)
created_by = models.ForeignKey(User, on_delete=models.PROTECT)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return f'{self.base_amount} {self.base_unit} -> {self.converted_amount} {self.converted_unit} {self.food}'
class Meta:
constraints = [
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')
]
class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, PermissionModelMixin): class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, PermissionModelMixin):
# delete method on Food and Unit checks if they are part of a Recipe, if it is raises a ProtectedError instead of cascading the delete # delete method on Food and Unit checks if they are part of a Recipe, if it is raises a ProtectedError instead of cascading the delete
food = models.ForeignKey(Food, on_delete=models.CASCADE, null=True, blank=True) food = models.ForeignKey(Food, on_delete=models.CASCADE, null=True, blank=True)
@@ -663,8 +714,6 @@ class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, Permiss
order = models.IntegerField(default=0) order = models.IntegerField(default=0)
original_text = models.CharField(max_length=512, null=True, blank=True, default=None) original_text = models.CharField(max_length=512, null=True, blank=True, default=None)
original_text = models.CharField(max_length=512, null=True, blank=True, default=None)
space = models.ForeignKey(Space, on_delete=models.CASCADE) space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space') objects = ScopedManager(space='space')
@@ -720,6 +769,64 @@ class Step(ExportModelOperationsMixin('step'), models.Model, PermissionModelMixi
indexes = (GinIndex(fields=["search_vector"]),) indexes = (GinIndex(fields=["search_vector"]),)
class PropertyType(models.Model, PermissionModelMixin):
NUTRITION = 'NUTRITION'
ALLERGEN = 'ALLERGEN'
PRICE = 'PRICE'
GOAL = 'GOAL'
OTHER = 'OTHER'
name = models.CharField(max_length=128)
unit = models.CharField(max_length=64, blank=True, null=True)
icon = models.CharField(max_length=16, blank=True, null=True)
description = models.CharField(max_length=512, blank=True, null=True)
category = models.CharField(max_length=64, choices=((NUTRITION, _('Nutrition')), (ALLERGEN, _('Allergen')), (PRICE, _('Price')), (GOAL, _('Goal')), (OTHER, _('Other'))), null=True, blank=True)
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
# TODO show if empty property?
# TODO formatting property?
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return f'{self.name}'
class Meta:
constraints = [
models.UniqueConstraint(fields=['space', 'name'], name='property_type_unique_name_per_space'),
models.UniqueConstraint(fields=['space', 'open_data_slug'], name='property_type_unique_open_data_slug_per_space')
]
class Property(models.Model, PermissionModelMixin):
property_amount = models.DecimalField(default=0, decimal_places=4, max_digits=32)
property_type = models.ForeignKey(PropertyType, on_delete=models.PROTECT)
import_food_id = models.IntegerField(null=True, blank=True) # field to hold food id when importing properties from the open data project
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
return f'{self.property_amount} {self.property_type.unit} {self.property_type.name}'
class Meta:
constraints = [
models.UniqueConstraint(fields=['space', 'property_type', 'import_food_id'], name='property_unique_import_food_per_space')
]
class FoodProperty(models.Model):
food = models.ForeignKey(Food, on_delete=models.CASCADE)
property = models.ForeignKey(Property, on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(fields=['food', 'property'], name='property_unique_food')
]
class NutritionInformation(models.Model, PermissionModelMixin): class NutritionInformation(models.Model, PermissionModelMixin):
fats = models.DecimalField(default=0, decimal_places=16, max_digits=32) fats = models.DecimalField(default=0, decimal_places=16, max_digits=32)
carbohydrates = models.DecimalField( carbohydrates = models.DecimalField(
@@ -736,14 +843,6 @@ class NutritionInformation(models.Model, PermissionModelMixin):
return f'Nutrition {self.pk}' return f'Nutrition {self.pk}'
# class NutritionType(models.Model, PermissionModelMixin):
# name = models.CharField(max_length=128)
# icon = models.CharField(max_length=16, blank=True, null=True)
# description = models.CharField(max_length=512, blank=True, null=True)
#
# space = models.ForeignKey(Space, on_delete=models.CASCADE)
# objects = ScopedManager(space='space')
class RecipeManager(models.Manager.from_queryset(models.QuerySet)): class RecipeManager(models.Manager.from_queryset(models.QuerySet)):
def get_queryset(self): def get_queryset(self):
return super(RecipeManager, self).get_queryset().annotate(rating=Avg('cooklog__rating')).annotate(last_cooked=Max('cooklog__created_at')) return super(RecipeManager, self).get_queryset().annotate(rating=Avg('cooklog__rating')).annotate(last_cooked=Max('cooklog__created_at'))
@@ -766,6 +865,7 @@ class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModel
waiting_time = models.IntegerField(default=0) waiting_time = models.IntegerField(default=0)
internal = models.BooleanField(default=False) internal = models.BooleanField(default=False)
nutrition = models.ForeignKey(NutritionInformation, blank=True, null=True, on_delete=models.CASCADE) nutrition = models.ForeignKey(NutritionInformation, blank=True, null=True, on_delete=models.CASCADE)
properties = models.ManyToManyField(Property, blank=True)
show_ingredient_overview = models.BooleanField(default=True) show_ingredient_overview = models.BooleanField(default=True)
private = models.BooleanField(default=False) private = models.BooleanField(default=False)
shared = models.ManyToManyField(User, blank=True, related_name='recipe_shared_with') shared = models.ManyToManyField(User, blank=True, related_name='recipe_shared_with')

View File

@@ -7,6 +7,7 @@ from html import escape
from smtplib import SMTPException from smtplib import SMTPException
from django.contrib.auth.models import Group, User, AnonymousUser from django.contrib.auth.models import Group, User, AnonymousUser
from django.core.cache import caches
from django.core.mail import send_mail from django.core.mail import send_mail
from django.db.models import Avg, Q, QuerySet, Sum from django.db.models import Avg, Q, QuerySet, Sum
from django.http import BadHeaderError from django.http import BadHeaderError
@@ -21,15 +22,18 @@ from rest_framework.exceptions import NotFound, ValidationError
from cookbook.helper.CustomStorageClass import CachedS3Boto3Storage from cookbook.helper.CustomStorageClass import CachedS3Boto3Storage
from cookbook.helper.HelperFunctions import str2bool from cookbook.helper.HelperFunctions import str2bool
from cookbook.helper.property_helper import FoodPropertyHelper
from cookbook.helper.permission_helper import above_space_limit from cookbook.helper.permission_helper import above_space_limit
from cookbook.helper.shopping_helper import RecipeShoppingEditor from cookbook.helper.shopping_helper import RecipeShoppingEditor
from cookbook.helper.unit_conversion_helper import UnitConversionHelper
from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, CustomFilter, from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, CustomFilter,
ExportLog, Food, FoodInheritField, ImportLog, Ingredient, InviteLink, ExportLog, Food, FoodInheritField, ImportLog, Ingredient, InviteLink,
Keyword, MealPlan, MealType, NutritionInformation, Recipe, RecipeBook, Keyword, MealPlan, MealType, NutritionInformation, Recipe, RecipeBook,
RecipeBookEntry, RecipeImport, ShareLink, ShoppingList, RecipeBookEntry, RecipeImport, ShareLink, ShoppingList,
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync,
SyncLog, Unit, UserFile, UserPreference, UserSpace, ViewLog) SyncLog, Unit, UserFile, UserPreference, UserSpace, ViewLog, UnitConversion, Property,
PropertyType, Property)
from cookbook.templatetags.custom_tags import markdown from cookbook.templatetags.custom_tags import markdown
from recipes.settings import AWS_ENABLED, MEDIA_URL from recipes.settings import AWS_ENABLED, MEDIA_URL
@@ -102,15 +106,21 @@ class CustomOnHandField(serializers.Field):
return instance return instance
def to_representation(self, obj): def to_representation(self, obj):
shared_users = None if not self.context["request"].user.is_authenticated:
if request := self.context.get('request', None): return []
shared_users = getattr(request, '_shared_users', None) shared_users = []
if shared_users is None: if c := caches['default'].get(f'shopping_shared_users_{self.context["request"].space.id}_{self.context["request"].user.id}', None):
shared_users = c
else:
try: try:
shared_users = [x.id for x in list(self.context['request'].user.get_shopping_share())] + [ shared_users = [x.id for x in list(self.context['request'].user.get_shopping_share())] + [
self.context['request'].user.id] self.context['request'].user.id]
caches['default'].set(
f'shopping_shared_users_{self.context["request"].space.id}_{self.context["request"].user.id}',
shared_users, timeout=5 * 60)
# TODO ugly hack that improves API performance significantly, should be done properly
except AttributeError: # Anonymous users (using share links) don't have shared users except AttributeError: # Anonymous users (using share links) don't have shared users
shared_users = [] pass
return obj.onhand_users.filter(id__in=shared_users).exists() return obj.onhand_users.filter(id__in=shared_users).exists()
def to_internal_value(self, data): def to_internal_value(self, data):
@@ -276,10 +286,13 @@ class SpaceSerializer(WritableNestedModelSerializer):
class Meta: class Meta:
model = Space model = Space
fields = ('id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users', fields = (
'id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users',
'allow_sharing', 'demo', 'food_inherit', 'show_facet_count', 'user_count', 'recipe_count', 'file_size_mb', 'allow_sharing', 'demo', 'food_inherit', 'show_facet_count', 'user_count', 'recipe_count', 'file_size_mb',
'image', 'use_plural',) 'image', 'use_plural',)
read_only_fields = ('id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo',) read_only_fields = (
'id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing',
'demo',)
class UserSpaceSerializer(WritableNestedModelSerializer): class UserSpaceSerializer(WritableNestedModelSerializer):
@@ -440,7 +453,8 @@ class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
return unit return unit
space = validated_data.pop('space', self.context['request'].space) space = validated_data.pop('space', self.context['request'].space)
obj, created = Unit.objects.get_or_create(name=name, plural_name=plural_name, space=space, defaults=validated_data) obj, created = Unit.objects.get_or_create(name=name, plural_name=plural_name, space=space,
defaults=validated_data)
return obj return obj
def update(self, instance, validated_data): def update(self, instance, validated_data):
@@ -451,7 +465,7 @@ class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
class Meta: class Meta:
model = Unit model = Unit
fields = ('id', 'name', 'plural_name', 'description', 'numrecipe', 'image') fields = ('id', 'name', 'plural_name', 'description', 'numrecipe', 'image', 'open_data_slug')
read_only_fields = ('id', 'numrecipe', 'image') read_only_fields = ('id', 'numrecipe', 'image')
@@ -484,7 +498,37 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer):
class Meta: class Meta:
model = Supermarket model = Supermarket
fields = ('id', 'name', 'description', 'category_to_supermarket') fields = ('id', 'name', 'description', 'category_to_supermarket', 'open_data_slug')
class PropertyTypeSerializer(serializers.ModelSerializer):
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
if property_type := PropertyType.objects.filter(Q(name=validated_data['name'])).first():
return property_type
return super().create(validated_data)
class Meta:
model = PropertyType
fields = ('id', 'name', 'icon', 'unit', 'description', 'open_data_slug')
class PropertySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
property_type = PropertyTypeSerializer()
property_amount = CustomDecimalField()
# TODO prevent updates
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
return super().create(validated_data)
class Meta:
model = Property
fields = ('id', 'property_amount', 'property_type')
read_only_fields = ('id',)
class RecipeSimpleSerializer(WritableNestedModelSerializer): class RecipeSimpleSerializer(WritableNestedModelSerializer):
@@ -523,19 +567,29 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
substitute_onhand = serializers.SerializerMethodField('get_substitute_onhand') substitute_onhand = serializers.SerializerMethodField('get_substitute_onhand')
substitute = FoodSimpleSerializer(many=True, allow_null=True, required=False) substitute = FoodSimpleSerializer(many=True, allow_null=True, required=False)
properties = PropertySerializer(many=True, allow_null=True, required=False)
properties_food_unit = UnitSerializer(allow_null=True, required=False)
recipe_filter = 'steps__ingredients__food' recipe_filter = 'steps__ingredients__food'
images = ['recipe__image'] images = ['recipe__image']
def get_substitute_onhand(self, obj): def get_substitute_onhand(self, obj):
shared_users = None if not self.context["request"].user.is_authenticated:
if request := self.context.get('request', None): return []
shared_users = getattr(request, '_shared_users', None) shared_users = []
if shared_users is None: if c := caches['default'].get(
f'shopping_shared_users_{self.context["request"].space.id}_{self.context["request"].user.id}', None):
shared_users = c
else:
try: try:
shared_users = [x.id for x in list(self.context['request'].user.get_shopping_share())] + [ shared_users = [x.id for x in list(self.context['request'].user.get_shopping_share())] + [
self.context['request'].user.id] self.context['request'].user.id]
except AttributeError: caches['default'].set(
shared_users = [] f'shopping_shared_users_{self.context["request"].space.id}_{self.context["request"].user.id}',
shared_users, timeout=5 * 60)
# TODO ugly hack that improves API performance significantly, should be done properly
except AttributeError: # Anonymous users (using share links) don't have shared users
pass
filter = Q(id__in=obj.substitute.all()) filter = Q(id__in=obj.substitute.all())
if obj.substitute_siblings: if obj.substitute_siblings:
filter |= Q(path__startswith=obj.path[:Food.steplen * (obj.depth - 1)], depth=obj.depth) filter |= Q(path__startswith=obj.path[:Food.steplen * (obj.depth - 1)], depth=obj.depth)
@@ -547,7 +601,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
# return ShoppingListEntry.objects.filter(space=obj.space, food=obj, checked=False).count() > 0 # return ShoppingListEntry.objects.filter(space=obj.space, food=obj, checked=False).count() > 0
def create(self, validated_data): def create(self, validated_data):
name = validated_data.pop('name').strip() name = validated_data['name'].strip()
if plural_name := validated_data.pop('plural_name', None): if plural_name := validated_data.pop('plural_name', None):
plural_name = plural_name.strip() plural_name = plural_name.strip()
@@ -579,7 +633,11 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
else: else:
validated_data['onhand_users'] = list(set(onhand_users) - set(shared_users)) validated_data['onhand_users'] = list(set(onhand_users) - set(shared_users))
obj, created = Food.objects.get_or_create(name=name, plural_name=plural_name, space=space, defaults=validated_data) if properties_food_unit := validated_data.pop('properties_food_unit', None):
properties_food_unit = Unit.objects.filter(name=properties_food_unit['name']).first()
obj, created = Food.objects.get_or_create(name=name, plural_name=plural_name, space=space, properties_food_unit=properties_food_unit,
defaults=validated_data)
return obj return obj
def update(self, instance, validated_data): def update(self, instance, validated_data):
@@ -606,9 +664,11 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
class Meta: class Meta:
model = Food model = Food
fields = ( fields = (
'id', 'name', 'plural_name', 'description', 'shopping', 'recipe', 'food_onhand', 'supermarket_category', 'id', 'name', 'plural_name', 'description', 'shopping', 'recipe',
'properties', 'properties_food_amount', 'properties_food_unit',
'food_onhand', 'supermarket_category',
'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name', 'ignore_shopping', 'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name', 'ignore_shopping',
'substitute', 'substitute_siblings', 'substitute_children', 'substitute_onhand', 'child_inherit_fields' 'substitute', 'substitute_siblings', 'substitute_children', 'substitute_onhand', 'child_inherit_fields', 'open_data_slug',
) )
read_only_fields = ('id', 'numchild', 'parent', 'image', 'numrecipe') read_only_fields = ('id', 'numchild', 'parent', 'image', 'numrecipe')
@@ -618,9 +678,24 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer):
unit = UnitSerializer(allow_null=True) unit = UnitSerializer(allow_null=True)
used_in_recipes = serializers.SerializerMethodField('get_used_in_recipes') used_in_recipes = serializers.SerializerMethodField('get_used_in_recipes')
amount = CustomDecimalField() amount = CustomDecimalField()
conversions = serializers.SerializerMethodField('get_conversions')
def get_used_in_recipes(self, obj): def get_used_in_recipes(self, obj):
return list(Recipe.objects.filter(steps__ingredients=obj.id).values('id', 'name')) used_in = []
for s in obj.step_set.all():
for r in s.recipe_set.all():
used_in.append({'id': r.id, 'name': r.name})
return used_in
def get_conversions(self, obj):
if obj.unit and obj.food:
uch = UnitConversionHelper(self.context['request'].space)
conversions = []
for c in uch.get_conversions(obj):
conversions.append({'food': c.food.name, 'unit': c.unit.name, 'amount': c.amount}) # TODO do formatting in helper
return conversions
else:
return []
def create(self, validated_data): def create(self, validated_data):
validated_data['space'] = self.context['request'].space validated_data['space'] = self.context['request'].space
@@ -633,10 +708,11 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer):
class Meta: class Meta:
model = Ingredient model = Ingredient
fields = ( fields = (
'id', 'food', 'unit', 'amount', 'note', 'order', 'id', 'food', 'unit', 'amount', 'conversions', 'note', 'order',
'is_header', 'no_amount', 'original_text', 'used_in_recipes', 'is_header', 'no_amount', 'original_text', 'used_in_recipes',
'always_use_plural_unit', 'always_use_plural_food', 'always_use_plural_unit', 'always_use_plural_food',
) )
read_only_fields = ['conversions', ]
class IngredientSerializer(IngredientSimpleSerializer): class IngredientSerializer(IngredientSimpleSerializer):
@@ -688,6 +764,30 @@ class StepRecipeSerializer(WritableNestedModelSerializer):
) )
class UnitConversionSerializer(WritableNestedModelSerializer):
name = serializers.SerializerMethodField('get_conversion_name')
base_unit = UnitSerializer()
converted_unit = UnitSerializer()
food = FoodSerializer(allow_null=True, required=False)
base_amount = CustomDecimalField()
converted_amount = CustomDecimalField()
def get_conversion_name(self, obj):
text = f'{round(obj.base_amount)} {obj.base_unit} '
if obj.food:
text += f' {obj.food}'
return text + f' = {round(obj.converted_amount)} {obj.converted_unit}'
def create(self, validated_data):
validated_data['space'] = self.context['request'].space
validated_data['created_by'] = self.context['request'].user
return super().create(validated_data)
class Meta:
model = UnitConversion
fields = ('id', 'name', 'base_amount', 'base_unit', 'converted_amount', 'converted_unit', 'food', 'open_data_slug')
class NutritionInformationSerializer(serializers.ModelSerializer): class NutritionInformationSerializer(serializers.ModelSerializer):
carbohydrates = CustomDecimalField() carbohydrates = CustomDecimalField()
fats = CustomDecimalField() fats = CustomDecimalField()
@@ -738,21 +838,28 @@ class RecipeOverviewSerializer(RecipeBaseSerializer):
class RecipeSerializer(RecipeBaseSerializer): class RecipeSerializer(RecipeBaseSerializer):
nutrition = NutritionInformationSerializer(allow_null=True, required=False) nutrition = NutritionInformationSerializer(allow_null=True, required=False)
properties = PropertySerializer(many=True, required=False)
steps = StepSerializer(many=True) steps = StepSerializer(many=True)
keywords = KeywordSerializer(many=True) keywords = KeywordSerializer(many=True)
shared = UserSerializer(many=True, required=False) shared = UserSerializer(many=True, required=False)
rating = CustomDecimalField(required=False, allow_null=True, read_only=True) rating = CustomDecimalField(required=False, allow_null=True, read_only=True)
last_cooked = serializers.DateTimeField(required=False, allow_null=True, read_only=True) last_cooked = serializers.DateTimeField(required=False, allow_null=True, read_only=True)
food_properties = serializers.SerializerMethodField('get_food_properties')
def get_food_properties(self, obj):
fph = FoodPropertyHelper(obj.space) # initialize with object space since recipes might be viewed anonymously
return fph.calculate_recipe_properties(obj)
class Meta: class Meta:
model = Recipe model = Recipe
fields = ( fields = (
'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time', 'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time',
'waiting_time', 'created_by', 'created_at', 'updated_at', 'source_url', 'waiting_time', 'created_by', 'created_at', 'updated_at', 'source_url',
'internal', 'show_ingredient_overview', 'nutrition', 'servings', 'file_path', 'servings_text', 'rating', 'last_cooked', 'internal', 'show_ingredient_overview', 'nutrition', 'properties', 'food_properties', 'servings', 'file_path', 'servings_text', 'rating',
'last_cooked',
'private', 'shared', 'private', 'shared',
) )
read_only_fields = ['image', 'created_by', 'created_at'] read_only_fields = ['image', 'created_by', 'created_at', 'food_properties']
def validate(self, data): def validate(self, data):
above_limit, msg = above_space_limit(self.context['request'].space) above_limit, msg = above_space_limit(self.context['request'].space)
@@ -1089,13 +1196,19 @@ class InviteLinkSerializer(WritableNestedModelSerializer):
if obj.email: if obj.email:
try: try:
if InviteLink.objects.filter(space=self.context['request'].space, created_at__gte=datetime.now() - timedelta(hours=4)).count() < 20: if InviteLink.objects.filter(space=self.context['request'].space,
message = _('Hello') + '!\n\n' + _('You have been invited by ') + escape(self.context['request'].user.get_user_display_name()) created_at__gte=datetime.now() - timedelta(hours=4)).count() < 20:
message += _(' to join their Tandoor Recipes space ') + escape(self.context['request'].space.name) + '.\n\n' message = _('Hello') + '!\n\n' + _('You have been invited by ') + escape(
message += _('Click the following link to activate your account: ') + self.context['request'].build_absolute_uri(reverse('view_invite', args=[str(obj.uuid)])) + '\n\n' self.context['request'].user.get_user_display_name())
message += _('If the link does not work use the following code to manually join the space: ') + str(obj.uuid) + '\n\n' message += _(' to join their Tandoor Recipes space ') + escape(
self.context['request'].space.name) + '.\n\n'
message += _('Click the following link to activate your account: ') + self.context[
'request'].build_absolute_uri(reverse('view_invite', args=[str(obj.uuid)])) + '\n\n'
message += _('If the link does not work use the following code to manually join the space: ') + str(
obj.uuid) + '\n\n'
message += _('The invitation is valid until ') + str(obj.valid_until) + '\n\n' message += _('The invitation is valid until ') + str(obj.valid_until) + '\n\n'
message += _('Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub ') + 'https://github.com/vabene1111/recipes/' message += _(
'Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub ') + 'https://github.com/vabene1111/recipes/'
send_mail( send_mail(
_('Tandoor Recipes Invite'), _('Tandoor Recipes Invite'),
@@ -1204,7 +1317,8 @@ class IngredientExportSerializer(WritableNestedModelSerializer):
class Meta: class Meta:
model = Ingredient model = Ingredient
fields = ('food', 'unit', 'amount', 'note', 'order', 'is_header', 'no_amount', 'always_use_plural_unit', 'always_use_plural_food') fields = ('food', 'unit', 'amount', 'note', 'order', 'is_header', 'no_amount', 'always_use_plural_unit',
'always_use_plural_food')
class StepExportSerializer(WritableNestedModelSerializer): class StepExportSerializer(WritableNestedModelSerializer):

View File

@@ -4,15 +4,17 @@ from functools import wraps
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.postgres.search import SearchVector from django.contrib.postgres.search import SearchVector
from django.core.cache import caches
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import translation from django.utils import translation
from django_scopes import scope, scopes_disabled from django_scopes import scope, scopes_disabled
from cookbook.helper.cache_helper import CacheHelper
from cookbook.helper.shopping_helper import RecipeShoppingEditor from cookbook.helper.shopping_helper import RecipeShoppingEditor
from cookbook.managers import DICTIONARY from cookbook.managers import DICTIONARY
from cookbook.models import (Food, FoodInheritField, Ingredient, MealPlan, Recipe, from cookbook.models import (Food, FoodInheritField, Ingredient, MealPlan, Recipe,
ShoppingListEntry, Step, UserPreference, SearchPreference, SearchFields) ShoppingListEntry, Step, UserPreference, SearchPreference, SearchFields, Unit, PropertyType)
SQLITE = True SQLITE = True
if settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2', if settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2',
@@ -149,3 +151,15 @@ def auto_add_shopping(sender, instance=None, created=False, weak=False, **kwargs
print("MEAL_AUTO_ADD Created SLR") print("MEAL_AUTO_ADD Created SLR")
except AttributeError: except AttributeError:
pass pass
@receiver(post_save, sender=Unit)
def clear_unit_cache(sender, instance=None, created=False, **kwargs):
if instance:
caches['default'].delete(CacheHelper(instance.space).BASE_UNITS_CACHE_KEY)
@receiver(post_save, sender=PropertyType)
def clear_property_type_cache(sender, instance=None, created=False, **kwargs):
if instance:
caches['default'].delete(CacheHelper(instance.space).PROPERTY_TYPE_CACHE_KEY)

View File

@@ -9,6 +9,7 @@
{% endblock %}</title> {% endblock %}</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="noindex,nofollow"/>
<link rel="shortcut icon" type="image/x-icon" href="{% static 'assets/favicon.svg' %}"> <link rel="shortcut icon" type="image/x-icon" href="{% static 'assets/favicon.svg' %}">
@@ -117,6 +118,10 @@
<a class="nav-link" href="{% url 'view_books' %}"><i <a class="nav-link" href="{% url 'view_books' %}"><i
class="fas fa-fw fa-book-open"></i> {% trans 'Books' %}</a> class="fas fa-fw fa-book-open"></i> {% trans 'Books' %}</a>
</li> </li>
{% plugin_main_nav_templates as plugin_main_nav_templates %}
{% for pn in plugin_main_nav_templates %}
{% include pn %}
{% endfor %}
</ul> </ul>
<ul class="navbar-nav ml-auto"> <ul class="navbar-nav ml-auto">
@@ -269,6 +274,33 @@
</div> </div>
</a> </a>
</div> </div>
<div class="col-4">
<a href="{% url 'list_property_type' %}" class="p-0 p-md-1">
<div class="card p-0 no-gutters border-0">
<div class="card-body text-center p-0 no-gutters">
<i class="fas fa-database fa-2x"></i>
</div>
<div class="card-body text-break text-center p-0 no-gutters text-muted menu-dropdown-text">
{% trans 'Properties' %}
</div>
</div>
</a>
</div>
</div>
<div class="row m-0 mt-2 mt-md-0">
<div class="col-4">
<a href="{% url 'list_unit_conversion' %}" class="p-0 p-md-1">
<div class="card p-0 no-gutters border-0">
<div class="card-body text-center p-0 no-gutters">
<i class="fas fa-exchange-alt fa-2x"></i>
</div>
<div class="card-body text-break text-center p-0 no-gutters text-muted menu-dropdown-text">
{% trans 'Unit Conversions' %}
</div>
</div>
</a>
</div>
</div> </div>
</div> </div>
</li> </li>
@@ -322,6 +354,12 @@
<a class="dropdown-item" href="{% url 'view_space_overview' %}"><i <a class="dropdown-item" href="{% url 'view_space_overview' %}"><i
class="fas fa-list"></i> {% trans 'Overview' %}</a> class="fas fa-list"></i> {% trans 'Overview' %}</a>
{% endif %} {% endif %}
{% plugin_dropdown_nav_templates as plugin_dropdown_nav_templates %}
{% for pn in plugin_dropdown_nav_templates %}
<div class="dropdown-divider"></div>
{% include pn %}
{% endfor %}
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item" href="{% url 'docs_markdown' %}"><i <a class="dropdown-item" href="{% url 'docs_markdown' %}"><i
class="fab fa-markdown fa-fw"></i> {% trans 'Markdown Guide' %}</a> class="fab fa-markdown fa-fw"></i> {% trans 'Markdown Guide' %}</a>
@@ -348,6 +386,7 @@
</div> </div>
</nav> </nav>
{% message_of_the_day request as message_of_the_day %} {% message_of_the_day request as message_of_the_day %}
{% if message_of_the_day %} {% if message_of_the_day %}
<div class="bg-info" style=" width: 100%; text-align: center!important; color: #ffffff; padding: 8px"> <div class="bg-info" style=" width: 100%; text-align: center!important; color: #ffffff; padding: 8px">

View File

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

View File

@@ -2,6 +2,7 @@ import json
import pytest import pytest
from django.contrib import auth from django.contrib import auth
from django.core.cache import caches
from django.urls import reverse from django.urls import reverse
from django_scopes import scope, scopes_disabled from django_scopes import scope, scopes_disabled
from pytest_factoryboy import LazyFixture, register from pytest_factoryboy import LazyFixture, register
@@ -28,7 +29,6 @@ if (Food.node_order_by):
else: else:
node_location = 'last-child' node_location = 'last-child'
register(FoodFactory, 'obj_1', space=LazyFixture('space_1')) register(FoodFactory, 'obj_1', space=LazyFixture('space_1'))
register(FoodFactory, 'obj_2', space=LazyFixture('space_1')) register(FoodFactory, 'obj_2', space=LazyFixture('space_1'))
register(FoodFactory, 'obj_3', space=LazyFixture('space_2')) register(FoodFactory, 'obj_3', space=LazyFixture('space_2'))
@@ -554,6 +554,7 @@ def test_inherit(request, obj_tree_1, field, inherit, new_val, u1_s1):
assert (getattr(obj_tree_1, field) == new_val) == inherit assert (getattr(obj_tree_1, field) == new_val) == inherit
assert (getattr(child, field) == new_val) == inherit assert (getattr(child, field) == new_val) == inherit
# TODO add test_inherit with child_inherit # TODO add test_inherit with child_inherit
@@ -613,11 +614,9 @@ def test_reset_inherit_no_food_instances(obj_tree_1, space_1, field):
parent.reset_inheritance(space=space_1) parent.reset_inheritance(space=space_1)
def test_onhand(obj_1, u1_s1, u2_s1): def test_onhand(obj_1, u1_s1, u2_s1, space_1):
assert json.loads(u1_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)[ assert json.loads(u1_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)['food_onhand'] is False
'food_onhand'] == False assert json.loads(u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)['food_onhand'] is False
assert json.loads(u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)[
'food_onhand'] == False
u1_s1.patch( u1_s1.patch(
reverse( reverse(
@@ -627,13 +626,12 @@ def test_onhand(obj_1, u1_s1, u2_s1):
{'food_onhand': True}, {'food_onhand': True},
content_type='application/json' content_type='application/json'
) )
assert json.loads(u1_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)[ assert json.loads(u1_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)['food_onhand'] is True
'food_onhand'] == True assert json.loads(u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)['food_onhand'] is False
assert json.loads(u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)[
'food_onhand'] == False
user1 = auth.get_user(u1_s1) user1 = auth.get_user(u1_s1)
user2 = auth.get_user(u2_s1) user2 = auth.get_user(u2_s1)
user1.userpreference.shopping_share.add(user2) user1.userpreference.shopping_share.add(user2)
assert json.loads(u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)[ caches['default'].set(f'shopping_shared_users_{space_1.id}_{user2.id}', None)
'food_onhand'] == True
assert json.loads(u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)['food_onhand'] is True

View File

@@ -0,0 +1,116 @@
import json
import pytest
from django.contrib import auth
from django.urls import reverse
from django_scopes import scopes_disabled
from cookbook.models import Food, MealType, PropertyType, Property
LIST_URL = 'api:property-list'
DETAIL_URL = 'api:property-detail'
@pytest.fixture()
def obj_1(space_1, u1_s1):
pt = PropertyType.objects.get_or_create(name='test_1', space=space_1)[0]
return Property.objects.get_or_create(property_amount=100, property_type=pt, space=space_1)[0]
@pytest.fixture
def obj_2(space_1, u1_s1):
pt = PropertyType.objects.get_or_create(name='test_2', space=space_1)[0]
return Property.objects.get_or_create(property_amount=100, property_type=pt, space=space_1)[0]
@pytest.mark.parametrize("arg", [
['a_u', 403],
['g1_s1', 403],
['u1_s1', 200],
['a1_s1', 200],
])
def test_list_permission(arg, request):
c = request.getfixturevalue(arg[0])
assert c.get(reverse(LIST_URL)).status_code == arg[1]
def test_list_space(obj_1, obj_2, u1_s1, u1_s2, space_2):
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 2
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0
obj_1.space = space_2
obj_1.save()
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 1
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 1
@pytest.mark.parametrize("arg", [
['a_u', 403],
['g1_s1', 403],
['u1_s1', 200],
['a1_s1', 200],
['g1_s2', 403],
['u1_s2', 404],
['a1_s2', 404],
])
def test_update(arg, request, obj_1):
c = request.getfixturevalue(arg[0])
r = c.patch(
reverse(
DETAIL_URL,
args={obj_1.id}
),
{'property_amount': 200},
content_type='application/json'
)
response = json.loads(r.content)
assert r.status_code == arg[1]
if r.status_code == 200:
assert response['property_amount'] == 200
@pytest.mark.parametrize("arg", [
['a_u', 403],
['g1_s1', 403],
['u1_s1', 201],
['a1_s1', 201],
])
def test_add(arg, request, u1_s2, space_1):
with scopes_disabled():
pt = PropertyType.objects.get_or_create(name='test_1', space=space_1)[0]
c = request.getfixturevalue(arg[0])
r = c.post(
reverse(LIST_URL),
{'property_amount': 100, 'property_type': {'id': pt.id, 'name': pt.name}},
content_type='application/json'
)
response = json.loads(r.content)
assert r.status_code == arg[1]
if r.status_code == 201:
assert response['property_amount'] == 100
r = c.get(reverse(DETAIL_URL, args={response['id']}))
assert r.status_code == 200
r = u1_s2.get(reverse(DETAIL_URL, args={response['id']}))
assert r.status_code == 404
def test_delete(u1_s1, u1_s2, obj_1):
r = u1_s2.delete(
reverse(
DETAIL_URL,
args={obj_1.id}
)
)
assert r.status_code == 404
r = u1_s1.delete(
reverse(
DETAIL_URL,
args={obj_1.id}
)
)
assert r.status_code == 204
with scopes_disabled():
assert MealType.objects.count() == 0

View File

@@ -0,0 +1,132 @@
import json
import pytest
from django.contrib import auth
from django.urls import reverse
from django_scopes import scopes_disabled
from cookbook.models import Food, MealType, PropertyType
LIST_URL = 'api:propertytype-list'
DETAIL_URL = 'api:propertytype-detail'
@pytest.fixture()
def obj_1(space_1, u1_s1):
return PropertyType.objects.get_or_create(name='test_1', space=space_1)[0]
@pytest.fixture
def obj_2(space_1, u1_s1):
return PropertyType.objects.get_or_create(name='test_2', space=space_1)[0]
@pytest.mark.parametrize("arg", [
['a_u', 403],
['g1_s1', 403],
['u1_s1', 200],
['a1_s1', 200],
])
def test_list_permission(arg, request):
c = request.getfixturevalue(arg[0])
assert c.get(reverse(LIST_URL)).status_code == arg[1]
def test_list_space(obj_1, obj_2, u1_s1, u1_s2, space_2):
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 2
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0
obj_1.space = space_2
obj_1.save()
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 1
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 1
@pytest.mark.parametrize("arg", [
['a_u', 403],
['g1_s1', 403],
['u1_s1', 200],
['a1_s1', 200],
['g1_s2', 403],
['u1_s2', 404],
['a1_s2', 404],
])
def test_update(arg, request, obj_1):
c = request.getfixturevalue(arg[0])
r = c.patch(
reverse(
DETAIL_URL,
args={obj_1.id}
),
{'name': 'new'},
content_type='application/json'
)
response = json.loads(r.content)
assert r.status_code == arg[1]
if r.status_code == 200:
assert response['name'] == 'new'
@pytest.mark.parametrize("arg", [
['a_u', 403],
['g1_s1', 403],
['u1_s1', 201],
['a1_s1', 201],
])
def test_add(arg, request, u1_s2):
c = request.getfixturevalue(arg[0])
r = c.post(
reverse(LIST_URL),
{'name': 'test'},
content_type='application/json'
)
response = json.loads(r.content)
assert r.status_code == arg[1]
if r.status_code == 201:
assert response['name'] == 'test'
r = c.get(reverse(DETAIL_URL, args={response['id']}))
assert r.status_code == 200
r = u1_s2.get(reverse(DETAIL_URL, args={response['id']}))
assert r.status_code == 404
def test_add_duplicate(u1_s1, u1_s2, obj_1):
r = u1_s1.post(
reverse(LIST_URL),
{'name': obj_1.name},
content_type='application/json'
)
response = json.loads(r.content)
assert r.status_code == 201
assert response['id'] == obj_1.id
r = u1_s2.post(
reverse(LIST_URL),
{'name': obj_1.name},
content_type='application/json'
)
response = json.loads(r.content)
assert r.status_code == 201
assert response['id'] != obj_1.id
def test_delete(u1_s1, u1_s2, obj_1):
r = u1_s2.delete(
reverse(
DETAIL_URL,
args={obj_1.id}
)
)
assert r.status_code == 404
r = u1_s1.delete(
reverse(
DETAIL_URL,
args={obj_1.id}
)
)
assert r.status_code == 204
with scopes_disabled():
assert MealType.objects.count() == 0

View File

@@ -0,0 +1,163 @@
import json
import pytest
from django.contrib import auth
from django.urls import reverse
from django_scopes import scopes_disabled
from cookbook.models import Food, MealType, UnitConversion
from cookbook.tests.conftest import get_random_food, get_random_unit
LIST_URL = 'api:unitconversion-list'
DETAIL_URL = 'api:unitconversion-detail'
@pytest.fixture()
def obj_1(space_1, u1_s1):
return UnitConversion.objects.get_or_create(
food=get_random_food(space_1, u1_s1),
base_amount=100,
base_unit=get_random_unit(space_1, u1_s1),
converted_amount=100,
converted_unit=get_random_unit(space_1, u1_s1),
created_by=auth.get_user(u1_s1),
space=space_1
)[0]
@pytest.fixture
def obj_2(space_1, u1_s1):
return UnitConversion.objects.get_or_create(
food=get_random_food(space_1, u1_s1),
base_amount=100,
base_unit=get_random_unit(space_1, u1_s1),
converted_amount=100,
converted_unit=get_random_unit(space_1, u1_s1),
created_by=auth.get_user(u1_s1),
space=space_1
)[0]
@pytest.mark.parametrize("arg", [
['a_u', 403],
['g1_s1', 403],
['u1_s1', 200],
['a1_s1', 200],
])
def test_list_permission(arg, request):
c = request.getfixturevalue(arg[0])
assert c.get(reverse(LIST_URL)).status_code == arg[1]
def test_list_space(obj_1, obj_2, u1_s1, u1_s2, space_2):
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 2
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0
obj_1.space = space_2
obj_1.save()
assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 1
assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 1
@pytest.mark.parametrize("arg", [
['a_u', 403],
['g1_s1', 403],
['u1_s1', 200],
['a1_s1', 200],
['g1_s2', 403],
['u1_s2', 404],
['a1_s2', 404],
])
def test_update(arg, request, obj_1):
c = request.getfixturevalue(arg[0])
r = c.patch(
reverse(
DETAIL_URL,
args={obj_1.id}
),
{'base_amount': 1000},
content_type='application/json'
)
response = json.loads(r.content)
assert r.status_code == arg[1]
if r.status_code == 200:
assert response['base_amount'] == 1000
@pytest.mark.parametrize("arg", [
['a_u', 403],
['g1_s1', 403],
['u1_s1', 201],
['a1_s1', 201],
])
def test_add(arg, request, u1_s2, space_1, u1_s1):
with scopes_disabled():
c = request.getfixturevalue(arg[0])
random_unit_1 = get_random_unit(space_1, u1_s1)
random_unit_2 = get_random_unit(space_1, u1_s1)
random_food_1 = get_random_unit(space_1, u1_s1)
r = c.post(
reverse(LIST_URL),
{
'food': {'id': random_food_1.id, 'name': random_food_1.name},
'base_amount': 100,
'base_unit': {'id': random_unit_1.id, 'name': random_unit_1.name},
'converted_amount': 100,
'converted_unit': {'id': random_unit_2.id, 'name': random_unit_2.name}
},
content_type='application/json'
)
response = json.loads(r.content)
print(response)
assert r.status_code == arg[1]
if r.status_code == 201:
assert response['base_amount'] == 100
r = c.get(reverse(DETAIL_URL, args={response['id']}))
assert r.status_code == 200
r = u1_s2.get(reverse(DETAIL_URL, args={response['id']}))
assert r.status_code == 404
# TODO make name in space unique
# def test_add_duplicate(u1_s1, u1_s2, obj_1):
# r = u1_s1.post(
# reverse(LIST_URL),
# {'name': obj_1.name},
# content_type='application/json'
# )
# response = json.loads(r.content)
# assert r.status_code == 201
# assert response['id'] == obj_1.id
#
# r = u1_s2.post(
# reverse(LIST_URL),
# {'name': obj_1.name},
# content_type='application/json'
# )
# response = json.loads(r.content)
# assert r.status_code == 201
# assert response['id'] != obj_1.id
def test_delete(u1_s1, u1_s2, obj_1):
r = u1_s2.delete(
reverse(
DETAIL_URL,
args={obj_1.id}
)
)
assert r.status_code == 404
r = u1_s1.delete(
reverse(
DETAIL_URL,
args={obj_1.id}
)
)
assert r.status_code == 204
with scopes_disabled():
assert MealType.objects.count() == 0

View File

@@ -13,6 +13,8 @@ from cookbook.tests.factories import SpaceFactory, UserFactory
register(SpaceFactory, 'space_1') register(SpaceFactory, 'space_1')
register(SpaceFactory, 'space_2') register(SpaceFactory, 'space_2')
# register(FoodFactory, space=LazyFixture('space_2')) # register(FoodFactory, space=LazyFixture('space_2'))
# TODO refactor clients to be factories # TODO refactor clients to be factories
@@ -169,7 +171,6 @@ def dict_compare(d1, d2, details=False):
def transpose(text, number=2): def transpose(text, number=2):
# select random token # select random token
tokens = text.split() tokens = text.split()
positions = list(i for i, e in enumerate(tokens) if len(e) > 1) positions = list(i for i, e in enumerate(tokens) if len(e) > 1)
@@ -212,6 +213,14 @@ def ext_recipe_1_s1(space_1, u1_s1):
return r return r
def get_random_food(space_1, u1_s1):
return Food.objects.get_or_create(name=str(uuid.uuid4()), space=space_1)[0]
def get_random_unit(space_1, u1_s1):
return Unit.objects.get_or_create(name=str(uuid.uuid4()), space=space_1)[0]
# ---------------------- USER FIXTURES ----------------------- # ---------------------- USER FIXTURES -----------------------
# maybe better with factories but this is very explict so ... # maybe better with factories but this is very explict so ...

View File

@@ -0,0 +1,129 @@
from django.contrib import auth
from django.core.cache import caches
from django_scopes import scopes_disabled
from decimal import Decimal
from cookbook.helper.cache_helper import CacheHelper
from cookbook.helper.property_helper import FoodPropertyHelper
from cookbook.models import Unit, Food, PropertyType, Property, Recipe, Step, UnitConversion, Property
def test_food_property(space_1, space_2, u1_s1):
with scopes_disabled():
unit_gram = Unit.objects.create(name='gram', base_unit='g', space=space_1)
unit_kg = Unit.objects.create(name='kg', base_unit='kg', space=space_1)
unit_pcs = Unit.objects.create(name='pcs', base_unit='', space=space_1)
unit_floz1 = Unit.objects.create(name='fl. oz 1', base_unit='imperial_fluid_ounce', space=space_1) # US and UK use different volume systems (US vs imperial)
unit_floz2 = Unit.objects.create(name='fl. oz 2', base_unit='fluid_ounce', space=space_1)
unit_fantasy = Unit.objects.create(name='Fantasy Unit', base_unit='', space=space_1)
food_1 = Food.objects.create(name='food_1', space=space_1, properties_food_unit=unit_gram, properties_food_amount=100)
food_2 = Food.objects.create(name='food_2', space=space_1, properties_food_unit=unit_gram, properties_food_amount=100)
property_fat = PropertyType.objects.create(name='property_fat', space=space_1)
property_calories = PropertyType.objects.create(name='property_calories', space=space_1)
property_nuts = PropertyType.objects.create(name='property_nuts', space=space_1)
property_price = PropertyType.objects.create(name='property_price', space=space_1)
food_1_property_fat = Property.objects.create(property_amount=50, property_type=property_fat, space=space_1)
food_1_property_nuts = Property.objects.create(property_amount=1, property_type=property_nuts, space=space_1)
food_1_property_price = Property.objects.create(property_amount=7.50, property_type=property_price, space=space_1)
food_1.properties.add(food_1_property_fat, food_1_property_nuts, food_1_property_price)
food_2_property_fat = Property.objects.create(property_amount=25, property_type=property_fat, space=space_1)
food_2_property_nuts = Property.objects.create(property_amount=0, property_type=property_nuts, space=space_1)
food_2_property_price = Property.objects.create(property_amount=2.50, property_type=property_price, space=space_1)
food_2.properties.add(food_2_property_fat, food_2_property_nuts, food_2_property_price)
print('\n----------- TEST PROPERTY - PROPERTY CALCULATION MULTI STEP IDENTICAL UNIT ---------------')
recipe_1 = Recipe.objects.create(name='recipe_1', waiting_time=0, working_time=0, space=space_1, created_by=auth.get_user(u1_s1))
step_1 = Step.objects.create(instruction='instruction_step_1', space=space_1)
step_1.ingredients.create(amount=500, unit=unit_gram, food=food_1, space=space_1)
step_1.ingredients.create(amount=1000, unit=unit_gram, food=food_2, space=space_1)
recipe_1.steps.add(step_1)
step_2 = Step.objects.create(instruction='instruction_step_1', space=space_1)
step_2.ingredients.create(amount=50, unit=unit_gram, food=food_1, space=space_1)
recipe_1.steps.add(step_2)
property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_1)
assert property_values[property_fat.id]['name'] == property_fat.name
assert abs(property_values[property_fat.id]['total_value'] - Decimal(525)) < 0.0001
assert abs(property_values[property_fat.id]['food_values'][food_1.id]['value'] - Decimal(275)) < 0.0001
assert abs(property_values[property_fat.id]['food_values'][food_2.id]['value'] - Decimal(250)) < 0.0001
print('\n----------- TEST PROPERTY - PROPERTY CALCULATION NO POSSIBLE CONVERSION ---------------')
recipe_2 = Recipe.objects.create(name='recipe_2', waiting_time=0, working_time=0, space=space_1, created_by=auth.get_user(u1_s1))
step_1 = Step.objects.create(instruction='instruction_step_1', space=space_1)
step_1.ingredients.create(amount=5, unit=unit_pcs, food=food_1, space=space_1)
step_1.ingredients.create(amount=10, unit=unit_pcs, food=food_2, space=space_1)
recipe_2.steps.add(step_1)
property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_2)
assert property_values[property_fat.id]['name'] == property_fat.name
assert abs(property_values[property_fat.id]['total_value']) < 0.0001
assert abs(property_values[property_fat.id]['food_values'][food_1.id]['value']) < 0.0001
print('\n----------- TEST PROPERTY - PROPERTY CALCULATION UNIT CONVERSION ---------------')
uc1 = UnitConversion.objects.create(
base_amount=100,
base_unit=unit_gram,
converted_amount=1,
converted_unit=unit_pcs,
space=space_1,
created_by=auth.get_user(u1_s1),
)
property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_2)
assert property_values[property_fat.id]['name'] == property_fat.name
assert abs(property_values[property_fat.id]['total_value'] - Decimal(500)) < 0.0001
assert abs(property_values[property_fat.id]['food_values'][food_1.id]['value'] - Decimal(250)) < 0.0001
assert abs(property_values[property_fat.id]['food_values'][food_2.id]['value'] - Decimal(250)) < 0.0001
print('\n----------- TEST PROPERTY - PROPERTY CALCULATION UNIT CONVERSION MULTIPLE ---------------')
uc1.delete()
uc1 = UnitConversion.objects.create(
base_amount=0.1,
base_unit=unit_kg,
converted_amount=1,
converted_unit=unit_pcs,
space=space_1,
created_by=auth.get_user(u1_s1),
)
property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_2)
assert property_values[property_fat.id]['name'] == property_fat.name
assert abs(property_values[property_fat.id]['total_value'] - Decimal(500)) < 0.0001
assert abs(property_values[property_fat.id]['food_values'][food_1.id]['value'] - Decimal(250)) < 0.0001
assert abs(property_values[property_fat.id]['food_values'][food_2.id]['value'] - Decimal(250)) < 0.0001
print('\n----------- TEST PROPERTY - MISSING FOOD REFERENCE AMOUNT ---------------')
food_1.properties_food_unit = None
food_1.save()
food_2.properties_food_amount = 0
food_2.save()
property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_1)
assert property_values[property_fat.id]['name'] == property_fat.name
assert property_values[property_fat.id]['total_value'] == 0
print('\n----------- TEST PROPERTY - SPACE SEPARATION ---------------')
property_fat.space = space_2
property_fat.save()
caches['default'].delete(CacheHelper(space_1).PROPERTY_TYPE_CACHE_KEY) # clear cache as objects won't change space in reality
property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_2)
assert property_fat.id not in property_values

View File

@@ -0,0 +1,187 @@
from _decimal import Decimal
from django.contrib import auth
from django_scopes import scopes_disabled
from cookbook.helper.unit_conversion_helper import UnitConversionHelper, ConversionException
from cookbook.models import Unit, Food, Ingredient, UnitConversion
def test_base_converter(space_1):
uch = UnitConversionHelper(space_1)
assert abs(uch.convert_from_to('g', 'kg', 1234) - Decimal(1.234)) < 0.0001
assert abs(uch.convert_from_to('kg', 'pound', 2) - Decimal(4.40924)) < 0.00001
assert abs(uch.convert_from_to('kg', 'g', 1) - Decimal(1000)) < 0.00001
assert abs(uch.convert_from_to('imperial_gallon', 'gallon', 1000) - Decimal(1200.95104)) < 0.00001
assert abs(uch.convert_from_to('tbsp', 'ml', 20) - Decimal(295.73549)) < 0.00001
try:
assert uch.convert_from_to('kg', 'tbsp', 2) == 1234
assert False
except ConversionException:
assert True
try:
assert uch.convert_from_to('kg', 'g2', 2) == 1234
assert False
except ConversionException:
assert True
def test_unit_conversions(space_1, space_2, u1_s1):
with scopes_disabled():
uch = UnitConversionHelper(space_1)
uch_space_2 = UnitConversionHelper(space_2)
unit_gram = Unit.objects.create(name='gram', base_unit='g', space=space_1)
unit_kg = Unit.objects.create(name='kg', base_unit='kg', space=space_1)
unit_pcs = Unit.objects.create(name='pcs', base_unit='', space=space_1)
unit_floz1 = Unit.objects.create(name='fl. oz 1', base_unit='imperial_fluid_ounce', space=space_1) # US and UK use different volume systems (US vs imperial)
unit_floz2 = Unit.objects.create(name='fl. oz 2', base_unit='fluid_ounce', space=space_1)
unit_fantasy = Unit.objects.create(name='Fantasy Unit', base_unit='', space=space_1)
food_1 = Food.objects.create(name='Test Food 1', space=space_1)
food_2 = Food.objects.create(name='Test Food 2', space=space_1)
print('\n----------- TEST BASE CONVERSIONS - GRAM ---------------')
ingredient_food_1_gram = Ingredient.objects.create(
food=food_1,
unit=unit_gram,
amount=100,
space=space_1,
)
conversions = uch.get_conversions(ingredient_food_1_gram)
print(conversions)
assert len(conversions) == 2
assert next(x for x in conversions if x.unit == unit_kg) is not None
assert abs(next(x for x in conversions if x.unit == unit_kg).amount - Decimal(0.1)) < 0.0001
print('\n----------- TEST BASE CONVERSIONS - VOLUMES ---------------')
ingredient_food_1_floz1 = Ingredient.objects.create(
food=food_1,
unit=unit_floz1,
amount=100,
space=space_1,
)
conversions = uch.get_conversions(ingredient_food_1_floz1)
assert len(conversions) == 2
assert next(x for x in conversions if x.unit == unit_floz2) is not None
assert abs(next(x for x in conversions if x.unit == unit_floz2).amount - Decimal(96.07599404038842)) < 0.001 # TODO validate value
print(conversions)
unit_pint = Unit.objects.create(name='pint', base_unit='pint', space=space_1)
conversions = uch.get_conversions(ingredient_food_1_floz1)
assert len(conversions) == 3
assert next(x for x in conversions if x.unit == unit_pint) is not None
assert abs(next(x for x in conversions if x.unit == unit_pint).amount - Decimal(6.004749627524276)) < 0.001 # TODO validate value
print(conversions)
print('\n----------- TEST BASE CUSTOM CONVERSION - TO CUSTOM CONVERSION ---------------')
UnitConversion.objects.create(
base_amount=1000,
base_unit=unit_gram,
converted_amount=1337,
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) == 3
assert next(x for x in conversions if x.unit == unit_fantasy) is not None
assert abs(next(x for x in conversions if x.unit == unit_fantasy).amount - Decimal('133.700')) < 0.001 # TODO validate value
print(conversions)
print('\n----------- TEST CUSTOM CONVERSION - NO PCS ---------------')
ingredient_food_1_pcs = Ingredient.objects.create(
food=food_1,
unit=unit_pcs,
amount=5,
space=space_1,
)
ingredient_food_2_pcs = Ingredient.objects.create(
food=food_2,
unit=unit_pcs,
amount=5,
space=space_1,
)
assert len(uch.get_conversions(ingredient_food_1_pcs)) == 1
assert len(uch.get_conversions(ingredient_food_2_pcs)) == 1
print(uch.get_conversions(ingredient_food_1_pcs))
print(uch.get_conversions(ingredient_food_2_pcs))
print('\n----------- TEST CUSTOM CONVERSION - PCS TO MULTIPLE BASE ---------------')
uc1 = UnitConversion.objects.create(
base_amount=1,
base_unit=unit_pcs,
converted_amount=200,
converted_unit=unit_gram,
food=food_1,
space=space_1,
created_by=auth.get_user(u1_s1),
)
conversions = uch.get_conversions(ingredient_food_1_pcs)
assert len(conversions) == 3
assert abs(next(x for x in conversions if x.unit == unit_gram).amount - Decimal(1000)) < 0.0001
assert abs(next(x for x in conversions if x.unit == unit_kg).amount - Decimal(1)) < 0.0001
print(conversions)
assert len(uch.get_conversions(ingredient_food_2_pcs)) == 1
print(uch.get_conversions(ingredient_food_2_pcs))
print('\n----------- TEST CUSTOM CONVERSION - CONVERT MULTI STEP ---------------')
# TODO add test for multi step conversion ... do I even do or want to support this ?
print('\n----------- TEST CUSTOM CONVERSION - REVERSE CONVERSION ---------------')
uc2 = UnitConversion.objects.create(
base_amount=200,
base_unit=unit_gram,
converted_amount=1,
converted_unit=unit_pcs,
food=food_2,
space=space_1,
created_by=auth.get_user(u1_s1),
)
conversions = uch.get_conversions(ingredient_food_1_pcs)
assert len(conversions) == 3
assert abs(next(x for x in conversions if x.unit == unit_gram).amount - Decimal(1000)) < 0.0001
assert abs(next(x for x in conversions if x.unit == unit_kg).amount - Decimal(1)) < 0.0001
print(conversions)
conversions = uch.get_conversions(ingredient_food_2_pcs)
assert len(conversions) == 3
assert abs(next(x for x in conversions if x.unit == unit_gram).amount - Decimal(1000)) < 0.0001
assert abs(next(x for x in conversions if x.unit == unit_kg).amount - Decimal(1)) < 0.0001
print(conversions)
print('\n----------- TEST SPACE SEPARATION ---------------')
uc2.space = space_2
uc2.save()
conversions = uch.get_conversions(ingredient_food_2_pcs)
assert len(conversions) == 1
print(conversions)
conversions = uch_space_2.get_conversions(ingredient_food_1_gram)
assert len(conversions) == 1
assert not any(x for x in conversions if x.unit == unit_kg)
print(conversions)
unit_kg_space_2 = Unit.objects.create(name='kg', base_unit='kg', space=space_2)
conversions = uch_space_2.get_conversions(ingredient_food_1_gram)
assert len(conversions) == 2
assert not any(x for x in conversions if x.unit == unit_kg)
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)

View File

@@ -6,17 +6,23 @@ from rest_framework import permissions, routers
from rest_framework.schemas import get_schema_view from rest_framework.schemas import get_schema_view
from cookbook.helper import dal from cookbook.helper import dal
from recipes.settings import DEBUG from recipes.settings import DEBUG, PLUGINS
from recipes.version import VERSION_NUMBER from recipes.version import VERSION_NUMBER
from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, MealPlan, Recipe, from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, MealPlan, Recipe,
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, Step, Storage, RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, Step, Storage,
Supermarket, SupermarketCategory, Sync, SyncLog, Unit, UserFile, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, UserFile,
get_model_name, UserSpace, Space) get_model_name, UserSpace, Space, PropertyType, UnitConversion)
from .views import api, data, delete, edit, import_export, lists, new, telegram, views from .views import api, data, delete, edit, import_export, lists, new, telegram, views
from .views.api import CustomAuthToken from .views.api import CustomAuthToken, ImportOpenData
router = routers.DefaultRouter() # extend DRF default router class to allow including additional routers
class DefaultRouter(routers.DefaultRouter):
def extend(self, r):
self.registry.extend(r.registry)
router = DefaultRouter()
router.register(r'automation', api.AutomationViewSet) router.register(r'automation', api.AutomationViewSet)
router.register(r'bookmarklet-import', api.BookmarkletImportViewSet) router.register(r'bookmarklet-import', api.BookmarkletImportViewSet)
router.register(r'cook-log', api.CookLogViewSet) router.register(r'cook-log', api.CookLogViewSet)
@@ -34,6 +40,9 @@ router.register(r'meal-type', api.MealTypeViewSet)
router.register(r'recipe', api.RecipeViewSet) router.register(r'recipe', api.RecipeViewSet)
router.register(r'recipe-book', api.RecipeBookViewSet) router.register(r'recipe-book', api.RecipeBookViewSet)
router.register(r'recipe-book-entry', api.RecipeBookEntryViewSet) router.register(r'recipe-book-entry', api.RecipeBookEntryViewSet)
router.register(r'unit-conversion', api.UnitConversionViewSet)
router.register(r'food-property-type', api.PropertyTypeViewSet)
router.register(r'food-property', api.PropertyViewSet)
router.register(r'shopping-list', api.ShoppingListViewSet) router.register(r'shopping-list', api.ShoppingListViewSet)
router.register(r'shopping-list-entry', api.ShoppingListEntryViewSet) router.register(r'shopping-list-entry', api.ShoppingListEntryViewSet)
router.register(r'shopping-list-recipe', api.ShoppingListRecipeViewSet) router.register(r'shopping-list-recipe', api.ShoppingListRecipeViewSet)
@@ -53,6 +62,13 @@ router.register(r'user-space', api.UserSpaceViewSet)
router.register(r'view-log', api.ViewLogViewSet) router.register(r'view-log', api.ViewLogViewSet)
router.register(r'access-token', api.AccessTokenViewSet) router.register(r'access-token', api.AccessTokenViewSet)
for p in PLUGINS:
if c := locate(f'{p["module"]}.urls.{p["api_router_name"]}'):
try:
router.extend(c)
except AttributeError:
pass
urlpatterns = [ urlpatterns = [
path('', views.index, name='index'), path('', views.index, name='index'),
path('setup/', views.setup, name='view_setup'), path('setup/', views.setup, name='view_setup'),
@@ -119,7 +135,6 @@ urlpatterns = [
path('api/switch-active-space/<int:space_id>/', api.switch_active_space, name='api_switch_active_space'), path('api/switch-active-space/<int:space_id>/', api.switch_active_space, name='api_switch_active_space'),
path('api/download-file/<int:file_id>/', api.download_file, name='api_download_file'), path('api/download-file/<int:file_id>/', api.download_file, name='api_download_file'),
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'), path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
# TODO is this deprecated? not yet, some old forms remain, could likely be changed to generic API endpoints # TODO is this deprecated? not yet, some old forms remain, could likely be changed to generic API endpoints
path('dal/food/', dal.IngredientsAutocomplete.as_view(), name='dal_food'), # TODO is this deprecated? path('dal/food/', dal.IngredientsAutocomplete.as_view(), name='dal_food'), # TODO is this deprecated?
@@ -139,6 +154,7 @@ urlpatterns = [
path('api/', include((router.urls, 'api'))), path('api/', include((router.urls, 'api'))),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('api-token-auth/', CustomAuthToken.as_view()), path('api-token-auth/', CustomAuthToken.as_view()),
path('api-import-open-data/', ImportOpenData.as_view(), name='api_import_open_data'),
path('offline/', views.offline, name='view_offline'), path('offline/', views.offline, name='view_offline'),
@@ -189,7 +205,7 @@ for m in generic_models:
) )
) )
vue_models = [Food, Keyword, Unit, Supermarket, SupermarketCategory, Automation, UserFile, Step, CustomFilter] vue_models = [Food, Keyword, Unit, Supermarket, SupermarketCategory, Automation, UserFile, Step, CustomFilter, UnitConversion, PropertyType]
for m in vue_models: for m in vue_models:
py_name = get_model_name(m) py_name = get_model_name(m)
url_name = py_name.replace('_', '-') url_name = py_name.replace('_', '-')

View File

@@ -19,6 +19,7 @@ from annoying.functions import get_object_or_None
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.contrib.postgres.search import TrigramSimilarity from django.contrib.postgres.search import TrigramSimilarity
from django.core.cache import caches
from django.core.exceptions import FieldError, ValidationError from django.core.exceptions import FieldError, ValidationError
from django.core.files import File from django.core.files import File
from django.db.models import Case, Count, Exists, OuterRef, ProtectedError, Q, Subquery, Value, When, Avg, Max from django.db.models import Case, Count, Exists, OuterRef, ProtectedError, Q, Subquery, Value, When, Avg, Max
@@ -44,6 +45,7 @@ from rest_framework.parsers import MultiPartParser
from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.throttling import AnonRateThrottle from rest_framework.throttling import AnonRateThrottle
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin from rest_framework.viewsets import ViewSetMixin
from treebeard.exceptions import InvalidMoveToDescendant, InvalidPosition, PathOverflow from treebeard.exceptions import InvalidMoveToDescendant, InvalidPosition, PathOverflow
@@ -52,10 +54,13 @@ from cookbook.helper import recipe_url_import as helper
from cookbook.helper.HelperFunctions import str2bool from cookbook.helper.HelperFunctions import str2bool
from cookbook.helper.image_processing import handle_image from cookbook.helper.image_processing import handle_image
from cookbook.helper.ingredient_parser import IngredientParser from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.open_data_importer import OpenDataImporter
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsOwner, from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsOwner,
CustomIsOwnerReadOnly, CustomIsShared, CustomIsOwnerReadOnly, CustomIsShared,
CustomIsSpaceOwner, CustomIsUser, group_required, CustomIsSpaceOwner, CustomIsUser, group_required,
is_space_owner, switch_user_active_space, above_space_limit, CustomRecipePermission, CustomUserPermission, CustomTokenHasReadWriteScope, CustomTokenHasScope, has_group_permission) is_space_owner, switch_user_active_space, above_space_limit,
CustomRecipePermission, CustomUserPermission,
CustomTokenHasReadWriteScope, CustomTokenHasScope, has_group_permission)
from cookbook.helper.recipe_search import RecipeFacet, RecipeSearch from cookbook.helper.recipe_search import RecipeFacet, RecipeSearch
from cookbook.helper.recipe_url_import import get_from_youtube_scraper, get_images_from_soup, clean_dict from cookbook.helper.recipe_url_import import get_from_youtube_scraper, get_images_from_soup, clean_dict
from cookbook.helper.scrapers.scrapers import text_scraper from cookbook.helper.scrapers.scrapers import text_scraper
@@ -65,7 +70,7 @@ from cookbook.models import (Automation, BookmarkletImport, CookLog, CustomFilte
MealType, Recipe, RecipeBook, RecipeBookEntry, ShareLink, ShoppingList, MealType, Recipe, RecipeBook, RecipeBookEntry, ShareLink, ShoppingList,
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync,
SyncLog, Unit, UserFile, UserPreference, UserSpace, ViewLog) SyncLog, Unit, UserFile, UserPreference, UserSpace, ViewLog, UnitConversion, PropertyType, Property)
from cookbook.provider.dropbox import Dropbox from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud from cookbook.provider.nextcloud import Nextcloud
@@ -88,7 +93,8 @@ from cookbook.serializer import (AutomationSerializer, BookmarkletImportListSeri
SupermarketCategorySerializer, SupermarketSerializer, SupermarketCategorySerializer, SupermarketSerializer,
SyncLogSerializer, SyncSerializer, UnitSerializer, SyncLogSerializer, SyncSerializer, UnitSerializer,
UserFileSerializer, UserSerializer, UserPreferenceSerializer, UserFileSerializer, UserSerializer, UserPreferenceSerializer,
UserSpaceSerializer, ViewLogSerializer, AccessTokenSerializer, FoodSimpleSerializer, RecipeExportSerializer) UserSpaceSerializer, ViewLogSerializer, AccessTokenSerializer, FoodSimpleSerializer,
RecipeExportSerializer, UnitConversionSerializer, PropertyTypeSerializer, PropertySerializer)
from cookbook.views.import_export import get_integration from cookbook.views.import_export import get_integration
from recipes import settings from recipes import settings
@@ -166,14 +172,17 @@ class FuzzyFilterMixin(ViewSetMixin, ExtendedRecipeMixin):
def get_queryset(self): def get_queryset(self):
self.queryset = self.queryset.filter(space=self.request.space).order_by(Lower('name').asc()) self.queryset = self.queryset.filter(space=self.request.space).order_by(Lower('name').asc())
query = self.request.query_params.get('query', None) query = self.request.query_params.get('query', None)
if self.request.user.is_authenticated:
fuzzy = self.request.user.searchpreference.lookup or any([self.model.__name__.lower() in x for x in fuzzy = self.request.user.searchpreference.lookup or any([self.model.__name__.lower() in x for x in
self.request.user.searchpreference.trigram.values_list( self.request.user.searchpreference.trigram.values_list(
'field', flat=True)]) 'field', flat=True)])
else:
fuzzy = True
if query is not None and query not in ["''", '']: if query is not None and query not in ["''", '']:
if fuzzy and (settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql']): if fuzzy and (settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2',
if any([self.model.__name__.lower() in x for x in 'django.db.backends.postgresql']):
self.request.user.searchpreference.unaccent.values_list('field', flat=True)]): if self.request.user.is_authenticated and any([self.model.__name__.lower() in x for x in self.request.user.searchpreference.unaccent.values_list('field', flat=True)]):
self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name__unaccent', query)) self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name__unaccent', query))
else: else:
self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name', query)) self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name', query))
@@ -181,13 +190,12 @@ class FuzzyFilterMixin(ViewSetMixin, ExtendedRecipeMixin):
else: else:
# TODO have this check unaccent search settings or other search preferences? # TODO have this check unaccent search settings or other search preferences?
filter = Q(name__icontains=query) filter = Q(name__icontains=query)
if any([self.model.__name__.lower() in x for x in if self.request.user.is_authenticated:
self.request.user.searchpreference.unaccent.values_list('field', flat=True)]): if any([self.model.__name__.lower() in x for x in self.request.user.searchpreference.unaccent.values_list('field', flat=True)]):
filter |= Q(name__unaccent__icontains=query) filter |= Q(name__unaccent__icontains=query)
self.queryset = ( self.queryset = (
self.queryset self.queryset.annotate(starts=Case(When(name__istartswith=query, then=(Value(100))),
.annotate(starts=Case(When(name__istartswith=query, then=(Value(100))),
default=Value(0))) # put exact matches at the top of the result set default=Value(0))) # put exact matches at the top of the result set
.filter(filter).order_by('-starts', Lower('name').asc()) .filter(filter).order_by('-starts', Lower('name').asc())
) )
@@ -243,6 +251,9 @@ class MergeMixin(ViewSetMixin):
isTree = False isTree = False
try: try:
if isinstance(source, Food):
source.properties.through.objects.all().delete()
for link in [field for field in source._meta.get_fields() if issubclass(type(field), ForeignObjectRel)]: for link in [field for field in source._meta.get_fields() if issubclass(type(field), ForeignObjectRel)]:
linkManager = getattr(source, link.get_accessor_name()) linkManager = getattr(source, link.get_accessor_name())
related = linkManager.all() related = linkManager.all()
@@ -272,6 +283,7 @@ class MergeMixin(ViewSetMixin):
source.delete() source.delete()
return Response(content, status=status.HTTP_200_OK) return Response(content, status=status.HTTP_200_OK)
except Exception: except Exception:
traceback.print_exc()
content = {'error': True, content = {'error': True,
'msg': _(f'An error occurred attempting to merge {source.name} with {target.name}')} 'msg': _(f'An error occurred attempting to merge {source.name} with {target.name}')}
return Response(content, status=status.HTTP_400_BAD_REQUEST) return Response(content, status=status.HTTP_400_BAD_REQUEST)
@@ -522,8 +534,20 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
pagination_class = DefaultPagination pagination_class = DefaultPagination
def get_queryset(self): def get_queryset(self):
self.request._shared_users = [x.id for x in list(self.request.user.get_shopping_share())] + [ shared_users = []
if c := caches['default'].get(
f'shopping_shared_users_{self.request.space.id}_{self.request.user.id}', None):
shared_users = c
else:
try:
shared_users = [x.id for x in list(self.request.user.get_shopping_share())] + [
self.request.user.id] self.request.user.id]
caches['default'].set(
f'shopping_shared_users_{self.request.space.id}_{self.request.user.id}',
shared_users, timeout=5 * 60)
# TODO ugly hack that improves API performance significantly, should be done properly
except AttributeError: # Anonymous users (using share links) don't have shared users
pass
self.queryset = super().get_queryset() self.queryset = super().get_queryset()
shopping_status = ShoppingListEntry.objects.filter(space=self.request.space, food=OuterRef('id'), shopping_status = ShoppingListEntry.objects.filter(space=self.request.space, food=OuterRef('id'),
@@ -792,7 +816,32 @@ class RecipeViewSet(viewsets.ModelViewSet):
if self.detail: # if detail request and not list, private condition is verified by permission class if self.detail: # if detail request and not list, private condition is verified by permission class
if not share: # filter for space only if not shared if not share: # filter for space only if not shared
self.queryset = self.queryset.filter(space=self.request.space) self.queryset = self.queryset.filter(space=self.request.space).prefetch_related(
'keywords',
'shared',
'properties',
'properties__property_type',
'steps',
'steps__ingredients',
'steps__ingredients__step_set',
'steps__ingredients__step_set__recipe_set',
'steps__ingredients__food',
'steps__ingredients__food__properties',
'steps__ingredients__food__properties__property_type',
'steps__ingredients__food__inherit_fields',
'steps__ingredients__food__supermarket_category',
'steps__ingredients__food__onhand_users',
'steps__ingredients__food__substitute',
'steps__ingredients__food__child_inherit_fields',
'steps__ingredients__unit',
'steps__ingredients__unit__unit_conversion_base_relation',
'steps__ingredients__unit__unit_conversion_base_relation__base_unit',
'steps__ingredients__unit__unit_conversion_converted_relation',
'steps__ingredients__unit__unit_conversion_converted_relation__converted_unit',
'cooklog_set',
).select_related('nutrition')
return super().get_queryset() return super().get_queryset()
self.queryset = self.queryset.filter(space=self.request.space).filter( self.queryset = self.queryset.filter(space=self.request.space).filter(
@@ -802,7 +851,7 @@ class RecipeViewSet(viewsets.ModelViewSet):
params = {x: self.request.GET.get(x) if len({**self.request.GET}[x]) == 1 else self.request.GET.getlist(x) for x params = {x: self.request.GET.get(x) if len({**self.request.GET}[x]) == 1 else self.request.GET.getlist(x) for x
in list(self.request.GET)} in list(self.request.GET)}
search = RecipeSearch(self.request, **params) search = RecipeSearch(self.request, **params)
self.queryset = search.get_queryset(self.queryset).prefetch_related('cooklog_set') self.queryset = search.get_queryset(self.queryset).prefetch_related('keywords', 'cooklog_set')
return self.queryset return self.queryset
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
@@ -921,6 +970,41 @@ class RecipeViewSet(viewsets.ModelViewSet):
return Response(self.serializer_class(qs, many=True).data) return Response(self.serializer_class(qs, many=True).data)
class UnitConversionViewSet(viewsets.ModelViewSet):
queryset = UnitConversion.objects
serializer_class = UnitConversionSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
query_params = [
QueryParam(name='food_id', description='ID of food to filter for', qtype='int'),
]
schema = QueryParamAutoSchema()
def get_queryset(self):
food_id = self.request.query_params.get('food_id', None)
if food_id is not None:
self.queryset = self.queryset.filter(food_id=food_id)
return self.queryset.filter(space=self.request.space)
class PropertyTypeViewSet(viewsets.ModelViewSet):
queryset = PropertyType.objects
serializer_class = PropertyTypeSerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
def get_queryset(self):
return self.queryset.filter(space=self.request.space)
class PropertyViewSet(viewsets.ModelViewSet):
queryset = Property.objects
serializer_class = PropertySerializer
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
def get_queryset(self):
return self.queryset.filter(space=self.request.space)
class ShoppingListRecipeViewSet(viewsets.ModelViewSet): class ShoppingListRecipeViewSet(viewsets.ModelViewSet):
queryset = ShoppingListRecipe.objects queryset = ShoppingListRecipe.objects
serializer_class = ShoppingListRecipeSerializer serializer_class = ShoppingListRecipeSerializer
@@ -1122,10 +1206,13 @@ class CustomAuthToken(ObtainAuthToken):
context={'request': request}) context={'request': request})
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user'] user = serializer.validated_data['user']
if token := AccessToken.objects.filter(user=user, expires__gt=timezone.now(), scope__contains='read').filter(scope__contains='write').first(): if token := AccessToken.objects.filter(user=user, expires__gt=timezone.now(), scope__contains='read').filter(
scope__contains='write').first():
access_token = token access_token = token
else: else:
access_token = AccessToken.objects.create(user=user, token=f'tda_{str(uuid.uuid4()).replace("-", "_")}', expires=(timezone.now() + timezone.timedelta(days=365 * 5)), scope='read write app') access_token = AccessToken.objects.create(user=user, token=f'tda_{str(uuid.uuid4()).replace("-", "_")}',
expires=(timezone.now() + timezone.timedelta(days=365 * 5)),
scope='read write app')
return Response({ return Response({
'id': access_token.id, 'id': access_token.id,
'token': access_token.token, 'token': access_token.token,
@@ -1153,7 +1240,8 @@ def recipe_from_source(request):
serializer = RecipeFromSourceSerializer(data=request.data) serializer = RecipeFromSourceSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
if (b_pk := serializer.validated_data.get('bookmarklet', None)) and (bookmarklet := BookmarkletImport.objects.filter(pk=b_pk).first()): if (b_pk := serializer.validated_data.get('bookmarklet', None)) and (
bookmarklet := BookmarkletImport.objects.filter(pk=b_pk).first()):
serializer.validated_data['url'] = bookmarklet.url serializer.validated_data['url'] = bookmarklet.url
serializer.validated_data['data'] = bookmarklet.html serializer.validated_data['data'] = bookmarklet.html
bookmarklet.delete() bookmarklet.delete()
@@ -1175,13 +1263,22 @@ def recipe_from_source(request):
# 'recipe_html': '', # 'recipe_html': '',
'recipe_images': [], 'recipe_images': [],
}, status=status.HTTP_200_OK) }, status=status.HTTP_200_OK)
if re.match('^(.)*/view/recipe/[0-9]+/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$', url): if re.match(
recipe_json = requests.get(url.replace('/view/recipe/', '/api/recipe/').replace(re.split('/view/recipe/[0-9]+', url)[1], '') + '?share=' + re.split('/view/recipe/[0-9]+', url)[1].replace('/', '')).json() '^(.)*/view/recipe/[0-9]+/[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$',
url):
recipe_json = requests.get(
url.replace('/view/recipe/', '/api/recipe/').replace(re.split('/view/recipe/[0-9]+', url)[1],
'') + '?share=' +
re.split('/view/recipe/[0-9]+', url)[1].replace('/', '')).json()
recipe_json = clean_dict(recipe_json, 'id') recipe_json = clean_dict(recipe_json, 'id')
serialized_recipe = RecipeExportSerializer(data=recipe_json, context={'request': request}) serialized_recipe = RecipeExportSerializer(data=recipe_json, context={'request': request})
if serialized_recipe.is_valid(): if serialized_recipe.is_valid():
recipe = serialized_recipe.save() recipe = serialized_recipe.save()
recipe.image = File(handle_image(request, File(io.BytesIO(requests.get(recipe_json['image']).content), name='image'), filetype=pathlib.Path(recipe_json['image']).suffix), if validators.url(recipe_json['image'], public=True):
recipe.image = File(handle_image(request,
File(io.BytesIO(requests.get(recipe_json['image']).content),
name='image'),
filetype=pathlib.Path(recipe_json['image']).suffix),
name=f'{uuid.uuid4()}_{recipe.pk}{pathlib.Path(recipe_json["image"]).suffix}') name=f'{uuid.uuid4()}_{recipe.pk}{pathlib.Path(recipe_json["image"]).suffix}')
recipe.save() recipe.save()
return Response({ return Response({
@@ -1323,11 +1420,44 @@ def import_files(request):
return Response({'import_id': il.pk}, status=status.HTTP_200_OK) return Response({'import_id': il.pk}, status=status.HTTP_200_OK)
except NotImplementedError: except NotImplementedError:
return Response({'error': True, 'msg': _('Importing is not implemented for this provider')}, status=status.HTTP_400_BAD_REQUEST) return Response({'error': True, 'msg': _('Importing is not implemented for this provider')},
status=status.HTTP_400_BAD_REQUEST)
else: else:
return Response({'error': True, 'msg': form.errors}, status=status.HTTP_400_BAD_REQUEST) return Response({'error': True, 'msg': form.errors}, status=status.HTTP_400_BAD_REQUEST)
class ImportOpenData(APIView):
permission_classes = [CustomIsAdmin & CustomTokenHasReadWriteScope]
def get(self, request, format=None):
response = requests.get('https://raw.githubusercontent.com/TandoorRecipes/open-tandoor-data/main/build/meta.json')
metadata = json.loads(response.content)
return Response(metadata)
def post(self, request, *args, **kwargs):
# TODO validate data
print(request.data)
selected_version = request.data['selected_version']
selected_datatypes = request.data['selected_datatypes']
update_existing = str2bool(request.data['update_existing'])
use_metric = str2bool(request.data['use_metric'])
response = requests.get(f'https://raw.githubusercontent.com/TandoorRecipes/open-tandoor-data/main/build/{selected_version}.json') # TODO catch 404, timeout, ...
data = json.loads(response.content)
response_obj = {}
data_importer = OpenDataImporter(request, data, update_existing=update_existing, use_metric=use_metric)
response_obj['unit'] = len(data_importer.import_units())
response_obj['category'] = len(data_importer.import_category())
response_obj['property'] = len(data_importer.import_property())
response_obj['store'] = len(data_importer.import_supermarket())
response_obj['food'] = len(data_importer.import_food())
response_obj['conversion'] = len(data_importer.import_conversion())
return Response(response_obj)
def get_recipe_provider(recipe): def get_recipe_provider(recipe):
if recipe.storage.method == Storage.DROPBOX: if recipe.storage.method == Storage.DROPBOX:
return Dropbox return Dropbox

View File

@@ -228,3 +228,33 @@ def step(request):
} }
} }
) )
@group_required('user')
def unit_conversion(request):
# model-name is the models.js name of the model, probably ALL-CAPS
return render(
request,
'generic/model_template.html',
{
"title": _("Unit Conversions"),
"config": {
'model': "UNIT_CONVERSION", # *REQUIRED* name of the model in models.js
}
}
)
@group_required('user')
def property_type(request):
# model-name is the models.js name of the model, probably ALL-CAPS
return render(
request,
'generic/model_template.html',
{
"title": _("Property Types"),
"config": {
'model': "PROPERTY_TYPE", # *REQUIRED* name of the model in models.js
}
}
)

View File

@@ -1,14 +1,11 @@
import os import os
import re import re
import uuid
from datetime import datetime from datetime import datetime
from uuid import UUID from uuid import UUID
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.auth.password_validation import validate_password from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@@ -18,11 +15,9 @@ from django.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from oauth2_provider.models import AccessToken
from cookbook.forms import (CommentForm, Recipe, SearchPreferenceForm, ShoppingPreferenceForm, from cookbook.forms import (CommentForm, Recipe, SearchPreferenceForm, SpaceCreateForm, SpaceJoinForm, User,
SpaceCreateForm, SpaceJoinForm, User, UserCreateForm, UserPreference)
UserCreateForm, UserNameForm, UserPreference, UserPreferenceForm)
from cookbook.helper.permission_helper import group_required, has_group_permission, share_link_valid, switch_user_active_space from cookbook.helper.permission_helper import group_required, has_group_permission, share_link_valid, switch_user_active_space
from cookbook.models import (Comment, CookLog, InviteLink, SearchFields, SearchPreference, ShareLink, from cookbook.models import (Comment, CookLog, InviteLink, SearchFields, SearchPreference, ShareLink,
Space, ViewLog, UserSpace) Space, ViewLog, UserSpace)

View File

@@ -239,6 +239,9 @@ RecetteTek exports are `.rtk` files which can simply be uploaded to tandoor to i
## Rezeptsuite.de ## Rezeptsuite.de
Rezeptsuite.de exports are `.xml` files which can simply be uploaded to tandoor to import all your recipes. Rezeptsuite.de exports are `.xml` files which can simply be uploaded to tandoor to import all your recipes.
It appears that Reptsuite, depending on the client, might export a `.zip` file containing a `.cml` file.
If this happens just unzip the zip file and change `.cml` to `.xml` to import your recipes.
## Melarecipes ## Melarecipes
Melarecipes provides multiple export formats but only the `MelaRecipes` format can export the complete collection. Melarecipes provides multiple export formats but only the `MelaRecipes` format can export the complete collection.

View File

@@ -40,6 +40,7 @@ nav:
- Templating: features/templating.md - Templating: features/templating.md
- Shopping: features/shopping.md - Shopping: features/shopping.md
- Authentication: features/authentication.md - Authentication: features/authentication.md
- Automation: features/automation.md
- Storages and Sync: features/external_recipes.md - Storages and Sync: features/external_recipes.md
- Import/Export: features/import_export.md - Import/Export: features/import_export.md
- System: - System:

View File

@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/2.0/ref/settings/
""" """
import ast import ast
import json import json
import mimetypes
import os import os
import re import re
import sys import sys
@@ -80,6 +81,8 @@ DJANGO_TABLES2_PAGE_RANGE = 8
HCAPTCHA_SITEKEY = os.getenv('HCAPTCHA_SITEKEY', '') HCAPTCHA_SITEKEY = os.getenv('HCAPTCHA_SITEKEY', '')
HCAPTCHA_SECRET = os.getenv('HCAPTCHA_SECRET', '') HCAPTCHA_SECRET = os.getenv('HCAPTCHA_SECRET', '')
FDA_API_KEY = os.getenv('FDA_API_KEY', 'DEMO_KEY')
SHARING_ABUSE = bool(int(os.getenv('SHARING_ABUSE', False))) SHARING_ABUSE = bool(int(os.getenv('SHARING_ABUSE', False)))
SHARING_LIMIT = int(os.getenv('SHARING_LIMIT', 0)) SHARING_LIMIT = int(os.getenv('SHARING_LIMIT', 0))
@@ -144,6 +147,9 @@ try:
'base_path': os.path.join(BASE_DIR, 'recipes', 'plugins', d), 'base_path': os.path.join(BASE_DIR, 'recipes', 'plugins', d),
'base_url': plugin_class.base_url, 'base_url': plugin_class.base_url,
'bundle_name': plugin_class.bundle_name if hasattr(plugin_class, 'bundle_name') else '', 'bundle_name': plugin_class.bundle_name if hasattr(plugin_class, 'bundle_name') else '',
'api_router_name': plugin_class.api_router_name if hasattr(plugin_class, 'api_router_name') else '',
'nav_main': plugin_class.nav_main if hasattr(plugin_class, 'nav_main') else '',
'nav_dropdown': plugin_class.nav_dropdown if hasattr(plugin_class, 'nav_dropdown') else '',
} }
PLUGINS.append(plugin_config) PLUGINS.append(plugin_config)
except Exception: except Exception:
@@ -412,7 +418,7 @@ for p in PLUGINS:
if p['bundle_name'] != '': if p['bundle_name'] != '':
WEBPACK_LOADER[p['bundle_name']] = { WEBPACK_LOADER[p['bundle_name']] = {
'CACHE': not DEBUG, 'CACHE': not DEBUG,
'BUNDLE_DIR_NAME': f'{p["base_path"]}/vue/', # must end with slash 'BUNDLE_DIR_NAME': f'vue/', # must end with slash
'STATS_FILE': os.path.join(p["base_path"], 'vue', 'webpack-stats.json'), 'STATS_FILE': os.path.join(p["base_path"], 'vue', 'webpack-stats.json'),
'POLL_INTERVAL': 0.1, 'POLL_INTERVAL': 0.1,
'TIMEOUT': None, 'TIMEOUT': None,
@@ -445,6 +451,7 @@ LANGUAGES = [
('hu', _('Hungarian')), ('hu', _('Hungarian')),
('it', _('Italian')), ('it', _('Italian')),
('lv', _('Latvian')), ('lv', _('Latvian')),
('nb', _('Norwegian ')),
('pl', _('Polish')), ('pl', _('Polish')),
('ru', _('Russian')), ('ru', _('Russian')),
('es', _('Spanish')), ('es', _('Spanish')),
@@ -514,3 +521,5 @@ EMAIL_USE_SSL = bool(int(os.getenv('EMAIL_USE_SSL', False)))
DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', 'webmaster@localhost') DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', 'webmaster@localhost')
ACCOUNT_EMAIL_SUBJECT_PREFIX = os.getenv( ACCOUNT_EMAIL_SUBJECT_PREFIX = os.getenv(
'ACCOUNT_EMAIL_SUBJECT_PREFIX', '[Tandoor Recipes] ') # allauth sender prefix 'ACCOUNT_EMAIL_SUBJECT_PREFIX', '[Tandoor Recipes] ') # allauth sender prefix
mimetypes.add_type("text/javascript", ".js", True)

View File

@@ -1,5 +1,5 @@
Django==4.1.7 Django==4.1.9
cryptography==39.0.1 cryptography==41.0.0
django-annoying==0.10.6 django-annoying==0.10.6
django-autocomplete-light==3.9.4 django-autocomplete-light==3.9.4
django-cleanup==7.0.0 django-cleanup==7.0.0
@@ -17,7 +17,7 @@ Markdown==3.4.3
Pillow==9.4.0 Pillow==9.4.0
psycopg2-binary==2.9.5 psycopg2-binary==2.9.5
python-dotenv==0.21.0 python-dotenv==0.21.0
requests==2.28.2 requests==2.31.0
six==1.16.0 six==1.16.0
webdavclient3==3.14.6 webdavclient3==3.14.6
whitenoise==6.2.0 whitenoise==6.2.0

View File

@@ -31,7 +31,7 @@
</div> </div>
</div> </div>
<!-- Image and misc properties --> <!-- Image and misc -->
<div class="row pt-2"> <div class="row pt-2">
<div class="col-md-6" style="max-height: 50vh; min-height: 30vh"> <div class="col-md-6" style="max-height: 50vh; min-height: 30vh">
<input id="id_file_upload" ref="file_upload" type="file" hidden <input id="id_file_upload" ref="file_upload" type="file" hidden
@@ -99,65 +99,53 @@
</div> </div>
</div> </div>
<!-- Nutrition -->
<div class="row pt-2"> <div class="row pt-2">
<div class="col-md-12"> <div class="col-md-12">
<div class="card border-grey"> <div class="card mt-2 mb-2">
<div class="card-header" style="display: table"> <div class="card-body pr-2 pl-2 pr-md-5 pl-md-5 pt-3 pb-3">
<div class="row"> <h6>{{ $t('Properties') }} <small class="text-muted"> {{$t('per_serving')}}</small></h6>
<div class="col-md-9 d-table">
<h5 class="d-table-cell align-middle">{{ $t("Nutrition") }}</h5> <div class="alert alert-info" role="alert">
{{ $t('recipe_property_info')}}
</div>
<div class="d-flex mt-2" v-for="p in recipe.properties" v-bind:key="p.id">
<div class="flex-fill w-50">
<generic-multiselect
@change="p.property_type = $event.val"
:initial_single_selection="p.property_type"
:label="'name'"
:model="Models.PROPERTY_TYPE"
:limit="25"
:multiple="false"
></generic-multiselect>
</div>
<div class="flex-fill w-50">
<div class="input-group">
<input type="number" class="form-control" v-model="p.property_amount">
<div class="input-group-append">
<span class="input-group-text" v-if="p.property_type !== null && p.property_type.unit !== ''">{{ p.property_type.unit }}</span>
<button class="btn btn-danger" @click="deleteProperty(p)"><i class="fa fa-trash fa-fw"></i></button>
</div>
</div>
</div>
</div>
<div class="flex-row mt-2">
<div class="flex-column w-25 offset-4">
<button class="btn btn-success btn-block" @click="addProperty()"><i class="fa fa-plus"></i></button>
</div>
</div>
</div> </div>
<div class="col-md-3">
<button
type="button"
@click="addNutrition()"
v-if="recipe.nutrition === null"
v-b-tooltip.hover
v-bind:title="$t('Add_nutrition_recipe')"
class="btn btn-sm btn-success shadow-none float-right"
>
<i class="fas fa-plus-circle"></i>
</button>
<button
type="button"
@click="removeNutrition()"
v-if="recipe.nutrition !== null"
v-b-tooltip.hover
v-bind:title="$t('Remove_nutrition_recipe')"
class="btn btn-sm btn-danger shadow-none float-right"
>
<i class="fas fa-trash-alt"></i>
</button>
</div> </div>
</div> </div>
</div> </div>
<b-collapse id="id_nutrition_collapse" class="mt-2" v-model="nutrition_visible"> <div class="row pt-2">
<div class="card-body" v-if="recipe.nutrition !== null"> <div class="col-md-12">
<b-alert show>
There is currently only very basic support for tracking nutritional information. A
<a href="https://github.com/vabene1111/recipes/issues/896" target="_blank"
rel="noreferrer nofollow">big update</a> is planned to improve on this in many
different areas.
</b-alert>
<label for="id_name"> {{ $t(energy()) }}</label>
<input class="form-control" id="id_calories" v-model="recipe.nutrition.calories"/>
<label for="id_name"> {{ $t("Carbohydrates") }}</label>
<input class="form-control" id="id_carbohydrates"
v-model="recipe.nutrition.carbohydrates"/>
<label for="id_name"> {{ $t("Fats") }}</label>
<input class="form-control" id="id_fats" v-model="recipe.nutrition.fats"/>
<label for="id_name"> {{ $t("Proteins") }}</label>
<input class="form-control" id="id_proteins" v-model="recipe.nutrition.proteins"/>
</div>
</b-collapse>
</div>
<b-card-header header-tag="header" class="p-1" role="tab"> <b-card-header header-tag="header" class="p-1" role="tab">
<b-button squared block v-b-toggle.additional_collapse class="text-left" <b-button squared block v-b-toggle.additional_collapse class="text-left"
variant="outline-primary">{{ $t("additional_options") }} variant="outline-primary">{{ $t("additional_options") }}
@@ -1121,6 +1109,14 @@ export default {
let new_keyword = {label: tag, name: tag} let new_keyword = {label: tag, name: tag}
this.recipe.keywords.push(new_keyword) this.recipe.keywords.push(new_keyword)
}, },
addProperty: function () {
this.recipe.properties.push(
{'property_amount': 0, 'property_type': null}
)
},
deleteProperty: function (recipe_property) {
this.recipe.properties = this.recipe.properties.filter(p => p.id !== recipe_property.id)
},
searchKeywords: function (query) { searchKeywords: function (query) {
let apiFactory = new ApiApiFactory() let apiFactory = new ApiApiFactory()

View File

@@ -1,158 +1,6 @@
<template> <template>
<div id="app"> <div id="app">
<template v-if="loading"> <recipe-view-component></recipe-view-component>
<loading-spinner></loading-spinner>
</template>
<div v-if="!loading" style="padding-bottom: 60px">
<RecipeSwitcher ref="ref_recipe_switcher" @switch="quickSwitch($event)"/>
<div class="row">
<div class="col-12" style="text-align: center">
<h3>{{ recipe.name }}</h3>
</div>
</div>
<div class="row text-center">
<div class="col col-md-12">
<recipe-rating :recipe="recipe"></recipe-rating>
<last-cooked :recipe="recipe" class="mt-2"></last-cooked>
</div>
</div>
<div class="my-auto">
<div class="col-12" style="text-align: center">
<i>{{ recipe.description }}</i>
</div>
</div>
<div style="text-align: center">
<keywords-component :recipe="recipe"></keywords-component>
</div>
<hr/>
<div class="row align-items-center">
<div class="col col-md-3">
<div class="d-flex">
<div class="my-auto mr-1">
<i class="fas fa-fw fa-user-clock fa-2x text-primary"></i>
</div>
<div class="my-auto mr-1">
<span class="text-primary"><b>{{ $t("Preparation") }}</b></span><br/>
{{ working_time }}
</div>
</div>
</div>
<div class="col col-md-3">
<div class="row d-flex">
<div class="my-auto mr-1">
<i class="far fa-fw fa-clock fa-2x text-primary"></i>
</div>
<div class="my-auto mr-1">
<span class="text-primary"><b>{{ $t("Waiting") }}</b></span><br/>
{{ waiting_time }}
</div>
</div>
</div>
<div class="col col-md-4 col-10 mt-2 mt-md-0">
<div class="d-flex">
<div class="my-auto mr-1">
<i class="fas fa-fw fa-pizza-slice fa-2x text-primary"></i>
</div>
<div class="my-auto mr-1">
<CustomInputSpinButton v-model.number="servings"/>
</div>
<div class="my-auto mr-1">
<span class="text-primary">
<b>
<template v-if="recipe.servings_text === ''">{{ $t("Servings") }}</template>
<template v-else>{{ recipe.servings_text }}</template>
</b>
</span>
</div>
</div>
</div>
<div class="col col-md-2 col-2 mt-2 mt-md-0 text-right">
<recipe-context-menu v-bind:recipe="recipe" :servings="servings"
:disabled_options="{print:false}"></recipe-context-menu>
</div>
</div>
<hr/>
<div class="row">
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2"
v-if="recipe && ingredient_count > 0 && (recipe.show_ingredient_overview || recipe.steps.length < 2)">
<ingredients-card
:recipe="recipe.id"
:steps="recipe.steps"
:ingredient_factor="ingredient_factor"
:servings="servings"
:header="true"
id="ingredient_container"
@checked-state-changed="updateIngredientCheckedState"
@change-servings="servings = $event"
/>
</div>
<div class="col-12 order-1 col-sm-12 order-sm-1 col-md-6 order-md-2">
<div class="row">
<div class="col-12">
<img class="img img-fluid rounded" :src="recipe.image" :alt="$t('Recipe_Image')"
v-if="recipe.image !== null" @load="onImgLoad"
:style="{ 'max-height': ingredient_height }"/>
</div>
</div>
</div>
</div>
<template v-if="!recipe.internal">
<div v-if="recipe.file_path.includes('.pdf')">
<PdfViewer :recipe="recipe"></PdfViewer>
</div>
<div
v-if="recipe.file_path.includes('.png') || recipe.file_path.includes('.jpg') || recipe.file_path.includes('.jpeg') || recipe.file_path.includes('.gif')">
<ImageViewer :recipe="recipe"></ImageViewer>
</div>
</template>
<div v-for="(s, index) in recipe.steps" v-bind:key="s.id" style="margin-top: 1vh">
<step-component
:recipe="recipe"
:step="s"
:ingredient_factor="ingredient_factor"
:index="index"
:start_time="start_time"
@update-start-time="updateStartTime"
@checked-state-changed="updateIngredientCheckedState"
></step-component>
</div>
<div v-if="recipe.source_url !== null">
<h6 class="d-print-none"><i class="fas fa-file-import"></i> {{ $t("Imported_From") }}</h6>
<span class="text-muted mt-1"><a style="overflow-wrap: break-word;"
:href="recipe.source_url">{{ recipe.source_url }}</a></span>
</div>
<div class="row" style="margin-top: 2vh; ">
<div class="col-lg-6 offset-lg-3 col-12">
<Nutrition-component :recipe="recipe" id="nutrition_container"
:ingredient_factor="ingredient_factor"></Nutrition-component>
</div>
</div>
</div>
<add-recipe-to-book :recipe="recipe"></add-recipe-to-book>
<div class="row text-center d-print-none" style="margin-top: 3vh; margin-bottom: 3vh"
v-if="share_uid !== 'None' && !loading">
<div class="col col-md-12">
<import-tandoor></import-tandoor> <br/>
<a :href="resolveDjangoUrl('view_report_share_abuse', share_uid)" class="mt-3">{{ $t("Report Abuse") }}</a>
</div>
</div>
<bottom-navigation-bar></bottom-navigation-bar> <bottom-navigation-bar></bottom-navigation-bar>
</div> </div>
@@ -163,192 +11,28 @@ import Vue from "vue"
import {BootstrapVue} from "bootstrap-vue" import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css" import "bootstrap-vue/dist/bootstrap-vue.css"
import {apiLoadRecipe} from "@/utils/api" import RecipeViewComponent from "@/components/RecipeViewComponent.vue";
import RecipeContextMenu from "@/components/RecipeContextMenu"
import {ResolveUrlMixin, ToastMixin, calculateHourMinuteSplit} from "@/utils/utils"
import PdfViewer from "@/components/PdfViewer"
import ImageViewer from "@/components/ImageViewer"
import moment from "moment"
import LoadingSpinner from "@/components/LoadingSpinner"
import AddRecipeToBook from "@/components/Modals/AddRecipeToBook"
import RecipeRating from "@/components/RecipeRating"
import LastCooked from "@/components/LastCooked"
import IngredientsCard from "@/components/IngredientsCard"
import StepComponent from "@/components/StepComponent"
import KeywordsComponent from "@/components/KeywordsComponent"
import NutritionComponent from "@/components/NutritionComponent"
import RecipeSwitcher from "@/components/Buttons/RecipeSwitcher"
import CustomInputSpinButton from "@/components/CustomInputSpinButton"
import {ApiApiFactory} from "@/utils/openapi/api";
import ImportTandoor from "@/components/Modals/ImportTandoor.vue";
import BottomNavigationBar from "@/components/BottomNavigationBar.vue";
Vue.prototype.moment = moment
Vue.use(BootstrapVue) Vue.use(BootstrapVue)
export default { export default {
name: "RecipeView", name: "RecipeView",
mixins: [ResolveUrlMixin, ToastMixin], mixins: [],
components: { components: {
ImportTandoor, RecipeViewComponent
LastCooked,
RecipeRating,
PdfViewer,
ImageViewer,
IngredientsCard,
StepComponent,
RecipeContextMenu,
NutritionComponent,
KeywordsComponent,
LoadingSpinner,
AddRecipeToBook,
RecipeSwitcher,
CustomInputSpinButton,
BottomNavigationBar,
},
computed: {
ingredient_factor: function () {
return this.servings / this.recipe.servings
},
ingredient_count() {
return this.recipe?.steps.map((x) => x.ingredients).flat().length
},
working_time: function () {
return calculateHourMinuteSplit(this.recipe.working_time)
},
waiting_time: function () {
return calculateHourMinuteSplit(this.recipe.waiting_time)
},
}, },
computed: {},
data() { data() {
return { return {}
loading: true,
recipe: undefined,
rootrecipe: undefined,
servings: 1,
servings_cache: {},
start_time: "",
share_uid: window.SHARE_UID,
wake_lock: null,
ingredient_height: '250',
}
},
watch: {
servings(newVal, oldVal) {
this.servings_cache[this.recipe.id] = this.servings
},
}, },
mounted() { mounted() {
this.loadRecipe(window.RECIPE_ID)
this.$i18n.locale = window.CUSTOM_LOCALE this.$i18n.locale = window.CUSTOM_LOCALE
this.requestWakeLock()
window.addEventListener('resize', this.handleResize);
},
beforeUnmount() {
this.destroyWakeLock()
},
methods: {
requestWakeLock: async function () {
if ('wakeLock' in navigator) {
try {
this.wake_lock = await navigator.wakeLock.request('screen')
document.addEventListener('visibilitychange', this.visibilityChange)
} catch (err) {
console.log(err)
}
}
},
handleResize: function () {
if (document.getElementById('nutrition_container') !== null) {
this.ingredient_height = document.getElementById('ingredient_container').clientHeight - document.getElementById('nutrition_container').clientHeight
} else {
this.ingredient_height = document.getElementById('ingredient_container').clientHeight
}
},
destroyWakeLock: function () {
if (this.wake_lock != null) {
this.wake_lock.release()
.then(() => {
this.wake_lock = null
});
}
document.removeEventListener('visibilitychange', this.visibilityChange)
},
visibilityChange: async function () {
if (this.wake_lock != null && document.visibilityState === 'visible') {
await this.requestWakeLock()
}
},
loadRecipe: function (recipe_id) {
apiLoadRecipe(recipe_id).then((recipe) => {
let total_time = 0
for (let step of recipe.steps) {
for (let ingredient of step.ingredients) {
this.$set(ingredient, "checked", false)
}
step.time_offset = total_time
total_time += step.time
}
// set start time only if there are any steps with timers (otherwise no timers are rendered)
if (total_time > 0) {
this.start_time = moment().format("yyyy-MM-DDTHH:mm")
}
if (recipe.image === null) this.printReady()
this.recipe = this.rootrecipe = recipe
this.servings = this.servings_cache[this.rootrecipe.id] = recipe.servings
this.loading = false
setTimeout(() => {
this.handleResize()
}, 100)
})
},
updateStartTime: function (e) {
this.start_time = e
},
updateIngredientCheckedState: function (e) {
for (let step of this.recipe.steps) {
for (let ingredient of step.ingredients) {
if (ingredient.id === e.id) {
this.$set(ingredient, "checked", !ingredient.checked)
}
}
}
},
quickSwitch: function (e) {
if (e === -1) {
this.recipe = this.rootrecipe
this.servings = this.servings_cache[this.rootrecipe?.id ?? 1]
} else {
this.recipe = e
this.servings = this.servings_cache?.[e.id] ?? e.servings
}
},
printReady: function () {
const template = document.createElement("template");
template.id = "printReady";
document.body.appendChild(template);
},
onImgLoad: function () {
this.printReady()
},
}, },
methods: {},
} }
</script> </script>
<style> <style>
#app > div > div {
break-inside: avoid;
}
</style> </style>

View File

@@ -164,6 +164,15 @@
</div> </div>
</div> </div>
<div class="row">
<div class="col-md-12">
<h4>{{ $t('Open_Data_Import') }}</h4>
<open-data-import-component></open-data-import-component>
</div>
</div>
<div class="row mt-4"> <div class="row mt-4">
<div class="col col-12"> <div class="col col-12">
<h4 class="mt-2"><i class="fas fa-trash"></i> {{ $t('Delete') }}</h4> <h4 class="mt-2"><i class="fas fa-trash"></i> {{ $t('Delete') }}</h4>
@@ -198,6 +207,7 @@ import GenericMultiselect from "@/components/GenericMultiselect";
import GenericModalForm from "@/components/Modals/GenericModalForm"; import GenericModalForm from "@/components/Modals/GenericModalForm";
import axios from "axios"; import axios from "axios";
import VueClipboard from 'vue-clipboard2' import VueClipboard from 'vue-clipboard2'
import OpenDataImportComponent from "@/components/OpenDataImportComponent.vue";
Vue.use(VueClipboard) Vue.use(VueClipboard)
@@ -206,7 +216,7 @@ Vue.use(BootstrapVue)
export default { export default {
name: "SpaceManageView", name: "SpaceManageView",
mixins: [ResolveUrlMixin, ToastMixin, ApiMixin], mixins: [ResolveUrlMixin, ToastMixin, ApiMixin],
components: {GenericMultiselect, GenericModalForm}, components: {GenericMultiselect, GenericModalForm, OpenDataImportComponent},
data() { data() {
return { return {
ACTIVE_SPACE_ID: window.ACTIVE_SPACE_ID, ACTIVE_SPACE_ID: window.ACTIVE_SPACE_ID,

View File

@@ -1,104 +1,42 @@
<template> <template>
<div id="app"> <div id="app">
<div class="row" v-if="food">
<div class="col-12"> <beta-warning></beta-warning>
<h2>{{ food.name }}</h2>
</div> <div v-if="metadata !== undefined">
{{ $t('Data_Import_Info') }}
<select class="form-control" v-model="selected_version">
<option v-for="v in metadata.versions" v-bind:key="v">{{ v }}</option>
</select>
<b-checkbox v-model="update_existing" class="mt-1">{{ $t('Update_Existing_Data') }}</b-checkbox>
<b-checkbox v-model="use_metric" class="mt-1">{{ $t('Use_Metric') }}</b-checkbox>
<div v-if="selected_version !== undefined" class="mt-3">
<table class="table">
<tr>
<th>{{ $t('Datatype') }}</th>
<th>{{ $t('Number of Objects') }}</th>
<th>{{ $t('Imported') }}</th>
</tr>
<tr v-for="d in metadata.datatypes" v-bind:key="d">
<td>{{ $t(d.charAt(0).toUpperCase() + d.slice(1)) }}</td>
<td>{{ metadata[selected_version][d] }}</td>
<td>
<template v-if="import_count !== undefined">{{ import_count[d] }}</template>
</td>
</tr>
</table>
<button class="btn btn-success" @click="doImport">{{ $t('Import') }}</button>
</div> </div>
<div class="row">
<div class="col-12">
<b-form v-if="food">
<b-form-group :label="$t('Name')" description="">
<b-form-input v-model="food.name"></b-form-input>
</b-form-group>
<b-form-group :label="$t('Plural')" description="">
<b-form-input v-model="food.plural_name"></b-form-input>
</b-form-group>
<b-form-group :label="$t('Recipe')" :description="$t('food_recipe_help')">
<generic-multiselect
@change="food.recipe = $event.val;"
:model="Models.RECIPE"
:initial_selection="food.recipe"
label="name"
:multiple="false"
:placeholder="$t('Recipe')"
></generic-multiselect>
</b-form-group>
<b-form-group :description="$t('OnHand_help')">
<b-form-checkbox v-model="food.food_onhand">{{ $t('OnHand') }}</b-form-checkbox>
</b-form-group>
<b-form-group :description="$t('ignore_shopping_help')">
<b-form-checkbox v-model="food.ignore_shopping">{{ $t('Ignore_Shopping') }}</b-form-checkbox>
</b-form-group>
<b-form-group :label="$t('Shopping_Category')" :description="$t('shopping_category_help')">
<generic-multiselect
@change="food.supermarket_category = $event.val;"
:model="Models.SHOPPING_CATEGORY"
:initial_selection="food.supermarket_category"
label="name"
:multiple="false"
:placeholder="$t('Shopping_Category')"
></generic-multiselect>
</b-form-group>
<hr/>
<!-- todo add conditions if false disable dont hide -->
<b-form-group :label="$t('Substitutes')" :description="$t('substitute_help')">
<generic-multiselect
@change="food.substitute = $event.val;"
:model="Models.FOOD"
:initial_selection="food.substitute"
label="name"
:multiple="false"
:placeholder="$t('Substitutes')"
></generic-multiselect>
</b-form-group>
<b-form-group :description="$t('substitute_siblings_help')">
<b-form-checkbox v-model="food.substitute_siblings">{{ $t('substitute_siblings') }}</b-form-checkbox>
</b-form-group>
<b-form-group :label="$t('InheritFields')" :description="$t('InheritFields_help')">
<generic-multiselect
@change="food.inherit_fields = $event.val;"
:model="Models.FOOD_INHERIT_FIELDS"
:initial_selection="food.inherit_fields"
label="name"
:multiple="false"
:placeholder="$t('InheritFields')"
></generic-multiselect>
</b-form-group>
<b-form-group :label="$t('ChildInheritFields')" :description="$t('ChildInheritFields_help')">
<generic-multiselect
@change="food.child_inherit_fields = $event.val;"
:model="Models.FOOD_INHERIT_FIELDS"
:initial_selection="food.child_inherit_fields"
label="name"
:multiple="false"
:placeholder="$t('ChildInheritFields')"
></generic-multiselect>
</b-form-group>
<!-- TODO change to a button -->
<b-form-group :description="$t('reset_children_help')">
<b-form-checkbox v-model="food.reset_inherit">{{ $t('reset_children') }}</b-form-checkbox>
</b-form-group>
<b-button variant="primary" @click="updateFood">{{ $t('Save') }}</b-button>
</b-form>
</div> </div>
</div> </div>
</div>
</template> </template>
@@ -107,10 +45,9 @@ import Vue from "vue"
import {BootstrapVue} from "bootstrap-vue" import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css" import "bootstrap-vue/dist/bootstrap-vue.css"
import {ApiApiFactory} from "@/utils/openapi/api"; import {ApiMixin, resolveDjangoUrl, StandardToasts} from "@/utils/utils";
import RecipeCard from "@/components/RecipeCard.vue"; import axios from "axios";
import GenericMultiselect from "@/components/GenericMultiselect.vue"; import BetaWarning from "@/components/BetaWarning.vue";
import {ApiMixin, StandardToasts} from "@/utils/utils";
Vue.use(BootstrapVue) Vue.use(BootstrapVue)
@@ -119,33 +56,39 @@ Vue.use(BootstrapVue)
export default { export default {
name: "TestView", name: "TestView",
mixins: [ApiMixin], mixins: [ApiMixin],
components: { components: {BetaWarning},
GenericMultiselect
},
data() { data() {
return { return {
food: undefined, metadata: undefined,
selected_version: undefined,
update_existing: true,
use_metric: true,
import_count: undefined,
} }
}, },
mounted() { mounted() {
this.$i18n.locale = window.CUSTOM_LOCALE this.$i18n.locale = window.CUSTOM_LOCALE
let apiClient = new ApiApiFactory()
apiClient.retrieveFood('1').then((r) => {
this.food = r.data
})
axios.get(resolveDjangoUrl('api_import_open_data')).then(r => {
this.metadata = r.data
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
})
}, },
methods: { methods: {
updateFood: function () { doImport: function () {
let apiClient = new ApiApiFactory() axios.post(resolveDjangoUrl('api_import_open_data'), {
apiClient.updateFood(this.food.id, this.food).then((r) => { 'selected_version': this.selected_version,
this.food = r.data 'selected_datatypes': this.metadata.datatypes,
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE) 'update_existing': this.update_existing,
'use_metric': this.use_metric,
}).then(r => {
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE)
this.import_count = r.data
}).catch(err => { }).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE, err)
}) })
} },
}, },
} }
</script> </script>

View File

@@ -0,0 +1,155 @@
<template>
<div id="app">
<div class="row" v-if="food">
<div class="col-12">
<h2>{{ food.name }}</h2>
</div>
</div>
<div class="row">
<div class="col-12">
<b-form v-if="food">
<b-form-group :label="$t('Name')" description="">
<b-form-input v-model="food.name"></b-form-input>
</b-form-group>
<b-form-group :label="$t('Plural')" description="">
<b-form-input v-model="food.plural_name"></b-form-input>
</b-form-group>
<b-form-group :label="$t('Recipe')" :description="$t('food_recipe_help')">
<generic-multiselect
@change="food.recipe = $event.val;"
:model="Models.RECIPE"
:initial_selection="food.recipe"
label="name"
:multiple="false"
:placeholder="$t('Recipe')"
></generic-multiselect>
</b-form-group>
<b-form-group :description="$t('OnHand_help')">
<b-form-checkbox v-model="food.food_onhand">{{ $t('OnHand') }}</b-form-checkbox>
</b-form-group>
<b-form-group :description="$t('ignore_shopping_help')">
<b-form-checkbox v-model="food.ignore_shopping">{{ $t('Ignore_Shopping') }}</b-form-checkbox>
</b-form-group>
<b-form-group :label="$t('Shopping_Category')" :description="$t('shopping_category_help')">
<generic-multiselect
@change="food.supermarket_category = $event.val;"
:model="Models.SHOPPING_CATEGORY"
:initial_selection="food.supermarket_category"
label="name"
:multiple="false"
:placeholder="$t('Shopping_Category')"
></generic-multiselect>
</b-form-group>
<hr/>
<!-- todo add conditions if false disable dont hide -->
<b-form-group :label="$t('Substitutes')" :description="$t('substitute_help')">
<generic-multiselect
@change="food.substitute = $event.val;"
:model="Models.FOOD"
:initial_selection="food.substitute"
label="name"
:multiple="false"
:placeholder="$t('Substitutes')"
></generic-multiselect>
</b-form-group>
<b-form-group :description="$t('substitute_siblings_help')">
<b-form-checkbox v-model="food.substitute_siblings">{{ $t('substitute_siblings') }}</b-form-checkbox>
</b-form-group>
<b-form-group :label="$t('InheritFields')" :description="$t('InheritFields_help')">
<generic-multiselect
@change="food.inherit_fields = $event.val;"
:model="Models.FOOD_INHERIT_FIELDS"
:initial_selection="food.inherit_fields"
label="name"
:multiple="false"
:placeholder="$t('InheritFields')"
></generic-multiselect>
</b-form-group>
<b-form-group :label="$t('ChildInheritFields')" :description="$t('ChildInheritFields_help')">
<generic-multiselect
@change="food.child_inherit_fields = $event.val;"
:model="Models.FOOD_INHERIT_FIELDS"
:initial_selection="food.child_inherit_fields"
label="name"
:multiple="false"
:placeholder="$t('ChildInheritFields')"
></generic-multiselect>
</b-form-group>
<!-- TODO change to a button -->
<b-form-group :description="$t('reset_children_help')">
<b-form-checkbox v-model="food.reset_inherit">{{ $t('reset_children') }}</b-form-checkbox>
</b-form-group>
<b-button variant="primary" @click="updateFood">{{ $t('Save') }}</b-button>
</b-form>
</div>
</div>
</div>
</template>
<script>
import Vue from "vue"
import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css"
import {ApiApiFactory} from "@/utils/openapi/api";
import RecipeCard from "@/components/RecipeCard.vue";
import GenericMultiselect from "@/components/GenericMultiselect.vue";
import {ApiMixin, StandardToasts} from "@/utils/utils";
Vue.use(BootstrapVue)
export default {
name: "TestView",
mixins: [ApiMixin],
components: {
GenericMultiselect
},
data() {
return {
food: undefined,
}
},
mounted() {
this.$i18n.locale = window.CUSTOM_LOCALE
let apiClient = new ApiApiFactory()
apiClient.retrieveFood('1').then((r) => {
this.food = r.data
})
},
methods: {
updateFood: function () {
let apiClient = new ApiApiFactory()
apiClient.updateFood(this.food.id, this.food).then((r) => {
this.food = r.data
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
}
},
}
</script>
<style>
</style>

View File

@@ -1,6 +1,6 @@
<template> <template>
<!-- bottom button nav --> <!-- bottom button nav -->
<div class="fixed-bottom p-1 pt-2 pl-2 pr-2 border-top text-center d-lg-none" style="background: white"> <div class="fixed-bottom p-1 pt-2 pl-2 pr-2 border-top text-center d-lg-none d-print-none" style="background: white">
<div class="d-flex flex-row justify-content-around"> <div class="d-flex flex-row justify-content-around">
<div class="flex-column" v-if="show_button_1"> <div class="flex-column" v-if="show_button_1">
<slot name="button_1"> <slot name="button_1">

View File

@@ -0,0 +1,412 @@
<template>
<div>
<b-modal :id="id" size="xl" @hidden="cancelAction" :body-class="`pr-3 pl-3`">
<template v-slot:modal-title>
<div class="row" v-if="food">
<div class="col-12">
<h2>{{ food.name }} <small class="text-muted" v-if="food.plural_name">{{
food.plural_name
}}</small>
</h2>
</div>
</div>
</template>
<div>
<b-tabs content-class="mt-3" v-if="food">
<b-tab title="General" active>
<b-form>
<b-form-group :label="$t('Name')" description="">
<b-form-input v-model="food.name"></b-form-input>
</b-form-group>
<b-form-group :label="$t('Plural')" description="">
<b-form-input v-model="food.plural_name"></b-form-input>
</b-form-group>
<!-- Food properties -->
<h5><i class="fas fa-database"></i> {{ $t('Properties') }}</h5>
<b-form-group :label="$t('Properties Food Amount')" description=""> <!-- TODO localize -->
<b-form-input v-model="food.properties_food_amount"></b-form-input>
</b-form-group>
<b-form-group :label="$t('Properties Food Unit')" description=""> <!-- TODO localize -->
<generic-multiselect
@change="food.properties_food_unit = $event.val;"
:model="Models.UNIT"
:initial_single_selection="food.properties_food_unit"
label="name"
:multiple="false"
:placeholder="$t('Unit')"
></generic-multiselect>
</b-form-group>
<table class="table table-bordered">
<thead>
<tr>
<th> {{ $t('Property Amount') }}</th> <!-- TODO localize -->
<th> {{ $t('Property Type') }}</th> <!-- TODO localize -->
<th></th>
<th></th>
</tr>
</thead>
<tr v-for="fp in food.properties" v-bind:key="fp.id">
<td><input v-model="fp.property_amount" type="number"> <span
v-if="fp.property_type">{{ fp.property_type.unit }}</span></td>
<td>
<generic-multiselect
@change="fp.property_type = $event.val"
:initial_single_selection="fp.property_type"
label="name" :model="Models.PROPERTY_TYPE"
:multiple="false"/>
</td>
<td> / <span>{{ food.properties_food_amount }} <span
v-if="food.properties_food_unit !== null">{{
food.properties_food_unit.name
}}</span></span>
</td>
<td>
<button class="btn btn-danger btn-small" @click="deleteProperty(fp)"><i
class="fas fa-trash-alt"></i></button>
</td>
</tr>
</table>
<div class="text-center">
<b-button-group>
<b-btn class="btn btn-success shadow-none" @click="addProperty()"><i
class="fa fa-plus"></i>
</b-btn>
<b-btn class="btn btn-secondary shadow-none" @click="addAllProperties()"><i
class="fa fa-plus"> <i class="ml-1 fas fa-list"></i></i>
</b-btn>
</b-button-group>
</div>
<b-form-group :label="$t('Shopping_Category')" :description="$t('shopping_category_help')">
<generic-multiselect
@change="food.supermarket_category = $event.val;"
:model="Models.SHOPPING_CATEGORY"
:initial_single_selection="food.supermarket_category"
label="name"
:multiple="false"
:allow_create="true"
:placeholder="$t('Shopping_Category')"
></generic-multiselect>
</b-form-group>
</b-form>
</b-tab>
<b-tab title="Conversions" @click="loadUnitConversions" v-if="this.food.id !== undefined">
<b-row v-for="uc in unit_conversions" :key="uc">
<b-col>
<span v-if="uc.id">
<b-btn class="btn btn-sm" variant="danger" @click="deleteUnitConversion(uc)"><i class="fas fa-trash-alt"></i></b-btn>
{{ uc.base_amount }}
{{ uc.base_unit.name }}
=
{{ uc.converted_amount }}
{{ uc.converted_unit.name }}
</span>
<b-form class="mt-1">
<b-input-group>
<b-input v-model="uc.base_amount" @change="uc.changed = true"></b-input>
<b-input-group-append>
<generic-multiselect
@change="uc.base_unit = $event.val; uc.changed = true"
:initial_single_selection="uc.base_unit"
label="name" :model="Models.UNIT"
:multiple="false"/>
</b-input-group-append>
</b-input-group>
<b-input-group>
<b-input v-model="uc.converted_amount" @change="uc.changed = true"></b-input>
<b-input-group-append>
<generic-multiselect
@change="uc.converted_unit = $event.val; uc.changed = true"
:initial_single_selection="uc.converted_unit"
label="name" :model="Models.UNIT"
:multiple="false"/>
</b-input-group-append>
</b-input-group>
</b-form>
</b-col>
<hr style="height: 1px"/>
</b-row>
<b-row>
<b-col class="text-center">
<b-btn variant="success" @click="addUnitConversion"><i class="fa fa-plus"></i></b-btn>
</b-col>
</b-row>
</b-tab>
<b-tab title="More">
<b-form>
<b-form-group :label="$t('Recipe')" :description="$t('food_recipe_help')">
<generic-multiselect
@change="food.recipe = $event.val;"
:model="Models.RECIPE"
:initial_single_selection="food.recipe"
label="name"
:multiple="false"
:placeholder="$t('Recipe')"
></generic-multiselect>
</b-form-group>
<b-form-group :description="$t('OnHand_help')">
<b-form-checkbox v-model="food.food_onhand">{{ $t('OnHand') }}</b-form-checkbox>
</b-form-group>
<b-form-group :description="$t('ignore_shopping_help')">
<b-form-checkbox v-model="food.ignore_shopping">{{
$t('Ignore_Shopping')
}}
</b-form-checkbox>
</b-form-group>
<hr/>
<!-- todo add conditions if false disable dont hide -->
<b-form-group :label="$t('Substitutes')" :description="$t('substitute_help')">
<generic-multiselect
@change="food.substitute = $event.val;"
:model="Models.FOOD"
:initial_selection="food.substitute"
label="name"
:multiple="true"
:placeholder="$t('Substitutes')"
></generic-multiselect>
</b-form-group>
<b-form-group :description="$t('substitute_siblings_help')">
<b-form-checkbox v-model="food.substitute_siblings">{{
$t('substitute_siblings')
}}
</b-form-checkbox>
</b-form-group>
<b-form-group :label="$t('InheritFields')" :description="$t('InheritFields_help')">
<generic-multiselect
@change="food.inherit_fields = $event.val;"
:model="Models.FOOD_INHERIT_FIELDS"
:initial_selection="food.inherit_fields"
label="name"
:multiple="true"
:placeholder="$t('InheritFields')"
></generic-multiselect>
</b-form-group>
<b-form-group :label="$t('ChildInheritFields')"
:description="$t('ChildInheritFields_help')">
<generic-multiselect
@change="food.child_inherit_fields = $event.val;"
:model="Models.FOOD_INHERIT_FIELDS"
:initial_sselection="food.child_inherit_fields"
label="name"
:multiple="true"
:placeholder="$t('ChildInheritFields')"
></generic-multiselect>
</b-form-group>
<!-- TODO change to a button -->
<b-form-group :description="$t('reset_children_help')">
<b-form-checkbox v-model="food.reset_inherit">{{
$t('reset_children')
}}
</b-form-checkbox>
</b-form-group>
</b-form>
</b-tab>
</b-tabs>
</div>
<template v-slot:modal-footer>
<b-button variant="primary" @click="updateFood">{{ $t('Save') }}</b-button>
</template>
</b-modal>
</div>
</template>
<script>
import Vue from "vue"
import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css"
import {ApiApiFactory} from "@/utils/openapi/api";
import GenericMultiselect from "@/components/GenericMultiselect.vue";
import {ApiMixin, formFunctions, getForm, StandardToasts} from "@/utils/utils";
Vue.use(BootstrapVue)
export default {
name: "FoodEditor",
mixins: [ApiMixin],
components: {
GenericMultiselect
},
props: {
id: {type: String, default: 'id_food_edit_modal_modal'},
show: {required: true, type: Boolean, default: false},
item1: {
type: Object,
default: undefined
},
},
watch: {
show: function () {
if (this.show) {
this.$bvModal.show(this.id)
} else {
this.$bvModal.hide(this.id)
}
},
},
data() {
return {
food: undefined,
unit_conversions: []
}
},
mounted() {
this.$bvModal.show(this.id)
this.$i18n.locale = window.CUSTOM_LOCALE
let apiClient = new ApiApiFactory()
let pf
if (this.item1.id !== undefined) {
pf = apiClient.retrieveFood(this.item1.id).then((r) => {
this.food = r.data
this.food.properties_food_unit = {name: 'g'}
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
})
} else {
this.food = {
name: "",
plural_name: "",
description: "",
shopping: false,
recipe: null,
properties: [],
properties_food_amount: 100,
properties_food_unit: {name: 'g'},
food_onhand: false,
supermarket_category: null,
parent: null,
numchild: 0,
inherit_fields: [],
ignore_shopping: false,
substitute: [],
substitute_siblings: false,
substitute_children: false,
substitute_onhand: false,
child_inherit_fields: [],
}
}
},
methods: {
updateFood: function () {
let apiClient = new ApiApiFactory()
if (this.food.id !== undefined) {
apiClient.updateFood(this.food.id, this.food).then((r) => {
this.food = r.data
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
} else {
apiClient.createFood(this.food).then((r) => {
this.food = r.data
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE)
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
}
this.unit_conversions.forEach(uc => {
if (uc.changed === true) {
if (uc.id === undefined) {
apiClient.createUnitConversion(uc).then(r => {
uc = r.data
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE, err, true)
})
} else {
apiClient.updateUnitConversion(uc.id, uc).then(r => {
uc = r.data
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err, true)
})
}
}
})
},
addProperty: function () {
this.food.properties.push({property_type: null, property_amount: 0})
},
addAllProperties: function () {
let apiClient = new ApiApiFactory()
apiClient.listPropertyTypes().then(r => {
r.data.forEach(x => {
this.food.properties.push({property_type: x, property_amount: 0})
})
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
})
},
deleteProperty: function (p) {
this.food.properties = this.food.properties.filter(x => x !== p)
},
cancelAction: function () {
this.$emit("hidden", "")
},
loadUnitConversions: function () {
let apiClient = new ApiApiFactory()
apiClient.listUnitConversions(this.food.id).then(r => {
this.unit_conversions = r.data
})
},
addUnitConversion: function () {
this.unit_conversions.push(
{
food: this.food,
base_amount: 1,
base_unit: null,
converted_amount: 0,
converted_unit: null,
}
)
},
deleteUnitConversion: function (uc) {
this.unit_conversions = this.unit_conversions.filter(u => u !== uc)
let apiClient = new ApiApiFactory()
apiClient.destroyUnitConversion(uc.id).then(r => {
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_DELETE)
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err)
})
}
},
}
</script>
<style>
</style>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<span class="pl-1" v-if="recipe.last_cooked !== null"> <span class="pl-1" v-if="recipe.last_cooked !== undefined && recipe.last_cooked !== null">
<b-badge pill variant="primary" class="font-weight-normal"><i class="fas fa-utensils"></i> {{ <b-badge pill variant="primary" class="font-weight-normal"><i class="fas fa-utensils"></i> {{
formatDate(recipe.last_cooked) formatDate(recipe.last_cooked)
}}</b-badge> }}</b-badge>

View File

@@ -1,6 +1,10 @@
<template> <template>
<div> <div>
<b-modal :id="'modal_' + id" @hidden="cancelAction"> <template v-if="form_component !== undefined">
<component :is="form_component" :id="'modal_' + id" :show="show" @hidden="cancelAction" :item1="item1"></component>
</template>
<template v-else>
<b-modal :id="'modal_' + id" @hidden="cancelAction" size="lg">
<template v-slot:modal-title> <template v-slot:modal-title>
<h4 class="d-inline">{{ form.title }}</h4> <h4 class="d-inline">{{ form.title }}</h4>
<help-badge v-if="form.show_help" @show="show_help = true" @hide="show_help = false" :component="`GenericModal${form.title}`"/> <help-badge v-if="form.show_help" @show="show_help = true" @hide="show_help = false" :component="`GenericModal${form.title}`"/>
@@ -9,7 +13,7 @@
<p v-if="visibleCondition(f, 'instruction')">{{ f.label }}</p> <p v-if="visibleCondition(f, 'instruction')">{{ f.label }}</p>
<lookup-input v-if="visibleCondition(f, 'lookup')" :form="f" :model="listModel(f.list)" @change="storeValue" :help="showHelp && f.help"/> <lookup-input v-if="visibleCondition(f, 'lookup')" :form="f" :model="listModel(f.list)" @change="storeValue" :help="showHelp && f.help"/>
<checkbox-input class="mb-3" v-if="visibleCondition(f, 'checkbox')" :label="f.label" :value="f.value" :field="f.field" :help="showHelp && f.help"/> <checkbox-input class="mb-3" v-if="visibleCondition(f, 'checkbox')" :label="f.label" :value="f.value" :field="f.field" :help="showHelp && f.help"/>
<text-input v-if="visibleCondition(f, 'text')" :label="f.label" :value="f.value" :field="f.field" :placeholder="f.placeholder" :help="showHelp && f.help" :subtitle="f.subtitle" /> <text-input v-if="visibleCondition(f, 'text')" :label="f.label" :value="f.value" :field="f.field" :placeholder="f.placeholder" :help="showHelp && f.help" :subtitle="f.subtitle" :disabled="f.disabled"/>
<choice-input v-if="visibleCondition(f, 'choice')" :label="f.label" :value="f.value" :field="f.field" :options="f.options" :placeholder="f.placeholder"/> <choice-input v-if="visibleCondition(f, 'choice')" :label="f.label" :value="f.value" :field="f.field" :options="f.options" :placeholder="f.placeholder"/>
<emoji-input v-if="visibleCondition(f, 'emoji')" :label="f.label" :value="f.value" :field="f.field" @change="storeValue"/> <emoji-input v-if="visibleCondition(f, 'emoji')" :label="f.label" :value="f.value" :field="f.field" @change="storeValue"/>
<file-input v-if="visibleCondition(f, 'file')" :label="f.label" :value="f.value" :field="f.field" @change="storeValue"/> <file-input v-if="visibleCondition(f, 'file')" :label="f.label" :value="f.value" :field="f.field" @change="storeValue"/>
@@ -29,6 +33,8 @@
</div> </div>
</template> </template>
</b-modal> </b-modal>
</template>
</div> </div>
</template> </template>
@@ -54,7 +60,18 @@ import NumberInput from "@/components/Modals/NumberInput.vue";
export default { export default {
name: "GenericModalForm", name: "GenericModalForm",
components: { FileInput, CheckboxInput, LookupInput, TextInput, EmojiInput, ChoiceInput, SmallText, HelpBadge,DateInput, NumberInput }, components: {
FileInput,
CheckboxInput,
LookupInput,
TextInput,
EmojiInput,
ChoiceInput,
SmallText,
HelpBadge,
DateInput,
NumberInput
},
mixins: [ApiMixin, ToastMixin], mixins: [ApiMixin, ToastMixin],
props: { props: {
model: {required: true, type: Object}, model: {required: true, type: Object},
@@ -77,6 +94,7 @@ export default {
}, },
}, },
show: {required: true, type: Boolean, default: false}, show: {required: true, type: Boolean, default: false},
models: {required: false, type: Function, default: null}
}, },
data() { data() {
return { return {
@@ -92,6 +110,10 @@ export default {
mounted() { mounted() {
this.id = Math.random() this.id = Math.random()
this.$root.$on("change", this.storeValue) // bootstrap modal placed at document so have to listen at root of component this.$root.$on("change", this.storeValue) // bootstrap modal placed at document so have to listen at root of component
if (this.models !== null) {
this.Models = this.models // override models definition file with prop
}
}, },
computed: { computed: {
advancedForm() { advancedForm() {
@@ -111,6 +133,15 @@ export default {
return undefined return undefined
} }
}, },
form_component() {
// TODO this leads webpack to create one .js file for each component in this folder because at runtime any one of them could be requested
// TODO this is not necessarily bad but maybe there are better options to do this
if (this.form.component !== undefined) {
return () => import(/* webpackChunkName: "header-component" */ `@/components/${this.form.component}`)
} else {
return undefined
}
},
}, },
watch: { watch: {
show: function () { show: function () {
@@ -153,6 +184,7 @@ export default {
if (this.dirty) { if (this.dirty) {
this.dirty = false this.dirty = false
this.$emit("finish-action", "cancel") this.$emit("finish-action", "cancel")
this.$emit("hidden")
} }
}, },
storeValue: function (field, value) { storeValue: function (field, value) {
@@ -250,7 +282,10 @@ export default {
target: this.form_data.target.id, target: this.form_data.target.id,
}) })
.then((result) => { .then((result) => {
this.$emit("finish-action", { target: this.form_data.target.id, target_object: this.form_data.target }) //TODO temporary workaround to not change other apis this.$emit("finish-action", {
target: this.form_data.target.id,
target_object: this.form_data.target
}) //TODO temporary workaround to not change other apis
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_MERGE) StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_MERGE)
}) })
.catch((err) => { .catch((err) => {

View File

@@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<b-form-group v-bind:label="label" class="mb-3"> <b-form-group v-bind:label="label" class="mb-3">
<b-form-input v-model="new_value" type="text" :placeholder="placeholder"></b-form-input> <b-form-input v-model="new_value" type="text" :placeholder="placeholder" :disabled="disabled"></b-form-input>
<em v-if="help" class="small text-muted">{{ help }}</em> <em v-if="help" class="small text-muted">{{ help }}</em>
<small v-if="subtitle" class="text-muted">{{ subtitle }}</small> <small v-if="subtitle" class="text-muted">{{ subtitle }}</small>
</b-form-group> </b-form-group>
@@ -18,6 +18,7 @@ export default {
placeholder: { type: String, default: "You Should Add Placeholder Text" }, placeholder: { type: String, default: "You Should Add Placeholder Text" },
help: { type: String, default: undefined }, help: { type: String, default: undefined },
subtitle: { type: String, default: undefined }, subtitle: { type: String, default: undefined },
disabled: { type: Boolean, default: false }
}, },
data() { data() {
return { return {

View File

@@ -0,0 +1,94 @@
<template>
<div>
<beta-warning></beta-warning>
<div v-if="metadata !== undefined">
{{ $t('Data_Import_Info') }}
<a href="https://github.com/TandoorRecipes/open-tandoor-data" target="_blank" rel="noreferrer nofollow">{{$t('Learn_More')}}</a>
<select class="form-control" v-model="selected_version">
<option v-for="v in metadata.versions" v-bind:key="v">{{ v }}</option>
</select>
<b-checkbox v-model="update_existing" class="mt-1">{{ $t('Update_Existing_Data') }}</b-checkbox>
<b-checkbox v-model="use_metric" class="mt-1">{{ $t('Use_Metric') }}</b-checkbox>
<div v-if="selected_version !== undefined" class="mt-3">
<table class="table">
<tr>
<th>{{ $t('Datatype') }}</th>
<th>{{ $t('Number of Objects') }}</th>
<th>{{ $t('Imported') }}</th>
</tr>
<tr v-for="d in metadata.datatypes" v-bind:key="d">
<td>{{ $t(d.charAt(0).toUpperCase() + d.slice(1)) }}</td>
<td>{{ metadata[selected_version][d] }}</td>
<td>
<template v-if="import_count !== undefined">{{ import_count[d] }}</template>
</td>
</tr>
</table>
<button class="btn btn-success" @click="doImport">{{ $t('Import') }}</button>
</div>
</div>
</div>
</template>
<script>
import Vue from "vue"
import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css"
import {ApiMixin, resolveDjangoUrl, StandardToasts} from "@/utils/utils";
import axios from "axios";
import BetaWarning from "@/components/BetaWarning.vue";
Vue.use(BootstrapVue)
export default {
name: "OpenDataImportComponent",
mixins: [ApiMixin],
components: {BetaWarning},
data() {
return {
metadata: undefined,
selected_version: undefined,
update_existing: true,
use_metric: true,
import_count: undefined,
}
},
mounted() {
this.$i18n.locale = window.CUSTOM_LOCALE
axios.get(resolveDjangoUrl('api_import_open_data')).then(r => {
this.metadata = r.data
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
})
},
methods: {
doImport: function () {
axios.post(resolveDjangoUrl('api_import_open_data'), {
'selected_version': this.selected_version,
'selected_datatypes': this.metadata.datatypes,
'update_existing': this.update_existing,
'use_metric': this.use_metric,
}).then(r => {
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE)
this.import_count = r.data
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE, err)
})
},
},
}
</script>

View File

@@ -0,0 +1,195 @@
<template>
<div>
<div class="card p-4 pb-2" v-if="recipe !== undefined">
<b-row>
<b-col>
<h5><i class="fas fa-database"></i> {{ $t('Properties') }}</h5>
</b-col>
<b-col class="text-right">
<span v-if="!show_total">{{ $t('per_serving') }} </span>
<span v-if="show_total">{{ $t('total') }} </span>
<a href="#" @click="show_total = !show_total">
<i class="fas fa-toggle-on" v-if="!show_total"></i>
<i class="fas fa-toggle-off" v-if="show_total"></i>
</a>
<div v-if="hasRecipeProperties && hasFoodProperties">
<span v-if="!show_recipe_properties">{{ $t('Food') }} </span>
<span v-if="show_recipe_properties">{{ $t('Recipe') }} </span>
<a href="#" @click="show_recipe_properties = !show_recipe_properties">
<i class="fas fa-toggle-on" v-if="!show_recipe_properties"></i>
<i class="fas fa-toggle-off" v-if="show_recipe_properties"></i>
</a>
</div>
</b-col>
</b-row>
<table class="table table-bordered table-sm">
<tr v-for="p in property_list" v-bind:key="`id_${p.id}`">
<td>
{{ p.icon }} {{ p.name }}
</td>
<td class="text-right">{{ get_amount(p.property_amount) }}</td>
<td class=""> {{ p.unit }}</td>
<td class="align-middle text-center" v-if="!show_recipe_properties">
<a href="#" @click="selected_property = p">
<i v-if="p.missing_value" class="text-warning fas fa-exclamation-triangle"></i>
<i v-if="!p.missing_value" class="text-muted fas fa-info-circle"></i>
</a>
</td>
</tr>
</table>
</div>
<b-modal id="id_modal_property_overview" :title="selected_property.name" v-model="show_modal" v-if="selected_property !== undefined"
@hidden="selected_property = undefined">
<template v-if="selected_property !== undefined">
{{ selected_property.description }}
<table class="table table-bordered">
<tr v-for="f in selected_property.food_values"
v-bind:key="`id_${selected_property.id}_food_${f.id}`">
<td><a href="#" @click="openFoodEditModal(f)">{{ f.food }}</a></td>
<td>{{ f.value }} {{ selected_property.unit }}</td>
</tr>
</table>
</template>
</b-modal>
<generic-modal-form
:model="Models.FOOD"
:action="Actions.UPDATE"
:item1="selected_food"
:show="show_food_edit_modal"
@hidden="foodEditorHidden"
>
</generic-modal-form>
</div>
</template>
<script>
import {ApiMixin, StandardToasts} from "@/utils/utils";
import GenericModalForm from "@/components/Modals/GenericModalForm.vue";
import {ApiApiFactory} from "@/utils/openapi/api";
export default {
name: "PropertyViewComponent",
mixins: [ApiMixin],
components: {GenericModalForm},
props: {
recipe: Object,
servings: Number,
},
data() {
return {
selected_property: undefined,
selected_food: undefined,
show_food_edit_modal: false,
show_total: false,
show_recipe_properties: false,
}
},
computed: {
show_modal: function () {
return this.selected_property !== undefined
},
hasRecipeProperties: function () {
return this.recipe.properties.length !== 0
},
hasFoodProperties: function () {
let has_food_properties = false
for (const [key, fp] of Object.entries(this.recipe.food_properties)) {
if (fp.total_value !== 0) {
has_food_properties = true
}
}
return has_food_properties
},
property_list: function () {
let pt_list = []
if (this.show_recipe_properties) {
this.recipe.properties.forEach(rp => {
pt_list.push(
{
'id': rp.property_type.id,
'name': rp.property_type.name,
'description': rp.property_type.description,
'icon': rp.property_type.icon,
'food_values': [],
'property_amount': rp.property_amount,
'missing_value': false,
'unit': rp.property_type.unit,
}
)
})
} else {
for (const [key, fp] of Object.entries(this.recipe.food_properties)) {
pt_list.push(
{
'id': fp.id,
'name': fp.name,
'description': fp.description,
'icon': fp.icon,
'food_values': fp.food_values,
'property_amount': fp.total_value,
'missing_value': fp.missing_value,
'unit': fp.unit,
}
)
}
}
return pt_list
}
},
mounted() {
if (this.hasRecipeProperties && !this.hasFoodProperties) {
this.show_recipe_properties = true
}
},
methods: {
get_amount: function (amount) {
if (this.show_total) {
return (amount * (this.servings / this.recipe.servings)).toLocaleString(window.CUSTOM_LOCALE, {
'maximumFractionDigits': 2,
'minimumFractionDigits': 2
})
} else {
return (amount / this.recipe.servings).toLocaleString(window.CUSTOM_LOCALE, {
'maximumFractionDigits': 2,
'minimumFractionDigits': 2
})
}
},
openFoodEditModal: function (food) {
console.log(food)
let apiClient = ApiApiFactory()
apiClient.retrieveFood(food.id).then(r => {
this.selected_food = r.data;
this.show_food_edit_modal = true
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
})
},
foodEditorHidden: function () {
this.show_food_edit_modal = false;
this.$emit("foodUpdated", "")
}
},
}
</script>
<style scoped>
</style>

View File

@@ -35,12 +35,12 @@
<div class="card-img-overlay d-flex flex-column justify-content-left float-left text-left pt-2" style="width:40%" <div class="card-img-overlay d-flex flex-column justify-content-left float-left text-left pt-2" style="width:40%"
v-if="recipe.working_time !== 0 || recipe.waiting_time !== 0"> v-if="recipe.working_time !== 0 || recipe.waiting_time !== 0">
<b-badge pill variant="light" class="mt-1 font-weight-normal" v-if="recipe.working_time !== 0"> <b-badge pill variant="light" class="mt-1 font-weight-normal" v-if="recipe.working_time !== 0 && recipe.working_time !== undefined">
<i <i
class="fa fa-clock"></i> {{ working_time }} class="fa fa-clock"></i> {{ working_time }}
</b-badge> </b-badge>
<b-badge pill variant="secondary" class="mt-1 font-weight-normal" <b-badge pill variant="secondary" class="mt-1 font-weight-normal"
v-if="recipe.waiting_time !== 0"> v-if="recipe.waiting_time !== 0 && recipe.waiting_time !== undefined">
<i class="fa fa-pause"></i> {{ waiting_time }} <i class="fa fa-pause"></i> {{ waiting_time }}
</b-badge> </b-badge>
</div> </div>
@@ -58,7 +58,7 @@
<div class="justify-content-end"> <div class="justify-content-end">
<recipe-context-menu :recipe="recipe" class="justify-content-end float-right align-items-end pr-0" <recipe-context-menu :recipe="recipe" class="justify-content-end float-right align-items-end pr-0"
:disabled_options="context_disabled_options" :disabled_options="context_disabled_options"
v-if="recipe !== null"></recipe-context-menu> v-if="recipe !== null && show_context_menu"></recipe-context-menu>
</div> </div>
</div> </div>
@@ -89,7 +89,7 @@
</div> </div>
</transition> </transition>
<b-badge pill variant="info" v-if="!recipe.internal">{{ $t("External") }}</b-badge> <b-badge pill variant="info" v-if="recipe.internal !== undefined && !recipe.internal">{{ $t("External") }}</b-badge>
</template> </template>
</b-card-text> </b-card-text>

View File

@@ -0,0 +1,351 @@
<template>
<div>
<template v-if="loading">
<loading-spinner></loading-spinner>
</template>
<div v-if="!loading" style="padding-bottom: 60px">
<RecipeSwitcher ref="ref_recipe_switcher" @switch="quickSwitch($event)"/>
<div class="row">
<div class="col-12" style="text-align: center">
<h3>{{ recipe.name }}</h3>
</div>
</div>
<div class="row text-center">
<div class="col col-md-12">
<recipe-rating :recipe="recipe"></recipe-rating>
<last-cooked :recipe="recipe" class="mt-2"></last-cooked>
</div>
</div>
<div class="my-auto">
<div class="col-12" style="text-align: center">
<i>{{ recipe.description }}</i>
</div>
</div>
<div style="text-align: center">
<keywords-component :recipe="recipe"></keywords-component>
</div>
<hr/>
<div class="row align-items-center">
<div class="col col-md-3">
<div class="d-flex">
<div class="my-auto mr-1">
<i class="fas fa-fw fa-user-clock fa-2x text-primary"></i>
</div>
<div class="my-auto mr-1">
<span class="text-primary"><b>{{ $t("Preparation") }}</b></span><br/>
{{ working_time }}
</div>
</div>
</div>
<div class="col col-md-3">
<div class="row d-flex">
<div class="my-auto mr-1">
<i class="far fa-fw fa-clock fa-2x text-primary"></i>
</div>
<div class="my-auto mr-1">
<span class="text-primary"><b>{{ $t("Waiting") }}</b></span><br/>
{{ waiting_time }}
</div>
</div>
</div>
<div class="col col-md-4 col-10 mt-2 mt-md-0">
<div class="d-flex">
<div class="my-auto mr-1">
<i class="fas fa-fw fa-pizza-slice fa-2x text-primary"></i>
</div>
<div class="my-auto mr-1">
<CustomInputSpinButton v-model.number="servings"/>
</div>
<div class="my-auto mr-1">
<span class="text-primary">
<b>
<template v-if="recipe.servings_text === ''">{{ $t("Servings") }}</template>
<template v-else>{{ recipe.servings_text }}</template>
</b>
</span>
</div>
</div>
</div>
<div class="col col-md-2 col-2 mt-2 mt-md-0 text-right">
<recipe-context-menu v-bind:recipe="recipe" :servings="servings"
:disabled_options="{print:false}"></recipe-context-menu>
</div>
</div>
<hr/>
<div class="row">
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2"
v-if="recipe && ingredient_count > 0 && (recipe.show_ingredient_overview || recipe.steps.length < 2)">
<ingredients-card
:recipe="recipe.id"
:steps="recipe.steps"
:ingredient_factor="ingredient_factor"
:servings="servings"
:header="true"
id="ingredient_container"
@checked-state-changed="updateIngredientCheckedState"
@change-servings="servings = $event"
/>
</div>
<div class="col-12 order-1 col-sm-12 order-sm-1 col-md-6 order-md-2">
<div class="row">
<div class="col-12">
<img class="img img-fluid rounded" :src="recipe.image" :alt="$t('Recipe_Image')"
v-if="recipe.image !== null" @load="onImgLoad"
:style="{ 'max-height': ingredient_height }"/>
</div>
</div>
</div>
</div>
<template v-if="!recipe.internal">
<div v-if="recipe.file_path.includes('.pdf')">
<PdfViewer :recipe="recipe"></PdfViewer>
</div>
<div
v-if="recipe.file_path.includes('.png') || recipe.file_path.includes('.jpg') || recipe.file_path.includes('.jpeg') || recipe.file_path.includes('.gif')">
<ImageViewer :recipe="recipe"></ImageViewer>
</div>
</template>
<div v-for="(s, index) in recipe.steps" v-bind:key="s.id" style="margin-top: 1vh">
<step-component
:recipe="recipe"
:step="s"
:ingredient_factor="ingredient_factor"
:index="index"
:start_time="start_time"
@update-start-time="updateStartTime"
@checked-state-changed="updateIngredientCheckedState"
></step-component>
</div>
<div v-if="recipe.source_url !== null">
<h6 class="d-print-none"><i class="fas fa-file-import"></i> {{ $t("Imported_From") }}</h6>
<span class="text-muted mt-1"><a style="overflow-wrap: break-word;"
:href="recipe.source_url">{{ recipe.source_url }}</a></span>
</div>
<div class="row" style="margin-top: 2vh; ">
<div class="col-lg-6 offset-lg-3 col-12">
<property-view-component :recipe="recipe" :servings="servings" @foodUpdated="loadRecipe(recipe.id)"></property-view-component>
</div>
</div>
</div>
<add-recipe-to-book :recipe="recipe"></add-recipe-to-book>
<div class="row text-center d-print-none" style="margin-top: 3vh; margin-bottom: 3vh"
v-if="share_uid !== 'None' && !loading">
<div class="col col-md-12">
<import-tandoor></import-tandoor> <br/>
<a :href="resolveDjangoUrl('view_report_share_abuse', share_uid)" class="mt-3">{{ $t("Report Abuse") }}</a>
</div>
</div>
</div>
</template>
<script>
import Vue from "vue"
import {BootstrapVue} from "bootstrap-vue"
import "bootstrap-vue/dist/bootstrap-vue.css"
import {apiLoadRecipe} from "@/utils/api"
import RecipeContextMenu from "@/components/RecipeContextMenu"
import {ResolveUrlMixin, ToastMixin, calculateHourMinuteSplit} from "@/utils/utils"
import PdfViewer from "@/components/PdfViewer"
import ImageViewer from "@/components/ImageViewer"
import moment from "moment"
import LoadingSpinner from "@/components/LoadingSpinner"
import AddRecipeToBook from "@/components/Modals/AddRecipeToBook"
import RecipeRating from "@/components/RecipeRating"
import LastCooked from "@/components/LastCooked"
import IngredientsCard from "@/components/IngredientsCard"
import StepComponent from "@/components/StepComponent"
import KeywordsComponent from "@/components/KeywordsComponent"
import RecipeSwitcher from "@/components/Buttons/RecipeSwitcher"
import CustomInputSpinButton from "@/components/CustomInputSpinButton"
import {ApiApiFactory} from "@/utils/openapi/api";
import ImportTandoor from "@/components/Modals/ImportTandoor.vue";
import PropertyViewComponent from "@/components/PropertyViewComponent.vue";
Vue.prototype.moment = moment
Vue.use(BootstrapVue)
export default {
name: "RecipeView",
mixins: [ResolveUrlMixin, ToastMixin],
components: {
ImportTandoor,
LastCooked,
RecipeRating,
PdfViewer,
ImageViewer,
IngredientsCard,
StepComponent,
RecipeContextMenu,
KeywordsComponent,
LoadingSpinner,
AddRecipeToBook,
RecipeSwitcher,
CustomInputSpinButton,
PropertyViewComponent,
},
computed: {
ingredient_factor: function () {
return this.servings / this.recipe.servings
},
ingredient_count() {
return this.recipe?.steps.map((x) => x.ingredients).flat().length
},
working_time: function () {
return calculateHourMinuteSplit(this.recipe.working_time)
},
waiting_time: function () {
return calculateHourMinuteSplit(this.recipe.waiting_time)
},
},
data() {
return {
loading: true,
recipe: undefined,
rootrecipe: undefined,
servings: 1,
servings_cache: {},
start_time: "",
share_uid: window.SHARE_UID,
wake_lock: null,
ingredient_height: '250',
}
},
watch: {
servings(newVal, oldVal) {
this.servings_cache[this.recipe.id] = this.servings
},
},
mounted() {
this.loadRecipe(window.RECIPE_ID)
this.$i18n.locale = window.CUSTOM_LOCALE
this.requestWakeLock()
window.addEventListener('resize', this.handleResize);
},
beforeUnmount() {
this.destroyWakeLock()
},
methods: {
requestWakeLock: async function () {
if ('wakeLock' in navigator) {
try {
this.wake_lock = await navigator.wakeLock.request('screen')
document.addEventListener('visibilitychange', this.visibilityChange)
} catch (err) {
console.log(err)
}
}
},
handleResize: function () {
if (document.getElementById('nutrition_container') !== null) {
this.ingredient_height = document.getElementById('ingredient_container').clientHeight - document.getElementById('nutrition_container').clientHeight
} else {
this.ingredient_height = document.getElementById('ingredient_container').clientHeight
}
},
destroyWakeLock: function () {
if (this.wake_lock != null) {
this.wake_lock.release()
.then(() => {
this.wake_lock = null
});
}
document.removeEventListener('visibilitychange', this.visibilityChange)
},
visibilityChange: async function () {
if (this.wake_lock != null && document.visibilityState === 'visible') {
await this.requestWakeLock()
}
},
loadRecipe: function (recipe_id) {
apiLoadRecipe(recipe_id).then((recipe) => {
let total_time = 0
for (let step of recipe.steps) {
for (let ingredient of step.ingredients) {
this.$set(ingredient, "checked", false)
}
step.time_offset = total_time
total_time += step.time
}
// set start time only if there are any steps with timers (otherwise no timers are rendered)
if (total_time > 0) {
this.start_time = moment().format("yyyy-MM-DDTHH:mm")
}
if (recipe.image === null) this.printReady()
this.recipe = this.rootrecipe = recipe
this.servings = this.servings_cache[this.rootrecipe.id] = recipe.servings
this.loading = false
setTimeout(() => {
this.handleResize()
}, 100)
})
},
updateStartTime: function (e) {
this.start_time = e
},
updateIngredientCheckedState: function (e) {
for (let step of this.recipe.steps) {
for (let ingredient of step.ingredients) {
if (ingredient.id === e.id) {
this.$set(ingredient, "checked", !ingredient.checked)
}
}
}
},
quickSwitch: function (e) {
if (e === -1) {
this.recipe = this.rootrecipe
this.servings = this.servings_cache[this.rootrecipe?.id ?? 1]
} else {
this.recipe = e
this.servings = this.servings_cache?.[e.id] ?? e.servings
}
},
printReady: function () {
const template = document.createElement("template");
template.id = "printReady";
document.body.appendChild(template);
},
onImgLoad: function () {
this.printReady()
},
},
}
</script>
<style>
#app > div > div {
break-inside: avoid;
}
</style>

View File

@@ -31,22 +31,22 @@
"Step_start_time": "", "Step_start_time": "",
"Sort_by_new": "", "Sort_by_new": "",
"Table_of_Contents": "", "Table_of_Contents": "",
"Recipes_per_page": "", "Recipes_per_page": "Receptů na stránku",
"Show_as_header": "", "Show_as_header": "",
"Hide_as_header": "", "Hide_as_header": "",
"Add_nutrition_recipe": "", "Add_nutrition_recipe": "Přidat nutriční hodnoty",
"Remove_nutrition_recipe": "", "Remove_nutrition_recipe": "Smazat nutriční hodnoty",
"Copy_template_reference": "", "Copy_template_reference": "",
"Save_and_View": "", "Save_and_View": "Uložit & Zobrazit",
"Manage_Books": "", "Manage_Books": "",
"Meal_Plan": "", "Meal_Plan": "Jídelníček",
"Select_Book": "", "Select_Book": "",
"Select_File": "", "Select_File": "Vybrat soubor",
"Recipe_Image": "", "Recipe_Image": "",
"Import_finished": "", "Import_finished": "Import dokončen",
"View_Recipes": "", "View_Recipes": "Zobrazit recepty",
"Log_Cooking": "", "Log_Cooking": "",
"New_Recipe": "", "New_Recipe": "Nový recept",
"Url_Import": "", "Url_Import": "",
"Reset_Search": "", "Reset_Search": "",
"Recently_Viewed": "", "Recently_Viewed": "",
@@ -54,21 +54,21 @@
"New_Keyword": "", "New_Keyword": "",
"Delete_Keyword": "", "Delete_Keyword": "",
"Edit_Keyword": "", "Edit_Keyword": "",
"Edit_Recipe": "", "Edit_Recipe": "Upravit recept",
"Move_Keyword": "", "Move_Keyword": "",
"Merge_Keyword": "", "Merge_Keyword": "",
"Hide_Keywords": "", "Hide_Keywords": "",
"Hide_Recipes": "", "Hide_Recipes": "",
"Move_Up": "", "Move_Up": "Nahoru",
"Move_Down": "", "Move_Down": "Dolů",
"Step_Name": "", "Step_Name": "Název kroku",
"Step_Type": "", "Step_Type": "",
"Make_Header": "", "Make_Header": "",
"Make_Ingredient": "", "Make_Ingredient": "",
"Amount": "", "Amount": "Množství",
"Enable_Amount": "", "Enable_Amount": "Zobrazit množství",
"Disable_Amount": "", "Disable_Amount": "Skrýt množství",
"Ingredient Editor": "", "Ingredient Editor": "Editace ingrediencí",
"Description_Replace": "", "Description_Replace": "",
"Instruction_Replace": "", "Instruction_Replace": "",
"Auto_Sort": "", "Auto_Sort": "",
@@ -79,8 +79,8 @@
"Add_Step": "", "Add_Step": "",
"Keywords": "", "Keywords": "",
"Books": "", "Books": "",
"Proteins": "", "Proteins": "Proteiny",
"Fats": "", "Fats": "Tuky",
"Carbohydrates": "", "Carbohydrates": "",
"Calories": "", "Calories": "",
"Energy": "", "Energy": "",
@@ -333,7 +333,7 @@
"Foods": "", "Foods": "",
"Account": "", "Account": "",
"Cosmetic": "", "Cosmetic": "",
"API": "", "API": "API",
"enable_expert": "", "enable_expert": "",
"expert_mode": "", "expert_mode": "",
"simple_mode": "", "simple_mode": "",
@@ -353,8 +353,8 @@
"Custom Filter": "", "Custom Filter": "",
"shared_with": "", "shared_with": "",
"sort_by": "", "sort_by": "",
"asc": "", "asc": "Vzestupně",
"desc": "", "desc": "Sestupně",
"date_viewed": "", "date_viewed": "",
"last_cooked": "", "last_cooked": "",
"times_cooked": "", "times_cooked": "",
@@ -362,29 +362,29 @@
"show_sortby": "", "show_sortby": "",
"search_rank": "", "search_rank": "",
"make_now": "", "make_now": "",
"recipe_filter": "", "recipe_filter": "Filtrovat recepty",
"book_filter_help": "", "book_filter_help": "",
"review_shopping": "", "review_shopping": "",
"view_recipe": "", "view_recipe": "Zobrazit recept",
"copy_to_new": "", "copy_to_new": "",
"recipe_name": "", "recipe_name": "Název receptu",
"paste_ingredients_placeholder": "", "paste_ingredients_placeholder": "",
"paste_ingredients": "", "paste_ingredients": "",
"ingredient_list": "", "ingredient_list": "",
"explain": "", "explain": "",
"filter": "", "filter": "Filtr",
"Website": "", "Website": "Web",
"App": "", "App": "Aplikace",
"Message": "", "Message": "",
"Bookmarklet": "", "Bookmarklet": "",
"Sticky_Nav": "", "Sticky_Nav": "",
"Sticky_Nav_Help": "", "Sticky_Nav_Help": "",
"Nav_Color": "", "Nav_Color": "",
"Nav_Color_Help": "", "Nav_Color_Help": "",
"Use_Kj": "", "Use_Kj": "Používat kJ místo kcal",
"Comments_setting": "", "Comments_setting": "Zobrazit komentáře",
"click_image_import": "", "click_image_import": "Vyberte obrázek, který chcete přiřadit k tomuto receptu",
"no_more_images_found": "", "no_more_images_found": "Žádné další obrázky na zadaném odkazu.",
"import_duplicates": "", "import_duplicates": "",
"paste_json": "", "paste_json": "",
"Click_To_Edit": "", "Click_To_Edit": "",
@@ -407,9 +407,9 @@
"InheritFields_help": "", "InheritFields_help": "",
"show_ingredient_overview": "", "show_ingredient_overview": "",
"Ingredient Overview": "", "Ingredient Overview": "",
"last_viewed": "", "last_viewed": "Naposledy zobrazeno",
"created_on": "", "created_on": "Vytvořeno",
"updatedon": "", "updatedon": "Upraveno",
"Imported_From": "", "Imported_From": "",
"advanced_search_settings": "", "advanced_search_settings": "",
"nothing_planned_today": "", "nothing_planned_today": "",
@@ -418,13 +418,13 @@
"Pinned": "", "Pinned": "",
"Imported": "", "Imported": "",
"Quick actions": "", "Quick actions": "",
"Ratings": "", "Ratings": "Hodnocení",
"Internal": "", "Internal": "",
"Units": "", "Units": "Jednotky",
"Manage_Emails": "", "Manage_Emails": "",
"Change_Password": "", "Change_Password": "Změna hesla",
"Social_Authentication": "", "Social_Authentication": "",
"Random Recipes": "", "Random Recipes": "Náhodné recepty",
"parameter_count": "", "parameter_count": "",
"select_keyword": "", "select_keyword": "",
"add_keyword": "", "add_keyword": "",
@@ -436,10 +436,10 @@
"empty_list": "", "empty_list": "",
"Select": "", "Select": "",
"Supermarkets": "", "Supermarkets": "",
"User": "", "User": "Uživatel",
"Username": "", "Username": "Uživatelské jméno",
"First_name": "", "First_name": "Jméno",
"Last_name": "", "Last_name": "Příjmení",
"Keyword": "", "Keyword": "",
"Advanced": "", "Advanced": "",
"Page": "", "Page": "",
@@ -452,15 +452,15 @@
"Create Food": "", "Create Food": "",
"create_food_desc": "", "create_food_desc": "",
"additional_options": "", "additional_options": "",
"Importer_Help": "", "Importer_Help": "Nápověda k importu z této aplikace:",
"Documentation": "", "Documentation": "Dokumentace",
"Select_App_To_Import": "", "Select_App_To_Import": "Vyberte aplikaci, ze které chcete importovat",
"Import_Supported": "", "Import_Supported": "Import podporován",
"Export_Supported": "", "Export_Supported": "Export podporován",
"Import_Not_Yet_Supported": "", "Import_Not_Yet_Supported": "Import není zatím podporován",
"Export_Not_Yet_Supported": "", "Export_Not_Yet_Supported": "Export není zatím podporován",
"Import_Result_Info": "", "Import_Result_Info": "{imported} z {total} receptů naimportováno",
"Recipes_In_Import": "", "Recipes_In_Import": "Receptů v importním souboru",
"Toggle": "", "Toggle": "",
"Import_Error": "", "Import_Error": "",
"Warning_Delete_Supermarket_Category": "", "Warning_Delete_Supermarket_Category": "",
@@ -477,6 +477,6 @@
"Use_Plural_Food_Always": "", "Use_Plural_Food_Always": "",
"Use_Plural_Food_Simple": "", "Use_Plural_Food_Simple": "",
"plural_usage_info": "", "plural_usage_info": "",
"Create Recipe": "", "Create Recipe": "Vytvořit recept",
"Import Recipe": "" "Import Recipe": "Importovat recept"
} }

View File

@@ -481,5 +481,22 @@
"Amount": "Menge", "Amount": "Menge",
"Original_Text": "Originaler Text", "Original_Text": "Originaler Text",
"Import Recipe": "Rezept importieren", "Import Recipe": "Rezept importieren",
"Create Recipe": "Rezept erstellen" "Create Recipe": "Rezept erstellen",
"recipe_property_info": "Sie können auch Eigenschaften zu Lebensmitteln hinzufügen, um sie automatisch auf der Grundlage Ihres Rezepts zu berechnen!",
"per_serving": "pro Portion",
"open_data_help_text": "Das Tandoor Open Data Projekt bietet von der Gemeinschaft bereitgestellte Daten für Tandoor. Dieses Feld wird beim Importieren automatisch ausgefüllt und ermöglicht künftige Aktualisierungen.",
"Open_Data_Import": "Datenimport öffnen",
"Update_Existing_Data": "Vorhandene Daten aktualisieren",
"Data_Import_Info": "Verbessern Sie Ihren Space, indem Sie eine von der Community kuratierte Liste von Lebensmitteln, Einheiten und mehr importieren, um Ihre Rezeptsammlung zu verbessern.",
"Learn_More": "Mehr erfahren",
"Use_Metric": "Metrische Einheiten verwenden",
"converted_unit": "Umgerechnete Einheit",
"converted_amount": "Umgerechneter Betrag",
"base_unit": "Basiseinheit",
"base_amount": "Grundbetrag",
"Datatype": "Datentyp",
"Number of Objects": "Anzahl von Objekten",
"Property": "Eigenschaft",
"Conversion": "Umrechnung",
"Properties": "Eigenschaften"
} }

View File

@@ -14,6 +14,7 @@
"success_moving_resource": "Successfully moved a resource!", "success_moving_resource": "Successfully moved a resource!",
"success_merging_resource": "Successfully merged a resource!", "success_merging_resource": "Successfully merged a resource!",
"file_upload_disabled": "File upload is not enabled for your space.", "file_upload_disabled": "File upload is not enabled for your space.",
"recipe_property_info": "You can also add properties to foods to calculate them automatically based on your recipe!",
"warning_space_delete": "You can delete your space including all recipes, shopping lists, meal plans and whatever else you have created. This cannot be undone! Are you sure you want to do this ?", "warning_space_delete": "You can delete your space including all recipes, shopping lists, meal plans and whatever else you have created. This cannot be undone! Are you sure you want to do this ?",
"food_inherit_info": "Fields on food that should be inherited by default.", "food_inherit_info": "Fields on food that should be inherited by default.",
"facet_count_info": "Show recipe counts on search filters.", "facet_count_info": "Show recipe counts on search filters.",
@@ -37,6 +38,7 @@
"Add_nutrition_recipe": "Add nutrition to recipe", "Add_nutrition_recipe": "Add nutrition to recipe",
"Remove_nutrition_recipe": "Delete nutrition from recipe", "Remove_nutrition_recipe": "Delete nutrition from recipe",
"Copy_template_reference": "Copy template reference", "Copy_template_reference": "Copy template reference",
"per_serving": "per servings",
"Save_and_View": "Save & View", "Save_and_View": "Save & View",
"Manage_Books": "Manage Books", "Manage_Books": "Manage Books",
"Meal_Plan": "Meal Plan", "Meal_Plan": "Meal Plan",
@@ -76,6 +78,19 @@
"Private_Recipe": "Private Recipe", "Private_Recipe": "Private Recipe",
"Private_Recipe_Help": "Recipe is only shown to you and people its shared with.", "Private_Recipe_Help": "Recipe is only shown to you and people its shared with.",
"reusable_help_text": "Should the invite link be usable for more than one user.", "reusable_help_text": "Should the invite link be usable for more than one user.",
"open_data_help_text": "The Tandoor Open Data project provides community contributed data for Tandoor. This field is filled automatically when importing it and allows updates in the future.",
"Open_Data_Slug": "Open Data Slug",
"Open_Data_Import": "Open Data Import",
"Data_Import_Info": "Enhance your Space by importing a community curated list of foods, units and more to improve your recipe collection.",
"Update_Existing_Data": "Update Existing Data",
"Use_Metric": "Use Metric Units",
"Learn_More": "Learn More",
"converted_unit": "Converted Unit",
"converted_amount": "Converted Amount",
"base_unit": "Base Unit",
"base_amount": "Base Amount",
"Datatype": "Datatype",
"Number of Objects": "Number of Objects",
"Add_Step": "Add Step", "Add_Step": "Add Step",
"Keywords": "Keywords", "Keywords": "Keywords",
"Books": "Books", "Books": "Books",
@@ -161,6 +176,8 @@
"merge_title": "Merge {type}", "merge_title": "Merge {type}",
"move_title": "Move {type}", "move_title": "Move {type}",
"Food": "Food", "Food": "Food",
"Property": "Property",
"Conversion": "Conversion",
"Original_Text": "Original Text", "Original_Text": "Original Text",
"Recipe_Book": "Recipe Book", "Recipe_Book": "Recipe Book",
"del_confirmation_tree": "Are you sure that you want to delete {source} and all of it's children?", "del_confirmation_tree": "Are you sure that you want to delete {source} and all of it's children?",
@@ -168,6 +185,7 @@
"create_title": "New {type}", "create_title": "New {type}",
"edit_title": "Edit {type}", "edit_title": "Edit {type}",
"Name": "Name", "Name": "Name",
"Properties": "Properties",
"Type": "Type", "Type": "Type",
"Description": "Description", "Description": "Description",
"Recipe": "Recipe", "Recipe": "Recipe",
@@ -462,6 +480,7 @@
"Import_Result_Info": "{imported} of {total} recipes were imported", "Import_Result_Info": "{imported} of {total} recipes were imported",
"Recipes_In_Import": "Recipes in your import file", "Recipes_In_Import": "Recipes in your import file",
"Toggle": "Toggle", "Toggle": "Toggle",
"total": "total",
"Import_Error": "An Error occurred during your import. Please expand the Details at the bottom of the page to view it.", "Import_Error": "An Error occurred during your import. Please expand the Details at the bottom of the page to view it.",
"Warning_Delete_Supermarket_Category": "Deleting a supermarket category will also delete all relations to foods. Are you sure?", "Warning_Delete_Supermarket_Category": "Deleting a supermarket category will also delete all relations to foods. Are you sure?",
"New_Supermarket": "Create new supermarket", "New_Supermarket": "Create new supermarket",

View File

@@ -73,7 +73,7 @@
"Carbohydrates": "Carbohydratos", "Carbohydrates": "Carbohydratos",
"Calories": "Calorias", "Calories": "Calorias",
"Energy": "Energia", "Energy": "Energia",
"Nutrition": "Nutricion", "Nutrition": "Nutrición",
"Date": "Fecha", "Date": "Fecha",
"Share": "Compartir", "Share": "Compartir",
"Automation": "Automatización", "Automation": "Automatización",
@@ -452,5 +452,6 @@
"Auto_Sort": "Ordenar Automáticamente", "Auto_Sort": "Ordenar Automáticamente",
"Auto_Sort_Help": "Mueva todos los ingredientes al paso que mejor se adapte.", "Auto_Sort_Help": "Mueva todos los ingredientes al paso que mejor se adapte.",
"Unpin": "Desanclar", "Unpin": "Desanclar",
"Amount": "Cantidad" "Amount": "Cantidad",
"PinnedConfirmation": "{recipe} ha sido fijada."
} }

View File

@@ -68,83 +68,83 @@
"Amount": "Mengde", "Amount": "Mengde",
"Enable_Amount": "Aktiver mengde", "Enable_Amount": "Aktiver mengde",
"Disable_Amount": "Deaktiver mengde", "Disable_Amount": "Deaktiver mengde",
"Ingredient Editor": "", "Ingredient Editor": "Ingrediens Behandler",
"Description_Replace": "", "Description_Replace": "Erstatt beskrivelse",
"Instruction_Replace": "", "Instruction_Replace": "Erstatt instruksjoner",
"Auto_Sort": "", "Auto_Sort": "Sorter Automatisk",
"Auto_Sort_Help": "", "Auto_Sort_Help": "Flytt alle ingredienser til det mest passende steget.",
"Private_Recipe": "", "Private_Recipe": "Privat Oppskrift",
"Private_Recipe_Help": "", "Private_Recipe_Help": "Oppskriften er bare vist til deg og dem du har delt den med.",
"reusable_help_text": "", "reusable_help_text": "Burde invitasjonslenken være brukbar for flere enn én bruker.",
"Add_Step": "", "Add_Step": "Legg til steg",
"Keywords": "", "Keywords": "Nøkkelord",
"Books": "Bøker", "Books": "Bøker",
"Proteins": "", "Proteins": "Protein",
"Fats": "", "Fats": "Fett",
"Carbohydrates": "Karbohydrater", "Carbohydrates": "Karbohydrater",
"Calories": "", "Calories": "Kalorier",
"Energy": "", "Energy": "Energi",
"Nutrition": "", "Nutrition": "Næring",
"Date": "", "Date": "Dato",
"Share": "", "Share": "Del",
"Automation": "", "Automation": "Automatiser",
"Parameter": "", "Parameter": "Parameter",
"Export": "", "Export": "Eksporter",
"Copy": "", "Copy": "Kopier",
"Rating": "Karakter", "Rating": "Vurdering",
"Close": "Lukk", "Close": "Lukk",
"Cancel": "", "Cancel": "Avbryt",
"Link": "Lenke", "Link": "Lenke",
"Add": "", "Add": "Legg til",
"New": "", "New": "Ny",
"Note": "", "Note": "Merk",
"Success": "", "Success": "Vellykket",
"Failure": "", "Failure": "Feil",
"Protected": "", "Protected": "Beskyttet",
"Ingredients": "Ingredienser", "Ingredients": "Ingredienser",
"Supermarket": "Butikk", "Supermarket": "Butikk",
"Categories": "", "Categories": "Kategorier",
"Category": "", "Category": "Kategori",
"Selected": "", "Selected": "Valgte",
"min": "", "min": "min",
"Servings": "", "Servings": "Porsjoner",
"Waiting": "", "Waiting": "Venter",
"Preparation": "", "Preparation": "Forberedelse",
"External": "", "External": "Ekstern",
"Size": "", "Size": "Størrelse",
"Files": "", "Files": "Filer",
"File": "", "File": "Fil",
"Edit": "", "Edit": "Rediger",
"Image": "", "Image": "Bilde",
"Delete": "", "Delete": "Slett",
"Open": "", "Open": "Åpne",
"Ok": "", "Ok": "Ok",
"Save": "", "Save": "Lagre",
"Step": "", "Step": "Steg",
"Search": "", "Search": "Søk",
"Import": "", "Import": "Importer",
"Print": "", "Print": "Skriv ut",
"Settings": "Innstillinger", "Settings": "Innstillinger",
"or": "", "or": "eller",
"and": "", "and": "og",
"Information": "", "Information": "Informasjon",
"Download": "", "Download": "Last ned",
"Create": "Opprett", "Create": "Opprett",
"Search Settings": "", "Search Settings": "Søk Instillinger",
"View": "", "View": "Visning",
"Recipes": "", "Recipes": "Oppskrift",
"Move": "", "Move": "Flytt",
"Merge": "", "Merge": "Slå sammen",
"Parent": "", "Parent": "Forelder",
"Copy Link": "", "Copy Link": "Kopier lenke",
"Copy Token": "", "Copy Token": "Kopier Token",
"delete_confirmation": "", "delete_confirmation": "Er du sikker på at du vill slette {source}?",
"move_confirmation": "", "move_confirmation": "Flytt<i>{child}</i> til forelder <i>{parent}</i>",
"merge_confirmation": "", "merge_confirmation": "Erstatt<i>{source}</i> med <i>{target}</i>",
"create_rule": "", "create_rule": "og opprett automasjon",
"move_selection": "", "move_selection": "Velg en forelder {type} å flytte {source} til.",
"merge_selection": "", "merge_selection": "Erstatt alle tilfeller av {source} med den valgte {type}.",
"Root": "", "Root": "Rot",
"Ignore_Shopping": "", "Ignore_Shopping": "",
"Shopping_Category": "", "Shopping_Category": "",
"Shopping_Categories": "", "Shopping_Categories": "",
@@ -284,33 +284,33 @@
"Hide_Keyword": "", "Hide_Keyword": "",
"Hour": "", "Hour": "",
"Hours": "", "Hours": "",
"Day": "", "Day": "Dag",
"Days": "", "Days": "Dager",
"Second": "", "Second": "",
"Seconds": "", "Seconds": "",
"Clear": "", "Clear": "",
"Users": "", "Users": "",
"Invites": "", "Invites": "",
"err_move_self": "", "err_move_self": "",
"nothing": "", "nothing": "Ingenting å gjøre",
"err_merge_self": "", "err_merge_self": "Kan ikke slå sammen linje med seg selv",
"show_sql": "", "show_sql": "Vis SQL",
"filter_to_supermarket_desc": "", "filter_to_supermarket_desc": "Som standard, filtrerer handlelisten til å kun inkludere kategorier for den valgte butikken.",
"CategoryName": "", "CategoryName": "Kategori navn",
"SupermarketName": "", "SupermarketName": "Butikk Navn",
"CategoryInstruction": "", "CategoryInstruction": "Dra kategorier for å endre på rekkefølgen de vises i handlelisten.",
"shopping_recent_days_desc": "", "shopping_recent_days_desc": "",
"shopping_recent_days": "", "shopping_recent_days": "",
"download_pdf": "", "download_pdf": "Last ned PDF",
"download_csv": "", "download_csv": "Last ned CSV",
"csv_delim_help": "", "csv_delim_help": "",
"csv_delim_label": "", "csv_delim_label": "",
"SuccessClipboard": "", "SuccessClipboard": "",
"copy_to_clipboard": "", "copy_to_clipboard": "Kopier til utklippstavle",
"csv_prefix_help": "", "csv_prefix_help": "Prefiks for å legge til når du kopierer listen til utklippstavlen.",
"csv_prefix_label": "", "csv_prefix_label": "Liste prefiks",
"copy_markdown_table": "", "copy_markdown_table": "Kopier som Markdown tabell",
"in_shopping": "", "in_shopping": "I handleliste",
"DelayUntil": "", "DelayUntil": "",
"Pin": "", "Pin": "",
"Unpin": "", "Unpin": "",
@@ -332,37 +332,37 @@
"food_recipe_help": "", "food_recipe_help": "",
"Foods": "", "Foods": "",
"Account": "", "Account": "",
"Cosmetic": "", "Cosmetic": "Kosmetisk",
"API": "", "API": "API",
"enable_expert": "", "enable_expert": "Aktiver Ekspert Modus",
"expert_mode": "", "expert_mode": "Ekspert Modus",
"simple_mode": "", "simple_mode": "Enkel Modus",
"advanced": "", "advanced": "Avansert",
"fields": "", "fields": "Felt",
"show_keywords": "", "show_keywords": "Vis Nøkkelord",
"show_foods": "", "show_foods": "Vis Mat",
"show_books": "", "show_books": "Vis bøker",
"show_rating": "", "show_rating": "Vis vurdering",
"show_units": "", "show_units": "Vis enheter",
"show_filters": "", "show_filters": "Vis filtre",
"not": "", "not": "ikke",
"save_filter": "", "save_filter": "Lagre filtre",
"filter_name": "", "filter_name": "Filtrer Navn",
"left_handed": "", "left_handed": "Venstrehendt Modus",
"left_handed_help": "", "left_handed_help": "Vil optimalisere bukergrensesnittet for bruk med venstre hånden.",
"Custom Filter": "", "Custom Filter": "Egendefinert Filter",
"shared_with": "", "shared_with": "Delt med",
"sort_by": "", "sort_by": "Sorter etter",
"asc": "", "asc": "Stigende",
"desc": "", "desc": "Fallende",
"date_viewed": "", "date_viewed": "Sist sett",
"last_cooked": "", "last_cooked": "Sist tilberedt",
"times_cooked": "", "times_cooked": "Antall ganger tilberedt",
"date_created": "", "date_created": "Dato laget",
"show_sortby": "", "show_sortby": "Vis sorter etter",
"search_rank": "", "search_rank": "Søk etter vurdering",
"make_now": "", "make_now": "Lag nå",
"recipe_filter": "", "recipe_filter": "Oppskrift filter",
"book_filter_help": "", "book_filter_help": "",
"review_shopping": "", "review_shopping": "",
"view_recipe": "", "view_recipe": "",
@@ -373,9 +373,9 @@
"ingredient_list": "", "ingredient_list": "",
"explain": "", "explain": "",
"filter": "", "filter": "",
"Website": "", "Website": "Nettside",
"App": "", "App": "App",
"Message": "", "Message": "Melding",
"Bookmarklet": "", "Bookmarklet": "",
"Sticky_Nav": "", "Sticky_Nav": "",
"Sticky_Nav_Help": "", "Sticky_Nav_Help": "",
@@ -420,11 +420,11 @@
"Quick actions": "", "Quick actions": "",
"Ratings": "", "Ratings": "",
"Internal": "", "Internal": "",
"Units": "", "Units": "Enhet",
"Manage_Emails": "", "Manage_Emails": "Administrer e-poster",
"Change_Password": "", "Change_Password": "Endre passord",
"Social_Authentication": "", "Social_Authentication": "",
"Random Recipes": "", "Random Recipes": "Tilfeldige oppskrifter",
"parameter_count": "", "parameter_count": "",
"select_keyword": "", "select_keyword": "",
"add_keyword": "", "add_keyword": "",
@@ -435,13 +435,13 @@
"remove_selection": "", "remove_selection": "",
"empty_list": "", "empty_list": "",
"Select": "Velg", "Select": "Velg",
"Supermarkets": "", "Supermarkets": "Butikker",
"User": "", "User": "Bruker",
"Username": "", "Username": "Brukernavn",
"First_name": "", "First_name": "Fornavn",
"Last_name": "", "Last_name": "Etternavn",
"Keyword": "Nøkkelord", "Keyword": "Nøkkelord",
"Advanced": "", "Advanced": "Avansert",
"Page": "", "Page": "",
"Single": "", "Single": "",
"Multiple": "", "Multiple": "",
@@ -478,5 +478,19 @@
"Use_Plural_Food_Simple": "", "Use_Plural_Food_Simple": "",
"plural_usage_info": "", "plural_usage_info": "",
"Create Recipe": "", "Create Recipe": "",
"Import Recipe": "" "Import Recipe": "",
"per_serving": "Per porsjon",
"open_data_help_text": "Tandoor Open Data prosjektet gir fra fellesskapet til Tandoor. Dette feltet fylles ut automatisk når det importeres og tillater oppdateringer i fremtiden.",
"Open_Data_Slug": "Åpne data Slug",
"Open_Data_Import": "Åpne Data Import",
"recipe_property_info": "Du kan også legge til egenskaper til mat for å kalkulere dem automatisk basert på oppskriften!",
"Update_Existing_Data": "Oppdater eksisterende data",
"Use_Metric": "Bruk metriske enheter",
"Learn_More": "Lær mer",
"converted_unit": "Konverter enhet",
"converted_amount": "Konverter mengde",
"base_unit": "Baseenhet",
"base_amount": "Basemengde",
"Datatype": "Data-type",
"Number of Objects": "Antall objekter"
} }

View File

@@ -480,5 +480,24 @@
"Description_Replace": "Zmień opis", "Description_Replace": "Zmień opis",
"Instruction_Replace": "Zmień instrukcję", "Instruction_Replace": "Zmień instrukcję",
"Import Recipe": "Importuj przepis", "Import Recipe": "Importuj przepis",
"Create Recipe": "Utwórz przepis" "Create Recipe": "Utwórz przepis",
"recipe_property_info": "Możesz także dodawać właściwości do żywności, aby przeliczać ją automatycznie na podstawie Twojego przepisu!",
"per_serving": "na porcje",
"open_data_help_text": "Projekt Tandoor Open Data dostarcza danych przesłanych przez społeczność dla Tandoor. To pole jest wypełniane automatycznie podczas importu i umożliwia aktualizacje w przyszłości.",
"Open_Data_Slug": "Open Data Slug",
"Open_Data_Import": "Open Data Import",
"Data_Import_Info": "Wzbogać swoją Przestrzeń, importując wyselekcjonowaną przez społeczność listę żywności, jednostek i nie tylko, aby ulepszyć swoją kolekcję przepisów.",
"Update_Existing_Data": "Zaktualizuj istniejące dane",
"Use_Metric": "Użyj jednostek metrycznych",
"Learn_More": "Dowiedz się więcej",
"converted_unit": "Przeliczona jednostka",
"converted_amount": "Przeliczona ilość",
"base_unit": "Jednostka podstawowa",
"base_amount": "Ilość bazowa",
"Datatype": "Typ danych",
"Number of Objects": "Ilość obiektów",
"Property": "Właściwość",
"Conversion": "Konwersja",
"Properties": "Właściwości",
"total": "łącznie"
} }

View File

@@ -1,11 +1,11 @@
{ {
"warning_feature_beta": "Данный функционал находится в сдадии BETA (тестируется). Возможны баги и серьезные изменения функционала в будующем.", "warning_feature_beta": "Данный функционал находится в стадии BETA (тестируется). Возможны баги и серьезные изменения функционала в будущем.",
"err_fetching_resource": "Ошибка при загрузке продукта!", "err_fetching_resource": "Ошибка при загрузке продукта!",
"err_creating_resource": "Ошибка при создании продукта!", "err_creating_resource": "Ошибка при создании продукта!",
"err_updating_resource": "Ошибка при редактировании продукта!", "err_updating_resource": "Ошибка при редактировании продукта!",
"err_deleting_resource": "Ошибка при удалении продукта!", "err_deleting_resource": "Ошибка при удалении продукта!",
"success_fetching_resource": "Продукт успешно загружен!", "success_fetching_resource": "Продукт успешно загружен!",
"success_creating_resource": "Продукт успешно загружен!", "success_creating_resource": "Продукт успешно создан!",
"success_updating_resource": "Продукт успешно обновлен!", "success_updating_resource": "Продукт успешно обновлен!",
"success_deleting_resource": "Продукт успешно удален!", "success_deleting_resource": "Продукт успешно удален!",
"file_upload_disabled": "Выгрузка файла не активирована в настройках.", "file_upload_disabled": "Выгрузка файла не активирована в настройках.",
@@ -13,7 +13,7 @@
"confirm_delete": "Вы уверены, что хотите удалить этот объект?", "confirm_delete": "Вы уверены, что хотите удалить этот объект?",
"import_running": "Идет загрузка, пожалуйста ждите!", "import_running": "Идет загрузка, пожалуйста ждите!",
"all_fields_optional": "Все поля не обязательны для заполнения.", "all_fields_optional": "Все поля не обязательны для заполнения.",
"convert_internal": "Конвретировать рецепт во внутренний формат", "convert_internal": "Конвертировать рецепт во внутренний формат",
"show_only_internal": "Показывать только рецепты во внутреннем формате", "show_only_internal": "Показывать только рецепты во внутреннем формате",
"show_split_screen": "Двухколоночный вид", "show_split_screen": "Двухколоночный вид",
"Log_Recipe_Cooking": "Журнал приготовления", "Log_Recipe_Cooking": "Журнал приготовления",
@@ -211,13 +211,13 @@
"FoodNotOnHand": "{food} отсутствует в наличии.", "FoodNotOnHand": "{food} отсутствует в наличии.",
"Undefined": "Неизвестно", "Undefined": "Неизвестно",
"AddFoodToShopping": "Добавить {food} в ваш список покупок", "AddFoodToShopping": "Добавить {food} в ваш список покупок",
"success_moving_resource": "Успешное перемещение ресурса!", "success_moving_resource": "Успешное перемещение продукта!",
"success_merging_resource": "Ресурс успешно присоединен!", "success_merging_resource": "Продукт успешно присоединен!",
"Shopping_Categories": "Категории покупок", "Shopping_Categories": "Категории покупок",
"Search Settings": "Искать настройки", "Search Settings": "Искать настройки",
"err_merging_resource": "Произошла ошибка при перемещении ресурса!", "err_merging_resource": "Произошла ошибка при перемещении продукта!",
"Remove_nutrition_recipe": "Уберите питательные вещества из рецепта", "Remove_nutrition_recipe": "Уберите питательные вещества из рецепта",
"err_moving_resource": "Произошла ошибка при перемещении ресурса!", "err_moving_resource": "Произошла ошибка при перемещении продукта!",
"NotInShopping": "{food} отсутствует в вашем списке покупок.", "NotInShopping": "{food} отсутствует в вашем списке покупок.",
"RemoveFoodFromShopping": "Удалить {food} из вашего списка покупок", "RemoveFoodFromShopping": "Удалить {food} из вашего списка покупок",
"ShowDelayed": "Показать отложенные элементы", "ShowDelayed": "Показать отложенные элементы",
@@ -345,6 +345,6 @@
"GroupBy": "Сгруппировать по", "GroupBy": "Сгруппировать по",
"facet_count_info": "Показывать количество рецептов в фильтрах поиска.", "facet_count_info": "Показывать количество рецептов в фильтрах поиска.",
"food_inherit_info": "Поля для продуктов питания, которые должны наследоваться по умолчанию.", "food_inherit_info": "Поля для продуктов питания, которые должны наследоваться по умолчанию.",
"warning_space_delete": "Вы можете удалить свое пространство, включая все рецепты, списки покупок, планы питания и все остальное, что вы создали. Этого нельзя отменить! Ты уверен, что хочешь это сделать?", "warning_space_delete": "Вы можете удалить свое пространство, включая все рецепты, списки покупок, планы питания и все остальное, что вы создали. Этого нельзя отменить! Вы уверены, что хотите это сделать?",
"Description_Replace": "Изменить описание" "Description_Replace": "Изменить описание"
} }

View File

View File

@@ -91,11 +91,13 @@ export class Models {
"substitute_children", "substitute_children",
"reset_inherit", "reset_inherit",
"child_inherit_fields", "child_inherit_fields",
"open_data_slug",
], ],
], ],
form: { form: {
show_help: true, show_help: true,
component: "FoodEditor",
name: { name: {
form_field: true, form_field: true,
type: "text", type: "text",
@@ -126,6 +128,14 @@ export class Models {
label: "Recipe", // form.label always translated in utils.getForm() label: "Recipe", // form.label always translated in utils.getForm()
help_text: "food_recipe_help", // form.help_text always translated help_text: "food_recipe_help", // form.help_text always translated
}, },
open_data_slug: {
form_field: true,
type: "text",
field: "open_data_slug",
disabled: true,
label: "Open_Data_Slug",
help_text: "open_data_help_text",
},
onhand: { onhand: {
form_field: true, form_field: true,
type: "checkbox", type: "checkbox",
@@ -269,8 +279,9 @@ export class Models {
apiName: "Unit", apiName: "Unit",
paginated: true, paginated: true,
create: { create: {
params: [["name", "plural_name", "description",]], params: [["name", "plural_name", "description", "open_data_slug",]],
form: { form: {
show_help: true,
name: { name: {
form_field: true, form_field: true,
type: "text", type: "text",
@@ -292,6 +303,14 @@ export class Models {
label: "Description", label: "Description",
placeholder: "", placeholder: "",
}, },
open_data_slug: {
form_field: true,
type: "text",
field: "open_data_slug",
disabled: true,
label: "Open_Data_Slug",
help_text: "open_data_help_text",
},
}, },
}, },
merge: true, merge: true,
@@ -418,6 +437,7 @@ export class Models {
create: { create: {
params: [["name", "description", "category_to_supermarket"]], params: [["name", "description", "category_to_supermarket"]],
form: { form: {
show_help: true,
name: { name: {
form_field: true, form_field: true,
type: "text", type: "text",
@@ -442,6 +462,14 @@ export class Models {
label: "Categories", // form.label always translated in utils.getForm() label: "Categories", // form.label always translated in utils.getForm()
placeholder: "", placeholder: "",
}, },
open_data_slug: {
form_field: true,
type: "text",
field: "open_data_slug",
disabled: true,
label: "Open_Data_Slug",
help_text: "open_data_help_text",
},
}, },
config: { config: {
function: "SupermarketWithCategories", function: "SupermarketWithCategories",
@@ -562,6 +590,129 @@ export class Models {
}, },
} }
static UNIT_CONVERSION = {
name: "Unit Conversion",
apiName: "UnitConversion",
paginated: false,
list: {
header_component: {
name: "BetaWarning",
},
},
create: {
params: [['food', 'base_amount', 'base_unit', 'converted_amount', 'converted_unit', 'open_data_slug']],
form: {
show_help: true,
// TODO add proper help texts for everything
food: {
form_field: true,
type: "lookup",
field: "food",
list: "FOOD",
list_label: "name",
label: "Food",
multiple: false,
},
base_amount: {
form_field: true,
type: "text",
field: "base_amount",
label: "base_amount",
placeholder: "",
},
base_unit: {
form_field: true,
type: "lookup",
field: "base_unit",
list: "UNIT",
list_label: "name",
label: "base_unit",
multiple: false,
},
converted_amount: {
form_field: true,
type: "text",
field: "converted_amount",
label: "converted_amount",
placeholder: "",
},
converted_unit: {
form_field: true,
type: "lookup",
field: "converted_unit",
list: "UNIT",
list_label: "name",
label: "converted_unit",
multiple: false,
},
open_data_slug: {
form_field: true,
type: "text",
field: "open_data_slug",
disabled: true,
label: "Open_Data_Slug",
help_text: "open_data_help_text",
},
},
},
}
static PROPERTY_TYPE = {
name: "Property Type",
apiName: "PropertyType",
paginated: false,
list: {
header_component: {
name: "BetaWarning",
},
},
create: {
params: [['name', 'icon', 'unit', 'description']],
form: {
show_help: true,
name: {
form_field: true,
type: "text",
field: "name",
label: "Name",
placeholder: "",
},
icon: {
form_field: true,
type: "emoji",
field: "icon",
label: "Icon",
placeholder: "",
},
unit: {
form_field: true,
type: "text",
field: "unit",
label: "Unit",
placeholder: "",
},
description: {
form_field: true,
type: "text",
field: "description",
label: "Description",
placeholder: "",
},
open_data_slug: {
form_field: true,
type: "text",
field: "open_data_slug",
disabled: true,
label: "Open_Data_Slug",
help_text: "open_data_help_text",
},
},
},
}
static RECIPE = { static RECIPE = {
name: "Recipe", name: "Recipe",
apiName: "Recipe", apiName: "Recipe",

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff