Skip to content

Commit

Permalink
Merge branch 'mealie-next' into feature/food-seed-plurals
Browse files Browse the repository at this point in the history
  • Loading branch information
Choromanski authored Sep 12, 2024
2 parents 27645ed + 0eb3e3f commit c9abf13
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 47 deletions.
27 changes: 16 additions & 11 deletions frontend/components/Domain/ShoppingList/ShoppingListItemEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,9 @@
<v-card outlined>
<v-card-text class="pb-3 pt-1">
<div v-if="listItem.isFood" class="d-md-flex align-center mb-2" style="gap: 20px">
<InputLabelType
v-model="listItem.food"
:items="foods"
:item-id.sync="listItem.foodId"
:label="$t('shopping-list.food')"
:icon="$globals.icons.foods"
@create="createAssignFood"
/>
<div>
<InputQuantity v-model="listItem.quantity" />
</div>
<InputLabelType
v-model="listItem.unit"
:items="units"
Expand All @@ -19,8 +14,20 @@
:icon="$globals.icons.units"
@create="createAssignUnit"
/>
<InputLabelType
v-model="listItem.food"
:items="foods"
:item-id.sync="listItem.foodId"
:label="$t('shopping-list.food')"
:icon="$globals.icons.foods"
@create="createAssignFood"
/>

</div>
<div class="d-md-flex align-center" style="gap: 20px">
<div v-if="!listItem.isFood">
<InputQuantity v-model="listItem.quantity" />
</div>
<v-textarea
v-model="listItem.note"
hide-details
Expand All @@ -32,9 +39,7 @@
</div>
<div class="d-flex flex-wrap align-end" style="gap: 20px">
<div class="d-flex align-end">
<div>
<InputQuantity v-model="listItem.quantity" />
</div>

