Compare commits

...

26 Commits
1.5.3 ... 1.5.4

Author SHA1 Message Date
vabene1111
c78b7a6928 Merge branch 'develop' 2023-07-05 16:33:51 +02:00
vabene1111
7a2ccc075c improved shopping entry api endpoint performance 2023-07-04 16:49:56 +02:00
vabene1111
237054c23e improved commonly used administrative admin fields 2023-07-03 22:30:27 +02:00
vabene1111
ac1d641bd5 added RO DRF permission and internal_note filters for invite/userspace 2023-07-03 21:59:15 +02:00
vabene1111
3545b6e98a plugin loader improvements 2023-07-03 17:56:05 +02:00
vabene1111
d3a56e00ea allow disabling plugins 2023-07-03 07:41:56 +02:00
vabene1111
e9f8578c25 re added path to plugin check 2023-07-03 07:02:56 +02:00
vabene1111
dccfc436be Merge branch 'develop' of https://github.com/vabene1111/recipes into develop 2023-07-03 05:55:17 +02:00
vabene1111
1e85c8587b fixed plugin error message 2023-07-03 05:55:12 +02:00
vabene1111
b8f92ab054 Merge pull request #2531 from michael-genson/feature/add-source-url-to-recipe-export
Add source URL to recipe export
2023-07-03 05:47:31 +02:00
Mára Štěpánek
766ed31f8e Translated using Weblate (Czech)
Currently translated at 79.7% (398 of 499 strings)

Translation: Tandoor/Recipes Frontend
Translate-URL: http://translate.tandoor.dev/projects/tandoor/recipes-frontend/cs/
2023-07-02 21:19:57 +00:00
Michael Genson
cad78e115d added source url to recipe export 2023-07-02 10:42:41 -05:00
vabene1111
b599c4f6a9 added internal notes and improved invite link form 2023-06-30 23:09:22 +02:00
vabene1111
439539f56d show optional fields in generic forms 2023-06-30 23:09:01 +02:00
vabene1111
237bcb92c9 fixed food editor default properties unit 2023-06-29 17:26:49 +02:00
vabene1111
ce02a23dbb fixed quick ingredient import in recipe editor 2023-06-29 17:13:53 +02:00
vabene1111
8e81512735 Merge branch 'develop' of https://github.com/vabene1111/recipes into develop 2023-06-29 17:05:37 +02:00
vabene1111
c69f0394a8 possibly fixed bug with food editor ingredient delete page reload 2023-06-29 17:05:32 +02:00
vabene1111
d7ca9e05de Merge pull request #2521 from gloriousDan/improve-docs
add note to docker-compose files and update postgres tag
2023-06-29 17:04:31 +02:00
vabene1111
64534ff810 fixed navbar color for non logged in users 2023-06-29 17:03:05 +02:00
vabene1111
d0164a6c28 Merge pull request #2522 from gloriousDan/fix-raspi
Fix Raspi build and consolidate with normal build and image
2023-06-27 16:10:39 +02:00
Daniel Schulz
0f898ddf4a unify raspi and normal build again 2023-06-27 00:51:55 +02:00
Daniel Schulz
e903382034 update alpine to v3.18 2023-06-27 00:51:22 +02:00
Daniel Schulz
0d225450da add note to docker-compose files and update postgres tag 2023-06-27 00:33:29 +02:00
vabene1111
c077a64484 further improvements 2023-06-26 20:57:51 +02:00
vabene1111
6c16094b42 added initial version of tandoor dark theme 2023-06-26 20:43:50 +02:00
35 changed files with 16929 additions and 478 deletions

View File

@@ -17,15 +17,9 @@ jobs:
# Standard build config
- name: Standard
dockerfile: Dockerfile
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/arm/v7
suffix: ""
continue-on-error: false
# Raspi build config
- name: Raspi
dockerfile: Dockerfile-raspi
platforms: linux/arm/v7
suffix: "-raspi"
continue-on-error: true
steps:
- uses: actions/checkout@v3

View File

@@ -1,7 +1,7 @@
FROM python:3.10-alpine3.15
FROM python:3.10-alpine3.18
#Install all dependencies.
RUN apk add --no-cache postgresql-libs postgresql-client gettext zlib libjpeg libwebp libxml2-dev libxslt-dev py-cryptography openldap
RUN apk add --no-cache postgresql-libs postgresql-client gettext zlib libjpeg libwebp libxml2-dev libxslt-dev openldap
#Print all logs without buffering it.
ENV PYTHONUNBUFFERED 1
@@ -15,6 +15,10 @@ WORKDIR /opt/recipes
COPY requirements.txt ./
RUN \
if [ `apk --print-arch` = "armv7" ]; then \
printf "[global]\nextra-index-url=https://www.piwheels.org/simple\n" > /etc/pip.conf ; \
fi
RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-dev jpeg-dev libwebp-dev openssl-dev libffi-dev cargo openldap-dev python3-dev git && \
echo -n "INPUT ( libldap.so )" > /usr/lib/libldap_r.so && \
python -m venv venv && \

View File

@@ -1,33 +0,0 @@
# builds of cryptography for raspberry pi (or better arm v7) fail for some
FROM python:3.9-alpine3.15
#Install all dependencies.
RUN apk add --no-cache postgresql-libs postgresql-client gettext zlib libjpeg libwebp libxml2-dev libxslt-dev py-cryptography openldap gcompat
#Print all logs without buffering it.
ENV PYTHONUNBUFFERED 1
#This port will be used by gunicorn.
EXPOSE 8080
#Create app dir and install requirements.
RUN mkdir /opt/recipes
WORKDIR /opt/recipes
COPY requirements.txt ./
RUN \
if [ `apk --print-arch` = "armv7" ]; then \
printf "[global]\nextra-index-url=https://www.piwheels.org/simple\n" > /etc/pip.conf ; \
fi
RUN apk add --no-cache --virtual .build-deps gcc musl-dev zlib-dev jpeg-dev libwebp-dev python3-dev git && \
echo -n "INPUT ( libldap.so )" > /usr/lib/libldap_r.so && \
python -m venv venv && \
/opt/recipes/venv/bin/python -m pip install --upgrade pip && \
venv/bin/pip install wheel==0.37.1 && \
venv/bin/pip install -r requirements.txt --no-cache-dir --no-binary=Pillow && \
apk --purge del .build-deps
#Copy project and execute it.
COPY . ./
RUN chmod +x boot.sh
ENTRYPOINT ["/opt/recipes/boot.sh"]

View File

@@ -39,6 +39,8 @@ def delete_space_action(modeladmin, request, queryset):
class SpaceAdmin(admin.ModelAdmin):
list_display = ('name', 'created_by', 'max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
search_fields = ('name', 'created_by__username')
autocomplete_fields = ('created_by',)
filter_horizontal = ('food_inherit',)
list_filter = ('max_recipes', 'max_users', 'max_file_storage_mb', 'allow_sharing')
date_hierarchy = 'created_at'
actions = [delete_space_action]
@@ -50,6 +52,8 @@ admin.site.register(Space, SpaceAdmin)
class UserSpaceAdmin(admin.ModelAdmin):
list_display = ('user', 'space',)
search_fields = ('user__username', 'space__name',)
filter_horizontal = ('groups',)
autocomplete_fields = ('user', 'space',)
admin.site.register(UserSpace, UserSpaceAdmin)
@@ -60,6 +64,7 @@ class UserPreferenceAdmin(admin.ModelAdmin):
search_fields = ('user__username',)
list_filter = ('theme', 'nav_color', 'default_page',)
date_hierarchy = 'created_at'
filter_horizontal = ('plan_share', 'shopping_share',)
@staticmethod
def name(obj):

View File

@@ -434,3 +434,10 @@ def switch_user_active_space(user, space):
return us
except ObjectDoesNotExist:
return None
class IsReadOnlyDRF(permissions.BasePermission):
message = 'You cannot interact with this object as it is not owned by you!'
def has_permission(self, request, view):
return request.method in SAFE_METHODS

View File

@@ -0,0 +1,34 @@
# Generated by Django 4.1.9 on 2023-06-30 20:34
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0194_alter_food_properties_food_amount'),
]
operations = [
migrations.AddField(
model_name='invitelink',
name='internal_note',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='userspace',
name='internal_note',
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name='userspace',
name='invite_link',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='cookbook.invitelink'),
),
migrations.AlterField(
model_name='userpreference',
name='theme',
field=models.CharField(choices=[('TANDOOR', 'Tandoor'), ('BOOTSTRAP', 'Bootstrap'), ('DARKLY', 'Darkly'), ('FLATLY', 'Flatly'), ('SUPERHERO', 'Superhero'), ('TANDOOR_DARK', 'Tandoor Dark (INCOMPLETE)')], default='TANDOOR', max_length=128),
),
]

