diff --git a/cookbook/locale/cs/LC_MESSAGES/django.po b/cookbook/locale/cs/LC_MESSAGES/django.po index 513d8c04..d028da3b 100644 --- a/cookbook/locale/cs/LC_MESSAGES/django.po +++ b/cookbook/locale/cs/LC_MESSAGES/django.po @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-02-09 18:01+0100\n" -"PO-Revision-Date: 2023-07-31 14:19+0000\n" -"Last-Translator: Mára Štěpánek \n" +"PO-Revision-Date: 2024-01-09 12:07+0000\n" +"Last-Translator: Jan Kubošek \n" "Language-Team: Czech \n" "Language: cs\n" @@ -190,7 +190,7 @@ msgid "" "Leave empty for dropbox and enter only base url for nextcloud " "(/remote.php/webdav/ is added automatically)" msgstr "" -"Pro dropbox ponechejte nevyplňené pole. Pro nextcloud použijte pouze " +"Pro dropbox ponechejte nevyplněné pole. Pro nextcloud použijte pouze " "základní url (/remote.php/webdav/ bude přidán automaticky)." #: .\cookbook\forms.py:263 @@ -529,7 +529,7 @@ msgstr "Dávková úprava receptu" #: .\cookbook\templates\batch\edit.html:20 msgid "Add the specified keywords to all recipes containing a word" -msgstr "Přidat štítek ke všem receptům, které obsahují specifické slovo." +msgstr "Přidat štítek ke všem receptům, které obsahují specifické slovo" #: .\cookbook\templates\batch\monitor.html:6 .\cookbook\views\edit.py:66 msgid "Sync" diff --git a/cookbook/migrations/0208_space_app_name_userpreference_max_owned_spaces.py b/cookbook/migrations/0208_space_app_name_userpreference_max_owned_spaces.py new file mode 100644 index 00000000..44837d48 --- /dev/null +++ b/cookbook/migrations/0208_space_app_name_userpreference_max_owned_spaces.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.7 on 2024-01-14 23:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0207_space_logo_color_128_space_logo_color_144_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='space', + name='app_name', + field=models.CharField(blank=True, max_length=40, null=True), + ), + migrations.AddField( + model_name='userpreference', + name='max_owned_spaces', + field=models.IntegerField(default=100), + ), + ] diff --git a/cookbook/migrations/0208_connectorconfig.py b/cookbook/migrations/0209_connectorconfig.py similarity index 93% rename from cookbook/migrations/0208_connectorconfig.py rename to cookbook/migrations/0209_connectorconfig.py index c29c0253..4f672e81 100644 --- a/cookbook/migrations/0208_connectorconfig.py +++ b/cookbook/migrations/0209_connectorconfig.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.7 on 2024-01-17 21:12 +# Generated by Django 4.2.7 on 2024-01-17 21:49 import cookbook.models from django.conf import settings @@ -11,7 +11,7 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('cookbook', '0207_space_logo_color_128_space_logo_color_144_and_more'), + ('cookbook', '0208_space_app_name_userpreference_max_owned_spaces'), ] operations = [ diff --git a/cookbook/models.py b/cookbook/models.py index f0bc1af6..5cb6fa00 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -24,7 +24,7 @@ from PIL import Image from treebeard.mp_tree import MP_Node, MP_NodeManager from recipes.settings import (COMMENT_PREF_DEFAULT, FRACTION_PREF_DEFAULT, KJ_PREF_DEFAULT, - SORT_TREE_BY_NAME, STICKY_NAV_PREF_DEFAULT) + SORT_TREE_BY_NAME, STICKY_NAV_PREF_DEFAULT, MAX_OWNED_SPACES_PREF_DEFAULT) def get_user_display_name(self): @@ -288,7 +288,7 @@ class Space(ExportModelOperationsMixin('space'), models.Model): nav_logo = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_nav_logo') nav_bg_color = models.CharField(max_length=8, default='', blank=True, ) nav_text_color = models.CharField(max_length=16, choices=NAV_TEXT_COLORS, default=BLANK) - + app_name = models.CharField(max_length=40, null=True, blank=True, ) logo_color_32 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_32') logo_color_128 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_128') logo_color_144 = models.ForeignKey("UserFile", on_delete=models.SET_NULL, null=True, blank=True, related_name='space_logo_color_144') @@ -435,7 +435,7 @@ class UserPreference(models.Model, PermissionModelMixin): nav_text_color = models.CharField(max_length=16, choices=NAV_TEXT_COLORS, default=DARK) nav_show_logo = models.BooleanField(default=True) nav_sticky = models.BooleanField(default=STICKY_NAV_PREF_DEFAULT) - + max_owned_spaces = models.IntegerField(default=MAX_OWNED_SPACES_PREF_DEFAULT) default_unit = models.CharField(max_length=32, default='g') use_fractions = models.BooleanField(default=FRACTION_PREF_DEFAULT) use_kj = models.BooleanField(default=KJ_PREF_DEFAULT) @@ -460,6 +460,15 @@ class UserPreference(models.Model, PermissionModelMixin): created_at = models.DateTimeField(auto_now_add=True) objects = ScopedManager(space='space') + def save(self, *args, **kwargs): + if not self.pk: + self.max_owned_spaces = MAX_OWNED_SPACES_PREF_DEFAULT + self.comments = COMMENT_PREF_DEFAULT + self.nav_sticky = STICKY_NAV_PREF_DEFAULT + self.use_kj = KJ_PREF_DEFAULT + self.use_fractions = FRACTION_PREF_DEFAULT + + return super().save(*args, **kwargs) def __str__(self): return str(self.user) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index e102c46f..f883a3f2 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -686,9 +686,8 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR properties = validated_data.pop('properties', None) - obj, created = Food.objects.get_or_create( - name=name, plural_name=plural_name, space=space, properties_food_unit=properties_food_unit, - defaults=validated_data) + obj, created = Food.objects.get_or_create(name=name, plural_name=plural_name, space=space, properties_food_unit=properties_food_unit, + defaults=validated_data) if properties and len(properties) > 0: for p in properties: @@ -1276,9 +1275,8 @@ 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: + 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( @@ -1433,15 +1431,12 @@ class RecipeExportSerializer(WritableNestedModelSerializer): class RecipeShoppingUpdateSerializer(serializers.ModelSerializer): - list_recipe = serializers.IntegerField( - write_only=True, allow_null=True, required=False, - help_text=_("Existing shopping list to update")) - ingredients = serializers.IntegerField( - write_only=True, allow_null=True, required=False, help_text=_( - "List of ingredient IDs from the recipe to add, if not provided all ingredients will be added.")) - servings = serializers.IntegerField( - default=1, write_only=True, allow_null=True, required=False, help_text=_( - "Providing a list_recipe ID and servings of 0 will delete that shopping list.")) + list_recipe = serializers.IntegerField(write_only=True, allow_null=True, required=False, + help_text=_("Existing shopping list to update")) + ingredients = serializers.IntegerField(write_only=True, allow_null=True, required=False, help_text=_( + "List of ingredient IDs from the recipe to add, if not provided all ingredients will be added.")) + servings = serializers.IntegerField(default=1, write_only=True, allow_null=True, required=False, help_text=_( + "Providing a list_recipe ID and servings of 0 will delete that shopping list.")) class Meta: model = Recipe @@ -1449,15 +1444,12 @@ class RecipeShoppingUpdateSerializer(serializers.ModelSerializer): class FoodShoppingUpdateSerializer(serializers.ModelSerializer): - amount = serializers.IntegerField( - write_only=True, allow_null=True, required=False, - help_text=_("Amount of food to add to the shopping list")) - unit = serializers.IntegerField( - write_only=True, allow_null=True, required=False, - help_text=_("ID of unit to use for the shopping list")) - delete = serializers.ChoiceField( - choices=['true'], write_only=True, allow_null=True, allow_blank=True, - help_text=_("When set to true will delete all food from active shopping lists.")) + amount = serializers.IntegerField(write_only=True, allow_null=True, required=False, + help_text=_("Amount of food to add to the shopping list")) + unit = serializers.IntegerField(write_only=True, allow_null=True, required=False, + help_text=_("ID of unit to use for the shopping list")) + delete = serializers.ChoiceField(choices=['true'], write_only=True, allow_null=True, allow_blank=True, + help_text=_("When set to true will delete all food from active shopping lists.")) class Meta: model = Recipe diff --git a/cookbook/templates/list_connectors.html b/cookbook/templates/list_connectors.html deleted file mode 100644 index 283f7c31..00000000 --- a/cookbook/templates/list_connectors.html +++ /dev/null @@ -1,55 +0,0 @@ -{% extends "base.html" %} -{% load crispy_forms_tags %} -{% load i18n %} -{% load django_tables2 %} - -{% block title %}{% trans 'List' %}{% endblock %} - - -{% block content %} - -{% if request.resolver_match.url_name in 'list_storage,list_recipe_import,list_sync_log' %} - -{% endif %} - -
- -

