mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-06 22:58:19 -05:00
Merge branch 'TandoorRecipes:develop' into Auto-Planner
This commit is contained in:
@@ -3,6 +3,9 @@
|
||||
DEBUG=0
|
||||
SQL_DEBUG=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
|
||||
# TANDOOR_PORT=8080
|
||||
|
||||
120
.github/workflows/build-docker-open-data.yml
vendored
Normal file
120
.github/workflows/build-docker-open-data.yml
vendored
Normal 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
|
||||
3
boot.sh
3
boot.sh
@@ -4,6 +4,7 @@ source venv/bin/activate
|
||||
TANDOOR_PORT="${TANDOOR_PORT:-8080}"
|
||||
GUNICORN_WORKERS="${GUNICORN_WORKERS:-3}"
|
||||
GUNICORN_THREADS="${GUNICORN_THREADS:-2}"
|
||||
GUNICORN_LOG_LEVEL="${GUNICORN_LOG_LEVEL:-'info'}"
|
||||
NGINX_CONF_FILE=/opt/recipes/nginx/conf.d/Recipes.conf
|
||||
|
||||
display_warning() {
|
||||
@@ -65,4 +66,4 @@ echo "Done"
|
||||
|
||||
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
|
||||
|
||||
@@ -10,12 +10,13 @@ from treebeard.forms import movenodeform_factory
|
||||
|
||||
from cookbook.managers import DICTIONARY
|
||||
|
||||
from .models import (BookmarkletImport, Comment, CookLog, Food, FoodInheritField, ImportLog,
|
||||
Ingredient, InviteLink, Keyword, MealPlan, MealType, NutritionInformation,
|
||||
Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchPreference, ShareLink,
|
||||
ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
|
||||
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog,
|
||||
TelegramBot, Unit, UserFile, UserPreference, ViewLog, Automation, UserSpace)
|
||||
from .models import (Automation, BookmarkletImport, Comment, CookLog, Food, FoodInheritField,
|
||||
ImportLog, Ingredient, InviteLink, Keyword, MealPlan, MealType,
|
||||
NutritionInformation, Property, PropertyType, Recipe, RecipeBook,
|
||||
RecipeBookEntry, RecipeImport, SearchPreference, ShareLink, ShoppingList,
|
||||
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage, Supermarket,
|
||||
SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog, TelegramBot,
|
||||
Unit, UnitConversion, UserFile, UserPreference, UserSpace, ViewLog)
|
||||
|
||||
|
||||
class CustomUserAdmin(UserAdmin):
|
||||
@@ -150,9 +151,16 @@ class KeywordAdmin(TreeAdmin):
|
||||
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):
|
||||
list_display = ('name', 'order',)
|
||||
search_fields = ('name',)
|
||||
actions = [delete_unattached_steps]
|
||||
|
||||
|
||||
admin.site.register(Step, StepAdmin)
|
||||
@@ -201,9 +209,24 @@ class FoodAdmin(TreeAdmin):
|
||||
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):
|
||||
list_display = ('food', 'amount', 'unit')
|
||||
search_fields = ('food__name', 'unit__name')
|
||||
actions = [delete_unattached_ingredients]
|
||||
|
||||
|
||||
admin.site.register(Ingredient, IngredientAdmin)
|
||||
@@ -319,6 +342,20 @@ class ShareLinkAdmin(admin.ModelAdmin):
|
||||
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):
|
||||
list_display = ('id',)
|
||||
|
||||
|
||||
@@ -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):
|
||||
files = forms.FileField(required=True, widget=forms.ClearableFileInput(attrs={'multiple': True}))
|
||||
files = MultipleFileField(required=True)
|
||||
duplicates = forms.BooleanField(help_text=_(
|
||||
'To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'),
|
||||
required=False)
|
||||
|
||||
11
cookbook/helper/cache_helper.py
Normal file
11
cookbook/helper/cache_helper.py
Normal 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'
|
||||
@@ -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
|
||||
# Because it's no longer optional, no reason to return it
|
||||
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 filetype == '.jpeg' or filetype == '.jpg':
|
||||
return rescale_image_jpeg(image_object)
|
||||
|
||||
214
cookbook/helper/open_data_importer.py
Normal file
214
cookbook/helper/open_data_importer.py
Normal 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'))
|
||||
71
cookbook/helper/property_helper.py
Normal file
71
cookbook/helper/property_helper.py
Normal 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
|
||||
@@ -1,5 +1,6 @@
|
||||
# import random
|
||||
import re
|
||||
import traceback
|
||||
from html import unescape
|
||||
|
||||
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.ingredient_parser import IngredientParser
|
||||
from cookbook.models import Automation, Keyword
|
||||
from cookbook.models import Automation, Keyword, PropertyType
|
||||
|
||||
|
||||
# from unicodedata import decomposition
|
||||
|
||||
@@ -33,6 +35,9 @@ def get_from_scraper(scrape, request):
|
||||
except Exception:
|
||||
recipe_json['name'] = ''
|
||||
|
||||
if isinstance(recipe_json['name'], list) and len(recipe_json['name']) > 0:
|
||||
recipe_json['name'] = recipe_json['name'][0]
|
||||
|
||||
try:
|
||||
description = scrape.description() or None
|
||||
except Exception:
|
||||
@@ -193,7 +198,14 @@ def get_from_scraper(scrape, request):
|
||||
except Exception:
|
||||
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]
|
||||
for a in automations:
|
||||
if re.match(a.param_1, (recipe_json['source_url'])[:512]):
|
||||
@@ -203,6 +215,30 @@ def get_from_scraper(scrape, request):
|
||||
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):
|
||||
"""A YouTube Information Scraper."""
|
||||
kw, created = Keyword.objects.get_or_create(name='YouTube', space=request.space)
|
||||
|
||||
141
cookbook/helper/unit_conversion_helper.py
Normal file
141
cookbook/helper/unit_conversion_helper.py
Normal 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)
|
||||
@@ -51,6 +51,12 @@ class NextcloudCookbook(Integration):
|
||||
|
||||
ingredients_added = False
|
||||
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(
|
||||
instruction=s, space=self.request.space,
|
||||
)
|
||||
@@ -102,7 +108,6 @@ class NextcloudCookbook(Integration):
|
||||
m = min % 60
|
||||
return f'PT{h}H{m}M0S'
|
||||
|
||||
|
||||
def get_file_from_recipe(self, recipe):
|
||||
|
||||
export = {}
|
||||
@@ -133,7 +138,6 @@ class NextcloudCookbook(Integration):
|
||||
export['recipeIngredient'] = recipeIngredient
|
||||
export['recipeInstructions'] = recipeInstructions
|
||||
|
||||
|
||||
return "recipe.json", json.dumps(export)
|
||||
|
||||
def get_files_from_recipes(self, recipes, el, cookie):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from io import BytesIO
|
||||
|
||||
import requests
|
||||
import validators
|
||||
|
||||
from cookbook.helper.ingredient_parser import IngredientParser
|
||||
from cookbook.integration.integration import Integration
|
||||
@@ -67,6 +68,7 @@ class Plantoeat(Integration):
|
||||
|
||||
if image_url:
|
||||
try:
|
||||
if validators.url(image_url, public=True):
|
||||
response = requests.get(image_url)
|
||||
self.import_recipe_image(recipe, BytesIO(response.content))
|
||||
except Exception as e:
|
||||
|
||||
@@ -5,6 +5,7 @@ import requests
|
||||
import validators
|
||||
|
||||
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.models import Ingredient, Recipe, Step
|
||||
|
||||
@@ -18,19 +19,21 @@ class RecipeSage(Integration):
|
||||
created_by=self.request.user, internal=True,
|
||||
space=self.request.space)
|
||||
|
||||
try:
|
||||
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'] != '':
|
||||
recipe.waiting_time = int(file['totalTime']) - int(file['timePrep'])
|
||||
try:
|
||||
if 'totalTime' in file and file['totalTime'] != '':
|
||||
recipe.working_time = parse_time(file['totalTime'])
|
||||
|
||||
if file['prepTime'] != '':
|
||||
recipe.working_time = int(file['timePrep'])
|
||||
if 'timePrep' in file and file['prepTime'] != '':
|
||||
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()
|
||||
except Exception as e:
|
||||
print('failed to parse yield or time ', str(e))
|
||||
|
||||
ingredient_parser = IngredientParser(self.request, True)
|
||||
ingredients_added = False
|
||||
|
||||
@@ -22,9 +22,12 @@ class Rezeptsuitede(Integration):
|
||||
name=recipe_xml.find('head').attrib['title'].strip(),
|
||||
created_by=self.request.user, internal=True, space=self.request.space)
|
||||
|
||||
try:
|
||||
if recipe_xml.find('head').attrib['servingtype']:
|
||||
recipe.servings = parse_servings(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').find('line') is not None:
|
||||
@@ -50,6 +53,8 @@ class Rezeptsuitede(Integration):
|
||||
for ingredient in recipe_xml.find('part').findall('ingredient'):
|
||||
f = ingredient_parser.get_food(ingredient.attrib['item'])
|
||||
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'])
|
||||
ingredient_step.ingredients.add(Ingredient.objects.create(food=f, unit=u, amount=amount, space=self.request.space, ))
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: noxonad <noxonad@proton.me>\n"
|
||||
"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."
|
||||
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 ""
|
||||
"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. "
|
||||
@@ -133,7 +133,7 @@ msgstr ""
|
||||
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."
|
||||
|
||||
#: .\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."
|
||||
msgstr ""
|
||||
"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 "
|
||||
"d'usuari"
|
||||
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:314
|
||||
msgid "Name"
|
||||
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"
|
||||
msgstr "Paraules clau"
|
||||
|
||||
@@ -171,7 +171,7 @@ msgstr "Temps de preparació en minuts"
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
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"
|
||||
msgstr "Ruta"
|
||||
|
||||
@@ -183,7 +183,7 @@ msgstr "UID Emmagatzematge"
|
||||
msgid "Default"
|
||||
msgstr "Per defecte"
|
||||
|
||||
#: .\cookbook\forms.py:173
|
||||
#: .\cookbook\forms.py:190
|
||||
msgid ""
|
||||
"To prevent duplicates recipes with the same name as existing ones are "
|
||||
"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 "
|
||||
"existents. Marqueu aquesta casella per importar-ho tot."
|
||||
|
||||
#: .\cookbook\forms.py:196
|
||||
#: .\cookbook\forms.py:213
|
||||
msgid "Add your comment: "
|
||||
msgstr "Afegir el teu comentari: "
|
||||
|
||||
#: .\cookbook\forms.py:211
|
||||
#: .\cookbook\forms.py:228
|
||||
msgid "Leave empty for dropbox and enter app password for nextcloud."
|
||||
msgstr ""
|
||||
"Deixeu-lo buit per a Dropbox i introduïu la contrasenya de l'aplicació per a "
|
||||
"nextcloud."
|
||||
|
||||
#: .\cookbook\forms.py:218
|
||||
#: .\cookbook\forms.py:235
|
||||
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."
|
||||
|
||||
#: .\cookbook\forms.py:227
|
||||
#: .\cookbook\forms.py:244
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"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 "
|
||||
"(<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"
|
||||
msgstr "Emmagatzematge"
|
||||
|
||||
#: .\cookbook\forms.py:267
|
||||
#: .\cookbook\forms.py:284
|
||||
msgid "Active"
|
||||
msgstr "Actiu"
|
||||
|
||||
#: .\cookbook\forms.py:273
|
||||
#: .\cookbook\forms.py:290
|
||||
msgid "Search String"
|
||||
msgstr "Cerca Cadena"
|
||||
|
||||
#: .\cookbook\forms.py:300
|
||||
#: .\cookbook\forms.py:317
|
||||
msgid "File ID"
|
||||
msgstr "ID d'Arxiu"
|
||||
|
||||
#: .\cookbook\forms.py:322
|
||||
#: .\cookbook\forms.py:339
|
||||
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."
|
||||
|
||||
#: .\cookbook\forms.py:335
|
||||
#: .\cookbook\forms.py:352
|
||||
msgid "You can list default users to share recipes with in the settings."
|
||||
msgstr ""
|
||||
"Podeu llistar els usuaris predeterminats amb els quals voleu compartir "
|
||||
"receptes a la configuració."
|
||||
|
||||
#: .\cookbook\forms.py:336
|
||||
#: .\cookbook\forms.py:353
|
||||
msgid ""
|
||||
"You can use markdown to format this field. See the <a href=\"/docs/markdown/"
|
||||
"\">docs here</a>"
|
||||
@@ -247,15 +247,15 @@ msgstr ""
|
||||
"Podeu utilitzar el marcador per donar format a aquest camp. Consulteu els <a "
|
||||
"href=\"/docs/markdown/\">documents aquí </a>"
|
||||
|
||||
#: .\cookbook\forms.py:362
|
||||
#: .\cookbook\forms.py:379
|
||||
msgid "Maximum number of users for this space reached."
|
||||
msgstr "Nombre màxim d'usuaris assolit per a aquest espai."
|
||||
|
||||
#: .\cookbook\forms.py:368
|
||||
#: .\cookbook\forms.py:385
|
||||
msgid "Email address already taken!"
|
||||
msgstr "Adreça de correu electrònic existent!"
|
||||
|
||||
#: .\cookbook\forms.py:376
|
||||
#: .\cookbook\forms.py:393
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be sent "
|
||||
"to the user."
|
||||
@@ -263,15 +263,15 @@ msgstr ""
|
||||
"No cal una adreça de correu electrònic, però si està present, s'enviarà "
|
||||
"l'enllaç d'invitació a l'usuari."
|
||||
|
||||
#: .\cookbook\forms.py:391
|
||||
#: .\cookbook\forms.py:408
|
||||
msgid "Name already taken."
|
||||
msgstr "Nom agafat."
|
||||
|
||||
#: .\cookbook\forms.py:402
|
||||
#: .\cookbook\forms.py:419
|
||||
msgid "Accept Terms and Privacy"
|
||||
msgstr "Accepteu les condicions i la privadesa"
|
||||
|
||||
#: .\cookbook\forms.py:434
|
||||
#: .\cookbook\forms.py:451
|
||||
msgid ""
|
||||
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
|
||||
"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 "
|
||||
"ortogràfics)."
|
||||
|
||||
#: .\cookbook\forms.py:444
|
||||
#: .\cookbook\forms.py:461
|
||||
msgid ""
|
||||
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
|
||||
"full description of choices."
|
||||
@@ -288,7 +288,7 @@ msgstr ""
|
||||
"Seleccioneu el tipus de mètode de cerca. Feu clic <a href=\"/docs/search/"
|
||||
"\">aquí</a> per obtenir una descripció completa de les opcions."
|
||||
|
||||
#: .\cookbook\forms.py:445
|
||||
#: .\cookbook\forms.py:462
|
||||
msgid ""
|
||||
"Use fuzzy matching on units, keywords and ingredients when editing and "
|
||||
"importing recipes."
|
||||
@@ -296,7 +296,7 @@ msgstr ""
|
||||
"Utilitzeu la concordança difusa en unitats, paraules clau i ingredients quan "
|
||||
"editeu i importeu receptes."
|
||||
|
||||
#: .\cookbook\forms.py:447
|
||||
#: .\cookbook\forms.py:464
|
||||
msgid ""
|
||||
"Fields to search ignoring accents. Selecting this option can improve or "
|
||||
"degrade search quality depending on language"
|
||||
@@ -304,7 +304,7 @@ msgstr ""
|
||||
"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"
|
||||
|
||||
#: .\cookbook\forms.py:449
|
||||
#: .\cookbook\forms.py:466
|
||||
msgid ""
|
||||
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
|
||||
"'pie' and 'piece' and 'soapie')"
|
||||
@@ -312,7 +312,7 @@ msgstr ""
|
||||
"Camps per cercar coincidències parcials. (p. ex., en cercar \"Pastís\" "
|
||||
"tornarà \"pastís\" i \"peça\" i \"sabó\")"
|
||||
|
||||
#: .\cookbook\forms.py:451
|
||||
#: .\cookbook\forms.py:468
|
||||
msgid ""
|
||||
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
|
||||
"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 "
|
||||
"\"sa\" es tornarà \"amanida\" i \"entrepà\")"
|
||||
|
||||
#: .\cookbook\forms.py:453
|
||||
#: .\cookbook\forms.py:470
|
||||
msgid ""
|
||||
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
|
||||
"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 "
|
||||
"mètodes de cerca \"web\" i \"cru\"."
|
||||
|
||||
#: .\cookbook\forms.py:455
|
||||
#: .\cookbook\forms.py:472
|
||||
msgid ""
|
||||
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
|
||||
"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\", "
|
||||
"\"frase\" i \"en brut\" només funcionen amb camps de text complet."
|
||||
|
||||
#: .\cookbook\forms.py:459
|
||||
#: .\cookbook\forms.py:476
|
||||
msgid "Search Method"
|
||||
msgstr "Mètode de cerca"
|
||||
|
||||
#: .\cookbook\forms.py:460
|
||||
#: .\cookbook\forms.py:477
|
||||
msgid "Fuzzy Lookups"
|
||||
msgstr "Cerques difuses"
|
||||
|
||||
#: .\cookbook\forms.py:461
|
||||
#: .\cookbook\forms.py:478
|
||||
msgid "Ignore Accent"
|
||||
msgstr "Ignora Accents"
|
||||
|
||||
#: .\cookbook\forms.py:462
|
||||
#: .\cookbook\forms.py:479
|
||||
msgid "Partial Match"
|
||||
msgstr "Cerca Parcial"
|
||||
|
||||
#: .\cookbook\forms.py:463
|
||||
#: .\cookbook\forms.py:480
|
||||
msgid "Starts With"
|
||||
msgstr "Comença amb"
|
||||
|
||||
#: .\cookbook\forms.py:464
|
||||
#: .\cookbook\forms.py:481
|
||||
msgid "Fuzzy Search"
|
||||
msgstr "Cerca Difusa"
|
||||
|
||||
#: .\cookbook\forms.py:465
|
||||
#: .\cookbook\forms.py:482
|
||||
msgid "Full Text"
|
||||
msgstr "Text Sencer"
|
||||
|
||||
#: .\cookbook\forms.py:490
|
||||
#: .\cookbook\forms.py:507
|
||||
msgid ""
|
||||
"Users will see all items you add to your shopping list. They must add you "
|
||||
"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 "
|
||||
"compra. Us han d'afegir per veure els elements de la seva llista."
|
||||
|
||||
#: .\cookbook\forms.py:496
|
||||
#: .\cookbook\forms.py:513
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"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 "
|
||||
"automàtica), inclou totes les receptes relacionades."
|
||||
|
||||
#: .\cookbook\forms.py:497
|
||||
#: .\cookbook\forms.py:514
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"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 "
|
||||
"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."
|
||||
msgstr ""
|
||||
"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."
|
||||
msgstr ""
|
||||
"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."
|
||||
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."
|
||||
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."
|
||||
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."
|
||||
msgstr "Prefix per afegir en copiar la llista al porta-retalls."
|
||||
|
||||
#: .\cookbook\forms.py:507
|
||||
#: .\cookbook\forms.py:524
|
||||
msgid "Share Shopping List"
|
||||
msgstr "Compartir Llista de la Compra"
|
||||
|
||||
#: .\cookbook\forms.py:508
|
||||
#: .\cookbook\forms.py:525
|
||||
msgid "Autosync"
|
||||
msgstr "Autosync"
|
||||
|
||||
#: .\cookbook\forms.py:509
|
||||
#: .\cookbook\forms.py:526
|
||||
msgid "Auto Add Meal Plan"
|
||||
msgstr "Afegeix automàticament un pla d'àpats"
|
||||
|
||||
#: .\cookbook\forms.py:510
|
||||
#: .\cookbook\forms.py:527
|
||||
msgid "Exclude On Hand"
|
||||
msgstr "Exclou a mà"
|
||||
|
||||
#: .\cookbook\forms.py:511
|
||||
#: .\cookbook\forms.py:528
|
||||
msgid "Include Related"
|
||||
msgstr "Incloure Relacionats"
|
||||
|
||||
#: .\cookbook\forms.py:512
|
||||
#: .\cookbook\forms.py:529
|
||||
msgid "Default Delay Hours"
|
||||
msgstr "Hores de retard per defecte"
|
||||
|
||||
#: .\cookbook\forms.py:513
|
||||
#: .\cookbook\forms.py:530
|
||||
msgid "Filter to Supermarket"
|
||||
msgstr "Filtrar a supermercat"
|
||||
|
||||
#: .\cookbook\forms.py:514
|
||||
#: .\cookbook\forms.py:531
|
||||
msgid "Recent Days"
|
||||
msgstr "Dies recents"
|
||||
|
||||
#: .\cookbook\forms.py:515
|
||||
#: .\cookbook\forms.py:532
|
||||
msgid "CSV Delimiter"
|
||||
msgstr "Delimitador CSV"
|
||||
|
||||
#: .\cookbook\forms.py:516
|
||||
#: .\cookbook\forms.py:533
|
||||
msgid "List Prefix"
|
||||
msgstr "Prefix de Llista"
|
||||
|
||||
#: .\cookbook\forms.py:517
|
||||
#: .\cookbook\forms.py:534
|
||||
msgid "Auto On Hand"
|
||||
msgstr "Auto a mà"
|
||||
|
||||
#: .\cookbook\forms.py:527
|
||||
#: .\cookbook\forms.py:544
|
||||
msgid "Reset Food Inheritance"
|
||||
msgstr "Restablir Herència Alimentària"
|
||||
|
||||
#: .\cookbook\forms.py:528
|
||||
#: .\cookbook\forms.py:545
|
||||
msgid "Reset all food to inherit the fields configured."
|
||||
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."
|
||||
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"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-07-12 19:20+0200\n"
|
||||
"PO-Revision-Date: 2022-10-17 11:33+0000\n"
|
||||
"Last-Translator: Sokratis Potamias <sokratespot@gmail.com>\n"
|
||||
"PO-Revision-Date: 2023-05-31 17:19+0000\n"
|
||||
"Last-Translator: sweeney <sweeneytodd91@protonmail.com>\n"
|
||||
"Language-Team: Greek <http://translate.tandoor.dev/projects/tandoor/"
|
||||
"recipes-backend/el/>\n"
|
||||
"Language: el\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.10.1\n"
|
||||
"X-Generator: Weblate 4.15\n"
|
||||
|
||||
#: .\cookbook\filters.py:23 .\cookbook\templates\forms\ingredients.html:34
|
||||
#: .\cookbook\templates\stats.html:28
|
||||
@@ -26,7 +26,7 @@ msgstr "Συστατικά"
|
||||
|
||||
#: .\cookbook\forms.py:53
|
||||
msgid "Default unit"
|
||||
msgstr ""
|
||||
msgstr "Προεπιλεγμένη μονάδα μέτρησης"
|
||||
|
||||
#: .\cookbook\forms.py:54
|
||||
msgid "Use fractions"
|
||||
@@ -34,7 +34,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:55
|
||||
msgid "Use KJ"
|
||||
msgstr ""
|
||||
msgstr "Χρήση KiloJoule(KJ)"
|
||||
|
||||
#: .\cookbook\forms.py:56
|
||||
msgid "Theme"
|
||||
@@ -42,7 +42,7 @@ msgstr "Θέμα"
|
||||
|
||||
#: .\cookbook\forms.py:57
|
||||
msgid "Navbar color"
|
||||
msgstr ""
|
||||
msgstr "Χρώμα μπάρας πλοήγησης"
|
||||
|
||||
#: .\cookbook\forms.py:58
|
||||
msgid "Sticky navbar"
|
||||
@@ -50,19 +50,19 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:59
|
||||
msgid "Default page"
|
||||
msgstr ""
|
||||
msgstr "Προεπιλεγμένη σελίδα"
|
||||
|
||||
#: .\cookbook\forms.py:60
|
||||
msgid "Show recent recipes"
|
||||
msgstr ""
|
||||
msgstr "Προβολή πρόσφατων συνταγών"
|
||||
|
||||
#: .\cookbook\forms.py:61
|
||||
msgid "Search style"
|
||||
msgstr ""
|
||||
msgstr "Τρόπος αναζήτησης"
|
||||
|
||||
#: .\cookbook\forms.py:62
|
||||
msgid "Plan sharing"
|
||||
msgstr ""
|
||||
msgstr "Κοινοποίηση προγράμματος"
|
||||
|
||||
#: .\cookbook\forms.py:63
|
||||
msgid "Ingredient decimal places"
|
||||
@@ -70,7 +70,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:64
|
||||
msgid "Shopping list auto sync period"
|
||||
msgstr ""
|
||||
msgstr "Χρονική περίοδος αυτόματου συγχρονισμού λίστας αγορών"
|
||||
|
||||
#: .\cookbook\forms.py:65 .\cookbook\templates\recipe_view.html:21
|
||||
#: .\cookbook\templates\stats.html:47
|
||||
@@ -79,17 +79,21 @@ msgstr "Σχόλια"
|
||||
|
||||
#: .\cookbook\forms.py:66
|
||||
msgid "Left-handed mode"
|
||||
msgstr ""
|
||||
msgstr "Έκδοση για αριστερόχειρες"
|
||||
|
||||
#: .\cookbook\forms.py:70
|
||||
msgid ""
|
||||
"Color of the top navigation bar. Not all colors work with all themes, just "
|
||||
"try them out!"
|
||||
msgstr ""
|
||||
"Χρώμα της πάνω μπάρας πλοήγησης. Δεν δουλεύουν όλα τα χρώματα με όλα τα "
|
||||
"θέματα, απλά δοκιμάστε τα!"
|
||||
|
||||
#: .\cookbook\forms.py:72
|
||||
msgid "Default Unit to be used when inserting a new ingredient into a recipe."
|
||||
msgstr ""
|
||||
"Προεπιλεγμένη μονάδα μέτρησης που θα χρησιμοποιείται όταν προστίθεται ένα "
|
||||
"υλικό σε μια συνταγή."
|
||||
|
||||
#: .\cookbook\forms.py:74
|
||||
msgid ""
|
||||
@@ -104,18 +108,20 @@ msgstr ""
|
||||
#: .\cookbook\forms.py:77
|
||||
msgid "Users with whom newly created meal plans should be shared by default."
|
||||
msgstr ""
|
||||
"Χρήστες με του οποίους η κοινοποίηση του προγραμματισμού των γευμάτων θα "
|
||||
"γίνεται από προεπιλογή."
|
||||
|
||||
#: .\cookbook\forms.py:78
|
||||
msgid "Users with whom to share shopping lists."
|
||||
msgstr ""
|
||||
msgstr "Χρήστες με του οποίους θα γίνει κοινοποίηση των λιστών αγορών."
|
||||
|
||||
#: .\cookbook\forms.py:80
|
||||
msgid "Show recently viewed recipes on search page."
|
||||
msgstr ""
|
||||
msgstr "Προβολή των προσφάτως προβεβλημένων συνταγών στη σελίδα αναζήτησης."
|
||||
|
||||
#: .\cookbook\forms.py:81
|
||||
msgid "Number of decimals to round ingredients."
|
||||
msgstr ""
|
||||
msgstr "Αριθμός των δεκαδικών στα οποία θα γίνεται στρογγυλοποίηση."
|
||||
|
||||
#: .\cookbook\forms.py:82
|
||||
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
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
msgstr "Όνομα"
|
||||
|
||||
#: .\cookbook\forms.py:129 .\cookbook\forms.py:302
|
||||
#: .\cookbook\templates\stats.html:24 .\cookbook\views\lists.py:88
|
||||
msgid "Keywords"
|
||||
msgstr ""
|
||||
msgstr "Λέξεις κλειδιά"
|
||||
|
||||
#: .\cookbook\forms.py:130
|
||||
msgid "Preparation time in minutes"
|
||||
msgstr ""
|
||||
msgstr "Χρόνος προετοιμασίας σε λεπτά"
|
||||
|
||||
#: .\cookbook\forms.py:131
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
msgstr ""
|
||||
msgstr "Χρόνος αναμονής (μαγείρεμα/ ψήσιμο) σε λεπτά"
|
||||
|
||||
#: .\cookbook\forms.py:132 .\cookbook\forms.py:270 .\cookbook\forms.py:303
|
||||
msgid "Path"
|
||||
@@ -188,15 +194,18 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:200
|
||||
msgid "Add your comment: "
|
||||
msgstr ""
|
||||
msgstr "Προσθήκη σχολίου: "
|
||||
|
||||
#: .\cookbook\forms.py:215
|
||||
msgid "Leave empty for dropbox and enter app password for nextcloud."
|
||||
msgstr ""
|
||||
"Για dropbox παρακαλώ αφήστε το κενό και πληκτρολογήστε το password για "
|
||||
"nextcloud."
|
||||
|
||||
#: .\cookbook\forms.py:222
|
||||
msgid "Leave empty for nextcloud and enter api token for dropbox."
|
||||
msgstr ""
|
||||
"Για nextcloud αφήστε το κενό και για dropbox πληκτρολογήστε το api token."
|
||||
|
||||
#: .\cookbook\forms.py:231
|
||||
msgid ""
|
||||
@@ -240,7 +249,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:372
|
||||
msgid "Email address already taken!"
|
||||
msgstr ""
|
||||
msgstr "Αυτή η διεύθυνση email δεν είναι διαθέσιμη!"
|
||||
|
||||
#: .\cookbook\forms.py:380
|
||||
msgid ""
|
||||
@@ -306,7 +315,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:463
|
||||
msgid "Search Method"
|
||||
msgstr ""
|
||||
msgstr "Μέθοδος αναζήτησης"
|
||||
|
||||
#: .\cookbook\forms.py:464
|
||||
msgid "Fuzzy Lookups"
|
||||
@@ -330,19 +339,24 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:469
|
||||
msgid "Full Text"
|
||||
msgstr ""
|
||||
msgstr "Πλήρες κείμενο"
|
||||
|
||||
#: .\cookbook\forms.py:494
|
||||
msgid ""
|
||||
"Users will see all items you add to your shopping list. They must add you "
|
||||
"to see items on their list."
|
||||
msgstr ""
|
||||
"Οι χρήστες θα μπορούν να δουν όλα τα αντικείμενα που προστίθενται στην λίστα "
|
||||
"αγορών σας. Για να δείτε τα αντικείμενα στις λίστα αυτών θα πρέπει να σας "
|
||||
"προστέσουν."
|
||||
|
||||
#: .\cookbook\forms.py:500
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"include all related recipes."
|
||||
msgstr ""
|
||||
"Όταν προστίθεται ένα πρόγραμμα γευμάτων στη λίστα αγορών (χειροκίνητα ή "
|
||||
"αυτόματα), να συμπεριλαμβάνονται όλες οι σχετικές συνταγές."
|
||||
|
||||
#: .\cookbook\forms.py:501
|
||||
msgid ""
|
||||
@@ -353,18 +367,23 @@ msgstr ""
|
||||
#: .\cookbook\forms.py:502
|
||||
msgid "Default number of hours to delay a shopping list entry."
|
||||
msgstr ""
|
||||
"Προεπιλεγμένος αριθμός ωρών για την καθυστέρηση μιας εγγραφής στη λίστα "
|
||||
"αγορών."
|
||||
|
||||
#: .\cookbook\forms.py:503
|
||||
msgid "Filter shopping list to only include supermarket categories."
|
||||
msgstr ""
|
||||
"Φιλτράρισμα λίστας αγορών ώστε να περιλαμβάνει μόνο τις κατηγορίες του "
|
||||
"supermarker."
|
||||
|
||||
#: .\cookbook\forms.py:504
|
||||
msgid "Days of recent shopping list entries to display."
|
||||
msgstr ""
|
||||
msgstr "Αριθμός ημερών για τη προβολή των πρόσφατων εγγραφών της λίστας αγορών."
|
||||
|
||||
#: .\cookbook\forms.py:505
|
||||
msgid "Mark food 'On Hand' when checked off shopping list."
|
||||
msgstr ""
|
||||
"Χαρακτηρισμός ενός τροφίμου ως 'Διαθέσιμο' όταν τσεκαριστεί στη λίστα αγορών."
|
||||
|
||||
#: .\cookbook\forms.py:506
|
||||
msgid "Delimiter to use for CSV exports."
|
||||
@@ -376,27 +395,27 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:511
|
||||
msgid "Share Shopping List"
|
||||
msgstr ""
|
||||
msgstr "Κοινοποίηση λίστας αγορών"
|
||||
|
||||
#: .\cookbook\forms.py:512
|
||||
msgid "Autosync"
|
||||
msgstr ""
|
||||
msgstr "Αυτόματος συγχρονισμός"
|
||||
|
||||
#: .\cookbook\forms.py:513
|
||||
msgid "Auto Add Meal Plan"
|
||||
msgstr ""
|
||||
msgstr "Αυτόματη προσθήκη προγραμματισμού γευμάτων"
|
||||
|
||||
#: .\cookbook\forms.py:514
|
||||
msgid "Exclude On Hand"
|
||||
msgstr ""
|
||||
msgstr "Αποκλεισμός διαθέσιμων"
|
||||
|
||||
#: .\cookbook\forms.py:515
|
||||
msgid "Include Related"
|
||||
msgstr ""
|
||||
msgstr "Συμπερίληψη σχετικών"
|
||||
|
||||
#: .\cookbook\forms.py:516
|
||||
msgid "Default Delay Hours"
|
||||
msgstr ""
|
||||
msgstr "Προεπιλεγμένες ώρες καθυστέρησης"
|
||||
|
||||
#: .\cookbook\forms.py:517
|
||||
msgid "Filter to Supermarket"
|
||||
@@ -404,7 +423,7 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:518
|
||||
msgid "Recent Days"
|
||||
msgstr ""
|
||||
msgstr "Πρόσφατες ημέρες"
|
||||
|
||||
#: .\cookbook\forms.py:519
|
||||
msgid "CSV Delimiter"
|
||||
@@ -439,11 +458,13 @@ msgid ""
|
||||
"In order to prevent spam, the requested email was not send. Please wait a "
|
||||
"few minutes and try again."
|
||||
msgstr ""
|
||||
"Για να αποκλείσουμε πιθανά spam, το email που ζητήθηκε δεν στάλθηκε. "
|
||||
"Παρακαλώ περιμένετε λίγα λεπτά και δοκιμάστε ξανά."
|
||||
|
||||
#: .\cookbook\helper\permission_helper.py:149
|
||||
#: .\cookbook\helper\permission_helper.py:172 .\cookbook\views\views.py:152
|
||||
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:159
|
||||
@@ -455,7 +476,7 @@ msgstr ""
|
||||
#: .\cookbook\views\views.py:163 .\cookbook\views\views.py:170
|
||||
#: .\cookbook\views\views.py:249
|
||||
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:200
|
||||
@@ -463,14 +484,15 @@ msgstr ""
|
||||
#: .\cookbook\helper\permission_helper.py:237
|
||||
msgid "You cannot interact with this object as it is not owned by you!"
|
||||
msgstr ""
|
||||
"Δεν μπορείτε να αλληλεπιδράστε με αυτό το αντικείμενο γιατί δεν σας ανήκει!"
|
||||
|
||||
#: .\cookbook\helper\permission_helper.py:321
|
||||
msgid "You have reached the maximum number of recipes for your space."
|
||||
msgstr ""
|
||||
msgstr "Έχετε υπερβεί τον μέγιστο αριθμό συνταγών για τον χώρο σας."
|
||||
|
||||
#: .\cookbook\helper\permission_helper.py:333
|
||||
msgid "You have more users than allowed in your space."
|
||||
msgstr ""
|
||||
msgstr "Έχετε περισσότερους χρήστες από το επιτρεπόμενο στον χώρο σας."
|
||||
|
||||
#: .\cookbook\helper\recipe_search.py:565
|
||||
msgid "One of queryset or hash_key must be provided"
|
||||
@@ -519,33 +541,33 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\integration\paprika.py:46
|
||||
msgid "Notes"
|
||||
msgstr ""
|
||||
msgstr "Σημειώσεις"
|
||||
|
||||
#: .\cookbook\integration\paprika.py:49
|
||||
msgid "Nutritional Information"
|
||||
msgstr ""
|
||||
msgstr "Διατροφικές πληροφορίες"
|
||||
|
||||
#: .\cookbook\integration\paprika.py:53
|
||||
msgid "Source"
|
||||
msgstr ""
|
||||
msgstr "Πηγή"
|
||||
|
||||
#: .\cookbook\integration\saffron.py:23
|
||||
msgid "Servings"
|
||||
msgstr ""
|
||||
msgstr "Μερίδες"
|
||||
|
||||
#: .\cookbook\integration\saffron.py:25
|
||||
msgid "Waiting time"
|
||||
msgstr ""
|
||||
msgstr "Χρόνος αναμονής"
|
||||
|
||||
#: .\cookbook\integration\saffron.py:27
|
||||
msgid "Preparation Time"
|
||||
msgstr ""
|
||||
msgstr "Χρόνος προετοιμασίας"
|
||||
|
||||
#: .\cookbook\integration\saffron.py:29
|
||||
#: .\cookbook\templates\forms\ingredients.html:7
|
||||
#: .\cookbook\templates\index.html:7
|
||||
msgid "Cookbook"
|
||||
msgstr ""
|
||||
msgstr "Βιβλίο συνταγών"
|
||||
|
||||
#: .\cookbook\integration\saffron.py:31
|
||||
msgid "Section"
|
||||
@@ -569,19 +591,19 @@ msgstr ""
|
||||
|
||||
#: .\cookbook\migrations\0047_auto_20200602_1133.py:14
|
||||
msgid "Breakfast"
|
||||
msgstr ""
|
||||
msgstr "Πρωινό"
|
||||
|
||||
#: .\cookbook\migrations\0047_auto_20200602_1133.py:19
|
||||
msgid "Lunch"
|
||||
msgstr ""
|
||||
msgstr "Μεσημεριανό"
|
||||
|
||||
#: .\cookbook\migrations\0047_auto_20200602_1133.py:24
|
||||
msgid "Dinner"
|
||||
msgstr ""
|
||||
msgstr "Βραδινό"
|
||||
|
||||
#: .\cookbook\migrations\0047_auto_20200602_1133.py:29
|
||||
msgid "Other"
|
||||
msgstr ""
|
||||
msgstr "Άλλο"
|
||||
|
||||
#: .\cookbook\models.py:251
|
||||
msgid ""
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\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."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492
|
||||
#: .\cookbook\forms.py:79 .\cookbook\forms.py:509
|
||||
msgid ""
|
||||
"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. "
|
||||
@@ -114,7 +114,7 @@ msgstr ""
|
||||
msgid "Makes the navbar stick to the top of the page."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
@@ -132,11 +132,11 @@ msgid ""
|
||||
"instead"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:314
|
||||
msgid "Name"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
@@ -148,7 +148,7 @@ msgstr ""
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
@@ -160,261 +160,261 @@ msgstr ""
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:173
|
||||
#: .\cookbook\forms.py:190
|
||||
msgid ""
|
||||
"To prevent duplicates recipes with the same name as existing ones are "
|
||||
"ignored. Check this box to import everything."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:196
|
||||
#: .\cookbook\forms.py:213
|
||||
msgid "Add your comment: "
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:211
|
||||
#: .\cookbook\forms.py:228
|
||||
msgid "Leave empty for dropbox and enter app password for nextcloud."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:218
|
||||
#: .\cookbook\forms.py:235
|
||||
msgid "Leave empty for nextcloud and enter api token for dropbox."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:227
|
||||
#: .\cookbook\forms.py:244
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"php/webdav/</code> is added automatically)"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157
|
||||
#: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
|
||||
msgid "Storage"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:267
|
||||
#: .\cookbook\forms.py:284
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:273
|
||||
#: .\cookbook\forms.py:290
|
||||
msgid "Search String"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:300
|
||||
#: .\cookbook\forms.py:317
|
||||
msgid "File ID"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:322
|
||||
#: .\cookbook\forms.py:339
|
||||
msgid "You must provide at least a recipe or a title."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:335
|
||||
#: .\cookbook\forms.py:352
|
||||
msgid "You can list default users to share recipes with in the settings."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:336
|
||||
#: .\cookbook\forms.py:353
|
||||
msgid ""
|
||||
"You can use markdown to format this field. See the <a href=\"/docs/markdown/"
|
||||
"\">docs here</a>"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:362
|
||||
#: .\cookbook\forms.py:379
|
||||
msgid "Maximum number of users for this space reached."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:368
|
||||
#: .\cookbook\forms.py:385
|
||||
msgid "Email address already taken!"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:376
|
||||
#: .\cookbook\forms.py:393
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be sent "
|
||||
"to the user."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:391
|
||||
#: .\cookbook\forms.py:408
|
||||
msgid "Name already taken."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:402
|
||||
#: .\cookbook\forms.py:419
|
||||
msgid "Accept Terms and Privacy"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:434
|
||||
#: .\cookbook\forms.py:451
|
||||
msgid ""
|
||||
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
|
||||
"g. low values mean more typos are ignored)."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:444
|
||||
#: .\cookbook\forms.py:461
|
||||
msgid ""
|
||||
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
|
||||
"full description of choices."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:445
|
||||
#: .\cookbook\forms.py:462
|
||||
msgid ""
|
||||
"Use fuzzy matching on units, keywords and ingredients when editing and "
|
||||
"importing recipes."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:447
|
||||
#: .\cookbook\forms.py:464
|
||||
msgid ""
|
||||
"Fields to search ignoring accents. Selecting this option can improve or "
|
||||
"degrade search quality depending on language"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:449
|
||||
#: .\cookbook\forms.py:466
|
||||
msgid ""
|
||||
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
|
||||
"'pie' and 'piece' and 'soapie')"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:451
|
||||
#: .\cookbook\forms.py:468
|
||||
msgid ""
|
||||
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
|
||||
"will return 'salad' and 'sandwich')"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:453
|
||||
#: .\cookbook\forms.py:470
|
||||
msgid ""
|
||||
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
|
||||
"Note: this option will conflict with 'web' and 'raw' methods of search."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:455
|
||||
#: .\cookbook\forms.py:472
|
||||
msgid ""
|
||||
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
|
||||
"only function with fulltext fields."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:459
|
||||
#: .\cookbook\forms.py:476
|
||||
msgid "Search Method"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:460
|
||||
#: .\cookbook\forms.py:477
|
||||
msgid "Fuzzy Lookups"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:461
|
||||
#: .\cookbook\forms.py:478
|
||||
msgid "Ignore Accent"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:462
|
||||
#: .\cookbook\forms.py:479
|
||||
msgid "Partial Match"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:463
|
||||
#: .\cookbook\forms.py:480
|
||||
msgid "Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:464
|
||||
#: .\cookbook\forms.py:481
|
||||
msgid "Fuzzy Search"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:465
|
||||
#: .\cookbook\forms.py:482
|
||||
msgid "Full Text"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:490
|
||||
#: .\cookbook\forms.py:507
|
||||
msgid ""
|
||||
"Users will see all items you add to your shopping list. They must add you "
|
||||
"to see items on their list."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:496
|
||||
#: .\cookbook\forms.py:513
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"include all related recipes."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:497
|
||||
#: .\cookbook\forms.py:514
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"exclude ingredients that are on hand."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:498
|
||||
#: .\cookbook\forms.py:515
|
||||
msgid "Default number of hours to delay a shopping list entry."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:499
|
||||
#: .\cookbook\forms.py:516
|
||||
msgid "Filter shopping list to only include supermarket categories."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:500
|
||||
#: .\cookbook\forms.py:517
|
||||
msgid "Days of recent shopping list entries to display."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:501
|
||||
#: .\cookbook\forms.py:518
|
||||
msgid "Mark food 'On Hand' when checked off shopping list."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:502
|
||||
#: .\cookbook\forms.py:519
|
||||
msgid "Delimiter to use for CSV exports."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:503
|
||||
#: .\cookbook\forms.py:520
|
||||
msgid "Prefix to add when copying list to the clipboard."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:507
|
||||
#: .\cookbook\forms.py:524
|
||||
msgid "Share Shopping List"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:508
|
||||
#: .\cookbook\forms.py:525
|
||||
msgid "Autosync"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:509
|
||||
#: .\cookbook\forms.py:526
|
||||
msgid "Auto Add Meal Plan"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:510
|
||||
#: .\cookbook\forms.py:527
|
||||
msgid "Exclude On Hand"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:511
|
||||
#: .\cookbook\forms.py:528
|
||||
msgid "Include Related"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:512
|
||||
#: .\cookbook\forms.py:529
|
||||
msgid "Default Delay Hours"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:513
|
||||
#: .\cookbook\forms.py:530
|
||||
msgid "Filter to Supermarket"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:514
|
||||
#: .\cookbook\forms.py:531
|
||||
msgid "Recent Days"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:515
|
||||
#: .\cookbook\forms.py:532
|
||||
msgid "CSV Delimiter"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:516
|
||||
#: .\cookbook\forms.py:533
|
||||
msgid "List Prefix"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:517
|
||||
#: .\cookbook\forms.py:534
|
||||
msgid "Auto On Hand"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:527
|
||||
#: .\cookbook\forms.py:544
|
||||
msgid "Reset Food Inheritance"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:528
|
||||
#: .\cookbook\forms.py:545
|
||||
msgid "Reset all food to inherit the fields configured."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:540
|
||||
#: .\cookbook\forms.py:557
|
||||
msgid "Fields on food that should be inherited by default."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:541
|
||||
#: .\cookbook\forms.py:558
|
||||
msgid "Show recipe counts on search filters"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:542
|
||||
#: .\cookbook\forms.py:559
|
||||
msgid "Use the plural form for units and food inside this space."
|
||||
msgstr ""
|
||||
|
||||
|
||||
Binary file not shown.
@@ -13,9 +13,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-04-26 07:46+0200\n"
|
||||
"PO-Revision-Date: 2023-03-13 06:55+0000\n"
|
||||
"Last-Translator: Amara Ude <apu24@drexel.edu>\n"
|
||||
"POT-Creation-Date: 2023-05-18 14:28+0200\n"
|
||||
"PO-Revision-Date: 2023-05-26 16:19+0000\n"
|
||||
"Last-Translator: Luis Cacho <luiscachog@gmail.com>\n"
|
||||
"Language-Team: Spanish <http://translate.tandoor.dev/projects/tandoor/"
|
||||
"recipes-backend/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."
|
||||
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 ""
|
||||
"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. "
|
||||
@@ -135,7 +135,7 @@ msgstr ""
|
||||
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."
|
||||
|
||||
#: .\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."
|
||||
msgstr ""
|
||||
"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 "
|
||||
"nombre de usuario en su lugar"
|
||||
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:314
|
||||
msgid "Name"
|
||||
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"
|
||||
msgstr "Palabras clave"
|
||||
|
||||
@@ -173,7 +173,7 @@ msgstr "Tiempo de preparación en minutos"
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
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"
|
||||
msgstr "Ruta"
|
||||
|
||||
@@ -185,7 +185,7 @@ msgstr "UID de almacenamiento"
|
||||
msgid "Default"
|
||||
msgstr "Por defecto"
|
||||
|
||||
#: .\cookbook\forms.py:173
|
||||
#: .\cookbook\forms.py:190
|
||||
msgid ""
|
||||
"To prevent duplicates recipes with the same name as existing ones are "
|
||||
"ignored. Check this box to import everything."
|
||||
@@ -193,22 +193,22 @@ msgstr ""
|
||||
"Para evitar duplicados, las recetas con el mismo nombre serán ignoradas. "
|
||||
"Marca esta opción para importar todas las recetas."
|
||||
|
||||
#: .\cookbook\forms.py:196
|
||||
#: .\cookbook\forms.py:213
|
||||
msgid "Add your comment: "
|
||||
msgstr "Añada su comentario: "
|
||||
|
||||
#: .\cookbook\forms.py:211
|
||||
#: .\cookbook\forms.py:228
|
||||
msgid "Leave empty for dropbox and enter app password for nextcloud."
|
||||
msgstr ""
|
||||
"Déjelo vacío para Dropbox e ingrese la contraseña de la aplicación para "
|
||||
"nextcloud."
|
||||
|
||||
#: .\cookbook\forms.py:218
|
||||
#: .\cookbook\forms.py:235
|
||||
msgid "Leave empty for nextcloud and enter api token for dropbox."
|
||||
msgstr ""
|
||||
"Déjelo en blanco para nextcloud e ingrese el token de api para dropbox."
|
||||
|
||||
#: .\cookbook\forms.py:227
|
||||
#: .\cookbook\forms.py:244
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"php/webdav/</code> is added automatically)"
|
||||
@@ -216,33 +216,33 @@ msgstr ""
|
||||
"Dejar vació para Dropbox e introducir sólo la URL base para Nextcloud "
|
||||
"(<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"
|
||||
msgstr "Almacenamiento"
|
||||
|
||||
#: .\cookbook\forms.py:267
|
||||
#: .\cookbook\forms.py:284
|
||||
msgid "Active"
|
||||
msgstr "Activo"
|
||||
|
||||
#: .\cookbook\forms.py:273
|
||||
#: .\cookbook\forms.py:290
|
||||
msgid "Search String"
|
||||
msgstr "Cadena de búsqueda"
|
||||
|
||||
#: .\cookbook\forms.py:300
|
||||
#: .\cookbook\forms.py:317
|
||||
msgid "File ID"
|
||||
msgstr "ID de Fichero"
|
||||
|
||||
#: .\cookbook\forms.py:322
|
||||
#: .\cookbook\forms.py:339
|
||||
msgid "You must provide at least a recipe or a title."
|
||||
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."
|
||||
msgstr ""
|
||||
"Puede enumerar los usuarios predeterminados con los que compartir recetas en "
|
||||
"la configuración."
|
||||
|
||||
#: .\cookbook\forms.py:336
|
||||
#: .\cookbook\forms.py:353
|
||||
msgid ""
|
||||
"You can use markdown to format this field. See the <a href=\"/docs/markdown/"
|
||||
"\">docs here</a>"
|
||||
@@ -250,15 +250,15 @@ msgstr ""
|
||||
"Puede utilizar Markdown para formatear este campo. Vea la <a href=\"/docs/"
|
||||
"markdown/\">documentación aqui</a>"
|
||||
|
||||
#: .\cookbook\forms.py:362
|
||||
#: .\cookbook\forms.py:379
|
||||
msgid "Maximum number of users for this space reached."
|
||||
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!"
|
||||
msgstr "¡El correo electrónico ya existe!"
|
||||
|
||||
#: .\cookbook\forms.py:376
|
||||
#: .\cookbook\forms.py:393
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be sent "
|
||||
"to the user."
|
||||
@@ -266,15 +266,15 @@ msgstr ""
|
||||
"El correo electrónico es opcional. Si se añade uno se mandará un link de "
|
||||
"invitación."
|
||||
|
||||
#: .\cookbook\forms.py:391
|
||||
#: .\cookbook\forms.py:408
|
||||
msgid "Name already taken."
|
||||
msgstr "El nombre ya existe."
|
||||
|
||||
#: .\cookbook\forms.py:402
|
||||
#: .\cookbook\forms.py:419
|
||||
msgid "Accept Terms and Privacy"
|
||||
msgstr "Aceptar términos y condiciones"
|
||||
|
||||
#: .\cookbook\forms.py:434
|
||||
#: .\cookbook\forms.py:451
|
||||
msgid ""
|
||||
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
|
||||
"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 "
|
||||
"van a ignorar)."
|
||||
|
||||
#: .\cookbook\forms.py:444
|
||||
#: .\cookbook\forms.py:461
|
||||
msgid ""
|
||||
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
|
||||
"full description of choices."
|
||||
@@ -291,7 +291,7 @@ msgstr ""
|
||||
"Selecciona el tipo de búsqueda. Haz click <a href=\"/docs/search/\">aquí</"
|
||||
"a> para una descripción completa de las opciones."
|
||||
|
||||
#: .\cookbook\forms.py:445
|
||||
#: .\cookbook\forms.py:462
|
||||
msgid ""
|
||||
"Use fuzzy matching on units, keywords and ingredients when editing and "
|
||||
"importing recipes."
|
||||
@@ -299,7 +299,7 @@ msgstr ""
|
||||
"Utilizar comparación difusa en unidades, palabras clave e ingredientes al "
|
||||
"editar e importar recetas."
|
||||
|
||||
#: .\cookbook\forms.py:447
|
||||
#: .\cookbook\forms.py:464
|
||||
msgid ""
|
||||
"Fields to search ignoring accents. Selecting this option can improve or "
|
||||
"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 "
|
||||
"mejorar o degradar la calidad de la búsqueda dependiendo del idioma"
|
||||
|
||||
#: .\cookbook\forms.py:449
|
||||
#: .\cookbook\forms.py:466
|
||||
msgid ""
|
||||
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
|
||||
"'pie' and 'piece' and 'soapie')"
|
||||
@@ -315,7 +315,7 @@ msgstr ""
|
||||
"Campos de búsqueda para coincidencias parciales. (por ejemplo, buscar 'Pie' "
|
||||
"devolverá 'pie' y 'piece' y 'soapie')"
|
||||
|
||||
#: .\cookbook\forms.py:451
|
||||
#: .\cookbook\forms.py:468
|
||||
msgid ""
|
||||
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
|
||||
"will return 'salad' and 'sandwich')"
|
||||
@@ -323,7 +323,7 @@ msgstr ""
|
||||
"Campos de búsqueda para coincidencias al principio de la palabra. (por "
|
||||
"ejemplo, buscar 'sa' devolverá 'ensalada' y 'sándwich')"
|
||||
|
||||
#: .\cookbook\forms.py:453
|
||||
#: .\cookbook\forms.py:470
|
||||
msgid ""
|
||||
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
|
||||
"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 "
|
||||
"búsqueda 'web' y 'raw'."
|
||||
|
||||
#: .\cookbook\forms.py:455
|
||||
#: .\cookbook\forms.py:472
|
||||
msgid ""
|
||||
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
|
||||
"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', "
|
||||
"'phrase' y 'raw' solo funcionan con campos de texto completo."
|
||||
|
||||
#: .\cookbook\forms.py:459
|
||||
#: .\cookbook\forms.py:476
|
||||
msgid "Search Method"
|
||||
msgstr "Método de Búsqueda"
|
||||
|
||||
#: .\cookbook\forms.py:460
|
||||
#: .\cookbook\forms.py:477
|
||||
msgid "Fuzzy Lookups"
|
||||
msgstr "Búsquedas difusas"
|
||||
|
||||
#: .\cookbook\forms.py:461
|
||||
#: .\cookbook\forms.py:478
|
||||
msgid "Ignore Accent"
|
||||
msgstr "Ignorar Acento"
|
||||
|
||||
#: .\cookbook\forms.py:462
|
||||
#: .\cookbook\forms.py:479
|
||||
msgid "Partial Match"
|
||||
msgstr "Coincidencia Parcial"
|
||||
|
||||
#: .\cookbook\forms.py:463
|
||||
#: .\cookbook\forms.py:480
|
||||
msgid "Starts With"
|
||||
msgstr "Comienza Con"
|
||||
|
||||
#: .\cookbook\forms.py:464
|
||||
#: .\cookbook\forms.py:481
|
||||
msgid "Fuzzy Search"
|
||||
msgstr "Búsqueda Difusa"
|
||||
|
||||
#: .\cookbook\forms.py:465
|
||||
#: .\cookbook\forms.py:482
|
||||
msgid "Full Text"
|
||||
msgstr "Texto Completo"
|
||||
|
||||
#: .\cookbook\forms.py:490
|
||||
#: .\cookbook\forms.py:507
|
||||
msgid ""
|
||||
"Users will see all items you add to your shopping list. They must add you "
|
||||
"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. "
|
||||
"Deben agregarte para ver los elementos en su lista."
|
||||
|
||||
#: .\cookbook\forms.py:496
|
||||
#: .\cookbook\forms.py:513
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"include all related recipes."
|
||||
@@ -384,7 +384,7 @@ msgstr ""
|
||||
"Al agregar un plan de comidas a la lista de compras (manualmente o "
|
||||
"automáticamente), incluir todas las recetas relacionadas."
|
||||
|
||||
#: .\cookbook\forms.py:497
|
||||
#: .\cookbook\forms.py:514
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"exclude ingredients that are on hand."
|
||||
@@ -392,96 +392,96 @@ msgstr ""
|
||||
"Al agregar un plan de comidas a la lista de compras (manualmente o "
|
||||
"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."
|
||||
msgstr ""
|
||||
"Número predeterminado de horas para retrasar una entrada en la lista de "
|
||||
"compras."
|
||||
|
||||
#: .\cookbook\forms.py:499
|
||||
#: .\cookbook\forms.py:516
|
||||
msgid "Filter shopping list to only include supermarket categories."
|
||||
msgstr ""
|
||||
"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."
|
||||
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."
|
||||
msgstr ""
|
||||
"Marcar los alimentos como 'Disponible' cuando se marca en la lista de "
|
||||
"compras."
|
||||
|
||||
#: .\cookbook\forms.py:502
|
||||
#: .\cookbook\forms.py:519
|
||||
msgid "Delimiter to use for CSV exports."
|
||||
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."
|
||||
msgstr "Prefijo a agregar al copiar la lista al portapapeles."
|
||||
|
||||
#: .\cookbook\forms.py:507
|
||||
#: .\cookbook\forms.py:524
|
||||
msgid "Share Shopping List"
|
||||
msgstr "Compartir Lista de la Compra"
|
||||
|
||||
#: .\cookbook\forms.py:508
|
||||
#: .\cookbook\forms.py:525
|
||||
msgid "Autosync"
|
||||
msgstr "Autosincronización"
|
||||
|
||||
#: .\cookbook\forms.py:509
|
||||
#: .\cookbook\forms.py:526
|
||||
msgid "Auto Add Meal Plan"
|
||||
msgstr "Agregar Plan de Comidas automáticamente"
|
||||
|
||||
#: .\cookbook\forms.py:510
|
||||
#: .\cookbook\forms.py:527
|
||||
msgid "Exclude On Hand"
|
||||
msgstr "Excluir Disponible"
|
||||
|
||||
#: .\cookbook\forms.py:511
|
||||
#: .\cookbook\forms.py:528
|
||||
msgid "Include Related"
|
||||
msgstr "Incluir Relacionados"
|
||||
|
||||
#: .\cookbook\forms.py:512
|
||||
#: .\cookbook\forms.py:529
|
||||
msgid "Default Delay Hours"
|
||||
msgstr "Horas de Retraso Predeterminadas"
|
||||
|
||||
#: .\cookbook\forms.py:513
|
||||
#: .\cookbook\forms.py:530
|
||||
msgid "Filter to Supermarket"
|
||||
msgstr "Filtrar según Supermercado"
|
||||
|
||||
#: .\cookbook\forms.py:514
|
||||
#: .\cookbook\forms.py:531
|
||||
msgid "Recent Days"
|
||||
msgstr "Días Recientes"
|
||||
|
||||
#: .\cookbook\forms.py:515
|
||||
#: .\cookbook\forms.py:532
|
||||
msgid "CSV Delimiter"
|
||||
msgstr "Delimitador CSV"
|
||||
|
||||
#: .\cookbook\forms.py:516
|
||||
#: .\cookbook\forms.py:533
|
||||
msgid "List Prefix"
|
||||
msgstr "Prefijo de la lista"
|
||||
|
||||
#: .\cookbook\forms.py:517
|
||||
#: .\cookbook\forms.py:534
|
||||
msgid "Auto On Hand"
|
||||
msgstr "Auto en existencia"
|
||||
|
||||
#: .\cookbook\forms.py:527
|
||||
#: .\cookbook\forms.py:544
|
||||
msgid "Reset Food Inheritance"
|
||||
msgstr "Restablecer la herencia de alimentos"
|
||||
|
||||
#: .\cookbook\forms.py:528
|
||||
#: .\cookbook\forms.py:545
|
||||
msgid "Reset all food to inherit the fields configured."
|
||||
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."
|
||||
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"
|
||||
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."
|
||||
msgstr ""
|
||||
"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."
|
||||
|
||||
#: .\cookbook\integration\openeats.py:26
|
||||
#, fuzzy
|
||||
#| msgid "Recipe Home"
|
||||
msgid "Recipe source:"
|
||||
msgstr "Página de inicio"
|
||||
msgstr "Recipe source:"
|
||||
|
||||
#: .\cookbook\integration\paprika.py:49
|
||||
msgid "Notes"
|
||||
|
||||
@@ -13,7 +13,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: noxonad <noxonad@proton.me>\n"
|
||||
"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 "
|
||||
"recettes."
|
||||
|
||||
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492
|
||||
#: .\cookbook\forms.py:79 .\cookbook\forms.py:509
|
||||
msgid ""
|
||||
"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. "
|
||||
@@ -139,7 +139,7 @@ msgstr ""
|
||||
msgid "Makes the navbar stick to the top of the 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."
|
||||
msgstr ""
|
||||
"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 n’est rempli, le nom "
|
||||
"d’utilisateur sera affiché à la place"
|
||||
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:314
|
||||
msgid "Name"
|
||||
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"
|
||||
msgstr "Mots-clés"
|
||||
|
||||
@@ -177,7 +177,7 @@ msgstr "Temps de préparation en minutes"
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
msgstr "Temps d’attente (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"
|
||||
msgstr "Chemin"
|
||||
|
||||
@@ -189,7 +189,7 @@ msgstr "UID de stockage"
|
||||
msgid "Default"
|
||||
msgstr "Par défaut"
|
||||
|
||||
#: .\cookbook\forms.py:173
|
||||
#: .\cookbook\forms.py:190
|
||||
msgid ""
|
||||
"To prevent duplicates recipes with the same name as existing ones are "
|
||||
"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 "
|
||||
"cette case pour tout importer."
|
||||
|
||||
#: .\cookbook\forms.py:196
|
||||
#: .\cookbook\forms.py:213
|
||||
msgid "Add your comment: "
|
||||
msgstr "Ajoutez votre commentaire : "
|
||||
|
||||
#: .\cookbook\forms.py:211
|
||||
#: .\cookbook\forms.py:228
|
||||
msgid "Leave empty for dropbox and enter app password for nextcloud."
|
||||
msgstr ""
|
||||
"Laissez vide pour Dropbox et renseignez votre mot de passe d’application "
|
||||
"pour Nextcloud."
|
||||
|
||||
#: .\cookbook\forms.py:218
|
||||
#: .\cookbook\forms.py:235
|
||||
msgid "Leave empty for nextcloud and enter api token for dropbox."
|
||||
msgstr ""
|
||||
"Laissez vide pour Nextcloud et renseignez votre jeton d’API pour Dropbox."
|
||||
|
||||
#: .\cookbook\forms.py:227
|
||||
#: .\cookbook\forms.py:244
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"php/webdav/</code> is added automatically)"
|
||||
@@ -220,33 +220,33 @@ msgstr ""
|
||||
"Laisser vide pour Dropbox et saisissez seulement l’URL de base pour "
|
||||
"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"
|
||||
msgstr "Stockage"
|
||||
|
||||
#: .\cookbook\forms.py:267
|
||||
#: .\cookbook\forms.py:284
|
||||
msgid "Active"
|
||||
msgstr "Actif"
|
||||
|
||||
#: .\cookbook\forms.py:273
|
||||
#: .\cookbook\forms.py:290
|
||||
msgid "Search String"
|
||||
msgstr "Texte recherché"
|
||||
|
||||
#: .\cookbook\forms.py:300
|
||||
#: .\cookbook\forms.py:317
|
||||
msgid "File ID"
|
||||
msgstr "ID du fichier"
|
||||
|
||||
#: .\cookbook\forms.py:322
|
||||
#: .\cookbook\forms.py:339
|
||||
msgid "You must provide at least a recipe or a title."
|
||||
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."
|
||||
msgstr ""
|
||||
"Vous pouvez lister les utilisateurs par défaut avec qui partager des "
|
||||
"recettes dans les paramètres."
|
||||
|
||||
#: .\cookbook\forms.py:336
|
||||
#: .\cookbook\forms.py:353
|
||||
msgid ""
|
||||
"You can use markdown to format this field. See the <a href=\"/docs/markdown/"
|
||||
"\">docs here</a>"
|
||||
@@ -254,15 +254,15 @@ msgstr ""
|
||||
"Vous pouvez utiliser du markdown pour mettre en forme ce champ. Voir la <a "
|
||||
"href=\"/docs/markdown/\">documentation ici</a>"
|
||||
|
||||
#: .\cookbook\forms.py:362
|
||||
#: .\cookbook\forms.py:379
|
||||
msgid "Maximum number of users for this space reached."
|
||||
msgstr "Nombre maximum d’utilisateurs atteint pour ce groupe."
|
||||
|
||||
#: .\cookbook\forms.py:368
|
||||
#: .\cookbook\forms.py:385
|
||||
msgid "Email address already taken!"
|
||||
msgstr "Adresse mail déjà utilisée !"
|
||||
|
||||
#: .\cookbook\forms.py:376
|
||||
#: .\cookbook\forms.py:393
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be sent "
|
||||
"to the user."
|
||||
@@ -270,15 +270,15 @@ msgstr ""
|
||||
"Une adresse mail n’est pas requise mais si elle est renseignée, le lien "
|
||||
"d’invitation sera envoyé à l’utilisateur."
|
||||
|
||||
#: .\cookbook\forms.py:391
|
||||
#: .\cookbook\forms.py:408
|
||||
msgid "Name already taken."
|
||||
msgstr "Nom déjà utilisé."
|
||||
|
||||
#: .\cookbook\forms.py:402
|
||||
#: .\cookbook\forms.py:419
|
||||
msgid "Accept Terms and Privacy"
|
||||
msgstr "Accepter les conditions d’utilisation"
|
||||
|
||||
#: .\cookbook\forms.py:434
|
||||
#: .\cookbook\forms.py:451
|
||||
msgid ""
|
||||
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
|
||||
"g. low values mean more typos are ignored)."
|
||||
@@ -287,7 +287,7 @@ msgstr ""
|
||||
"par similarité de trigrammes (par exemple, des valeurs faibles signifient "
|
||||
"que davantage de fautes de frappe sont ignorées)."
|
||||
|
||||
#: .\cookbook\forms.py:444
|
||||
#: .\cookbook\forms.py:461
|
||||
msgid ""
|
||||
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
|
||||
"full description of choices."
|
||||
@@ -295,7 +295,7 @@ msgstr ""
|
||||
"Sélectionner la méthode de recherche. Cliquer <a href=\"/docs/search/"
|
||||
"\">ici</a> pour une description complète des choix."
|
||||
|
||||
#: .\cookbook\forms.py:445
|
||||
#: .\cookbook\forms.py:462
|
||||
msgid ""
|
||||
"Use fuzzy matching on units, keywords and ingredients when editing and "
|
||||
"importing recipes."
|
||||
@@ -303,7 +303,7 @@ msgstr ""
|
||||
"Utilisez la correspondance floue sur les unités, les mots-clés et les "
|
||||
"ingrédients lors de l’édition et de l’importation de recettes."
|
||||
|
||||
#: .\cookbook\forms.py:447
|
||||
#: .\cookbook\forms.py:464
|
||||
msgid ""
|
||||
"Fields to search ignoring accents. Selecting this option can improve or "
|
||||
"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 "
|
||||
"langue."
|
||||
|
||||
#: .\cookbook\forms.py:449
|
||||
#: .\cookbook\forms.py:466
|
||||
msgid ""
|
||||
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
|
||||
"'pie' and 'piece' and 'soapie')"
|
||||
@@ -320,7 +320,7 @@ msgstr ""
|
||||
"Champs à rechercher pour les correspondances partielles. (par exemple, la "
|
||||
"recherche de « Tarte » renverra « tarte », « tartelette » et « tartes »)"
|
||||
|
||||
#: .\cookbook\forms.py:451
|
||||
#: .\cookbook\forms.py:468
|
||||
msgid ""
|
||||
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
|
||||
"will return 'salad' and 'sandwich')"
|
||||
@@ -329,7 +329,7 @@ msgstr ""
|
||||
"exemple, si vous recherchez « sa », vous obtiendrez « salade » et "
|
||||
"« sandwich»)."
|
||||
|
||||
#: .\cookbook\forms.py:453
|
||||
#: .\cookbook\forms.py:470
|
||||
msgid ""
|
||||
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
|
||||
"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 "
|
||||
"incompatible avec les méthodes de recherche « web » et « brute »."
|
||||
|
||||
#: .\cookbook\forms.py:455
|
||||
#: .\cookbook\forms.py:472
|
||||
msgid ""
|
||||
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
|
||||
"only function with fulltext fields."
|
||||
@@ -347,35 +347,35 @@ msgstr ""
|
||||
"« web », « phrase » et « brute » ne fonctionnent qu’avec des champs en texte "
|
||||
"intégral."
|
||||
|
||||
#: .\cookbook\forms.py:459
|
||||
#: .\cookbook\forms.py:476
|
||||
msgid "Search Method"
|
||||
msgstr "Méthode de recherche"
|
||||
|
||||
#: .\cookbook\forms.py:460
|
||||
#: .\cookbook\forms.py:477
|
||||
msgid "Fuzzy Lookups"
|
||||
msgstr "Recherches floues"
|
||||
|
||||
#: .\cookbook\forms.py:461
|
||||
#: .\cookbook\forms.py:478
|
||||
msgid "Ignore Accent"
|
||||
msgstr "Ignorer les accents"
|
||||
|
||||
#: .\cookbook\forms.py:462
|
||||
#: .\cookbook\forms.py:479
|
||||
msgid "Partial Match"
|
||||
msgstr "correspondance partielle"
|
||||
|
||||
#: .\cookbook\forms.py:463
|
||||
#: .\cookbook\forms.py:480
|
||||
msgid "Starts With"
|
||||
msgstr "Commence par"
|
||||
|
||||
#: .\cookbook\forms.py:464
|
||||
#: .\cookbook\forms.py:481
|
||||
msgid "Fuzzy Search"
|
||||
msgstr "Recherche floue"
|
||||
|
||||
#: .\cookbook\forms.py:465
|
||||
#: .\cookbook\forms.py:482
|
||||
msgid "Full Text"
|
||||
msgstr "Texte intégral"
|
||||
|
||||
#: .\cookbook\forms.py:490
|
||||
#: .\cookbook\forms.py:507
|
||||
msgid ""
|
||||
"Users will see all items you add to your shopping list. They must add you "
|
||||
"to see items on their list."
|
||||
@@ -384,7 +384,7 @@ msgstr ""
|
||||
"courses. Ils doivent vous ajouter pour que vous puissiez voir les éléments "
|
||||
"de leur liste."
|
||||
|
||||
#: .\cookbook\forms.py:496
|
||||
#: .\cookbook\forms.py:513
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"include all related recipes."
|
||||
@@ -392,7 +392,7 @@ msgstr ""
|
||||
"Lors de l’ajout d’un menu de la semaine à la liste de courses (manuel ou "
|
||||
"automatique), inclure toutes les recettes connexes."
|
||||
|
||||
#: .\cookbook\forms.py:497
|
||||
#: .\cookbook\forms.py:514
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"exclude ingredients that are on hand."
|
||||
@@ -400,97 +400,97 @@ msgstr ""
|
||||
"Lors de l’ajout d’un menu de la semaine à la liste de courses (manuel ou "
|
||||
"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."
|
||||
msgstr ""
|
||||
"Nombre d'heures par défaut pour retarder l'ajoût d'un article à la liste de "
|
||||
"courses."
|
||||
|
||||
#: .\cookbook\forms.py:499
|
||||
#: .\cookbook\forms.py:516
|
||||
msgid "Filter shopping list to only include supermarket categories."
|
||||
msgstr ""
|
||||
"Filtrer la liste de courses pour n’inclure que des catégories de "
|
||||
"supermarchés."
|
||||
|
||||
#: .\cookbook\forms.py:500
|
||||
#: .\cookbook\forms.py:517
|
||||
msgid "Days of recent shopping list entries to display."
|
||||
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."
|
||||
msgstr ""
|
||||
"Marquer l’aliment comme disponible lorsqu’il est rayé de la liste de courses."
|
||||
|
||||
#: .\cookbook\forms.py:502
|
||||
#: .\cookbook\forms.py:519
|
||||
msgid "Delimiter to use for CSV exports."
|
||||
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."
|
||||
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"
|
||||
msgstr "Partager la liste de courses"
|
||||
|
||||
#: .\cookbook\forms.py:508
|
||||
#: .\cookbook\forms.py:525
|
||||
msgid "Autosync"
|
||||
msgstr "Synchronisation automatique"
|
||||
|
||||
#: .\cookbook\forms.py:509
|
||||
#: .\cookbook\forms.py:526
|
||||
msgid "Auto Add Meal Plan"
|
||||
msgstr "Ajouter le menu de la semaine automatiquement"
|
||||
|
||||
#: .\cookbook\forms.py:510
|
||||
#: .\cookbook\forms.py:527
|
||||
msgid "Exclude On Hand"
|
||||
msgstr "Exclure ingrédients disponibles"
|
||||
|
||||
#: .\cookbook\forms.py:511
|
||||
#: .\cookbook\forms.py:528
|
||||
msgid "Include Related"
|
||||
msgstr "Inclure recettes connexes"
|
||||
|
||||
#: .\cookbook\forms.py:512
|
||||
#: .\cookbook\forms.py:529
|
||||
msgid "Default Delay Hours"
|
||||
msgstr "Heures de retard par défaut"
|
||||
|
||||
#: .\cookbook\forms.py:513
|
||||
#: .\cookbook\forms.py:530
|
||||
msgid "Filter to Supermarket"
|
||||
msgstr "Filtrer par supermarché"
|
||||
|
||||
#: .\cookbook\forms.py:514
|
||||
#: .\cookbook\forms.py:531
|
||||
msgid "Recent Days"
|
||||
msgstr "Jours récents"
|
||||
|
||||
#: .\cookbook\forms.py:515
|
||||
#: .\cookbook\forms.py:532
|
||||
msgid "CSV Delimiter"
|
||||
msgstr "Caractère de séparation CSV"
|
||||
|
||||
#: .\cookbook\forms.py:516
|
||||
#: .\cookbook\forms.py:533
|
||||
msgid "List Prefix"
|
||||
msgstr "Préfixe de la liste"
|
||||
|
||||
#: .\cookbook\forms.py:517
|
||||
#: .\cookbook\forms.py:534
|
||||
msgid "Auto On Hand"
|
||||
msgstr "Disponible automatique"
|
||||
|
||||
#: .\cookbook\forms.py:527
|
||||
#: .\cookbook\forms.py:544
|
||||
msgid "Reset Food Inheritance"
|
||||
msgstr "Réinitialiser l'héritage alimentaire"
|
||||
|
||||
#: .\cookbook\forms.py:528
|
||||
#: .\cookbook\forms.py:545
|
||||
msgid "Reset all food to inherit the fields configured."
|
||||
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."
|
||||
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"
|
||||
msgstr ""
|
||||
"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."
|
||||
msgstr ""
|
||||
"Utiliser la forme plurielle pour les unités et les aliments dans ce groupe."
|
||||
|
||||
@@ -10,7 +10,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: noxonad <noxonad@proton.me>\n"
|
||||
"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 "
|
||||
"alatt."
|
||||
|
||||
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492
|
||||
#: .\cookbook\forms.py:79 .\cookbook\forms.py:509
|
||||
msgid ""
|
||||
"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. "
|
||||
@@ -133,7 +133,7 @@ msgstr ""
|
||||
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."
|
||||
|
||||
#: .\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."
|
||||
msgstr ""
|
||||
"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 "
|
||||
"jelenik meg helyette"
|
||||
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:314
|
||||
msgid "Name"
|
||||
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"
|
||||
msgstr "Kulcsszavak"
|
||||
|
||||
@@ -170,7 +170,7 @@ msgstr "Előkészítési idő percben"
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
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"
|
||||
msgstr "Elérési útvonal"
|
||||
|
||||
@@ -182,7 +182,7 @@ msgstr "Tárhely UID"
|
||||
msgid "Default"
|
||||
msgstr "Alapértelmezett"
|
||||
|
||||
#: .\cookbook\forms.py:173
|
||||
#: .\cookbook\forms.py:190
|
||||
msgid ""
|
||||
"To prevent duplicates recipes with the same name as existing ones are "
|
||||
"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 "
|
||||
"mindent importálni szeretne."
|
||||
|
||||
#: .\cookbook\forms.py:196
|
||||
#: .\cookbook\forms.py:213
|
||||
msgid "Add your comment: "
|
||||
msgstr "Add hozzá a kommented: "
|
||||
|
||||
#: .\cookbook\forms.py:211
|
||||
#: .\cookbook\forms.py:228
|
||||
msgid "Leave empty for dropbox and enter app password for nextcloud."
|
||||
msgstr ""
|
||||
"A dropbox esetében hagyja üresen, a nextcloud esetében pedig adja meg az "
|
||||
"alkalmazás jelszavát."
|
||||
|
||||
#: .\cookbook\forms.py:218
|
||||
#: .\cookbook\forms.py:235
|
||||
msgid "Leave empty for nextcloud and enter api token for dropbox."
|
||||
msgstr ""
|
||||
"A nextcloud esetében hagyja üresen, a dropbox esetében pedig adja meg az api "
|
||||
"tokent."
|
||||
|
||||
#: .\cookbook\forms.py:227
|
||||
#: .\cookbook\forms.py:244
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"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 "
|
||||
"(<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"
|
||||
msgstr "Tárhely"
|
||||
|
||||
#: .\cookbook\forms.py:267
|
||||
#: .\cookbook\forms.py:284
|
||||
msgid "Active"
|
||||
msgstr "Aktív"
|
||||
|
||||
#: .\cookbook\forms.py:273
|
||||
#: .\cookbook\forms.py:290
|
||||
msgid "Search String"
|
||||
msgstr "Keresési kifejezés"
|
||||
|
||||
#: .\cookbook\forms.py:300
|
||||
#: .\cookbook\forms.py:317
|
||||
msgid "File ID"
|
||||
msgstr "Fájl ID"
|
||||
|
||||
#: .\cookbook\forms.py:322
|
||||
#: .\cookbook\forms.py:339
|
||||
msgid "You must provide at least a recipe or a title."
|
||||
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."
|
||||
msgstr ""
|
||||
"A beállításokban megadhatja a receptek megosztására szolgáló alapértelmezett "
|
||||
"felhasználókat."
|
||||
|
||||
#: .\cookbook\forms.py:336
|
||||
#: .\cookbook\forms.py:353
|
||||
msgid ""
|
||||
"You can use markdown to format this field. See the <a href=\"/docs/markdown/"
|
||||
"\">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=\"/"
|
||||
"docs/markdown/\">dokumentációt itt</a>"
|
||||
|
||||
#: .\cookbook\forms.py:362
|
||||
#: .\cookbook\forms.py:379
|
||||
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."
|
||||
|
||||
#: .\cookbook\forms.py:368
|
||||
#: .\cookbook\forms.py:385
|
||||
msgid "Email address already taken!"
|
||||
msgstr "Az e-mail cím már foglalt!"
|
||||
|
||||
#: .\cookbook\forms.py:376
|
||||
#: .\cookbook\forms.py:393
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be sent "
|
||||
"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 "
|
||||
"felhasználónak."
|
||||
|
||||
#: .\cookbook\forms.py:391
|
||||
#: .\cookbook\forms.py:408
|
||||
msgid "Name already taken."
|
||||
msgstr "A név már foglalt."
|
||||
|
||||
#: .\cookbook\forms.py:402
|
||||
#: .\cookbook\forms.py:419
|
||||
msgid "Accept Terms and Privacy"
|
||||
msgstr "Feltételek és adatvédelem elfogadása"
|
||||
|
||||
#: .\cookbook\forms.py:434
|
||||
#: .\cookbook\forms.py:451
|
||||
msgid ""
|
||||
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
|
||||
"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 "
|
||||
"hibát figyelmen kívül hagynak)."
|
||||
|
||||
#: .\cookbook\forms.py:444
|
||||
#: .\cookbook\forms.py:461
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "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> "
|
||||
"a lehetőségek teljes leírásáért."
|
||||
|
||||
#: .\cookbook\forms.py:445
|
||||
#: .\cookbook\forms.py:462
|
||||
msgid ""
|
||||
"Use fuzzy matching on units, keywords and ingredients when editing and "
|
||||
"importing recipes."
|
||||
@@ -302,7 +302,7 @@ msgstr ""
|
||||
"A receptek szerkesztése és importálása során az egységek, kulcsszavak és "
|
||||
"összetevők bizonytalan megfeleltetése."
|
||||
|
||||
#: .\cookbook\forms.py:447
|
||||
#: .\cookbook\forms.py:464
|
||||
msgid ""
|
||||
"Fields to search ignoring accents. Selecting this option can improve or "
|
||||
"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 "
|
||||
"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 ""
|
||||
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
|
||||
"'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 "
|
||||
"'pie' és a 'piece' és a 'soapie' kifejezéseket adja vissza.)"
|
||||
|
||||
#: .\cookbook\forms.py:451
|
||||
#: .\cookbook\forms.py:468
|
||||
msgid ""
|
||||
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
|
||||
"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 "
|
||||
"'sandwich' kifejezéseket adja vissza)"
|
||||
|
||||
#: .\cookbook\forms.py:453
|
||||
#: .\cookbook\forms.py:470
|
||||
msgid ""
|
||||
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
|
||||
"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 "
|
||||
"módszerekkel."
|
||||
|
||||
#: .\cookbook\forms.py:455
|
||||
#: .\cookbook\forms.py:472
|
||||
msgid ""
|
||||
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
|
||||
"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' "
|
||||
"keresési módszerek csak teljes szöveges mezőkkel működnek."
|
||||
|
||||
#: .\cookbook\forms.py:459
|
||||
#: .\cookbook\forms.py:476
|
||||
msgid "Search Method"
|
||||
msgstr "Keresési módszer"
|
||||
|
||||
#: .\cookbook\forms.py:460
|
||||
#: .\cookbook\forms.py:477
|
||||
msgid "Fuzzy Lookups"
|
||||
msgstr "Bizonytalan keresések"
|
||||
|
||||
#: .\cookbook\forms.py:461
|
||||
#: .\cookbook\forms.py:478
|
||||
msgid "Ignore Accent"
|
||||
msgstr "Ékezetek ignorálása"
|
||||
|
||||
#: .\cookbook\forms.py:462
|
||||
#: .\cookbook\forms.py:479
|
||||
msgid "Partial Match"
|
||||
msgstr "Részleges találat"
|
||||
|
||||
#: .\cookbook\forms.py:463
|
||||
#: .\cookbook\forms.py:480
|
||||
#, fuzzy
|
||||
#| msgid "Starts Wtih"
|
||||
msgid "Starts With"
|
||||
msgstr "Kezdődik a következővel"
|
||||
|
||||
#: .\cookbook\forms.py:464
|
||||
#: .\cookbook\forms.py:481
|
||||
msgid "Fuzzy Search"
|
||||
msgstr "Bizonytalan keresés"
|
||||
|
||||
#: .\cookbook\forms.py:465
|
||||
#: .\cookbook\forms.py:482
|
||||
msgid "Full Text"
|
||||
msgstr "Teljes szöveg"
|
||||
|
||||
#: .\cookbook\forms.py:490
|
||||
#: .\cookbook\forms.py:507
|
||||
msgid ""
|
||||
"Users will see all items you add to your shopping list. They must add you "
|
||||
"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 "
|
||||
"adniuk téged."
|
||||
|
||||
#: .\cookbook\forms.py:496
|
||||
#: .\cookbook\forms.py:513
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"include all related recipes."
|
||||
@@ -390,7 +390,7 @@ msgstr ""
|
||||
"Amikor étkezési tervet ad hozzá a bevásárlólistához (kézzel vagy "
|
||||
"automatikusan), vegye fel az összes kapcsolódó receptet."
|
||||
|
||||
#: .\cookbook\forms.py:497
|
||||
#: .\cookbook\forms.py:514
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"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 "
|
||||
"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."
|
||||
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."
|
||||
msgstr ""
|
||||
"Szűrje a bevásárlólistát úgy, hogy csak a szupermarket kategóriákat "
|
||||
"tartalmazza."
|
||||
|
||||
#: .\cookbook\forms.py:500
|
||||
#: .\cookbook\forms.py:517
|
||||
msgid "Days of recent shopping list entries to display."
|
||||
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."
|
||||
msgstr ""
|
||||
"Jelölje meg a \" Kéznél van\" jelölést, ha a bevásárlólistáról kipipálta az "
|
||||
"élelmiszert."
|
||||
|
||||
#: .\cookbook\forms.py:502
|
||||
#: .\cookbook\forms.py:519
|
||||
msgid "Delimiter to use for CSV exports."
|
||||
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."
|
||||
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"
|
||||
msgstr "Bevásárlólista megosztása"
|
||||
|
||||
#: .\cookbook\forms.py:508
|
||||
#: .\cookbook\forms.py:525
|
||||
msgid "Autosync"
|
||||
msgstr "Automatikus szinkronizálás"
|
||||
|
||||
#: .\cookbook\forms.py:509
|
||||
#: .\cookbook\forms.py:526
|
||||
msgid "Auto Add Meal Plan"
|
||||
msgstr "Automatikus étkezési terv hozzáadása"
|
||||
|
||||
#: .\cookbook\forms.py:510
|
||||
#: .\cookbook\forms.py:527
|
||||
msgid "Exclude On Hand"
|
||||
msgstr "Kéznél levő kihagyása"
|
||||
|
||||
#: .\cookbook\forms.py:511
|
||||
#: .\cookbook\forms.py:528
|
||||
msgid "Include Related"
|
||||
msgstr "Tartalmazza a kapcsolódókat"
|
||||
|
||||
#: .\cookbook\forms.py:512
|
||||
#: .\cookbook\forms.py:529
|
||||
msgid "Default Delay Hours"
|
||||
msgstr "Alapértelmezett késleltetési órák"
|
||||
|
||||
#: .\cookbook\forms.py:513
|
||||
#: .\cookbook\forms.py:530
|
||||
msgid "Filter to Supermarket"
|
||||
msgstr "Szűrő a szupermarkethez"
|
||||
|
||||
#: .\cookbook\forms.py:514
|
||||
#: .\cookbook\forms.py:531
|
||||
msgid "Recent Days"
|
||||
msgstr "Legutóbbi napok"
|
||||
|
||||
#: .\cookbook\forms.py:515
|
||||
#: .\cookbook\forms.py:532
|
||||
msgid "CSV Delimiter"
|
||||
msgstr "CSV elválasztó"
|
||||
|
||||
#: .\cookbook\forms.py:516
|
||||
#: .\cookbook\forms.py:533
|
||||
msgid "List Prefix"
|
||||
msgstr "Lista előtagja"
|
||||
|
||||
#: .\cookbook\forms.py:517
|
||||
#: .\cookbook\forms.py:534
|
||||
msgid "Auto On Hand"
|
||||
msgstr "Automatikus Kéznél lévő"
|
||||
|
||||
#: .\cookbook\forms.py:527
|
||||
#: .\cookbook\forms.py:544
|
||||
msgid "Reset Food Inheritance"
|
||||
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."
|
||||
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."
|
||||
msgstr ""
|
||||
"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"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
||||
Binary file not shown.
@@ -11,7 +11,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Oliver Cervera <olivercervera@yahoo.it>\n"
|
||||
"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 ""
|
||||
"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 ""
|
||||
"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. "
|
||||
@@ -134,7 +134,7 @@ msgstr ""
|
||||
msgid "Makes the navbar stick to the top of the page."
|
||||
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."
|
||||
msgstr ""
|
||||
"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 "
|
||||
"il nome utente"
|
||||
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:314
|
||||
msgid "Name"
|
||||
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"
|
||||
msgstr "Parole chiave"
|
||||
|
||||
@@ -172,7 +172,7 @@ msgstr "Tempo di preparazione in minuti"
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
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"
|
||||
msgstr "Percorso"
|
||||
|
||||
@@ -184,7 +184,7 @@ msgstr "UID di archiviazione"
|
||||
msgid "Default"
|
||||
msgstr "Predefinito"
|
||||
|
||||
#: .\cookbook\forms.py:173
|
||||
#: .\cookbook\forms.py:190
|
||||
msgid ""
|
||||
"To prevent duplicates recipes with the same name as existing ones are "
|
||||
"ignored. Check this box to import everything."
|
||||
@@ -192,20 +192,20 @@ msgstr ""
|
||||
"Per prevenire duplicati, vengono ignorate le ricette che hanno lo stesso "
|
||||
"nome di quelle esistenti. Metti la spunta per importare tutto."
|
||||
|
||||
#: .\cookbook\forms.py:196
|
||||
#: .\cookbook\forms.py:213
|
||||
msgid "Add your comment: "
|
||||
msgstr "Aggiungi il tuo commento: "
|
||||
|
||||
#: .\cookbook\forms.py:211
|
||||
#: .\cookbook\forms.py:228
|
||||
msgid "Leave empty for dropbox and enter app password for nextcloud."
|
||||
msgstr ""
|
||||
"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."
|
||||
msgstr "Lascia vuoto per nextcloud e inserisci l'api token per dropbox."
|
||||
|
||||
#: .\cookbook\forms.py:227
|
||||
#: .\cookbook\forms.py:244
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"php/webdav/</code> is added automatically)"
|
||||
@@ -213,33 +213,33 @@ msgstr ""
|
||||
"Lascia vuoto per dropbox e inserisci solo l'url base per nextcloud (<code>/"
|
||||
"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"
|
||||
msgstr "Archiviazione"
|
||||
|
||||
#: .\cookbook\forms.py:267
|
||||
#: .\cookbook\forms.py:284
|
||||
msgid "Active"
|
||||
msgstr "Attivo"
|
||||
|
||||
#: .\cookbook\forms.py:273
|
||||
#: .\cookbook\forms.py:290
|
||||
msgid "Search String"
|
||||
msgstr "Stringa di Ricerca"
|
||||
|
||||
#: .\cookbook\forms.py:300
|
||||
#: .\cookbook\forms.py:317
|
||||
msgid "File ID"
|
||||
msgstr "ID del File"
|
||||
|
||||
#: .\cookbook\forms.py:322
|
||||
#: .\cookbook\forms.py:339
|
||||
msgid "You must provide at least a recipe or a title."
|
||||
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."
|
||||
msgstr ""
|
||||
"È possibile visualizzare l'elenco degli utenti predefiniti con cui "
|
||||
"condividere le ricette nelle impostazioni."
|
||||
|
||||
#: .\cookbook\forms.py:336
|
||||
#: .\cookbook\forms.py:353
|
||||
msgid ""
|
||||
"You can use markdown to format this field. See the <a href=\"/docs/markdown/"
|
||||
"\">docs here</a>"
|
||||
@@ -247,15 +247,15 @@ msgstr ""
|
||||
"Puoi usare markdown per formattare questo campo. Guarda la <a href=\"/docs/"
|
||||
"markdown/\">documentazione qui</a>"
|
||||
|
||||
#: .\cookbook\forms.py:362
|
||||
#: .\cookbook\forms.py:379
|
||||
msgid "Maximum number of users for this space reached."
|
||||
msgstr "È stato raggiunto il numero massimo di utenti per questa istanza."
|
||||
|
||||
#: .\cookbook\forms.py:368
|
||||
#: .\cookbook\forms.py:385
|
||||
msgid "Email address already taken!"
|
||||
msgstr "Questo indirizzo email è già in uso!"
|
||||
|
||||
#: .\cookbook\forms.py:376
|
||||
#: .\cookbook\forms.py:393
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be sent "
|
||||
"to the user."
|
||||
@@ -263,15 +263,15 @@ msgstr ""
|
||||
"Non è obbligatorio specificare l'indirizzo email, ma se presente verrà "
|
||||
"utilizzato per mandare all'utente un link di invito."
|
||||
|
||||
#: .\cookbook\forms.py:391
|
||||
#: .\cookbook\forms.py:408
|
||||
msgid "Name already taken."
|
||||
msgstr "Nome già in uso."
|
||||
|
||||
#: .\cookbook\forms.py:402
|
||||
#: .\cookbook\forms.py:419
|
||||
msgid "Accept Terms and Privacy"
|
||||
msgstr "Accetta i Termini d'uso e Privacy"
|
||||
|
||||
#: .\cookbook\forms.py:434
|
||||
#: .\cookbook\forms.py:451
|
||||
msgid ""
|
||||
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
|
||||
"g. low values mean more typos are ignored)."
|
||||
@@ -280,7 +280,7 @@ msgstr ""
|
||||
"trigrammi (ad esempio, valori bassi significano che vengono ignorati più "
|
||||
"errori di battitura)."
|
||||
|
||||
#: .\cookbook\forms.py:444
|
||||
#: .\cookbook\forms.py:461
|
||||
msgid ""
|
||||
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
|
||||
"full description of choices."
|
||||
@@ -288,7 +288,7 @@ msgstr ""
|
||||
"Seleziona il metodo di ricerca. Clicca <a href=\"/docs/search/\">qui</a> "
|
||||
"per avere maggiori informazioni."
|
||||
|
||||
#: .\cookbook\forms.py:445
|
||||
#: .\cookbook\forms.py:462
|
||||
msgid ""
|
||||
"Use fuzzy matching on units, keywords and ingredients when editing and "
|
||||
"importing recipes."
|
||||
@@ -296,7 +296,7 @@ msgstr ""
|
||||
"Usa la corrispondenza vaga per unità, parole chiave e ingredienti durante la "
|
||||
"modifica e l'importazione di ricette."
|
||||
|
||||
#: .\cookbook\forms.py:447
|
||||
#: .\cookbook\forms.py:464
|
||||
msgid ""
|
||||
"Fields to search ignoring accents. Selecting this option can improve or "
|
||||
"degrade search quality depending on language"
|
||||
@@ -304,7 +304,7 @@ msgstr ""
|
||||
"Campi da cercare ignorando gli accenti. A seconda alla lingua utilizzata, "
|
||||
"questa opzione può migliorare o peggiorare la ricerca"
|
||||
|
||||
#: .\cookbook\forms.py:449
|
||||
#: .\cookbook\forms.py:466
|
||||
msgid ""
|
||||
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
|
||||
"'pie' and 'piece' and 'soapie')"
|
||||
@@ -312,7 +312,7 @@ msgstr ""
|
||||
"Campi da cercare con corrispondenza parziale. (ad esempio, cercando 'Torta' "
|
||||
"verranno mostrati 'torta', 'tortino' e 'contorta')"
|
||||
|
||||
#: .\cookbook\forms.py:451
|
||||
#: .\cookbook\forms.py:468
|
||||
msgid ""
|
||||
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
|
||||
"will return 'salad' and 'sandwich')"
|
||||
@@ -320,7 +320,7 @@ msgstr ""
|
||||
"Campi da cercare all'inizio di parole corrispondenti (es. cercando per 'ins' "
|
||||
"mostrerà 'insalata' e 'insaccati')"
|
||||
|
||||
#: .\cookbook\forms.py:453
|
||||
#: .\cookbook\forms.py:470
|
||||
msgid ""
|
||||
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
|
||||
"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 "
|
||||
"ricerca 'web' o 'raw'."
|
||||
|
||||
#: .\cookbook\forms.py:455
|
||||
#: .\cookbook\forms.py:472
|
||||
msgid ""
|
||||
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
|
||||
"only function with fulltext fields."
|
||||
@@ -337,35 +337,35 @@ msgstr ""
|
||||
"Campi per la ricerca full-text. Nota: i metodi di ricerca 'web', 'frase' e "
|
||||
"'raw' funzionano solo con i campi full-text."
|
||||
|
||||
#: .\cookbook\forms.py:459
|
||||
#: .\cookbook\forms.py:476
|
||||
msgid "Search Method"
|
||||
msgstr "Metodo di ricerca"
|
||||
|
||||
#: .\cookbook\forms.py:460
|
||||
#: .\cookbook\forms.py:477
|
||||
msgid "Fuzzy Lookups"
|
||||
msgstr "Ricerche vaghe"
|
||||
|
||||
#: .\cookbook\forms.py:461
|
||||
#: .\cookbook\forms.py:478
|
||||
msgid "Ignore Accent"
|
||||
msgstr "Ignora accento"
|
||||
|
||||
#: .\cookbook\forms.py:462
|
||||
#: .\cookbook\forms.py:479
|
||||
msgid "Partial Match"
|
||||
msgstr "Corrispondenza parziale"
|
||||
|
||||
#: .\cookbook\forms.py:463
|
||||
#: .\cookbook\forms.py:480
|
||||
msgid "Starts With"
|
||||
msgstr "Inizia con"
|
||||
|
||||
#: .\cookbook\forms.py:464
|
||||
#: .\cookbook\forms.py:481
|
||||
msgid "Fuzzy Search"
|
||||
msgstr "Ricerca vaga"
|
||||
|
||||
#: .\cookbook\forms.py:465
|
||||
#: .\cookbook\forms.py:482
|
||||
msgid "Full Text"
|
||||
msgstr "Full Text"
|
||||
|
||||
#: .\cookbook\forms.py:490
|
||||
#: .\cookbook\forms.py:507
|
||||
msgid ""
|
||||
"Users will see all items you add to your shopping list. They must add you "
|
||||
"to see items on their list."
|
||||
@@ -373,7 +373,7 @@ msgstr ""
|
||||
"Gli utenti potranno vedere tutti gli elementi che aggiungi alla tua lista "
|
||||
"della spesa. Devono aggiungerti per vedere gli elementi nella loro lista."
|
||||
|
||||
#: .\cookbook\forms.py:496
|
||||
#: .\cookbook\forms.py:513
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"include all related recipes."
|
||||
@@ -381,7 +381,7 @@ msgstr ""
|
||||
"Quando si aggiunge un piano alimentare alla lista della spesa (manualmente o "
|
||||
"automaticamente), includi tutte le ricette correlate."
|
||||
|
||||
#: .\cookbook\forms.py:497
|
||||
#: .\cookbook\forms.py:514
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"exclude ingredients that are on hand."
|
||||
@@ -389,97 +389,97 @@ msgstr ""
|
||||
"Quando si aggiunge un piano alimentare alla lista della spesa (manualmente o "
|
||||
"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."
|
||||
msgstr ""
|
||||
"Il numero predefinito di ore per ritardare l'inserimento di una lista della "
|
||||
"spesa."
|
||||
|
||||
#: .\cookbook\forms.py:499
|
||||
#: .\cookbook\forms.py:516
|
||||
msgid "Filter shopping list to only include supermarket categories."
|
||||
msgstr ""
|
||||
"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."
|
||||
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."
|
||||
msgstr ""
|
||||
"Contrassegna gli alimenti come 'Disponibili' quando spuntati dalla lista "
|
||||
"della spesa."
|
||||
|
||||
#: .\cookbook\forms.py:502
|
||||
#: .\cookbook\forms.py:519
|
||||
msgid "Delimiter to use for CSV exports."
|
||||
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."
|
||||
msgstr "Prefisso da aggiungere quando si copia una lista negli appunti."
|
||||
|
||||
#: .\cookbook\forms.py:507
|
||||
#: .\cookbook\forms.py:524
|
||||
msgid "Share Shopping List"
|
||||
msgstr "Condividi lista della spesa"
|
||||
|
||||
#: .\cookbook\forms.py:508
|
||||
#: .\cookbook\forms.py:525
|
||||
msgid "Autosync"
|
||||
msgstr "Sincronizzazione automatica"
|
||||
|
||||
#: .\cookbook\forms.py:509
|
||||
#: .\cookbook\forms.py:526
|
||||
msgid "Auto Add Meal Plan"
|
||||
msgstr "Aggiungi automaticamente al piano alimentare"
|
||||
|
||||
#: .\cookbook\forms.py:510
|
||||
#: .\cookbook\forms.py:527
|
||||
msgid "Exclude On Hand"
|
||||
msgstr "Escludi Disponibile"
|
||||
|
||||
#: .\cookbook\forms.py:511
|
||||
#: .\cookbook\forms.py:528
|
||||
msgid "Include Related"
|
||||
msgstr "Includi correlati"
|
||||
|
||||
#: .\cookbook\forms.py:512
|
||||
#: .\cookbook\forms.py:529
|
||||
msgid "Default Delay Hours"
|
||||
msgstr "Ore di ritardo predefinite"
|
||||
|
||||
#: .\cookbook\forms.py:513
|
||||
#: .\cookbook\forms.py:530
|
||||
msgid "Filter to Supermarket"
|
||||
msgstr "Filtra per supermercato"
|
||||
|
||||
#: .\cookbook\forms.py:514
|
||||
#: .\cookbook\forms.py:531
|
||||
msgid "Recent Days"
|
||||
msgstr "Giorni recenti"
|
||||
|
||||
#: .\cookbook\forms.py:515
|
||||
#: .\cookbook\forms.py:532
|
||||
msgid "CSV Delimiter"
|
||||
msgstr "Delimitatore CSV"
|
||||
|
||||
#: .\cookbook\forms.py:516
|
||||
#: .\cookbook\forms.py:533
|
||||
msgid "List Prefix"
|
||||
msgstr "Prefisso lista"
|
||||
|
||||
#: .\cookbook\forms.py:517
|
||||
#: .\cookbook\forms.py:534
|
||||
msgid "Auto On Hand"
|
||||
msgstr "Disponibilità automatica"
|
||||
|
||||
#: .\cookbook\forms.py:527
|
||||
#: .\cookbook\forms.py:544
|
||||
msgid "Reset Food Inheritance"
|
||||
msgstr "Ripristina Eredità Alimenti"
|
||||
|
||||
#: .\cookbook\forms.py:528
|
||||
#: .\cookbook\forms.py:545
|
||||
msgid "Reset all food to inherit the fields configured."
|
||||
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."
|
||||
msgstr ""
|
||||
"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"
|
||||
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."
|
||||
msgstr ""
|
||||
"Usare la forma plurale per le unità e gli alimenti all'interno di questo "
|
||||
|
||||
@@ -10,7 +10,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Joachim Weber <joachim.weber@gmx.de>\n"
|
||||
"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 ""
|
||||
"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 ""
|
||||
"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. "
|
||||
@@ -143,7 +143,7 @@ msgstr ""
|
||||
msgid "Makes the navbar stick to the top of the page."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
@@ -163,11 +163,11 @@ msgstr ""
|
||||
"Abi lauki nav obligāti. Ja neviens nav norādīts, tā vietā tiks parādīts "
|
||||
"lietotājvārds"
|
||||
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:314
|
||||
msgid "Name"
|
||||
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"
|
||||
msgstr "Atslēgvārdi"
|
||||
|
||||
@@ -179,7 +179,7 @@ msgstr "Pagatavošanas laiks minūtēs"
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
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"
|
||||
msgstr "Ceļš"
|
||||
|
||||
@@ -191,25 +191,25 @@ msgstr "Krātuves UID"
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:173
|
||||
#: .\cookbook\forms.py:190
|
||||
msgid ""
|
||||
"To prevent duplicates recipes with the same name as existing ones are "
|
||||
"ignored. Check this box to import everything."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:196
|
||||
#: .\cookbook\forms.py:213
|
||||
msgid "Add your comment: "
|
||||
msgstr "Pievienot komentāru: "
|
||||
|
||||
#: .\cookbook\forms.py:211
|
||||
#: .\cookbook\forms.py:228
|
||||
msgid "Leave empty for dropbox and enter app password for 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."
|
||||
msgstr "Atstājiet tukšu Nextcloud un ievadiet API tokenu Dropbox."
|
||||
|
||||
#: .\cookbook\forms.py:227
|
||||
#: .\cookbook\forms.py:244
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"php/webdav/</code> is added automatically)"
|
||||
@@ -217,33 +217,33 @@ msgstr ""
|
||||
"Atstājiet tukšu Dropbox un ievadiet tikai Nextcloud bāzes URL (<kods> /"
|
||||
"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"
|
||||
msgstr "Krātuve"
|
||||
|
||||
#: .\cookbook\forms.py:267
|
||||
#: .\cookbook\forms.py:284
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:273
|
||||
#: .\cookbook\forms.py:290
|
||||
msgid "Search String"
|
||||
msgstr "Meklēšanas virkne"
|
||||
|
||||
#: .\cookbook\forms.py:300
|
||||
#: .\cookbook\forms.py:317
|
||||
msgid "File ID"
|
||||
msgstr "Faila ID"
|
||||
|
||||
#: .\cookbook\forms.py:322
|
||||
#: .\cookbook\forms.py:339
|
||||
msgid "You must provide at least a recipe or a title."
|
||||
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."
|
||||
msgstr ""
|
||||
"Iestatījumos varat uzskaitīt noklusējuma lietotājus, ar kuriem koplietot "
|
||||
"receptes."
|
||||
|
||||
#: .\cookbook\forms.py:336
|
||||
#: .\cookbook\forms.py:353
|
||||
msgid ""
|
||||
"You can use markdown to format this field. See the <a href=\"/docs/markdown/"
|
||||
"\">docs here</a>"
|
||||
@@ -251,219 +251,219 @@ msgstr ""
|
||||
"Lai formatētu šo lauku, varat izmantot Markdown. Skatiet <a href=\"/docs/"
|
||||
"markdown/\"> dokumentus šeit </a>"
|
||||
|
||||
#: .\cookbook\forms.py:362
|
||||
#: .\cookbook\forms.py:379
|
||||
msgid "Maximum number of users for this space reached."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:368
|
||||
#: .\cookbook\forms.py:385
|
||||
msgid "Email address already taken!"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:376
|
||||
#: .\cookbook\forms.py:393
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be sent "
|
||||
"to the user."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:391
|
||||
#: .\cookbook\forms.py:408
|
||||
msgid "Name already taken."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:402
|
||||
#: .\cookbook\forms.py:419
|
||||
msgid "Accept Terms and Privacy"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:434
|
||||
#: .\cookbook\forms.py:451
|
||||
msgid ""
|
||||
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
|
||||
"g. low values mean more typos are ignored)."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:444
|
||||
#: .\cookbook\forms.py:461
|
||||
msgid ""
|
||||
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
|
||||
"full description of choices."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:445
|
||||
#: .\cookbook\forms.py:462
|
||||
msgid ""
|
||||
"Use fuzzy matching on units, keywords and ingredients when editing and "
|
||||
"importing recipes."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:447
|
||||
#: .\cookbook\forms.py:464
|
||||
msgid ""
|
||||
"Fields to search ignoring accents. Selecting this option can improve or "
|
||||
"degrade search quality depending on language"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:449
|
||||
#: .\cookbook\forms.py:466
|
||||
msgid ""
|
||||
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
|
||||
"'pie' and 'piece' and 'soapie')"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:451
|
||||
#: .\cookbook\forms.py:468
|
||||
msgid ""
|
||||
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
|
||||
"will return 'salad' and 'sandwich')"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:453
|
||||
#: .\cookbook\forms.py:470
|
||||
msgid ""
|
||||
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
|
||||
"Note: this option will conflict with 'web' and 'raw' methods of search."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:455
|
||||
#: .\cookbook\forms.py:472
|
||||
msgid ""
|
||||
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
|
||||
"only function with fulltext fields."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:459
|
||||
#: .\cookbook\forms.py:476
|
||||
#, fuzzy
|
||||
#| msgid "Search"
|
||||
msgid "Search Method"
|
||||
msgstr "Meklēt"
|
||||
|
||||
#: .\cookbook\forms.py:460
|
||||
#: .\cookbook\forms.py:477
|
||||
msgid "Fuzzy Lookups"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:461
|
||||
#: .\cookbook\forms.py:478
|
||||
msgid "Ignore Accent"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:462
|
||||
#: .\cookbook\forms.py:479
|
||||
msgid "Partial Match"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:463
|
||||
#: .\cookbook\forms.py:480
|
||||
msgid "Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:464
|
||||
#: .\cookbook\forms.py:481
|
||||
#, fuzzy
|
||||
#| msgid "Search"
|
||||
msgid "Fuzzy Search"
|
||||
msgstr "Meklēt"
|
||||
|
||||
#: .\cookbook\forms.py:465
|
||||
#: .\cookbook\forms.py:482
|
||||
#, fuzzy
|
||||
#| msgid "Text"
|
||||
msgid "Full Text"
|
||||
msgstr "Teskts"
|
||||
|
||||
#: .\cookbook\forms.py:490
|
||||
#: .\cookbook\forms.py:507
|
||||
msgid ""
|
||||
"Users will see all items you add to your shopping list. They must add you "
|
||||
"to see items on their list."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:496
|
||||
#: .\cookbook\forms.py:513
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"include all related recipes."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:497
|
||||
#: .\cookbook\forms.py:514
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"exclude ingredients that are on hand."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:498
|
||||
#: .\cookbook\forms.py:515
|
||||
msgid "Default number of hours to delay a shopping list entry."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:499
|
||||
#: .\cookbook\forms.py:516
|
||||
msgid "Filter shopping list to only include supermarket categories."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:500
|
||||
#: .\cookbook\forms.py:517
|
||||
msgid "Days of recent shopping list entries to display."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:501
|
||||
#: .\cookbook\forms.py:518
|
||||
msgid "Mark food 'On Hand' when checked off shopping list."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:502
|
||||
#: .\cookbook\forms.py:519
|
||||
msgid "Delimiter to use for CSV exports."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:503
|
||||
#: .\cookbook\forms.py:520
|
||||
msgid "Prefix to add when copying list to the clipboard."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:507
|
||||
#: .\cookbook\forms.py:524
|
||||
#, fuzzy
|
||||
#| msgid "Shopping List"
|
||||
msgid "Share Shopping List"
|
||||
msgstr "Iepirkumu saraksts"
|
||||
|
||||
#: .\cookbook\forms.py:508
|
||||
#: .\cookbook\forms.py:525
|
||||
msgid "Autosync"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:509
|
||||
#: .\cookbook\forms.py:526
|
||||
msgid "Auto Add Meal Plan"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:510
|
||||
#: .\cookbook\forms.py:527
|
||||
msgid "Exclude On Hand"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:511
|
||||
#: .\cookbook\forms.py:528
|
||||
msgid "Include Related"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:512
|
||||
#: .\cookbook\forms.py:529
|
||||
msgid "Default Delay Hours"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:513
|
||||
#: .\cookbook\forms.py:530
|
||||
msgid "Filter to Supermarket"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:514
|
||||
#: .\cookbook\forms.py:531
|
||||
msgid "Recent Days"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:515
|
||||
#: .\cookbook\forms.py:532
|
||||
msgid "CSV Delimiter"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:516
|
||||
#: .\cookbook\forms.py:533
|
||||
msgid "List Prefix"
|
||||
msgstr "Saraksta prefikss"
|
||||
|
||||
#: .\cookbook\forms.py:517
|
||||
#: .\cookbook\forms.py:534
|
||||
msgid "Auto On Hand"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:527
|
||||
#: .\cookbook\forms.py:544
|
||||
msgid "Reset Food Inheritance"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:528
|
||||
#: .\cookbook\forms.py:545
|
||||
msgid "Reset all food to inherit the fields configured."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:540
|
||||
#: .\cookbook\forms.py:557
|
||||
#, fuzzy
|
||||
#| msgid "Food that should be replaced."
|
||||
msgid "Fields on food that should be inherited by default."
|
||||
msgstr "Ēdiens, kas būtu jāaizstāj."
|
||||
|
||||
#: .\cookbook\forms.py:541
|
||||
#: .\cookbook\forms.py:558
|
||||
#, fuzzy
|
||||
#| msgid "Show recently viewed recipes on search page."
|
||||
msgid "Show recipe counts on search filters"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Jesse <jesse.kamps@pm.me>\n"
|
||||
"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."
|
||||
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 ""
|
||||
"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. "
|
||||
@@ -133,7 +133,7 @@ msgstr ""
|
||||
msgid "Makes the navbar stick to the top of the page."
|
||||
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."
|
||||
msgstr "Zet maaltijdplan ingrediënten automatisch op boodschappenlijst."
|
||||
|
||||
@@ -153,11 +153,11 @@ msgstr ""
|
||||
"Beide velden zijn optioneel. Indien niks is opgegeven wordt de "
|
||||
"gebruikersnaam weergegeven"
|
||||
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:314
|
||||
msgid "Name"
|
||||
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"
|
||||
msgstr "Etiketten"
|
||||
|
||||
@@ -169,7 +169,7 @@ msgstr "Voorbereidingstijd in minuten"
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
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"
|
||||
msgstr "Pad"
|
||||
|
||||
@@ -181,7 +181,7 @@ msgstr "Opslag UID"
|
||||
msgid "Default"
|
||||
msgstr "Standaard waarde"
|
||||
|
||||
#: .\cookbook\forms.py:173
|
||||
#: .\cookbook\forms.py:190
|
||||
msgid ""
|
||||
"To prevent duplicates recipes with the same name as existing ones are "
|
||||
"ignored. Check this box to import everything."
|
||||
@@ -189,19 +189,19 @@ msgstr ""
|
||||
"Om dubbelingen te voorkomen worden recepten met dezelfde naam als een "
|
||||
"bestaand recept genegeerd. Vink aan om alles te importeren."
|
||||
|
||||
#: .\cookbook\forms.py:196
|
||||
#: .\cookbook\forms.py:213
|
||||
msgid "Add your comment: "
|
||||
msgstr "Voeg een opmerking toe: "
|
||||
|
||||
#: .\cookbook\forms.py:211
|
||||
#: .\cookbook\forms.py:228
|
||||
msgid "Leave empty for dropbox and enter app password for 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."
|
||||
msgstr "Laat leeg voor nextcloud en vul de api token in voor dropbox."
|
||||
|
||||
#: .\cookbook\forms.py:227
|
||||
#: .\cookbook\forms.py:244
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"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>/"
|
||||
"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"
|
||||
msgstr "Opslag"
|
||||
|
||||
#: .\cookbook\forms.py:267
|
||||
#: .\cookbook\forms.py:284
|
||||
msgid "Active"
|
||||
msgstr "Actief"
|
||||
|
||||
#: .\cookbook\forms.py:273
|
||||
#: .\cookbook\forms.py:290
|
||||
msgid "Search String"
|
||||
msgstr "Zoekopdracht"
|
||||
|
||||
#: .\cookbook\forms.py:300
|
||||
#: .\cookbook\forms.py:317
|
||||
msgid "File ID"
|
||||
msgstr "Bestands ID"
|
||||
|
||||
#: .\cookbook\forms.py:322
|
||||
#: .\cookbook\forms.py:339
|
||||
msgid "You must provide at least a recipe or a title."
|
||||
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."
|
||||
msgstr ""
|
||||
"Je kan in de instellingen standaard gebruikers in stellen om de recepten met "
|
||||
"te delen."
|
||||
|
||||
#: .\cookbook\forms.py:336
|
||||
#: .\cookbook\forms.py:353
|
||||
msgid ""
|
||||
"You can use markdown to format this field. See the <a href=\"/docs/markdown/"
|
||||
"\">docs here</a>"
|
||||
@@ -243,15 +243,15 @@ msgstr ""
|
||||
"Je kunt markdown gebruiken om dit veld te op te maken. Bekijk de <a href=\"/"
|
||||
"docs/markdown/\">documentatie hier</a>"
|
||||
|
||||
#: .\cookbook\forms.py:362
|
||||
#: .\cookbook\forms.py:379
|
||||
msgid "Maximum number of users for this space reached."
|
||||
msgstr "Maximum aantal gebruikers voor deze ruimte bereikt."
|
||||
|
||||
#: .\cookbook\forms.py:368
|
||||
#: .\cookbook\forms.py:385
|
||||
msgid "Email address already taken!"
|
||||
msgstr "E-mailadres reeds in gebruik!"
|
||||
|
||||
#: .\cookbook\forms.py:376
|
||||
#: .\cookbook\forms.py:393
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be sent "
|
||||
"to the user."
|
||||
@@ -259,15 +259,15 @@ msgstr ""
|
||||
"Een e-mailadres is niet vereist, maar indien aanwezig zal de "
|
||||
"uitnodigingslink naar de gebruiker worden gestuurd."
|
||||
|
||||
#: .\cookbook\forms.py:391
|
||||
#: .\cookbook\forms.py:408
|
||||
msgid "Name already taken."
|
||||
msgstr "Naam reeds in gebruik."
|
||||
|
||||
#: .\cookbook\forms.py:402
|
||||
#: .\cookbook\forms.py:419
|
||||
msgid "Accept Terms and Privacy"
|
||||
msgstr "Accepteer voorwaarden"
|
||||
|
||||
#: .\cookbook\forms.py:434
|
||||
#: .\cookbook\forms.py:451
|
||||
msgid ""
|
||||
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
|
||||
"g. low values mean more typos are ignored)."
|
||||
@@ -275,7 +275,7 @@ msgstr ""
|
||||
"Bepaalt hoe 'fuzzy' een zoekopdracht is als het trigram vergelijken gebruikt "
|
||||
"(lage waarden betekenen bijvoorbeeld dat meer typefouten genegeerd worden)."
|
||||
|
||||
#: .\cookbook\forms.py:444
|
||||
#: .\cookbook\forms.py:461
|
||||
msgid ""
|
||||
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
|
||||
"full description of choices."
|
||||
@@ -283,7 +283,7 @@ msgstr ""
|
||||
"Selecteer zoekmethode. Klik <a href=\"/docs/search/\">hier</a> voor een "
|
||||
"beschrijving van de keuzes."
|
||||
|
||||
#: .\cookbook\forms.py:445
|
||||
#: .\cookbook\forms.py:462
|
||||
msgid ""
|
||||
"Use fuzzy matching on units, keywords and ingredients when editing and "
|
||||
"importing recipes."
|
||||
@@ -291,7 +291,7 @@ msgstr ""
|
||||
"Gebruik 'fuzzy' koppelen bij eenheden, etiketten en ingrediënten bij "
|
||||
"bewerken en importeren van recepten."
|
||||
|
||||
#: .\cookbook\forms.py:447
|
||||
#: .\cookbook\forms.py:464
|
||||
msgid ""
|
||||
"Fields to search ignoring accents. Selecting this option can improve or "
|
||||
"degrade search quality depending on language"
|
||||
@@ -300,7 +300,7 @@ msgstr ""
|
||||
"deze optie kan de zoekkwaliteit afhankelijk van de taal, zowel verbeteren "
|
||||
"als verslechteren"
|
||||
|
||||
#: .\cookbook\forms.py:449
|
||||
#: .\cookbook\forms.py:466
|
||||
msgid ""
|
||||
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
|
||||
"'pie' and 'piece' and 'soapie')"
|
||||
@@ -308,7 +308,7 @@ msgstr ""
|
||||
"Velden doorzoeken op gedeelde overeenkomsten. (zoeken op 'Appel' vindt "
|
||||
"'appel', 'aardappel' en 'appelsap')"
|
||||
|
||||
#: .\cookbook\forms.py:451
|
||||
#: .\cookbook\forms.py:468
|
||||
msgid ""
|
||||
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
|
||||
"will return 'salad' and 'sandwich')"
|
||||
@@ -316,7 +316,7 @@ msgstr ""
|
||||
"Velden doorzoeken op overeenkomsten aan het begin van het woord. (zoeken op "
|
||||
"'sa' vindt 'salade' en 'sandwich')"
|
||||
|
||||
#: .\cookbook\forms.py:453
|
||||
#: .\cookbook\forms.py:470
|
||||
msgid ""
|
||||
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
|
||||
"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: "
|
||||
"deze optie conflicteert met de zoekmethoden 'web' en 'raw'."
|
||||
|
||||
#: .\cookbook\forms.py:455
|
||||
#: .\cookbook\forms.py:472
|
||||
msgid ""
|
||||
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
|
||||
"only function with fulltext fields."
|
||||
@@ -332,35 +332,35 @@ msgstr ""
|
||||
"Velden doorzoeken op volledige tekst. Noot: Web, Zin en Raw zoekmethoden "
|
||||
"werken alleen met volledige tekstvelden."
|
||||
|
||||
#: .\cookbook\forms.py:459
|
||||
#: .\cookbook\forms.py:476
|
||||
msgid "Search Method"
|
||||
msgstr "Zoekmethode"
|
||||
|
||||
#: .\cookbook\forms.py:460
|
||||
#: .\cookbook\forms.py:477
|
||||
msgid "Fuzzy Lookups"
|
||||
msgstr "'Fuzzy' zoekopdrachten"
|
||||
|
||||
#: .\cookbook\forms.py:461
|
||||
#: .\cookbook\forms.py:478
|
||||
msgid "Ignore Accent"
|
||||
msgstr "Negeer accent"
|
||||
|
||||
#: .\cookbook\forms.py:462
|
||||
#: .\cookbook\forms.py:479
|
||||
msgid "Partial Match"
|
||||
msgstr "Gedeeltelijke overeenkomst"
|
||||
|
||||
#: .\cookbook\forms.py:463
|
||||
#: .\cookbook\forms.py:480
|
||||
msgid "Starts With"
|
||||
msgstr "Begint met"
|
||||
|
||||
#: .\cookbook\forms.py:464
|
||||
#: .\cookbook\forms.py:481
|
||||
msgid "Fuzzy Search"
|
||||
msgstr "'Fuzzy' zoeken"
|
||||
|
||||
#: .\cookbook\forms.py:465
|
||||
#: .\cookbook\forms.py:482
|
||||
msgid "Full Text"
|
||||
msgstr "Volledige tekst"
|
||||
|
||||
#: .\cookbook\forms.py:490
|
||||
#: .\cookbook\forms.py:507
|
||||
msgid ""
|
||||
"Users will see all items you add to your shopping list. They must add you "
|
||||
"to see items on their list."
|
||||
@@ -368,7 +368,7 @@ msgstr ""
|
||||
"Gebruikers zien alle items die je op je boodschappenlijst zet. Ze moeten "
|
||||
"jou toevoegen om items op hun lijst te zien."
|
||||
|
||||
#: .\cookbook\forms.py:496
|
||||
#: .\cookbook\forms.py:513
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"include all related recipes."
|
||||
@@ -376,7 +376,7 @@ msgstr ""
|
||||
"Als een maaltijdplan aan de boodschappenlijst toegevoegd wordt (handmatig of "
|
||||
"automatisch), neem dan alle recepten op."
|
||||
|
||||
#: .\cookbook\forms.py:497
|
||||
#: .\cookbook\forms.py:514
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"exclude ingredients that are on hand."
|
||||
@@ -384,94 +384,94 @@ msgstr ""
|
||||
"Als een maaltijdplan aan de boodschappenlijst toegevoegd wordt (handmatig of "
|
||||
"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."
|
||||
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."
|
||||
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."
|
||||
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."
|
||||
msgstr ""
|
||||
"Markeer eten 'Op voorraad' wanneer het van het boodschappenlijstje is "
|
||||
"afgevinkt."
|
||||
|
||||
#: .\cookbook\forms.py:502
|
||||
#: .\cookbook\forms.py:519
|
||||
msgid "Delimiter to use for 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."
|
||||
msgstr ""
|
||||
"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"
|
||||
msgstr "Deel boodschappenlijst"
|
||||
|
||||
#: .\cookbook\forms.py:508
|
||||
#: .\cookbook\forms.py:525
|
||||
msgid "Autosync"
|
||||
msgstr "Autosync"
|
||||
|
||||
#: .\cookbook\forms.py:509
|
||||
#: .\cookbook\forms.py:526
|
||||
msgid "Auto Add Meal Plan"
|
||||
msgstr "Voeg maaltijdplan automatisch toe"
|
||||
|
||||
#: .\cookbook\forms.py:510
|
||||
#: .\cookbook\forms.py:527
|
||||
msgid "Exclude On Hand"
|
||||
msgstr "Sluit op voorraad uit"
|
||||
|
||||
#: .\cookbook\forms.py:511
|
||||
#: .\cookbook\forms.py:528
|
||||
msgid "Include Related"
|
||||
msgstr "Neem gerelateerde op"
|
||||
|
||||
#: .\cookbook\forms.py:512
|
||||
#: .\cookbook\forms.py:529
|
||||
msgid "Default Delay Hours"
|
||||
msgstr "Standaard vertraging in uren"
|
||||
|
||||
#: .\cookbook\forms.py:513
|
||||
#: .\cookbook\forms.py:530
|
||||
msgid "Filter to Supermarket"
|
||||
msgstr "Filter op supermarkt"
|
||||
|
||||
#: .\cookbook\forms.py:514
|
||||
#: .\cookbook\forms.py:531
|
||||
msgid "Recent Days"
|
||||
msgstr "Afgelopen dagen"
|
||||
|
||||
#: .\cookbook\forms.py:515
|
||||
#: .\cookbook\forms.py:532
|
||||
msgid "CSV Delimiter"
|
||||
msgstr "CSV scheidingsteken"
|
||||
|
||||
#: .\cookbook\forms.py:516
|
||||
#: .\cookbook\forms.py:533
|
||||
msgid "List Prefix"
|
||||
msgstr "Lijst voorvoegsel"
|
||||
|
||||
#: .\cookbook\forms.py:517
|
||||
#: .\cookbook\forms.py:534
|
||||
msgid "Auto On Hand"
|
||||
msgstr "Auto op voorraad"
|
||||
|
||||
#: .\cookbook\forms.py:527
|
||||
#: .\cookbook\forms.py:544
|
||||
msgid "Reset Food Inheritance"
|
||||
msgstr "Herstel Ingrediënt overname"
|
||||
|
||||
#: .\cookbook\forms.py:528
|
||||
#: .\cookbook\forms.py:545
|
||||
msgid "Reset all food to inherit the fields configured."
|
||||
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."
|
||||
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"
|
||||
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."
|
||||
msgstr "Gebruik de meervoudsvorm voor eenheden en voedsel in deze ruimte."
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Joachim Weber <joachim.weber@gmx.de>\n"
|
||||
"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."
|
||||
msgstr "Ativar a funcionalidade comentar receitas."
|
||||
|
||||
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492
|
||||
#: .\cookbook\forms.py:79 .\cookbook\forms.py:509
|
||||
msgid ""
|
||||
"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. "
|
||||
@@ -131,7 +131,7 @@ msgstr ""
|
||||
msgid "Makes the navbar stick to the top of the page."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
@@ -151,11 +151,11 @@ msgstr ""
|
||||
"Ambos os campos são opcionais. Se nenhum for preenchido o nome de utilizador "
|
||||
"será apresentado"
|
||||
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:314
|
||||
msgid "Name"
|
||||
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"
|
||||
msgstr "Palavras-chave"
|
||||
|
||||
@@ -167,7 +167,7 @@ msgstr "Tempo de preparação em minutos"
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
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"
|
||||
msgstr "Caminho"
|
||||
|
||||
@@ -179,7 +179,7 @@ msgstr "UID de armazenamento"
|
||||
msgid "Default"
|
||||
msgstr "Predefinição"
|
||||
|
||||
#: .\cookbook\forms.py:173
|
||||
#: .\cookbook\forms.py:190
|
||||
msgid ""
|
||||
"To prevent duplicates recipes with the same name as existing ones are "
|
||||
"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 "
|
||||
"são ignoradas. Marque esta caixa para importar tudo."
|
||||
|
||||
#: .\cookbook\forms.py:196
|
||||
#: .\cookbook\forms.py:213
|
||||
msgid "Add your comment: "
|
||||
msgstr "Adicionar comentário: "
|
||||
|
||||
#: .\cookbook\forms.py:211
|
||||
#: .\cookbook\forms.py:228
|
||||
msgid "Leave empty for dropbox and enter app password for nextcloud."
|
||||
msgstr ""
|
||||
"Deixar vazio para Dropbox e inserir palavra-passe de aplicação para "
|
||||
"Nextcloud."
|
||||
|
||||
#: .\cookbook\forms.py:218
|
||||
#: .\cookbook\forms.py:235
|
||||
msgid "Leave empty for nextcloud and enter api token for dropbox."
|
||||
msgstr "Deixar vazio para Nextcloud e inserir token api para Dropbox."
|
||||
|
||||
#: .\cookbook\forms.py:227
|
||||
#: .\cookbook\forms.py:244
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"php/webdav/</code> is added automatically)"
|
||||
@@ -209,33 +209,33 @@ msgstr ""
|
||||
"Deixar vazio para Dropbox e inserir apenas url base para Nextcloud (<code>/"
|
||||
"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"
|
||||
msgstr "Armazenamento"
|
||||
|
||||
#: .\cookbook\forms.py:267
|
||||
#: .\cookbook\forms.py:284
|
||||
msgid "Active"
|
||||
msgstr "Ativo"
|
||||
|
||||
#: .\cookbook\forms.py:273
|
||||
#: .\cookbook\forms.py:290
|
||||
msgid "Search String"
|
||||
msgstr "Procurar"
|
||||
|
||||
#: .\cookbook\forms.py:300
|
||||
#: .\cookbook\forms.py:317
|
||||
msgid "File ID"
|
||||
msgstr "ID the ficheiro"
|
||||
|
||||
#: .\cookbook\forms.py:322
|
||||
#: .\cookbook\forms.py:339
|
||||
msgid "You must provide at least a recipe or a title."
|
||||
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."
|
||||
msgstr ""
|
||||
"É possível escolher os utilizadores com quem partilhar receitas por defeitos "
|
||||
"nas definições."
|
||||
|
||||
#: .\cookbook\forms.py:336
|
||||
#: .\cookbook\forms.py:353
|
||||
msgid ""
|
||||
"You can use markdown to format this field. See the <a href=\"/docs/markdown/"
|
||||
"\">docs here</a>"
|
||||
@@ -243,15 +243,15 @@ msgstr ""
|
||||
"É possível utilizar markdown para editar este campo. Documentação <a href=\"/"
|
||||
"docs/markdown/\">disponível aqui</a>"
|
||||
|
||||
#: .\cookbook\forms.py:362
|
||||
#: .\cookbook\forms.py:379
|
||||
msgid "Maximum number of users for this space reached."
|
||||
msgstr "Número máximo de utilizadores alcançado."
|
||||
|
||||
#: .\cookbook\forms.py:368
|
||||
#: .\cookbook\forms.py:385
|
||||
msgid "Email address already taken!"
|
||||
msgstr "Endereço email já utilizado!"
|
||||
|
||||
#: .\cookbook\forms.py:376
|
||||
#: .\cookbook\forms.py:393
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be sent "
|
||||
"to the user."
|
||||
@@ -259,15 +259,15 @@ msgstr ""
|
||||
"Um endereço de email não é obrigatório mas se fornecido será enviada uma "
|
||||
"mensagem ao utilizador."
|
||||
|
||||
#: .\cookbook\forms.py:391
|
||||
#: .\cookbook\forms.py:408
|
||||
msgid "Name already taken."
|
||||
msgstr "Nome já existente."
|
||||
|
||||
#: .\cookbook\forms.py:402
|
||||
#: .\cookbook\forms.py:419
|
||||
msgid "Accept Terms and Privacy"
|
||||
msgstr "Aceitar Termos e Condições"
|
||||
|
||||
#: .\cookbook\forms.py:434
|
||||
#: .\cookbook\forms.py:451
|
||||
msgid ""
|
||||
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
|
||||
"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 "
|
||||
"ignorados)."
|
||||
|
||||
#: .\cookbook\forms.py:444
|
||||
#: .\cookbook\forms.py:461
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "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 "
|
||||
"encontrada <a href=\"/docs/search/\">aqui</a>."
|
||||
|
||||
#: .\cookbook\forms.py:445
|
||||
#: .\cookbook\forms.py:462
|
||||
msgid ""
|
||||
"Use fuzzy matching on units, keywords and ingredients when editing and "
|
||||
"importing recipes."
|
||||
@@ -296,7 +296,7 @@ msgstr ""
|
||||
"Utilizar correspondência difusa em unidades, palavras-chave e ingredientes "
|
||||
"ao editar e importar receitas."
|
||||
|
||||
#: .\cookbook\forms.py:447
|
||||
#: .\cookbook\forms.py:464
|
||||
msgid ""
|
||||
"Fields to search ignoring accents. Selecting this option can improve or "
|
||||
"degrade search quality depending on language"
|
||||
@@ -304,171 +304,171 @@ msgstr ""
|
||||
"Campos de pesquisa que ignoram pontuação. Esta opção pode aumentar ou "
|
||||
"diminuir a qualidade de pesquisa dependendo da língua em uso"
|
||||
|
||||
#: .\cookbook\forms.py:449
|
||||
#: .\cookbook\forms.py:466
|
||||
msgid ""
|
||||
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
|
||||
"'pie' and 'piece' and 'soapie')"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:451
|
||||
#: .\cookbook\forms.py:468
|
||||
msgid ""
|
||||
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
|
||||
"will return 'salad' and 'sandwich')"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:453
|
||||
#: .\cookbook\forms.py:470
|
||||
msgid ""
|
||||
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
|
||||
"Note: this option will conflict with 'web' and 'raw' methods of search."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:455
|
||||
#: .\cookbook\forms.py:472
|
||||
msgid ""
|
||||
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
|
||||
"only function with fulltext fields."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:459
|
||||
#: .\cookbook\forms.py:476
|
||||
#, fuzzy
|
||||
#| msgid "Search"
|
||||
msgid "Search Method"
|
||||
msgstr "Procurar"
|
||||
|
||||
#: .\cookbook\forms.py:460
|
||||
#: .\cookbook\forms.py:477
|
||||
msgid "Fuzzy Lookups"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:461
|
||||
#: .\cookbook\forms.py:478
|
||||
msgid "Ignore Accent"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:462
|
||||
#: .\cookbook\forms.py:479
|
||||
msgid "Partial Match"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:463
|
||||
#: .\cookbook\forms.py:480
|
||||
msgid "Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:464
|
||||
#: .\cookbook\forms.py:481
|
||||
#, fuzzy
|
||||
#| msgid "Search"
|
||||
msgid "Fuzzy Search"
|
||||
msgstr "Procurar"
|
||||
|
||||
#: .\cookbook\forms.py:465
|
||||
#: .\cookbook\forms.py:482
|
||||
#, fuzzy
|
||||
#| msgid "Text"
|
||||
msgid "Full Text"
|
||||
msgstr "Texto"
|
||||
|
||||
#: .\cookbook\forms.py:490
|
||||
#: .\cookbook\forms.py:507
|
||||
msgid ""
|
||||
"Users will see all items you add to your shopping list. They must add you "
|
||||
"to see items on their list."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:496
|
||||
#: .\cookbook\forms.py:513
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"include all related recipes."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:497
|
||||
#: .\cookbook\forms.py:514
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"exclude ingredients that are on hand."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:498
|
||||
#: .\cookbook\forms.py:515
|
||||
msgid "Default number of hours to delay a shopping list entry."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:499
|
||||
#: .\cookbook\forms.py:516
|
||||
msgid "Filter shopping list to only include supermarket categories."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:500
|
||||
#: .\cookbook\forms.py:517
|
||||
msgid "Days of recent shopping list entries to display."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:501
|
||||
#: .\cookbook\forms.py:518
|
||||
msgid "Mark food 'On Hand' when checked off shopping list."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:502
|
||||
#: .\cookbook\forms.py:519
|
||||
msgid "Delimiter to use for CSV exports."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:503
|
||||
#: .\cookbook\forms.py:520
|
||||
msgid "Prefix to add when copying list to the clipboard."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:507
|
||||
#: .\cookbook\forms.py:524
|
||||
#, fuzzy
|
||||
#| msgid "Shopping"
|
||||
msgid "Share Shopping List"
|
||||
msgstr "Compras"
|
||||
|
||||
#: .\cookbook\forms.py:508
|
||||
#: .\cookbook\forms.py:525
|
||||
msgid "Autosync"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:509
|
||||
#: .\cookbook\forms.py:526
|
||||
msgid "Auto Add Meal Plan"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:510
|
||||
#: .\cookbook\forms.py:527
|
||||
msgid "Exclude On Hand"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:511
|
||||
#: .\cookbook\forms.py:528
|
||||
msgid "Include Related"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:512
|
||||
#: .\cookbook\forms.py:529
|
||||
msgid "Default Delay Hours"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:513
|
||||
#: .\cookbook\forms.py:530
|
||||
msgid "Filter to Supermarket"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:514
|
||||
#: .\cookbook\forms.py:531
|
||||
msgid "Recent Days"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:515
|
||||
#: .\cookbook\forms.py:532
|
||||
msgid "CSV Delimiter"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:516
|
||||
#: .\cookbook\forms.py:533
|
||||
msgid "List Prefix"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:517
|
||||
#: .\cookbook\forms.py:534
|
||||
msgid "Auto On Hand"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:527
|
||||
#: .\cookbook\forms.py:544
|
||||
msgid "Reset Food Inheritance"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:528
|
||||
#: .\cookbook\forms.py:545
|
||||
msgid "Reset all food to inherit the fields configured."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:540
|
||||
#: .\cookbook\forms.py:557
|
||||
#, fuzzy
|
||||
#| msgid "Food that should be replaced."
|
||||
msgid "Fields on food that should be inherited by default."
|
||||
msgstr "Prato a ser alterado."
|
||||
|
||||
#: .\cookbook\forms.py:541
|
||||
#: .\cookbook\forms.py:558
|
||||
msgid "Show recipe counts on search filters"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\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."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492
|
||||
#: .\cookbook\forms.py:79 .\cookbook\forms.py:509
|
||||
msgid ""
|
||||
"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. "
|
||||
@@ -114,7 +114,7 @@ msgstr ""
|
||||
msgid "Makes the navbar stick to the top of the page."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
@@ -132,11 +132,11 @@ msgid ""
|
||||
"instead"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:314
|
||||
msgid "Name"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
@@ -148,7 +148,7 @@ msgstr ""
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
@@ -160,261 +160,261 @@ msgstr ""
|
||||
msgid "Default"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:173
|
||||
#: .\cookbook\forms.py:190
|
||||
msgid ""
|
||||
"To prevent duplicates recipes with the same name as existing ones are "
|
||||
"ignored. Check this box to import everything."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:196
|
||||
#: .\cookbook\forms.py:213
|
||||
msgid "Add your comment: "
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:211
|
||||
#: .\cookbook\forms.py:228
|
||||
msgid "Leave empty for dropbox and enter app password for nextcloud."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:218
|
||||
#: .\cookbook\forms.py:235
|
||||
msgid "Leave empty for nextcloud and enter api token for dropbox."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:227
|
||||
#: .\cookbook\forms.py:244
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"php/webdav/</code> is added automatically)"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157
|
||||
#: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
|
||||
msgid "Storage"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:267
|
||||
#: .\cookbook\forms.py:284
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:273
|
||||
#: .\cookbook\forms.py:290
|
||||
msgid "Search String"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:300
|
||||
#: .\cookbook\forms.py:317
|
||||
msgid "File ID"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:322
|
||||
#: .\cookbook\forms.py:339
|
||||
msgid "You must provide at least a recipe or a title."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:335
|
||||
#: .\cookbook\forms.py:352
|
||||
msgid "You can list default users to share recipes with in the settings."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:336
|
||||
#: .\cookbook\forms.py:353
|
||||
msgid ""
|
||||
"You can use markdown to format this field. See the <a href=\"/docs/markdown/"
|
||||
"\">docs here</a>"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:362
|
||||
#: .\cookbook\forms.py:379
|
||||
msgid "Maximum number of users for this space reached."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:368
|
||||
#: .\cookbook\forms.py:385
|
||||
msgid "Email address already taken!"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:376
|
||||
#: .\cookbook\forms.py:393
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be sent "
|
||||
"to the user."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:391
|
||||
#: .\cookbook\forms.py:408
|
||||
msgid "Name already taken."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:402
|
||||
#: .\cookbook\forms.py:419
|
||||
msgid "Accept Terms and Privacy"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:434
|
||||
#: .\cookbook\forms.py:451
|
||||
msgid ""
|
||||
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
|
||||
"g. low values mean more typos are ignored)."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:444
|
||||
#: .\cookbook\forms.py:461
|
||||
msgid ""
|
||||
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
|
||||
"full description of choices."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:445
|
||||
#: .\cookbook\forms.py:462
|
||||
msgid ""
|
||||
"Use fuzzy matching on units, keywords and ingredients when editing and "
|
||||
"importing recipes."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:447
|
||||
#: .\cookbook\forms.py:464
|
||||
msgid ""
|
||||
"Fields to search ignoring accents. Selecting this option can improve or "
|
||||
"degrade search quality depending on language"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:449
|
||||
#: .\cookbook\forms.py:466
|
||||
msgid ""
|
||||
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
|
||||
"'pie' and 'piece' and 'soapie')"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:451
|
||||
#: .\cookbook\forms.py:468
|
||||
msgid ""
|
||||
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
|
||||
"will return 'salad' and 'sandwich')"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:453
|
||||
#: .\cookbook\forms.py:470
|
||||
msgid ""
|
||||
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
|
||||
"Note: this option will conflict with 'web' and 'raw' methods of search."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:455
|
||||
#: .\cookbook\forms.py:472
|
||||
msgid ""
|
||||
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
|
||||
"only function with fulltext fields."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:459
|
||||
#: .\cookbook\forms.py:476
|
||||
msgid "Search Method"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:460
|
||||
#: .\cookbook\forms.py:477
|
||||
msgid "Fuzzy Lookups"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:461
|
||||
#: .\cookbook\forms.py:478
|
||||
msgid "Ignore Accent"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:462
|
||||
#: .\cookbook\forms.py:479
|
||||
msgid "Partial Match"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:463
|
||||
#: .\cookbook\forms.py:480
|
||||
msgid "Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:464
|
||||
#: .\cookbook\forms.py:481
|
||||
msgid "Fuzzy Search"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:465
|
||||
#: .\cookbook\forms.py:482
|
||||
msgid "Full Text"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:490
|
||||
#: .\cookbook\forms.py:507
|
||||
msgid ""
|
||||
"Users will see all items you add to your shopping list. They must add you "
|
||||
"to see items on their list."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:496
|
||||
#: .\cookbook\forms.py:513
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"include all related recipes."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:497
|
||||
#: .\cookbook\forms.py:514
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"exclude ingredients that are on hand."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:498
|
||||
#: .\cookbook\forms.py:515
|
||||
msgid "Default number of hours to delay a shopping list entry."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:499
|
||||
#: .\cookbook\forms.py:516
|
||||
msgid "Filter shopping list to only include supermarket categories."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:500
|
||||
#: .\cookbook\forms.py:517
|
||||
msgid "Days of recent shopping list entries to display."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:501
|
||||
#: .\cookbook\forms.py:518
|
||||
msgid "Mark food 'On Hand' when checked off shopping list."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:502
|
||||
#: .\cookbook\forms.py:519
|
||||
msgid "Delimiter to use for CSV exports."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:503
|
||||
#: .\cookbook\forms.py:520
|
||||
msgid "Prefix to add when copying list to the clipboard."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:507
|
||||
#: .\cookbook\forms.py:524
|
||||
msgid "Share Shopping List"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:508
|
||||
#: .\cookbook\forms.py:525
|
||||
msgid "Autosync"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:509
|
||||
#: .\cookbook\forms.py:526
|
||||
msgid "Auto Add Meal Plan"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:510
|
||||
#: .\cookbook\forms.py:527
|
||||
msgid "Exclude On Hand"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:511
|
||||
#: .\cookbook\forms.py:528
|
||||
msgid "Include Related"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:512
|
||||
#: .\cookbook\forms.py:529
|
||||
msgid "Default Delay Hours"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:513
|
||||
#: .\cookbook\forms.py:530
|
||||
msgid "Filter to Supermarket"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:514
|
||||
#: .\cookbook\forms.py:531
|
||||
msgid "Recent Days"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:515
|
||||
#: .\cookbook\forms.py:532
|
||||
msgid "CSV Delimiter"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:516
|
||||
#: .\cookbook\forms.py:533
|
||||
msgid "List Prefix"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:517
|
||||
#: .\cookbook\forms.py:534
|
||||
msgid "Auto On Hand"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:527
|
||||
#: .\cookbook\forms.py:544
|
||||
msgid "Reset Food Inheritance"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:528
|
||||
#: .\cookbook\forms.py:545
|
||||
msgid "Reset all food to inherit the fields configured."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:540
|
||||
#: .\cookbook\forms.py:557
|
||||
msgid "Fields on food that should be inherited by default."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:541
|
||||
#: .\cookbook\forms.py:558
|
||||
msgid "Show recipe counts on search filters"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:542
|
||||
#: .\cookbook\forms.py:559
|
||||
msgid "Use the plural form for units and food inside this space."
|
||||
msgstr ""
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -10,7 +10,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: Gorkem <g.kalipcilar@gmail.com>\n"
|
||||
"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."
|
||||
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 ""
|
||||
"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. "
|
||||
@@ -127,7 +127,7 @@ msgstr ""
|
||||
msgid "Makes the navbar stick to the top of the page."
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
@@ -145,11 +145,11 @@ msgid ""
|
||||
"instead"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:314
|
||||
msgid "Name"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
@@ -161,7 +161,7 @@ msgstr ""
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
@@ -173,263 +173,263 @@ msgstr ""
|
||||
msgid "Default"
|
||||
msgstr "Varsayılan"
|
||||
|
||||
#: .\cookbook\forms.py:173
|
||||
#: .\cookbook\forms.py:190
|
||||
msgid ""
|
||||
"To prevent duplicates recipes with the same name as existing ones are "
|
||||
"ignored. Check this box to import everything."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:196
|
||||
#: .\cookbook\forms.py:213
|
||||
msgid "Add your comment: "
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:211
|
||||
#: .\cookbook\forms.py:228
|
||||
msgid "Leave empty for dropbox and enter app password for nextcloud."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:218
|
||||
#: .\cookbook\forms.py:235
|
||||
msgid "Leave empty for nextcloud and enter api token for dropbox."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:227
|
||||
#: .\cookbook\forms.py:244
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"php/webdav/</code> is added automatically)"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:265 .\cookbook\views\edit.py:157
|
||||
#: .\cookbook\forms.py:282 .\cookbook\views\edit.py:157
|
||||
msgid "Storage"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:267
|
||||
#: .\cookbook\forms.py:284
|
||||
msgid "Active"
|
||||
msgstr "Aktif"
|
||||
|
||||
#: .\cookbook\forms.py:273
|
||||
#: .\cookbook\forms.py:290
|
||||
msgid "Search String"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:300
|
||||
#: .\cookbook\forms.py:317
|
||||
msgid "File ID"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:322
|
||||
#: .\cookbook\forms.py:339
|
||||
msgid "You must provide at least a recipe or a title."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:335
|
||||
#: .\cookbook\forms.py:352
|
||||
msgid "You can list default users to share recipes with in the settings."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:336
|
||||
#: .\cookbook\forms.py:353
|
||||
msgid ""
|
||||
"You can use markdown to format this field. See the <a href=\"/docs/markdown/"
|
||||
"\">docs here</a>"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:362
|
||||
#: .\cookbook\forms.py:379
|
||||
msgid "Maximum number of users for this space reached."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:368
|
||||
#: .\cookbook\forms.py:385
|
||||
msgid "Email address already taken!"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:376
|
||||
#: .\cookbook\forms.py:393
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be sent "
|
||||
"to the user."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:391
|
||||
#: .\cookbook\forms.py:408
|
||||
msgid "Name already taken."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:402
|
||||
#: .\cookbook\forms.py:419
|
||||
msgid "Accept Terms and Privacy"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:434
|
||||
#: .\cookbook\forms.py:451
|
||||
msgid ""
|
||||
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
|
||||
"g. low values mean more typos are ignored)."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:444
|
||||
#: .\cookbook\forms.py:461
|
||||
msgid ""
|
||||
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
|
||||
"full description of choices."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:445
|
||||
#: .\cookbook\forms.py:462
|
||||
msgid ""
|
||||
"Use fuzzy matching on units, keywords and ingredients when editing and "
|
||||
"importing recipes."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:447
|
||||
#: .\cookbook\forms.py:464
|
||||
msgid ""
|
||||
"Fields to search ignoring accents. Selecting this option can improve or "
|
||||
"degrade search quality depending on language"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:449
|
||||
#: .\cookbook\forms.py:466
|
||||
msgid ""
|
||||
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
|
||||
"'pie' and 'piece' and 'soapie')"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:451
|
||||
#: .\cookbook\forms.py:468
|
||||
msgid ""
|
||||
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
|
||||
"will return 'salad' and 'sandwich')"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:453
|
||||
#: .\cookbook\forms.py:470
|
||||
msgid ""
|
||||
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
|
||||
"Note: this option will conflict with 'web' and 'raw' methods of search."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:455
|
||||
#: .\cookbook\forms.py:472
|
||||
msgid ""
|
||||
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
|
||||
"only function with fulltext fields."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:459
|
||||
#: .\cookbook\forms.py:476
|
||||
msgid "Search Method"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:460
|
||||
#: .\cookbook\forms.py:477
|
||||
msgid "Fuzzy Lookups"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:461
|
||||
#: .\cookbook\forms.py:478
|
||||
msgid "Ignore Accent"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:462
|
||||
#: .\cookbook\forms.py:479
|
||||
msgid "Partial Match"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:463
|
||||
#: .\cookbook\forms.py:480
|
||||
msgid "Starts With"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:464
|
||||
#: .\cookbook\forms.py:481
|
||||
msgid "Fuzzy Search"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:465
|
||||
#: .\cookbook\forms.py:482
|
||||
msgid "Full Text"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:490
|
||||
#: .\cookbook\forms.py:507
|
||||
msgid ""
|
||||
"Users will see all items you add to your shopping list. They must add you "
|
||||
"to see items on their list."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:496
|
||||
#: .\cookbook\forms.py:513
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"include all related recipes."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:497
|
||||
#: .\cookbook\forms.py:514
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"exclude ingredients that are on hand."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:498
|
||||
#: .\cookbook\forms.py:515
|
||||
msgid "Default number of hours to delay a shopping list entry."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:499
|
||||
#: .\cookbook\forms.py:516
|
||||
msgid "Filter shopping list to only include supermarket categories."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:500
|
||||
#: .\cookbook\forms.py:517
|
||||
msgid "Days of recent shopping list entries to display."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:501
|
||||
#: .\cookbook\forms.py:518
|
||||
msgid "Mark food 'On Hand' when checked off shopping list."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:502
|
||||
#: .\cookbook\forms.py:519
|
||||
msgid "Delimiter to use for CSV exports."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:503
|
||||
#: .\cookbook\forms.py:520
|
||||
msgid "Prefix to add when copying list to the clipboard."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:507
|
||||
#: .\cookbook\forms.py:524
|
||||
msgid "Share Shopping List"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:508
|
||||
#: .\cookbook\forms.py:525
|
||||
msgid "Autosync"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:509
|
||||
#: .\cookbook\forms.py:526
|
||||
msgid "Auto Add Meal Plan"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:510
|
||||
#: .\cookbook\forms.py:527
|
||||
msgid "Exclude On Hand"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:511
|
||||
#: .\cookbook\forms.py:528
|
||||
msgid "Include Related"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:512
|
||||
#: .\cookbook\forms.py:529
|
||||
msgid "Default Delay Hours"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:513
|
||||
#: .\cookbook\forms.py:530
|
||||
msgid "Filter to Supermarket"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:514
|
||||
#: .\cookbook\forms.py:531
|
||||
msgid "Recent Days"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:515
|
||||
#: .\cookbook\forms.py:532
|
||||
msgid "CSV Delimiter"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:516
|
||||
#: .\cookbook\forms.py:533
|
||||
msgid "List Prefix"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:517
|
||||
#: .\cookbook\forms.py:534
|
||||
msgid "Auto On Hand"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:527
|
||||
#: .\cookbook\forms.py:544
|
||||
msgid "Reset Food Inheritance"
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:528
|
||||
#: .\cookbook\forms.py:545
|
||||
msgid "Reset all food to inherit the fields configured."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:540
|
||||
#: .\cookbook\forms.py:557
|
||||
msgid "Fields on food that should be inherited by default."
|
||||
msgstr ""
|
||||
|
||||
#: .\cookbook\forms.py:541
|
||||
#: .\cookbook\forms.py:558
|
||||
#, fuzzy
|
||||
#| msgid "Show recently viewed recipes on search page."
|
||||
msgid "Show recipe counts on search filters"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: 吕楪 <thy@irithys.com>\n"
|
||||
"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."
|
||||
msgstr "如果你希望能够在菜谱下面创建并看到评论。"
|
||||
|
||||
#: .\cookbook\forms.py:79 .\cookbook\forms.py:492
|
||||
#: .\cookbook\forms.py:79 .\cookbook\forms.py:509
|
||||
msgid ""
|
||||
"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. "
|
||||
@@ -119,7 +119,7 @@ msgstr ""
|
||||
msgid "Makes the navbar stick to the top of the page."
|
||||
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."
|
||||
msgstr "自动将膳食计划食材添加到购物清单中。"
|
||||
|
||||
@@ -137,11 +137,11 @@ msgid ""
|
||||
"instead"
|
||||
msgstr "这两个字段都是可选的。如果没有给出,将显示用户名"
|
||||
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:297
|
||||
#: .\cookbook\forms.py:123 .\cookbook\forms.py:314
|
||||
msgid "Name"
|
||||
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"
|
||||
msgstr "关键词"
|
||||
|
||||
@@ -153,7 +153,7 @@ msgstr "准备时间(分钟)"
|
||||
msgid "Waiting time (cooking/baking) in minutes"
|
||||
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"
|
||||
msgstr "路径"
|
||||
|
||||
@@ -165,7 +165,7 @@ msgstr "存储 UID"
|
||||
msgid "Default"
|
||||
msgstr "默认"
|
||||
|
||||
#: .\cookbook\forms.py:173
|
||||
#: .\cookbook\forms.py:190
|
||||
msgid ""
|
||||
"To prevent duplicates recipes with the same name as existing ones are "
|
||||
"ignored. Check this box to import everything."
|
||||
@@ -173,19 +173,19 @@ msgstr ""
|
||||
"为防止重复,忽略与现有同名的菜谱。选中此框可导入所有内容(危险操作,请先备"
|
||||
"份)。"
|
||||
|
||||
#: .\cookbook\forms.py:196
|
||||
#: .\cookbook\forms.py:213
|
||||
msgid "Add your comment: "
|
||||
msgstr "发表评论: "
|
||||
|
||||
#: .\cookbook\forms.py:211
|
||||
#: .\cookbook\forms.py:228
|
||||
msgid "Leave empty for dropbox and enter app password for nextcloud."
|
||||
msgstr "Dropbox 留空并输入 Nextcloud 应用密码。"
|
||||
|
||||
#: .\cookbook\forms.py:218
|
||||
#: .\cookbook\forms.py:235
|
||||
msgid "Leave empty for nextcloud and enter api token for dropbox."
|
||||
msgstr "Nextcloud 留空并输入 Dropbox API 令牌。"
|
||||
|
||||
#: .\cookbook\forms.py:227
|
||||
#: .\cookbook\forms.py:244
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"php/webdav/</code> is added automatically)"
|
||||
@@ -193,60 +193,60 @@ msgstr ""
|
||||
"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"
|
||||
msgstr "存储"
|
||||
|
||||
#: .\cookbook\forms.py:267
|
||||
#: .\cookbook\forms.py:284
|
||||
msgid "Active"
|
||||
msgstr "活跃"
|
||||
|
||||
#: .\cookbook\forms.py:273
|
||||
#: .\cookbook\forms.py:290
|
||||
msgid "Search String"
|
||||
msgstr "搜索字符串"
|
||||
|
||||
#: .\cookbook\forms.py:300
|
||||
#: .\cookbook\forms.py:317
|
||||
msgid "File ID"
|
||||
msgstr "文件编号"
|
||||
|
||||
#: .\cookbook\forms.py:322
|
||||
#: .\cookbook\forms.py:339
|
||||
msgid "You must provide at least a recipe or a title."
|
||||
msgstr "你必须至少提供一份菜谱或一个标题。"
|
||||
|
||||
#: .\cookbook\forms.py:335
|
||||
#: .\cookbook\forms.py:352
|
||||
msgid "You can list default users to share recipes with in the settings."
|
||||
msgstr "你可以在设置中列出默认用户来分享菜谱。"
|
||||
|
||||
#: .\cookbook\forms.py:336
|
||||
#: .\cookbook\forms.py:353
|
||||
msgid ""
|
||||
"You can use markdown to format this field. See the <a href=\"/docs/markdown/"
|
||||
"\">docs here</a>"
|
||||
msgstr ""
|
||||
"可以使用 Markdown 设置此字段格式。<a href=\"/docs/markdown/\">查看文档</a>"
|
||||
|
||||
#: .\cookbook\forms.py:362
|
||||
#: .\cookbook\forms.py:379
|
||||
msgid "Maximum number of users for this space reached."
|
||||
msgstr "已达到该空间的最大用户数。"
|
||||
|
||||
#: .\cookbook\forms.py:368
|
||||
#: .\cookbook\forms.py:385
|
||||
msgid "Email address already taken!"
|
||||
msgstr "电子邮件地址已被注册!"
|
||||
|
||||
#: .\cookbook\forms.py:376
|
||||
#: .\cookbook\forms.py:393
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be sent "
|
||||
"to the user."
|
||||
msgstr "电子邮件地址不是必需的,但如果存在,邀请链接将被发送给用户。"
|
||||
|
||||
#: .\cookbook\forms.py:391
|
||||
#: .\cookbook\forms.py:408
|
||||
msgid "Name already taken."
|
||||
msgstr "名字已被占用。"
|
||||
|
||||
#: .\cookbook\forms.py:402
|
||||
#: .\cookbook\forms.py:419
|
||||
msgid "Accept Terms and Privacy"
|
||||
msgstr "接受条款及隐私政策"
|
||||
|
||||
#: .\cookbook\forms.py:434
|
||||
#: .\cookbook\forms.py:451
|
||||
msgid ""
|
||||
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
|
||||
"g. low values mean more typos are ignored)."
|
||||
@@ -254,7 +254,7 @@ msgstr ""
|
||||
"确定使用三元图相似性匹配时搜索的模糊程度(例如,较低的值意味着忽略更多的打字"
|
||||
"错误)。"
|
||||
|
||||
#: .\cookbook\forms.py:444
|
||||
#: .\cookbook\forms.py:461
|
||||
msgid ""
|
||||
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
|
||||
"full description of choices."
|
||||
@@ -262,31 +262,31 @@ msgstr ""
|
||||
"选择搜索类型方法。 <a href=\"/docs/search/\">点击此处</a> 查看选项的完整说"
|
||||
"明。"
|
||||
|
||||
#: .\cookbook\forms.py:445
|
||||
#: .\cookbook\forms.py:462
|
||||
msgid ""
|
||||
"Use fuzzy matching on units, keywords and ingredients when editing and "
|
||||
"importing recipes."
|
||||
msgstr "编辑和导入菜谱时,对单位、关键词和食材使用模糊匹配。"
|
||||
|
||||
#: .\cookbook\forms.py:447
|
||||
#: .\cookbook\forms.py:464
|
||||
msgid ""
|
||||
"Fields to search ignoring accents. Selecting this option can improve or "
|
||||
"degrade search quality depending on language"
|
||||
msgstr "忽略搜索字段的重音。此选项会因语言差异导致搜索质量产生变化"
|
||||
|
||||
#: .\cookbook\forms.py:449
|
||||
#: .\cookbook\forms.py:466
|
||||
msgid ""
|
||||
"Fields to search for partial matches. (e.g. searching for 'Pie' will return "
|
||||
"'pie' and 'piece' and 'soapie')"
|
||||
msgstr "用于搜索部分匹配的字段。(如搜索“Pie”会返回“pie”、“piece”和“soapie”)"
|
||||
|
||||
#: .\cookbook\forms.py:451
|
||||
#: .\cookbook\forms.py:468
|
||||
msgid ""
|
||||
"Fields to search for beginning of word matches. (e.g. searching for 'sa' "
|
||||
"will return 'salad' and 'sandwich')"
|
||||
msgstr "用于搜索开头匹配的字段。(如搜索“sa”会返回“salad”和“sandwich”)"
|
||||
|
||||
#: .\cookbook\forms.py:453
|
||||
#: .\cookbook\forms.py:470
|
||||
msgid ""
|
||||
"Fields to 'fuzzy' search. (e.g. searching for 'recpie' will find 'recipe'.) "
|
||||
"Note: this option will conflict with 'web' and 'raw' methods of search."
|
||||
@@ -294,41 +294,41 @@ msgstr ""
|
||||
"“模糊”搜索字段。(例如搜索“recpie”将会找到“recipe”。)注意:此选项将"
|
||||
"与“web”和“raw”搜索方法冲突。"
|
||||
|
||||
#: .\cookbook\forms.py:455
|
||||
#: .\cookbook\forms.py:472
|
||||
msgid ""
|
||||
"Fields to full text search. Note: 'web', 'phrase', and 'raw' search methods "
|
||||
"only function with fulltext fields."
|
||||
msgstr "全文搜索字段。“web”、“phrase”和“raw”搜索方法仅适用于全文字段。"
|
||||
|
||||
#: .\cookbook\forms.py:459
|
||||
#: .\cookbook\forms.py:476
|
||||
msgid "Search Method"
|
||||
msgstr "搜索方法"
|
||||
|
||||
#: .\cookbook\forms.py:460
|
||||
#: .\cookbook\forms.py:477
|
||||
msgid "Fuzzy Lookups"
|
||||
msgstr "模糊查找"
|
||||
|
||||
#: .\cookbook\forms.py:461
|
||||
#: .\cookbook\forms.py:478
|
||||
msgid "Ignore Accent"
|
||||
msgstr "忽略重音"
|
||||
|
||||
#: .\cookbook\forms.py:462
|
||||
#: .\cookbook\forms.py:479
|
||||
msgid "Partial Match"
|
||||
msgstr "部分匹配"
|
||||
|
||||
#: .\cookbook\forms.py:463
|
||||
#: .\cookbook\forms.py:480
|
||||
msgid "Starts With"
|
||||
msgstr "起始于"
|
||||
|
||||
#: .\cookbook\forms.py:464
|
||||
#: .\cookbook\forms.py:481
|
||||
msgid "Fuzzy Search"
|
||||
msgstr "模糊搜索"
|
||||
|
||||
#: .\cookbook\forms.py:465
|
||||
#: .\cookbook\forms.py:482
|
||||
msgid "Full Text"
|
||||
msgstr "全文"
|
||||
|
||||
#: .\cookbook\forms.py:490
|
||||
#: .\cookbook\forms.py:507
|
||||
msgid ""
|
||||
"Users will see all items you add to your shopping list. They must add you "
|
||||
"to see items on their list."
|
||||
@@ -336,103 +336,103 @@ msgstr ""
|
||||
"用户将看到你添加到购物清单中的所有商品。他们必须将你添加到列表才能看到他们清"
|
||||
"单上的项目。"
|
||||
|
||||
#: .\cookbook\forms.py:496
|
||||
#: .\cookbook\forms.py:513
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"include all related recipes."
|
||||
msgstr "将膳食计划(手动或自动)添加到购物清单时,包括所有相关食谱。"
|
||||
|
||||
#: .\cookbook\forms.py:497
|
||||
#: .\cookbook\forms.py:514
|
||||
msgid ""
|
||||
"When adding a meal plan to the shopping list (manually or automatically), "
|
||||
"exclude ingredients that are on hand."
|
||||
msgstr "将膳食计划(手动或自动)添加到购物清单时,排除现有食材。"
|
||||
|
||||
#: .\cookbook\forms.py:498
|
||||
#: .\cookbook\forms.py:515
|
||||
msgid "Default number of hours to delay a shopping list entry."
|
||||
msgstr "延迟购物清单条目的默认小时数。"
|
||||
|
||||
#: .\cookbook\forms.py:499
|
||||
#: .\cookbook\forms.py:516
|
||||
msgid "Filter shopping list to only include supermarket categories."
|
||||
msgstr "筛选购物清单仅包含超市分类。"
|
||||
|
||||
#: .\cookbook\forms.py:500
|
||||
#: .\cookbook\forms.py:517
|
||||
msgid "Days of recent shopping list entries to display."
|
||||
msgstr "显示最近几天的购物清单列表。"
|
||||
|
||||
#: .\cookbook\forms.py:501
|
||||
#: .\cookbook\forms.py:518
|
||||
msgid "Mark food 'On Hand' when checked off shopping list."
|
||||
msgstr "在核对购物清单时,将食物标记为“入手”。"
|
||||
|
||||
#: .\cookbook\forms.py:502
|
||||
#: .\cookbook\forms.py:519
|
||||
msgid "Delimiter to use for CSV exports."
|
||||
msgstr "用于 CSV 导出的分隔符。"
|
||||
|
||||
#: .\cookbook\forms.py:503
|
||||
#: .\cookbook\forms.py:520
|
||||
msgid "Prefix to add when copying list to the clipboard."
|
||||
msgstr "将清单复制到剪贴板时要添加的前缀。"
|
||||
|
||||
#: .\cookbook\forms.py:507
|
||||
#: .\cookbook\forms.py:524
|
||||
msgid "Share Shopping List"
|
||||
msgstr "分享购物清单"
|
||||
|
||||
#: .\cookbook\forms.py:508
|
||||
#: .\cookbook\forms.py:525
|
||||
msgid "Autosync"
|
||||
msgstr "自动同步"
|
||||
|
||||
#: .\cookbook\forms.py:509
|
||||
#: .\cookbook\forms.py:526
|
||||
msgid "Auto Add Meal Plan"
|
||||
msgstr "自动添加膳食计划"
|
||||
|
||||
#: .\cookbook\forms.py:510
|
||||
#: .\cookbook\forms.py:527
|
||||
msgid "Exclude On Hand"
|
||||
msgstr "排除现有"
|
||||
|
||||
#: .\cookbook\forms.py:511
|
||||
#: .\cookbook\forms.py:528
|
||||
msgid "Include Related"
|
||||
msgstr "包括相关"
|
||||
|
||||
#: .\cookbook\forms.py:512
|
||||
#: .\cookbook\forms.py:529
|
||||
msgid "Default Delay Hours"
|
||||
msgstr "默认延迟时间"
|
||||
|
||||
#: .\cookbook\forms.py:513
|
||||
#: .\cookbook\forms.py:530
|
||||
msgid "Filter to Supermarket"
|
||||
msgstr "按超市筛选"
|
||||
|
||||
#: .\cookbook\forms.py:514
|
||||
#: .\cookbook\forms.py:531
|
||||
msgid "Recent Days"
|
||||
msgstr "最近几天"
|
||||
|
||||
#: .\cookbook\forms.py:515
|
||||
#: .\cookbook\forms.py:532
|
||||
msgid "CSV Delimiter"
|
||||
msgstr "CSV 分隔符"
|
||||
|
||||
#: .\cookbook\forms.py:516
|
||||
#: .\cookbook\forms.py:533
|
||||
msgid "List Prefix"
|
||||
msgstr "清单前缀"
|
||||
|
||||
#: .\cookbook\forms.py:517
|
||||
#: .\cookbook\forms.py:534
|
||||
msgid "Auto On Hand"
|
||||
msgstr "自动入手"
|
||||
|
||||
#: .\cookbook\forms.py:527
|
||||
#: .\cookbook\forms.py:544
|
||||
msgid "Reset Food Inheritance"
|
||||
msgstr "重置食物材料"
|
||||
|
||||
#: .\cookbook\forms.py:528
|
||||
#: .\cookbook\forms.py:545
|
||||
msgid "Reset all food to inherit the fields configured."
|
||||
msgstr "重置所有食物以继承配置的字段。"
|
||||
|
||||
#: .\cookbook\forms.py:540
|
||||
#: .\cookbook\forms.py:557
|
||||
msgid "Fields on food that should be inherited by default."
|
||||
msgstr "默认情况下应继承的食物上的字段。"
|
||||
|
||||
#: .\cookbook\forms.py:541
|
||||
#: .\cookbook\forms.py:558
|
||||
msgid "Show recipe counts on search filters"
|
||||
msgstr "显示搜索筛选器上的食谱计数"
|
||||
|
||||
#: .\cookbook\forms.py:542
|
||||
#: .\cookbook\forms.py:559
|
||||
msgid "Use the plural form for units and food inside this space."
|
||||
msgstr "在此空间内使用复数形式表示单位和食物。"
|
||||
|
||||
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
38
cookbook/migrations/0190_auto_20230525_1506.py
Normal file
38
cookbook/migrations/0190_auto_20230525_1506.py
Normal 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)
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
18
cookbook/migrations/0193_space_internal_note.py
Normal file
18
cookbook/migrations/0193_space_internal_note.py
Normal 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),
|
||||
),
|
||||
]
|
||||
@@ -82,10 +82,13 @@ class TreeManager(MP_NodeManager):
|
||||
# model.Manager get_or_create() is not compatible with MP_Tree
|
||||
def get_or_create(self, *args, **kwargs):
|
||||
kwargs['name'] = kwargs['name'].strip()
|
||||
|
||||
if hasattr(self, 'space'):
|
||||
if obj := self.filter(name__iexact=kwargs['name'], space=kwargs['space']).first():
|
||||
return obj, False
|
||||
else:
|
||||
if obj := self.filter(name__iexact=kwargs['name']).first():
|
||||
return obj, False
|
||||
|
||||
with scopes_disabled():
|
||||
try:
|
||||
defaults = kwargs.pop('defaults', None)
|
||||
@@ -267,6 +270,8 @@ class Space(ExportModelOperationsMixin('space'), models.Model):
|
||||
food_inherit = models.ManyToManyField(FoodInheritField, blank=True)
|
||||
show_facet_count = models.BooleanField(default=False)
|
||||
|
||||
internal_note = models.TextField(blank=True, null=True)
|
||||
|
||||
def safe_delete(self):
|
||||
"""
|
||||
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):
|
||||
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
|
||||
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)
|
||||
objects = ScopedManager(space='space')
|
||||
@@ -463,7 +469,8 @@ class SupermarketCategory(models.Model, PermissionModelMixin):
|
||||
|
||||
class Meta:
|
||||
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)])
|
||||
description = models.TextField(blank=True, null=True)
|
||||
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)
|
||||
objects = ScopedManager(space='space')
|
||||
@@ -480,7 +488,8 @@ class Supermarket(models.Model, PermissionModelMixin):
|
||||
|
||||
class Meta:
|
||||
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'
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['supermarket', 'category'], name='unique_sm_category_relation')
|
||||
]
|
||||
ordering = ('order',)
|
||||
|
||||
|
||||
@@ -534,6 +546,8 @@ class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixi
|
||||
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
|
||||
plural_name = models.CharField(max_length=128, null=True, blank=True, default=None)
|
||||
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)
|
||||
objects = ScopedManager(space='space')
|
||||
@@ -543,7 +557,8 @@ class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixi
|
||||
|
||||
class Meta:
|
||||
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)
|
||||
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)
|
||||
objects = ScopedManager(space='space', _manager_class=TreeManager)
|
||||
|
||||
@@ -642,7 +666,8 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
||||
|
||||
class Meta:
|
||||
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 = (
|
||||
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):
|
||||
# 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)
|
||||
@@ -663,8 +714,6 @@ class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, Permiss
|
||||
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)
|
||||
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
objects = ScopedManager(space='space')
|
||||
|
||||
@@ -720,6 +769,64 @@ class Step(ExportModelOperationsMixin('step'), models.Model, PermissionModelMixi
|
||||
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):
|
||||
fats = models.DecimalField(default=0, decimal_places=16, max_digits=32)
|
||||
carbohydrates = models.DecimalField(
|
||||
@@ -736,14 +843,6 @@ class NutritionInformation(models.Model, PermissionModelMixin):
|
||||
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)):
|
||||
def get_queryset(self):
|
||||
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)
|
||||
internal = models.BooleanField(default=False)
|
||||
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)
|
||||
private = models.BooleanField(default=False)
|
||||
shared = models.ManyToManyField(User, blank=True, related_name='recipe_shared_with')
|
||||
|
||||
@@ -7,6 +7,7 @@ from html import escape
|
||||
from smtplib import SMTPException
|
||||
|
||||
from django.contrib.auth.models import Group, User, AnonymousUser
|
||||
from django.core.cache import caches
|
||||
from django.core.mail import send_mail
|
||||
from django.db.models import Avg, Q, QuerySet, Sum
|
||||
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.HelperFunctions import str2bool
|
||||
from cookbook.helper.property_helper import FoodPropertyHelper
|
||||
from cookbook.helper.permission_helper import above_space_limit
|
||||
from cookbook.helper.shopping_helper import RecipeShoppingEditor
|
||||
from cookbook.helper.unit_conversion_helper import UnitConversionHelper
|
||||
from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, CustomFilter,
|
||||
ExportLog, Food, FoodInheritField, ImportLog, Ingredient, InviteLink,
|
||||
Keyword, MealPlan, MealType, NutritionInformation, Recipe, RecipeBook,
|
||||
RecipeBookEntry, RecipeImport, ShareLink, ShoppingList,
|
||||
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
|
||||
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 recipes.settings import AWS_ENABLED, MEDIA_URL
|
||||
|
||||
@@ -102,15 +106,21 @@ class CustomOnHandField(serializers.Field):
|
||||
return instance
|
||||
|
||||
def to_representation(self, obj):
|
||||
shared_users = None
|
||||
if request := self.context.get('request', None):
|
||||
shared_users = getattr(request, '_shared_users', None)
|
||||
if shared_users is None:
|
||||
if not self.context["request"].user.is_authenticated:
|
||||
return []
|
||||
shared_users = []
|
||||
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:
|
||||
shared_users = [x.id for x in list(self.context['request'].user.get_shopping_share())] + [
|
||||
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
|
||||
shared_users = []
|
||||
pass
|
||||
return obj.onhand_users.filter(id__in=shared_users).exists()
|
||||
|
||||
def to_internal_value(self, data):
|
||||
@@ -276,10 +286,13 @@ class SpaceSerializer(WritableNestedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
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',
|
||||
'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):
|
||||
@@ -440,7 +453,8 @@ class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
|
||||
return unit
|
||||
|
||||
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
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
@@ -451,7 +465,7 @@ class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
|
||||
|
||||
class Meta:
|
||||
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')
|
||||
|
||||
|
||||
@@ -484,7 +498,37 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
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):
|
||||
@@ -523,19 +567,29 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
|
||||
substitute_onhand = serializers.SerializerMethodField('get_substitute_onhand')
|
||||
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'
|
||||
images = ['recipe__image']
|
||||
|
||||
def get_substitute_onhand(self, obj):
|
||||
shared_users = None
|
||||
if request := self.context.get('request', None):
|
||||
shared_users = getattr(request, '_shared_users', None)
|
||||
if shared_users is None:
|
||||
if not self.context["request"].user.is_authenticated:
|
||||
return []
|
||||
shared_users = []
|
||||
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:
|
||||
shared_users = [x.id for x in list(self.context['request'].user.get_shopping_share())] + [
|
||||
self.context['request'].user.id]
|
||||
except AttributeError:
|
||||
shared_users = []
|
||||
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
|
||||
pass
|
||||
filter = Q(id__in=obj.substitute.all())
|
||||
if obj.substitute_siblings:
|
||||
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
|
||||
|
||||
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):
|
||||
plural_name = plural_name.strip()
|
||||
@@ -579,7 +633,11 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
|
||||
else:
|
||||
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
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
@@ -606,9 +664,11 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
|
||||
class Meta:
|
||||
model = Food
|
||||
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',
|
||||
'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')
|
||||
|
||||
@@ -618,9 +678,24 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer):
|
||||
unit = UnitSerializer(allow_null=True)
|
||||
used_in_recipes = serializers.SerializerMethodField('get_used_in_recipes')
|
||||
amount = CustomDecimalField()
|
||||
conversions = serializers.SerializerMethodField('get_conversions')
|
||||
|
||||
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):
|
||||
validated_data['space'] = self.context['request'].space
|
||||
@@ -633,10 +708,11 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer):
|
||||
class Meta:
|
||||
model = Ingredient
|
||||
fields = (
|
||||
'id', 'food', 'unit', 'amount', 'note', 'order',
|
||||
'id', 'food', 'unit', 'amount', 'conversions', 'note', 'order',
|
||||
'is_header', 'no_amount', 'original_text', 'used_in_recipes',
|
||||
'always_use_plural_unit', 'always_use_plural_food',
|
||||
)
|
||||
read_only_fields = ['conversions', ]
|
||||
|
||||
|
||||
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):
|
||||
carbohydrates = CustomDecimalField()
|
||||
fats = CustomDecimalField()
|
||||
@@ -738,21 +838,28 @@ class RecipeOverviewSerializer(RecipeBaseSerializer):
|
||||
|
||||
class RecipeSerializer(RecipeBaseSerializer):
|
||||
nutrition = NutritionInformationSerializer(allow_null=True, required=False)
|
||||
properties = PropertySerializer(many=True, required=False)
|
||||
steps = StepSerializer(many=True)
|
||||
keywords = KeywordSerializer(many=True)
|
||||
shared = UserSerializer(many=True, required=False)
|
||||
rating = CustomDecimalField(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:
|
||||
model = Recipe
|
||||
fields = (
|
||||
'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time',
|
||||
'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',
|
||||
)
|
||||
read_only_fields = ['image', 'created_by', 'created_at']
|
||||
read_only_fields = ['image', 'created_by', 'created_at', 'food_properties']
|
||||
|
||||
def validate(self, data):
|
||||
above_limit, msg = above_space_limit(self.context['request'].space)
|
||||
@@ -1089,13 +1196,19 @@ class InviteLinkSerializer(WritableNestedModelSerializer):
|
||||
|
||||
if obj.email:
|
||||
try:
|
||||
if InviteLink.objects.filter(space=self.context['request'].space, created_at__gte=datetime.now() - timedelta(hours=4)).count() < 20:
|
||||
message = _('Hello') + '!\n\n' + _('You have been invited by ') + escape(self.context['request'].user.get_user_display_name())
|
||||
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'
|
||||
if InviteLink.objects.filter(space=self.context['request'].space,
|
||||
created_at__gte=datetime.now() - timedelta(hours=4)).count() < 20:
|
||||
message = _('Hello') + '!\n\n' + _('You have been invited by ') + escape(
|
||||
self.context['request'].user.get_user_display_name())
|
||||
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 += _('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(
|
||||
_('Tandoor Recipes Invite'),
|
||||
@@ -1204,7 +1317,8 @@ class IngredientExportSerializer(WritableNestedModelSerializer):
|
||||
|
||||
class Meta:
|
||||
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):
|
||||
|
||||
@@ -4,15 +4,17 @@ from functools import wraps
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.postgres.search import SearchVector
|
||||
from django.core.cache import caches
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils import translation
|
||||
from django_scopes import scope, scopes_disabled
|
||||
|
||||
from cookbook.helper.cache_helper import CacheHelper
|
||||
from cookbook.helper.shopping_helper import RecipeShoppingEditor
|
||||
from cookbook.managers import DICTIONARY
|
||||
from cookbook.models import (Food, FoodInheritField, Ingredient, MealPlan, Recipe,
|
||||
ShoppingListEntry, Step, UserPreference, SearchPreference, SearchFields)
|
||||
ShoppingListEntry, Step, UserPreference, SearchPreference, SearchFields, Unit, PropertyType)
|
||||
|
||||
SQLITE = True
|
||||
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")
|
||||
except AttributeError:
|
||||
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)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
{% endblock %}</title>
|
||||
<meta charset="utf-8">
|
||||
<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' %}">
|
||||
@@ -117,6 +118,10 @@
|
||||
<a class="nav-link" href="{% url 'view_books' %}"><i
|
||||
class="fas fa-fw fa-book-open"></i> {% trans 'Books' %}</a>
|
||||
</li>
|
||||
{% plugin_main_nav_templates as plugin_main_nav_templates %}
|
||||
{% for pn in plugin_main_nav_templates %}
|
||||
{% include pn %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<ul class="navbar-nav ml-auto">
|
||||
@@ -269,6 +274,33 @@
|
||||
</div>
|
||||
</a>
|
||||
</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>
|
||||
</li>
|
||||
@@ -322,6 +354,12 @@
|
||||
<a class="dropdown-item" href="{% url 'view_space_overview' %}"><i
|
||||
class="fas fa-list"></i> {% trans 'Overview' %}</a>
|
||||
{% 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>
|
||||
<a class="dropdown-item" href="{% url 'docs_markdown' %}"><i
|
||||
class="fab fa-markdown fa-fw"></i> {% trans 'Markdown Guide' %}</a>
|
||||
@@ -348,6 +386,7 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
{% message_of_the_day request as message_of_the_day %}
|
||||
{% if message_of_the_day %}
|
||||
<div class="bg-info" style=" width: 100%; text-align: center!important; color: #ffffff; padding: 8px">
|
||||
|
||||
@@ -16,7 +16,7 @@ from cookbook.helper.mdx_attributes import MarkdownFormatExtension
|
||||
from cookbook.helper.mdx_urlize import UrlizeExtension
|
||||
from cookbook.models import Space, get_model_name
|
||||
from recipes import settings
|
||||
from recipes.settings import STATIC_URL
|
||||
from recipes.settings import STATIC_URL, PLUGINS
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@@ -132,6 +132,22 @@ def is_debug():
|
||||
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>"
|
||||
|
||||
@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
|
||||
def bookmarklet(request):
|
||||
|
||||
@@ -2,6 +2,7 @@ import json
|
||||
|
||||
import pytest
|
||||
from django.contrib import auth
|
||||
from django.core.cache import caches
|
||||
from django.urls import reverse
|
||||
from django_scopes import scope, scopes_disabled
|
||||
from pytest_factoryboy import LazyFixture, register
|
||||
@@ -28,7 +29,6 @@ if (Food.node_order_by):
|
||||
else:
|
||||
node_location = 'last-child'
|
||||
|
||||
|
||||
register(FoodFactory, 'obj_1', space=LazyFixture('space_1'))
|
||||
register(FoodFactory, 'obj_2', space=LazyFixture('space_1'))
|
||||
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(child, field) == new_val) == 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)
|
||||
|
||||
|
||||
def test_onhand(obj_1, u1_s1, u2_s1):
|
||||
assert json.loads(u1_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)[
|
||||
'food_onhand'] == False
|
||||
assert json.loads(u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)[
|
||||
'food_onhand'] == False
|
||||
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)['food_onhand'] is False
|
||||
assert json.loads(u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)['food_onhand'] is False
|
||||
|
||||
u1_s1.patch(
|
||||
reverse(
|
||||
@@ -627,13 +626,12 @@ def test_onhand(obj_1, u1_s1, u2_s1):
|
||||
{'food_onhand': True},
|
||||
content_type='application/json'
|
||||
)
|
||||
assert json.loads(u1_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)[
|
||||
'food_onhand'] == True
|
||||
assert json.loads(u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)[
|
||||
'food_onhand'] == False
|
||||
assert json.loads(u1_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)['food_onhand'] is True
|
||||
assert json.loads(u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)['food_onhand'] is False
|
||||
|
||||
user1 = auth.get_user(u1_s1)
|
||||
user2 = auth.get_user(u2_s1)
|
||||
user1.userpreference.shopping_share.add(user2)
|
||||
assert json.loads(u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)[
|
||||
'food_onhand'] == True
|
||||
caches['default'].set(f'shopping_shared_users_{space_1.id}_{user2.id}', None)
|
||||
|
||||
assert json.loads(u2_s1.get(reverse(DETAIL_URL, args={obj_1.id})).content)['food_onhand'] is True
|
||||
|
||||
116
cookbook/tests/api/test_api_property.py
Normal file
116
cookbook/tests/api/test_api_property.py
Normal 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
|
||||
132
cookbook/tests/api/test_api_property_type.py
Normal file
132
cookbook/tests/api/test_api_property_type.py
Normal 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
|
||||
163
cookbook/tests/api/test_api_unit_conversion.py
Normal file
163
cookbook/tests/api/test_api_unit_conversion.py
Normal 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
|
||||
@@ -13,6 +13,8 @@ from cookbook.tests.factories import SpaceFactory, UserFactory
|
||||
|
||||
register(SpaceFactory, 'space_1')
|
||||
register(SpaceFactory, 'space_2')
|
||||
|
||||
|
||||
# register(FoodFactory, space=LazyFixture('space_2'))
|
||||
# TODO refactor clients to be factories
|
||||
|
||||
@@ -169,7 +171,6 @@ def dict_compare(d1, d2, details=False):
|
||||
|
||||
|
||||
def transpose(text, number=2):
|
||||
|
||||
# select random token
|
||||
tokens = text.split()
|
||||
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
|
||||
|
||||
|
||||
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 -----------------------
|
||||
# maybe better with factories but this is very explict so ...
|
||||
|
||||
|
||||
129
cookbook/tests/other/test_food_property.py
Normal file
129
cookbook/tests/other/test_food_property.py
Normal 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
|
||||
|
||||
|
||||
187
cookbook/tests/other/test_unit_conversion.py
Normal file
187
cookbook/tests/other/test_unit_conversion.py
Normal 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)
|
||||
@@ -6,17 +6,23 @@ from rest_framework import permissions, routers
|
||||
from rest_framework.schemas import get_schema_view
|
||||
|
||||
from cookbook.helper import dal
|
||||
from recipes.settings import DEBUG
|
||||
from recipes.settings import DEBUG, PLUGINS
|
||||
from recipes.version import VERSION_NUMBER
|
||||
|
||||
from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, MealPlan, Recipe,
|
||||
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, Step, Storage,
|
||||
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.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'bookmarklet-import', api.BookmarkletImportViewSet)
|
||||
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-book', api.RecipeBookViewSet)
|
||||
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-entry', api.ShoppingListEntryViewSet)
|
||||
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'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 = [
|
||||
path('', views.index, name='index'),
|
||||
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/download-file/<int:file_id>/', api.download_file, name='api_download_file'),
|
||||
|
||||
|
||||
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
|
||||
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-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
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'),
|
||||
|
||||
@@ -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:
|
||||
py_name = get_model_name(m)
|
||||
url_name = py_name.replace('_', '-')
|
||||
|
||||
@@ -19,6 +19,7 @@ from annoying.functions import get_object_or_None
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.contrib.postgres.search import TrigramSimilarity
|
||||
from django.core.cache import caches
|
||||
from django.core.exceptions import FieldError, ValidationError
|
||||
from django.core.files import File
|
||||
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.response import Response
|
||||
from rest_framework.throttling import AnonRateThrottle
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.viewsets import ViewSetMixin
|
||||
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.image_processing import handle_image
|
||||
from cookbook.helper.ingredient_parser import IngredientParser
|
||||
from cookbook.helper.open_data_importer import OpenDataImporter
|
||||
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsOwner,
|
||||
CustomIsOwnerReadOnly, CustomIsShared,
|
||||
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_url_import import get_from_youtube_scraper, get_images_from_soup, clean_dict
|
||||
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,
|
||||
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
|
||||
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.local import Local
|
||||
from cookbook.provider.nextcloud import Nextcloud
|
||||
@@ -88,7 +93,8 @@ from cookbook.serializer import (AutomationSerializer, BookmarkletImportListSeri
|
||||
SupermarketCategorySerializer, SupermarketSerializer,
|
||||
SyncLogSerializer, SyncSerializer, UnitSerializer,
|
||||
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 recipes import settings
|
||||
|
||||
@@ -166,14 +172,17 @@ class FuzzyFilterMixin(ViewSetMixin, ExtendedRecipeMixin):
|
||||
def get_queryset(self):
|
||||
self.queryset = self.queryset.filter(space=self.request.space).order_by(Lower('name').asc())
|
||||
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
|
||||
self.request.user.searchpreference.trigram.values_list(
|
||||
'field', flat=True)])
|
||||
else:
|
||||
fuzzy = True
|
||||
|
||||
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 any([self.model.__name__.lower() in x for x in
|
||||
self.request.user.searchpreference.unaccent.values_list('field', flat=True)]):
|
||||
if fuzzy and (settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2',
|
||||
'django.db.backends.postgresql']):
|
||||
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))
|
||||
else:
|
||||
self.queryset = self.queryset.annotate(trigram=TrigramSimilarity('name', query))
|
||||
@@ -181,13 +190,12 @@ class FuzzyFilterMixin(ViewSetMixin, ExtendedRecipeMixin):
|
||||
else:
|
||||
# TODO have this check unaccent search settings or other search preferences?
|
||||
filter = Q(name__icontains=query)
|
||||
if any([self.model.__name__.lower() in x for x in
|
||||
self.request.user.searchpreference.unaccent.values_list('field', flat=True)]):
|
||||
if self.request.user.is_authenticated:
|
||||
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)
|
||||
|
||||
self.queryset = (
|
||||
self.queryset
|
||||
.annotate(starts=Case(When(name__istartswith=query, then=(Value(100))),
|
||||
self.queryset.annotate(starts=Case(When(name__istartswith=query, then=(Value(100))),
|
||||
default=Value(0))) # put exact matches at the top of the result set
|
||||
.filter(filter).order_by('-starts', Lower('name').asc())
|
||||
)
|
||||
@@ -243,6 +251,9 @@ class MergeMixin(ViewSetMixin):
|
||||
isTree = False
|
||||
|
||||
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)]:
|
||||
linkManager = getattr(source, link.get_accessor_name())
|
||||
related = linkManager.all()
|
||||
@@ -272,6 +283,7 @@ class MergeMixin(ViewSetMixin):
|
||||
source.delete()
|
||||
return Response(content, status=status.HTTP_200_OK)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
content = {'error': True,
|
||||
'msg': _(f'An error occurred attempting to merge {source.name} with {target.name}')}
|
||||
return Response(content, status=status.HTTP_400_BAD_REQUEST)
|
||||
@@ -522,8 +534,20 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
|
||||
pagination_class = DefaultPagination
|
||||
|
||||
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]
|
||||
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()
|
||||
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 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()
|
||||
|
||||
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
|
||||
in list(self.request.GET)}
|
||||
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
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
@@ -921,6 +970,41 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
||||
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):
|
||||
queryset = ShoppingListRecipe.objects
|
||||
serializer_class = ShoppingListRecipeSerializer
|
||||
@@ -1122,10 +1206,13 @@ class CustomAuthToken(ObtainAuthToken):
|
||||
context={'request': request})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
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
|
||||
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({
|
||||
'id': access_token.id,
|
||||
'token': access_token.token,
|
||||
@@ -1153,7 +1240,8 @@ def recipe_from_source(request):
|
||||
serializer = RecipeFromSourceSerializer(data=request.data)
|
||||
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['data'] = bookmarklet.html
|
||||
bookmarklet.delete()
|
||||
@@ -1175,13 +1263,22 @@ def recipe_from_source(request):
|
||||
# 'recipe_html': '',
|
||||
'recipe_images': [],
|
||||
}, 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):
|
||||
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()
|
||||
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):
|
||||
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')
|
||||
serialized_recipe = RecipeExportSerializer(data=recipe_json, context={'request': request})
|
||||
if serialized_recipe.is_valid():
|
||||
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}')
|
||||
recipe.save()
|
||||
return Response({
|
||||
@@ -1323,11 +1420,44 @@ def import_files(request):
|
||||
|
||||
return Response({'import_id': il.pk}, status=status.HTTP_200_OK)
|
||||
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:
|
||||
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):
|
||||
if recipe.storage.method == Storage.DROPBOX:
|
||||
return Dropbox
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import os
|
||||
import re
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from django.conf import settings
|
||||
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.forms import PasswordChangeForm
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.password_validation import validate_password
|
||||
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.translation import gettext as _
|
||||
from django_scopes import scopes_disabled
|
||||
from oauth2_provider.models import AccessToken
|
||||
|
||||
from cookbook.forms import (CommentForm, Recipe, SearchPreferenceForm, ShoppingPreferenceForm,
|
||||
SpaceCreateForm, SpaceJoinForm, User,
|
||||
UserCreateForm, UserNameForm, UserPreference, UserPreferenceForm)
|
||||
from cookbook.forms import (CommentForm, Recipe, SearchPreferenceForm, SpaceCreateForm, SpaceJoinForm, User,
|
||||
UserCreateForm, UserPreference)
|
||||
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,
|
||||
Space, ViewLog, UserSpace)
|
||||
|
||||
@@ -239,6 +239,9 @@ RecetteTek exports are `.rtk` files which can simply be uploaded to tandoor to i
|
||||
## Rezeptsuite.de
|
||||
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 provides multiple export formats but only the `MelaRecipes` format can export the complete collection.
|
||||
|
||||
@@ -40,6 +40,7 @@ nav:
|
||||
- Templating: features/templating.md
|
||||
- Shopping: features/shopping.md
|
||||
- Authentication: features/authentication.md
|
||||
- Automation: features/automation.md
|
||||
- Storages and Sync: features/external_recipes.md
|
||||
- Import/Export: features/import_export.md
|
||||
- System:
|
||||
|
||||
@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/2.0/ref/settings/
|
||||
"""
|
||||
import ast
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
@@ -80,6 +81,8 @@ DJANGO_TABLES2_PAGE_RANGE = 8
|
||||
HCAPTCHA_SITEKEY = os.getenv('HCAPTCHA_SITEKEY', '')
|
||||
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_LIMIT = int(os.getenv('SHARING_LIMIT', 0))
|
||||
|
||||
@@ -144,6 +147,9 @@ try:
|
||||
'base_path': os.path.join(BASE_DIR, 'recipes', 'plugins', d),
|
||||
'base_url': plugin_class.base_url,
|
||||
'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)
|
||||
except Exception:
|
||||
@@ -412,7 +418,7 @@ for p in PLUGINS:
|
||||
if p['bundle_name'] != '':
|
||||
WEBPACK_LOADER[p['bundle_name']] = {
|
||||
'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'),
|
||||
'POLL_INTERVAL': 0.1,
|
||||
'TIMEOUT': None,
|
||||
@@ -445,6 +451,7 @@ LANGUAGES = [
|
||||
('hu', _('Hungarian')),
|
||||
('it', _('Italian')),
|
||||
('lv', _('Latvian')),
|
||||
('nb', _('Norwegian ')),
|
||||
('pl', _('Polish')),
|
||||
('ru', _('Russian')),
|
||||
('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')
|
||||
ACCOUNT_EMAIL_SUBJECT_PREFIX = os.getenv(
|
||||
'ACCOUNT_EMAIL_SUBJECT_PREFIX', '[Tandoor Recipes] ') # allauth sender prefix
|
||||
|
||||
mimetypes.add_type("text/javascript", ".js", True)
|
||||
@@ -1,5 +1,5 @@
|
||||
Django==4.1.7
|
||||
cryptography==39.0.1
|
||||
Django==4.1.9
|
||||
cryptography==41.0.0
|
||||
django-annoying==0.10.6
|
||||
django-autocomplete-light==3.9.4
|
||||
django-cleanup==7.0.0
|
||||
@@ -17,7 +17,7 @@ Markdown==3.4.3
|
||||
Pillow==9.4.0
|
||||
psycopg2-binary==2.9.5
|
||||
python-dotenv==0.21.0
|
||||
requests==2.28.2
|
||||
requests==2.31.0
|
||||
six==1.16.0
|
||||
webdavclient3==3.14.6
|
||||
whitenoise==6.2.0
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image and misc properties -->
|
||||
<!-- Image and misc -->
|
||||
<div class="row pt-2">
|
||||
<div class="col-md-6" style="max-height: 50vh; min-height: 30vh">
|
||||
<input id="id_file_upload" ref="file_upload" type="file" hidden
|
||||
@@ -99,65 +99,53 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nutrition -->
|
||||
<div class="row pt-2">
|
||||
<div class="col-md-12">
|
||||
<div class="card border-grey">
|
||||
<div class="card-header" style="display: table">
|
||||
<div class="row">
|
||||
<div class="col-md-9 d-table">
|
||||
<h5 class="d-table-cell align-middle">{{ $t("Nutrition") }}</h5>
|
||||
<div class="card mt-2 mb-2">
|
||||
<div class="card-body pr-2 pl-2 pr-md-5 pl-md-5 pt-3 pb-3">
|
||||
<h6>{{ $t('Properties') }} <small class="text-muted"> {{$t('per_serving')}}</small></h6>
|
||||
|
||||
<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 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>
|
||||
|
||||
<b-collapse id="id_nutrition_collapse" class="mt-2" v-model="nutrition_visible">
|
||||
<div class="card-body" v-if="recipe.nutrition !== null">
|
||||
<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>
|
||||
<div class="row pt-2">
|
||||
<div class="col-md-12">
|
||||
|
||||
<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-button squared block v-b-toggle.additional_collapse class="text-left"
|
||||
variant="outline-primary">{{ $t("additional_options") }}
|
||||
@@ -1121,6 +1109,14 @@ export default {
|
||||
let new_keyword = {label: tag, name: tag}
|
||||
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) {
|
||||
let apiFactory = new ApiApiFactory()
|
||||
|
||||
|
||||
@@ -1,158 +1,6 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<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">
|
||||
<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>
|
||||
<recipe-view-component></recipe-view-component>
|
||||
|
||||
<bottom-navigation-bar></bottom-navigation-bar>
|
||||
</div>
|
||||
@@ -163,192 +11,28 @@ 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 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
|
||||
import RecipeViewComponent from "@/components/RecipeViewComponent.vue";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
export default {
|
||||
name: "RecipeView",
|
||||
mixins: [ResolveUrlMixin, ToastMixin],
|
||||
mixins: [],
|
||||
components: {
|
||||
ImportTandoor,
|
||||
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)
|
||||
},
|
||||
RecipeViewComponent
|
||||
},
|
||||
computed: {},
|
||||
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
|
||||
},
|
||||
return {}
|
||||
},
|
||||
|
||||
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()
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#app > div > div {
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -164,6 +164,15 @@
|
||||
</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="col col-12">
|
||||
<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 axios from "axios";
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
import OpenDataImportComponent from "@/components/OpenDataImportComponent.vue";
|
||||
|
||||
Vue.use(VueClipboard)
|
||||
|
||||
@@ -206,7 +216,7 @@ Vue.use(BootstrapVue)
|
||||
export default {
|
||||
name: "SpaceManageView",
|
||||
mixins: [ResolveUrlMixin, ToastMixin, ApiMixin],
|
||||
components: {GenericMultiselect, GenericModalForm},
|
||||
components: {GenericMultiselect, GenericModalForm, OpenDataImportComponent},
|
||||
data() {
|
||||
return {
|
||||
ACTIVE_SPACE_ID: window.ACTIVE_SPACE_ID,
|
||||
|
||||
@@ -1,104 +1,42 @@
|
||||
<template>
|
||||
|
||||
<div id="app">
|
||||
<div class="row" v-if="food">
|
||||
<div class="col-12">
|
||||
<h2>{{ food.name }}</h2>
|
||||
</div>
|
||||
|
||||
<beta-warning></beta-warning>
|
||||
|
||||
<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 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>
|
||||
|
||||
|
||||
@@ -107,10 +45,9 @@ 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";
|
||||
import {ApiMixin, resolveDjangoUrl, StandardToasts} from "@/utils/utils";
|
||||
import axios from "axios";
|
||||
import BetaWarning from "@/components/BetaWarning.vue";
|
||||
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
@@ -119,33 +56,39 @@ Vue.use(BootstrapVue)
|
||||
export default {
|
||||
name: "TestView",
|
||||
mixins: [ApiMixin],
|
||||
components: {
|
||||
GenericMultiselect
|
||||
},
|
||||
components: {BetaWarning},
|
||||
data() {
|
||||
return {
|
||||
food: undefined,
|
||||
|
||||
metadata: undefined,
|
||||
selected_version: undefined,
|
||||
update_existing: true,
|
||||
use_metric: true,
|
||||
import_count: undefined,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
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: {
|
||||
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)
|
||||
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_UPDATE, err)
|
||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE, err)
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
155
vue/src/apps/TestView/TestViewBackup.vue
Normal file
155
vue/src/apps/TestView/TestViewBackup.vue
Normal 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>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 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="flex-column" v-if="show_button_1">
|
||||
<slot name="button_1">
|
||||
|
||||
412
vue/src/components/FoodEditor.vue
Normal file
412
vue/src/components/FoodEditor.vue
Normal 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>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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> {{
|
||||
formatDate(recipe.last_cooked)
|
||||
}}</b-badge>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<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>
|
||||
<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}`"/>
|
||||
@@ -9,7 +13,7 @@
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
@@ -29,6 +33,8 @@
|
||||
</div>
|
||||
</template>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -54,7 +60,18 @@ import NumberInput from "@/components/Modals/NumberInput.vue";
|
||||
|
||||
export default {
|
||||
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],
|
||||
props: {
|
||||
model: {required: true, type: Object},
|
||||
@@ -77,6 +94,7 @@ export default {
|
||||
},
|
||||
},
|
||||
show: {required: true, type: Boolean, default: false},
|
||||
models: {required: false, type: Function, default: null}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -92,6 +110,10 @@ export default {
|
||||
mounted() {
|
||||
this.id = Math.random()
|
||||
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: {
|
||||
advancedForm() {
|
||||
@@ -111,6 +133,15 @@ export default {
|
||||
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: {
|
||||
show: function () {
|
||||
@@ -153,6 +184,7 @@ export default {
|
||||
if (this.dirty) {
|
||||
this.dirty = false
|
||||
this.$emit("finish-action", "cancel")
|
||||
this.$emit("hidden")
|
||||
}
|
||||
},
|
||||
storeValue: function (field, value) {
|
||||
@@ -250,7 +282,10 @@ export default {
|
||||
target: this.form_data.target.id,
|
||||
})
|
||||
.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)
|
||||
})
|
||||
.catch((err) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<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>
|
||||
<small v-if="subtitle" class="text-muted">{{ subtitle }}</small>
|
||||
</b-form-group>
|
||||
@@ -18,6 +18,7 @@ export default {
|
||||
placeholder: { type: String, default: "You Should Add Placeholder Text" },
|
||||
help: { type: String, default: undefined },
|
||||
subtitle: { type: String, default: undefined },
|
||||
disabled: { type: Boolean, default: false }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
94
vue/src/components/OpenDataImportComponent.vue
Normal file
94
vue/src/components/OpenDataImportComponent.vue
Normal 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>
|
||||
|
||||
195
vue/src/components/PropertyViewComponent.vue
Normal file
195
vue/src/components/PropertyViewComponent.vue
Normal 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>
|
||||
@@ -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%"
|
||||
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
|
||||
class="fa fa-clock"></i> {{ working_time }}
|
||||
</b-badge>
|
||||
<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 }}
|
||||
</b-badge>
|
||||
</div>
|
||||
@@ -58,7 +58,7 @@
|
||||
<div class="justify-content-end">
|
||||
<recipe-context-menu :recipe="recipe" class="justify-content-end float-right align-items-end pr-0"
|
||||
:disabled_options="context_disabled_options"
|
||||
v-if="recipe !== null"></recipe-context-menu>
|
||||
v-if="recipe !== null && show_context_menu"></recipe-context-menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
</div>
|
||||
</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>
|
||||
|
||||
</b-card-text>
|
||||
|
||||
351
vue/src/components/RecipeViewComponent.vue
Normal file
351
vue/src/components/RecipeViewComponent.vue
Normal 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>
|
||||
@@ -31,22 +31,22 @@
|
||||
"Step_start_time": "",
|
||||
"Sort_by_new": "",
|
||||
"Table_of_Contents": "",
|
||||
"Recipes_per_page": "",
|
||||
"Recipes_per_page": "Receptů na stránku",
|
||||
"Show_as_header": "",
|
||||
"Hide_as_header": "",
|
||||
"Add_nutrition_recipe": "",
|
||||
"Remove_nutrition_recipe": "",
|
||||
"Add_nutrition_recipe": "Přidat nutriční hodnoty",
|
||||
"Remove_nutrition_recipe": "Smazat nutriční hodnoty",
|
||||
"Copy_template_reference": "",
|
||||
"Save_and_View": "",
|
||||
"Save_and_View": "Uložit & Zobrazit",
|
||||
"Manage_Books": "",
|
||||
"Meal_Plan": "",
|
||||
"Meal_Plan": "Jídelníček",
|
||||
"Select_Book": "",
|
||||
"Select_File": "",
|
||||
"Select_File": "Vybrat soubor",
|
||||
"Recipe_Image": "",
|
||||
"Import_finished": "",
|
||||
"View_Recipes": "",
|
||||
"Import_finished": "Import dokončen",
|
||||
"View_Recipes": "Zobrazit recepty",
|
||||
"Log_Cooking": "",
|
||||
"New_Recipe": "",
|
||||
"New_Recipe": "Nový recept",
|
||||
"Url_Import": "",
|
||||
"Reset_Search": "",
|
||||
"Recently_Viewed": "",
|
||||
@@ -54,21 +54,21 @@
|
||||
"New_Keyword": "",
|
||||
"Delete_Keyword": "",
|
||||
"Edit_Keyword": "",
|
||||
"Edit_Recipe": "",
|
||||
"Edit_Recipe": "Upravit recept",
|
||||
"Move_Keyword": "",
|
||||
"Merge_Keyword": "",
|
||||
"Hide_Keywords": "",
|
||||
"Hide_Recipes": "",
|
||||
"Move_Up": "",
|
||||
"Move_Down": "",
|
||||
"Step_Name": "",
|
||||
"Move_Up": "Nahoru",
|
||||
"Move_Down": "Dolů",
|
||||
"Step_Name": "Název kroku",
|
||||
"Step_Type": "",
|
||||
"Make_Header": "",
|
||||
"Make_Ingredient": "",
|
||||
"Amount": "",
|
||||
"Enable_Amount": "",
|
||||
"Disable_Amount": "",
|
||||
"Ingredient Editor": "",
|
||||
"Amount": "Množství",
|
||||
"Enable_Amount": "Zobrazit množství",
|
||||
"Disable_Amount": "Skrýt množství",
|
||||
"Ingredient Editor": "Editace ingrediencí",
|
||||
"Description_Replace": "",
|
||||
"Instruction_Replace": "",
|
||||
"Auto_Sort": "",
|
||||
@@ -79,8 +79,8 @@
|
||||
"Add_Step": "",
|
||||
"Keywords": "",
|
||||
"Books": "",
|
||||
"Proteins": "",
|
||||
"Fats": "",
|
||||
"Proteins": "Proteiny",
|
||||
"Fats": "Tuky",
|
||||
"Carbohydrates": "",
|
||||
"Calories": "",
|
||||
"Energy": "",
|
||||
@@ -333,7 +333,7 @@
|
||||
"Foods": "",
|
||||
"Account": "",
|
||||
"Cosmetic": "",
|
||||
"API": "",
|
||||
"API": "API",
|
||||
"enable_expert": "",
|
||||
"expert_mode": "",
|
||||
"simple_mode": "",
|
||||
@@ -353,8 +353,8 @@
|
||||
"Custom Filter": "",
|
||||
"shared_with": "",
|
||||
"sort_by": "",
|
||||
"asc": "",
|
||||
"desc": "",
|
||||
"asc": "Vzestupně",
|
||||
"desc": "Sestupně",
|
||||
"date_viewed": "",
|
||||
"last_cooked": "",
|
||||
"times_cooked": "",
|
||||
@@ -362,29 +362,29 @@
|
||||
"show_sortby": "",
|
||||
"search_rank": "",
|
||||
"make_now": "",
|
||||
"recipe_filter": "",
|
||||
"recipe_filter": "Filtrovat recepty",
|
||||
"book_filter_help": "",
|
||||
"review_shopping": "",
|
||||
"view_recipe": "",
|
||||
"view_recipe": "Zobrazit recept",
|
||||
"copy_to_new": "",
|
||||
"recipe_name": "",
|
||||
"recipe_name": "Název receptu",
|
||||
"paste_ingredients_placeholder": "",
|
||||
"paste_ingredients": "",
|
||||
"ingredient_list": "",
|
||||
"explain": "",
|
||||
"filter": "",
|
||||
"Website": "",
|
||||
"App": "",
|
||||
"filter": "Filtr",
|
||||
"Website": "Web",
|
||||
"App": "Aplikace",
|
||||
"Message": "",
|
||||
"Bookmarklet": "",
|
||||
"Sticky_Nav": "",
|
||||
"Sticky_Nav_Help": "",
|
||||
"Nav_Color": "",
|
||||
"Nav_Color_Help": "",
|
||||
"Use_Kj": "",
|
||||
"Comments_setting": "",
|
||||
"click_image_import": "",
|
||||
"no_more_images_found": "",
|
||||
"Use_Kj": "Používat kJ místo kcal",
|
||||
"Comments_setting": "Zobrazit komentáře",
|
||||
"click_image_import": "Vyberte obrázek, který chcete přiřadit k tomuto receptu",
|
||||
"no_more_images_found": "Žádné další obrázky na zadaném odkazu.",
|
||||
"import_duplicates": "",
|
||||
"paste_json": "",
|
||||
"Click_To_Edit": "",
|
||||
@@ -407,9 +407,9 @@
|
||||
"InheritFields_help": "",
|
||||
"show_ingredient_overview": "",
|
||||
"Ingredient Overview": "",
|
||||
"last_viewed": "",
|
||||
"created_on": "",
|
||||
"updatedon": "",
|
||||
"last_viewed": "Naposledy zobrazeno",
|
||||
"created_on": "Vytvořeno",
|
||||
"updatedon": "Upraveno",
|
||||
"Imported_From": "",
|
||||
"advanced_search_settings": "",
|
||||
"nothing_planned_today": "",
|
||||
@@ -418,13 +418,13 @@
|
||||
"Pinned": "",
|
||||
"Imported": "",
|
||||
"Quick actions": "",
|
||||
"Ratings": "",
|
||||
"Ratings": "Hodnocení",
|
||||
"Internal": "",
|
||||
"Units": "",
|
||||
"Units": "Jednotky",
|
||||
"Manage_Emails": "",
|
||||
"Change_Password": "",
|
||||
"Change_Password": "Změna hesla",
|
||||
"Social_Authentication": "",
|
||||
"Random Recipes": "",
|
||||
"Random Recipes": "Náhodné recepty",
|
||||
"parameter_count": "",
|
||||
"select_keyword": "",
|
||||
"add_keyword": "",
|
||||
@@ -436,10 +436,10 @@
|
||||
"empty_list": "",
|
||||
"Select": "",
|
||||
"Supermarkets": "",
|
||||
"User": "",
|
||||
"Username": "",
|
||||
"First_name": "",
|
||||
"Last_name": "",
|
||||
"User": "Uživatel",
|
||||
"Username": "Uživatelské jméno",
|
||||
"First_name": "Jméno",
|
||||
"Last_name": "Příjmení",
|
||||
"Keyword": "",
|
||||
"Advanced": "",
|
||||
"Page": "",
|
||||
@@ -452,15 +452,15 @@
|
||||
"Create Food": "",
|
||||
"create_food_desc": "",
|
||||
"additional_options": "",
|
||||
"Importer_Help": "",
|
||||
"Documentation": "",
|
||||
"Select_App_To_Import": "",
|
||||
"Import_Supported": "",
|
||||
"Export_Supported": "",
|
||||
"Import_Not_Yet_Supported": "",
|
||||
"Export_Not_Yet_Supported": "",
|
||||
"Import_Result_Info": "",
|
||||
"Recipes_In_Import": "",
|
||||
"Importer_Help": "Nápověda k importu z této aplikace:",
|
||||
"Documentation": "Dokumentace",
|
||||
"Select_App_To_Import": "Vyberte aplikaci, ze které chcete importovat",
|
||||
"Import_Supported": "Import podporován",
|
||||
"Export_Supported": "Export podporován",
|
||||
"Import_Not_Yet_Supported": "Import není zatím podporován",
|
||||
"Export_Not_Yet_Supported": "Export není zatím podporován",
|
||||
"Import_Result_Info": "{imported} z {total} receptů naimportováno",
|
||||
"Recipes_In_Import": "Receptů v importním souboru",
|
||||
"Toggle": "",
|
||||
"Import_Error": "",
|
||||
"Warning_Delete_Supermarket_Category": "",
|
||||
@@ -477,6 +477,6 @@
|
||||
"Use_Plural_Food_Always": "",
|
||||
"Use_Plural_Food_Simple": "",
|
||||
"plural_usage_info": "",
|
||||
"Create Recipe": "",
|
||||
"Import Recipe": ""
|
||||
"Create Recipe": "Vytvořit recept",
|
||||
"Import Recipe": "Importovat recept"
|
||||
}
|
||||
|
||||
@@ -481,5 +481,22 @@
|
||||
"Amount": "Menge",
|
||||
"Original_Text": "Originaler Text",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"success_moving_resource": "Successfully moved a resource!",
|
||||
"success_merging_resource": "Successfully merged a resource!",
|
||||
"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 ?",
|
||||
"food_inherit_info": "Fields on food that should be inherited by default.",
|
||||
"facet_count_info": "Show recipe counts on search filters.",
|
||||
@@ -37,6 +38,7 @@
|
||||
"Add_nutrition_recipe": "Add nutrition to recipe",
|
||||
"Remove_nutrition_recipe": "Delete nutrition from recipe",
|
||||
"Copy_template_reference": "Copy template reference",
|
||||
"per_serving": "per servings",
|
||||
"Save_and_View": "Save & View",
|
||||
"Manage_Books": "Manage Books",
|
||||
"Meal_Plan": "Meal Plan",
|
||||
@@ -76,6 +78,19 @@
|
||||
"Private_Recipe": "Private Recipe",
|
||||
"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.",
|
||||
"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",
|
||||
"Keywords": "Keywords",
|
||||
"Books": "Books",
|
||||
@@ -161,6 +176,8 @@
|
||||
"merge_title": "Merge {type}",
|
||||
"move_title": "Move {type}",
|
||||
"Food": "Food",
|
||||
"Property": "Property",
|
||||
"Conversion": "Conversion",
|
||||
"Original_Text": "Original Text",
|
||||
"Recipe_Book": "Recipe Book",
|
||||
"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}",
|
||||
"edit_title": "Edit {type}",
|
||||
"Name": "Name",
|
||||
"Properties": "Properties",
|
||||
"Type": "Type",
|
||||
"Description": "Description",
|
||||
"Recipe": "Recipe",
|
||||
@@ -462,6 +480,7 @@
|
||||
"Import_Result_Info": "{imported} of {total} recipes were imported",
|
||||
"Recipes_In_Import": "Recipes in your import file",
|
||||
"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.",
|
||||
"Warning_Delete_Supermarket_Category": "Deleting a supermarket category will also delete all relations to foods. Are you sure?",
|
||||
"New_Supermarket": "Create new supermarket",
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"Carbohydrates": "Carbohydratos",
|
||||
"Calories": "Calorias",
|
||||
"Energy": "Energia",
|
||||
"Nutrition": "Nutricion",
|
||||
"Nutrition": "Nutrición",
|
||||
"Date": "Fecha",
|
||||
"Share": "Compartir",
|
||||
"Automation": "Automatización",
|
||||
@@ -452,5 +452,6 @@
|
||||
"Auto_Sort": "Ordenar Automáticamente",
|
||||
"Auto_Sort_Help": "Mueva todos los ingredientes al paso que mejor se adapte.",
|
||||
"Unpin": "Desanclar",
|
||||
"Amount": "Cantidad"
|
||||
"Amount": "Cantidad",
|
||||
"PinnedConfirmation": "{recipe} ha sido fijada."
|
||||
}
|
||||
|
||||
@@ -68,83 +68,83 @@
|
||||
"Amount": "Mengde",
|
||||
"Enable_Amount": "Aktiver mengde",
|
||||
"Disable_Amount": "Deaktiver mengde",
|
||||
"Ingredient Editor": "",
|
||||
"Description_Replace": "",
|
||||
"Instruction_Replace": "",
|
||||
"Auto_Sort": "",
|
||||
"Auto_Sort_Help": "",
|
||||
"Private_Recipe": "",
|
||||
"Private_Recipe_Help": "",
|
||||
"reusable_help_text": "",
|
||||
"Add_Step": "",
|
||||
"Keywords": "",
|
||||
"Ingredient Editor": "Ingrediens Behandler",
|
||||
"Description_Replace": "Erstatt beskrivelse",
|
||||
"Instruction_Replace": "Erstatt instruksjoner",
|
||||
"Auto_Sort": "Sorter Automatisk",
|
||||
"Auto_Sort_Help": "Flytt alle ingredienser til det mest passende steget.",
|
||||
"Private_Recipe": "Privat Oppskrift",
|
||||
"Private_Recipe_Help": "Oppskriften er bare vist til deg og dem du har delt den med.",
|
||||
"reusable_help_text": "Burde invitasjonslenken være brukbar for flere enn én bruker.",
|
||||
"Add_Step": "Legg til steg",
|
||||
"Keywords": "Nøkkelord",
|
||||
"Books": "Bøker",
|
||||
"Proteins": "",
|
||||
"Fats": "",
|
||||
"Proteins": "Protein",
|
||||
"Fats": "Fett",
|
||||
"Carbohydrates": "Karbohydrater",
|
||||
"Calories": "",
|
||||
"Energy": "",
|
||||
"Nutrition": "",
|
||||
"Date": "",
|
||||
"Share": "",
|
||||
"Automation": "",
|
||||
"Parameter": "",
|
||||
"Export": "",
|
||||
"Copy": "",
|
||||
"Rating": "Karakter",
|
||||
"Calories": "Kalorier",
|
||||
"Energy": "Energi",
|
||||
"Nutrition": "Næring",
|
||||
"Date": "Dato",
|
||||
"Share": "Del",
|
||||
"Automation": "Automatiser",
|
||||
"Parameter": "Parameter",
|
||||
"Export": "Eksporter",
|
||||
"Copy": "Kopier",
|
||||
"Rating": "Vurdering",
|
||||
"Close": "Lukk",
|
||||
"Cancel": "",
|
||||
"Cancel": "Avbryt",
|
||||
"Link": "Lenke",
|
||||
"Add": "",
|
||||
"New": "",
|
||||
"Note": "",
|
||||
"Success": "",
|
||||
"Failure": "",
|
||||
"Protected": "",
|
||||
"Add": "Legg til",
|
||||
"New": "Ny",
|
||||
"Note": "Merk",
|
||||
"Success": "Vellykket",
|
||||
"Failure": "Feil",
|
||||
"Protected": "Beskyttet",
|
||||
"Ingredients": "Ingredienser",
|
||||
"Supermarket": "Butikk",
|
||||
"Categories": "",
|
||||
"Category": "",
|
||||
"Selected": "",
|
||||
"min": "",
|
||||
"Servings": "",
|
||||
"Waiting": "",
|
||||
"Preparation": "",
|
||||
"External": "",
|
||||
"Size": "",
|
||||
"Files": "",
|
||||
"File": "",
|
||||
"Edit": "",
|
||||
"Image": "",
|
||||
"Delete": "",
|
||||
"Open": "",
|
||||
"Ok": "",
|
||||
"Save": "",
|
||||
"Step": "",
|
||||
"Search": "",
|
||||
"Import": "",
|
||||
"Print": "",
|
||||
"Categories": "Kategorier",
|
||||
"Category": "Kategori",
|
||||
"Selected": "Valgte",
|
||||
"min": "min",
|
||||
"Servings": "Porsjoner",
|
||||
"Waiting": "Venter",
|
||||
"Preparation": "Forberedelse",
|
||||
"External": "Ekstern",
|
||||
"Size": "Størrelse",
|
||||
"Files": "Filer",
|
||||
"File": "Fil",
|
||||
"Edit": "Rediger",
|
||||
"Image": "Bilde",
|
||||
"Delete": "Slett",
|
||||
"Open": "Åpne",
|
||||
"Ok": "Ok",
|
||||
"Save": "Lagre",
|
||||
"Step": "Steg",
|
||||
"Search": "Søk",
|
||||
"Import": "Importer",
|
||||
"Print": "Skriv ut",
|
||||
"Settings": "Innstillinger",
|
||||
"or": "",
|
||||
"and": "",
|
||||
"Information": "",
|
||||
"Download": "",
|
||||
"or": "eller",
|
||||
"and": "og",
|
||||
"Information": "Informasjon",
|
||||
"Download": "Last ned",
|
||||
"Create": "Opprett",
|
||||
"Search Settings": "",
|
||||
"View": "",
|
||||
"Recipes": "",
|
||||
"Move": "",
|
||||
"Merge": "",
|
||||
"Parent": "",
|
||||
"Copy Link": "",
|
||||
"Copy Token": "",
|
||||
"delete_confirmation": "",
|
||||
"move_confirmation": "",
|
||||
"merge_confirmation": "",
|
||||
"create_rule": "",
|
||||
"move_selection": "",
|
||||
"merge_selection": "",
|
||||
"Root": "",
|
||||
"Search Settings": "Søk Instillinger",
|
||||
"View": "Visning",
|
||||
"Recipes": "Oppskrift",
|
||||
"Move": "Flytt",
|
||||
"Merge": "Slå sammen",
|
||||
"Parent": "Forelder",
|
||||
"Copy Link": "Kopier lenke",
|
||||
"Copy Token": "Kopier Token",
|
||||
"delete_confirmation": "Er du sikker på at du vill slette {source}?",
|
||||
"move_confirmation": "Flytt<i>{child}</i> til forelder <i>{parent}</i>",
|
||||
"merge_confirmation": "Erstatt<i>{source}</i> med <i>{target}</i>",
|
||||
"create_rule": "og opprett automasjon",
|
||||
"move_selection": "Velg en forelder {type} å flytte {source} til.",
|
||||
"merge_selection": "Erstatt alle tilfeller av {source} med den valgte {type}.",
|
||||
"Root": "Rot",
|
||||
"Ignore_Shopping": "",
|
||||
"Shopping_Category": "",
|
||||
"Shopping_Categories": "",
|
||||
@@ -284,33 +284,33 @@
|
||||
"Hide_Keyword": "",
|
||||
"Hour": "",
|
||||
"Hours": "",
|
||||
"Day": "",
|
||||
"Days": "",
|
||||
"Day": "Dag",
|
||||
"Days": "Dager",
|
||||
"Second": "",
|
||||
"Seconds": "",
|
||||
"Clear": "",
|
||||
"Users": "",
|
||||
"Invites": "",
|
||||
"err_move_self": "",
|
||||
"nothing": "",
|
||||
"err_merge_self": "",
|
||||
"show_sql": "",
|
||||
"filter_to_supermarket_desc": "",
|
||||
"CategoryName": "",
|
||||
"SupermarketName": "",
|
||||
"CategoryInstruction": "",
|
||||
"nothing": "Ingenting å gjøre",
|
||||
"err_merge_self": "Kan ikke slå sammen linje med seg selv",
|
||||
"show_sql": "Vis SQL",
|
||||
"filter_to_supermarket_desc": "Som standard, filtrerer handlelisten til å kun inkludere kategorier for den valgte butikken.",
|
||||
"CategoryName": "Kategori navn",
|
||||
"SupermarketName": "Butikk Navn",
|
||||
"CategoryInstruction": "Dra kategorier for å endre på rekkefølgen de vises i handlelisten.",
|
||||
"shopping_recent_days_desc": "",
|
||||
"shopping_recent_days": "",
|
||||
"download_pdf": "",
|
||||
"download_csv": "",
|
||||
"download_pdf": "Last ned PDF",
|
||||
"download_csv": "Last ned CSV",
|
||||
"csv_delim_help": "",
|
||||
"csv_delim_label": "",
|
||||
"SuccessClipboard": "",
|
||||
"copy_to_clipboard": "",
|
||||
"csv_prefix_help": "",
|
||||
"csv_prefix_label": "",
|
||||
"copy_markdown_table": "",
|
||||
"in_shopping": "",
|
||||
"copy_to_clipboard": "Kopier til utklippstavle",
|
||||
"csv_prefix_help": "Prefiks for å legge til når du kopierer listen til utklippstavlen.",
|
||||
"csv_prefix_label": "Liste prefiks",
|
||||
"copy_markdown_table": "Kopier som Markdown tabell",
|
||||
"in_shopping": "I handleliste",
|
||||
"DelayUntil": "",
|
||||
"Pin": "",
|
||||
"Unpin": "",
|
||||
@@ -332,37 +332,37 @@
|
||||
"food_recipe_help": "",
|
||||
"Foods": "",
|
||||
"Account": "",
|
||||
"Cosmetic": "",
|
||||
"API": "",
|
||||
"enable_expert": "",
|
||||
"expert_mode": "",
|
||||
"simple_mode": "",
|
||||
"advanced": "",
|
||||
"fields": "",
|
||||
"show_keywords": "",
|
||||
"show_foods": "",
|
||||
"show_books": "",
|
||||
"show_rating": "",
|
||||
"show_units": "",
|
||||
"show_filters": "",
|
||||
"not": "",
|
||||
"save_filter": "",
|
||||
"filter_name": "",
|
||||
"left_handed": "",
|
||||
"left_handed_help": "",
|
||||
"Custom Filter": "",
|
||||
"shared_with": "",
|
||||
"sort_by": "",
|
||||
"asc": "",
|
||||
"desc": "",
|
||||
"date_viewed": "",
|
||||
"last_cooked": "",
|
||||
"times_cooked": "",
|
||||
"date_created": "",
|
||||
"show_sortby": "",
|
||||
"search_rank": "",
|
||||
"make_now": "",
|
||||
"recipe_filter": "",
|
||||
"Cosmetic": "Kosmetisk",
|
||||
"API": "API",
|
||||
"enable_expert": "Aktiver Ekspert Modus",
|
||||
"expert_mode": "Ekspert Modus",
|
||||
"simple_mode": "Enkel Modus",
|
||||
"advanced": "Avansert",
|
||||
"fields": "Felt",
|
||||
"show_keywords": "Vis Nøkkelord",
|
||||
"show_foods": "Vis Mat",
|
||||
"show_books": "Vis bøker",
|
||||
"show_rating": "Vis vurdering",
|
||||
"show_units": "Vis enheter",
|
||||
"show_filters": "Vis filtre",
|
||||
"not": "ikke",
|
||||
"save_filter": "Lagre filtre",
|
||||
"filter_name": "Filtrer Navn",
|
||||
"left_handed": "Venstrehendt Modus",
|
||||
"left_handed_help": "Vil optimalisere bukergrensesnittet for bruk med venstre hånden.",
|
||||
"Custom Filter": "Egendefinert Filter",
|
||||
"shared_with": "Delt med",
|
||||
"sort_by": "Sorter etter",
|
||||
"asc": "Stigende",
|
||||
"desc": "Fallende",
|
||||
"date_viewed": "Sist sett",
|
||||
"last_cooked": "Sist tilberedt",
|
||||
"times_cooked": "Antall ganger tilberedt",
|
||||
"date_created": "Dato laget",
|
||||
"show_sortby": "Vis sorter etter",
|
||||
"search_rank": "Søk etter vurdering",
|
||||
"make_now": "Lag nå",
|
||||
"recipe_filter": "Oppskrift filter",
|
||||
"book_filter_help": "",
|
||||
"review_shopping": "",
|
||||
"view_recipe": "",
|
||||
@@ -373,9 +373,9 @@
|
||||
"ingredient_list": "",
|
||||
"explain": "",
|
||||
"filter": "",
|
||||
"Website": "",
|
||||
"App": "",
|
||||
"Message": "",
|
||||
"Website": "Nettside",
|
||||
"App": "App",
|
||||
"Message": "Melding",
|
||||
"Bookmarklet": "",
|
||||
"Sticky_Nav": "",
|
||||
"Sticky_Nav_Help": "",
|
||||
@@ -420,11 +420,11 @@
|
||||
"Quick actions": "",
|
||||
"Ratings": "",
|
||||
"Internal": "",
|
||||
"Units": "",
|
||||
"Manage_Emails": "",
|
||||
"Change_Password": "",
|
||||
"Units": "Enhet",
|
||||
"Manage_Emails": "Administrer e-poster",
|
||||
"Change_Password": "Endre passord",
|
||||
"Social_Authentication": "",
|
||||
"Random Recipes": "",
|
||||
"Random Recipes": "Tilfeldige oppskrifter",
|
||||
"parameter_count": "",
|
||||
"select_keyword": "",
|
||||
"add_keyword": "",
|
||||
@@ -435,13 +435,13 @@
|
||||
"remove_selection": "",
|
||||
"empty_list": "",
|
||||
"Select": "Velg",
|
||||
"Supermarkets": "",
|
||||
"User": "",
|
||||
"Username": "",
|
||||
"First_name": "",
|
||||
"Last_name": "",
|
||||
"Supermarkets": "Butikker",
|
||||
"User": "Bruker",
|
||||
"Username": "Brukernavn",
|
||||
"First_name": "Fornavn",
|
||||
"Last_name": "Etternavn",
|
||||
"Keyword": "Nøkkelord",
|
||||
"Advanced": "",
|
||||
"Advanced": "Avansert",
|
||||
"Page": "",
|
||||
"Single": "",
|
||||
"Multiple": "",
|
||||
@@ -478,5 +478,19 @@
|
||||
"Use_Plural_Food_Simple": "",
|
||||
"plural_usage_info": "",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -480,5 +480,24 @@
|
||||
"Description_Replace": "Zmień opis",
|
||||
"Instruction_Replace": "Zmień instrukcję",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"warning_feature_beta": "Данный функционал находится в сдадии BETA (тестируется). Возможны баги и серьезные изменения функционала в будующем.",
|
||||
"warning_feature_beta": "Данный функционал находится в стадии BETA (тестируется). Возможны баги и серьезные изменения функционала в будущем.",
|
||||
"err_fetching_resource": "Ошибка при загрузке продукта!",
|
||||
"err_creating_resource": "Ошибка при создании продукта!",
|
||||
"err_updating_resource": "Ошибка при редактировании продукта!",
|
||||
"err_deleting_resource": "Ошибка при удалении продукта!",
|
||||
"success_fetching_resource": "Продукт успешно загружен!",
|
||||
"success_creating_resource": "Продукт успешно загружен!",
|
||||
"success_creating_resource": "Продукт успешно создан!",
|
||||
"success_updating_resource": "Продукт успешно обновлен!",
|
||||
"success_deleting_resource": "Продукт успешно удален!",
|
||||
"file_upload_disabled": "Выгрузка файла не активирована в настройках.",
|
||||
@@ -13,7 +13,7 @@
|
||||
"confirm_delete": "Вы уверены, что хотите удалить этот объект?",
|
||||
"import_running": "Идет загрузка, пожалуйста ждите!",
|
||||
"all_fields_optional": "Все поля не обязательны для заполнения.",
|
||||
"convert_internal": "Конвретировать рецепт во внутренний формат",
|
||||
"convert_internal": "Конвертировать рецепт во внутренний формат",
|
||||
"show_only_internal": "Показывать только рецепты во внутреннем формате",
|
||||
"show_split_screen": "Двухколоночный вид",
|
||||
"Log_Recipe_Cooking": "Журнал приготовления",
|
||||
@@ -211,13 +211,13 @@
|
||||
"FoodNotOnHand": "{food} отсутствует в наличии.",
|
||||
"Undefined": "Неизвестно",
|
||||
"AddFoodToShopping": "Добавить {food} в ваш список покупок",
|
||||
"success_moving_resource": "Успешное перемещение ресурса!",
|
||||
"success_merging_resource": "Ресурс успешно присоединен!",
|
||||
"success_moving_resource": "Успешное перемещение продукта!",
|
||||
"success_merging_resource": "Продукт успешно присоединен!",
|
||||
"Shopping_Categories": "Категории покупок",
|
||||
"Search Settings": "Искать настройки",
|
||||
"err_merging_resource": "Произошла ошибка при перемещении ресурса!",
|
||||
"err_merging_resource": "Произошла ошибка при перемещении продукта!",
|
||||
"Remove_nutrition_recipe": "Уберите питательные вещества из рецепта",
|
||||
"err_moving_resource": "Произошла ошибка при перемещении ресурса!",
|
||||
"err_moving_resource": "Произошла ошибка при перемещении продукта!",
|
||||
"NotInShopping": "{food} отсутствует в вашем списке покупок.",
|
||||
"RemoveFoodFromShopping": "Удалить {food} из вашего списка покупок",
|
||||
"ShowDelayed": "Показать отложенные элементы",
|
||||
@@ -345,6 +345,6 @@
|
||||
"GroupBy": "Сгруппировать по",
|
||||
"facet_count_info": "Показывать количество рецептов в фильтрах поиска.",
|
||||
"food_inherit_info": "Поля для продуктов питания, которые должны наследоваться по умолчанию.",
|
||||
"warning_space_delete": "Вы можете удалить свое пространство, включая все рецепты, списки покупок, планы питания и все остальное, что вы создали. Этого нельзя отменить! Ты уверен, что хочешь это сделать?",
|
||||
"warning_space_delete": "Вы можете удалить свое пространство, включая все рецепты, списки покупок, планы питания и все остальное, что вы создали. Этого нельзя отменить! Вы уверены, что хотите это сделать?",
|
||||
"Description_Replace": "Изменить описание"
|
||||
}
|
||||
|
||||
0
vue/src/stores/GenericApiStore.js
Normal file
0
vue/src/stores/GenericApiStore.js
Normal file
@@ -91,11 +91,13 @@ export class Models {
|
||||
"substitute_children",
|
||||
"reset_inherit",
|
||||
"child_inherit_fields",
|
||||
"open_data_slug",
|
||||
],
|
||||
],
|
||||
|
||||
form: {
|
||||
show_help: true,
|
||||
component: "FoodEditor",
|
||||
name: {
|
||||
form_field: true,
|
||||
type: "text",
|
||||
@@ -126,6 +128,14 @@ export class Models {
|
||||
label: "Recipe", // form.label always translated in utils.getForm()
|
||||
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: {
|
||||
form_field: true,
|
||||
type: "checkbox",
|
||||
@@ -269,8 +279,9 @@ export class Models {
|
||||
apiName: "Unit",
|
||||
paginated: true,
|
||||
create: {
|
||||
params: [["name", "plural_name", "description",]],
|
||||
params: [["name", "plural_name", "description", "open_data_slug",]],
|
||||
form: {
|
||||
show_help: true,
|
||||
name: {
|
||||
form_field: true,
|
||||
type: "text",
|
||||
@@ -292,6 +303,14 @@ export class Models {
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
merge: true,
|
||||
@@ -418,6 +437,7 @@ export class Models {
|
||||
create: {
|
||||
params: [["name", "description", "category_to_supermarket"]],
|
||||
form: {
|
||||
show_help: true,
|
||||
name: {
|
||||
form_field: true,
|
||||
type: "text",
|
||||
@@ -442,6 +462,14 @@ export class Models {
|
||||
label: "Categories", // form.label always translated in utils.getForm()
|
||||
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: {
|
||||
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 = {
|
||||
name: "Recipe",
|
||||
apiName: "Recipe",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,7 +50,7 @@ export class StandardToasts {
|
||||
static FAIL_MOVE = "FAIL_MOVE"
|
||||
static FAIL_MERGE = "FAIL_MERGE"
|
||||
|
||||
static makeStandardToast(context, toast, err) {
|
||||
static makeStandardToast(context, toast, err = undefined, always_show_errors = false) {
|
||||
let title = ''
|
||||
let msg = ''
|
||||
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 (DEBUG && err.response.headers['content-type'] === 'application/json' && err.response.status < 500) {
|
||||
@@ -368,6 +368,9 @@ export const ApiMixin = {
|
||||
let func = setup.function
|
||||
let parameters = buildParams(options, setup)
|
||||
let apiClient = new ApiApiFactory()
|
||||
if (model.apiClient !== undefined) {
|
||||
apiClient = model.apiClient
|
||||
}
|
||||
return apiClient[func](...parameters)
|
||||
},
|
||||
genericGetAPI: function (url, options) {
|
||||
|
||||
28857
vue/yarn.lock
28857
vue/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user