mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-04 21:58:54 -05:00
Merge branch 'develop' into feature/vue3
# Conflicts: # requirements.txt
This commit is contained in:
@@ -1,26 +1,35 @@
|
||||
import logging
|
||||
from logging import Logger
|
||||
from typing import Dict, Tuple
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from homeassistant_api import Client, HomeassistantAPIError, Domain
|
||||
from aiohttp import ClientError, request
|
||||
|
||||
from cookbook.connectors.connector import Connector
|
||||
from cookbook.models import ShoppingListEntry, ConnectorConfig, Space
|
||||
|
||||
|
||||
class HomeAssistant(Connector):
|
||||
_domains_cache: dict[str, Domain]
|
||||
_config: ConnectorConfig
|
||||
_logger: Logger
|
||||
_client: Client
|
||||
|
||||
def __init__(self, config: ConnectorConfig):
|
||||
if not config.token or not config.url or not config.todo_entity:
|
||||
raise ValueError("config for HomeAssistantConnector in incomplete")
|
||||
|
||||
self._domains_cache = dict()
|
||||
if config.url[-1] != "/":
|
||||
config.url += "/"
|
||||
self._config = config
|
||||
self._logger = logging.getLogger("connector.HomeAssistant")
|
||||
self._client = Client(self._config.url, self._config.token, async_cache_session=False, use_async=True)
|
||||
|
||||
async def homeassistant_api_call(self, method: str, path: str, data: Dict) -> str:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self._config.token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
async with request(method, urljoin(self._config.url, path), headers=headers, json=data) as response:
|
||||
response.raise_for_status()
|
||||
return await response.json()
|
||||
|
||||
async def on_shopping_list_entry_created(self, space: Space, shopping_list_entry: ShoppingListEntry) -> None:
|
||||
if not self._config.on_shopping_list_entry_created_enabled:
|
||||
@@ -28,15 +37,17 @@ class HomeAssistant(Connector):
|
||||
|
||||
item, description = _format_shopping_list_entry(shopping_list_entry)
|
||||
|
||||
todo_domain = self._domains_cache.get('todo')
|
||||
try:
|
||||
if todo_domain is None:
|
||||
todo_domain = await self._client.async_get_domain('todo')
|
||||
self._domains_cache['todo'] = todo_domain
|
||||
logging.debug(f"adding {item=} to {self._config.name}")
|
||||
|
||||
logging.debug(f"pushing {item} to {self._config.name}")
|
||||
await todo_domain.add_item(entity_id=self._config.todo_entity, item=item)
|
||||
except HomeassistantAPIError as err:
|
||||
data = {
|
||||
"entity_id": self._config.todo_entity,
|
||||
"item": item,
|
||||
"description": description,
|
||||
}
|
||||
|
||||
try:
|
||||
await self.homeassistant_api_call("POST", "services/todo/add_item", data)
|
||||
except ClientError as err:
|
||||
self._logger.warning(f"[HomeAssistant {self._config.name}] Received an exception from the api: {err=}, {type(err)=}")
|
||||
|
||||
async def on_shopping_list_entry_updated(self, space: Space, shopping_list_entry: ShoppingListEntry) -> None:
|
||||
@@ -48,24 +59,31 @@ class HomeAssistant(Connector):
|
||||
if not self._config.on_shopping_list_entry_deleted_enabled:
|
||||
return
|
||||
|
||||
item, description = _format_shopping_list_entry(shopping_list_entry)
|
||||
if not hasattr(shopping_list_entry._state.fields_cache, "food"):
|
||||
# Sometimes the food foreign key is not loaded, and we cant load it from an async process
|
||||
self._logger.debug("required property was not present in ShoppingListEntry")
|
||||
return
|
||||
|
||||
item, _ = _format_shopping_list_entry(shopping_list_entry)
|
||||
|
||||
logging.debug(f"removing {item=} from {self._config.name}")
|
||||
|
||||
data = {
|
||||
"entity_id": self._config.todo_entity,
|
||||
"item": item,
|
||||
}
|
||||
|
||||
todo_domain = self._domains_cache.get('todo')
|
||||
try:
|
||||
if todo_domain is None:
|
||||
todo_domain = await self._client.async_get_domain('todo')
|
||||
self._domains_cache['todo'] = todo_domain
|
||||
|
||||
logging.debug(f"deleting {item} from {self._config.name}")
|
||||
await todo_domain.remove_item(entity_id=self._config.todo_entity, item=item)
|
||||
except HomeassistantAPIError as err:
|
||||
self._logger.warning(f"[HomeAssistant {self._config.name}] Received an exception from the api: {err=}, {type(err)=}")
|
||||
await self.homeassistant_api_call("POST", "services/todo/remove_item", data)
|
||||
except ClientError as err:
|
||||
# This error will always trigger if the item is not present/found
|
||||
self._logger.debug(f"[HomeAssistant {self._config.name}] Received an exception from the api: {err=}, {type(err)=}")
|
||||
|
||||
async def close(self) -> None:
|
||||
await self._client.async_cache_session.close()
|
||||
pass
|
||||
|
||||
|
||||
def _format_shopping_list_entry(shopping_list_entry: ShoppingListEntry):
|
||||
def _format_shopping_list_entry(shopping_list_entry: ShoppingListEntry) -> Tuple[str, str]:
|
||||
item = shopping_list_entry.food.name
|
||||
if shopping_list_entry.amount > 0:
|
||||
item += f" ({shopping_list_entry.amount:.2f}".rstrip('0').rstrip('.')
|
||||
@@ -76,10 +94,10 @@ def _format_shopping_list_entry(shopping_list_entry: ShoppingListEntry):
|
||||
else:
|
||||
item += ")"
|
||||
|
||||
description = "Imported by TandoorRecipes"
|
||||
description = "From TandoorRecipes"
|
||||
if shopping_list_entry.created_by.first_name and len(shopping_list_entry.created_by.first_name) > 0:
|
||||
description += f", created by {shopping_list_entry.created_by.first_name}"
|
||||
description += f", by {shopping_list_entry.created_by.first_name}"
|
||||
else:
|
||||
description += f", created by {shopping_list_entry.created_by.username}"
|
||||
description += f", by {shopping_list_entry.created_by.username}"
|
||||
|
||||
return item, description
|
||||
|
||||
@@ -14,8 +14,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-03-21 14:39+0100\n"
|
||||
"PO-Revision-Date: 2023-09-25 09:59+0000\n"
|
||||
"Last-Translator: Matias Laporte <laportematias+weblate@gmail.com>\n"
|
||||
"PO-Revision-Date: 2024-03-27 19:02+0000\n"
|
||||
"Last-Translator: Axel Breiterman <axelbreiterman@gmail.com>\n"
|
||||
"Language-Team: Spanish <http://translate.tandoor.dev/projects/tandoor/"
|
||||
"recipes-backend/es/>\n"
|
||||
"Language: es\n"
|
||||
@@ -23,7 +23,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.15\n"
|
||||
"X-Generator: Weblate 5.4.2\n"
|
||||
|
||||
#: .\cookbook\forms.py:45
|
||||
msgid ""
|
||||
@@ -97,14 +97,16 @@ msgid ""
|
||||
"<a href=\"https://www.home-assistant.io/docs/authentication/#your-account-"
|
||||
"profile\">Long Lived Access Token</a> for your HomeAssistant instance"
|
||||
msgstr ""
|
||||
"<a href=\"https://www.home-assistant.io/docs/authentication/#your-account-"
|
||||
"profile\">Token de larga duración</a>para tu instancia de HomeAssistant"
|
||||
|
||||
#: .\cookbook\forms.py:193
|
||||
msgid "Something like http://homeassistant.local:8123/api"
|
||||
msgstr ""
|
||||
msgstr "Algo similar a http://homeassistant.local:8123/api"
|
||||
|
||||
#: .\cookbook\forms.py:205
|
||||
msgid "http://homeassistant.local:8123/api for example"
|
||||
msgstr ""
|
||||
msgstr "por ejemplo http://homeassistant.local:8123/api for example"
|
||||
|
||||
#: .\cookbook\forms.py:222 .\cookbook\views\edit.py:117
|
||||
msgid "Storage"
|
||||
@@ -279,7 +281,7 @@ msgstr "Ha alcanzado el número máximo de recetas para su espacio."
|
||||
|
||||
#: .\cookbook\helper\permission_helper.py:414
|
||||
msgid "You have more users than allowed in your space."
|
||||
msgstr ""
|
||||
msgstr "Tenés mas usuarios que los permitidos en tu espacio"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:304
|
||||
#, fuzzy
|
||||
@@ -309,7 +311,7 @@ msgstr "fermentar"
|
||||
|
||||
#: .\cookbook\helper\recipe_url_import.py:310
|
||||
msgid "sous-vide"
|
||||
msgstr ""
|
||||
msgstr "sous-vide"
|
||||
|
||||
#: .\cookbook\helper\shopping_helper.py:150
|
||||
msgid "You must supply a servings size"
|
||||
@@ -318,7 +320,7 @@ msgstr "Debe proporcionar un tamaño de porción"
|
||||
#: .\cookbook\helper\template_helper.py:95
|
||||
#: .\cookbook\helper\template_helper.py:97
|
||||
msgid "Could not parse template code."
|
||||
msgstr ""
|
||||
msgstr "No se pudo parsear el código de la planitlla."
|
||||
|
||||
#: .\cookbook\integration\copymethat.py:44
|
||||
#: .\cookbook\integration\melarecipes.py:37
|
||||
@@ -342,6 +344,8 @@ msgid ""
|
||||
"An unexpected error occurred during the import. Please make sure you have "
|
||||
"uploaded a valid file."
|
||||
msgstr ""
|
||||
"Ocurrió un error inesperado al importar. Por favor asegurate de haber subido "
|
||||
"un archivo válido."
|
||||
|
||||
#: .\cookbook\integration\integration.py:217
|
||||
msgid "The following recipes were ignored because they already existed:"
|
||||
@@ -457,7 +461,7 @@ msgstr "Calorías"
|
||||
|
||||
#: .\cookbook\migrations\0190_auto_20230525_1506.py:20
|
||||
msgid "kcal"
|
||||
msgstr ""
|
||||
msgstr "kcal"
|
||||
|
||||
#: .\cookbook\models.py:325
|
||||
msgid ""
|
||||
|
||||
@@ -11,8 +11,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-03-21 14:39+0100\n"
|
||||
"PO-Revision-Date: 2024-03-03 23:19+0000\n"
|
||||
"Last-Translator: M Ugur <mugurd@gmail.com>\n"
|
||||
"PO-Revision-Date: 2024-04-01 22:04+0000\n"
|
||||
"Last-Translator: atom karinca <atomkarinca@tutanota.com>\n"
|
||||
"Language-Team: Turkish <http://translate.tandoor.dev/projects/tandoor/"
|
||||
"recipes-backend/tr/>\n"
|
||||
"Language: tr\n"
|
||||
@@ -20,7 +20,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"X-Generator: Weblate 4.15\n"
|
||||
"X-Generator: Weblate 5.4.2\n"
|
||||
|
||||
#: .\cookbook\forms.py:45
|
||||
msgid ""
|
||||
@@ -63,42 +63,48 @@ msgid ""
|
||||
"To prevent duplicates recipes with the same name as existing ones are "
|
||||
"ignored. Check this box to import everything."
|
||||
msgstr ""
|
||||
"Varolan tariflerden benzer isimli olanlar mükerrerliği engellemek için "
|
||||
"gözardı edilecektir. Tümünü içeri aktarmak için bu kutucuğu işaretleyin."
|
||||
|
||||
#: .\cookbook\forms.py:143
|
||||
msgid "Add your comment: "
|
||||
msgstr ""
|
||||
msgstr "Yorum ekleyin: "
|
||||
|
||||
#: .\cookbook\forms.py:151
|
||||
msgid "Leave empty for dropbox and enter app password for nextcloud."
|
||||
msgstr ""
|
||||
msgstr "Dropbox için boş bırakın ve Nextcloud için uygulama şifresini girin."
|
||||
|
||||
#: .\cookbook\forms.py:154
|
||||
msgid "Leave empty for nextcloud and enter api token for dropbox."
|
||||
msgstr ""
|
||||
msgstr "Nextcloud için boş bırakın ve Dropbox için API anahtarını girin."
|
||||
|
||||
#: .\cookbook\forms.py:160
|
||||
msgid ""
|
||||
"Leave empty for dropbox and enter only base url for nextcloud (<code>/remote."
|
||||
"php/webdav/</code> is added automatically)"
|
||||
msgstr ""
|
||||
"Dropbox için boş bırakın ve Nextcloud için yalnızca ana URL'yi "
|
||||
"girin(<code>/remote.php/webdav/</code> otomatik olarak eklenir)"
|
||||
|
||||
#: .\cookbook\forms.py:188
|
||||
msgid ""
|
||||
"<a href=\"https://www.home-assistant.io/docs/authentication/#your-account-"
|
||||
"profile\">Long Lived Access Token</a> for your HomeAssistant instance"
|
||||
msgstr ""
|
||||
"HomeAssistant uygulamanız için <a href=\"https://www.home-assistant.io/docs/"
|
||||
"authentication/#your-account-profile\">Uzun Süreli Erişim Anahtarı</a>"
|
||||
|
||||
#: .\cookbook\forms.py:193
|
||||
msgid "Something like http://homeassistant.local:8123/api"
|
||||
msgstr ""
|
||||
msgstr "Örneğin http://homeassistant.local:8123/api"
|
||||
|
||||
#: .\cookbook\forms.py:205
|
||||
msgid "http://homeassistant.local:8123/api for example"
|
||||
msgstr ""
|
||||
msgstr "http://homeassistant.local:8123/api örneğin"
|
||||
|
||||
#: .\cookbook\forms.py:222 .\cookbook\views\edit.py:117
|
||||
msgid "Storage"
|
||||
msgstr ""
|
||||
msgstr "Depolama"
|
||||
|
||||
#: .\cookbook\forms.py:222
|
||||
msgid "Active"
|
||||
@@ -106,51 +112,60 @@ msgstr "Aktif"
|
||||
|
||||
#: .\cookbook\forms.py:226
|
||||
msgid "Search String"
|
||||
msgstr ""
|
||||
msgstr "Arama Sorgusu"
|
||||
|
||||
#: .\cookbook\forms.py:246
|
||||
msgid "File ID"
|
||||
msgstr ""
|
||||
msgstr "Dosya ID"
|
||||
|
||||
#: .\cookbook\forms.py:262
|
||||
msgid "Maximum number of users for this space reached."
|
||||
msgstr ""
|
||||
msgstr "Bu alan için maksimum kullanıcı sayısına ulaşıldı."
|
||||
|
||||
#: .\cookbook\forms.py:268
|
||||
msgid "Email address already taken!"
|
||||
msgstr ""
|
||||
msgstr "Email adresi zaten alınmış!"
|
||||
|
||||
#: .\cookbook\forms.py:275
|
||||
msgid ""
|
||||
"An email address is not required but if present the invite link will be sent "
|
||||
"to the user."
|
||||
msgstr ""
|
||||
"Email adresi zorunlu değildir fakat verilmesi halinde davet linki "
|
||||
"kullanıcıya gönderilecektir."
|
||||
|
||||
#: .\cookbook\forms.py:287
|
||||
msgid "Name already taken."
|
||||
msgstr ""
|
||||
msgstr "İsim zaten alınmış."
|
||||
|
||||
#: .\cookbook\forms.py:298
|
||||
msgid "Accept Terms and Privacy"
|
||||
msgstr ""
|
||||
msgstr "Koşulları ve Gizliliği Onayla"
|
||||
|
||||
#: .\cookbook\forms.py:332
|
||||
msgid ""
|
||||
"Determines how fuzzy a search is if it uses trigram similarity matching (e."
|
||||
"g. low values mean more typos are ignored)."
|
||||
msgstr ""
|
||||
"Trigram benzerlik eşleşmesi kullanılması halinde aramanın ne kadar bulanık "
|
||||
"olduğunu belirler (ör. düşük değerler daha fazla yazım hatasını gözardı "
|
||||
"eder)."
|
||||
|
||||
#: .\cookbook\forms.py:340
|
||||
msgid ""
|
||||
"Select type method of search. Click <a href=\"/docs/search/\">here</a> for "
|
||||
"full description of choices."
|
||||
msgstr ""
|
||||
"Arama tipi metodunu seçin. Seçeneklerin tam açıklamasını görmek için <a "
|
||||
"href=\"/docs/search/\">buraya</a> tıklayın."
|
||||
|
||||
#: .\cookbook\forms.py:341
|
||||
msgid ""
|
||||
"Use fuzzy matching on units, keywords and ingredients when editing and "
|
||||
"importing recipes."
|
||||
msgstr ""
|
||||
"Tarifleri düzenlerken ve içeri aktarırken birimler, anahtar kelimeler ve "
|
||||
"malzemelerde bulanık eşleştirme kullan."
|
||||
|
||||
#: .\cookbook\forms.py:342
|
||||
msgid ""
|
||||
|
||||
Reference in New Issue
Block a user