<div style="max-width: 300px" class="mt-3 mr-auto">
<InputLabelType
v-model="listItem.label"
Expand Down
42 changes: 21 additions & 21 deletions frontend/lang/messages/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,14 +283,14 @@
"manage-households": "Administrar Casas",
"admin-household-management": "Administración de la Casa",
"admin-household-management-text": "Changes to this household will be reflected immediately.",
"household-id-value": "Household Id: {0}",
"household-id-value": "Id del hogar: {0}",
"private-household": "Casa Privada",
"private-household-description": "Setting your household to private will default all public view options to default. This overrides any individual recipes public view settings.",
"household-recipe-preferences": "Household Recipe Preferences",
"default-recipe-preferences-description": "These are the default settings when a new recipe is created in your household. These can be changed for individual recipes in the recipe settings menu.",
"allow-users-outside-of-your-household-to-see-your-recipes": "Allow users outside of your household to see your recipes",
"allow-users-outside-of-your-household-to-see-your-recipes-description": "When enabled you can use a public share link to share specific recipes without authorizing the user. When disabled, you can only share recipes with users who are in your household or with a pre-generated private link",
"household-preferences": "Household Preferences"
"private-household-description": "Configurar tu hogar a privado predeterminará todas las opciones de vista pública por defecto. Esto anula cualquier receta individual de configuración de vista pública.",
"household-recipe-preferences": "Preferencias de la Receta de la Casa",
"default-recipe-preferences-description": "Estas son las configuraciones por defecto cuando se crea una nueva receta en su hogar. Estos se pueden cambiar para recetas individuales en el menú de ajustes de receta.",
"allow-users-outside-of-your-household-to-see-your-recipes": "Permite a los usuarios fuera de tu hogar ver tus recetas",
"allow-users-outside-of-your-household-to-see-your-recipes-description": "Cuando está habilitado puede utilizar un enlace público para compartir recetas específicas sin autorizar al usuario. Cuando está desactivado, sólo puedes compartir recetas con usuarios que estén en tu hogar o con un enlace privado generado previamente",
"household-preferences": "Preferencias de la Casa"
},
"meal-plan": {
"create-a-new-meal-plan": "Crear un nuevo menú",
Expand Down Expand Up @@ -583,12 +583,12 @@
"create-recipe-description": "Crear nueva receta desde cero.",
"create-recipes": "Crear Recetas",
"import-with-zip": "Importar desde .zip",
"create-recipe-from-an-image": "Create Recipe from an Image",
"create-recipe-from-an-image-description": "Create a recipe by uploading an image of it. Mealie will attempt to extract the text from the image using AI and create a recipe from it.",
"crop-and-rotate-the-image": "Crop and rotate the image so that only the text is visible, and it's in the correct orientation.",
"create-from-image": "Create from Image",
"should-translate-description": "Translate the recipe into my language",
"please-wait-image-procesing": "Please wait, the image is processing. This may take some time.",
"create-recipe-from-an-image": "Crear receta a partir de una imagen",
"create-recipe-from-an-image-description": "Crea una receta cargando una imagen de ella. Mealie intentará extraer el texto de la imagen usando IA y crear una receta de ella.",
"crop-and-rotate-the-image": "Recortar y rotar la imagen de manera que sólo el texto sea visible, y esté en la orientación correcta.",
"create-from-image": "Crear desde imagen",
"should-translate-description": "Traducir la receta a mi idioma",
"please-wait-image-procesing": "Por favor, espere, la imagen se está procesando. Esto puede tardar un tiempo.",
"bulk-url-import": "Importación masiva desde URL",
"debug-scraper": "Depurar analizador",
"create-a-recipe-by-providing-the-name-all-recipes-must-have-unique-names": "Crear una receta proporcionando el nombre. Todas las recetas deben tener nombres únicos.",
Expand Down Expand Up @@ -1252,8 +1252,8 @@
"account-summary-description": "Aquí hay un resumen de la información del grupo.",
"group-statistics": "Estadísticas del grupo",
"group-statistics-description": "Tus estadísticas de grupo proporcionan información sobre cómo utilizas Mealie.",
"household-statistics": "Household Statistics",
"household-statistics-description": "Your Household Statistics provide some insight how you're using Mealie.",
"household-statistics": "Estadísticas de la Casa",
"household-statistics-description": "Tus estadísticas de la casa proporcionan información sobre cómo utilizas Mealie.",
"storage-capacity": "Capacidad de almacenamiento",
"storage-capacity-description": "Tu capacidad de almacenamiento es el cálculo de las imágenes y recursos que has subido.",
"personal": "Personal",
Expand All @@ -1263,13 +1263,13 @@
"api-tokens-description": "Administra tus API tokens para el acceso desde apps externas.",
"group-description": "Estos elementos se comparten dentro del grupo. ¡Editar cualquiera de ellos lo modificará para todo el grupo!",
"group-settings": "Ajustes de grupo",
"group-settings-description": "Manage your common group settings, like privacy settings.",
"household-description": "These items are shared within your household. Editing one of them will change it for the whole household!",
"household-settings": "Household Settings",
"household-settings-description": "Manage your household settings, like mealplan and privacy settings.",
"group-settings-description": "Administra tu configuración común de grupo, como la configuración de privacidad.",
"household-description": "Estos elementos se comparten dentro de la casa. ¡Editar cualquiera de ellos lo modificará para todos en la casa!",
"household-settings": "Configuración de la Casa",
"household-settings-description": "Administre los ajustes de su hogar, como el plan de comidas y la configuración de privacidad.",
"cookbooks-description": "Gestiona un a colección de categorías de receta y genera páginas para estas.",
"members": "Miembros",
"members-description": "See who's in your household and manage their permissions.",
"members-description": "Mira quién está en tu hogar y administra sus permisos.",
"webhooks-description": "Setup webhooks that trigger on days that you have have mealplan scheduled.",
"notifiers": "Notificaciones",
"notifiers-description": "Setup email and push notifications that trigger on specific events.",
Expand Down Expand Up @@ -1304,7 +1304,7 @@
"require-all-tools": "Requiere todos los utensilios",
"cookbook-name": "Nombre del recetario",
"cookbook-with-name": "Recetario {0}",
"household-cookbook-name": "{0} Cookbook {1}",
"household-cookbook-name": "{0} Libro de Cocina {1}",
"create-a-cookbook": "Crear Recetario",
"cookbook": "Recetario"
}
Expand Down
14 changes: 7 additions & 7 deletions mealie/routes/spa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,13 @@ def serve_recipe_with_meta_public(
public_repos = AllRepositories(session)
group = public_repos.groups.get_by_slug_or_id(group_slug)

if not group or group.preferences.private_group: # type: ignore
if not (group and group.preferences) or group.preferences.private_group:
return response_404()

group_repos = AllRepositories(session, group_id=group.id)
group_repos = AllRepositories(session, group_id=group.id, household_id=None)
recipe = group_repos.recipes.get_one(recipe_slug)

if not recipe or not recipe.settings.public: # type: ignore
if not (recipe and recipe.settings) or not recipe.settings.public:
return response_404()

# Inject meta tags
Expand All @@ -190,9 +190,9 @@ async def serve_recipe_with_meta(
return serve_recipe_with_meta_public(group_slug, recipe_slug, session)

try:
repos = AllRepositories(session, group_id=user.group_id)
group_repos = AllRepositories(session, group_id=user.group_id, household_id=None)

recipe = repos.recipes.get_one(recipe_slug, "slug")
recipe = group_repos.recipes.get_one(recipe_slug, "slug")
if recipe is None:
return response_404()

Expand All @@ -204,8 +204,8 @@ async def serve_recipe_with_meta(

async def serve_shared_recipe_with_meta(group_slug: str, token_id: str, session: Session = Depends(generate_session)):
try:
repos = AllRepositories(session)
token_summary = repos.recipe_share_tokens.get_one(token_id)
public_repos = AllRepositories(session, group_id=None)
token_summary = public_repos.recipe_share_tokens.get_one(token_id)
if token_summary is None:
raise Exception("Token Not Found")

Expand Down
6 changes: 3 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

131 changes: 126 additions & 5 deletions tests/integration_tests/test_spa.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,46 @@
import pytest
from bs4 import BeautifulSoup

from mealie.routes.spa import MetaTag, inject_meta, inject_recipe_json
from mealie.routes import spa
from mealie.schema.recipe.recipe import Recipe
from mealie.schema.recipe.recipe_share_token import RecipeShareTokenSave
from tests import data as test_data
from tests.utils.factories import random_string
from tests.utils.fixture_schemas import TestUser


@pytest.fixture(autouse=True)
def set_spa_contents():
"""Inject a simple HTML string into the SPA module to enable metadata injection"""

spa.__contents = "<!DOCTYPE html><html><head></head><body></body></html>"


def set_group_is_private(unique_user: TestUser, *, is_private: bool):
group = unique_user.repos.groups.get_by_slug_or_id(unique_user.group_id)
assert group and group.preferences
group.preferences.private_group = is_private
unique_user.repos.group_preferences.update(group.id, group.preferences)


def set_recipe_is_public(unique_user: TestUser, recipe: Recipe, *, is_public: bool):
assert recipe.settings
recipe.settings.public = is_public
unique_user.repos.recipes.update(recipe.slug, recipe)


def create_recipe(user: TestUser) -> Recipe:
recipe = user.repos.recipes.create(
Recipe(
user_id=user.user_id,
group_id=user.group_id,
name=random_string(),
)
)
set_group_is_private(user, is_private=False)
set_recipe_is_public(user, recipe, is_public=True)

return recipe


def test_spa_metadata_injection():
Expand All @@ -22,9 +60,9 @@ def test_spa_metadata_injection():

assert title_tag and title_tag["content"]

new_title_tag = MetaTag(hid="og:title", property_name="og:title", content=random_string())
new_arbitrary_tag = MetaTag(hid=random_string(), property_name=random_string(), content=random_string())
new_html = inject_meta(str(soup), [new_title_tag, new_arbitrary_tag])
new_title_tag = spa.MetaTag(hid="og:title", property_name="og:title", content=random_string())
new_arbitrary_tag = spa.MetaTag(hid=random_string(), property_name=random_string(), content=random_string())
new_html = spa.inject_meta(str(soup), [new_title_tag, new_arbitrary_tag])

# verify changes were injected
soup = BeautifulSoup(new_html, "lxml")
Expand Down Expand Up @@ -63,8 +101,91 @@ def test_spa_recipe_json_injection():
soup = BeautifulSoup(f, "lxml")
assert "https://schema.org" not in str(soup)

html = inject_recipe_json(str(soup), schema)
html = spa.inject_recipe_json(str(soup), schema)

assert "@context" in html
assert "https://schema.org" in html
assert recipe_name in html


@pytest.mark.parametrize("use_public_user", [True, False])
@pytest.mark.asyncio
async def test_spa_serve_recipe_with_meta(unique_user: TestUser, use_public_user: bool):
recipe = create_recipe(unique_user)
user = unique_user.repos.users.get_by_username(unique_user.username)
assert user

response = await spa.serve_recipe_with_meta(
user.group_slug, recipe.slug, user=None if use_public_user else user, session=unique_user.repos.session
)
assert response.status_code == 200
assert "https://schema.org" in response.body.decode()


@pytest.mark.parametrize("use_public_user", [True, False])
@pytest.mark.asyncio
async def test_spa_serve_recipe_with_meta_invalid_data(unique_user: TestUser, use_public_user: bool):
recipe = create_recipe(unique_user)
user = unique_user.repos.users.get_by_username(unique_user.username)
assert user

response = await spa.serve_recipe_with_meta(
random_string(), recipe.slug, user=None if use_public_user else user, session=unique_user.repos.session
)
assert response.status_code == 404

response = await spa.serve_recipe_with_meta(
user.group_slug, random_string(), user=None if use_public_user else user, session=unique_user.repos.session
)
assert response.status_code == 404

set_recipe_is_public(unique_user, recipe, is_public=False)
response = await spa.serve_recipe_with_meta(
user.group_slug, recipe.slug, user=None if use_public_user else user, session=unique_user.repos.session
)
if use_public_user:
assert response.status_code == 404
else:
assert response.status_code == 200

set_group_is_private(unique_user, is_private=True)
set_recipe_is_public(unique_user, recipe, is_public=True)
response = await spa.serve_recipe_with_meta(
user.group_slug, recipe.slug, user=None if use_public_user else user, session=unique_user.repos.session
)
if use_public_user:
assert response.status_code == 404
else:
assert response.status_code == 200


@pytest.mark.parametrize("use_private_group", [True, False])
@pytest.mark.parametrize("use_public_recipe", [True, False])
@pytest.mark.asyncio
async def test_spa_service_shared_recipe_with_meta(
unique_user: TestUser, use_private_group: bool, use_public_recipe: bool
):
group = unique_user.repos.groups.get_by_slug_or_id(unique_user.group_id)
assert group
recipe = create_recipe(unique_user)

# visibility settings shouldn't matter for shared recipes
set_group_is_private(unique_user, is_private=use_private_group)
set_recipe_is_public(unique_user, recipe, is_public=use_public_recipe)

token = unique_user.repos.recipe_share_tokens.create(
RecipeShareTokenSave(recipe_id=recipe.id, group_id=unique_user.group_id)
)

response = await spa.serve_shared_recipe_with_meta(group.slug, token.id, session=unique_user.repos.session)
assert response.status_code == 200
assert "https://schema.org" in response.body.decode()


@pytest.mark.asyncio
async def test_spa_service_shared_recipe_with_meta_invalid_data(unique_user: TestUser):
group = unique_user.repos.groups.get_by_slug_or_id(unique_user.group_id)
assert group

response = await spa.serve_shared_recipe_with_meta(group.slug, random_string(), session=unique_user.repos.session)
assert response.status_code == 404

0 comments on commit c9abf13

Please sign in to comment.