View File

@@ -331,6 +331,7 @@ class UserPreference(models.Model, PermissionModelMixin):
FLATLY = 'FLATLY'
SUPERHERO = 'SUPERHERO'
TANDOOR = 'TANDOOR'
TANDOOR_DARK = 'TANDOOR_DARK'
THEMES = (
(TANDOOR, 'Tandoor'),
@@ -338,6 +339,7 @@ class UserPreference(models.Model, PermissionModelMixin):
(DARKLY, 'Darkly'),
(FLATLY, 'Flatly'),
(SUPERHERO, 'Superhero'),
(TANDOOR_DARK, 'Tandoor Dark (INCOMPLETE)'),
)
# Nav colors
@@ -413,6 +415,9 @@ class UserSpace(models.Model, PermissionModelMixin):
# that having more than one active space should just break certain parts of the application and not leak any data
active = models.BooleanField(default=False)
invite_link = models.ForeignKey("InviteLink", on_delete=models.PROTECT, null=True, blank=True)
internal_note = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@@ -717,25 +722,6 @@ class Ingredient(ExportModelOperationsMixin('ingredient'), models.Model, Permiss
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')
def __str__(self):
food = ""
unit = ""
if self.always_use_plural_food and self.food.plural_name not in (None, "") and not self.no_amount:
food = self.food.plural_name
else:
if self.amount > 1 and self.food.plural_name not in (None, "") and not self.no_amount:
food = self.food.plural_name
else:
food = str(self.food)
if self.always_use_plural_unit and self.unit.plural_name not in (None, "") and not self.no_amount:
unit = self.unit.plural_name
else:
if self.amount > 1 and self.unit is not None and self.unit.plural_name not in (None, "") and not self.no_amount:
unit = self.unit.plural_name
else:
unit = str(self.unit)
return str(self.amount) + ' ' + str(unit) + ' ' + str(food)
class Meta:
ordering = ['order', 'pk']
indexes = (
@@ -1142,6 +1128,8 @@ class InviteLink(ExportModelOperationsMixin('invite_link'), models.Model, Permis
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
internal_note = models.TextField(blank=True, null=True)
space = models.ForeignKey(Space, on_delete=models.CASCADE)
objects = ScopedManager(space='space')

View File

@@ -322,8 +322,8 @@ class UserSpaceSerializer(WritableNestedModelSerializer):
class Meta:
model = UserSpace
fields = ('id', 'user', 'space', 'groups', 'active', 'created_at', 'updated_at',)
read_only_fields = ('id', 'created_at', 'updated_at', 'space')
fields = ('id', 'user', 'space', 'groups', 'active', 'internal_note', 'invite_link', 'created_at', 'updated_at',)
read_only_fields = ('id', 'invite_link', 'created_at', 'updated_at', 'space')
class SpacedModelSerializer(serializers.ModelSerializer):
@@ -1245,7 +1245,7 @@ class InviteLinkSerializer(WritableNestedModelSerializer):
class Meta:
model = InviteLink
fields = (
'id', 'uuid', 'email', 'group', 'valid_until', 'used_by', 'reusable', 'created_by', 'created_at',)
'id', 'uuid', 'email', 'group', 'valid_until', 'used_by', 'reusable', 'internal_note', 'created_by', 'created_at',)
read_only_fields = ('id', 'uuid', 'created_by', 'created_at',)
@@ -1362,7 +1362,7 @@ class RecipeExportSerializer(WritableNestedModelSerializer):
model = Recipe
fields = (
'name', 'description', 'keywords', 'steps', 'working_time',
'waiting_time', 'internal', 'nutrition', 'servings', 'servings_text',
'waiting_time', 'internal', 'nutrition', 'servings', 'servings_text', 'source_url',
)
def create(self, validated_data):

10486
cookbook/static/themes/tandoor_dark.min.css vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@ def theme_url(request):
UserPreference.DARKLY: 'themes/darkly.min.css',
UserPreference.SUPERHERO: 'themes/superhero.min.css',
UserPreference.TANDOOR: 'themes/tandoor.min.css',
UserPreference.TANDOOR_DARK: 'themes/tandoor_dark.min.css',
}
if request.user.userpreference.theme in themes:
return static(themes[request.user.userpreference.theme])
@@ -26,7 +27,7 @@ def theme_url(request):
@register.simple_tag
def nav_color(request):
if not request.user.is_authenticated:
return 'primary'
return 'navbar-light bg-primary'
if request.user.userpreference.nav_color.lower() in ['light', 'warning', 'info', 'success']:
return f'navbar-light bg-{request.user.userpreference.nav_color.lower()}'

View File

@@ -421,6 +421,10 @@ class UserSpaceViewSet(viewsets.ModelViewSet):
return super().destroy(request, *args, **kwargs)
def get_queryset(self):
internal_note = self.request.query_params.get('internal_note', None)
if internal_note is not None:
self.queryset = self.queryset.filter(internal_note=internal_note)
if is_space_owner(self.request.user, self.request.space):
return self.queryset.filter(space=self.request.space)
else:
@@ -1047,6 +1051,21 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet):
Q(created_by=self.request.user)
| Q(shoppinglist__shared=self.request.user)
| Q(created_by__in=list(self.request.user.get_shopping_share()))
).prefetch_related(
'created_by',
'food',
'food__properties',
'food__properties__property_type',
'food__inherit_fields',
'food__supermarket_category',
'food__onhand_users',
'food__substitute',
'food__child_inherit_fields',
'unit',
'list_recipe',
'list_recipe__mealplan',
'list_recipe__mealplan__recipe',
).distinct().all()
if pk := self.request.query_params.getlist('id', []):
@@ -1165,6 +1184,11 @@ class InviteLinkViewSet(viewsets.ModelViewSet, StandardFilterMixin):
permission_classes = [CustomIsSpaceOwner & CustomIsAdmin & CustomTokenHasReadWriteScope]
def get_queryset(self):
internal_note = self.request.query_params.get('internal_note', None)
if internal_note is not None:
self.queryset = self.queryset.filter(internal_note=internal_note)
if is_space_owner(self.request.user, self.request.space):
self.queryset = self.queryset.filter(space=self.request.space).all()
return super().get_queryset()

View File

@@ -370,7 +370,7 @@ def invite_link(request, token):
link.used_by = request.user
link.save()
user_space = UserSpace.objects.create(user=request.user, space=link.space, active=False)
user_space = UserSpace.objects.create(user=request.user, space=link.space, internal_note=link.internal_note, invite_link=link, active=False)
if request.user.userspace_set.count() == 1:
user_space.active = True

View File

@@ -2,7 +2,7 @@ version: "2.4"
services:
db_recipes:
restart: always
image: postgres:11-alpine
image: postgres:15-alpine
volumes:
- ${POSTGRES_DATA_DIR:-./postgresql}:/var/lib/postgresql/data
env_file:
@@ -22,6 +22,7 @@ services:
- ./.env
volumes:
- staticfiles:/opt/recipes/staticfiles
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/opt/recipes/nginx/conf.d
- ${MEDIA_FILES_DIR:-./mediafiles}:/opt/recipes/mediafiles
depends_on:
@@ -41,6 +42,7 @@ services:
depends_on:
- web_recipes
volumes:
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/etc/nginx/conf.d:ro
- staticfiles:/static:ro
- ${MEDIA_FILES_DIR:-./mediafiles}:/media:ro

View File

@@ -2,7 +2,7 @@ version: "3"
services:
db_recipes:
restart: always
image: postgres:11-alpine
image: postgres:15-alpine
volumes:
- ./postgresql:/var/lib/postgresql/data
env_file:
@@ -17,6 +17,7 @@ services:
- ./.env
volumes:
- staticfiles:/opt/recipes/staticfiles
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/opt/recipes/nginx/conf.d
- ./mediafiles:/opt/recipes/mediafiles
depends_on:
@@ -32,6 +33,7 @@ services:
depends_on:
- web_recipes
volumes:
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/etc/nginx/conf.d:ro
- staticfiles:/static:ro
- ./mediafiles:/media:ro

View File