{{ title }} {% trans 'List' %}

-
- - {% if filter %} -
-
-
- {% csrf_token %} - {{ filter.form|crispy }} - -
- {% endif %} - - {% if import_btn %} - {% trans 'Import all' %} -
-
- {% endif %} - - {% for table in tables %} - -

{{ table.attrs.table_name }} {% trans 'List' %} - {% if table.attrs.create_url %} - - {% endif %} -

-
- - {% render_table table %} - {% endfor %} - -
- -{% endblock content %} \ No newline at end of file diff --git a/cookbook/templatetags/theming_tags.py b/cookbook/templatetags/theming_tags.py index 68695082..d69ae7b0 100644 --- a/cookbook/templatetags/theming_tags.py +++ b/cookbook/templatetags/theming_tags.py @@ -10,17 +10,17 @@ register = template.Library() @register.simple_tag def theme_values(request): + return get_theming_values(request) + + +def get_theming_values(request): space = None - if space in request and request.space: + if getattr(request,'space',None): space = request.space if not request.user.is_authenticated and UNAUTHENTICATED_THEME_FROM_SPACE > 0: with scopes_disabled(): space = Space.objects.filter(id=UNAUTHENTICATED_THEME_FROM_SPACE).first() - return get_theming_values(space, request.user) - - -def get_theming_values(space, user): themes = { UserPreference.BOOTSTRAP: 'themes/bootstrap.min.css', UserPreference.FLATLY: 'themes/flatly.min.css', @@ -46,31 +46,34 @@ def get_theming_values(space, user): 'nav_bg_color': '#ddbf86', 'nav_text_class': 'navbar-light', 'sticky_nav': 'position: sticky; top: 0; left: 0; z-index: 1000;', + 'app_name': 'Tandoor Recipes', } - - if user.is_authenticated: - if user.userpreference.theme in themes: - tv['theme'] = static(themes[user.userpreference.theme]) - if user.userpreference.nav_bg_color: - tv['nav_bg_color'] = user.userpreference.nav_bg_color - if user.userpreference.nav_text_color and user.userpreference.nav_text_color in nav_text_type_mapping: - tv['nav_text_class'] = nav_text_type_mapping[user.userpreference.nav_text_color] - if not user.userpreference.nav_sticky: + if request.user.is_authenticated: + if request.user.userpreference.theme in themes: + tv['theme'] = static(themes[request.user.userpreference.theme]) + if request.user.userpreference.nav_bg_color: + tv['nav_bg_color'] = request.user.userpreference.nav_bg_color + if request.user.userpreference.nav_text_color and request.user.userpreference.nav_text_color in nav_text_type_mapping: + tv['nav_text_class'] = nav_text_type_mapping[request.user.userpreference.nav_text_color] + if not request.user.userpreference.nav_sticky: tv['sticky_nav'] = '' if space: for logo in list(tv.keys()): if logo.startswith('logo_color_') and getattr(space, logo, None): tv[logo] = getattr(space, logo).file.url - if space.custom_space_theme: - tv['custom_theme'] = space.custom_space_theme.file.url - if space.space_theme in themes: - tv['theme'] = static(themes[space.space_theme]) - if space.nav_logo: - tv['nav_logo'] = space.nav_logo.file.url - if space.nav_bg_color: - tv['nav_bg_color'] = space.nav_bg_color - if space.nav_text_color and space.nav_text_color in nav_text_type_mapping: - tv['nav_text_class'] = nav_text_type_mapping[space.nav_text_color] + + if space.custom_space_theme: + tv['custom_theme'] = space.custom_space_theme.file.url + if space.space_theme in themes: + tv['theme'] = static(themes[space.space_theme]) + if space.nav_logo: + tv['nav_logo'] = space.nav_logo.file.url + if space.nav_bg_color: + tv['nav_bg_color'] = space.nav_bg_color + if space.nav_text_color and space.nav_text_color in nav_text_type_mapping: + tv['nav_text_class'] = nav_text_type_mapping[space.nav_text_color] + if space.app_name: + tv['app_name'] = space.app_name return tv diff --git a/cookbook/tests/api/test_api_home_assistant_config.py b/cookbook/tests/api/test_api_connector_config.py similarity index 100% rename from cookbook/tests/api/test_api_home_assistant_config.py rename to cookbook/tests/api/test_api_connector_config.py diff --git a/cookbook/tests/other/test_theming.py b/cookbook/tests/other/test_theming.py index d9aa0189..7abaccda 100644 --- a/cookbook/tests/other/test_theming.py +++ b/cookbook/tests/other/test_theming.py @@ -1,36 +1,68 @@ from django.contrib import auth from django.templatetags.static import static +from django.test import RequestFactory +from django_scopes import scopes_disabled from cookbook.models import Space, UserPreference, UserFile from cookbook.templatetags.theming_tags import theme_values, get_theming_values def test_theming_function(space_1, u1_s1): - user = auth.get_user(u1_s1) + # uf = UserFile.objects.create(name='test', space=space_1, created_by=user) #TODO add file tests + user = auth.get_user(u1_s1) + request = RequestFactory() + request.user = auth.get_user(u1_s1) + request.space = space_1 - assert get_theming_values(space_1, user)['theme'] == static('themes/tandoor.min.css') - assert get_theming_values(space_1, user)['nav_bg_color'] == '#ddbf86' - assert get_theming_values(space_1, user)['nav_text_class'] == 'navbar-light' - assert get_theming_values(space_1, user)['nav_logo'] == static('assets/brand_logo.png') - assert get_theming_values(space_1, user)['sticky_nav'] == 'position: sticky; top: 0; left: 0; z-index: 1000;' + # defaults apply without setting anything (user preference is automatically created with these defaults) + assert get_theming_values(request)['theme'] == static('themes/tandoor.min.css') + assert get_theming_values(request)['nav_bg_color'] == '#ddbf86' + assert get_theming_values(request)['nav_text_class'] == 'navbar-light' + assert get_theming_values(request)['nav_logo'] == static('assets/brand_logo.png') + assert get_theming_values(request)['sticky_nav'] == 'position: sticky; top: 0; left: 0; z-index: 1000;' + assert get_theming_values(request)['app_name'] == 'Tandoor Recipes' - user.userpreference.theme = UserPreference.TANDOOR_DARK - user.userpreference.nav_bg_color = '#ffffff' - user.userpreference.nav_text_color = UserPreference.LIGHT - user.userpreference.nav_sticky = False - user.userpreference.save() + with scopes_disabled(): + up = UserPreference.objects.filter(user=request.user).first() + up.theme = UserPreference.TANDOOR_DARK + up.nav_bg_color = '#ffffff' + up.nav_text_color = UserPreference.LIGHT + up.nav_sticky = False + up.save() - assert get_theming_values(space_1, user)['theme'] == static('themes/tandoor_dark.min.css') - assert get_theming_values(space_1, user)['nav_bg_color'] == '#ffffff' - assert get_theming_values(space_1, user)['nav_text_class'] == 'navbar-dark' - assert get_theming_values(space_1, user)['sticky_nav'] == '' + request = RequestFactory() + request.user = auth.get_user(u1_s1) + request.space = space_1 + + # user values apply if only those are present + assert get_theming_values(request)['theme'] == static('themes/tandoor_dark.min.css') + assert get_theming_values(request)['nav_bg_color'] == '#ffffff' + assert get_theming_values(request)['nav_text_class'] == 'navbar-dark' + assert get_theming_values(request)['sticky_nav'] == '' + assert get_theming_values(request)['app_name'] == 'Tandoor Recipes' space_1.space_theme = Space.BOOTSTRAP space_1.nav_bg_color = '#000000' space_1.nav_text_color = UserPreference.DARK + space_1.app_name = 'test_app_name' space_1.save() - assert get_theming_values(space_1, user)['theme'] == static('themes/bootstrap.min.css') - assert get_theming_values(space_1, user)['nav_bg_color'] == '#000000' - assert get_theming_values(space_1, user)['nav_text_class'] == 'navbar-light' + request = RequestFactory() + request.user = auth.get_user(u1_s1) + request.space = space_1 + + # space settings apply when set + assert get_theming_values(request)['theme'] == static('themes/bootstrap.min.css') + assert get_theming_values(request)['nav_bg_color'] == '#000000' + assert get_theming_values(request)['nav_text_class'] == 'navbar-light' + assert get_theming_values(request)['app_name'] == 'test_app_name' + + user.userspace_set.all().delete() + request = RequestFactory() + request.user = auth.get_user(u1_s1) + + # default user settings should apply when user has no space + assert get_theming_values(request)['nav_bg_color'] == '#ffffff' + assert get_theming_values(request)['nav_text_class'] == 'navbar-dark' + assert get_theming_values(request)['nav_logo'] == static('assets/brand_logo.png') diff --git a/cookbook/urls.py b/cookbook/urls.py index 54d29ae4..d7512664 100644 --- a/cookbook/urls.py +++ b/cookbook/urls.py @@ -51,7 +51,7 @@ router.register(r'shopping-list-recipe', api.ShoppingListRecipeViewSet) router.register(r'space', api.SpaceViewSet) router.register(r'step', api.StepViewSet) router.register(r'storage', api.StorageViewSet) -router.register(r'home-assistant-config', api.ConnectorConfigConfigViewSet) +router.register(r'connector-config', api.ConnectorConfigConfigViewSet) router.register(r'supermarket', api.SupermarketViewSet) router.register(r'supermarket-category', api.SupermarketCategoryViewSet) router.register(r'supermarket-category-relation', api.SupermarketCategoryRelationViewSet) diff --git a/cookbook/views/lists.py b/cookbook/views/lists.py index 53b1ae79..32389d19 100644 --- a/cookbook/views/lists.py +++ b/cookbook/views/lists.py @@ -87,12 +87,11 @@ def invite_link(request): InviteLink.objects.filter(valid_until__gte=datetime.today(), used_by=None, space=request.space).all()) RequestConfig(request, paginate={'per_page': 25}).configure(table) - return render( - request, 'generic/list_template.html', { - 'title': _("Invite Links"), - 'table': table, - 'create_url': 'new_invite_link' - }) + return render(request, 'generic/list_template.html', { + 'title': _("Invite Links"), + 'table': table, + 'create_url': 'new_invite_link' + }) @group_required('user') @@ -212,7 +211,7 @@ def custom_filter(request): def user_file(request): try: current_file_size_mb = UserFile.objects.filter(space=request.space).aggregate(Sum('file_size_kb'))[ - 'file_size_kb__sum'] / 1000 + 'file_size_kb__sum'] / 1000 except TypeError: current_file_size_mb = 0 diff --git a/cookbook/views/views.py b/cookbook/views/views.py index 328ea65e..1c12c77d 100644 --- a/cookbook/views/views.py +++ b/cookbook/views/views.py @@ -31,6 +31,7 @@ from cookbook.helper.permission_helper import (group_required, has_group_permiss from cookbook.models import (Comment, CookLog, InviteLink, SearchFields, SearchPreference, ShareLink, Space, UserSpace, ViewLog) from cookbook.tables import CookLogTable, ViewLogTable +from cookbook.templatetags.theming_tags import get_theming_values from cookbook.version_info import VERSION_INFO from recipes.settings import PLUGINS, BASE_DIR @@ -78,6 +79,11 @@ def space_overview(request): messages.add_message(request, messages.WARNING, _('This feature is not available in the demo version!')) else: if create_form.is_valid(): + if Space.objects.filter(created_by=request.user).count() >= request.user.userpreference.max_owned_spaces: + messages.add_message(request, messages.ERROR, + _('You have the reached the maximum amount of spaces that can be owned by you.') + f' ({request.user.userpreference.max_owned_spaces})') + return HttpResponseRedirect(reverse('view_space_overview')) + created_space = Space.objects.create( name=create_form.cleaned_data['name'], created_by=request.user, @@ -481,29 +487,24 @@ def report_share_abuse(request, token): def web_manifest(request): + theme_values = get_theming_values(request) + icons = [ - {"src": static("/assets/logo_color.svg"), "sizes": "any"}, - {"src": static("/assets/logo_color144.png"), "type": "image/png", "sizes": "144x144"}, - {"src": static("/assets/logo_color512.png"), "type": "image/png", "sizes": "512x512"} + {"src": theme_values['logo_color_svg'], "sizes": "any"}, + {"src": theme_values['logo_color_144'], "type": "image/png", "sizes": "144x144"}, + {"src": theme_values['logo_color_512'], "type": "image/png", "sizes": "512x512"} ] - if request.user.is_authenticated and getattr(request.space, 'logo_color_svg') and getattr(request.space, 'logo_color_144') and getattr(request.space, 'logo_color_512'): - icons = [ - {"src": request.space.logo_color_svg.file.url, "sizes": "any"}, - {"src": request.space.logo_color_144.file.url, "type": "image/png", "sizes": "144x144"}, - {"src": request.space.logo_color_512.file.url, "type": "image/png", "sizes": "512x512"} - ] - manifest_info = { - "name": "Tandoor Recipes", - "short_name": "Tandoor", + "name": theme_values['app_name'], + "short_name": theme_values['app_name'], "description": _("Manage recipes, shopping list, meal plans and more."), "icons": icons, "start_url": "./search", - "background_color": "#ffcb76", + "background_color": theme_values['nav_bg_color'], "display": "standalone", "scope": ".", - "theme_color": "#ffcb76", + "theme_color": theme_values['nav_bg_color'], "shortcuts": [ { "name": _("Plan"), diff --git a/docs/system/configuration.md b/docs/system/configuration.md index c2351134..f6e64d24 100644 --- a/docs/system/configuration.md +++ b/docs/system/configuration.md @@ -524,6 +524,18 @@ The default value for the user preference 'sticky navigation' (always show navba STICKY_NAV_PREF_DEFAULT=1 ``` +#### Max owned spaces + +> default `100` - options: `0-X` + +The default for the number of spaces a user can own. By setting to 0 space creation for users will be disabled. +Superusers can always bypass this limit. + +``` +MAX_OWNED_SPACES_PREF_DEFAULT=100 +``` + + ### Cosmetic / Preferences #### Timezone diff --git a/recipes/settings.py b/recipes/settings.py index 09c037ae..319271ea 100644 --- a/recipes/settings.py +++ b/recipes/settings.py @@ -57,7 +57,8 @@ COMMENT_PREF_DEFAULT = bool(int(os.getenv('COMMENT_PREF_DEFAULT', True))) FRACTION_PREF_DEFAULT = bool(int(os.getenv('FRACTION_PREF_DEFAULT', False))) KJ_PREF_DEFAULT = bool(int(os.getenv('KJ_PREF_DEFAULT', False))) STICKY_NAV_PREF_DEFAULT = bool(int(os.getenv('STICKY_NAV_PREF_DEFAULT', True))) -UNAUTHENTICATED_THEME_FROM_SPACE = 2 #int(os.getenv('UNAUTHENTICATED_THEME_FROM_SPACE', 0)) +MAX_OWNED_SPACES_PREF_DEFAULT = int(os.getenv('MAX_OWNED_SPACES_PREF_DEFAULT', 100)) +UNAUTHENTICATED_THEME_FROM_SPACE = int(os.getenv('UNAUTHENTICATED_THEME_FROM_SPACE', 0)) # minimum interval that users can set for automatic sync of shopping lists SHOPPING_MIN_AUTOSYNC_INTERVAL = int( diff --git a/vue/src/locales/cs.json b/vue/src/locales/cs.json index 15c27a48..ebf1e49b 100644 --- a/vue/src/locales/cs.json +++ b/vue/src/locales/cs.json @@ -16,14 +16,14 @@ "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": "Pole potravin, která budou standardně zděděna.", - "step_time_minutes": "Nastavte čas v minutách", + "step_time_minutes": "Délka kroku 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": "", + "Log_Recipe_Cooking": "Záznam vaření receptu", "External_Recipe_Image": "Externí obrázek receptu", "Add_to_Shopping": "Přidat k nákupu", "Add_to_Plan": "Přidat do jídelníčku", @@ -35,8 +35,8 @@ "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", + "Copy_template_reference": "Zkopírovat šablonu odkazu", + "Save_and_View": "Uložit a zobrazit", "Manage_Books": "Spravovat kuchařky", "Meal_Plan": "Jídelníček", "Select_Book": "Vyber kuchařku", @@ -44,19 +44,19 @@ "Recipe_Image": "Obrázek k receptu", "Import_finished": "Import dokončen", "View_Recipes": "Zobrazit recepty", - "Log_Cooking": "", + "Log_Cooking": "Zaznamenat vaření", "New_Recipe": "Nový recept", "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", + "New_Keyword": "Nový štítek", + "Delete_Keyword": "Smazat štítek", + "Edit_Keyword": "Upravit štítek", "Edit_Recipe": "Upravit recept", - "Move_Keyword": "Přesunout klíčové slovo", - "Merge_Keyword": "Sloučit klíčové slovo", - "Hide_Keywords": "Skrýt klíčové slovo", + "Move_Keyword": "Přesunout štítek", + "Merge_Keyword": "Sloučit štítek", + "Hide_Keywords": "Skrýt štítek", "Hide_Recipes": "Skrýt recept", "Move_Up": "Nahoru", "Move_Down": "Dolů", @@ -76,14 +76,14 @@ "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", + "Keywords": "Štítky", "Books": "Kuchařky", "Proteins": "Proteiny", "Fats": "Tuky", "Carbohydrates": "Sacharidy", "Calories": "Kalorie", "Energy": "Energie", - "Nutrition": "", + "Nutrition": "Výživové hodnoty", "Date": "Datum", "Share": "Sdílet", "Automation": "Automatizace", @@ -153,7 +153,7 @@ "Hide_Food": "Skrýt potravinu", "Food_Alias": "Přezdívka potraviny", "Unit_Alias": "Přezdívka jednotky", - "Keyword_Alias": "Přezdívka klíčového slova", + "Keyword_Alias": "Přezdívka štítku", "Delete_Food": "Smazat potravinu", "No_ID": "ID nenalezeno, odstranění není možné.", "Meal_Plan_Days": "Budoucí jídelníčky", @@ -162,7 +162,7 @@ "Food": "Potravina", "Original_Text": "Původní text", "Recipe_Book": "Kuchařka", - "del_confirmation_tree": "Jste si jistí, že chcete odstranit {source} se všemi pořazenými ?", + "del_confirmation_tree": "Jste si jistí, že chcete odstranit {source} i se všemi potomky?", "delete_title": "Smazat {type}", "create_title": "Nový {type}", "edit_title": "Upravit {type}", @@ -170,7 +170,7 @@ "Type": "Typ", "Description": "Popis", "Recipe": "Recept", - "tree_root": "", + "tree_root": "Kořen stromu", "Icon": "Ikona", "Unit": "Jednotka", "Decimals": "Desetinná místa", @@ -179,7 +179,7 @@ "New_Unit": "Nová jednotka", "Create_New_Shopping Category": "Vytvořit novou nákupní kategorii", "Create_New_Food": "Přidat novou potravinu", - "Create_New_Keyword": "Přidat nové klíčové slovo", + "Create_New_Keyword": "Přidat nový štítek", "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", @@ -254,7 +254,7 @@ "SupermarketCategoriesOnly": "Pouze kategorie obchodu", "MoveCategory": "Přesunout do: ", "CountMore": "...+{count} víc", - "IgnoreThis": "Nikdy automaticky nepřídávat {food} na nákupní seznam", + "IgnoreThis": "Nikdy nepřidávat automaticky {food} na nákupní seznam", "DelayFor": "Odložit na {hours} hodin", "Warning": "Varování", "NoCategory": "Není vybrána žádná kategorie.", @@ -271,7 +271,7 @@ "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.", + "shopping_auto_sync_desc": "Zadáním hodnoty 0 se automatická synchronizace vypne. Při prohlížení nákupního seznamu se seznam aktualizuje po stanovených sekundách, aby se synchronizovaly změny, které mohl provést někdo jiný. To je užitečné při nakupování s více lidmi, ale spotřebovávají se při tom mobilní data.", "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ů.", @@ -280,7 +280,7 @@ "Coming_Soon": "Již brzy", "Auto_Planner": "Automatický plánovač", "New_Cookbook": "Nová kuchařka", - "Hide_Keyword": "Skrýt klíčová slova", + "Hide_Keyword": "Skrýt štítky", "Hour": "Hodina", "Hours": "Hodiny", "Day": "Den", @@ -328,7 +328,7 @@ "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": "", + "food_recipe_help": "Zde uvedený recept bude připojen k jakémukoli jinému receptu, který používá tuto potravinu", "Foods": "Potraviny", "Account": "Účet", "Cosmetic": "Zobrazení", @@ -338,7 +338,7 @@ "simple_mode": "Jednoduchý režim", "advanced": "Pokročilé", "fields": "Pole", - "show_keywords": "Zobrazit klíčová slova", + "show_keywords": "Zobrazit štítky", "show_foods": "Zobrazit potraviny", "show_books": "Zobrazit kuchařky", "show_rating": "Zobrazit hodnocení", @@ -359,8 +359,8 @@ "times_cooked": "Kolkrát vařeno", "date_created": "Datum vytvoření", "show_sortby": "Zobrazit Seřazeno podle", - "search_rank": "", - "make_now": "", + "search_rank": "Skóre shody", + "make_now": "Udělat hned", "recipe_filter": "Filtrovat recepty", "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", @@ -392,9 +392,9 @@ "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": "Resetovat propsání podřízených", - "reset_children_help": "", + "reset_children_help": "Přepíše všechny potomky hodnotami dle nastavení propisovaných polí. Pokud není nastaveno pole Propisovaná pole podřízených, tak bude nastaveno pole Propsat hodnoty polí na aktuální hodnotu.", "reset_food_inheritance": "Resetovat propisování", - "reset_food_inheritance_info": "", + "reset_food_inheritance_info": "Obnoví u všech potravin propisovaná pole na výchozí hodnotu a nastavit daná pole dle nadřazené položky.", "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.", @@ -425,8 +425,8 @@ "Social_Authentication": "Přihlašování pomocí účtů sociálních sítí", "Random Recipes": "Náhodné recepty", "parameter_count": "Parametr {count}", - "select_keyword": "Vybrat klíčové slovo", - "add_keyword": "Přidat klíčové slovo", + "select_keyword": "Vybrat štítek", + "add_keyword": "Přidat štítek", "select_file": "Vybrat soubor", "select_recipe": "Vybrat recept", "select_unit": "Vybrat jednotku", @@ -439,7 +439,7 @@ "Username": "Uživatelské jméno", "First_name": "Jméno", "Last_name": "Příjmení", - "Keyword": "Klíčové slovo", + "Keyword": "Štítek", "Advanced": "Rozšířené", "Page": "Stránka", "Single": "Jednoduchý", @@ -479,10 +479,10 @@ "Create Recipe": "Vytvořit 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 potravin, jednotek a další spravované komunitou, a vylepšete tak svoji sbírku receptů.", + "open_data_help_text": "Projekt Tandoor Open Data nabízí komunitou poskytnutá otevřená data pro Tandoor. Toto pole se vyplní automaticky při importu a umožňuje budoucí aktualizace.", + "Data_Import_Info": "Rozšiřte svůj prostor o seznamy potravin, jednotek a dalších položek spravovaných komunitou, a vylepšete tak svoji sbírku receptů.", "Update_Existing_Data": "Aktualizovat existující data", - "Use_Metric": "Používat metrické jednotky", + "Use_Metric": "Použít metrické jednotky", "Learn_More": "Zjistit víc", "converted_unit": "Převedená jendotka", "converted_amount": "Převedené množství", @@ -490,9 +490,67 @@ "base_amount": "Základní množství", "Datatype": "Datový typ", "Number of Objects": "Počet Objektů", - "Property": "Vlastnost", + "Property": "Nutriční vlastnost", "Conversion": "Převod", - "Properties": "Vlastnosti", - "recipe_property_info": "Můžete také přidávat vlastnosti k Vašim potravinám. Hodnoty budou automaticky přepočteny na základě Vašeho receptu!", - "total": "celkem" + "Properties": "Nutriční vlastnosti", + "recipe_property_info": "Nutriční hodnoty se automaticky dopočtou podle receptu, pokud zadáte nutriční hodnoty přímo potravinám!", + "total": "celkem", + "CustomTheme": "Vlastní téma", + "CustomThemeHelp": "Přepsat styly vybraného motivu nahráním vlastního souboru CSS.", + "CustomLogoHelp": "Nahrajte čtvercové obrázky různých velikostí pro úpravu loga v záložce prohlížeče a v nainstalované webové aplikaci.", + "err_importing_recipe": "Během importu receptu došlo k chybě!", + "Open_Data_Slug": "Identifikátor pro otevřená data", + "Open_Data_Import": "Import otevřených dat", + "FDC_Search": "Vyhledávání v FDC", + "property_type_fdc_hint": "Data z databáze FDC mohou automaticky čerpat pouze typy vlastností se zadaným FDC ID", + "StartDate": "Počáteční datum", + "EndDate": "Konečné datum", + "Welcome": "Vítejte", + "Property_Editor": "Editovat nutriční vlastnosti", + "FDC_ID": "FDC ID", + "FDC_ID_help": "ID v databázi FDC", + "CustomImageHelp": "Nahrajte obrázek, který se zobrazí v přehledu prostoru.", + "CustomNavLogoHelp": "Nahrajte obrázek, který se má zobrazit jako logo v navigačním panelu.", + "CustomLogos": "Vlastní loga", + "OrderInformation": "Položky jsou seřazeny podle čísel od malých po velké.", + "kg": "kilogram [kg] (metrický systém, hmotnost)", + "g": "gram [g] (metrický systém, hmotnost)", + "ounce": "unce [oz] (imperiální systém, hmotnost)", + "pound": "libra (hmotnost)", + "Properties_Food_Unit": "Jednotka nutriční vlastnosti", + "Properties_Food_Amount": "Množství nutriční vlastnosti", + "tsp": "lžička [tsp] (US, objem)", + "imperial_tsp": "lžička imperiální [imp tbsp] (UK, objem)", + "Transpose_Words": "Transponovat slova", + "show_step_ingredients_setting": "Zobrazit ingredience u jednotlivých kroků receptu", + "Logo": "Logo", + "Show_Logo": "Zobrazit logo", + "show_step_ingredients_setting_help": "Zobrazí tabulku ingrediencí vedle kroků receptu. Nastavení se aplikuje při vytváření receptu a následně je možné volbu změnit při úpravě receptu.", + "show_step_ingredients": "Zobrazit ingredience u kroku", + "hide_step_ingredients": "Skrýt ingredience u kroku", + "Show_Logo_Help": "Zobrazit logo Tandoor nebo logo prostoru na navigačním panelu.", + "Nav_Text_Mode_Help": "Pro každé téma se chová jinak.", + "Space_Cosmetic_Settings": "Některá kosmetická nastavení mohou měnit správci prostoru a budou mít přednost před nastavením klienta pro daný prostor.", + "Nav_Text_Mode": "Textový režim navigace", + "show_ingredients_table": "Zobrazit tabulku složek vedle textu kroku", + "pint": "pinta [pt] (US, objem)", + "quart": "quart [qt] (US, objem)", + "imperial_fluid_ounce": "tekutá unce imperiální [imp fl oz] (UK, objem)", + "imperial_pint": "pinta imperiální [imp pt] (UK, objem)", + "imperial_quart": "quart imperiální [imp qt] (UK, objem)", + "gallon": "galon [gal] (US, objem)", + "tbsp": "lžíce [tbsp] (US, objem)", + "imperial_gallon": "galon imperiální [imp gal] (UK, objem)", + "imperial_tbsp": "lžíce imperiální [imp tbsp] (UK, objem)", + "Choose_Category": "Vyberte kategorii", + "Back": "Zpět", + "Food_Replace": "Nahrazení v potravině", + "Unit_Replace": "Nahrazení v jednotce", + "Name_Replace": "Nahrazení v názvu", + "ml": "mililitr [ml] (metrický systém, objem)", + "l": "litr [l] (metrický systém, objem)", + "fluid_ounce": "tekutá unce [fl oz] (US, objem)", + "make_now_count": "Nejvyšší počet chybějících ingrediencí", + "Alignment": "Zarovnání", + "Never_Unit": "Není jednotkou" } diff --git a/vue/src/locales/de.json b/vue/src/locales/de.json index 6bdea528..c2f3df5b 100644 --- a/vue/src/locales/de.json +++ b/vue/src/locales/de.json @@ -536,5 +536,24 @@ "Unit_Replace": "Einheit Ersetzen", "quart": "\"Quart\" [qt] (US, Volumen)", "imperial_quart": "Engl. \"Quart\" [imp qt] (UK, Volumen)", - "err_importing_recipe": "Beim Importieren des Rezeptes ist ein Fehler aufgetreten!" + "err_importing_recipe": "Beim Importieren des Rezeptes ist ein Fehler aufgetreten!", + "property_type_fdc_hint": "Nur Nährwerte mit einer FDC ID können automatisch Daten aus der FDC Datenbank beziehen", + "Property_Editor": "Nährwerte bearbeiten", + "CustomTheme": "Benutzerdefiniertes Theme", + "CustomThemeHelp": "Überschreiben Sie die Stile des ausgewählten Themes, indem Sie eine eigene CSS-Datei hochladen.", + "CustomLogoHelp": "Laden Sie quadratische Bilder in verschiedenen Größen hoch, um das Logo im Browsertab und der installierten Webanwendung zu ändern.", + "Show_Logo_Help": "Zeigen Sie das Tandoor- oder Space-Logo in der Navigationsleiste an.", + "Space_Cosmetic_Settings": "Einige optische Einstellungen können von Administratoren des Bereichs geändert werden und setzen die Client-Einstellungen für diesen Bereich außer Kraft.", + "Properties_Food_Amount": "Nährwertangaben", + "Properties_Food_Unit": "Nährwert Einheit", + "FDC_Search": "FDC Suche", + "Logo": "Logo", + "Show_Logo": "Logo anzeigen", + "Nav_Text_Mode": "Navigation Textmodus", + "Nav_Text_Mode_Help": "Verhält sich bei jedem Theme anders.", + "FDC_ID": "FDC ID", + "FDC_ID_help": "FDC Datenbank ID", + "CustomImageHelp": "Laden Sie ein Bild hoch, das in der Space-Übersicht angezeigt werden soll.", + "CustomNavLogoHelp": "Laden Sie ein Bild hoch, das als Logo für die Navigationsleiste verwendet werden soll.", + "CustomLogos": "Individuelle Logos" }