@@ -2,7 +2,7 @@ version: "3"
services:
db_recipes:
restart: always
image: postgres:11-alpine
image: postgres:15-alpine
volumes:
- ./postgresql:/var/lib/postgresql/data
env_file:
@@ -15,6 +15,7 @@ services:
- ./.env
volumes:
- staticfiles:/opt/recipes/staticfiles
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/opt/recipes/nginx/conf.d
- ./mediafiles:/opt/recipes/mediafiles
depends_on:
@@ -30,6 +31,7 @@ services:
depends_on:
- web_recipes
volumes:
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/etc/nginx/conf.d:ro
- staticfiles:/static:ro
- ./mediafiles:/media:ro

View File

@@ -2,7 +2,7 @@ version: "3"
services:
db_recipes:
restart: always
image: postgres:11-alpine
image: postgres:15-alpine
volumes:
- ./postgresql:/var/lib/postgresql/data
env_file:
@@ -17,6 +17,7 @@ services:
- ./.env
volumes:
- staticfiles:/opt/recipes/staticfiles
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/opt/recipes/nginx/conf.d
- ./mediafiles:/opt/recipes/mediafiles
depends_on:
@@ -30,6 +31,7 @@ services:
env_file:
- ./.env
volumes:
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/etc/nginx/conf.d:ro
- staticfiles:/static:ro
- ./mediafiles:/media:ro

View File

@@ -69,7 +69,7 @@ services:
db_recipes:
restart: always
container_name: db_recipes
image: postgres:11-alpine
image: postgres:15-alpine
volumes:
- ./recipes/db:/var/lib/postgresql/data
env_file:

View File

@@ -80,7 +80,7 @@ Basic guide to setup Docker and Portainer TrueNAS Core.
services:
db_recipes:
restart: always
image: postgres:11-alpine
image: postgres:15-alpine
volumes:
- ./postgresql:/var/lib/postgresql/data
env_file:
@@ -93,7 +93,8 @@ services:
- stack.env
volumes:
- staticfiles:/opt/recipes/staticfiles
- nginx_config:/opt/recipes/nginx/conf.d
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/opt/recipes/nginx/conf.d
- ./mediafiles:/opt/recipes/mediafiles
depends_on:
- db_recipes
@@ -108,6 +109,7 @@ services:
depends_on:
- web_recipes
volumes:
# Do not make this a bind mount, see https://docs.tandoor.dev/install/docker/#volumes-vs-bind-mounts
- nginx_config:/etc/nginx/conf.d:ro
- staticfiles:/static
- ./mediafiles:/media

View File

@@ -128,34 +128,40 @@ INSTALLED_APPS = [
'treebeard',
]
PLUGINS_DIRECTORY = os.path.join(BASE_DIR, 'recipes', 'plugins')
PLUGINS = []
try:
for d in os.listdir(os.path.join(BASE_DIR, 'recipes', 'plugins')):
if d != '__pycache__':
try:
apps_path = f'recipes.plugins.{d}.apps'
__import__(apps_path)
app_config_classname = dir(sys.modules[apps_path])[1]
plugin_module = f'recipes.plugins.{d}.apps.{app_config_classname}'
if plugin_module not in INSTALLED_APPS:
INSTALLED_APPS.append(plugin_module)
plugin_class = getattr(
sys.modules[apps_path], app_config_classname)
plugin_config = {
'name': plugin_class.verbose_name if hasattr(plugin_class, 'verbose_name') else plugin_class.name,
'module': f'recipes.plugins.{d}',
'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:
if DEBUG:
traceback.print_exc()
print(f'ERROR failed to initialize plugin {d}')
if os.path.isdir(PLUGINS_DIRECTORY):
for d in os.listdir(PLUGINS_DIRECTORY):
if d != '__pycache__':
try:
apps_path = f'recipes.plugins.{d}.apps'
__import__(apps_path)
app_config_classname = dir(sys.modules[apps_path])[1]
plugin_module = f'recipes.plugins.{d}.apps.{app_config_classname}'
plugin_class = getattr(sys.modules[apps_path], app_config_classname)
plugin_disabled = False
if hasattr(plugin_class, 'disabled'):
plugin_disabled = plugin_class.disabled
if plugin_module not in INSTALLED_APPS and not plugin_disabled:
INSTALLED_APPS.append(plugin_module)
plugin_config = {
'name': plugin_class.verbose_name if hasattr(plugin_class, 'verbose_name') else plugin_class.name,
'module': f'recipes.plugins.{d}',
'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)
print(f'PLUGIN {d} loaded')
except Exception:
if DEBUG:
traceback.print_exc()
print(f'ERROR failed to initialize plugin {d}')
except Exception:
if DEBUG:
print('ERROR failed to initialize plugins')
@@ -522,4 +528,4 @@ 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)
mimetypes.add_type("text/javascript", ".js", True)

View File

@@ -1254,23 +1254,28 @@ export default {
ing_list.forEach((ing) => {
if (ing.trim() !== "") {
promises.push(this.genericPostAPI("api_ingredient_from_string", {text: ing}).then((result) => {
let unit = null
if (result.data.unit !== "" && result.data.unit !== null) {
unit = {name: result.data.unit}
}
parsed_ing_list.push({
let new_ingredient = {
amount: result.data.amount,
unit: unit,
food: {name: result.data.food},
note: result.data.note,
original_text: ing,
})
}
console.log(ing, new_ingredient)
parsed_ing_list.push(new_ingredient)
}))
}
})
Promise.allSettled(promises).then(() => {
ing_list.forEach(ing => {
step.ingredients.push(parsed_ing_list.find(x => x.original_text === ing))
if(ing.trim() !== ""){
step.ingredients.push(parsed_ing_list.find(x => x.original_text === ing))
}
})
})
},

View File

@@ -71,8 +71,8 @@
}}</span></span>
</td>
<td>
<button class="btn btn-danger btn-small" @click="deleteProperty(fp)"><i
class="fas fa-trash-alt"></i></button>
<a class="btn btn-danger btn-small" @click="deleteProperty(fp)"><i
class="fas fa-trash-alt"></i></a>
</td>
</tr>
</table>
@@ -293,7 +293,9 @@ export default {
if (this.item1.id !== undefined) {
pf = apiClient.retrieveFood(this.item1.id).then((r) => {
this.food = r.data
this.food.properties_food_unit = {name: 'g'}
if (this.food.properties_food_unit === null) {
this.food.properties_food_unit = {name: 'g'}
}
}).catch(err => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
})

View File

@@ -1,6 +1,6 @@
<template>
<div>
<b-form-group v-bind:label="label" class="mb-3">
<b-form-group v-bind:label="field_label" class="mb-3">
<b-form-select v-model="new_value" :placeholder="placeholder" :options="translatedOptions"></b-form-select>
</b-form-group>
</div>
@@ -10,12 +10,13 @@
export default {
name: "ChoiceInput",
props: {
field: { type: String, default: "You Forgot To Set Field Name" },
label: { type: String, default: "Text Field" },
value: { type: String, default: "" },
field: {type: String, default: "You Forgot To Set Field Name"},
label: {type: String, default: "Text Field"},
value: {type: String, default: ""},
options: [],
placeholder: { type: String, default: "You Should Add Placeholder Text" },
show_merge: { type: Boolean, default: false },
placeholder: {type: String, default: "You Should Add Placeholder Text"},
show_merge: {type: Boolean, default: false},
optional: {type: Boolean, default: false},
},
data() {
return {
@@ -31,9 +32,16 @@ export default {
},
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
},
translatedOptions() {
return this.options.map((x) => {
return { ...x, text: this.$t(x.text) }
return {...x, text: this.$t(x.text)}
})
},
},

View File

@@ -1,6 +1,6 @@
<template>
<div>
<b-form-group v-bind:label="label" class="mb-3">
<b-form-group v-bind:label="field_label" class="mb-3">
<b-form-input v-model="new_value" type="date" ></b-form-input>
<em v-if="help" class="small text-muted">{{ help }}</em>
<small v-if="subtitle" class="text-muted">{{ subtitle }}</small>
@@ -17,6 +17,16 @@ export default {
value: { type: String, default: "" },
help: { type: String, default: undefined },
subtitle: { type: String, default: undefined },
optional: {type: Boolean, default: false},
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
}
},
data() {
return {

View File

@@ -1,7 +1,7 @@
<template>
<div>
<b-form-group
v-bind:label="label"
v-bind:label="field_label"
class="mb-3">
<input class="form-control" v-model="new_value">
@@ -27,6 +27,7 @@ export default {
field: {type: String, default: 'You Forgot To Set Field Name'},
label: {type: String, default: ''},
value: {type: String, default: ''},
optional: {type: Boolean, default: false},
},
data() {
return {
@@ -36,6 +37,15 @@ export default {
emojisOutput: ""
}
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
}
},
watch: {
'new_value': function () {
this.$root.$emit('change', this.field, this.new_value ?? null)

View File

@@ -1,7 +1,7 @@
<template>
<div>
<b-form-group
v-bind:label="label"
v-bind:label="field_label"
class="mb-3">
<b-form-file
v-model="new_value"
@@ -30,7 +30,17 @@ export default {
value: {type: String, default: ''},
placeholder: {type: String, default: ''},
show_merge: {type: Boolean, default: false},
optional: {type: Boolean, default: false},
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
}
},
data() {
return {
new_value: undefined,

View File

@@ -11,15 +11,16 @@
</template>
<div v-for="(f, i) in form.fields" v-bind:key="i">
<p v-if="visibleCondition(f, 'instruction')">{{ f.label }}</p>
<lookup-input v-if="visibleCondition(f, 'lookup')" :form="f" :model="listModel(f.list)" @change="storeValue" :help="showHelp && f.help"/>
<lookup-input v-if="visibleCondition(f, 'lookup')" :form="f" :model="listModel(f.list)" @change="storeValue" :help="showHelp && f.help" :optional="f.optional"/>
<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" :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"/>
<small-text v-if="visibleCondition(f, 'smalltext')" :value="f.value"/>
<date-input v-if="visibleCondition(f, 'date')" :label="f.label" :value="f.value" :field="f.field" :help="showHelp && f.help" :subtitle="f.subtitle"/>
<number-input v-if="visibleCondition(f, 'number')" :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" :optional="f.optional"/>
<text-area-input v-if="visibleCondition(f, 'textarea')" :label="f.label" :value="f.value" :field="f.field" :placeholder="f.placeholder" :help="showHelp && f.help" :subtitle="f.subtitle" :disabled="f.disabled" :optional="f.optional"/>
<choice-input v-if="visibleCondition(f, 'choice')" :label="f.label" :value="f.value" :field="f.field" :options="f.options" :placeholder="f.placeholder" :optional="f.optional"/>
<emoji-input v-if="visibleCondition(f, 'emoji')" :label="f.label" :value="f.value" :field="f.field" @change="storeValue" :optional="f.optional"/>
<file-input v-if="visibleCondition(f, 'file')" :label="f.label" :value="f.value" :field="f.field" @change="storeValue" :optional="f.optional"/>
<small-text v-if="visibleCondition(f, 'smalltext')" :value="f.value" />
<date-input v-if="visibleCondition(f, 'date')" :label="f.label" :value="f.value" :field="f.field" :help="showHelp && f.help" :subtitle="f.subtitle" :optional="f.optional"/>
<number-input v-if="visibleCondition(f, 'number')" :label="f.label" :value="f.value" :field="f.field" :placeholder="f.placeholder" :help="showHelp && f.help" :subtitle="f.subtitle" :optional="f.optional"/>
</div>
<template v-slot:modal-footer>
<div class="row w-100">
@@ -57,6 +58,7 @@ import FileInput from "@/components/Modals/FileInput"
import SmallText from "@/components/Modals/SmallText"
import HelpBadge from "@/components/Badges/Help"
import NumberInput from "@/components/Modals/NumberInput.vue";
import TextAreaInput from "@/components/Modals/TextAreaInput.vue";
export default {
name: "GenericModalForm",
@@ -70,7 +72,8 @@ export default {
SmallText,
HelpBadge,
DateInput,
NumberInput
NumberInput,
TextAreaInput
},
mixins: [ApiMixin, ToastMixin],
props: {

View File

@@ -2,7 +2,7 @@
<div>
<b-form-group :class="class_list">
<template #label v-if="show_label">
{{ form.label }}
{{ field_label }}
</template>
<generic-multiselect
@change="new_value = $event.val"
@@ -27,11 +27,11 @@
<script>
import GenericMultiselect from "@/components/GenericMultiselect"
import { StandardToasts, ApiMixin } from "@/utils/utils"
import {StandardToasts, ApiMixin} from "@/utils/utils"
export default {
name: "LookupInput",
components: { GenericMultiselect },
components: {GenericMultiselect},
mixins: [ApiMixin],
props: {
form: {
@@ -46,11 +46,13 @@ export default {
return undefined
},
},
class_list: { type: String, default: "mb-3" },
show_label: { type: Boolean, default: true },
clear: { type: Number },
help: { type: String, default: undefined },
class_list: {type: String, default: "mb-3"},
show_label: {type: Boolean, default: true},
clear: {type: Number},
help: {type: String, default: undefined},
optional: {type: Boolean, default: false},
},
data() {
return {
new_value: undefined,
@@ -67,7 +69,7 @@ export default {
this.label = this.form?.label ?? ""
this.sticky_options = this.form?.sticky_options ?? []
this.sticky_options = this.sticky_options.map((x) => {
return { ...x, name: this.$t(x.name) }
return {...x, name: this.$t(x.name)}
})
this.list_label = this.form?.list_label ?? undefined
if (this.list_label?.includes("::")) {
@@ -75,6 +77,13 @@ export default {
}
},
computed: {
field_label: function () {
if (this.optional) {
return this.form.label
} else {
return this.form.label + '*'
}
},
modelName() {
return this.$t(this?.model?.name) ?? this.$t("Search")
},
@@ -124,7 +133,7 @@ export default {
let item = undefined
let label = this.form.list_label.split("::")
itemlist.forEach((x) => {
item = { ...x }
item = {...x}
for (const [k, v] of Object.entries(x)) {
if (k == label[0]) {
item["id"] = v.id

View File

@@ -1,6 +1,6 @@
<template>
<div>
<b-form-group v-bind:label="label" class="mb-3">
<b-form-group v-bind:label="field_label" class="mb-3">
<b-form-input v-model="new_value" type="number" :placeholder="placeholder"></b-form-input>
<em v-if="help" class="small text-muted">{{ help }}</em>
<small v-if="subtitle" class="text-muted">{{ subtitle }}</small>
@@ -18,6 +18,16 @@ export default {
placeholder: { type: Number, default: 0 },
help: { type: String, default: undefined },
subtitle: { type: String, default: undefined },
optional: {type: Boolean, default: false},
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
}
},
data() {
return {

View File

@@ -0,0 +1,48 @@
<template>
<div>
<b-form-group v-bind:label="field_label" class="mb-3">
<b-form-textarea v-model="new_value" type="text" :placeholder="placeholder" :disabled="disabled"></b-form-textarea>
<em v-if="help" class="small text-muted">{{ help }}</em>
<small v-if="subtitle" class="text-muted">{{ subtitle }}</small>
</b-form-group>
</div>
</template>
<script>
export default {
name: "TextAreaInput",
props: {
field: {type: String, default: "You Forgot To Set Field Name"},
label: {type: String, default: "Text Field"},
value: {type: String, 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},
optional: {type: Boolean, default: false},
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
}
},
data() {
return {
new_value: undefined,
}
},
mounted() {
this.new_value = this.value
},
watch: {
new_value: function () {
this.$root.$emit("change", this.field, this.new_value)
},
},
methods: {},
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div>
<b-form-group v-bind:label="label" class="mb-3">
<b-form-group v-bind:label="field_label" class="mb-3">
<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>
@@ -18,13 +18,23 @@ 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 }
disabled: { type: Boolean, default: false },
optional: {type: Boolean, default: false},
},
data() {
return {
new_value: undefined,
}
},
computed: {
field_label: function () {
if (this.optional) {
return this.label
} else {
return this.label + '*'
}
}
},
mounted() {
this.new_value = this.value
},

View File

@@ -50,6 +50,7 @@
<b-form-select-option value="DARKLY">Darkly</b-form-select-option>
<b-form-select-option value="FLATLY">Flatly</b-form-select-option>
<b-form-select-option value="SUPERHERO">Superhero</b-form-select-option>
<b-form-select-option value="TANDOOR_DARK">Tandoor Dark (INCOMPLETE)</b-form-select-option>
</b-form-select>
</b-form-group>

View File

@@ -1,5 +1,5 @@
{
"warning_feature_beta": "",
"warning_feature_beta": "Tato funkce je momentálně ve fázi Beta (testování). Očekávejte, prosím, chyby. V budoucnosti může dojít i ke ztrátě dat.",
"err_fetching_resource": "",
"err_creating_resource": "",
"err_updating_resource": "",
@@ -13,395 +13,395 @@
"success_deleting_resource": "",
"success_moving_resource": "",
"success_merging_resource": "",
"file_upload_disabled": "",
"warning_space_delete": "",
"file_upload_disabled": "Nahrávání souborů není povoleno pro Váš prostor.",
"warning_space_delete": "Můžete smazat váš prostor včetně všech receptů, nákupních seznamů, jídelníčků a všeho ostatního, co jste vytvořili. Tuto akci nemůžete vzít zpět! Jste si jisti, že chcete pokračovat?",
"food_inherit_info": "",
"facet_count_info": "",
"step_time_minutes": "",
"confirm_delete": "",
"import_running": "",
"all_fields_optional": "",
"convert_internal": "",
"show_only_internal": "",
"show_split_screen": "",
"facet_count_info": "Zobraz počet receptů u filtrů vyhledávání.",
"step_time_minutes": "Nastavte čas v minutách",
"confirm_delete": "Jste si jisti že chcete odstranit tento {objekt}?",
"import_running": "Probíhá import, čekejte prosím!",
"all_fields_optional": "Všechna pole jsou nepviná a mohou být ponechána prázdná.",
"convert_internal": "Převést na interní recept",
"show_only_internal": "Zobrazit pouze interní recepty",
"show_split_screen": "Rozdělené zobrazení",
"Log_Recipe_Cooking": "",
"External_Recipe_Image": "",
"Add_to_Shopping": "",
"Add_to_Plan": "",
"Step_start_time": "",
"Sort_by_new": "",
"Table_of_Contents": "",
"External_Recipe_Image": "Externí obrázek receptu",
"Add_to_Shopping": "Přidat k nákupu",
"Add_to_Plan": "Přidat do jídelníčku",
"Step_start_time": "Nastav počáteční čas",
"Sort_by_new": "Seřadit od nejnovějšího",
"Table_of_Contents": "Obsah",
"Recipes_per_page": "Receptů na stránku",
"Show_as_header": "",
"Hide_as_header": "",
"Show_as_header": "Nastav jako nadpis",
"Hide_as_header": "Skryj jako nadpis",
"Add_nutrition_recipe": "Přidat nutriční hodnoty",
"Remove_nutrition_recipe": "Smazat nutriční hodnoty",
"Copy_template_reference": "",
"Save_and_View": "Uložit & Zobrazit",
"Manage_Books": "",
"Manage_Books": "Spravovat kuchařky",
"Meal_Plan": "Jídelníček",
"Select_Book": "",
"Select_Book": "Vyber kuchařku",
"Select_File": "Vybrat soubor",
"Recipe_Image": "",
"Recipe_Image": "Obrázek k receptu",
"Import_finished": "Import dokončen",
"View_Recipes": "Zobrazit recepty",
"Log_Cooking": "",
"New_Recipe": "Nový recept",
"Url_Import": "",
"Reset_Search": "",
"Recently_Viewed": "",
"Load_More": "",
"New_Keyword": "",
"Delete_Keyword": "",
"Edit_Keyword": "",
"Url_Import": "Import pomocí URL odkazu",
"Reset_Search": "Zrušit filtry vyhledávání",
"Recently_Viewed": "Naposledy prohlížené",
"Load_More": "Načíst další",
"New_Keyword": "Nové klíčové slovo",
"Delete_Keyword": "Smazat klíčové slovo",
"Edit_Keyword": "Upravit klíčové slovo",
"Edit_Recipe": "Upravit recept",
"Move_Keyword": "",
"Merge_Keyword": "",
"Hide_Keywords": "",
"Hide_Recipes": "",
"Move_Keyword": "Přesunout klíčové slovo",
"Merge_Keyword": "Sloučit klíčové slovo",
"Hide_Keywords": "Skrýt klíčové slovo",
"Hide_Recipes": "Skrýt recept",
"Move_Up": "Nahoru",
"Move_Down": "Dolů",
"Step_Name": "Název kroku",
"Step_Type": "",
"Make_Header": "",
"Make_Ingredient": "",
"Step_Type": "Druh kroku",
"Make_Header": "Použij jako nadpis",
"Make_Ingredient": "Použij jako ingredienci",
"Amount": "Množství",
"Enable_Amount": "Zobrazit množství",
"Disable_Amount": "Skrýt množství",
"Ingredient Editor": "Editace ingrediencí",
"Description_Replace": "",
"Instruction_Replace": "",
"Auto_Sort": "",
"Auto_Sort_Help": "",
"Private_Recipe": "",
"Private_Recipe_Help": "",
"reusable_help_text": "",
"Add_Step": "",
"Keywords": "",
"Books": "",
"Description_Replace": "Nahraď popis",
"Instruction_Replace": "Nahraď instrukce",
"Auto_Sort": "Automatické řazení",
"Auto_Sort_Help": "Přiřadit všechny ingredience k nejlépe vyhovujícímu kroku.",
"Private_Recipe": "Soukromý recept",
"Private_Recipe_Help": "Recept můžete zobrazit pouze vy a lidé, se kterými jej sdílíte.",
"reusable_help_text": "Má-li pozvánka platit pro více než jednoho uživatele.",
"Add_Step": "Přidat krok",
"Keywords": "Klíčová slova",
"Books": "Kuchařky",
"Proteins": "Proteiny",
"Fats": "Tuky",
"Carbohydrates": "",
"Calories": "",
"Energy": "",
"Carbohydrates": "Sacharidy",
"Calories": "Kalorie",
"Energy": "Energie",
"Nutrition": "",
"Date": "",
"Share": "",
"Automation": "",
"Parameter": "",
"Export": "",
"Copy": "",
"Rating": "",
"Close": "",
"Cancel": "",
"Link": "",
"Add": "",
"New": "",
"Note": "",
"Success": "",
"Failure": "",
"Protected": "",
"Ingredients": "",
"Date": "Datum",
"Share": "Sdílet",
"Automation": "Automatizace",
"Parameter": "Parametr",
"Export": "Export",
"Copy": "Kopírovat",
"Rating": "Hodnocení",
"Close": "Zavřít",
"Cancel": "Zrušit",
"Link": "Odkaz",
"Add": "Přidat",
"New": "Nový",
"Note": "Poznámka",
"Success": "Úspěch",
"Failure": "Selhání",
"Protected": "Chráněný",
"Ingredients": "Ingredience",
"Supermarket": "",
"Categories": "",
"Category": "",
"Selected": "",
"min": "",
"Servings": "",
"Categories": "Kategorie",
"Category": "Kategorie",
"Selected": "Vybrané",
"min": "min",
"Servings": "Porce",
"Waiting": "",
"Preparation": "",
"External": "",
"Size": "",
"Files": "",
"File": "",
"Edit": "",
"Image": "",
"Delete": "",
"Open": "",
"Ok": "",
"Save": "",
"Step": "",
"Search": "",
"Import": "",
"Print": "",
"Settings": "",
"or": "",
"and": "",
"Information": "",
"Download": "",
"Create": "",
"Search Settings": "",
"View": "",
"Recipes": "",
"Move": "",
"Merge": "",
"Parent": "",
"Copy Link": "",
"Copy Token": "",
"delete_confirmation": "",
"move_confirmation": "",
"merge_confirmation": "",
"create_rule": "",
"move_selection": "",
"merge_selection": "",
"Preparation": "Příprava",
"External": "Externí",
"Size": "Velikost",
"Files": "Soubory",
"File": "Soubor",
"Edit": "Upravit",
"Image": "Obrázek",
"Delete": "Smazat",
"Open": "Otevřít",
"Ok": "Ok",
"Save": "Uložit",
"Step": "Krok",
"Search": "Hledat",
"Import": "Import",
"Print": "Tisk",
"Settings": "Nastavení",
"or": "nebo",
"and": "a",
"Information": "Informace",
"Download": "Stáhnout",
"Create": "Vytvořit",
"Search Settings": "Nastavení vyhledávání",
"View": "Zobrazit",
"Recipes": "Recepty",
"Move": "Přesunout",
"Merge": "Spojit",
"Parent": "Nadřazená",
"Copy Link": "Kopírovat odkaz",
"Copy Token": "Kopírovat token",
"delete_confirmation": "Jste si jistí, že chcete smazat {source}?",
"move_confirmation": "Přesunout <i>{child}</i> do nadřazené <i>{parent}</i>",
"merge_confirmation": "Nahradit <i>{source}</i> za <i>{target}</i>",
"create_rule": "a vytvořit automatizaci",
"move_selection": "Vybrat nadřazený {type} kam {source} přesunout.",
"merge_selection": "Nahradit všechny výskyty {source} za vybraný {type}.",
"Root": "",
"Ignore_Shopping": "",
"Shopping_Category": "",
"Shopping_Categories": "",
"Edit_Food": "",
"Move_Food": "",
"New_Food": "",
"Hide_Food": "",
"Food_Alias": "",
"Unit_Alias": "",
"Keyword_Alias": "",
"Delete_Food": "",
"No_ID": "",
"Meal_Plan_Days": "",
"merge_title": "",
"move_title": "",
"Food": "",
"Original_Text": "",
"Recipe_Book": "",
"Ignore_Shopping": "Ignorovat nákupní seznam",
"Shopping_Category": "Kategorie nákupního seznamu",
"Shopping_Categories": "Kategorie nákupního seznamu",
"Edit_Food": "Upravit jídlo",
"Move_Food": "Přesunout jídlo",
"New_Food": "Nové jídlo",
"Hide_Food": "Skrýt jídlo",
"Food_Alias": "Přezdívka jídla",
"Unit_Alias": "Přezdívka jednotky",
"Keyword_Alias": "Přezdívka klíčového slova",
"Delete_Food": "Smazat jídlo",
"No_ID": "ID nenalezeno, odstranění není možné.",
"Meal_Plan_Days": "Budoucí jídelníčky",
"merge_title": "Sloučit {type}",
"move_title": "Přesunout {type}",
"Food": "Jídlo",
"Original_Text": "Původní text",
"Recipe_Book": "Kuchařka",
"del_confirmation_tree": "",
"delete_title": "",
"create_title": "",
"edit_title": "",
"Name": "",
"Type": "",
"Description": "",
"Recipe": "",
"delete_title": "Smazat {type}",
"create_title": "Nový {type}",
"edit_title": "Upravit {type}",
"Name": "Jméno",
"Type": "Typ",
"Description": "Popis",
"Recipe": "Recept",
"tree_root": "",
"Icon": "",
"Unit": "",
"Decimals": "",
"Default_Unit": "",
"No_Results": "",
"New_Unit": "",
"Create_New_Shopping Category": "",
"Create_New_Food": "",
"Create_New_Keyword": "",
"Create_New_Unit": "",
"Create_New_Meal_Type": "",
"Create_New_Shopping_Category": "",
"and_up": "",
"and_down": "",
"Instructions": "",
"Unrated": "",
"Automate": "",
"Empty": "",
"Key_Ctrl": "",
"Key_Shift": "",
"Time": "",
"Text": "",
"Shopping_list": "",
"Added_by": "",
"Icon": "Ikona",
"Unit": "Jednotka",
"Decimals": "Desetinná místa",
"Default_Unit": "Výchozí jednotka",
"No_Results": "Žádné výsledky",
"New_Unit": "Nová jednotka",
"Create_New_Shopping Category": "Vytvořit novou nákupní kategorii",
"Create_New_Food": "Přidat nové jídlo",
"Create_New_Keyword": "Přidat nové klíčové slovo",
"Create_New_Unit": "Přidat novou jednotku",
"Create_New_Meal_Type": "Přidat nový druh jídla",
"Create_New_Shopping_Category": "Přidat novou nákupní kategorii",
"and_up": "a nahoru",
"and_down": "a dolů",
"Instructions": "Instrukce",
"Unrated": "Nehodnocené",
"Automate": "Automatizovat",
"Empty": "Prázdné",
"Key_Ctrl": "Ctrl",
"Key_Shift": "Shift",
"Time": "Čas",
"Text": "Text",
"Shopping_list": "Nákupní seznam",
"Added_by": "Přidáno uživatelem",
"Added_on": "",
"AddToShopping": "",
"IngredientInShopping": "",
"NotInShopping": "",
"OnHand": "",
"FoodOnHand": "",
"FoodNotOnHand": "",
"Undefined": "",
"Create_Meal_Plan_Entry": "",
"Edit_Meal_Plan_Entry": "",
"Title": "",
"Week": "",
"Month": "",
"Year": "",
"Planner": "",
"Planner_Settings": "",
"Period": "",
"Plan_Period_To_Show": "",
"Periods": "",
"Plan_Show_How_Many_Periods": "",
"Starting_Day": "",
"Meal_Types": "",
"Meal_Type": "",
"New_Entry": "",
"Clone": "",
"Drag_Here_To_Delete": "",
"Meal_Type_Required": "",
"Title_or_Recipe_Required": "",
"Color": "",
"New_Meal_Type": "",
"Use_Fractions": "",
"Use_Fractions_Help": "",
"AddFoodToShopping": "",
"RemoveFoodFromShopping": "",
"DeleteShoppingConfirm": "",
"AddToShopping": "Přidat do nákupního seznamu",
"IngredientInShopping": "Tato ingredience je na vašem nákupním seznamu.",
"NotInShopping": "{food} není na vašem nákupním seznamu.",
"OnHand": "Momentálně k dispozici",
"FoodOnHand": "{food} máte k dispozici.",
"FoodNotOnHand": "Nemáte {food} k dispozici.",
"Undefined": "Neurčeno",
"Create_Meal_Plan_Entry": "Vytvořit položku v jídelníčku",
"Edit_Meal_Plan_Entry": "Upravit položku v jídelníčku",
"Title": "Název",
"Week": "Týden",
"Month": "Měsíc",
"Year": "Rok",
"Planner": "Plánovač",
"Planner_Settings": "Nastavení plánovače",
"Period": "Období",
"Plan_Period_To_Show": "Zobrazit týdny, měsíce nebo roky",
"Periods": "Období",
"Plan_Show_How_Many_Periods": "Kolik období bude zobrazeno",
"Starting_Day": "První den v týdnu",
"Meal_Types": "Druhy jídel",
"Meal_Type": "Druh jídla",
"New_Entry": "Nová položka",
"Clone": "Klonovat",
"Drag_Here_To_Delete": "Přesunutím sem smazat",
"Meal_Type_Required": "Druh jídla je povinný",
"Title_or_Recipe_Required": "Je požadován název nebo výběr receptu",
"Color": "Barva",
"New_Meal_Type": "Nový druh jídla",
"Use_Fractions": "Použít zlomky",
"Use_Fractions_Help": "Automaticky převézt desetinná čísla na zlomky při prohlížení repetu.",
"AddFoodToShopping": "Přidat {food} na váš nákupní seznam",
"RemoveFoodFromShopping": "Odstranit {food} z nákupního seznamu",
"DeleteShoppingConfirm": "Jste si jistí, že chcete odstranit všechno {food} z nákupního seznamu?",
"IgnoredFood": "",
"Add_Servings_to_Shopping": "",
"Week_Numbers": "",
"Show_Week_Numbers": "",
"Export_As_ICal": "",
"Export_To_ICal": "",
"Cannot_Add_Notes_To_Shopping": "",
"Added_To_Shopping_List": "",
"Shopping_List_Empty": "",
"Next_Period": "",
"Previous_Period": "",
"Current_Period": "",
"Next_Day": "",
"Previous_Day": "",
"Add_Servings_to_Shopping": "Přidat {servings} porce na nákupní seznam",
"Week_Numbers": "Číslo týdne",
"Show_Week_Numbers": "Zobrazit čísla týdnů?",
"Export_As_ICal": "Exportovat současný úsek do formátu iCal",
"Export_To_ICal": "Export ovat .ics",
"Cannot_Add_Notes_To_Shopping": "Poznámky nemohou být přidány na nákupní seznam",
"Added_To_Shopping_List": "Přidáno na nákupní seznam",
"Shopping_List_Empty": "Váš nákupní seznam je momentálně prázdný, můžete přidat položky pomocí kontextového menu záznamu v jídelníčku (pravým kliknutím na kartu nebo levým kliknutím na ikonu menu)",
"Next_Period": "Další období",
"Previous_Period": "Předchozí období",
"Current_Period": "Současné období",
"Next_Day": "Následující den",
"Previous_Day": "Předchozí den",
"Inherit": "",
"InheritFields": "",
"FoodInherit": "",
"ShowUncategorizedFood": "",
"GroupBy": "",
"Language": "",
"Theme": "",
"ShowUncategorizedFood": "Zobrazit nedefinované",
"GroupBy": "Seskupit podle",
"Language": "Jazyk",
"Theme": "Téma",
"SupermarketCategoriesOnly": "",
"MoveCategory": "",
"CountMore": "",
"IgnoreThis": "",
"DelayFor": "",
"Warning": "",
"NoCategory": "",
"MoveCategory": "Předunout do: ",
"CountMore": "...+{count} víc",
"IgnoreThis": "Nikdy automaticky nepřídávat {food} na nákupní seznam",
"DelayFor": "Odložit na {hours} hodin",
"Warning": "Varování",
"NoCategory": "Není vybrána žádná kategorie.",
"InheritWarning": "",
"ShowDelayed": "",
"Completed": "",
"OfflineAlert": "",
"shopping_share": "",
"shopping_auto_sync": "",
"one_url_per_line": "",
"mealplan_autoadd_shopping": "",
"mealplan_autoexclude_onhand": "",
"mealplan_autoinclude_related": "",
"default_delay": "",
"plan_share_desc": "",
"shopping_share_desc": "",
"shopping_auto_sync_desc": "",
"mealplan_autoadd_shopping_desc": "",
"mealplan_autoexclude_onhand_desc": "",
"mealplan_autoinclude_related_desc": "",
"default_delay_desc": "",
"ShowDelayed": "Zobrazit odložené položky",
"Completed": "Dokončeno",
"OfflineAlert": "Jste offline, nákupní seznam nemusí být synchronizován.",
"shopping_share": "Sdílet nákupní seznam",
"shopping_auto_sync": "Automatická synchronizace",
"one_url_per_line": "Jeden URL odkaz na řádek",
"mealplan_autoadd_shopping": "Automaticky přidat jídelníček",
"mealplan_autoexclude_onhand": "Nezahrnovat jídlo k dispozici",
"mealplan_autoinclude_related": "Přidat podobné recepty",
"default_delay": "Výchozí doba odložení v hodinách",
"plan_share_desc": "Nové položky v jídelníčku budou automaticky sdíleny s vybranými uživateli.",
"shopping_share_desc": "Uživatelé uvidí všechny položky na vašem nákupním seznamu. Abyste viděli položky na jejich seznamu, musí si přidat vás.",
"shopping_auto_sync_desc": "Nastavením 0 dojde k vypnutí automatické synchronizace. Při prohlížení nákupního seznamu je vždy po uplynutí nastaveného počtu vteřin aktualizován o změny, které mohli provést jiní uživatelé. To je užitečné, pokud nakupujete ve více lidech, ale může používat více dat.",
"mealplan_autoadd_shopping_desc": "Automaticky podle jídelníčku přidat ingredience na nákupní seznam.",
"mealplan_autoexclude_onhand_desc": "Nepřidávat ingredience, které jsou k dispozici, na nákupní seznam, když je vytvořen podle jídelníčku (manuálně nebo automaticky).",
"mealplan_autoinclude_related_desc": "Když je nákupní seznam vytvořen podle jídelníčku, přidat i položky z přidružených receptů.",
"default_delay_desc": "Výchozí odložení položek v nákupním seznamu v hodinách.",
"filter_to_supermarket": "",
"Coming_Soon": "",
"Auto_Planner": "",
"New_Cookbook": "",
"Hide_Keyword": "",
"Hour": "",
"Hours": "",
"Day": "",
"Days": "",
"Second": "",
"Seconds": "",
"Clear": "",
"Users": "",
"Invites": "",
"err_move_self": "",
"nothing": "",
"err_merge_self": "",
"show_sql": "",
"filter_to_supermarket_desc": "",
"CategoryName": "",
"SupermarketName": "",
"CategoryInstruction": "",
"Coming_Soon": "Již brzy",
"Auto_Planner": "Automatický plánovač",
"New_Cookbook": "Nová kuchařka",
"Hide_Keyword": "Skrýt klíčová slova",
"Hour": "Hodina",
"Hours": "Hodiny",
"Day": "Den",
"Days": "Dny",
"Second": "Vteřina",
"Seconds": "Vteřiny",
"Clear": "Vyčistit",
"Users": "Uživatelé",
"Invites": "Pozvánky",
"err_move_self": "Není možné přesunout jednu položku do sebe samé",
"nothing": "Není co dělat",
"err_merge_self": "Není možné sloučit položku samu se sebou",
"show_sql": "Zobrazit SQL",
"filter_to_supermarket_desc": "Standartně zobrazovat položky v nákupním seznamu pouze pro vybraný obchod.",
"CategoryName": "Název kategorie",
"SupermarketName": "Název obchodu",
"CategoryInstruction": "Přetáhnutím kategorií změníte pořadí, ve kterém se zobrazují v nákupním seznamu.",
"shopping_recent_days_desc": "",
"shopping_recent_days": "",
"download_pdf": "",
"download_csv": "",
"csv_delim_help": "",
"csv_delim_label": "",
"SuccessClipboard": "",
"copy_to_clipboard": "",
"shopping_recent_days": "Nedávné dny",
"download_pdf": "Stáhnout PDF",
"download_csv": "Stáhnout CSV",
"csv_delim_help": "Který znak bude použit pro oddělení záznamů v CSV.",
"csv_delim_label": "Oddělovač záznamů v CSV",
"SuccessClipboard": "Nákupní seznam byl zkopírován do schránky",
"copy_to_clipboard": "Zkopírovat do schránky",
"csv_prefix_help": "",
"csv_prefix_label": "",
"copy_markdown_table": "",
"in_shopping": "",
"DelayUntil": "",
"Pin": "",
"Unpin": "",
"PinnedConfirmation": "",
"UnpinnedConfirmation": "",
"mark_complete": "",
"QuickEntry": "",
"shopping_add_onhand_desc": "",
"shopping_add_onhand": "",
"related_recipes": "",
"today_recipes": "",
"sql_debug": "",
"remember_search": "",
"remember_hours": "",
"tree_select": "",
"OnHand_help": "",
"ignore_shopping_help": "",
"shopping_category_help": "",
"copy_markdown_table": "Kopírovat jako Markdown tabulku",
"in_shopping": "V nákupním seznamu",
"DelayUntil": "Odložit do",
"Pin": "Připnout",
"Unpin": "Odepnout",
"PinnedConfirmation": "{recipe} byl připnut.",
"UnpinnedConfirmation": "{recipe} byl odepnut.",
"mark_complete": "Označit jako hotové",
"QuickEntry": "Rychlý záznam",
"shopping_add_onhand_desc": "Označit potravinu jako \"K dispozici\" když je odškrtnuta na nákupním seznamu.",
"shopping_add_onhand": "Automaticky K dispozici",
"related_recipes": "Související recepty",
"today_recipes": "Dnešní recepty",
"sql_debug": "SQL Debug",
"remember_search": "Pamatovat vyhledávání",
"remember_hours": "Kolik hodin pamatovat",
"tree_select": "Použít stromový výběr",
"OnHand_help": "Potravina je v inventáři a nebude automaticky přidána na nákupní seznam. Status \"k dipozici\" je sdílen s nakupujícími uživateli.",
"ignore_shopping_help": "Nikdy nepřidávat potravinu na nákupní seznam (např. voda)",
"shopping_category_help": "Obchody mohou být seřazeny a třízeny pomocí nákupních kategorií podle rozvržení uliček a regálů.",
"food_recipe_help": "",
"Foods": "",
"Account": "",
"Cosmetic": "",
"Foods": "Potraviny",
"Account": "Účet",
"Cosmetic": "Zobrazení",
"API": "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": "",
"enable_expert": "Povolit Expertní režim",
"expert_mode": "Expertní režim",
"simple_mode": "Jednoduchý režim",
"advanced": "Pokročilé",
"fields": "Pole",
"show_keywords": "Zobrazit klíčová slova",
"show_foods": "Zobrazit potraviny",
"show_books": "Zobrazit kuchařky",
"show_rating": "Zobrazit hodnocení",
"show_units": "Zobrazit jednotky",
"show_filters": "Zobrazit filtry",
"not": "ne",
"save_filter": "Uložit filtr",
"filter_name": "Název filtru",
"left_handed": "Režim pro leváky",
"left_handed_help": "Optimalizuje uživatelské prostředí pro levou ruku.",
"Custom Filter": "Uživatelský filtr",
"shared_with": "Sdíleno s",
"sort_by": "Seřadit podle",
"asc": "Vzestupně",
"desc": "Sestupně",
"date_viewed": "",
"last_cooked": "",
"times_cooked": "",
"date_created": "",
"show_sortby": "",
"date_viewed": "Poslední prohlížené",
"last_cooked": "Naposledy vařeno",
"times_cooked": "Kolkrát vařeno",
"date_created": "Datum vytvoření",
"show_sortby": "Zobrazit Seřazeno podle",
"search_rank": "",
"make_now": "",
"recipe_filter": "Filtrovat recepty",
"book_filter_help": "",
"review_shopping": "",
"book_filter_help": "Zahrnout i recepty z filtru stejně jako manuálně přidané.",
"review_shopping": "Zkontrolovat nákupní položky před uložením",
"view_recipe": "Zobrazit recept",
"copy_to_new": "",
"copy_to_new": "Zkopírovat do nového receptu",
"recipe_name": "Název receptu",
"paste_ingredients_placeholder": "",
"paste_ingredients": "",
"ingredient_list": "",
"explain": "",
"paste_ingredients_placeholder": "Zde vložit seznam ingrediencí.",
"paste_ingredients": "Vložit ingredince",
"ingredient_list": "Seznam ingrediencí",
"explain": "Vysvětlit",
"filter": "Filtr",
"Website": "Web",
"App": "Aplikace",
"Message": "",
"Bookmarklet": "",
"Sticky_Nav": "",
"Sticky_Nav_Help": "",
"Nav_Color": "",
"Nav_Color_Help": "",
"Message": "Zpráva",
"Bookmarklet": "Bookmarklet",
"Sticky_Nav": "Připnout navigační panel",
"Sticky_Nav_Help": "Vždy zobrazit navigační panel na vrchu stránky.",
"Nav_Color": "Barva navigačního panelu",
"Nav_Color_Help": "Zmenit barvu navigačního panelu.",
"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": "",
"search_no_recipes": "",
"search_import_help_text": "",
"search_create_help_text": "",
"warning_duplicate_filter": "",
"import_duplicates": "Aby bylo zamezeno duplicitním receptům, recepty se stejným jménem jako již existující jsou ignorovány. Pokud chcete přidat všechno, zaškrtněte toto políčko.",
"paste_json": "Sem zkopírujte zdroj ve formátu json nebo html.",
"Click_To_Edit": "Kliknutím editovat",
"search_no_recipes": "Nebyly nealezeny žádné recepty!",
"search_import_help_text": "Importovat recept z externí webové stránky nebo aplikace.",
"search_create_help_text": "Vytvořit nový recept přímo v Tandoor.",
"warning_duplicate_filter": "Varování: Kvůli technickým omezení může použití několika filtrů se stejnou kombinací (a/nebo/ne) přinést neočekávaný výsledek.",
"reset_children": "",
"reset_children_help": "",
"reset_food_inheritance": "",
"reset_food_inheritance_info": "",
"substitute_help": "",
"substitute_siblings_help": "",
"substitute_children_help": "",
"substitute_help": "Při hledání podle ingrediencí, které jsou k dispozici, jsou zvažovány náhrady.",
"substitute_siblings_help": "Všechny potraviny, které sdílejí nadřazenou položku jsou považovány za náhrady.",
"substitute_children_help": "Všechny potraviny, které jsou podřízeny této, jsou považovány za náhražky.",
"substitute_siblings": "",
"substitute_children": "",
"SubstituteOnHand": "",
"SubstituteOnHand": "Máte k dispozici náhradu.",
"ChildInheritFields": "",
"ChildInheritFields_help": "",
"InheritFields_help": "",
@@ -478,5 +478,21 @@
"Use_Plural_Food_Simple": "",
"plural_usage_info": "",
"Create Recipe": "Vytvořit recept",
"Import Recipe": "Importovat recept"
"Import Recipe": "Importovat recept",
"per_serving": "na porci",
"open_data_help_text": "Projekt Tandoor Open Data nabízí komunitou poskytnutá data pro Tandoor. Toto pole je automaticky vyplněno při importu a může být později upraveno.",
"Data_Import_Info": "Rozšiřte svůj prostor o seznamy jídel, jednotek a další spravované komunitou, a vylepšete tak svoji sbírku receptů.",
"Update_Existing_Data": "Aktualizovat existující data",
"Use_Metric": "Používat metrické jednotky",
"Learn_More": "Zjistit víc",
"converted_unit": "Převedená jendotka",
"converted_amount": "Převedené množství",
"base_unit": "Základní jednotka",
"base_amount": "Základní množství",
"Datatype": "Datový typ",
"Number of Objects": "Počet Objektů",
"Property": "Vlastnost",
"Conversion": "Převod",
"Properties": "Vlastnosti",
"recipe_property_info": "Můžete také přidávat vlastnosti k Vašim jídlům. Hodnoty budou automaticky přepočteny na základě Vašeho receptu!"
}

View File

@@ -258,12 +258,14 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
icon: {
form_field: true,
type: "emoji",
field: "icon",
label: "Icon",
optional: true,
},
full_name: {
form_field: true,
@@ -295,6 +297,7 @@ export class Models {
field: "plural_name",
label: "Plural name",
placeholder: "",
optional: true,
},
description: {
form_field: true,
@@ -302,6 +305,7 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
open_data_slug: {
form_field: true,
@@ -310,6 +314,7 @@ export class Models {
disabled: true,
label: "Open_Data_Slug",
help_text: "open_data_help_text",
optional: true,
},
},
},
@@ -364,12 +369,14 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
icon: {
form_field: true,
type: "emoji",
field: "icon",
label: "Icon",
optional: true,
},
filter: {
form_field: true,
@@ -377,6 +384,7 @@ export class Models {
field: "filter",
label: "Custom Filter",
list: "CUSTOM_FILTER",
optional: true,
},
},
},
@@ -401,6 +409,7 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
},
},
@@ -451,6 +460,7 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
categories: {
form_field: true,
@@ -461,6 +471,7 @@ export class Models {
field: "category_to_supermarket",
label: "Categories", // form.label always translated in utils.getForm()
placeholder: "",
optional: true,
},
open_data_slug: {
form_field: true,
@@ -469,6 +480,7 @@ export class Models {
disabled: true,
label: "Open_Data_Slug",
help_text: "open_data_help_text",
optional: true,
},
},
config: {
@@ -507,6 +519,7 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
type: {
form_field: true,
@@ -652,6 +665,7 @@ export class Models {
disabled: true,
label: "Open_Data_Slug",
help_text: "open_data_help_text",
optional: true,
},
},
@@ -685,6 +699,7 @@ export class Models {
field: "icon",
label: "Icon",
placeholder: "",
optional: true,
},
unit: {
form_field: true,
@@ -692,6 +707,7 @@ export class Models {
field: "unit",
label: "Unit",
placeholder: "",
optional: true,
},
description: {
form_field: true,
@@ -699,6 +715,7 @@ export class Models {
field: "description",
label: "Description",
placeholder: "",
optional: true,
},
open_data_slug: {
form_field: true,
@@ -707,6 +724,7 @@ export class Models {
disabled: true,
label: "Open_Data_Slug",
help_text: "open_data_help_text",
optional: true,
},
},
@@ -853,15 +871,8 @@ export class Models {
apiName: "InviteLink",
paginated: false,
create: {
params: [["email", "group", "valid_until", "reusable"]],
params: [["email", "group", "valid_until", "reusable", "internal_note"]],
form: {
email: {
form_field: true,
type: "text",
field: "email",
label: "Email",
placeholder: "",
},
group: {
form_field: true,
type: "lookup",
@@ -878,6 +889,14 @@ export class Models {
label: "Valid Until",
placeholder: "",
},
email: {
form_field: true,
type: "text",
field: "email",
label: "Email",
placeholder: "",
optional: true,
},
reusable: {
form_field: true,
type: "checkbox",
@@ -886,6 +905,15 @@ export class Models {
help_text: "reusable_help_text",
placeholder: "",
},
internal_note: {
form_field: true,
type: "textarea",
field: "internal_note",
label: "Note",
placeholder: "",
optional: true,
},
form_function: "InviteLinkDefaultValid",
},
},
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,6 +14,7 @@ import {BToast} from "bootstrap-vue"
// * */
import Vue from "vue"
import {Actions, Models} from "./models"
import moment from "moment";
export const ToastMixin = {
name: "ToastMixin",
@@ -721,6 +722,10 @@ export const formFunctions = {
form.fields.filter((x) => x.field === "inherit_fields")[0].value = getUserPreference("food_inherit_default")
return form
},
InviteLinkDefaultValid: function (form){
form.fields.filter((x) => x.field === "valid_until")[0].value = moment().add(7, "days").format('yyyy-MM-DD')
return form
},
AutomationOrderDefault: function (form) {
if (form.fields.filter((x) => x.field === "order")[0].value === undefined) {
form.fields.filter((x) => x.field === "order")[0].value = 1000